dsh-gate-agent 0.0.0-stage → 0.1.0
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/README.md +284 -2
- package/cordis.patch.yml +144 -0
- package/icon.svg +4 -0
- package/launchers/verify-notes.mjs +20 -0
- package/launchers/verify-notes.yml +34 -0
- package/lib/index.js +2199 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +32 -4
- package/skills/gate-code-review/SKILL.md +115 -0
- package/skills/gate-code-review/assets/worker-briefs/gate-implement-review.md +19 -0
- package/skills/gate-diagnosing-bugs/SKILL.md +143 -0
- package/skills/gate-diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/skills/gate-domain-modeling/CONTEXT-FORMAT.md +64 -0
- package/skills/gate-domain-modeling/GLOSSARY-FORMAT.md +64 -0
- package/skills/gate-domain-modeling/SKILL.md +47 -0
- package/skills/gate-fix-bug/SKILL.md +99 -0
- package/skills/gate-fix-bug/assets/worker-briefs/fix-bug-review.md +23 -0
- package/skills/gate-fix-bug/assets/worker-briefs/gate-fix-bug.md +33 -0
- package/skills/gate-handoff/SKILL.md +20 -0
- package/skills/gate-implement/SKILL.md +82 -0
- package/skills/gate-implement/assets/worker-briefs/gate-enter-implement.md +24 -0
- package/skills/gate-implement/tickets.md +114 -0
- package/skills/gate-plan-review/SKILL.md +87 -0
- package/skills/gate-plan-review/assets/worker-briefs/plan-review.md +19 -0
- package/skills/gate-planning/SKILL.md +96 -0
- package/skills/gate-planning/assets/worker-briefs/gate-enter-plan.md +24 -0
- package/skills/gate-planning/proposal.md +51 -0
- package/skills/gate-planning/rfc.md +73 -0
- package/skills/gate-playwright-e2e/SKILL.md +178 -0
- package/skills/gate-playwright-e2e/reference/config.md +129 -0
- package/skills/gate-playwright-e2e/reference/patterns.md +83 -0
- package/skills/gate-prd/SKILL.md +42 -0
- package/skills/gate-research/SKILL.md +17 -0
- package/skills/gate-tdd/SKILL.md +39 -0
- package/skills/gate-tdd/mocking.md +61 -0
- package/skills/gate-tdd/tests.md +79 -0
- package/skills/gate-user-verify/DIAGNOSE.md +75 -0
- package/skills/gate-user-verify/SKILL.md +185 -0
- package/skills/gate-user-verify/assets/worker-briefs/gate-code-verify.md +32 -0
- package/skills/gate-verify/SKILL.md +73 -0
- package/skills/gate-verify/assets/worker-briefs/gate-verify.md +21 -0
- package/skills/gate-write-agent-notes/SKILL.md +199 -0
- package/skills/gate-write-agent-notes/assets/agent-notes-board.html +1779 -0
- package/skills/gate-write-agent-notes/references/archiving.md +113 -0
- package/skills/gate-write-agent-notes/references/classification.md +34 -0
- package/skills/gate-write-agent-notes/references/note-format.md +72 -0
- package/skills/gate-write-agent-notes/references/prose-checklist.md +52 -0
- package/skills/gate-write-agent-notes/references/quality-gate.md +60 -0
- package/skills/gate-write-agent-notes/references/simplification-checklist.md +27 -0
- package/skills/gate-write-agent-notes/references/verification.md +47 -0
- package/skills/gate-write-agent-notes/references/when-to-write.md +86 -0
- package/skills/gate-write-agent-notes/scripts/agent-note-tree.ts +85 -0
- package/skills/gate-write-agent-notes/scripts/archive-agent-note.ts +218 -0
- package/skills/gate-write-agent-notes/scripts/build-board.ts +240 -0
- package/skills/gate-write-agent-notes/scripts/check-note-anchors.ts +105 -0
- package/skills/gate-write-agent-notes/scripts/verify-agent-note-format.ts +158 -0
- package/skills/gate-write-agent-notes/scripts/verify-agent-note-lifecycle.ts +74 -0
- package/skills/gate-write-agent-notes/scripts/verify-agent-note-tree.ts +56 -0
- package/skills/gate-write-agent-notes/scripts/verify-archived-agent-notes.ts +127 -0
- package/skills/gate-write-agent-notes/templates/implemented.md +35 -0
- package/skills/gate-write-agent-notes/templates/proposed.md +41 -0
- package/skills/gate-write-agent-notes/templates/rejected.md +30 -0
- package/skills/gate-writing-for-agents/SKILL-MECHANICS.md +24 -0
- package/skills/gate-writing-for-agents/SKILL.md +85 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,2199 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var DEFAULT_REVIEW_LIMIT = 5;
|
|
3
|
+
var SCHEMASTERY = /* @__PURE__ */ Symbol.for("schemastery");
|
|
4
|
+
var Config = {
|
|
5
|
+
[SCHEMASTERY]: true,
|
|
6
|
+
type: "object",
|
|
7
|
+
meta: { default: {} },
|
|
8
|
+
dict: {
|
|
9
|
+
reviewLimit: {
|
|
10
|
+
[SCHEMASTERY]: true,
|
|
11
|
+
type: "number",
|
|
12
|
+
meta: { default: DEFAULT_REVIEW_LIMIT, min: 1, max: 20, step: 1 }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"~standard": {
|
|
16
|
+
version: 1,
|
|
17
|
+
vendor: "dsh-gate-agent",
|
|
18
|
+
validate(value) {
|
|
19
|
+
if (value === void 0 || value === null) return { value: {} };
|
|
20
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
21
|
+
return { issues: [{ message: "gate config must be an object" }] };
|
|
22
|
+
}
|
|
23
|
+
const unknown = Object.keys(value).filter((key) => key !== "reviewLimit");
|
|
24
|
+
if (unknown.length > 0) {
|
|
25
|
+
return { issues: [{ message: `unknown gate config keys: ${unknown.join(", ")}` }] };
|
|
26
|
+
}
|
|
27
|
+
const out = {};
|
|
28
|
+
if (value.reviewLimit !== void 0) {
|
|
29
|
+
if (!Number.isInteger(value.reviewLimit) || value.reviewLimit < 1 || value.reviewLimit > 20) {
|
|
30
|
+
return { issues: [{ message: "reviewLimit must be an integer from 1 to 20" }] };
|
|
31
|
+
}
|
|
32
|
+
out.reviewLimit = value.reviewLimit;
|
|
33
|
+
}
|
|
34
|
+
return { value: out };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/protocol.ts
|
|
40
|
+
var DISCUSS_ORCHESTRATION = `## Discuss
|
|
41
|
+
|
|
42
|
+
discuss \u662F\u9ED8\u8BA4\u9636\u6BB5\uFF0C\u4E0D\u9700\u8981\u547D\u4EE4\u8FDB\u5165\u3002
|
|
43
|
+
|
|
44
|
+
1. \u5148\u8BFB\u76F8\u5173\u4EE3\u7801\u3001\u6587\u6863\u3001\u5386\u53F2 Note \u4E0E\u9879\u76EE\u7EA6\u675F\u3002
|
|
45
|
+
2. \u660E\u786E\u5B9E\u9645\u95EE\u9898\u3001\u7EA6\u675F\u3001\u5DF2\u6709\u51B3\u5B9A\u4E0E\u51B2\u7A81\u3002
|
|
46
|
+
3. \u7ED9\u51FA\u6709\u5B9E\u8D28\u5DEE\u5F02\u7684\u53EF\u884C\u65B9\u6848\u3001\u63A8\u8350\u4E0E\u5173\u952E\u6743\u8861\u3002
|
|
47
|
+
4. \u53EA\u5BF9\u672A\u51B3\u7684\u4EA7\u54C1\u3001\u8303\u56F4\u3001\u517C\u5BB9\u6027\u3001\u6210\u672C\u3001\u98CE\u9669\u7B49\u4EF7\u503C\u5224\u65AD\u63D0\u95EE\u3002
|
|
48
|
+
5. \u4E0D\u521B\u5EFA\u63D0\u6848\u3001Note \u6216\u89C4\u683C\u6587\u4EF6\u3002
|
|
49
|
+
6. \u6C47\u62A5\uFF1A\u5DF2\u89E3\u51B3\u4E8B\u9879\u3001\u5269\u4F59\u672A\u51B3\u4E8B\u9879\u3001\u5EFA\u8BAE\u7684\u4E0B\u4E00\u9636\u6BB5\u3002
|
|
50
|
+
7. \u4E0D\u81EA\u884C\u79BB\u5F00 discuss\uFF1B\u79BB\u5F00\u5FC5\u987B\u7531\u7528\u6237\u660E\u786E\u6388\u6743\u3002\u6388\u6743\u53EF\u4EE5\u5728\u5BF9\u8BDD\u6216 ask_user_question \u4E2D\u7ED9\u51FA\uFF1B\u83B7\u5F97\u6388\u6743\u540E\u8FDB\u5165 plan/implement\uFF0C\u4E0D\u8981\u8981\u6C42\u7528\u6237\u518D\u8F93\u5165 slash command\u3002`;
|
|
51
|
+
var PLAN_ORCHESTRATION = `## /gate-enter-plan \u7F16\u6392
|
|
52
|
+
\u4E3B\u4EE3\u7406\u6D3E worker\uFF1Bgate \u4E0D\u6D3E\u3002
|
|
53
|
+
|
|
54
|
+
## Step 1 \u2014 Planning
|
|
55
|
+
\u4E3B agent \u52A0\u8F7D\u6280\u80FD gate-planning \u548C gate-write-agent-notes\uFF1B\u8BFB\u53D6 gate-planning \u7684 SKILL.md\u3001proposal.md \u548C assets/worker-briefs/gate-enter-plan.md \u5199 planning \u4EFB\u52A1\u3002
|
|
56
|
+
\u82E5\u5DE5\u4F5C\u9879\u8981\u5199\u8BBE\u8BA1\u8282\uFF08Note \u7684 \u201C## Design\u201D / \u201C### \u8BBE\u8BA1\u201D / \u81EA\u7531\u8BBE\u8BA1\u8282\uFF09\uFF0C\u5FC5\u987B\u5148\u8BFB\u53D6 rfc.md\uFF0C\u6309\u5176\u68C0\u67E5\u9879\u6536\u655B\uFF0C\u5E76\u628A\u9002\u7528\u68C0\u67E5\u70B9\u5199\u8FDB worker prompt\uFF1Bmechanical \u6216\u4E0D\u5199\u8BBE\u8BA1\u8282\u7684\u5DE5\u4F5C\u9879\u4E0D\u5F3A\u5236\u8BFB rfc\u3002
|
|
57
|
+
\u5148\u6309 write-agent-notes \xA73 \u68C0\u7D22\u5386\u53F2\uFF0C\u518D\u6309 \xA74 \u5224\u5B9A\u5F52\u5C5E\u4E0E class\uFF1B\u7528 gate_note_write \u5199 Note\uFF0C\u8DEF\u5F84\u4E0E class \u5FC5\u987B\u4E00\u81F4\u3002\u53D1\u73B0\u65E7 implemented Note \u88AB\u65B0\u51B3\u5B9A\u5B8C\u5168\u53D6\u4EE3\u65F6\uFF0C\u8BB0\u4E0B\u5F52\u6863\u5019\u9009\uFF1B\u65B0 Note \u5C1A\u672A\u843D\u5730\u524D\u4E0D\u8981\u5F52\u6863\u3002
|
|
58
|
+
\u628A\u5DE5\u4F5C\u9879\u3001\u7EA6\u675F\u548C\u63D0\u70BC\u540E\u7684\u89C4\u5212\u65B9\u6CD5\u5199\u8FDB subagent prompt\uFF1Bplanning worker \u4E0D\u52A0\u8F7D gate \u6280\u80FD\u3002
|
|
59
|
+
\u5B8C\u6210\u540E\u8C03 gate_record({node:'plan', task, notePaths, noteDecision, noteSearch, workerSessionIds})\u3002
|
|
60
|
+
notePaths \u662F [{path,class,lifecycle}] \u6570\u7EC4\uFF1Bpath \u63A5\u53D7 .agents/notes/<lifecycle>/<class>/...\u3001<lifecycle>/<class>/... \u6216 <class>/...\uFF0Cgate_record \u5F52\u4E00\u4E3A\u5B8C\u6574\u8DEF\u5F84\uFF1Bmechanical:<\u8BF4\u660E> \u7528 noteDecision:'mechanical' \u4E14 notePaths \u4E3A\u7A7A\u3002
|
|
61
|
+
|
|
62
|
+
## Step 2 \u2014 Plan Review
|
|
63
|
+
\u4E3B agent \u52A0\u8F7D\u6280\u80FD gate-plan-review\uFF1B\u8BFB\u53D6\u8BE5\u6280\u80FD\u7684 assets/worker-briefs/plan-review.md \u5199 review \u4EFB\u52A1\uFF0C\u6309\u4EFB\u52A1\u89C4\u6A21\u548C\u98CE\u9669\u81EA\u884C\u51B3\u5B9A\u6D3E\u4E00\u4E2A\u6216\u591A\u4E2A\u53EA\u8BFB review worker\uFF0C\u8986\u76D6 Standards\u3001Spec\u3001Architecture \u4E09\u8F74\u3002
|
|
64
|
+
\u7ED9 worker \u5DE5\u4F5C\u9879\u3001Note\u3001\u56FA\u5B9A\u70B9\u548C\u8BC4\u5BA1\u65B9\u6CD5\uFF1B\u4E0D\u8981\u5728 review worker prompt \u4E2D\u9644\u5E26\u9879\u76EE\u73AF\u5883\u9A8C\u8BC1\uFF08package.json\u3001Node \u7248\u672C\u3001git \u72B6\u6001\u3001\u6587\u4EF6\u5B58\u5728\u6027\u3001\u521D\u59CB\u5316 smoke \u7ED3\u679C\uFF09\u3002
|
|
65
|
+
\u5B8C\u6210\u540E\u8C03 gate_record({node:'plan-review',findings,reviewAxes,workerSessionIds,requiresDiscussion})\u3002
|
|
66
|
+
reviewAxes \u5FC5\u987B\u5305\u542B standards\u3001spec\u3001architecture \u4E09\u8F74\u3002
|
|
67
|
+
\u9700\u8981\u7528\u6237\u51B3\u5B9A\uFF1A\u8FDB\u5165 blocked\uFF0C\u7B49\u5F85 /gate-resume\uFF1B\u4ECD\u6709 findings\uFF1A\u5148 gate_record \u672C\u8F6E plan-review\uFF0C\u786E\u8BA4 node \u5DF2\u53D8\u4E3A plan-revise\uFF0C\u518D\u91CD\u65B0\u52A0\u8F7D gate-planning\uFF0C\u53EA\u5E26 findings \u548C Note \u8DEF\u5F84\u6D3E revise\uFF0C\u518D review\u3002
|
|
68
|
+
|
|
69
|
+
## Step 3 \u2014 \u56DE\u5230 Discuss
|
|
70
|
+
review \u4E3A\u7A7A\u65F6\u56DE\u5230 discuss\uFF0CplanReady=true\u3002
|
|
71
|
+
\u5B9E\u65BD\u9700\u8981\u7528\u6237\u53E6\u884C\u6267\u884C /gate-enter-implement\uFF1B\u4E0D\u8981\u5728 planning \u91CC\u5F00\u59CB\u5B9E\u73B0\u3002`;
|
|
72
|
+
var IMPLEMENT_ORCHESTRATION = `## /gate-enter-implement \u7F16\u6392
|
|
73
|
+
\u4E3B\u4EE3\u7406\u6D3E worker\uFF1Bgate \u4E0D\u6D3E\u3002
|
|
74
|
+
|
|
75
|
+
## Step 1 \u2014 Implement
|
|
76
|
+
\u4E3B agent \u52A0\u8F7D\u6280\u80FD gate-implement\uFF1B\u8BFB\u53D6\u8BE5\u6280\u80FD\u7684 assets/worker-briefs/gate-enter-implement.md \u5199 implement \u4EFB\u52A1\u3002\u91C7\u7528\u6D4B\u8BD5\u4F18\u5148\u65F6\u540C\u65F6\u52A0\u8F7D gate-tdd\uFF0C\u5E76\u628A\u76F8\u5173\u89C4\u5219\u4EA4\u7ED9 implement worker\u3002
|
|
77
|
+
worker \u4E0D\u52A0\u8F7D gate \u6280\u80FD\u3002
|
|
78
|
+
\u5B8C\u6210\u540E\u8C03 gate_record({node:'implement', workerSessionIds})\u3002
|
|
79
|
+
|
|
80
|
+
## Step 2 \u2014 Implementation Review
|
|
81
|
+
\u4E3B agent \u52A0\u8F7D\u6280\u80FD gate-code-review\uFF1B\u8BFB\u53D6\u8BE5\u6280\u80FD\u7684 assets/worker-briefs/gate-implement-review.md \u5199 review \u4EFB\u52A1\uFF0C\u6309\u6539\u52A8\u89C4\u6A21\u81EA\u884C\u51B3\u5B9A\u6D3E\u4E00\u4E2A\u6216\u591A\u4E2A\u53EA\u8BFB review worker\uFF0C\u8986\u76D6 Standards \u4E0E Spec \u4E24\u8F74\u3002
|
|
82
|
+
\u5B8C\u6210\u540E\u8C03 gate_record({node:'code-review', findings, reviewAxes, workerSessionIds})\uFF1B\u6709 findings \u65F6\u5148\u8BB0\u5F55\uFF0C\u786E\u8BA4 node \u5DF2\u53D8\u4E3A implement-revise\uFF0C\u518D\u6D3E revise worker\u3002
|
|
83
|
+
\u5BA1\u67E5\u8981\u6C42\u6EE1\u8DB3\u540E\u7EE7\u7EED Step 3\uFF0C\u65E0\u9700\u518D\u6B21\u6388\u6743\u3002
|
|
84
|
+
\u5B9E\u73B0\u4E2D\u53D1\u73B0\u8303\u56F4\u5916\u95EE\u9898\u3001\u5408\u540C\u53D8\u5316\u6216\u9700\u8981\u91CD\u65B0\u8BA8\u8BBA\u65F6\uFF0Cworker \u505C\u6B62\u5E76\u8FD4\u56DE receipt\uFF1B\u4E3B agent \u5411\u7528\u6237\u8BF4\u660E\uFF0C\u4F7F\u7528 gate_record \u7684 requiresDiscussion \u6216\u63D0\u793A\u7528\u6237\u6267\u884C /gate-return-discuss\u3002\u4E0D\u8981\u505C\u5728 implement \u91CC\u7EE7\u7EED\u6269\u5927\u8303\u56F4\u3002
|
|
85
|
+
|
|
86
|
+
## Step 3 \u2014 Verify
|
|
87
|
+
\u4E3B agent \u52A0\u8F7D\u6280\u80FD gate-verify\uFF0C\u8BFB\u53D6\u8BE5\u6280\u80FD\u7684 assets/worker-briefs/gate-verify.md \u5199 verify \u4EFB\u52A1\uFF0C\u6D3E verify worker \u5B8C\u6210\u6700\u7EC8\u9A8C\u6536\u3002
|
|
88
|
+
\u5B8C\u6210\u540E\u8C03 gate_record({node:'verify', verification, workerSessionIds})\uFF1Bverification.toolResults \u5FC5\u987B\u662F verify worker session log \u4E2D\u771F\u5B9E tool/result \u7684 call_... id\uFF0Cevidence \u662F workspace \u5185\u7684\u666E\u901A\u6587\u4EF6\u3002
|
|
89
|
+
\u53EA\u6709\u6700\u7EC8 PASS \u624D\u5B8C\u6210\u4EA4\u4ED8\uFF1BFAIL \u56DE\u5230 implement-revise\u3002
|
|
90
|
+
\u72B6\u6001\u66F4\u65B0\u9075\u5FAA\u9879\u76EE\u767B\u8BB0\u7684\u5DE5\u4F5C\u9879\u4E0E\u51B3\u7B56\u8BB0\u5F55\u751F\u547D\u5468\u671F\u3002
|
|
91
|
+
|
|
92
|
+
## Step 4 \u2014 Note lifecycle
|
|
93
|
+
Verify PASS \u540E\u8FDB\u5165 note-lifecycle\uFF0C\u4E0D\u5F97\u76F4\u63A5 complete\u3002
|
|
94
|
+
\u4E3B agent \u8C03 gate_note_finalize\uFF0C\u518D\u8C03 gate_note_verify\uFF1B\u901A\u8FC7\u540E gate_record({node:'note-lifecycle'})\u3002
|
|
95
|
+
mechanical \u5DE5\u4F5C\u9879\u81EA\u52A8\u8DF3\u8FC7\uFF1B\u8DE8\u4F1A\u8BDD journal \u53EA\u62A5\u544A\u4E0D\u6062\u590D\uFF1Barchived manifest \u53EA\u7531\u7528\u6237\u6267\u884C /gate-note-seal\u3002note-lifecycle \u5B8C\u6210\u540E\u5EFA\u8BAE\u63D0\u4EA4\u672C\u6B21\u5B9E\u73B0\u4E0E Note\uFF1Bgate \u4E0D\u81EA\u52A8\u6267\u884C git add/commit\u3002`;
|
|
96
|
+
var FIX_BUG_ORCHESTRATION = `## /gate-fix-bug \u7F16\u6392
|
|
97
|
+
\u4E3B agent \u8BFB\u53D6 gate-fix-bug\uFF1B\u4E3B agent \u53EA\u6C47\u603B\uFF0C\u4E0D\u81EA\u884C\u4FEE\u590D\u3002
|
|
98
|
+
|
|
99
|
+
## Step 1 \u2014 Fix
|
|
100
|
+
\u4E3B agent \u8BFB\u53D6 gate-fix-bug \u6280\u80FD\u7684 assets/worker-briefs/gate-fix-bug.md\uFF0C\u6D3E fix worker\u3002fix worker \u4F7F\u7528 gate-fix-bug \u6267\u884C Process 1-9\uFF1A\u590D\u73B0\u3001\u56DE\u5F52\u8986\u76D6\u3001\u6839\u56E0\u3001\u6700\u5C0F\u4FEE\u590D\u3001\u539F\u590D\u73B0\u4E0E\u76F8\u5173\u6D4B\u8BD5\u3002\u5B8C\u6210\u540E\u8FD4\u56DE\u4FEE\u590D receipt\uFF1B\u4E0D\u8981\u81EA\u884C\u5B8C\u6210 Step 10-12\uFF0C\u4E5F\u4E0D\u8981\u63D0\u4EA4\u3002
|
|
101
|
+
|
|
102
|
+
## Step 2 \u2014 Independent review
|
|
103
|
+
\u4E3B agent \u52A0\u8F7D gate-code-review\uFF0C\u63D0\u70BC\u4E24\u8F74\u8BC4\u5BA1\u8981\u6C42\uFF0C\u518D\u8BFB\u53D6 gate-fix-bug \u6280\u80FD\u7684 assets/worker-briefs/fix-bug-review.md\uFF0C\u6D3E\u4E00\u4E2A\u72EC\u7ACB review worker\uFF1B\u4FEE\u590D\u57FA\u7EBF\u3001\u9009\u5B9A\u5931\u8D25\u3001\u5408\u540C\u5F15\u7528\u548C\u5B8C\u6574 diff \u5199\u8FDB\u7B80\u62A5\u3002review \u53EA\u62A5 findings\u3002
|
|
104
|
+
|
|
105
|
+
## Step 3 \u2014 Resolve
|
|
106
|
+
\u628A material in-scope findings \u4EA4\u56DE fix worker\uFF08send_message \u6216\u65B0 worker\uFF09\uFF0C\u5B8C\u6210 gate-fix-bug Step 11-12\uFF0C\u91CD\u8DD1\u539F\u590D\u73B0\u548C\u76F8\u5173\u6D4B\u8BD5\uFF1B\u91CD\u590D Step 2-3 \u76F4\u5230\u6CA1\u6709 material findings\u3002
|
|
107
|
+
|
|
108
|
+
## Step 4 \u2014 Record
|
|
109
|
+
\u5B8C\u6210\u540E\u8C03 gate_record({node:'fix-bug', workerSessionIds})\uFF0CworkerSessionIds \u5305\u542B fix \u4E0E review worker\uFF1Bgate \u4E0D\u518D\u81EA\u52A8\u63D0\u4EA4\u3002`;
|
|
110
|
+
var DOCS_ORCHESTRATION = `## /gate-docs \u7F16\u6392
|
|
111
|
+
\u4E0D\u52A0\u8F7D gate \u6280\u80FD\uFF1B\u8303\u56F4\u7531\u6A21\u578B\u6309\u9879\u76EE\u5B9E\u9645\u6587\u6863\u9700\u8981\u5224\u65AD\u3002
|
|
112
|
+
\u5B8C\u6210\u540E\u8C03 gate_record({node:'docs'}) \u56DE\u5230 discuss\u3002`;
|
|
113
|
+
var NOTE_LIFECYCLE_ORCHESTRATION = `## Note lifecycle
|
|
114
|
+
Verify \u5DF2 PASS\uFF1B\u5F53\u524D\u4E0D\u6D3E worker\uFF0C\u4E5F\u4E0D\u5199\u4E1A\u52A1\u4EE3\u7801\u3002
|
|
115
|
+
\u6309\u987A\u5E8F\u6267\u884C\uFF1Agate_note_finalize \u2192 gate_note_verify \u2192 gate_record({node:'note-lifecycle'})\u3002
|
|
116
|
+
finalize/verify \u5931\u8D25\u7559\u5728\u672C\u8282\u70B9\u4FEE\u590D\uFF1Barchived manifest \u7F3A\u5931\u65F6\u63D0\u793A\u7528\u6237\u6267\u884C /gate-note-seal\u3002
|
|
117
|
+
\u53EA\u6709 noteVerified=true \u624D\u80FD gate_record \u5B8C\u6210\uFF1Bmechanical \u5DE5\u4F5C\u9879\u53EF\u8DF3\u8FC7 finalize\u3002\u5B8C\u6210\u540E\u5EFA\u8BAE\u63D0\u4EA4\u672C\u6B21\u5B9E\u73B0\u4E0E Note\uFF1Bgate \u4E0D\u81EA\u52A8 git add/commit\u3002\u5B8C\u6210\u540E\u68C0\u67E5\u672C\u6B21\u65B0 Note \u662F\u5426\u5B8C\u5168\u53D6\u4EE3\u4E86\u67D0\u7BC7 implemented Note\uFF1A\u6709\u72EC\u7ACB\u53C2\u8003\u4EF7\u503C\u5C31\u5728\u6700\u7EC8\u6C47\u62A5\u4E2D\u7ED9\u51FA\u5F52\u6863\u5019\u9009\u548C\u76EE\u6807\u547D\u4EE4\uFF0C\u6CA1\u6709\u72EC\u7ACB\u4EF7\u503C\u5219\u5EFA\u8BAE\u5220\u9664\uFF1B\u4E0D\u8981\u81EA\u52A8\u5F52\u6863\u3002`;
|
|
118
|
+
var GATE_RECORD_NODES = ["plan", "plan-review", "proposal-review", "plan-revise", "implement", "code-review", "implement-revise", "verify", "verify-standalone", "note-lifecycle", "docs", "code-review-standalone", "fix-bug"];
|
|
119
|
+
var NODE_INFO = {
|
|
120
|
+
plan: { variant: "plan", method: "`gate-planning` + `gate-write-agent-notes`" },
|
|
121
|
+
"plan-review": { variant: "plan", method: "`gate-plan-review`" },
|
|
122
|
+
"plan-revise": { variant: "plan", method: "`gate-planning` + `gate-write-agent-notes`" },
|
|
123
|
+
"proposal-review": { variant: "reviewing-proposal", method: "`gate-plan-review`\uFF08\u4E09\u8F74\uFF09" },
|
|
124
|
+
implement: { variant: "implement", method: "`gate-implement`" },
|
|
125
|
+
"implement-revise": { variant: "implement", method: "`gate-implement`" },
|
|
126
|
+
"code-review": { variant: "implement", method: "`gate-code-review` (one skill, both axes)" },
|
|
127
|
+
"code-review-standalone": { variant: "reviewing-code", method: "`gate-code-review` (one skill, both axes)" },
|
|
128
|
+
verify: { variant: "implement", method: "`gate-verify`" },
|
|
129
|
+
"verify-standalone": { variant: "reviewing-verify", method: "\u65E0" },
|
|
130
|
+
"note-lifecycle": { variant: "note-lifecycle", method: "`gate-write-agent-notes`\uFF08lifecycle \u5DE5\u5177\uFF0C\u4E0D\u662F worker\uFF09" },
|
|
131
|
+
docs: { variant: "docs", method: "\u65E0" },
|
|
132
|
+
"fix-bug": { variant: "fix-bug", method: "`gate-fix-bug`" }
|
|
133
|
+
};
|
|
134
|
+
var NODE_METHODS = Object.fromEntries(Object.entries(NODE_INFO).map(([node, info]) => [node, info.method]));
|
|
135
|
+
var WORKER_NODES = new Set(GATE_RECORD_NODES.filter((node) => node !== "docs" && node !== "note-lifecycle"));
|
|
136
|
+
var DISPATCH_RULE = `## \u6D3E\u53D1\u89C4\u5219
|
|
137
|
+
\u6BCF\u6B21\u6D3E subagent \u524D\uFF0C\u6309\u5F53\u524D procedure \u7684 method \u91CD\u65B0\u52A0\u8F7D\u6280\u80FD\uFF1B\u4E0D\u8981\u4F7F\u7528\u8BB0\u5FC6\u4E2D\u7684\u8282\u70B9\u6216\u6280\u80FD\uFF1B\u591A\u8F6E/\u538B\u7F29\u540E\u91CD\u65B0\u786E\u8BA4\u3002`;
|
|
138
|
+
var REVIEWING_CODE_PROCEDURE = `## /gate-implement-review \u7F16\u6392
|
|
139
|
+
\u4E3B agent \u52A0\u8F7D gate-code-review\uFF1B\u6D3E\u53EA\u8BFB review worker \u8986\u76D6 Standards \u4E0E Spec \u4E24\u8F74\uFF1B\u5B8C\u6210\u540E gate_record({node:'code-review-standalone', findings, reviewAxes, workerSessionIds}) \u56DE discuss\u3002`;
|
|
140
|
+
var REVIEWING_PROPOSAL_PROCEDURE = `## /gate-propose-review \u7F16\u6392
|
|
141
|
+
\u4E3B agent \u52A0\u8F7D gate-plan-review\uFF1B\u6D3E\u53EA\u8BFB review worker \u8986\u76D6 Standards/Spec/Architecture \u4E09\u8F74\uFF1B\u5B8C\u6210\u540E gate_record({node:'proposal-review', findings, reviewAxes, workerSessionIds}) \u56DE discuss\u3002`;
|
|
142
|
+
var REVIEWING_VERIFY_PROCEDURE = `## /gate-code-verify \u7F16\u6392
|
|
143
|
+
\u6D3E\u5B50\u4EE3\u7406\u52A0\u8F7D\u4F7F\u7528 gate-user-verify \u6309 Step 6 \u6C47\u62A5\uFF1Bverification.toolResults \u5FC5\u987B\u662F verify worker session log \u4E2D\u771F\u5B9E tool/result \u7684 call_... id\uFF0Cevidence \u662F workspace \u5185\u7684\u666E\u901A\u6587\u4EF6\uFF1B\u5B8C\u6210\u540E gate_record({node:'verify-standalone', verification, workerSessionIds}) \u56DE discuss\u3002`;
|
|
144
|
+
function stepSection(text, from, to) {
|
|
145
|
+
const start = text.indexOf(from);
|
|
146
|
+
if (start < 0) return "";
|
|
147
|
+
const end = to === void 0 ? text.length : text.indexOf(to, start);
|
|
148
|
+
return text.slice(start, end < 0 ? text.length : end).trim();
|
|
149
|
+
}
|
|
150
|
+
var PLAN_STEP_1 = stepSection(PLAN_ORCHESTRATION, "## Step 1", "## Step 2");
|
|
151
|
+
var PLAN_STEP_2 = stepSection(PLAN_ORCHESTRATION, "## Step 2", "## Step 3");
|
|
152
|
+
var IMPLEMENT_STEP_1 = stepSection(IMPLEMENT_ORCHESTRATION, "## Step 1", "## Step 2");
|
|
153
|
+
var IMPLEMENT_STEP_2 = stepSection(IMPLEMENT_ORCHESTRATION, "## Step 2", "## Step 3");
|
|
154
|
+
var IMPLEMENT_STEP_3 = stepSection(IMPLEMENT_ORCHESTRATION, "## Step 3", "## Step 4");
|
|
155
|
+
var NODE_PROCEDURES = {
|
|
156
|
+
plan: PLAN_STEP_1,
|
|
157
|
+
"plan-review": PLAN_STEP_2,
|
|
158
|
+
"plan-revise": `## Plan revise
|
|
159
|
+
\u6839\u636E findings \u7528 gate_note_write \u66F4\u65B0 Note\uFF1B\u5B8C\u6210\u540E gate_record({node:'plan-revise'}) \u56DE\u5230 plan-review\uFF0C\u518D\u6309 plan-review procedure review\u3002`,
|
|
160
|
+
implement: IMPLEMENT_STEP_1,
|
|
161
|
+
"code-review": IMPLEMENT_STEP_2,
|
|
162
|
+
"implement-revise": `## Implement revise
|
|
163
|
+
\u4FEE\u590D findings\uFF1B\u91CD\u8DD1\u6D4B\u8BD5\uFF1B\u5B8C\u6210\u540E gate_record({node:'implement-revise'}) \u56DE\u5230 code-review\uFF0C\u518D\u6309 code-review procedure review\u3002`,
|
|
164
|
+
verify: IMPLEMENT_STEP_3,
|
|
165
|
+
"note-lifecycle": NOTE_LIFECYCLE_ORCHESTRATION,
|
|
166
|
+
"fix-bug": FIX_BUG_ORCHESTRATION,
|
|
167
|
+
"code-review-standalone": REVIEWING_CODE_PROCEDURE,
|
|
168
|
+
"proposal-review": REVIEWING_PROPOSAL_PROCEDURE,
|
|
169
|
+
"verify-standalone": REVIEWING_VERIFY_PROCEDURE
|
|
170
|
+
};
|
|
171
|
+
var GATE_METHODOLOGY = `## Gate \u65B9\u6CD5\u8BBA
|
|
172
|
+
\u672C\u6BB5\u53EA\u5B9A\u4E49\u5404\u9636\u6BB5\u7684\u5DE5\u4F5C\u65B9\u6CD5\u4E0E\u8D28\u91CF\u6807\u51C6\u3002\u5F53\u524D phase/node\u3001procedure \u548C\u8981\u52A0\u8F7D\u7684\u6280\u80FD\u6765\u81EA\u6700\u8FD1\u4E00\u6B21 gate_status\u3001gate_record \u6216\u547D\u4EE4\u7ED3\u679C\uFF1B\u53EA\u6267\u884C\u5F53\u524D phase \u7684 procedure\uFF0C\u5176\u4ED6\u9636\u6BB5\u7684\u65B9\u6CD5\u8BBA\u4EC5\u4F5C\u80CC\u666F\u53C2\u8003\uFF1Bcompaction \u540E\u5FC5\u987B\u91CD\u65B0\u83B7\u53D6\u3002
|
|
173
|
+
|
|
174
|
+
## discuss
|
|
175
|
+
discuss \u662F\u9ED8\u8BA4\u9636\u6BB5\u3002\u5148\u8BFB\u76F8\u5173\u4EE3\u7801\u3001\u6587\u6863\u3001\u5386\u53F2 Note \u4E0E\u9879\u76EE\u7EA6\u675F\uFF1B\u660E\u786E\u5B9E\u9645\u95EE\u9898\u3001\u7EA6\u675F\u3001\u5DF2\u6709\u51B3\u5B9A\u4E0E\u51B2\u7A81\uFF1B\u7ED9\u51FA\u6709\u5B9E\u8D28\u5DEE\u5F02\u7684\u53EF\u884C\u65B9\u6848\u3001\u63A8\u8350\u4E0E\u5173\u952E\u6743\u8861\uFF1B\u53EA\u5BF9\u672A\u51B3\u7684\u4EA7\u54C1\u3001\u8303\u56F4\u3001\u517C\u5BB9\u6027\u3001\u6210\u672C\u3001\u98CE\u9669\u7B49\u4EF7\u503C\u5224\u65AD\u63D0\u95EE\uFF1B\u6C47\u62A5\u5DF2\u89E3\u51B3\u3001\u672A\u51B3\u4E8B\u9879\u548C\u5EFA\u8BAE\u4E0B\u4E00\u9636\u6BB5\u3002\u4E0D\u8FDB\u5165\u5B9E\u65BD\uFF0C\u53EA\u6709\u7528\u6237\u660E\u786E\u6388\u6743\u624D\u79BB\u5F00\u3002
|
|
176
|
+
|
|
177
|
+
## plan
|
|
178
|
+
\u628A\u7528\u6237\u7B80\u8FF0\u6536\u655B\u6210\u53EF\u5B9E\u65BD\u8BA1\u5212\u4E0E proposed Note\uFF1B\u8BA1\u5212\u5FC5\u987B\u5199\u6E05\u8303\u56F4\u3001\u9A8C\u6536\u3001\u5907\u9009\u4E0E\u98CE\u9669\u3002Plan Review \u72EC\u7ACB\u5224\u65AD\uFF1Bfindings \u5148\u8BB0\u5F55\u518D revise\uFF1Breview \u901A\u8FC7\u540E\u56DE discuss\u3002\u4E0D\u8981\u5728\u89C4\u5212\u91CC\u5F00\u59CB\u5B9E\u73B0\u3002
|
|
179
|
+
|
|
180
|
+
## implement
|
|
181
|
+
\u6309\u5DF2\u786E\u8BA4\u7684\u8BA1\u5212\u5B9E\u65BD\u5E76\u5B8C\u6210\u6D4B\u8BD5\uFF1B\u5B9E\u73B0\u540E\u505A\u72EC\u7ACB review\uFF0C\u518D\u7528\u53EF\u6267\u884C\u8BC1\u636E verify\uFF1B\u6700\u540E\u5B8C\u6210 Note lifecycle\u3002\u4E0D\u6269\u5927\u8303\u56F4\uFF0C\u4E0D\u5728\u5B9E\u73B0\u4E2D\u91CD\u65B0\u89C4\u5212\u3002
|
|
182
|
+
|
|
183
|
+
## code-review
|
|
184
|
+
\u72EC\u7ACB\u5224\u65AD\u6539\u52A8\u662F\u5426\u6EE1\u8DB3\u5408\u540C\u4E0E\u6807\u51C6\uFF1B\u53EA\u62A5 findings\uFF0C\u4E0D\u4FEE\u4EE3\u7801\u3001\u4E0D\u66FF\u4EE3 verify\uFF1Bfindings \u8BB0\u5F55\u540E\u518D\u8FDB\u5165 revise\u3002
|
|
185
|
+
|
|
186
|
+
## verify
|
|
187
|
+
\u7528\u53EF\u6267\u884C\u8BC1\u636E\u5224\u5B9A PASS/FAIL\uFF1B\u4E0D\u4FEE\u6539\u4EA7\u54C1/\u6D4B\u8BD5\u8BA9\u7ED3\u679C\u53D8\u7EFF\uFF1BFAIL \u56DE implement-revise\u3002
|
|
188
|
+
|
|
189
|
+
## note-lifecycle
|
|
190
|
+
\u5B8C\u6210 Note \u7684 finalize \u4E0E\u6821\u9A8C\uFF1B\u4E0D\u5199\u4E1A\u52A1\u4EE3\u7801\uFF1B\u68C0\u67E5\u662F\u5426\u4EA7\u751F\u5F52\u6863\u5019\u9009\uFF1B\u5B8C\u6210\u540E\u56DE discuss\u3002
|
|
191
|
+
|
|
192
|
+
## fix-bug
|
|
193
|
+
\u5BF9\u5DF2\u6388\u6743\u7684\u5B9E\u73B0\u7F3A\u9677\uFF1A\u590D\u73B0\u3001\u56DE\u5F52\u8986\u76D6\u3001\u6839\u56E0\u3001\u6700\u5C0F\u4FEE\u590D\u3001\u72EC\u7ACB review\uFF1B\u4E0D\u505A\u65B0\u529F\u80FD\u3001\u4E0D\u6269\u5927\u8303\u56F4\uFF1B\u5B8C\u6210\u540E\u56DE discuss\u3002
|
|
194
|
+
|
|
195
|
+
## docs
|
|
196
|
+
\u6587\u6863\u6CBB\u7406\uFF1B\u4E0D\u6539\u53D8\u529F\u80FD\u884C\u4E3A\uFF1B\u8303\u56F4\u7531\u6A21\u578B\u6309\u9879\u76EE\u5B9E\u9645\u6587\u6863\u9700\u8981\u5224\u65AD\uFF1B\u5B8C\u6210\u540E\u56DE discuss\u3002
|
|
197
|
+
|
|
198
|
+
## reviewing-code
|
|
199
|
+
\u53EA\u8BFB code review\uFF1A\u72EC\u7ACB\u5224\u65AD Standards \u4E0E Spec \u4E24\u8F74\uFF0C\u53EA\u8F93\u51FA findings\uFF0C\u4E0D\u4FEE\u4EE3\u7801\u3001\u4E0D\u66FF\u4EE3 verify\u3002
|
|
200
|
+
|
|
201
|
+
## reviewing-proposal
|
|
202
|
+
\u53EA\u8BFB\u63D0\u6848\u8BC4\u5BA1\uFF1A\u8986\u76D6 Standards\u3001Spec\u3001Architecture \u4E09\u8F74\uFF0C\u53EA\u8F93\u51FA\u5224\u65AD\uFF0C\u4E0D\u4FEE\u6539\u89C4\u5212\u3001\u4E0D\u81EA\u52A8 revise\u3002
|
|
203
|
+
|
|
204
|
+
## reviewing-verify
|
|
205
|
+
\u53EA\u8BFB\u72EC\u7ACB\u9A8C\u6536\uFF1A\u4E0D\u6539\u4EE3\u7801\u3001\u4E0D\u66FF\u4EE3 implement\uFF0C\u53EA\u8F93\u51FA PASS/FAIL \u4E0E\u8BC1\u636E\u5224\u65AD\u3002
|
|
206
|
+
|
|
207
|
+
## blocked / complete
|
|
208
|
+
blocked \u8868\u793A\u51BB\u7ED3\uFF1A\u7B49\u5F85 /gate-resume \u6216 /gate-return-discuss\u3002complete \u8868\u793A\u5F53\u524D\u4EFB\u52A1\u5B8C\u6210\uFF0C\u65B0\u4EFB\u52A1\u9700\u8981\u91CD\u65B0\u6388\u6743\u3002`;
|
|
209
|
+
function protocolVariant(state) {
|
|
210
|
+
if (state.phase === "orchestrating") return NODE_INFO[state.node ?? ""]?.variant ?? state.phase;
|
|
211
|
+
if (state.phase === "reviewing") return NODE_INFO[state.node ?? ""]?.variant ?? "reviewing-code";
|
|
212
|
+
return state.phase;
|
|
213
|
+
}
|
|
214
|
+
function procedureFor(state, { reviewLimit = DEFAULT_REVIEW_LIMIT } = {}) {
|
|
215
|
+
const variant = protocolVariant(state);
|
|
216
|
+
const method = NODE_METHODS[state.node ?? ""] ?? "\u65E0";
|
|
217
|
+
let text;
|
|
218
|
+
if (variant === "discuss") {
|
|
219
|
+
text = "discuss \u4E0D\u6D3E worker\uFF1B\u4E3B\u4EE3\u7406\u53EF\u81EA\u884C\u8BFB/\u5199\uFF1B\u7528\u6237\u6388\u6743\u540E\u8C03\u7528 gate_start_phase \u8FDB\u5165 plan/implement\u3002";
|
|
220
|
+
} else if (variant === "blocked") {
|
|
221
|
+
text = `Gate \u5DF2\u51BB\u7ED3\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u89E3\u51BB\u540E\u56DE ${state.blockedFrom?.phase ?? "discuss"} / ${state.blockedFrom?.node ?? "\u65E0"}\uFF1B\u7528 /gate-resume \u6216 /gate-return-discuss\u3002`;
|
|
222
|
+
} else if (variant === "complete") {
|
|
223
|
+
text = "Gate \u5DF2\u5B8C\u6210\uFF1B\u65B0\u4EFB\u52A1\u9700\u8981\u91CD\u65B0\u6388\u6743\u3002";
|
|
224
|
+
} else if (variant === "docs") {
|
|
225
|
+
text = DOCS_ORCHESTRATION;
|
|
226
|
+
} else {
|
|
227
|
+
text = NODE_PROCEDURES[state.node ?? ""] ?? "";
|
|
228
|
+
}
|
|
229
|
+
if (text === "") {
|
|
230
|
+
text = "\u5F53\u524D phase/node \u6CA1\u6709\u5339\u914D\u7684 procedure\uFF1B\u8C03\u7528 gate_status \u91CD\u65B0\u8BFB\u53D6\uFF1B\u82E5\u4ECD\u4E0D\u4E00\u81F4\uFF0C\u56DE discuss \u4FEE\u590D Gate \u72B6\u6001\u3002";
|
|
231
|
+
}
|
|
232
|
+
if (["plan", "implement", "fix-bug", "reviewing-code", "reviewing-proposal", "reviewing-verify"].includes(variant)) {
|
|
233
|
+
text = [text, DISPATCH_RULE].filter(Boolean).join("\n\n");
|
|
234
|
+
}
|
|
235
|
+
if (variant === "plan" || variant === "implement") {
|
|
236
|
+
text = [text, `## \u5F53\u524D\u8F6E\u6B21
|
|
237
|
+
${roundsText(state, reviewLimit)}`].filter(Boolean).join("\n\n");
|
|
238
|
+
}
|
|
239
|
+
return { phase: state.phase, node: state.node, method, text, hasRounds: variant === "plan" || variant === "implement" };
|
|
240
|
+
}
|
|
241
|
+
function procedureFailure(state, reviewLimit, fields) {
|
|
242
|
+
const procedure = procedureFor(state, { reviewLimit });
|
|
243
|
+
return { ok: false, ...fields, procedure, method: procedure.method };
|
|
244
|
+
}
|
|
245
|
+
function roundNumbers(state) {
|
|
246
|
+
const inPlan = state.node === "plan-review";
|
|
247
|
+
const inCode = state.node === "code-review";
|
|
248
|
+
return {
|
|
249
|
+
inPlan,
|
|
250
|
+
inCode,
|
|
251
|
+
planRound: (state.rounds?.planReview ?? 0) + (inPlan ? 1 : 0),
|
|
252
|
+
codeRound: (state.rounds?.codeReview ?? 0) + (inCode ? 1 : 0)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function roundsText(state, reviewLimit) {
|
|
256
|
+
const { inPlan, inCode, planRound, codeRound } = roundNumbers(state);
|
|
257
|
+
return `plan ${planRound}/${reviewLimit}${inPlan ? "\uFF08\u5F53\u524D\uFF09" : ""}, code ${codeRound}/${reviewLimit}${inCode ? "\uFF08\u5F53\u524D\uFF09" : ""}`;
|
|
258
|
+
}
|
|
259
|
+
function statusText(state, reviewLimit = DEFAULT_REVIEW_LIMIT, { includeRounds = true } = {}) {
|
|
260
|
+
const { inPlan, inCode } = roundNumbers(state);
|
|
261
|
+
const lines = [
|
|
262
|
+
`\u72B6\u6001\uFF1A${state.phase}`,
|
|
263
|
+
`\u8282\u70B9\uFF1A${state.node ?? "\u65E0"}`,
|
|
264
|
+
`\u4E8B\u9879\uFF1A${state.task ?? "\u65E0"}`,
|
|
265
|
+
...state.phase === "blocked" ? [`\u89E3\u51BB\u540E\uFF1A${state.blockedFrom?.phase ?? "discuss"}${state.blockedFrom?.node === null || state.blockedFrom?.node === void 0 ? "" : ` / ${state.blockedFrom.node}`}`] : [],
|
|
266
|
+
`work\uFF1A${state.work}`,
|
|
267
|
+
`blockReason\uFF1A${state.blockReason ?? "\u65E0"}`,
|
|
268
|
+
`planReady\uFF1A${state.planReady ? "\u662F" : "\u5426"}`,
|
|
269
|
+
...includeRounds ? [`rounds\uFF1A${roundsText(state, reviewLimit)}`] : []
|
|
270
|
+
];
|
|
271
|
+
if (state.notePaths?.length > 0 || state.noteDecision === "mechanical") {
|
|
272
|
+
lines.push(`notes\uFF1A${state.noteDecision ?? "\u65E0"} ${state.notePaths?.length ?? 0} \u7BC7\uFF0Cverified=${state.noteVerified === true ? "\u662F" : "\u5426"}`);
|
|
273
|
+
}
|
|
274
|
+
if (state.exhausted?.plan === true || state.exhausted?.code === true) {
|
|
275
|
+
lines.push(`exhausted\uFF1Aplan ${state.exhausted.plan ? "\u662F" : "\u5426"}, code ${state.exhausted.code ? "\u662F" : "\u5426"}`);
|
|
276
|
+
}
|
|
277
|
+
if (state.verification !== null) {
|
|
278
|
+
lines.push(`verify\uFF1A${state.verification.passed === true ? "PASS" : "FAIL"}\uFF08${state.verification.selfReported === true ? "self-reported" : "verified"}\uFF09`);
|
|
279
|
+
}
|
|
280
|
+
return lines.join("\n");
|
|
281
|
+
}
|
|
282
|
+
function procedureBlock(procedure) {
|
|
283
|
+
return `\u5F53\u524D phase/node\uFF1A${procedure.phase} / ${procedure.node ?? "\u65E0"}
|
|
284
|
+
\u65B9\u6CD5\uFF1A${procedure.method}
|
|
285
|
+
|
|
286
|
+
${procedure.text}`;
|
|
287
|
+
}
|
|
288
|
+
function procedureResultText(procedure, trailing) {
|
|
289
|
+
return [procedureBlock(procedure), trailing].filter(Boolean).join("\n\n---\n\n");
|
|
290
|
+
}
|
|
291
|
+
function renderProcedureResult(value) {
|
|
292
|
+
const { procedure, ...rest } = value ?? {};
|
|
293
|
+
const trailing = JSON.stringify(rest);
|
|
294
|
+
return [{ type: "text", text: procedure !== null && procedure !== void 0 ? procedureResultText(procedure, trailing) : trailing }];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/state.ts
|
|
298
|
+
var PHASES = ["discuss", "orchestrating", "reviewing", "docs", "blocked", "complete"];
|
|
299
|
+
var states = /* @__PURE__ */ new WeakMap();
|
|
300
|
+
var idStates = /* @__PURE__ */ new Map();
|
|
301
|
+
function idKey(root, agent) {
|
|
302
|
+
return `${root}\0${String(agent?.id ?? "anonymous")}`;
|
|
303
|
+
}
|
|
304
|
+
function clone(value) {
|
|
305
|
+
return JSON.parse(JSON.stringify(value));
|
|
306
|
+
}
|
|
307
|
+
function defaultState() {
|
|
308
|
+
return {
|
|
309
|
+
phase: "discuss",
|
|
310
|
+
node: null,
|
|
311
|
+
task: null,
|
|
312
|
+
work: "active",
|
|
313
|
+
blockReason: null,
|
|
314
|
+
rounds: { planReview: 0, codeReview: 0 },
|
|
315
|
+
exhausted: { plan: false, code: false },
|
|
316
|
+
planReady: false,
|
|
317
|
+
verification: null,
|
|
318
|
+
blockedFrom: null,
|
|
319
|
+
notePaths: [],
|
|
320
|
+
noteDecision: null,
|
|
321
|
+
noteSearch: null,
|
|
322
|
+
noteVerified: false,
|
|
323
|
+
noteFinalized: false,
|
|
324
|
+
noteFinalizeRecovery: [],
|
|
325
|
+
pending: {},
|
|
326
|
+
updatedAt: null
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function textOrNull(value) {
|
|
330
|
+
return typeof value === "string" ? value : null;
|
|
331
|
+
}
|
|
332
|
+
function normalizeState(value) {
|
|
333
|
+
const base = defaultState();
|
|
334
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return base;
|
|
335
|
+
const source = value;
|
|
336
|
+
const phase = PHASES.includes(source.phase) ? source.phase : "discuss";
|
|
337
|
+
const blockedFrom = source.blockedFrom !== null && typeof source.blockedFrom === "object" && !Array.isArray(source.blockedFrom) ? { phase: textOrNull(source.blockedFrom.phase), node: textOrNull(source.blockedFrom.node) } : null;
|
|
338
|
+
return {
|
|
339
|
+
phase,
|
|
340
|
+
node: textOrNull(source.node),
|
|
341
|
+
task: textOrNull(source.task),
|
|
342
|
+
work: textOrNull(source.work) ?? "active",
|
|
343
|
+
blockReason: textOrNull(source.blockReason),
|
|
344
|
+
rounds: {
|
|
345
|
+
planReview: Number(source.rounds?.planReview) || 0,
|
|
346
|
+
codeReview: Number(source.rounds?.codeReview) || 0
|
|
347
|
+
},
|
|
348
|
+
exhausted: {
|
|
349
|
+
plan: source.exhausted?.plan === true,
|
|
350
|
+
code: source.exhausted?.code === true
|
|
351
|
+
},
|
|
352
|
+
planReady: source.planReady === true,
|
|
353
|
+
verification: source.verification !== null && typeof source.verification === "object" && !Array.isArray(source.verification) ? clone(source.verification) : null,
|
|
354
|
+
blockedFrom,
|
|
355
|
+
notePaths: Array.isArray(source.notePaths) ? source.notePaths.map((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) ? { ...entry } : entry) : [],
|
|
356
|
+
noteDecision: textOrNull(source.noteDecision),
|
|
357
|
+
noteSearch: textOrNull(source.noteSearch),
|
|
358
|
+
noteVerified: source.noteVerified === true,
|
|
359
|
+
noteFinalized: source.noteFinalized === true,
|
|
360
|
+
noteFinalizeRecovery: Array.isArray(source.noteFinalizeRecovery) ? source.noteFinalizeRecovery : [],
|
|
361
|
+
pending: source.pending !== null && typeof source.pending === "object" && !Array.isArray(source.pending) ? source.pending : {},
|
|
362
|
+
updatedAt: textOrNull(source.updatedAt)
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function loadState(root, agent) {
|
|
366
|
+
const session = agent?.session;
|
|
367
|
+
if (session !== null && typeof session === "object" && states.has(session)) {
|
|
368
|
+
return clone(states.get(session));
|
|
369
|
+
}
|
|
370
|
+
return clone(idStates.get(idKey(root, agent)) ?? defaultState());
|
|
371
|
+
}
|
|
372
|
+
function saveState(root, agent, state) {
|
|
373
|
+
const next = normalizeState({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
374
|
+
const session = agent?.session;
|
|
375
|
+
if (session !== null && typeof session === "object") states.set(session, clone(next));
|
|
376
|
+
idStates.set(idKey(root, agent), clone(next));
|
|
377
|
+
return clone(next);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/notes/note-files.ts
|
|
381
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
382
|
+
import path4 from "node:path";
|
|
383
|
+
|
|
384
|
+
// src/notes/fs.ts
|
|
385
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
386
|
+
import path2 from "node:path";
|
|
387
|
+
|
|
388
|
+
// src/paths.ts
|
|
389
|
+
import path from "node:path";
|
|
390
|
+
import { fileURLToPath } from "node:url";
|
|
391
|
+
var PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
392
|
+
var SKILLS = path.join(PACKAGE_ROOT, "skills");
|
|
393
|
+
var NOTES_SKILL_DIR = path.join(SKILLS, "gate-write-agent-notes");
|
|
394
|
+
var NOTES_LAUNCHER_TEMPLATE = path.join(PACKAGE_ROOT, "launchers", "verify-notes.mjs");
|
|
395
|
+
var NOTES_CI_TEMPLATE = path.join(PACKAGE_ROOT, "launchers", "verify-notes.yml");
|
|
396
|
+
var NOTES_CI_SCRIPTS = [
|
|
397
|
+
"agent-note-tree.ts",
|
|
398
|
+
"verify-agent-note-tree.ts",
|
|
399
|
+
"verify-agent-note-format.ts",
|
|
400
|
+
"verify-agent-note-lifecycle.ts",
|
|
401
|
+
"verify-archived-agent-notes.ts"
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
// src/notes/fs.ts
|
|
405
|
+
function isInside(root, target) {
|
|
406
|
+
const relative = path2.relative(root, target);
|
|
407
|
+
return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
|
|
408
|
+
}
|
|
409
|
+
function resolveNotesSkill() {
|
|
410
|
+
const skill = path2.join(NOTES_SKILL_DIR, "SKILL.md");
|
|
411
|
+
if (!existsSync(skill)) return void 0;
|
|
412
|
+
const template = path2.join(NOTES_SKILL_DIR, "templates", "proposed.md");
|
|
413
|
+
return { skill, template: existsSync(template) ? template : void 0 };
|
|
414
|
+
}
|
|
415
|
+
function resolveNodeBinary() {
|
|
416
|
+
if (!process.versions?.electron) return process.execPath;
|
|
417
|
+
const configured = process.env.DSH_DESKTOP_NODE_EXECUTABLE;
|
|
418
|
+
if (typeof configured === "string" && configured !== "" && existsSync(configured)) return configured;
|
|
419
|
+
if (typeof process.resourcesPath === "string" && process.resourcesPath !== "") {
|
|
420
|
+
const bin = path2.join(process.resourcesPath, "runtime", "primary-runtime", "dependencies", "node", "bin", process.platform === "win32" ? "node.exe" : "node");
|
|
421
|
+
if (existsSync(bin)) return bin;
|
|
422
|
+
}
|
|
423
|
+
return void 0;
|
|
424
|
+
}
|
|
425
|
+
function pathHasSymlink(base, absolute) {
|
|
426
|
+
const relative = path2.relative(base, absolute);
|
|
427
|
+
if (relative === "" || relative.startsWith("..")) return true;
|
|
428
|
+
let current = base;
|
|
429
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink()) return true;
|
|
430
|
+
for (const part of relative.split(path2.sep)) {
|
|
431
|
+
if (part === "") continue;
|
|
432
|
+
current = path2.join(current, part);
|
|
433
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink()) return true;
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/notes/scripts.ts
|
|
439
|
+
import { execFileSync } from "node:child_process";
|
|
440
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
441
|
+
import path3 from "node:path";
|
|
442
|
+
import { randomUUID } from "node:crypto";
|
|
443
|
+
import { rmSync } from "node:fs";
|
|
444
|
+
function runNotesScripts(root) {
|
|
445
|
+
const node = resolveNodeBinary();
|
|
446
|
+
if (node === void 0) return { ok: false, code: "notes-runtime-unavailable", results: [] };
|
|
447
|
+
const notes = path3.join(root, ".agents", "notes");
|
|
448
|
+
const scripts = [
|
|
449
|
+
"verify-agent-note-tree.ts",
|
|
450
|
+
"verify-agent-note-format.ts",
|
|
451
|
+
"verify-agent-note-lifecycle.ts",
|
|
452
|
+
"verify-archived-agent-notes.ts"
|
|
453
|
+
];
|
|
454
|
+
const results = [];
|
|
455
|
+
for (const name of scripts) {
|
|
456
|
+
const script = path3.join(NOTES_SKILL_DIR, "scripts", name);
|
|
457
|
+
try {
|
|
458
|
+
const output = execFileSync(node, [script, "--root", notes], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
459
|
+
results.push({ name, ok: true, output: String(output).trim() });
|
|
460
|
+
} catch (error) {
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
code: "note-verification-failed",
|
|
464
|
+
results: [...results, { name, ok: false, output: String(error?.stdout ?? "").trim(), error: String(error?.stderr ?? error?.message ?? error).trim() }]
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return { ok: true, results };
|
|
469
|
+
}
|
|
470
|
+
function runFormatOnNote(root, entry, content) {
|
|
471
|
+
const node = resolveNodeBinary();
|
|
472
|
+
if (node === void 0) return { code: "notes-runtime-unavailable", reason: "\u65E0\u6CD5\u5B9A\u4F4D Node \u53EF\u6267\u884C\u6587\u4EF6\u3002" };
|
|
473
|
+
const temp = path3.join(root, ".agents", "notes", `.gate-validate-${randomUUID()}`);
|
|
474
|
+
const relative = path3.join(String(entry.lifecycle ?? "proposed"), String(entry.class ?? "feature"), path3.basename(String(entry.path)));
|
|
475
|
+
try {
|
|
476
|
+
mkdirSync(path3.dirname(path3.join(temp, relative)), { recursive: true });
|
|
477
|
+
writeFileSync(path3.join(temp, relative), content);
|
|
478
|
+
execFileSync(node, [path3.join(NOTES_SKILL_DIR, "scripts", "verify-agent-note-format.ts"), "--root", temp], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
479
|
+
return void 0;
|
|
480
|
+
} catch (error) {
|
|
481
|
+
return { code: "note-format-invalid", path: entry.path, reason: String(error?.stderr ?? error?.message ?? error).trim() };
|
|
482
|
+
} finally {
|
|
483
|
+
rmSync(temp, { recursive: true, force: true });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/notes/note-files.ts
|
|
488
|
+
function splitNoteSections(content) {
|
|
489
|
+
const lines = content.replace(/\r\n?/gu, "\n").split("\n");
|
|
490
|
+
const preamble = [];
|
|
491
|
+
const sections = [];
|
|
492
|
+
let current = null;
|
|
493
|
+
let fenced = false;
|
|
494
|
+
for (const line of lines) {
|
|
495
|
+
if (/^\s*```/u.test(line)) fenced = !fenced;
|
|
496
|
+
if (!fenced && /^## /u.test(line)) {
|
|
497
|
+
current = { heading: line, body: [] };
|
|
498
|
+
sections.push(current);
|
|
499
|
+
} else if (current === null) {
|
|
500
|
+
preamble.push(line);
|
|
501
|
+
} else {
|
|
502
|
+
current.body.push(line);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return { preamble, sections };
|
|
506
|
+
}
|
|
507
|
+
function transformImplemented(content) {
|
|
508
|
+
const { preamble, sections } = splitNoteSections(content);
|
|
509
|
+
const statusIndex = preamble.findIndex((line) => /^Status:/u.test(line));
|
|
510
|
+
if (statusIndex >= 0) preamble[statusIndex] = "Status: implemented";
|
|
511
|
+
const kept = [];
|
|
512
|
+
const extras = [];
|
|
513
|
+
let hasDecision = false;
|
|
514
|
+
let consequences = null;
|
|
515
|
+
for (const section of sections) {
|
|
516
|
+
if (/^## Proposal\s*$/u.test(section.heading)) {
|
|
517
|
+
section.heading = "## Decision";
|
|
518
|
+
hasDecision = true;
|
|
519
|
+
}
|
|
520
|
+
if (/^## Plan\s*$/u.test(section.heading)) continue;
|
|
521
|
+
if (/^## Acceptance criteria\s*$/u.test(section.heading)) {
|
|
522
|
+
extras.push({ heading: "### Acceptance criteria", body: section.body.join("\n").trim() });
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (/^## Risks\s*$/u.test(section.heading)) {
|
|
526
|
+
extras.push({ heading: "### Risks", body: section.body.join("\n").trim() });
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (/^## Consequences\s*$/u.test(section.heading)) consequences = section;
|
|
530
|
+
kept.push(section);
|
|
531
|
+
}
|
|
532
|
+
if (!hasDecision) throw new Error("Note \u7F3A\u5C11 ## Proposal\uFF0C\u65E0\u6CD5\u8F6C\u6210 ## Decision");
|
|
533
|
+
const extraText = extras.filter((item) => item.body !== "").map((item) => `${item.heading}
|
|
534
|
+
|
|
535
|
+
${item.body}`).join("\n\n");
|
|
536
|
+
if (extraText !== "") {
|
|
537
|
+
if (consequences === null) {
|
|
538
|
+
consequences = { heading: "## Consequences", body: [] };
|
|
539
|
+
kept.push(consequences);
|
|
540
|
+
}
|
|
541
|
+
consequences.body.push("", extraText);
|
|
542
|
+
}
|
|
543
|
+
const out = [...preamble, ...kept.flatMap((section) => [section.heading, ...section.body])];
|
|
544
|
+
return `${out.join("\n").replace(/\s+$/u, "")}
|
|
545
|
+
`;
|
|
546
|
+
}
|
|
547
|
+
function replaceSectionBody(content, heading, body) {
|
|
548
|
+
const { preamble, sections } = splitNoteSections(content);
|
|
549
|
+
const target = sections.find((section) => section.heading === heading);
|
|
550
|
+
if (target === void 0) throw new Error(`\u7F3A\u5C11 ${heading}`);
|
|
551
|
+
target.body = ["", body];
|
|
552
|
+
return `${[...preamble, ...sections.flatMap((section) => [section.heading, ...section.body])].join("\n").replace(/\s+$/u, "")}
|
|
553
|
+
`;
|
|
554
|
+
}
|
|
555
|
+
function recoverFinalize(root, sessionId) {
|
|
556
|
+
const journal = path4.join(root, ".agents", "notes", `.gate-finalize-${sessionId}.json`);
|
|
557
|
+
if (!existsSync2(journal)) return [];
|
|
558
|
+
let data;
|
|
559
|
+
try {
|
|
560
|
+
data = JSON.parse(readFileSync(journal, "utf8"));
|
|
561
|
+
} catch {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
if (data?.owner !== sessionId) return [];
|
|
565
|
+
const recovered = [];
|
|
566
|
+
const source = path4.join(root, data.source);
|
|
567
|
+
const backup = path4.join(root, data.backup);
|
|
568
|
+
const target = path4.join(root, data.target);
|
|
569
|
+
try {
|
|
570
|
+
if (existsSync2(backup)) {
|
|
571
|
+
if (!existsSync2(source) && !existsSync2(target)) {
|
|
572
|
+
renameSync(backup, source);
|
|
573
|
+
recovered.push("restored-source");
|
|
574
|
+
} else if (!existsSync2(source) && existsSync2(target)) {
|
|
575
|
+
const check = runNotesScripts(root);
|
|
576
|
+
if (!check.ok) {
|
|
577
|
+
rmSync2(target, { force: true });
|
|
578
|
+
renameSync(backup, source);
|
|
579
|
+
recovered.push("restored-source-after-bad-target");
|
|
580
|
+
} else {
|
|
581
|
+
rmSync2(backup, { force: true });
|
|
582
|
+
recovered.push("kept-target");
|
|
583
|
+
}
|
|
584
|
+
} else if (existsSync2(source) && !existsSync2(target)) {
|
|
585
|
+
rmSync2(backup, { force: true });
|
|
586
|
+
recovered.push("removed-backup");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const temp = typeof data.temp === "string" ? path4.join(root, data.temp) : void 0;
|
|
590
|
+
if (!existsSync2(backup) && (temp === void 0 || !existsSync2(temp))) recovered.push("journal-orphan");
|
|
591
|
+
rmSync2(journal, { force: true });
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
return recovered;
|
|
595
|
+
}
|
|
596
|
+
function finalizeNote(root, entry, sessionId, decisionBody) {
|
|
597
|
+
const sourceRelative = entry.path.replaceAll("\\", "/");
|
|
598
|
+
const source = path4.join(root, sourceRelative);
|
|
599
|
+
const targetRelative = sourceRelative.replace(".agents/notes/proposed/", ".agents/notes/implemented/");
|
|
600
|
+
const target = path4.join(root, targetRelative);
|
|
601
|
+
if (existsSync2(target)) throw new Error(`\u76EE\u6807\u5DF2\u5B58\u5728\uFF1A${targetRelative}`);
|
|
602
|
+
if (pathHasSymlink(root, source) || pathHasSymlink(root, target)) throw new Error("finalize \u8DEF\u5F84\u542B symlink");
|
|
603
|
+
const content = readFileSync(source, "utf8");
|
|
604
|
+
let next = transformImplemented(content);
|
|
605
|
+
if (typeof decisionBody === "string" && decisionBody !== "") next = replaceSectionBody(next, "## Decision", decisionBody);
|
|
606
|
+
const sourceFromNotes = sourceRelative.replace(/^\.agents\/notes\//u, "");
|
|
607
|
+
const targetFromNotes = targetRelative.replace(/^\.agents\/notes\//u, "");
|
|
608
|
+
next = next.replaceAll(sourceRelative, targetRelative).replaceAll(sourceFromNotes, targetFromNotes);
|
|
609
|
+
const dir = path4.dirname(target);
|
|
610
|
+
mkdirSync2(dir, { recursive: true });
|
|
611
|
+
const journalRelative = path4.join(".agents", "notes", `.gate-finalize-${sessionId}.json`).replaceAll("\\", "/");
|
|
612
|
+
const journal = path4.join(root, journalRelative);
|
|
613
|
+
const backup = `${source}.gate-backup`;
|
|
614
|
+
const temp = path4.join(dir, `.gate-finalize-${path4.basename(target)}.tmp`);
|
|
615
|
+
writeFileSync2(journal, JSON.stringify({ owner: sessionId, source: sourceRelative, target: targetRelative, backup: path4.relative(root, backup).replaceAll("\\", "/"), temp: path4.relative(root, temp).replaceAll("\\", "/") }, null, 2));
|
|
616
|
+
try {
|
|
617
|
+
writeFileSync2(temp, next);
|
|
618
|
+
renameSync(source, backup);
|
|
619
|
+
renameSync(temp, target);
|
|
620
|
+
const check = runNotesScripts(root);
|
|
621
|
+
if (!check.ok) throw new Error(`finalize \u540E\u7684 notes \u6821\u9A8C\u5931\u8D25\uFF1A${JSON.stringify(check.results)}`);
|
|
622
|
+
rmSync2(backup, { force: true });
|
|
623
|
+
rmSync2(journal, { force: true });
|
|
624
|
+
return targetRelative.replaceAll("\\", "/");
|
|
625
|
+
} catch (error) {
|
|
626
|
+
rmSync2(target, { force: true });
|
|
627
|
+
rmSync2(temp, { force: true });
|
|
628
|
+
if (existsSync2(backup) && !existsSync2(source)) renameSync(backup, source);
|
|
629
|
+
rmSync2(journal, { force: true });
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
var NOTE_CLASSES = /* @__PURE__ */ new Set(["feature", "bug-fix", "simplification", "architecture", "process", "testing"]);
|
|
634
|
+
function noteTopic(file) {
|
|
635
|
+
return path4.basename(file).replace(/^\d{4}-\d{2}-\d{2}-/u, "").replace(/\.md$/u, "").replace(/-implemented$/u, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-|-$/gu, "");
|
|
636
|
+
}
|
|
637
|
+
function normalizeNotePaths(value) {
|
|
638
|
+
if (!Array.isArray(value)) return value;
|
|
639
|
+
return value.map((entry) => {
|
|
640
|
+
if (entry === null || typeof entry !== "object" || typeof entry.path !== "string") return entry;
|
|
641
|
+
const normalized = entry.path.replaceAll("\\", "/").replace(/^\.\//u, "");
|
|
642
|
+
if (normalized.startsWith(".agents/notes/")) return { ...entry, path: normalized };
|
|
643
|
+
const parts = normalized.split("/");
|
|
644
|
+
if (parts[0] === "proposed" || parts[0] === "implemented") return { ...entry, path: `.agents/notes/${normalized}` };
|
|
645
|
+
if (typeof entry.class === "string" && parts[0] === entry.class && typeof entry.lifecycle === "string") {
|
|
646
|
+
return { ...entry, path: `.agents/notes/${entry.lifecycle}/${normalized}` };
|
|
647
|
+
}
|
|
648
|
+
return { ...entry, path: normalized };
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
function notePathsForDecision(value, decision) {
|
|
652
|
+
return decision === "mechanical" ? value : normalizeNotePaths(value);
|
|
653
|
+
}
|
|
654
|
+
function validateNotePaths(root, value, decision) {
|
|
655
|
+
if (decision === "mechanical") {
|
|
656
|
+
if (!Array.isArray(value) || value.length !== 0) return { code: "note-paths-required", reason: "mechanical \u7684 notePaths \u5FC5\u987B\u4E3A\u7A7A\u3002" };
|
|
657
|
+
return void 0;
|
|
658
|
+
}
|
|
659
|
+
if (!Array.isArray(value) || value.length === 0) return { code: "note-paths-required", reason: "\u975E mechanical \u5DE5\u4F5C\u9879\u81F3\u5C11\u9700\u8981\u4E00\u4E2A notePath\u3002" };
|
|
660
|
+
const seenTopics = /* @__PURE__ */ new Set();
|
|
661
|
+
for (const entry of value) {
|
|
662
|
+
if (entry === null || typeof entry !== "object" || typeof entry.path !== "string" || typeof entry.class !== "string" || typeof entry.lifecycle !== "string") {
|
|
663
|
+
return { code: "invalid-note-path", reason: "notePaths[] \u9700\u8981 {path,class,lifecycle}\u3002" };
|
|
664
|
+
}
|
|
665
|
+
if (!NOTE_CLASSES.has(entry.class)) return { code: "invalid-note-class", class: entry.class };
|
|
666
|
+
if (!["proposed", "implemented"].includes(entry.lifecycle)) return { code: "invalid-note-lifecycle", lifecycle: entry.lifecycle };
|
|
667
|
+
if (decision === "new" && entry.lifecycle !== "proposed") return { code: "note-decision-mismatch", reason: "new \u53EA\u80FD proposed\u3002" };
|
|
668
|
+
if (decision === "update" && entry.lifecycle !== "proposed") return { code: "note-decision-mismatch", reason: "update \u53EA\u80FD proposed\u3002" };
|
|
669
|
+
if (decision === "in-place" && entry.lifecycle !== "implemented") return { code: "note-decision-mismatch", reason: "in-place \u53EA\u80FD implemented\u3002" };
|
|
670
|
+
const normalized = entry.path.replaceAll("\\", "/");
|
|
671
|
+
if (normalized.startsWith("/") || normalized.includes("..")) return { code: "invalid-note-path", path: entry.path };
|
|
672
|
+
const parts = normalized.split("/");
|
|
673
|
+
if (parts.length !== 5 || parts[0] !== ".agents" || parts[1] !== "notes" || parts[2] !== entry.lifecycle || parts[3] !== entry.class) {
|
|
674
|
+
return { code: "invalid-note-path", path: entry.path, reason: "\u9700\u8981 .agents/notes/<lifecycle>/<class>/yyyy-mm-dd-topic.md\u3002" };
|
|
675
|
+
}
|
|
676
|
+
const filePart = parts[4];
|
|
677
|
+
if (!/^\d{4}-\d{2}-\d{2}-.+\.md$/u.test(String(filePart))) return { code: "invalid-note-path", path: entry.path, reason: "\u6587\u4EF6\u540D\u9700\u8981 yyyy-mm-dd-topic.md\u3002" };
|
|
678
|
+
const absolute = path4.join(root, ...parts);
|
|
679
|
+
if (!isInside(root, absolute) || pathHasSymlink(root, absolute)) return { code: "invalid-note-path", path: entry.path, reason: "\u8DEF\u5F84\u8D8A\u754C\u6216\u542B symlink\u3002" };
|
|
680
|
+
if (!existsSync2(absolute) || !lstatSync2(absolute).isFile()) return { code: "note-missing", path: entry.path };
|
|
681
|
+
const content = readFileSync(absolute, "utf8");
|
|
682
|
+
const status = content.match(/^Status:\s*(proposed|implemented)\s*$/mu)?.[1];
|
|
683
|
+
if (status !== entry.lifecycle) return { code: "note-status-mismatch", path: entry.path, expected: entry.lifecycle, got: status ?? "missing" };
|
|
684
|
+
const format = runFormatOnNote(root, entry, content);
|
|
685
|
+
if (format !== void 0) return format;
|
|
686
|
+
const topic = noteTopic(String(filePart));
|
|
687
|
+
if (seenTopics.has(topic)) return { code: "note-topic-duplicate", topic, reason: "\u540C\u4E00 topic \u53EA\u80FD\u6709\u4E00\u7BC7\u3002" };
|
|
688
|
+
seenTopics.add(topic);
|
|
689
|
+
for (const other of ["proposed", "implemented", "rejected"]) {
|
|
690
|
+
const dir = path4.join(root, ".agents", "notes", other, entry.class);
|
|
691
|
+
if (!existsSync2(dir)) continue;
|
|
692
|
+
for (const name of readdirSync(dir)) {
|
|
693
|
+
if (!name.endsWith(".md") || name === path4.basename(String(filePart))) continue;
|
|
694
|
+
if (noteTopic(name) === topic) return { code: "note-topic-duplicate", topic, other: path4.join(dir, name) };
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return void 0;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/runtime/store.ts
|
|
702
|
+
function applyCommandEvent(state, name, args) {
|
|
703
|
+
const text = String(args ?? "").trim();
|
|
704
|
+
switch (name) {
|
|
705
|
+
case "gate-enter-plan":
|
|
706
|
+
return {
|
|
707
|
+
...state,
|
|
708
|
+
phase: "orchestrating",
|
|
709
|
+
node: "plan",
|
|
710
|
+
task: null,
|
|
711
|
+
work: "active",
|
|
712
|
+
blockReason: null,
|
|
713
|
+
blockedFrom: null,
|
|
714
|
+
rounds: { planReview: 0, codeReview: 0 },
|
|
715
|
+
exhausted: { plan: false, code: false },
|
|
716
|
+
planReady: false,
|
|
717
|
+
verification: null,
|
|
718
|
+
notePaths: [],
|
|
719
|
+
noteDecision: null,
|
|
720
|
+
noteSearch: null,
|
|
721
|
+
noteVerified: false,
|
|
722
|
+
noteFinalizeRecovery: []
|
|
723
|
+
};
|
|
724
|
+
case "gate-enter-implement":
|
|
725
|
+
return {
|
|
726
|
+
...state,
|
|
727
|
+
phase: "orchestrating",
|
|
728
|
+
node: "implement",
|
|
729
|
+
work: "active",
|
|
730
|
+
task: state.planReady !== true && text !== "" ? text : state.task,
|
|
731
|
+
blockReason: null,
|
|
732
|
+
blockedFrom: null,
|
|
733
|
+
verification: null,
|
|
734
|
+
noteVerified: false,
|
|
735
|
+
noteFinalizeRecovery: []
|
|
736
|
+
};
|
|
737
|
+
case "gate-fix-bug":
|
|
738
|
+
return { ...state, phase: "orchestrating", task: text === "" ? state.task ?? "\u5F53\u524D\u4E0A\u4E0B\u6587\u4E2D\u7684\u7F3A\u9677" : text, node: "fix-bug", work: "active", blockReason: null, blockedFrom: null };
|
|
739
|
+
case "gate-docs":
|
|
740
|
+
return { ...state, phase: "docs", task: text, node: null, work: "active", blockReason: null, blockedFrom: null };
|
|
741
|
+
case "gate-implement-review":
|
|
742
|
+
return { ...state, phase: "reviewing", task: text, node: "code-review-standalone", work: "active", blockReason: null, blockedFrom: null };
|
|
743
|
+
case "gate-code-verify":
|
|
744
|
+
return { ...state, phase: "reviewing", task: text, node: "verify-standalone", work: "active", blockReason: null, blockedFrom: null };
|
|
745
|
+
case "gate-propose-review":
|
|
746
|
+
return { ...state, phase: "reviewing", task: text, node: "proposal-review", work: "active", blockReason: null, blockedFrom: null };
|
|
747
|
+
case "gate-block":
|
|
748
|
+
return {
|
|
749
|
+
...state,
|
|
750
|
+
blockedFrom: { phase: state.phase, node: state.node },
|
|
751
|
+
phase: "blocked",
|
|
752
|
+
work: "blocked",
|
|
753
|
+
node: null,
|
|
754
|
+
blockReason: text === "" ? "user-blocked" : text
|
|
755
|
+
};
|
|
756
|
+
case "gate-resume":
|
|
757
|
+
return {
|
|
758
|
+
...state,
|
|
759
|
+
phase: state.blockedFrom?.phase ?? "discuss",
|
|
760
|
+
node: state.blockedFrom?.node ?? null,
|
|
761
|
+
work: "active",
|
|
762
|
+
blockReason: null,
|
|
763
|
+
blockedFrom: null
|
|
764
|
+
};
|
|
765
|
+
case "gate-return-discuss":
|
|
766
|
+
return { ...state, phase: "discuss", node: null, work: "active", blockReason: null, blockedFrom: null };
|
|
767
|
+
default:
|
|
768
|
+
return state;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function applyProjectionEvent(state, event) {
|
|
772
|
+
if (event.type === "command/run" && typeof event.data?.name === "string" && event.data.name.startsWith("gate-")) {
|
|
773
|
+
return {
|
|
774
|
+
...state,
|
|
775
|
+
pending: { ...state.pending ?? {}, [event.data.commandId]: { name: event.data.name, args: event.data.args ?? "" } }
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
if (event.type === "command/done") {
|
|
779
|
+
const pending = state.pending?.[event.data?.commandId];
|
|
780
|
+
if (pending === void 0) return state;
|
|
781
|
+
const next = { ...state, pending: { ...state.pending } };
|
|
782
|
+
delete next.pending[event.data.commandId];
|
|
783
|
+
if (event.data.kind !== "success") return next;
|
|
784
|
+
return applyCommandEvent(next, pending.name, pending.args);
|
|
785
|
+
}
|
|
786
|
+
if (event.type === "tool/result") {
|
|
787
|
+
const snapshot = event.data?.meta?.gate?.state;
|
|
788
|
+
if (snapshot !== void 0) return normalizeState(snapshot);
|
|
789
|
+
}
|
|
790
|
+
return state;
|
|
791
|
+
}
|
|
792
|
+
function createStateRuntime(ctx) {
|
|
793
|
+
let handle;
|
|
794
|
+
ctx.inject(["sessionProjections"], async (scope) => {
|
|
795
|
+
try {
|
|
796
|
+
handle = await scope.sessionProjections.register({
|
|
797
|
+
key: "gate",
|
|
798
|
+
stateSchema: { parse: normalizeState },
|
|
799
|
+
init: () => defaultState(),
|
|
800
|
+
apply: applyProjectionEvent,
|
|
801
|
+
stateVersion: 1
|
|
802
|
+
});
|
|
803
|
+
} catch {
|
|
804
|
+
handle = void 0;
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
return {
|
|
808
|
+
read(root, agent) {
|
|
809
|
+
if (handle !== void 0 && agent?.session !== void 0) {
|
|
810
|
+
try {
|
|
811
|
+
return normalizeState(handle.stateOf(agent.session));
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return loadState(root, agent);
|
|
816
|
+
},
|
|
817
|
+
write(root, agent, state) {
|
|
818
|
+
if (handle !== void 0) return state;
|
|
819
|
+
return saveState(root, agent, state);
|
|
820
|
+
},
|
|
821
|
+
get source() {
|
|
822
|
+
return handle === void 0 ? "memory" : "projection";
|
|
823
|
+
},
|
|
824
|
+
get projection() {
|
|
825
|
+
return handle;
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/runtime/gate.ts
|
|
831
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
832
|
+
|
|
833
|
+
// src/runtime/session.ts
|
|
834
|
+
import path5 from "node:path";
|
|
835
|
+
function workspaceBoundary(ctx, agent) {
|
|
836
|
+
try {
|
|
837
|
+
const policy = ctx.get("sandboxPolicy");
|
|
838
|
+
const resolved = policy?.resolve?.({ session: agent?.session });
|
|
839
|
+
if (resolved?.workspaceRoot !== void 0) return resolved.workspaceRoot;
|
|
840
|
+
} catch {
|
|
841
|
+
}
|
|
842
|
+
return agent?.session?.header?.cwd;
|
|
843
|
+
}
|
|
844
|
+
function resolveGate(ctx, agent) {
|
|
845
|
+
const workspace = workspaceBoundary(ctx, agent);
|
|
846
|
+
const root = workspace === void 0 ? void 0 : path5.resolve(workspace);
|
|
847
|
+
if (root === void 0) return { workspace, root: void 0 };
|
|
848
|
+
return { workspace: root, root };
|
|
849
|
+
}
|
|
850
|
+
function sessionCwd(ctx, agent, fallback) {
|
|
851
|
+
try {
|
|
852
|
+
const cwd = ctx.get("workingDirectory")?.get?.(agent?.session);
|
|
853
|
+
if (typeof cwd === "string" && cwd !== "") return cwd;
|
|
854
|
+
} catch {
|
|
855
|
+
}
|
|
856
|
+
return fallback;
|
|
857
|
+
}
|
|
858
|
+
function rootAgentOf(ctx, agent) {
|
|
859
|
+
const header = agent?.session?.header;
|
|
860
|
+
const delegated = header?.origin === "subagent" && typeof header.parentSession === "string";
|
|
861
|
+
try {
|
|
862
|
+
const root = ctx.get("subagents")?.rootOf?.(agent);
|
|
863
|
+
if (root !== void 0 && root.id !== agent?.id) return root;
|
|
864
|
+
} catch {
|
|
865
|
+
}
|
|
866
|
+
if (delegated) {
|
|
867
|
+
try {
|
|
868
|
+
const live = ctx.get("agents")?.get?.(header.parentSession);
|
|
869
|
+
if (live !== void 0) return live;
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
return { id: header.parentSession };
|
|
873
|
+
}
|
|
874
|
+
return agent;
|
|
875
|
+
}
|
|
876
|
+
function effectiveState(ctx, agent, gate, store) {
|
|
877
|
+
const root = rootAgentOf(ctx, agent);
|
|
878
|
+
return { root, state: store.read(gate.root, root), worker: root?.id !== agent?.id };
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/runtime/gate.ts
|
|
882
|
+
function isMechanical(task) {
|
|
883
|
+
return /^mechanical:/iu.test(String(task ?? "").trim());
|
|
884
|
+
}
|
|
885
|
+
function enterPhase(ctx, agent, node, reviewLimit, store, current) {
|
|
886
|
+
const gate = resolveGate(ctx, agent);
|
|
887
|
+
const state = current ?? store.read(gate.root, agent);
|
|
888
|
+
if (state.phase !== "discuss" && state.phase !== "complete") return { kind: "error", text: `\u5F53\u524D phase \u662F ${state.phase}` };
|
|
889
|
+
if (state.work === "blocked" || state.blockReason !== null) {
|
|
890
|
+
return { kind: "error", text: `\u5F53\u524D\u88AB blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
|
|
891
|
+
}
|
|
892
|
+
if (node === "plan") {
|
|
893
|
+
const notes = resolveNotesSkill();
|
|
894
|
+
if (notes === void 0) return { kind: "error", code: "notes-builtin-missing", text: "\u5185\u7F6E write-agent-notes \u7F3A\u5931\uFF1B\u91CD\u88C5\u63D2\u4EF6\u3002" };
|
|
895
|
+
}
|
|
896
|
+
if (node === "implement") {
|
|
897
|
+
if (typeof state.task !== "string" || state.task === "") return { kind: "error", text: "\u6CA1\u6709\u53EF\u5B9E\u65BD\u7684\u5185\u5BB9\uFF1A\u5148\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u8981\u6539\u4EC0\u4E48" };
|
|
898
|
+
if (state.exhausted?.plan === true || state.exhausted?.code === true) {
|
|
899
|
+
return { kind: "error", text: "review \u8F6E\u6B21\u5DF2\u8017\u5C3D\uFF1B\u9700\u8981\u7528\u6237\u51B3\u5B9A\u540E\u91CD\u65B0 /gate-enter-plan" };
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
state.phase = "orchestrating";
|
|
903
|
+
state.node = node;
|
|
904
|
+
state.work = "active";
|
|
905
|
+
state.blockReason = null;
|
|
906
|
+
state.blockedFrom = null;
|
|
907
|
+
if (node === "plan") {
|
|
908
|
+
state.rounds = { planReview: 0, codeReview: 0 };
|
|
909
|
+
state.exhausted = { plan: false, code: false };
|
|
910
|
+
state.task = null;
|
|
911
|
+
state.planReady = false;
|
|
912
|
+
state.verification = null;
|
|
913
|
+
state.notePaths = [];
|
|
914
|
+
state.noteDecision = null;
|
|
915
|
+
state.noteSearch = null;
|
|
916
|
+
state.noteVerified = false;
|
|
917
|
+
state.noteFinalized = false;
|
|
918
|
+
state.noteFinalizeRecovery = [];
|
|
919
|
+
}
|
|
920
|
+
if (node === "implement") {
|
|
921
|
+
state.verification = null;
|
|
922
|
+
state.noteVerified = false;
|
|
923
|
+
state.noteFinalized = false;
|
|
924
|
+
state.noteFinalizeRecovery = [];
|
|
925
|
+
}
|
|
926
|
+
store.write(gate.root, agent, state);
|
|
927
|
+
return { gate, state, reviewLimit };
|
|
928
|
+
}
|
|
929
|
+
function wakeGateAgent(invocation, text, command) {
|
|
930
|
+
const followup = invocation?.agent?.followup;
|
|
931
|
+
if (typeof followup !== "function") return;
|
|
932
|
+
try {
|
|
933
|
+
followup.call(invocation.agent, {
|
|
934
|
+
id: randomUUID2(),
|
|
935
|
+
role: "user",
|
|
936
|
+
content: [{ type: "text", text }],
|
|
937
|
+
source: { kind: "gate-command", command }
|
|
938
|
+
});
|
|
939
|
+
} catch {
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function makeWake(reviewLimit) {
|
|
943
|
+
return (invocation, state, text, command) => {
|
|
944
|
+
const procedure = procedureFor(state, { reviewLimit });
|
|
945
|
+
wakeGateAgent(invocation, `${procedureBlock(procedure)}
|
|
946
|
+
|
|
947
|
+
${text}`, command);
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/runtime/workers.ts
|
|
952
|
+
function workerProtocol(node) {
|
|
953
|
+
return [
|
|
954
|
+
"## Gate worker",
|
|
955
|
+
"\u4F60\u662F gate \u4E3A\u8BE5\u8282\u70B9\u6D3E\u53D1\u7684 worker\u3002\u53EA\u6267\u884C\u8BE5\u8282\u70B9\uFF1B\u4E0D\u8981\u8C03\u7528 gate_record\uFF0C\u4E0D\u8981\u63A8\u8FDB gate\u3002",
|
|
956
|
+
node === "verify-standalone" ? "\u52A0\u8F7D\u4F7F\u7528 gate-user-verify \u5B8C\u6210\u9A8C\u6536\uFF0C\u5E76\u6309\u8BE5\u6280\u80FD Step 6 \u6C47\u62A5\u3002\u4EFB\u52A1\u4E0E\u6C47\u62A5\u6A21\u677F\u89C1\u8BE5\u6280\u80FD assets/worker-briefs/gate-code-verify.md\u3002\u4E0D\u4FEE\u6539\u4EA7\u54C1\u4EE3\u7801\u3001\u6D4B\u8BD5\u3001\u9700\u6C42\u6216\u8BBE\u8BA1\uFF1B\u53EF\u4EE5\u8FD0\u884C\u6D4B\u8BD5/\u547D\u4EE4\u4F5C\u4E3A\u8BC1\u636E\uFF0C\u4F46\u4E0D\u5F97\u4FEE\u6539\u3001\u653E\u5BBD\u6216\u91CD\u5199\u6D4B\u8BD5\uFF1B\u4E0D\u8981\u81EA\u884C\u63D0\u4EA4\u3002" : node === "fix-bug" ? "\u4E3B\u4EE3\u7406\u5DF2\u8BFB\u53D6 gate-fix-bug\uFF1B\u4F60\u6267\u884C gate-fix-bug \u7684 Process 1-9\uFF08\u590D\u73B0\u3001\u56DE\u5F52\u8986\u76D6\u3001\u6839\u56E0\u3001\u6700\u5C0F\u4FEE\u590D\u3001\u6D4B\u8BD5\uFF09\uFF0C\u8FD4\u56DE\u4FEE\u590D receipt\uFF1B\u4E0D\u8981\u81EA\u884C\u5B8C\u6210 Step 10-12\uFF0C\u4E0D\u8981\u63D0\u4EA4\u3002\u4E0D\u6539\u4EA7\u54C1\u5408\u540C\u3001\u4E0D\u6269\u5927\u8303\u56F4\u3002" : "\u4E3B\u4EE3\u7406\u5DF2\u7ECF\u52A0\u8F7D\u6240\u9700 gate \u6280\u80FD\uFF0C\u5E76\u628A\u5177\u4F53\u4EFB\u52A1\u4E0E\u76F8\u5173\u65B9\u6CD5\u8981\u6C42\u4EA4\u7ED9\u4F60\u3002\u4E0D\u8981\u81EA\u5DF1\u52A0\u8F7D gate \u6280\u80FD\u3002",
|
|
957
|
+
node === "plan" || node === "plan-revise" ? "\u7528 gate_note_write \u5728 .agents/notes/proposed \u4E0B\u521B\u5EFA\u6216\u66F4\u65B0 proposed Agent Note\uFF1B\u539F\u5730\u540C\u6B65\u65E2\u6709 implemented Note \u65F6\u8DEF\u5F84\u5199 implemented/<class>/...\uFF1Bplanning \u4E2D\u901A\u7528 write/edit/shell \u88AB\u9650\u5236\u3002" : ""
|
|
958
|
+
].filter(Boolean).join("\n\n");
|
|
959
|
+
}
|
|
960
|
+
var REVIEW_NODES = /* @__PURE__ */ new Set(["plan-review", "proposal-review", "code-review", "code-review-standalone"]);
|
|
961
|
+
function reviewAxesError(args) {
|
|
962
|
+
if (!REVIEW_NODES.has(args.node)) return void 0;
|
|
963
|
+
const axes = args.reviewAxes;
|
|
964
|
+
const required = args.node === "plan-review" || args.node === "proposal-review" ? ["standards", "spec", "architecture"] : ["standards", "spec"];
|
|
965
|
+
if (axes === null || typeof axes !== "object" || Array.isArray(axes)) return { code: "review-axes-required", reason: `review \u8282\u70B9\u9700\u8981 ${required.join("\u3001")} \u8F74\u7ED3\u679C\u3002` };
|
|
966
|
+
for (const axis of required) {
|
|
967
|
+
const value = axes[axis];
|
|
968
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || !Array.isArray(value.findings)) {
|
|
969
|
+
return { code: "review-axes-required", reason: `${axis} \u8F74\u9700\u8981 findings \u6570\u7EC4\u3002` };
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return void 0;
|
|
973
|
+
}
|
|
974
|
+
async function workerEvidenceError(ctx, root, args) {
|
|
975
|
+
if (!WORKER_NODES.has(args.node)) return void 0;
|
|
976
|
+
const ids = Array.isArray(args.workerSessionIds) ? [...new Set(args.workerSessionIds.filter((value) => typeof value === "string" && value !== ""))] : [];
|
|
977
|
+
if (ids.length === 0) return { code: "worker-required", reason: "gate_record \u9700\u8981 workerSessionIds\uFF1B\u6CA1\u6709 worker \u4E0D\u80FD\u63A8\u8FDB\u3002" };
|
|
978
|
+
const service = ctx.get("subagents");
|
|
979
|
+
if (service === void 0 || typeof service.listDescendants !== "function") {
|
|
980
|
+
return { code: "worker-evidence-unavailable", reason: "subagents.listDescendants \u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u9A8C\u8BC1 worker\u3002" };
|
|
981
|
+
}
|
|
982
|
+
let entries;
|
|
983
|
+
try {
|
|
984
|
+
entries = await service.listDescendants(root.id);
|
|
985
|
+
} catch {
|
|
986
|
+
return { code: "worker-evidence-unavailable", reason: "\u8BFB\u53D6 subagent tree \u5931\u8D25\uFF0C\u65E0\u6CD5\u9A8C\u8BC1 worker\u3002" };
|
|
987
|
+
}
|
|
988
|
+
const children = Array.isArray(entries) ? entries.filter((entry) => entry?.kind === "child") : [];
|
|
989
|
+
for (const id of ids) {
|
|
990
|
+
const row = children.find((entry) => String(entry.id) === String(id));
|
|
991
|
+
if (row === void 0) return { code: "worker-not-descendant", workerSessionId: id, reason: "workerSessionId \u4E0D\u5728\u5F53\u524D root \u7684 subagent tree \u4E2D\u3002" };
|
|
992
|
+
}
|
|
993
|
+
return void 0;
|
|
994
|
+
}
|
|
995
|
+
async function bindToolResults(ctx, workerSessionIds, toolResults) {
|
|
996
|
+
const query = ctx.get("sessionQuery");
|
|
997
|
+
if (query === void 0 || typeof query.readSession !== "function") {
|
|
998
|
+
return { code: "verification-required", reason: "sessionQuery \u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u7ED1\u5B9A toolResults\u3002" };
|
|
999
|
+
}
|
|
1000
|
+
const found = /* @__PURE__ */ new Set();
|
|
1001
|
+
for (const sessionId of workerSessionIds) {
|
|
1002
|
+
try {
|
|
1003
|
+
const snapshot = await query.readSession(sessionId);
|
|
1004
|
+
for (const event of snapshot?.events ?? []) {
|
|
1005
|
+
if (event?.type !== "tool/result") continue;
|
|
1006
|
+
const callId = event.data?.message?.toolCallId ?? event.data?.callId;
|
|
1007
|
+
if (callId !== void 0) found.add(String(callId));
|
|
1008
|
+
}
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const missing = toolResults.filter((id) => !found.has(String(id)));
|
|
1013
|
+
if (missing.length > 0) {
|
|
1014
|
+
return { code: "verification-required", unboundToolResults: missing, reason: "toolResults \u672A\u5728 worker session log \u4E2D\u627E\u5230\u3002" };
|
|
1015
|
+
}
|
|
1016
|
+
return void 0;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/runtime/prompt.ts
|
|
1020
|
+
function installSystemPrompt(ctx, deps) {
|
|
1021
|
+
ctx.inject(["systemPrompt"], (scope) => {
|
|
1022
|
+
scope.systemPrompt.section({
|
|
1023
|
+
name: "gate:protocol",
|
|
1024
|
+
order: scope.systemPrompt.getSectionOrder("PLAN_POLICY") + 1,
|
|
1025
|
+
text: (context) => {
|
|
1026
|
+
const gate = resolveGate(ctx, context.agent);
|
|
1027
|
+
const effective = effectiveState(ctx, context.agent, gate, deps.store);
|
|
1028
|
+
if (effective.worker) return ["## Gate", "Gate worker\uFF1A\u53EA\u6267\u884C\u5F53\u524D node\uFF0C\u4E0D\u63A8\u8FDB phase\u3002", workerProtocol(effective.state.node ?? "")].filter(Boolean).join("\n\n");
|
|
1029
|
+
return GATE_METHODOLOGY;
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/classify.ts
|
|
1036
|
+
import path6 from "node:path";
|
|
1037
|
+
function classifyPath(root, value, cwd = root) {
|
|
1038
|
+
const absolute = path6.resolve(cwd, String(value ?? ""));
|
|
1039
|
+
const relative = path6.relative(root, absolute).replaceAll("\\", "/");
|
|
1040
|
+
const outside = relative === ".." || relative.startsWith("../") || path6.isAbsolute(relative);
|
|
1041
|
+
return { absolute, relative, outside };
|
|
1042
|
+
}
|
|
1043
|
+
var WRITE_VERB = /(?:^|[\s;(])(Set-Content|Add-Content|Out-File|New-Item|Remove-Item|Move-Item|Copy-Item|Rename-Item|Clear-Content|Set-Item|mkdir|md|touch|tee|rm|mv|cp|sed\s+-i)\b/iu;
|
|
1044
|
+
function pathArgs(text) {
|
|
1045
|
+
const beforePipe = String(text).split("|")[0] ?? "";
|
|
1046
|
+
return [...beforePipe.matchAll(/(?:'([^']+)'|"([^"]+)"|(\S+))/gu)].map((match) => match[1] ?? match[2] ?? match[3]).filter((value) => value !== void 0 && !value.startsWith("-"));
|
|
1047
|
+
}
|
|
1048
|
+
function assignments(text) {
|
|
1049
|
+
const map = /* @__PURE__ */ new Map();
|
|
1050
|
+
for (const match of String(text).matchAll(/(?:^|[;\n])\s*\$?([A-Za-z_]\w*)\s*=\s*(?:'([^']*)'|"([^"]*)"|(\S+))/gu)) {
|
|
1051
|
+
const key = match[1];
|
|
1052
|
+
if (key !== void 0) map.set(key, match[2] ?? match[3] ?? match[4] ?? "");
|
|
1053
|
+
}
|
|
1054
|
+
return map;
|
|
1055
|
+
}
|
|
1056
|
+
function resolveToken(token, map) {
|
|
1057
|
+
const variable = /^\$([A-Za-z_]\w*)$/u.exec(token);
|
|
1058
|
+
if (variable === null) return token;
|
|
1059
|
+
const key = variable[1];
|
|
1060
|
+
return (key === void 0 ? token : map.get(key)) ?? token;
|
|
1061
|
+
}
|
|
1062
|
+
function redirectionTargets(text) {
|
|
1063
|
+
return [...String(text).matchAll(/(?:^|\s)>{1,2}\s*(?:'([^']+)'|"([^"]+)"|([^\s;&|]+))/gu)].map((match) => match[1] ?? match[2] ?? match[3]).filter((value) => value !== void 0);
|
|
1064
|
+
}
|
|
1065
|
+
function pipeTargets(text) {
|
|
1066
|
+
const targets = [];
|
|
1067
|
+
for (const part of String(text).split("|").slice(1)) {
|
|
1068
|
+
const body = part.replace(/^\s*(?:tee|Set-Content|Out-File|Add-Content)\b/iu, "");
|
|
1069
|
+
if (body === part) continue;
|
|
1070
|
+
for (const token of pathArgs(body)) targets.push(token);
|
|
1071
|
+
}
|
|
1072
|
+
return targets;
|
|
1073
|
+
}
|
|
1074
|
+
function addTarget(targets, token, map) {
|
|
1075
|
+
if (token === void 0 || token === "") return;
|
|
1076
|
+
if (token.startsWith("$")) {
|
|
1077
|
+
const resolved = resolveToken(token, map);
|
|
1078
|
+
if (resolved !== token) targets.push(resolved);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
targets.push(token);
|
|
1082
|
+
}
|
|
1083
|
+
function segmentAccess(text, root, cwd, map) {
|
|
1084
|
+
if (/\bgit\s+(?:status|add|commit|push)\b/iu.test(text)) {
|
|
1085
|
+
return { write: false, outsideTargets: [] };
|
|
1086
|
+
}
|
|
1087
|
+
if (/\bgit\s+(?:checkout|reset|clean)\b/iu.test(text)) {
|
|
1088
|
+
return { write: true, outsideTargets: [] };
|
|
1089
|
+
}
|
|
1090
|
+
const raw = [];
|
|
1091
|
+
const verb = WRITE_VERB.exec(text);
|
|
1092
|
+
if (verb !== null) {
|
|
1093
|
+
const tail = text.slice(verb.index + verb[0].length);
|
|
1094
|
+
for (const token of pathArgs(tail)) addTarget(raw, token, map);
|
|
1095
|
+
}
|
|
1096
|
+
for (const token of redirectionTargets(text)) addTarget(raw, token, map);
|
|
1097
|
+
for (const token of pipeTargets(text)) addTarget(raw, token, map);
|
|
1098
|
+
const outsideTargets = [];
|
|
1099
|
+
for (const value of raw) {
|
|
1100
|
+
const target = classifyPath(root, path6.resolve(cwd, value));
|
|
1101
|
+
if (target.outside) outsideTargets.push(target.absolute);
|
|
1102
|
+
}
|
|
1103
|
+
return { write: raw.length > 0 || WRITE_VERB.test(text), outsideTargets };
|
|
1104
|
+
}
|
|
1105
|
+
function segments(text) {
|
|
1106
|
+
return String(text).split(/&&|\|\||;|\r?\n/u).map((part) => part.trim()).filter(Boolean);
|
|
1107
|
+
}
|
|
1108
|
+
function shellAccess(command, root, cwd = root) {
|
|
1109
|
+
const map = assignments(command);
|
|
1110
|
+
const parts = segments(command).map((part) => segmentAccess(part, root, cwd, map)).filter((item) => item.write);
|
|
1111
|
+
return {
|
|
1112
|
+
write: parts.length > 0,
|
|
1113
|
+
outsideTargets: parts.flatMap((item) => item.outsideTargets ?? [])
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/runtime/guards.ts
|
|
1118
|
+
import path7 from "node:path";
|
|
1119
|
+
function restrictionFilter(state, worker) {
|
|
1120
|
+
const writeTools = ["write", "edit", "apply_patch", "pwsh", "bash"];
|
|
1121
|
+
if (worker) {
|
|
1122
|
+
if (state.node === "implement" || state.node === "implement-revise" || state.node === "fix-bug") return void 0;
|
|
1123
|
+
return { deny: writeTools };
|
|
1124
|
+
}
|
|
1125
|
+
return state.phase === "blocked" ? { deny: ["write", "edit", "apply_patch"] } : void 0;
|
|
1126
|
+
}
|
|
1127
|
+
var restrictionState = /* @__PURE__ */ new WeakMap();
|
|
1128
|
+
function applyRestrictions(ctx, agent, store) {
|
|
1129
|
+
const tools = agent?.ctx?.tools;
|
|
1130
|
+
if (typeof tools?.restrict !== "function") return;
|
|
1131
|
+
try {
|
|
1132
|
+
const gate = resolveGate(ctx, agent);
|
|
1133
|
+
const previous = restrictionState.get(agent);
|
|
1134
|
+
const effective = effectiveState(ctx, agent, gate, store);
|
|
1135
|
+
const filter = restrictionFilter(effective.state, effective.worker);
|
|
1136
|
+
const key = `${effective.state.phase}/${effective.state.node ?? ""}/${effective.worker ? "worker" : "root"}/${JSON.stringify(filter ?? null)}`;
|
|
1137
|
+
if (previous?.key === key) return;
|
|
1138
|
+
previous?.disposer?.();
|
|
1139
|
+
restrictionState.delete(agent);
|
|
1140
|
+
if (filter !== void 0) {
|
|
1141
|
+
const names = filter.deny.filter((name) => {
|
|
1142
|
+
try {
|
|
1143
|
+
return typeof tools.get !== "function" || tools.get(name) !== void 0;
|
|
1144
|
+
} catch {
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
if (names.length > 0) {
|
|
1149
|
+
let disposer;
|
|
1150
|
+
try {
|
|
1151
|
+
disposer = tools.restrict({ deny: names });
|
|
1152
|
+
} catch {
|
|
1153
|
+
const disposers = [];
|
|
1154
|
+
for (const name of names) {
|
|
1155
|
+
try {
|
|
1156
|
+
disposers.push(tools.restrict({ deny: [name] }));
|
|
1157
|
+
} catch {
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (disposers.length > 0) disposer = () => {
|
|
1161
|
+
for (const release of disposers) release();
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
if (disposer !== void 0) restrictionState.set(agent, { key, disposer });
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
} catch {
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
function workerWriteAllowed(state, target) {
|
|
1171
|
+
if (state.phase !== "orchestrating") return false;
|
|
1172
|
+
if (state.node === "plan" || state.node === "plan-revise") return false;
|
|
1173
|
+
if (state.node === "implement" || state.node === "implement-revise" || state.node === "fix-bug") return true;
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
function workerShellAllowed(state, access) {
|
|
1177
|
+
if (state.phase !== "orchestrating") return false;
|
|
1178
|
+
return state.node === "implement" || state.node === "implement-revise" || state.node === "fix-bug";
|
|
1179
|
+
}
|
|
1180
|
+
function targetList(name, args) {
|
|
1181
|
+
if (name !== "apply_patch") return [args?.file_path];
|
|
1182
|
+
return [...String(args?.input ?? "").matchAll(/^\*\*\* (?:Add File|Update File|Delete File|Move to):\s*(.+)$/gmu)].map((match) => String(match[1] ?? "").trim());
|
|
1183
|
+
}
|
|
1184
|
+
var GATE_FROZEN_REASON = "Gate \u5DF2\u51BB\u7ED3\uFF1A\u5148 /gate-resume \u6216 /gate-return-discuss";
|
|
1185
|
+
function gateDenyReason(ctx, exec, store) {
|
|
1186
|
+
if (exec.name === "gate_record") {
|
|
1187
|
+
const root = rootAgentOf(ctx, exec.agent);
|
|
1188
|
+
return root?.id === exec.agent?.id ? void 0 : "gate_record \u53EA\u80FD\u7531 root agent \u8C03\u7528";
|
|
1189
|
+
}
|
|
1190
|
+
if (["subagent", "subagent_fork", "workflow"].includes(exec.name)) {
|
|
1191
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
1192
|
+
const state = store.read(gate.root, exec.agent);
|
|
1193
|
+
if (state.phase === "blocked") return GATE_FROZEN_REASON;
|
|
1194
|
+
if (state.phase === "discuss") return "discuss \u4E0D\u6D3E worker";
|
|
1195
|
+
if (state.node === "note-lifecycle") return "note-lifecycle \u53EA\u505A Note \u6536\u5C3E\uFF0C\u4E0D\u6D3E worker";
|
|
1196
|
+
return void 0;
|
|
1197
|
+
}
|
|
1198
|
+
if (exec.name === "write" || exec.name === "edit" || exec.name === "apply_patch") {
|
|
1199
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
1200
|
+
const effective = effectiveState(ctx, exec.agent, gate, store);
|
|
1201
|
+
const state = effective.state;
|
|
1202
|
+
const cwd = sessionCwd(ctx, exec.agent, gate.workspace);
|
|
1203
|
+
const targets = targetList(exec.name, exec.arguments).filter((value) => typeof value === "string" && value !== "");
|
|
1204
|
+
if (targets.length === 0) return void 0;
|
|
1205
|
+
for (const value of targets) {
|
|
1206
|
+
const target = classifyPath(gate.workspace, value, cwd);
|
|
1207
|
+
if (target.outside) return "\u5DE5\u4F5C\u533A\u5916\u5199\u5165\u88AB\u62D2\u7EDD";
|
|
1208
|
+
if (effective.worker) {
|
|
1209
|
+
if (!workerWriteAllowed(state, target)) return `worker ${state.node ?? "unknown"} \u4E0D\u5141\u8BB8\u5199\u8BE5\u76EE\u6807`;
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (state.phase === "blocked") return GATE_FROZEN_REASON;
|
|
1213
|
+
}
|
|
1214
|
+
return void 0;
|
|
1215
|
+
}
|
|
1216
|
+
if (exec.name === "pwsh" || exec.name === "bash") {
|
|
1217
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
1218
|
+
const effective = effectiveState(ctx, exec.agent, gate, store);
|
|
1219
|
+
const state = effective.state;
|
|
1220
|
+
const base = sessionCwd(ctx, exec.agent, gate.workspace);
|
|
1221
|
+
const requested = exec.arguments?.workdir;
|
|
1222
|
+
const cwd = typeof requested === "string" && requested !== "" ? path7.resolve(base, requested) : base;
|
|
1223
|
+
const access = shellAccess(exec.arguments?.command ?? "", gate.workspace, cwd);
|
|
1224
|
+
if (/\bgit(?:\.exe)?\b[^\n;&|]*\scommit\b/iu.test(String(exec.arguments?.command ?? "")) && effective.worker && !["implement", "implement-revise", "fix-bug"].includes(state.node ?? "")) {
|
|
1225
|
+
return `worker ${state.node ?? "unknown"} \u4E0D\u5141\u8BB8 git commit`;
|
|
1226
|
+
}
|
|
1227
|
+
if (!access.write) return void 0;
|
|
1228
|
+
if (access.outsideTargets.length > 0) return "\u5DE5\u4F5C\u533A\u5916\u5199\u5165\u88AB\u62D2\u7EDD";
|
|
1229
|
+
if (effective.worker) return workerShellAllowed(state, access) ? void 0 : `worker ${state.node ?? "unknown"} \u4E0D\u5141\u8BB8\u5199\u8BE5\u76EE\u6807`;
|
|
1230
|
+
if (state.phase === "blocked") return GATE_FROZEN_REASON;
|
|
1231
|
+
return void 0;
|
|
1232
|
+
}
|
|
1233
|
+
return void 0;
|
|
1234
|
+
}
|
|
1235
|
+
function installGuard(ctx, deps) {
|
|
1236
|
+
ctx.tools.guard((exec) => gateDenyReason(ctx, exec, deps.store));
|
|
1237
|
+
}
|
|
1238
|
+
function installRestrictions(ctx, deps) {
|
|
1239
|
+
ctx.on("agent/pre-step", async (payload, next) => {
|
|
1240
|
+
applyRestrictions(ctx, payload.agent, deps.store);
|
|
1241
|
+
return next();
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/commands.ts
|
|
1246
|
+
import path9 from "node:path";
|
|
1247
|
+
|
|
1248
|
+
// src/notes/provision.ts
|
|
1249
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1250
|
+
import path8 from "node:path";
|
|
1251
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1252
|
+
import { lstatSync as lstatSync3 } from "node:fs";
|
|
1253
|
+
var NOTES_BLOCK_START = "<!-- gate-notes:start -->";
|
|
1254
|
+
var NOTES_BLOCK_END = "<!-- gate-notes:end -->";
|
|
1255
|
+
var NOTES_AGENTS_TEXT = `${NOTES_BLOCK_START}
|
|
1256
|
+
## \u91CD\u8981\u6539\u52A8\u5FC5\u987B\u7559\u7B14\u8BB0
|
|
1257
|
+
|
|
1258
|
+
1. \u975E\u5E73\u51E1\u6539\u52A8\uFF08\u884C\u4E3A\u3001\u67B6\u6784\u3001\u8DE8\u6587\u4EF6\u5951\u7EA6\u3001\u6D41\u7A0B/\u5DE5\u5177\u94FE\u3001\u6D4B\u8BD5\u7B56\u7565\u3001\u843D\u76D8/\u7F51\u7EDC/\u914D\u7F6E\u683C\u5F0F\uFF09\u5728\u52A8\u624B\u524D\u4F7F\u7528\u5185\u7F6E\u6280\u80FD \`gate-write-agent-notes\`\uFF0C\u6309 \xA73 \u68C0\u7D22\u3001\xA74 \u5224\u5B9A\u5F52\u5C5E\u4E0E class\uFF1B\u673A\u68B0\u6027\u5C0F\u6539\u76F4\u63A5\u63D0\u4EA4\u3002
|
|
1259
|
+
2. \u65E2\u6709\u51B3\u5B9A\u672A\u53D8\u65F6\u539F\u5730\u540C\u6B65 \`implemented/\` Note\uFF1B\u65B0\u60F3\u6CD5\u5148\u653E \`proposed/\`\uFF1B\u843D\u5730\u968F\u540C\u4EE3\u7801\u8F6C \`implemented/\`\u3002
|
|
1260
|
+
3. \u88AB\u653E\u5F03\u7684\u65B9\u6848\u5148\u5199\u6700\u5F3A\u7406\u7531\u518D\u5426\u51B3\u3002
|
|
1261
|
+
4. \u63D0\u4EA4\u524D\u8FD0\u884C \`npm run verify-notes\` \u68C0\u67E5\uFF1Bgate \u4F1A\u8BDD\u5185\u4E5F\u53EF\u7528 \`/gate-note-verify\`\u3002gate \u7684 lifecycle \u7531 \`gate_note_finalize\` \u2192 \`gate_note_verify\` \u2192 \`gate_record({node:'note-lifecycle'})\` \u5B8C\u6210\u3002
|
|
1262
|
+
${NOTES_BLOCK_END}`;
|
|
1263
|
+
function provisionNotes(root) {
|
|
1264
|
+
const notes = path8.join(root, ".agents", "notes");
|
|
1265
|
+
if (pathHasSymlink(root, notes)) throw new Error("notes \u8DEF\u5F84\u5305\u542B symlink");
|
|
1266
|
+
for (const lifecycle of ["proposed", "implemented", "rejected", "archived"]) {
|
|
1267
|
+
const dir = path8.join(notes, lifecycle);
|
|
1268
|
+
if (pathHasSymlink(root, dir)) throw new Error(`${lifecycle} \u8DEF\u5F84\u5305\u542B symlink`);
|
|
1269
|
+
mkdirSync3(dir, { recursive: true });
|
|
1270
|
+
const keep = path8.join(dir, ".gitkeep");
|
|
1271
|
+
if (!existsSync3(keep)) writeFileSync3(keep, "");
|
|
1272
|
+
}
|
|
1273
|
+
const agents = path8.join(root, "AGENTS.md");
|
|
1274
|
+
if (existsSync3(agents) && lstatSync3(agents).isSymbolicLink()) throw new Error("AGENTS.md \u662F symlink");
|
|
1275
|
+
const existing = existsSync3(agents) ? readFileSync2(agents, "utf8") : "";
|
|
1276
|
+
const block = /<!-- gate-notes:start -->[\s\S]*?<!-- gate-notes:end -->/u;
|
|
1277
|
+
const next = block.test(existing) ? existing.replace(block, NOTES_AGENTS_TEXT) : `${existing}${existing === "" || existing.endsWith("\n") ? "" : "\n"}
|
|
1278
|
+
${NOTES_AGENTS_TEXT}
|
|
1279
|
+
`;
|
|
1280
|
+
writeFileSync3(agents, next);
|
|
1281
|
+
return { notes: path8.relative(root, notes).replaceAll("\\", "/"), agents: path8.relative(root, agents).replaceAll("\\", "/") };
|
|
1282
|
+
}
|
|
1283
|
+
function provisionProjectScripts(root) {
|
|
1284
|
+
const dir = path8.join(root, ".agents", "gate");
|
|
1285
|
+
const launcher = path8.join(dir, "verify-notes.mjs");
|
|
1286
|
+
mkdirSync3(dir, { recursive: true });
|
|
1287
|
+
copyFileSync(NOTES_LAUNCHER_TEMPLATE, launcher);
|
|
1288
|
+
const packagePath = path8.join(root, "package.json");
|
|
1289
|
+
let pkg = { private: true };
|
|
1290
|
+
if (existsSync3(packagePath)) {
|
|
1291
|
+
try {
|
|
1292
|
+
pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
1293
|
+
} catch {
|
|
1294
|
+
throw new Error("package.json \u4E0D\u662F\u6709\u6548 JSON");
|
|
1295
|
+
}
|
|
1296
|
+
if (pkg === null || typeof pkg !== "object" || Array.isArray(pkg)) throw new Error("package.json \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1297
|
+
}
|
|
1298
|
+
pkg.scripts = pkg.scripts !== null && typeof pkg.scripts === "object" && !Array.isArray(pkg.scripts) ? pkg.scripts : {};
|
|
1299
|
+
const command = "node .agents/gate/verify-notes.mjs";
|
|
1300
|
+
const existing = pkg.scripts["verify-notes"];
|
|
1301
|
+
if (existing === void 0) pkg.scripts["verify-notes"] = command;
|
|
1302
|
+
writeFileSync3(packagePath, `${JSON.stringify(pkg, null, 2)}
|
|
1303
|
+
`);
|
|
1304
|
+
return {
|
|
1305
|
+
launcher: path8.relative(root, launcher).replaceAll("\\", "/"),
|
|
1306
|
+
package: path8.relative(root, packagePath).replaceAll("\\", "/"),
|
|
1307
|
+
command: existing === void 0 ? command : String(existing),
|
|
1308
|
+
changed: existing === void 0
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
var BASE_GITIGNORE = `node_modules/
|
|
1312
|
+
.env
|
|
1313
|
+
.env.*
|
|
1314
|
+
!.env.example
|
|
1315
|
+
*.log
|
|
1316
|
+
.DS_Store
|
|
1317
|
+
Thumbs.db
|
|
1318
|
+
coverage/
|
|
1319
|
+
dist/
|
|
1320
|
+
.agents/notes/.gate-finalize-*
|
|
1321
|
+
.agents/notes/.gate-validate-*
|
|
1322
|
+
.agents/notes/*.gate-backup
|
|
1323
|
+
`;
|
|
1324
|
+
function provisionGit(root) {
|
|
1325
|
+
const ignore = path8.join(root, ".gitignore");
|
|
1326
|
+
let ignoreStatus = "existing";
|
|
1327
|
+
if (!existsSync3(ignore)) {
|
|
1328
|
+
writeFileSync3(ignore, BASE_GITIGNORE);
|
|
1329
|
+
ignoreStatus = "created";
|
|
1330
|
+
}
|
|
1331
|
+
const git = (args, options = {}) => execFileSync2("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...options });
|
|
1332
|
+
let gitError = null;
|
|
1333
|
+
const init = () => {
|
|
1334
|
+
try {
|
|
1335
|
+
git(["init", "-b", "main"]);
|
|
1336
|
+
} catch (first) {
|
|
1337
|
+
try {
|
|
1338
|
+
git(["init"]);
|
|
1339
|
+
} catch (second) {
|
|
1340
|
+
gitError = String(second?.stderr ?? second?.message ?? first?.stderr ?? first?.message ?? second).trim();
|
|
1341
|
+
return "init-failed";
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
return "initialized";
|
|
1345
|
+
};
|
|
1346
|
+
let gitStatus;
|
|
1347
|
+
try {
|
|
1348
|
+
gitStatus = git(["rev-parse", "--is-inside-work-tree"]).trim() === "true" ? "existing" : init();
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
gitStatus = error?.code === "ENOENT" ? "unavailable" : init();
|
|
1351
|
+
}
|
|
1352
|
+
return { git: gitStatus, ignore: ignoreStatus, gitError };
|
|
1353
|
+
}
|
|
1354
|
+
function provisionCi(root) {
|
|
1355
|
+
const ciDir = path8.join(root, ".agents", "gate", "ci");
|
|
1356
|
+
mkdirSync3(ciDir, { recursive: true });
|
|
1357
|
+
for (const name of NOTES_CI_SCRIPTS) {
|
|
1358
|
+
copyFileSync(path8.join(NOTES_SKILL_DIR, "scripts", name), path8.join(ciDir, name));
|
|
1359
|
+
}
|
|
1360
|
+
const workflow = path8.join(root, ".github", "workflows", "verify-notes.yml");
|
|
1361
|
+
mkdirSync3(path8.dirname(workflow), { recursive: true });
|
|
1362
|
+
const existing = existsSync3(workflow);
|
|
1363
|
+
if (!existing) copyFileSync(NOTES_CI_TEMPLATE, workflow);
|
|
1364
|
+
return { workflow: path8.relative(root, workflow).replaceAll("\\", "/"), scripts: path8.relative(root, ciDir).replaceAll("\\", "/"), changed: !existing };
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// src/commands.ts
|
|
1368
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1369
|
+
function registerCommands(ctx, deps) {
|
|
1370
|
+
const { store, reviewLimit, wake } = deps;
|
|
1371
|
+
ctx.inject(["commands"], (scope) => {
|
|
1372
|
+
const register = (name, description, handler, input = { hint: "<\u5DE5\u4F5C\u9879>" }) => scope.commands.register({
|
|
1373
|
+
name,
|
|
1374
|
+
description,
|
|
1375
|
+
...input === null ? {} : { input },
|
|
1376
|
+
handler
|
|
1377
|
+
});
|
|
1378
|
+
register("gate-init", "\u521D\u59CB\u5316 notes\u3001AGENTS.md\u3001verify-notes \u4E0E GitHub Actions CI", async (invocation) => {
|
|
1379
|
+
const workspace = workspaceBoundary(ctx, invocation.agent);
|
|
1380
|
+
if (workspace === void 0) return { kind: "error", text: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A" };
|
|
1381
|
+
const root = path9.resolve(workspace);
|
|
1382
|
+
let provisioned;
|
|
1383
|
+
let projectScripts;
|
|
1384
|
+
let projectCi;
|
|
1385
|
+
let projectGit;
|
|
1386
|
+
try {
|
|
1387
|
+
provisioned = provisionNotes(root);
|
|
1388
|
+
projectCi = provisionCi(root);
|
|
1389
|
+
projectScripts = provisionProjectScripts(root);
|
|
1390
|
+
projectGit = provisionGit(root);
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
return { kind: "error", text: `notes \u521D\u59CB\u5316\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}` };
|
|
1393
|
+
}
|
|
1394
|
+
wake(invocation, store.read(root, invocation.agent), `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09\uFF0Cnotes \u5DF2\u521D\u59CB\u5316\u4E3A ${provisioned.notes}\uFF0CAGENTS.md \u5DF2\u66F4\u65B0\uFF0Cverify-notes \u5DF2\u914D\u7F6E\u4E3A ${projectScripts.command}\uFF0CCI \u5DF2\u914D\u7F6E\u4E3A ${projectCi.workflow}\uFF0Cgit\uFF1A${projectGit.git}${projectGit.gitError === null ? "" : `\uFF08${projectGit.gitError}\uFF09`}\uFF0Cignore\uFF1A${projectGit.ignore}\u3002`, "gate-init");
|
|
1395
|
+
return { kind: "success", text: `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09
|
|
1396
|
+
notes\uFF1A${provisioned.notes}
|
|
1397
|
+
AGENTS\uFF1A${provisioned.agents}
|
|
1398
|
+
verify-notes\uFF1A${projectScripts.command}
|
|
1399
|
+
CI\uFF1A${projectCi.workflow}
|
|
1400
|
+
git\uFF1A${projectGit.git}${projectGit.gitError === null ? "" : `\uFF08${projectGit.gitError}\uFF09`}
|
|
1401
|
+
ignore\uFF1A${projectGit.ignore}` };
|
|
1402
|
+
}, null);
|
|
1403
|
+
register("gate-enter-plan", "\u6309\u7528\u6237\u7B80\u8FF0\u8FDB\u5165 planning", async (invocation) => {
|
|
1404
|
+
const result = enterPhase(ctx, invocation.agent, "plan", reviewLimit, store, void 0);
|
|
1405
|
+
if (result.kind === "error") return result;
|
|
1406
|
+
const brief = String(invocation.rawInput ?? "").trim();
|
|
1407
|
+
wake(invocation, result.state, `Gate \u5DF2\u8FDB\u5165 plan\u3002\u7528\u6237\u89C4\u5212\u8F93\u5165\uFF1A${brief === "" ? "\uFF08\u672A\u63D0\u4F9B\u989D\u5916\u8F93\u5165\uFF09" : brief}\u3002`, "gate-enter-plan");
|
|
1408
|
+
return { kind: "success", text: "\u8FDB\u5165 planning\u3002" };
|
|
1409
|
+
}, { hint: "[\u7B80\u5355\u9700\u6C42]" });
|
|
1410
|
+
register("gate-enter-implement", "\u4ECE\u5DF2\u786E\u8BA4\u89C4\u5212\u6216\u7528\u6237\u7B80\u8FF0\u8FDB\u5165 implementation", async (invocation) => {
|
|
1411
|
+
const brief = String(invocation.rawInput ?? "").trim();
|
|
1412
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1413
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1414
|
+
const direct = state.planReady !== true;
|
|
1415
|
+
if (direct) {
|
|
1416
|
+
if (brief === "") return { kind: "error", text: "\u8BF7\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u8981\u5B9E\u65BD\u7684\u5185\u5BB9\uFF1B\u4E0D\u8981\u6C42\u5DE5\u4F5C\u9879\u683C\u5F0F\u3002" };
|
|
1417
|
+
state.task = brief;
|
|
1418
|
+
state.planReady = false;
|
|
1419
|
+
state.noteDecision = null;
|
|
1420
|
+
state.notePaths = [];
|
|
1421
|
+
state.noteSearch = null;
|
|
1422
|
+
state.noteVerified = false;
|
|
1423
|
+
state.noteFinalized = false;
|
|
1424
|
+
state.noteFinalizeRecovery = [];
|
|
1425
|
+
store.write(gate.root, invocation.agent, state);
|
|
1426
|
+
}
|
|
1427
|
+
const result = enterPhase(ctx, invocation.agent, "implement", reviewLimit, store, direct ? state : void 0);
|
|
1428
|
+
if (result.kind === "error") return result;
|
|
1429
|
+
const constraints = direct ? "\uFF08\u672C\u53E5\u5373\u5DE5\u4F5C\u9879\u7B80\u8FF0\uFF09" : brief === "" ? "\uFF08\u65E0\u989D\u5916\u7EA6\u675F\uFF09" : brief;
|
|
1430
|
+
wake(invocation, result.state, `Gate \u5DF2\u8FDB\u5165 implement\uFF1A${result.state.task ?? ""}${direct ? "\uFF08\u76F4\u63A5\u5B9E\u65BD\uFF0C\u672A\u8D70 planning\uFF09" : ""}\u3002\u7528\u6237\u8BF4\u660E\uFF1A${constraints}\u3002`, "gate-enter-implement");
|
|
1431
|
+
return { kind: "success", text: `\u8FDB\u5165 implement\uFF1A${result.state.task}${direct ? "\uFF08\u76F4\u63A5\u5B9E\u65BD\uFF09" : ""}` };
|
|
1432
|
+
}, { hint: "[\u7B80\u5355\u8BF4\u660E]" });
|
|
1433
|
+
register("gate-fix-bug", "\u4E3A\u5DF2\u6388\u6743\u7684\u5B9E\u73B0\u7F3A\u9677\u6D3E\u53D1 fix worker", async (invocation) => {
|
|
1434
|
+
const brief = String(invocation.rawInput ?? "").trim();
|
|
1435
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1436
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1437
|
+
if (state.phase !== "discuss" && state.phase !== "complete") return { kind: "error", text: `\u5F53\u524D phase \u662F ${state.phase}` };
|
|
1438
|
+
if (state.work === "blocked" || state.blockReason !== null) return { kind: "error", text: `\u5F53\u524D\u88AB blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
|
|
1439
|
+
const scope2 = brief === "" ? state.task ?? "\u5F53\u524D\u4E0A\u4E0B\u6587\u4E2D\u7684\u7F3A\u9677" : brief;
|
|
1440
|
+
state.phase = "orchestrating";
|
|
1441
|
+
state.node = "fix-bug";
|
|
1442
|
+
state.task = scope2;
|
|
1443
|
+
state.work = "active";
|
|
1444
|
+
state.blockReason = null;
|
|
1445
|
+
state.blockedFrom = null;
|
|
1446
|
+
store.write(gate.root, invocation.agent, state);
|
|
1447
|
+
wake(invocation, state, `Gate \u5DF2\u8FDB\u5165 fix-bug\uFF1A${scope2}\u3002`, "gate-fix-bug");
|
|
1448
|
+
return { kind: "success", text: `\u8FDB\u5165 fix-bug\uFF1A${scope2}` };
|
|
1449
|
+
}, { hint: "[\u7F3A\u9677\u7B80\u8FF0]" });
|
|
1450
|
+
register("gate-docs", "\u5F00\u59CB\u6587\u6863\u6CBB\u7406", async (invocation) => {
|
|
1451
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1452
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1453
|
+
if (state.phase !== "discuss" && state.phase !== "complete") return { kind: "error", text: `\u5F53\u524D phase \u662F ${state.phase}` };
|
|
1454
|
+
if (state.work === "blocked" || state.blockReason !== null) return { kind: "error", text: `\u5F53\u524D\u88AB blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
|
|
1455
|
+
state.phase = "docs";
|
|
1456
|
+
state.task = "\u6587\u6863\u6CBB\u7406";
|
|
1457
|
+
state.work = "active";
|
|
1458
|
+
state.blockReason = null;
|
|
1459
|
+
state.blockedFrom = null;
|
|
1460
|
+
state.node = null;
|
|
1461
|
+
store.write(gate.root, invocation.agent, state);
|
|
1462
|
+
wake(invocation, state, "Gate \u5DF2\u8FDB\u5165 docs\u3002", "gate-docs");
|
|
1463
|
+
return { kind: "success", text: "\u8FDB\u5165 docs\uFF1A\u6587\u6863\u6CBB\u7406" };
|
|
1464
|
+
}, null);
|
|
1465
|
+
register("gate-implement-review", "\u5F00\u59CB\u72EC\u7ACB\u53EA\u8BFB code review", async (invocation) => {
|
|
1466
|
+
const brief = String(invocation.rawInput ?? "").trim();
|
|
1467
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1468
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1469
|
+
if (state.phase !== "discuss" && state.phase !== "complete") return { kind: "error", text: `\u5F53\u524D phase \u662F ${state.phase}` };
|
|
1470
|
+
if (state.work === "blocked" || state.blockReason !== null) return { kind: "error", text: `\u5F53\u524D\u88AB blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
|
|
1471
|
+
const scope2 = brief === "" ? state.task ?? "\u5F53\u524D\u4E0A\u4E0B\u6587\u4E2D\u7684\u6539\u52A8" : brief;
|
|
1472
|
+
state.phase = "reviewing";
|
|
1473
|
+
state.task = scope2;
|
|
1474
|
+
state.node = "code-review-standalone";
|
|
1475
|
+
state.work = "active";
|
|
1476
|
+
state.blockReason = null;
|
|
1477
|
+
state.blockedFrom = null;
|
|
1478
|
+
store.write(gate.root, invocation.agent, state);
|
|
1479
|
+
wake(invocation, state, `Gate \u5DF2\u8FDB\u5165\u53EA\u8BFB code review\uFF1A${scope2}\u3002`, "gate-implement-review");
|
|
1480
|
+
return { kind: "success", text: `\u8FDB\u5165\u53EA\u8BFB code review\uFF1A${scope2}` };
|
|
1481
|
+
});
|
|
1482
|
+
register("gate-code-verify", "\u5F00\u59CB\u72EC\u7ACB\u53EA\u8BFB\u9A8C\u6536", async (invocation) => {
|
|
1483
|
+
const brief = String(invocation.rawInput ?? "").trim();
|
|
1484
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1485
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1486
|
+
if (state.phase !== "discuss" && state.phase !== "complete") return { kind: "error", text: `\u5F53\u524D phase \u662F ${state.phase}` };
|
|
1487
|
+
if (state.work === "blocked" || state.blockReason !== null) return { kind: "error", text: `\u5F53\u524D\u88AB blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
|
|
1488
|
+
const scope2 = brief === "" ? state.task ?? "\u5F53\u524D\u4E0A\u4E0B\u6587\u4E2D\u7684\u6539\u52A8" : brief;
|
|
1489
|
+
state.phase = "reviewing";
|
|
1490
|
+
state.task = scope2;
|
|
1491
|
+
state.node = "verify-standalone";
|
|
1492
|
+
state.work = "active";
|
|
1493
|
+
state.blockReason = null;
|
|
1494
|
+
state.blockedFrom = null;
|
|
1495
|
+
store.write(gate.root, invocation.agent, state);
|
|
1496
|
+
wake(invocation, state, `Gate \u5DF2\u8FDB\u5165\u72EC\u7ACB\u9A8C\u6536\uFF1A${scope2}\u3002`, "gate-code-verify");
|
|
1497
|
+
return { kind: "success", text: `\u8FDB\u5165\u72EC\u7ACB\u9A8C\u6536\uFF1A${scope2}` };
|
|
1498
|
+
});
|
|
1499
|
+
register("gate-propose-review", "\u5F00\u59CB\u72EC\u7ACB\u63D0\u6848\u8BC4\u5BA1", async (invocation) => {
|
|
1500
|
+
const brief = String(invocation.rawInput ?? "").trim();
|
|
1501
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1502
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1503
|
+
if (state.phase !== "discuss" && state.phase !== "complete") return { kind: "error", text: `\u5F53\u524D phase \u662F ${state.phase}` };
|
|
1504
|
+
if (state.work === "blocked" || state.blockReason !== null) return { kind: "error", text: `\u5F53\u524D\u88AB blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
|
|
1505
|
+
const scope2 = brief === "" ? state.task ?? "\u5F53\u524D\u63D0\u6848/Note" : brief;
|
|
1506
|
+
state.phase = "reviewing";
|
|
1507
|
+
state.task = scope2;
|
|
1508
|
+
state.node = "proposal-review";
|
|
1509
|
+
state.work = "active";
|
|
1510
|
+
state.blockReason = null;
|
|
1511
|
+
state.blockedFrom = null;
|
|
1512
|
+
store.write(gate.root, invocation.agent, state);
|
|
1513
|
+
wake(invocation, state, `Gate \u5DF2\u8FDB\u5165\u63D0\u6848\u8BC4\u5BA1\uFF1A${scope2}\u3002`, "gate-propose-review");
|
|
1514
|
+
return { kind: "success", text: `\u8FDB\u5165\u63D0\u6848\u8BC4\u5BA1\uFF1A${scope2}` };
|
|
1515
|
+
});
|
|
1516
|
+
register("gate-block", "\u624B\u52A8\u6682\u505C\uFF1A\u4FDD\u7559\u5F53\u524D\u8282\u70B9\u5E76\u505C\u6B62\u5199\u5165/\u6D3E worker\uFF1B\u5BF9\u8BDD\u4ECD\u53EF\u7EE7\u7EED", async (invocation) => {
|
|
1517
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1518
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1519
|
+
if (state.phase === "blocked") return { kind: "success", text: "\u5DF2\u7ECF\u662F blocked" };
|
|
1520
|
+
state.blockedFrom = { phase: state.phase, node: state.node };
|
|
1521
|
+
state.phase = "blocked";
|
|
1522
|
+
state.work = "blocked";
|
|
1523
|
+
state.node = null;
|
|
1524
|
+
state.blockReason = String(invocation.rawInput ?? "").trim() || "user-blocked";
|
|
1525
|
+
store.write(gate.root, invocation.agent, state);
|
|
1526
|
+
return { kind: "success", text: `\u5DF2\u6682\u505C\uFF1A${state.blockReason}
|
|
1527
|
+
\u89E3\u51BB\u540E\uFF1A${state.blockedFrom?.phase ?? "discuss"}${state.blockedFrom?.node === null || state.blockedFrom?.node === void 0 ? "" : ` / ${state.blockedFrom.node}`}
|
|
1528
|
+
\u540C\u4E00\u4E8B\u9879\uFF1A/gate-resume \u56DE\u5230\u8BE5\u8282\u70B9\uFF1B\u6539\u53D8\u65B9\u6848\uFF1A/gate-return-discuss\u3002` };
|
|
1529
|
+
}, { hint: "[\u539F\u56E0]" });
|
|
1530
|
+
register("gate-resume", "\u89E3\u51BB blocked\uFF1A\u56DE\u5230 block \u524D\u7684\u8282\u70B9\uFF1B\u8F6E\u6B21\u8017\u5C3D\u56DE discuss", async (invocation) => {
|
|
1531
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1532
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1533
|
+
if (state.phase !== "blocked") return { kind: "error", text: "\u5F53\u524D\u4E0D\u662F blocked" };
|
|
1534
|
+
const from = state.blockedFrom;
|
|
1535
|
+
const reason = state.blockReason;
|
|
1536
|
+
state.phase = from?.phase ?? "discuss";
|
|
1537
|
+
state.node = from?.node ?? null;
|
|
1538
|
+
state.work = "active";
|
|
1539
|
+
state.blockReason = null;
|
|
1540
|
+
state.blockedFrom = null;
|
|
1541
|
+
store.write(gate.root, invocation.agent, state);
|
|
1542
|
+
if (state.phase === "orchestrating" || state.phase === "reviewing" || state.phase === "docs") {
|
|
1543
|
+
wake(invocation, state, `Gate \u5DF2\u6062\u590D\u5230 ${state.phase}${state.node === null ? "" : ` / ${state.node}`}\u3002`, "gate-resume");
|
|
1544
|
+
}
|
|
1545
|
+
const exhausted = reason === "plan-review-rounds-exhausted" || reason === "code-review-rounds-exhausted";
|
|
1546
|
+
return { kind: "success", text: `\u5DF2\u89E3\u9664 blocked\uFF1A${state.phase}${state.node === null ? "" : ` / ${state.node}`}${exhausted ? "\nreview \u8F6E\u6B21\u5DF2\u8017\u5C3D\uFF0C\u4E0D\u80FD\u7EE7\u7EED\u539F\u8282\u70B9\uFF1B\u7528 /gate-enter-plan \u91CD\u65B0\u89C4\u5212\u3002" : "\n\u5DF2\u56DE\u5230\u6682\u505C\u524D\u8282\u70B9\uFF0C\u6309\u5F53\u524D procedure \u7EE7\u7EED\u3002"}` };
|
|
1547
|
+
}, null);
|
|
1548
|
+
register("gate-return-discuss", "\u56DE\u5230 discuss", async (invocation) => {
|
|
1549
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1550
|
+
const state = store.read(gate.root, invocation.agent);
|
|
1551
|
+
if (state.phase === "blocked") return { kind: "error", text: `\u5F53\u524D blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
|
|
1552
|
+
state.phase = "discuss";
|
|
1553
|
+
state.node = null;
|
|
1554
|
+
state.work = "active";
|
|
1555
|
+
state.blockReason = null;
|
|
1556
|
+
state.blockedFrom = null;
|
|
1557
|
+
state.task = null;
|
|
1558
|
+
state.planReady = false;
|
|
1559
|
+
state.rounds = { planReview: 0, codeReview: 0 };
|
|
1560
|
+
state.verification = null;
|
|
1561
|
+
state.notePaths = [];
|
|
1562
|
+
state.noteDecision = null;
|
|
1563
|
+
state.noteSearch = null;
|
|
1564
|
+
state.noteVerified = false;
|
|
1565
|
+
state.noteFinalized = false;
|
|
1566
|
+
state.noteFinalizeRecovery = [];
|
|
1567
|
+
state.pending = {};
|
|
1568
|
+
store.write(gate.root, invocation.agent, state);
|
|
1569
|
+
return { kind: "success", text: "\u5DF2\u56DE\u5230 discuss" };
|
|
1570
|
+
}, null);
|
|
1571
|
+
register("gate-status", "\u67E5\u770B\u5F53\u524D gate \u72B6\u6001", async (invocation) => {
|
|
1572
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1573
|
+
const root = rootAgentOf(ctx, invocation.agent);
|
|
1574
|
+
return { kind: "success", text: statusText(store.read(gate.root, root), reviewLimit) };
|
|
1575
|
+
}, null);
|
|
1576
|
+
register("gate-note-verify", "\u53EA\u8BFB\u68C0\u67E5 notes tree/format/lifecycle/archived", async (invocation) => {
|
|
1577
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1578
|
+
const check = runNotesScripts(gate.root);
|
|
1579
|
+
return check.ok ? { kind: "success", text: check.results.map((item) => `${item.name}: ok`).join("\n") } : { kind: "error", text: `notes \u6821\u9A8C\u5931\u8D25\uFF1A
|
|
1580
|
+
${JSON.stringify(check.results, null, 2)}` };
|
|
1581
|
+
}, null);
|
|
1582
|
+
register("gate-note-archive", "\u5C06 implemented Note \u5F52\u6863\u5230 archived/\uFF1A\u52A0 Archived\u3001\u79FB\u52A8\u3001\u5C01\u5370\u5E76\u62A5\u544A\u5165\u7AD9\u94FE\u63A5", async (invocation) => {
|
|
1583
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1584
|
+
const raw = String(invocation.rawInput ?? "").trim();
|
|
1585
|
+
const args = raw === "" ? [] : raw.split(/\s+/u);
|
|
1586
|
+
if (args.length === 0) return { kind: "error", text: "\u7528\u6CD5\uFF1A/gate-note-archive implemented/<class>/<note>.md [--superseded-by implemented/<class>/<new>.md]" };
|
|
1587
|
+
const node = resolveNodeBinary();
|
|
1588
|
+
if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
|
|
1589
|
+
try {
|
|
1590
|
+
const script = path9.join(NOTES_SKILL_DIR, "scripts", "archive-agent-note.ts");
|
|
1591
|
+
const output = execFileSync3(node, [script, "--root", path9.join(gate.root, ".agents", "notes"), ...args], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
1592
|
+
return { kind: "success", text: String(output).trim() || "Note \u5DF2\u5F52\u6863" };
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
|
|
1595
|
+
}
|
|
1596
|
+
}, { hint: "<implemented-note> [--superseded-by <new-note>]" });
|
|
1597
|
+
register("gate-note-board", "\u751F\u6210\u51B3\u7B56\u770B\u677F board.html\uFF08\u8F7B\u91CF\u6A21\u5F0F\uFF0C\u8FDE\u63A5\u672C\u5730 notes \u76EE\u5F55\uFF09", async (invocation) => {
|
|
1598
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1599
|
+
const raw = String(invocation.rawInput ?? "").trim();
|
|
1600
|
+
const args = raw === "" ? [] : raw.split(/\s+/u);
|
|
1601
|
+
const target = args[0] ?? "board.html";
|
|
1602
|
+
const title = args[1] ?? `${path9.basename(gate.root)} \u51B3\u7B56\u770B\u677F`;
|
|
1603
|
+
const node = resolveNodeBinary();
|
|
1604
|
+
if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
|
|
1605
|
+
try {
|
|
1606
|
+
const script = path9.join(NOTES_SKILL_DIR, "scripts", "build-board.ts");
|
|
1607
|
+
const output = execFileSync3(node, [script, "--init", target, title], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
1608
|
+
return { kind: "success", text: String(output).trim() || `\u51B3\u7B56\u770B\u677F\u5DF2\u751F\u6210\uFF1A${target}` };
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
|
|
1611
|
+
}
|
|
1612
|
+
}, { hint: "[\u8F93\u51FA\u8DEF\u5F84] [\u6807\u9898]" });
|
|
1613
|
+
register("gate-note-seal", "\u53EA\u7ED9 archived/ \u4E2D\u5DF2\u6709 Note \u8865\u5199 manifest.json \u7684 SHA-256 \u5C01\u5370\uFF1B\u4E0D\u5F52\u6863\u3001\u4E0D\u6539 Note \u5185\u5BB9", async (invocation) => {
|
|
1614
|
+
const gate = resolveGate(ctx, invocation.agent);
|
|
1615
|
+
const node = resolveNodeBinary();
|
|
1616
|
+
if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
|
|
1617
|
+
try {
|
|
1618
|
+
const script = path9.join(NOTES_SKILL_DIR, "scripts", "verify-archived-agent-notes.ts");
|
|
1619
|
+
const output = execFileSync3(node, [script, "--root", path9.join(gate.root, ".agents", "notes"), "--write"], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
1620
|
+
return { kind: "success", text: String(output).trim() || "archived manifest \u5DF2\u5199\u5165" };
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
|
|
1623
|
+
}
|
|
1624
|
+
}, null);
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// src/tools/start-phase.ts
|
|
1629
|
+
function registerStartPhase(ctx, deps) {
|
|
1630
|
+
const { store, reviewLimit, wake } = deps;
|
|
1631
|
+
ctx.tools.register({
|
|
1632
|
+
name: "gate_start_phase",
|
|
1633
|
+
description: "\u5728\u7528\u6237\u660E\u786E\u6388\u6743\u540E\u4ECE discuss/complete \u8FDB\u5165 plan \u6216 implement\u3002\u4EC5 root agent \u53EF\u8C03\u7528\uFF1B\u6388\u6743\u53EF\u6765\u81EA\u5BF9\u8BDD\u6216 ask_user_question\uFF0C\u4E0D\u8981\u6C42\u7528\u6237\u8F93\u5165 slash command\u3002",
|
|
1634
|
+
parameters: {
|
|
1635
|
+
type: "object",
|
|
1636
|
+
additionalProperties: false,
|
|
1637
|
+
properties: {
|
|
1638
|
+
node: { type: "string", enum: ["plan", "implement"] },
|
|
1639
|
+
brief: { type: "string" }
|
|
1640
|
+
},
|
|
1641
|
+
required: ["node"]
|
|
1642
|
+
},
|
|
1643
|
+
output: {
|
|
1644
|
+
schema: { type: "object", additionalProperties: true },
|
|
1645
|
+
render: (_args, value) => renderProcedureResult(value),
|
|
1646
|
+
presentationMeta: (_args, value) => value?.state === void 0 ? { gate: {} } : { gate: { state: value.state } }
|
|
1647
|
+
},
|
|
1648
|
+
async execute(args, exec) {
|
|
1649
|
+
const root = rootAgentOf(ctx, exec.agent);
|
|
1650
|
+
if (root?.id !== exec.agent?.id) return { ok: false, code: "root-only", reason: "gate_start_phase \u53EA\u80FD\u7531 root agent \u8C03\u7528\u3002" };
|
|
1651
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
1652
|
+
const state = store.read(gate.root, exec.agent);
|
|
1653
|
+
const fail = (fields) => procedureFailure(state, reviewLimit, fields);
|
|
1654
|
+
if (args.node !== "plan" && args.node !== "implement") return fail({ code: "invalid-node", reason: "node \u5FC5\u987B\u662F plan \u6216 implement\u3002" });
|
|
1655
|
+
const direct = args.node === "implement" && state.planReady !== true;
|
|
1656
|
+
if (direct) {
|
|
1657
|
+
const brief = String(args.brief ?? "").trim();
|
|
1658
|
+
if (brief === "") return fail({ code: "brief-required", reason: "\u76F4\u63A5 implement \u9700\u8981\u4E00\u53E5\u8BF4\u660E\u3002" });
|
|
1659
|
+
state.task = brief;
|
|
1660
|
+
state.planReady = false;
|
|
1661
|
+
state.noteDecision = null;
|
|
1662
|
+
state.notePaths = [];
|
|
1663
|
+
state.noteSearch = null;
|
|
1664
|
+
state.noteVerified = false;
|
|
1665
|
+
state.noteFinalized = false;
|
|
1666
|
+
state.noteFinalizeRecovery = [];
|
|
1667
|
+
store.write(gate.root, exec.agent, state);
|
|
1668
|
+
}
|
|
1669
|
+
const result = enterPhase(ctx, exec.agent, args.node, reviewLimit, store, direct ? state : void 0);
|
|
1670
|
+
if (result.kind === "error") return fail({ code: "phase-enter-failed", reason: result.text });
|
|
1671
|
+
const procedure = procedureFor(result.state, { reviewLimit });
|
|
1672
|
+
return {
|
|
1673
|
+
ok: true,
|
|
1674
|
+
phase: result.state.phase,
|
|
1675
|
+
node: result.state.node,
|
|
1676
|
+
task: result.state.task,
|
|
1677
|
+
procedure,
|
|
1678
|
+
method: procedure.method,
|
|
1679
|
+
state: normalizeState(result.state)
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// src/tools/record.ts
|
|
1686
|
+
import { createHash } from "node:crypto";
|
|
1687
|
+
import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
1688
|
+
function registerRecord(ctx, deps) {
|
|
1689
|
+
const { store, reviewLimit, wake } = deps;
|
|
1690
|
+
ctx.tools.register({
|
|
1691
|
+
name: "gate_record",
|
|
1692
|
+
description: "\u8BB0\u5F55 root agent \u6D3E\u53D1\u7684\u8282\u70B9 worker \u7ED3\u679C\u5E76\u63A8\u8FDB gate \u72B6\u6001\u3002worker \u8282\u70B9\u8981\u6C42 workerSessionIds\uFF1Breview \u8282\u70B9\u8981\u6C42\u4E24\u8F74\u7ED3\u679C\uFF1Bverify \u8981\u6C42 toolResults\u3002\u4EC5 root agent \u53EF\u8C03\u7528\u3002",
|
|
1693
|
+
parameters: {
|
|
1694
|
+
type: "object",
|
|
1695
|
+
additionalProperties: false,
|
|
1696
|
+
properties: {
|
|
1697
|
+
node: { type: "string", enum: GATE_RECORD_NODES },
|
|
1698
|
+
workerSessionIds: { type: "array", items: { type: "string" } },
|
|
1699
|
+
reviewAxes: { type: "object", additionalProperties: true },
|
|
1700
|
+
task: { type: "string" },
|
|
1701
|
+
findings: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
1702
|
+
requiresDiscussion: { type: "boolean" },
|
|
1703
|
+
discussionReason: { type: "string" },
|
|
1704
|
+
notePaths: {
|
|
1705
|
+
type: "array",
|
|
1706
|
+
items: {
|
|
1707
|
+
type: "object",
|
|
1708
|
+
additionalProperties: false,
|
|
1709
|
+
properties: {
|
|
1710
|
+
path: { type: "string", description: "\u63A5\u53D7 .agents/notes/<lifecycle>/<class>/...\u3001<lifecycle>/<class>/... \u6216 <class>/...\uFF1Bstate \u5F52\u4E00\u4E3A\u5B8C\u6574\u8DEF\u5F84\u3002" },
|
|
1711
|
+
class: { type: "string", enum: ["feature", "bug-fix", "simplification", "architecture", "process", "testing"] },
|
|
1712
|
+
lifecycle: { type: "string", enum: ["proposed", "implemented"] }
|
|
1713
|
+
},
|
|
1714
|
+
required: ["path", "class", "lifecycle"]
|
|
1715
|
+
}
|
|
1716
|
+
},
|
|
1717
|
+
noteDecision: { type: "string", enum: ["new", "update", "in-place", "mechanical"] },
|
|
1718
|
+
noteSearch: { type: "string" },
|
|
1719
|
+
verification: {
|
|
1720
|
+
type: "object",
|
|
1721
|
+
additionalProperties: false,
|
|
1722
|
+
properties: {
|
|
1723
|
+
commands: { type: "array", items: { type: "string" } },
|
|
1724
|
+
evidence: { type: "array", items: { type: "string", description: "gate workspace \u5185\u666E\u901A\u6587\u4EF6\u8DEF\u5F84\u3002" } },
|
|
1725
|
+
toolResults: { type: "array", items: { type: "string", description: "verify worker session log \u4E2D\u771F\u5B9E tool/result \u7684 call_... id\u3002" } },
|
|
1726
|
+
passed: { type: "boolean" }
|
|
1727
|
+
},
|
|
1728
|
+
required: ["commands", "evidence", "toolResults", "passed"]
|
|
1729
|
+
}
|
|
1730
|
+
},
|
|
1731
|
+
required: ["node"]
|
|
1732
|
+
},
|
|
1733
|
+
output: {
|
|
1734
|
+
schema: { type: "object", additionalProperties: true },
|
|
1735
|
+
render: (_args, value) => renderProcedureResult(value),
|
|
1736
|
+
presentationMeta: (_args, value) => value?.state === void 0 ? { gate: {} } : { gate: { state: value.state } }
|
|
1737
|
+
},
|
|
1738
|
+
async execute(args, exec) {
|
|
1739
|
+
const root = rootAgentOf(ctx, exec.agent);
|
|
1740
|
+
if (root?.id !== exec.agent?.id) return { ok: false, code: "root-only", reason: "gate_record \u53EA\u80FD\u7531 root agent \u8C03\u7528\uFF1Bworker \u5B8C\u6210\u5F53\u524D node \u540E\u8FD4\u56DE receipt\u3002" };
|
|
1741
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
1742
|
+
const state = store.read(gate.root, exec.agent);
|
|
1743
|
+
const fail = (fields) => procedureFailure(state, reviewLimit, fields);
|
|
1744
|
+
if (state.phase === "docs") {
|
|
1745
|
+
if (args.node !== "docs") return fail({ code: "wrong-node", expected: "docs", got: args.node ?? null, reason: 'docs \u9636\u6BB5\u53EA\u80FD\u901A\u8FC7 gate_record({node:"docs"}) \u7ED3\u675F\u3002' });
|
|
1746
|
+
state.phase = "discuss";
|
|
1747
|
+
state.node = null;
|
|
1748
|
+
state.work = "active";
|
|
1749
|
+
state.blockReason = null;
|
|
1750
|
+
state.blockedFrom = null;
|
|
1751
|
+
store.write(gate.root, exec.agent, state);
|
|
1752
|
+
const procedure2 = procedureFor(state, { reviewLimit });
|
|
1753
|
+
return { ok: true, phase: state.phase, node: state.node, procedure: procedure2, method: procedure2.method, state: normalizeState(state) };
|
|
1754
|
+
}
|
|
1755
|
+
if (state.phase !== "orchestrating" && state.phase !== "reviewing") {
|
|
1756
|
+
return fail({ code: "not-orchestrating", phase: state.phase, stateSource: store.source, reason: `\u5F53\u524D phase \u662F ${state.phase}\uFF1B\u5148\u8FDB\u5165\u5BF9\u5E94\u9636\u6BB5\u3002` });
|
|
1757
|
+
}
|
|
1758
|
+
if (state.node !== args.node) return fail({ code: "wrong-node", expected: state.node ?? null, got: args.node ?? null, reason: `\u5F53\u524D\u8282\u70B9\u662F ${state.node ?? "\u65E0"}\uFF0C\u8BF7\u6C42\u7684 node \u662F ${args.node ?? "\u672A\u63D0\u4F9B"}\u3002` });
|
|
1759
|
+
const axesError = reviewAxesError(args);
|
|
1760
|
+
if (axesError !== void 0) return fail(axesError);
|
|
1761
|
+
const workerError = await workerEvidenceError(ctx, root, args);
|
|
1762
|
+
if (workerError !== void 0) return fail(workerError);
|
|
1763
|
+
const topFindings = Array.isArray(args.findings) ? args.findings : [];
|
|
1764
|
+
const axesFindings = REVIEW_NODES.has(args.node) ? [
|
|
1765
|
+
...args.reviewAxes.standards.findings,
|
|
1766
|
+
...args.reviewAxes.spec.findings,
|
|
1767
|
+
...args.node === "plan-review" || args.node === "proposal-review" ? args.reviewAxes.architecture.findings : []
|
|
1768
|
+
] : [];
|
|
1769
|
+
const findings = [...axesFindings, ...topFindings];
|
|
1770
|
+
let commitHint = null;
|
|
1771
|
+
if (args.requiresDiscussion === true) {
|
|
1772
|
+
if (args.node === "plan-review") state.planReady = false;
|
|
1773
|
+
state.blockedFrom = { phase: state.phase, node: state.node };
|
|
1774
|
+
state.phase = "blocked";
|
|
1775
|
+
state.node = null;
|
|
1776
|
+
state.work = "blocked";
|
|
1777
|
+
state.blockReason = "needs-user-decision";
|
|
1778
|
+
store.write(gate.root, exec.agent, state);
|
|
1779
|
+
const procedure2 = procedureFor(state, { reviewLimit });
|
|
1780
|
+
return { ok: true, phase: state.phase, blockReason: state.blockReason, reason: args.discussionReason ?? null, procedure: procedure2, method: procedure2.method, state: normalizeState(state) };
|
|
1781
|
+
}
|
|
1782
|
+
if (args.node === "proposal-review") {
|
|
1783
|
+
state.phase = "discuss";
|
|
1784
|
+
state.node = null;
|
|
1785
|
+
state.work = "active";
|
|
1786
|
+
state.blockReason = null;
|
|
1787
|
+
state.blockedFrom = null;
|
|
1788
|
+
} else if (args.node === "code-review-standalone") {
|
|
1789
|
+
state.phase = "discuss";
|
|
1790
|
+
state.node = null;
|
|
1791
|
+
state.work = "active";
|
|
1792
|
+
state.blockReason = null;
|
|
1793
|
+
} else if (args.node === "plan") {
|
|
1794
|
+
if (typeof args.task !== "string" || args.task.trim() === "") {
|
|
1795
|
+
return fail({ code: "task-required", reason: "node=plan \u5FC5\u987B\u643A\u5E26 task\uFF08planning \u4EA7\u51FA\u7684\u5DE5\u4F5C\u9879\u6216 mechanical:<\u8BF4\u660E>\uFF09\u3002" });
|
|
1796
|
+
}
|
|
1797
|
+
const decision = args.noteDecision;
|
|
1798
|
+
if (!["new", "update", "in-place", "mechanical"].includes(decision)) {
|
|
1799
|
+
return fail({ code: "note-decision-required", reason: "node=plan \u9700\u8981 noteDecision\uFF08new/update/in-place/mechanical\uFF09\u3002" });
|
|
1800
|
+
}
|
|
1801
|
+
if (decision === "mechanical" && !isMechanical(args.task)) {
|
|
1802
|
+
return fail({ code: "mechanical-required", reason: "mechanical \u53EA\u5141\u8BB8 task \u4EE5 mechanical: \u5F00\u5934\u3002" });
|
|
1803
|
+
}
|
|
1804
|
+
const optionNotePaths = notePathsForDecision(args.notePaths, decision);
|
|
1805
|
+
const noteError = validateNotePaths(gate.root, optionNotePaths, decision);
|
|
1806
|
+
if (noteError !== void 0) return fail(noteError);
|
|
1807
|
+
state.task = args.task.trim();
|
|
1808
|
+
state.notePaths = decision === "mechanical" ? [] : optionNotePaths;
|
|
1809
|
+
state.noteDecision = decision;
|
|
1810
|
+
state.noteSearch = typeof args.noteSearch === "string" ? args.noteSearch : null;
|
|
1811
|
+
state.noteVerified = decision === "mechanical";
|
|
1812
|
+
state.node = "plan-review";
|
|
1813
|
+
} else if (args.node === "plan-revise") {
|
|
1814
|
+
if (args.noteDecision !== void 0 || args.notePaths !== void 0) {
|
|
1815
|
+
const decision = args.noteDecision ?? state.noteDecision;
|
|
1816
|
+
if (!["new", "update", "in-place", "mechanical"].includes(decision)) {
|
|
1817
|
+
return fail({ code: "note-decision-required", reason: "plan-revise \u9700\u8981\u5408\u6CD5 noteDecision\u3002" });
|
|
1818
|
+
}
|
|
1819
|
+
if (decision === "mechanical" && !isMechanical(state.task)) {
|
|
1820
|
+
return fail({ code: "mechanical-required", reason: "mechanical \u53EA\u5141\u8BB8 task \u4EE5 mechanical: \u5F00\u5934\u3002" });
|
|
1821
|
+
}
|
|
1822
|
+
const requestedNotePaths = args.notePaths ?? state.notePaths;
|
|
1823
|
+
const optionNotePaths = notePathsForDecision(requestedNotePaths, decision);
|
|
1824
|
+
const noteError = validateNotePaths(gate.root, optionNotePaths, decision);
|
|
1825
|
+
if (noteError !== void 0) return fail(noteError);
|
|
1826
|
+
state.notePaths = decision === "mechanical" ? [] : optionNotePaths;
|
|
1827
|
+
state.noteDecision = decision;
|
|
1828
|
+
if (typeof args.noteSearch === "string") state.noteSearch = args.noteSearch;
|
|
1829
|
+
state.noteVerified = decision === "mechanical";
|
|
1830
|
+
}
|
|
1831
|
+
state.node = "plan-review";
|
|
1832
|
+
} else if (args.node === "plan-review") {
|
|
1833
|
+
state.rounds.planReview += 1;
|
|
1834
|
+
if (findings.length === 0) {
|
|
1835
|
+
state.phase = "discuss";
|
|
1836
|
+
state.node = null;
|
|
1837
|
+
state.planReady = true;
|
|
1838
|
+
state.work = "active";
|
|
1839
|
+
state.blockReason = null;
|
|
1840
|
+
commitHint = "\u5EFA\u8BAE\u63D0\u4EA4 planning baseline\uFF08Note + \u8BA1\u5212\uFF09";
|
|
1841
|
+
} else if (state.rounds.planReview >= reviewLimit) {
|
|
1842
|
+
state.exhausted.plan = true;
|
|
1843
|
+
state.blockedFrom = { phase: "discuss", node: null };
|
|
1844
|
+
state.phase = "blocked";
|
|
1845
|
+
state.node = null;
|
|
1846
|
+
state.work = "blocked";
|
|
1847
|
+
state.blockReason = "plan-review-rounds-exhausted";
|
|
1848
|
+
} else state.node = "plan-revise";
|
|
1849
|
+
} else if (args.node === "implement") {
|
|
1850
|
+
if (args.noteDecision === "mechanical") {
|
|
1851
|
+
state.noteDecision = "mechanical";
|
|
1852
|
+
state.notePaths = [];
|
|
1853
|
+
state.noteVerified = true;
|
|
1854
|
+
} else {
|
|
1855
|
+
if (Array.isArray(args.notePaths)) {
|
|
1856
|
+
const nextNotePaths = normalizeNotePaths(args.notePaths);
|
|
1857
|
+
const noteError = validateNotePaths(gate.root, nextNotePaths, args.noteDecision ?? state.noteDecision);
|
|
1858
|
+
if (noteError !== void 0) return fail(noteError);
|
|
1859
|
+
state.notePaths = nextNotePaths;
|
|
1860
|
+
}
|
|
1861
|
+
if (["new", "update", "in-place"].includes(args.noteDecision)) state.noteDecision = args.noteDecision;
|
|
1862
|
+
}
|
|
1863
|
+
state.node = "code-review";
|
|
1864
|
+
} else if (args.node === "implement-revise") state.node = "code-review";
|
|
1865
|
+
else if (args.node === "code-review") {
|
|
1866
|
+
state.rounds.codeReview += 1;
|
|
1867
|
+
if (findings.length === 0) state.node = "verify";
|
|
1868
|
+
else if (state.rounds.codeReview >= reviewLimit) {
|
|
1869
|
+
state.exhausted.code = true;
|
|
1870
|
+
state.blockedFrom = { phase: "discuss", node: null };
|
|
1871
|
+
state.phase = "blocked";
|
|
1872
|
+
state.node = null;
|
|
1873
|
+
state.work = "blocked";
|
|
1874
|
+
state.blockReason = "code-review-rounds-exhausted";
|
|
1875
|
+
} else state.node = "implement-revise";
|
|
1876
|
+
} else if (args.node === "verify" || args.node === "verify-standalone") {
|
|
1877
|
+
const verification = args.verification;
|
|
1878
|
+
const commands = Array.isArray(verification?.commands) ? verification.commands.filter((item) => typeof item === "string" && item.trim() !== "") : [];
|
|
1879
|
+
const evidence = Array.isArray(verification?.evidence) ? verification.evidence.filter((item) => typeof item === "string" && item.trim() !== "") : [];
|
|
1880
|
+
const toolResults = Array.isArray(verification?.toolResults) ? verification.toolResults.filter((item) => typeof item === "string" && item.trim() !== "") : [];
|
|
1881
|
+
const unbound = await bindToolResults(ctx, Array.isArray(args.workerSessionIds) ? args.workerSessionIds : [], toolResults);
|
|
1882
|
+
if (unbound !== void 0) return fail(unbound);
|
|
1883
|
+
const records = [];
|
|
1884
|
+
const invalidEvidence = [];
|
|
1885
|
+
for (const item of evidence) {
|
|
1886
|
+
const target = classifyPath(gate.workspace, item);
|
|
1887
|
+
try {
|
|
1888
|
+
if (target.outside || !existsSync4(target.absolute) || !lstatSync4(target.absolute).isFile()) {
|
|
1889
|
+
invalidEvidence.push(item);
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
const bytes = readFileSync3(target.absolute);
|
|
1893
|
+
records.push({ path: target.relative, size: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") });
|
|
1894
|
+
} catch {
|
|
1895
|
+
invalidEvidence.push(item);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (commands.length === 0 || evidence.length === 0 || toolResults.length === 0 || invalidEvidence.length > 0 || records.length !== evidence.length || typeof verification?.passed !== "boolean") {
|
|
1899
|
+
return fail({ code: "verification-required", invalidEvidence, commands, evidence, toolResults, reason: "verify \u9700\u8981\u975E\u7A7A commands\u3001workspace \u5185\u7684\u666E\u901A evidence \u6587\u4EF6\u3001toolResults\uFF0C\u4EE5\u53CA\u660E\u786E\u7684 passed\u3002" });
|
|
1900
|
+
}
|
|
1901
|
+
state.verification = {
|
|
1902
|
+
commands,
|
|
1903
|
+
evidence: records,
|
|
1904
|
+
toolResults,
|
|
1905
|
+
passed: verification.passed,
|
|
1906
|
+
selfReported: false,
|
|
1907
|
+
executionBound: true,
|
|
1908
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1909
|
+
};
|
|
1910
|
+
if (args.node === "verify-standalone") {
|
|
1911
|
+
state.phase = "discuss";
|
|
1912
|
+
state.node = null;
|
|
1913
|
+
state.work = "active";
|
|
1914
|
+
state.blockReason = null;
|
|
1915
|
+
state.blockedFrom = null;
|
|
1916
|
+
} else if (verification.passed !== true) {
|
|
1917
|
+
state.node = "implement-revise";
|
|
1918
|
+
state.noteVerified = false;
|
|
1919
|
+
} else if (state.noteDecision === "mechanical") {
|
|
1920
|
+
state.noteVerified = true;
|
|
1921
|
+
state.noteFinalized = true;
|
|
1922
|
+
commitHint = "\u5EFA\u8BAE\u63D0\u4EA4\u5B9E\u73B0";
|
|
1923
|
+
state.phase = "discuss";
|
|
1924
|
+
state.node = null;
|
|
1925
|
+
state.work = "active";
|
|
1926
|
+
state.blockReason = null;
|
|
1927
|
+
state.blockedFrom = null;
|
|
1928
|
+
state.task = null;
|
|
1929
|
+
state.planReady = false;
|
|
1930
|
+
} else {
|
|
1931
|
+
state.phase = "orchestrating";
|
|
1932
|
+
state.node = "note-lifecycle";
|
|
1933
|
+
state.work = "active";
|
|
1934
|
+
state.noteVerified = false;
|
|
1935
|
+
state.noteFinalized = false;
|
|
1936
|
+
state.noteFinalizeRecovery = [];
|
|
1937
|
+
}
|
|
1938
|
+
} else if (args.node === "fix-bug") {
|
|
1939
|
+
state.phase = "discuss";
|
|
1940
|
+
state.node = null;
|
|
1941
|
+
state.work = "active";
|
|
1942
|
+
state.blockReason = null;
|
|
1943
|
+
state.blockedFrom = null;
|
|
1944
|
+
state.task = null;
|
|
1945
|
+
state.planReady = false;
|
|
1946
|
+
} else if (args.node === "note-lifecycle") {
|
|
1947
|
+
if (state.noteVerified !== true) {
|
|
1948
|
+
return fail({ code: "note-lifecycle-incomplete", reason: "note-lifecycle \u9700\u8981\u5148 gate_note_finalize + gate_note_verify\u3002" });
|
|
1949
|
+
}
|
|
1950
|
+
commitHint = "\u5EFA\u8BAE\u63D0\u4EA4\u672C\u6B21\u5B9E\u73B0\u4E0E Note";
|
|
1951
|
+
state.phase = "discuss";
|
|
1952
|
+
state.node = null;
|
|
1953
|
+
state.work = "active";
|
|
1954
|
+
state.blockReason = null;
|
|
1955
|
+
state.blockedFrom = null;
|
|
1956
|
+
state.task = null;
|
|
1957
|
+
state.planReady = false;
|
|
1958
|
+
}
|
|
1959
|
+
store.write(gate.root, exec.agent, state);
|
|
1960
|
+
const next = normalizeState(state);
|
|
1961
|
+
const procedure = procedureFor(next, { reviewLimit });
|
|
1962
|
+
return { ok: true, phase: state.phase, node: state.node, rounds: state.rounds, blockReason: state.blockReason, verification: state.verification, procedure, method: procedure.method, state: next, ...commitHint === null ? {} : { commitHint } };
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// src/tools/status.ts
|
|
1968
|
+
function registerStatus(ctx, deps) {
|
|
1969
|
+
const { store, reviewLimit, wake } = deps;
|
|
1970
|
+
ctx.tools.register({
|
|
1971
|
+
name: "gate_status",
|
|
1972
|
+
description: "\u8BFB\u53D6 Gate \u5F53\u524D\u72B6\u6001\u4E0E\u5F53\u524D procedure\uFF08root\uFF09\u3002\u53EA\u8BFB\uFF1B\u6D3E\u53D1 worker \u524D\u3001\u63A8\u8FDB\u8282\u70B9\u524D\u3001compaction \u540E\u3001\u5931\u8D25\u540E\u6062\u590D\u65F6\u4F7F\u7528\u3002worker \u8C03\u7528\u8FD4\u56DE root \u72B6\u6001\u4F46\u4E0D\u8FD4\u56DE root procedure\u3002",
|
|
1973
|
+
parameters: { type: "object", additionalProperties: false, properties: {} },
|
|
1974
|
+
output: {
|
|
1975
|
+
schema: { type: "object", additionalProperties: true },
|
|
1976
|
+
render: (_args, value) => [{ type: "text", text: typeof value?.text === "string" ? value.text : JSON.stringify(value) }]
|
|
1977
|
+
},
|
|
1978
|
+
async execute(_args, exec) {
|
|
1979
|
+
const root = rootAgentOf(ctx, exec.agent);
|
|
1980
|
+
if (exec.agent?.session?.header?.origin === "subagent" && root?.id === exec.agent?.id) {
|
|
1981
|
+
return { ok: false, code: "root-unresolved", reason: "\u65E0\u6CD5\u89E3\u6790 worker \u7684 root session\uFF1B\u4E0D\u8FD4\u56DE worker \u81EA\u8EAB\u7684\u72B6\u6001\u3002" };
|
|
1982
|
+
}
|
|
1983
|
+
if (root?.session === void 0) {
|
|
1984
|
+
return { ok: false, code: "root-unresolved", reason: "root session \u4E0D\u5B8C\u6574\uFF1B\u65E0\u6CD5\u8BFB\u53D6 Gate \u72B6\u6001\u3002" };
|
|
1985
|
+
}
|
|
1986
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
1987
|
+
if (gate.root === void 0) return { ok: false, code: "state-unavailable", reason: "\u65E0\u6CD5\u786E\u5B9A\u5DE5\u4F5C\u533A\u3002" };
|
|
1988
|
+
const state = store.read(gate.root, root);
|
|
1989
|
+
if (root?.id !== exec.agent?.id) {
|
|
1990
|
+
return { ok: true, procedure: null, method: null, text: statusText(state, reviewLimit) };
|
|
1991
|
+
}
|
|
1992
|
+
const procedure = procedureFor(state, { reviewLimit });
|
|
1993
|
+
const includeRounds = !procedure.hasRounds;
|
|
1994
|
+
return {
|
|
1995
|
+
ok: true,
|
|
1996
|
+
procedure,
|
|
1997
|
+
method: procedure.method,
|
|
1998
|
+
text: procedureResultText(procedure, statusText(state, reviewLimit, { includeRounds }))
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/tools/notes.ts
|
|
2005
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2006
|
+
import path10 from "node:path";
|
|
2007
|
+
function registerNoteTools(ctx, deps) {
|
|
2008
|
+
const { store, reviewLimit, wake } = deps;
|
|
2009
|
+
ctx.tools.register({
|
|
2010
|
+
name: "gate_note_write",
|
|
2011
|
+
description: "\u5199 proposed Agent Note\uFF0C\u6216\u5728 planning \u539F\u5730\u66F4\u65B0\u5DF2\u5B58\u5728\u7684 implemented Note\u3002\u4EC5 planning worker\uFF1B\u901A\u7528 write/edit/shell \u88AB\u9650\u5236\u3002",
|
|
2012
|
+
parameters: {
|
|
2013
|
+
type: "object",
|
|
2014
|
+
additionalProperties: false,
|
|
2015
|
+
properties: {
|
|
2016
|
+
path: { type: "string", description: "\u76F8\u5BF9 .agents/notes \u7684\u8DEF\u5F84\uFF1Bproposed/ \u53EF\u65B0\u5EFA\uFF0Cimplemented/ \u53EA\u80FD\u7F16\u8F91\u5DF2\u5B58\u5728\u6587\u4EF6\u3002" },
|
|
2017
|
+
content: { type: "string" }
|
|
2018
|
+
},
|
|
2019
|
+
required: ["path", "content"]
|
|
2020
|
+
},
|
|
2021
|
+
output: {
|
|
2022
|
+
schema: { type: "object", additionalProperties: true },
|
|
2023
|
+
render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }]
|
|
2024
|
+
},
|
|
2025
|
+
async execute(args, exec) {
|
|
2026
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
2027
|
+
const effective = effectiveState(ctx, exec.agent, gate, store);
|
|
2028
|
+
if (!effective.worker || !["plan", "plan-revise"].includes(effective.state.node ?? "")) {
|
|
2029
|
+
return { ok: false, code: "planning-only", reason: "gate_note_write \u53EA\u5141\u8BB8 planning worker \u5728 plan/plan-revise \u8282\u70B9\u4F7F\u7528\u3002" };
|
|
2030
|
+
}
|
|
2031
|
+
let relative = String(args.path ?? "").replaceAll("\\", "/").replace(/^\.?\//u, "");
|
|
2032
|
+
if (relative === "" || relative.includes("..") || path10.isAbsolute(relative) || !relative.endsWith(".md")) {
|
|
2033
|
+
return { ok: false, code: "invalid-path" };
|
|
2034
|
+
}
|
|
2035
|
+
let lifecycle = "proposed";
|
|
2036
|
+
if (relative.startsWith("proposed/")) relative = relative.slice("proposed/".length);
|
|
2037
|
+
else if (relative.startsWith("implemented/")) {
|
|
2038
|
+
lifecycle = "implemented";
|
|
2039
|
+
relative = relative.slice("implemented/".length);
|
|
2040
|
+
}
|
|
2041
|
+
if (!/^(?:feature|bug-fix|simplification|architecture|process|testing)\/\d{4}-\d{2}-\d{2}-.+\.md$/u.test(relative)) {
|
|
2042
|
+
return { ok: false, code: "invalid-path", reason: "\u9700\u8981 <class>/yyyy-mm-dd-topic.md\uFF0C\u6216 proposed|implemented/<class>/...\u3002" };
|
|
2043
|
+
}
|
|
2044
|
+
const base = path10.join(gate.root, ".agents", "notes", String(lifecycle));
|
|
2045
|
+
const absolute = path10.resolve(base, relative);
|
|
2046
|
+
if (!isInside(base, absolute)) return { ok: false, code: "invalid-path" };
|
|
2047
|
+
if (pathHasSymlink(base, absolute)) return { ok: false, code: "invalid-path" };
|
|
2048
|
+
if (lifecycle === "implemented" && !existsSync5(absolute)) {
|
|
2049
|
+
return { ok: false, code: "implemented-must-exist", reason: "implemented Note \u53EA\u80FD\u539F\u5730\u7F16\u8F91\uFF0C\u4E0D\u80FD\u65B0\u5EFA\u3002" };
|
|
2050
|
+
}
|
|
2051
|
+
mkdirSync4(path10.dirname(absolute), { recursive: true });
|
|
2052
|
+
writeFileSync4(absolute, String(args.content ?? ""));
|
|
2053
|
+
return { ok: true, path: path10.relative(gate.root, absolute).replaceAll("\\", "/"), lifecycle };
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
ctx.tools.register({
|
|
2057
|
+
name: "gate_note_verify",
|
|
2058
|
+
description: "\u8FD0\u884C\u5185\u7F6E notes \u6821\u9A8C\uFF1Bnote-lifecycle \u4E2D\u901A\u8FC7\u540E\u8BBE\u7F6E noteVerified\u3002root-only\u3002",
|
|
2059
|
+
parameters: { type: "object", additionalProperties: false, properties: {} },
|
|
2060
|
+
output: {
|
|
2061
|
+
schema: { type: "object", additionalProperties: true },
|
|
2062
|
+
render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }],
|
|
2063
|
+
presentationMeta: (_args, value) => value?.state === void 0 ? { gate: {} } : { gate: { state: value.state } }
|
|
2064
|
+
},
|
|
2065
|
+
async execute(_args, exec) {
|
|
2066
|
+
const root = rootAgentOf(ctx, exec.agent);
|
|
2067
|
+
if (root?.id !== exec.agent?.id) return { ok: false, code: "root-only", reason: "gate_note_verify \u53EA\u80FD\u7531 root agent \u8C03\u7528\u3002" };
|
|
2068
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
2069
|
+
const state = store.read(gate.root, exec.agent);
|
|
2070
|
+
const recovered = recoverFinalize(gate.root, String(exec.agent?.session?.id ?? exec.agent?.id ?? "unknown"));
|
|
2071
|
+
if (recovered.length > 0) state.noteFinalizeRecovery = [...state.noteFinalizeRecovery, ...recovered];
|
|
2072
|
+
const check = runNotesScripts(gate.root);
|
|
2073
|
+
if (!check.ok) return { ...check, state: normalizeState(state) };
|
|
2074
|
+
if (state.node === "note-lifecycle") {
|
|
2075
|
+
if (state.noteFinalized !== true) return { ok: false, code: "note-lifecycle-incomplete", reason: "\u5148\u8C03\u7528 gate_note_finalize\u3002", state: normalizeState(state) };
|
|
2076
|
+
const implemented = state.notePaths.length > 0 && state.notePaths.every((entry) => entry.lifecycle === "implemented" && existsSync5(path10.join(gate.root, entry.path)));
|
|
2077
|
+
if (!implemented) return { ok: false, code: "note-lifecycle-incomplete", reason: "notePaths \u5C1A\u672A\u5168\u90E8\u8FDB\u5165 implemented\u3002", results: check.results, state: normalizeState(state) };
|
|
2078
|
+
state.noteVerified = true;
|
|
2079
|
+
store.write(gate.root, exec.agent, state);
|
|
2080
|
+
return { ok: true, noteVerified: true, results: check.results, state: normalizeState(state) };
|
|
2081
|
+
}
|
|
2082
|
+
return { ok: true, noteVerified: false, results: check.results, state: normalizeState(state) };
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
ctx.tools.register({
|
|
2086
|
+
name: "gate_note_finalize",
|
|
2087
|
+
description: "\u628A note-lifecycle \u4E2D\u7684 proposed Note \u79FB\u5230 implemented \u5E76\u66F4\u65B0\u5934\u5757\uFF1Broot-only\u3002",
|
|
2088
|
+
parameters: {
|
|
2089
|
+
type: "object",
|
|
2090
|
+
additionalProperties: false,
|
|
2091
|
+
properties: {
|
|
2092
|
+
decisionBodies: {
|
|
2093
|
+
type: "array",
|
|
2094
|
+
items: {
|
|
2095
|
+
type: "object",
|
|
2096
|
+
additionalProperties: false,
|
|
2097
|
+
properties: { path: { type: "string" }, body: { type: "string" } },
|
|
2098
|
+
required: ["path", "body"]
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
output: {
|
|
2104
|
+
schema: { type: "object", additionalProperties: true },
|
|
2105
|
+
render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }],
|
|
2106
|
+
presentationMeta: (_args, value) => value?.state === void 0 ? { gate: {} } : { gate: { state: value.state } }
|
|
2107
|
+
},
|
|
2108
|
+
async execute(args, exec) {
|
|
2109
|
+
const root = rootAgentOf(ctx, exec.agent);
|
|
2110
|
+
if (root?.id !== exec.agent?.id) return { ok: false, code: "root-only", reason: "gate_note_finalize \u53EA\u80FD\u7531 root agent \u8C03\u7528\u3002" };
|
|
2111
|
+
const gate = resolveGate(ctx, exec.agent);
|
|
2112
|
+
const state = store.read(gate.root, exec.agent);
|
|
2113
|
+
if (state.node !== "note-lifecycle") return { ok: false, code: "not-note-lifecycle", reason: "\u53EA\u5728 note-lifecycle \u8282\u70B9\u53EF\u7528\u3002" };
|
|
2114
|
+
if (state.noteDecision === "mechanical") {
|
|
2115
|
+
state.noteVerified = true;
|
|
2116
|
+
state.noteFinalized = true;
|
|
2117
|
+
store.write(gate.root, exec.agent, state);
|
|
2118
|
+
return { ok: true, skipped: true, state: normalizeState(state) };
|
|
2119
|
+
}
|
|
2120
|
+
const sessionId = String(exec.agent?.session?.id ?? exec.agent?.id ?? "unknown");
|
|
2121
|
+
const recovered = recoverFinalize(gate.root, sessionId);
|
|
2122
|
+
for (const entry of state.notePaths) {
|
|
2123
|
+
if (entry.lifecycle !== "proposed") continue;
|
|
2124
|
+
const source = path10.join(gate.root, entry.path);
|
|
2125
|
+
const targetRelative = entry.path.replaceAll("\\", "/").replace(".agents/notes/proposed/", ".agents/notes/implemented/");
|
|
2126
|
+
if (!existsSync5(source) && existsSync5(path10.join(gate.root, targetRelative))) {
|
|
2127
|
+
entry.path = targetRelative;
|
|
2128
|
+
entry.lifecycle = "implemented";
|
|
2129
|
+
recovered.push("adopted-target");
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
const moved = [];
|
|
2133
|
+
const decisionBodies = new Map((Array.isArray(args.decisionBodies) ? args.decisionBodies : []).map((item) => [String(item.path).replaceAll("\\", "/"), String(item.body ?? "")]));
|
|
2134
|
+
try {
|
|
2135
|
+
for (const entry of state.notePaths) {
|
|
2136
|
+
if (entry.lifecycle !== "proposed") continue;
|
|
2137
|
+
const original = entry.path.replaceAll("\\", "/");
|
|
2138
|
+
const relative = finalizeNote(gate.root, entry, sessionId, decisionBodies.get(original));
|
|
2139
|
+
moved.push({ from: original, to: relative });
|
|
2140
|
+
entry.path = relative;
|
|
2141
|
+
entry.lifecycle = "implemented";
|
|
2142
|
+
}
|
|
2143
|
+
const finalCheck = runNotesScripts(gate.root);
|
|
2144
|
+
if (!finalCheck.ok) throw new Error(`finalize/decisionBodies \u540E\u7684 notes \u6821\u9A8C\u5931\u8D25\uFF1A${JSON.stringify(finalCheck.results)}`);
|
|
2145
|
+
state.noteFinalizeRecovery = recovered;
|
|
2146
|
+
state.noteVerified = false;
|
|
2147
|
+
state.noteFinalized = true;
|
|
2148
|
+
store.write(gate.root, exec.agent, state);
|
|
2149
|
+
return { ok: true, moved, recovered, state: normalizeState(state) };
|
|
2150
|
+
} catch (error) {
|
|
2151
|
+
state.noteFinalizeRecovery = recovered;
|
|
2152
|
+
store.write(gate.root, exec.agent, state);
|
|
2153
|
+
return { ok: false, code: "note-finalize-failed", reason: error instanceof Error ? error.message : String(error), moved, recovered, state: normalizeState(state) };
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// src/tools/index.ts
|
|
2160
|
+
function registerTools(ctx, deps) {
|
|
2161
|
+
registerStartPhase(ctx, deps);
|
|
2162
|
+
registerRecord(ctx, deps);
|
|
2163
|
+
registerStatus(ctx, deps);
|
|
2164
|
+
registerNoteTools(ctx, deps);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// src/index.ts
|
|
2168
|
+
var inject = ["tools"];
|
|
2169
|
+
function apply(ctx, config = {}) {
|
|
2170
|
+
const reviewLimit = Number.isInteger(config?.reviewLimit) ? config.reviewLimit : DEFAULT_REVIEW_LIMIT;
|
|
2171
|
+
const store = createStateRuntime(ctx);
|
|
2172
|
+
const wake = makeWake(reviewLimit);
|
|
2173
|
+
const deps = { ctx, store, reviewLimit, wake };
|
|
2174
|
+
installSystemPrompt(ctx, deps);
|
|
2175
|
+
installGuard(ctx, deps);
|
|
2176
|
+
installRestrictions(ctx, deps);
|
|
2177
|
+
registerCommands(ctx, deps);
|
|
2178
|
+
registerTools(ctx, deps);
|
|
2179
|
+
}
|
|
2180
|
+
export {
|
|
2181
|
+
Config,
|
|
2182
|
+
DISCUSS_ORCHESTRATION,
|
|
2183
|
+
DOCS_ORCHESTRATION,
|
|
2184
|
+
FIX_BUG_ORCHESTRATION,
|
|
2185
|
+
GATE_RECORD_NODES,
|
|
2186
|
+
IMPLEMENT_ORCHESTRATION,
|
|
2187
|
+
NODE_METHODS,
|
|
2188
|
+
NOTE_LIFECYCLE_ORCHESTRATION,
|
|
2189
|
+
PHASES,
|
|
2190
|
+
PLAN_ORCHESTRATION,
|
|
2191
|
+
WORKER_NODES,
|
|
2192
|
+
apply,
|
|
2193
|
+
defaultState,
|
|
2194
|
+
inject,
|
|
2195
|
+
loadState,
|
|
2196
|
+
normalizeState,
|
|
2197
|
+
saveState,
|
|
2198
|
+
transformImplemented
|
|
2199
|
+
};
|