mancode 0.3.7 → 0.3.9

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/dist/cli.js CHANGED
@@ -108,7 +108,7 @@ var PLAN_COACH_AGENT = {
108
108
  tools: ["Read", "Grep", "Glob"],
109
109
  body: `\u4F60\u662F mancode \u6559\u7EC3\u7EC4\u7684 Plan Coach\uFF08\u8BA1\u5212\u6559\u7EC3\uFF09\u3002
110
110
 
111
- \u4F60\u7684\u804C\u8D23\uFF1A\u5728\u7528\u6237\u786E\u8BA4\u524D\uFF0C\u628A Scout Report \u548C\u56E2\u961F\u4E0A\u4E0B\u6587\u6574\u7406\u6210\u53EF\u6267\u884C plan\u3002
111
+ \u4F60\u7684\u804C\u8D23\uFF1A\u5148\u68C0\u67E5\u9700\u6C42\u662F\u5426\u8DB3\u591F\u652F\u6491\u8BA1\u5212\uFF0C\u518D\u5728\u7528\u6237\u786E\u8BA4\u524D\u628A Scout Report\u3001\u7ED3\u6784\u5316 requirements \u548C\u56E2\u961F\u4E0A\u4E0B\u6587\u6574\u7406\u6210\u53EF\u6267\u884C plan\u3002
112
112
 
113
113
  ## \u786C\u7EA6\u675F
114
114
 
@@ -116,6 +116,16 @@ var PLAN_COACH_AGENT = {
116
116
  - \u4E0D\u521B\u5EFA README\u3001\u6E90\u7801\u3001\u914D\u7F6E\u3001\u6D4B\u8BD5\u6216\u56E2\u961F memory\u3002
117
117
  - \u4E0D\u628A proposal \u5199\u6210 decision\u3002\u56E2\u961F\u51B3\u7B56\u53EA\u80FD\u5728\u7528\u6237\u786E\u8BA4\u5E76\u5B8C\u6210\u5B9E\u73B0\u540E\u8FDB\u5165 \`.mancode/memory/decisions.md\`\u3002
118
118
  - \u53EA\u5728\u6700\u7EC8\u54CD\u5E94\u91CC\u8FD4\u56DE\u8BA1\u5212\u6587\u672C\uFF1B\u8C03\u7528\u65B9\u4F1A\u8D1F\u8D23\u5199\u5165 \`.mancode/workflows/<taskId>/plan.md\`\u3002
119
+ - \u4E0D\u7528\u81EA\u5DF1\u7684\u5047\u8BBE\u586B\u8865\u4F1A\u6539\u53D8\u8303\u56F4\u3001\u67B6\u6784\u3001\u6210\u672C\u6216\u9A8C\u6536\u7684 blocking \u51B3\u7B56\u3002
120
+
121
+ ## \u8F93\u5165\u5C31\u7EEA\u68C0\u67E5
122
+
123
+ \u5148\u8BFB\u53D6 \`requirements.json\`\uFF0C\u4EE5\u5176\u4E2D\u7684 confirmedScope\u3001excludedScope\u3001technicalDecisions\u3001defaults\u3001blockingUnknowns\u3001coverage \u548C acceptanceCriteria \u4E3A\u6743\u5A01\u8F93\u5165\uFF1B\`requirements.md\` \u53EA\u7528\u4E8E\u9605\u8BFB\u3002\u9010\u9879\u68C0\u67E5 platform\u3001core_scope\u3001technical_stack\u3001data_and_persistence\u3001performance\u3001compatibility\u3001security \u7684\u72B6\u6001\u548C\u7406\u7531\u662F\u5426\u4E0E\u4E8B\u5B9E\u4E00\u81F4\u3002\u68C0\u67E5\u9700\u6C42\u662F\u5426\u8986\u76D6\u4EFB\u52A1\u5B9E\u9645\u9002\u7528\u7684\u7528\u6237\u76EE\u6807\u3001\u6838\u5FC3\u6D41\u7A0B\u3001\u9996\u671F\u8303\u56F4\u3001\u6392\u9664\u9879\u3001\u6280\u672F\u4E0E\u8FD0\u884C\u7EA6\u675F\u3001\u6570\u636E/\u72B6\u6001/\u96C6\u6210\u3001\u5173\u952E\u6027\u80FD/\u517C\u5BB9\u6027/\u5B89\u5168\u8981\u6C42\u548C\u9A8C\u6536\u6807\u51C6\u3002blockingUnknowns \u975E\u7A7A\u3001coverage \u7528\u65E0\u6839\u636E\u7684 not_applicable \u63A9\u76D6\u51B3\u7B56\u3001\u6838\u5FC3\u884C\u4E3A\u7F3A\u5C11\u9A8C\u6536 ID\uFF0C\u6216\u6587\u6863\u4E0E\u7ED3\u6784\u5316\u8F93\u5165\u77DB\u76FE\u65F6\u5FC5\u987B\u8FD4\u56DE NEEDS_CLARIFICATION\u3002
124
+
125
+ \u53EA\u80FD\u8FD4\u56DE\u4E24\u79CD\u7ED3\u679C\uFF1A
126
+
127
+ 1. \`NEEDS_CLARIFICATION\`\uFF1A\u5B58\u5728 blocking \u7F3A\u53E3\u65F6\uFF0C\u4E0D\u751F\u6210\u8BA1\u5212\u3002\u5217\u51FA\u7F3A\u5931\u51B3\u7B56\u3001\u5BF9\u8BA1\u5212\u7684\u5F71\u54CD\u30012\u20133 \u4E2A\u53EF\u884C\u9009\u9879\u3001\u660E\u786E\u63A8\u8350\u548C\u5EFA\u8BAE\u95EE\u9898\u3002
128
+ 2. \`READY_FOR_PLAN\`\uFF1A\u6CA1\u6709 blocking \u7F3A\u53E3\u65F6\uFF0C\u8F93\u51FA\u6807\u8BB0\u540E\u518D\u751F\u6210\u5B8C\u6574\u8BA1\u5212\u3002defaultable \u7EC6\u8282\u5FC5\u987B\u4F5C\u4E3A\u663E\u5F0F\u9ED8\u8BA4\u503C\u5199\u5165\u8BA1\u5212\u3002
119
129
 
120
130
  ## \u8F93\u51FA\u683C\u5F0F
121
131
 
@@ -127,7 +137,7 @@ var PLAN_COACH_AGENT = {
127
137
  - \u7B80\u5355 / \u4E2D\u7B49 / \u590D\u6742\uFF1B\u8BF4\u660E\u7406\u7531
128
138
  ## \u6A21\u5757\u7D22\u5F15
129
139
  ## \u6280\u672F\u4E0E\u4EA4\u4ED8\u7EA6\u675F\uFF08\u4EC5\u65B0\u9879\u76EE\u6216\u6280\u672F\u9009\u62E9\u672A\u5B9A\u65F6\uFF09
130
- - \u5019\u9009\u65B9\u6848\uFF082\u20133 \u4E2A\uFF09\u3001\u4F18\u7F3A\u70B9\u3001\u63A8\u8350\u4E0E\u786E\u8BA4\u70B9\uFF1B\u5DF2\u6709\u9879\u76EE\u5199\u201C\u6CBF\u7528\u68C0\u6D4B\u5230\u7684\u9879\u76EE\u7EA6\u5B9A\u201D
140
+ - \u5DF2\u786E\u8BA4\u65B9\u6848\u3001\u9009\u62E9\u7406\u7531\u548C\u53D6\u820D\uFF1B\u5C1A\u672A\u786E\u8BA4\u4F1A\u6539\u53D8\u67B6\u6784\u7684\u6280\u672F\u9009\u62E9\u65F6\u8FD4\u56DE NEEDS_CLARIFICATION\uFF1B\u5DF2\u6709\u9879\u76EE\u5199\u201C\u6CBF\u7528\u68C0\u6D4B\u5230\u7684\u9879\u76EE\u7EA6\u5B9A\u201D
131
141
  ## \u6A21\u5757\uFF1A<\u540D\u79F0>
132
142
  ### \u6539\u52A8\u6587\u4EF6 / \u65B0\u5EFA\u6587\u4EF6
133
143
  ### \u590D\u7528\u8D44\u6E90\uFF08\u5F15\u7528 scout-report.md \u884C\u53F7\uFF09
@@ -135,13 +145,16 @@ var PLAN_COACH_AGENT = {
135
145
  ### \u4E0D\u505A\u4EC0\u4E48
136
146
  ### \u5B9E\u65BD\u6B65\u9AA4
137
147
  ### \u98CE\u9669\u70B9
148
+ ### \u56DE\u9000\u65B9\u5F0F
138
149
  ### \u5B8C\u6210\u5B9A\u4E49
150
+ ## \u975E\u963B\u585E\u9ED8\u8BA4\u503C
139
151
  ## \u9A8C\u8BC1\u8BA1\u5212
152
+ - \u628A\u6BCF\u4E2A required \u9A8C\u6536 ID \u6620\u5C04\u5230 automated / manual / hybrid \u9A8C\u8BC1\uFF0C\u4E0D\u5F97\u7528\u9875\u9762\u52A0\u8F7D\u6216\u4EE3\u7801\u9605\u8BFB\u4EE3\u66FF\u6838\u5FC3\u4EA4\u4E92
140
153
  - build / lint / typecheck / test / smoke test
141
154
  ## \u9884\u4F30
142
155
  \`\`\`
143
156
 
144
- \u5982\u679C\u4EFB\u52A1\u4E0D\u5E94\u7EE7\u7EED\u5B9E\u65BD\uFF0C\u660E\u786E\u5199\u5728 Risks \u6216 Questions \u4E2D\u3002`
157
+ \u5982\u679C\u4EFB\u52A1\u4E0D\u5E94\u7EE7\u7EED\u5B9E\u65BD\uFF0C\u8FD4\u56DE NEEDS_CLARIFICATION\uFF0C\u4E0D\u8981\u7528\u4E00\u4EFD\u770B\u4F3C\u5B8C\u6574\u7684\u8BA1\u5212\u63A9\u76D6\u7F3A\u5931\u9700\u6C42\u3002`
145
158
  };
146
159
 
147
160
  // src/templates/agents/head-coach.ts
@@ -584,6 +597,21 @@ if (state.teamModeAutoDetected === true && mode === 'solo') {
584
597
  );
585
598
  }
586
599
 
600
+ if (
601
+ mode === 'solo' &&
602
+ state.activeSoloPlan &&
603
+ typeof state.activeSoloPlan.taskId === 'string'
604
+ ) {
605
+ output.push(
606
+ '',
607
+ '### 已确认的 solo 实施计划',
608
+ 'Task: ' + sanitize(state.activeSoloPlan.taskId),
609
+ 'Plan version: ' + sanitize(state.activeSoloPlan.planVersion || 1),
610
+ '- 收到继续实施指令时,先读对应 workflow 的 requirements.md 和 plan.md。',
611
+ '- 按已确认范围轻量开发,不重新进入 /man,不运行独立 reviewer。',
612
+ );
613
+ }
614
+
587
615
  console.log(output.join('\n'));
588
616
 
589
617
  function readJson(filePath) {
@@ -646,8 +674,31 @@ if (mode === 'solo') {
646
674
  const rawInput = readFileSync(0, 'utf8');
647
675
  const userPrompt = readPrompt(rawInput);
648
676
  const planningPattern = /先(?:别|不要|看|看看|调研|分析|评估)|给.*方案|给.*计划|怎么.*做|如何.*做|怎么.*实现|如何.*实现|应该怎么|怎么.*拆|拆分|只给.*计划|不要.*改代码|别.*改代码|不要.*动代码|别.*动代码|评估.*风险|风险.*评估|设计.*方案|架构|迁移|集成|\b(?:plan|planning|research|investigate|approach|proposal|architecture|risk|migration|integration)\b|how (?:should|would|to)|do not (?:edit|modify|change)|don.t (?:edit|modify|change)|no code changes|without changing code/iu;
677
+ const approvedPlanExecutionPattern = /按.*计划|继续.*计划|执行.*计划|实现.*计划|(?:implement|execute|continue|resume).*(?:approved )?plan/iu;
678
+ const hasActiveSoloPlan =
679
+ mode === 'solo' &&
680
+ state.activeSoloPlan &&
681
+ typeof state.activeSoloPlan.taskId === 'string';
682
+
683
+ if (hasActiveSoloPlan) {
684
+ output.push(
685
+ '## mancode 已确认计划',
686
+ '',
687
+ '当前 solo 计划:.mancode/workflows/' +
688
+ sanitize(state.activeSoloPlan.taskId) +
689
+ '/plan.md(v' +
690
+ sanitize(state.activeSoloPlan.planVersion || 1) +
691
+ ')。',
692
+ '实施前同时读取 requirements.md;只执行确认范围,完成后运行 workflow handoff <taskId> --complete。',
693
+ '',
694
+ );
695
+ }
649
696
 
650
- if (mode === 'solo' && planningPattern.test(userPrompt)) {
697
+ if (
698
+ mode === 'solo' &&
699
+ planningPattern.test(userPrompt) &&
700
+ !(hasActiveSoloPlan && approvedPlanExecutionPattern.test(userPrompt))
701
+ ) {
651
702
  output.push(
652
703
  '## mancode 自动路由',
653
704
  '',
@@ -771,6 +822,14 @@ var SOLO_SKILL = `# mancode \xB7 solo mode
771
822
  3. \u641C\u7D22\u76F8\u4F3C\u5B9E\u73B0\uFF08"\u8FD9\u4E2A\u9879\u76EE\u91CC\u6709\u6CA1\u6709\uFF1F"\uFF09
772
823
  4. \u68C0\u67E5\u53EF\u590D\u7528\u8D44\u6E90
773
824
 
825
+ ### \u6267\u884C\u5DF2\u786E\u8BA4\u7684 /man \u8BA1\u5212
826
+ \u5F53 \`.mancode/state.json\` \u7684 \`activeSoloPlan\` \u975E\u7A7A\u4E14\u7528\u6237\u8981\u6C42\u7EE7\u7EED\u8BE5\u8BA1\u5212\u65F6\uFF1A
827
+ 1. \u8BFB\u53D6\u5BF9\u5E94 workflow \u7684 \`requirements.md\` \u548C \`plan.md\`\uFF0C\u786E\u8BA4 taskId \u4E0E planVersion\u3002
828
+ 2. \u5F00\u5DE5\u524D\u56DE\u6267\u76EE\u6807\u3001\u6280\u672F\u65B9\u6848\u3001\u5305\u542B\u8303\u56F4\u3001\u6392\u9664\u8303\u56F4\u3001\u9A8C\u8BC1\u65B9\u5F0F\u548C\u6B8B\u4F59\u5047\u8BBE\u3002
829
+ 3. \u53EA\u6309\u8BA1\u5212\u8F7B\u91CF\u5B9E\u65BD\uFF1B\u4E0D\u91CD\u65B0\u89C4\u5212\u3001\u4E0D\u8C03\u7528 Film Reviewer\u3001\u4E0D\u521B\u5EFA\u65B0\u7684 /man workflow\u3002
830
+ 4. \u53D1\u73B0\u4F1A\u6539\u53D8\u67B6\u6784\u3001\u8303\u56F4\u6216\u9A8C\u6536\u7684\u7F3A\u53E3\u65F6\u6682\u505C\u5E76\u8BE2\u95EE\uFF0C\u4E0D\u81EA\u884C\u6269\u5927\u8BA1\u5212\u3002
831
+ 5. \u5B8C\u6210\u6700\u7A84\u6709\u6548\u9A8C\u8BC1\u548C\u4E00\u6B21\u53D7\u9650 diff \u81EA\u68C0\u540E\uFF0C\u8FD0\u884C \`mancode workflow handoff <taskId> --complete\`\uFF1B\u7531 CLI \u539F\u5B50\u6E05\u7406 \`activeSoloPlan\`\u3001\u628A workflow \u6807\u8BB0 completed \u5E76\u79FB\u51FA Active Plans\uFF0C\u4FDD\u7559\u8BA1\u5212\u6587\u4EF6\u3002
832
+
774
833
  ### UI \u4EFB\u52A1\uFF08\u6761\u4EF6\u6267\u884C\uFF09
775
834
  \u4EC5\u5F53 project-profile \u786E\u8BA4\u6709 UI \u8D44\u4EA7\u4E14\u4EFB\u52A1\u786E\u5B9E\u6D89\u53CA\u754C\u9762\u65F6\uFF1A
776
835
  1. UserPromptSubmit hook \u4F1A\u6CE8\u5165\u9879\u76EE\u5BA1\u7F8E token\uFF08\u5982\u6709\uFF09
@@ -813,16 +872,18 @@ var CORE_CODING_PRINCIPLES = `## \u94C1\u5F8B\uFF08\u6C38\u4E0D\u8FDD\u53CD\uFF0
813
872
  // src/templates/skills/man.ts
814
873
  var MAN_SKILL = {
815
874
  name: "man",
816
- description: "Progressive 9-step engineering workflow: research, clarify, plan, choose plan-only or execution, implement, validate, review, and wrap up. Use for complex or high-risk changes.",
875
+ description: "Planning and progressive engineering workflow: research, align requirements, recommend options, produce a confirmed plan, then choose plan-only, lightweight solo implementation, or the full governed workflow. Use for planning requests and complex or high-risk changes.",
817
876
  body: `# mancode \xB7 /man (Progressive Governance)
818
877
 
819
- \u7528\u6237\u7528 \`/man <task>\` \u89E6\u53D1\u4F60\u3002\u7ACB\u5373\u8BFB \`.mancode/state.json\`\u3002\u82E5\u5DF2\u6709\u4E0D\u540C\u7684 active workflow\uFF0C\u4E0D\u5F97\u76F4\u63A5\u6E05\u7A7A\u6307\u9488\uFF1B\u5148\u8BA9\u7528\u6237\u9009\u62E9\u6062\u590D\u539F\u6D41\u7A0B\u6216\u7528 \`/mansolo\` \u653E\u5F03\u3002task \u4E3A\u7A7A\u65F6\u8BE2\u95EE\u4EFB\u52A1\uFF0C\u7B49\u5F85\u671F\u95F4\u4ECD\u662F \`man\` \u6A21\u5F0F\u3002
878
+ \u7528\u6237\u7528 \`/man <task>\` \u89E6\u53D1\u4F60\u3002\u7ACB\u5373\u8BFB \`.mancode/state.json\`\u3002\u82E5\u5DF2\u6709\u4E0D\u540C\u7684 active workflow\uFF0C\u4E0D\u5F97\u76F4\u63A5\u6E05\u7A7A\u6307\u9488\uFF1B\u5148\u8BA9\u7528\u6237\u9009\u62E9\u6062\u590D\u539F\u6D41\u7A0B\u6216\u7528 \`/mansolo\` \u653E\u5F03\u3002\u82E5 \`activeSoloPlan\` \u975E\u7A7A\uFF0C\u5148\u8BA9\u7528\u6237\u9009\u62E9\u7EE7\u7EED\u8BE5\u8BA1\u5212\uFF1B\u5DF2\u5B8C\u6210\u65F6\u8FD0\u884C \`mancode workflow handoff <\u65E7taskId> --complete\`\uFF0C\u653E\u5F03\u65F6\u53D6\u5F97\u786E\u8BA4\u540E\u901A\u8FC7 CLI \u6807\u8BB0\u65E7 workflow abandoned \u5E76\u6E05\u7406\u6307\u9488\uFF0C\u7136\u540E\u624D\u80FD\u521B\u5EFA\u65B0\u8BA1\u5212\u3002handoff CLI \u4F1A\u62D2\u7EDD\u9759\u9ED8\u8986\u76D6\u3002task \u4E3A\u7A7A\u65F6\u8BE2\u95EE\u4EFB\u52A1\uFF0C\u7B49\u5F85\u671F\u95F4\u4ECD\u662F \`man\` \u6A21\u5F0F\u3002
879
+
880
+ \u7528 \`mancode workflow create man "<task>" --json\` \u521B\u5EFA workflow \u5E76\u8BFB\u53D6\u8FD4\u56DE\u7684 taskId\uFF1B\u4E0D\u5F97\u76F4\u63A5\u521B\u5EFA\u6216\u6539\u5199 metadata.json\u3002\u968F\u540E\u53EA\u7528 workflow CLI \u66F4\u65B0\u9700\u6C42\u3001\u9A8C\u8BC1\u3001step/status/planVersion/skippedSteps\u3002\u901A\u7528 \`--skipped\` \u53EA\u7528\u4E8E Step 1\u20132 \u7684 \`clarification\`\uFF1B\u7528\u6237\u660E\u786E\u8DF3\u8FC7\u6574\u4E2A review \u65F6\u5FC5\u987B\u5728 Step 6 \u4F7F\u7528\u4E13\u7528 \`workflow review ... skip --reason\`\uFF0C\u4E0D\u80FD\u5199\u6570\u5B57\u3001\`film-1\` \u6216 \`film-2\`\u3002state \u7684 \`currentMode\`\u3001\`currentTask\` \u548C \`currentWorkflowMode\` \u6307\u5411\u672C task\u3002
820
881
 
821
- \u7528 \`mancode workflow create man "<task>" --json\` \u521B\u5EFA workflow \u5E76\u8BFB\u53D6\u8FD4\u56DE\u7684 taskId\uFF1B\u4E0D\u5F97\u76F4\u63A5\u521B\u5EFA\u6216\u6539\u5199 metadata.json\u3002\u968F\u540E\u53EA\u7528 \`mancode workflow update\` \u66F4\u65B0 step/status/planVersion/skippedSteps\u3002state \u7684 \`currentMode\`\u3001\`currentTask\` \u548C \`currentWorkflowMode\` \u6307\u5411\u672C task\u3002
882
+ ## \u8BA1\u5212\u804C\u8D23\u4E0E\u6280\u672F\u9009\u62E9\u5173\u5361
822
883
 
823
- ## \u65B0\u9879\u76EE\u6280\u672F\u9009\u62E9\u5173\u5361\uFF08\u4EC5\u5728\u9002\u7528\u65F6\uFF09
884
+ \u628A \`/man\` \u4F5C\u4E3A\u6B63\u5F0F\u8BA1\u5212\u5165\u53E3\uFF1A\u5148\u5BF9\u9F50\u9700\u6C42\u5E76\u4EA7\u51FA\u53EF\u786E\u8BA4\u7684\u8BA1\u5212\uFF0C\u4E0D\u56E0\u8BA1\u5212\u5B8C\u6210\u800C\u81EA\u52A8\u8FDB\u5165\u5B8C\u6574\u5B9E\u65BD\u3002\u8BA1\u5212\u5173\u5361\u5FC5\u987B\u8BA9\u7528\u6237\u9009\u62E9\u53EA\u4FDD\u7559\u8BA1\u5212\u3001\u4EA4\u7ED9\u9ED8\u8BA4 \`solo\` \u8F7B\u91CF\u5B9E\u65BD\u3001\u7EE7\u7EED\u5B8C\u6574 \`/man\` \u6216\u4FEE\u6539\u8BA1\u5212\u3002
824
885
 
825
- \u5148\u8BFB \`.mancode/project-profile.json\`\u3002\u5982\u679C\u7528\u6237\u8981\u65B0\u5EFA\u9879\u76EE\u3001profile \u4E3A unknown\uFF0C\u4E14\u7528\u6237\u6CA1\u6709\u6307\u5B9A\u6280\u672F\u6808\uFF1A\u5148\u6536\u96C6\u76EE\u6807\u5E73\u53F0\u3001\u7528\u6237\u89C4\u6A21\u3001\u79BB\u7EBF/\u90E8\u7F72\u3001\u56E2\u961F\u719F\u6089\u5EA6\u3001\u9884\u7B97\u4E0E\u96C6\u6210\u7EA6\u675F\uFF1B\u5217\u51FA 2\u20133 \u4E2A\u53EF\u884C\u65B9\u6848\u7684\u4F18\u7F3A\u70B9\u548C\u63A8\u8350\u7406\u7531\uFF0C\u53D6\u5F97\u7528\u6237\u786E\u8BA4\u540E\u624D\u521B\u5EFA\u811A\u624B\u67B6\u3002\u4E0D\u8981\u628A\u4EFB\u4F55\u8BED\u8A00\u3001\u6846\u67B6\u3001UI \u5E93\u6216\u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5F53\u4F5C\u9ED8\u8BA4\u3002\u5DF2\u6709\u9879\u76EE\u5219\u4EE5\u68C0\u6D4B\u5230\u7684\u4E8B\u5B9E\u548C\u4ED3\u5E93\u7EA6\u5B9A\u4E3A\u51C6\u3002
886
+ \u5148\u8BFB \`.mancode/project-profile.json\`\u3002\u5982\u679C\u7528\u6237\u8981\u65B0\u5EFA\u9879\u76EE\u3001profile \u4E3A unknown\uFF0C\u4E14\u7528\u6237\u6CA1\u6709\u6307\u5B9A\u6280\u672F\u6808\uFF1A\u6536\u96C6\u9002\u7528\u7684\u76EE\u6807\u5E73\u53F0\u3001\u89C4\u6A21\u3001\u79BB\u7EBF/\u90E8\u7F72\u3001\u56E2\u961F\u719F\u6089\u5EA6\u3001\u9884\u7B97\u4E0E\u96C6\u6210\u7EA6\u675F\uFF1B\u5217\u51FA 2\u20133 \u4E2A\u53EF\u884C\u65B9\u6848\u7684\u4F18\u7F3A\u70B9\u3001\u660E\u786E\u63A8\u8350\u548C\u63A8\u8350\u7406\u7531\uFF0C\u53D6\u5F97\u7528\u6237\u786E\u8BA4\u3002\u4E0D\u8981\u628A\u8BED\u8A00\u3001\u6846\u67B6\u3001UI \u5E93\u3001\u6570\u636E\u5B58\u50A8\u6216\u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5F53\u4F5C\u9759\u9ED8\u9ED8\u8BA4\u3002\u5DF2\u6709\u9879\u76EE\u4EE5\u68C0\u6D4B\u5230\u7684\u4E8B\u5B9E\u548C\u4ED3\u5E93\u7EA6\u5B9A\u4E3A\u51C6\u3002
826
887
 
827
888
  ### Step 1: Scout \u8C03\u7814
828
889
 
@@ -830,18 +891,34 @@ var MAN_SKILL = {
830
891
 
831
892
  ### Step 2: \u9700\u6C42\u6F84\u6E05
832
893
 
833
- \u4E3B skill \u76F4\u63A5\u57FA\u4E8E Scout \u62A5\u544A\u548C\u4EFB\u52A1\u6B67\u4E49\u63D0\u95EE\uFF1A\u5BA2\u89C2\u9898\u7528 AskUserQuestion\uFF08\u6BCF\u6B21\u6700\u591A 4 \u4E2A\uFF09\uFF0C\u4E3B\u89C2\u9898\u5F00\u653E\u63D0\u95EE\u3002\u6700\u591A\u4E24\u8F6E\uFF0C\u56DE\u7B54\u5199 \`requirements.md\`\uFF1B\u65E0\u95EE\u9898\u5219\u901A\u8FC7 CLI \u5C06 \`clarify\` \u5199\u5165\u7D2F\u8BA1 skippedSteps\u3002\u7B2C\u4E8C\u8F6E\u540E\u5C06\u5047\u8BBE\u3001\u672A\u51B3\u98CE\u9669\u548C\u4FDD\u5B88\u9ED8\u8BA4\u503C\u5199\u5165 requirements\uFF0C\u4E0D\u65E0\u9650\u8FFD\u95EE\u3002\u5B8C\u6210\u540E\u66F4\u65B0\u81F3 Step 3\u3002
894
+ \u57FA\u4E8E Scout \u62A5\u544A\u505A\u9700\u6C42\u5C31\u7EEA\u5224\u65AD\uFF0C\u4E0D\u7528\u56FA\u5B9A\u8F6E\u6570\u6216\u95EE\u9898\u6570\u91CF\u4EE3\u66FF\u7406\u89E3\u8D28\u91CF\u3002\u628A\u6240\u6709\u4F1A\u6539\u53D8\u8303\u56F4\u3001\u67B6\u6784\u3001\u6210\u672C\u6216\u9A8C\u6536\u4E14\u65E0\u6CD5\u4ECE\u9879\u76EE\u4E8B\u5B9E\u4E2D\u67E5\u6E05\u7684\u7591\u95EE\u95EE\u51FA\u6765\uFF1B\u5141\u8BB8\u6309\u9700\u8981\u5206\u591A\u6279\u6F84\u6E05\uFF0C\u4E0D\u9650\u5236\u6BCF\u6279\u6570\u91CF\u3002\u6BCF\u6B21\u56DE\u7B54\u540E\u91CD\u65B0\u5224\u65AD\uFF0C\u53EA\u8FFD\u95EE\u4ECD\u963B\u585E\u8BA1\u5212\u7684\u672A\u77E5\u9879\uFF0C\u4E0D\u91CD\u590D\u5DF2\u786E\u8BA4\u5185\u5BB9\u3002
895
+
896
+ \u628A\u672A\u77E5\u9879\u5206\u4E3A\uFF1A
897
+ - **blocking**\uFF1A\u6280\u672F\u6808\u3001\u6838\u5FC3\u8303\u56F4\u3001\u6301\u4E45\u5316\u3001\u4E3B\u8981\u4EA4\u4E92\u3001\u5173\u952E\u6027\u80FD/\u517C\u5BB9\u6027/\u5B89\u5168\u76EE\u6807\u7B49\u4F1A\u6539\u53D8\u65B9\u6848\u7684\u51B3\u7B56\uFF0C\u5FC5\u987B\u786E\u8BA4\u3002
898
+ - **recommendable**\uFF1A\u6709\u5408\u9002\u65B9\u6848\u65F6\u76F4\u63A5\u7ED9 2\u20133 \u4E2A\u65B9\u6848\u3001\u4F18\u7F3A\u70B9\u548C\u660E\u786E\u63A8\u8350\uFF0C\u8BA9\u7528\u6237\u63A5\u53D7\u6216\u8C03\u6574\uFF1B\u4E0D\u8981\u53EA\u628A\u9009\u62E9\u9898\u629B\u7ED9\u7528\u6237\u3002
899
+ - **defaultable**\uFF1A\u547D\u540D\u3001\u5FAE\u5C0F\u6837\u5F0F\u7B49\u4F4E\u5F71\u54CD\u7EC6\u8282\uFF0C\u53EF\u91C7\u7528\u9ED8\u8BA4\u503C\uFF0C\u4F46\u5199\u660E\u9ED8\u8BA4\u503C\u548C\u7406\u7531\u3002
900
+
901
+ \u6309\u4EFB\u52A1\u5B9E\u9645\u9002\u7528\u8303\u56F4\u68C0\u67E5\uFF1A\u7528\u6237\u76EE\u6807\u4E0E\u5E73\u53F0\u3001\u6838\u5FC3\u6D41\u7A0B\u3001\u9996\u671F\u8303\u56F4\u3001\u6392\u9664\u9879\u3001\u6280\u672F\u4E0E\u8FD0\u884C\u7EA6\u675F\u3001\u6570\u636E/\u72B6\u6001/\u96C6\u6210\u3001\u6027\u80FD/\u517C\u5BB9\u6027/\u5B89\u5168\u3001\u53EF\u9A8C\u8BC1\u5B8C\u6210\u6807\u51C6\u3002\u8FDE\u7EED\u6F84\u6E05\u6CA1\u6709\u51CF\u5C11 blocking \u9879\u65F6\uFF0C\u4E0D\u6563\u95EE\uFF1B\u8BA9\u7528\u6237\u9009\u62E9\u7F29\u5C0F\u9996\u671F\u8303\u56F4\u3001\u63A5\u53D7\u5217\u660E\u7684\u63A8\u8350\u9ED8\u8BA4\u503C\u6216\u6682\u505C\u3002
902
+
903
+ \u628A\u7ED3\u8BBA\u5199\u5165\u7ED3\u6784\u5316\u9700\u6C42\u8F93\u5165\uFF0C\u81F3\u5C11\u5305\u542B \`goal\`\u3001\u975E\u7A7A \`confirmedScope\`\u3001\`excludedScope\`\u3001\`technicalDecisions\`\u3001\`defaults\`\u3001\`blockingUnknowns\`\u3001\`coverage\` \u548C \`acceptanceCriteria\`\u3002coverage \u5FC5\u987B\u9010\u9879\u8BF4\u660E platform\u3001core_scope\u3001technical_stack\u3001data_and_persistence\u3001performance\u3001compatibility\u3001security \u662F \`confirmed\`\u3001\`defaulted\` \u6216\u5E26\u7406\u7531\u7684 \`not_applicable\`\uFF0C\u4E0D\u80FD\u7528\u7A7A\u6570\u7EC4\u5047\u88C5\u5DF2\u7ECF\u8003\u8651\u3002\u6BCF\u4E2A\u6838\u5FC3\u7528\u6237\u884C\u4E3A\u5FC5\u987B\u6709\u7A33\u5B9A\u9A8C\u6536 ID\uFF08\`AC-1\` \u7B49\uFF09\u3001\u63CF\u8FF0\u3001\`required\` \u548C\u9A8C\u8BC1\u65B9\u5F0F \`automated\` / \`manual\` / \`hybrid\`\uFF1B\u81F3\u5C11\u4E00\u4E2A\u9A8C\u6536\u9879\u5FC5\u9700\u3002\u8FD0\u884C \`mancode workflow requirements <taskId> finalize --file <requirements-input.json>\`\uFF0C\u7531 CLI \u6821\u9A8C\u5E76\u751F\u6210\u6743\u5A01 \`requirements.json\` \u4E0E \`requirements.md\`\uFF0C\u4E0D\u8981\u624B\u5DE5\u5236\u9020\u4E24\u4EFD\u53EF\u80FD\u51B2\u7A81\u7684\u7ED3\u8BBA\u3002\u65E0\u987B\u63D0\u95EE\u65F6\u53EF\u628A \`clarification\` \u8BB0\u5165\u7D2F\u8BA1 skippedSteps\uFF0C\u4F46\u4E0D\u80FD\u8DF3\u8FC7\u9700\u6C42\u6458\u8981\u4E0E\u5C31\u7EEA\u5224\u65AD\u3002\u4ECD\u6709 blocking \u9879\u65F6\u505C\u5728 Step 2\uFF1B\u53EA\u6709 CLI \u8FD4\u56DE ready \u624D\u8FD0\u884C \`mancode workflow update <taskId> --step 3\`\u3002
834
904
 
835
905
  ### Step 3: Plan Coach \u51FA\u8BA1\u5212
836
906
 
837
- \u8C03\u7528\u53EA\u8BFB \`plan-coach\`\uFF0C\u8F93\u5165 task\u3001scout-report \u548C requirements\uFF1BPlan Coach \u53EA\u8FD4\u56DE\u8BA1\u5212\u6587\u672C\uFF0C\u7531\u4E3B skill \u5199\u5165 \`plan.md\`\u3002\u8BA1\u5212\u5FC5\u987B\u542B\u4EFB\u52A1\u5206\u7EA7\u3001\u6A21\u5757\u7D22\u5F15\u3001\u590D\u7528\u8D44\u6E90\u4E0E scout \u884C\u53F7\u3001\u6700\u5C0F\u7B56\u7565\u3001\u4E0D\u505A\u4EC0\u4E48\u3001\u5B8C\u6210\u5B9A\u4E49\u3001\u9A8C\u8BC1\u4E0E smoke test\u3001\u9884\u4F30\u3002\u9996\u6B21\u8BA1\u5212\u5199\u5165\u6210\u529F\u540E\u8FD0\u884C \`mancode workflow update <taskId> --step 4\`\uFF0C\u7531 CLI upsert Active Plans \u5E76\u8FDB\u5165\u8BA1\u5212\u5173\u5361\uFF1B\u91CD\u5199\u8BA1\u5212\u65F6\u4FDD\u6301\u5728 Step 4\uFF0C\u8FD0\u884C \`--plan-version <\u5F53\u524D\u7248\u672C+1>\`\uFF0C\u4E0D\u5F97\u76F4\u63A5\u7F16\u8F91 planVersion\u3002
907
+ \u8C03\u7528\u53EA\u8BFB \`plan-coach\`\uFF0C\u8F93\u5165 task\u3001scout-report\u3001\`requirements.json\` \u548C\u6E32\u67D3\u540E\u7684 \`requirements.md\`\u3002Plan Coach \u5148\u8FD4\u56DE \`READY_FOR_PLAN\` \u6216 \`NEEDS_CLARIFICATION\`\u3002\u540E\u8005\u53EA\u5217\u7F3A\u5931\u51B3\u7B56\u3001\u5F71\u54CD\u3001\u63A8\u8350\u548C\u95EE\u9898\uFF1B\u4E3B skill \u5C06 workflow \u9000\u56DE Step 2 \u5E76\u91CD\u65B0 finalize requirements\uFF0C\u4E0D\u5F97\u5F3A\u884C\u8865\u5168\u8BA1\u5212\u3002\u524D\u8005\u8FD4\u56DE\u8BA1\u5212\u6587\u672C\uFF0C\u7531\u4E3B skill \u5199\u5165 \`plan.md\`\u3002
908
+
909
+ \u8BA1\u5212\u5FC5\u987B\u542B\u9700\u6C42\u6458\u8981\u3001\u4EFB\u52A1\u5206\u7EA7\u3001\u6280\u672F\u9009\u62E9\u53CA\u7406\u7531\u3001\u6A21\u5757\u7D22\u5F15\u3001\u590D\u7528\u8D44\u6E90\u4E0E scout \u884C\u53F7\u3001\u6838\u5FC3\u884C\u4E3A\u3001\u6700\u5C0F\u7B56\u7565\u3001\u4E0D\u505A\u4EC0\u4E48\u3001\u6B65\u9AA4\u3001\u98CE\u9669/\u56DE\u9000\u3001\u5B8C\u6210\u5B9A\u4E49\u3001\u771F\u5B9E\u9A8C\u8BC1\u4E0E smoke test\u3001\u9884\u4F30\u548C\u975E\u963B\u585E\u9ED8\u8BA4\u503C\u3002\u9996\u6B21\u8BA1\u5212\u5199\u5165\u6210\u529F\u540E\u8FD0\u884C \`mancode workflow update <taskId> --step 4\`\uFF1B\u91CD\u5199\u8BA1\u5212\u65F6\u4FDD\u6301 Step 4\uFF0C\u8FD0\u884C \`--plan-version <\u5F53\u524D\u7248\u672C+1>\`\uFF0C\u4E0D\u5F97\u76F4\u63A5\u7F16\u8F91 planVersion\u3002
838
910
 
839
911
  ### Step 4: \u8BA1\u5212\u5173\u5361
840
912
 
841
913
  \u7528 AskUserQuestion \u8BA9\u7528\u6237\u9009\u62E9\uFF1A
842
- 1. **\u53EA\u8981\u8BA1\u5212**\uFF1ACLI \u66F4\u65B0 \`status: planned\`\uFF0Cstate \u56DE solo \u5E76\u6E05\u7A7A workflow \u6307\u9488\uFF0C\u7ED3\u675F\u3002
843
- 2. **\u7EE7\u7EED\u6267\u884C\uFF08\u63A8\u8350\uFF09**\uFF1ACLI \u66F4\u65B0\u81F3 Step 5\u3002
844
- 3. **\u4FEE\u6539\u8BA1\u5212**\uFF1A\u6536\u96C6\u610F\u89C1\u5E76\u91CD\u8DD1 Step 3 \u7684 Plan Coach\uFF0Cworkflow \u6307\u9488\u4FDD\u6301 Step 4\uFF1B\u8BA1\u5212\u91CD\u5199\u5B8C\u6210\u540E\u9012\u589E planVersion\u3002
914
+ 1. **\u4EA4\u7ED9 solo \u8F7B\u91CF\u6267\u884C**\uFF1A\u5E38\u89C4\u4F4E\u98CE\u9669\u4EFB\u52A1\u63A8\u8350\u3002\u8FD0\u884C \`mancode workflow handoff <taskId> --to solo\`\uFF1B\u4FDD\u7559\u5DF2\u786E\u8BA4\u8BA1\u5212\u548C Active Plans\uFF0C\u7531 solo \u6309\u8BA1\u5212\u505A\u6700\u7A84\u9A8C\u8BC1\u548C\u4E00\u6B21\u53D7\u9650\u81EA\u68C0\uFF0C\u4E0D\u8FDB\u5165 Step 5\u20139\u3002
915
+ 2. **\u7EE7\u7EED\u5B8C\u6574 /man**\uFF1A\u9AD8\u98CE\u9669\u6216\u9700\u8981\u72EC\u7ACB\u5BA1\u67E5\u65F6\u63A8\u8350\u3002\u5148\u8FD0\u884C \`mancode workflow decide <taskId> --plan-decision governed_execution\`\uFF0C\u8F93\u51FA\u5F00\u5DE5\u56DE\u6267\uFF0C\u518D\u66F4\u65B0\u81F3 Step 5\u3002
916
+ 3. **\u53EA\u8981\u8BA1\u5212\uFF08\u4EC5\u4FDD\u7559\uFF09**\uFF1A\u8FD0\u884C \`mancode workflow decide <taskId> --plan-decision plan_only\`\uFF0C\u7531 CLI \u539F\u5B50\u4FDD\u5B58 planned \u72B6\u6001\u3001\u5207\u56DE solo \u5E76\u6E05\u7A7A\u53D7\u7BA1 workflow \u6307\u9488\uFF0C\u7ED3\u675F\u3002
917
+ 4. **\u4FEE\u6539\u8BA1\u5212**\uFF1A\u6536\u96C6\u610F\u89C1\uFF1B\u82E5\u6539\u53D8\u9700\u6C42\u5219\u56DE Step 2\uFF0C\u5426\u5219\u91CD\u8DD1 Plan Coach\u3002\u91CD\u5199\u5B8C\u6210\u540E\u9012\u589E planVersion\uFF0C\u4FDD\u6301\u8BA1\u5212\u51B3\u7B56\u4E3A\u7A7A\u3002
918
+
919
+ \u6839\u636E\u98CE\u9669\u660E\u786E\u63A8\u8350\u7406\u7531\uFF0C\u4E0D\u628A\u5B8C\u6574 \`/man\` \u6C38\u8FDC\u6807\u4E3A\u63A8\u8350\u3002\u9274\u6743\u3001\u652F\u4ED8\u3001\u654F\u611F\u6570\u636E\u3001\u8FC1\u79FB/\u5220\u9664\u3001\u516C\u5F00 API\u3001\u672A\u53EF\u4FE1\u8F93\u5165\u3001\u5E76\u53D1\u3001\u8DE8\u670D\u52A1\u6216\u57FA\u7840\u8BBE\u65BD\u9ED8\u8BA4\u63A8\u8350\u5B8C\u6574 \`/man\`\uFF1B\u666E\u901A\u539F\u578B\u3001\u5185\u90E8\u5DE5\u5177\u548C\u4F4E\u98CE\u9669\u529F\u80FD\u9ED8\u8BA4\u63A8\u8350 solo\u3002
920
+
921
+ \u786E\u8BA4\u6267\u884C\u540E\u3001\u4FEE\u6539\u4E1A\u52A1\u6587\u4EF6\u524D\u8F93\u51FA\u5F00\u5DE5\u56DE\u6267\uFF1A\u8BA1\u5212\u7248\u672C\u3001\u6267\u884C\u65B9\u5F0F\u3001\u76EE\u6807\u4E0E\u4EA4\u4ED8\u7269\u3001\u6280\u672F\u65B9\u6848\u3001\u5305\u542B\u8303\u56F4\u3001\u6392\u9664\u8303\u56F4\u3001\u9A8C\u8BC1\u65B9\u5F0F\u548C\u6B8B\u4F59\u5047\u8BBE\u3002\u4ECD\u6709 blocking \u672A\u77E5\u9879\u65F6\u4E0D\u5F97\u58F0\u79F0\u5F00\u59CB\u5B9E\u65BD\u3002
845
922
 
846
923
  ### Step 5: \u5B9E\u65BD
847
924
 
@@ -849,9 +926,11 @@ var MAN_SKILL = {
849
926
 
850
927
  ### Step 6: \u81EA\u6D4B\u3001\u8BCA\u65AD\u4E0E\u56DE\u5F52
851
928
 
852
- \u8FD0\u884C\u5B9E\u9645 build/lint/typecheck/test \u548C smoke test\u3002\u76F8\u540C\u4EE3\u7801\u3001\u73AF\u5883\u3001\u547D\u4EE4\u4E0B\u76F8\u540C\u9519\u8BEF\u7B7E\u540D\u5931\u8D25\u4E24\u6B21\uFF0C\u505C\u6B62\u76F2\u8BD5\u5E76\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u6D4F\u89C8\u5668\u3001\u590D\u6742\u590D\u73B0\u6216\u56DE\u5F52\u65F6\uFF0C\u7528 \`mancode workflow create manba "<\u95EE\u9898>" --parent-task <taskId> --json\` \u521B\u5EFA\u5B50 workflow\uFF1B\u7236\u4EFB\u52A1\u4FDD\u6301 Step 6\u3002\u5B50\u4EFB\u52A1 fixed/verified/no_repro \u540E\u6062\u590D\u672C\u4EFB\u52A1\uFF1B\u82E5\u7236\u66FE\u56E0\u8BE5\u5B50\u4EFB\u52A1 blocked\uFF0C\u5148\u901A\u8FC7 \`workflow update --status in_progress\` \u6062\u590D\uFF0C\u518D\u66F4\u65B0\u81F3 Step 7\u3002blocked \u6216 manual_test_required \u4F1A\u7531 CLI \u81EA\u52A8\u963B\u585E\u7236\u4EFB\u52A1\uFF0C\u4E0D\u5F97\u624B\u6539\u7236 metadata\uFF0C\u4E5F\u4E0D\u5F97\u81EA\u52A8\u8D8A\u8FC7\u4EBA\u5DE5\u9A8C\u8BC1\u8981\u6C42\u3002
929
+ \u5148\u8FD0\u884C \`mancode workflow verify <taskId> init\`\uFF0C\u518D\u6309 \`requirements.json\` \u7684\u6BCF\u4E2A\u9A8C\u6536 ID \u8BB0\u5F55\u771F\u5B9E\u7ED3\u679C\u3002\u81EA\u52A8 passed/failed \u5FC5\u987B\u4F7F\u7528 \`mancode workflow verify <taskId> record --acceptance AC-N --method automated --result passed|failed --evidence "<\u6458\u8981>" --command "<\u5B9E\u9645\u547D\u4EE4>" --exit-code <\u9000\u51FA\u7801> [--evidence-file <\u62A5\u544A>]\`\uFF1BCLI \u6821\u9A8C passed \u7684\u9000\u51FA\u7801\u4E3A 0\u3001failed \u4E3A\u975E 0\u3002\u9700\u8981\u771F\u5B9E\u6D4F\u89C8\u5668\u3001\u8BBE\u5907\u6216\u4EBA\u7684\u5224\u65AD\u65F6\uFF0C\u4F7F\u7528 \`mancode workflow verify <taskId> require-manual --acceptance AC-N --evidence "<\u81EA\u52A8\u5316\u4E0D\u80FD\u8986\u76D6\u7684\u539F\u56E0>"\`\uFF1BCLI \u4F1A\u963B\u585E\u4E3B\u4EFB\u52A1\u3002\u660E\u786E\u544A\u8BC9\u7528\u6237\u5177\u4F53\u5B9E\u6D4B\u6B65\u9AA4\u5E76\u505C\u4E0B\uFF0C\u53EA\u6709\u6536\u5230\u7528\u6237\u660E\u786E\u786E\u8BA4\u540E\u624D\u4F7F\u7528 \`mancode workflow verify <taskId> confirm-manual --acceptance AC-N --evidence "<\u7528\u6237\u786E\u8BA4\u539F\u6587>"\`\u3002\u6B64\u8BC1\u636E\u7528\u4E8E\u5BA1\u8BA1\uFF0C\u4E0D\u4EE3\u8868 CLI \u80FD\u8BA4\u8BC1\u64CD\u4F5C\u8005\u8EAB\u4EFD\u3002\u4E0D\u5F97\u7528\u9875\u9762\u52A0\u8F7D\u3001\u63A7\u5236\u63D0\u793A\u3001\u622A\u56FE\u3001\u4EE3\u7801\u9605\u8BFB\u6216 reviewer \u4EE3\u66FF\u6838\u5FC3\u4EA4\u4E92\u9A8C\u6536\u3002
853
930
 
854
- \u9A8C\u8BC1\u540E\u57FA\u4E8E**\u5B9E\u9645 diff**\u5199 \`review-scope.md\`\uFF1Abase\u3001\u6539\u52A8\u6587\u4EF6\u3001\u9700\u6C42\u3001\u5DF2\u8DD1\u9A8C\u8BC1\u3001\u786C\u98CE\u9669\u548C\u5BA1\u67E5\u6DF1\u5EA6\u3002\u9274\u6743\u3001\u652F\u4ED8\u3001\u654F\u611F\u6570\u636E\u3001\u8FC1\u79FB/\u5220\u9664\u3001\u516C\u5F00 API\u3001\u672A\u53EF\u4FE1\u8F93\u5165\u3001\u5E76\u53D1\u3001\u8DE8\u670D\u52A1\u6216\u57FA\u7840\u8BBE\u65BD\u547D\u4E2D\u4EFB\u4E00\u9879\u65F6\u7528\u5B8C\u6574\u5BA1\u67E5 \`full\`\uFF1B\u5426\u5219\u7528\u5B9A\u5411\u5BA1\u67E5 \`targeted\`\u3002\u8FD0\u884C \`mancode workflow review <taskId> init --review-depth targeted --review-domain quality\` \u6216 \`--review-depth full\`\u3002\u7528\u6237\u660E\u786E\u8DF3\u8FC7\u5BA1\u67E5\u65F6\u624D\u628A \`review\` \u5199\u5165\u7D2F\u8BA1 skippedSteps\uFF0C\u5E76\u8BB0\u5F55\u6B8B\u4F59\u98CE\u9669\u3002
931
+ \u8FD0\u884C\u5B9E\u9645 build/lint/typecheck/test \u548C smoke test\u3002\u76F8\u540C\u4EE3\u7801\u3001\u73AF\u5883\u3001\u547D\u4EE4\u4E0B\u76F8\u540C\u9519\u8BEF\u7B7E\u540D\u5931\u8D25\u4E24\u6B21\uFF0C\u505C\u6B62\u76F2\u8BD5\u5E76\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u590D\u6742\u590D\u73B0\u6216\u56DE\u5F52\u65F6\uFF0C\u7528 \`mancode workflow create manba "<\u95EE\u9898>" --parent-task <taskId> --json\` \u521B\u5EFA\u5B50 workflow\uFF1B\u7236\u4EFB\u52A1\u4FDD\u6301 Step 6\u3002\u5B50\u4EFB\u52A1 fixed/verified/no_repro \u540E\u6062\u590D\u672C\u4EFB\u52A1\uFF1B\`manual_test_required\` \u4ECD\u5FC5\u987B\u8D70\u4E0A\u8FF0\u4EBA\u5DE5\u786E\u8BA4\u3002\u6240\u6709 required \u9A8C\u6536\u53CA hybrid \u7684\u4E24\u4E2A\u90E8\u5206\u90FD passed \u540E\uFF0CCLI \u624D\u5141\u8BB8\u8FDB\u5165 Step 7 \u6216\u542F\u52A8 review\uFF1B\u8BA1\u5212\u7248\u672C\u6216\u7ED3\u6784\u5316\u9700\u6C42\u53D8\u5316\u4F1A\u4F7F\u65E7\u9A8C\u8BC1\u5931\u6548\u3002
932
+
933
+ \u9A8C\u8BC1\u540E\u57FA\u4E8E**\u5B9E\u9645 diff**\u5199 \`review-scope.md\`\uFF1Abase\u3001\u6539\u52A8\u6587\u4EF6\u3001\u9700\u6C42\u3001\u5DF2\u8DD1\u9A8C\u8BC1\u3001\u786C\u98CE\u9669\u548C\u5BA1\u67E5\u6DF1\u5EA6\u3002\u9274\u6743\u3001\u652F\u4ED8\u3001\u654F\u611F\u6570\u636E\u3001\u8FC1\u79FB/\u5220\u9664\u3001\u516C\u5F00 API\u3001\u672A\u53EF\u4FE1\u8F93\u5165\u3001\u5E76\u53D1\u3001\u8DE8\u670D\u52A1\u6216\u57FA\u7840\u8BBE\u65BD\u547D\u4E2D\u4EFB\u4E00\u9879\u65F6\u7528\u5B8C\u6574\u5BA1\u67E5 \`full\`\uFF1B\u5426\u5219\u7528\u5B9A\u5411\u5BA1\u67E5 \`targeted\`\u3002\u8FD0\u884C \`mancode workflow review <taskId> init --review-depth targeted --review-domain quality\` \u6216 \`--review-depth full\`\u3002\u53EA\u6709\u7528\u6237\u660E\u786E\u8981\u6C42\u8DF3\u8FC7\u5BA1\u67E5\u65F6\u624D\u8FD0\u884C \`mancode workflow review <taskId> skip --reason "<\u7528\u6237\u7406\u7531>"\`\uFF1BCLI \u4F1A\u8BB0\u5F55\u539F\u56E0\u5E76\u7D2F\u8BA1 \`review\`\uFF0C\u4E0D\u5F97\u901A\u8FC7\u901A\u7528 skipped \u53C2\u6570\u7ED5\u8FC7\u3002
855
934
 
856
935
  ### Step 7: Film #1 \u4EE3\u7801\u8D28\u91CF\u5BA1\u67E5\u4E0E\u4FEE\u590D
857
936
 
@@ -864,7 +943,7 @@ var MAN_SKILL = {
864
943
  ### Step 9: \u589E\u5F3A\u6536\u5C3E
865
944
 
866
945
  1. \u5B58\u5728 open blocker \u65F6\uFF0CHead Coach \u4E00\u6B21\u6027\u4FEE\u590D\u5168\u90E8 blocker\uFF0C\u5E76\u7528 \`mancode workflow review <taskId> remediate --resolved Q1,D1\` \u8BB0\u5F55\u552F\u4E00\u4E00\u8F6E\u4FEE\u590D\uFF1B\u6CA1\u6709 blocker \u65F6\u4E0D\u8FD0\u884C remediate\u3002\u4E0D\u8981\u4E3A \u{1F7E1}/\u{1F7E2} \u6269\u5927\u6539\u52A8\u3002
867
- 2. \u91CD\u8DD1\u53D7\u5F71\u54CD\u9A8C\u8BC1\uFF0C\u4E0D\u91CD\u65B0\u8FD0\u884C\u5DF2\u5B8C\u6210\u7684 reviewer\u3002\u4FEE\u590D\u82E5\u5F15\u5165\u65B0\u7684\u9AD8\u98CE\u9669\u9762\u5219\u6807\u8BB0 blocked\uFF0C\u4E0D\u80FD\u5F00\u542F\u65E0\u754C review \u5FAA\u73AF\u3002
946
+ 2. remediation \u4F1A\u4F7F\u65E7\u9A8C\u8BC1\u6574\u6279\u5931\u6548\u3002\u5728 Step 9 \u91CD\u8DD1\u5168\u90E8 required \u9A8C\u6536\uFF0C\u5E76\u901A\u8FC7 verify record/require-manual/confirm-manual \u91CD\u65B0\u767B\u8BB0\u8BC1\u636E\uFF1B\u672A\u91CD\u65B0\u5168\u90E8\u901A\u8FC7\u4E0D\u80FD completed\u3002\u4E0D\u91CD\u65B0\u8FD0\u884C\u5DF2\u5B8C\u6210\u7684 reviewer\u3002\u4FEE\u590D\u82E5\u5F15\u5165\u65B0\u7684\u9AD8\u98CE\u9669\u9762\u5219\u6807\u8BB0 blocked\uFF0C\u4E0D\u80FD\u5F00\u542F\u65E0\u754C review \u5FAA\u73AF\u3002
868
947
  3. \u5199 \`summary.md\`\uFF1A\u6539\u52A8\u3001\u65B0\u5EFA\u3001\u590D\u7528\u3001\u9A8C\u8BC1\u3001\u5BA1\u67E5\u6DF1\u5EA6\u3001findings \u5904\u7F6E\u3001\u8DF3\u8FC7\u6B65\u9AA4\u548C\u6B8B\u4F59\u98CE\u9669\u3002
869
948
  4. CLI \u786E\u8BA4\u6240\u9700\u5BA1\u67E5\u9886\u57DF\u5B8C\u6210\u4E14 blocker \u6E05\u96F6\u540E\u624D\u5199 \`completed\`\uFF1B\u5426\u5219\u7528 \`--status blocked --blocking-reason "<\u539F\u56E0>"\`\u3002
870
949
  5. \u5173\u952E\u51B3\u7B56 appendTeamDecision \u5230 \`decisions.md\`\uFF0C\u66F4\u65B0 Active Plans\u3002
@@ -1190,13 +1269,13 @@ var MANTEAM_SKILL = {
1190
1269
  \u6309 \`/man\` \u7684 9 \u6B65\u6D41\u7A0B\u6267\u884C\uFF08\u542B\u6F84\u6E05\u3001\u8BA1\u5212\u5173\u5361\u548C\u589E\u5F3A\u6536\u5C3E\uFF09\uFF0C\u4F46\u6BCF\u4E00\u6B65\u589E\u52A0\u56E2\u961F\u7EA6\u675F\uFF1A
1191
1270
 
1192
1271
  1. Scout Report\uFF1A\u5FC5\u987B\u5217\u51FA\u5171\u4EAB\u6587\u4EF6\u3001\u8FD1\u671F\u76F8\u5173\u63D0\u4EA4\u3001\u6F5C\u5728\u51B2\u7A81\u6587\u4EF6\u3002
1193
- 2. \u6F84\u6E05\uFF1A\u6700\u591A\u4E24\u8F6E\uFF1B\u628A\u5DF2\u786E\u8BA4\u7EA6\u675F\u548C\u4FDD\u5B88\u9ED8\u8BA4\u503C\u5199\u5165 requirements.md\u3002
1194
- 3. Game Plan\uFF1A\u8BA1\u5212\u91CC\u5FC5\u987B\u5305\u542B\u53D8\u66F4\u8FB9\u754C\u3001\u517C\u5BB9\u6027\u98CE\u9669\u3001\u56DE\u6EDA\u65B9\u5F0F\uFF1BPlan Coach \u53EA\u8FD4\u56DE\u8BA1\u5212\u6587\u672C\uFF0C\u7981\u6B62\u63D0\u524D\u4FEE\u6539\u4E1A\u52A1\u6587\u4EF6\u6216\u56E2\u961F memory\u3002
1195
- 4. \u8BA1\u5212\u5173\u5361\uFF1A\u7528\u6237\u53EF\u53EA\u4FDD\u7559\u8BA1\u5212\u3001\u7EE7\u7EED\u6267\u884C\u6216\u91CD\u5199\u8BA1\u5212\uFF1BActive Plans \u6309 taskId \u66F4\u65B0\u3002
1272
+ 2. \u6F84\u6E05\uFF1A\u6CBF\u7528 /man \u7684\u9700\u6C42\u5C31\u7EEA\u95E8\u69DB\uFF0C\u4E0D\u8BBE\u56FA\u5B9A\u8F6E\u6570\u6216\u6BCF\u6279\u95EE\u9898\u6570\u91CF\uFF1B\u95EE\u51FA\u6240\u6709\u4F1A\u6539\u53D8\u51B3\u7B56\u4E14\u65E0\u6CD5\u4ECE\u9879\u76EE\u4E8B\u5B9E\u67E5\u6E05\u7684\u7591\u95EE\uFF0C\u4E0D\u91CD\u590D\u5DF2\u786E\u8BA4\u5185\u5BB9\u3002\u6709\u5408\u9002\u65B9\u6848\u65F6\u76F4\u63A5\u7ED9\u51FA\u9009\u9879\u3001\u4F18\u7F3A\u70B9\u548C\u660E\u786E\u63A8\u8350\u3002\u901A\u8FC7 \`workflow requirements ... finalize\` \u56FA\u5316\u4E03\u4E2A coverage \u7EF4\u5EA6\u3001\u7ED3\u6784\u5316\u9700\u6C42\u548C\u5E26\u9A8C\u8BC1\u65B9\u5F0F\u7684\u9A8C\u6536 ID\uFF0C\u53EA\u6709 CLI \u5224\u5B9A ready \u624D\u8FDB\u5165\u8BA1\u5212\u3002
1273
+ 3. Game Plan\uFF1APlan Coach \u5148\u505A\u8F93\u5165\u5C31\u7EEA\u68C0\u67E5\uFF1B\u8BA1\u5212\u91CC\u5FC5\u987B\u5305\u542B\u53D8\u66F4\u8FB9\u754C\u3001\u6280\u672F\u9009\u62E9\u7406\u7531\u3001\u517C\u5BB9\u6027\u98CE\u9669\u3001\u56DE\u6EDA\u65B9\u5F0F\uFF0C\u7981\u6B62\u63D0\u524D\u4FEE\u6539\u4E1A\u52A1\u6587\u4EF6\u6216\u56E2\u961F memory\u3002
1274
+ 4. \u8BA1\u5212\u5173\u5361\uFF1A\u7528\u6237\u53EF\u53EA\u4FDD\u7559\u8BA1\u5212\u3001\u7EE7\u7EED\u5B8C\u6574\u56E2\u961F\u6267\u884C\u3001\u660E\u786E\u4EA4\u7ED9 solo \u8F7B\u91CF\u6267\u884C\u6216\u91CD\u5199\u8BA1\u5212\uFF1BActive Plans \u6309 taskId \u66F4\u65B0\u3002\u56E2\u961F\u5171\u4EAB\u6587\u4EF6\u6216\u4EA4\u63A5\u98CE\u9669\u5B58\u5728\u65F6\u63A8\u8350\u5B8C\u6574\u56E2\u961F\u6267\u884C\u3002
1196
1275
  5. \u5B9E\u65BD\uFF1A\u6539\u52A8\u524D\u518D\u6B21\u68C0\u67E5 \`git status --short\`\uFF0C\u907F\u514D\u8E29\u7528\u6237\u6216\u961F\u53CB\u6539\u52A8\u3002
1197
- 6. Self-test\uFF1A\u4F18\u5148\u8DD1\u9879\u76EE\u5DF2\u6709\u9A8C\u8BC1\u547D\u4EE4\uFF1B\u5931\u8D25\u4E24\u6B21\u505C\u4E0B\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u9A8C\u8BC1\u65F6\u521B\u5EFA\u5E26 parentTaskId \u7684 /manba \u5B50\u4EFB\u52A1\u3002\u5B50\u4EFB\u52A1\u4FEE\u590D\u540E\u82E5\u7236\u4EFB\u52A1\u66FE\u56E0\u6B64 blocked\uFF0C\u5148\u7528 workflow CLI \u6062\u590D\u7236\u4EFB\u52A1\u4E3A in_progress\uFF1Bmanual_test_required \u4E0D\u5F97\u81EA\u52A8\u6062\u590D\u3002
1276
+ 6. Self-test\uFF1A\u5728 Step 6 \u521D\u59CB\u5316 verification ledger\uFF0C\u628A\u6BCF\u4E2A required \u9A8C\u6536 ID \u7684\u81EA\u52A8\u3001\u4EBA\u5DE5\u6216 hybrid \u7ED3\u679C\u548C\u8BC1\u636E\u901A\u8FC7 CLI \u8BB0\u5F55\uFF1B\u81EA\u52A8\u7ED3\u679C\u5305\u542B\u547D\u4EE4\u4E0E\u9000\u51FA\u7801\uFF0C\u672A\u5168\u90E8\u901A\u8FC7\u4E0D\u5F97\u8FDB\u5165 review\u3002\u4F18\u5148\u8DD1\u9879\u76EE\u5DF2\u6709\u9A8C\u8BC1\u547D\u4EE4\uFF1B\u5931\u8D25\u4E24\u6B21\u505C\u4E0B\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u4EBA\u5DE5\u9A8C\u8BC1\u65F6\u6807\u8BB0 require-manual \u5E76\u7B49\u5F85\u7528\u6237\u660E\u786E\u786E\u8BA4\uFF0C\u4E0D\u5F97\u81EA\u52A8\u6062\u590D\u3002remediation \u540E\u5728 Step 9 \u91CD\u65B0\u767B\u8BB0\u5168\u90E8\u9A8C\u6536\u3002
1198
1277
  7. Review scope + Film #1\uFF1A\u57FA\u4E8E\u5B9E\u9645 diff \u5199 \`review-scope.md\`\uFF0C\u7528 \`workflow review ... init\` \u9009\u62E9 targeted \u6216 full\uFF1B\u91CD\u70B9\u5BA1\u67E5\u884C\u4E3A\u3001\u53EF\u7EF4\u62A4\u6027\u3001\u56E2\u961F\u98CE\u683C\u4E0E\u6D4B\u8BD5\uFF0Cfinding \u5FC5\u987B\u6709\u8BC1\u636E\u548C\u7A33\u5B9A ID\u3002
1199
- 8. Film #2\uFF1A\u4EC5 full \u6267\u884C\uFF0C\u5148\u8BFB Film #1 \u62A5\u544A\u5E76\u53BB\u91CD\uFF0C\u53EA\u5BA1\u67E5\u8FB9\u754C\u3001\u5B89\u5168\u3001\u6027\u80FD\u3001\u5E76\u53D1\u548C\u517C\u5BB9\u6027\u3002\u7528\u6237\u660E\u786E\u8981\u6C42\u624D\u53EF\u8DF3\u8FC7\u5168\u90E8 review\uFF0C\u5E76\u5199\u5165 \`skippedSteps\` \u548C\u6B8B\u4F59\u98CE\u9669\u3002
1278
+ 8. Film #2\uFF1A\u4EC5 full \u6267\u884C\uFF0C\u5148\u8BFB Film #1 \u62A5\u544A\u5E76\u53BB\u91CD\uFF0C\u53EA\u5BA1\u67E5\u8FB9\u754C\u3001\u5B89\u5168\u3001\u6027\u80FD\u3001\u5E76\u53D1\u548C\u517C\u5BB9\u6027\u3002targeted \u7684\u7B2C\u4E8C\u5BA1\u662F\u4E0D\u9002\u7528\uFF0C\u4E0D\u80FD\u8BB0\u4E3A skipped\uFF1B\u53EA\u6709\u7528\u6237\u660E\u786E\u8981\u6C42\u624D\u53EF\u8DF3\u8FC7\u5168\u90E8 review\uFF0C\u5E76\u5199\u5165 \`review\` \u548C\u6B8B\u4F59\u98CE\u9669\u3002
1200
1279
  9. Post-game\uFF1A\u6C47\u603B blocker\uFF0C\u53EA\u505A\u4E00\u8F6E\u4FEE\u590D\u5E76\u7528 \`workflow review ... remediate\` \u8BB0\u5F55\uFF1B\u590D\u9A8C\u540E\u5199 summary \u4E0E hand-off\u3002\u9A8C\u8BC1\u5931\u8D25\u3001\u5BA1\u67E5\u4E0D\u5B8C\u6574\u6216\u4ECD\u6709 blocker \u65F6\u6807\u8BB0 blocked\uFF0C\u4E0D\u5F97\u6807 completed\u3002
1201
1280
 
1202
1281
  \u5982\u679C\u7528\u6237\u5728\u786E\u8BA4\u9636\u6BB5\u9009\u62E9"\u9000\u51FA"\u6216\u653E\u5F03\u56E2\u961F workflow\uFF1A\u7528 \`mancode workflow update <taskId> --status abandoned\` \u66F4\u65B0\u5E76\u6E05\u7406 Active Plans\uFF1B\u82E5\u6709\u6D3B\u8DC3 manba \u5B50\u4EFB\u52A1\uFF0C\u5148\u53D6\u5F97\u786E\u8BA4\u5E76\u9010\u4E2A abandoned\u3002\u6210\u529F\u540E\u518D\u7528 Edit \u66F4\u65B0 state \u56DE solo\u3002\u4E0D\u8981\u76F4\u63A5\u6539 metadata\uFF0C\u4E5F\u4E0D\u8981\u628A abandoned workflow \u7559\u5728 active state\u3002
@@ -2164,8 +2243,8 @@ var MODE_META = {
2164
2243
  ].join("\n")
2165
2244
  },
2166
2245
  man: {
2167
- description: "Full high-risk workflow with plan, implementation, verification, and review. Use when the user asks for man or a high-risk production change.",
2168
- intro: "# mancode man \u2014 Full Review Workflow",
2246
+ description: "Planning and progressive workflow that aligns requirements, recommends options, produces a confirmed plan, and then offers plan-only, solo handoff, or full governed execution.",
2247
+ intro: "# mancode man \u2014 Planning and Progressive Delivery",
2169
2248
  workflow: [
2170
2249
  "## Progressive nine-step workflow (simulate roles in one conversation)",
2171
2250
  "",
@@ -2177,16 +2256,16 @@ var MODE_META = {
2177
2256
  "never advance state merely because a report was requested or an agent was called.",
2178
2257
  "",
2179
2258
  "Step 1 \u2014 Scout: inspect the profile, existing implementation, dependencies, risks, and unknowns; write `scout-report.md`.",
2180
- "Step 2 \u2014 Clarify: ask at most two rounds; write answers, conservative defaults, and unresolved risks to `requirements.md`.",
2181
- "Step 3 \u2014 Plan: write `plan.md` with scope, reuse, exclusions, risks, completion criteria, and real validation. First plan is v1; revisions use `--plan-version <current+1>` and Active Plans is updated by the CLI.",
2182
- "Step 4 \u2014 Plan gate: the user chooses plan-only (`status planned`), continue to execution, or revise the plan. Do not implement before this choice.",
2259
+ "Step 2 \u2014 Clarify by readiness, not a fixed round or question count. Ask every decision-changing question that cannot be resolved from project facts; use as many batches as needed without repeating confirmed answers. Classify unknowns as blocking, recommendable, or defaultable. When a good approach exists, present 2\u20133 options and make a clear recommendation. Create structured input with non-empty scope, exclusions, technical decisions, defaults, blocking unknowns, stable acceptance IDs, and explicit confirmed/defaulted/not-applicable coverage for platform, core scope, stack, data, performance, compatibility, and security. Run `mancode workflow requirements <taskId> finalize --file <input.json>`; only advance to Step 3 when the CLI returns ready.",
2260
+ "Step 3 \u2014 Plan Coach first returns READY_FOR_PLAN or NEEDS_CLARIFICATION. Never hide blocking gaps inside assumptions. When ready, write `plan.md` with confirmed scope, technical choices and reasons, reuse, exclusions, behavior, risks/rollback, completion criteria, defaults, and real validation. First plan is v1; revisions use `--plan-version <current+1>`.",
2261
+ "Step 4 \u2014 Plan gate: recommend based on risk and let the user choose (1) hand the confirmed plan to lightweight solo via `mancode workflow handoff <taskId> --to solo`, (2) continue full governed execution via `mancode workflow decide <taskId> --plan-decision governed_execution` before Step 5, (3) keep the plan only via `mancode workflow decide <taskId> --plan-decision plan_only`, or (4) revise. Do not implement before this choice. Before either execution path, echo the plan version, delivery, stack, inclusions, exclusions, validation, and residual assumptions.",
2183
2262
  "Step 5 \u2014 Implement the confirmed minimum change.",
2184
- "Step 6 \u2014 Run detected build/lint/typecheck/test and smoke checks. A real or complex diagnosis creates a linked manba child while the parent remains at Step 6. After a resolved child, restore a parent blocked by that child with `workflow update --status in_progress` before Step 7; never auto-resume manual_test_required. Then write `review-scope.md` from the actual diff, verification evidence, and hard-risk triggers. Initialize a targeted review for routine governed changes or a full review for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, cross-service, or infrastructure changes using `mancode workflow review <taskId> init ...`.",
2263
+ "Step 6 \u2014 Run `workflow verify <taskId> init`, execute detected build/lint/typecheck/test and smoke checks, and record every required acceptance ID with reproducible evidence; automated passed/failed records include the command and exit code. Use require-manual when a foreground browser, device, or human judgment is necessary; stop for explicit user confirmation before confirm-manual. The CLI blocks Step 7 and review until all checks pass. Then write `review-scope.md` and initialize targeted or full review. An explicit user review skip uses `workflow review <taskId> skip --reason <reason>` at Step 6, never generic skipped metadata.",
2185
2264
  "Step 7 \u2014 Run one quality review limited to the changed diff and direct impact. Findings require changed-line evidence and user impact, with at most three new findings. Record stable blocker IDs through `workflow review ... complete`; do not fix yet.",
2186
- "Step 8 \u2014 Only full review runs the security/boundary reviewer. It must read Film #1, mark the same root cause duplicate, and stay within security, permissions, recovery, resources, and boundaries. A targeted review ends after Film #1.",
2187
- "Step 9 \u2014 If open blockers exist, fix them in one remediation round and record resolved IDs through `workflow review ... remediate`; with no blockers, do not create a remediation round. Re-run affected verification without re-running completed reviewers. Write `summary.md` and set completed only when required domains are complete and blockers are zero; otherwise set blocked+blockingReason.",
2265
+ "Step 8 \u2014 Only full review runs the security/boundary reviewer. It must read Film #1, mark the same root cause duplicate, and stay within security, permissions, recovery, resources, and boundaries. A targeted review treats the second domain as not applicable; it is never recorded as a skipped step.",
2266
+ "Step 9 \u2014 If open blockers exist, fix them in one remediation round and record resolved IDs through `workflow review ... remediate`; remediation invalidates all earlier acceptance evidence, so re-run and re-record every required check at Step 9 without re-running completed reviewers. Write `summary.md` and set completed only when verification and required review domains are complete and blockers are zero.",
2188
2267
  "",
2189
- "After planned/completed/abandoned, return state to solo. On blocked, preserve enough state to resume. A CLI rejection is a hard stop, never a reason to bypass metadata validation."
2268
+ "A solo handoff keeps the workflow planned and stores activeSoloPlan in state; solo reads requirements.md and plan.md, implements the confirmed scope with narrow validation and one bounded self-check, and does not run the /man reviewers. After successful solo verification, run `mancode workflow handoff <taskId> --complete` to atomically clear the pointer, complete the workflow at Step 4, and remove it from Active Plans. After plan-only/completed/abandoned, return state to solo. On blocked, preserve enough state to resume. A CLI rejection is a hard stop, never a reason to bypass metadata validation."
2190
2269
  ].join("\n")
2191
2270
  },
2192
2271
  manteam: {
@@ -2288,6 +2367,9 @@ function renderProjectContext(state, tokens, profile, options) {
2288
2367
  `- UI library: ${uiLibrary}`,
2289
2368
  `- Project profile: ${currentProfile?.projectKind || "unknown"}; validation: ${currentProfile?.availableValidation?.join(", ") || "inspect project"}`,
2290
2369
  "- At the start of each session, read `.mancode/state.json` to check the current mode and project context.",
2370
+ ...state?.activeSoloPlan?.taskId ? [
2371
+ `- Active solo plan: ${state.activeSoloPlan.taskId} (plan v${state.activeSoloPlan.planVersion ?? 1}); read its requirements.md and plan.md before implementation.`
2372
+ ] : [],
2291
2373
  "- Read `.mancode/project-profile.json` before choosing tools or validation. Only for a UI task in a profile with detected UI assets, read `.mancode/aesthetics/style-tokens.json`."
2292
2374
  ].join("\n");
2293
2375
  }
@@ -4382,6 +4464,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
4382
4464
  currentTask: null,
4383
4465
  currentWorkflowMode: null,
4384
4466
  skippedSteps: [],
4467
+ activeSoloPlan: null,
4385
4468
  teamModeAutoDetected: teamModeEnabled,
4386
4469
  contributors: team.contributors,
4387
4470
  projectMode: isGenericProject ? "generic" : "detected"
@@ -6584,23 +6667,225 @@ async function pathExists9(p) {
6584
6667
  // src/commands/status.ts
6585
6668
  import { spawn } from "child_process";
6586
6669
  import { promises as fs9 } from "fs";
6587
- import path24 from "path";
6670
+ import path26 from "path";
6588
6671
  import process10 from "process";
6589
6672
 
6590
6673
  // src/system/workflow.ts
6591
6674
  import {
6592
6675
  mkdir as mkdir9,
6593
- readFile as readFile10,
6676
+ readFile as readFile12,
6594
6677
  readdir as readdir5,
6595
6678
  rm as rm4,
6596
6679
  stat as stat4,
6597
- writeFile as writeFile11
6680
+ writeFile as writeFile13
6598
6681
  } from "fs/promises";
6599
- import path23 from "path";
6682
+ import path25 from "path";
6600
6683
 
6601
- // src/system/review-ledger.ts
6602
- import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
6684
+ // src/system/requirements-ledger.ts
6685
+ import { createHash as createHash2 } from "crypto";
6686
+ import { readFile as readFile9, writeFile as writeFile10 } from "fs/promises";
6603
6687
  import path22 from "path";
6688
+ var REQUIREMENTS_FILE = "requirements.json";
6689
+ var REQUIREMENTS_MARKDOWN_FILE = "requirements.md";
6690
+ var ACCEPTANCE_ID_PATTERN = /^AC-[A-Z0-9][A-Z0-9-]{0,27}$/;
6691
+ var REQUIREMENT_DIMENSIONS = [
6692
+ "platform",
6693
+ "core_scope",
6694
+ "technical_stack",
6695
+ "data_and_persistence",
6696
+ "performance",
6697
+ "compatibility",
6698
+ "security"
6699
+ ];
6700
+ function parseRequirementsLedger(raw) {
6701
+ let value;
6702
+ try {
6703
+ value = JSON.parse(raw);
6704
+ } catch {
6705
+ throw new Error("requirements input must be valid JSON");
6706
+ }
6707
+ if (!isRecord8(value) || value.version !== 1) {
6708
+ throw new Error("requirements version must be 1");
6709
+ }
6710
+ const stringFields = [
6711
+ "confirmedScope",
6712
+ "excludedScope",
6713
+ "technicalDecisions",
6714
+ "defaults",
6715
+ "blockingUnknowns"
6716
+ ];
6717
+ if (typeof value.goal !== "string" || !value.goal.trim()) {
6718
+ throw new Error("requirements goal is required");
6719
+ }
6720
+ for (const field of stringFields) {
6721
+ if (!isNonEmptyStringArray(value[field])) {
6722
+ if (!Array.isArray(value[field]) || value[field].length > 0) {
6723
+ throw new Error(`requirements ${field} must contain non-empty strings`);
6724
+ }
6725
+ }
6726
+ }
6727
+ if (!Array.isArray(value.coverage)) {
6728
+ throw new Error("requirements coverage is required");
6729
+ }
6730
+ const coverageDimensions = /* @__PURE__ */ new Set();
6731
+ const coverage = value.coverage.map((item) => {
6732
+ if (!isRecord8(item) || !isRequirementDimension(item.dimension) || !isCoverageStatus(item.status) || typeof item.rationale !== "string" || !item.rationale.trim()) {
6733
+ throw new Error("invalid requirements coverage item");
6734
+ }
6735
+ if (coverageDimensions.has(item.dimension)) {
6736
+ throw new Error(`duplicate requirements coverage: ${item.dimension}`);
6737
+ }
6738
+ coverageDimensions.add(item.dimension);
6739
+ return {
6740
+ dimension: item.dimension,
6741
+ status: item.status,
6742
+ rationale: item.rationale.trim()
6743
+ };
6744
+ });
6745
+ const missingDimensions = REQUIREMENT_DIMENSIONS.filter(
6746
+ (dimension) => !coverageDimensions.has(dimension)
6747
+ );
6748
+ if (missingDimensions.length > 0) {
6749
+ throw new Error(
6750
+ `requirements coverage is missing: ${missingDimensions.join(", ")}`
6751
+ );
6752
+ }
6753
+ if (value.confirmedScope.length === 0) {
6754
+ throw new Error("requirements confirmedScope must not be empty");
6755
+ }
6756
+ const stackCoverage = coverage.find(
6757
+ (item) => item.dimension === "technical_stack"
6758
+ );
6759
+ if (stackCoverage?.status !== "not_applicable" && value.technicalDecisions.length === 0) {
6760
+ throw new Error(
6761
+ "requirements technicalDecisions are required when technical_stack applies"
6762
+ );
6763
+ }
6764
+ if (!Array.isArray(value.acceptanceCriteria) || value.acceptanceCriteria.length === 0) {
6765
+ throw new Error("requirements need at least one acceptance criterion");
6766
+ }
6767
+ const ids = /* @__PURE__ */ new Set();
6768
+ const acceptanceCriteria = value.acceptanceCriteria.map((item) => {
6769
+ if (!isRecord8(item) || typeof item.id !== "string" || !ACCEPTANCE_ID_PATTERN.test(item.id) || typeof item.description !== "string" || !item.description.trim() || typeof item.required !== "boolean" || !isVerificationMethod(item.method)) {
6770
+ throw new Error("invalid acceptance criterion");
6771
+ }
6772
+ if (ids.has(item.id)) {
6773
+ throw new Error(`duplicate acceptance criterion: ${item.id}`);
6774
+ }
6775
+ ids.add(item.id);
6776
+ return {
6777
+ id: item.id,
6778
+ description: item.description.trim(),
6779
+ required: item.required,
6780
+ method: item.method
6781
+ };
6782
+ });
6783
+ if (!acceptanceCriteria.some((item) => item.required)) {
6784
+ throw new Error(
6785
+ "requirements need at least one required acceptance criterion"
6786
+ );
6787
+ }
6788
+ return {
6789
+ version: 1,
6790
+ goal: value.goal.trim(),
6791
+ confirmedScope: normalizeStrings(value.confirmedScope),
6792
+ excludedScope: normalizeStrings(value.excludedScope),
6793
+ technicalDecisions: normalizeStrings(value.technicalDecisions),
6794
+ defaults: normalizeStrings(value.defaults),
6795
+ blockingUnknowns: normalizeStrings(value.blockingUnknowns),
6796
+ coverage,
6797
+ acceptanceCriteria
6798
+ };
6799
+ }
6800
+ function requirementsAreReady(ledger) {
6801
+ return ledger.blockingUnknowns.length === 0;
6802
+ }
6803
+ function requirementsDigest(ledger) {
6804
+ return createHash2("sha256").update(JSON.stringify(ledger)).digest("hex");
6805
+ }
6806
+ async function readRequirementsLedger(projectRoot, taskId) {
6807
+ try {
6808
+ const raw = await readFile9(requirementsPath(projectRoot, taskId), "utf-8");
6809
+ return parseRequirementsLedger(raw);
6810
+ } catch {
6811
+ return null;
6812
+ }
6813
+ }
6814
+ async function writeRequirementsArtifacts(projectRoot, taskId, ledger) {
6815
+ const dir = workflowDir(projectRoot, taskId);
6816
+ await writeFile10(
6817
+ path22.join(dir, REQUIREMENTS_FILE),
6818
+ `${JSON.stringify(ledger, null, 2)}
6819
+ `,
6820
+ "utf-8"
6821
+ );
6822
+ await writeFile10(
6823
+ path22.join(dir, REQUIREMENTS_MARKDOWN_FILE),
6824
+ renderRequirementsMarkdown(ledger),
6825
+ "utf-8"
6826
+ );
6827
+ }
6828
+ function renderRequirementsMarkdown(ledger) {
6829
+ const section = (title, items) => `## ${title}
6830
+
6831
+ ${items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : "- \u65E0"}
6832
+ `;
6833
+ const criteria = ledger.acceptanceCriteria.map(
6834
+ (item) => `- **${item.id}** [${item.required ? "\u5FC5\u9700" : "\u53EF\u9009"} / ${item.method}] ${item.description}`
6835
+ ).join("\n");
6836
+ const coverage = ledger.coverage.map((item) => `- **${item.dimension}** [${item.status}] ${item.rationale}`).join("\n");
6837
+ return `# Requirements
6838
+
6839
+ ## \u7528\u6237\u76EE\u6807
6840
+
6841
+ ${ledger.goal}
6842
+
6843
+ ${section("\u5DF2\u786E\u8BA4\u8303\u56F4", ledger.confirmedScope)}
6844
+ ${section("\u660E\u786E\u6392\u9664\u9879", ledger.excludedScope)}
6845
+ ${section("\u6280\u672F\u51B3\u7B56", ledger.technicalDecisions)}
6846
+ ${section("\u9ED8\u8BA4\u503C\u4E0E\u7406\u7531", ledger.defaults)}
6847
+ ## \u9700\u6C42\u8986\u76D6
6848
+
6849
+ ${coverage}
6850
+
6851
+ ## \u9A8C\u6536\u6807\u51C6
6852
+
6853
+ ${criteria}
6854
+
6855
+ ${section("\u963B\u585E\u6027\u672A\u51B3\u95EE\u9898", ledger.blockingUnknowns)}
6856
+ ## Readiness
6857
+
6858
+ ${requirementsAreReady(ledger) ? "READY" : "NEEDS_CLARIFICATION"}
6859
+ `;
6860
+ }
6861
+ function requirementsPath(projectRoot, taskId) {
6862
+ return path22.join(workflowDir(projectRoot, taskId), REQUIREMENTS_FILE);
6863
+ }
6864
+ function workflowDir(projectRoot, taskId) {
6865
+ return path22.join(projectRoot, ".mancode", "workflows", taskId);
6866
+ }
6867
+ function normalizeStrings(value) {
6868
+ return value.map((item) => item.trim());
6869
+ }
6870
+ function isNonEmptyStringArray(value) {
6871
+ return Array.isArray(value) && value.every((item) => typeof item === "string" && item.trim().length > 0);
6872
+ }
6873
+ function isVerificationMethod(value) {
6874
+ return value === "automated" || value === "manual" || value === "hybrid";
6875
+ }
6876
+ function isRequirementDimension(value) {
6877
+ return REQUIREMENT_DIMENSIONS.some((dimension) => dimension === value);
6878
+ }
6879
+ function isCoverageStatus(value) {
6880
+ return value === "confirmed" || value === "defaulted" || value === "not_applicable";
6881
+ }
6882
+ function isRecord8(value) {
6883
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6884
+ }
6885
+
6886
+ // src/system/review-ledger.ts
6887
+ import { mkdir as mkdir8, readFile as readFile10, stat as stat3, writeFile as writeFile11 } from "fs/promises";
6888
+ import path23 from "path";
6604
6889
  var REVIEW_FILE = "review-ledger.json";
6605
6890
  var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
6606
6891
  var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
@@ -6629,6 +6914,27 @@ async function initializeReview(projectRoot, taskId, depth, targetDomain) {
6629
6914
  await writeReviewLedger(projectRoot, taskId, ledger);
6630
6915
  return ledger;
6631
6916
  }
6917
+ async function initializeSkippedReview(projectRoot, taskId, reason) {
6918
+ assertValidTaskId(taskId);
6919
+ if (!reason.trim()) throw new Error("review skip reason is required");
6920
+ const existing = await readReviewLedger(projectRoot, taskId);
6921
+ if (existing) throw new Error(`review already initialized: ${taskId}`);
6922
+ const ledger = {
6923
+ version: "1.0",
6924
+ depth: "targeted",
6925
+ requiredDomains: [],
6926
+ completedDomains: [],
6927
+ reports: {},
6928
+ blockers: [],
6929
+ remediationRounds: 0,
6930
+ skipped: {
6931
+ reason: reason.trim(),
6932
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString()
6933
+ }
6934
+ };
6935
+ await writeReviewLedger(projectRoot, taskId, ledger);
6936
+ return ledger;
6937
+ }
6632
6938
  async function completeReviewDomain(projectRoot, taskId, domain, report, blockerIds) {
6633
6939
  const ledger = await requireReviewLedger(projectRoot, taskId);
6634
6940
  if (!ledger.requiredDomains.includes(domain)) {
@@ -6701,6 +7007,7 @@ async function remediateReviewBlockers(projectRoot, taskId, resolvedIds) {
6701
7007
  async function reviewCanComplete(projectRoot, taskId) {
6702
7008
  const ledger = await readReviewLedger(projectRoot, taskId);
6703
7009
  if (!ledger) return false;
7010
+ if (ledger.skipped) return true;
6704
7011
  return ledger.requiredDomains.every(
6705
7012
  (domain) => ledger.completedDomains.includes(domain)
6706
7013
  ) && ledger.blockers.every((blocker) => blocker.status === "resolved");
@@ -6708,7 +7015,7 @@ async function reviewCanComplete(projectRoot, taskId) {
6708
7015
  async function readReviewLedger(projectRoot, taskId) {
6709
7016
  if (!TASK_ID_PATTERN.test(taskId)) return null;
6710
7017
  try {
6711
- const raw = await readFile9(reviewPath(projectRoot, taskId), "utf-8");
7018
+ const raw = await readFile10(reviewPath(projectRoot, taskId), "utf-8");
6712
7019
  const value = JSON.parse(raw);
6713
7020
  return isReviewLedger(value) ? value : null;
6714
7021
  } catch {
@@ -6716,8 +7023,8 @@ async function readReviewLedger(projectRoot, taskId) {
6716
7023
  }
6717
7024
  }
6718
7025
  function isReviewLedger(value) {
6719
- if (!isRecord8(value)) return false;
6720
- if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord8(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
7026
+ if (!isRecord9(value)) return false;
7027
+ if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord9(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
6721
7028
  return false;
6722
7029
  }
6723
7030
  const requiredDomains = value.requiredDomains;
@@ -6725,6 +7032,9 @@ function isReviewLedger(value) {
6725
7032
  const reports = value.reports;
6726
7033
  const blockers = value.blockers;
6727
7034
  const expectedDomains = value.depth === "full" ? ["quality", "security"] : requiredDomains;
7035
+ if (value.skipped !== void 0) {
7036
+ return isRecord9(value.skipped) && typeof value.skipped.reason === "string" && value.skipped.reason.trim().length > 0 && typeof value.skipped.recordedAt === "string" && requiredDomains.length === 0 && completedDomains.length === 0 && Object.keys(reports).length === 0 && blockers.length === 0 && value.remediationRounds === 0;
7037
+ }
6728
7038
  if (value.depth === "targeted" && requiredDomains.length !== 1 || requiredDomains.length !== expectedDomains.length || !expectedDomains.every((domain) => requiredDomains.includes(domain)) || new Set(completedDomains).size !== completedDomains.length || completedDomains.some((domain) => !requiredDomains.includes(domain)) || completedDomains.some((domain) => {
6729
7039
  const report = reports[domain];
6730
7040
  return typeof report !== "string" || !isSafeReportPath(report);
@@ -6733,13 +7043,13 @@ function isReviewLedger(value) {
6733
7043
  }
6734
7044
  const blockerIds = /* @__PURE__ */ new Set();
6735
7045
  return blockers.every(
6736
- (blocker) => isRecord8(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
7046
+ (blocker) => isRecord9(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
6737
7047
  );
6738
7048
  }
6739
7049
  function isDomainArray(value) {
6740
7050
  return Array.isArray(value) && value.every(isReviewDomain);
6741
7051
  }
6742
- function isRecord8(value) {
7052
+ function isRecord9(value) {
6743
7053
  return typeof value === "object" && value !== null && !Array.isArray(value);
6744
7054
  }
6745
7055
  async function requireReviewLedger(projectRoot, taskId) {
@@ -6749,17 +7059,21 @@ async function requireReviewLedger(projectRoot, taskId) {
6749
7059
  return ledger;
6750
7060
  }
6751
7061
  async function writeReviewLedger(projectRoot, taskId, ledger) {
6752
- const dir = path22.join(projectRoot, ".mancode", "workflows", taskId);
7062
+ const dir = path23.join(projectRoot, ".mancode", "workflows", taskId);
6753
7063
  await mkdir8(dir, { recursive: true });
6754
- await writeFile10(
6755
- path22.join(dir, REVIEW_FILE),
7064
+ await writeFile11(
7065
+ path23.join(dir, REVIEW_FILE),
6756
7066
  `${JSON.stringify(ledger, null, 2)}
6757
7067
  `,
6758
7068
  "utf-8"
6759
7069
  );
6760
7070
  }
7071
+ function reviewLedgerPath(projectRoot, taskId) {
7072
+ assertValidTaskId(taskId);
7073
+ return reviewPath(projectRoot, taskId);
7074
+ }
6761
7075
  function reviewPath(projectRoot, taskId) {
6762
- return path22.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
7076
+ return path23.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
6763
7077
  }
6764
7078
  function assertValidTaskId(taskId) {
6765
7079
  if (!TASK_ID_PATTERN.test(taskId)) {
@@ -6772,7 +7086,7 @@ function assertSafeReportPath(report) {
6772
7086
  }
6773
7087
  }
6774
7088
  async function assertReportExists(projectRoot, taskId, report) {
6775
- const reportPath = path22.join(
7089
+ const reportPath = path23.join(
6776
7090
  projectRoot,
6777
7091
  ".mancode",
6778
7092
  "workflows",
@@ -6786,11 +7100,221 @@ async function assertReportExists(projectRoot, taskId, report) {
6786
7100
  throw new Error(`review report not found: ${report}`);
6787
7101
  }
6788
7102
  function isSafeReportPath(report) {
6789
- const normalized = path22.posix.normalize(report.replaceAll("\\", "/"));
7103
+ const normalized = path23.posix.normalize(report.replaceAll("\\", "/"));
7104
+ return Boolean(
7105
+ report.trim() && !path23.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
7106
+ );
7107
+ }
7108
+
7109
+ // src/system/verification-ledger.ts
7110
+ import { readFile as readFile11, writeFile as writeFile12 } from "fs/promises";
7111
+ import path24 from "path";
7112
+ var VERIFICATION_FILE = "verification-ledger.json";
7113
+ function initializeVerificationLedger(requirements, planVersion, remediationRound = 0) {
7114
+ const checks = requirements.acceptanceCriteria.map((criterion) => ({
7115
+ acceptanceId: criterion.id,
7116
+ required: criterion.required,
7117
+ ...criterion.method === "automated" || criterion.method === "hybrid" ? { automated: { status: "pending" } } : {},
7118
+ ...criterion.method === "manual" || criterion.method === "hybrid" ? { manual: { status: "pending" } } : {}
7119
+ }));
7120
+ return {
7121
+ version: 1,
7122
+ planVersion,
7123
+ requirementsDigest: requirementsDigest(requirements),
7124
+ remediationRound,
7125
+ status: deriveVerificationStatus(checks),
7126
+ checks
7127
+ };
7128
+ }
7129
+ function recordVerification(ledger, acceptanceId, component, result, evidence, automatedDetails) {
7130
+ if (!evidence.trim()) throw new Error("verification evidence is required");
7131
+ if (component === "automated" && result === "manual_required") {
7132
+ throw new Error("automated verification cannot be manual_required");
7133
+ }
7134
+ if (component === "automated" && (result === "passed" || result === "failed")) {
7135
+ if (!automatedDetails?.command.trim() || !Number.isInteger(automatedDetails.exitCode)) {
7136
+ throw new Error(
7137
+ "automated passed/failed verification requires command and exit code"
7138
+ );
7139
+ }
7140
+ if (result === "passed" && automatedDetails.exitCode !== 0) {
7141
+ throw new Error("passed verification requires exit code 0");
7142
+ }
7143
+ if (result === "failed" && automatedDetails.exitCode === 0) {
7144
+ throw new Error("failed verification requires a non-zero exit code");
7145
+ }
7146
+ }
7147
+ if (component === "manual" && result !== "manual_required") {
7148
+ throw new Error(
7149
+ "manual verification must use require-manual or confirm-manual"
7150
+ );
7151
+ }
7152
+ const index = ledger.checks.findIndex(
7153
+ (check2) => check2.acceptanceId === acceptanceId
7154
+ );
7155
+ if (index < 0)
7156
+ throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
7157
+ const check = ledger.checks[index];
7158
+ if (!check) throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
7159
+ if (!check[component]) {
7160
+ throw new Error(
7161
+ `${acceptanceId} does not require ${component} verification`
7162
+ );
7163
+ }
7164
+ const checks = [...ledger.checks];
7165
+ checks[index] = {
7166
+ ...check,
7167
+ [component]: {
7168
+ status: result,
7169
+ evidence: evidence.trim(),
7170
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7171
+ ...component === "automated" && automatedDetails ? {
7172
+ command: automatedDetails.command.trim(),
7173
+ exitCode: automatedDetails.exitCode,
7174
+ ...automatedDetails.evidenceFile ? { evidenceFile: automatedDetails.evidenceFile } : {}
7175
+ } : {}
7176
+ }
7177
+ };
7178
+ return { ...ledger, status: deriveVerificationStatus(checks), checks };
7179
+ }
7180
+ function resetVerificationForRemediation(ledger, remediationRound) {
7181
+ if (!Number.isInteger(remediationRound) || remediationRound < ledger.remediationRound) {
7182
+ throw new Error("invalid verification remediation round");
7183
+ }
7184
+ if (remediationRound === ledger.remediationRound) return ledger;
7185
+ const checks = ledger.checks.map((check) => ({
7186
+ acceptanceId: check.acceptanceId,
7187
+ required: check.required,
7188
+ ...check.automated ? { automated: { status: "pending" } } : {},
7189
+ ...check.manual ? { manual: { status: "pending" } } : {}
7190
+ }));
7191
+ return {
7192
+ ...ledger,
7193
+ remediationRound,
7194
+ status: deriveVerificationStatus(checks),
7195
+ checks
7196
+ };
7197
+ }
7198
+ function confirmManualVerification(ledger, acceptanceId, evidence) {
7199
+ if (!evidence.trim())
7200
+ throw new Error("manual confirmation evidence is required");
7201
+ const index = ledger.checks.findIndex(
7202
+ (check2) => check2.acceptanceId === acceptanceId
7203
+ );
7204
+ if (index < 0)
7205
+ throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
7206
+ const check = ledger.checks[index];
7207
+ if (!check) throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
7208
+ if (!check.manual) {
7209
+ throw new Error(`${acceptanceId} does not require manual verification`);
7210
+ }
7211
+ if (check.manual.status !== "manual_required") {
7212
+ throw new Error(`${acceptanceId} is not awaiting manual confirmation`);
7213
+ }
7214
+ const checks = [...ledger.checks];
7215
+ checks[index] = {
7216
+ ...check,
7217
+ manual: {
7218
+ status: "passed",
7219
+ evidence: evidence.trim(),
7220
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7221
+ }
7222
+ };
7223
+ return { ...ledger, status: deriveVerificationStatus(checks), checks };
7224
+ }
7225
+ function deriveVerificationStatus(checks) {
7226
+ const statuses = checks.filter((check) => check.required).flatMap((check) => [check.automated?.status, check.manual?.status]).filter((status2) => Boolean(status2));
7227
+ if (statuses.includes("blocked")) return "blocked";
7228
+ if (statuses.includes("manual_required")) return "manual_required";
7229
+ if (statuses.includes("failed")) return "failed";
7230
+ if (statuses.every((status2) => status2 === "passed")) return "passed";
7231
+ return "pending";
7232
+ }
7233
+ async function readVerificationLedger(projectRoot, taskId) {
7234
+ try {
7235
+ const raw = await readFile11(verificationPath(projectRoot, taskId), "utf-8");
7236
+ const value = JSON.parse(raw);
7237
+ return isVerificationLedger(value) ? value : null;
7238
+ } catch {
7239
+ return null;
7240
+ }
7241
+ }
7242
+ async function writeVerificationLedger(projectRoot, taskId, ledger) {
7243
+ await writeFile12(
7244
+ verificationPath(projectRoot, taskId),
7245
+ `${JSON.stringify(ledger, null, 2)}
7246
+ `,
7247
+ "utf-8"
7248
+ );
7249
+ }
7250
+ async function verificationCanAdvance(projectRoot, taskId, planVersion) {
7251
+ const [ledger, requirements, review] = await Promise.all([
7252
+ readVerificationLedger(projectRoot, taskId),
7253
+ readRequirementsLedger(projectRoot, taskId),
7254
+ readReviewLedger(projectRoot, taskId)
7255
+ ]);
6790
7256
  return Boolean(
6791
- report.trim() && !path22.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
7257
+ ledger && requirements && ledger.status === "passed" && ledger.planVersion === planVersion && ledger.remediationRound === (review?.remediationRounds ?? 0) && ledger.requirementsDigest === requirementsDigest(requirements) && ledgerMatchesRequirements(ledger, requirements)
7258
+ );
7259
+ }
7260
+ function verificationLedgerPath(projectRoot, taskId) {
7261
+ return verificationPath(projectRoot, taskId);
7262
+ }
7263
+ function verificationPath(projectRoot, taskId) {
7264
+ return path24.join(
7265
+ projectRoot,
7266
+ ".mancode",
7267
+ "workflows",
7268
+ taskId,
7269
+ VERIFICATION_FILE
6792
7270
  );
6793
7271
  }
7272
+ function isVerificationLedger(value) {
7273
+ if (typeof value !== "object" || value === null || !("version" in value) || value.version !== 1 || !("planVersion" in value) || !Number.isInteger(value.planVersion) || !("requirementsDigest" in value) || typeof value.requirementsDigest !== "string" || !("remediationRound" in value) || typeof value.remediationRound !== "number" || !Number.isInteger(value.remediationRound) || value.remediationRound < 0 || !("status" in value) || !isComponentStatus(value.status) || !("checks" in value) || !Array.isArray(value.checks)) {
7274
+ return false;
7275
+ }
7276
+ const ids = /* @__PURE__ */ new Set();
7277
+ const validChecks = value.checks.every((check) => {
7278
+ if (typeof check !== "object" || check === null || !("acceptanceId" in check) || typeof check.acceptanceId !== "string" || !("required" in check) || typeof check.required !== "boolean") {
7279
+ return false;
7280
+ }
7281
+ if (ids.has(check.acceptanceId)) return false;
7282
+ ids.add(check.acceptanceId);
7283
+ return ["automated", "manual"].every((component) => {
7284
+ const evidence = check[component];
7285
+ if (evidence === void 0) return true;
7286
+ const valid = typeof evidence === "object" && evidence !== null && "status" in evidence && isComponentStatus(evidence.status);
7287
+ if (!valid) return false;
7288
+ if (evidence.status === "pending") {
7289
+ return !("evidence" in evidence) && !("updatedAt" in evidence);
7290
+ }
7291
+ const baseEvidenceIsValid = "evidence" in evidence && typeof evidence.evidence === "string" && evidence.evidence.trim().length > 0 && "updatedAt" in evidence && typeof evidence.updatedAt === "string";
7292
+ if (!baseEvidenceIsValid) return false;
7293
+ if (component === "automated" && (evidence.status === "passed" || evidence.status === "failed")) {
7294
+ return "command" in evidence && typeof evidence.command === "string" && evidence.command.trim().length > 0 && "exitCode" in evidence && Number.isInteger(evidence.exitCode) && (evidence.status === "passed" ? evidence.exitCode === 0 : evidence.exitCode !== 0);
7295
+ }
7296
+ return true;
7297
+ });
7298
+ });
7299
+ return validChecks && value.status === deriveVerificationStatus(value.checks);
7300
+ }
7301
+ function ledgerMatchesRequirements(ledger, requirements) {
7302
+ if (ledger.checks.length !== requirements.acceptanceCriteria.length) {
7303
+ return false;
7304
+ }
7305
+ return requirements.acceptanceCriteria.every((criterion) => {
7306
+ const check = ledger.checks.find(
7307
+ (candidate) => candidate.acceptanceId === criterion.id
7308
+ );
7309
+ if (!check || check.required !== criterion.required) return false;
7310
+ const needsAutomated = criterion.method === "automated" || criterion.method === "hybrid";
7311
+ const needsManual = criterion.method === "manual" || criterion.method === "hybrid";
7312
+ return Boolean(check.automated) === needsAutomated && Boolean(check.manual) === needsManual;
7313
+ });
7314
+ }
7315
+ function isComponentStatus(value) {
7316
+ return value === "pending" || value === "passed" || value === "failed" || value === "manual_required" || value === "blocked";
7317
+ }
6794
7318
 
6795
7319
  // src/system/workflow.ts
6796
7320
  var METADATA_FILE = "metadata.json";
@@ -6812,7 +7336,7 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
6812
7336
  await validateParentTask(projectRoot, mode, options.parentTaskId, true);
6813
7337
  await mkdir9(workflowsRoot(projectRoot), { recursive: true });
6814
7338
  const taskId = await allocateTaskId(projectRoot, generateTaskId(task));
6815
- const dir = workflowDir(projectRoot, taskId);
7339
+ const dir = workflowDir2(projectRoot, taskId);
6816
7340
  const now = (/* @__PURE__ */ new Date()).toISOString();
6817
7341
  const meta = {
6818
7342
  taskId,
@@ -6824,7 +7348,12 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
6824
7348
  updatedAt: now,
6825
7349
  status: "in_progress",
6826
7350
  ...options.parentTaskId ? { parentTaskId: options.parentTaskId } : {},
6827
- ...mode === "man" || mode === "manteam" ? { planVersion: 1, reviewPolicyVersion: 1 } : {}
7351
+ ...mode === "man" || mode === "manteam" ? {
7352
+ planVersion: 1,
7353
+ reviewPolicyVersion: options.planningPolicyVersion === 2 ? 2 : 1,
7354
+ ...options.planningPolicyVersion === 2 ? { verificationPolicyVersion: 1 } : {},
7355
+ ...options.planningPolicyVersion ? { planningPolicyVersion: options.planningPolicyVersion } : {}
7356
+ } : {}
6828
7357
  };
6829
7358
  await writeMetadata(dir, meta);
6830
7359
  return meta;
@@ -6833,7 +7362,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
6833
7362
  for (let attempt = 0; attempt < 1e3; attempt++) {
6834
7363
  const taskId = attempt === 0 ? baseTaskId : `${baseTaskId}-${attempt + 1}`;
6835
7364
  try {
6836
- await mkdir9(workflowDir(projectRoot, taskId));
7365
+ await mkdir9(workflowDir2(projectRoot, taskId));
6837
7366
  return taskId;
6838
7367
  } catch (err) {
6839
7368
  if (isNodeError9(err) && err.code === "EEXIST") {
@@ -6848,13 +7377,13 @@ async function readWorkflow(projectRoot, taskId) {
6848
7377
  if (!isValidWorkflowTaskId(taskId)) return null;
6849
7378
  const file = metadataPath(projectRoot, taskId);
6850
7379
  try {
6851
- const raw = await readFile10(file, "utf-8");
7380
+ const raw = await readFile12(file, "utf-8");
6852
7381
  return parseWorkflowMeta(raw, taskId);
6853
7382
  } catch {
6854
7383
  return null;
6855
7384
  }
6856
7385
  }
6857
- async function updateWorkflow(projectRoot, taskId, patch) {
7386
+ async function updateWorkflow(projectRoot, taskId, patch, options = {}) {
6858
7387
  assertValidTaskId2(taskId);
6859
7388
  const existing = await readWorkflow(projectRoot, taskId);
6860
7389
  if (!existing) {
@@ -6874,8 +7403,8 @@ async function updateWorkflow(projectRoot, taskId, patch) {
6874
7403
  if (updated.status !== "blocked" && !explicitlyPatchedBlockingReason) {
6875
7404
  updated.blockingReason = void 0;
6876
7405
  }
6877
- await validateWorkflowMeta(projectRoot, updated, existing);
6878
- await writeMetadata(workflowDir(projectRoot, taskId), updated);
7406
+ await validateWorkflowMeta(projectRoot, updated, existing, options);
7407
+ await writeMetadata(workflowDir2(projectRoot, taskId), updated);
6879
7408
  if (updated.parentTaskId) {
6880
7409
  await propagateChildStatus(projectRoot, updated);
6881
7410
  }
@@ -6898,7 +7427,7 @@ async function listWorkflows(projectRoot) {
6898
7427
  }
6899
7428
  async function deleteWorkflow(projectRoot, taskId) {
6900
7429
  if (!isValidWorkflowTaskId(taskId)) return false;
6901
- const dir = workflowDir(projectRoot, taskId);
7430
+ const dir = workflowDir2(projectRoot, taskId);
6902
7431
  try {
6903
7432
  await stat4(dir);
6904
7433
  } catch {
@@ -6912,19 +7441,19 @@ async function deleteWorkflow(projectRoot, taskId) {
6912
7441
  return true;
6913
7442
  }
6914
7443
  function workflowsRoot(projectRoot) {
6915
- return path23.join(projectRoot, ".mancode", "workflows");
7444
+ return path25.join(projectRoot, ".mancode", "workflows");
6916
7445
  }
6917
- function workflowDir(projectRoot, taskId) {
7446
+ function workflowDir2(projectRoot, taskId) {
6918
7447
  assertValidTaskId2(taskId);
6919
- return path23.join(workflowsRoot(projectRoot), taskId);
7448
+ return path25.join(workflowsRoot(projectRoot), taskId);
6920
7449
  }
6921
7450
  function metadataPath(projectRoot, taskId) {
6922
- return path23.join(workflowDir(projectRoot, taskId), METADATA_FILE);
7451
+ return path25.join(workflowDir2(projectRoot, taskId), METADATA_FILE);
6923
7452
  }
6924
7453
  async function writeMetadata(dir, meta) {
6925
7454
  const content = `${JSON.stringify(meta, null, 2)}
6926
7455
  `;
6927
- await writeFile11(path23.join(dir, METADATA_FILE), content, "utf-8");
7456
+ await writeFile13(path25.join(dir, METADATA_FILE), content, "utf-8");
6928
7457
  }
6929
7458
  function parseWorkflowMeta(raw, taskId) {
6930
7459
  try {
@@ -6949,6 +7478,12 @@ function isWorkflowStatus(value) {
6949
7478
  function isWorkflowOutcome(value) {
6950
7479
  return value === "fixed" || value === "verified" || value === "no_repro" || value === "manual_test_required";
6951
7480
  }
7481
+ function isRequirementsStatus(value) {
7482
+ return value === "ready" || value === "needs_clarification";
7483
+ }
7484
+ function isPlanDecision(value) {
7485
+ return value === "plan_only" || value === "solo_handoff" || value === "governed_execution";
7486
+ }
6952
7487
  async function validateParentTask(projectRoot, mode, parentTaskId, requireStepSix = false) {
6953
7488
  if (!parentTaskId) return;
6954
7489
  if (mode !== "mamba") {
@@ -6964,7 +7499,7 @@ async function validateParentTask(projectRoot, mode, parentTaskId, requireStepSi
6964
7499
  );
6965
7500
  }
6966
7501
  }
6967
- async function validateWorkflowMeta(projectRoot, updated, existing) {
7502
+ async function validateWorkflowMeta(projectRoot, updated, existing, options) {
6968
7503
  if (updated.mode !== existing.mode) {
6969
7504
  throw new Error("workflow mode cannot be changed");
6970
7505
  }
@@ -6977,6 +7512,17 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
6977
7512
  if (updated.reviewPolicyVersion !== existing.reviewPolicyVersion) {
6978
7513
  throw new Error("workflow review policy version cannot be changed");
6979
7514
  }
7515
+ if (updated.planningPolicyVersion !== existing.planningPolicyVersion) {
7516
+ throw new Error("workflow planning policy version cannot be changed");
7517
+ }
7518
+ if (updated.requirementsDigest !== existing.requirementsDigest && existing.currentStep > 2) {
7519
+ throw new Error(
7520
+ "workflow requirements cannot change after planning starts"
7521
+ );
7522
+ }
7523
+ if (updated.verificationPolicyVersion !== existing.verificationPolicyVersion) {
7524
+ throw new Error("workflow verification policy version cannot be changed");
7525
+ }
6980
7526
  if (!Number.isInteger(updated.currentStep) || updated.currentStep < 1 || updated.currentStep > maxWorkflowStep(updated.mode)) {
6981
7527
  throw new Error(`invalid workflow step: ${updated.currentStep}`);
6982
7528
  }
@@ -6986,6 +7532,13 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
6986
7532
  if (!Array.isArray(updated.skippedSteps) || updated.skippedSteps.some((step) => typeof step !== "string")) {
6987
7533
  throw new Error("workflow skipped steps must be strings");
6988
7534
  }
7535
+ if (updated.reviewPolicyVersion === 2 && updated.skippedSteps.some(
7536
+ (step) => step !== "clarification" && step !== "review"
7537
+ )) {
7538
+ throw new Error(
7539
+ "workflow policy v2 only allows skipping clarification or review"
7540
+ );
7541
+ }
6989
7542
  if (!canTransition(existing.status, updated.status)) {
6990
7543
  throw new Error(
6991
7544
  `invalid workflow status transition: ${existing.status} -> ${updated.status}`
@@ -6997,7 +7550,7 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
6997
7550
  if (updated.status === "planned" && updated.currentStep !== 4) {
6998
7551
  throw new Error("planned workflows must be at step 4");
6999
7552
  }
7000
- if (updated.status === "completed" && updated.currentStep !== maxWorkflowStep(updated.mode)) {
7553
+ if (updated.status === "completed" && updated.currentStep !== maxWorkflowStep(updated.mode) && !(updated.currentStep === 4 && updated.planDecision === "solo_handoff")) {
7001
7554
  throw new Error(
7002
7555
  `completed ${updated.mode === "mamba" ? "manba" : updated.mode} workflows must be at step ${maxWorkflowStep(updated.mode)}`
7003
7556
  );
@@ -7027,12 +7580,106 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
7027
7580
  if (updated.mode === "mamba" && updated.planVersion !== void 0) {
7028
7581
  throw new Error("manba workflows cannot have a plan version");
7029
7582
  }
7030
- if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1) {
7583
+ if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1 && updated.reviewPolicyVersion !== 2) {
7031
7584
  throw new Error("invalid workflow review policy version");
7032
7585
  }
7033
7586
  if (updated.mode === "mamba" && updated.reviewPolicyVersion !== void 0) {
7034
7587
  throw new Error("manba workflows cannot have a review policy version");
7035
7588
  }
7589
+ if (updated.planningPolicyVersion !== void 0 && updated.planningPolicyVersion !== 1 && updated.planningPolicyVersion !== 2) {
7590
+ throw new Error("invalid workflow planning policy version");
7591
+ }
7592
+ if (updated.mode === "mamba" && updated.planningPolicyVersion !== void 0) {
7593
+ throw new Error("manba workflows cannot have a planning policy version");
7594
+ }
7595
+ if (updated.verificationPolicyVersion !== void 0 && updated.verificationPolicyVersion !== 1) {
7596
+ throw new Error("invalid workflow verification policy version");
7597
+ }
7598
+ if (updated.mode === "mamba" && updated.verificationPolicyVersion !== void 0) {
7599
+ throw new Error(
7600
+ "manba workflows cannot have a verification policy version"
7601
+ );
7602
+ }
7603
+ if (updated.verificationStatus !== void 0 && !isVerificationStatus(updated.verificationStatus)) {
7604
+ throw new Error("invalid workflow verification status");
7605
+ }
7606
+ if (updated.mode === "mamba" && updated.verificationStatus !== void 0) {
7607
+ throw new Error("manba workflows cannot have a verification status");
7608
+ }
7609
+ if (updated.requirementsStatus !== void 0 && !isRequirementsStatus(updated.requirementsStatus)) {
7610
+ throw new Error("invalid workflow requirements status");
7611
+ }
7612
+ if (updated.mode === "mamba" && updated.requirementsStatus !== void 0) {
7613
+ throw new Error("manba workflows cannot have a requirements status");
7614
+ }
7615
+ if (updated.requirementsDigest !== void 0 && typeof updated.requirementsDigest !== "string") {
7616
+ throw new Error("workflow requirements digest must be a string");
7617
+ }
7618
+ if (updated.mode === "mamba" && updated.requirementsDigest !== void 0) {
7619
+ throw new Error("manba workflows cannot have a requirements digest");
7620
+ }
7621
+ if (updated.planDecision !== void 0 && !isPlanDecision(updated.planDecision)) {
7622
+ throw new Error("invalid workflow plan decision");
7623
+ }
7624
+ if (updated.mode === "mamba" && updated.planDecision !== void 0) {
7625
+ throw new Error("manba workflows cannot have a plan decision");
7626
+ }
7627
+ if (updated.planDecision !== existing.planDecision && existing.planDecision !== void 0) {
7628
+ throw new Error("workflow plan decision cannot be changed");
7629
+ }
7630
+ if (updated.planDecision !== existing.planDecision && existing.currentStep !== 4) {
7631
+ throw new Error("workflow plan decision can only be set at step 4");
7632
+ }
7633
+ if (updated.planningPolicyVersion === 1 || updated.planningPolicyVersion === 2) {
7634
+ if (updated.currentStep >= 3) {
7635
+ if (updated.requirementsStatus !== "ready") {
7636
+ throw new Error("requirements must be ready before planning");
7637
+ }
7638
+ if (!await workflowArtifactExists(
7639
+ projectRoot,
7640
+ updated.taskId,
7641
+ "requirements.md"
7642
+ )) {
7643
+ throw new Error("requirements.md is required before planning");
7644
+ }
7645
+ if (updated.planningPolicyVersion === 2) {
7646
+ const requirements = await readRequirementsLedger(
7647
+ projectRoot,
7648
+ updated.taskId
7649
+ );
7650
+ if (!requirements || !requirementsAreReady(requirements) || updated.requirementsDigest !== requirementsDigest(requirements)) {
7651
+ throw new Error(
7652
+ "finalized requirements.json with no blocking unknowns is required before planning"
7653
+ );
7654
+ }
7655
+ }
7656
+ }
7657
+ if (updated.currentStep >= 4 && !await workflowArtifactExists(projectRoot, updated.taskId, "plan.md")) {
7658
+ throw new Error("plan.md is required before the plan gate");
7659
+ }
7660
+ if (updated.status === "planned" && updated.planDecision !== "plan_only" && updated.planDecision !== "solo_handoff") {
7661
+ throw new Error(
7662
+ "planned workflows require a plan-only or solo-handoff decision"
7663
+ );
7664
+ }
7665
+ if (updated.currentStep >= 5 && updated.status === "in_progress" && updated.planDecision !== "governed_execution") {
7666
+ throw new Error("governed execution must be confirmed before step 5");
7667
+ }
7668
+ }
7669
+ if (updated.verificationPolicyVersion === 1 && updated.currentStep >= 7 && updated.planDecision !== "solo_handoff" && !options.allowIncompleteVerification && !await verificationCanAdvance(
7670
+ projectRoot,
7671
+ updated.taskId,
7672
+ updated.planVersion ?? 1
7673
+ )) {
7674
+ throw new Error(
7675
+ "workflow verification is incomplete, stale, or requires manual confirmation"
7676
+ );
7677
+ }
7678
+ if (existing.status === "blocked" && existing.blockingReason?.startsWith("[verification]") && updated.status === "in_progress" && (updated.verificationStatus === "manual_required" || updated.verificationStatus === "blocked")) {
7679
+ throw new Error(
7680
+ "verification-blocked workflows must resume through verify"
7681
+ );
7682
+ }
7036
7683
  if (updated.planVersion !== void 0 && (!Number.isInteger(updated.planVersion) || updated.planVersion < 1)) {
7037
7684
  throw new Error("workflow plan version must be a positive integer");
7038
7685
  }
@@ -7046,16 +7693,22 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
7046
7693
  if ((isTerminalWorkflowStatus(updated.status) || updated.status === "planned") && (await listActiveMambaChildren(projectRoot, updated.taskId)).length > 0) {
7047
7694
  throw new Error("cannot finish workflow with an active manba child");
7048
7695
  }
7049
- if (updated.status === "completed" && updated.reviewPolicyVersion === 1 && !reviewWasExplicitlySkipped(updated) && !await reviewCanComplete(projectRoot, updated.taskId)) {
7696
+ if (updated.status === "completed" && updated.reviewPolicyVersion === 1 && updated.planDecision !== "solo_handoff" && !reviewWasExplicitlySkipped(updated) && !await reviewCanComplete(projectRoot, updated.taskId)) {
7697
+ throw new Error("workflow review is incomplete or still has open blockers");
7698
+ }
7699
+ if (updated.status === "completed" && updated.reviewPolicyVersion === 2 && updated.planDecision !== "solo_handoff" && !await reviewCanComplete(projectRoot, updated.taskId)) {
7050
7700
  throw new Error("workflow review is incomplete or still has open blockers");
7051
7701
  }
7702
+ if (updated.status === "completed" && updated.verificationPolicyVersion === 1 && updated.planDecision !== "solo_handoff" && !await workflowArtifactExists(projectRoot, updated.taskId, "summary.md")) {
7703
+ throw new Error("summary.md is required before workflow completion");
7704
+ }
7052
7705
  }
7053
7706
  function canTransition(from, to) {
7054
7707
  if (from === to) return true;
7055
7708
  if (from === "in_progress") return true;
7056
- if (from === "planned" || from === "blocked") {
7057
- return to === "in_progress" || to === "abandoned";
7058
- }
7709
+ if (from === "planned")
7710
+ return to === "in_progress" || to === "completed" || to === "abandoned";
7711
+ if (from === "blocked") return to === "in_progress" || to === "abandoned";
7059
7712
  return false;
7060
7713
  }
7061
7714
  function isTerminalWorkflowStatus(status2) {
@@ -7076,15 +7729,27 @@ function isWorkflowShape(obj) {
7076
7729
  return false;
7077
7730
  if (obj.planVersion !== void 0 && (!Number.isInteger(obj.planVersion) || obj.planVersion < 1))
7078
7731
  return false;
7079
- if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1)
7732
+ if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1 && obj.reviewPolicyVersion !== 2)
7080
7733
  return false;
7081
- if (status2 === "blocked") {
7082
- if (!obj.blockingReason?.trim()) return false;
7734
+ if (obj.planningPolicyVersion !== void 0 && obj.planningPolicyVersion !== 1 && obj.planningPolicyVersion !== 2)
7735
+ return false;
7736
+ if (obj.verificationPolicyVersion !== void 0 && obj.verificationPolicyVersion !== 1)
7737
+ return false;
7738
+ if (obj.requirementsStatus !== void 0 && !isRequirementsStatus(obj.requirementsStatus))
7739
+ return false;
7740
+ if (obj.requirementsDigest !== void 0 && typeof obj.requirementsDigest !== "string")
7741
+ return false;
7742
+ if (obj.planDecision !== void 0 && !isPlanDecision(obj.planDecision))
7743
+ return false;
7744
+ if (obj.verificationStatus !== void 0 && !isVerificationStatus(obj.verificationStatus))
7745
+ return false;
7746
+ if (status2 === "blocked") {
7747
+ if (!obj.blockingReason?.trim()) return false;
7083
7748
  } else if (obj.blockingReason !== void 0) {
7084
7749
  return false;
7085
7750
  }
7086
7751
  if (mode === "mamba") {
7087
- if (status2 === "planned" || obj.planVersion !== void 0 || obj.reviewPolicyVersion !== void 0)
7752
+ if (status2 === "planned" || obj.planVersion !== void 0 || obj.reviewPolicyVersion !== void 0 || obj.planningPolicyVersion !== void 0 || obj.verificationPolicyVersion !== void 0 || obj.requirementsStatus !== void 0 || obj.requirementsDigest !== void 0 || obj.planDecision !== void 0 || obj.verificationStatus !== void 0)
7088
7753
  return false;
7089
7754
  if (obj.parentTaskId !== void 0 && !isValidWorkflowTaskId(obj.parentTaskId))
7090
7755
  return false;
@@ -7095,9 +7760,20 @@ function isWorkflowShape(obj) {
7095
7760
  }
7096
7761
  return true;
7097
7762
  }
7763
+ async function workflowArtifactExists(projectRoot, taskId, filename) {
7764
+ try {
7765
+ await stat4(path25.join(workflowDir2(projectRoot, taskId), filename));
7766
+ return true;
7767
+ } catch {
7768
+ return false;
7769
+ }
7770
+ }
7098
7771
  function reviewWasExplicitlySkipped(meta) {
7099
7772
  return meta.skippedSteps.includes("review") || meta.skippedSteps.includes("film-1") && meta.skippedSteps.includes("film-2");
7100
7773
  }
7774
+ function isVerificationStatus(value) {
7775
+ return value === "pending" || value === "passed" || value === "failed" || value === "manual_required" || value === "blocked";
7776
+ }
7101
7777
  async function listActiveMambaChildren(projectRoot, parentTaskId) {
7102
7778
  return (await listMambaChildren(projectRoot, parentTaskId)).filter(
7103
7779
  (workflow2) => workflow2.status === "in_progress" || workflow2.status === "blocked"
@@ -7116,7 +7792,7 @@ async function propagateChildStatus(projectRoot, child) {
7116
7792
  const parent = await readWorkflow(projectRoot, parentTaskId);
7117
7793
  if (!parent || parent.status !== "in_progress") return;
7118
7794
  const reason = child.status === "blocked" ? `manba child ${child.taskId} is blocked: ${child.blockingReason ?? "unknown reason"}` : `manba child ${child.taskId} requires manual testing`;
7119
- await writeMetadata(workflowDir(projectRoot, parent.taskId), {
7795
+ await writeMetadata(workflowDir2(projectRoot, parent.taskId), {
7120
7796
  ...parent,
7121
7797
  status: "blocked",
7122
7798
  blockingReason: reason,
@@ -7145,7 +7821,7 @@ var EXIT_OK7 = 0;
7145
7821
  var EXIT_NOT_INITIALIZED5 = 1;
7146
7822
  var EXIT_CORRUPT_STATE2 = 2;
7147
7823
  async function status(rootDir = process10.cwd(), options = {}) {
7148
- const stateFile = path24.join(rootDir, ".mancode", "state.json");
7824
+ const stateFile = path26.join(rootDir, ".mancode", "state.json");
7149
7825
  if (!await pathExists10(stateFile)) {
7150
7826
  console.error("\u2717 mancode not initialized.");
7151
7827
  console.error(" Run `mancode init` to get started.");
@@ -7196,6 +7872,7 @@ async function status(rootDir = process10.cwd(), options = {}) {
7196
7872
  hookInjection,
7197
7873
  team: effectiveTeam,
7198
7874
  currentWorkflow,
7875
+ activeSoloPlan: state.activeSoloPlan ?? null,
7199
7876
  platformStatus: {},
7200
7877
  projectRefreshRecommended
7201
7878
  };
@@ -7209,10 +7886,10 @@ async function status(rootDir = process10.cwd(), options = {}) {
7209
7886
  }
7210
7887
  async function shouldRefreshProject(rootDir, state) {
7211
7888
  if (state.projectMode !== "generic") return false;
7212
- const hasGit = await pathExists10(path24.join(rootDir, ".git"));
7889
+ const hasGit = await pathExists10(path26.join(rootDir, ".git"));
7213
7890
  if (hasGit) return true;
7214
7891
  for (const manifest of PROJECT_MANIFESTS) {
7215
- if (await pathExists10(path24.join(rootDir, manifest))) return true;
7892
+ if (await pathExists10(path26.join(rootDir, manifest))) return true;
7216
7893
  }
7217
7894
  return false;
7218
7895
  }
@@ -7256,19 +7933,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
7256
7933
  }
7257
7934
  async function getProjectName(rootDir) {
7258
7935
  try {
7259
- const raw = await fs9.readFile(path24.join(rootDir, "package.json"), "utf-8");
7936
+ const raw = await fs9.readFile(path26.join(rootDir, "package.json"), "utf-8");
7260
7937
  const pkg = JSON.parse(raw);
7261
7938
  if (pkg.name && typeof pkg.name === "string") {
7262
7939
  return pkg.name;
7263
7940
  }
7264
7941
  } catch {
7265
7942
  }
7266
- return path24.basename(rootDir);
7943
+ return path26.basename(rootDir);
7267
7944
  }
7268
7945
  async function readConfig2(rootDir) {
7269
7946
  try {
7270
7947
  const raw = await fs9.readFile(
7271
- path24.join(rootDir, ".mancode", "config.json"),
7948
+ path26.join(rootDir, ".mancode", "config.json"),
7272
7949
  "utf-8"
7273
7950
  );
7274
7951
  return JSON.parse(raw);
@@ -7296,9 +7973,9 @@ function getEffectiveTeamStatus(state, config, detected) {
7296
7973
  }
7297
7974
  async function checkHooks(rootDir) {
7298
7975
  const [sessionStart, userPromptSubmit, registered] = await Promise.all([
7299
- pathExists10(path24.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
7976
+ pathExists10(path26.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
7300
7977
  pathExists10(
7301
- path24.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
7978
+ path26.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
7302
7979
  ),
7303
7980
  isRegistered(rootDir)
7304
7981
  ]);
@@ -7307,7 +7984,7 @@ async function checkHooks(rootDir) {
7307
7984
  async function isRegistered(rootDir) {
7308
7985
  try {
7309
7986
  const raw = await fs9.readFile(
7310
- path24.join(rootDir, ".claude", "settings.json"),
7987
+ path26.join(rootDir, ".claude", "settings.json"),
7311
7988
  "utf-8"
7312
7989
  );
7313
7990
  const settings = JSON.parse(raw);
@@ -7317,9 +7994,9 @@ async function isRegistered(rootDir) {
7317
7994
  }
7318
7995
  }
7319
7996
  function hooksRegistered2(settings) {
7320
- if (!isRecord9(settings)) return false;
7997
+ if (!isRecord10(settings)) return false;
7321
7998
  const hooks = settings.hooks;
7322
- if (!isRecord9(hooks)) return false;
7999
+ if (!isRecord10(hooks)) return false;
7323
8000
  return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
7324
8001
  hooks.UserPromptSubmit,
7325
8002
  ".mancode/hooks/user-prompt-submit.mjs"
@@ -7328,15 +8005,15 @@ function hooksRegistered2(settings) {
7328
8005
  function hasHookCommand2(value, needle) {
7329
8006
  if (!Array.isArray(value)) return false;
7330
8007
  return value.some((group) => {
7331
- if (!isRecord9(group) || !Array.isArray(group.hooks)) return false;
8008
+ if (!isRecord10(group) || !Array.isArray(group.hooks)) return false;
7332
8009
  return group.hooks.some((hook) => {
7333
- if (!isRecord9(hook) || typeof hook.command !== "string") return false;
8010
+ if (!isRecord10(hook) || typeof hook.command !== "string") return false;
7334
8011
  return hook.command.includes(needle);
7335
8012
  });
7336
8013
  });
7337
8014
  }
7338
8015
  async function estimateHookInjection(rootDir) {
7339
- const hookPath = path24.join(
8016
+ const hookPath = path26.join(
7340
8017
  rootDir,
7341
8018
  ".mancode",
7342
8019
  "hooks",
@@ -7428,6 +8105,11 @@ function printText(r) {
7428
8105
  );
7429
8106
  }
7430
8107
  }
8108
+ if (r.activeSoloPlan) {
8109
+ console.log(
8110
+ `Solo plan: ${r.activeSoloPlan.taskId} (plan v${r.activeSoloPlan.planVersion})`
8111
+ );
8112
+ }
7431
8113
  console.log("");
7432
8114
  console.log("Installed platforms:");
7433
8115
  for (const p of r.platforms) {
@@ -7468,7 +8150,7 @@ function formatTeamStatus(team) {
7468
8150
  function workflowStepMax(mode) {
7469
8151
  return maxWorkflowStep(mode);
7470
8152
  }
7471
- function isRecord9(value) {
8153
+ function isRecord10(value) {
7472
8154
  return typeof value === "object" && value !== null;
7473
8155
  }
7474
8156
  async function pathExists10(p) {
@@ -7481,14 +8163,14 @@ async function pathExists10(p) {
7481
8163
  }
7482
8164
 
7483
8165
  // src/commands/uninstall.ts
7484
- import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
7485
- import path25 from "path";
8166
+ import { access as access5, readFile as readFile13, rm as rm5, writeFile as writeFile14 } from "fs/promises";
8167
+ import path27 from "path";
7486
8168
  import process11 from "process";
7487
8169
  var EXIT_OK8 = 0;
7488
8170
  var EXIT_NOT_INITIALIZED6 = 1;
7489
8171
  var EXIT_UNSUPPORTED_PLATFORM2 = 2;
7490
8172
  async function uninstall(rootDir = process11.cwd(), platform, options = {}) {
7491
- const stateFile = path25.join(rootDir, ".mancode", "state.json");
8173
+ const stateFile = path27.join(rootDir, ".mancode", "state.json");
7492
8174
  if (!await pathExists11(stateFile)) {
7493
8175
  console.error("\u2717 mancode not initialized.");
7494
8176
  console.error(" Run `mancode init` first.");
@@ -7540,7 +8222,7 @@ async function uninstallAll(rootDir) {
7540
8222
  await uninstallPlatform(rootDir, p);
7541
8223
  }
7542
8224
  console.log("\u2713 Removing .mancode/ directory...");
7543
- await rm5(path25.join(rootDir, ".mancode"), {
8225
+ await rm5(path27.join(rootDir, ".mancode"), {
7544
8226
  recursive: true,
7545
8227
  force: true
7546
8228
  });
@@ -7550,10 +8232,10 @@ async function uninstallClaudeCode(rootDir) {
7550
8232
  await cleanClaudeSettings(rootDir);
7551
8233
  }
7552
8234
  async function cleanClaudeSettings(rootDir) {
7553
- const settingsPath = path25.join(rootDir, ".claude", "settings.json");
8235
+ const settingsPath = path27.join(rootDir, ".claude", "settings.json");
7554
8236
  let content;
7555
8237
  try {
7556
- content = await readFile11(settingsPath, "utf-8");
8238
+ content = await readFile13(settingsPath, "utf-8");
7557
8239
  } catch {
7558
8240
  return;
7559
8241
  }
@@ -7564,14 +8246,14 @@ async function cleanClaudeSettings(rootDir) {
7564
8246
  return;
7565
8247
  }
7566
8248
  const cleanedHooks = {};
7567
- if (isRecord10(settings.hooks)) {
8249
+ if (isRecord11(settings.hooks)) {
7568
8250
  for (const [event, value] of Object.entries(settings.hooks)) {
7569
8251
  const cleaned = cleanClaudeHookValue(value);
7570
8252
  if (cleaned !== void 0) cleanedHooks[event] = cleaned;
7571
8253
  }
7572
8254
  settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
7573
8255
  }
7574
- if (isRecord10(settings.skills)) {
8256
+ if (isRecord11(settings.skills)) {
7575
8257
  const retainedSkills = Object.fromEntries(
7576
8258
  Object.entries(settings.skills).filter(
7577
8259
  ([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
@@ -7579,7 +8261,7 @@ async function cleanClaudeSettings(rootDir) {
7579
8261
  );
7580
8262
  settings.skills = Object.keys(retainedSkills).length > 0 ? retainedSkills : void 0;
7581
8263
  }
7582
- await writeFile12(
8264
+ await writeFile14(
7583
8265
  settingsPath,
7584
8266
  `${JSON.stringify(settings, null, 2)}
7585
8267
  `,
@@ -7591,7 +8273,7 @@ function cleanClaudeHookValue(value) {
7591
8273
  const entries2 = value.flatMap(cleanClaudeHookEntry);
7592
8274
  return entries2.length > 0 ? entries2 : void 0;
7593
8275
  }
7594
- if (!isRecord10(value)) return value;
8276
+ if (!isRecord11(value)) return value;
7595
8277
  if (Array.isArray(value.hooks) || typeof value.command === "string") {
7596
8278
  const entries2 = cleanClaudeHookEntry(value);
7597
8279
  return entries2.length > 0 ? entries2 : void 0;
@@ -7605,7 +8287,7 @@ function cleanClaudeHookValue(value) {
7605
8287
  }
7606
8288
  function cleanClaudeHookEntry(entry) {
7607
8289
  if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
7608
- if (!isRecord10(entry)) return [entry];
8290
+ if (!isRecord11(entry)) return [entry];
7609
8291
  if (Array.isArray(entry.hooks)) {
7610
8292
  const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
7611
8293
  return hooks.length > 0 ? [{ ...entry, hooks }] : [];
@@ -7613,11 +8295,11 @@ function cleanClaudeHookEntry(entry) {
7613
8295
  return isMancodeHookEntry(entry) ? [] : [entry];
7614
8296
  }
7615
8297
  function isMancodeHookEntry(value) {
7616
- return isRecord10(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
8298
+ return isRecord11(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
7617
8299
  }
7618
8300
  function containsLegacyMancodeHookPath2(value) {
7619
8301
  if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
7620
- if (!isRecord10(value)) return false;
8302
+ if (!isRecord11(value)) return false;
7621
8303
  if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
7622
8304
  return true;
7623
8305
  }
@@ -7628,12 +8310,12 @@ async function uninstallCursor(rootDir) {
7628
8310
  await removeCursorCommands(rootDir);
7629
8311
  }
7630
8312
  async function uninstallCodex(rootDir) {
7631
- const agentsPath = path25.join(rootDir, "AGENTS.md");
8313
+ const agentsPath = path27.join(rootDir, "AGENTS.md");
7632
8314
  try {
7633
- const content = await readFile11(agentsPath, "utf-8");
8315
+ const content = await readFile13(agentsPath, "utf-8");
7634
8316
  const cleaned = removeManagedBlock(content);
7635
8317
  if (cleaned.trim()) {
7636
- await writeFile12(agentsPath, `${cleaned}
8318
+ await writeFile14(agentsPath, `${cleaned}
7637
8319
  `, "utf-8");
7638
8320
  } else {
7639
8321
  await rm5(agentsPath, { force: true });
@@ -7643,16 +8325,16 @@ async function uninstallCodex(rootDir) {
7643
8325
  await removeCodexSkills(rootDir);
7644
8326
  }
7645
8327
  async function uninstallCopilot(rootDir) {
7646
- const instructionsPath = path25.join(
8328
+ const instructionsPath = path27.join(
7647
8329
  rootDir,
7648
8330
  ".github",
7649
8331
  "copilot-instructions.md"
7650
8332
  );
7651
8333
  try {
7652
- const content = await readFile11(instructionsPath, "utf-8");
8334
+ const content = await readFile13(instructionsPath, "utf-8");
7653
8335
  const cleaned = removeManagedBlock(content);
7654
8336
  if (cleaned.trim()) {
7655
- await writeFile12(instructionsPath, `${cleaned}
8337
+ await writeFile14(instructionsPath, `${cleaned}
7656
8338
  `, "utf-8");
7657
8339
  } else {
7658
8340
  await rm5(instructionsPath, { force: true });
@@ -7662,16 +8344,16 @@ async function uninstallCopilot(rootDir) {
7662
8344
  await removeCopilotPrompts(rootDir);
7663
8345
  }
7664
8346
  async function uninstallZcode(rootDir) {
7665
- const agentsPath = path25.join(rootDir, "AGENTS.md");
8347
+ const agentsPath = path27.join(rootDir, "AGENTS.md");
7666
8348
  try {
7667
- const content = await readFile11(agentsPath, "utf-8");
8349
+ const content = await readFile13(agentsPath, "utf-8");
7668
8350
  const cleaned = removeManagedBlock(
7669
8351
  content,
7670
8352
  ZCODE_MANCODE_START_MARKER,
7671
8353
  ZCODE_MANCODE_END_MARKER
7672
8354
  );
7673
8355
  if (cleaned.trim()) {
7674
- await writeFile12(agentsPath, `${cleaned}
8356
+ await writeFile14(agentsPath, `${cleaned}
7675
8357
  `, "utf-8");
7676
8358
  } else {
7677
8359
  await rm5(agentsPath, { force: true });
@@ -7681,13 +8363,13 @@ async function uninstallZcode(rootDir) {
7681
8363
  await removeZcodeSkills(rootDir);
7682
8364
  }
7683
8365
  async function removeFromConfig(rootDir, platform) {
7684
- const configPath = path25.join(rootDir, ".mancode", "config.json");
8366
+ const configPath = path27.join(rootDir, ".mancode", "config.json");
7685
8367
  try {
7686
- const raw = await readFile11(configPath, "utf-8");
8368
+ const raw = await readFile13(configPath, "utf-8");
7687
8369
  const config = JSON.parse(raw);
7688
8370
  if (Array.isArray(config.platforms)) {
7689
8371
  config.platforms = config.platforms.filter((p) => p !== platform);
7690
- if (isRecord10(config.platformOptions)) {
8372
+ if (isRecord11(config.platformOptions)) {
7691
8373
  const platformOptions = Object.fromEntries(
7692
8374
  Object.entries(config.platformOptions).filter(
7693
8375
  ([name]) => name !== platform
@@ -7695,7 +8377,7 @@ async function removeFromConfig(rootDir, platform) {
7695
8377
  );
7696
8378
  config.platformOptions = Object.keys(platformOptions).length > 0 ? platformOptions : void 0;
7697
8379
  }
7698
- await writeFile12(
8380
+ await writeFile14(
7699
8381
  configPath,
7700
8382
  `${JSON.stringify(config, null, 2)}
7701
8383
  `,
@@ -7705,7 +8387,7 @@ async function removeFromConfig(rootDir, platform) {
7705
8387
  } catch {
7706
8388
  }
7707
8389
  }
7708
- function isRecord10(value) {
8390
+ function isRecord11(value) {
7709
8391
  return typeof value === "object" && value !== null && !Array.isArray(value);
7710
8392
  }
7711
8393
  async function pathExists11(p) {
@@ -7728,13 +8410,13 @@ function version() {
7728
8410
  }
7729
8411
 
7730
8412
  // src/commands/workflow.ts
7731
- import { access as access6 } from "fs/promises";
7732
- import path26 from "path";
8413
+ import { access as access6, readFile as readFile14, rm as rm6, writeFile as writeFile15 } from "fs/promises";
8414
+ import path28 from "path";
7733
8415
  var EXIT_OK9 = 0;
7734
8416
  var EXIT_NOT_INITIALIZED7 = 1;
7735
8417
  var EXIT_INVALID_ARG2 = 2;
7736
8418
  async function workflow(rootDir, subcommand, args = [], options = {}) {
7737
- if (!await pathExists12(path26.join(rootDir, ".mancode", "state.json"))) {
8419
+ if (!await pathExists12(path28.join(rootDir, ".mancode", "state.json"))) {
7738
8420
  if (options.json) {
7739
8421
  console.log(JSON.stringify({ error: "not initialized" }, null, 2));
7740
8422
  } else {
@@ -7752,8 +8434,16 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
7752
8434
  return workflowList(rootDir, options);
7753
8435
  case "show":
7754
8436
  return workflowShow(rootDir, args[0], options);
8437
+ case "handoff":
8438
+ return workflowHandoff(rootDir, args[0], options);
8439
+ case "decide":
8440
+ return workflowDecide(rootDir, args[0], options);
7755
8441
  case "review":
7756
8442
  return workflowReview(rootDir, args, options);
8443
+ case "requirements":
8444
+ return workflowRequirements(rootDir, args, options);
8445
+ case "verify":
8446
+ return workflowVerify(rootDir, args, options);
7757
8447
  case "clean":
7758
8448
  return workflowClean(rootDir, options);
7759
8449
  default:
@@ -7768,12 +8458,278 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
7768
8458
  } else {
7769
8459
  console.error(`\u2717 Invalid workflow subcommand: ${subcommand}`);
7770
8460
  console.error(
7771
- " Use: create <man|manba|manteam> <task> | update <taskId> | review <taskId> <init|complete|remediate|show> | list | show <taskId> | clean"
8461
+ " Use: create <man|manba|manteam> <task> | requirements <taskId> finalize --file <path> | verify <taskId> <init|record|require-manual|confirm-manual|show> | decide <taskId> --plan-decision <plan_only|governed_execution> | handoff <taskId> --to solo | review <taskId> <init|complete|remediate|skip|show> | list | show <taskId> | clean"
7772
8462
  );
7773
8463
  }
7774
8464
  return EXIT_INVALID_ARG2;
7775
8465
  }
7776
8466
  }
8467
+ async function workflowRequirements(rootDir, args, options) {
8468
+ const taskId = args[0];
8469
+ const action = args[1];
8470
+ if (!taskId || !isValidWorkflowTaskId(taskId)) {
8471
+ return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
8472
+ }
8473
+ if (action !== "finalize" || !options.file) {
8474
+ return invalidArg(options, "requirements requires finalize --file <path>");
8475
+ }
8476
+ const meta = await readWorkflow(rootDir, taskId);
8477
+ if (!meta || meta.mode !== "man" && meta.mode !== "manteam" || meta.status !== "in_progress" || meta.currentStep > 2) {
8478
+ return invalidArg(
8479
+ options,
8480
+ "requirements can only be finalized for an in-progress man or manteam workflow at step 1 or 2"
8481
+ );
8482
+ }
8483
+ const workflowPath = path28.join(rootDir, ".mancode", "workflows", taskId);
8484
+ const jsonPath = path28.join(workflowPath, "requirements.json");
8485
+ const markdownPath = path28.join(workflowPath, "requirements.md");
8486
+ const metadataPath2 = path28.join(workflowPath, "metadata.json");
8487
+ let originalJson;
8488
+ let originalMarkdown;
8489
+ let originalMetadata;
8490
+ try {
8491
+ const inputPath = path28.isAbsolute(options.file) ? options.file : path28.resolve(rootDir, options.file);
8492
+ const input = await readFile14(inputPath, "utf-8");
8493
+ const requirements = parseRequirementsLedger(input);
8494
+ [originalJson, originalMarkdown, originalMetadata] = await Promise.all([
8495
+ readOptionalText2(jsonPath),
8496
+ readOptionalText2(markdownPath),
8497
+ readFile14(metadataPath2, "utf-8")
8498
+ ]);
8499
+ await writeRequirementsArtifacts(rootDir, taskId, requirements);
8500
+ await updateWorkflow(rootDir, taskId, {
8501
+ requirementsStatus: requirementsAreReady(requirements) ? "ready" : "needs_clarification",
8502
+ requirementsDigest: requirementsDigest(requirements)
8503
+ });
8504
+ if (options.json) {
8505
+ console.log(
8506
+ JSON.stringify(
8507
+ {
8508
+ requirements,
8509
+ requirementsStatus: requirementsAreReady(requirements) ? "ready" : "needs_clarification"
8510
+ },
8511
+ null,
8512
+ 2
8513
+ )
8514
+ );
8515
+ } else {
8516
+ console.log(
8517
+ `Finalized requirements: ${taskId} (${requirementsAreReady(requirements) ? "ready" : "needs_clarification"})`
8518
+ );
8519
+ }
8520
+ return EXIT_OK9;
8521
+ } catch (error) {
8522
+ let rollbackIncomplete = false;
8523
+ if (originalJson !== void 0 && originalMarkdown !== void 0 && originalMetadata !== void 0) {
8524
+ const rollback = await Promise.allSettled([
8525
+ restoreOptionalText(jsonPath, originalJson),
8526
+ restoreOptionalText(markdownPath, originalMarkdown),
8527
+ writeFile15(metadataPath2, originalMetadata, "utf-8")
8528
+ ]);
8529
+ rollbackIncomplete = rollback.some(
8530
+ (result) => result.status === "rejected"
8531
+ );
8532
+ }
8533
+ const message = error instanceof Error ? error.message : "unable to finalize requirements";
8534
+ return invalidArg(
8535
+ options,
8536
+ rollbackIncomplete ? `${message}; rollback was incomplete` : message
8537
+ );
8538
+ }
8539
+ }
8540
+ async function workflowVerify(rootDir, args, options) {
8541
+ const taskId = args[0];
8542
+ const action = args[1];
8543
+ if (!taskId || !isValidWorkflowTaskId(taskId)) {
8544
+ return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
8545
+ }
8546
+ const meta = await readWorkflow(rootDir, taskId);
8547
+ if (!meta || meta.mode !== "man" && meta.mode !== "manteam") {
8548
+ return invalidArg(options, `verification is not valid for: ${taskId}`);
8549
+ }
8550
+ if (action === "show") {
8551
+ const ledger = await readVerificationLedger(rootDir, taskId);
8552
+ if (!ledger) {
8553
+ return invalidArg(options, `verification not initialized: ${taskId}`);
8554
+ }
8555
+ outputVerificationLedger(ledger, options);
8556
+ return EXIT_OK9;
8557
+ }
8558
+ if (meta.verificationPolicyVersion !== 1 || (action === "init" ? meta.currentStep !== 6 : meta.currentStep !== 6 && meta.currentStep !== 9) || meta.planDecision !== "governed_execution" || meta.status !== "in_progress" && meta.status !== "blocked") {
8559
+ return invalidArg(
8560
+ options,
8561
+ "verification requires a governed policy-v2 workflow at step 6, or step 9 when refreshing evidence"
8562
+ );
8563
+ }
8564
+ try {
8565
+ let ledger;
8566
+ if (action === "init") {
8567
+ if (await readVerificationLedger(rootDir, taskId)) {
8568
+ return invalidArg(
8569
+ options,
8570
+ `verification already initialized: ${taskId}`
8571
+ );
8572
+ }
8573
+ const requirements = await readRequirementsLedger(rootDir, taskId);
8574
+ if (!requirements || !requirementsAreReady(requirements)) {
8575
+ return invalidArg(options, "ready requirements.json is required");
8576
+ }
8577
+ ledger = initializeVerificationLedger(
8578
+ requirements,
8579
+ meta.planVersion ?? 1,
8580
+ 0
8581
+ );
8582
+ } else {
8583
+ let existing = await readVerificationLedger(rootDir, taskId);
8584
+ if (!existing) {
8585
+ return invalidArg(options, `verification not initialized: ${taskId}`);
8586
+ }
8587
+ const review = await readReviewLedger(rootDir, taskId);
8588
+ existing = resetVerificationForRemediation(
8589
+ existing,
8590
+ review?.remediationRounds ?? 0
8591
+ );
8592
+ if (!options.acceptance || !options.evidence) {
8593
+ return invalidArg(
8594
+ options,
8595
+ `${action ?? "verification"} requires --acceptance and --evidence`
8596
+ );
8597
+ }
8598
+ if (action === "record") {
8599
+ if (options.method !== "automated" || options.result !== "passed" && options.result !== "failed" && options.result !== "blocked") {
8600
+ return invalidArg(
8601
+ options,
8602
+ "record requires --method automated and --result passed|failed|blocked"
8603
+ );
8604
+ }
8605
+ let automatedDetails;
8606
+ if (options.result === "passed" || options.result === "failed") {
8607
+ const exitCode = parseExactInteger(options.exitCode);
8608
+ if (!options.command?.trim() || exitCode === null) {
8609
+ return invalidArg(
8610
+ options,
8611
+ "automated passed/failed verification requires --command and --exit-code"
8612
+ );
8613
+ }
8614
+ if (options.evidenceFile) {
8615
+ const evidencePath = path28.isAbsolute(options.evidenceFile) ? options.evidenceFile : path28.resolve(rootDir, options.evidenceFile);
8616
+ if (!await pathExists12(evidencePath)) {
8617
+ return invalidArg(
8618
+ options,
8619
+ `verification evidence file not found: ${options.evidenceFile}`
8620
+ );
8621
+ }
8622
+ }
8623
+ automatedDetails = {
8624
+ command: options.command,
8625
+ exitCode,
8626
+ ...options.evidenceFile ? { evidenceFile: options.evidenceFile } : {}
8627
+ };
8628
+ }
8629
+ ledger = recordVerification(
8630
+ existing,
8631
+ options.acceptance,
8632
+ "automated",
8633
+ options.result,
8634
+ options.evidence,
8635
+ automatedDetails
8636
+ );
8637
+ } else if (action === "require-manual") {
8638
+ ledger = recordVerification(
8639
+ existing,
8640
+ options.acceptance,
8641
+ "manual",
8642
+ "manual_required",
8643
+ options.evidence
8644
+ );
8645
+ } else if (action === "confirm-manual") {
8646
+ ledger = confirmManualVerification(
8647
+ existing,
8648
+ options.acceptance,
8649
+ options.evidence
8650
+ );
8651
+ } else {
8652
+ return invalidArg(
8653
+ options,
8654
+ `invalid verification action: ${action ?? ""}`
8655
+ );
8656
+ }
8657
+ }
8658
+ const transitionError = await commitVerificationTransition(
8659
+ rootDir,
8660
+ meta,
8661
+ ledger
8662
+ );
8663
+ if (transitionError) return invalidArg(options, transitionError);
8664
+ outputVerificationLedger(ledger, options);
8665
+ return EXIT_OK9;
8666
+ } catch (error) {
8667
+ return invalidArg(
8668
+ options,
8669
+ error instanceof Error ? error.message : "unable to update verification"
8670
+ );
8671
+ }
8672
+ }
8673
+ async function commitVerificationTransition(rootDir, meta, ledger) {
8674
+ const ledgerPath = verificationLedgerPath(rootDir, meta.taskId);
8675
+ const metadataPath2 = path28.join(
8676
+ rootDir,
8677
+ ".mancode",
8678
+ "workflows",
8679
+ meta.taskId,
8680
+ "metadata.json"
8681
+ );
8682
+ const specPath = path28.join(rootDir, ".mancode", "memory", "spec.md");
8683
+ const [originalLedger, originalMetadata, originalSpec] = await Promise.all([
8684
+ readOptionalText2(ledgerPath),
8685
+ readFile14(metadataPath2, "utf-8"),
8686
+ readOptionalText2(specPath)
8687
+ ]);
8688
+ const wasVerificationBlocked = meta.status === "blocked" && meta.blockingReason?.startsWith("[verification]");
8689
+ const shouldBlock = ledger.status === "manual_required" || ledger.status === "blocked";
8690
+ const workflowPatch = {
8691
+ verificationStatus: ledger.status,
8692
+ ...shouldBlock ? {
8693
+ status: "blocked",
8694
+ blockingReason: `[verification] ${ledger.status === "manual_required" ? "manual confirmation required" : "verification blocked"}`
8695
+ } : wasVerificationBlocked ? { status: "in_progress", blockingReason: void 0 } : {}
8696
+ };
8697
+ try {
8698
+ await writeVerificationLedger(rootDir, meta.taskId, ledger);
8699
+ await updateWorkflow(rootDir, meta.taskId, workflowPatch, {
8700
+ allowIncompleteVerification: true
8701
+ });
8702
+ const updated = await readWorkflow(rootDir, meta.taskId);
8703
+ if (updated) {
8704
+ await upsertActivePlan(rootDir, {
8705
+ taskId: updated.taskId,
8706
+ status: updated.status,
8707
+ planVersion: updated.planVersion ?? 1
8708
+ });
8709
+ }
8710
+ return null;
8711
+ } catch (error) {
8712
+ const rollback = await Promise.allSettled([
8713
+ restoreOptionalText(ledgerPath, originalLedger),
8714
+ writeFile15(metadataPath2, originalMetadata, "utf-8"),
8715
+ restoreOptionalText(specPath, originalSpec)
8716
+ ]);
8717
+ const message = error instanceof Error ? error.message : "verification transition failed";
8718
+ return rollback.some((result) => result.status === "rejected") ? `${message}; rollback was incomplete` : message;
8719
+ }
8720
+ }
8721
+ function outputVerificationLedger(ledger, options) {
8722
+ if (options.json) {
8723
+ console.log(JSON.stringify(ledger, null, 2));
8724
+ return;
8725
+ }
8726
+ const passed = ledger.checks.filter(
8727
+ (check) => [check.automated, check.manual].filter(Boolean).every((component) => component?.status === "passed")
8728
+ ).length;
8729
+ console.log(
8730
+ `Verification: ${ledger.status}; criteria ${passed}/${ledger.checks.length}; plan v${ledger.planVersion}`
8731
+ );
8732
+ }
7777
8733
  async function workflowReview(rootDir, args, options) {
7778
8734
  const taskId = args[0];
7779
8735
  const action = args[1];
@@ -7798,6 +8754,36 @@ async function workflowReview(rootDir, args, options) {
7798
8754
  "review requires an in_progress workflow at step 6 or later"
7799
8755
  );
7800
8756
  }
8757
+ if ((action === "init" || action === "skip") && meta.verificationPolicyVersion === 1 && !await verificationCanAdvance(rootDir, taskId, meta.planVersion ?? 1)) {
8758
+ return invalidArg(
8759
+ options,
8760
+ "workflow verification must pass before review can start"
8761
+ );
8762
+ }
8763
+ if (action === "skip") {
8764
+ if (meta.reviewPolicyVersion !== 2 || meta.currentStep !== 6 || meta.planDecision !== "governed_execution" || !options.reason?.trim()) {
8765
+ return invalidArg(
8766
+ options,
8767
+ "review skip requires a governed policy-v2 workflow at step 6 and --reason"
8768
+ );
8769
+ }
8770
+ try {
8771
+ const error = await commitReviewSkip(
8772
+ rootDir,
8773
+ meta,
8774
+ options.reason.trim()
8775
+ );
8776
+ if (error) return invalidArg(options, error);
8777
+ const ledger = await readReviewLedger(rootDir, taskId);
8778
+ outputReviewLedger(ledger, options);
8779
+ return EXIT_OK9;
8780
+ } catch (error) {
8781
+ return invalidArg(
8782
+ options,
8783
+ error instanceof Error ? error.message : "unable to skip review"
8784
+ );
8785
+ }
8786
+ }
7801
8787
  try {
7802
8788
  let ledger;
7803
8789
  if (action === "init") {
@@ -7843,7 +8829,7 @@ async function workflowReview(rootDir, args, options) {
7843
8829
  return invalidArg(
7844
8830
  options,
7845
8831
  `invalid review action: ${action ?? ""}`,
7846
- "Use: mancode workflow review <taskId> <init|complete|remediate|show>"
8832
+ "Use: mancode workflow review <taskId> <init|complete|remediate|skip|show>"
7847
8833
  );
7848
8834
  }
7849
8835
  outputReviewLedger(ledger, options);
@@ -7855,12 +8841,44 @@ async function workflowReview(rootDir, args, options) {
7855
8841
  );
7856
8842
  }
7857
8843
  }
8844
+ async function commitReviewSkip(rootDir, meta, reason) {
8845
+ const ledgerPath = reviewLedgerPath(rootDir, meta.taskId);
8846
+ const metadataPath2 = path28.join(
8847
+ rootDir,
8848
+ ".mancode",
8849
+ "workflows",
8850
+ meta.taskId,
8851
+ "metadata.json"
8852
+ );
8853
+ const [originalLedger, originalMetadata] = await Promise.all([
8854
+ readOptionalText2(ledgerPath),
8855
+ readFile14(metadataPath2, "utf-8")
8856
+ ]);
8857
+ try {
8858
+ await initializeSkippedReview(rootDir, meta.taskId, reason);
8859
+ await updateWorkflow(rootDir, meta.taskId, {
8860
+ skippedSteps: [.../* @__PURE__ */ new Set([...meta.skippedSteps, "review"])]
8861
+ });
8862
+ return null;
8863
+ } catch (error) {
8864
+ const rollback = await Promise.allSettled([
8865
+ restoreOptionalText(ledgerPath, originalLedger),
8866
+ writeFile15(metadataPath2, originalMetadata, "utf-8")
8867
+ ]);
8868
+ const message = error instanceof Error ? error.message : "review skip failed";
8869
+ return rollback.some((result) => result.status === "rejected") ? `${message}; rollback was incomplete` : message;
8870
+ }
8871
+ }
7858
8872
  function outputReviewLedger(ledger, options) {
7859
8873
  if (options.json) {
7860
8874
  console.log(JSON.stringify(ledger, null, 2));
7861
8875
  return;
7862
8876
  }
7863
8877
  if (!ledger) return;
8878
+ if (ledger.skipped) {
8879
+ console.log(`Review: skipped; reason ${ledger.skipped.reason}`);
8880
+ return;
8881
+ }
7864
8882
  const openBlockers = ledger.blockers.filter(
7865
8883
  (blocker) => blocker.status === "open"
7866
8884
  );
@@ -7891,7 +8909,8 @@ async function workflowCreate(rootDir, args, options) {
7891
8909
  }
7892
8910
  try {
7893
8911
  const meta = await createWorkflow(rootDir, task, mode, {
7894
- parentTaskId: options.parentTask
8912
+ parentTaskId: options.parentTask,
8913
+ planningPolicyVersion: mode === "man" || mode === "manteam" ? 2 : void 0
7895
8914
  });
7896
8915
  if (options.json) {
7897
8916
  console.log(JSON.stringify(meta, null, 2));
@@ -7911,7 +8930,7 @@ async function workflowUpdate(rootDir, taskId, options) {
7911
8930
  return invalidArg(
7912
8931
  options,
7913
8932
  "missing taskId",
7914
- "Use: mancode workflow update <taskId> [--step N] [--status in_progress|planned|completed|blocked|abandoned] [--blocking-reason <reason>] [--outcome <outcome>] [--plan-version N] [--skipped a,b]"
8933
+ "Use: mancode workflow update <taskId> [--step N] [--status in_progress|planned|completed|blocked|abandoned] [--requirements-status ready|needs_clarification] [--blocking-reason <reason>] [--outcome <outcome>] [--plan-version N] [--skipped clarification]"
7915
8934
  );
7916
8935
  }
7917
8936
  if (!isValidWorkflowTaskId(taskId)) {
@@ -7972,8 +8991,45 @@ async function workflowUpdate(rootDir, taskId, options) {
7972
8991
  }
7973
8992
  patch.planVersion = planVersion;
7974
8993
  }
8994
+ if (options.requirementsStatus !== void 0) {
8995
+ if (!isRequirementsStatus(options.requirementsStatus) || existing.mode !== "man" && existing.mode !== "manteam") {
8996
+ return invalidArg(
8997
+ options,
8998
+ `invalid --requirements-status: ${options.requirementsStatus}`
8999
+ );
9000
+ }
9001
+ patch.requirementsStatus = options.requirementsStatus;
9002
+ }
9003
+ if (options.planDecision !== void 0) {
9004
+ if (options.planDecision !== "governed_execution" || existing.mode !== "man" && existing.mode !== "manteam") {
9005
+ return invalidArg(
9006
+ options,
9007
+ `invalid --plan-decision: ${options.planDecision}`
9008
+ );
9009
+ }
9010
+ patch.planDecision = options.planDecision;
9011
+ }
7975
9012
  if (options.skipped !== void 0) {
7976
- patch.skippedSteps = options.skipped.split(",").map((item) => item.trim()).filter(Boolean);
9013
+ const requestedSkipped = options.skipped.split(",").map((item) => item.trim()).filter(Boolean);
9014
+ if (existing.reviewPolicyVersion === 2) {
9015
+ if (requestedSkipped.includes("review")) {
9016
+ return invalidArg(
9017
+ options,
9018
+ "policy-v2 review skips require workflow review <taskId> skip --reason <reason>"
9019
+ );
9020
+ }
9021
+ if (existing.currentStep > 2 || requestedSkipped.some((item) => item !== "clarification")) {
9022
+ return invalidArg(
9023
+ options,
9024
+ "policy-v2 clarification can only be skipped at step 1 or 2"
9025
+ );
9026
+ }
9027
+ patch.skippedSteps = [
9028
+ .../* @__PURE__ */ new Set([...existing.skippedSteps, ...requestedSkipped])
9029
+ ];
9030
+ } else {
9031
+ patch.skippedSteps = requestedSkipped;
9032
+ }
7977
9033
  }
7978
9034
  if (Object.keys(patch).length === 0) {
7979
9035
  return invalidArg(options, "missing update fields");
@@ -8004,6 +9060,307 @@ async function workflowUpdate(rootDir, taskId, options) {
8004
9060
  }
8005
9061
  return EXIT_OK9;
8006
9062
  }
9063
+ async function workflowHandoff(rootDir, taskId, options) {
9064
+ if (!taskId || !isValidWorkflowTaskId(taskId)) {
9065
+ return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
9066
+ }
9067
+ if (options.complete) {
9068
+ if (options.to !== void 0) {
9069
+ return invalidArg(options, "--complete cannot be combined with --to");
9070
+ }
9071
+ return workflowCompleteHandoff(rootDir, taskId, options);
9072
+ }
9073
+ if (options.to !== "solo") {
9074
+ return invalidArg(options, "handoff requires --to solo");
9075
+ }
9076
+ const meta = await readWorkflow(rootDir, taskId);
9077
+ if (!meta || meta.mode !== "man" && meta.mode !== "manteam") {
9078
+ return invalidArg(options, `workflow cannot be handed off: ${taskId}`);
9079
+ }
9080
+ if (meta.status !== "in_progress" || meta.currentStep !== 4 || meta.requirementsStatus !== "ready" || meta.planDecision !== void 0) {
9081
+ return invalidArg(
9082
+ options,
9083
+ "solo handoff requires an undecided in-progress workflow at step 4 with ready requirements"
9084
+ );
9085
+ }
9086
+ const workflowPath = path28.join(rootDir, ".mancode", "workflows", taskId);
9087
+ if (!await pathExists12(path28.join(workflowPath, "requirements.md")) || !await pathExists12(path28.join(workflowPath, "plan.md"))) {
9088
+ return invalidArg(
9089
+ options,
9090
+ "solo handoff requires requirements.md and plan.md"
9091
+ );
9092
+ }
9093
+ const statePath = path28.join(rootDir, ".mancode", "state.json");
9094
+ let originalState;
9095
+ let state;
9096
+ try {
9097
+ originalState = await readFile14(statePath, "utf-8");
9098
+ state = JSON.parse(originalState);
9099
+ } catch {
9100
+ return invalidArg(
9101
+ options,
9102
+ "unable to read .mancode/state.json for handoff"
9103
+ );
9104
+ }
9105
+ if (state.currentTask !== taskId) {
9106
+ return invalidArg(
9107
+ options,
9108
+ "current workflow state does not match the handoff task"
9109
+ );
9110
+ }
9111
+ const activeSoloPlan = readActiveSoloPlan(state.activeSoloPlan);
9112
+ if (activeSoloPlan && activeSoloPlan.taskId !== taskId) {
9113
+ return invalidArg(
9114
+ options,
9115
+ `another solo plan is active: ${activeSoloPlan.taskId}; complete or abandon it before handoff`
9116
+ );
9117
+ }
9118
+ const nextState = {
9119
+ ...state,
9120
+ currentMode: "solo",
9121
+ lastMode: typeof state.currentMode === "string" ? state.currentMode : meta.mode,
9122
+ currentTask: null,
9123
+ currentWorkflowMode: null,
9124
+ skippedSteps: [],
9125
+ activeSoloPlan: {
9126
+ taskId,
9127
+ planVersion: meta.planVersion ?? 1
9128
+ }
9129
+ };
9130
+ const transitionError = await commitPlanningTransition({
9131
+ rootDir,
9132
+ taskId,
9133
+ statePath,
9134
+ originalState,
9135
+ nextState,
9136
+ workflowPatch: {
9137
+ status: "planned",
9138
+ planDecision: "solo_handoff"
9139
+ },
9140
+ activePlanStatus: "planned",
9141
+ planVersion: meta.planVersion ?? 1
9142
+ });
9143
+ if (transitionError) {
9144
+ return invalidArg(options, transitionError);
9145
+ }
9146
+ const result = await readWorkflow(rootDir, taskId);
9147
+ if (options.json) {
9148
+ console.log(
9149
+ JSON.stringify(
9150
+ { workflow: result, activeSoloPlan: nextState.activeSoloPlan },
9151
+ null,
9152
+ 2
9153
+ )
9154
+ );
9155
+ } else {
9156
+ console.log(`Handed off plan to solo: ${taskId}`);
9157
+ }
9158
+ return EXIT_OK9;
9159
+ }
9160
+ async function workflowCompleteHandoff(rootDir, taskId, options) {
9161
+ const meta = await readWorkflow(rootDir, taskId);
9162
+ if (!meta || meta.mode !== "man" && meta.mode !== "manteam" || meta.status !== "planned" || meta.currentStep !== 4 || meta.planDecision !== "solo_handoff") {
9163
+ return invalidArg(
9164
+ options,
9165
+ `workflow is not an active solo handoff: ${taskId}`
9166
+ );
9167
+ }
9168
+ const statePath = path28.join(rootDir, ".mancode", "state.json");
9169
+ let originalState;
9170
+ let state;
9171
+ try {
9172
+ originalState = await readFile14(statePath, "utf-8");
9173
+ state = JSON.parse(originalState);
9174
+ } catch {
9175
+ return invalidArg(
9176
+ options,
9177
+ "unable to read .mancode/state.json for handoff completion"
9178
+ );
9179
+ }
9180
+ const activeSoloPlan = readActiveSoloPlan(state.activeSoloPlan);
9181
+ if (activeSoloPlan?.taskId !== taskId) {
9182
+ return invalidArg(
9183
+ options,
9184
+ "active solo plan does not match the handoff task"
9185
+ );
9186
+ }
9187
+ const nextState = {
9188
+ ...state,
9189
+ activeSoloPlan: null
9190
+ };
9191
+ const transitionError = await commitPlanningTransition({
9192
+ rootDir,
9193
+ taskId,
9194
+ statePath,
9195
+ originalState,
9196
+ nextState,
9197
+ workflowPatch: { status: "completed" },
9198
+ activePlanStatus: "completed",
9199
+ planVersion: meta.planVersion ?? 1
9200
+ });
9201
+ if (transitionError) return invalidArg(options, transitionError);
9202
+ const result = await readWorkflow(rootDir, taskId);
9203
+ if (options.json) {
9204
+ console.log(
9205
+ JSON.stringify({ workflow: result, activeSoloPlan: null }, null, 2)
9206
+ );
9207
+ } else {
9208
+ console.log(`Completed solo handoff: ${taskId}`);
9209
+ }
9210
+ return EXIT_OK9;
9211
+ }
9212
+ async function workflowDecide(rootDir, taskId, options) {
9213
+ if (!taskId || !isValidWorkflowTaskId(taskId)) {
9214
+ return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
9215
+ }
9216
+ if (!isPlanDecision(options.planDecision) || options.planDecision === "solo_handoff") {
9217
+ return invalidArg(
9218
+ options,
9219
+ "decide requires --plan-decision plan_only or governed_execution"
9220
+ );
9221
+ }
9222
+ if (options.planDecision === "governed_execution") {
9223
+ return workflowUpdate(rootDir, taskId, {
9224
+ ...options,
9225
+ planDecision: "governed_execution"
9226
+ });
9227
+ }
9228
+ const meta = await readWorkflow(rootDir, taskId);
9229
+ if (!meta || meta.mode !== "man" && meta.mode !== "manteam" || meta.status !== "in_progress" || meta.currentStep !== 4 || meta.requirementsStatus !== "ready" || meta.planDecision !== void 0) {
9230
+ return invalidArg(
9231
+ options,
9232
+ `workflow is not ready for a plan-only decision: ${taskId}`
9233
+ );
9234
+ }
9235
+ const workflowPath = path28.join(rootDir, ".mancode", "workflows", taskId);
9236
+ if (!await pathExists12(path28.join(workflowPath, "requirements.md")) || !await pathExists12(path28.join(workflowPath, "plan.md"))) {
9237
+ return invalidArg(
9238
+ options,
9239
+ "plan-only requires requirements.md and plan.md"
9240
+ );
9241
+ }
9242
+ const statePath = path28.join(rootDir, ".mancode", "state.json");
9243
+ let originalState;
9244
+ let state;
9245
+ try {
9246
+ originalState = await readFile14(statePath, "utf-8");
9247
+ state = JSON.parse(originalState);
9248
+ } catch {
9249
+ return invalidArg(
9250
+ options,
9251
+ "unable to read .mancode/state.json for decision"
9252
+ );
9253
+ }
9254
+ if (state.currentTask !== taskId) {
9255
+ return invalidArg(
9256
+ options,
9257
+ "current workflow state does not match the decision task"
9258
+ );
9259
+ }
9260
+ const activeSoloPlan = readActiveSoloPlan(state.activeSoloPlan);
9261
+ if (activeSoloPlan) {
9262
+ return invalidArg(
9263
+ options,
9264
+ `another solo plan is active: ${activeSoloPlan.taskId}; resolve it before plan-only`
9265
+ );
9266
+ }
9267
+ const nextState = {
9268
+ ...state,
9269
+ currentMode: "solo",
9270
+ lastMode: typeof state.currentMode === "string" ? state.currentMode : meta.mode,
9271
+ currentTask: null,
9272
+ currentWorkflowMode: null,
9273
+ skippedSteps: [],
9274
+ activeSoloPlan: null
9275
+ };
9276
+ const transitionError = await commitPlanningTransition({
9277
+ rootDir,
9278
+ taskId,
9279
+ statePath,
9280
+ originalState,
9281
+ nextState,
9282
+ workflowPatch: { status: "planned", planDecision: "plan_only" },
9283
+ activePlanStatus: "planned",
9284
+ planVersion: meta.planVersion ?? 1
9285
+ });
9286
+ if (transitionError) return invalidArg(options, transitionError);
9287
+ const result = await readWorkflow(rootDir, taskId);
9288
+ if (options.json) {
9289
+ console.log(
9290
+ JSON.stringify({ workflow: result, state: nextState }, null, 2)
9291
+ );
9292
+ } else {
9293
+ console.log(`Saved plan without execution: ${taskId}`);
9294
+ }
9295
+ return EXIT_OK9;
9296
+ }
9297
+ async function commitPlanningTransition(args) {
9298
+ const metadataPath2 = path28.join(
9299
+ args.rootDir,
9300
+ ".mancode",
9301
+ "workflows",
9302
+ args.taskId,
9303
+ "metadata.json"
9304
+ );
9305
+ const specPath = path28.join(args.rootDir, ".mancode", "memory", "spec.md");
9306
+ let originalMetadata;
9307
+ let originalSpec;
9308
+ try {
9309
+ originalMetadata = await readFile14(metadataPath2, "utf-8");
9310
+ originalSpec = await readOptionalText2(specPath);
9311
+ } catch (error) {
9312
+ return error instanceof Error ? `unable to prepare planning transition: ${error.message}` : "unable to prepare planning transition";
9313
+ }
9314
+ try {
9315
+ await updateWorkflow(args.rootDir, args.taskId, args.workflowPatch);
9316
+ await writeFile15(
9317
+ args.statePath,
9318
+ `${JSON.stringify(args.nextState, null, 2)}
9319
+ `,
9320
+ "utf-8"
9321
+ );
9322
+ await upsertActivePlan(args.rootDir, {
9323
+ taskId: args.taskId,
9324
+ status: args.activePlanStatus,
9325
+ planVersion: args.planVersion
9326
+ });
9327
+ return null;
9328
+ } catch (error) {
9329
+ const rollback = await Promise.allSettled([
9330
+ writeFile15(metadataPath2, originalMetadata, "utf-8"),
9331
+ writeFile15(args.statePath, args.originalState, "utf-8"),
9332
+ originalSpec === null ? rm6(specPath, { force: true }) : writeFile15(specPath, originalSpec, "utf-8")
9333
+ ]);
9334
+ const message = error instanceof Error ? error.message : "planning transition failed";
9335
+ return rollback.some((result) => result.status === "rejected") ? `${message}; rollback was incomplete` : message;
9336
+ }
9337
+ }
9338
+ async function readOptionalText2(filePath) {
9339
+ try {
9340
+ return await readFile14(filePath, "utf-8");
9341
+ } catch (error) {
9342
+ if (isNodeError10(error) && error.code === "ENOENT") return null;
9343
+ throw error;
9344
+ }
9345
+ }
9346
+ async function restoreOptionalText(filePath, content) {
9347
+ if (content === null) {
9348
+ await rm6(filePath, { force: true });
9349
+ return;
9350
+ }
9351
+ await writeFile15(filePath, content, "utf-8");
9352
+ }
9353
+ function readActiveSoloPlan(value) {
9354
+ if (!value || typeof value !== "object") return null;
9355
+ const plan = value;
9356
+ if (typeof plan.taskId !== "string" || !Number.isInteger(plan.planVersion)) {
9357
+ return null;
9358
+ }
9359
+ return { taskId: plan.taskId, planVersion: plan.planVersion };
9360
+ }
9361
+ function isNodeError10(error) {
9362
+ return error instanceof Error && "code" in error;
9363
+ }
8007
9364
  async function workflowList(rootDir, options) {
8008
9365
  const workflows = await listWorkflows(rootDir);
8009
9366
  const views = attachActiveChildren(workflows);
@@ -8075,6 +9432,11 @@ async function workflowShow(rootDir, taskId, options) {
8075
9432
  if (meta.parentTaskId) console.log(`Parent: ${meta.parentTaskId}`);
8076
9433
  if (meta.planVersion !== void 0)
8077
9434
  console.log(`Plan version: ${meta.planVersion}`);
9435
+ if (meta.requirementsStatus)
9436
+ console.log(`Requirements: ${meta.requirementsStatus}`);
9437
+ if (meta.verificationStatus)
9438
+ console.log(`Verification: ${meta.verificationStatus}`);
9439
+ if (meta.planDecision) console.log(`Plan choice: ${meta.planDecision}`);
8078
9440
  if (meta.outcome) console.log(`Outcome: ${meta.outcome}`);
8079
9441
  if (meta.blockingReason) console.log(`Blocked: ${meta.blockingReason}`);
8080
9442
  if (children !== void 0) {
@@ -8193,6 +9555,11 @@ function parseExactPositiveInteger(value) {
8193
9555
  const parsed = Number(value);
8194
9556
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
8195
9557
  }
9558
+ function parseExactInteger(value) {
9559
+ if (!value || !/^-?\d+$/.test(value)) return null;
9560
+ const parsed = Number(value);
9561
+ return Number.isSafeInteger(parsed) ? parsed : null;
9562
+ }
8196
9563
  function parseOlderThan(value) {
8197
9564
  if (!value) return void 0;
8198
9565
  const match = /^(\d+)([dhm])$/.exec(value);
@@ -8267,13 +9634,22 @@ program.command("uninstall [platform]").description("Remove platform adapter or
8267
9634
  program.command("workflow <subcommand> [args...]").description("Manage mancode workflows").option("--dry-run", "Preview clean without deleting").option("--older-than <duration>", "Clean workflows older than (e.g. 30d)").option("--step <n>", "Update workflow current step").option("--status <status>", "Update workflow status").option(
8268
9635
  "--parent-task <taskId>",
8269
9636
  "Parent /man or /manteam workflow for manba"
8270
- ).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set manba outcome").option("--plan-version <n>", "Set the next man/manteam plan revision").option("--skipped <steps>", "Update skipped steps as comma-separated values").option("--review-depth <depth>", "Review depth: targeted or full").option("--review-domain <domain>", "Review domain: quality or security").option(
9637
+ ).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set manba outcome").option("--plan-version <n>", "Set the next man/manteam plan revision").option(
9638
+ "--requirements-status <status>",
9639
+ "Planning readiness: ready or needs_clarification"
9640
+ ).option(
9641
+ "--plan-decision <decision>",
9642
+ "Plan gate choice: plan_only or governed_execution"
9643
+ ).option("--to <mode>", "Workflow handoff target (solo)").option("--complete", "Complete an active solo handoff").option(
9644
+ "--skipped <steps>",
9645
+ "Policy v2: clarification only; use workflow review skip for review"
9646
+ ).option("--review-depth <depth>", "Review depth: targeted or full").option("--review-domain <domain>", "Review domain: quality or security").option(
8271
9647
  "--report <path>",
8272
9648
  "Relative Markdown report path for a review domain"
8273
9649
  ).option("--blockers <ids>", "Comma-separated blocker ids found by a review").option(
8274
9650
  "--resolved <ids>",
8275
9651
  "Comma-separated blocker ids resolved in remediation"
8276
- ).option("--json", "Output as JSON (for scripts)").action(async (subcommand, args, options) => {
9652
+ ).option("--file <path>", "Structured requirements JSON input file").option("--acceptance <id>", "Acceptance criterion id (for example AC-1)").option("--method <method>", "Verification method: automated or manual").option("--result <result>", "Verification result").option("--evidence <text>", "Verification evidence or user confirmation").option("--command <command>", "Command used for automated verification").option("--exit-code <code>", "Exit code from automated verification").option("--evidence-file <path>", "Existing verification report or artifact").option("--reason <reason>", "Reason for an explicit review skip").option("--json", "Output as JSON (for scripts)").action(async (subcommand, args, options) => {
8277
9653
  const code = await workflow(process.cwd(), subcommand, args ?? [], options);
8278
9654
  process.exitCode = code;
8279
9655
  });