mancode 0.3.6 → 0.3.8

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
@@ -7,9 +7,9 @@ import {
7
7
  import { program } from "commander";
8
8
 
9
9
  // src/commands/init.ts
10
- import { promises as fs3 } from "fs";
10
+ import { promises as fs4 } from "fs";
11
11
  import os from "os";
12
- import path14 from "path";
12
+ import path15 from "path";
13
13
  import process5 from "process";
14
14
 
15
15
  // src/installers/claude-code.ts
@@ -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
  }
@@ -2631,9 +2713,9 @@ function renderManpsRule() {
2631
2713
  return renderModeSkill("manps", "/");
2632
2714
  }
2633
2715
 
2634
- // src/installers/codex.ts
2635
- import { writeFile as writeFile6 } from "fs/promises";
2636
- import path7 from "path";
2716
+ // src/installers/platform-status.ts
2717
+ import { promises as fs } from "fs";
2718
+ import path8 from "path";
2637
2719
 
2638
2720
  // src/installers/managed-block.ts
2639
2721
  var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
@@ -2725,104 +2807,14 @@ function findMarkerLine(content, marker) {
2725
2807
  return null;
2726
2808
  }
2727
2809
 
2728
- // src/installers/codex.ts
2729
- async function installCodex(projectRoot, options) {
2730
- await installMancodeCore(projectRoot);
2731
- const agentsPath = path7.join(projectRoot, "AGENTS.md");
2732
- const existing = await readTextIfExists(agentsPath);
2733
- const sharedContent = await generateSharedContent(projectRoot, {
2734
- platform: "codex",
2735
- displayName: "Codex (ChatGPT desktop/CLI)",
2736
- capabilities: {
2737
- slashCommands: "partial",
2738
- subagents: false,
2739
- hooks: false,
2740
- skills: "agents-skills"
2741
- },
2742
- minimal: options.minimal,
2743
- techStack: options.techStack,
2744
- uiLibrary: options.uiLibrary,
2745
- projectProfile: options.projectProfile
2746
- });
2747
- const block = [
2748
- DEFAULT_MANCODE_START_MARKER,
2749
- "<!-- Managed by mancode. Do not edit this block manually. -->",
2750
- "",
2751
- "# mancode Configuration",
2752
- "",
2753
- sharedContent.trim(),
2754
- DEFAULT_MANCODE_END_MARKER
2755
- ].join("\n");
2756
- await writeFile6(agentsPath, replaceManagedBlock(existing, block), "utf-8");
2757
- await installCodexSkills(projectRoot, options.minimal ?? false);
2758
- }
2759
-
2760
- // src/installers/copilot.ts
2761
- import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
2762
- import path8 from "path";
2763
- async function installCopilot(projectRoot, options) {
2764
- await installMancodeCore(projectRoot);
2765
- const githubDir = path8.join(projectRoot, ".github");
2766
- await mkdir6(githubDir, { recursive: true });
2767
- const instructionsPath = path8.join(githubDir, "copilot-instructions.md");
2768
- const existing = await readTextIfExists(instructionsPath);
2769
- const sharedContent = await generateSharedContent(projectRoot, {
2770
- platform: "copilot",
2771
- displayName: "GitHub Copilot",
2772
- capabilities: {
2773
- slashCommands: "none",
2774
- subagents: false,
2775
- hooks: false,
2776
- skills: "instructions"
2777
- },
2778
- minimal: true,
2779
- techStack: options.techStack,
2780
- uiLibrary: options.uiLibrary,
2781
- projectProfile: options.projectProfile
2782
- });
2783
- const sections = [
2784
- DEFAULT_MANCODE_START_MARKER,
2785
- "<!-- Managed by mancode. Do not edit this block manually. -->",
2786
- "",
2787
- "# mancode for GitHub Copilot",
2788
- "",
2789
- sharedContent.trim()
2790
- ];
2791
- if (!options.minimal) {
2792
- sections.push("", renderCopilotPromptConventions());
2793
- }
2794
- sections.push(DEFAULT_MANCODE_END_MARKER);
2795
- await writeFile7(
2796
- instructionsPath,
2797
- replaceManagedBlock(existing, sections.join("\n")),
2798
- "utf-8"
2799
- );
2800
- await installCopilotPrompts(projectRoot, options.minimal ?? false);
2801
- }
2802
- function renderCopilotPromptConventions() {
2803
- return [
2804
- "## mancode Prompt Conventions",
2805
- "",
2806
- "GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
2807
- "",
2808
- "- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
2809
- "- manba: diagnose bugs and validate real user flows or regressions.",
2810
- "- manteam: read team memory and write handoff-friendly summaries.",
2811
- "- manps: prefer `mancode manps [area]` before cleanup.",
2812
- "- mansolo: exit any active mode and return to default solo behavior.",
2813
- "",
2814
- "These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
2815
- ].join("\n");
2816
- }
2817
-
2818
2810
  // src/installers/zcode.ts
2819
- import { writeFile as writeFile8 } from "fs/promises";
2820
- import path9 from "path";
2811
+ import { writeFile as writeFile6 } from "fs/promises";
2812
+ import path7 from "path";
2821
2813
  var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
2822
2814
  var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
