Add .loop-build harness and IntelliJ project configuration files

This commit is contained in:
2026-02-25 01:18:49 +08:00
commit 6f02ef607f
27 changed files with 1792 additions and 0 deletions

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

4
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KubernetesApiProvider">{}</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/skills.iml" filepath="$PROJECT_DIR$/.idea/skills.iml" />
</modules>
</component>
</project>

19
.idea/php.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

8
.idea/skills.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

189
.loop-build/HELP.md Normal file
View File

@@ -0,0 +1,189 @@
# loop-build 使用指南(简体中文)
本文件是 `loop-build (Codex CLI harness)` 的中文操作手册,面向日常使用者。
目标是让你按固定节奏推进任务:**先计划、再审批、单步执行、验证、再次审批**。
## 1. 这套 harness 是做什么的
`loop-build` 用于长时间运行、可中断、可回滚的任务执行。
它把一次任务拆成 3~7 个步骤,并强制执行以下状态机:
1. 生成 PLAN
2. 人类审批 PLAN`APPROVE_PLAN / REVISE_PLAN / CANCEL_TASK`
3. 每次只执行 1 个 step
4. step 后汇报并等待下一次审批(`APPROVE_NEXT / REVISE_PLAN / CANCEL_TASK`
## 2. 首次使用(一次性)
在仓库根目录执行:
```bash
./.loop-build/scripts/init.sh
```
初始化脚本会:
- 检查依赖:`git``bash``rg``jq`
- 初始化 `.loop-build` 所需目录与默认配置
- 生成任务模板与状态文件(若缺失)
- 提示你尽量在 git 工作树较干净时开始
## 3. 每个任务的标准流程
### 第一步:准备任务输入
你需要准备 4 类输入给 planner
- `goal`:任务目标
- `constraints`:约束条件
- `repo_context_hint`:仓库上下文提示
- `current_state_summary`:当前状态摘要(可选)
### 第二步:产出并写入 PLAN
`./.loop-build/prompts/planner.md` 让 Codex 生成严格 JSON写入
```bash
./.loop-build/state/current_task.json
```
PLAN 必须满足:
- 步数 3~7
- 每步包含:
- `step_id`
- `action`
- `verification`
- `rollback`
- `risk_level`
- `expected_output`
### 第三步:审批 PLAN强制
```bash
./.loop-build/scripts/run_task.sh APPROVE_PLAN
```
如果要改计划:
```bash
./.loop-build/scripts/run_task.sh REVISE_PLAN "说明你要改什么"
```
取消任务:
```bash
./.loop-build/scripts/run_task.sh CANCEL_TASK
```
### 第四步:单步执行
```bash
./.loop-build/scripts/run_task.sh step
```
或指定步号:
```bash
./.loop-build/scripts/run_task.sh step 2
```
执行特性:
- 一次只推进一个 step
- 默认输出 unified diff不整文件倾泻
- 按 step 的 `verification` 决定是否触发验证
- 写入历史记录到 `state/history.ndjson`
- 更新 `current_task.json` 的进度字段
### 第五步:每步后再次审批
```bash
./.loop-build/scripts/run_task.sh APPROVE_NEXT
```
或要求改计划:
```bash
./.loop-build/scripts/run_task.sh REVISE_PLAN "下一步前先调整计划"
```
然后继续 `step`,直到完成。
## 4. 常用命令速查
查看当前任务状态:
```bash
./.loop-build/scripts/run_task.sh status
```
执行验证(手动):
```bash
APPROVE=1 ./.loop-build/scripts/verify.sh unit
APPROVE=1 ./.loop-build/scripts/verify.sh lint
APPROVE=1 ./.loop-build/scripts/verify.sh typecheck
APPROVE=1 ./.loop-build/scripts/verify.sh all
```
创建快照:
```bash
./.loop-build/scripts/snapshot.sh
./.loop-build/scripts/snapshot.sh before-step-2
```
## 5. 安全策略说明(必须了解)
策略文件在 `./.loop-build/config/`
- `allowlist.txt`:默认允许的只读命令
- `denylist.txt`:默认禁止的高风险命令
- `policy.env`:显式解锁开关
关键点:
- 命令会按分段检查(如 `&&``||``;``|`
- 命中 denylist 默认拦截
- 需在 `policy.env` 显式解锁并启用二次确认,才允许执行高风险动作
## 6. 实战建议(推荐)
- 每个 step 只做一个最小可验证改动MVC
- 每个 step 最多改 2 个文件,超过就拆分计划。
- `action` 尽量写成可直接执行的最小命令列表。
- `verification` 只写当前 step 真正需要的 target避免全量跑。
- 每步都写清 rollback失败时能快速恢复。
- 发现计划偏离时优先 `REVISE_PLAN`,不要硬推进。
## 7. 常见问题排查
`run_task.sh` 提示 plan 未审批:
- 先执行 `APPROVE_PLAN`
验证被阻止:
- 手动运行 verify 时需要 `APPROVE=1`
step 被策略拦截:
- 查看 denylist 命中原因。
- 评估是否真的需要解锁,不建议为省事放开高风险命令。
快照文件需要检查:
```bash
latest=$(ls -1t ./.loop-build/state/snapshots/snapshot-*.json | head -n 1)
jq . "$latest"
```
## 8. 推荐协作模板(给 Codex CLI
你可以直接用下面这段话发起任务:
```text
请基于 .loop-build/prompts/planner.md 生成严格 JSON 计划,目标是:
<goal>
约束是:
<constraints>
仓库上下文:
<repo_context_hint>
当前状态:
<current_state_summary>
要求 3~7 步,每步包含 step_id/action/verification/rollback/risk_level/expected_output。
```
生成后先写入 `state/current_task.json`,再走审批与 step 循环。

172
.loop-build/README.md Normal file
View File

@@ -0,0 +1,172 @@
# loop-buildCodex CLI harness
本目录提供一套受控的长周期执行流程:**PLAN -> APPROVE -> 单步 LOOP -> VERIFY -> RE-APPROVE -> 重复**。
## 1一次性初始化
1. 运行:
```bash
./.loop-build/scripts/init.sh
```
2. 阅读依赖检查结果建议在工作树基本干净clean时开始任务。
3. 保留生成的 `.loop-build/TASKS.md`,作为任务记录模板。
## 2创建任务并产出 PLAN
你需要提供:
- `goal`
- `constraints`
- `repo_context_hint`
- `current_state_summary`
随后在 Codex CLI 会话中使用 `prompts/planner.md`,生成严格 JSON`plan_steps` 需为 3~7 步。
将结果写入:
```bash
./.loop-build/state/current_task.json
```
推荐包含字段:
- `task_id``goal``constraints``plan_steps[]``success_criteria``risk_notes``required_files``verify_targets`
每个 `plan_step` 必须包含:
- `step_id`
- `action`
- `verification`
- `rollback`
- `risk_level`
- `expected_output`
### Plan 审批(强制)
未记录审批前,**不要**执行任何 `step`
执行:
```bash
./.loop-build/scripts/run_task.sh APPROVE_PLAN
```
需要修改 plan 时:
```bash
./.loop-build/scripts/run_task.sh REVISE_PLAN "need narrower scope"
```
取消任务:
```bash
./.loop-build/scripts/run_task.sh CANCEL_TASK
```
`run_task.sh` 会读取 `current_task.json`,输出 plan 概览(步骤、风险、文件、验证、回滚),并等待一次性人类回复。
## 3循环执行每次只跑 1 步)
审批通过后:
```bash
./.loop-build/scripts/run_task.sh step 1
```
执行行为:
- 仅执行一个 plan step。
- 默认输出统一 diff`git diff` 风格)。
- 仅当该 step 的 plan 指定 verification 时才执行验证。
-`.loop-build/state/history.ndjson` 追加一条 NDJSON 记录。
- 更新 `.loop-build/state/current_task.json``completed_steps` / `next_step` / `open_questions`
- 暂停并等待 `APPROVE_NEXT`,再继续下一步。
### 每步后再次审批
一步完成后,选择其中一个:
```bash
./.loop-build/scripts/run_task.sh APPROVE_NEXT
./.loop-build/scripts/run_task.sh REVISE_PLAN "scope too large for current diff"
./.loop-build/scripts/run_task.sh CANCEL_TASK
```
继续执行:
```bash
./.loop-build/scripts/run_task.sh step 2
```
## 4验证流程
`verify.sh` 支持:
```bash
./.loop-build/scripts/verify.sh unit
./.loop-build/scripts/verify.sh lint
./.loop-build/scripts/verify.sh typecheck
./.loop-build/scripts/verify.sh all
```
手动验证时:
```bash
APPROVE=1 ./.loop-build/scripts/verify.sh unit
```
`run_task.sh` 的循环中,验证会经过同一套策略门禁。
## 5防“代码倾泻”规则由 prompt 与脚本共同执行)
- 默认只输出统一 diff不输出完整文件。
- 每个 step 默认最多改动 **2 个文件**(超过则必须拆分 step
- 单步失败会自动重试,最多 2 次。
- 错误输出只保留前后关键片段,避免噪声。
## 6安全策略模型
策略文件位于 `.loop-build/config/`
- `policy.env`:开关配置和显式解锁变量。
- `allowlist.txt`:默认只读安全前缀(无需审批)。
- `denylist.txt`:默认禁止的动作,需显式解锁。
`run_task.sh` 执行任何命令前都会经过 `check_policy`(前缀匹配 + 显式解锁校验)。
默认受限动作示例:`sudo``rm -rf``curl``wget``git clone``curl|bash` / 下载并执行类写法。
## 7示例任务
### 示例 1修复一个失败测试
1. 填写 goal/constraints。
2. 生成 3~7 步 PLAN。
3. 执行 `APPROVE_PLAN`
4. `step 1` -> `verify`(如配置)。
5. `APPROVE_NEXT` -> `step 2` -> `step ...`
### 示例 2小范围重构<=2 文件)
1. 明确范围仅限文件 A/B。
2. 要求 plan step 包含 `risk_level=LOW` 且有回滚说明。
3. 每步独立执行,并在每个 diff 后重新审批。
### 示例 3新增一个测试用例
1. Plan 应包含 fixtures/setup、断言、针对性验证。
2. 确保每步最多触及 1 个测试文件 + 1 个源码文件。
3. 步骤间重新审批以控制变更边界。
## 8文件清单
- `.loop-build/README.md`
- `.loop-build/TASKS.md`
- `.loop-build/state/current_task.json`
- `.loop-build/state/history.ndjson`
- `.loop-build/prompts/planner.md`
- `.loop-build/prompts/executor.md`
- `.loop-build/prompts/reviewer.md`
- `.loop-build/scripts/init.sh`
- `.loop-build/scripts/verify.sh`
- `.loop-build/scripts/run_task.sh`
- `.loop-build/scripts/apply_patch.sh`
- `.loop-build/scripts/snapshot.sh`
- `.loop-build/config/policy.env`
- `.loop-build/config/allowlist.txt`
- `.loop-build/config/denylist.txt`

51
.loop-build/TASKS.md Normal file
View File

@@ -0,0 +1,51 @@
# loop-build Task Template
Use this file to record each task and planned execution notes.
- Task file format: one block per task, one per line or section.
- Keep task scope tight; avoid changing unrelated files.
## Required fields
- `goal`
- `constraints`
- `plan_source`
- `planner.md` output source / timestamp
- `status`
- `current_task_file`
## Current Task Header
```text
Task ID:
Goal:
Owner:
Status:
Created:
Updated:
Constraints:
Repo context hint:
Verify policy:
```
## Running Notes
- `PLAN` generated from `prompts/planner.md` (3~7 steps)
- approvals: `APPROVE_PLAN -> step loop -> APPROVE_NEXT`
- verify targets per step
- rollback notes per step
- decision points / open questions
## Log (manual)
- Step 1: result + summary
- Step 2: result + summary
- Step n: result + summary
## Completion Check
- expected acceptance criteria met:
- diff size policy followed (<=2 files/step):
- `current_task.json` synchronized:
- history.ndjson rows appended:

View File

@@ -0,0 +1,27 @@
ls
ls -la
cat
rg
grep
tree
git status
git diff
git rev-parse
git log
git show
git branch
git branch --show-current
git status --short
git blame
sed
awk
head
tail
wc
printf
echo
find
pwd
git restore --source
true
false

View File

@@ -0,0 +1,24 @@
sudo
rm -rf
rm -fr
curl
wget
git clone
git push
git pull
git commit
git rebase
git reset
npm install
npm i
yarn add
yarn install
pnpm add
pnpm install
pip install
pip3 install
composer install
bash -c
sh -c
curl | bash
wget | sh

View File

@@ -0,0 +1,13 @@
# Loop Build policy toggles
# Safe-by-default behavior:
# - read-only commands in allowlist pass without explicit approval
# - all other non-denylist commands can run only when the harness is in an approved execution context
POLICY_SECONDARY_CONFIRM=0
ALLOW_SUDO=0
ALLOW_RM_RF=0
ALLOW_NETWORK=0
ALLOW_CURL_BASH=0
ALLOW_INSTALL=0
ALLOW_GIT_NETWORK=0
ALLOW_PROFILE_MODIFY=0

View File

@@ -0,0 +1,34 @@
# loop-build Executor Prompt
## Inputs
- `current_task`: contents of `.loop-build/state/current_task.json`
- `step_id`: current step id
- `context_snippets`: short file snippets around affected areas
- `verify_summary`: latest verify summary if any
## Task
Produce a strictly scoped execution plan for exactly **one** step.
Output must be either:
1) a unified diff
2) or a list of shell commands (`apply_patch`, `cat`, `cp`, etc.)
No commands or patch may touch more than 2 files.
If returning commands:
- they are for the single step only
- include only minimal verification command needed for this step
- all non-read actions require explicit approval in harness policy
Example accepted outputs:
- `git diff` style block
- command list with one command per line
Disallowed:
- Full file dumps
- Multiple-step execution beyond the selected step
- Extra unrelated refactors
- Implicitly chaining future step actions

