dsh-gate-agent 0.1.3 → 0.1.5

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
@@ -126,7 +126,7 @@ var FIX_BUG_ORCHESTRATION = `## /gate-fix-bug \u7F16\u6392
126
126
  ## Step 4 \u2014 Record
127
127
  \u5B8C\u6210\u540E\u8C03 gate_record({node:'fix-bug', workerSessionIds})\uFF0CworkerSessionIds \u5305\u542B fix \u4E0E review worker\uFF1Bgate \u4E0D\u518D\u81EA\u52A8\u63D0\u4EA4\u3002`;
128
128
  var DOCS_ORCHESTRATION = `## /gate-docs \u7F16\u6392
129
- \u4E0D\u52A0\u8F7D gate \u6280\u80FD\uFF1B\u8303\u56F4\u7531\u6A21\u578B\u6309\u9879\u76EE\u5B9E\u9645\u6587\u6863\u9700\u8981\u5224\u65AD\u3002
129
+ \u8303\u56F4\u7531\u6A21\u578B\u6309\u9879\u76EE\u5B9E\u9645\u6587\u6863\u9700\u8981\u5224\u65AD\u3002
130
130
  \u5B8C\u6210\u540E\u8C03 gate_record({node:'docs'}) \u56DE\u5230 discuss\u3002`;
131
131
  var NOTE_LIFECYCLE_ORCHESTRATION = `## Note lifecycle
132
132
  Verify \u5DF2 PASS\uFF1B\u5F53\u524D\u4E0D\u6D3E worker\uFF0C\u4E5F\u4E0D\u5199\u4E1A\u52A1\u4EE3\u7801\u3002
@@ -189,43 +189,80 @@ var NODE_PROCEDURES = {
189
189
  "proposal-review": REVIEWING_PROPOSAL_PROCEDURE,
190
190
  "verify-standalone": REVIEWING_VERIFY_PROCEDURE
191
191
  };
192
+ var METHODOLOGY_KEYS = [
193
+ "discuss",
194
+ "orchestrating/plan",
195
+ "orchestrating/plan-review",
196
+ "orchestrating/plan-revise",
197
+ "orchestrating/implement",
198
+ "orchestrating/code-review",
199
+ "orchestrating/implement-revise",
200
+ "orchestrating/verify",
201
+ "orchestrating/note-lifecycle",
202
+ "orchestrating/fix-bug",
203
+ "docs",
204
+ "reviewing/code-review-standalone",
205
+ "reviewing/proposal-review",
206
+ "reviewing/verify-standalone",
207
+ "blocked",
208
+ "complete",
209
+ "orchestrating",
210
+ "reviewing"
211
+ ];
212
+ var METHODOLOGY_KEY_SET = new Set(METHODOLOGY_KEYS);
213
+ function methodologyKeyFor(state) {
214
+ if (state.phase === "orchestrating" || state.phase === "reviewing") {
215
+ const key = state.node === null ? state.phase : `${state.phase}/${state.node}`;
216
+ return METHODOLOGY_KEY_SET.has(key) ? key : state.phase;
217
+ }
218
+ return state.phase;
219
+ }
192
220
  var GATE_METHODOLOGY = `## Gate \u65B9\u6CD5\u8BBA
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
221
+ \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\u4E0D\u786E\u5B9A\u6709\u54EA\u4E9B /gate-* \u547D\u4EE4\u6216\u5DE5\u5177\u7528\u9014\u65F6\uFF0C\u8C03\u7528\u53EA\u8BFB gate_help\u3002
194
222
 
195
223
  ${DISCUSS_ORCHESTRATION}
196
224
 
197
- ## plan
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
225
+ ## orchestrating/plan \xB7 orchestrating/plan-review \xB7 orchestrating/plan-revise
226
+ \u628A\u8BA8\u8BBA\u5171\u8BC6\u3001\u65E2\u6709\u51B3\u7B56\u4E0E\u7528\u6237\u9700\u6C42\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
199
227
 
200
- ## implement
201
- \u6309\u5DF2\u786E\u8BA4\u7684\u8BA1\u5212\u5B9E\u65BD\u5E76\u5B8C\u6210\u6D4B\u8BD5\uFF1B\u5B9E\u73B0\u540E\u505A\u72EC\u7ACB review\uFF0C\u518D\u7528\u53EF\u6267\u884C\u8BC1\u636E verify\uFF1B\u6700\u540E\u5B8C\u6210 Note lifecycle\u3002\u4E0D\u6269\u5927\u8303\u56F4\uFF0C\u4E0D\u5728\u5B9E\u73B0\u4E2D\u91CD\u65B0\u89C4\u5212\u3002
228
+ ## orchestrating/implement \xB7 orchestrating/implement-revise
229
+ \u6309\u5DF2\u786E\u8BA4\u7684\u8BA1\u5212\u6216\u8BA8\u8BBA\u540E\u7684\u5B9E\u65BD\u8BF4\u660E\u5B9E\u65BD\u5E76\u5B8C\u6210\u6D4B\u8BD5\uFF1B\u5B9E\u73B0\u540E\u505A\u72EC\u7ACB review\uFF0C\u518D\u7528\u53EF\u6267\u884C\u8BC1\u636E verify\uFF1B\u6700\u540E\u5B8C\u6210 Note lifecycle\u3002\u4E0D\u6269\u5927\u8303\u56F4\uFF0C\u4E0D\u5728\u5B9E\u73B0\u4E2D\u91CD\u65B0\u89C4\u5212\u3002
202
230
 
203
- ## code-review
231
+ ## orchestrating/code-review
204
232
  \u72EC\u7ACB\u5224\u65AD\u6539\u52A8\u662F\u5426\u6EE1\u8DB3\u5408\u540C\u4E0E\u6807\u51C6\uFF1B\u53EA\u62A5 findings\uFF0C\u4E0D\u4FEE\u4EE3\u7801\u3001\u4E0D\u66FF\u4EE3 verify\uFF1Bfindings \u8BB0\u5F55\u540E\u518D\u8FDB\u5165 revise\u3002
205
233
 
206
- ## verify
234
+ ## orchestrating/verify
207
235
  \u7528\u53EF\u6267\u884C\u8BC1\u636E\u5224\u5B9A PASS/FAIL\uFF1B\u4E0D\u4FEE\u6539\u4EA7\u54C1/\u6D4B\u8BD5\u8BA9\u7ED3\u679C\u53D8\u7EFF\uFF1BFAIL \u56DE implement-revise\u3002
208
236
 
209
- ## note-lifecycle
237
+ ## orchestrating/note-lifecycle
210
238
  \u5B8C\u6210 Note \u7684 finalize \u4E0E\u6821\u9A8C\uFF1B\u4E0D\u5199\u4E1A\u52A1\u4EE3\u7801\uFF1B\u68C0\u67E5\u662F\u5426\u4EA7\u751F\u5F52\u6863\u5019\u9009\uFF1B\u5B8C\u6210\u540E\u56DE discuss\u3002
211
239
 
212
- ## fix-bug
240
+ ## orchestrating/fix-bug
213
241
  \u5BF9\u5DF2\u6388\u6743\u7684\u5B9E\u73B0\u7F3A\u9677\uFF1A\u590D\u73B0\u3001\u56DE\u5F52\u8986\u76D6\u3001\u6839\u56E0\u3001\u6700\u5C0F\u4FEE\u590D\u3001\u72EC\u7ACB review\uFF1B\u4E0D\u505A\u65B0\u529F\u80FD\u3001\u4E0D\u6269\u5927\u8303\u56F4\uFF1B\u5B8C\u6210\u540E\u56DE discuss\u3002
214
242
 
215
243
  ## docs
216
244
  \u6587\u6863\u6CBB\u7406\uFF1B\u4E0D\u6539\u53D8\u529F\u80FD\u884C\u4E3A\uFF1B\u8303\u56F4\u7531\u6A21\u578B\u6309\u9879\u76EE\u5B9E\u9645\u6587\u6863\u9700\u8981\u5224\u65AD\uFF1B\u5B8C\u6210\u540E\u56DE discuss\u3002
217
245
 
218
- ## reviewing-code
246
+ ## reviewing/code-review-standalone
219
247
  \u53EA\u8BFB code review\uFF1A\u72EC\u7ACB\u5224\u65AD Standards \u4E0E Spec \u4E24\u8F74\uFF0C\u53EA\u8F93\u51FA findings\uFF0C\u4E0D\u4FEE\u4EE3\u7801\u3001\u4E0D\u66FF\u4EE3 verify\u3002
220
248
 
221
- ## reviewing-proposal
249
+ ## reviewing/proposal-review
222
250
  \u53EA\u8BFB\u63D0\u6848\u8BC4\u5BA1\uFF1A\u8986\u76D6 Standards\u3001Spec\u3001Architecture \u4E09\u8F74\uFF0C\u53EA\u8F93\u51FA\u5224\u65AD\uFF0C\u4E0D\u4FEE\u6539\u89C4\u5212\u3001\u4E0D\u81EA\u52A8 revise\u3002
223
251
 
224
- ## reviewing-verify
252
+ ## reviewing/verify-standalone
225
253
  \u53EA\u8BFB\u72EC\u7ACB\u9A8C\u6536\uFF1A\u4E0D\u6539\u4EE3\u7801\u3001\u4E0D\u66FF\u4EE3 implement\uFF0C\u53EA\u8F93\u51FA PASS/FAIL \u4E0E\u8BC1\u636E\u5224\u65AD\u3002
226
254
 
