@fitlab-ai/agent-infra 0.7.4 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +13 -11
- package/dist/bin/cli.js +13 -11
- package/dist/lib/init.js +1 -1
- package/dist/lib/merge.js +1 -1
- package/dist/lib/sandbox/commands/create.js +26 -4
- package/dist/lib/sandbox/index.js +21 -21
- package/dist/lib/sandbox/tools.js +20 -1
- package/dist/lib/task/commands/log.js +56 -6
- package/dist/lib/task/index.js +13 -13
- package/dist/lib/update.js +1 -1
- package/lib/init.ts +1 -1
- package/lib/merge.ts +1 -1
- package/lib/sandbox/commands/create.ts +33 -4
- package/lib/sandbox/index.ts +21 -21
- package/lib/sandbox/tools.ts +28 -1
- package/lib/task/commands/log.ts +59 -6
- package/lib/task/index.ts +13 -13
- package/lib/update.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +7 -3
- package/templates/.agents/rules/README.zh-CN.md +7 -3
- package/templates/.agents/rules/cli-help-format.en.md +49 -0
- package/templates/.agents/rules/cli-help-format.zh-CN.md +49 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +25 -2
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +25 -2
- package/templates/.agents/rules/pr-sync.github.en.md +8 -6
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +8 -6
- package/templates/.agents/rules/review-handshake.en.md +97 -0
- package/templates/.agents/rules/review-handshake.zh-CN.md +97 -0
- package/templates/.agents/rules/task-management.en.md +25 -0
- package/templates/.agents/rules/task-management.zh-CN.md +29 -0
- package/templates/.agents/scripts/lib/post-review-commit.js +56 -0
- package/templates/.agents/scripts/lib/review-artifacts.js +117 -0
- package/templates/.agents/scripts/review-diff-fingerprint.js +99 -0
- package/templates/.agents/scripts/validate-artifact.js +251 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +63 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +63 -6
- package/templates/.agents/skills/block-task/SKILL.en.md +10 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/cancel-task/SKILL.en.md +10 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +10 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +10 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +11 -0
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/code-task/config/verify.en.json +3 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +3 -0
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -3
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -3
- package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +2 -107
- package/templates/.agents/skills/commit/SKILL.en.md +16 -0
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +16 -0
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +8 -0
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +8 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +20 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -0
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +20 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +20 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +16 -1
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +11 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/import-codescan/SKILL.en.md +11 -0
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +11 -0
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/import-issue/SKILL.en.md +16 -0
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +16 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -1
- package/templates/.agents/skills/plan-task/config/verify.en.json +3 -0
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +3 -0
- package/templates/.agents/skills/restore-task/SKILL.en.md +10 -0
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/review-analysis/SKILL.en.md +10 -0
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/review-analysis/config/verify.en.json +2 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -4
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -4
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-code/config/verify.en.json +5 -2
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +5 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -4
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -4
- package/templates/.agents/skills/review-code/reference/report-template.en.md +6 -0
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +6 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +10 -0
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/review-plan/config/verify.en.json +2 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -4
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -4
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +10 -0
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +10 -0
- package/templates/.agents/templates/task.en.md +12 -0
- package/templates/.agents/templates/task.zh-CN.md +12 -0
- package/templates/.github/workflows/metadata-sync.yml +1 -1
- package/templates/.github/workflows/pr-label.yml +1 -1
- package/templates/.github/workflows/status-label.yml +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 双向审查握手协议
|
|
2
|
+
|
|
3
|
+
> 三阶段(analysis / plan / code)的执行方与检视方在执行 `review-*` 与 `*-task` 技能时共用本协议。
|
|
4
|
+
> 这是协议的**单一事实源**;各 SKILL 只 `Read` 本文件,不重复抄写词表。
|
|
5
|
+
|
|
6
|
+
## 核心原则
|
|
7
|
+
|
|
8
|
+
- **检视意见是待验证输入,不是执行命令**。执行方必须逐条核实后再处置,不默认认账、不盲目反驳。
|
|
9
|
+
- **对称证据负担**:无论接受还是反驳,每条处置都要附**相称证据**。"接受"不是零成本默认路径。
|
|
10
|
+
- **达成一致再推进**:存在未关闭分歧、替代修法、无法判断或 review 后新增提交时,不得静默进入下一阶段、归档或合并。
|
|
11
|
+
|
|
12
|
+
## 执行方四态处置(`*-task` 技能,Round ≥ 2 响应上一轮审查时)
|
|
13
|
+
|
|
14
|
+
对上一轮 `review-*` 的每条 finding,先 Read/Grep 核实其引用的 `file:line` / 命令,再落一个状态:
|
|
15
|
+
|
|
16
|
+
| 状态 | 含义 | 必附证据 |
|
|
17
|
+
|------|------|----------|
|
|
18
|
+
| `accepted` | 成立,将按建议修复 | 指向修复点的 `file:line` 或本轮将施加的改动说明 |
|
|
19
|
+
| `adjusted` | 成立,但采用替代修法 | 替代修法说明 + 为何更优;待检视方确认 |
|
|
20
|
+
| `refuted` | 核实后判定不成立 / 幻觉 / 基于错误 `file:line` | 反证(`file:line` 或命令原文);待检视方确认 |
|
|
21
|
+
| `cannot-judge` | 证据不足,无法判断 | 已尝试的核实路径;交检视方/人工 |
|
|
22
|
+
|
|
23
|
+
## 检视方回交义务(`review-*` 技能,对执行方响应复核时)
|
|
24
|
+
|
|
25
|
+
执行方给出 `adjusted` / `refuted` / `cannot-judge` 后,检视方必须逐条回应,不得复读原意见或无视:
|
|
26
|
+
|
|
27
|
+
- **撤回 finding** → 账本置 `confirmed`(接受反驳)。
|
|
28
|
+
- **接受替代修法** → 账本置 `confirmed`。
|
|
29
|
+
- **补充新证据后坚持** → 账本置回 `open`(带新证据,回到执行方)。
|
|
30
|
+
- **升级人工裁决** → 账本置 `needs-human-decision`。
|
|
31
|
+
|
|
32
|
+
## 收敛终止语义(防死循环)
|
|
33
|
+
|
|
34
|
+
- 单条 finding 的握手轮次上限 `MAX_HANDSHAKE_ROUNDS`,默认 **3**,可在 `.agents/.airc.json` 的 `review.maxHandshakeRounds` 覆盖。
|
|
35
|
+
- 某条 finding 的 `round` 达到上限仍未进入终态,必须强制置 `needs-human-decision`;gate 会拦截"达限却未升级"的行。
|
|
36
|
+
- `needs-human-decision` 持续阻塞完成,直到人工在 task.md `## 人工裁决` 段记录裁定并把该行翻为 `human-decided`。
|
|
37
|
+
|
|
38
|
+
## 同源模型收敛偏差缓解(文档级纪律)
|
|
39
|
+
|
|
40
|
+
执行方与检视方常由相近模型承担,天然容易互相同意。检视时遵守:
|
|
41
|
+
|
|
42
|
+
1. **先看证据再看结论**:先读 `git diff` / 产物本体并独立形成 findings,**再**读执行方的结论与响应,避免被其结论锚定。
|
|
43
|
+
2. **默认怀疑框架**:把"看起来没问题"视为未验证;每条放行都要有可复现证据支撑(见各 `review-*` 的 `证据原文` 段硬门禁)。
|
|
44
|
+
|
|
45
|
+
> 唯一的机械杠杆是**对称证据 gate**(账本非 `open` 行必须有证据);模型同源性本身不可机械校验,故本节为纪律而非门禁。
|
|
46
|
+
|
|
47
|
+
## 机械账本(task.md `## 审查分歧账本`)
|
|
48
|
+
|
|
49
|
+
分歧状态的**单一事实源**是 task.md 的固定段 `## 审查分歧账本`,单张可解析表。阶段推进与 `complete-task` 的 gate 读取本段。
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
## 审查分歧账本
|
|
53
|
+
|
|
54
|
+
<!-- 每条 review finding 一行;状态机/证据规则见 .agents/rules/review-handshake.md。阶段推进与 complete-task gate 读取本段。 -->
|
|
55
|
+
|
|
56
|
+
| id | stage | round | severity | status | evidence |
|
|
57
|
+
|----|-------|-------|----------|--------|----------|
|
|
58
|
+
| CD-1 | code | 1 | blocker | open | review-code.md#1 |
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `id`:阶段前缀 + 序号——analysis→`AN-`、plan→`PL-`、code→`CD-`;执行方自提的人工裁决行使用 `HD-`。
|
|
62
|
+
- `stage` ∈ `{analysis, plan, code}`(外加保留值 `post-review-commit`,仅用于 post-review 豁免行)。
|
|
63
|
+
- `status` 合法枚举:`open` / `accepted` / `adjusted` / `refuted` / `cannot-judge` / `confirmed` / `needs-human-decision` / `closed` / `human-decided`。
|
|
64
|
+
- **终态集合(gate 放行)**:`{confirmed, closed, human-decided}`;其余为阻塞态。
|
|
65
|
+
- **写入责任**:`review-*` 提 finding → upsert `open` 行;`*-task` 响应 → 改四态并填 `evidence`、`round` +1;下一轮 `review-*` → `confirmed` / 置回 `open` / `needs-human-decision`;执行方修复经下一轮 review 验证通过 → `closed`;人工裁决 → `human-decided`。
|
|
66
|
+
- **向后兼容**:task.md 无此段时,gate 视为无未决分歧而放行。
|
|
67
|
+
|
|
68
|
+
### 执行方自提人工裁决行
|
|
69
|
+
|
|
70
|
+
当执行方在产物 `## 未决问题` 中标记 `[needs-human-decision]` 时,必须在 task.md `## 审查分歧账本` upsert 对应 `HD-` 行:
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+
| HD-1 | plan | - | decision | needs-human-decision | plan.md#HD-1 |
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `stage` 填该决策产生的阶段:`analysis` / `plan` / `code`。
|
|
77
|
+
- `round` 填 `-`,因为它不是 review finding 的握手轮次。
|
|
78
|
+
- `severity` 固定填 `decision`。
|
|
79
|
+
- `status` 初始填 `needs-human-decision`,因此会被现有 gate 阻塞。
|
|
80
|
+
- 人工在 task.md `## 人工裁决` 段记录裁定后,把对应 `HD-` 行翻为 `human-decided`,`evidence` 指向该裁定记录。
|
|
81
|
+
|
|
82
|
+
## post-review commit 门禁(仅 code 阶段)
|
|
83
|
+
|
|
84
|
+
- `review-code` 在最高轮报告中记录 `审查基线提交`(R,`git rev-parse HEAD`)和 `审查差异指纹`(F,完整工作区 diff fingerprint)。
|
|
85
|
+
- `commit` 只读取最高轮 `review-code` 产物;当该产物 Approved、提交前 HEAD 等于 R、且 staged diff fingerprint 等于 F 时,在 task.md 写入 `last_reviewed_commit`(B,新提交 SHA)。
|
|
86
|
+
- `complete-task` 的 `post-review-commit` gate 优先使用 B;B 缺失或非法时回退最高轮 `review-code` 的 R。
|
|
87
|
+
- 若 B / R 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code`。
|
|
88
|
+
- **豁免**:在账本追加一行 `| PRC-1 | post-review-commit | - | - | human-decided | <裁定说明> |`,记录人工明确允许该批提交免复审。
|
|
89
|
+
|
|
90
|
+
## gate 行为速查
|
|
91
|
+
|
|
92
|
+
| 调用方 | `review-ledger` 作用域 | `post-review-commit` |
|
|
93
|
+
|--------|------------------------|----------------------|
|
|
94
|
+
| `plan-task` | 仅 `analysis` 阶段行须终态 | 不挂 |
|
|
95
|
+
| `code-task` | `analysis` + `plan` 阶段行须终态 | 不挂 |
|
|
96
|
+
| `complete-task` | 全部阶段行须终态 | 挂(见上) |
|
|
97
|
+
| `analyze-task` | 不挂(首阶段) | 不挂 |
|
|
@@ -37,3 +37,28 @@ Map user intent to the corresponding workflow command:
|
|
|
37
37
|
- `complete-task`: update `status`, `current_step`, `completed_at`, `updated_at`, `agent_infra_version`
|
|
38
38
|
- `block-task`: update `status`, `blocked_at`, `blocked_reason`, `updated_at`, `agent_infra_version`
|
|
39
39
|
- `cancel-task`: update `status`, `cancelled_at`, `cancel_reason`, `updated_at`, `agent_infra_version`
|
|
40
|
+
|
|
41
|
+
## Activity Log started / done dual-marker convention (single source of truth)
|
|
42
|
+
|
|
43
|
+
> This section is the sole authoritative definition of the started/done dual marker. The skills, the renderer (`lib/task/commands/log.ts`), and the validator (`.agents/scripts/validate-artifact.js`) all defer to it; keep this section in sync when changing any of them.
|
|
44
|
+
|
|
45
|
+
**Line grammar is unchanged**: both started and done use the existing entry grammar `- {YYYY-MM-DD HH:mm:ss±HH:MM} — **{action}** by {agent} — {note}`, so the parsing regexes (`log.ts:ENTRY_RE` and `validate-artifact.js:ACTIVITY_LOG_PATTERN`) need no change.
|
|
46
|
+
|
|
47
|
+
- **started line** (written when the step begins): the action suffixes the existing base with ` [started]`, note is `started`:
|
|
48
|
+
`- {time} — **{base} [started]** by {agent} — started`
|
|
49
|
+
- **done line** (written when the step completes, unchanged from today): the action is the base itself:
|
|
50
|
+
`- {time} — **{base}** by {agent} — {completion summary}`
|
|
51
|
+
- `{base}` is that skill's existing done action text, including `(Round {N})` (e.g. `Plan Task (Round 1)`). started and done must share the same `{base}` to pair.
|
|
52
|
+
|
|
53
|
+
**Pairing and rendering** (`ai task log`): a started entry pairs with the next same-`{base}` done entry onto one row (repeated executions of the same base pair FIFO by ascending time). The STARTED column shows the start time, DONE the completion time; started with no done = in progress (DONE shows `(in progress)`); done with no started (legacy logs) = a standalone completed row. All three shapes are valid and never error.
|
|
54
|
+
|
|
55
|
+
**Gate** (`checkActivityLog`): when computing the "latest action / freshness" it skips `[started]` lines (ascending-order and format checks still cover every line), so a started marker never satisfies a skill's `expected_action_pattern`.
|
|
56
|
+
|
|
57
|
+
**Skills that write started**: every workflow skill that **appends entries to a task's `## Activity Log`** writes started, so the STARTED column stays uniformly complete across the whole `ai task log` table. Two forms, depending on whether task.md already exists:
|
|
58
|
+
|
|
59
|
+
- **Standard form (task.md already exists)** — append the started line when that round's real work begins (after prerequisites, before the first artifact action) and the done line on completion:
|
|
60
|
+
`analyze-task`, `plan-task`, `code-task`, `review-analysis`, `review-plan`, `review-code`, `commit`, `complete-task`, `create-pr`, `watch-pr`, `block-task`, `cancel-task`, `restore-task`, `close-codescan`, `close-dependabot`.
|
|
61
|
+
- **Deferred form (the skill creates task.md, so there is no file to write to at the start)** — capture `started_at` in memory before running, then when writing the Activity Log at the end, **append both lines at once** (started line uses `started_at`, done line uses the completion time):
|
|
62
|
+
`create-task`, `import-issue`, `import-codescan`, `import-dependabot`.
|
|
63
|
+
|
|
64
|
+
**Exceptions**: read-only inspection skills that do not represent real progress (e.g. `check-task`) do not write started. A bare operation with no task.md context (e.g. a `commit` not tied to a task) likewise skips it.
|
|
@@ -37,3 +37,32 @@
|
|
|
37
37
|
- `complete-task`:更新 `status`、`current_step`、`completed_at`、`updated_at`、`agent_infra_version`
|
|
38
38
|
- `block-task`:更新 `status`、`blocked_at`、`blocked_reason`、`updated_at`、`agent_infra_version`
|
|
39
39
|
- `cancel-task`:更新 `status`、`cancelled_at`、`cancel_reason`、`updated_at`、`agent_infra_version`
|
|
40
|
+
|
|
41
|
+
## Activity Log started / done 双标记约定(单一事实源)
|
|
42
|
+
|
|
43
|
+
> 本节是 started/done 双标记的唯一权威定义。各 SKILL、渲染器(`lib/task/commands/log.ts`)、
|
|
44
|
+
> 校验脚本(`.agents/scripts/validate-artifact.js`)的相关行为都以本节为准;改动任一端时同步本节。
|
|
45
|
+
|
|
46
|
+
**行语法不变**:started 与 done 都沿用既有条目语法
|
|
47
|
+
`- {YYYY-MM-DD HH:mm:ss±HH:MM} — **{action}** by {agent} — {note}`,因此解析正则
|
|
48
|
+
(`log.ts:ENTRY_RE` 与 `validate-artifact.js:ACTIVITY_LOG_PATTERN`)无需改动。
|
|
49
|
+
|
|
50
|
+
- **started 行**(步骤开始时写):action 在既有基名末尾加后缀 ` [started]`,note 用 `started`:
|
|
51
|
+
`- {time} — **{基名} [started]** by {agent} — started`
|
|
52
|
+
- **done 行**(步骤完成时写,与现状一致):action 即基名本身:
|
|
53
|
+
`- {time} — **{基名}** by {agent} — {完成说明}`
|
|
54
|
+
- `{基名}` 指该 SKILL 既有 done 条目的 action 文本,含 `(Round {N})`(如 `Plan Task (Round 1)`)。
|
|
55
|
+
started 与 done 共用同一 `{基名}` 才能配对。
|
|
56
|
+
|
|
57
|
+
**配对与渲染**(`ai task log`):按 `{基名}` 把 started 与其后最近的同名 done 配成一行(同基名多次执行按时间升序 FIFO 配对)。STARTED 列显示 started 时间、DONE 列显示 done 时间;只有 started 无 done = 进行中(DONE 显示 `(in progress)`);只有 done 无 started(历史日志)= 单态完成行。三种形态都合法、不报错。
|
|
58
|
+
|
|
59
|
+
**gate**(`checkActivityLog`):计算「最新 action / freshness」时跳过 `[started]` 行(升序与格式校验仍覆盖全部行),故 started 标记不会污染各 SKILL 的 `expected_action_pattern`。
|
|
60
|
+
|
|
61
|
+
**写 started 的 SKILL**:所有**会向某个任务的 `## 活动日志` 追加条目**的工作流 SKILL 都写 started,保证 `ai task log` 整张表的 STARTED 列一致完整。两种写法按技能是否已有 task.md 区分:
|
|
62
|
+
|
|
63
|
+
- **常规写法(task.md 已存在)**——在「该轮实质工作开始时」(前置条件确认后、第一个产出动作前)追加 started 行,完成时写 done 行:
|
|
64
|
+
`analyze-task`、`plan-task`、`code-task`、`review-analysis`、`review-plan`、`review-code`、`commit`、`complete-task`、`create-pr`、`watch-pr`、`block-task`、`cancel-task`、`restore-task`、`close-codescan`、`close-dependabot`。
|
|
65
|
+
- **延迟补写(本技能创建 task.md,开始时无文件可写)**——开始执行前先在内存记录 `started_at`,最后写活动日志时**一次性补两条**(started 行用 `started_at`、done 行用完成时间):
|
|
66
|
+
`create-task`、`import-issue`、`import-codescan`、`import-dependabot`。
|
|
67
|
+
|
|
68
|
+
**例外**:`check-task` 等只读巡检类、不代表实质工作推进的技能不写 started。无 task.md 上下文的纯操作(如无关联任务的 `commit`)同样跳过。
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { artifactName, maxRound } from "./review-artifacts.js";
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_POST_REVIEW_GLOBS = [
|
|
7
|
+
".agents/skills",
|
|
8
|
+
".agents/scripts",
|
|
9
|
+
".agents/rules",
|
|
10
|
+
".agents/workflows",
|
|
11
|
+
"bin",
|
|
12
|
+
"lib",
|
|
13
|
+
"src",
|
|
14
|
+
"templates"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function resolvePostReviewGlobs(config = {}, reviewConfig = {}) {
|
|
18
|
+
if (Array.isArray(config.post_review_globs)) {
|
|
19
|
+
return config.post_review_globs;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(reviewConfig.post_review_globs)) {
|
|
22
|
+
return reviewConfig.post_review_globs;
|
|
23
|
+
}
|
|
24
|
+
return DEFAULT_POST_REVIEW_GLOBS;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function findAuthoritativeReviewCodeArtifact(taskDir) {
|
|
28
|
+
const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
|
|
29
|
+
const round = maxRound(entries, "review-code");
|
|
30
|
+
if (round === 0) {
|
|
31
|
+
return { ok: false, round: 0, fileName: null, path: null };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const fileName = artifactName("review-code", round);
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
round,
|
|
38
|
+
fileName,
|
|
39
|
+
path: path.join(taskDir, fileName)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function extractReviewBaseline(content) {
|
|
44
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查基线提交|Review Baseline Commit)\*\*[::]\s*(.*?)\s*$/m);
|
|
45
|
+
return match ? match[1].trim().replace(/`/g, "") : "";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function extractReviewDiffFingerprint(content) {
|
|
49
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查差异指纹|Reviewed Diff Fingerprint)\*\*[::]\s*(.*?)\s*$/m);
|
|
50
|
+
return match ? match[1].trim().replace(/`/g, "") : "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseReviewVerdict(content) {
|
|
54
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.*?)\s*$/m);
|
|
55
|
+
return match ? match[1].trim() : "";
|
|
56
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Shared helpers for review-artifact parsing.
|
|
2
|
+
// Imported by both .agents/skills/code-task/scripts/detect-mode.js and
|
|
3
|
+
// .agents/scripts/validate-artifact.js so the round/verdict vocabulary stays
|
|
4
|
+
// in a single source of truth (prevents the cross-file drift this lifecycle
|
|
5
|
+
// is designed to eliminate).
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
export function escapeRegExp(value) {
|
|
10
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function maxRound(entries, stem) {
|
|
14
|
+
let max = 0;
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (entry === `${stem}.md`) {
|
|
17
|
+
max = Math.max(max, 1);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const match = entry.match(new RegExp(`^${escapeRegExp(stem)}-r(\\d+)\\.md$`));
|
|
22
|
+
if (match) {
|
|
23
|
+
max = Math.max(max, Number(match[1]));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return max;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function artifactName(stem, round) {
|
|
30
|
+
return round === 1 ? `${stem}.md` : `${stem}-r${round}.md`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function normalizeVerdict(raw) {
|
|
34
|
+
const value = String(raw).trim().toLowerCase();
|
|
35
|
+
if (value === "通过" || value === "approved") {
|
|
36
|
+
return "Approved";
|
|
37
|
+
}
|
|
38
|
+
if (value === "需要修改" || value === "changes requested") {
|
|
39
|
+
return "Changes Requested";
|
|
40
|
+
}
|
|
41
|
+
if (value === "拒绝" || value === "rejected") {
|
|
42
|
+
return "Rejected";
|
|
43
|
+
}
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function extractSection(content, names) {
|
|
48
|
+
const lines = content.split(/\r?\n/);
|
|
49
|
+
const nameSet = new Set(names);
|
|
50
|
+
const start = lines.findIndex((line) => {
|
|
51
|
+
const match = line.trim().match(/^##\s+(.+?)\s*$/);
|
|
52
|
+
return match ? nameSet.has(match[1]) : false;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (start === -1) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sectionLines = [];
|
|
60
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
61
|
+
if (/^##\s+/.test(lines[index])) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
sectionLines.push(lines[index]);
|
|
65
|
+
}
|
|
66
|
+
return sectionLines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Parse the canonical verdict out of a review-* artifact.
|
|
70
|
+
// Returns { ok, verdict, message }. Verdict collapses Approved into
|
|
71
|
+
// "Approved-with-issues" when the findings counts are non-zero.
|
|
72
|
+
export function parseVerdict(reviewPath) {
|
|
73
|
+
if (!fs.existsSync(reviewPath)) {
|
|
74
|
+
return { ok: false, verdict: null, message: `Review artifact not found: ${path.basename(reviewPath)}` };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const content = fs.readFileSync(reviewPath, "utf8");
|
|
78
|
+
const summary = extractSection(content, ["审查摘要", "Review Summary"]);
|
|
79
|
+
const fileName = path.basename(reviewPath);
|
|
80
|
+
if (!summary) {
|
|
81
|
+
return { ok: false, verdict: null, message: `cannot locate review summary section in ${fileName}` };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const verdictMatch = summary.match(/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/im);
|
|
85
|
+
if (!verdictMatch) {
|
|
86
|
+
return { ok: false, verdict: null, message: `cannot parse verdict in ${fileName}` };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const verdict = normalizeVerdict(verdictMatch[1]);
|
|
90
|
+
if (!verdict) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
verdict: null,
|
|
94
|
+
message: `unrecognized verdict '${verdictMatch[1].trim()}' in ${fileName}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (verdict !== "Approved") {
|
|
99
|
+
return { ok: true, verdict };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const findingsMatch = summary.match(/^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/im);
|
|
103
|
+
if (!findingsMatch) {
|
|
104
|
+
return { ok: false, verdict, message: `cannot parse findings count in ${fileName}` };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const counts = findingsMatch[1].match(/(\d+)\s*(?:阻塞项|blockers?).*?(\d+)\s*(?:主要|majors?).*?(\d+)\s*(?:次要|minors?)/i);
|
|
108
|
+
if (!counts) {
|
|
109
|
+
return { ok: false, verdict, message: `cannot parse findings count in ${fileName}` };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const [, blockers, majors, minors] = counts.map(Number);
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
verdict: blockers === 0 && majors === 0 && minors === 0 ? "Approved" : "Approved-with-issues"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
import { resolvePostReviewGlobs } from "./lib/post-review-commit.js";
|
|
9
|
+
|
|
10
|
+
const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
11
|
+
cwd: process.cwd(),
|
|
12
|
+
encoding: "utf8"
|
|
13
|
+
}).trim();
|
|
14
|
+
|
|
15
|
+
function usage() {
|
|
16
|
+
console.error("Usage: node .agents/scripts/review-diff-fingerprint.js <worktree|staged> <baseline>");
|
|
17
|
+
process.exit(2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function git(args, options = {}) {
|
|
21
|
+
return execFileSync("git", args, {
|
|
22
|
+
cwd: repoRoot,
|
|
23
|
+
encoding: options.encoding || "utf8",
|
|
24
|
+
env: options.env || process.env
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadReviewConfig() {
|
|
29
|
+
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
30
|
+
if (!fs.existsSync(configPath)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(fs.readFileSync(configPath, "utf8")).review || {};
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function splitNull(output) {
|
|
42
|
+
return output.split("\0").filter((value) => value !== "");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hashDiff(args, env = process.env) {
|
|
46
|
+
const diff = execFileSync("git", args, {
|
|
47
|
+
cwd: repoRoot,
|
|
48
|
+
encoding: "buffer",
|
|
49
|
+
env
|
|
50
|
+
});
|
|
51
|
+
return `sha256:${crypto.createHash("sha256").update(diff).digest("hex")}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function worktreeFingerprint(baseline, globs) {
|
|
55
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-review-index-"));
|
|
56
|
+
const tempIndex = path.join(tempDir, "index");
|
|
57
|
+
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
execFileSync("git", ["read-tree", baseline], { cwd: repoRoot, env });
|
|
61
|
+
|
|
62
|
+
const tracked = splitNull(git(["diff", "--name-only", "-z", baseline, "--", ...globs]));
|
|
63
|
+
const untracked = splitNull(git(["ls-files", "-o", "--exclude-standard", "-z", "--", ...globs]));
|
|
64
|
+
const paths = [...new Set([...tracked, ...untracked])];
|
|
65
|
+
|
|
66
|
+
for (const filePath of paths) {
|
|
67
|
+
const absolutePath = path.join(repoRoot, filePath);
|
|
68
|
+
if (fs.existsSync(absolutePath)) {
|
|
69
|
+
execFileSync("git", ["update-index", "--add", "--", filePath], { cwd: repoRoot, env });
|
|
70
|
+
} else {
|
|
71
|
+
execFileSync("git", ["update-index", "--remove", "--", filePath], { cwd: repoRoot, env });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return hashDiff(["diff", "--cached", "--binary", baseline, "--", ...globs], env);
|
|
76
|
+
} finally {
|
|
77
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function stagedFingerprint(baseline, globs) {
|
|
82
|
+
return hashDiff(["diff", "--cached", "--binary", baseline, "--", ...globs]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function main(argv) {
|
|
86
|
+
const [mode, baseline] = argv;
|
|
87
|
+
if (!["worktree", "staged"].includes(mode) || !baseline) {
|
|
88
|
+
usage();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
git(["rev-parse", "--verify", `${baseline}^{commit}`]);
|
|
92
|
+
const globs = resolvePostReviewGlobs({}, loadReviewConfig());
|
|
93
|
+
const fingerprint = mode === "worktree"
|
|
94
|
+
? worktreeFingerprint(baseline, globs)
|
|
95
|
+
: stagedFingerprint(baseline, globs);
|
|
96
|
+
process.stdout.write(`${fingerprint}\n`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main(process.argv.slice(2));
|