View File

@@ -0,0 +1,63 @@
# loop-build Planner Prompt
## Inputs
- `goal`: user goal for the task
- `constraints`: hard constraints and guardrails
- `repo_context_hint`: optional repo context (size, language, architecture, risk areas)
- `current_state_summary`: optional summary from previous run
## Task
Generate a strict JSON object for `TASKS` creation only (no implementation code).
Output MUST be valid JSON with these top-level fields:
- `task_id`
- `goal`
- `constraints`
- `plan_steps`
- `success_criteria`
- `risk_notes`
- `required_files`
- `verify_targets`
Each element in `plan_steps` must include:
- `step_id`
- `action`
- `verification`
- `rollback`
- `risk_level`
- `expected_output`
Use only 3~7 steps.
## Output format (example)
```json
{
"task_id": "task-2026-001",
"goal": "...",
"constraints": ["..."],
"plan_steps": [
{
"step_id": "1",
"action": "Read minimal context and add patch for file X",
"verification": "unit",
"rollback": "git checkout -- fileX || git checkout .",
"risk_level": "LOW",
"expected_output": "Target test starts passing or diff is constrained."
}
],
"success_criteria": ["..."],
"risk_notes": ["..."],
"required_files": ["path/to/file1", "path/to/file2"],
"verify_targets": ["unit", "lint"]
}
```
Rules:
- Do not return implementation code or full diffs.
- Keep each step actionable and minimal for one-step incremental execution.
- One step must map to one incremental change and one verification check.