227
- ## blocked / complete
228
- blocked \u8868\u793A\u51BB\u7ED3\uFF1A\u7B49\u5F85 /gate-resume \u6216 /gate-return-discuss\u3002complete \u8868\u793A\u5F53\u524D\u4EFB\u52A1\u5B8C\u6210\uFF0C\u65B0\u4EFB\u52A1\u9700\u8981\u91CD\u65B0\u6388\u6743\u3002`;
255
+ ## blocked
256
+ blocked \u8868\u793A\u51BB\u7ED3\uFF1A\u7B49\u5F85 /gate-resume\u3002
257
+
258
+ ## complete
259
+ complete \u8868\u793A\u5F53\u524D\u4EFB\u52A1\u5B8C\u6210\uFF0C\u65B0\u4EFB\u52A1\u9700\u8981\u91CD\u65B0\u6388\u6743\u3002
260
+
261
+ ## orchestrating
262
+ \u5F53\u524D\u5904\u4E8E orchestrating \u4F46\u6CA1\u6709\u5339\u914D node\uFF1B\u8C03\u7528 gate_status \u91CD\u65B0\u8BFB\u53D6\uFF1B\u82E5\u4ECD\u4E0D\u4E00\u81F4\uFF0C\u56DE discuss \u4FEE\u590D Gate \u72B6\u6001\u3002
263
+
264
+ ## reviewing
265
+ \u5F53\u524D\u5904\u4E8E reviewing \u4F46\u6CA1\u6709\u5339\u914D node\uFF1B\u8C03\u7528 gate_status \u91CD\u65B0\u8BFB\u53D6\uFF1B\u82E5\u4ECD\u4E0D\u4E00\u81F4\uFF0C\u56DE discuss \u4FEE\u590D Gate \u72B6\u6001\u3002`;
229
266
  function protocolVariant(state) {
230
267
  if (state.phase === "orchestrating") return NODE_INFO[state.node ?? ""]?.variant ?? state.phase;
231
268
  if (state.phase === "reviewing") return NODE_INFO[state.node ?? ""]?.variant ?? "reviewing-code";
@@ -238,7 +275,7 @@ function procedureFor(state, { reviewLimit = DEFAULT_REVIEW_LIMIT } = {}) {
238
275
  if (variant === "discuss") {
239
276
  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";
240
277
  } else if (variant === "blocked") {
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`;
278
+ 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\u3002`;
242
279
  } else if (variant === "complete") {
243
280
  text = "Gate \u5DF2\u5B8C\u6210\uFF1B\u65B0\u4EFB\u52A1\u9700\u8981\u91CD\u65B0\u6388\u6743\u3002";
244
281
  } else if (variant === "docs") {
@@ -256,7 +293,7 @@ function procedureFor(state, { reviewLimit = DEFAULT_REVIEW_LIMIT } = {}) {
256
293
  text = [text, `## \u5F53\u524D\u8F6E\u6B21
257
294
  ${roundsText(state, reviewLimit)}`].filter(Boolean).join("\n\n");
258
295
  }
259
- return { phase: state.phase, node: state.node, method, text, hasRounds: variant === "plan" || variant === "implement" };
296
+ return { phase: state.phase, node: state.node, methodologyKey: methodologyKeyFor(state), method, text, hasRounds: variant === "plan" || variant === "implement" };
260
297
  }
261
298
  function procedureFailure(state, reviewLimit, fields) {
262
299
  const procedure = procedureFor(state, { reviewLimit });
@@ -301,6 +338,7 @@ function statusText(state, reviewLimit = DEFAULT_REVIEW_LIMIT, { includeRounds =
301
338
  }
302
339
  function procedureBlock(procedure) {
303
340
  return `\u5F53\u524D phase/node\uFF1A${procedure.phase} / ${procedure.node ?? "\u65E0"}
341
+ \u65B9\u6CD5\u8BBA\uFF1A${procedure.methodologyKey}
304
342
  \u65B9\u6CD5\uFF1A${procedure.method}
305
343
 
306
344
  ${procedure.text}`;
@@ -910,9 +948,9 @@ function classifyPath(root, value, cwd = root) {
910
948
  var GIT_VALUE_OPTIONS = /* @__PURE__ */ new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env"]);
911
949
  var GIT_FILE_WRITE = /* @__PURE__ */ new Set(["checkout", "switch", "reset", "clean", "restore", "stash", "merge", "rebase", "cherry-pick", "apply", "pull"]);
912
950
  var GIT_NON_WRITE = /* @__PURE__ */ new Set(["status", "add", "commit", "push"]);
913
- function gitSubcommands(command) {
951
+ function gitSubcommands(command2) {
914
952
  const subcommands = /* @__PURE__ */ new Set();
915
- for (const segment of String(command).split(/&&|\|\||;|\r?\n|\|/u)) {
953
+ for (const segment of String(command2).split(/&&|\|\||;|\r?\n|\|/u)) {
916
954
  const tokens = [...segment.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/gu)].map((match) => match[1] ?? match[2] ?? match[3] ?? "");
917
955
  const gitIndex = tokens.findIndex((token) => /^git(?:\.exe)?$/iu.test(token));
918
956
  if (gitIndex < 0) continue;
@@ -975,8 +1013,8 @@ function addTarget(targets, token, map) {
975
1013
  }
976
1014
  function segmentAccess(text, root, cwd, map) {
977
1015
  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: [] };
1016
+ if ([...git].some((command2) => GIT_FILE_WRITE.has(command2))) return { write: true, outsideTargets: [] };
1017
+ if ([...git].some((command2) => GIT_NON_WRITE.has(command2))) return { write: false, outsideTargets: [] };
980
1018
  const raw = [];
981
1019
  const verb = WRITE_VERB.exec(text);
982
1020
  if (verb !== null) {
@@ -995,9 +1033,9 @@ function segmentAccess(text, root, cwd, map) {
995
1033
  function segments(text) {
996
1034
  return String(text).split(/&&|\|\||;|\r?\n/u).map((part) => part.trim()).filter(Boolean);
997
1035
  }
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);
1036
+ function shellAccess(command2, root, cwd = root) {
1037
+ const map = assignments(command2);
1038
+ const parts = segments(command2).map((part) => segmentAccess(part, root, cwd, map)).filter((item) => item.write);
1001
1039
  return {
1002
1040
  write: parts.length > 0,
1003
1041
  outsideTargets: parts.flatMap((item) => item.outsideTargets ?? [])
@@ -1032,9 +1070,9 @@ function accessFor(state, worker) {
1032
1070
  function accessText(access) {
1033
1071
  return `\u6743\u9650\uFF1Aread=${access.read ? "yes" : "no"} write=${access.write} noteWrite=${access.noteWrite ? "yes" : "no"} shell=${access.shell} repoWrite=${access.repoWrite}`;
1034
1072
  }
1035
- function repoCommands(command) {
1073
+ function repoCommands(command2) {
1036
1074
  const commands = /* @__PURE__ */ new Set();
1037
- for (const subcommand of gitSubcommands(command)) {
1075
+ for (const subcommand of gitSubcommands(command2)) {
1038
1076
  if (subcommand === "add" || subcommand === "commit" || subcommand === "push") commands.add(subcommand);
1039
1077
  }
1040
1078
  return commands;
@@ -1085,7 +1123,7 @@ function enterPhase(ctx, agent, node, reviewLimit, store, current) {
1085
1123
  store.write(gate.root, agent, state);
1086
1124
  return { gate, state, reviewLimit };
1087
1125
  }
1088
- function wakeGateAgent(invocation, text, command) {
1126
+ function wakeGateAgent(invocation, text, command2) {
1089
1127
  const followup = invocation?.agent?.followup;
1090
1128
  if (typeof followup !== "function") return;
1091
1129
  try {
@@ -1093,19 +1131,19 @@ function wakeGateAgent(invocation, text, command) {
1093
1131
  id: randomUUID2(),
1094
1132
  role: "user",
1095
1133
  content: [{ type: "text", text }],
1096
- source: { kind: "gate-command", command }
1134
+ source: { kind: "gate-command", command: command2 }
1097
1135
  });
1098
1136
  } catch {
1099
1137
  }
1100
1138
  }
1101
1139
  function makeWake(reviewLimit) {
1102
- return (invocation, state, text, command) => {
1140
+ return (invocation, state, text, command2) => {
1103
1141
  const procedure = procedureFor(state, { reviewLimit });
1104
1142
  wakeGateAgent(invocation, `${procedureBlock(procedure)}
1105
1143
 
1106
1144
  ${accessText(accessFor(state, false))}
1107
1145
 
1108
- ${text}`, command);
1146
+ ${text}`, command2);
1109
1147
  };
1110
1148
  }
1111
1149
 
@@ -1249,7 +1287,7 @@ function targetList(name, args) {
1249
1287
  if (name !== "apply_patch") return [args?.file_path];
1250
1288
  return [...String(args?.input ?? "").matchAll(/^\*\*\* (?:Add File|Update File|Delete File|Move to):\s*(.+)$/gmu)].map((match) => String(match[1] ?? "").trim());
1251
1289
  }
1252
- var GATE_FROZEN_REASON = "Gate \u5DF2\u51BB\u7ED3\uFF1A\u5148 /gate-resume \u6216 /gate-return-discuss";
1290
+ var GATE_FROZEN_REASON = "Gate \u5DF2\u51BB\u7ED3\uFF1A\u5148 /gate-resume";
1253
1291
  function fileWriteDenied(state, effective) {
1254
1292
  if (state.phase === "blocked") return GATE_FROZEN_REASON;
1255
1293
  if (!effective.worker && state.phase === "discuss") return DISCUSS_READ_ONLY;
@@ -1303,10 +1341,10 @@ function gateDenyReason(ctx, exec, store) {
1303
1341
  const base = sessionCwd(ctx, exec.agent, gate.workspace);
1304
1342
  const requested = exec.arguments?.workdir;
1305
1343
  const cwd = typeof requested === "string" && requested !== "" ? path7.resolve(base, requested) : base;
1306
- const command = String(exec.arguments?.command ?? "");
1307
- const repos = repoCommands(command);
1344
+ const command2 = String(exec.arguments?.command ?? "");
1345
+ const repos = repoCommands(command2);
1308
1346
  if (repos.size > 0 && access.repoWrite === "none") return repoDenied(state, effective, [...repos].join("/"));
1309
- const shell = shellAccess(command, gate.workspace, cwd);
1347
+ const shell = shellAccess(command2, gate.workspace, cwd);
1310
1348
  if (!shell.write) return void 0;
1311
1349
  if (shell.outsideTargets.length > 0) return "\u5DE5\u4F5C\u533A\u5916\u5199\u5165\u88AB\u62D2\u7EDD";
1312
1350
  if (access.write === "none") return shellWriteDenied(state, effective);
@@ -1327,7 +1365,7 @@ function installRestrictions(ctx, deps) {
1327
1365
  }
1328
1366
 
1329
1367
  // src/commands.ts
1330
- import path9 from "node:path";
1368
+ import path10 from "node:path";
1331
1369
 
1332
1370
  // src/notes/provision.ts
1333
1371
  import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
@@ -1380,15 +1418,15 @@ function provisionProjectScripts(root) {
1380
1418
  if (pkg === null || typeof pkg !== "object" || Array.isArray(pkg)) throw new Error("package.json \u5FC5\u987B\u662F\u5BF9\u8C61");
1381
1419
  }
1382
1420
  pkg.scripts = pkg.scripts !== null && typeof pkg.scripts === "object" && !Array.isArray(pkg.scripts) ? pkg.scripts : {};
1383
- const command = "node .agents/gate/verify-notes.mjs";
1421
+ const command2 = "node .agents/gate/verify-notes.mjs";
1384
1422
  const existing = pkg.scripts["verify-notes"];
1385
- if (existing === void 0) pkg.scripts["verify-notes"] = command;
1423
+ if (existing === void 0) pkg.scripts["verify-notes"] = command2;
1386
1424
  writeFileSync3(packagePath, `${JSON.stringify(pkg, null, 2)}
1387
1425
  `);
1388
1426
  return {
1389
1427
  launcher: path8.relative(root, launcher).replaceAll("\\", "/"),
1390
1428
  package: path8.relative(root, packagePath).replaceAll("\\", "/"),
1391
- command: existing === void 0 ? command : String(existing),
1429
+ command: existing === void 0 ? command2 : String(existing),
1392
1430
  changed: existing === void 0
1393
1431
  };
1394
1432
  }
@@ -1401,6 +1439,7 @@ var BASE_GITIGNORE = `node_modules/
1401
1439
  Thumbs.db
1402
1440
  coverage/
1403
1441
  dist/
1442
+ .scratch/
1404
1443
  .agents/notes/.gate-finalize-*
1405
1444
  .agents/notes/.gate-validate-*
1406
1445
  .agents/notes/*.gate-backup
@@ -1448,50 +1487,243 @@ function provisionCi(root) {
1448
1487
  return { workflow: path8.relative(root, workflow).replaceAll("\\", "/"), scripts: path8.relative(root, ciDir).replaceAll("\\", "/"), changed: !existing };
1449
1488
  }
1450
1489
 
1490
+ // src/artifacts/policy.ts
1491
+ import { existsSync as existsSync4, lstatSync as lstatSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
1492
+ import path9 from "node:path";
1493
+ var ARTIFACT_POLICY_PATH = ".agents/gate/artifact-policy.json";
1494
+ var BASE_ARTIFACT_POLICY = {
1495
+ version: 1,
1496
+ artifacts: {
1497
+ handoff: { phases: ["*"], roles: ["root"], paths: [".scratch/handoff/**"] },
1498
+ prd: { phases: ["discuss"], roles: ["root"], paths: ["docs/prd/**", ".scratch/prd/**"] },
1499
+ research: {
1500
+ phases: ["discuss", "orchestrating/plan"],
1501
+ roles: ["root", "worker"],
1502
+ paths: ["docs/research/**", ".scratch/research/**"]
1503
+ }
1504
+ }
1505
+ };
1506
+ var PHASES2 = new Set(METHODOLOGY_KEYS);
1507
+ var RESERVED = [".agents/gate/artifact-policy.json", ".agents/notes/"];
1508
+ function normalizeArtifactPath(value) {
1509
+ return path9.posix.normalize(String(value).replaceAll("\\", "/").replace(/^\.\//u, ""));
1510
+ }
1511
+ function isReservedPath(value) {
1512
+ const normalized = value.toLowerCase();
1513
+ return RESERVED.some((prefix) => normalized === prefix || normalized.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`));
1514
+ }
1515
+ function artifactPolicyText(policy = BASE_ARTIFACT_POLICY) {
1516
+ return `${JSON.stringify(policy, null, 2)}
1517
+ `;
1518
+ }
1519
+ function provisionArtifactPolicy(root) {
1520
+ const file = path9.join(root, ARTIFACT_POLICY_PATH);
1521
+ if (pathHasSymlink(root, file)) throw new Error("artifact policy \u8DEF\u5F84\u5305\u542B symlink");
1522
+ mkdirSync4(path9.dirname(file), { recursive: true });
1523
+ if (existsSync4(file)) return { path: ARTIFACT_POLICY_PATH, status: "existing" };
1524
+ writeFileSync4(file, artifactPolicyText());
1525
+ return { path: ARTIFACT_POLICY_PATH, status: "created" };
1526
+ }
1527
+ function parseArtifactPolicy(value) {
1528
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return { error: "artifact policy \u5FC5\u987B\u662F\u5BF9\u8C61" };
1529
+ const source = value;
1530
+ if (source.version !== 1) return { error: "artifact policy version \u5FC5\u987B\u4E3A 1" };
1531
+ if (source.artifacts === null || typeof source.artifacts !== "object" || Array.isArray(source.artifacts)) {
1532
+ return { error: "artifact policy artifacts \u5FC5\u987B\u662F\u5BF9\u8C61" };
1533
+ }
1534
+ for (const key of Object.keys(source)) {
1535
+ if (key !== "version" && key !== "artifacts") return { error: `artifact policy \u672A\u77E5\u5B57\u6BB5\uFF1A${key}` };
1536
+ }
1537
+ const artifacts = {};
1538
+ for (const [kind, raw] of Object.entries(source.artifacts)) {
1539
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { error: `artifact ${kind} \u5FC5\u987B\u662F\u5BF9\u8C61` };
1540
+ const rule = raw;
1541
+ for (const key of Object.keys(rule)) {
1542
+ if (key !== "phases" && key !== "roles" && key !== "paths") return { error: `artifact ${kind} \u672A\u77E5\u5B57\u6BB5\uFF1A${key}` };
1543
+ }
1544
+ if (!Array.isArray(rule.phases) || rule.phases.length === 0 || !rule.phases.every((phase) => phase === "*" || typeof phase === "string" && PHASES2.has(phase))) {
1545
+ return { error: `artifact ${kind} phases \u975E\u6CD5` };
1546
+ }
1547
+ if (!Array.isArray(rule.roles) || rule.roles.length === 0 || !rule.roles.every((role) => role === "root" || role === "worker")) {
1548
+ return { error: `artifact ${kind} roles \u975E\u6CD5` };
1549
+ }
1550
+ if (!Array.isArray(rule.paths) || rule.paths.length === 0) return { error: `artifact ${kind} paths \u975E\u6CD5` };
1551
+ const paths = [];
1552
+ for (const item of rule.paths) {
1553
+ if (typeof item !== "string" || item.trim() === "") return { error: `artifact ${kind} path \u975E\u6CD5` };
1554
+ const normalized = normalizeArtifactPath(item);
1555
+ if (path9.isAbsolute(normalized) || normalized.split("/").includes("..")) return { error: `artifact ${kind} path \u8D8A\u754C\uFF1A${item}` };
1556
+ if (isReservedPath(normalized)) return { error: `artifact ${kind} path \u662F\u4FDD\u7559\u8DEF\u5F84\uFF1A${item}` };
1557
+ paths.push(normalized);
1558
+ }
1559
+ artifacts[kind] = { phases: [...rule.phases], roles: [...rule.roles], paths };
1560
+ }
1561
+ return { policy: { version: 1, artifacts } };
1562
+ }
1563
+ function loadArtifactPolicy(root) {
1564
+ const file = path9.join(root, ARTIFACT_POLICY_PATH);
1565
+ if (!existsSync4(file)) return { error: "artifact policy \u4E0D\u5B58\u5728\uFF1B\u5148\u8FD0\u884C /gate-init \u5E76\u586B\u5199 .agents/gate/artifact-policy.json" };
1566
+ if (lstatSync4(file).isSymbolicLink()) return { error: "artifact policy \u4E0D\u80FD\u662F symlink" };
1567
+ try {
1568
+ return parseArtifactPolicy(JSON.parse(readFileSync3(file, "utf8")));
1569
+ } catch {
1570
+ return { error: "artifact policy \u4E0D\u662F\u6709\u6548 JSON" };
1571
+ }
1572
+ }
1573
+ function segmentMatches(pattern, value) {
1574
+ const regex = new RegExp(`^${pattern.split("").map((char) => {
1575
+ if (char === "*") return "[^/]*";
1576
+ if (char === "?") return "[^/]";
1577
+ return char.replace(/[\\^$.*+?()[\]{}|]/gu, "\\$&");
1578
+ }).join("")}$`, "u");
1579
+ return regex.test(value);
1580
+ }
1581
+ function pathMatches(pattern, relative) {
1582
+ const patternParts = pattern.split("/").filter(Boolean);
1583
+ const valueParts = relative.split("/").filter(Boolean);
1584
+ const walk = (p, v) => {
1585
+ if (p === patternParts.length) return v === valueParts.length;
1586
+ const part = patternParts[p] ?? "";
1587
+ if (part === "**") return walk(p + 1, v) || v < valueParts.length && walk(p, v + 1);
1588
+ if (v >= valueParts.length) return false;
1589
+ if (!segmentMatches(part, valueParts[v] ?? "")) return false;
1590
+ return walk(p + 1, v + 1);
1591
+ };
1592
+ return walk(0, 0);
1593
+ }
1594
+ function artifactRuleAllows(policy, state, worker, kind) {
1595
+ if (state.phase === "blocked") return { error: "blocked \u4E0D\u5141\u8BB8 artifact \u5199\u5165" };
1596
+ const rule = policy.artifacts[kind];
1597
+ if (rule === void 0) return { error: `artifact kind \u672A\u6388\u6743\uFF1A${kind}` };
1598
+ const phaseKey = methodologyKeyFor(state);
1599
+ if (!rule.phases.includes("*") && !rule.phases.includes(phaseKey)) return { error: `\u5F53\u524D\u9636\u6BB5 ${phaseKey} \u4E0D\u5141\u8BB8 artifact ${kind}` };
1600
+ if (!rule.roles.includes(worker ? "worker" : "root")) return { error: `\u5F53\u524D\u89D2\u8272\u4E0D\u5141\u8BB8 artifact ${kind}` };
1601
+ return { rule };
1602
+ }
1603
+ function checkArtifactPath(root, rule, requested, kind) {
1604
+ const normalized = normalizeArtifactPath(requested);
1605
+ if (path9.isAbsolute(normalized) || normalized.split("/").includes("..")) return { error: "artifact path \u8D8A\u754C" };
1606
+ if (isReservedPath(normalized)) return { error: "artifact path \u662F\u4FDD\u7559\u8DEF\u5F84" };
1607
+ if (!rule.paths.some((pattern) => pathMatches(pattern, normalized))) return { error: `artifact ${kind} path \u4E0D\u5728\u767D\u540D\u5355\uFF1A${requested}` };
1608
+ const absolute = path9.resolve(root, normalized);
1609
+ if (!isInside(root, absolute)) return { error: "artifact path \u8D8A\u754C" };
1610
+ if (pathHasSymlink(root, absolute)) return { error: "artifact path \u5305\u542B symlink" };
1611
+ return { absolute, relative: path9.relative(root, absolute).replaceAll("\\", "/") };
1612
+ }
1613
+ function writeArtifact(root, requested, content) {
1614
+ const normalized = normalizeArtifactPath(requested);
1615
+ if (path9.isAbsolute(normalized) || normalized.split("/").includes("..") || isReservedPath(normalized)) return { error: "artifact path \u88AB\u62D2\u7EDD" };
1616
+ const target = path9.resolve(root, normalized);
1617
+ if (!isInside(root, target)) return { error: "artifact path \u8D8A\u754C" };
1618
+ if (pathHasSymlink(root, target)) return { error: "artifact path \u5305\u542B symlink" };
1619
+ mkdirSync4(path9.dirname(target), { recursive: true });
1620
+ writeFileSync4(target, content);
1621
+ return { relative: path9.relative(root, target).replaceAll("\\", "/") };
1622
+ }
1623
+
1451
1624
  // src/commands.ts
1452
1625
  import { execFileSync as execFileSync3 } from "node:child_process";
1626
+ import { readFileSync as readFileSync4 } from "node:fs";
1627
+
1628
+ // src/command-catalog.ts
1629
+ var GATE_COMMANDS = [
1630
+ { name: "gate-init", description: "\u521D\u59CB\u5316 notes\u3001AGENTS.md\u3001verify-notes\u3001CI\u3001git \u4E0E\u57FA\u7840 .gitignore", hint: null, when: "any" },
1631
+ { name: "gate-enter-plan", description: "\u57FA\u4E8E\u8BA8\u8BBA\u5171\u8BC6\u3001\u65E2\u6709\u51B3\u7B56\u4E0E\u7528\u6237\u9700\u6C42\u8FDB\u5165 planning", hint: "<\u5177\u4F53\u5DE5\u4F5C\u4E8B\u9879\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49>", when: "discuss/complete" },
1632
+ { name: "gate-enter-implement", description: "\u57FA\u4E8E\u5DF2\u786E\u8BA4\u8BA1\u5212\u6216\u8BA8\u8BBA\u540E\u7684\u5B9E\u65BD\u8BF4\u660E\u8FDB\u5165 implementation", hint: "<\u5177\u4F53\u5DE5\u4F5C\u4E8B\u9879\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49>", when: "discuss/complete" },
1633
+ { name: "gate-prd", description: "\u5728 discuss \u4E2D\u6309\u7528\u6237\u8981\u6C42\u521B\u5EFA\u6216\u66F4\u65B0 PRD", hint: "[\u4E3B\u9898\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49]", when: "discuss" },
1634
+ { name: "gate-fix-bug", description: "\u4E3A\u5DF2\u6388\u6743\u7F3A\u9677\u6D3E\u53D1 fix worker\uFF0C\u57FA\u4E8E\u63CF\u8FF0\u6216\u5F53\u524D\u4E0A\u4E0B\u6587\u4FEE\u590D", hint: "<\u5177\u4F53\u5DE5\u4F5C\u4E8B\u9879\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49>", when: "discuss/complete" },
1635
+ { name: "gate-docs", description: "\u8FDB\u5165\u6587\u6863\u6CBB\u7406\uFF0C\u6309\u9879\u76EE\u6587\u6863\u89C4\u8303\u5904\u7406", hint: "[\u6587\u6863\u8303\u56F4\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49]", when: "discuss/complete" },
1636
+ { name: "gate-implement-review", description: "\u5BF9\u5F53\u524D\u6539\u52A8\u505A\u72EC\u7ACB\u53EA\u8BFB code review", hint: "<\u5177\u4F53\u5DE5\u4F5C\u4E8B\u9879\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49>", when: "discuss/complete" },
1637
+ { name: "gate-code-verify", description: "\u5BF9\u5F53\u524D\u6539\u52A8\u505A\u72EC\u7ACB\u53EA\u8BFB\u9A8C\u6536\uFF08gate-user-verify\uFF09", hint: "<\u5177\u4F53\u5DE5\u4F5C\u4E8B\u9879\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49>", when: "discuss/complete" },
1638
+ { name: "gate-propose-review", description: "\u5BF9\u63D0\u6848\u505A\u72EC\u7ACB\u53EA\u8BFB\u4E09\u8F74\u8BC4\u5BA1", hint: "<\u5177\u4F53\u5DE5\u4F5C\u4E8B\u9879\u6216\u4E0A\u4E0B\u6587\u8BED\u4E49>", when: "discuss/complete" },
1639
+ { name: "gate-block", description: "\u624B\u52A8\u6682\u505C\uFF1A\u4FDD\u7559\u5F53\u524D\u8282\u70B9\u5E76\u505C\u6B62\u5199\u5165/\u6D3E worker\uFF1B\u5BF9\u8BDD\u4ECD\u53EF\u7EE7\u7EED", hint: "[\u539F\u56E0]", when: "any" },
1640
+ { name: "gate-resume", description: "\u89E3\u51BB blocked\uFF1A\u56DE\u5230 block \u524D\u7684\u8282\u70B9\uFF1B\u8F6E\u6B21\u8017\u5C3D\u56DE discuss", hint: null, when: "blocked" },
1641
+ { name: "gate-return-discuss", description: "\u653E\u5F03\u5F53\u524D\u8282\u70B9\uFF0C\u56DE\u5230 discuss", hint: null, when: "non-blocked" },
1642
+ { name: "gate-status", description: "\u67E5\u770B\u5F53\u524D phase/node\u3001procedure\u3001access \u4E0E\u4E0B\u4E00\u6B65\u547D\u4EE4", hint: null, when: "any" },
1643
+ { name: "gate-note-verify", description: "\u53EA\u8BFB\u68C0\u67E5 notes tree/format/lifecycle/archived", hint: null, when: "any" },
1644
+ { name: "gate-note-archive", description: "\u5C06 implemented Note \u5F52\u6863\u5230 archived/\uFF1A\u52A0 Archived\u3001\u79FB\u52A8\u3001\u5C01\u5370\u5E76\u62A5\u544A\u5165\u7AD9\u94FE\u63A5", hint: "<implemented-note> [--superseded-by <new-note>]", when: "any" },
1645
+ { name: "gate-note-board", description: "\u751F\u6210\u51B3\u7B56\u770B\u677F board.html\uFF08\u8F7B\u91CF\u6A21\u5F0F\uFF0C\u8FDE\u63A5\u672C\u5730 notes \u76EE\u5F55\uFF09", hint: "[\u8F93\u51FA\u8DEF\u5F84] [\u6807\u9898]", when: "any" },
1646
+ { name: "gate-note-seal", description: "\u53EA\u7ED9 archived/ \u4E2D\u5DF2\u6709 Note \u8865\u5199 manifest.json \u7684 SHA-256 \u5C01\u5370\uFF1B\u4E0D\u5F52\u6863\u3001\u4E0D\u6539 Note \u5185\u5BB9", hint: null, when: "any" }
1647
+ ];
1648
+ function commandSpec(name) {
1649
+ return GATE_COMMANDS.find((command2) => command2.name === name);
1650
+ }
1651
+ function commandLabel(spec) {
1652
+ return `/${spec.name}`;
1653
+ }
1654
+
1655
+ // src/command-next.ts
1656
+ function command(name, reason) {
1657
+ const spec = commandSpec(name);
1658
+ return { command: spec === void 0 ? `/${name}` : commandLabel(spec), reason };
1659
+ }
1660
+ function nextCommandsFor(state) {
1661
+ if (state.phase === "blocked") {
1662
+ return [command("gate-resume", "\u89E3\u51BB\u540E\u56DE\u5230\u6682\u505C\u524D\u8282\u70B9\uFF1B\u8F6E\u6B21\u8017\u5C3D\u65F6\u56DE discuss\u3002")];
1663
+ }
1664
+ if (state.phase === "discuss" || state.phase === "complete") {
1665
+ return [
1666
+ command("gate-enter-plan", "\u9700\u8981\u7528\u6237\u6388\u6743\u540E\u8FDB\u5165 planning\u3002"),
1667
+ command("gate-enter-implement", "\u9700\u8981\u7528\u6237\u6388\u6743\u540E\u8FDB\u5165\u5B9E\u65BD\uFF1B\u590D\u7528\u5DF2\u901A\u8FC7\u7684 plan task\uFF0C\u6216\u76F4\u63A5\u4ECE\u7528\u6237\u7B80\u8FF0\u5B9E\u65BD\u3002")
1668
+ ];
1669
+ }
1670
+ return [];
1671
+ }
1672
+ function nextCommandsText(commands) {
1673
+ if (commands.length === 0) return "";
1674
+ return `\u4E0B\u4E00\u6B65\u547D\u4EE4\uFF1A${commands.map((item) => `${item.command}\uFF08${item.reason}\uFF09`).join("\uFF1B")}`;
1675
+ }
1676
+
1677
+ // src/commands.ts
1453
1678
  function registerCommands(ctx, deps) {
1454
1679
  const { store, reviewLimit, wake } = deps;
1455
1680
  ctx.inject(["commands"], (scope) => {
1456
- const register = (name, description, handler, input = { hint: "<\u5DE5\u4F5C\u9879>" }) => scope.commands.register({
1457
- name,
1458
- description,
1459
- ...input === null ? {} : { input },
1460
- handler
1461
- });
1462
- register("gate-init", "\u521D\u59CB\u5316 notes\u3001AGENTS.md\u3001verify-notes \u4E0E GitHub Actions CI", async (invocation) => {
1681
+ const command2 = (name, handler) => {
1682
+ const spec = commandSpec(name);
1683
+ if (spec === void 0) throw new Error(`unknown gate command: ${name}`);
1684
+ scope.commands.register({
1685
+ name: spec.name,
1686
+ description: spec.description,
1687
+ ...spec.hint === null ? {} : { input: { hint: spec.hint } },
1688
+ handler
1689
+ });
1690
+ };
1691
+ command2("gate-init", async (invocation) => {
1463
1692
  const workspace = workspaceBoundary(ctx, invocation.agent);
1464
1693
  if (workspace === void 0) return { kind: "error", text: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A" };
1465
- const root = path9.resolve(workspace);
1694
+ const root = path10.resolve(workspace);
1466
1695
  let provisioned;
1467
1696
  let projectScripts;
1468
1697
  let projectCi;
1469
1698
  let projectGit;
1699
+ let artifactPolicy;
1470
1700
  try {
1471
1701
  provisioned = provisionNotes(root);
1702
+ artifactPolicy = provisionArtifactPolicy(root);
1472
1703
  projectCi = provisionCi(root);
1473
1704
  projectScripts = provisionProjectScripts(root);
1474
1705
  projectGit = provisionGit(root);
1475
1706
  } catch (error) {
1476
1707
  return { kind: "error", text: `notes \u521D\u59CB\u5316\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}` };
1477
1708
  }
1478
- wake(invocation, store.read(root, invocation.agent), `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09\uFF0Cnotes \u5DF2\u521D\u59CB\u5316\u4E3A ${provisioned.notes}\uFF0CAGENTS.md \u5DF2\u66F4\u65B0\uFF0Cverify-notes \u5DF2\u914D\u7F6E\u4E3A ${projectScripts.command}\uFF0CCI \u5DF2\u914D\u7F6E\u4E3A ${projectCi.workflow}\uFF0Cgit\uFF1A${projectGit.git}${projectGit.gitError === null ? "" : `\uFF08${projectGit.gitError}\uFF09`}\uFF0Cignore\uFF1A${projectGit.ignore}\u3002`, "gate-init");
1709
+ wake(invocation, store.read(root, invocation.agent), `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09\uFF0Cnotes \u5DF2\u521D\u59CB\u5316\u4E3A ${provisioned.notes}\uFF0Cartifact policy \u4E3A ${artifactPolicy.path}\uFF08${artifactPolicy.status}\uFF09\uFF0CAGENTS.md \u5DF2\u66F4\u65B0\uFF0Cverify-notes \u5DF2\u914D\u7F6E\u4E3A ${projectScripts.command}\uFF0CCI \u5DF2\u914D\u7F6E\u4E3A ${projectCi.workflow}\uFF0Cgit\uFF1A${projectGit.git}${projectGit.gitError === null ? "" : `\uFF08${projectGit.gitError}\uFF09`}\uFF0Cignore\uFF1A${projectGit.ignore}\u3002`, "gate-init");
1479
1710
  return { kind: "success", text: `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09
1480
1711
  notes\uFF1A${provisioned.notes}
1712
+ artifact policy\uFF1A${artifactPolicy.path}\uFF08${artifactPolicy.status}\uFF09
1481
1713
  AGENTS\uFF1A${provisioned.agents}
1482
1714
  verify-notes\uFF1A${projectScripts.command}
1483
1715
  CI\uFF1A${projectCi.workflow}
1484
1716
  git\uFF1A${projectGit.git}${projectGit.gitError === null ? "" : `\uFF08${projectGit.gitError}\uFF09`}
1485
1717
  ignore\uFF1A${projectGit.ignore}` };
1486
- }, null);
1487
- register("gate-enter-plan", "\u6309\u7528\u6237\u7B80\u8FF0\u8FDB\u5165 planning", async (invocation) => {
1718
+ });
1719
+ command2("gate-enter-plan", async (invocation) => {
1488
1720
  const result = enterPhase(ctx, invocation.agent, "plan", reviewLimit, store, void 0);
1489
1721
  if (result.kind === "error") return result;
1490
1722
  const brief = String(invocation.rawInput ?? "").trim();
1491
1723
  wake(invocation, result.state, `Gate \u5DF2\u8FDB\u5165 plan\u3002\u7528\u6237\u89C4\u5212\u8F93\u5165\uFF1A${brief === "" ? "\uFF08\u672A\u63D0\u4F9B\u989D\u5916\u8F93\u5165\uFF09" : brief}\u3002`, "gate-enter-plan");
1492
1724
  return { kind: "success", text: "\u8FDB\u5165 planning\u3002" };
1493
- }, { hint: "[\u7B80\u5355\u9700\u6C42]" });
1494
- register("gate-enter-implement", "\u4ECE\u5DF2\u786E\u8BA4\u89C4\u5212\u6216\u7528\u6237\u7B80\u8FF0\u8FDB\u5165 implementation", async (invocation) => {
1725
+ });
1726
+ command2("gate-enter-implement", async (invocation) => {
1495
1727
  const brief = String(invocation.rawInput ?? "").trim();
1496
1728
  const gate = resolveGate(ctx, invocation.agent);
1497
1729
  const state = store.read(gate.root, invocation.agent);
@@ -1513,8 +1745,21 @@ ignore\uFF1A${projectGit.ignore}` };
1513
1745
  const constraints = direct ? "\uFF08\u672C\u53E5\u5373\u5DE5\u4F5C\u9879\u7B80\u8FF0\uFF09" : brief === "" ? "\uFF08\u65E0\u989D\u5916\u7EA6\u675F\uFF09" : brief;
1514
1746
  wake(invocation, result.state, `Gate \u5DF2\u8FDB\u5165 implement\uFF1A${result.state.task ?? ""}${direct ? "\uFF08\u76F4\u63A5\u5B9E\u65BD\uFF0C\u672A\u8D70 planning\uFF09" : ""}\u3002\u7528\u6237\u8BF4\u660E\uFF1A${constraints}\u3002`, "gate-enter-implement");
1515
1747
  return { kind: "success", text: `\u8FDB\u5165 implement\uFF1A${result.state.task}${direct ? "\uFF08\u76F4\u63A5\u5B9E\u65BD\uFF09" : ""}` };
1516
- }, { hint: "[\u7B80\u5355\u8BF4\u660E]" });
1517
- register("gate-fix-bug", "\u4E3A\u5DF2\u6388\u6743\u7684\u5B9E\u73B0\u7F3A\u9677\u6D3E\u53D1 fix worker", async (invocation) => {
1748
+ });
1749
+ command2("gate-prd", async (invocation) => {
1750
+ const gate = resolveGate(ctx, invocation.agent);
1751
+ const state = store.read(gate.root, invocation.agent);
1752
+ if (state.phase !== "discuss") return { kind: "error", text: "gate-prd \u53EA\u5728 discuss \u9636\u6BB5\u53EF\u7528" };
1753
+ const topic = String(invocation.rawInput ?? "").trim() || "\u5F53\u524D\u8BA8\u8BBA";
1754
+ let skill = "";
1755
+ try {
1756
+ skill = readFileSync4(path10.join(SKILLS, "gate-prd", "SKILL.md"), "utf8");
1757
+ } catch {
1758
+ }
1759
+ wake(invocation, state, ["\u8BF7\u6309 gate-prd \u6280\u80FD\u521B\u5EFA\u6216\u66F4\u65B0 PRD\u3002", "\u4E3B\u9898/\u4E0A\u4E0B\u6587\uFF1A" + topic, skill].filter(Boolean).join("\n\n"), "gate-prd");
1760
+ return { kind: "success", text: "\u5DF2\u8BF7\u6C42 PRD\uFF1A" + topic };
1761
+ });
1762
+ command2("gate-fix-bug", async (invocation) => {
1518
1763
  const brief = String(invocation.rawInput ?? "").trim();
1519
1764
  const gate = resolveGate(ctx, invocation.agent);
1520
1765
  const state = store.read(gate.root, invocation.agent);
@@ -1530,23 +1775,24 @@ ignore\uFF1A${projectGit.ignore}` };
1530
1775
  store.write(gate.root, invocation.agent, state);
1531
1776
  wake(invocation, state, `Gate \u5DF2\u8FDB\u5165 fix-bug\uFF1A${scope2}\u3002`, "gate-fix-bug");
1532
1777
  return { kind: "success", text: `\u8FDB\u5165 fix-bug\uFF1A${scope2}` };
1533
- }, { hint: "[\u7F3A\u9677\u7B80\u8FF0]" });
1534
- register("gate-docs", "\u5F00\u59CB\u6587\u6863\u6CBB\u7406", async (invocation) => {
1778
+ });
1779
+ command2("gate-docs", async (invocation) => {
1780
+ const scope2 = String(invocation.rawInput ?? "").trim() || "\u6587\u6863\u6CBB\u7406";
1535
1781
  const gate = resolveGate(ctx, invocation.agent);
1536
1782
  const state = store.read(gate.root, invocation.agent);
1537
1783
  if (state.phase !== "discuss" && state.phase !== "complete") return { kind: "error", text: `\u5F53\u524D phase \u662F ${state.phase}` };
1538
1784
  if (state.work === "blocked" || state.blockReason !== null) return { kind: "error", text: `\u5F53\u524D\u88AB blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
1539
1785
  state.phase = "docs";
1540
- state.task = "\u6587\u6863\u6CBB\u7406";
1786
+ state.task = scope2;
1541
1787
  state.work = "active";
1542
1788
  state.blockReason = null;
1543
1789
  state.blockedFrom = null;
1544
1790
  state.node = null;
1545
1791
  store.write(gate.root, invocation.agent, state);
1546
- wake(invocation, state, "Gate \u5DF2\u8FDB\u5165 docs\u3002", "gate-docs");
1547
- return { kind: "success", text: "\u8FDB\u5165 docs\uFF1A\u6587\u6863\u6CBB\u7406" };
1548
- }, null);
1549
- register("gate-implement-review", "\u5F00\u59CB\u72EC\u7ACB\u53EA\u8BFB code review", async (invocation) => {
1792
+ wake(invocation, state, `Gate \u5DF2\u8FDB\u5165 docs\uFF1A${scope2}\u3002`, "gate-docs");
1793
+ return { kind: "success", text: `\u8FDB\u5165 docs\uFF1A${scope2}` };
1794
+ });
1795
+ command2("gate-implement-review", async (invocation) => {
1550
1796
  const brief = String(invocation.rawInput ?? "").trim();
1551
1797
  const gate = resolveGate(ctx, invocation.agent);
1552
1798
  const state = store.read(gate.root, invocation.agent);
@@ -1563,7 +1809,7 @@ ignore\uFF1A${projectGit.ignore}` };
1563
1809
  wake(invocation, state, `Gate \u5DF2\u8FDB\u5165\u53EA\u8BFB code review\uFF1A${scope2}\u3002`, "gate-implement-review");
1564
1810
  return { kind: "success", text: `\u8FDB\u5165\u53EA\u8BFB code review\uFF1A${scope2}` };
1565
1811
  });
1566
- register("gate-code-verify", "\u5F00\u59CB\u72EC\u7ACB\u53EA\u8BFB\u9A8C\u6536", async (invocation) => {
1812
+ command2("gate-code-verify", async (invocation) => {
1567
1813
  const brief = String(invocation.rawInput ?? "").trim();
1568
1814
  const gate = resolveGate(ctx, invocation.agent);
1569
1815
  const state = store.read(gate.root, invocation.agent);
@@ -1580,7 +1826,7 @@ ignore\uFF1A${projectGit.ignore}` };
1580
1826
  wake(invocation, state, `Gate \u5DF2\u8FDB\u5165\u72EC\u7ACB\u9A8C\u6536\uFF1A${scope2}\u3002`, "gate-code-verify");
1581
1827
  return { kind: "success", text: `\u8FDB\u5165\u72EC\u7ACB\u9A8C\u6536\uFF1A${scope2}` };
1582
1828
  });
1583
- register("gate-propose-review", "\u5F00\u59CB\u72EC\u7ACB\u63D0\u6848\u8BC4\u5BA1", async (invocation) => {
1829
+ command2("gate-propose-review", async (invocation) => {
1584
1830
  const brief = String(invocation.rawInput ?? "").trim();
1585
1831
  const gate = resolveGate(ctx, invocation.agent);
1586
1832
  const state = store.read(gate.root, invocation.agent);
@@ -1597,7 +1843,7 @@ ignore\uFF1A${projectGit.ignore}` };
1597
1843
  wake(invocation, state, `Gate \u5DF2\u8FDB\u5165\u63D0\u6848\u8BC4\u5BA1\uFF1A${scope2}\u3002`, "gate-propose-review");
1598
1844
  return { kind: "success", text: `\u8FDB\u5165\u63D0\u6848\u8BC4\u5BA1\uFF1A${scope2}` };
1599
1845
  });
1600
- register("gate-block", "\u624B\u52A8\u6682\u505C\uFF1A\u4FDD\u7559\u5F53\u524D\u8282\u70B9\u5E76\u505C\u6B62\u5199\u5165/\u6D3E worker\uFF1B\u5BF9\u8BDD\u4ECD\u53EF\u7EE7\u7EED", async (invocation) => {
1846
+ command2("gate-block", async (invocation) => {
1601
1847
  const gate = resolveGate(ctx, invocation.agent);
1602
1848
  const state = store.read(gate.root, invocation.agent);
1603
1849
  if (state.phase === "blocked") return { kind: "success", text: "\u5DF2\u7ECF\u662F blocked" };
@@ -1609,9 +1855,9 @@ ignore\uFF1A${projectGit.ignore}` };
1609
1855
  store.write(gate.root, invocation.agent, state);
1610
1856
  return { kind: "success", text: `\u5DF2\u6682\u505C\uFF1A${state.blockReason}
1611
1857
  \u89E3\u51BB\u540E\uFF1A${state.blockedFrom?.phase ?? "discuss"}${state.blockedFrom?.node === null || state.blockedFrom?.node === void 0 ? "" : ` / ${state.blockedFrom.node}`}
1612
- \u540C\u4E00\u4E8B\u9879\uFF1A/gate-resume \u56DE\u5230\u8BE5\u8282\u70B9\uFF1B\u6539\u53D8\u65B9\u6848\uFF1A/gate-return-discuss\u3002` };
1613
- }, { hint: "[\u539F\u56E0]" });
1614
- register("gate-resume", "\u89E3\u51BB blocked\uFF1A\u56DE\u5230 block \u524D\u7684\u8282\u70B9\uFF1B\u8F6E\u6B21\u8017\u5C3D\u56DE discuss", async (invocation) => {
1858
+ \u540C\u4E00\u4E8B\u9879\uFF1A/gate-resume \u56DE\u5230\u8BE5\u8282\u70B9\uFF1B\u6539\u53D8\u65B9\u6848\uFF1A\u5148 /gate-resume\uFF0C\u518D /gate-return-discuss\u3002` };
1859
+ });
1860
+ command2("gate-resume", async (invocation) => {
1615
1861
  const gate = resolveGate(ctx, invocation.agent);
1616
1862
  const state = store.read(gate.root, invocation.agent);
1617
1863
  if (state.phase !== "blocked") return { kind: "error", text: "\u5F53\u524D\u4E0D\u662F blocked" };
@@ -1628,8 +1874,8 @@ ignore\uFF1A${projectGit.ignore}` };
1628
1874
  }
1629
1875
  const exhausted = reason === "plan-review-rounds-exhausted" || reason === "code-review-rounds-exhausted";
1630
1876
  return { kind: "success", text: `\u5DF2\u89E3\u9664 blocked\uFF1A${state.phase}${state.node === null ? "" : ` / ${state.node}`}${exhausted ? "\nreview \u8F6E\u6B21\u5DF2\u8017\u5C3D\uFF0C\u4E0D\u80FD\u7EE7\u7EED\u539F\u8282\u70B9\uFF1B\u7528 /gate-enter-plan \u91CD\u65B0\u89C4\u5212\u3002" : "\n\u5DF2\u56DE\u5230\u6682\u505C\u524D\u8282\u70B9\uFF0C\u6309\u5F53\u524D procedure \u7EE7\u7EED\u3002"}` };
1631
- }, null);
1632
- register("gate-return-discuss", "\u56DE\u5230 discuss", async (invocation) => {
1877
+ });
1878
+ command2("gate-return-discuss", async (invocation) => {
1633
1879
  const gate = resolveGate(ctx, invocation.agent);
1634
1880
  const state = store.read(gate.root, invocation.agent);
1635
1881
  if (state.phase === "blocked") return { kind: "error", text: `\u5F53\u524D blocked\uFF1A${state.blockReason ?? "unknown"}\uFF1B\u5148 /gate-resume` };
@@ -1651,21 +1897,22 @@ ignore\uFF1A${projectGit.ignore}` };
1651
1897
  state.pending = {};
1652
1898
  store.write(gate.root, invocation.agent, state);
1653
1899
  return { kind: "success", text: "\u5DF2\u56DE\u5230 discuss" };
1654
- }, null);
1655
- register("gate-status", "\u67E5\u770B\u5F53\u524D gate \u72B6\u6001", async (invocation) => {
1900
+ });
1901
+ command2("gate-status", async (invocation) => {
1656
1902
  const gate = resolveGate(ctx, invocation.agent);
1657
1903
  const root = rootAgentOf(ctx, invocation.agent);
1658
1904
  const state = store.read(gate.root, root);
1659
1905
  const worker = root?.id !== invocation.agent?.id;
1660
- return { kind: "success", text: [accessText(accessFor(state, worker)), statusText(state, reviewLimit)].join("\n\n") };
1661
- }, null);
1662
- register("gate-note-verify", "\u53EA\u8BFB\u68C0\u67E5 notes tree/format/lifecycle/archived", async (invocation) => {
1906
+ const next = worker ? "" : nextCommandsText(nextCommandsFor(state));
1907
+ return { kind: "success", text: [accessText(accessFor(state, worker)), statusText(state, reviewLimit), next].filter(Boolean).join("\n\n") };
1908
+ });
1909
+ command2("gate-note-verify", async (invocation) => {
1663
1910
  const gate = resolveGate(ctx, invocation.agent);
1664
1911
  const check = runNotesScripts(gate.root);
1665
1912
  return check.ok ? { kind: "success", text: check.results.map((item) => `${item.name}: ok`).join("\n") } : { kind: "error", text: `notes \u6821\u9A8C\u5931\u8D25\uFF1A
1666
1913
  ${JSON.stringify(check.results, null, 2)}` };
1667
- }, null);
1668
- register("gate-note-archive", "\u5C06 implemented Note \u5F52\u6863\u5230 archived/\uFF1A\u52A0 Archived\u3001\u79FB\u52A8\u3001\u5C01\u5370\u5E76\u62A5\u544A\u5165\u7AD9\u94FE\u63A5", async (invocation) => {
1914
+ });
1915
+ command2("gate-note-archive", async (invocation) => {
1669
1916
  const gate = resolveGate(ctx, invocation.agent);
1670
1917
  const raw = String(invocation.rawInput ?? "").trim();
1671
1918
  const args = raw === "" ? [] : raw.split(/\s+/u);
@@ -1673,41 +1920,41 @@ ${JSON.stringify(check.results, null, 2)}` };
1673
1920
  const node = resolveNodeBinary();
1674
1921
  if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
1675
1922
  try {
1676
- const script = path9.join(NOTES_SKILL_DIR, "scripts", "archive-agent-note.ts");
1677
- const output = execFileSync3(node, [script, "--root", path9.join(gate.root, ".agents", "notes"), ...args], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1923
+ const script = path10.join(NOTES_SKILL_DIR, "scripts", "archive-agent-note.ts");
1924
+ const output = execFileSync3(node, [script, "--root", path10.join(gate.root, ".agents", "notes"), ...args], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1678
1925
  return { kind: "success", text: String(output).trim() || "Note \u5DF2\u5F52\u6863" };
1679
1926
  } catch (error) {
1680
1927
  return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
1681
1928
  }
1682
- }, { hint: "<implemented-note> [--superseded-by <new-note>]" });
1683
- register("gate-note-board", "\u751F\u6210\u51B3\u7B56\u770B\u677F board.html\uFF08\u8F7B\u91CF\u6A21\u5F0F\uFF0C\u8FDE\u63A5\u672C\u5730 notes \u76EE\u5F55\uFF09", async (invocation) => {
1929
+ });
1930
+ command2("gate-note-board", async (invocation) => {
1684
1931
  const gate = resolveGate(ctx, invocation.agent);
1685
1932
  const raw = String(invocation.rawInput ?? "").trim();
1686
1933
  const args = raw === "" ? [] : raw.split(/\s+/u);
1687
1934
  const target = args[0] ?? "board.html";
1688
- const title = args[1] ?? `${path9.basename(gate.root)} \u51B3\u7B56\u770B\u677F`;
1935
+ const title = args[1] ?? `${path10.basename(gate.root)} \u51B3\u7B56\u770B\u677F`;
1689
1936
  const node = resolveNodeBinary();
1690
1937
  if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
1691
1938
  try {
1692
- const script = path9.join(NOTES_SKILL_DIR, "scripts", "build-board.ts");
1939
+ const script = path10.join(NOTES_SKILL_DIR, "scripts", "build-board.ts");
1693
1940
  const output = execFileSync3(node, [script, "--init", target, title], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1694
1941
  return { kind: "success", text: String(output).trim() || `\u51B3\u7B56\u770B\u677F\u5DF2\u751F\u6210\uFF1A${target}` };
1695
1942
  } catch (error) {
1696
1943
  return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
1697
1944
  }
1698
- }, { hint: "[\u8F93\u51FA\u8DEF\u5F84] [\u6807\u9898]" });
1699
- register("gate-note-seal", "\u53EA\u7ED9 archived/ \u4E2D\u5DF2\u6709 Note \u8865\u5199 manifest.json \u7684 SHA-256 \u5C01\u5370\uFF1B\u4E0D\u5F52\u6863\u3001\u4E0D\u6539 Note \u5185\u5BB9", async (invocation) => {
1945
+ });
1946
+ command2("gate-note-seal", async (invocation) => {
1700
1947
  const gate = resolveGate(ctx, invocation.agent);
1701
1948
  const node = resolveNodeBinary();
1702
1949
  if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
1703
1950
  try {
1704
- const script = path9.join(NOTES_SKILL_DIR, "scripts", "verify-archived-agent-notes.ts");
1705
- const output = execFileSync3(node, [script, "--root", path9.join(gate.root, ".agents", "notes"), "--write"], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1951
+ const script = path10.join(NOTES_SKILL_DIR, "scripts", "verify-archived-agent-notes.ts");
1952
+ const output = execFileSync3(node, [script, "--root", path10.join(gate.root, ".agents", "notes"), "--write"], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1706
1953
  return { kind: "success", text: String(output).trim() || "archived manifest \u5DF2\u5199\u5165" };
1707
1954
  } catch (error) {
1708
1955
  return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
1709
1956
  }
1710
- }, null);
1957
+ });
1711
1958
  });
1712
1959
  }
1713
1960
 
@@ -1771,7 +2018,7 @@ function registerStartPhase(ctx, deps) {
1771
2018
 
1772
2019
  // src/tools/record.ts
1773
2020
  import { createHash } from "node:crypto";
1774
- import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as readFileSync3 } from "node:fs";
2021
+ import { existsSync as existsSync5, lstatSync as lstatSync5, readFileSync as readFileSync5 } from "node:fs";
1775
2022
  function registerRecord(ctx, deps) {
1776
2023
  const { store, reviewLimit, wake } = deps;
1777
2024
  ctx.tools.register({
@@ -1977,11 +2224,11 @@ function registerRecord(ctx, deps) {
1977
2224
  for (const item of evidence) {
1978
2225
  const target = classifyPath(gate.workspace, item);
1979
2226
  try {
1980
- if (target.outside || !existsSync4(target.absolute) || !lstatSync4(target.absolute).isFile()) {
2227
+ if (target.outside || !existsSync5(target.absolute) || !lstatSync5(target.absolute).isFile()) {
1981
2228
  invalidEvidence.push(item);
1982
2229
  continue;
1983
2230
  }
1984
- const bytes = readFileSync3(target.absolute);
2231
+ const bytes = readFileSync5(target.absolute);
1985
2232
  records.push({ path: target.relative, size: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") });
1986
2233
  } catch {
1987
2234
  invalidEvidence.push(item);
@@ -2081,8 +2328,10 @@ function registerStatus(ctx, deps) {
2081
2328
  const worker = root?.id !== exec.agent?.id;
2082
2329
  const access = accessFor(state, worker);
2083
2330
  if (worker) {
2084
- return { ok: true, procedure: null, method: null, access, text: [accessText(access), statusText(state, reviewLimit)].join("\n\n") };
2331
+ return { ok: true, procedure: null, method: null, access, nextCommands: [], text: [accessText(access), statusText(state, reviewLimit)].join("\n\n") };
2085
2332
  }
2333
+ const nextCommands = nextCommandsFor(state);
2334
+ const nextText = nextCommandsText(nextCommands);
2086
2335
  const procedure = procedureFor(state, { reviewLimit });
2087
2336
  const includeRounds = !procedure.hasRounds;
2088
2337
  return {
@@ -2090,15 +2339,16 @@ function registerStatus(ctx, deps) {
2090
2339
  procedure,
2091
2340
  method: procedure.method,
2092
2341
  access,
2093
- text: procedureResultText(procedure, [statusText(state, reviewLimit, { includeRounds }), accessText(access)].join("\n\n"))
2342
+ nextCommands,
2343
+ text: procedureResultText(procedure, [statusText(state, reviewLimit, { includeRounds }), nextText, accessText(access)].filter(Boolean).join("\n\n"))
2094
2344
  };
2095
2345
  }
2096
2346
  });
2097
2347
  }
2098
2348
 
2099
2349
  // src/tools/notes.ts
2100
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
2101
- import path10 from "node:path";
2350
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
2351
+ import path11 from "node:path";
2102
2352
  function registerNoteTools(ctx, deps) {
2103
2353
  const { store, reviewLimit, wake } = deps;
2104
2354
  ctx.tools.register({
@@ -2124,7 +2374,7 @@ function registerNoteTools(ctx, deps) {
2124
2374
  return { ok: false, code: "planning-only", reason: "gate_note_write \u53EA\u5141\u8BB8 planning worker \u5728 plan/plan-revise \u8282\u70B9\u4F7F\u7528\u3002" };
2125
2375
  }
2126
2376
  let relative = String(args.path ?? "").replaceAll("\\", "/").replace(/^\.?\//u, "");
2127
- if (relative === "" || relative.includes("..") || path10.isAbsolute(relative) || !relative.endsWith(".md")) {
2377
+ if (relative === "" || relative.includes("..") || path11.isAbsolute(relative) || !relative.endsWith(".md")) {
2128
2378
  return { ok: false, code: "invalid-path" };
2129
2379
  }
2130
2380
  let lifecycle = "proposed";
@@ -2136,16 +2386,16 @@ function registerNoteTools(ctx, deps) {
2136
2386
  if (!/^(?:feature|bug-fix|simplification|architecture|process|testing)\/\d{4}-\d{2}-\d{2}-.+\.md$/u.test(relative)) {
2137
2387
  return { ok: false, code: "invalid-path", reason: "\u9700\u8981 <class>/yyyy-mm-dd-topic.md\uFF0C\u6216 proposed|implemented/<class>/...\u3002" };
2138
2388
  }
2139
- const base = path10.join(gate.root, ".agents", "notes", String(lifecycle));
2140
- const absolute = path10.resolve(base, relative);
2389
+ const base = path11.join(gate.root, ".agents", "notes", String(lifecycle));
2390
+ const absolute = path11.resolve(base, relative);
2141
2391
  if (!isInside(base, absolute)) return { ok: false, code: "invalid-path" };
2142
2392
  if (pathHasSymlink(base, absolute)) return { ok: false, code: "invalid-path" };
2143
- if (lifecycle === "implemented" && !existsSync5(absolute)) {
2393
+ if (lifecycle === "implemented" && !existsSync6(absolute)) {
2144
2394
  return { ok: false, code: "implemented-must-exist", reason: "implemented Note \u53EA\u80FD\u539F\u5730\u7F16\u8F91\uFF0C\u4E0D\u80FD\u65B0\u5EFA\u3002" };
2145
2395
  }
2146
- mkdirSync4(path10.dirname(absolute), { recursive: true });
2147
- writeFileSync4(absolute, String(args.content ?? ""));
2148
- return { ok: true, path: path10.relative(gate.root, absolute).replaceAll("\\", "/"), lifecycle };
2396
+ mkdirSync5(path11.dirname(absolute), { recursive: true });
2397
+ writeFileSync5(absolute, String(args.content ?? ""));
2398
+ return { ok: true, path: path11.relative(gate.root, absolute).replaceAll("\\", "/"), lifecycle };
2149
2399
  }
2150
2400
  });
2151
2401
  ctx.tools.register({
@@ -2168,7 +2418,7 @@ function registerNoteTools(ctx, deps) {
2168
2418
  if (!check.ok) return { ...check, state: normalizeState(state) };
2169
2419
  if (state.node === "note-lifecycle") {
2170
2420
  if (state.noteFinalized !== true) return { ok: false, code: "note-lifecycle-incomplete", reason: "\u5148\u8C03\u7528 gate_note_finalize\u3002", state: normalizeState(state) };
2171
- const implemented = state.notePaths.length > 0 && state.notePaths.every((entry) => entry.lifecycle === "implemented" && existsSync5(path10.join(gate.root, entry.path)));
2421
+ const implemented = state.notePaths.length > 0 && state.notePaths.every((entry) => entry.lifecycle === "implemented" && existsSync6(path11.join(gate.root, entry.path)));
2172
2422
  if (!implemented) return { ok: false, code: "note-lifecycle-incomplete", reason: "notePaths \u5C1A\u672A\u5168\u90E8\u8FDB\u5165 implemented\u3002", results: check.results, state: normalizeState(state) };
2173
2423
  state.noteVerified = true;
2174
2424
  store.write(gate.root, exec.agent, state);
@@ -2216,9 +2466,9 @@ function registerNoteTools(ctx, deps) {
2216
2466
  const recovered = recoverFinalize(gate.root, sessionId);
2217
2467
  for (const entry of state.notePaths) {
2218
2468
  if (entry.lifecycle !== "proposed") continue;
2219
- const source = path10.join(gate.root, entry.path);
2469
+ const source = path11.join(gate.root, entry.path);
2220
2470
  const targetRelative = entry.path.replaceAll("\\", "/").replace(".agents/notes/proposed/", ".agents/notes/implemented/");
2221
- if (!existsSync5(source) && existsSync5(path10.join(gate.root, targetRelative))) {
2471
+ if (!existsSync6(source) && existsSync6(path11.join(gate.root, targetRelative))) {
2222
2472
  entry.path = targetRelative;
2223
2473
  entry.lifecycle = "implemented";
2224
2474
  recovered.push("adopted-target");
@@ -2251,12 +2501,193 @@ function registerNoteTools(ctx, deps) {
2251
2501
  });
2252
2502
  }
2253
2503
 
2504
+ // src/tools/catalog.ts
2505
+ var GATE_TOOLS = [
2506
+ { name: "gate_status", summary: "\u8BFB\u53D6\u5F53\u524D phase/node\u3001procedure\u3001access \u548C nextCommands\uFF1Broot/worker \u5747\u53EF\u8C03\u7528\u3002" },
2507
+ { name: "gate_record", summary: "\u8BB0\u5F55 worker \u7ED3\u679C\u5E76\u63A8\u8FDB gate\uFF1B\u4EC5 root \u53EF\u8C03\u7528\u3002" },
2508
+ { name: "gate_start_phase", summary: "\u5728\u7528\u6237\u6388\u6743\u540E\u8FDB\u5165 plan/implement\uFF1Bplan \u57FA\u4E8E\u8BA8\u8BBA\u5171\u8BC6\uFF0Cimplement \u57FA\u4E8E\u5DF2\u786E\u8BA4\u8BA1\u5212\u6216\u5B9E\u65BD\u8BF4\u660E\uFF1B\u4EC5 root \u53EF\u8C03\u7528\u3002" },
2509
+ { name: "gate_note_write", summary: "planning worker \u5199 proposed Note \u6216\u539F\u5730\u66F4\u65B0 implemented Note\u3002" },
2510
+ { name: "gate_note_verify", summary: "\u5728 note-lifecycle \u4E2D\u8FD0\u884C notes \u6821\u9A8C\uFF1Broot-only\u3002" },
2511
+ { name: "gate_note_finalize", summary: "\u628A proposed Note \u79FB\u5230 implemented \u5E76\u66F4\u65B0\u5934\u5757\uFF1Broot-only\u3002" },
2512
+ { name: "gate_help", summary: "\u5217\u51FA gate \u547D\u4EE4\u3001\u5DE5\u5177\u548C\u5F53\u524D phase/node\uFF1B\u53EA\u8BFB\uFF0Croot-only\u3002" },
2513
+ { name: "gate_artifact_write", summary: "\u6309 artifact policy \u5199\u767D\u540D\u5355\u6587\u4EF6\uFF1B\u4E0D\u8D70\u666E\u901A workspace write\u3002" },
2514
+ { name: "gate_research_dispatch", summary: "root-only \u5728 discuss/plan \u6D3E research worker\uFF1Bworker \u53EA\u5199 research artifact\u3002" }
2515
+ ];
2516
+
2517
+ // src/tools/help.ts
2518
+ function commandLine(spec) {
2519
+ const hint = spec.hint === null ? "" : ` ${spec.hint}`;
2520
+ return `- ${commandLabel(spec)}${hint} \u2014 ${spec.description}\uFF08${spec.when}\uFF09`;
2521
+ }
2522
+ function helpText(phase, node) {
2523
+ return [
2524
+ "## Gate help",
2525
+ `\u5F53\u524D phase/node\uFF1A${phase} / ${node ?? "\u65E0"}`,
2526
+ "",
2527
+ "\u547D\u4EE4\uFF08\u7531\u7528\u6237\u6267\u884C\uFF09\uFF1A",
2528
+ ...GATE_COMMANDS.map(commandLine),
2529
+ "",
2530
+ "\u5DE5\u5177\uFF08\u7531\u6A21\u578B\u8C03\u7528\uFF09\uFF1A",
2531
+ ...GATE_TOOLS.map((tool) => `- ${tool.name} \u2014 ${tool.summary}`),
2532
+ "",
2533
+ "\u547D\u4EE4\u7531\u7528\u6237\u6267\u884C\uFF1B\u5DE5\u5177\u7531\u6A21\u578B\u8C03\u7528\u3002\u547D\u4EE4\u4E0D\u4F1A\u4F5C\u4E3A tool \u51FA\u73B0\u5728\u5DE5\u5177\u76EE\u5F55\u3002"
2534
+ ].join("\n");
2535
+ }
2536
+ function registerHelp(ctx, deps) {
2537
+ const { store } = deps;
2538
+ ctx.tools.register({
2539
+ name: "gate_help",
2540
+ description: "\u53EA\u8BFB\u5217\u51FA gate \u547D\u4EE4\u3001\u5DE5\u5177\u7528\u9014\u548C\u5F53\u524D phase/node\u3002\u4EC5 root agent\uFF1B\u53EA\u5728\u4E0D\u786E\u5B9A\u547D\u4EE4\u540D/\u5DE5\u5177\u7528\u9014\u65F6\u4F7F\u7528\u3002",
2541
+ parameters: { type: "object", additionalProperties: false, properties: {} },
2542
+ output: {
2543
+ schema: { type: "object", additionalProperties: true },
2544
+ render: (_args, value) => [{ type: "text", text: typeof value?.text === "string" ? value.text : JSON.stringify(value) }]
2545
+ },
2546
+ async execute(_args, exec) {
2547
+ const root = rootAgentOf(ctx, exec.agent);
2548
+ if (root?.id !== exec.agent?.id) return { ok: false, code: "root-only", reason: "gate_help \u53EA\u80FD\u7531 root agent \u8C03\u7528\u3002" };
2549
+ const gate = resolveGate(ctx, exec.agent);
2550
+ if (gate.root === void 0) return { ok: false, code: "state-unavailable", reason: "\u65E0\u6CD5\u786E\u5B9A\u5DE5\u4F5C\u533A\u3002" };
2551
+ const state = store.read(gate.root, root);
2552
+ return {
2553
+ ok: true,
2554
+ phase: state.phase,
2555
+ node: state.node,
2556
+ current: { phase: state.phase, node: state.node },
2557
+ note: "\u547D\u4EE4\u7531\u7528\u6237\u6267\u884C\uFF1B\u5DE5\u5177\u7531\u6A21\u578B\u8C03\u7528\u3002",
2558
+ commands: GATE_COMMANDS.map((spec) => ({ ...spec, command: commandLabel(spec) })),
2559
+ tools: GATE_TOOLS,
2560
+ text: helpText(state.phase, state.node)
2561
+ };
2562
+ }
2563
+ });
2564
+ }
2565
+
2566
+ // src/tools/artifact.ts
2567
+ function registerArtifactTools(ctx, deps) {
2568
+ const { store } = deps;
2569
+ ctx.tools.register({
2570
+ name: "gate_artifact_write",
2571
+ description: "\u6309 .agents/gate/artifact-policy.json \u5199\u767D\u540D\u5355 artifact\uFF08handoff/prd/research \u7B49\uFF09\u3002\u53EA\u5199\u6587\u4EF6\uFF0C\u4E0D\u8D70\u666E\u901A workspace write \u6743\u9650\u3002",
2572
+ parameters: {
2573
+ type: "object",
2574
+ additionalProperties: false,
2575
+ properties: {
2576
+ kind: { type: "string", description: "policy \u4E2D\u7684 artifact kind\u3002" },
2577
+ path: { type: "string", description: "workspace \u5185\u76F8\u5BF9\u8DEF\u5F84\u3002" },
2578
+ content: { type: "string" }
2579
+ },
2580
+ required: ["kind", "path", "content"]
2581
+ },
2582
+ output: {
2583
+ schema: { type: "object", additionalProperties: true },
2584
+ render: (_args, value) => [{ type: "text", text: typeof value?.text === "string" ? value.text : JSON.stringify(value) }]
2585
+ },
2586
+ async execute(args, exec) {
2587
+ const gate = resolveGate(ctx, exec.agent);
2588
+ if (gate.root === void 0) return { ok: false, code: "state-unavailable", reason: "\u65E0\u6CD5\u786E\u5B9A\u5DE5\u4F5C\u533A\u3002" };
2589
+ const effective = effectiveState(ctx, exec.agent, gate, store);
2590
+ const loaded = loadArtifactPolicy(gate.root);
2591
+ if (loaded.policy === void 0) return { ok: false, code: "artifact-policy-invalid", reason: loaded.error };
2592
+ const allowed = artifactRuleAllows(loaded.policy, effective.state, effective.worker, String(args?.kind ?? ""));
2593
+ if (allowed.rule === void 0) return { ok: false, code: "artifact-denied", reason: allowed.error };
2594
+ const checked = checkArtifactPath(gate.root, allowed.rule, String(args?.path ?? ""), String(args?.kind ?? ""));
2595
+ if (checked.absolute === void 0) return { ok: false, code: "artifact-denied", reason: checked.error };
2596
+ const written = writeArtifact(gate.root, checked.relative ?? String(args?.path ?? ""), String(args?.content ?? ""));
2597
+ if (written.relative === void 0) return { ok: false, code: "artifact-write-failed", reason: written.error };
2598
+ return { ok: true, kind: String(args.kind), path: written.relative, bytes: Buffer.byteLength(String(args.content ?? "")) };
2599
+ }
2600
+ });
2601
+ }
2602
+
2603
+ // src/tools/research.ts
2604
+ function registerResearchDispatch(ctx, deps) {
2605
+ const { store } = deps;
2606
+ ctx.tools.register({
2607
+ name: "gate_research_dispatch",
2608
+ description: "root-only\uFF1A\u5728 discuss \u6216 orchestrating/plan \u6D3E research worker\uFF0C\u7B49\u5F85\u7ED3\u679C\uFF1Bworker \u53EA\u80FD\u901A\u8FC7 artifact policy \u5199 research \u6587\u6863\u3002",
2609
+ parameters: {
2610
+ type: "object",
2611
+ additionalProperties: false,
2612
+ properties: {
2613
+ question: { type: "string" },
2614
+ purpose: { type: "string" }
2615
+ },
2616
+ required: ["question"]
2617
+ },
2618
+ output: {
2619
+ schema: { type: "object", additionalProperties: true },
2620
+ render: (_args, value) => [{ type: "text", text: typeof value?.text === "string" ? value.text : JSON.stringify(value) }]
2621
+ },
2622
+ async execute(args, exec) {
2623
+ const root = rootAgentOf(ctx, exec.agent);
2624
+ if (root?.id !== exec.agent?.id) return { ok: false, code: "root-only", reason: "gate_research_dispatch \u53EA\u80FD\u7531 root agent \u8C03\u7528\u3002" };
2625
+ const gate = resolveGate(ctx, exec.agent);
2626
+ if (gate.root === void 0) return { ok: false, code: "state-unavailable", reason: "\u65E0\u6CD5\u786E\u5B9A\u5DE5\u4F5C\u533A\u3002" };
2627
+ const state = store.read(gate.root, root);
2628
+ if (!(state.phase === "discuss" || state.phase === "orchestrating" && state.node === "plan")) {
2629
+ return { ok: false, code: "wrong-phase", reason: "research dispatch \u53EA\u5141\u8BB8 discuss \u6216 orchestrating/plan\u3002" };
2630
+ }
2631
+ const loaded = loadArtifactPolicy(gate.root);
2632
+ if (loaded.policy === void 0) return { ok: false, code: "artifact-policy-invalid", reason: loaded.error };
2633
+ const allowed = artifactRuleAllows(loaded.policy, state, true, "research");
2634
+ if (allowed.rule === void 0) return { ok: false, code: "artifact-denied", reason: allowed.error };
2635
+ const subagents = typeof ctx.get === "function" ? ctx.get("subagents") : void 0;
2636
+ if (subagents?.startActivation === void 0 || subagents?.getProvider === void 0) {
2637
+ return { ok: false, code: "subagents-unavailable", reason: "ctx.subagents \u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u6D3E research worker\u3002" };
2638
+ }
2639
+ const provider = subagents.getProvider("spawn");
2640
+ if (provider === void 0) return { ok: false, code: "provider-unavailable", reason: "ctx.subagents \u6CA1\u6709 spawn provider\u3002" };
2641
+ const question = String(args?.question ?? "").trim();
2642
+ if (question === "") return { ok: false, code: "question-required", reason: "research \u9700\u8981 question\u3002" };
2643
+ const purpose = String(args?.purpose ?? "").trim();
2644
+ const prompt = [{
2645
+ type: "text",
2646
+ text: [
2647
+ "Research worker task. Investigate primary sources and write a Markdown report.",
2648
+ `Question: ${question}`,
2649
+ purpose === "" ? "" : `Purpose: ${purpose}`,
2650
+ "Use gate_artifact_write({ kind: 'research', path, content }) to save the report.",
2651
+ "Prefer project docs research location; otherwise .scratch/research/. Cite sources. Do not modify product code."
2652
+ ].filter(Boolean).join("\n")
2653
+ }];
2654
+ let activation;
2655
+ try {
2656
+ activation = await subagents.startActivation({
2657
+ provider: "spawn",
2658
+ label: "gate-research",
2659
+ prompt,
2660
+ parent: exec.agent,
2661
+ signal: exec.signal,
2662
+ delivery: "caller"
2663
+ });
2664
+ const result = await activation.result;
2665
+ const output = result?.output;
2666
+ const text = typeof output === "string" ? output : JSON.stringify(output ?? result);
2667
+ return { ok: true, name: activation.name, text };
2668
+ } catch (error) {
2669
+ return { ok: false, code: "research-dispatch-failed", reason: error instanceof Error ? error.message : String(error) };
2670
+ } finally {
2671
+ if (activation?.dispose !== void 0) {
2672
+ try {
2673
+ await activation.dispose();
2674
+ } catch {
2675
+ }
2676
+ }
2677
+ }
2678
+ }
2679
+ });
2680
+ }
2681
+
2254
2682
  // src/tools/index.ts
2255
2683
  function registerTools(ctx, deps) {
2256
2684
  registerStartPhase(ctx, deps);
2257
2685
  registerRecord(ctx, deps);
2258
2686
  registerStatus(ctx, deps);
2259
2687
  registerNoteTools(ctx, deps);
2688
+ registerHelp(ctx, deps);
2689
+ registerArtifactTools(ctx, deps);
2690
+ registerResearchDispatch(ctx, deps);
2260
2691
  }
2261
2692
 
2262
2693
  // src/index.ts