dsh-gate-agent 0.1.1 → 0.1.3
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 +70 -8
- package/cordis.patch.yml +144 -144
- package/icon.svg +4 -4
- package/launchers/verify-notes.mjs +20 -20
- package/launchers/verify-notes.yml +34 -34
- package/lib/index.js +261 -169
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +34 -34
- package/skills/gate-code-review/SKILL.md +2 -2
- package/skills/gate-code-review/assets/worker-briefs/gate-implement-review.md +17 -17
- package/skills/gate-domain-modeling/CONTEXT-FORMAT.md +3 -3
- package/skills/gate-domain-modeling/GLOSSARY-FORMAT.md +3 -3
- package/skills/gate-fix-bug/assets/worker-briefs/fix-bug-review.md +20 -20
- package/skills/gate-fix-bug/assets/worker-briefs/gate-fix-bug.md +30 -30
- package/skills/gate-implement/SKILL.md +78 -78
- package/skills/gate-implement/assets/worker-briefs/gate-enter-implement.md +22 -22
- package/skills/gate-plan-review/assets/worker-briefs/plan-review.md +17 -17
- package/skills/gate-planning/assets/worker-briefs/gate-enter-plan.md +24 -24
- package/skills/gate-planning/proposal.md +49 -49
- package/skills/gate-user-verify/assets/worker-briefs/gate-code-verify.md +32 -32
- package/skills/gate-verify/SKILL.md +71 -71
- package/skills/gate-verify/assets/worker-briefs/gate-verify.md +21 -21
- package/skills/gate-write-agent-notes/SKILL.md +1 -1
- package/skills/gate-write-agent-notes/scripts/verify-agent-note-lifecycle.ts +74 -74
- package/skills/gate-writing-for-agents/SKILL-MECHANICS.md +4 -4
package/lib/index.js
CHANGED
|
@@ -36,18 +36,36 @@ var Config = {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
// src/note-decisions.ts
|
|
40
|
+
var MECHANICAL_DECISION = "mechanical";
|
|
41
|
+
var NOTE_DECISIONS = ["new", "update", "in-place", MECHANICAL_DECISION];
|
|
42
|
+
function isNoteDecision(value) {
|
|
43
|
+
return typeof value === "string" && NOTE_DECISIONS.includes(value);
|
|
44
|
+
}
|
|
45
|
+
var NOTE_DECISION_LIFECYCLE = {
|
|
46
|
+
new: "proposed",
|
|
47
|
+
update: "proposed",
|
|
48
|
+
"in-place": "implemented",
|
|
49
|
+
mechanical: null
|
|
50
|
+
};
|
|
51
|
+
function isMechanicalDecision(value) {
|
|
52
|
+
return value === MECHANICAL_DECISION;
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
// src/protocol.ts
|
|
40
|
-
var DISCUSS_ORCHESTRATION = `##
|
|
56
|
+
var DISCUSS_ORCHESTRATION = `## discuss
|
|
41
57
|
|
|
42
58
|
discuss \u662F\u9ED8\u8BA4\u9636\u6BB5\uFF0C\u4E0D\u9700\u8981\u547D\u4EE4\u8FDB\u5165\u3002
|
|
43
59
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
\u8BA8\u8BBA\u4E0D\u662F\u88AB\u52A8\u8BBF\u8C08\uFF1B\u4F18\u5148\u81EA\u884C\u5206\u6790\u5DF2\u6709\u9879\u76EE\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u628A\u53EF\u5224\u65AD\u7684\u95EE\u9898\u4EA4\u7ED9\u7528\u6237\u3002\u4E0D\u8981\u8981\u6C42\u7528\u6237\u91CD\u590D\u5DF2\u6709\u4FE1\u606F\uFF0C\u4E5F\u4E0D\u8981\u6C42\u5728 discuss \u4E2D\u786E\u5B9A\u6240\u6709\u5B9E\u73B0\u7EC6\u8282\u3002
|
|
61
|
+
|
|
62
|
+
- \u5148\u8BFB\u76F8\u5173\u4EE3\u7801\u3001\u6587\u6863\u3001\u5386\u53F2 Note \u4E0E\u9879\u76EE\u7EA6\u675F\u3002
|
|
63
|
+
- \u660E\u786E\u5B9E\u9645\u95EE\u9898\u3001\u7EA6\u675F\u3001\u5DF2\u6709\u51B3\u5B9A\u4E0E\u51B2\u7A81\u3002
|
|
64
|
+
- \u7ED9\u51FA\u6709\u5B9E\u8D28\u5DEE\u5F02\u7684\u53EF\u884C\u65B9\u6848\u3001\u63A8\u8350\u4E0E\u5173\u952E\u6743\u8861\u3002
|
|
65
|
+
- \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
|
|
66
|
+
- \u4E0D\u521B\u5EFA\u63D0\u6848\u3001Note \u6216\u89C4\u683C\u6587\u4EF6\uFF1B\u6587\u4EF6\u9ED8\u8BA4\u53EA\u8BFB\uFF0C\u4E0D\u5199\u5DE5\u4F5C\u533A\u3002\u63D0\u4EA4\u5DF2\u6709\u6539\u52A8\uFF08git add/commit/push\uFF09\u4E0D\u53D7\u6587\u4EF6\u53EA\u8BFB\u5F71\u54CD\u3002
|
|
67
|
+
- \u6C47\u62A5\uFF1A\u5DF2\u89E3\u51B3\u4E8B\u9879\u3001\u5269\u4F59\u672A\u51B3\u4E8B\u9879\u3001\u5EFA\u8BAE\u7684\u4E0B\u4E00\u9636\u6BB5\u3002
|
|
68
|
+
- \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\u4E2D\u8981\u6C42\u7528\u6237\u518D\u8F93\u5165 slash command\u6216 ask_user_question \u4E2D\u7ED9\u51FA\uFF1B\u83B7\u5F97\u6388\u6743\u540E\u8FDB\u5165 plan/implement\u3002`;
|
|
51
69
|
var PLAN_ORCHESTRATION = `## /gate-enter-plan \u7F16\u6392
|
|
52
70
|
\u4E3B\u4EE3\u7406\u6D3E worker\uFF1Bgate \u4E0D\u6D3E\u3002
|
|
53
71
|
|
|
@@ -57,7 +75,7 @@ var PLAN_ORCHESTRATION = `## /gate-enter-plan \u7F16\u6392
|
|
|
57
75
|
\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
76
|
\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
77
|
\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
|
|
78
|
+
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 \u7528 noteDecision:'mechanical' \u4E14 notePaths \u4E3A\u7A7A\uFF1B\u4E0D\u9700\u8981 task \u524D\u7F00\u3002
|
|
61
79
|
|
|
62
80
|
## Step 2 \u2014 Plan Review
|
|
63
81
|
\u4E3B agent \u9996\u6B21\u8FDB\u5165 plan-review \u65F6\u52A0\u8F7D\u6280\u80FD gate-plan-review\uFF1B\u82E5\u672C\u4F1A\u8BDD\u5DF2\u52A0\u8F7D\u4E14\u5185\u5BB9\u4ECD\u5728\u4E0A\u6587\uFF0C\u540E\u7EED\u591A\u8F6E review \u76F4\u63A5\u590D\u7528\uFF0C\u4E0D\u8981\u91CD\u590D\u8C03\u7528 skill\u3002\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
|
|
@@ -134,10 +152,10 @@ var NODE_INFO = {
|
|
|
134
152
|
var NODE_METHODS = Object.fromEntries(Object.entries(NODE_INFO).map(([node, info]) => [node, info.method]));
|
|
135
153
|
var WORKER_NODES = new Set(GATE_RECORD_NODES.filter((node) => node !== "docs" && node !== "note-lifecycle"));
|
|
136
154
|
var DISPATCH_RULE = `## \u6280\u80FD\u52A0\u8F7D\u89C4\u5219
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
- \u672C\u4F1A\u8BDD\u5DF2\u7ECF\u901A\u8FC7 skill \u5DE5\u5177\u52A0\u8F7D\u8FC7\u7684\u6280\u80FD\uFF0C\u5185\u5BB9\u4ECD\u5728\u4E0A\u6587\u65F6\u76F4\u63A5\u590D\u7528\uFF0C\u4E0D\u8981\u91CD\u590D\u8C03\u7528 skill\u3002
|
|
156
|
+
- \u53EA\u6709\u4EE5\u4E0B\u60C5\u51B5\u624D\u91CD\u65B0\u52A0\u8F7D\uFF1A\u672C\u4F1A\u8BDD\u9996\u6B21\u8FDB\u5165\u8BE5\u6280\u80FD\uFF1Bcompaction \u540E\u5185\u5BB9\u5DF2\u4E0D\u5728\u4E0A\u4E0B\u6587\uFF1B\u6280\u80FD\u6587\u4EF6/\u7248\u672C\u53D1\u751F\u53D8\u5316\uFF1B\u7528\u6237\u660E\u786E\u8981\u6C42\u5237\u65B0\u3002
|
|
157
|
+
- gate_record / gate_status \u8FD4\u56DE\u65B0\u7684 procedure \u53EA\u4EE3\u8868 node\u3001\u8F6E\u6B21\u6216 findings \u53D8\u5316\uFF0C\u4E0D\u4EE3\u8868\u8981\u91CD\u65B0\u52A0\u8F7D\u6280\u80FD\u3002
|
|
158
|
+
- \u591A\u8F6E review/revise \u590D\u7528\u540C\u4E00\u4EFD\u6280\u80FD\u5185\u5BB9\uFF1B\u628A\u65B0\u7684 findings\u3001\u6587\u4EF6\u8303\u56F4\u548C\u8981\u6C42\u4F20\u7ED9 worker\uFF0C\u4E0D\u8981\u91CD\u65B0\u52A0\u8F7D\u6280\u80FD\u3002`;
|
|
141
159
|
var REVIEWING_CODE_PROCEDURE = `## /gate-implement-review \u7F16\u6392
|
|
142
160
|
\u4E3B agent \u9996\u6B21\u8FDB\u5165\u65F6\u52A0\u8F7D gate-code-review\uFF1B\u5DF2\u52A0\u8F7D\u5219\u590D\u7528\uFF0C\u4E0D\u8981\u91CD\u590D\u8C03\u7528 skill\u3002\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`;
|
|
143
161
|
var REVIEWING_PROPOSAL_PROCEDURE = `## /gate-propose-review \u7F16\u6392
|
|
@@ -174,8 +192,7 @@ var NODE_PROCEDURES = {
|
|
|
174
192
|
var GATE_METHODOLOGY = `## Gate \u65B9\u6CD5\u8BBA
|
|
175
193
|
\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 procedure\uFF0C\u5E76\u786E\u8BA4\u6280\u80FD\u5185\u5BB9\u662F\u5426\u4ECD\u5728\u4E0A\u4E0B\u6587\uFF1A\u9996\u6B21\u9700\u8981\u65F6\u52A0\u8F7D\uFF0C\u5DF2\u5728\u4E0A\u6587\u5219\u590D\u7528\uFF0C\u4E0D\u8981\u56E0 procedure \u66F4\u65B0\u91CD\u590D\u52A0\u8F7D\u3002
|
|
176
194
|
|
|
177
|
-
|
|
178
|
-
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
|
|
195
|
+
${DISCUSS_ORCHESTRATION}
|
|
179
196
|
|
|
180
197
|
## plan
|
|
181
198
|
\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
|
|
@@ -219,7 +236,7 @@ function procedureFor(state, { reviewLimit = DEFAULT_REVIEW_LIMIT } = {}) {
|
|
|
219
236
|
const method = NODE_METHODS[state.node ?? ""] ?? "\u65E0";
|
|
220
237
|
let text;
|
|
221
238
|
if (variant === "discuss") {
|
|
222
|
-
text = "discuss \u4E0D\u6D3E worker\uFF1B\
|
|
239
|
+
text = "discuss \u4E0D\u6D3E worker\uFF1B\u6587\u4EF6\u9ED8\u8BA4\u53EA\u8BFB\uFF0C\u63D0\u4EA4\u5DF2\u6709\u6539\u52A8\u4E0D\u53D7\u5F71\u54CD\uFF1B\u7528\u6237\u6388\u6743\u540E\u8C03\u7528 gate_start_phase \u8FDB\u5165 plan/implement\u3002";
|
|
223
240
|
} else if (variant === "blocked") {
|
|
224
241
|
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`;
|
|
225
242
|
} else if (variant === "complete") {
|
|
@@ -271,7 +288,7 @@ function statusText(state, reviewLimit = DEFAULT_REVIEW_LIMIT, { includeRounds =
|
|
|
271
288
|
`planReady\uFF1A${state.planReady ? "\u662F" : "\u5426"}`,
|
|
272
289
|
...includeRounds ? [`rounds\uFF1A${roundsText(state, reviewLimit)}`] : []
|
|
273
290
|
];
|
|
274
|
-
if (state.notePaths?.length > 0 || state.noteDecision
|
|
291
|
+
if (state.notePaths?.length > 0 || isMechanicalDecision(state.noteDecision)) {
|
|
275
292
|
lines.push(`notes\uFF1A${state.noteDecision ?? "\u65E0"} ${state.notePaths?.length ?? 0} \u7BC7\uFF0Cverified=${state.noteVerified === true ? "\u662F" : "\u5426"}`);
|
|
276
293
|
}
|
|
277
294
|
if (state.exhausted?.plan === true || state.exhausted?.code === true) {
|
|
@@ -356,7 +373,7 @@ function normalizeState(value) {
|
|
|
356
373
|
verification: source.verification !== null && typeof source.verification === "object" && !Array.isArray(source.verification) ? clone(source.verification) : null,
|
|
357
374
|
blockedFrom,
|
|
358
375
|
notePaths: Array.isArray(source.notePaths) ? source.notePaths.map((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) ? { ...entry } : entry) : [],
|
|
359
|
-
noteDecision:
|
|
376
|
+
noteDecision: isNoteDecision(source.noteDecision) ? source.noteDecision : null,
|
|
360
377
|
noteSearch: textOrNull(source.noteSearch),
|
|
361
378
|
noteVerified: source.noteVerified === true,
|
|
362
379
|
noteFinalized: source.noteFinalized === true,
|
|
@@ -652,10 +669,10 @@ function normalizeNotePaths(value) {
|
|
|
652
669
|
});
|
|
653
670
|
}
|
|
654
671
|
function notePathsForDecision(value, decision) {
|
|
655
|
-
return decision
|
|
672
|
+
return isMechanicalDecision(decision) ? value : normalizeNotePaths(value);
|
|
656
673
|
}
|
|
657
674
|
function validateNotePaths(root, value, decision) {
|
|
658
|
-
if (decision
|
|
675
|
+
if (isMechanicalDecision(decision)) {
|
|
659
676
|
if (!Array.isArray(value) || value.length !== 0) return { code: "note-paths-required", reason: "mechanical \u7684 notePaths \u5FC5\u987B\u4E3A\u7A7A\u3002" };
|
|
660
677
|
return void 0;
|
|
661
678
|
}
|
|
@@ -667,9 +684,10 @@ function validateNotePaths(root, value, decision) {
|
|
|
667
684
|
}
|
|
668
685
|
if (!NOTE_CLASSES.has(entry.class)) return { code: "invalid-note-class", class: entry.class };
|
|
669
686
|
if (!["proposed", "implemented"].includes(entry.lifecycle)) return { code: "invalid-note-lifecycle", lifecycle: entry.lifecycle };
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
|
|
687
|
+
const expectedLifecycle = NOTE_DECISION_LIFECYCLE[decision];
|
|
688
|
+
if (expectedLifecycle !== null && entry.lifecycle !== expectedLifecycle) {
|
|
689
|
+
return { code: "note-decision-mismatch", reason: `${decision} \u53EA\u80FD ${expectedLifecycle}\u3002` };
|
|
690
|
+
}
|
|
673
691
|
const normalized = entry.path.replaceAll("\\", "/");
|
|
674
692
|
if (normalized.startsWith("/") || normalized.includes("..")) return { code: "invalid-note-path", path: entry.path };
|
|
675
693
|
const parts = normalized.split("/");
|
|
@@ -881,10 +899,148 @@ function effectiveState(ctx, agent, gate, store) {
|
|
|
881
899
|
return { root, state: store.read(gate.root, root), worker: root?.id !== agent?.id };
|
|
882
900
|
}
|
|
883
901
|
|
|
884
|
-
// src/
|
|
885
|
-
|
|
886
|
-
|
|
902
|
+
// src/classify.ts
|
|
903
|
+
import path6 from "node:path";
|
|
904
|
+
function classifyPath(root, value, cwd = root) {
|
|
905
|
+
const absolute = path6.resolve(cwd, String(value ?? ""));
|
|
906
|
+
const relative = path6.relative(root, absolute).replaceAll("\\", "/");
|
|
907
|
+
const outside = relative === ".." || relative.startsWith("../") || path6.isAbsolute(relative);
|
|
908
|
+
return { absolute, relative, outside };
|
|
909
|
+
}
|
|
910
|
+
var GIT_VALUE_OPTIONS = /* @__PURE__ */ new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env"]);
|
|
911
|
+
var GIT_FILE_WRITE = /* @__PURE__ */ new Set(["checkout", "switch", "reset", "clean", "restore", "stash", "merge", "rebase", "cherry-pick", "apply", "pull"]);
|
|
912
|
+
var GIT_NON_WRITE = /* @__PURE__ */ new Set(["status", "add", "commit", "push"]);
|
|
913
|
+
function gitSubcommands(command) {
|
|
914
|
+
const subcommands = /* @__PURE__ */ new Set();
|
|
915
|
+
for (const segment of String(command).split(/&&|\|\||;|\r?\n|\|/u)) {
|
|
916
|
+
const tokens = [...segment.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/gu)].map((match) => match[1] ?? match[2] ?? match[3] ?? "");
|
|
917
|
+
const gitIndex = tokens.findIndex((token) => /^git(?:\.exe)?$/iu.test(token));
|
|
918
|
+
if (gitIndex < 0) continue;
|
|
919
|
+
let index = gitIndex + 1;
|
|
920
|
+
while (index < tokens.length) {
|
|
921
|
+
const token = tokens[index] ?? "";
|
|
922
|
+
if (token === "--") {
|
|
923
|
+
index += 1;
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
if (!token.startsWith("-")) break;
|
|
927
|
+
const option = token.split("=", 1)[0] ?? token;
|
|
928
|
+
if (GIT_VALUE_OPTIONS.has(option) && !token.includes("=")) index += 2;
|
|
929
|
+
else index += 1;
|
|
930
|
+
}
|
|
931
|
+
const subcommand = tokens[index]?.toLowerCase();
|
|
932
|
+
if (subcommand !== void 0) subcommands.add(subcommand);
|
|
933
|
+
}
|
|
934
|
+
return subcommands;
|
|
935
|
+
}
|
|
936
|
+
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;
|
|
937
|
+
function pathArgs(text) {
|
|
938
|
+
const beforePipe = String(text).split("|")[0] ?? "";
|
|
939
|
+
return [...beforePipe.matchAll(/(?:'([^']+)'|"([^"]+)"|(\S+))/gu)].map((match) => match[1] ?? match[2] ?? match[3]).filter((value) => value !== void 0 && !value.startsWith("-"));
|
|
940
|
+
}
|
|
941
|
+
function assignments(text) {
|
|
942
|
+
const map = /* @__PURE__ */ new Map();
|
|
943
|
+
for (const match of String(text).matchAll(/(?:^|[;\n])\s*\$?([A-Za-z_]\w*)\s*=\s*(?:'([^']*)'|"([^"]*)"|(\S+))/gu)) {
|
|
944
|
+
const key = match[1];
|
|
945
|
+
if (key !== void 0) map.set(key, match[2] ?? match[3] ?? match[4] ?? "");
|
|
946
|
+
}
|
|
947
|
+
return map;
|
|
948
|
+
}
|
|
949
|
+
function resolveToken(token, map) {
|
|
950
|
+
const variable = /^\$([A-Za-z_]\w*)$/u.exec(token);
|
|
951
|
+
if (variable === null) return token;
|
|
952
|
+
const key = variable[1];
|
|
953
|
+
return (key === void 0 ? token : map.get(key)) ?? token;
|
|
954
|
+
}
|
|
955
|
+
function redirectionTargets(text) {
|
|
956
|
+
return [...String(text).matchAll(/(?:^|\s)>{1,2}\s*(?:'([^']+)'|"([^"]+)"|([^\s;&|]+))/gu)].map((match) => match[1] ?? match[2] ?? match[3]).filter((value) => value !== void 0);
|
|
957
|
+
}
|
|
958
|
+
function pipeTargets(text) {
|
|
959
|
+
const targets = [];
|
|
960
|
+
for (const part of String(text).split("|").slice(1)) {
|
|
961
|
+
const body = part.replace(/^\s*(?:tee|Set-Content|Out-File|Add-Content)\b/iu, "");
|
|
962
|
+
if (body === part) continue;
|
|
963
|
+
for (const token of pathArgs(body)) targets.push(token);
|
|
964
|
+
}
|
|
965
|
+
return targets;
|
|
966
|
+
}
|
|
967
|
+
function addTarget(targets, token, map) {
|
|
968
|
+
if (token === void 0 || token === "") return;
|
|
969
|
+
if (token.startsWith("$")) {
|
|
970
|
+
const resolved = resolveToken(token, map);
|
|
971
|
+
if (resolved !== token) targets.push(resolved);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
targets.push(token);
|
|
975
|
+
}
|
|
976
|
+
function segmentAccess(text, root, cwd, map) {
|
|
977
|
+
const git = gitSubcommands(text);
|
|
978
|
+
if ([...git].some((command) => GIT_FILE_WRITE.has(command))) return { write: true, outsideTargets: [] };
|
|
979
|
+
if ([...git].some((command) => GIT_NON_WRITE.has(command))) return { write: false, outsideTargets: [] };
|
|
980
|
+
const raw = [];
|
|
981
|
+
const verb = WRITE_VERB.exec(text);
|
|
982
|
+
if (verb !== null) {
|
|
983
|
+
const tail = text.slice(verb.index + verb[0].length);
|
|
984
|
+
for (const token of pathArgs(tail)) addTarget(raw, token, map);
|
|
985
|
+
}
|
|
986
|
+
for (const token of redirectionTargets(text)) addTarget(raw, token, map);
|
|
987
|
+
for (const token of pipeTargets(text)) addTarget(raw, token, map);
|
|
988
|
+
const outsideTargets = [];
|
|
989
|
+
for (const value of raw) {
|
|
990
|
+
const target = classifyPath(root, path6.resolve(cwd, value));
|
|
991
|
+
if (target.outside) outsideTargets.push(target.absolute);
|
|
992
|
+
}
|
|
993
|
+
return { write: raw.length > 0 || WRITE_VERB.test(text), outsideTargets };
|
|
994
|
+
}
|
|
995
|
+
function segments(text) {
|
|
996
|
+
return String(text).split(/&&|\|\||;|\r?\n/u).map((part) => part.trim()).filter(Boolean);
|
|
997
|
+
}
|
|
998
|
+
function shellAccess(command, root, cwd = root) {
|
|
999
|
+
const map = assignments(command);
|
|
1000
|
+
const parts = segments(command).map((part) => segmentAccess(part, root, cwd, map)).filter((item) => item.write);
|
|
1001
|
+
return {
|
|
1002
|
+
write: parts.length > 0,
|
|
1003
|
+
outsideTargets: parts.flatMap((item) => item.outsideTargets ?? [])
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/access.ts
|
|
1008
|
+
function accessFor(state, worker) {
|
|
1009
|
+
if (state.phase === "blocked") {
|
|
1010
|
+
return { read: true, write: "none", noteWrite: false, shell: "read", repoWrite: worker ? "none" : "push" };
|
|
1011
|
+
}
|
|
1012
|
+
if (!worker) {
|
|
1013
|
+
if (state.phase === "discuss") {
|
|
1014
|
+
return { read: true, write: "none", noteWrite: false, shell: "read", repoWrite: "push" };
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
read: true,
|
|
1018
|
+
write: "workspace",
|
|
1019
|
+
noteWrite: state.node === "note-lifecycle",
|
|
1020
|
+
shell: "write",
|
|
1021
|
+
repoWrite: "push"
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
if (state.node === "plan" || state.node === "plan-revise") {
|
|
1025
|
+
return { read: true, write: "none", noteWrite: true, shell: "read", repoWrite: "none" };
|
|
1026
|
+
}
|
|
1027
|
+
if (state.node === "implement" || state.node === "implement-revise" || state.node === "fix-bug") {
|
|
1028
|
+
return { read: true, write: "workspace", noteWrite: false, shell: "write", repoWrite: "push" };
|
|
1029
|
+
}
|
|
1030
|
+
return { read: true, write: "none", noteWrite: false, shell: "read", repoWrite: "none" };
|
|
1031
|
+
}
|
|
1032
|
+
function accessText(access) {
|
|
1033
|
+
return `\u6743\u9650\uFF1Aread=${access.read ? "yes" : "no"} write=${access.write} noteWrite=${access.noteWrite ? "yes" : "no"} shell=${access.shell} repoWrite=${access.repoWrite}`;
|
|
1034
|
+
}
|
|
1035
|
+
function repoCommands(command) {
|
|
1036
|
+
const commands = /* @__PURE__ */ new Set();
|
|
1037
|
+
for (const subcommand of gitSubcommands(command)) {
|
|
1038
|
+
if (subcommand === "add" || subcommand === "commit" || subcommand === "push") commands.add(subcommand);
|
|
1039
|
+
}
|
|
1040
|
+
return commands;
|
|
887
1041
|
}
|
|
1042
|
+
|
|
1043
|
+
// src/runtime/gate.ts
|
|
888
1044
|
function enterPhase(ctx, agent, node, reviewLimit, store, current) {
|
|
889
1045
|
const gate = resolveGate(ctx, agent);
|
|
890
1046
|
const state = current ?? store.read(gate.root, agent);
|
|
@@ -947,6 +1103,8 @@ function makeWake(reviewLimit) {
|
|
|
947
1103
|
const procedure = procedureFor(state, { reviewLimit });
|
|
948
1104
|
wakeGateAgent(invocation, `${procedureBlock(procedure)}
|
|
949
1105
|
|
|
1106
|
+
${accessText(accessFor(state, false))}
|
|
1107
|
+
|
|
950
1108
|
${text}`, command);
|
|
951
1109
|
};
|
|
952
1110
|
}
|
|
@@ -957,7 +1115,7 @@ function workerProtocol(node) {
|
|
|
957
1115
|
"## Gate worker",
|
|
958
1116
|
"\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",
|
|
959
1117
|
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",
|
|
960
|
-
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
|
|
1118
|
+
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 write/edit/apply_patch \u548C\u4ED3\u5E93\u5199\u88AB\u62D2\u7EDD\uFF0C\u53EA\u8BFB shell \u53EF\u7528\u3002" : ""
|
|
961
1119
|
].filter(Boolean).join("\n\n");
|
|
962
1120
|
}
|
|
963
1121
|
var REVIEW_NODES = /* @__PURE__ */ new Set(["plan-review", "proposal-review", "code-review", "code-review-standalone"]);
|
|
@@ -1028,104 +1186,21 @@ function installSystemPrompt(ctx, deps) {
|
|
|
1028
1186
|
text: (context) => {
|
|
1029
1187
|
const gate = resolveGate(ctx, context.agent);
|
|
1030
1188
|
const effective = effectiveState(ctx, context.agent, gate, deps.store);
|
|
1031
|
-
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");
|
|
1189
|
+
if (effective.worker) return ["## Gate", "Gate worker\uFF1A\u53EA\u6267\u884C\u5F53\u524D node\uFF0C\u4E0D\u63A8\u8FDB phase\u3002", accessText(accessFor(effective.state, true)), workerProtocol(effective.state.node ?? "")].filter(Boolean).join("\n\n");
|
|
1032
1190
|
return GATE_METHODOLOGY;
|
|
1033
1191
|
}
|
|
1034
1192
|
});
|
|
1035
1193
|
});
|
|
1036
1194
|
}
|
|
1037
1195
|
|
|
1038
|
-
// src/classify.ts
|
|
1039
|
-
import path6 from "node:path";
|
|
1040
|
-
function classifyPath(root, value, cwd = root) {
|
|
1041
|
-
const absolute = path6.resolve(cwd, String(value ?? ""));
|
|
1042
|
-
const relative = path6.relative(root, absolute).replaceAll("\\", "/");
|
|
1043
|
-
const outside = relative === ".." || relative.startsWith("../") || path6.isAbsolute(relative);
|
|
1044
|
-
return { absolute, relative, outside };
|
|
1045
|
-
}
|
|
1046
|
-
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;
|
|
1047
|
-
function pathArgs(text) {
|
|
1048
|
-
const beforePipe = String(text).split("|")[0] ?? "";
|
|
1049
|
-
return [...beforePipe.matchAll(/(?:'([^']+)'|"([^"]+)"|(\S+))/gu)].map((match) => match[1] ?? match[2] ?? match[3]).filter((value) => value !== void 0 && !value.startsWith("-"));
|
|
1050
|
-
}
|
|
1051
|
-
function assignments(text) {
|
|
1052
|
-
const map = /* @__PURE__ */ new Map();
|
|
1053
|
-
for (const match of String(text).matchAll(/(?:^|[;\n])\s*\$?([A-Za-z_]\w*)\s*=\s*(?:'([^']*)'|"([^"]*)"|(\S+))/gu)) {
|
|
1054
|
-
const key = match[1];
|
|
1055
|
-
if (key !== void 0) map.set(key, match[2] ?? match[3] ?? match[4] ?? "");
|
|
1056
|
-
}
|
|
1057
|
-
return map;
|
|
1058
|
-
}
|
|
1059
|
-
function resolveToken(token, map) {
|
|
1060
|
-
const variable = /^\$([A-Za-z_]\w*)$/u.exec(token);
|
|
1061
|
-
if (variable === null) return token;
|
|
1062
|
-
const key = variable[1];
|
|
1063
|
-
return (key === void 0 ? token : map.get(key)) ?? token;
|
|
1064
|
-
}
|
|
1065
|
-
function redirectionTargets(text) {
|
|
1066
|
-
return [...String(text).matchAll(/(?:^|\s)>{1,2}\s*(?:'([^']+)'|"([^"]+)"|([^\s;&|]+))/gu)].map((match) => match[1] ?? match[2] ?? match[3]).filter((value) => value !== void 0);
|
|
1067
|
-
}
|
|
1068
|
-
function pipeTargets(text) {
|
|
1069
|
-
const targets = [];
|
|
1070
|
-
for (const part of String(text).split("|").slice(1)) {
|
|
1071
|
-
const body = part.replace(/^\s*(?:tee|Set-Content|Out-File|Add-Content)\b/iu, "");
|
|
1072
|
-
if (body === part) continue;
|
|
1073
|
-
for (const token of pathArgs(body)) targets.push(token);
|
|
1074
|
-
}
|
|
1075
|
-
return targets;
|
|
1076
|
-
}
|
|
1077
|
-
function addTarget(targets, token, map) {
|
|
1078
|
-
if (token === void 0 || token === "") return;
|
|
1079
|
-
if (token.startsWith("$")) {
|
|
1080
|
-
const resolved = resolveToken(token, map);
|
|
1081
|
-
if (resolved !== token) targets.push(resolved);
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
targets.push(token);
|
|
1085
|
-
}
|
|
1086
|
-
function segmentAccess(text, root, cwd, map) {
|
|
1087
|
-
if (/\bgit\s+(?:status|add|commit|push)\b/iu.test(text)) {
|
|
1088
|
-
return { write: false, outsideTargets: [] };
|
|
1089
|
-
}
|
|
1090
|
-
if (/\bgit\s+(?:checkout|reset|clean)\b/iu.test(text)) {
|
|
1091
|
-
return { write: true, outsideTargets: [] };
|
|
1092
|
-
}
|
|
1093
|
-
const raw = [];
|
|
1094
|
-
const verb = WRITE_VERB.exec(text);
|
|
1095
|
-
if (verb !== null) {
|
|
1096
|
-
const tail = text.slice(verb.index + verb[0].length);
|
|
1097
|
-
for (const token of pathArgs(tail)) addTarget(raw, token, map);
|
|
1098
|
-
}
|
|
1099
|
-
for (const token of redirectionTargets(text)) addTarget(raw, token, map);
|
|
1100
|
-
for (const token of pipeTargets(text)) addTarget(raw, token, map);
|
|
1101
|
-
const outsideTargets = [];
|
|
1102
|
-
for (const value of raw) {
|
|
1103
|
-
const target = classifyPath(root, path6.resolve(cwd, value));
|
|
1104
|
-
if (target.outside) outsideTargets.push(target.absolute);
|
|
1105
|
-
}
|
|
1106
|
-
return { write: raw.length > 0 || WRITE_VERB.test(text), outsideTargets };
|
|
1107
|
-
}
|
|
1108
|
-
function segments(text) {
|
|
1109
|
-
return String(text).split(/&&|\|\||;|\r?\n/u).map((part) => part.trim()).filter(Boolean);
|
|
1110
|
-
}
|
|
1111
|
-
function shellAccess(command, root, cwd = root) {
|
|
1112
|
-
const map = assignments(command);
|
|
1113
|
-
const parts = segments(command).map((part) => segmentAccess(part, root, cwd, map)).filter((item) => item.write);
|
|
1114
|
-
return {
|
|
1115
|
-
write: parts.length > 0,
|
|
1116
|
-
outsideTargets: parts.flatMap((item) => item.outsideTargets ?? [])
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
1196
|
// src/runtime/guards.ts
|
|
1121
1197
|
import path7 from "node:path";
|
|
1198
|
+
var DISCUSS_READ_ONLY = "discuss \u6587\u4EF6\u53EA\u8BFB\uFF1B\u8981\u4FEE\u6539\u8BF7\u5148\u53D6\u5F97\u7528\u6237\u6388\u6743\uFF0C\u7136\u540E gate_start_phase({node:'plan'|'implement', brief}) \u6216 /gate-enter-plan / /gate-enter-implement\u3002\u63D0\u4EA4\u5DF2\u6709\u6539\u52A8\u4E0D\u53D7\u5F71\u54CD\u3002";
|
|
1122
1199
|
function restrictionFilter(state, worker) {
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}
|
|
1128
|
-
return state.phase === "blocked" ? { deny: ["write", "edit", "apply_patch"] } : void 0;
|
|
1200
|
+
const access = accessFor(state, worker);
|
|
1201
|
+
const deny = [];
|
|
1202
|
+
if (access.write === "none") deny.push("write", "edit", "apply_patch");
|
|
1203
|
+
return deny.length > 0 ? { deny } : void 0;
|
|
1129
1204
|
}
|
|
1130
1205
|
var restrictionState = /* @__PURE__ */ new WeakMap();
|
|
1131
1206
|
function applyRestrictions(ctx, agent, store) {
|
|
@@ -1170,21 +1245,28 @@ function applyRestrictions(ctx, agent, store) {
|
|
|
1170
1245
|
} catch {
|
|
1171
1246
|
}
|
|
1172
1247
|
}
|
|
1173
|
-
function workerWriteAllowed(state, target) {
|
|
1174
|
-
if (state.phase !== "orchestrating") return false;
|
|
1175
|
-
if (state.node === "plan" || state.node === "plan-revise") return false;
|
|
1176
|
-
if (state.node === "implement" || state.node === "implement-revise" || state.node === "fix-bug") return true;
|
|
1177
|
-
return false;
|
|
1178
|
-
}
|
|
1179
|
-
function workerShellAllowed(state, access) {
|
|
1180
|
-
if (state.phase !== "orchestrating") return false;
|
|
1181
|
-
return state.node === "implement" || state.node === "implement-revise" || state.node === "fix-bug";
|
|
1182
|
-
}
|
|
1183
1248
|
function targetList(name, args) {
|
|
1184
1249
|
if (name !== "apply_patch") return [args?.file_path];
|
|
1185
1250
|
return [...String(args?.input ?? "").matchAll(/^\*\*\* (?:Add File|Update File|Delete File|Move to):\s*(.+)$/gmu)].map((match) => String(match[1] ?? "").trim());
|
|
1186
1251
|
}
|
|
1187
1252
|
var GATE_FROZEN_REASON = "Gate \u5DF2\u51BB\u7ED3\uFF1A\u5148 /gate-resume \u6216 /gate-return-discuss";
|
|
1253
|
+
function fileWriteDenied(state, effective) {
|
|
1254
|
+
if (state.phase === "blocked") return GATE_FROZEN_REASON;
|
|
1255
|
+
if (!effective.worker && state.phase === "discuss") return DISCUSS_READ_ONLY;
|
|
1256
|
+
if (effective.worker && (state.node === "plan" || state.node === "plan-revise")) return "planning worker \u53EA\u5141\u8BB8 gate_note_write\uFF1B\u4EA7\u54C1\u6587\u4EF6\u548C\u4ED3\u5E93\u5199\u88AB\u62D2\u7EDD\u3002";
|
|
1257
|
+
if (effective.worker) return `\u5F53\u524D ${state.node ?? "worker"} worker \u4E0D\u5141\u8BB8\u5199\u6587\u4EF6\uFF1Breview/verify worker \u53EA\u8BFB\u3002`;
|
|
1258
|
+
return "\u5F53\u524D\u89D2\u8272\u4E0D\u5141\u8BB8\u5199\u6587\u4EF6";
|
|
1259
|
+
}
|
|
1260
|
+
function shellWriteDenied(state, effective) {
|
|
1261
|
+
if (state.phase === "blocked") return GATE_FROZEN_REASON;
|
|
1262
|
+
if (!effective.worker && state.phase === "discuss") return DISCUSS_READ_ONLY;
|
|
1263
|
+
if (effective.worker) return `worker ${state.node ?? "unknown"} \u4E0D\u5141\u8BB8\u5199\u578B shell\u3002`;
|
|
1264
|
+
return "\u5F53\u524D\u89D2\u8272\u4E0D\u5141\u8BB8\u5199\u578B shell";
|
|
1265
|
+
}
|
|
1266
|
+
function repoDenied(state, effective, commands) {
|
|
1267
|
+
if (effective.worker) return `\u5F53\u524D ${state.node ?? "worker"} worker \u4E0D\u5141\u8BB8\u4ED3\u5E93\u5199\uFF08${commands}\uFF09\u3002`;
|
|
1268
|
+
return `\u5F53\u524D root \u4E0D\u5141\u8BB8\u4ED3\u5E93\u5199\uFF08${commands}\uFF09\u3002`;
|
|
1269
|
+
}
|
|
1188
1270
|
function gateDenyReason(ctx, exec, store) {
|
|
1189
1271
|
if (exec.name === "gate_record") {
|
|
1190
1272
|
const root = rootAgentOf(ctx, exec.agent);
|
|
@@ -1202,17 +1284,14 @@ function gateDenyReason(ctx, exec, store) {
|
|
|
1202
1284
|
const gate = resolveGate(ctx, exec.agent);
|
|
1203
1285
|
const effective = effectiveState(ctx, exec.agent, gate, store);
|
|
1204
1286
|
const state = effective.state;
|
|
1287
|
+
const access = accessFor(state, effective.worker);
|
|
1205
1288
|
const cwd = sessionCwd(ctx, exec.agent, gate.workspace);
|
|
1206
1289
|
const targets = targetList(exec.name, exec.arguments).filter((value) => typeof value === "string" && value !== "");
|
|
1207
1290
|
if (targets.length === 0) return void 0;
|
|
1208
1291
|
for (const value of targets) {
|
|
1209
1292
|
const target = classifyPath(gate.workspace, value, cwd);
|
|
1210
1293
|
if (target.outside) return "\u5DE5\u4F5C\u533A\u5916\u5199\u5165\u88AB\u62D2\u7EDD";
|
|
1211
|
-
if (
|
|
1212
|
-
if (!workerWriteAllowed(state, target)) return `worker ${state.node ?? "unknown"} \u4E0D\u5141\u8BB8\u5199\u8BE5\u76EE\u6807`;
|
|
1213
|
-
continue;
|
|
1214
|
-
}
|
|
1215
|
-
if (state.phase === "blocked") return GATE_FROZEN_REASON;
|
|
1294
|
+
if (access.write === "none") return fileWriteDenied(state, effective);
|
|
1216
1295
|
}
|
|
1217
1296
|
return void 0;
|
|
1218
1297
|
}
|
|
@@ -1220,16 +1299,18 @@ function gateDenyReason(ctx, exec, store) {
|
|
|
1220
1299
|
const gate = resolveGate(ctx, exec.agent);
|
|
1221
1300
|
const effective = effectiveState(ctx, exec.agent, gate, store);
|
|
1222
1301
|
const state = effective.state;
|
|
1302
|
+
const access = accessFor(state, effective.worker);
|
|
1223
1303
|
const base = sessionCwd(ctx, exec.agent, gate.workspace);
|
|
1224
1304
|
const requested = exec.arguments?.workdir;
|
|
1225
1305
|
const cwd = typeof requested === "string" && requested !== "" ? path7.resolve(base, requested) : base;
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
if (!
|
|
1231
|
-
if (
|
|
1232
|
-
if (
|
|
1306
|
+
const command = String(exec.arguments?.command ?? "");
|
|
1307
|
+
const repos = repoCommands(command);
|
|
1308
|
+
if (repos.size > 0 && access.repoWrite === "none") return repoDenied(state, effective, [...repos].join("/"));
|
|
1309
|
+
const shell = shellAccess(command, gate.workspace, cwd);
|
|
1310
|
+
if (!shell.write) return void 0;
|
|
1311
|
+
if (shell.outsideTargets.length > 0) return "\u5DE5\u4F5C\u533A\u5916\u5199\u5165\u88AB\u62D2\u7EDD";
|
|
1312
|
+
if (access.write === "none") return shellWriteDenied(state, effective);
|
|
1313
|
+
if (effective.worker && access.shell !== "write") return shellWriteDenied(state, effective);
|
|
1233
1314
|
if (state.phase === "blocked") return GATE_FROZEN_REASON;
|
|
1234
1315
|
return void 0;
|
|
1235
1316
|
}
|
|
@@ -1419,7 +1500,7 @@ ignore\uFF1A${projectGit.ignore}` };
|
|
|
1419
1500
|
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" };
|
|
1420
1501
|
state.task = brief;
|
|
1421
1502
|
state.planReady = false;
|
|
1422
|
-
state.noteDecision =
|
|
1503
|
+
state.noteDecision = MECHANICAL_DECISION;
|
|
1423
1504
|
state.notePaths = [];
|
|
1424
1505
|
state.noteSearch = null;
|
|
1425
1506
|
state.noteVerified = false;
|
|
@@ -1574,7 +1655,9 @@ ignore\uFF1A${projectGit.ignore}` };
|
|
|
1574
1655
|
register("gate-status", "\u67E5\u770B\u5F53\u524D gate \u72B6\u6001", async (invocation) => {
|
|
1575
1656
|
const gate = resolveGate(ctx, invocation.agent);
|
|
1576
1657
|
const root = rootAgentOf(ctx, invocation.agent);
|
|
1577
|
-
|
|
1658
|
+
const state = store.read(gate.root, root);
|
|
1659
|
+
const worker = root?.id !== invocation.agent?.id;
|
|
1660
|
+
return { kind: "success", text: [accessText(accessFor(state, worker)), statusText(state, reviewLimit)].join("\n\n") };
|
|
1578
1661
|
}, null);
|
|
1579
1662
|
register("gate-note-verify", "\u53EA\u8BFB\u68C0\u67E5 notes tree/format/lifecycle/archived", async (invocation) => {
|
|
1580
1663
|
const gate = resolveGate(ctx, invocation.agent);
|
|
@@ -1633,7 +1716,7 @@ function registerStartPhase(ctx, deps) {
|
|
|
1633
1716
|
const { store, reviewLimit, wake } = deps;
|
|
1634
1717
|
ctx.tools.register({
|
|
1635
1718
|
name: "gate_start_phase",
|
|
1636
|
-
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\
|
|
1719
|
+
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\u4EE5\u5728\u5BF9\u8BDD\u4E2D\u8981\u6C42\u7528\u6237\u518D\u8F93\u5165 slash command\u6216 ask_user_question \u4E2D\u7ED9\u51FA\u3002",
|
|
1637
1720
|
parameters: {
|
|
1638
1721
|
type: "object",
|
|
1639
1722
|
additionalProperties: false,
|
|
@@ -1661,7 +1744,7 @@ function registerStartPhase(ctx, deps) {
|
|
|
1661
1744
|
if (brief === "") return fail({ code: "brief-required", reason: "\u76F4\u63A5 implement \u9700\u8981\u4E00\u53E5\u8BF4\u660E\u3002" });
|
|
1662
1745
|
state.task = brief;
|
|
1663
1746
|
state.planReady = false;
|
|
1664
|
-
state.noteDecision =
|
|
1747
|
+
state.noteDecision = MECHANICAL_DECISION;
|
|
1665
1748
|
state.notePaths = [];
|
|
1666
1749
|
state.noteSearch = null;
|
|
1667
1750
|
state.noteVerified = false;
|
|
@@ -1679,6 +1762,7 @@ function registerStartPhase(ctx, deps) {
|
|
|
1679
1762
|
task: result.state.task,
|
|
1680
1763
|
procedure,
|
|
1681
1764
|
method: procedure.method,
|
|
1765
|
+
access: accessFor(normalizeState(result.state), false),
|
|
1682
1766
|
state: normalizeState(result.state)
|
|
1683
1767
|
};
|
|
1684
1768
|
}
|
|
@@ -1717,7 +1801,7 @@ function registerRecord(ctx, deps) {
|
|
|
1717
1801
|
required: ["path", "class", "lifecycle"]
|
|
1718
1802
|
}
|
|
1719
1803
|
},
|
|
1720
|
-
noteDecision: { type: "string", enum: [
|
|
1804
|
+
noteDecision: { type: "string", enum: [...NOTE_DECISIONS] },
|
|
1721
1805
|
noteSearch: { type: "string" },
|
|
1722
1806
|
verification: {
|
|
1723
1807
|
type: "object",
|
|
@@ -1753,7 +1837,7 @@ function registerRecord(ctx, deps) {
|
|
|
1753
1837
|
state.blockedFrom = null;
|
|
1754
1838
|
store.write(gate.root, exec.agent, state);
|
|
1755
1839
|
const procedure2 = procedureFor(state, { reviewLimit });
|
|
1756
|
-
return { ok: true, phase: state.phase, node: state.node, procedure: procedure2, method: procedure2.method, state: normalizeState(state) };
|
|
1840
|
+
return { ok: true, phase: state.phase, node: state.node, procedure: procedure2, method: procedure2.method, access: accessFor(normalizeState(state), false), state: normalizeState(state) };
|
|
1757
1841
|
}
|
|
1758
1842
|
if (state.phase !== "orchestrating" && state.phase !== "reviewing") {
|
|
1759
1843
|
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` });
|
|
@@ -1780,7 +1864,7 @@ function registerRecord(ctx, deps) {
|
|
|
1780
1864
|
state.blockReason = "needs-user-decision";
|
|
1781
1865
|
store.write(gate.root, exec.agent, state);
|
|
1782
1866
|
const procedure2 = procedureFor(state, { reviewLimit });
|
|
1783
|
-
return { ok: true, phase: state.phase, blockReason: state.blockReason, reason: args.discussionReason ?? null, procedure: procedure2, method: procedure2.method, state: normalizeState(state) };
|
|
1867
|
+
return { ok: true, phase: state.phase, blockReason: state.blockReason, reason: args.discussionReason ?? null, procedure: procedure2, method: procedure2.method, access: accessFor(normalizeState(state), false), state: normalizeState(state) };
|
|
1784
1868
|
}
|
|
1785
1869
|
if (args.node === "proposal-review") {
|
|
1786
1870
|
state.phase = "discuss";
|
|
@@ -1795,41 +1879,35 @@ function registerRecord(ctx, deps) {
|
|
|
1795
1879
|
state.blockReason = null;
|
|
1796
1880
|
} else if (args.node === "plan") {
|
|
1797
1881
|
if (typeof args.task !== "string" || args.task.trim() === "") {
|
|
1798
|
-
return fail({ code: "task-required", reason: "node=plan \u5FC5\u987B\u643A\u5E26 task\
|
|
1882
|
+
return fail({ code: "task-required", reason: "node=plan \u5FC5\u987B\u643A\u5E26 task\uFF08\u5DE5\u4F5C\u9879\u7B80\u8FF0\uFF1Bmechanical \u7528 noteDecision \u8868\u793A\uFF09\u3002" });
|
|
1799
1883
|
}
|
|
1800
1884
|
const decision = args.noteDecision;
|
|
1801
|
-
if (!
|
|
1885
|
+
if (!isNoteDecision(decision)) {
|
|
1802
1886
|
return fail({ code: "note-decision-required", reason: "node=plan \u9700\u8981 noteDecision\uFF08new/update/in-place/mechanical\uFF09\u3002" });
|
|
1803
1887
|
}
|
|
1804
|
-
if (decision === "mechanical" && !isMechanical(args.task)) {
|
|
1805
|
-
return fail({ code: "mechanical-required", reason: "mechanical \u53EA\u5141\u8BB8 task \u4EE5 mechanical: \u5F00\u5934\u3002" });
|
|
1806
|
-
}
|
|
1807
1888
|
const optionNotePaths = notePathsForDecision(args.notePaths, decision);
|
|
1808
1889
|
const noteError = validateNotePaths(gate.root, optionNotePaths, decision);
|
|
1809
1890
|
if (noteError !== void 0) return fail(noteError);
|
|
1810
1891
|
state.task = args.task.trim();
|
|
1811
|
-
state.notePaths = decision
|
|
1892
|
+
state.notePaths = isMechanicalDecision(decision) ? [] : optionNotePaths;
|
|
1812
1893
|
state.noteDecision = decision;
|
|
1813
1894
|
state.noteSearch = typeof args.noteSearch === "string" ? args.noteSearch : null;
|
|
1814
|
-
state.noteVerified = decision
|
|
1895
|
+
state.noteVerified = isMechanicalDecision(decision);
|
|
1815
1896
|
state.node = "plan-review";
|
|
1816
1897
|
} else if (args.node === "plan-revise") {
|
|
1817
1898
|
if (args.noteDecision !== void 0 || args.notePaths !== void 0) {
|
|
1818
1899
|
const decision = args.noteDecision ?? state.noteDecision;
|
|
1819
|
-
if (!
|
|
1900
|
+
if (!isNoteDecision(decision)) {
|
|
1820
1901
|
return fail({ code: "note-decision-required", reason: "plan-revise \u9700\u8981\u5408\u6CD5 noteDecision\u3002" });
|
|
1821
1902
|
}
|
|
1822
|
-
if (decision === "mechanical" && !isMechanical(state.task)) {
|
|
1823
|
-
return fail({ code: "mechanical-required", reason: "mechanical \u53EA\u5141\u8BB8 task \u4EE5 mechanical: \u5F00\u5934\u3002" });
|
|
1824
|
-
}
|
|
1825
1903
|
const requestedNotePaths = args.notePaths ?? state.notePaths;
|
|
1826
1904
|
const optionNotePaths = notePathsForDecision(requestedNotePaths, decision);
|
|
1827
1905
|
const noteError = validateNotePaths(gate.root, optionNotePaths, decision);
|
|
1828
1906
|
if (noteError !== void 0) return fail(noteError);
|
|
1829
|
-
state.notePaths = decision
|
|
1907
|
+
state.notePaths = isMechanicalDecision(decision) ? [] : optionNotePaths;
|
|
1830
1908
|
state.noteDecision = decision;
|
|
1831
1909
|
if (typeof args.noteSearch === "string") state.noteSearch = args.noteSearch;
|
|
1832
|
-
state.noteVerified = decision
|
|
1910
|
+
state.noteVerified = isMechanicalDecision(decision);
|
|
1833
1911
|
}
|
|
1834
1912
|
state.node = "plan-review";
|
|
1835
1913
|
} else if (args.node === "plan-review") {
|
|
@@ -1850,18 +1928,29 @@ function registerRecord(ctx, deps) {
|
|
|
1850
1928
|
state.blockReason = "plan-review-rounds-exhausted";
|
|
1851
1929
|
} else state.node = "plan-revise";
|
|
1852
1930
|
} else if (args.node === "implement") {
|
|
1853
|
-
if (args.noteDecision
|
|
1854
|
-
|
|
1931
|
+
if (args.noteDecision !== void 0 && !isNoteDecision(args.noteDecision)) {
|
|
1932
|
+
return fail({ code: "note-decision-required", reason: "implement \u7684 noteDecision \u5FC5\u987B\u662F new/update/in-place/mechanical\u3002" });
|
|
1933
|
+
}
|
|
1934
|
+
const decision = args.noteDecision ?? state.noteDecision;
|
|
1935
|
+
if (decision !== null && !isNoteDecision(decision)) {
|
|
1936
|
+
return fail({ code: "note-decision-required", reason: "implement \u7684 noteDecision \u5FC5\u987B\u662F new/update/in-place/mechanical\u3002" });
|
|
1937
|
+
}
|
|
1938
|
+
if (isMechanicalDecision(args.noteDecision)) {
|
|
1939
|
+
state.noteDecision = MECHANICAL_DECISION;
|
|
1855
1940
|
state.notePaths = [];
|
|
1856
1941
|
state.noteVerified = true;
|
|
1942
|
+
} else if (args.noteDecision === void 0 && isMechanicalDecision(state.noteDecision)) {
|
|
1857
1943
|
} else {
|
|
1858
1944
|
if (Array.isArray(args.notePaths)) {
|
|
1945
|
+
if (!isNoteDecision(decision)) {
|
|
1946
|
+
return fail({ code: "note-decision-required", reason: "implement \u5199 notePaths \u9700\u8981\u5408\u6CD5 noteDecision\u3002" });
|
|
1947
|
+
}
|
|
1859
1948
|
const nextNotePaths = normalizeNotePaths(args.notePaths);
|
|
1860
|
-
const noteError = validateNotePaths(gate.root, nextNotePaths,
|
|
1949
|
+
const noteError = validateNotePaths(gate.root, nextNotePaths, decision);
|
|
1861
1950
|
if (noteError !== void 0) return fail(noteError);
|
|
1862
1951
|
state.notePaths = nextNotePaths;
|
|
1863
1952
|
}
|
|
1864
|
-
if (
|
|
1953
|
+
if (isNoteDecision(args.noteDecision) && !isMechanicalDecision(args.noteDecision)) state.noteDecision = args.noteDecision;
|
|
1865
1954
|
}
|
|
1866
1955
|
state.node = "code-review";
|
|
1867
1956
|
} else if (args.node === "implement-revise") state.node = "code-review";
|
|
@@ -1919,7 +2008,7 @@ function registerRecord(ctx, deps) {
|
|
|
1919
2008
|
} else if (verification.passed !== true) {
|
|
1920
2009
|
state.node = "implement-revise";
|
|
1921
2010
|
state.noteVerified = false;
|
|
1922
|
-
} else if (state.noteDecision
|
|
2011
|
+
} else if (isMechanicalDecision(state.noteDecision)) {
|
|
1923
2012
|
state.noteVerified = true;
|
|
1924
2013
|
state.noteFinalized = true;
|
|
1925
2014
|
commitHint = "\u5EFA\u8BAE\u63D0\u4EA4\u5B9E\u73B0";
|
|
@@ -1962,7 +2051,7 @@ function registerRecord(ctx, deps) {
|
|
|
1962
2051
|
store.write(gate.root, exec.agent, state);
|
|
1963
2052
|
const next = normalizeState(state);
|
|
1964
2053
|
const procedure = procedureFor(next, { reviewLimit });
|
|
1965
|
-
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 } };
|
|
2054
|
+
return { ok: true, phase: state.phase, node: state.node, rounds: state.rounds, blockReason: state.blockReason, verification: state.verification, procedure, method: procedure.method, access: accessFor(next, false), state: next, ...commitHint === null ? {} : { commitHint } };
|
|
1966
2055
|
}
|
|
1967
2056
|
});
|
|
1968
2057
|
}
|
|
@@ -1989,8 +2078,10 @@ function registerStatus(ctx, deps) {
|
|
|
1989
2078
|
const gate = resolveGate(ctx, exec.agent);
|
|
1990
2079
|
if (gate.root === void 0) return { ok: false, code: "state-unavailable", reason: "\u65E0\u6CD5\u786E\u5B9A\u5DE5\u4F5C\u533A\u3002" };
|
|
1991
2080
|
const state = store.read(gate.root, root);
|
|
1992
|
-
|
|
1993
|
-
|
|
2081
|
+
const worker = root?.id !== exec.agent?.id;
|
|
2082
|
+
const access = accessFor(state, worker);
|
|
2083
|
+
if (worker) {
|
|
2084
|
+
return { ok: true, procedure: null, method: null, access, text: [accessText(access), statusText(state, reviewLimit)].join("\n\n") };
|
|
1994
2085
|
}
|
|
1995
2086
|
const procedure = procedureFor(state, { reviewLimit });
|
|
1996
2087
|
const includeRounds = !procedure.hasRounds;
|
|
@@ -1998,7 +2089,8 @@ function registerStatus(ctx, deps) {
|
|
|
1998
2089
|
ok: true,
|
|
1999
2090
|
procedure,
|
|
2000
2091
|
method: procedure.method,
|
|
2001
|
-
|
|
2092
|
+
access,
|
|
2093
|
+
text: procedureResultText(procedure, [statusText(state, reviewLimit, { includeRounds }), accessText(access)].join("\n\n"))
|
|
2002
2094
|
};
|
|
2003
2095
|
}
|
|
2004
2096
|
});
|
|
@@ -2011,7 +2103,7 @@ function registerNoteTools(ctx, deps) {
|
|
|
2011
2103
|
const { store, reviewLimit, wake } = deps;
|
|
2012
2104
|
ctx.tools.register({
|
|
2013
2105
|
name: "gate_note_write",
|
|
2014
|
-
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/
|
|
2106
|
+
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/apply_patch \u548C\u4ED3\u5E93\u5199\u88AB\u62D2\u7EDD\uFF0C\u53EA\u8BFB shell \u53EF\u7528\u3002",
|
|
2015
2107
|
parameters: {
|
|
2016
2108
|
type: "object",
|
|
2017
2109
|
additionalProperties: false,
|
|
@@ -2114,7 +2206,7 @@ function registerNoteTools(ctx, deps) {
|
|
|
2114
2206
|
const gate = resolveGate(ctx, exec.agent);
|
|
2115
2207
|
const state = store.read(gate.root, exec.agent);
|
|
2116
2208
|
if (state.node !== "note-lifecycle") return { ok: false, code: "not-note-lifecycle", reason: "\u53EA\u5728 note-lifecycle \u8282\u70B9\u53EF\u7528\u3002" };
|
|
2117
|
-
if (state.noteDecision
|
|
2209
|
+
if (isMechanicalDecision(state.noteDecision)) {
|
|
2118
2210
|
state.noteVerified = true;
|
|
2119
2211
|
state.noteFinalized = true;
|
|
2120
2212
|
store.write(gate.root, exec.agent, state);
|