View File

@@ -0,0 +1,31 @@
# loop-build Reviewer Prompt
## Inputs
- `diff_summary`: short unified diff summary
- `verify_result`: result of the most recent `verify.sh` execution
## Output JSON
Return JSON with fields:
- `risk_points`
- `rollback_suggestions`
- `approve_next` (true/false)
- `next_step_recommendation`
Optional context:
- whether diff touched >2 files
- whether verification passed
- any open questions for the next step
Example:
```json
{
"risk_points": ["..."],
"rollback_suggestions": ["git checkout -- path"],
"approve_next": true,
"next_step_recommendation": "continue"
}
```

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
if [[ ! -d "$ROOT_DIR/.git" ]]; then
echo "[apply_patch][ERROR] no git repository available"
exit 2
fi
patch_tmp=$(mktemp)
cleanup() { rm -f "$patch_tmp"; }
trap cleanup EXIT
if [[ $# -ge 1 ]]; then
cp "$1" "$patch_tmp"
else
cat > "$patch_tmp"
fi
if [[ ! -s "$patch_tmp" ]]; then
echo "[apply_patch][ERROR] patch is empty"
exit 2
fi
if ! git -C "$ROOT_DIR" apply --check "$patch_tmp" >/dev/null 2>&1; then
echo "[apply_patch][ERROR] patch does not apply cleanly"
exit 2
fi
file_count=$(git -C "$ROOT_DIR" apply --numstat "$patch_tmp" | awk '$3 != "" {print $3}' | sort -u | wc -l | tr -d ' ')
if (( file_count > 2 )); then
echo "[apply_patch][BLOCK] patch touches $file_count files; max is 2 per step."
exit 2
fi
git -C "$ROOT_DIR" apply "$patch_tmp"
echo "[apply_patch][OK] patch applied"

214
.loop-build/scripts/init.sh Executable file
View File

@@ -0,0 +1,214 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
LB_DIR="$ROOT_DIR/.loop-build"
STATE_DIR="$LB_DIR/state"
CONFIG_DIR="$LB_DIR/config"
TASKS_FILE="$LB_DIR/TASKS.md"
STATE_FILE="$STATE_DIR/current_task.json"
HISTORY_FILE="$STATE_DIR/history.ndjson"
need_approval_files=("$TASKS_FILE" "$STATE_FILE" "$HISTORY_FILE")
log() {
printf '%s\n' "$*"
}
check_dependencies() {
local missing=0
for dep in git bash rg jq; do
if ! command -v "$dep" >/dev/null 2>&1; then
printf '[init][MISSING] %s\n' "$dep"
printf ' install suggestion: use your package manager (e.g. apt, brew, or winget)\n'
missing=1
else
printf '[init][OK] %s\n' "$dep"
fi
done
if (( missing != 0 )); then
log "[init] dependency check found missing tools. init continues with warnings only."
fi
}
render_task_template() {
cat > "$TASKS_FILE" <<'EOF_TPL'
# loop-build Task Template
## Task
- task_id:
- goal:
- constraints:
- repo_context_hint:
- created_at:
- updated_at:
## Plan source
- generated_by: prompts/planner.md
- plan_status: not_started
- current_step:
- verify_targets:
## Progress
- completed_steps:
- next_step:
- open_questions:
- last_verification:
## Notes
- plan diff scope should stay <= 2 files/step
- each step should be one incremental change and one verification
- avoid full-file rewrites
EOF_TPL
}
init_state_file() {
if [[ -f "$STATE_FILE" ]]; then
return 0
fi
cat > "$STATE_FILE" <<EOF_STATE
{
"task_id": "",
"goal": "",
"constraints": [],
"plan_steps": [],
"success_criteria": [],
"risk_notes": [],
"required_files": [],
"verify_targets": [],
"plan_status": "not_started",
"pending_action": "need_plan_approval",
"next_step": 1,
"completed_steps": [],
"open_questions": [],
"current_step": null,
"last_verification": null,
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF_STATE
}
init_default_config() {
if [[ -f "$CONFIG_DIR/policy.env" ]]; then
return 0
fi
cat > "$CONFIG_DIR/policy.env" <<'EOF_CFG'
# Loop Build policy toggles
# Commands in denylist can execute only after explicit unlock + secondary confirm.
POLICY_SECONDARY_CONFIRM=0
ALLOW_SUDO=0
ALLOW_RM_RF=0
ALLOW_NETWORK=0
ALLOW_CURL_BASH=0
ALLOW_INSTALL=0
ALLOW_GIT_NETWORK=0
ALLOW_PROFILE_MODIFY=0
EOF_CFG
}
ensure_default_files() {
if [[ ! -f "$STATE_DIR/history.ndjson" ]]; then
: > "$HISTORY_FILE"
fi
if [[ ! -f "$TASKS_FILE" ]]; then
render_task_template
fi
if [[ ! -f "$CONFIG_DIR/allowlist.txt" ]]; then
cat > "$CONFIG_DIR/allowlist.txt" <<'EOF_ALLOW'
ls
ls -la
cat
rg
grep
tree
git status
git diff
git rev-parse
git log
git show
git branch
git branch --show-current
git status --short
git blame
sed
awk
head
tail
wc
printf
echo
find
pwd
false
true
EOF_ALLOW
fi
if [[ ! -f "$CONFIG_DIR/denylist.txt" ]]; then
cat > "$CONFIG_DIR/denylist.txt" <<'EOF_DENY'
sudo
rm -rf
rm -fr
git clone
git push
git pull
git commit
git rebase
git reset
curl
wget
npm install
npm i
yarn add
yarn install
pnpm add
pnpm install
pip install
pip3 install
composer install
bash -c
sh -c
curl | bash
wget | sh
EOF_DENY
fi
}
check_git_state_hint() {
if [[ -d "$ROOT_DIR/.git" ]] && command -v git >/dev/null 2>&1; then
changed=$(git -C "$ROOT_DIR" status --short | wc -l | tr -d ' ')
if [[ "$changed" != "0" ]]; then
log "[init][WARN] repository is not clean; recommended for safer incremental steps."
else
log "[init] repo clean check: passed"
fi
else
log "[init][WARN] no .git metadata detected; diff/rollback features are limited."
fi
}
main() {
log "[init] starting loop-build bootstrap"
check_dependencies
mkdir -p "$STATE_DIR" "$CONFIG_DIR"
init_state_file
ensure_default_files
init_default_config
check_git_state_hint
log "[init] done."
log "[init] next steps:"
log " 1) complete .loop-build/state/current_task.json from a planner response"
log " 2) run ./ .loop-build/scripts/run_task.sh APPROVE_PLAN"
log " 3) run ./ .loop-build/scripts/run_task.sh step"
}
main "$@"

645
.loop-build/scripts/run_task.sh Executable file
View File

@@ -0,0 +1,645 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
LB_DIR="$ROOT_DIR/.loop-build"
STATE_FILE="$LB_DIR/state/current_task.json"
HISTORY_FILE="$LB_DIR/state/history.ndjson"
POLICY_DIR="$LB_DIR/config"
POLICY_ENV="$POLICY_DIR/policy.env"
ALLOWLIST="$POLICY_DIR/allowlist.txt"
DENYLIST="$POLICY_DIR/denylist.txt"
SCRIPTS_DIR="$LB_DIR/scripts"
if [[ ! -f "$POLICY_ENV" ]]; then
echo "[loop-build] missing policy file: $POLICY_ENV"
exit 1
fi
source "$POLICY_ENV"
if ! command -v jq >/dev/null 2>&1; then
echo "[loop-build] missing dependency: jq"
exit 1
fi
if [[ ! -f "$STATE_FILE" ]]; then
echo "[loop-build] missing state file: $STATE_FILE"
exit 1
fi
now_ts() {
date -u +%Y-%m-%dT%H:%M:%SZ
}
state_get() {
jq -r "$1" "$STATE_FILE"
}
state_set_expr() {
local expr="$1"
local tmp
tmp=$(mktemp)
jq "$expr" "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"
}
state_set_string() {
local path="$1"
local value="$2"
local tmp
tmp=$(mktemp)
jq --arg value "$value" "${path} = \$value" "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"
}
state_set_number() {
local path="$1"
local value="$2"
local tmp
tmp=$(mktemp)
jq "${path} = ${value}" "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"
}
state_append() {
local path="$1"
local value="$2"
local tmp
tmp=$(mktemp)
jq --arg value "$value" "${path} += [\$value]" "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"
}
state_set_last_verification() {
local target="$1"
local status="$2"
local tmp
tmp=$(mktemp)
jq --arg target "$target" --arg status "$status" \
'.last_verification = {"target": $target, "status": $status}' \
"$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"
}
state_mark_updated() {
state_set_expr '.updated_at = "'"$(now_ts)"'"'
}
touch_history() {
[[ -f "$HISTORY_FILE" ]] || : > "$HISTORY_FILE"
}
append_history() {
local step_id="$1"
local result="$2"
local summary="$3"
local verification="$4"
local next_recommendation="$5"
touch_history
jq -n \
--arg ts "$(now_ts)" \
--arg sid "$step_id" \
--arg res "$result" \
--arg sum "$summary" \
--arg v "$verification" \
--arg next "$next_recommendation" \
'{"timestamp":$ts,"step_id":$sid,"result":$res,"summary":$sum,"verification_result":$v,"next_step_recommendation":$next}' \
>> "$HISTORY_FILE"
echo >> "$HISTORY_FILE"
}
match_prefix() {
local token="$1" list="$2"
local pattern
[[ -f "$list" ]] || return 1
while IFS= read -r pattern; do
[[ -z "$pattern" || "$pattern" =~ ^# ]] && continue
if [[ "$token" == "$pattern" || "$token" == "$pattern"* ]]; then
return 0
fi
done < "$list"
return 1
}
policy_unlock_key() {
local cmd="$1"
local base="$2"
if [[ "$base" == "sudo" ]]; then
echo "ALLOW_SUDO"
return
fi
if [[ "$base" == "rm" && "$cmd" == *" -rf"* ]]; then
echo "ALLOW_RM_RF"
return
fi
if [[ "$base" == "curl" || "$base" == "wget" ]]; then
if [[ "$cmd" == *"|"* ]]; then
echo "ALLOW_CURL_BASH"
return
fi
echo "ALLOW_NETWORK"
return
fi
if [[ "$base" == "git" && ("$cmd" == *" clone "* || "$cmd" == *" push "* || "$cmd" == *" pull "*) ]]; then
echo "ALLOW_GIT_NETWORK"
return
fi
if [[ "$base" == npm || "$base" == yarn || "$base" == pnpm || "$base" == pip || "$base" == pip3 || "$base" == composer ]]; then
if [[ "$cmd" == *" install"* || "$cmd" == " install"* || "$cmd" == *" i "* || "$cmd" == " i "* ]]; then
echo "ALLOW_INSTALL"
return
fi
fi
echo ""
}
policy_check() {
local cmd="$1"
local task_context="$2" # 1 for approved task step
cmd="$(printf '%s' "$cmd" | sed 's/^ *//;s/ *$//')"
local segments
local segment
local token
local key
segments="$(printf '%s' "$cmd" | sed -E 's/&&/\n/g; s/\|\|/\n/g; s/[;|]/\n/g')"
while IFS= read -r segment; do
segment="$(printf '%s' "$segment" | sed 's/^ *//;s/ *$//')"
[[ -z "$segment" ]] && continue
token="${segment%% *}"
token="${token##*/}"
if match_prefix "$segment" "$DENYLIST" || match_prefix "$token" "$DENYLIST"; then
key="$(policy_unlock_key "$segment" "$token")"
if [[ -n "$key" && "${!key:-0}" == "1" && "${POLICY_SECONDARY_CONFIRM:-0}" == "1" ]]; then
continue
fi
echo "[policy] blocked by denylist segment: $segment"
[[ -n "$key" ]] && echo "unlock: set $key=1 and POLICY_SECONDARY_CONFIRM=1 in $POLICY_ENV"
return 2
fi
if match_prefix "$segment" "$ALLOWLIST" || match_prefix "$token" "$ALLOWLIST"; then
continue
fi
if [[ "$task_context" != "1" ]]; then
echo "[policy] blocked: this command requires approval context"
echo "segment: $segment"
return 2
fi
done <<< "$segments"
return 0
}
error_excerpt() {
local file="$1"
local hit
if [[ ! -s "$file" ]]; then
return
fi
if grep -nEi "error|fail|fatal|exception|traceback" "$file" >/dev/null 2>&1; then
hit=$(grep -nEi "error|fail|fatal|exception|traceback" "$file" | tail -n 1 | cut -d: -f1)
awk -v start=$((hit > 50 ? hit - 50 : 1)) -v end=$((hit + 50)) 'NR>=start && NR<=end { print NR":"$0 }' "$file"
else
tail -n 80 "$file"
fi
}
run_command() {
local cmd="$1"
local ctx="$2"
local out err rc
local out_file err_file
out_file=$(mktemp)
err_file=$(mktemp)
if ! policy_check "$cmd" "$ctx"; then
rm -f "$out_file" "$err_file"
return 2
fi
set +e
bash -lc "$cmd" >"$out_file" 2>"$err_file"
rc=$?
set -e
if (( rc != 0 )); then
echo "[command][FAIL] $cmd"
error_excerpt "$err_file"
rm -f "$out_file" "$err_file"
return "$rc"
fi
if [[ -s "$out_file" ]]; then
echo "[command][OUT]"
tail -n 40 "$out_file"
fi
rm -f "$out_file" "$err_file"
return 0
}
collect_changed_files() {
if [[ ! -d "$ROOT_DIR/.git" ]]; then
return 0
fi
git -C "$ROOT_DIR" status --short --untracked-files=normal | awk '{print $NF}' | sort -u
}
count_lines() {
local file="$1"
[[ -f "$file" ]] || { echo 0; return; }
wc -l < "$file" | tr -d ' '
}
collect_step_actions() {
local step_json="$1"
local type
type="$(jq -r 'type' <<<"$step_json")"
case "$type" in
string)
jq -r '.' <<<"$step_json"
;;
array)
jq -r '.[]' <<<"$step_json"
;;
object)
if jq -e '.command' <<<"$step_json" >/dev/null 2>&1; then
jq -r '.command // empty' <<<"$step_json"
elif jq -e '.commands' <<<"$step_json" >/dev/null 2>&1; then
jq -r '.commands[]?' <<<"$step_json"
else
return 1
fi
;;
*)
return 1
;;
esac
}
print_plan() {
local steps status pending next
status="$(state_get '.plan_status')"
pending="$(state_get '.pending_action')"
next="$(state_get '.next_step')"
steps="$(state_get '.plan_steps | length')"
echo "task_id: $(state_get '.task_id')"
echo "plan_status: $status"
echo "pending_action: $pending"
echo "next_step: $next"
echo "total_steps: $steps"
echo "required_files: $(state_get '.required_files | join(",")')"
echo "verify_targets: $(state_get '.verify_targets | join(",")')"
echo "completion: $(state_get '.completed_steps')"
if (( steps == 0 )); then
echo "No plan present"
return
fi
for i in $(seq 0 $((steps - 1))); do
sid=$(jq -r ".plan_steps[$i].step_id // ( $i + 1 )" "$STATE_FILE")
risk=$(jq -r ".plan_steps[$i].risk_level // \"LOW\"" "$STATE_FILE")
verify=$(jq -r ".plan_steps[$i].verification // \"\"" "$STATE_FILE")
action=$(jq -r ".plan_steps[$i].action // \"\"" "$STATE_FILE")
rollback=$(jq -r ".plan_steps[$i].rollback // \"\"" "$STATE_FILE")
echo "- step=$sid risk=$risk verify=$verify"
echo " action: $action"
echo " rollback: $rollback"
done
}
status() {
print_plan
if [[ "$(state_get '.pending_action')" == "need_plan_approval" ]]; then
echo "approval: APPROVE_PLAN / REVISE_PLAN / CANCEL_TASK"
elif [[ "$(state_get '.pending_action')" == "step_ready" ]]; then
echo "approval: step N then APPROVE_NEXT / REVISE_PLAN / CANCEL_TASK"
elif [[ "$(state_get '.pending_action')" == "await_next_approval" ]]; then
echo "approval: APPROVE_NEXT / REVISE_PLAN / CANCEL_TASK"
fi
}
ensure_plan_exists() {
local steps
steps="$(state_get '.plan_steps | length')"
if [[ "$steps" == "0" ]]; then
echo "[plan] no plan_steps found; generate with planner first."
exit 1
fi
}
ensure_open_json() {
# Ensure required state fields exist for operations in malformed initial states.
state_set_expr '.completed_steps |= if type=="array" then . else [] end'
state_set_expr '.open_questions |= if type=="array" then . else [] end'
}
run_verify_if_needed() {
local target="$1"
if [[ -z "$target" || "$target" == "null" ]]; then
echo "not_run"
return 0
fi
if ! policy_check "${SCRIPTS_DIR}/verify.sh $target" 1 >/dev/stderr; then
echo "blocked"
return 0
fi
local out err rc
out=$(mktemp)
err=$(mktemp)
set +e
APPROVE=1 "$SCRIPTS_DIR/verify.sh" "$target" >"$out" 2>"$err"
rc=$?
set -e
if (( rc != 0 )); then
echo "FAIL"
error_excerpt "$err" >&2
rm -f "$out" "$err"
return 0
fi
if [[ -s "$out" ]]; then
head -n 20 "$out" >&2
fi
rm -f "$out" "$err"
echo "PASS"
return 0
}
run_step() {
local step_num="$1"
local total
local step
local step_id
local action_json
local verification
local rollback
local expected
local before_file
local after_file
local delta_file
local diff_file
local cmd_file
local changed
total="$(state_get '.plan_steps | length')"
if (( step_num < 1 || step_num > total )); then
echo "[step] invalid step index: $step_num"
return 1
fi
step="$(jq -c ".plan_steps[$((step_num - 1))]" "$STATE_FILE")"
step_id="$(jq -r '.step_id // empty' <<<"$step")"
[[ -z "$step_id" ]] && step_id="$step_num"
verification="$(jq -r '.verification // empty' <<<"$step")"
rollback="$(jq -r '.rollback // empty' <<<"$step")"
expected="$(jq -r '.expected_output // empty' <<<"$step")"
action_json="$(jq -c '.action' <<<"$step")"
if [[ -z "$action_json" || "$action_json" == "null" ]]; then
echo "[step] empty action for step $step_num"
append_question "step_${step_num}: empty action"
state_set_string '.pending_action' 'await_next_approval'
return 1
fi
echo "[step] step_id=$step_id"
echo "[step] action=$action_json"
echo "[step] expected=$expected"
echo "[step] rollback=$rollback"
before_file=$(mktemp)
after_file=$(mktemp)
delta_file=$(mktemp)
diff_file=$(mktemp)
cmd_file=$(mktemp)
trap 'rm -f "$before_file" "$after_file" "$delta_file" "$diff_file" "$cmd_file"' RETURN
collect_changed_files > "$before_file"
if ! collect_step_actions "$action_json" > "$cmd_file"; then
echo "[step] invalid action payload for step $step_num"
append_question "step_${step_num}_invalid_action_payload"
state_set_string '.pending_action' 'await_next_approval'
state_set_expr '.plan_status = "approved"'
state_set_expr '.updated_at = "'"$(now_ts)"'"'
state_set_last_verification "$verification" "not_run"
append_history "$step_id" "failure" "invalid action payload" "not_run" "revise"
echo "[step] FAIL"
return 1
fi
if [[ ! -s "$cmd_file" ]]; then
echo "[step] no executable commands resolved for step $step_num"
append_question "step_${step_num}_empty_action_commands"
state_set_string '.pending_action' 'await_next_approval'
state_set_expr '.plan_status = "approved"'
state_set_expr '.updated_at = "'"$(now_ts)"'"'
state_set_last_verification "$verification" "not_run"
append_history "$step_id" "failure" "empty action command list" "not_run" "revise"
echo "[step] FAIL"
return 1
fi
local attempt=1
local step_status="failure"
while (( attempt <= 2 )); do
local cmd_rc=0
echo "[step] attempt $attempt/2"
while IFS= read -r cmd; do
[[ -z "$(printf '%s' "$cmd")" ]] && continue
if ! run_command "$cmd" 1; then
cmd_rc=1
break
fi
done < "$cmd_file"
if [[ $cmd_rc -eq 0 ]]; then
step_status="success"
break
fi
if (( attempt == 2 )); then
break
fi
echo "[step] retrying"
((attempt++))
done
collect_changed_files > "$after_file"
comm -3 "$before_file" "$after_file" > "$delta_file" || true
changed="$(count_lines "$delta_file")"
if [[ "$changed" -gt 2 ]]; then
echo "[step] BLOCK: changed files count for this step is $changed, max is 2"
step_status="failure"
fi
if [[ -d "$ROOT_DIR/.git" ]]; then
git -C "$ROOT_DIR" diff -- . > "$diff_file"
fi
echo "[step] unified diff:"
if [[ -s "$diff_file" ]]; then
cat "$diff_file"
else
echo "(no tracked diff for this step)"
fi
local verify_result="not_run"
if [[ "$step_status" == "success" ]]; then
verify_result="$(run_verify_if_needed "$verification")"
if [[ "$verify_result" != "PASS" && "$verify_result" != "not_run" ]]; then
step_status="failure"
fi
fi
if [[ "$step_status" == "success" ]]; then
state_append '.completed_steps' "$step_id"
state_set_number '.current_step' "$step_num"
state_set_number '.next_step' "$((step_num + 1))"
state_set_last_verification "$verification" "$verify_result"
if (( step_num >= total )); then
state_set_string '.plan_status' 'completed'
state_set_string '.pending_action' 'completed'
else
state_set_string '.plan_status' 'approved'
state_set_string '.pending_action' 'await_next_approval'
fi
state_set_expr '.open_questions = []'
state_set_expr '.updated_at = "'"$(now_ts)"'"'
append_history "$step_id" "success" "step complete" "$verify_result" "await_next"
echo "[step] PASS"
return 0
fi
append_question "step_${step_num}_failed: command failed or verify failed"
state_set_string '.pending_action' 'await_next_approval'
state_set_expr '.plan_status = "approved"'
state_set_expr '.updated_at = "'"$(now_ts)"'"'
state_set_last_verification "$verification" "$verify_result"
append_history "$step_id" "failure" "step failed" "$verify_result" "revise"
echo "[step] FAIL"
return 1
}
append_question() {
local msg="$1"
state_append '.open_questions' "$msg"
}
ensure_open_json
ensure_plan_status() {
local ps
ps="$(state_get '.plan_status')"
if [[ "$ps" == "null" || -z "$ps" ]]; then
state_set_string '.plan_status' 'not_started'
fi
local pa
pa="$(state_get '.pending_action')"
if [[ "$pa" == "null" || -z "$pa" ]]; then
state_set_string '.pending_action' 'need_plan_approval'
fi
}
ensure_plan_status
COMMAND="${1:-status}"
ARG="${2:-}"
case "${COMMAND,,}" in
status)
status
;;
approve_plan)
ensure_plan_exists
state_set_string '.plan_status' 'approved'
state_set_string '.pending_action' 'step_ready'
state_set_number '.next_step' 1
state_set_expr '.open_questions = []'
state_mark_updated
echo "[plan] APPROVE_PLAN accepted"
;;
revise_plan)
ensure_plan_exists
append_question "${ARG:-plan requires revision}"
state_set_string '.plan_status' 'revise_requested'
state_set_string '.pending_action' 'revise_requested'
state_mark_updated
echo "[plan] REVISE_PLAN"
;;
cancel_task)
append_question "${ARG:-user cancelled}"
state_set_string '.plan_status' 'cancelled'
state_set_string '.pending_action' 'cancelled'
state_mark_updated
echo "[plan] CANCEL_TASK"
;;
approve_next)
if [[ "$(state_get '.pending_action')" != "await_next_approval" ]]; then
echo "[step] no pending step for approve-next"
status
exit 1
fi
state_set_string '.pending_action' 'step_ready'
state_mark_updated
echo "[step] APPROVE_NEXT accepted"
;;
step)
ensure_plan_exists
if [[ "$(state_get '.plan_status')" != "approved" ]]; then
echo "[step] plan is not approved"
status
exit 1
fi
if [[ "$(state_get '.pending_action')" != "step_ready" ]]; then
echo "[step] waiting token mismatch: not in step_ready"
status
exit 1
fi
target_step="$ARG"
if [[ -z "$target_step" ]]; then
target_step="$(state_get '.next_step')"
fi
if ! [[ "$target_step" =~ ^[0-9]+$ ]]; then
echo "[step] step must be integer"
exit 1
fi
run_step "$target_step"
status
;;
*)
if [[ "${COMMAND,,}" == "approve-plan" ]]; then
exec "$0" APPROVE_PLAN "${ARG:-}"
elif [[ "${COMMAND,,}" == "revise-plan" ]]; then
exec "$0" REVISE_PLAN "${ARG:-}"
elif [[ "${COMMAND,,}" == "cancel-task" ]]; then
exec "$0" CANCEL_TASK "${ARG:-}"
elif [[ "${COMMAND,,}" == "approve-next" ]]; then
exec "$0" APPROVE_NEXT
else
cat <<'EOF_USAGE'
Usage:
run_task.sh status
run_task.sh APPROVE_PLAN
run_task.sh REVISE_PLAN [note]
run_task.sh CANCEL_TASK
run_task.sh step [N]
run_task.sh APPROVE_NEXT
EOF_USAGE
fi
;;
esac

