dsh-gate-agent 0.1.2 → 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/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 = `## Discuss
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
- 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`;
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:<\u8BF4\u660E> \u7528 noteDecision:'mechanical' \u4E14 notePaths \u4E3A\u7A7A\u3002
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
- 1. \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
138
- 2. \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
139
- 3. 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
140
- 4. \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`;
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
- ## discuss
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\u4E3B\u4EE3\u7406\u53EF\u81EA\u884C\u8BFB/\u5199\uFF1B\u7528\u6237\u6388\u6743\u540E\u8C03\u7528 gate_start_phase \u8FDB\u5165 plan/implement\u3002";
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 === "mechanical") {
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: textOrNull(source.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 === "mechanical" ? value : normalizeNotePaths(value);
672
+ return isMechanicalDecision(decision) ? value : normalizeNotePaths(value);
656
673
  }
657
674
  function validateNotePaths(root, value, decision) {
658
- if (decision === "mechanical") {
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
- if (decision === "new" && entry.lifecycle !== "proposed") return { code: "note-decision-mismatch", reason: "new \u53EA\u80FD proposed\u3002" };
671
- if (decision === "update" && entry.lifecycle !== "proposed") return { code: "note-decision-mismatch", reason: "update \u53EA\u80FD proposed\u3002" };
672
- if (decision === "in-place" && entry.lifecycle !== "implemented") return { code: "note-decision-mismatch", reason: "in-place \u53EA\u80FD implemented\u3002" };
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/runtime/gate.ts
885
- function isMechanical(task) {
886
- return /^mechanical:/iu.test(String(task ?? "").trim());
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\u901A\u7528 write/edit/shell \u88AB\u9650\u5236\u3002" : ""
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 writeTools = ["write", "edit", "apply_patch", "pwsh", "bash"];
1124
- if (worker) {
1125
- if (state.node === "implement" || state.node === "implement-revise" || state.node === "fix-bug") return void 0;
1126
- return { deny: writeTools };
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 (effective.worker) {
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 access = shellAccess(exec.arguments?.command ?? "", gate.workspace, cwd);
1227
- if (/\bgit(?:\.exe)?\b[^\n;&|]*\scommit\b/iu.test(String(exec.arguments?.command ?? "")) && effective.worker && !["implement", "implement-revise", "fix-bug"].includes(state.node ?? "")) {
1228
- return `worker ${state.node ?? "unknown"} \u4E0D\u5141\u8BB8 git commit`;
1229
- }
1230
- if (!access.write) return void 0;
1231
- if (access.outsideTargets.length > 0) return "\u5DE5\u4F5C\u533A\u5916\u5199\u5165\u88AB\u62D2\u7EDD";
1232
- if (effective.worker) return workerShellAllowed(state, access) ? void 0 : `worker ${state.node ?? "unknown"} \u4E0D\u5141\u8BB8\u5199\u8BE5\u76EE\u6807`;
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 = null;
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
- return { kind: "success", text: statusText(store.read(gate.root, root), reviewLimit) };
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\u6765\u81EA\u5BF9\u8BDD\u6216 ask_user_question\uFF0C\u4E0D\u8981\u6C42\u7528\u6237\u8F93\u5165 slash command\u3002",
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 = null;
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: ["new", "update", "in-place", "mechanical"] },
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\uFF08planning \u4EA7\u51FA\u7684\u5DE5\u4F5C\u9879\u6216 mechanical:<\u8BF4\u660E>\uFF09\u3002" });
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 (!["new", "update", "in-place", "mechanical"].includes(decision)) {
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 === "mechanical" ? [] : optionNotePaths;
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 === "mechanical";
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 (!["new", "update", "in-place", "mechanical"].includes(decision)) {
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 === "mechanical" ? [] : optionNotePaths;
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 === "mechanical";
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 === "mechanical") {
1854
- state.noteDecision = "mechanical";
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, args.noteDecision ?? state.noteDecision);
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 (["new", "update", "in-place"].includes(args.noteDecision)) state.noteDecision = args.noteDecision;
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 === "mechanical") {
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
- if (root?.id !== exec.agent?.id) {
1993
- return { ok: true, procedure: null, method: null, text: statusText(state, reviewLimit) };
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
- text: procedureResultText(procedure, statusText(state, reviewLimit, { includeRounds }))
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/shell \u88AB\u9650\u5236\u3002",
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 === "mechanical") {
2209
+ if (isMechanicalDecision(state.noteDecision)) {
2118
2210
  state.noteVerified = true;
2119
2211
  state.noteFinalized = true;
2120
2212
  store.write(gate.root, exec.agent, state);