2823
2815
  async function installZcode(projectRoot, options) {
2824
2816
  await installMancodeCore(projectRoot);
2825
- const agentsPath = path9.join(projectRoot, "AGENTS.md");
2817
+ const agentsPath = path7.join(projectRoot, "AGENTS.md");
2826
2818
  const existing = await readTextIfExists(agentsPath);
2827
2819
  const sharedContent = await generateSharedContent(projectRoot, {
2828
2820
  platform: "zcode",
@@ -2847,7 +2839,7 @@ async function installZcode(projectRoot, options) {
2847
2839
  sharedContent.trim(),
2848
2840
  ZCODE_MANCODE_END_MARKER
2849
2841
  ].join("\n");
2850
- await writeFile8(
2842
+ await writeFile6(
2851
2843
  agentsPath,
2852
2844
  replaceManagedBlock(
2853
2845
  existing,
@@ -2860,120 +2852,491 @@ async function installZcode(projectRoot, options) {
2860
2852
  await installZcodeSkills(projectRoot, options.minimal ?? false);
2861
2853
  }
2862
2854
 
2863
- // src/installers/registry.ts
2864
- var PLATFORM_INSTALLERS = {
2865
- "claude-code": {
2866
- name: "claude-code",
2867
- displayName: "Claude Code",
2868
- capabilities: {
2869
- slashCommands: "native",
2870
- subagents: true,
2871
- hooks: true,
2872
- skills: "native"
2873
- },
2874
- install: installClaudeCode
2875
- },
2876
- cursor: {
2877
- name: "cursor",
2878
- displayName: "Cursor",
2879
- capabilities: {
2880
- slashCommands: "native",
2881
- subagents: false,
2882
- hooks: false,
2883
- skills: "rules"
2884
- },
2885
- install: installCursor
2886
- },
2887
- codex: {
2888
- name: "codex",
2889
- displayName: "Codex (ChatGPT desktop/CLI)",
2890
- capabilities: {
2891
- slashCommands: "partial",
2892
- subagents: false,
2893
- hooks: false,
2894
- skills: "agents-skills"
2895
- },
2896
- install: installCodex
2897
- },
2898
- copilot: {
2899
- name: "copilot",
2900
- displayName: "GitHub Copilot",
2901
- capabilities: {
2902
- slashCommands: "none",
2903
- subagents: false,
2904
- hooks: false,
2905
- skills: "instructions"
2906
- },
2907
- install: installCopilot
2908
- },
2909
- zcode: {
2910
- name: "zcode",
2911
- displayName: "ZCode",
2912
- capabilities: {
2913
- slashCommands: "partial",
2914
- subagents: false,
2915
- hooks: false,
2916
- skills: "agents-skills"
2917
- },
2918
- install: installZcode
2919
- }
2920
- };
2921
- function getPlatformInstaller(platform) {
2922
- if (!isPlatformName(platform)) return null;
2923
- return PLATFORM_INSTALLERS[platform];
2924
- }
2925
- function getPlatformInstallers() {
2926
- return Object.values(PLATFORM_INSTALLERS);
2927
- }
2928
- function formatPlatformName(platform) {
2929
- return getPlatformInstaller(platform)?.displayName ?? platform;
2855
+ // src/installers/platform-status.ts
2856
+ async function checkPlatformStatus(rootDir, platform, installed) {
2857
+ const readiness = await checkPlatformReadiness(rootDir, platform);
2858
+ return {
2859
+ installed,
2860
+ ready: installed && readiness.ready,
2861
+ target: readiness.target,
2862
+ detail: describeStatus(installed, readiness)
2863
+ };
2930
2864
  }
2931
- function isPlatformName(platform) {
2932
- return Object.prototype.hasOwnProperty.call(PLATFORM_INSTALLERS, platform);
2865
+ async function isPlatformPresent(rootDir, platform) {
2866
+ return (await checkPlatformReadiness(rootDir, platform)).present;
2933
2867
  }
2934
-
2935
- // src/system/detect-team.ts
2936
- import { execFile } from "child_process";
2937
- import { access as access2 } from "fs/promises";
2938
- import path10 from "path";
2939
- import process2 from "process";
2940
- import { promisify } from "util";
2941
- var execFileAsync = promisify(execFile);
2942
- var NO_TEAM = {
2943
- isTeam: false,
2944
- contributors: 1,
2945
- recentActive: 1,
2946
- hasRemote: false
2947
- };
2948
- var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
2949
- var GIT_ENV = {
2950
- ...process2.env,
2951
- GIT_PAGER: "cat",
2952
- PAGER: "cat",
2953
- GIT_TERMINAL_PROMPT: "0"
2954
- };
2955
- async function detectTeamStatus(projectRoot) {
2956
- try {
2957
- await access2(path10.join(projectRoot, ".git"));
2958
- const inside = await runGit(projectRoot, [
2959
- "rev-parse",
2960
- "--is-inside-work-tree"
2961
- ]);
2962
- if (inside.trim() !== "true") return NO_TEAM;
2963
- const [allEmails, recentEmails, remotes] = await Promise.all([
2964
- runGit(projectRoot, ["log", "--all", "--format=%ae"]),
2965
- runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
2966
- runGit(projectRoot, ["remote", "-v"])
2868
+ async function checkPlatformReadiness(rootDir, platform) {
2869
+ if (platform === "claude-code") {
2870
+ const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
2871
+ fileMatches(
2872
+ path8.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
2873
+ (content) => isGeneratedClaudeSkill(content, "solo")
2874
+ ),
2875
+ claudeHooksRegistered(rootDir),
2876
+ pathsExist([
2877
+ path8.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
2878
+ path8.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
2879
+ ])
2967
2880
  ]);
2968
- const contributors = countUniqueLines(allEmails);
2969
- const recentActive = countUniqueLines(recentEmails);
2970
- const hasRemote = REMOTE_PATTERN.test(remotes);
2881
+ const present = hasSoloSkill && registered && hasHookFiles;
2882
+ const minimal = await isPlatformMinimal(rootDir, "claude-code");
2883
+ const fullContentReady = minimal ? true : await claudeFullContentReady(rootDir);
2971
2884
  return {
2972
- isTeam: contributors > 1 && hasRemote && recentActive > 1,
2973
- contributors: contributors || 1,
2974
- recentActive: recentActive || 1,
2975
- hasRemote
2976
- };
2885
+ present,
2886
+ ready: present && fullContentReady,
2887
+ target: ".claude/",
2888
+ readyDetail: present && fullContentReady ? minimal ? "solo skill and hooks ready" : "skills, agents, and hooks ready" : "skills, agents, hook files, or hook registration are incomplete"
2889
+ };
2890
+ }
2891
+ if (platform === "cursor") {
2892
+ const hasCoreRules = await allManagedSkills(
2893
+ MANCODE_CURSOR_CORE_RULE_FILES.map(
2894
+ (file) => path8.join(rootDir, ".cursor", "rules", file)
2895
+ ),
2896
+ [CURSOR_RULE_MANAGED_MARKER]
2897
+ );
2898
+ if (await isPlatformMinimal(rootDir, "cursor")) {
2899
+ return {
2900
+ present: hasCoreRules,
2901
+ ready: hasCoreRules,
2902
+ target: ".cursor/rules/",
2903
+ readyDetail: "mancode core rules present"
2904
+ };
2905
+ }
2906
+ const [hasRules, hasCommands] = await Promise.all([
2907
+ allManagedSkills(
2908
+ MANCODE_CURSOR_RULE_FILES.map(
2909
+ (file) => path8.join(rootDir, ".cursor", "rules", file)
2910
+ ),
2911
+ [CURSOR_RULE_MANAGED_MARKER]
2912
+ ),
2913
+ allManagedSkills(
2914
+ MODE_NAMES.map(
2915
+ (mode) => path8.join(rootDir, ".cursor", "commands", `${mode}.md`)
2916
+ ),
2917
+ [MODE_FILE_MANAGED_MARKER]
2918
+ )
2919
+ ]);
2920
+ return {
2921
+ present: hasCoreRules,
2922
+ ready: hasRules && hasCommands,
2923
+ target: ".cursor/rules/ + .cursor/commands/",
2924
+ readyDetail: hasRules && hasCommands ? "mancode rules and commands present" : "mode rules or commands are missing, incomplete, or user-authored"
2925
+ };
2926
+ }
2927
+ if (platform === "codex") {
2928
+ const hasBlock2 = await fileHasManagedBlock(path8.join(rootDir, "AGENTS.md"));
2929
+ if (!hasBlock2) {
2930
+ return {
2931
+ present: false,
2932
+ ready: false,
2933
+ target: "AGENTS.md",
2934
+ readyDetail: "managed block missing"
2935
+ };
2936
+ }
2937
+ if (await isPlatformMinimal(rootDir, "codex")) {
2938
+ return {
2939
+ present: true,
2940
+ ready: true,
2941
+ target: "AGENTS.md",
2942
+ readyDetail: "managed block present"
2943
+ };
2944
+ }
2945
+ const skillsDir = path8.join(rootDir, ".agents", "skills");
2946
+ const hasSkills = await allManagedSkills(
2947
+ MODE_NAMES.map((mode) => path8.join(skillsDir, mode, "SKILL.md")),
2948
+ MANCODE_AGENT_SKILL_MARKERS
2949
+ );
2950
+ return {
2951
+ present: true,
2952
+ ready: hasSkills,
2953
+ target: "AGENTS.md + .agents/skills/",
2954
+ readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
2955
+ };
2956
+ }
2957
+ if (platform === "zcode") {
2958
+ const hasBlock2 = await fileHasManagedBlock(
2959
+ path8.join(rootDir, "AGENTS.md"),
2960
+ ZCODE_MANCODE_START_MARKER,
2961
+ ZCODE_MANCODE_END_MARKER
2962
+ );
2963
+ if (!hasBlock2) {
2964
+ return {
2965
+ present: false,
2966
+ ready: false,
2967
+ target: "AGENTS.md",
2968
+ readyDetail: "managed block missing"
2969
+ };
2970
+ }
2971
+ if (await isPlatformMinimal(rootDir, "zcode")) {
2972
+ return {
2973
+ present: true,
2974
+ ready: true,
2975
+ target: "AGENTS.md",
2976
+ readyDetail: "managed block present"
2977
+ };
2978
+ }
2979
+ const skillsDir = path8.join(rootDir, ".agents", "skills");
2980
+ const hasSkills = await allManagedSkills(
2981
+ MODE_NAMES.map((mode) => path8.join(skillsDir, mode, "SKILL.md")),
2982
+ MANCODE_AGENT_SKILL_MARKERS
2983
+ );
2984
+ return {
2985
+ present: true,
2986
+ ready: hasSkills,
2987
+ target: "AGENTS.md + .agents/skills/",
2988
+ readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
2989
+ };
2990
+ }
2991
+ const hasBlock = await fileHasManagedBlock(
2992
+ path8.join(rootDir, ".github", "copilot-instructions.md")
2993
+ );
2994
+ if (!hasBlock) {
2995
+ return {
2996
+ present: false,
2997
+ ready: false,
2998
+ target: ".github/copilot-instructions.md",
2999
+ readyDetail: "managed block missing"
3000
+ };
3001
+ }
3002
+ if (!await isPlatformMinimal(rootDir, "copilot")) {
3003
+ const promptsDir = path8.join(rootDir, ".github", "prompts");
3004
+ const hasPrompts = await allManagedSkills(
3005
+ MODE_NAMES.map((mode) => path8.join(promptsDir, `${mode}.prompt.md`)),
3006
+ [MODE_FILE_MANAGED_MARKER]
3007
+ );
3008
+ return {
3009
+ present: true,
3010
+ ready: hasPrompts,
3011
+ target: ".github/copilot-instructions.md + .github/prompts/",
3012
+ readyDetail: hasPrompts ? "managed block and prompts present" : "mode prompts are missing, incomplete, or user-authored"
3013
+ };
3014
+ }
3015
+ return {
3016
+ present: true,
3017
+ ready: true,
3018
+ target: ".github/copilot-instructions.md",
3019
+ readyDetail: "managed block present"
3020
+ };
3021
+ }
3022
+ function describeStatus(installed, readiness) {
3023
+ if (installed && readiness.present) return readiness.readyDetail;
3024
+ if (!installed && readiness.present) {
3025
+ return "target files present but platform is not recorded in config";
3026
+ }
3027
+ return "missing generated files or registration";
3028
+ }
3029
+ async function allManagedSkills(paths, markers) {
3030
+ const results = await Promise.all(
3031
+ paths.map((filePath) => fileHasAnyMarker(filePath, markers))
3032
+ );
3033
+ return results.every(Boolean);
3034
+ }
3035
+ async function claudeFullContentReady(rootDir) {
3036
+ const skillChecks = MVP2_SKILLS.map(
3037
+ (skill) => fileMatches(
3038
+ path8.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
3039
+ (content) => isGeneratedClaudeSkill(content, skill.name)
3040
+ )
3041
+ );
3042
+ const agentChecks = ALL_AGENTS.map(
3043
+ (agent) => fileMatches(
3044
+ path8.join(rootDir, ".claude", "agents", `${agent.name}.md`),
3045
+ (content) => isGeneratedClaudeAgent(content, agent.name)
3046
+ )
3047
+ );
3048
+ const results = await Promise.all([...skillChecks, ...agentChecks]);
3049
+ return results.every(Boolean);
3050
+ }
3051
+ async function fileMatches(filePath, predicate) {
3052
+ try {
3053
+ return predicate(await fs.readFile(filePath, "utf-8"));
3054
+ } catch {
3055
+ return false;
3056
+ }
3057
+ }
3058
+ async function pathsExist(paths) {
3059
+ return (await Promise.all(paths.map(pathExists2))).every(Boolean);
3060
+ }
3061
+ async function fileHasAnyMarker(filePath, needles) {
3062
+ try {
3063
+ const content = await fs.readFile(filePath, "utf-8");
3064
+ return needles.some((needle) => content.includes(needle));
3065
+ } catch {
3066
+ return false;
3067
+ }
3068
+ }
3069
+ async function isPlatformMinimal(rootDir, platform) {
3070
+ try {
3071
+ const raw = await fs.readFile(
3072
+ path8.join(rootDir, ".mancode", "config.json"),
3073
+ "utf-8"
3074
+ );
3075
+ const config = JSON.parse(raw);
3076
+ if (!isRecord3(config.platformOptions)) return false;
3077
+ const options = config.platformOptions[platform];
3078
+ return isRecord3(options) && options.minimal === true;
3079
+ } catch {
3080
+ return false;
3081
+ }
3082
+ }
3083
+ async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
3084
+ try {
3085
+ const content = await fs.readFile(filePath, "utf-8");
3086
+ return hasManagedBlock(content, startMarker, endMarker);
3087
+ } catch {
3088
+ return false;
3089
+ }
3090
+ }
3091
+ async function claudeHooksRegistered(rootDir) {
3092
+ try {
3093
+ const raw = await fs.readFile(
3094
+ path8.join(rootDir, ".claude", "settings.json"),
3095
+ "utf-8"
3096
+ );
3097
+ const settings = JSON.parse(raw);
3098
+ return hooksRegistered(settings);
3099
+ } catch {
3100
+ return false;
3101
+ }
3102
+ }
3103
+ function hooksRegistered(settings) {
3104
+ if (!isRecord3(settings)) return false;
3105
+ const hooks = settings.hooks;
3106
+ if (!isRecord3(hooks)) return false;
3107
+ return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
3108
+ hooks.UserPromptSubmit,
3109
+ ".mancode/hooks/user-prompt-submit.mjs"
3110
+ );
3111
+ }
3112
+ function hasHookCommand(value, needle) {
3113
+ if (!Array.isArray(value)) return false;
3114
+ return value.some((group) => {
3115
+ if (!isRecord3(group) || !Array.isArray(group.hooks)) return false;
3116
+ return group.hooks.some((hook) => {
3117
+ if (!isRecord3(hook) || typeof hook.command !== "string") return false;
3118
+ return hook.command.includes(needle);
3119
+ });
3120
+ });
3121
+ }
3122
+ async function pathExists2(p) {
3123
+ try {
3124
+ await fs.access(p);
3125
+ return true;
3126
+ } catch {
3127
+ return false;
3128
+ }
3129
+ }
3130
+ function isRecord3(value) {
3131
+ return typeof value === "object" && value !== null;
3132
+ }
3133
+
3134
+ // src/installers/codex.ts
3135
+ import { writeFile as writeFile7 } from "fs/promises";
3136
+ import path9 from "path";
3137
+ async function installCodex(projectRoot, options) {
3138
+ await installMancodeCore(projectRoot);
3139
+ const agentsPath = path9.join(projectRoot, "AGENTS.md");
3140
+ const existing = await readTextIfExists(agentsPath);
3141
+ const sharedContent = await generateSharedContent(projectRoot, {
3142
+ platform: "codex",
3143
+ displayName: "Codex (ChatGPT desktop/CLI)",
3144
+ capabilities: {
3145
+ slashCommands: "partial",
3146
+ subagents: false,
3147
+ hooks: false,
3148
+ skills: "agents-skills"
3149
+ },
3150
+ minimal: options.minimal,
3151
+ techStack: options.techStack,
3152
+ uiLibrary: options.uiLibrary,
3153
+ projectProfile: options.projectProfile
3154
+ });
3155
+ const block = [
3156
+ DEFAULT_MANCODE_START_MARKER,
3157
+ "<!-- Managed by mancode. Do not edit this block manually. -->",
3158
+ "",
3159
+ "# mancode Configuration",
3160
+ "",
3161
+ sharedContent.trim(),
3162
+ DEFAULT_MANCODE_END_MARKER
3163
+ ].join("\n");
3164
+ await writeFile7(agentsPath, replaceManagedBlock(existing, block), "utf-8");
3165
+ await installCodexSkills(projectRoot, options.minimal ?? false);
3166
+ }
3167
+
3168
+ // src/installers/copilot.ts
3169
+ import { mkdir as mkdir6, writeFile as writeFile8 } from "fs/promises";
3170
+ import path10 from "path";
3171
+ async function installCopilot(projectRoot, options) {
3172
+ await installMancodeCore(projectRoot);
3173
+ const githubDir = path10.join(projectRoot, ".github");
3174
+ await mkdir6(githubDir, { recursive: true });
3175
+ const instructionsPath = path10.join(githubDir, "copilot-instructions.md");
3176
+ const existing = await readTextIfExists(instructionsPath);
3177
+ const sharedContent = await generateSharedContent(projectRoot, {
3178
+ platform: "copilot",
3179
+ displayName: "GitHub Copilot",
3180
+ capabilities: {
3181
+ slashCommands: "none",
3182
+ subagents: false,
3183
+ hooks: false,
3184
+ skills: "instructions"
3185
+ },
3186
+ minimal: true,
3187
+ techStack: options.techStack,
3188
+ uiLibrary: options.uiLibrary,
3189
+ projectProfile: options.projectProfile
3190
+ });
3191
+ const sections = [
3192
+ DEFAULT_MANCODE_START_MARKER,
3193
+ "<!-- Managed by mancode. Do not edit this block manually. -->",
3194
+ "",
3195
+ "# mancode for GitHub Copilot",
3196
+ "",
3197
+ sharedContent.trim()
3198
+ ];
3199
+ if (!options.minimal) {
3200
+ sections.push("", renderCopilotPromptConventions());
3201
+ }
3202
+ sections.push(DEFAULT_MANCODE_END_MARKER);
3203
+ await writeFile8(
3204
+ instructionsPath,
3205
+ replaceManagedBlock(existing, sections.join("\n")),
3206
+ "utf-8"
3207
+ );
3208
+ await installCopilotPrompts(projectRoot, options.minimal ?? false);
3209
+ }
3210
+ function renderCopilotPromptConventions() {
3211
+ return [
3212
+ "## mancode Prompt Conventions",
3213
+ "",
3214
+ "GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
3215
+ "",
3216
+ "- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
3217
+ "- manba: diagnose bugs and validate real user flows or regressions.",
3218
+ "- manteam: read team memory and write handoff-friendly summaries.",
3219
+ "- manps: prefer `mancode manps [area]` before cleanup.",
3220
+ "- mansolo: exit any active mode and return to default solo behavior.",
3221
+ "",
3222
+ "These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
3223
+ ].join("\n");
3224
+ }
3225
+
3226
+ // src/installers/registry.ts
3227
+ var PLATFORM_INSTALLERS = {
3228
+ "claude-code": {
3229
+ name: "claude-code",
3230
+ displayName: "Claude Code",
3231
+ capabilities: {
3232
+ slashCommands: "native",
3233
+ subagents: true,
3234
+ hooks: true,
3235
+ skills: "native"
3236
+ },
3237
+ install: installClaudeCode
3238
+ },
3239
+ cursor: {
3240
+ name: "cursor",
3241
+ displayName: "Cursor",
3242
+ capabilities: {
3243
+ slashCommands: "native",
3244
+ subagents: false,
3245
+ hooks: false,
3246
+ skills: "rules"
3247
+ },
3248
+ install: installCursor
3249
+ },
3250
+ codex: {
3251
+ name: "codex",
3252
+ displayName: "Codex (ChatGPT desktop/CLI)",
3253
+ capabilities: {
3254
+ slashCommands: "partial",
3255
+ subagents: false,
3256
+ hooks: false,
3257
+ skills: "agents-skills"
3258
+ },
3259
+ install: installCodex
3260
+ },
3261
+ copilot: {
3262
+ name: "copilot",
3263
+ displayName: "GitHub Copilot",
3264
+ capabilities: {
3265
+ slashCommands: "none",
3266
+ subagents: false,
3267
+ hooks: false,
3268
+ skills: "instructions"
3269
+ },
3270
+ install: installCopilot
3271
+ },
3272
+ zcode: {
3273
+ name: "zcode",
3274
+ displayName: "ZCode",
3275
+ capabilities: {
3276
+ slashCommands: "partial",
3277
+ subagents: false,
3278
+ hooks: false,
3279
+ skills: "agents-skills"
3280
+ },
3281
+ install: installZcode
3282
+ }
3283
+ };
3284
+ function getPlatformInstaller(platform) {
3285
+ if (!isPlatformName(platform)) return null;
3286
+ return PLATFORM_INSTALLERS[platform];
3287
+ }
3288
+ function getPlatformInstallers() {
3289
+ return Object.values(PLATFORM_INSTALLERS);
3290
+ }
3291
+ function formatPlatformName(platform) {
3292
+ return getPlatformInstaller(platform)?.displayName ?? platform;
3293
+ }
3294
+ function isPlatformName(platform) {
3295
+ return Object.prototype.hasOwnProperty.call(PLATFORM_INSTALLERS, platform);
3296
+ }
3297
+
3298
+ // src/system/detect-team.ts
3299
+ import { execFile } from "child_process";
3300
+ import { access as access2 } from "fs/promises";
3301
+ import path11 from "path";
3302
+ import process2 from "process";
3303
+ import { promisify } from "util";
3304
+ var execFileAsync = promisify(execFile);
3305
+ var NO_TEAM = {
3306
+ isTeam: false,
3307
+ contributors: 1,
3308
+ recentActive: 1,
3309
+ hasRemote: false
3310
+ };
3311
+ var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
3312
+ var GIT_ENV = {
3313
+ ...process2.env,
3314
+ GIT_PAGER: "cat",
3315
+ PAGER: "cat",
3316
+ GIT_TERMINAL_PROMPT: "0"
3317
+ };
3318
+ async function detectTeamStatus(projectRoot) {
3319
+ try {
3320
+ await access2(path11.join(projectRoot, ".git"));
3321
+ const inside = await runGit(projectRoot, [
3322
+ "rev-parse",
3323
+ "--is-inside-work-tree"
3324
+ ]);
3325
+ if (inside.trim() !== "true") return NO_TEAM;
3326
+ const [allEmails, recentEmails, remotes] = await Promise.all([
3327
+ runGit(projectRoot, ["log", "--all", "--format=%ae"]),
3328
+ runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
3329
+ runGit(projectRoot, ["remote", "-v"])
3330
+ ]);
3331
+ const contributors = countUniqueLines(allEmails);
3332
+ const recentActive = countUniqueLines(recentEmails);
3333
+ const hasRemote = REMOTE_PATTERN.test(remotes);
3334
+ return {
3335
+ isTeam: contributors > 1 && hasRemote && recentActive > 1,
3336
+ contributors: contributors || 1,
3337
+ recentActive: recentActive || 1,
3338
+ hasRemote
3339
+ };
2977
3340
  } catch {
2978
3341
  return NO_TEAM;
2979
3342
  }
@@ -3012,8 +3375,8 @@ async function detectSystemDeps(env = process3.env) {
3012
3375
 
3013
3376
  // src/system/init-onboarding.ts
3014
3377
  import { execFileSync } from "child_process";
3015
- import { promises as fs } from "fs";
3016
- import path11 from "path";
3378
+ import { promises as fs2 } from "fs";
3379
+ import path12 from "path";
3017
3380
  import process4 from "process";
3018
3381
  import { stdin, stdout } from "process";
3019
3382
  import { createInterface } from "readline/promises";
@@ -3118,7 +3481,7 @@ async function detectPlatformHints(rootDir, environment = process4.env) {
3118
3481
  hints.add("copilot");
3119
3482
  const exists = async (relative) => {
3120
3483
  try {
3121
- await fs.access(path11.join(rootDir, relative));
3484
+ await fs2.access(path12.join(rootDir, relative));
3122
3485
  return true;
3123
3486
  } catch {
3124
3487
  return false;
@@ -3194,7 +3557,7 @@ function createTerminalPrompter() {
3194
3557
 
3195
3558
  // src/system/project-profile.ts
3196
3559
  import { readFile as readFile7, readdir as readdir3, stat as stat2 } from "fs/promises";
3197
- import path12 from "path";
3560
+ import path13 from "path";
3198
3561
  var PROJECT_MANIFESTS = [
3199
3562
  "package.json",
3200
3563
  "pyproject.toml",
@@ -3227,12 +3590,12 @@ async function detectProjectProfile(projectRoot) {
3227
3590
  "data",
3228
3591
  "notebooks"
3229
3592
  ]);
3230
- const packageJson = await readJson2(path12.join(projectRoot, "package.json"));
3593
+ const packageJson = await readJson2(path13.join(projectRoot, "package.json"));
3231
3594
  const [pubspec, pythonManifests] = await Promise.all([
3232
- readText(path12.join(projectRoot, "pubspec.yaml")),
3595
+ readText(path13.join(projectRoot, "pubspec.yaml")),
3233
3596
  Promise.all([
3234
- readText(path12.join(projectRoot, "pyproject.toml")),
3235
- readText(path12.join(projectRoot, "requirements.txt"))
3597
+ readText(path13.join(projectRoot, "pyproject.toml")),
3598
+ readText(path13.join(projectRoot, "requirements.txt"))
3236
3599
  ]).then((parts) => parts.filter(Boolean).join("\n"))
3237
3600
  ]);
3238
3601
  const deps = Object.keys({
@@ -3241,7 +3604,7 @@ async function detectProjectProfile(projectRoot) {
3241
3604
  ...packageJson?.peerDependencies
3242
3605
  });
3243
3606
  const flutter = isFlutterProject(pubspec);
3244
- const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path12.join(projectRoot, "src", "components", "ui"));
3607
+ const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path13.join(projectRoot, "src", "components", "ui"));
3245
3608
  const languages = inferLanguages(manifests, entries);
3246
3609
  const frameworks = inferFrameworks(
3247
3610
  deps,
@@ -3284,7 +3647,7 @@ async function existingDirs(root, candidates) {
3284
3647
  const found = [];
3285
3648
  for (const candidate of candidates) {
3286
3649
  try {
3287
- if ((await stat2(path12.join(root, candidate))).isDirectory())
3650
+ if ((await stat2(path13.join(root, candidate))).isDirectory())
3288
3651
  found.push(candidate);
3289
3652
  } catch {
3290
3653
  }
@@ -3458,8 +3821,8 @@ function isFlutterProject(pubspec) {
3458
3821
  }
3459
3822
 
3460
3823
  // src/system/scan-aesthetics.ts
3461
- import { promises as fs2 } from "fs";
3462
- import path13 from "path";
3824
+ import { promises as fs3 } from "fs";
3825
+ import path14 from "path";
3463
3826
  var MAX_COMPONENT_SCAN_DEPTH = 12;
3464
3827
  var MAX_COMPONENT_FILES = 2e3;
3465
3828
  async function scanAesthetics(projectRoot, uiLibrary = null) {
@@ -3472,7 +3835,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
3472
3835
  const cssScan = await scanCssVariables(projectRoot);
3473
3836
  if (configResult) {
3474
3837
  sourceFiles.push(configResult.relPath);
3475
- const content = await fs2.readFile(configResult.absPath, "utf-8");
3838
+ const content = await fs3.readFile(configResult.absPath, "utf-8");
3476
3839
  const inspectableContent = stripJsComments(content);
3477
3840
  const themeBlock = findKeyBlock(inspectableContent, "theme");
3478
3841
  if (themeBlock) {
@@ -3495,7 +3858,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
3495
3858
  } else if (await hasTailwindDep(projectRoot) || uiLibrary) {
3496
3859
  matchLevel = "low";
3497
3860
  }
3498
- if (uiLibrary && await pathExists2(path13.join(projectRoot, "package.json"))) {
3861
+ if (uiLibrary && await pathExists3(path14.join(projectRoot, "package.json"))) {
3499
3862
  sourceFiles.push("package.json");
3500
3863
  }
3501
3864
  sourceFiles.push(...cssScan.sourceFiles);
@@ -3520,8 +3883,8 @@ async function findTailwindConfig(projectRoot) {
3520
3883
  "tailwind.config.mjs"
3521
3884
  ];
3522
3885
  for (const name of candidates) {
3523
- const absPath = path13.join(projectRoot, name);
3524
- if (await pathExists2(absPath)) {
3886
+ const absPath = path14.join(projectRoot, name);
3887
+ if (await pathExists3(absPath)) {
3525
3888
  return { absPath, relPath: name };
3526
3889
  }
3527
3890
  }
@@ -3747,8 +4110,8 @@ async function scanComponents(projectRoot) {
3747
4110
  const names = /* @__PURE__ */ new Set();
3748
4111
  let visitedFiles = 0;
3749
4112
  for (const relRoot of roots) {
3750
- const absRoot = path13.join(projectRoot, relRoot);
3751
- if (!await pathExists2(absRoot)) continue;
4113
+ const absRoot = path14.join(projectRoot, relRoot);
4114
+ if (!await pathExists3(absRoot)) continue;
3752
4115
  visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
3753
4116
  if (visitedFiles >= MAX_COMPONENT_FILES) break;
3754
4117
  }
@@ -3761,16 +4124,16 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
3761
4124
  let fileCount = visitedFiles;
3762
4125
  let entries;
3763
4126
  try {
3764
- entries = await fs2.readdir(dir);
4127
+ entries = await fs3.readdir(dir);
3765
4128
  } catch {
3766
4129
  return fileCount;
3767
4130
  }
3768
4131
  for (const entry of entries) {
3769
4132
  if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
3770
- const abs = path13.join(dir, entry);
4133
+ const abs = path14.join(dir, entry);
3771
4134
  let info;
3772
4135
  try {
3773
- info = await fs2.lstat(abs);
4136
+ info = await fs3.lstat(abs);
3774
4137
  } catch {
3775
4138
  continue;
3776
4139
  }
@@ -3788,7 +4151,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
3788
4151
  ""
3789
4152
  );
3790
4153
  if (base === "index") {
3791
- base = path13.basename(dir);
4154
+ base = path14.basename(dir);
3792
4155
  }
3793
4156
  if (base.startsWith(".")) continue;
3794
4157
  const componentName = toPascalCase(base);
@@ -3810,9 +4173,9 @@ async function scanCssVariables(projectRoot) {
3810
4173
  const variables = {};
3811
4174
  const sourceFiles = [];
3812
4175
  for (const relPath of candidates) {
3813
- const absPath = path13.join(projectRoot, relPath);
3814
- if (!await pathExists2(absPath)) continue;
3815
- const content = await fs2.readFile(absPath, "utf-8");
4176
+ const absPath = path14.join(projectRoot, relPath);
4177
+ if (!await pathExists3(absPath)) continue;
4178
+ const content = await fs3.readFile(absPath, "utf-8");
3816
4179
  const found = extractCssVariables(content);
3817
4180
  if (Object.keys(found).length === 0) continue;
3818
4181
  Object.assign(variables, found);
@@ -3855,8 +4218,8 @@ function toPascalCase(value) {
3855
4218
  }
3856
4219
  async function hasTailwindDep(projectRoot) {
3857
4220
  try {
3858
- const raw = await fs2.readFile(
3859
- path13.join(projectRoot, "package.json"),
4221
+ const raw = await fs3.readFile(
4222
+ path14.join(projectRoot, "package.json"),
3860
4223
  "utf-8"
3861
4224
  );
3862
4225
  const pkg = JSON.parse(raw);
@@ -3870,9 +4233,9 @@ async function hasTailwindDep(projectRoot) {
3870
4233
  function escapeRegex(s) {
3871
4234
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3872
4235
  }
3873
- async function pathExists2(p) {
4236
+ async function pathExists3(p) {
3874
4237
  try {
3875
- await fs2.access(p);
4238
+ await fs3.access(p);
3876
4239
  return true;
3877
4240
  } catch {
3878
4241
  return false;
@@ -3887,9 +4250,9 @@ var EXIT_USER_CANCEL = 3;
3887
4250
  var EXIT_INIT_FAILED = 5;
3888
4251
  var DEFAULT_INIT_PLATFORM = "claude-code";
3889
4252
  async function init(rootDir = process5.cwd(), options = {}) {
3890
- const mancodeDir = path14.join(rootDir, ".mancode");
3891
- const stateFile = path14.join(mancodeDir, "state.json");
3892
- const wasInitialized = await pathExists3(stateFile);
4253
+ const mancodeDir = path15.join(rootDir, ".mancode");
4254
+ const stateFile = path15.join(mancodeDir, "state.json");
4255
+ const wasInitialized = await pathExists4(stateFile);
3893
4256
  let mutationSnapshots = [];
3894
4257
  let directorySnapshots = [];
3895
4258
  const locale = detectInitLocale(options.lang);
@@ -3925,7 +4288,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
3925
4288
  )
3926
4289
  );
3927
4290
  }
3928
- if (!await pathExists3(rootDir)) {
4291
+ if (!await pathExists4(rootDir)) {
3929
4292
  console.error(
3930
4293
  localize(
3931
4294
  locale,
@@ -3935,26 +4298,28 @@ async function init(rootDir = process5.cwd(), options = {}) {
3935
4298
  );
3936
4299
  return EXIT_NOT_A_PROJECT_DIR;
3937
4300
  }
3938
- const isGitRepo = await pathExists3(path14.join(rootDir, ".git"));
4301
+ const isGitRepo = await pathExists4(path15.join(rootDir, ".git"));
3939
4302
  const hasManifest = await hasProjectManifest(rootDir);
3940
4303
  let isGenericProject = false;
3941
4304
  if (!isGitRepo && !hasManifest) {
3942
4305
  const genericSafety = await canInitializeGenericProject(rootDir);
3943
- if (!genericSafety.ok) {
4306
+ if (!genericSafety.ok && !(wasInitialized && genericSafety.reason === "nonempty")) {
3944
4307
  printNotProjectDirectory(rootDir, locale, genericSafety.reason);
3945
4308
  return EXIT_NOT_A_PROJECT_DIR;
3946
4309
  }
3947
- const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
3948
- const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
3949
- if (!confirmed) {
3950
- if (options.interactive) {
3951
- console.log(
3952
- locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
3953
- );
3954
- return EXIT_USER_CANCEL;
4310
+ if (!wasInitialized) {
4311
+ const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
4312
+ const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
4313
+ if (!confirmed) {
4314
+ if (options.interactive) {
4315
+ console.log(
4316
+ locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
4317
+ );
4318
+ return EXIT_USER_CANCEL;
4319
+ }
4320
+ printNotProjectDirectory(rootDir, locale, "empty");
4321
+ return EXIT_NOT_A_PROJECT_DIR;
3955
4322
  }
3956
- printNotProjectDirectory(rootDir, locale, "empty");
3957
- return EXIT_NOT_A_PROJECT_DIR;
3958
4323
  }
3959
4324
  isGenericProject = true;
3960
4325
  }
@@ -4009,9 +4374,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
4009
4374
  );
4010
4375
  }
4011
4376
  const existingPlatform = wasInitialized ? await readExistingInitPlatform(stateFile) : null;
4377
+ const existingConfig = wasInitialized ? await readExistingInitPreferences(mancodeDir) : { platforms: [], minimalByPlatform: {} };
4378
+ const recoverablePlatforms = wasInitialized && existingConfig.platforms.length === 0 ? await detectManagedInitPlatforms(rootDir) : [];
4379
+ const forceReinstallPlatforms = options.force && options.platform === void 0 ? existingConfig.platforms.length > 0 ? existingConfig.platforms : recoverablePlatforms : [];
4012
4380
  const platformHints = await detectPlatformHints(rootDir);
4013
4381
  const selectedPlatforms = await selectInitPlatforms({
4014
- option: options.platform,
4382
+ option: options.platform ?? (forceReinstallPlatforms.length > 0 ? forceReinstallPlatforms.join(",") : void 0),
4015
4383
  existingPlatform,
4016
4384
  hints: platformHints,
4017
4385
  interactive: options.interactive,
@@ -4048,13 +4416,17 @@ async function init(rootDir = process5.cwd(), options = {}) {
4048
4416
  const managedFiles = getInitManagedFilePaths(rootDir, selectedPlatforms);
4049
4417
  mutationSnapshots = await snapshotFiles(managedFiles);
4050
4418
  directorySnapshots = await snapshotDirectories(managedFiles, [
4051
- path14.join(mancodeDir, "workflows"),
4052
- path14.join(mancodeDir, "preseason-reports")
4419
+ path15.join(mancodeDir, "workflows"),
4420
+ path15.join(mancodeDir, "preseason-reports")
4053
4421
  ]);
4054
4422
  const team = await detectTeamStatus(rootDir);
4055
- const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, primaryPlatform) : {};
4056
- const initialMinimal = existingPreferences.minimal ?? false;
4057
- const existingTeamMode = existingPreferences.forceTeamMode === true ? "on" : existingPreferences.teamMode ?? "auto";
4423
+ const platformMinimal = Object.fromEntries(
4424
+ selectedPlatforms.map((platform) => [
4425
+ platform,
4426
+ existingConfig.minimalByPlatform[platform] ?? false
4427
+ ])
4428
+ );
4429
+ const existingTeamMode = existingConfig.forceTeamMode === true ? "on" : existingConfig.teamMode ?? "auto";
4058
4430
  const teamModeEnabled = options.team ?? (existingTeamMode === "on" ? true : existingTeamMode === "off" ? false : team.isTeam);
4059
4431
  if (options.team === true) {
4060
4432
  console.log(
@@ -4092,6 +4464,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
4092
4464
  currentTask: null,
4093
4465
  currentWorkflowMode: null,
4094
4466
  skippedSteps: [],
4467
+ activeSoloPlan: null,
4095
4468
  teamModeAutoDetected: teamModeEnabled,
4096
4469
  contributors: team.contributors,
4097
4470
  projectMode: isGenericProject ? "generic" : "detected"
@@ -4100,12 +4473,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
4100
4473
  await validateClaudeCodeSettings(rootDir);
4101
4474
  }
4102
4475
  await installMancodeCore(rootDir);
4103
- await fs3.mkdir(mancodeDir, { recursive: true });
4476
+ await fs4.mkdir(mancodeDir, { recursive: true });
4104
4477
  const stateContent = `${JSON.stringify(state, null, 2)}
4105
4478
  `;
4106
- await fs3.writeFile(stateFile, stateContent, "utf-8");
4107
- await fs3.writeFile(
4108
- path14.join(mancodeDir, "project-profile.json"),
4479
+ await fs4.writeFile(stateFile, stateContent, "utf-8");
4480
+ await fs4.writeFile(
4481
+ path15.join(mancodeDir, "project-profile.json"),
4109
4482
  `${JSON.stringify(profile, null, 2)}
4110
4483
  `,
4111
4484
  "utf-8"
@@ -4121,7 +4494,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
4121
4494
  ...Object.fromEntries(
4122
4495
  selectedPlatforms.map((platform) => [
4123
4496
  platform,
4124
- { minimal: initialMinimal }
4497
+ { minimal: platformMinimal[platform] ?? false }
4125
4498
  ])
4126
4499
  )
4127
4500
  }
@@ -4142,12 +4515,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
4142
4515
  )
4143
4516
  );
4144
4517
  const tokens = await scanAesthetics(rootDir, uiLibrary);
4145
- const tokensPath = path14.join(
4518
+ const tokensPath = path15.join(
4146
4519
  mancodeDir,
4147
4520
  "aesthetics",
4148
4521
  "style-tokens.json"
4149
4522
  );
4150
- await fs3.writeFile(
4523
+ await fs4.writeFile(
4151
4524
  tokensPath,
4152
4525
  `${JSON.stringify(tokens, null, 2)}
4153
4526
  `,
@@ -4208,7 +4581,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
4208
4581
  techStack: profileStack,
4209
4582
  uiLibrary,
4210
4583
  projectProfile: profile,
4211
- minimal: initialMinimal,
4584
+ minimal: platformMinimal[installer.name] ?? false,
4212
4585
  force: options.force
4213
4586
  });
4214
4587
  }
@@ -4293,16 +4666,16 @@ async function init(rootDir = process5.cwd(), options = {}) {
4293
4666
  }
4294
4667
  }
4295
4668
  async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms) {
4296
- const configPath = path14.join(mancodeDir, "config.json");
4669
+ const configPath = path15.join(mancodeDir, "config.json");
4297
4670
  let config = {};
4298
4671
  try {
4299
- config = JSON.parse(await fs3.readFile(configPath, "utf-8"));
4672
+ config = JSON.parse(await fs4.readFile(configPath, "utf-8"));
4300
4673
  } catch {
4301
4674
  }
4302
4675
  const existingPlatforms = Array.isArray(config.platforms) ? config.platforms.filter(
4303
4676
  (platform) => typeof platform === "string"
4304
4677
  ) : [];
4305
- const existingPlatformOptions = isRecord3(config.platformOptions) ? config.platformOptions : {};
4678
+ const existingPlatformOptions = isRecord4(config.platformOptions) ? config.platformOptions : {};
4306
4679
  const definedPatch = Object.fromEntries(
4307
4680
  Object.entries(patch).filter(([, value]) => value !== void 0)
4308
4681
  );
@@ -4316,570 +4689,306 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
4316
4689
  ...patch.platformOptions
4317
4690
  }
4318
4691
  } : definedPatch;
4319
- await fs3.writeFile(
4692
+ await fs4.writeFile(
4320
4693
  configPath,
4321
4694
  `${JSON.stringify({ ...config, ...mergedPatch }, null, 2)}
4322
4695
  `,
4323
4696
  "utf-8"
4324
4697
  );
4325
4698
  }
4326
- async function readExistingInitPreferences(mancodeDir, platform) {
4699
+ async function readExistingInitPreferences(mancodeDir) {
4327
4700
  try {
4328
4701
  const config = JSON.parse(
4329
- await fs3.readFile(path14.join(mancodeDir, "config.json"), "utf-8")
4702
+ await fs4.readFile(path15.join(mancodeDir, "config.json"), "utf-8")
4330
4703
  );
4331
- const preferences = {};
4704
+ const preferences = {
4705
+ platforms: Array.isArray(config.platforms) ? config.platforms.filter(
4706
+ (platform) => typeof platform === "string" && getPlatformInstaller(platform) !== null
4707
+ ) : [],
4708
+ minimalByPlatform: {}
4709
+ };
4332
4710
  if (typeof config.forceTeamMode === "boolean") {
4333
4711
  preferences.forceTeamMode = config.forceTeamMode;
4334
4712
  }
4335
4713
  if (config.teamMode === "auto" || config.teamMode === "on" || config.teamMode === "off") {
4336
4714
  preferences.teamMode = config.teamMode;
4337
4715
  }
4338
- if (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
4339
- const platformOptions = config.platformOptions[platform];
4340
- if (isRecord3(platformOptions) && platformOptions.minimal === true) {
4341
- preferences.minimal = true;
4716
+ if (isRecord4(config.platformOptions)) {
4717
+ for (const platform of preferences.platforms) {
4718
+ const platformOptions = config.platformOptions[platform];
4719
+ if (isRecord4(platformOptions) && platformOptions.minimal === true) {
4720
+ preferences.minimalByPlatform[platform] = true;
4721
+ }
4342
4722
  }
4343
4723
  }
4344
4724
  return preferences;
4345
4725
  } catch {
4346
- return {};
4726
+ return { platforms: [], minimalByPlatform: {} };
4347
4727
  }
4348
4728
  }
4349
4729
  async function readExistingInitPlatform(stateFile) {
4350
4730
  try {
4351
- const state = JSON.parse(await fs3.readFile(stateFile, "utf-8"));
4731
+ const state = JSON.parse(await fs4.readFile(stateFile, "utf-8"));
4352
4732
  return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
4353
4733
  } catch {
4354
4734
  return null;
4355
4735
  }
4356
- }
4357
- async function selectInitPlatforms(input) {
4358
- if (input.option !== void 0) {
4359
- return parsePlatformSelection(input.option);
4360
- }
4361
- if (input.existingPlatform) return [input.existingPlatform];
4362
- if (input.yes && input.interactive !== void 0) {
4363
- return input.hints.length === 1 ? input.hints : null;
4364
- }
4365
- if (input.interactive) {
4366
- const prompter = input.prompter ?? createTerminalPrompter();
4367
- return prompter.selectPlatforms({
4368
- locale: input.locale,
4369
- detected: input.hints
4370
- });
4371
- }
4372
- if (input.interactive === false) {
4373
- return input.hints.length === 1 ? input.hints : null;
4374
- }
4375
- return [DEFAULT_INIT_PLATFORM];
4376
- }
4377
- async function hasProjectManifest(rootDir) {
4378
- for (const name of PROJECT_MANIFESTS) {
4379
- if (await pathExists3(path14.join(rootDir, name))) return true;
4380
- }
4381
- return false;
4382
- }
4383
- async function canInitializeGenericProject(rootDir) {
4384
- const resolved = path14.resolve(rootDir);
4385
- if (resolved === path14.parse(resolved).root || resolved === path14.resolve(os.homedir())) {
4386
- return { ok: false, reason: "unsafe" };
4387
- }
4388
- try {
4389
- const entries = await fs3.readdir(resolved);
4390
- const meaningfulEntries = entries.filter(
4391
- (entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
4392
- );
4393
- return meaningfulEntries.length === 0 ? { ok: true } : { ok: false, reason: "nonempty" };
4394
- } catch {
4395
- return { ok: false, reason: "unsafe" };
4396
- }
4397
- }
4398
- function printNotProjectDirectory(rootDir, locale, reason) {
4399
- if (locale === "zh-CN") {
4400
- console.error(`\u2717 \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u53EF\u521D\u59CB\u5316\u7684\u9879\u76EE\u76EE\u5F55\uFF1A${rootDir}`);
4401
- if (reason === "unsafe") {
4402
- console.error(" \u4E3A\u907F\u514D\u8BEF\u5199\uFF0C\u4E0D\u80FD\u5728 Home \u76EE\u5F55\u6216\u78C1\u76D8\u6839\u76EE\u5F55\u521D\u59CB\u5316\u3002");
4403
- } else if (reason === "nonempty") {
4404
- console.error(
4405
- " \u672A\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\uFF0C\u4E14\u76EE\u5F55\u4E2D\u5DF2\u6709\u6587\u4EF6\u3002\u8BF7\u5148\u8FDB\u5165\u9879\u76EE\u76EE\u5F55\u3002"
4406
- );
4407
- } else {
4408
- console.error(
4409
- " \u672A\u53D1\u73B0 .git \u6216\u9879\u76EE\u6587\u4EF6\u3002\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u53EF\u786E\u8BA4\u901A\u7528\u9879\u76EE\uFF0C\u6216\u4F7F\u7528 --empty\u3002"
4410
- );
4411
- }
4412
- return;
4413
- }
4414
- console.error(`\u2717 Not a project directory: ${rootDir}`);
4415
- if (reason === "unsafe") {
4416
- console.error(
4417
- " Refusing to initialize a home directory or filesystem root."
4418
- );
4419
- } else if (reason === "nonempty") {
4420
- console.error(
4421
- " No project files were detected and the directory is not empty."
4422
- );
4423
- } else {
4424
- console.error(
4425
- " No .git or recognized project manifest found. Use an interactive terminal or --empty for a new project."
4426
- );
4427
- }
4428
- }
4429
- function getInitManagedFilePaths(rootDir, platforms) {
4430
- const files = [
4431
- ".mancode/state.json",
4432
- ".mancode/config.json",
4433
- ".mancode/project-profile.json",
4434
- ".mancode/aesthetics/style-tokens.json",
4435
- ".mancode/hooks/session-start.mjs",
4436
- ".mancode/hooks/user-prompt-submit.mjs",
4437
- ".mancode/logs/hooks.log",
4438
- ".mancode/memory/prd.md",
4439
- ".mancode/memory/spec.md",
4440
- ".mancode/memory/decisions.md"
4441
- ];
4442
- if (platforms.includes("claude-code")) {
4443
- files.push(
4444
- ".mancode/hooks/session-start.sh",
4445
- ".mancode/hooks/user-prompt-submit.sh",
4446
- ".claude/settings.json",
4447
- ".claude/skills/man8/SKILL.md",
4448
- ".claude/skills/mamba/SKILL.md",
4449
- ".claude/skills/solo/SKILL.md",
4450
- ".claude/skills/mancode-mamba.md",
4451
- ".claude/skills/mancode-solo.md",
4452
- ".claude/skills/mancode-man8.md"
4453
- );
4454
- for (const mode of MODE_NAMES) {
4455
- files.push(
4456
- `.claude/skills/${mode}/SKILL.md`,
4457
- `.claude/skills/mancode-${mode}.md`
4458
- );
4459
- }
4460
- for (const agent of ALL_AGENTS) {
4461
- files.push(`.claude/agents/${agent.name}.md`);
4462
- }
4463
- }
4464
- if (platforms.includes("cursor")) {
4465
- files.push(
4466
- ".cursor/rules/mancode-mamba.mdc",
4467
- ".cursor/rules/mancode-man8.mdc",
4468
- ".cursor/commands/mamba.md",
4469
- ".cursor/commands/man8.md"
4470
- );
4471
- for (const fileName of MANCODE_CURSOR_RULE_FILES) {
4472
- files.push(`.cursor/rules/${fileName}`);
4473
- }
4474
- for (const mode of MODE_NAMES) {
4475
- files.push(`.cursor/commands/${mode}.md`);
4476
- }
4477
- }
4478
- if (platforms.includes("codex") || platforms.includes("zcode")) {
4479
- files.push(
4480
- "AGENTS.md",
4481
- ".agents/skills/mamba/SKILL.md",
4482
- ".agents/skills/man8/SKILL.md"
4483
- );
4484
- for (const mode of MODE_NAMES) {
4485
- files.push(`.agents/skills/${mode}/SKILL.md`);
4486
- }
4487
- }
4488
- if (platforms.includes("codex")) {
4489
- files.push(".codex/skills/mamba/SKILL.md", ".codex/skills/man8/SKILL.md");
4490
- for (const mode of MODE_NAMES) {
4491
- files.push(`.codex/skills/${mode}/SKILL.md`);
4492
- }
4493
- }
4494
- if (platforms.includes("zcode")) {
4495
- files.push(".zcode/skills/mamba/SKILL.md", ".zcode/skills/man8/SKILL.md");
4496
- for (const mode of MODE_NAMES) {
4497
- files.push(`.zcode/skills/${mode}/SKILL.md`);
4498
- }
4499
- }
4500
- if (platforms.includes("copilot")) {
4501
- files.push(
4502
- ".github/copilot-instructions.md",
4503
- ".github/prompts/mamba.prompt.md",
4504
- ".github/prompts/man8.prompt.md"
4505
- );
4506
- for (const mode of MODE_NAMES) {
4507
- files.push(`.github/prompts/${mode}.prompt.md`);
4508
- }
4509
- }
4510
- return [...new Set(files.map((file) => path14.join(rootDir, file)))];
4511
- }
4512
- async function snapshotFiles(filePaths) {
4513
- return Promise.all(
4514
- filePaths.map(async (filePath) => {
4515
- try {
4516
- return { filePath, content: await fs3.readFile(filePath, "utf-8") };
4517
- } catch (error) {
4518
- if (isNodeError5(error) && error.code === "ENOENT") {
4519
- return { filePath, content: null };
4520
- }
4521
- throw error;
4522
- }
4523
- })
4524
- );
4525
- }
4526
- async function snapshotDirectories(filePaths, additionalDirectories = []) {
4527
- const directories = new Set(additionalDirectories);
4528
- for (const filePath of filePaths) {
4529
- let current = path14.dirname(filePath);
4530
- while (current !== path14.dirname(current)) {
4531
- directories.add(current);
4532
- current = path14.dirname(current);
4533
- }
4534
- }
4535
- return Promise.all(
4536
- [...directories].map(async (dirPath) => ({
4537
- dirPath,
4538
- existed: await pathExists3(dirPath)
4539
- }))
4540
- );
4541
- }
4542
- async function restoreFiles(snapshots) {
4543
- for (const snapshot of snapshots) {
4544
- if (snapshot.content === null) {
4545
- await fs3.rm(snapshot.filePath, { force: true });
4546
- continue;
4547
- }
4548
- await fs3.mkdir(path14.dirname(snapshot.filePath), { recursive: true });
4549
- await fs3.writeFile(snapshot.filePath, snapshot.content, "utf-8");
4550
- }
4551
- }
4552
- async function removeNewEmptyDirectories(snapshots) {
4553
- const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
4554
- for (const { dirPath } of candidates) {
4555
- try {
4556
- await fs3.rmdir(dirPath);
4557
- } catch {
4558
- }
4559
- }
4560
- }
4561
- function isRecord3(value) {
4562
- return typeof value === "object" && value !== null && !Array.isArray(value);
4563
- }
4564
- function isNodeError5(error) {
4565
- return error instanceof Error && "code" in error;
4566
- }
4567
- function printPlatformCreatedFiles(platform, locale) {
4568
- if (platform === "claude-code") {
4569
- console.log(
4570
- localize(
4571
- locale,
4572
- " .claude/settings.json # hook \u6CE8\u518C",
4573
- " .claude/settings.json # hook registration"
4574
- )
4575
- );
4576
- console.log(
4577
- localize(
4578
- locale,
4579
- " .claude/skills/ # solo + MVP-2 slash skills",
4580
- " .claude/skills/ # solo + MVP-2 slash skills"
4581
- )
4582
- );
4583
- return;
4736
+ }
4737
+ async function detectManagedInitPlatforms(rootDir) {
4738
+ const detected = await Promise.all(
4739
+ getPlatformInstallers().map(async (platform) => ({
4740
+ name: platform.name,
4741
+ present: await isPlatformPresent(rootDir, platform.name)
4742
+ }))
4743
+ );
4744
+ return detected.filter((platform) => platform.present).map(({ name }) => name);
4745
+ }
4746
+ async function selectInitPlatforms(input) {
4747
+ if (input.option !== void 0) {
4748
+ return parsePlatformSelection(input.option);
4584
4749
  }
4585
- if (platform === "cursor") {
4586
- console.log(" .cursor/rules/ # Cursor project rules");
4587
- return;
4750
+ if (input.existingPlatform) return [input.existingPlatform];
4751
+ if (input.yes && input.interactive !== void 0) {
4752
+ return input.hints.length === 1 ? input.hints : null;
4588
4753
  }
4589
- if (platform === "codex") {
4590
- console.log(" AGENTS.md # Codex managed block");
4591
- console.log(" .agents/skills/ # Codex mode skills");
4592
- return;
4754
+ if (input.interactive) {
4755
+ const prompter = input.prompter ?? createTerminalPrompter();
4756
+ return prompter.selectPlatforms({
4757
+ locale: input.locale,
4758
+ detected: input.hints
4759
+ });
4593
4760
  }
4594
- if (platform === "zcode") {
4595
- console.log(" AGENTS.md # ZCode managed block");
4596
- console.log(" .agents/skills/ # ZCode mode skills");
4597
- return;
4761
+ if (input.interactive === false) {
4762
+ return input.hints.length === 1 ? input.hints : null;
4598
4763
  }
4599
- console.log(" .github/copilot-instructions.md # Copilot instructions");
4764
+ return [DEFAULT_INIT_PLATFORM];
4600
4765
  }
4601
- function localize(locale, chinese, english) {
4602
- return locale === "zh-CN" ? chinese : english;
4766
+ async function hasProjectManifest(rootDir) {
4767
+ for (const name of PROJECT_MANIFESTS) {
4768
+ if (await pathExists4(path15.join(rootDir, name))) return true;
4769
+ }
4770
+ return false;
4603
4771
  }
4604
- async function pathExists3(p) {
4772
+ async function canInitializeGenericProject(rootDir) {
4773
+ const resolved = path15.resolve(rootDir);
4774
+ if (resolved === path15.parse(resolved).root || resolved === path15.resolve(os.homedir())) {
4775
+ return { ok: false, reason: "unsafe" };
4776
+ }
4605
4777
  try {
4606
- await fs3.access(p);
4607
- return true;
4778
+ const entries = await fs4.readdir(resolved);
4779
+ const meaningfulEntries = entries.filter(
4780
+ (entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
4781
+ );
4782
+ return meaningfulEntries.length === 0 ? { ok: true } : { ok: false, reason: "nonempty" };
4608
4783
  } catch {
4609
- return false;
4784
+ return { ok: false, reason: "unsafe" };
4610
4785
  }
4611
4786
  }
4612
-
4613
- // src/commands/install.ts
4614
- import { promises as fs5 } from "fs";
4615
- import path16 from "path";
4616
- import process6 from "process";
4617
-
4618
- // src/installers/platform-status.ts
4619
- import { promises as fs4 } from "fs";
4620
- import path15 from "path";
4621
- async function checkPlatformStatus(rootDir, platform, installed) {
4622
- const readiness = await checkPlatformReadiness(rootDir, platform);
4623
- return {
4624
- installed,
4625
- ready: installed && readiness.ready,
4626
- target: readiness.target,
4627
- detail: describeStatus(installed, readiness)
4628
- };
4629
- }
4630
- async function checkPlatformReadiness(rootDir, platform) {
4631
- if (platform === "claude-code") {
4632
- const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
4633
- fileMatches(
4634
- path15.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
4635
- (content) => isGeneratedClaudeSkill(content, "solo")
4636
- ),
4637
- claudeHooksRegistered(rootDir),
4638
- pathsExist([
4639
- path15.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
4640
- path15.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
4641
- ])
4642
- ]);
4643
- const present = hasSoloSkill && registered && hasHookFiles;
4644
- const minimal = await isPlatformMinimal(rootDir, "claude-code");
4645
- const fullContentReady = minimal ? true : await claudeFullContentReady(rootDir);
4646
- return {
4647
- present,
4648
- ready: present && fullContentReady,
4649
- target: ".claude/",
4650
- readyDetail: present && fullContentReady ? minimal ? "solo skill and hooks ready" : "skills, agents, and hooks ready" : "skills, agents, hook files, or hook registration are incomplete"
4651
- };
4787
+ function printNotProjectDirectory(rootDir, locale, reason) {
4788
+ if (locale === "zh-CN") {
4789
+ console.error(`\u2717 \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u53EF\u521D\u59CB\u5316\u7684\u9879\u76EE\u76EE\u5F55\uFF1A${rootDir}`);
4790
+ if (reason === "unsafe") {
4791
+ console.error(" \u4E3A\u907F\u514D\u8BEF\u5199\uFF0C\u4E0D\u80FD\u5728 Home \u76EE\u5F55\u6216\u78C1\u76D8\u6839\u76EE\u5F55\u521D\u59CB\u5316\u3002");
4792
+ } else if (reason === "nonempty") {
4793
+ console.error(
4794
+ " \u672A\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\uFF0C\u4E14\u76EE\u5F55\u4E2D\u5DF2\u6709\u6587\u4EF6\u3002\u8BF7\u5148\u8FDB\u5165\u9879\u76EE\u76EE\u5F55\u3002"
4795
+ );
4796
+ } else {
4797
+ console.error(
4798
+ " \u672A\u53D1\u73B0 .git \u6216\u9879\u76EE\u6587\u4EF6\u3002\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u53EF\u786E\u8BA4\u901A\u7528\u9879\u76EE\uFF0C\u6216\u4F7F\u7528 --empty\u3002"
4799
+ );
4800
+ }
4801
+ return;
4652
4802
  }
4653
- if (platform === "cursor") {
4654
- const hasCoreRules = await allManagedSkills(
4655
- MANCODE_CURSOR_CORE_RULE_FILES.map(
4656
- (file) => path15.join(rootDir, ".cursor", "rules", file)
4657
- ),
4658
- [CURSOR_RULE_MANAGED_MARKER]
4803
+ console.error(`\u2717 Not a project directory: ${rootDir}`);
4804
+ if (reason === "unsafe") {
4805
+ console.error(
4806
+ " Refusing to initialize a home directory or filesystem root."
4807
+ );
4808
+ } else if (reason === "nonempty") {
4809
+ console.error(
4810
+ " No project files were detected and the directory is not empty."
4811
+ );
4812
+ } else {
4813
+ console.error(
4814
+ " No .git or recognized project manifest found. Use an interactive terminal or --empty for a new project."
4659
4815
  );
4660
- if (await isPlatformMinimal(rootDir, "cursor")) {
4661
- return {
4662
- present: hasCoreRules,
4663
- ready: hasCoreRules,
4664
- target: ".cursor/rules/",
4665
- readyDetail: "mancode core rules present"
4666
- };
4667
- }
4668
- const [hasRules, hasCommands] = await Promise.all([
4669
- allManagedSkills(
4670
- MANCODE_CURSOR_RULE_FILES.map(
4671
- (file) => path15.join(rootDir, ".cursor", "rules", file)
4672
- ),
4673
- [CURSOR_RULE_MANAGED_MARKER]
4674
- ),
4675
- allManagedSkills(
4676
- MODE_NAMES.map(
4677
- (mode) => path15.join(rootDir, ".cursor", "commands", `${mode}.md`)
4678
- ),
4679
- [MODE_FILE_MANAGED_MARKER]
4680
- )
4681
- ]);
4682
- return {
4683
- present: hasCoreRules,
4684
- ready: hasRules && hasCommands,
4685
- target: ".cursor/rules/ + .cursor/commands/",
4686
- readyDetail: hasRules && hasCommands ? "mancode rules and commands present" : "mode rules or commands are missing, incomplete, or user-authored"
4687
- };
4688
4816
  }
4689
- if (platform === "codex") {
4690
- const hasBlock2 = await fileHasManagedBlock(path15.join(rootDir, "AGENTS.md"));
4691
- if (!hasBlock2) {
4692
- return {
4693
- present: false,
4694
- ready: false,
4695
- target: "AGENTS.md",
4696
- readyDetail: "managed block missing"
4697
- };
4817
+ }
4818
+ function getInitManagedFilePaths(rootDir, platforms) {
4819
+ const files = [
4820
+ ".mancode/state.json",
4821
+ ".mancode/config.json",
4822
+ ".mancode/project-profile.json",
4823
+ ".mancode/aesthetics/style-tokens.json",
4824
+ ".mancode/hooks/session-start.mjs",
4825
+ ".mancode/hooks/user-prompt-submit.mjs",
4826
+ ".mancode/logs/hooks.log",
4827
+ ".mancode/memory/prd.md",
4828
+ ".mancode/memory/spec.md",
4829
+ ".mancode/memory/decisions.md"
4830
+ ];
4831
+ if (platforms.includes("claude-code")) {
4832
+ files.push(
4833
+ ".mancode/hooks/session-start.sh",
4834
+ ".mancode/hooks/user-prompt-submit.sh",
4835
+ ".claude/settings.json",
4836
+ ".claude/skills/man8/SKILL.md",
4837
+ ".claude/skills/mamba/SKILL.md",
4838
+ ".claude/skills/solo/SKILL.md",
4839
+ ".claude/skills/mancode-mamba.md",
4840
+ ".claude/skills/mancode-solo.md",
4841
+ ".claude/skills/mancode-man8.md"
4842
+ );
4843
+ for (const mode of MODE_NAMES) {
4844
+ files.push(
4845
+ `.claude/skills/${mode}/SKILL.md`,
4846
+ `.claude/skills/mancode-${mode}.md`
4847
+ );
4698
4848
  }
4699
- if (await isPlatformMinimal(rootDir, "codex")) {
4700
- return {
4701
- present: true,
4702
- ready: true,
4703
- target: "AGENTS.md",
4704
- readyDetail: "managed block present"
4705
- };
4849
+ for (const agent of ALL_AGENTS) {
4850
+ files.push(`.claude/agents/${agent.name}.md`);
4706
4851
  }
4707
- const skillsDir = path15.join(rootDir, ".agents", "skills");
4708
- const hasSkills = await allManagedSkills(
4709
- MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
4710
- MANCODE_AGENT_SKILL_MARKERS
4711
- );
4712
- return {
4713
- present: true,
4714
- ready: hasSkills,
4715
- target: "AGENTS.md + .agents/skills/",
4716
- readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
4717
- };
4718
4852
  }
4719
- if (platform === "zcode") {
4720
- const hasBlock2 = await fileHasManagedBlock(
4721
- path15.join(rootDir, "AGENTS.md"),
4722
- ZCODE_MANCODE_START_MARKER,
4723
- ZCODE_MANCODE_END_MARKER
4853
+ if (platforms.includes("cursor")) {
4854
+ files.push(
4855
+ ".cursor/rules/mancode-mamba.mdc",
4856
+ ".cursor/rules/mancode-man8.mdc",
4857
+ ".cursor/commands/mamba.md",
4858
+ ".cursor/commands/man8.md"
4724
4859
  );
4725
- if (!hasBlock2) {
4726
- return {
4727
- present: false,
4728
- ready: false,
4729
- target: "AGENTS.md",
4730
- readyDetail: "managed block missing"
4731
- };
4860
+ for (const fileName of MANCODE_CURSOR_RULE_FILES) {
4861
+ files.push(`.cursor/rules/${fileName}`);
4732
4862
  }
4733
- if (await isPlatformMinimal(rootDir, "zcode")) {
4734
- return {
4735
- present: true,
4736
- ready: true,
4737
- target: "AGENTS.md",
4738
- readyDetail: "managed block present"
4739
- };
4863
+ for (const mode of MODE_NAMES) {
4864
+ files.push(`.cursor/commands/${mode}.md`);
4740
4865
  }
4741
- const skillsDir = path15.join(rootDir, ".agents", "skills");
4742
- const hasSkills = await allManagedSkills(
4743
- MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
4744
- MANCODE_AGENT_SKILL_MARKERS
4745
- );
4746
- return {
4747
- present: true,
4748
- ready: hasSkills,
4749
- target: "AGENTS.md + .agents/skills/",
4750
- readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
4751
- };
4752
- }
4753
- const hasBlock = await fileHasManagedBlock(
4754
- path15.join(rootDir, ".github", "copilot-instructions.md")
4755
- );
4756
- if (!hasBlock) {
4757
- return {
4758
- present: false,
4759
- ready: false,
4760
- target: ".github/copilot-instructions.md",
4761
- readyDetail: "managed block missing"
4762
- };
4763
4866
  }
4764
- if (!await isPlatformMinimal(rootDir, "copilot")) {
4765
- const promptsDir = path15.join(rootDir, ".github", "prompts");
4766
- const hasPrompts = await allManagedSkills(
4767
- MODE_NAMES.map((mode) => path15.join(promptsDir, `${mode}.prompt.md`)),
4768
- [MODE_FILE_MANAGED_MARKER]
4867
+ if (platforms.includes("codex") || platforms.includes("zcode")) {
4868
+ files.push(
4869
+ "AGENTS.md",
4870
+ ".agents/skills/mamba/SKILL.md",
4871
+ ".agents/skills/man8/SKILL.md"
4769
4872
  );
4770
- return {
4771
- present: true,
4772
- ready: hasPrompts,
4773
- target: ".github/copilot-instructions.md + .github/prompts/",
4774
- readyDetail: hasPrompts ? "managed block and prompts present" : "mode prompts are missing, incomplete, or user-authored"
4775
- };
4873
+ for (const mode of MODE_NAMES) {
4874
+ files.push(`.agents/skills/${mode}/SKILL.md`);
4875
+ }
4776
4876
  }
4777
- return {
4778
- present: true,
4779
- ready: true,
4780
- target: ".github/copilot-instructions.md",
4781
- readyDetail: "managed block present"
4782
- };
4783
- }
4784
- function describeStatus(installed, readiness) {
4785
- if (installed && readiness.present) return readiness.readyDetail;
4786
- if (!installed && readiness.present) {
4787
- return "target files present but platform is not recorded in config";
4877
+ if (platforms.includes("codex")) {
4878
+ files.push(".codex/skills/mamba/SKILL.md", ".codex/skills/man8/SKILL.md");
4879
+ for (const mode of MODE_NAMES) {
4880
+ files.push(`.codex/skills/${mode}/SKILL.md`);
4881
+ }
4788
4882
  }
4789
- return "missing generated files or registration";
4883
+ if (platforms.includes("zcode")) {
4884
+ files.push(".zcode/skills/mamba/SKILL.md", ".zcode/skills/man8/SKILL.md");
4885
+ for (const mode of MODE_NAMES) {
4886
+ files.push(`.zcode/skills/${mode}/SKILL.md`);
4887
+ }
4888
+ }
4889
+ if (platforms.includes("copilot")) {
4890
+ files.push(
4891
+ ".github/copilot-instructions.md",
4892
+ ".github/prompts/mamba.prompt.md",
4893
+ ".github/prompts/man8.prompt.md"
4894
+ );
4895
+ for (const mode of MODE_NAMES) {
4896
+ files.push(`.github/prompts/${mode}.prompt.md`);
4897
+ }
4898
+ }
4899
+ return [...new Set(files.map((file) => path15.join(rootDir, file)))];
4790
4900
  }
4791
- async function allManagedSkills(paths, markers) {
4792
- const results = await Promise.all(
4793
- paths.map((filePath) => fileHasAnyMarker(filePath, markers))
4901
+ async function snapshotFiles(filePaths) {
4902
+ return Promise.all(
4903
+ filePaths.map(async (filePath) => {
4904
+ try {
4905
+ return { filePath, content: await fs4.readFile(filePath, "utf-8") };
4906
+ } catch (error) {
4907
+ if (isNodeError5(error) && error.code === "ENOENT") {
4908
+ return { filePath, content: null };
4909
+ }
4910
+ throw error;
4911
+ }
4912
+ })
4794
4913
  );
4795
- return results.every(Boolean);
4796
4914
  }
4797
- async function claudeFullContentReady(rootDir) {
4798
- const skillChecks = MVP2_SKILLS.map(
4799
- (skill) => fileMatches(
4800
- path15.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
4801
- (content) => isGeneratedClaudeSkill(content, skill.name)
4802
- )
4803
- );
4804
- const agentChecks = ALL_AGENTS.map(
4805
- (agent) => fileMatches(
4806
- path15.join(rootDir, ".claude", "agents", `${agent.name}.md`),
4807
- (content) => isGeneratedClaudeAgent(content, agent.name)
4808
- )
4915
+ async function snapshotDirectories(filePaths, additionalDirectories = []) {
4916
+ const directories = new Set(additionalDirectories);
4917
+ for (const filePath of filePaths) {
4918
+ let current = path15.dirname(filePath);
4919
+ while (current !== path15.dirname(current)) {
4920
+ directories.add(current);
4921
+ current = path15.dirname(current);
4922
+ }
4923
+ }
4924
+ return Promise.all(
4925
+ [...directories].map(async (dirPath) => ({
4926
+ dirPath,
4927
+ existed: await pathExists4(dirPath)
4928
+ }))
4809
4929
  );
4810
- const results = await Promise.all([...skillChecks, ...agentChecks]);
4811
- return results.every(Boolean);
4812
4930
  }
4813
- async function fileMatches(filePath, predicate) {
4814
- try {
4815
- return predicate(await fs4.readFile(filePath, "utf-8"));
4816
- } catch {
4817
- return false;
4931
+ async function restoreFiles(snapshots) {
4932
+ for (const snapshot of snapshots) {
4933
+ if (snapshot.content === null) {
4934
+ await fs4.rm(snapshot.filePath, { force: true });
4935
+ continue;
4936
+ }
4937
+ await fs4.mkdir(path15.dirname(snapshot.filePath), { recursive: true });
4938
+ await fs4.writeFile(snapshot.filePath, snapshot.content, "utf-8");
4818
4939
  }
4819
4940
  }
4820
- async function pathsExist(paths) {
4821
- return (await Promise.all(paths.map(pathExists4))).every(Boolean);
4822
- }
4823
- async function fileHasAnyMarker(filePath, needles) {
4824
- try {
4825
- const content = await fs4.readFile(filePath, "utf-8");
4826
- return needles.some((needle) => content.includes(needle));
4827
- } catch {
4828
- return false;
4941
+ async function removeNewEmptyDirectories(snapshots) {
4942
+ const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
4943
+ for (const { dirPath } of candidates) {
4944
+ try {
4945
+ await fs4.rmdir(dirPath);
4946
+ } catch {
4947
+ }
4829
4948
  }
4830
4949
  }
4831
- async function isPlatformMinimal(rootDir, platform) {
4832
- try {
4833
- const raw = await fs4.readFile(
4834
- path15.join(rootDir, ".mancode", "config.json"),
4835
- "utf-8"
4836
- );
4837
- const config = JSON.parse(raw);
4838
- if (!isRecord4(config.platformOptions)) return false;
4839
- const options = config.platformOptions[platform];
4840
- return isRecord4(options) && options.minimal === true;
4841
- } catch {
4842
- return false;
4843
- }
4950
+ function isRecord4(value) {
4951
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4844
4952
  }
4845
- async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
4846
- try {
4847
- const content = await fs4.readFile(filePath, "utf-8");
4848
- return hasManagedBlock(content, startMarker, endMarker);
4849
- } catch {
4850
- return false;
4851
- }
4953
+ function isNodeError5(error) {
4954
+ return error instanceof Error && "code" in error;
4852
4955
  }
4853
- async function claudeHooksRegistered(rootDir) {
4854
- try {
4855
- const raw = await fs4.readFile(
4856
- path15.join(rootDir, ".claude", "settings.json"),
4857
- "utf-8"
4956
+ function printPlatformCreatedFiles(platform, locale) {
4957
+ if (platform === "claude-code") {
4958
+ console.log(
4959
+ localize(
4960
+ locale,
4961
+ " .claude/settings.json # hook \u6CE8\u518C",
4962
+ " .claude/settings.json # hook registration"
4963
+ )
4858
4964
  );
4859
- const settings = JSON.parse(raw);
4860
- return hooksRegistered(settings);
4861
- } catch {
4862
- return false;
4965
+ console.log(
4966
+ localize(
4967
+ locale,
4968
+ " .claude/skills/ # solo + MVP-2 slash skills",
4969
+ " .claude/skills/ # solo + MVP-2 slash skills"
4970
+ )
4971
+ );
4972
+ return;
4863
4973
  }
4974
+ if (platform === "cursor") {
4975
+ console.log(" .cursor/rules/ # Cursor project rules");
4976
+ return;
4977
+ }
4978
+ if (platform === "codex") {
4979
+ console.log(" AGENTS.md # Codex managed block");
4980
+ console.log(" .agents/skills/ # Codex mode skills");
4981
+ return;
4982
+ }
4983
+ if (platform === "zcode") {
4984
+ console.log(" AGENTS.md # ZCode managed block");
4985
+ console.log(" .agents/skills/ # ZCode mode skills");
4986
+ return;
4987
+ }
4988
+ console.log(" .github/copilot-instructions.md # Copilot instructions");
4864
4989
  }
4865
- function hooksRegistered(settings) {
4866
- if (!isRecord4(settings)) return false;
4867
- const hooks = settings.hooks;
4868
- if (!isRecord4(hooks)) return false;
4869
- return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
4870
- hooks.UserPromptSubmit,
4871
- ".mancode/hooks/user-prompt-submit.mjs"
4872
- );
4873
- }
4874
- function hasHookCommand(value, needle) {
4875
- if (!Array.isArray(value)) return false;
4876
- return value.some((group) => {
4877
- if (!isRecord4(group) || !Array.isArray(group.hooks)) return false;
4878
- return group.hooks.some((hook) => {
4879
- if (!isRecord4(hook) || typeof hook.command !== "string") return false;
4880
- return hook.command.includes(needle);
4881
- });
4882
- });
4990
+ function localize(locale, chinese, english) {
4991
+ return locale === "zh-CN" ? chinese : english;
4883
4992
  }
4884
4993
  async function pathExists4(p) {
4885
4994
  try {
@@ -4889,11 +4998,11 @@ async function pathExists4(p) {
4889
4998
  return false;
4890
4999
  }
4891
5000
  }
4892
- function isRecord4(value) {
4893
- return typeof value === "object" && value !== null;
4894
- }
4895
5001
 
4896
5002
  // src/commands/install.ts
5003
+ import { promises as fs5 } from "fs";
5004
+ import path16 from "path";
5005
+ import process6 from "process";
4897
5006
  var EXIT_OK2 = 0;
4898
5007
  var EXIT_NOT_INITIALIZED = 1;
4899
5008
  var EXIT_UNSUPPORTED_PLATFORM = 2;
@@ -6558,23 +6667,225 @@ async function pathExists9(p) {
6558
6667
  // src/commands/status.ts
6559
6668
  import { spawn } from "child_process";
6560
6669
  import { promises as fs9 } from "fs";
6561
- import path24 from "path";
6670
+ import path26 from "path";
6562
6671
  import process10 from "process";
6563
6672
 
6564
- // src/system/workflow.ts
6565
- import {
6566
- mkdir as mkdir9,
6567
- readFile as readFile10,
6568
- readdir as readdir5,
6569
- rm as rm4,
6570
- stat as stat4,
6571
- writeFile as writeFile11
6572
- } from "fs/promises";
6573
- import path23 from "path";
6673
+ // src/system/workflow.ts
6674
+ import {
6675
+ mkdir as mkdir9,
6676
+ readFile as readFile12,
6677
+ readdir as readdir5,
6678
+ rm as rm4,
6679
+ stat as stat4,
6680
+ writeFile as writeFile13
6681
+ } from "fs/promises";
6682
+ import path25 from "path";
6683
+
6684
+ // src/system/requirements-ledger.ts
6685
+ import { createHash as createHash2 } from "crypto";
6686
+ import { readFile as readFile9, writeFile as writeFile10 } from "fs/promises";
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
+ }
6574
6885
 
6575
6886
  // src/system/review-ledger.ts
6576
- import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
6577
- import path22 from "path";
6887
+ import { mkdir as mkdir8, readFile as readFile10, stat as stat3, writeFile as writeFile11 } from "fs/promises";
6888
+ import path23 from "path";
6578
6889
  var REVIEW_FILE = "review-ledger.json";
6579
6890
  var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
6580
6891
  var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
@@ -6603,6 +6914,27 @@ async function initializeReview(projectRoot, taskId, depth, targetDomain) {
6603
6914
  await writeReviewLedger(projectRoot, taskId, ledger);
6604
6915
  return ledger;
6605
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
+ }
6606
6938
  async function completeReviewDomain(projectRoot, taskId, domain, report, blockerIds) {
6607
6939
  const ledger = await requireReviewLedger(projectRoot, taskId);
6608
6940
  if (!ledger.requiredDomains.includes(domain)) {
@@ -6675,6 +7007,7 @@ async function remediateReviewBlockers(projectRoot, taskId, resolvedIds) {
6675
7007
  async function reviewCanComplete(projectRoot, taskId) {
6676
7008
  const ledger = await readReviewLedger(projectRoot, taskId);
6677
7009
  if (!ledger) return false;
7010
+ if (ledger.skipped) return true;
6678
7011
  return ledger.requiredDomains.every(
6679
7012
  (domain) => ledger.completedDomains.includes(domain)
6680
7013
  ) && ledger.blockers.every((blocker) => blocker.status === "resolved");
@@ -6682,7 +7015,7 @@ async function reviewCanComplete(projectRoot, taskId) {
6682
7015
  async function readReviewLedger(projectRoot, taskId) {
6683
7016
  if (!TASK_ID_PATTERN.test(taskId)) return null;
6684
7017
  try {
6685
- const raw = await readFile9(reviewPath(projectRoot, taskId), "utf-8");
7018
+ const raw = await readFile10(reviewPath(projectRoot, taskId), "utf-8");
6686
7019
  const value = JSON.parse(raw);
6687
7020
  return isReviewLedger(value) ? value : null;
6688
7021
  } catch {
@@ -6690,8 +7023,8 @@ async function readReviewLedger(projectRoot, taskId) {
6690
7023
  }
6691
7024
  }
6692
7025
  function isReviewLedger(value) {
6693
- if (!isRecord8(value)) return false;
6694
- 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) {
6695
7028
  return false;
6696
7029
  }
6697
7030
  const requiredDomains = value.requiredDomains;
@@ -6699,6 +7032,9 @@ function isReviewLedger(value) {
6699
7032
  const reports = value.reports;
6700
7033
  const blockers = value.blockers;
6701
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
+ }
6702
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) => {
6703
7039
  const report = reports[domain];
6704
7040
  return typeof report !== "string" || !isSafeReportPath(report);
@@ -6707,13 +7043,13 @@ function isReviewLedger(value) {
6707
7043
  }
6708
7044
  const blockerIds = /* @__PURE__ */ new Set();
6709
7045
  return blockers.every(
6710
- (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")
6711
7047
  );
6712
7048
  }
6713
7049
  function isDomainArray(value) {
6714
7050
  return Array.isArray(value) && value.every(isReviewDomain);
6715
7051
  }
6716
- function isRecord8(value) {
7052
+ function isRecord9(value) {
6717
7053
  return typeof value === "object" && value !== null && !Array.isArray(value);
6718
7054
  }
6719
7055
  async function requireReviewLedger(projectRoot, taskId) {
@@ -6723,17 +7059,21 @@ async function requireReviewLedger(projectRoot, taskId) {
6723
7059
  return ledger;
6724
7060
  }
6725
7061
  async function writeReviewLedger(projectRoot, taskId, ledger) {
6726
- const dir = path22.join(projectRoot, ".mancode", "workflows", taskId);
7062
+ const dir = path23.join(projectRoot, ".mancode", "workflows", taskId);
6727
7063
  await mkdir8(dir, { recursive: true });
6728
- await writeFile10(
6729
- path22.join(dir, REVIEW_FILE),
7064
+ await writeFile11(
7065
+ path23.join(dir, REVIEW_FILE),
6730
7066
  `${JSON.stringify(ledger, null, 2)}
6731
7067
  `,
6732
7068
  "utf-8"
6733
7069
  );
6734
7070
  }
7071
+ function reviewLedgerPath(projectRoot, taskId) {
7072
+ assertValidTaskId(taskId);
7073
+ return reviewPath(projectRoot, taskId);
7074
+ }
6735
7075
  function reviewPath(projectRoot, taskId) {
6736
- return path22.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
7076
+ return path23.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
6737
7077
  }
6738
7078
  function assertValidTaskId(taskId) {
6739
7079
  if (!TASK_ID_PATTERN.test(taskId)) {
@@ -6746,7 +7086,7 @@ function assertSafeReportPath(report) {
6746
7086
  }
6747
7087
  }
6748
7088
  async function assertReportExists(projectRoot, taskId, report) {
6749
- const reportPath = path22.join(
7089
+ const reportPath = path23.join(
6750
7090
  projectRoot,
6751
7091
  ".mancode",
6752
7092
  "workflows",
@@ -6760,11 +7100,221 @@ async function assertReportExists(projectRoot, taskId, report) {
6760
7100
  throw new Error(`review report not found: ${report}`);
6761
7101
  }
6762
7102
  function isSafeReportPath(report) {
6763
- 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
+ ]);
6764
7256
  return Boolean(
6765
- 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
6766
7270
  );
6767
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
+ }
6768
7318
 
6769
7319
  // src/system/workflow.ts
6770
7320
  var METADATA_FILE = "metadata.json";
@@ -6786,7 +7336,7 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
6786
7336
  await validateParentTask(projectRoot, mode, options.parentTaskId, true);
6787
7337
  await mkdir9(workflowsRoot(projectRoot), { recursive: true });
6788
7338
  const taskId = await allocateTaskId(projectRoot, generateTaskId(task));
6789
- const dir = workflowDir(projectRoot, taskId);
7339
+ const dir = workflowDir2(projectRoot, taskId);
6790
7340
  const now = (/* @__PURE__ */ new Date()).toISOString();
6791
7341
  const meta = {
6792
7342
  taskId,
@@ -6798,7 +7348,12 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
6798
7348
  updatedAt: now,
6799
7349
  status: "in_progress",
6800
7350
  ...options.parentTaskId ? { parentTaskId: options.parentTaskId } : {},
6801
- ...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
+ } : {}
6802
7357
  };
6803
7358
  await writeMetadata(dir, meta);
6804
7359
  return meta;
@@ -6807,7 +7362,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
6807
7362
  for (let attempt = 0; attempt < 1e3; attempt++) {
6808
7363
  const taskId = attempt === 0 ? baseTaskId : `${baseTaskId}-${attempt + 1}`;
6809
7364
  try {
6810
- await mkdir9(workflowDir(projectRoot, taskId));
7365
+ await mkdir9(workflowDir2(projectRoot, taskId));
6811
7366
  return taskId;
6812
7367
  } catch (err) {
6813
7368
  if (isNodeError9(err) && err.code === "EEXIST") {
@@ -6822,13 +7377,13 @@ async function readWorkflow(projectRoot, taskId) {
6822
7377
  if (!isValidWorkflowTaskId(taskId)) return null;
6823
7378
  const file = metadataPath(projectRoot, taskId);
6824
7379
  try {
6825
- const raw = await readFile10(file, "utf-8");
7380
+ const raw = await readFile12(file, "utf-8");
6826
7381
  return parseWorkflowMeta(raw, taskId);
6827
7382
  } catch {
6828
7383
  return null;
6829
7384
  }
6830
7385
  }
6831
- async function updateWorkflow(projectRoot, taskId, patch) {
7386
+ async function updateWorkflow(projectRoot, taskId, patch, options = {}) {
6832
7387
  assertValidTaskId2(taskId);
6833
7388
  const existing = await readWorkflow(projectRoot, taskId);
6834
7389
  if (!existing) {
@@ -6848,8 +7403,8 @@ async function updateWorkflow(projectRoot, taskId, patch) {
6848
7403
  if (updated.status !== "blocked" && !explicitlyPatchedBlockingReason) {
6849
7404
  updated.blockingReason = void 0;
6850
7405
  }
6851
- await validateWorkflowMeta(projectRoot, updated, existing);
6852
- await writeMetadata(workflowDir(projectRoot, taskId), updated);
7406
+ await validateWorkflowMeta(projectRoot, updated, existing, options);
7407
+ await writeMetadata(workflowDir2(projectRoot, taskId), updated);
6853
7408
  if (updated.parentTaskId) {
6854
7409
  await propagateChildStatus(projectRoot, updated);
6855
7410
  }
@@ -6872,7 +7427,7 @@ async function listWorkflows(projectRoot) {
6872
7427
  }
6873
7428
  async function deleteWorkflow(projectRoot, taskId) {
6874
7429
  if (!isValidWorkflowTaskId(taskId)) return false;
6875
- const dir = workflowDir(projectRoot, taskId);
7430
+ const dir = workflowDir2(projectRoot, taskId);
6876
7431
  try {
6877
7432
  await stat4(dir);
6878
7433
  } catch {
@@ -6886,19 +7441,19 @@ async function deleteWorkflow(projectRoot, taskId) {
6886
7441
  return true;
6887
7442
  }
6888
7443
  function workflowsRoot(projectRoot) {
6889
- return path23.join(projectRoot, ".mancode", "workflows");
7444
+ return path25.join(projectRoot, ".mancode", "workflows");
6890
7445
  }
6891
- function workflowDir(projectRoot, taskId) {
7446
+ function workflowDir2(projectRoot, taskId) {
6892
7447
  assertValidTaskId2(taskId);
6893
- return path23.join(workflowsRoot(projectRoot), taskId);
7448
+ return path25.join(workflowsRoot(projectRoot), taskId);
6894
7449
  }
6895
7450
  function metadataPath(projectRoot, taskId) {
6896
- return path23.join(workflowDir(projectRoot, taskId), METADATA_FILE);
7451
+ return path25.join(workflowDir2(projectRoot, taskId), METADATA_FILE);
6897
7452
  }
6898
7453
  async function writeMetadata(dir, meta) {
6899
7454
  const content = `${JSON.stringify(meta, null, 2)}
6900
7455
  `;
6901
- await writeFile11(path23.join(dir, METADATA_FILE), content, "utf-8");
7456
+ await writeFile13(path25.join(dir, METADATA_FILE), content, "utf-8");
6902
7457
  }
6903
7458
  function parseWorkflowMeta(raw, taskId) {
6904
7459
  try {
@@ -6923,6 +7478,12 @@ function isWorkflowStatus(value) {
6923
7478
  function isWorkflowOutcome(value) {
6924
7479
  return value === "fixed" || value === "verified" || value === "no_repro" || value === "manual_test_required";
6925
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
+ }
6926
7487
  async function validateParentTask(projectRoot, mode, parentTaskId, requireStepSix = false) {
6927
7488
  if (!parentTaskId) return;
6928
7489
  if (mode !== "mamba") {
@@ -6938,7 +7499,7 @@ async function validateParentTask(projectRoot, mode, parentTaskId, requireStepSi
6938
7499
  );
6939
7500
  }
6940
7501
  }
6941
- async function validateWorkflowMeta(projectRoot, updated, existing) {
7502
+ async function validateWorkflowMeta(projectRoot, updated, existing, options) {
6942
7503
  if (updated.mode !== existing.mode) {
6943
7504
  throw new Error("workflow mode cannot be changed");
6944
7505
  }
@@ -6951,6 +7512,17 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
6951
7512
  if (updated.reviewPolicyVersion !== existing.reviewPolicyVersion) {
6952
7513
  throw new Error("workflow review policy version cannot be changed");
6953
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
+ }
6954
7526
  if (!Number.isInteger(updated.currentStep) || updated.currentStep < 1 || updated.currentStep > maxWorkflowStep(updated.mode)) {
6955
7527
  throw new Error(`invalid workflow step: ${updated.currentStep}`);
6956
7528
  }
@@ -6960,6 +7532,13 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
6960
7532
  if (!Array.isArray(updated.skippedSteps) || updated.skippedSteps.some((step) => typeof step !== "string")) {
6961
7533
  throw new Error("workflow skipped steps must be strings");
6962
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
+ }
6963
7542
  if (!canTransition(existing.status, updated.status)) {
6964
7543
  throw new Error(
6965
7544
  `invalid workflow status transition: ${existing.status} -> ${updated.status}`
@@ -6971,7 +7550,7 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
6971
7550
  if (updated.status === "planned" && updated.currentStep !== 4) {
6972
7551
  throw new Error("planned workflows must be at step 4");
6973
7552
  }
6974
- 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")) {
6975
7554
  throw new Error(
6976
7555
  `completed ${updated.mode === "mamba" ? "manba" : updated.mode} workflows must be at step ${maxWorkflowStep(updated.mode)}`
6977
7556
  );
@@ -7001,12 +7580,106 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
7001
7580
  if (updated.mode === "mamba" && updated.planVersion !== void 0) {
7002
7581
  throw new Error("manba workflows cannot have a plan version");
7003
7582
  }
7004
- if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1) {
7583
+ if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1 && updated.reviewPolicyVersion !== 2) {
7005
7584
  throw new Error("invalid workflow review policy version");
7006
7585
  }
7007
7586
  if (updated.mode === "mamba" && updated.reviewPolicyVersion !== void 0) {
7008
7587
  throw new Error("manba workflows cannot have a review policy version");
7009
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
+ }
7010
7683
  if (updated.planVersion !== void 0 && (!Number.isInteger(updated.planVersion) || updated.planVersion < 1)) {
7011
7684
  throw new Error("workflow plan version must be a positive integer");
7012
7685
  }
@@ -7020,16 +7693,22 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
7020
7693
  if ((isTerminalWorkflowStatus(updated.status) || updated.status === "planned") && (await listActiveMambaChildren(projectRoot, updated.taskId)).length > 0) {
7021
7694
  throw new Error("cannot finish workflow with an active manba child");
7022
7695
  }
7023
- 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)) {
7024
7700
  throw new Error("workflow review is incomplete or still has open blockers");
7025
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
+ }
7026
7705
  }
7027
7706
  function canTransition(from, to) {
7028
7707
  if (from === to) return true;
7029
7708
  if (from === "in_progress") return true;
7030
- if (from === "planned" || from === "blocked") {
7031
- return to === "in_progress" || to === "abandoned";
7032
- }
7709
+ if (from === "planned")
7710
+ return to === "in_progress" || to === "completed" || to === "abandoned";
7711
+ if (from === "blocked") return to === "in_progress" || to === "abandoned";
7033
7712
  return false;
7034
7713
  }
7035
7714
  function isTerminalWorkflowStatus(status2) {
@@ -7050,7 +7729,19 @@ function isWorkflowShape(obj) {
7050
7729
  return false;
7051
7730
  if (obj.planVersion !== void 0 && (!Number.isInteger(obj.planVersion) || obj.planVersion < 1))
7052
7731
  return false;
7053
- if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1)
7732
+ if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1 && obj.reviewPolicyVersion !== 2)
7733
+ 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))
7054
7745
  return false;
7055
7746
  if (status2 === "blocked") {
7056
7747
  if (!obj.blockingReason?.trim()) return false;
@@ -7058,7 +7749,7 @@ function isWorkflowShape(obj) {
7058
7749
  return false;
7059
7750
  }
7060
7751
  if (mode === "mamba") {
7061
- 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)
7062
7753
  return false;
7063
7754
  if (obj.parentTaskId !== void 0 && !isValidWorkflowTaskId(obj.parentTaskId))
7064
7755
  return false;
@@ -7069,9 +7760,20 @@ function isWorkflowShape(obj) {
7069
7760
  }
7070
7761
  return true;
7071
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
+ }
7072
7771
  function reviewWasExplicitlySkipped(meta) {
7073
7772
  return meta.skippedSteps.includes("review") || meta.skippedSteps.includes("film-1") && meta.skippedSteps.includes("film-2");
7074
7773
  }
7774
+ function isVerificationStatus(value) {
7775
+ return value === "pending" || value === "passed" || value === "failed" || value === "manual_required" || value === "blocked";
7776
+ }
7075
7777
  async function listActiveMambaChildren(projectRoot, parentTaskId) {
7076
7778
  return (await listMambaChildren(projectRoot, parentTaskId)).filter(
7077
7779
  (workflow2) => workflow2.status === "in_progress" || workflow2.status === "blocked"
@@ -7090,7 +7792,7 @@ async function propagateChildStatus(projectRoot, child) {
7090
7792
  const parent = await readWorkflow(projectRoot, parentTaskId);
7091
7793
  if (!parent || parent.status !== "in_progress") return;
7092
7794
  const reason = child.status === "blocked" ? `manba child ${child.taskId} is blocked: ${child.blockingReason ?? "unknown reason"}` : `manba child ${child.taskId} requires manual testing`;
7093
- await writeMetadata(workflowDir(projectRoot, parent.taskId), {
7795
+ await writeMetadata(workflowDir2(projectRoot, parent.taskId), {
7094
7796
  ...parent,
7095
7797
  status: "blocked",
7096
7798
  blockingReason: reason,
@@ -7119,7 +7821,7 @@ var EXIT_OK7 = 0;
7119
7821
  var EXIT_NOT_INITIALIZED5 = 1;
7120
7822
  var EXIT_CORRUPT_STATE2 = 2;
7121
7823
  async function status(rootDir = process10.cwd(), options = {}) {
7122
- const stateFile = path24.join(rootDir, ".mancode", "state.json");
7824
+ const stateFile = path26.join(rootDir, ".mancode", "state.json");
7123
7825
  if (!await pathExists10(stateFile)) {
7124
7826
  console.error("\u2717 mancode not initialized.");
7125
7827
  console.error(" Run `mancode init` to get started.");
@@ -7170,6 +7872,7 @@ async function status(rootDir = process10.cwd(), options = {}) {
7170
7872
  hookInjection,
7171
7873
  team: effectiveTeam,
7172
7874
  currentWorkflow,
7875
+ activeSoloPlan: state.activeSoloPlan ?? null,
7173
7876
  platformStatus: {},
7174
7877
  projectRefreshRecommended
7175
7878
  };
@@ -7183,10 +7886,10 @@ async function status(rootDir = process10.cwd(), options = {}) {
7183
7886
  }
7184
7887
  async function shouldRefreshProject(rootDir, state) {
7185
7888
  if (state.projectMode !== "generic") return false;
7186
- const hasGit = await pathExists10(path24.join(rootDir, ".git"));
7889
+ const hasGit = await pathExists10(path26.join(rootDir, ".git"));
7187
7890
  if (hasGit) return true;
7188
7891
  for (const manifest of PROJECT_MANIFESTS) {
7189
- if (await pathExists10(path24.join(rootDir, manifest))) return true;
7892
+ if (await pathExists10(path26.join(rootDir, manifest))) return true;
7190
7893
  }
7191
7894
  return false;
7192
7895
  }
@@ -7230,19 +7933,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
7230
7933
  }
7231
7934
  async function getProjectName(rootDir) {
7232
7935
  try {
7233
- 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");
7234
7937
  const pkg = JSON.parse(raw);
7235
7938
  if (pkg.name && typeof pkg.name === "string") {
7236
7939
  return pkg.name;
7237
7940
  }
7238
7941
  } catch {
7239
7942
  }
7240
- return path24.basename(rootDir);
7943
+ return path26.basename(rootDir);
7241
7944
  }
7242
7945
  async function readConfig2(rootDir) {
7243
7946
  try {
7244
7947
  const raw = await fs9.readFile(
7245
- path24.join(rootDir, ".mancode", "config.json"),
7948
+ path26.join(rootDir, ".mancode", "config.json"),
7246
7949
  "utf-8"
7247
7950
  );
7248
7951
  return JSON.parse(raw);
@@ -7270,9 +7973,9 @@ function getEffectiveTeamStatus(state, config, detected) {
7270
7973
  }
7271
7974
  async function checkHooks(rootDir) {
7272
7975
  const [sessionStart, userPromptSubmit, registered] = await Promise.all([
7273
- pathExists10(path24.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
7976
+ pathExists10(path26.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
7274
7977
  pathExists10(
7275
- path24.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
7978
+ path26.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
7276
7979
  ),
7277
7980
  isRegistered(rootDir)
7278
7981
  ]);
@@ -7281,7 +7984,7 @@ async function checkHooks(rootDir) {
7281
7984
  async function isRegistered(rootDir) {
7282
7985
  try {
7283
7986
  const raw = await fs9.readFile(
7284
- path24.join(rootDir, ".claude", "settings.json"),
7987
+ path26.join(rootDir, ".claude", "settings.json"),
7285
7988
  "utf-8"
7286
7989
  );
7287
7990
  const settings = JSON.parse(raw);
@@ -7291,9 +7994,9 @@ async function isRegistered(rootDir) {
7291
7994
  }
7292
7995
  }
7293
7996
  function hooksRegistered2(settings) {
7294
- if (!isRecord9(settings)) return false;
7997
+ if (!isRecord10(settings)) return false;
7295
7998
  const hooks = settings.hooks;
7296
- if (!isRecord9(hooks)) return false;
7999
+ if (!isRecord10(hooks)) return false;
7297
8000
  return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
7298
8001
  hooks.UserPromptSubmit,
7299
8002
  ".mancode/hooks/user-prompt-submit.mjs"
@@ -7302,15 +8005,15 @@ function hooksRegistered2(settings) {
7302
8005
  function hasHookCommand2(value, needle) {
7303
8006
  if (!Array.isArray(value)) return false;
7304
8007
  return value.some((group) => {
7305
- if (!isRecord9(group) || !Array.isArray(group.hooks)) return false;
8008
+ if (!isRecord10(group) || !Array.isArray(group.hooks)) return false;
7306
8009
  return group.hooks.some((hook) => {
7307
- if (!isRecord9(hook) || typeof hook.command !== "string") return false;
8010
+ if (!isRecord10(hook) || typeof hook.command !== "string") return false;
7308
8011
  return hook.command.includes(needle);
7309
8012
  });
7310
8013
  });
7311
8014
  }
7312
8015
  async function estimateHookInjection(rootDir) {
7313
- const hookPath = path24.join(
8016
+ const hookPath = path26.join(
7314
8017
  rootDir,
7315
8018
  ".mancode",
7316
8019
  "hooks",
@@ -7402,6 +8105,11 @@ function printText(r) {
7402
8105
  );
7403
8106
  }
7404
8107
  }
8108
+ if (r.activeSoloPlan) {
8109
+ console.log(
8110
+ `Solo plan: ${r.activeSoloPlan.taskId} (plan v${r.activeSoloPlan.planVersion})`
8111
+ );
8112
+ }
7405
8113
  console.log("");
7406
8114
  console.log("Installed platforms:");
7407
8115
  for (const p of r.platforms) {
@@ -7442,7 +8150,7 @@ function formatTeamStatus(team) {
7442
8150
  function workflowStepMax(mode) {
7443
8151
  return maxWorkflowStep(mode);
7444
8152
  }
7445
- function isRecord9(value) {
8153
+ function isRecord10(value) {
7446
8154
  return typeof value === "object" && value !== null;
7447
8155
  }
7448
8156
  async function pathExists10(p) {
@@ -7455,14 +8163,14 @@ async function pathExists10(p) {
7455
8163
  }
7456
8164
 
7457
8165
  // src/commands/uninstall.ts
7458
- import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
7459
- 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";
7460
8168
  import process11 from "process";
7461
8169
  var EXIT_OK8 = 0;
7462
8170
  var EXIT_NOT_INITIALIZED6 = 1;
7463
8171
  var EXIT_UNSUPPORTED_PLATFORM2 = 2;
7464
8172
  async function uninstall(rootDir = process11.cwd(), platform, options = {}) {
7465
- const stateFile = path25.join(rootDir, ".mancode", "state.json");
8173
+ const stateFile = path27.join(rootDir, ".mancode", "state.json");
7466
8174
  if (!await pathExists11(stateFile)) {
7467
8175
  console.error("\u2717 mancode not initialized.");
7468
8176
  console.error(" Run `mancode init` first.");
@@ -7514,7 +8222,7 @@ async function uninstallAll(rootDir) {
7514
8222
  await uninstallPlatform(rootDir, p);
7515
8223
  }
7516
8224
  console.log("\u2713 Removing .mancode/ directory...");
7517
- await rm5(path25.join(rootDir, ".mancode"), {
8225
+ await rm5(path27.join(rootDir, ".mancode"), {
7518
8226
  recursive: true,
7519
8227
  force: true
7520
8228
  });
@@ -7524,10 +8232,10 @@ async function uninstallClaudeCode(rootDir) {
7524
8232
  await cleanClaudeSettings(rootDir);
7525
8233
  }
7526
8234
  async function cleanClaudeSettings(rootDir) {
7527
- const settingsPath = path25.join(rootDir, ".claude", "settings.json");
8235
+ const settingsPath = path27.join(rootDir, ".claude", "settings.json");
7528
8236
  let content;
7529
8237
  try {
7530
- content = await readFile11(settingsPath, "utf-8");
8238
+ content = await readFile13(settingsPath, "utf-8");
7531
8239
  } catch {
7532
8240
  return;
7533
8241
  }
@@ -7538,14 +8246,14 @@ async function cleanClaudeSettings(rootDir) {
7538
8246
  return;
7539
8247
  }
7540
8248
  const cleanedHooks = {};
7541
- if (isRecord10(settings.hooks)) {
8249
+ if (isRecord11(settings.hooks)) {
7542
8250
  for (const [event, value] of Object.entries(settings.hooks)) {
7543
8251
  const cleaned = cleanClaudeHookValue(value);
7544
8252
  if (cleaned !== void 0) cleanedHooks[event] = cleaned;
7545
8253
  }
7546
8254
  settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
7547
8255
  }
7548
- if (isRecord10(settings.skills)) {
8256
+ if (isRecord11(settings.skills)) {
7549
8257
  const retainedSkills = Object.fromEntries(
7550
8258
  Object.entries(settings.skills).filter(
7551
8259
  ([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
@@ -7553,7 +8261,7 @@ async function cleanClaudeSettings(rootDir) {
7553
8261
  );
7554
8262
  settings.skills = Object.keys(retainedSkills).length > 0 ? retainedSkills : void 0;
7555
8263
  }
7556
- await writeFile12(
8264
+ await writeFile14(
7557
8265
  settingsPath,
7558
8266
  `${JSON.stringify(settings, null, 2)}
7559
8267
  `,
@@ -7565,7 +8273,7 @@ function cleanClaudeHookValue(value) {
7565
8273
  const entries2 = value.flatMap(cleanClaudeHookEntry);
7566
8274
  return entries2.length > 0 ? entries2 : void 0;
7567
8275
  }
7568
- if (!isRecord10(value)) return value;
8276
+ if (!isRecord11(value)) return value;
7569
8277
  if (Array.isArray(value.hooks) || typeof value.command === "string") {
7570
8278
  const entries2 = cleanClaudeHookEntry(value);
7571
8279
  return entries2.length > 0 ? entries2 : void 0;
@@ -7579,7 +8287,7 @@ function cleanClaudeHookValue(value) {
7579
8287
  }
7580
8288
  function cleanClaudeHookEntry(entry) {
7581
8289
  if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
7582
- if (!isRecord10(entry)) return [entry];
8290
+ if (!isRecord11(entry)) return [entry];
7583
8291
  if (Array.isArray(entry.hooks)) {
7584
8292
  const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
7585
8293
  return hooks.length > 0 ? [{ ...entry, hooks }] : [];
@@ -7587,11 +8295,11 @@ function cleanClaudeHookEntry(entry) {
7587
8295
  return isMancodeHookEntry(entry) ? [] : [entry];
7588
8296
  }
7589
8297
  function isMancodeHookEntry(value) {
7590
- return isRecord10(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
8298
+ return isRecord11(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
7591
8299
  }
7592
8300
  function containsLegacyMancodeHookPath2(value) {
7593
8301
  if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
7594
- if (!isRecord10(value)) return false;
8302
+ if (!isRecord11(value)) return false;
7595
8303
  if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
7596
8304
  return true;
7597
8305
  }
@@ -7602,12 +8310,12 @@ async function uninstallCursor(rootDir) {
7602
8310
  await removeCursorCommands(rootDir);
7603
8311
  }
7604
8312
  async function uninstallCodex(rootDir) {
7605
- const agentsPath = path25.join(rootDir, "AGENTS.md");
8313
+ const agentsPath = path27.join(rootDir, "AGENTS.md");
7606
8314
  try {
7607
- const content = await readFile11(agentsPath, "utf-8");
8315
+ const content = await readFile13(agentsPath, "utf-8");
7608
8316
  const cleaned = removeManagedBlock(content);
7609
8317
  if (cleaned.trim()) {
7610
- await writeFile12(agentsPath, `${cleaned}
8318
+ await writeFile14(agentsPath, `${cleaned}
7611
8319
  `, "utf-8");
7612
8320
  } else {
7613
8321
  await rm5(agentsPath, { force: true });
@@ -7617,16 +8325,16 @@ async function uninstallCodex(rootDir) {
7617
8325
  await removeCodexSkills(rootDir);
7618
8326
  }
7619
8327
  async function uninstallCopilot(rootDir) {
7620
- const instructionsPath = path25.join(
8328
+ const instructionsPath = path27.join(
7621
8329
  rootDir,
7622
8330
  ".github",
7623
8331
  "copilot-instructions.md"
7624
8332
  );
7625
8333
  try {
7626
- const content = await readFile11(instructionsPath, "utf-8");
8334
+ const content = await readFile13(instructionsPath, "utf-8");
7627
8335
  const cleaned = removeManagedBlock(content);
7628
8336
  if (cleaned.trim()) {
7629
- await writeFile12(instructionsPath, `${cleaned}
8337
+ await writeFile14(instructionsPath, `${cleaned}
7630
8338
  `, "utf-8");
7631
8339
  } else {
7632
8340
  await rm5(instructionsPath, { force: true });
@@ -7636,16 +8344,16 @@ async function uninstallCopilot(rootDir) {
7636
8344
  await removeCopilotPrompts(rootDir);
7637
8345
  }
7638
8346
  async function uninstallZcode(rootDir) {
7639
- const agentsPath = path25.join(rootDir, "AGENTS.md");
8347
+ const agentsPath = path27.join(rootDir, "AGENTS.md");
7640
8348
  try {
7641
- const content = await readFile11(agentsPath, "utf-8");
8349
+ const content = await readFile13(agentsPath, "utf-8");
7642
8350
  const cleaned = removeManagedBlock(
7643
8351
  content,
7644
8352
  ZCODE_MANCODE_START_MARKER,
7645
8353
  ZCODE_MANCODE_END_MARKER
7646
8354
  );
7647
8355
  if (cleaned.trim()) {
7648
- await writeFile12(agentsPath, `${cleaned}
8356
+ await writeFile14(agentsPath, `${cleaned}
7649
8357
  `, "utf-8");
7650
8358
  } else {
7651
8359
  await rm5(agentsPath, { force: true });
@@ -7655,13 +8363,13 @@ async function uninstallZcode(rootDir) {
7655
8363
  await removeZcodeSkills(rootDir);
7656
8364
  }
7657
8365
  async function removeFromConfig(rootDir, platform) {
7658
- const configPath = path25.join(rootDir, ".mancode", "config.json");
8366
+ const configPath = path27.join(rootDir, ".mancode", "config.json");
7659
8367
  try {
7660
- const raw = await readFile11(configPath, "utf-8");
8368
+ const raw = await readFile13(configPath, "utf-8");
7661
8369
  const config = JSON.parse(raw);
7662
8370
  if (Array.isArray(config.platforms)) {
7663
8371
  config.platforms = config.platforms.filter((p) => p !== platform);
7664
- if (isRecord10(config.platformOptions)) {
8372
+ if (isRecord11(config.platformOptions)) {
7665
8373
  const platformOptions = Object.fromEntries(
7666
8374
  Object.entries(config.platformOptions).filter(
7667
8375
  ([name]) => name !== platform
@@ -7669,7 +8377,7 @@ async function removeFromConfig(rootDir, platform) {
7669
8377
  );
7670
8378
  config.platformOptions = Object.keys(platformOptions).length > 0 ? platformOptions : void 0;
7671
8379
  }
7672
- await writeFile12(
8380
+ await writeFile14(
7673
8381
  configPath,
7674
8382
  `${JSON.stringify(config, null, 2)}
7675
8383
  `,
@@ -7679,7 +8387,7 @@ async function removeFromConfig(rootDir, platform) {
7679
8387
  } catch {
7680
8388
  }
7681
8389
  }
7682
- function isRecord10(value) {
8390
+ function isRecord11(value) {
7683
8391
  return typeof value === "object" && value !== null && !Array.isArray(value);
7684
8392
  }
7685
8393
  async function pathExists11(p) {
@@ -7702,13 +8410,13 @@ function version() {
7702
8410
  }
7703
8411
 
7704
8412
  // src/commands/workflow.ts
7705
- import { access as access6 } from "fs/promises";
7706
- 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";
7707
8415
  var EXIT_OK9 = 0;
7708
8416
  var EXIT_NOT_INITIALIZED7 = 1;
7709
8417
  var EXIT_INVALID_ARG2 = 2;
7710
8418
  async function workflow(rootDir, subcommand, args = [], options = {}) {
7711
- if (!await pathExists12(path26.join(rootDir, ".mancode", "state.json"))) {
8419
+ if (!await pathExists12(path28.join(rootDir, ".mancode", "state.json"))) {
7712
8420
  if (options.json) {
7713
8421
  console.log(JSON.stringify({ error: "not initialized" }, null, 2));
7714
8422
  } else {
@@ -7726,8 +8434,16 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
7726
8434
  return workflowList(rootDir, options);
7727
8435
  case "show":
7728
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);
7729
8441
  case "review":
7730
8442
  return workflowReview(rootDir, args, options);
8443
+ case "requirements":
8444
+ return workflowRequirements(rootDir, args, options);
8445
+ case "verify":
8446
+ return workflowVerify(rootDir, args, options);
7731
8447
  case "clean":
7732
8448
  return workflowClean(rootDir, options);
7733
8449
  default:
@@ -7740,13 +8456,279 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
7740
8456
  )
7741
8457
  );
7742
8458
  } else {
7743
- console.error(`\u2717 Invalid workflow subcommand: ${subcommand}`);
7744
- console.error(
7745
- " Use: create <man|manba|manteam> <task> | update <taskId> | review <taskId> <init|complete|remediate|show> | list | show <taskId> | clean"
8459
+ console.error(`\u2717 Invalid workflow subcommand: ${subcommand}`);
8460
+ console.error(
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"
8462
+ );
8463
+ }
8464
+ return EXIT_INVALID_ARG2;
8465
+ }
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 ?? ""}`
7746
8655
  );
7747
8656
  }
7748
- return EXIT_INVALID_ARG2;
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;
7749
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
+ );
7750
8732
  }
7751
8733
  async function workflowReview(rootDir, args, options) {
7752
8734
  const taskId = args[0];
@@ -7772,6 +8754,36 @@ async function workflowReview(rootDir, args, options) {
7772
8754
  "review requires an in_progress workflow at step 6 or later"
7773
8755
  );
7774
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
+ }
7775
8787
  try {
7776
8788
  let ledger;
7777
8789
  if (action === "init") {
@@ -7817,7 +8829,7 @@ async function workflowReview(rootDir, args, options) {
7817
8829
  return invalidArg(
7818
8830
  options,
7819
8831
  `invalid review action: ${action ?? ""}`,
7820
- "Use: mancode workflow review <taskId> <init|complete|remediate|show>"
8832
+ "Use: mancode workflow review <taskId> <init|complete|remediate|skip|show>"
7821
8833
  );
7822
8834
  }
7823
8835
  outputReviewLedger(ledger, options);
@@ -7829,12 +8841,44 @@ async function workflowReview(rootDir, args, options) {
7829
8841
  );
7830
8842
  }
7831
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
+ }
7832
8872
  function outputReviewLedger(ledger, options) {
7833
8873
  if (options.json) {
7834
8874
  console.log(JSON.stringify(ledger, null, 2));
7835
8875
  return;
7836
8876
  }
7837
8877
  if (!ledger) return;
8878
+ if (ledger.skipped) {
8879
+ console.log(`Review: skipped; reason ${ledger.skipped.reason}`);
8880
+ return;
8881
+ }
7838
8882
  const openBlockers = ledger.blockers.filter(
7839
8883
  (blocker) => blocker.status === "open"
7840
8884
  );
@@ -7865,7 +8909,8 @@ async function workflowCreate(rootDir, args, options) {
7865
8909
  }
7866
8910
  try {
7867
8911
  const meta = await createWorkflow(rootDir, task, mode, {
7868
- parentTaskId: options.parentTask
8912
+ parentTaskId: options.parentTask,
8913
+ planningPolicyVersion: mode === "man" || mode === "manteam" ? 2 : void 0
7869
8914
  });
7870
8915
  if (options.json) {
7871
8916
  console.log(JSON.stringify(meta, null, 2));
@@ -7885,7 +8930,7 @@ async function workflowUpdate(rootDir, taskId, options) {
7885
8930
  return invalidArg(
7886
8931
  options,
7887
8932
  "missing taskId",
7888
- "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]"
7889
8934
  );
7890
8935
  }
7891
8936
  if (!isValidWorkflowTaskId(taskId)) {
@@ -7946,8 +8991,45 @@ async function workflowUpdate(rootDir, taskId, options) {
7946
8991
  }
7947
8992
  patch.planVersion = planVersion;
7948
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
+ }
7949
9012
  if (options.skipped !== void 0) {
7950
- 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
+ }
7951
9033
  }
7952
9034
  if (Object.keys(patch).length === 0) {
7953
9035
  return invalidArg(options, "missing update fields");
@@ -7978,6 +9060,307 @@ async function workflowUpdate(rootDir, taskId, options) {
7978
9060
  }
7979
9061
  return EXIT_OK9;
7980
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
+ }
7981
9364
  async function workflowList(rootDir, options) {
7982
9365
  const workflows = await listWorkflows(rootDir);
7983
9366
  const views = attachActiveChildren(workflows);
@@ -8049,6 +9432,11 @@ async function workflowShow(rootDir, taskId, options) {
8049
9432
  if (meta.parentTaskId) console.log(`Parent: ${meta.parentTaskId}`);
8050
9433
  if (meta.planVersion !== void 0)
8051
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}`);
8052
9440
  if (meta.outcome) console.log(`Outcome: ${meta.outcome}`);
8053
9441
  if (meta.blockingReason) console.log(`Blocked: ${meta.blockingReason}`);
8054
9442
  if (children !== void 0) {
@@ -8167,6 +9555,11 @@ function parseExactPositiveInteger(value) {
8167
9555
  const parsed = Number(value);
8168
9556
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
8169
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
+ }
8170
9563
  function parseOlderThan(value) {
8171
9564
  if (!value) return void 0;
8172
9565
  const match = /^(\d+)([dhm])$/.exec(value);
@@ -8241,13 +9634,22 @@ program.command("uninstall [platform]").description("Remove platform adapter or
8241
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(
8242
9635
  "--parent-task <taskId>",
8243
9636
  "Parent /man or /manteam workflow for manba"
8244
- ).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(
8245
9647
  "--report <path>",
8246
9648
  "Relative Markdown report path for a review domain"
8247
9649
  ).option("--blockers <ids>", "Comma-separated blocker ids found by a review").option(
8248
9650
  "--resolved <ids>",
8249
9651
  "Comma-separated blocker ids resolved in remediation"
8250
- ).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) => {
8251
9653
  const code = await workflow(process.cwd(), subcommand, args ?? [], options);
8252
9654
  process.exitCode = code;
8253
9655
  });