44
.loop-build/scripts/snapshot.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
LB_DIR="$ROOT_DIR/.loop-build"
SNAP_DIR="$LB_DIR/state/snapshots"
mkdir -p "$SNAP_DIR"
label="${1:-manual}"
mkdir -p "$LB_DIR/state"
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
out="$SNAP_DIR/snapshot-${stamp}-${label}.json"
if [[ -d "$ROOT_DIR/.git" ]]; then
head="$(git -C "$ROOT_DIR" rev-parse --short HEAD 2>/dev/null || true)"
if [[ -z "$head" ]]; then
head="NO_HEAD"
fi
else
head="UNKNOWN"
fi
changed_lines=""
if [[ -d "$ROOT_DIR/.git" ]]; then
changed_lines="$(git -C "$ROOT_DIR" status --porcelain --untracked-files=normal | cut -c4- | sed '/^$/d' | sort -u || true)"
fi
changed_json='[]'
if [[ -n "$changed_lines" ]]; then
changed_json="$(printf '%s\n' "$changed_lines" | jq -R -s 'split("\n") | map(select(length > 0))')"
fi
jq -n \
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg snap_label "$label" \
--arg repo "$ROOT_DIR" \
--arg head "$head" \
--argjson changed_files "$changed_json" \
'{timestamp:$timestamp,label:$snap_label,repo:$repo,head:$head,changed_files:$changed_files}' \
> "$out"
echo "[snapshot] created: $out"
if [[ -d "$ROOT_DIR/.git" ]]; then
git -C "$ROOT_DIR" status --short --untracked-files=normal
fi

