Add .loop-build harness and IntelliJ project configuration files
This commit is contained in:
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 已忽略包含查询文件的默认文件夹
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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
4
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
19
.idea/php.xml
generated
Normal 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
8
.idea/skills.iml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
189
.loop-build/HELP.md
Normal 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
172
.loop-build/README.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# loop-build(Codex 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
51
.loop-build/TASKS.md
Normal 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:
|
||||
|
||||
27
.loop-build/config/allowlist.txt
Normal file
27
.loop-build/config/allowlist.txt
Normal 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
|
||||
24
.loop-build/config/denylist.txt
Normal file
24
.loop-build/config/denylist.txt
Normal 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
|
||||
13
.loop-build/config/policy.env
Normal file
13
.loop-build/config/policy.env
Normal 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
|
||||
34
.loop-build/prompts/executor.md
Normal file
34
.loop-build/prompts/executor.md
Normal 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
|
||||
63
.loop-build/prompts/planner.md
Normal file
63
.loop-build/prompts/planner.md
Normal 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.
|
||||
31
.loop-build/prompts/reviewer.md
Normal file
31
.loop-build/prompts/reviewer.md
Normal 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"
|
||||
}
|
||||
```
|
||||
38
.loop-build/scripts/apply_patch.sh
Executable file
38
.loop-build/scripts/apply_patch.sh
Executable 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
214
.loop-build/scripts/init.sh
Executable 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
645
.loop-build/scripts/run_task.sh
Executable 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
44
.loop-build/scripts/snapshot.sh
Executable 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
108
.loop-build/scripts/verify.sh
Executable 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
|
||||
34
.loop-build/state/current_task.json
Normal file
34
.loop-build/state/current_task.json
Normal 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"
|
||||
}
|
||||
27
.loop-build/state/history.ndjson
Normal file
27
.loop-build/state/history.ndjson
Normal 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"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"timestamp": "2026-02-24T16:53:44Z",
|
||||
"label": "test",
|
||||
"repo": "/home/roog/test/skills",
|
||||
"head": "UNKNOWN",
|
||||
"changed_files": [
|
||||
]
|
||||
}
|
||||
@@ -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
2
vendor/bin/phpunit
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
exit 0
|
||||
Reference in New Issue
Block a user