108
.loop-build/scripts/verify.sh Executable file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
require_approval=1
if [[ "${APPROVE:-}" != "1" && $require_approval -eq 1 ]]; then
echo "[verify] blocked: verification commands are policy-gated."
echo "Run with APPROVE=1 ./verify.sh <target>"
exit 2
fi
show_help() {
cat <<'EOF_HELP'
usage: ./verify.sh <target>
Targets:
unit
lint
typecheck
all
EOF_HELP
}
run_if() {
local label="$1"
local cmd="$2"
echo "[verify] $label"
if eval "$cmd"; then
return 0
fi
return 1
}
run_unit() {
if [[ -f "$ROOT_DIR/package.json" ]] && jq -e '.scripts.test' "$ROOT_DIR/package.json" >/dev/null 2>&1; then
run_if unit "cd '$ROOT_DIR' && npm run -s test"
return
fi
if [[ -x "$ROOT_DIR/vendor/bin/phpunit" ]]; then
run_if unit "cd '$ROOT_DIR' && ./vendor/bin/phpunit"
return
fi
if [[ -x "$ROOT_DIR/bin/phpunit" ]]; then
run_if unit "cd '$ROOT_DIR' && ./bin/phpunit"
return
fi
if [[ -x "$ROOT_DIR/node_modules/.bin/jest" ]]; then
run_if unit "cd '$ROOT_DIR' && ./node_modules/.bin/jest"
return
fi
echo "[verify] no unit target auto-detected"
return 2
}
run_lint() {
if [[ -f "$ROOT_DIR/package.json" ]] && jq -e '.scripts.lint' "$ROOT_DIR/package.json" >/dev/null 2>&1; then
run_if lint "cd '$ROOT_DIR' && npm run -s lint"
return
fi
if [[ -x "$ROOT_DIR/vendor/bin/pint" ]]; then
run_if lint "cd '$ROOT_DIR' && ./vendor/bin/pint --test"
return
fi
if [[ -f "$ROOT_DIR/package.json" ]] && jq -e '.scripts.style' "$ROOT_DIR/package.json" >/dev/null 2>&1; then
run_if lint "cd '$ROOT_DIR' && npm run -s style"
return
fi
echo "[verify] no lint target auto-detected"
return 2
}
run_typecheck() {
if [[ -f "$ROOT_DIR/package.json" ]] && jq -e '.scripts.typecheck' "$ROOT_DIR/package.json" >/dev/null 2>&1; then
run_if typecheck "cd '$ROOT_DIR' && npm run -s typecheck"
return
fi
if [[ -x "$ROOT_DIR/node_modules/.bin/tsc" ]]; then
run_if typecheck "cd '$ROOT_DIR' && ./node_modules/.bin/tsc -p ."
return
fi
if [[ -x "$ROOT_DIR/.venv/bin/mypy" ]]; then
run_if typecheck "cd '$ROOT_DIR' && .venv/bin/mypy ."
return
fi
echo "[verify] no typecheck target auto-detected"
return 2
}
case "${1:-help}" in
unit)
run_unit
;;
lint)
run_lint
;;
typecheck)
run_typecheck
;;
all)
run_unit
run_lint
run_typecheck
;;
help|*)
show_help
;;
esac

View File

@@ -0,0 +1,34 @@
{
"task_id": "t4",
"goal": "g",
"constraints": [],
"plan_steps": [
{
"step_id": "1",
"action": "echo start && rm -rf /tmp/policytestdir",
"verification": "",
"rollback": "",
"expected_output": ""
}
],
"success_criteria": [],
"risk_notes": [],
"required_files": [],
"verify_targets": [],
"plan_status": "approved",
"pending_action": "await_next_approval",
"next_step": 1,
"completed_steps": [
"1"
],
"open_questions": [
"step_1_failed: command failed or verify failed"
],
"current_step": 1,
"last_verification": {
"target": "",
"status": "not_run"
},
"created_at": "",
"updated_at": "2026-02-24T17:04:42Z"
}

View File

@@ -0,0 +1,27 @@
{
"timestamp": "2026-02-24T16:53:12Z",
"step_id": "1",
"result": "success",
"summary": "step complete",
"verification_result": "not_run",
"next_step_recommendation": "await_next"
}
{
"timestamp": "2026-02-24T16:54:52Z",
"step_id": "1",
"result": "success",
"summary": "step complete",
"verification_result": "not_run",
"next_step_recommendation": "await_next"
}
{
"timestamp": "2026-02-24T17:04:42Z",
"step_id": "1",
"result": "failure",
"summary": "step failed",
"verification_result": "not_run",
"next_step_recommendation": "revise"
}

View File

@@ -0,0 +1,8 @@
{
"timestamp": "2026-02-24T16:53:44Z",
"label": "test",
"repo": "/home/roog/test/skills",
"head": "UNKNOWN",
"changed_files": [
]
}

View File

@@ -0,0 +1,7 @@
{
"timestamp": "2026-02-24T17:12:49Z",
"label": "manual",
"repo": "/home/roog/test/skills",
"head": "UNKNOWN",
"changed_files": []
}

2
vendor/bin/phpunit vendored Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
exit 0