mancode 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +710 -0
- package/README.md +308 -350
- package/dist/cli.js +1502 -126
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
- package/README.zh-CN.md +0 -621
package/dist/cli.js
CHANGED
|
@@ -108,7 +108,7 @@ var PLAN_COACH_AGENT = {
|
|
|
108
108
|
tools: ["Read", "Grep", "Glob"],
|
|
109
109
|
body: `\u4F60\u662F mancode \u6559\u7EC3\u7EC4\u7684 Plan Coach\uFF08\u8BA1\u5212\u6559\u7EC3\uFF09\u3002
|
|
110
110
|
|
|
111
|
-
\u4F60\u7684\u804C\u8D23\uFF1A\u5728\u7528\u6237\u786E\u8BA4\u524D\
|
|
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
|
-
- \
|
|
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\
|
|
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 (
|
|
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: "
|
|
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
|
-
|
|
882
|
+
## \u8BA1\u5212\u804C\u8D23\u4E0E\u6280\u672F\u9009\u62E9\u5173\u5361
|
|
822
883
|
|
|
823
|
-
|
|
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\
|
|
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
|
-
\
|
|
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
|
|
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. **\
|
|
843
|
-
2. **\u7EE7\u7EED\
|
|
844
|
-
3. **\
|
|
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\
|
|
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
|
-
\
|
|
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\
|
|
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\
|
|
1194
|
-
3. Game Plan\
|
|
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\
|
|
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\
|
|
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: "
|
|
2168
|
-
intro: "# mancode man \u2014
|
|
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
|
|
2181
|
-
"Step 3 \u2014 Plan
|
|
2182
|
-
"Step 4 \u2014 Plan gate: the user
|
|
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
|
|
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
|
|
2187
|
-
"Step 9 \u2014 If open blockers exist, fix them in one remediation round and record resolved IDs through `workflow review ... remediate`;
|
|
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
|
-
"
|
|
2268
|
+
"A solo handoff keeps the workflow planned and stores activeSoloPlan in state; solo reads requirements.md and plan.md, implements the confirmed scope with narrow validation and one bounded self-check, and does not run the /man reviewers. After successful solo verification, run `mancode workflow handoff <taskId> --complete` to atomically clear the pointer, complete the workflow at Step 4, and remove it from Active Plans. After plan-only/completed/abandoned, return state to solo. On blocked, preserve enough state to resume. A CLI rejection is a hard stop, never a reason to bypass metadata validation."
|
|
2190
2269
|
].join("\n")
|
|
2191
2270
|
},
|
|
2192
2271
|
manteam: {
|
|
@@ -2288,6 +2367,9 @@ function renderProjectContext(state, tokens, profile, options) {
|
|
|
2288
2367
|
`- UI library: ${uiLibrary}`,
|
|
2289
2368
|
`- Project profile: ${currentProfile?.projectKind || "unknown"}; validation: ${currentProfile?.availableValidation?.join(", ") || "inspect project"}`,
|
|
2290
2369
|
"- At the start of each session, read `.mancode/state.json` to check the current mode and project context.",
|
|
2370
|
+
...state?.activeSoloPlan?.taskId ? [
|
|
2371
|
+
`- Active solo plan: ${state.activeSoloPlan.taskId} (plan v${state.activeSoloPlan.planVersion ?? 1}); read its requirements.md and plan.md before implementation.`
|
|
2372
|
+
] : [],
|
|
2291
2373
|
"- Read `.mancode/project-profile.json` before choosing tools or validation. Only for a UI task in a profile with detected UI assets, read `.mancode/aesthetics/style-tokens.json`."
|
|
2292
2374
|
].join("\n");
|
|
2293
2375
|
}
|
|
@@ -4382,6 +4464,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4382
4464
|
currentTask: null,
|
|
4383
4465
|
currentWorkflowMode: null,
|
|
4384
4466
|
skippedSteps: [],
|
|
4467
|
+
activeSoloPlan: null,
|
|
4385
4468
|
teamModeAutoDetected: teamModeEnabled,
|
|
4386
4469
|
contributors: team.contributors,
|
|
4387
4470
|
projectMode: isGenericProject ? "generic" : "detected"
|
|
@@ -6584,23 +6667,225 @@ async function pathExists9(p) {
|
|
|
6584
6667
|
// src/commands/status.ts
|
|
6585
6668
|
import { spawn } from "child_process";
|
|
6586
6669
|
import { promises as fs9 } from "fs";
|
|
6587
|
-
import
|
|
6670
|
+
import path26 from "path";
|
|
6588
6671
|
import process10 from "process";
|
|
6589
6672
|
|
|
6590
6673
|
// src/system/workflow.ts
|
|
6591
6674
|
import {
|
|
6592
6675
|
mkdir as mkdir9,
|
|
6593
|
-
readFile as
|
|
6676
|
+
readFile as readFile12,
|
|
6594
6677
|
readdir as readdir5,
|
|
6595
6678
|
rm as rm4,
|
|
6596
6679
|
stat as stat4,
|
|
6597
|
-
writeFile as
|
|
6680
|
+
writeFile as writeFile13
|
|
6598
6681
|
} from "fs/promises";
|
|
6599
|
-
import
|
|
6682
|
+
import path25 from "path";
|
|
6600
6683
|
|
|
6601
|
-
// src/system/
|
|
6602
|
-
import {
|
|
6684
|
+
// src/system/requirements-ledger.ts
|
|
6685
|
+
import { createHash as createHash2 } from "crypto";
|
|
6686
|
+
import { readFile as readFile9, writeFile as writeFile10 } from "fs/promises";
|
|
6603
6687
|
import path22 from "path";
|
|
6688
|
+
var REQUIREMENTS_FILE = "requirements.json";
|
|
6689
|
+
var REQUIREMENTS_MARKDOWN_FILE = "requirements.md";
|
|
6690
|
+
var ACCEPTANCE_ID_PATTERN = /^AC-[A-Z0-9][A-Z0-9-]{0,27}$/;
|
|
6691
|
+
var REQUIREMENT_DIMENSIONS = [
|
|
6692
|
+
"platform",
|
|
6693
|
+
"core_scope",
|
|
6694
|
+
"technical_stack",
|
|
6695
|
+
"data_and_persistence",
|
|
6696
|
+
"performance",
|
|
6697
|
+
"compatibility",
|
|
6698
|
+
"security"
|
|
6699
|
+
];
|
|
6700
|
+
function parseRequirementsLedger(raw) {
|
|
6701
|
+
let value;
|
|
6702
|
+
try {
|
|
6703
|
+
value = JSON.parse(raw);
|
|
6704
|
+
} catch {
|
|
6705
|
+
throw new Error("requirements input must be valid JSON");
|
|
6706
|
+
}
|
|
6707
|
+
if (!isRecord8(value) || value.version !== 1) {
|
|
6708
|
+
throw new Error("requirements version must be 1");
|
|
6709
|
+
}
|
|
6710
|
+
const stringFields = [
|
|
6711
|
+
"confirmedScope",
|
|
6712
|
+
"excludedScope",
|
|
6713
|
+
"technicalDecisions",
|
|
6714
|
+
"defaults",
|
|
6715
|
+
"blockingUnknowns"
|
|
6716
|
+
];
|
|
6717
|
+
if (typeof value.goal !== "string" || !value.goal.trim()) {
|
|
6718
|
+
throw new Error("requirements goal is required");
|
|
6719
|
+
}
|
|
6720
|
+
for (const field of stringFields) {
|
|
6721
|
+
if (!isNonEmptyStringArray(value[field])) {
|
|
6722
|
+
if (!Array.isArray(value[field]) || value[field].length > 0) {
|
|
6723
|
+
throw new Error(`requirements ${field} must contain non-empty strings`);
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
if (!Array.isArray(value.coverage)) {
|
|
6728
|
+
throw new Error("requirements coverage is required");
|
|
6729
|
+
}
|
|
6730
|
+
const coverageDimensions = /* @__PURE__ */ new Set();
|
|
6731
|
+
const coverage = value.coverage.map((item) => {
|
|
6732
|
+
if (!isRecord8(item) || !isRequirementDimension(item.dimension) || !isCoverageStatus(item.status) || typeof item.rationale !== "string" || !item.rationale.trim()) {
|
|
6733
|
+
throw new Error("invalid requirements coverage item");
|
|
6734
|
+
}
|
|
6735
|
+
if (coverageDimensions.has(item.dimension)) {
|
|
6736
|
+
throw new Error(`duplicate requirements coverage: ${item.dimension}`);
|
|
6737
|
+
}
|
|
6738
|
+
coverageDimensions.add(item.dimension);
|
|
6739
|
+
return {
|
|
6740
|
+
dimension: item.dimension,
|
|
6741
|
+
status: item.status,
|
|
6742
|
+
rationale: item.rationale.trim()
|
|
6743
|
+
};
|
|
6744
|
+
});
|
|
6745
|
+
const missingDimensions = REQUIREMENT_DIMENSIONS.filter(
|
|
6746
|
+
(dimension) => !coverageDimensions.has(dimension)
|
|
6747
|
+
);
|
|
6748
|
+
if (missingDimensions.length > 0) {
|
|
6749
|
+
throw new Error(
|
|
6750
|
+
`requirements coverage is missing: ${missingDimensions.join(", ")}`
|
|
6751
|
+
);
|
|
6752
|
+
}
|
|
6753
|
+
if (value.confirmedScope.length === 0) {
|
|
6754
|
+
throw new Error("requirements confirmedScope must not be empty");
|
|
6755
|
+
}
|
|
6756
|
+
const stackCoverage = coverage.find(
|
|
6757
|
+
(item) => item.dimension === "technical_stack"
|
|
6758
|
+
);
|
|
6759
|
+
if (stackCoverage?.status !== "not_applicable" && value.technicalDecisions.length === 0) {
|
|
6760
|
+
throw new Error(
|
|
6761
|
+
"requirements technicalDecisions are required when technical_stack applies"
|
|
6762
|
+
);
|
|
6763
|
+
}
|
|
6764
|
+
if (!Array.isArray(value.acceptanceCriteria) || value.acceptanceCriteria.length === 0) {
|
|
6765
|
+
throw new Error("requirements need at least one acceptance criterion");
|
|
6766
|
+
}
|
|
6767
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6768
|
+
const acceptanceCriteria = value.acceptanceCriteria.map((item) => {
|
|
6769
|
+
if (!isRecord8(item) || typeof item.id !== "string" || !ACCEPTANCE_ID_PATTERN.test(item.id) || typeof item.description !== "string" || !item.description.trim() || typeof item.required !== "boolean" || !isVerificationMethod(item.method)) {
|
|
6770
|
+
throw new Error("invalid acceptance criterion");
|
|
6771
|
+
}
|
|
6772
|
+
if (ids.has(item.id)) {
|
|
6773
|
+
throw new Error(`duplicate acceptance criterion: ${item.id}`);
|
|
6774
|
+
}
|
|
6775
|
+
ids.add(item.id);
|
|
6776
|
+
return {
|
|
6777
|
+
id: item.id,
|
|
6778
|
+
description: item.description.trim(),
|
|
6779
|
+
required: item.required,
|
|
6780
|
+
method: item.method
|
|
6781
|
+
};
|
|
6782
|
+
});
|
|
6783
|
+
if (!acceptanceCriteria.some((item) => item.required)) {
|
|
6784
|
+
throw new Error(
|
|
6785
|
+
"requirements need at least one required acceptance criterion"
|
|
6786
|
+
);
|
|
6787
|
+
}
|
|
6788
|
+
return {
|
|
6789
|
+
version: 1,
|
|
6790
|
+
goal: value.goal.trim(),
|
|
6791
|
+
confirmedScope: normalizeStrings(value.confirmedScope),
|
|
6792
|
+
excludedScope: normalizeStrings(value.excludedScope),
|
|
6793
|
+
technicalDecisions: normalizeStrings(value.technicalDecisions),
|
|
6794
|
+
defaults: normalizeStrings(value.defaults),
|
|
6795
|
+
blockingUnknowns: normalizeStrings(value.blockingUnknowns),
|
|
6796
|
+
coverage,
|
|
6797
|
+
acceptanceCriteria
|
|
6798
|
+
};
|
|
6799
|
+
}
|
|
6800
|
+
function requirementsAreReady(ledger) {
|
|
6801
|
+
return ledger.blockingUnknowns.length === 0;
|
|
6802
|
+
}
|
|
6803
|
+
function requirementsDigest(ledger) {
|
|
6804
|
+
return createHash2("sha256").update(JSON.stringify(ledger)).digest("hex");
|
|
6805
|
+
}
|
|
6806
|
+
async function readRequirementsLedger(projectRoot, taskId) {
|
|
6807
|
+
try {
|
|
6808
|
+
const raw = await readFile9(requirementsPath(projectRoot, taskId), "utf-8");
|
|
6809
|
+
return parseRequirementsLedger(raw);
|
|
6810
|
+
} catch {
|
|
6811
|
+
return null;
|
|
6812
|
+
}
|
|
6813
|
+
}
|
|
6814
|
+
async function writeRequirementsArtifacts(projectRoot, taskId, ledger) {
|
|
6815
|
+
const dir = workflowDir(projectRoot, taskId);
|
|
6816
|
+
await writeFile10(
|
|
6817
|
+
path22.join(dir, REQUIREMENTS_FILE),
|
|
6818
|
+
`${JSON.stringify(ledger, null, 2)}
|
|
6819
|
+
`,
|
|
6820
|
+
"utf-8"
|
|
6821
|
+
);
|
|
6822
|
+
await writeFile10(
|
|
6823
|
+
path22.join(dir, REQUIREMENTS_MARKDOWN_FILE),
|
|
6824
|
+
renderRequirementsMarkdown(ledger),
|
|
6825
|
+
"utf-8"
|
|
6826
|
+
);
|
|
6827
|
+
}
|
|
6828
|
+
function renderRequirementsMarkdown(ledger) {
|
|
6829
|
+
const section = (title, items) => `## ${title}
|
|
6830
|
+
|
|
6831
|
+
${items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : "- \u65E0"}
|
|
6832
|
+
`;
|
|
6833
|
+
const criteria = ledger.acceptanceCriteria.map(
|
|
6834
|
+
(item) => `- **${item.id}** [${item.required ? "\u5FC5\u9700" : "\u53EF\u9009"} / ${item.method}] ${item.description}`
|
|
6835
|
+
).join("\n");
|
|
6836
|
+
const coverage = ledger.coverage.map((item) => `- **${item.dimension}** [${item.status}] ${item.rationale}`).join("\n");
|
|
6837
|
+
return `# Requirements
|
|
6838
|
+
|
|
6839
|
+
## \u7528\u6237\u76EE\u6807
|
|
6840
|
+
|
|
6841
|
+
${ledger.goal}
|
|
6842
|
+
|
|
6843
|
+
${section("\u5DF2\u786E\u8BA4\u8303\u56F4", ledger.confirmedScope)}
|
|
6844
|
+
${section("\u660E\u786E\u6392\u9664\u9879", ledger.excludedScope)}
|
|
6845
|
+
${section("\u6280\u672F\u51B3\u7B56", ledger.technicalDecisions)}
|
|
6846
|
+
${section("\u9ED8\u8BA4\u503C\u4E0E\u7406\u7531", ledger.defaults)}
|
|
6847
|
+
## \u9700\u6C42\u8986\u76D6
|
|
6848
|
+
|
|
6849
|
+
${coverage}
|
|
6850
|
+
|
|
6851
|
+
## \u9A8C\u6536\u6807\u51C6
|
|
6852
|
+
|
|
6853
|
+
${criteria}
|
|
6854
|
+
|
|
6855
|
+
${section("\u963B\u585E\u6027\u672A\u51B3\u95EE\u9898", ledger.blockingUnknowns)}
|
|
6856
|
+
## Readiness
|
|
6857
|
+
|
|
6858
|
+
${requirementsAreReady(ledger) ? "READY" : "NEEDS_CLARIFICATION"}
|
|
6859
|
+
`;
|
|
6860
|
+
}
|
|
6861
|
+
function requirementsPath(projectRoot, taskId) {
|
|
6862
|
+
return path22.join(workflowDir(projectRoot, taskId), REQUIREMENTS_FILE);
|
|
6863
|
+
}
|
|
6864
|
+
function workflowDir(projectRoot, taskId) {
|
|
6865
|
+
return path22.join(projectRoot, ".mancode", "workflows", taskId);
|
|
6866
|
+
}
|
|
6867
|
+
function normalizeStrings(value) {
|
|
6868
|
+
return value.map((item) => item.trim());
|
|
6869
|
+
}
|
|
6870
|
+
function isNonEmptyStringArray(value) {
|
|
6871
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string" && item.trim().length > 0);
|
|
6872
|
+
}
|
|
6873
|
+
function isVerificationMethod(value) {
|
|
6874
|
+
return value === "automated" || value === "manual" || value === "hybrid";
|
|
6875
|
+
}
|
|
6876
|
+
function isRequirementDimension(value) {
|
|
6877
|
+
return REQUIREMENT_DIMENSIONS.some((dimension) => dimension === value);
|
|
6878
|
+
}
|
|
6879
|
+
function isCoverageStatus(value) {
|
|
6880
|
+
return value === "confirmed" || value === "defaulted" || value === "not_applicable";
|
|
6881
|
+
}
|
|
6882
|
+
function isRecord8(value) {
|
|
6883
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6884
|
+
}
|
|
6885
|
+
|
|
6886
|
+
// src/system/review-ledger.ts
|
|
6887
|
+
import { mkdir as mkdir8, readFile as readFile10, stat as stat3, writeFile as writeFile11 } from "fs/promises";
|
|
6888
|
+
import path23 from "path";
|
|
6604
6889
|
var REVIEW_FILE = "review-ledger.json";
|
|
6605
6890
|
var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
6606
6891
|
var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
|
|
@@ -6629,6 +6914,27 @@ async function initializeReview(projectRoot, taskId, depth, targetDomain) {
|
|
|
6629
6914
|
await writeReviewLedger(projectRoot, taskId, ledger);
|
|
6630
6915
|
return ledger;
|
|
6631
6916
|
}
|
|
6917
|
+
async function initializeSkippedReview(projectRoot, taskId, reason) {
|
|
6918
|
+
assertValidTaskId(taskId);
|
|
6919
|
+
if (!reason.trim()) throw new Error("review skip reason is required");
|
|
6920
|
+
const existing = await readReviewLedger(projectRoot, taskId);
|
|
6921
|
+
if (existing) throw new Error(`review already initialized: ${taskId}`);
|
|
6922
|
+
const ledger = {
|
|
6923
|
+
version: "1.0",
|
|
6924
|
+
depth: "targeted",
|
|
6925
|
+
requiredDomains: [],
|
|
6926
|
+
completedDomains: [],
|
|
6927
|
+
reports: {},
|
|
6928
|
+
blockers: [],
|
|
6929
|
+
remediationRounds: 0,
|
|
6930
|
+
skipped: {
|
|
6931
|
+
reason: reason.trim(),
|
|
6932
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6933
|
+
}
|
|
6934
|
+
};
|
|
6935
|
+
await writeReviewLedger(projectRoot, taskId, ledger);
|
|
6936
|
+
return ledger;
|
|
6937
|
+
}
|
|
6632
6938
|
async function completeReviewDomain(projectRoot, taskId, domain, report, blockerIds) {
|
|
6633
6939
|
const ledger = await requireReviewLedger(projectRoot, taskId);
|
|
6634
6940
|
if (!ledger.requiredDomains.includes(domain)) {
|
|
@@ -6701,6 +7007,7 @@ async function remediateReviewBlockers(projectRoot, taskId, resolvedIds) {
|
|
|
6701
7007
|
async function reviewCanComplete(projectRoot, taskId) {
|
|
6702
7008
|
const ledger = await readReviewLedger(projectRoot, taskId);
|
|
6703
7009
|
if (!ledger) return false;
|
|
7010
|
+
if (ledger.skipped) return true;
|
|
6704
7011
|
return ledger.requiredDomains.every(
|
|
6705
7012
|
(domain) => ledger.completedDomains.includes(domain)
|
|
6706
7013
|
) && ledger.blockers.every((blocker) => blocker.status === "resolved");
|
|
@@ -6708,7 +7015,7 @@ async function reviewCanComplete(projectRoot, taskId) {
|
|
|
6708
7015
|
async function readReviewLedger(projectRoot, taskId) {
|
|
6709
7016
|
if (!TASK_ID_PATTERN.test(taskId)) return null;
|
|
6710
7017
|
try {
|
|
6711
|
-
const raw = await
|
|
7018
|
+
const raw = await readFile10(reviewPath(projectRoot, taskId), "utf-8");
|
|
6712
7019
|
const value = JSON.parse(raw);
|
|
6713
7020
|
return isReviewLedger(value) ? value : null;
|
|
6714
7021
|
} catch {
|
|
@@ -6716,8 +7023,8 @@ async function readReviewLedger(projectRoot, taskId) {
|
|
|
6716
7023
|
}
|
|
6717
7024
|
}
|
|
6718
7025
|
function isReviewLedger(value) {
|
|
6719
|
-
if (!
|
|
6720
|
-
if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !
|
|
7026
|
+
if (!isRecord9(value)) return false;
|
|
7027
|
+
if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord9(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
|
|
6721
7028
|
return false;
|
|
6722
7029
|
}
|
|
6723
7030
|
const requiredDomains = value.requiredDomains;
|
|
@@ -6725,6 +7032,9 @@ function isReviewLedger(value) {
|
|
|
6725
7032
|
const reports = value.reports;
|
|
6726
7033
|
const blockers = value.blockers;
|
|
6727
7034
|
const expectedDomains = value.depth === "full" ? ["quality", "security"] : requiredDomains;
|
|
7035
|
+
if (value.skipped !== void 0) {
|
|
7036
|
+
return isRecord9(value.skipped) && typeof value.skipped.reason === "string" && value.skipped.reason.trim().length > 0 && typeof value.skipped.recordedAt === "string" && requiredDomains.length === 0 && completedDomains.length === 0 && Object.keys(reports).length === 0 && blockers.length === 0 && value.remediationRounds === 0;
|
|
7037
|
+
}
|
|
6728
7038
|
if (value.depth === "targeted" && requiredDomains.length !== 1 || requiredDomains.length !== expectedDomains.length || !expectedDomains.every((domain) => requiredDomains.includes(domain)) || new Set(completedDomains).size !== completedDomains.length || completedDomains.some((domain) => !requiredDomains.includes(domain)) || completedDomains.some((domain) => {
|
|
6729
7039
|
const report = reports[domain];
|
|
6730
7040
|
return typeof report !== "string" || !isSafeReportPath(report);
|
|
@@ -6733,13 +7043,13 @@ function isReviewLedger(value) {
|
|
|
6733
7043
|
}
|
|
6734
7044
|
const blockerIds = /* @__PURE__ */ new Set();
|
|
6735
7045
|
return blockers.every(
|
|
6736
|
-
(blocker) =>
|
|
7046
|
+
(blocker) => isRecord9(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
|
|
6737
7047
|
);
|
|
6738
7048
|
}
|
|
6739
7049
|
function isDomainArray(value) {
|
|
6740
7050
|
return Array.isArray(value) && value.every(isReviewDomain);
|
|
6741
7051
|
}
|
|
6742
|
-
function
|
|
7052
|
+
function isRecord9(value) {
|
|
6743
7053
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6744
7054
|
}
|
|
6745
7055
|
async function requireReviewLedger(projectRoot, taskId) {
|
|
@@ -6749,17 +7059,21 @@ async function requireReviewLedger(projectRoot, taskId) {
|
|
|
6749
7059
|
return ledger;
|
|
6750
7060
|
}
|
|
6751
7061
|
async function writeReviewLedger(projectRoot, taskId, ledger) {
|
|
6752
|
-
const dir =
|
|
7062
|
+
const dir = path23.join(projectRoot, ".mancode", "workflows", taskId);
|
|
6753
7063
|
await mkdir8(dir, { recursive: true });
|
|
6754
|
-
await
|
|
6755
|
-
|
|
7064
|
+
await writeFile11(
|
|
7065
|
+
path23.join(dir, REVIEW_FILE),
|
|
6756
7066
|
`${JSON.stringify(ledger, null, 2)}
|
|
6757
7067
|
`,
|
|
6758
7068
|
"utf-8"
|
|
6759
7069
|
);
|
|
6760
7070
|
}
|
|
7071
|
+
function reviewLedgerPath(projectRoot, taskId) {
|
|
7072
|
+
assertValidTaskId(taskId);
|
|
7073
|
+
return reviewPath(projectRoot, taskId);
|
|
7074
|
+
}
|
|
6761
7075
|
function reviewPath(projectRoot, taskId) {
|
|
6762
|
-
return
|
|
7076
|
+
return path23.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
|
|
6763
7077
|
}
|
|
6764
7078
|
function assertValidTaskId(taskId) {
|
|
6765
7079
|
if (!TASK_ID_PATTERN.test(taskId)) {
|
|
@@ -6772,7 +7086,7 @@ function assertSafeReportPath(report) {
|
|
|
6772
7086
|
}
|
|
6773
7087
|
}
|
|
6774
7088
|
async function assertReportExists(projectRoot, taskId, report) {
|
|
6775
|
-
const reportPath =
|
|
7089
|
+
const reportPath = path23.join(
|
|
6776
7090
|
projectRoot,
|
|
6777
7091
|
".mancode",
|
|
6778
7092
|
"workflows",
|
|
@@ -6786,11 +7100,221 @@ async function assertReportExists(projectRoot, taskId, report) {
|
|
|
6786
7100
|
throw new Error(`review report not found: ${report}`);
|
|
6787
7101
|
}
|
|
6788
7102
|
function isSafeReportPath(report) {
|
|
6789
|
-
const normalized =
|
|
7103
|
+
const normalized = path23.posix.normalize(report.replaceAll("\\", "/"));
|
|
7104
|
+
return Boolean(
|
|
7105
|
+
report.trim() && !path23.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
|
|
7106
|
+
);
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7109
|
+
// src/system/verification-ledger.ts
|
|
7110
|
+
import { readFile as readFile11, writeFile as writeFile12 } from "fs/promises";
|
|
7111
|
+
import path24 from "path";
|
|
7112
|
+
var VERIFICATION_FILE = "verification-ledger.json";
|
|
7113
|
+
function initializeVerificationLedger(requirements, planVersion, remediationRound = 0) {
|
|
7114
|
+
const checks = requirements.acceptanceCriteria.map((criterion) => ({
|
|
7115
|
+
acceptanceId: criterion.id,
|
|
7116
|
+
required: criterion.required,
|
|
7117
|
+
...criterion.method === "automated" || criterion.method === "hybrid" ? { automated: { status: "pending" } } : {},
|
|
7118
|
+
...criterion.method === "manual" || criterion.method === "hybrid" ? { manual: { status: "pending" } } : {}
|
|
7119
|
+
}));
|
|
7120
|
+
return {
|
|
7121
|
+
version: 1,
|
|
7122
|
+
planVersion,
|
|
7123
|
+
requirementsDigest: requirementsDigest(requirements),
|
|
7124
|
+
remediationRound,
|
|
7125
|
+
status: deriveVerificationStatus(checks),
|
|
7126
|
+
checks
|
|
7127
|
+
};
|
|
7128
|
+
}
|
|
7129
|
+
function recordVerification(ledger, acceptanceId, component, result, evidence, automatedDetails) {
|
|
7130
|
+
if (!evidence.trim()) throw new Error("verification evidence is required");
|
|
7131
|
+
if (component === "automated" && result === "manual_required") {
|
|
7132
|
+
throw new Error("automated verification cannot be manual_required");
|
|
7133
|
+
}
|
|
7134
|
+
if (component === "automated" && (result === "passed" || result === "failed")) {
|
|
7135
|
+
if (!automatedDetails?.command.trim() || !Number.isInteger(automatedDetails.exitCode)) {
|
|
7136
|
+
throw new Error(
|
|
7137
|
+
"automated passed/failed verification requires command and exit code"
|
|
7138
|
+
);
|
|
7139
|
+
}
|
|
7140
|
+
if (result === "passed" && automatedDetails.exitCode !== 0) {
|
|
7141
|
+
throw new Error("passed verification requires exit code 0");
|
|
7142
|
+
}
|
|
7143
|
+
if (result === "failed" && automatedDetails.exitCode === 0) {
|
|
7144
|
+
throw new Error("failed verification requires a non-zero exit code");
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
if (component === "manual" && result !== "manual_required") {
|
|
7148
|
+
throw new Error(
|
|
7149
|
+
"manual verification must use require-manual or confirm-manual"
|
|
7150
|
+
);
|
|
7151
|
+
}
|
|
7152
|
+
const index = ledger.checks.findIndex(
|
|
7153
|
+
(check2) => check2.acceptanceId === acceptanceId
|
|
7154
|
+
);
|
|
7155
|
+
if (index < 0)
|
|
7156
|
+
throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
|
|
7157
|
+
const check = ledger.checks[index];
|
|
7158
|
+
if (!check) throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
|
|
7159
|
+
if (!check[component]) {
|
|
7160
|
+
throw new Error(
|
|
7161
|
+
`${acceptanceId} does not require ${component} verification`
|
|
7162
|
+
);
|
|
7163
|
+
}
|
|
7164
|
+
const checks = [...ledger.checks];
|
|
7165
|
+
checks[index] = {
|
|
7166
|
+
...check,
|
|
7167
|
+
[component]: {
|
|
7168
|
+
status: result,
|
|
7169
|
+
evidence: evidence.trim(),
|
|
7170
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7171
|
+
...component === "automated" && automatedDetails ? {
|
|
7172
|
+
command: automatedDetails.command.trim(),
|
|
7173
|
+
exitCode: automatedDetails.exitCode,
|
|
7174
|
+
...automatedDetails.evidenceFile ? { evidenceFile: automatedDetails.evidenceFile } : {}
|
|
7175
|
+
} : {}
|
|
7176
|
+
}
|
|
7177
|
+
};
|
|
7178
|
+
return { ...ledger, status: deriveVerificationStatus(checks), checks };
|
|
7179
|
+
}
|
|
7180
|
+
function resetVerificationForRemediation(ledger, remediationRound) {
|
|
7181
|
+
if (!Number.isInteger(remediationRound) || remediationRound < ledger.remediationRound) {
|
|
7182
|
+
throw new Error("invalid verification remediation round");
|
|
7183
|
+
}
|
|
7184
|
+
if (remediationRound === ledger.remediationRound) return ledger;
|
|
7185
|
+
const checks = ledger.checks.map((check) => ({
|
|
7186
|
+
acceptanceId: check.acceptanceId,
|
|
7187
|
+
required: check.required,
|
|
7188
|
+
...check.automated ? { automated: { status: "pending" } } : {},
|
|
7189
|
+
...check.manual ? { manual: { status: "pending" } } : {}
|
|
7190
|
+
}));
|
|
7191
|
+
return {
|
|
7192
|
+
...ledger,
|
|
7193
|
+
remediationRound,
|
|
7194
|
+
status: deriveVerificationStatus(checks),
|
|
7195
|
+
checks
|
|
7196
|
+
};
|
|
7197
|
+
}
|
|
7198
|
+
function confirmManualVerification(ledger, acceptanceId, evidence) {
|
|
7199
|
+
if (!evidence.trim())
|
|
7200
|
+
throw new Error("manual confirmation evidence is required");
|
|
7201
|
+
const index = ledger.checks.findIndex(
|
|
7202
|
+
(check2) => check2.acceptanceId === acceptanceId
|
|
7203
|
+
);
|
|
7204
|
+
if (index < 0)
|
|
7205
|
+
throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
|
|
7206
|
+
const check = ledger.checks[index];
|
|
7207
|
+
if (!check) throw new Error(`unknown acceptance criterion: ${acceptanceId}`);
|
|
7208
|
+
if (!check.manual) {
|
|
7209
|
+
throw new Error(`${acceptanceId} does not require manual verification`);
|
|
7210
|
+
}
|
|
7211
|
+
if (check.manual.status !== "manual_required") {
|
|
7212
|
+
throw new Error(`${acceptanceId} is not awaiting manual confirmation`);
|
|
7213
|
+
}
|
|
7214
|
+
const checks = [...ledger.checks];
|
|
7215
|
+
checks[index] = {
|
|
7216
|
+
...check,
|
|
7217
|
+
manual: {
|
|
7218
|
+
status: "passed",
|
|
7219
|
+
evidence: evidence.trim(),
|
|
7220
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7221
|
+
}
|
|
7222
|
+
};
|
|
7223
|
+
return { ...ledger, status: deriveVerificationStatus(checks), checks };
|
|
7224
|
+
}
|
|
7225
|
+
function deriveVerificationStatus(checks) {
|
|
7226
|
+
const statuses = checks.filter((check) => check.required).flatMap((check) => [check.automated?.status, check.manual?.status]).filter((status2) => Boolean(status2));
|
|
7227
|
+
if (statuses.includes("blocked")) return "blocked";
|
|
7228
|
+
if (statuses.includes("manual_required")) return "manual_required";
|
|
7229
|
+
if (statuses.includes("failed")) return "failed";
|
|
7230
|
+
if (statuses.every((status2) => status2 === "passed")) return "passed";
|
|
7231
|
+
return "pending";
|
|
7232
|
+
}
|
|
7233
|
+
async function readVerificationLedger(projectRoot, taskId) {
|
|
7234
|
+
try {
|
|
7235
|
+
const raw = await readFile11(verificationPath(projectRoot, taskId), "utf-8");
|
|
7236
|
+
const value = JSON.parse(raw);
|
|
7237
|
+
return isVerificationLedger(value) ? value : null;
|
|
7238
|
+
} catch {
|
|
7239
|
+
return null;
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
async function writeVerificationLedger(projectRoot, taskId, ledger) {
|
|
7243
|
+
await writeFile12(
|
|
7244
|
+
verificationPath(projectRoot, taskId),
|
|
7245
|
+
`${JSON.stringify(ledger, null, 2)}
|
|
7246
|
+
`,
|
|
7247
|
+
"utf-8"
|
|
7248
|
+
);
|
|
7249
|
+
}
|
|
7250
|
+
async function verificationCanAdvance(projectRoot, taskId, planVersion) {
|
|
7251
|
+
const [ledger, requirements, review] = await Promise.all([
|
|
7252
|
+
readVerificationLedger(projectRoot, taskId),
|
|
7253
|
+
readRequirementsLedger(projectRoot, taskId),
|
|
7254
|
+
readReviewLedger(projectRoot, taskId)
|
|
7255
|
+
]);
|
|
6790
7256
|
return Boolean(
|
|
6791
|
-
|
|
7257
|
+
ledger && requirements && ledger.status === "passed" && ledger.planVersion === planVersion && ledger.remediationRound === (review?.remediationRounds ?? 0) && ledger.requirementsDigest === requirementsDigest(requirements) && ledgerMatchesRequirements(ledger, requirements)
|
|
7258
|
+
);
|
|
7259
|
+
}
|
|
7260
|
+
function verificationLedgerPath(projectRoot, taskId) {
|
|
7261
|
+
return verificationPath(projectRoot, taskId);
|
|
7262
|
+
}
|
|
7263
|
+
function verificationPath(projectRoot, taskId) {
|
|
7264
|
+
return path24.join(
|
|
7265
|
+
projectRoot,
|
|
7266
|
+
".mancode",
|
|
7267
|
+
"workflows",
|
|
7268
|
+
taskId,
|
|
7269
|
+
VERIFICATION_FILE
|
|
6792
7270
|
);
|
|
6793
7271
|
}
|
|
7272
|
+
function isVerificationLedger(value) {
|
|
7273
|
+
if (typeof value !== "object" || value === null || !("version" in value) || value.version !== 1 || !("planVersion" in value) || !Number.isInteger(value.planVersion) || !("requirementsDigest" in value) || typeof value.requirementsDigest !== "string" || !("remediationRound" in value) || typeof value.remediationRound !== "number" || !Number.isInteger(value.remediationRound) || value.remediationRound < 0 || !("status" in value) || !isComponentStatus(value.status) || !("checks" in value) || !Array.isArray(value.checks)) {
|
|
7274
|
+
return false;
|
|
7275
|
+
}
|
|
7276
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7277
|
+
const validChecks = value.checks.every((check) => {
|
|
7278
|
+
if (typeof check !== "object" || check === null || !("acceptanceId" in check) || typeof check.acceptanceId !== "string" || !("required" in check) || typeof check.required !== "boolean") {
|
|
7279
|
+
return false;
|
|
7280
|
+
}
|
|
7281
|
+
if (ids.has(check.acceptanceId)) return false;
|
|
7282
|
+
ids.add(check.acceptanceId);
|
|
7283
|
+
return ["automated", "manual"].every((component) => {
|
|
7284
|
+
const evidence = check[component];
|
|
7285
|
+
if (evidence === void 0) return true;
|
|
7286
|
+
const valid = typeof evidence === "object" && evidence !== null && "status" in evidence && isComponentStatus(evidence.status);
|
|
7287
|
+
if (!valid) return false;
|
|
7288
|
+
if (evidence.status === "pending") {
|
|
7289
|
+
return !("evidence" in evidence) && !("updatedAt" in evidence);
|
|
7290
|
+
}
|
|
7291
|
+
const baseEvidenceIsValid = "evidence" in evidence && typeof evidence.evidence === "string" && evidence.evidence.trim().length > 0 && "updatedAt" in evidence && typeof evidence.updatedAt === "string";
|
|
7292
|
+
if (!baseEvidenceIsValid) return false;
|
|
7293
|
+
if (component === "automated" && (evidence.status === "passed" || evidence.status === "failed")) {
|
|
7294
|
+
return "command" in evidence && typeof evidence.command === "string" && evidence.command.trim().length > 0 && "exitCode" in evidence && Number.isInteger(evidence.exitCode) && (evidence.status === "passed" ? evidence.exitCode === 0 : evidence.exitCode !== 0);
|
|
7295
|
+
}
|
|
7296
|
+
return true;
|
|
7297
|
+
});
|
|
7298
|
+
});
|
|
7299
|
+
return validChecks && value.status === deriveVerificationStatus(value.checks);
|
|
7300
|
+
}
|
|
7301
|
+
function ledgerMatchesRequirements(ledger, requirements) {
|
|
7302
|
+
if (ledger.checks.length !== requirements.acceptanceCriteria.length) {
|
|
7303
|
+
return false;
|
|
7304
|
+
}
|
|
7305
|
+
return requirements.acceptanceCriteria.every((criterion) => {
|
|
7306
|
+
const check = ledger.checks.find(
|
|
7307
|
+
(candidate) => candidate.acceptanceId === criterion.id
|
|
7308
|
+
);
|
|
7309
|
+
if (!check || check.required !== criterion.required) return false;
|
|
7310
|
+
const needsAutomated = criterion.method === "automated" || criterion.method === "hybrid";
|
|
7311
|
+
const needsManual = criterion.method === "manual" || criterion.method === "hybrid";
|
|
7312
|
+
return Boolean(check.automated) === needsAutomated && Boolean(check.manual) === needsManual;
|
|
7313
|
+
});
|
|
7314
|
+
}
|
|
7315
|
+
function isComponentStatus(value) {
|
|
7316
|
+
return value === "pending" || value === "passed" || value === "failed" || value === "manual_required" || value === "blocked";
|
|
7317
|
+
}
|
|
6794
7318
|
|
|
6795
7319
|
// src/system/workflow.ts
|
|
6796
7320
|
var METADATA_FILE = "metadata.json";
|
|
@@ -6812,7 +7336,7 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
|
|
|
6812
7336
|
await validateParentTask(projectRoot, mode, options.parentTaskId, true);
|
|
6813
7337
|
await mkdir9(workflowsRoot(projectRoot), { recursive: true });
|
|
6814
7338
|
const taskId = await allocateTaskId(projectRoot, generateTaskId(task));
|
|
6815
|
-
const dir =
|
|
7339
|
+
const dir = workflowDir2(projectRoot, taskId);
|
|
6816
7340
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6817
7341
|
const meta = {
|
|
6818
7342
|
taskId,
|
|
@@ -6824,7 +7348,12 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
|
|
|
6824
7348
|
updatedAt: now,
|
|
6825
7349
|
status: "in_progress",
|
|
6826
7350
|
...options.parentTaskId ? { parentTaskId: options.parentTaskId } : {},
|
|
6827
|
-
...mode === "man" || mode === "manteam" ? {
|
|
7351
|
+
...mode === "man" || mode === "manteam" ? {
|
|
7352
|
+
planVersion: 1,
|
|
7353
|
+
reviewPolicyVersion: options.planningPolicyVersion === 2 ? 2 : 1,
|
|
7354
|
+
...options.planningPolicyVersion === 2 ? { verificationPolicyVersion: 1 } : {},
|
|
7355
|
+
...options.planningPolicyVersion ? { planningPolicyVersion: options.planningPolicyVersion } : {}
|
|
7356
|
+
} : {}
|
|
6828
7357
|
};
|
|
6829
7358
|
await writeMetadata(dir, meta);
|
|
6830
7359
|
return meta;
|
|
@@ -6833,7 +7362,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
|
|
|
6833
7362
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
6834
7363
|
const taskId = attempt === 0 ? baseTaskId : `${baseTaskId}-${attempt + 1}`;
|
|
6835
7364
|
try {
|
|
6836
|
-
await mkdir9(
|
|
7365
|
+
await mkdir9(workflowDir2(projectRoot, taskId));
|
|
6837
7366
|
return taskId;
|
|
6838
7367
|
} catch (err) {
|
|
6839
7368
|
if (isNodeError9(err) && err.code === "EEXIST") {
|
|
@@ -6848,13 +7377,13 @@ async function readWorkflow(projectRoot, taskId) {
|
|
|
6848
7377
|
if (!isValidWorkflowTaskId(taskId)) return null;
|
|
6849
7378
|
const file = metadataPath(projectRoot, taskId);
|
|
6850
7379
|
try {
|
|
6851
|
-
const raw = await
|
|
7380
|
+
const raw = await readFile12(file, "utf-8");
|
|
6852
7381
|
return parseWorkflowMeta(raw, taskId);
|
|
6853
7382
|
} catch {
|
|
6854
7383
|
return null;
|
|
6855
7384
|
}
|
|
6856
7385
|
}
|
|
6857
|
-
async function updateWorkflow(projectRoot, taskId, patch) {
|
|
7386
|
+
async function updateWorkflow(projectRoot, taskId, patch, options = {}) {
|
|
6858
7387
|
assertValidTaskId2(taskId);
|
|
6859
7388
|
const existing = await readWorkflow(projectRoot, taskId);
|
|
6860
7389
|
if (!existing) {
|
|
@@ -6874,8 +7403,8 @@ async function updateWorkflow(projectRoot, taskId, patch) {
|
|
|
6874
7403
|
if (updated.status !== "blocked" && !explicitlyPatchedBlockingReason) {
|
|
6875
7404
|
updated.blockingReason = void 0;
|
|
6876
7405
|
}
|
|
6877
|
-
await validateWorkflowMeta(projectRoot, updated, existing);
|
|
6878
|
-
await writeMetadata(
|
|
7406
|
+
await validateWorkflowMeta(projectRoot, updated, existing, options);
|
|
7407
|
+
await writeMetadata(workflowDir2(projectRoot, taskId), updated);
|
|
6879
7408
|
if (updated.parentTaskId) {
|
|
6880
7409
|
await propagateChildStatus(projectRoot, updated);
|
|
6881
7410
|
}
|
|
@@ -6898,7 +7427,7 @@ async function listWorkflows(projectRoot) {
|
|
|
6898
7427
|
}
|
|
6899
7428
|
async function deleteWorkflow(projectRoot, taskId) {
|
|
6900
7429
|
if (!isValidWorkflowTaskId(taskId)) return false;
|
|
6901
|
-
const dir =
|
|
7430
|
+
const dir = workflowDir2(projectRoot, taskId);
|
|
6902
7431
|
try {
|
|
6903
7432
|
await stat4(dir);
|
|
6904
7433
|
} catch {
|
|
@@ -6912,19 +7441,19 @@ async function deleteWorkflow(projectRoot, taskId) {
|
|
|
6912
7441
|
return true;
|
|
6913
7442
|
}
|
|
6914
7443
|
function workflowsRoot(projectRoot) {
|
|
6915
|
-
return
|
|
7444
|
+
return path25.join(projectRoot, ".mancode", "workflows");
|
|
6916
7445
|
}
|
|
6917
|
-
function
|
|
7446
|
+
function workflowDir2(projectRoot, taskId) {
|
|
6918
7447
|
assertValidTaskId2(taskId);
|
|
6919
|
-
return
|
|
7448
|
+
return path25.join(workflowsRoot(projectRoot), taskId);
|
|
6920
7449
|
}
|
|
6921
7450
|
function metadataPath(projectRoot, taskId) {
|
|
6922
|
-
return
|
|
7451
|
+
return path25.join(workflowDir2(projectRoot, taskId), METADATA_FILE);
|
|
6923
7452
|
}
|
|
6924
7453
|
async function writeMetadata(dir, meta) {
|
|
6925
7454
|
const content = `${JSON.stringify(meta, null, 2)}
|
|
6926
7455
|
`;
|
|
6927
|
-
await
|
|
7456
|
+
await writeFile13(path25.join(dir, METADATA_FILE), content, "utf-8");
|
|
6928
7457
|
}
|
|
6929
7458
|
function parseWorkflowMeta(raw, taskId) {
|
|
6930
7459
|
try {
|
|
@@ -6949,6 +7478,12 @@ function isWorkflowStatus(value) {
|
|
|
6949
7478
|
function isWorkflowOutcome(value) {
|
|
6950
7479
|
return value === "fixed" || value === "verified" || value === "no_repro" || value === "manual_test_required";
|
|
6951
7480
|
}
|
|
7481
|
+
function isRequirementsStatus(value) {
|
|
7482
|
+
return value === "ready" || value === "needs_clarification";
|
|
7483
|
+
}
|
|
7484
|
+
function isPlanDecision(value) {
|
|
7485
|
+
return value === "plan_only" || value === "solo_handoff" || value === "governed_execution";
|
|
7486
|
+
}
|
|
6952
7487
|
async function validateParentTask(projectRoot, mode, parentTaskId, requireStepSix = false) {
|
|
6953
7488
|
if (!parentTaskId) return;
|
|
6954
7489
|
if (mode !== "mamba") {
|
|
@@ -6964,7 +7499,7 @@ async function validateParentTask(projectRoot, mode, parentTaskId, requireStepSi
|
|
|
6964
7499
|
);
|
|
6965
7500
|
}
|
|
6966
7501
|
}
|
|
6967
|
-
async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
7502
|
+
async function validateWorkflowMeta(projectRoot, updated, existing, options) {
|
|
6968
7503
|
if (updated.mode !== existing.mode) {
|
|
6969
7504
|
throw new Error("workflow mode cannot be changed");
|
|
6970
7505
|
}
|
|
@@ -6977,6 +7512,17 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
6977
7512
|
if (updated.reviewPolicyVersion !== existing.reviewPolicyVersion) {
|
|
6978
7513
|
throw new Error("workflow review policy version cannot be changed");
|
|
6979
7514
|
}
|
|
7515
|
+
if (updated.planningPolicyVersion !== existing.planningPolicyVersion) {
|
|
7516
|
+
throw new Error("workflow planning policy version cannot be changed");
|
|
7517
|
+
}
|
|
7518
|
+
if (updated.requirementsDigest !== existing.requirementsDigest && existing.currentStep > 2) {
|
|
7519
|
+
throw new Error(
|
|
7520
|
+
"workflow requirements cannot change after planning starts"
|
|
7521
|
+
);
|
|
7522
|
+
}
|
|
7523
|
+
if (updated.verificationPolicyVersion !== existing.verificationPolicyVersion) {
|
|
7524
|
+
throw new Error("workflow verification policy version cannot be changed");
|
|
7525
|
+
}
|
|
6980
7526
|
if (!Number.isInteger(updated.currentStep) || updated.currentStep < 1 || updated.currentStep > maxWorkflowStep(updated.mode)) {
|
|
6981
7527
|
throw new Error(`invalid workflow step: ${updated.currentStep}`);
|
|
6982
7528
|
}
|
|
@@ -6986,6 +7532,13 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
6986
7532
|
if (!Array.isArray(updated.skippedSteps) || updated.skippedSteps.some((step) => typeof step !== "string")) {
|
|
6987
7533
|
throw new Error("workflow skipped steps must be strings");
|
|
6988
7534
|
}
|
|
7535
|
+
if (updated.reviewPolicyVersion === 2 && updated.skippedSteps.some(
|
|
7536
|
+
(step) => step !== "clarification" && step !== "review"
|
|
7537
|
+
)) {
|
|
7538
|
+
throw new Error(
|
|
7539
|
+
"workflow policy v2 only allows skipping clarification or review"
|
|
7540
|
+
);
|
|
7541
|
+
}
|
|
6989
7542
|
if (!canTransition(existing.status, updated.status)) {
|
|
6990
7543
|
throw new Error(
|
|
6991
7544
|
`invalid workflow status transition: ${existing.status} -> ${updated.status}`
|
|
@@ -6997,7 +7550,7 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
6997
7550
|
if (updated.status === "planned" && updated.currentStep !== 4) {
|
|
6998
7551
|
throw new Error("planned workflows must be at step 4");
|
|
6999
7552
|
}
|
|
7000
|
-
if (updated.status === "completed" && updated.currentStep !== maxWorkflowStep(updated.mode)) {
|
|
7553
|
+
if (updated.status === "completed" && updated.currentStep !== maxWorkflowStep(updated.mode) && !(updated.currentStep === 4 && updated.planDecision === "solo_handoff")) {
|
|
7001
7554
|
throw new Error(
|
|
7002
7555
|
`completed ${updated.mode === "mamba" ? "manba" : updated.mode} workflows must be at step ${maxWorkflowStep(updated.mode)}`
|
|
7003
7556
|
);
|
|
@@ -7027,12 +7580,106 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
7027
7580
|
if (updated.mode === "mamba" && updated.planVersion !== void 0) {
|
|
7028
7581
|
throw new Error("manba workflows cannot have a plan version");
|
|
7029
7582
|
}
|
|
7030
|
-
if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1) {
|
|
7583
|
+
if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1 && updated.reviewPolicyVersion !== 2) {
|
|
7031
7584
|
throw new Error("invalid workflow review policy version");
|
|
7032
7585
|
}
|
|
7033
7586
|
if (updated.mode === "mamba" && updated.reviewPolicyVersion !== void 0) {
|
|
7034
7587
|
throw new Error("manba workflows cannot have a review policy version");
|
|
7035
7588
|
}
|
|
7589
|
+
if (updated.planningPolicyVersion !== void 0 && updated.planningPolicyVersion !== 1 && updated.planningPolicyVersion !== 2) {
|
|
7590
|
+
throw new Error("invalid workflow planning policy version");
|
|
7591
|
+
}
|
|
7592
|
+
if (updated.mode === "mamba" && updated.planningPolicyVersion !== void 0) {
|
|
7593
|
+
throw new Error("manba workflows cannot have a planning policy version");
|
|
7594
|
+
}
|
|
7595
|
+
if (updated.verificationPolicyVersion !== void 0 && updated.verificationPolicyVersion !== 1) {
|
|
7596
|
+
throw new Error("invalid workflow verification policy version");
|
|
7597
|
+
}
|
|
7598
|
+
if (updated.mode === "mamba" && updated.verificationPolicyVersion !== void 0) {
|
|
7599
|
+
throw new Error(
|
|
7600
|
+
"manba workflows cannot have a verification policy version"
|
|
7601
|
+
);
|
|
7602
|
+
}
|
|
7603
|
+
if (updated.verificationStatus !== void 0 && !isVerificationStatus(updated.verificationStatus)) {
|
|
7604
|
+
throw new Error("invalid workflow verification status");
|
|
7605
|
+
}
|
|
7606
|
+
if (updated.mode === "mamba" && updated.verificationStatus !== void 0) {
|
|
7607
|
+
throw new Error("manba workflows cannot have a verification status");
|
|
7608
|
+
}
|
|
7609
|
+
if (updated.requirementsStatus !== void 0 && !isRequirementsStatus(updated.requirementsStatus)) {
|
|
7610
|
+
throw new Error("invalid workflow requirements status");
|
|
7611
|
+
}
|
|
7612
|
+
if (updated.mode === "mamba" && updated.requirementsStatus !== void 0) {
|
|
7613
|
+
throw new Error("manba workflows cannot have a requirements status");
|
|
7614
|
+
}
|
|
7615
|
+
if (updated.requirementsDigest !== void 0 && typeof updated.requirementsDigest !== "string") {
|
|
7616
|
+
throw new Error("workflow requirements digest must be a string");
|
|
7617
|
+
}
|
|
7618
|
+
if (updated.mode === "mamba" && updated.requirementsDigest !== void 0) {
|
|
7619
|
+
throw new Error("manba workflows cannot have a requirements digest");
|
|
7620
|
+
}
|
|
7621
|
+
if (updated.planDecision !== void 0 && !isPlanDecision(updated.planDecision)) {
|
|
7622
|
+
throw new Error("invalid workflow plan decision");
|
|
7623
|
+
}
|
|
7624
|
+
if (updated.mode === "mamba" && updated.planDecision !== void 0) {
|
|
7625
|
+
throw new Error("manba workflows cannot have a plan decision");
|
|
7626
|
+
}
|
|
7627
|
+
if (updated.planDecision !== existing.planDecision && existing.planDecision !== void 0) {
|
|
7628
|
+
throw new Error("workflow plan decision cannot be changed");
|
|
7629
|
+
}
|
|
7630
|
+
if (updated.planDecision !== existing.planDecision && existing.currentStep !== 4) {
|
|
7631
|
+
throw new Error("workflow plan decision can only be set at step 4");
|
|
7632
|
+
}
|
|
7633
|
+
if (updated.planningPolicyVersion === 1 || updated.planningPolicyVersion === 2) {
|
|
7634
|
+
if (updated.currentStep >= 3) {
|
|
7635
|
+
if (updated.requirementsStatus !== "ready") {
|
|
7636
|
+
throw new Error("requirements must be ready before planning");
|
|
7637
|
+
}
|
|
7638
|
+
if (!await workflowArtifactExists(
|
|
7639
|
+
projectRoot,
|
|
7640
|
+
updated.taskId,
|
|
7641
|
+
"requirements.md"
|
|
7642
|
+
)) {
|
|
7643
|
+
throw new Error("requirements.md is required before planning");
|
|
7644
|
+
}
|
|
7645
|
+
if (updated.planningPolicyVersion === 2) {
|
|
7646
|
+
const requirements = await readRequirementsLedger(
|
|
7647
|
+
projectRoot,
|
|
7648
|
+
updated.taskId
|
|
7649
|
+
);
|
|
7650
|
+
if (!requirements || !requirementsAreReady(requirements) || updated.requirementsDigest !== requirementsDigest(requirements)) {
|
|
7651
|
+
throw new Error(
|
|
7652
|
+
"finalized requirements.json with no blocking unknowns is required before planning"
|
|
7653
|
+
);
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
if (updated.currentStep >= 4 && !await workflowArtifactExists(projectRoot, updated.taskId, "plan.md")) {
|
|
7658
|
+
throw new Error("plan.md is required before the plan gate");
|
|
7659
|
+
}
|
|
7660
|
+
if (updated.status === "planned" && updated.planDecision !== "plan_only" && updated.planDecision !== "solo_handoff") {
|
|
7661
|
+
throw new Error(
|
|
7662
|
+
"planned workflows require a plan-only or solo-handoff decision"
|
|
7663
|
+
);
|
|
7664
|
+
}
|
|
7665
|
+
if (updated.currentStep >= 5 && updated.status === "in_progress" && updated.planDecision !== "governed_execution") {
|
|
7666
|
+
throw new Error("governed execution must be confirmed before step 5");
|
|
7667
|
+
}
|
|
7668
|
+
}
|
|
7669
|
+
if (updated.verificationPolicyVersion === 1 && updated.currentStep >= 7 && updated.planDecision !== "solo_handoff" && !options.allowIncompleteVerification && !await verificationCanAdvance(
|
|
7670
|
+
projectRoot,
|
|
7671
|
+
updated.taskId,
|
|
7672
|
+
updated.planVersion ?? 1
|
|
7673
|
+
)) {
|
|
7674
|
+
throw new Error(
|
|
7675
|
+
"workflow verification is incomplete, stale, or requires manual confirmation"
|
|
7676
|
+
);
|
|
7677
|
+
}
|
|
7678
|
+
if (existing.status === "blocked" && existing.blockingReason?.startsWith("[verification]") && updated.status === "in_progress" && (updated.verificationStatus === "manual_required" || updated.verificationStatus === "blocked")) {
|
|
7679
|
+
throw new Error(
|
|
7680
|
+
"verification-blocked workflows must resume through verify"
|
|
7681
|
+
);
|
|
7682
|
+
}
|
|
7036
7683
|
if (updated.planVersion !== void 0 && (!Number.isInteger(updated.planVersion) || updated.planVersion < 1)) {
|
|
7037
7684
|
throw new Error("workflow plan version must be a positive integer");
|
|
7038
7685
|
}
|
|
@@ -7046,16 +7693,22 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
7046
7693
|
if ((isTerminalWorkflowStatus(updated.status) || updated.status === "planned") && (await listActiveMambaChildren(projectRoot, updated.taskId)).length > 0) {
|
|
7047
7694
|
throw new Error("cannot finish workflow with an active manba child");
|
|
7048
7695
|
}
|
|
7049
|
-
if (updated.status === "completed" && updated.reviewPolicyVersion === 1 && !reviewWasExplicitlySkipped(updated) && !await reviewCanComplete(projectRoot, updated.taskId)) {
|
|
7696
|
+
if (updated.status === "completed" && updated.reviewPolicyVersion === 1 && updated.planDecision !== "solo_handoff" && !reviewWasExplicitlySkipped(updated) && !await reviewCanComplete(projectRoot, updated.taskId)) {
|
|
7697
|
+
throw new Error("workflow review is incomplete or still has open blockers");
|
|
7698
|
+
}
|
|
7699
|
+
if (updated.status === "completed" && updated.reviewPolicyVersion === 2 && updated.planDecision !== "solo_handoff" && !await reviewCanComplete(projectRoot, updated.taskId)) {
|
|
7050
7700
|
throw new Error("workflow review is incomplete or still has open blockers");
|
|
7051
7701
|
}
|
|
7702
|
+
if (updated.status === "completed" && updated.verificationPolicyVersion === 1 && updated.planDecision !== "solo_handoff" && !await workflowArtifactExists(projectRoot, updated.taskId, "summary.md")) {
|
|
7703
|
+
throw new Error("summary.md is required before workflow completion");
|
|
7704
|
+
}
|
|
7052
7705
|
}
|
|
7053
7706
|
function canTransition(from, to) {
|
|
7054
7707
|
if (from === to) return true;
|
|
7055
7708
|
if (from === "in_progress") return true;
|
|
7056
|
-
if (from === "planned"
|
|
7057
|
-
return to === "in_progress" || to === "abandoned";
|
|
7058
|
-
|
|
7709
|
+
if (from === "planned")
|
|
7710
|
+
return to === "in_progress" || to === "completed" || to === "abandoned";
|
|
7711
|
+
if (from === "blocked") return to === "in_progress" || to === "abandoned";
|
|
7059
7712
|
return false;
|
|
7060
7713
|
}
|
|
7061
7714
|
function isTerminalWorkflowStatus(status2) {
|
|
@@ -7076,15 +7729,27 @@ function isWorkflowShape(obj) {
|
|
|
7076
7729
|
return false;
|
|
7077
7730
|
if (obj.planVersion !== void 0 && (!Number.isInteger(obj.planVersion) || obj.planVersion < 1))
|
|
7078
7731
|
return false;
|
|
7079
|
-
if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1)
|
|
7732
|
+
if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1 && obj.reviewPolicyVersion !== 2)
|
|
7080
7733
|
return false;
|
|
7081
|
-
if (
|
|
7082
|
-
|
|
7734
|
+
if (obj.planningPolicyVersion !== void 0 && obj.planningPolicyVersion !== 1 && obj.planningPolicyVersion !== 2)
|
|
7735
|
+
return false;
|
|
7736
|
+
if (obj.verificationPolicyVersion !== void 0 && obj.verificationPolicyVersion !== 1)
|
|
7737
|
+
return false;
|
|
7738
|
+
if (obj.requirementsStatus !== void 0 && !isRequirementsStatus(obj.requirementsStatus))
|
|
7739
|
+
return false;
|
|
7740
|
+
if (obj.requirementsDigest !== void 0 && typeof obj.requirementsDigest !== "string")
|
|
7741
|
+
return false;
|
|
7742
|
+
if (obj.planDecision !== void 0 && !isPlanDecision(obj.planDecision))
|
|
7743
|
+
return false;
|
|
7744
|
+
if (obj.verificationStatus !== void 0 && !isVerificationStatus(obj.verificationStatus))
|
|
7745
|
+
return false;
|
|
7746
|
+
if (status2 === "blocked") {
|
|
7747
|
+
if (!obj.blockingReason?.trim()) return false;
|
|
7083
7748
|
} else if (obj.blockingReason !== void 0) {
|
|
7084
7749
|
return false;
|
|
7085
7750
|
}
|
|
7086
7751
|
if (mode === "mamba") {
|
|
7087
|
-
if (status2 === "planned" || obj.planVersion !== void 0 || obj.reviewPolicyVersion !== void 0)
|
|
7752
|
+
if (status2 === "planned" || obj.planVersion !== void 0 || obj.reviewPolicyVersion !== void 0 || obj.planningPolicyVersion !== void 0 || obj.verificationPolicyVersion !== void 0 || obj.requirementsStatus !== void 0 || obj.requirementsDigest !== void 0 || obj.planDecision !== void 0 || obj.verificationStatus !== void 0)
|
|
7088
7753
|
return false;
|
|
7089
7754
|
if (obj.parentTaskId !== void 0 && !isValidWorkflowTaskId(obj.parentTaskId))
|
|
7090
7755
|
return false;
|
|
@@ -7095,9 +7760,20 @@ function isWorkflowShape(obj) {
|
|
|
7095
7760
|
}
|
|
7096
7761
|
return true;
|
|
7097
7762
|
}
|
|
7763
|
+
async function workflowArtifactExists(projectRoot, taskId, filename) {
|
|
7764
|
+
try {
|
|
7765
|
+
await stat4(path25.join(workflowDir2(projectRoot, taskId), filename));
|
|
7766
|
+
return true;
|
|
7767
|
+
} catch {
|
|
7768
|
+
return false;
|
|
7769
|
+
}
|
|
7770
|
+
}
|
|
7098
7771
|
function reviewWasExplicitlySkipped(meta) {
|
|
7099
7772
|
return meta.skippedSteps.includes("review") || meta.skippedSteps.includes("film-1") && meta.skippedSteps.includes("film-2");
|
|
7100
7773
|
}
|
|
7774
|
+
function isVerificationStatus(value) {
|
|
7775
|
+
return value === "pending" || value === "passed" || value === "failed" || value === "manual_required" || value === "blocked";
|
|
7776
|
+
}
|
|
7101
7777
|
async function listActiveMambaChildren(projectRoot, parentTaskId) {
|
|
7102
7778
|
return (await listMambaChildren(projectRoot, parentTaskId)).filter(
|
|
7103
7779
|
(workflow2) => workflow2.status === "in_progress" || workflow2.status === "blocked"
|
|
@@ -7116,7 +7792,7 @@ async function propagateChildStatus(projectRoot, child) {
|
|
|
7116
7792
|
const parent = await readWorkflow(projectRoot, parentTaskId);
|
|
7117
7793
|
if (!parent || parent.status !== "in_progress") return;
|
|
7118
7794
|
const reason = child.status === "blocked" ? `manba child ${child.taskId} is blocked: ${child.blockingReason ?? "unknown reason"}` : `manba child ${child.taskId} requires manual testing`;
|
|
7119
|
-
await writeMetadata(
|
|
7795
|
+
await writeMetadata(workflowDir2(projectRoot, parent.taskId), {
|
|
7120
7796
|
...parent,
|
|
7121
7797
|
status: "blocked",
|
|
7122
7798
|
blockingReason: reason,
|
|
@@ -7145,7 +7821,7 @@ var EXIT_OK7 = 0;
|
|
|
7145
7821
|
var EXIT_NOT_INITIALIZED5 = 1;
|
|
7146
7822
|
var EXIT_CORRUPT_STATE2 = 2;
|
|
7147
7823
|
async function status(rootDir = process10.cwd(), options = {}) {
|
|
7148
|
-
const stateFile =
|
|
7824
|
+
const stateFile = path26.join(rootDir, ".mancode", "state.json");
|
|
7149
7825
|
if (!await pathExists10(stateFile)) {
|
|
7150
7826
|
console.error("\u2717 mancode not initialized.");
|
|
7151
7827
|
console.error(" Run `mancode init` to get started.");
|
|
@@ -7196,6 +7872,7 @@ async function status(rootDir = process10.cwd(), options = {}) {
|
|
|
7196
7872
|
hookInjection,
|
|
7197
7873
|
team: effectiveTeam,
|
|
7198
7874
|
currentWorkflow,
|
|
7875
|
+
activeSoloPlan: state.activeSoloPlan ?? null,
|
|
7199
7876
|
platformStatus: {},
|
|
7200
7877
|
projectRefreshRecommended
|
|
7201
7878
|
};
|
|
@@ -7209,10 +7886,10 @@ async function status(rootDir = process10.cwd(), options = {}) {
|
|
|
7209
7886
|
}
|
|
7210
7887
|
async function shouldRefreshProject(rootDir, state) {
|
|
7211
7888
|
if (state.projectMode !== "generic") return false;
|
|
7212
|
-
const hasGit = await pathExists10(
|
|
7889
|
+
const hasGit = await pathExists10(path26.join(rootDir, ".git"));
|
|
7213
7890
|
if (hasGit) return true;
|
|
7214
7891
|
for (const manifest of PROJECT_MANIFESTS) {
|
|
7215
|
-
if (await pathExists10(
|
|
7892
|
+
if (await pathExists10(path26.join(rootDir, manifest))) return true;
|
|
7216
7893
|
}
|
|
7217
7894
|
return false;
|
|
7218
7895
|
}
|
|
@@ -7256,19 +7933,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
|
|
|
7256
7933
|
}
|
|
7257
7934
|
async function getProjectName(rootDir) {
|
|
7258
7935
|
try {
|
|
7259
|
-
const raw = await fs9.readFile(
|
|
7936
|
+
const raw = await fs9.readFile(path26.join(rootDir, "package.json"), "utf-8");
|
|
7260
7937
|
const pkg = JSON.parse(raw);
|
|
7261
7938
|
if (pkg.name && typeof pkg.name === "string") {
|
|
7262
7939
|
return pkg.name;
|
|
7263
7940
|
}
|
|
7264
7941
|
} catch {
|
|
7265
7942
|
}
|
|
7266
|
-
return
|
|
7943
|
+
return path26.basename(rootDir);
|
|
7267
7944
|
}
|
|
7268
7945
|
async function readConfig2(rootDir) {
|
|
7269
7946
|
try {
|
|
7270
7947
|
const raw = await fs9.readFile(
|
|
7271
|
-
|
|
7948
|
+
path26.join(rootDir, ".mancode", "config.json"),
|
|
7272
7949
|
"utf-8"
|
|
7273
7950
|
);
|
|
7274
7951
|
return JSON.parse(raw);
|
|
@@ -7296,9 +7973,9 @@ function getEffectiveTeamStatus(state, config, detected) {
|
|
|
7296
7973
|
}
|
|
7297
7974
|
async function checkHooks(rootDir) {
|
|
7298
7975
|
const [sessionStart, userPromptSubmit, registered] = await Promise.all([
|
|
7299
|
-
pathExists10(
|
|
7976
|
+
pathExists10(path26.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
|
|
7300
7977
|
pathExists10(
|
|
7301
|
-
|
|
7978
|
+
path26.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
7302
7979
|
),
|
|
7303
7980
|
isRegistered(rootDir)
|
|
7304
7981
|
]);
|
|
@@ -7307,7 +7984,7 @@ async function checkHooks(rootDir) {
|
|
|
7307
7984
|
async function isRegistered(rootDir) {
|
|
7308
7985
|
try {
|
|
7309
7986
|
const raw = await fs9.readFile(
|
|
7310
|
-
|
|
7987
|
+
path26.join(rootDir, ".claude", "settings.json"),
|
|
7311
7988
|
"utf-8"
|
|
7312
7989
|
);
|
|
7313
7990
|
const settings = JSON.parse(raw);
|
|
@@ -7317,9 +7994,9 @@ async function isRegistered(rootDir) {
|
|
|
7317
7994
|
}
|
|
7318
7995
|
}
|
|
7319
7996
|
function hooksRegistered2(settings) {
|
|
7320
|
-
if (!
|
|
7997
|
+
if (!isRecord10(settings)) return false;
|
|
7321
7998
|
const hooks = settings.hooks;
|
|
7322
|
-
if (!
|
|
7999
|
+
if (!isRecord10(hooks)) return false;
|
|
7323
8000
|
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
|
|
7324
8001
|
hooks.UserPromptSubmit,
|
|
7325
8002
|
".mancode/hooks/user-prompt-submit.mjs"
|
|
@@ -7328,15 +8005,15 @@ function hooksRegistered2(settings) {
|
|
|
7328
8005
|
function hasHookCommand2(value, needle) {
|
|
7329
8006
|
if (!Array.isArray(value)) return false;
|
|
7330
8007
|
return value.some((group) => {
|
|
7331
|
-
if (!
|
|
8008
|
+
if (!isRecord10(group) || !Array.isArray(group.hooks)) return false;
|
|
7332
8009
|
return group.hooks.some((hook) => {
|
|
7333
|
-
if (!
|
|
8010
|
+
if (!isRecord10(hook) || typeof hook.command !== "string") return false;
|
|
7334
8011
|
return hook.command.includes(needle);
|
|
7335
8012
|
});
|
|
7336
8013
|
});
|
|
7337
8014
|
}
|
|
7338
8015
|
async function estimateHookInjection(rootDir) {
|
|
7339
|
-
const hookPath =
|
|
8016
|
+
const hookPath = path26.join(
|
|
7340
8017
|
rootDir,
|
|
7341
8018
|
".mancode",
|
|
7342
8019
|
"hooks",
|
|
@@ -7428,6 +8105,11 @@ function printText(r) {
|
|
|
7428
8105
|
);
|
|
7429
8106
|
}
|
|
7430
8107
|
}
|
|
8108
|
+
if (r.activeSoloPlan) {
|
|
8109
|
+
console.log(
|
|
8110
|
+
`Solo plan: ${r.activeSoloPlan.taskId} (plan v${r.activeSoloPlan.planVersion})`
|
|
8111
|
+
);
|
|
8112
|
+
}
|
|
7431
8113
|
console.log("");
|
|
7432
8114
|
console.log("Installed platforms:");
|
|
7433
8115
|
for (const p of r.platforms) {
|
|
@@ -7468,7 +8150,7 @@ function formatTeamStatus(team) {
|
|
|
7468
8150
|
function workflowStepMax(mode) {
|
|
7469
8151
|
return maxWorkflowStep(mode);
|
|
7470
8152
|
}
|
|
7471
|
-
function
|
|
8153
|
+
function isRecord10(value) {
|
|
7472
8154
|
return typeof value === "object" && value !== null;
|
|
7473
8155
|
}
|
|
7474
8156
|
async function pathExists10(p) {
|
|
@@ -7481,14 +8163,14 @@ async function pathExists10(p) {
|
|
|
7481
8163
|
}
|
|
7482
8164
|
|
|
7483
8165
|
// src/commands/uninstall.ts
|
|
7484
|
-
import { access as access5, readFile as
|
|
7485
|
-
import
|
|
8166
|
+
import { access as access5, readFile as readFile13, rm as rm5, writeFile as writeFile14 } from "fs/promises";
|
|
8167
|
+
import path27 from "path";
|
|
7486
8168
|
import process11 from "process";
|
|
7487
8169
|
var EXIT_OK8 = 0;
|
|
7488
8170
|
var EXIT_NOT_INITIALIZED6 = 1;
|
|
7489
8171
|
var EXIT_UNSUPPORTED_PLATFORM2 = 2;
|
|
7490
8172
|
async function uninstall(rootDir = process11.cwd(), platform, options = {}) {
|
|
7491
|
-
const stateFile =
|
|
8173
|
+
const stateFile = path27.join(rootDir, ".mancode", "state.json");
|
|
7492
8174
|
if (!await pathExists11(stateFile)) {
|
|
7493
8175
|
console.error("\u2717 mancode not initialized.");
|
|
7494
8176
|
console.error(" Run `mancode init` first.");
|
|
@@ -7540,7 +8222,7 @@ async function uninstallAll(rootDir) {
|
|
|
7540
8222
|
await uninstallPlatform(rootDir, p);
|
|
7541
8223
|
}
|
|
7542
8224
|
console.log("\u2713 Removing .mancode/ directory...");
|
|
7543
|
-
await rm5(
|
|
8225
|
+
await rm5(path27.join(rootDir, ".mancode"), {
|
|
7544
8226
|
recursive: true,
|
|
7545
8227
|
force: true
|
|
7546
8228
|
});
|
|
@@ -7550,10 +8232,10 @@ async function uninstallClaudeCode(rootDir) {
|
|
|
7550
8232
|
await cleanClaudeSettings(rootDir);
|
|
7551
8233
|
}
|
|
7552
8234
|
async function cleanClaudeSettings(rootDir) {
|
|
7553
|
-
const settingsPath =
|
|
8235
|
+
const settingsPath = path27.join(rootDir, ".claude", "settings.json");
|
|
7554
8236
|
let content;
|
|
7555
8237
|
try {
|
|
7556
|
-
content = await
|
|
8238
|
+
content = await readFile13(settingsPath, "utf-8");
|
|
7557
8239
|
} catch {
|
|
7558
8240
|
return;
|
|
7559
8241
|
}
|
|
@@ -7564,14 +8246,14 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
7564
8246
|
return;
|
|
7565
8247
|
}
|
|
7566
8248
|
const cleanedHooks = {};
|
|
7567
|
-
if (
|
|
8249
|
+
if (isRecord11(settings.hooks)) {
|
|
7568
8250
|
for (const [event, value] of Object.entries(settings.hooks)) {
|
|
7569
8251
|
const cleaned = cleanClaudeHookValue(value);
|
|
7570
8252
|
if (cleaned !== void 0) cleanedHooks[event] = cleaned;
|
|
7571
8253
|
}
|
|
7572
8254
|
settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
|
|
7573
8255
|
}
|
|
7574
|
-
if (
|
|
8256
|
+
if (isRecord11(settings.skills)) {
|
|
7575
8257
|
const retainedSkills = Object.fromEntries(
|
|
7576
8258
|
Object.entries(settings.skills).filter(
|
|
7577
8259
|
([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
|
|
@@ -7579,7 +8261,7 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
7579
8261
|
);
|
|
7580
8262
|
settings.skills = Object.keys(retainedSkills).length > 0 ? retainedSkills : void 0;
|
|
7581
8263
|
}
|
|
7582
|
-
await
|
|
8264
|
+
await writeFile14(
|
|
7583
8265
|
settingsPath,
|
|
7584
8266
|
`${JSON.stringify(settings, null, 2)}
|
|
7585
8267
|
`,
|
|
@@ -7591,7 +8273,7 @@ function cleanClaudeHookValue(value) {
|
|
|
7591
8273
|
const entries2 = value.flatMap(cleanClaudeHookEntry);
|
|
7592
8274
|
return entries2.length > 0 ? entries2 : void 0;
|
|
7593
8275
|
}
|
|
7594
|
-
if (!
|
|
8276
|
+
if (!isRecord11(value)) return value;
|
|
7595
8277
|
if (Array.isArray(value.hooks) || typeof value.command === "string") {
|
|
7596
8278
|
const entries2 = cleanClaudeHookEntry(value);
|
|
7597
8279
|
return entries2.length > 0 ? entries2 : void 0;
|
|
@@ -7605,7 +8287,7 @@ function cleanClaudeHookValue(value) {
|
|
|
7605
8287
|
}
|
|
7606
8288
|
function cleanClaudeHookEntry(entry) {
|
|
7607
8289
|
if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
|
|
7608
|
-
if (!
|
|
8290
|
+
if (!isRecord11(entry)) return [entry];
|
|
7609
8291
|
if (Array.isArray(entry.hooks)) {
|
|
7610
8292
|
const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
|
|
7611
8293
|
return hooks.length > 0 ? [{ ...entry, hooks }] : [];
|
|
@@ -7613,11 +8295,11 @@ function cleanClaudeHookEntry(entry) {
|
|
|
7613
8295
|
return isMancodeHookEntry(entry) ? [] : [entry];
|
|
7614
8296
|
}
|
|
7615
8297
|
function isMancodeHookEntry(value) {
|
|
7616
|
-
return
|
|
8298
|
+
return isRecord11(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
|
|
7617
8299
|
}
|
|
7618
8300
|
function containsLegacyMancodeHookPath2(value) {
|
|
7619
8301
|
if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
|
|
7620
|
-
if (!
|
|
8302
|
+
if (!isRecord11(value)) return false;
|
|
7621
8303
|
if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
|
|
7622
8304
|
return true;
|
|
7623
8305
|
}
|
|
@@ -7628,12 +8310,12 @@ async function uninstallCursor(rootDir) {
|
|
|
7628
8310
|
await removeCursorCommands(rootDir);
|
|
7629
8311
|
}
|
|
7630
8312
|
async function uninstallCodex(rootDir) {
|
|
7631
|
-
const agentsPath =
|
|
8313
|
+
const agentsPath = path27.join(rootDir, "AGENTS.md");
|
|
7632
8314
|
try {
|
|
7633
|
-
const content = await
|
|
8315
|
+
const content = await readFile13(agentsPath, "utf-8");
|
|
7634
8316
|
const cleaned = removeManagedBlock(content);
|
|
7635
8317
|
if (cleaned.trim()) {
|
|
7636
|
-
await
|
|
8318
|
+
await writeFile14(agentsPath, `${cleaned}
|
|
7637
8319
|
`, "utf-8");
|
|
7638
8320
|
} else {
|
|
7639
8321
|
await rm5(agentsPath, { force: true });
|
|
@@ -7643,16 +8325,16 @@ async function uninstallCodex(rootDir) {
|
|
|
7643
8325
|
await removeCodexSkills(rootDir);
|
|
7644
8326
|
}
|
|
7645
8327
|
async function uninstallCopilot(rootDir) {
|
|
7646
|
-
const instructionsPath =
|
|
8328
|
+
const instructionsPath = path27.join(
|
|
7647
8329
|
rootDir,
|
|
7648
8330
|
".github",
|
|
7649
8331
|
"copilot-instructions.md"
|
|
7650
8332
|
);
|
|
7651
8333
|
try {
|
|
7652
|
-
const content = await
|
|
8334
|
+
const content = await readFile13(instructionsPath, "utf-8");
|
|
7653
8335
|
const cleaned = removeManagedBlock(content);
|
|
7654
8336
|
if (cleaned.trim()) {
|
|
7655
|
-
await
|
|
8337
|
+
await writeFile14(instructionsPath, `${cleaned}
|
|
7656
8338
|
`, "utf-8");
|
|
7657
8339
|
} else {
|
|
7658
8340
|
await rm5(instructionsPath, { force: true });
|
|
@@ -7662,16 +8344,16 @@ async function uninstallCopilot(rootDir) {
|
|
|
7662
8344
|
await removeCopilotPrompts(rootDir);
|
|
7663
8345
|
}
|
|
7664
8346
|
async function uninstallZcode(rootDir) {
|
|
7665
|
-
const agentsPath =
|
|
8347
|
+
const agentsPath = path27.join(rootDir, "AGENTS.md");
|
|
7666
8348
|
try {
|
|
7667
|
-
const content = await
|
|
8349
|
+
const content = await readFile13(agentsPath, "utf-8");
|
|
7668
8350
|
const cleaned = removeManagedBlock(
|
|
7669
8351
|
content,
|
|
7670
8352
|
ZCODE_MANCODE_START_MARKER,
|
|
7671
8353
|
ZCODE_MANCODE_END_MARKER
|
|
7672
8354
|
);
|
|
7673
8355
|
if (cleaned.trim()) {
|
|
7674
|
-
await
|
|
8356
|
+
await writeFile14(agentsPath, `${cleaned}
|
|
7675
8357
|
`, "utf-8");
|
|
7676
8358
|
} else {
|
|
7677
8359
|
await rm5(agentsPath, { force: true });
|
|
@@ -7681,13 +8363,13 @@ async function uninstallZcode(rootDir) {
|
|
|
7681
8363
|
await removeZcodeSkills(rootDir);
|
|
7682
8364
|
}
|
|
7683
8365
|
async function removeFromConfig(rootDir, platform) {
|
|
7684
|
-
const configPath =
|
|
8366
|
+
const configPath = path27.join(rootDir, ".mancode", "config.json");
|
|
7685
8367
|
try {
|
|
7686
|
-
const raw = await
|
|
8368
|
+
const raw = await readFile13(configPath, "utf-8");
|
|
7687
8369
|
const config = JSON.parse(raw);
|
|
7688
8370
|
if (Array.isArray(config.platforms)) {
|
|
7689
8371
|
config.platforms = config.platforms.filter((p) => p !== platform);
|
|
7690
|
-
if (
|
|
8372
|
+
if (isRecord11(config.platformOptions)) {
|
|
7691
8373
|
const platformOptions = Object.fromEntries(
|
|
7692
8374
|
Object.entries(config.platformOptions).filter(
|
|
7693
8375
|
([name]) => name !== platform
|
|
@@ -7695,7 +8377,7 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
7695
8377
|
);
|
|
7696
8378
|
config.platformOptions = Object.keys(platformOptions).length > 0 ? platformOptions : void 0;
|
|
7697
8379
|
}
|
|
7698
|
-
await
|
|
8380
|
+
await writeFile14(
|
|
7699
8381
|
configPath,
|
|
7700
8382
|
`${JSON.stringify(config, null, 2)}
|
|
7701
8383
|
`,
|
|
@@ -7705,7 +8387,7 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
7705
8387
|
} catch {
|
|
7706
8388
|
}
|
|
7707
8389
|
}
|
|
7708
|
-
function
|
|
8390
|
+
function isRecord11(value) {
|
|
7709
8391
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7710
8392
|
}
|
|
7711
8393
|
async function pathExists11(p) {
|
|
@@ -7728,13 +8410,13 @@ function version() {
|
|
|
7728
8410
|
}
|
|
7729
8411
|
|
|
7730
8412
|
// src/commands/workflow.ts
|
|
7731
|
-
import { access as access6 } from "fs/promises";
|
|
7732
|
-
import
|
|
8413
|
+
import { access as access6, readFile as readFile14, rm as rm6, writeFile as writeFile15 } from "fs/promises";
|
|
8414
|
+
import path28 from "path";
|
|
7733
8415
|
var EXIT_OK9 = 0;
|
|
7734
8416
|
var EXIT_NOT_INITIALIZED7 = 1;
|
|
7735
8417
|
var EXIT_INVALID_ARG2 = 2;
|
|
7736
8418
|
async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
7737
|
-
if (!await pathExists12(
|
|
8419
|
+
if (!await pathExists12(path28.join(rootDir, ".mancode", "state.json"))) {
|
|
7738
8420
|
if (options.json) {
|
|
7739
8421
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
7740
8422
|
} else {
|
|
@@ -7752,8 +8434,16 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
|
7752
8434
|
return workflowList(rootDir, options);
|
|
7753
8435
|
case "show":
|
|
7754
8436
|
return workflowShow(rootDir, args[0], options);
|
|
8437
|
+
case "handoff":
|
|
8438
|
+
return workflowHandoff(rootDir, args[0], options);
|
|
8439
|
+
case "decide":
|
|
8440
|
+
return workflowDecide(rootDir, args[0], options);
|
|
7755
8441
|
case "review":
|
|
7756
8442
|
return workflowReview(rootDir, args, options);
|
|
8443
|
+
case "requirements":
|
|
8444
|
+
return workflowRequirements(rootDir, args, options);
|
|
8445
|
+
case "verify":
|
|
8446
|
+
return workflowVerify(rootDir, args, options);
|
|
7757
8447
|
case "clean":
|
|
7758
8448
|
return workflowClean(rootDir, options);
|
|
7759
8449
|
default:
|
|
@@ -7768,12 +8458,278 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
|
7768
8458
|
} else {
|
|
7769
8459
|
console.error(`\u2717 Invalid workflow subcommand: ${subcommand}`);
|
|
7770
8460
|
console.error(
|
|
7771
|
-
" Use: create <man|manba|manteam> <task> |
|
|
8461
|
+
" Use: create <man|manba|manteam> <task> | requirements <taskId> finalize --file <path> | verify <taskId> <init|record|require-manual|confirm-manual|show> | decide <taskId> --plan-decision <plan_only|governed_execution> | handoff <taskId> --to solo | review <taskId> <init|complete|remediate|skip|show> | list | show <taskId> | clean"
|
|
7772
8462
|
);
|
|
7773
8463
|
}
|
|
7774
8464
|
return EXIT_INVALID_ARG2;
|
|
7775
8465
|
}
|
|
7776
8466
|
}
|
|
8467
|
+
async function workflowRequirements(rootDir, args, options) {
|
|
8468
|
+
const taskId = args[0];
|
|
8469
|
+
const action = args[1];
|
|
8470
|
+
if (!taskId || !isValidWorkflowTaskId(taskId)) {
|
|
8471
|
+
return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
|
|
8472
|
+
}
|
|
8473
|
+
if (action !== "finalize" || !options.file) {
|
|
8474
|
+
return invalidArg(options, "requirements requires finalize --file <path>");
|
|
8475
|
+
}
|
|
8476
|
+
const meta = await readWorkflow(rootDir, taskId);
|
|
8477
|
+
if (!meta || meta.mode !== "man" && meta.mode !== "manteam" || meta.status !== "in_progress" || meta.currentStep > 2) {
|
|
8478
|
+
return invalidArg(
|
|
8479
|
+
options,
|
|
8480
|
+
"requirements can only be finalized for an in-progress man or manteam workflow at step 1 or 2"
|
|
8481
|
+
);
|
|
8482
|
+
}
|
|
8483
|
+
const workflowPath = path28.join(rootDir, ".mancode", "workflows", taskId);
|
|
8484
|
+
const jsonPath = path28.join(workflowPath, "requirements.json");
|
|
8485
|
+
const markdownPath = path28.join(workflowPath, "requirements.md");
|
|
8486
|
+
const metadataPath2 = path28.join(workflowPath, "metadata.json");
|
|
8487
|
+
let originalJson;
|
|
8488
|
+
let originalMarkdown;
|
|
8489
|
+
let originalMetadata;
|
|
8490
|
+
try {
|
|
8491
|
+
const inputPath = path28.isAbsolute(options.file) ? options.file : path28.resolve(rootDir, options.file);
|
|
8492
|
+
const input = await readFile14(inputPath, "utf-8");
|
|
8493
|
+
const requirements = parseRequirementsLedger(input);
|
|
8494
|
+
[originalJson, originalMarkdown, originalMetadata] = await Promise.all([
|
|
8495
|
+
readOptionalText2(jsonPath),
|
|
8496
|
+
readOptionalText2(markdownPath),
|
|
8497
|
+
readFile14(metadataPath2, "utf-8")
|
|
8498
|
+
]);
|
|
8499
|
+
await writeRequirementsArtifacts(rootDir, taskId, requirements);
|
|
8500
|
+
await updateWorkflow(rootDir, taskId, {
|
|
8501
|
+
requirementsStatus: requirementsAreReady(requirements) ? "ready" : "needs_clarification",
|
|
8502
|
+
requirementsDigest: requirementsDigest(requirements)
|
|
8503
|
+
});
|
|
8504
|
+
if (options.json) {
|
|
8505
|
+
console.log(
|
|
8506
|
+
JSON.stringify(
|
|
8507
|
+
{
|
|
8508
|
+
requirements,
|
|
8509
|
+
requirementsStatus: requirementsAreReady(requirements) ? "ready" : "needs_clarification"
|
|
8510
|
+
},
|
|
8511
|
+
null,
|
|
8512
|
+
2
|
|
8513
|
+
)
|
|
8514
|
+
);
|
|
8515
|
+
} else {
|
|
8516
|
+
console.log(
|
|
8517
|
+
`Finalized requirements: ${taskId} (${requirementsAreReady(requirements) ? "ready" : "needs_clarification"})`
|
|
8518
|
+
);
|
|
8519
|
+
}
|
|
8520
|
+
return EXIT_OK9;
|
|
8521
|
+
} catch (error) {
|
|
8522
|
+
let rollbackIncomplete = false;
|
|
8523
|
+
if (originalJson !== void 0 && originalMarkdown !== void 0 && originalMetadata !== void 0) {
|
|
8524
|
+
const rollback = await Promise.allSettled([
|
|
8525
|
+
restoreOptionalText(jsonPath, originalJson),
|
|
8526
|
+
restoreOptionalText(markdownPath, originalMarkdown),
|
|
8527
|
+
writeFile15(metadataPath2, originalMetadata, "utf-8")
|
|
8528
|
+
]);
|
|
8529
|
+
rollbackIncomplete = rollback.some(
|
|
8530
|
+
(result) => result.status === "rejected"
|
|
8531
|
+
);
|
|
8532
|
+
}
|
|
8533
|
+
const message = error instanceof Error ? error.message : "unable to finalize requirements";
|
|
8534
|
+
return invalidArg(
|
|
8535
|
+
options,
|
|
8536
|
+
rollbackIncomplete ? `${message}; rollback was incomplete` : message
|
|
8537
|
+
);
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8540
|
+
async function workflowVerify(rootDir, args, options) {
|
|
8541
|
+
const taskId = args[0];
|
|
8542
|
+
const action = args[1];
|
|
8543
|
+
if (!taskId || !isValidWorkflowTaskId(taskId)) {
|
|
8544
|
+
return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
|
|
8545
|
+
}
|
|
8546
|
+
const meta = await readWorkflow(rootDir, taskId);
|
|
8547
|
+
if (!meta || meta.mode !== "man" && meta.mode !== "manteam") {
|
|
8548
|
+
return invalidArg(options, `verification is not valid for: ${taskId}`);
|
|
8549
|
+
}
|
|
8550
|
+
if (action === "show") {
|
|
8551
|
+
const ledger = await readVerificationLedger(rootDir, taskId);
|
|
8552
|
+
if (!ledger) {
|
|
8553
|
+
return invalidArg(options, `verification not initialized: ${taskId}`);
|
|
8554
|
+
}
|
|
8555
|
+
outputVerificationLedger(ledger, options);
|
|
8556
|
+
return EXIT_OK9;
|
|
8557
|
+
}
|
|
8558
|
+
if (meta.verificationPolicyVersion !== 1 || (action === "init" ? meta.currentStep !== 6 : meta.currentStep !== 6 && meta.currentStep !== 9) || meta.planDecision !== "governed_execution" || meta.status !== "in_progress" && meta.status !== "blocked") {
|
|
8559
|
+
return invalidArg(
|
|
8560
|
+
options,
|
|
8561
|
+
"verification requires a governed policy-v2 workflow at step 6, or step 9 when refreshing evidence"
|
|
8562
|
+
);
|
|
8563
|
+
}
|
|
8564
|
+
try {
|
|
8565
|
+
let ledger;
|
|
8566
|
+
if (action === "init") {
|
|
8567
|
+
if (await readVerificationLedger(rootDir, taskId)) {
|
|
8568
|
+
return invalidArg(
|
|
8569
|
+
options,
|
|
8570
|
+
`verification already initialized: ${taskId}`
|
|
8571
|
+
);
|
|
8572
|
+
}
|
|
8573
|
+
const requirements = await readRequirementsLedger(rootDir, taskId);
|
|
8574
|
+
if (!requirements || !requirementsAreReady(requirements)) {
|
|
8575
|
+
return invalidArg(options, "ready requirements.json is required");
|
|
8576
|
+
}
|
|
8577
|
+
ledger = initializeVerificationLedger(
|
|
8578
|
+
requirements,
|
|
8579
|
+
meta.planVersion ?? 1,
|
|
8580
|
+
0
|
|
8581
|
+
);
|
|
8582
|
+
} else {
|
|
8583
|
+
let existing = await readVerificationLedger(rootDir, taskId);
|
|
8584
|
+
if (!existing) {
|
|
8585
|
+
return invalidArg(options, `verification not initialized: ${taskId}`);
|
|
8586
|
+
}
|
|
8587
|
+
const review = await readReviewLedger(rootDir, taskId);
|
|
8588
|
+
existing = resetVerificationForRemediation(
|
|
8589
|
+
existing,
|
|
8590
|
+
review?.remediationRounds ?? 0
|
|
8591
|
+
);
|
|
8592
|
+
if (!options.acceptance || !options.evidence) {
|
|
8593
|
+
return invalidArg(
|
|
8594
|
+
options,
|
|
8595
|
+
`${action ?? "verification"} requires --acceptance and --evidence`
|
|
8596
|
+
);
|
|
8597
|
+
}
|
|
8598
|
+
if (action === "record") {
|
|
8599
|
+
if (options.method !== "automated" || options.result !== "passed" && options.result !== "failed" && options.result !== "blocked") {
|
|
8600
|
+
return invalidArg(
|
|
8601
|
+
options,
|
|
8602
|
+
"record requires --method automated and --result passed|failed|blocked"
|
|
8603
|
+
);
|
|
8604
|
+
}
|
|
8605
|
+
let automatedDetails;
|
|
8606
|
+
if (options.result === "passed" || options.result === "failed") {
|
|
8607
|
+
const exitCode = parseExactInteger(options.exitCode);
|
|
8608
|
+
if (!options.command?.trim() || exitCode === null) {
|
|
8609
|
+
return invalidArg(
|
|
8610
|
+
options,
|
|
8611
|
+
"automated passed/failed verification requires --command and --exit-code"
|
|
8612
|
+
);
|
|
8613
|
+
}
|
|
8614
|
+
if (options.evidenceFile) {
|
|
8615
|
+
const evidencePath = path28.isAbsolute(options.evidenceFile) ? options.evidenceFile : path28.resolve(rootDir, options.evidenceFile);
|
|
8616
|
+
if (!await pathExists12(evidencePath)) {
|
|
8617
|
+
return invalidArg(
|
|
8618
|
+
options,
|
|
8619
|
+
`verification evidence file not found: ${options.evidenceFile}`
|
|
8620
|
+
);
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
automatedDetails = {
|
|
8624
|
+
command: options.command,
|
|
8625
|
+
exitCode,
|
|
8626
|
+
...options.evidenceFile ? { evidenceFile: options.evidenceFile } : {}
|
|
8627
|
+
};
|
|
8628
|
+
}
|
|
8629
|
+
ledger = recordVerification(
|
|
8630
|
+
existing,
|
|
8631
|
+
options.acceptance,
|
|
8632
|
+
"automated",
|
|
8633
|
+
options.result,
|
|
8634
|
+
options.evidence,
|
|
8635
|
+
automatedDetails
|
|
8636
|
+
);
|
|
8637
|
+
} else if (action === "require-manual") {
|
|
8638
|
+
ledger = recordVerification(
|
|
8639
|
+
existing,
|
|
8640
|
+
options.acceptance,
|
|
8641
|
+
"manual",
|
|
8642
|
+
"manual_required",
|
|
8643
|
+
options.evidence
|
|
8644
|
+
);
|
|
8645
|
+
} else if (action === "confirm-manual") {
|
|
8646
|
+
ledger = confirmManualVerification(
|
|
8647
|
+
existing,
|
|
8648
|
+
options.acceptance,
|
|
8649
|
+
options.evidence
|
|
8650
|
+
);
|
|
8651
|
+
} else {
|
|
8652
|
+
return invalidArg(
|
|
8653
|
+
options,
|
|
8654
|
+
`invalid verification action: ${action ?? ""}`
|
|
8655
|
+
);
|
|
8656
|
+
}
|
|
8657
|
+
}
|
|
8658
|
+
const transitionError = await commitVerificationTransition(
|
|
8659
|
+
rootDir,
|
|
8660
|
+
meta,
|
|
8661
|
+
ledger
|
|
8662
|
+
);
|
|
8663
|
+
if (transitionError) return invalidArg(options, transitionError);
|
|
8664
|
+
outputVerificationLedger(ledger, options);
|
|
8665
|
+
return EXIT_OK9;
|
|
8666
|
+
} catch (error) {
|
|
8667
|
+
return invalidArg(
|
|
8668
|
+
options,
|
|
8669
|
+
error instanceof Error ? error.message : "unable to update verification"
|
|
8670
|
+
);
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
async function commitVerificationTransition(rootDir, meta, ledger) {
|
|
8674
|
+
const ledgerPath = verificationLedgerPath(rootDir, meta.taskId);
|
|
8675
|
+
const metadataPath2 = path28.join(
|
|
8676
|
+
rootDir,
|
|
8677
|
+
".mancode",
|
|
8678
|
+
"workflows",
|
|
8679
|
+
meta.taskId,
|
|
8680
|
+
"metadata.json"
|
|
8681
|
+
);
|
|
8682
|
+
const specPath = path28.join(rootDir, ".mancode", "memory", "spec.md");
|
|
8683
|
+
const [originalLedger, originalMetadata, originalSpec] = await Promise.all([
|
|
8684
|
+
readOptionalText2(ledgerPath),
|
|
8685
|
+
readFile14(metadataPath2, "utf-8"),
|
|
8686
|
+
readOptionalText2(specPath)
|
|
8687
|
+
]);
|
|
8688
|
+
const wasVerificationBlocked = meta.status === "blocked" && meta.blockingReason?.startsWith("[verification]");
|
|
8689
|
+
const shouldBlock = ledger.status === "manual_required" || ledger.status === "blocked";
|
|
8690
|
+
const workflowPatch = {
|
|
8691
|
+
verificationStatus: ledger.status,
|
|
8692
|
+
...shouldBlock ? {
|
|
8693
|
+
status: "blocked",
|
|
8694
|
+
blockingReason: `[verification] ${ledger.status === "manual_required" ? "manual confirmation required" : "verification blocked"}`
|
|
8695
|
+
} : wasVerificationBlocked ? { status: "in_progress", blockingReason: void 0 } : {}
|
|
8696
|
+
};
|
|
8697
|
+
try {
|
|
8698
|
+
await writeVerificationLedger(rootDir, meta.taskId, ledger);
|
|
8699
|
+
await updateWorkflow(rootDir, meta.taskId, workflowPatch, {
|
|
8700
|
+
allowIncompleteVerification: true
|
|
8701
|
+
});
|
|
8702
|
+
const updated = await readWorkflow(rootDir, meta.taskId);
|
|
8703
|
+
if (updated) {
|
|
8704
|
+
await upsertActivePlan(rootDir, {
|
|
8705
|
+
taskId: updated.taskId,
|
|
8706
|
+
status: updated.status,
|
|
8707
|
+
planVersion: updated.planVersion ?? 1
|
|
8708
|
+
});
|
|
8709
|
+
}
|
|
8710
|
+
return null;
|
|
8711
|
+
} catch (error) {
|
|
8712
|
+
const rollback = await Promise.allSettled([
|
|
8713
|
+
restoreOptionalText(ledgerPath, originalLedger),
|
|
8714
|
+
writeFile15(metadataPath2, originalMetadata, "utf-8"),
|
|
8715
|
+
restoreOptionalText(specPath, originalSpec)
|
|
8716
|
+
]);
|
|
8717
|
+
const message = error instanceof Error ? error.message : "verification transition failed";
|
|
8718
|
+
return rollback.some((result) => result.status === "rejected") ? `${message}; rollback was incomplete` : message;
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
function outputVerificationLedger(ledger, options) {
|
|
8722
|
+
if (options.json) {
|
|
8723
|
+
console.log(JSON.stringify(ledger, null, 2));
|
|
8724
|
+
return;
|
|
8725
|
+
}
|
|
8726
|
+
const passed = ledger.checks.filter(
|
|
8727
|
+
(check) => [check.automated, check.manual].filter(Boolean).every((component) => component?.status === "passed")
|
|
8728
|
+
).length;
|
|
8729
|
+
console.log(
|
|
8730
|
+
`Verification: ${ledger.status}; criteria ${passed}/${ledger.checks.length}; plan v${ledger.planVersion}`
|
|
8731
|
+
);
|
|
8732
|
+
}
|
|
7777
8733
|
async function workflowReview(rootDir, args, options) {
|
|
7778
8734
|
const taskId = args[0];
|
|
7779
8735
|
const action = args[1];
|
|
@@ -7798,6 +8754,36 @@ async function workflowReview(rootDir, args, options) {
|
|
|
7798
8754
|
"review requires an in_progress workflow at step 6 or later"
|
|
7799
8755
|
);
|
|
7800
8756
|
}
|
|
8757
|
+
if ((action === "init" || action === "skip") && meta.verificationPolicyVersion === 1 && !await verificationCanAdvance(rootDir, taskId, meta.planVersion ?? 1)) {
|
|
8758
|
+
return invalidArg(
|
|
8759
|
+
options,
|
|
8760
|
+
"workflow verification must pass before review can start"
|
|
8761
|
+
);
|
|
8762
|
+
}
|
|
8763
|
+
if (action === "skip") {
|
|
8764
|
+
if (meta.reviewPolicyVersion !== 2 || meta.currentStep !== 6 || meta.planDecision !== "governed_execution" || !options.reason?.trim()) {
|
|
8765
|
+
return invalidArg(
|
|
8766
|
+
options,
|
|
8767
|
+
"review skip requires a governed policy-v2 workflow at step 6 and --reason"
|
|
8768
|
+
);
|
|
8769
|
+
}
|
|
8770
|
+
try {
|
|
8771
|
+
const error = await commitReviewSkip(
|
|
8772
|
+
rootDir,
|
|
8773
|
+
meta,
|
|
8774
|
+
options.reason.trim()
|
|
8775
|
+
);
|
|
8776
|
+
if (error) return invalidArg(options, error);
|
|
8777
|
+
const ledger = await readReviewLedger(rootDir, taskId);
|
|
8778
|
+
outputReviewLedger(ledger, options);
|
|
8779
|
+
return EXIT_OK9;
|
|
8780
|
+
} catch (error) {
|
|
8781
|
+
return invalidArg(
|
|
8782
|
+
options,
|
|
8783
|
+
error instanceof Error ? error.message : "unable to skip review"
|
|
8784
|
+
);
|
|
8785
|
+
}
|
|
8786
|
+
}
|
|
7801
8787
|
try {
|
|
7802
8788
|
let ledger;
|
|
7803
8789
|
if (action === "init") {
|
|
@@ -7843,7 +8829,7 @@ async function workflowReview(rootDir, args, options) {
|
|
|
7843
8829
|
return invalidArg(
|
|
7844
8830
|
options,
|
|
7845
8831
|
`invalid review action: ${action ?? ""}`,
|
|
7846
|
-
"Use: mancode workflow review <taskId> <init|complete|remediate|show>"
|
|
8832
|
+
"Use: mancode workflow review <taskId> <init|complete|remediate|skip|show>"
|
|
7847
8833
|
);
|
|
7848
8834
|
}
|
|
7849
8835
|
outputReviewLedger(ledger, options);
|
|
@@ -7855,12 +8841,44 @@ async function workflowReview(rootDir, args, options) {
|
|
|
7855
8841
|
);
|
|
7856
8842
|
}
|
|
7857
8843
|
}
|
|
8844
|
+
async function commitReviewSkip(rootDir, meta, reason) {
|
|
8845
|
+
const ledgerPath = reviewLedgerPath(rootDir, meta.taskId);
|
|
8846
|
+
const metadataPath2 = path28.join(
|
|
8847
|
+
rootDir,
|
|
8848
|
+
".mancode",
|
|
8849
|
+
"workflows",
|
|
8850
|
+
meta.taskId,
|
|
8851
|
+
"metadata.json"
|
|
8852
|
+
);
|
|
8853
|
+
const [originalLedger, originalMetadata] = await Promise.all([
|
|
8854
|
+
readOptionalText2(ledgerPath),
|
|
8855
|
+
readFile14(metadataPath2, "utf-8")
|
|
8856
|
+
]);
|
|
8857
|
+
try {
|
|
8858
|
+
await initializeSkippedReview(rootDir, meta.taskId, reason);
|
|
8859
|
+
await updateWorkflow(rootDir, meta.taskId, {
|
|
8860
|
+
skippedSteps: [.../* @__PURE__ */ new Set([...meta.skippedSteps, "review"])]
|
|
8861
|
+
});
|
|
8862
|
+
return null;
|
|
8863
|
+
} catch (error) {
|
|
8864
|
+
const rollback = await Promise.allSettled([
|
|
8865
|
+
restoreOptionalText(ledgerPath, originalLedger),
|
|
8866
|
+
writeFile15(metadataPath2, originalMetadata, "utf-8")
|
|
8867
|
+
]);
|
|
8868
|
+
const message = error instanceof Error ? error.message : "review skip failed";
|
|
8869
|
+
return rollback.some((result) => result.status === "rejected") ? `${message}; rollback was incomplete` : message;
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
7858
8872
|
function outputReviewLedger(ledger, options) {
|
|
7859
8873
|
if (options.json) {
|
|
7860
8874
|
console.log(JSON.stringify(ledger, null, 2));
|
|
7861
8875
|
return;
|
|
7862
8876
|
}
|
|
7863
8877
|
if (!ledger) return;
|
|
8878
|
+
if (ledger.skipped) {
|
|
8879
|
+
console.log(`Review: skipped; reason ${ledger.skipped.reason}`);
|
|
8880
|
+
return;
|
|
8881
|
+
}
|
|
7864
8882
|
const openBlockers = ledger.blockers.filter(
|
|
7865
8883
|
(blocker) => blocker.status === "open"
|
|
7866
8884
|
);
|
|
@@ -7891,7 +8909,8 @@ async function workflowCreate(rootDir, args, options) {
|
|
|
7891
8909
|
}
|
|
7892
8910
|
try {
|
|
7893
8911
|
const meta = await createWorkflow(rootDir, task, mode, {
|
|
7894
|
-
parentTaskId: options.parentTask
|
|
8912
|
+
parentTaskId: options.parentTask,
|
|
8913
|
+
planningPolicyVersion: mode === "man" || mode === "manteam" ? 2 : void 0
|
|
7895
8914
|
});
|
|
7896
8915
|
if (options.json) {
|
|
7897
8916
|
console.log(JSON.stringify(meta, null, 2));
|
|
@@ -7911,7 +8930,7 @@ async function workflowUpdate(rootDir, taskId, options) {
|
|
|
7911
8930
|
return invalidArg(
|
|
7912
8931
|
options,
|
|
7913
8932
|
"missing taskId",
|
|
7914
|
-
"Use: mancode workflow update <taskId> [--step N] [--status in_progress|planned|completed|blocked|abandoned] [--blocking-reason <reason>] [--outcome <outcome>] [--plan-version N] [--skipped
|
|
8933
|
+
"Use: mancode workflow update <taskId> [--step N] [--status in_progress|planned|completed|blocked|abandoned] [--requirements-status ready|needs_clarification] [--blocking-reason <reason>] [--outcome <outcome>] [--plan-version N] [--skipped clarification]"
|
|
7915
8934
|
);
|
|
7916
8935
|
}
|
|
7917
8936
|
if (!isValidWorkflowTaskId(taskId)) {
|
|
@@ -7972,8 +8991,45 @@ async function workflowUpdate(rootDir, taskId, options) {
|
|
|
7972
8991
|
}
|
|
7973
8992
|
patch.planVersion = planVersion;
|
|
7974
8993
|
}
|
|
8994
|
+
if (options.requirementsStatus !== void 0) {
|
|
8995
|
+
if (!isRequirementsStatus(options.requirementsStatus) || existing.mode !== "man" && existing.mode !== "manteam") {
|
|
8996
|
+
return invalidArg(
|
|
8997
|
+
options,
|
|
8998
|
+
`invalid --requirements-status: ${options.requirementsStatus}`
|
|
8999
|
+
);
|
|
9000
|
+
}
|
|
9001
|
+
patch.requirementsStatus = options.requirementsStatus;
|
|
9002
|
+
}
|
|
9003
|
+
if (options.planDecision !== void 0) {
|
|
9004
|
+
if (options.planDecision !== "governed_execution" || existing.mode !== "man" && existing.mode !== "manteam") {
|
|
9005
|
+
return invalidArg(
|
|
9006
|
+
options,
|
|
9007
|
+
`invalid --plan-decision: ${options.planDecision}`
|
|
9008
|
+
);
|
|
9009
|
+
}
|
|
9010
|
+
patch.planDecision = options.planDecision;
|
|
9011
|
+
}
|
|
7975
9012
|
if (options.skipped !== void 0) {
|
|
7976
|
-
|
|
9013
|
+
const requestedSkipped = options.skipped.split(",").map((item) => item.trim()).filter(Boolean);
|
|
9014
|
+
if (existing.reviewPolicyVersion === 2) {
|
|
9015
|
+
if (requestedSkipped.includes("review")) {
|
|
9016
|
+
return invalidArg(
|
|
9017
|
+
options,
|
|
9018
|
+
"policy-v2 review skips require workflow review <taskId> skip --reason <reason>"
|
|
9019
|
+
);
|
|
9020
|
+
}
|
|
9021
|
+
if (existing.currentStep > 2 || requestedSkipped.some((item) => item !== "clarification")) {
|
|
9022
|
+
return invalidArg(
|
|
9023
|
+
options,
|
|
9024
|
+
"policy-v2 clarification can only be skipped at step 1 or 2"
|
|
9025
|
+
);
|
|
9026
|
+
}
|
|
9027
|
+
patch.skippedSteps = [
|
|
9028
|
+
.../* @__PURE__ */ new Set([...existing.skippedSteps, ...requestedSkipped])
|
|
9029
|
+
];
|
|
9030
|
+
} else {
|
|
9031
|
+
patch.skippedSteps = requestedSkipped;
|
|
9032
|
+
}
|
|
7977
9033
|
}
|
|
7978
9034
|
if (Object.keys(patch).length === 0) {
|
|
7979
9035
|
return invalidArg(options, "missing update fields");
|
|
@@ -8004,6 +9060,307 @@ async function workflowUpdate(rootDir, taskId, options) {
|
|
|
8004
9060
|
}
|
|
8005
9061
|
return EXIT_OK9;
|
|
8006
9062
|
}
|
|
9063
|
+
async function workflowHandoff(rootDir, taskId, options) {
|
|
9064
|
+
if (!taskId || !isValidWorkflowTaskId(taskId)) {
|
|
9065
|
+
return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
|
|
9066
|
+
}
|
|
9067
|
+
if (options.complete) {
|
|
9068
|
+
if (options.to !== void 0) {
|
|
9069
|
+
return invalidArg(options, "--complete cannot be combined with --to");
|
|
9070
|
+
}
|
|
9071
|
+
return workflowCompleteHandoff(rootDir, taskId, options);
|
|
9072
|
+
}
|
|
9073
|
+
if (options.to !== "solo") {
|
|
9074
|
+
return invalidArg(options, "handoff requires --to solo");
|
|
9075
|
+
}
|
|
9076
|
+
const meta = await readWorkflow(rootDir, taskId);
|
|
9077
|
+
if (!meta || meta.mode !== "man" && meta.mode !== "manteam") {
|
|
9078
|
+
return invalidArg(options, `workflow cannot be handed off: ${taskId}`);
|
|
9079
|
+
}
|
|
9080
|
+
if (meta.status !== "in_progress" || meta.currentStep !== 4 || meta.requirementsStatus !== "ready" || meta.planDecision !== void 0) {
|
|
9081
|
+
return invalidArg(
|
|
9082
|
+
options,
|
|
9083
|
+
"solo handoff requires an undecided in-progress workflow at step 4 with ready requirements"
|
|
9084
|
+
);
|
|
9085
|
+
}
|
|
9086
|
+
const workflowPath = path28.join(rootDir, ".mancode", "workflows", taskId);
|
|
9087
|
+
if (!await pathExists12(path28.join(workflowPath, "requirements.md")) || !await pathExists12(path28.join(workflowPath, "plan.md"))) {
|
|
9088
|
+
return invalidArg(
|
|
9089
|
+
options,
|
|
9090
|
+
"solo handoff requires requirements.md and plan.md"
|
|
9091
|
+
);
|
|
9092
|
+
}
|
|
9093
|
+
const statePath = path28.join(rootDir, ".mancode", "state.json");
|
|
9094
|
+
let originalState;
|
|
9095
|
+
let state;
|
|
9096
|
+
try {
|
|
9097
|
+
originalState = await readFile14(statePath, "utf-8");
|
|
9098
|
+
state = JSON.parse(originalState);
|
|
9099
|
+
} catch {
|
|
9100
|
+
return invalidArg(
|
|
9101
|
+
options,
|
|
9102
|
+
"unable to read .mancode/state.json for handoff"
|
|
9103
|
+
);
|
|
9104
|
+
}
|
|
9105
|
+
if (state.currentTask !== taskId) {
|
|
9106
|
+
return invalidArg(
|
|
9107
|
+
options,
|
|
9108
|
+
"current workflow state does not match the handoff task"
|
|
9109
|
+
);
|
|
9110
|
+
}
|
|
9111
|
+
const activeSoloPlan = readActiveSoloPlan(state.activeSoloPlan);
|
|
9112
|
+
if (activeSoloPlan && activeSoloPlan.taskId !== taskId) {
|
|
9113
|
+
return invalidArg(
|
|
9114
|
+
options,
|
|
9115
|
+
`another solo plan is active: ${activeSoloPlan.taskId}; complete or abandon it before handoff`
|
|
9116
|
+
);
|
|
9117
|
+
}
|
|
9118
|
+
const nextState = {
|
|
9119
|
+
...state,
|
|
9120
|
+
currentMode: "solo",
|
|
9121
|
+
lastMode: typeof state.currentMode === "string" ? state.currentMode : meta.mode,
|
|
9122
|
+
currentTask: null,
|
|
9123
|
+
currentWorkflowMode: null,
|
|
9124
|
+
skippedSteps: [],
|
|
9125
|
+
activeSoloPlan: {
|
|
9126
|
+
taskId,
|
|
9127
|
+
planVersion: meta.planVersion ?? 1
|
|
9128
|
+
}
|
|
9129
|
+
};
|
|
9130
|
+
const transitionError = await commitPlanningTransition({
|
|
9131
|
+
rootDir,
|
|
9132
|
+
taskId,
|
|
9133
|
+
statePath,
|
|
9134
|
+
originalState,
|
|
9135
|
+
nextState,
|
|
9136
|
+
workflowPatch: {
|
|
9137
|
+
status: "planned",
|
|
9138
|
+
planDecision: "solo_handoff"
|
|
9139
|
+
},
|
|
9140
|
+
activePlanStatus: "planned",
|
|
9141
|
+
planVersion: meta.planVersion ?? 1
|
|
9142
|
+
});
|
|
9143
|
+
if (transitionError) {
|
|
9144
|
+
return invalidArg(options, transitionError);
|
|
9145
|
+
}
|
|
9146
|
+
const result = await readWorkflow(rootDir, taskId);
|
|
9147
|
+
if (options.json) {
|
|
9148
|
+
console.log(
|
|
9149
|
+
JSON.stringify(
|
|
9150
|
+
{ workflow: result, activeSoloPlan: nextState.activeSoloPlan },
|
|
9151
|
+
null,
|
|
9152
|
+
2
|
|
9153
|
+
)
|
|
9154
|
+
);
|
|
9155
|
+
} else {
|
|
9156
|
+
console.log(`Handed off plan to solo: ${taskId}`);
|
|
9157
|
+
}
|
|
9158
|
+
return EXIT_OK9;
|
|
9159
|
+
}
|
|
9160
|
+
async function workflowCompleteHandoff(rootDir, taskId, options) {
|
|
9161
|
+
const meta = await readWorkflow(rootDir, taskId);
|
|
9162
|
+
if (!meta || meta.mode !== "man" && meta.mode !== "manteam" || meta.status !== "planned" || meta.currentStep !== 4 || meta.planDecision !== "solo_handoff") {
|
|
9163
|
+
return invalidArg(
|
|
9164
|
+
options,
|
|
9165
|
+
`workflow is not an active solo handoff: ${taskId}`
|
|
9166
|
+
);
|
|
9167
|
+
}
|
|
9168
|
+
const statePath = path28.join(rootDir, ".mancode", "state.json");
|
|
9169
|
+
let originalState;
|
|
9170
|
+
let state;
|
|
9171
|
+
try {
|
|
9172
|
+
originalState = await readFile14(statePath, "utf-8");
|
|
9173
|
+
state = JSON.parse(originalState);
|
|
9174
|
+
} catch {
|
|
9175
|
+
return invalidArg(
|
|
9176
|
+
options,
|
|
9177
|
+
"unable to read .mancode/state.json for handoff completion"
|
|
9178
|
+
);
|
|
9179
|
+
}
|
|
9180
|
+
const activeSoloPlan = readActiveSoloPlan(state.activeSoloPlan);
|
|
9181
|
+
if (activeSoloPlan?.taskId !== taskId) {
|
|
9182
|
+
return invalidArg(
|
|
9183
|
+
options,
|
|
9184
|
+
"active solo plan does not match the handoff task"
|
|
9185
|
+
);
|
|
9186
|
+
}
|
|
9187
|
+
const nextState = {
|
|
9188
|
+
...state,
|
|
9189
|
+
activeSoloPlan: null
|
|
9190
|
+
};
|
|
9191
|
+
const transitionError = await commitPlanningTransition({
|
|
9192
|
+
rootDir,
|
|
9193
|
+
taskId,
|
|
9194
|
+
statePath,
|
|
9195
|
+
originalState,
|
|
9196
|
+
nextState,
|
|
9197
|
+
workflowPatch: { status: "completed" },
|
|
9198
|
+
activePlanStatus: "completed",
|
|
9199
|
+
planVersion: meta.planVersion ?? 1
|
|
9200
|
+
});
|
|
9201
|
+
if (transitionError) return invalidArg(options, transitionError);
|
|
9202
|
+
const result = await readWorkflow(rootDir, taskId);
|
|
9203
|
+
if (options.json) {
|
|
9204
|
+
console.log(
|
|
9205
|
+
JSON.stringify({ workflow: result, activeSoloPlan: null }, null, 2)
|
|
9206
|
+
);
|
|
9207
|
+
} else {
|
|
9208
|
+
console.log(`Completed solo handoff: ${taskId}`);
|
|
9209
|
+
}
|
|
9210
|
+
return EXIT_OK9;
|
|
9211
|
+
}
|
|
9212
|
+
async function workflowDecide(rootDir, taskId, options) {
|
|
9213
|
+
if (!taskId || !isValidWorkflowTaskId(taskId)) {
|
|
9214
|
+
return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
|
|
9215
|
+
}
|
|
9216
|
+
if (!isPlanDecision(options.planDecision) || options.planDecision === "solo_handoff") {
|
|
9217
|
+
return invalidArg(
|
|
9218
|
+
options,
|
|
9219
|
+
"decide requires --plan-decision plan_only or governed_execution"
|
|
9220
|
+
);
|
|
9221
|
+
}
|
|
9222
|
+
if (options.planDecision === "governed_execution") {
|
|
9223
|
+
return workflowUpdate(rootDir, taskId, {
|
|
9224
|
+
...options,
|
|
9225
|
+
planDecision: "governed_execution"
|
|
9226
|
+
});
|
|
9227
|
+
}
|
|
9228
|
+
const meta = await readWorkflow(rootDir, taskId);
|
|
9229
|
+
if (!meta || meta.mode !== "man" && meta.mode !== "manteam" || meta.status !== "in_progress" || meta.currentStep !== 4 || meta.requirementsStatus !== "ready" || meta.planDecision !== void 0) {
|
|
9230
|
+
return invalidArg(
|
|
9231
|
+
options,
|
|
9232
|
+
`workflow is not ready for a plan-only decision: ${taskId}`
|
|
9233
|
+
);
|
|
9234
|
+
}
|
|
9235
|
+
const workflowPath = path28.join(rootDir, ".mancode", "workflows", taskId);
|
|
9236
|
+
if (!await pathExists12(path28.join(workflowPath, "requirements.md")) || !await pathExists12(path28.join(workflowPath, "plan.md"))) {
|
|
9237
|
+
return invalidArg(
|
|
9238
|
+
options,
|
|
9239
|
+
"plan-only requires requirements.md and plan.md"
|
|
9240
|
+
);
|
|
9241
|
+
}
|
|
9242
|
+
const statePath = path28.join(rootDir, ".mancode", "state.json");
|
|
9243
|
+
let originalState;
|
|
9244
|
+
let state;
|
|
9245
|
+
try {
|
|
9246
|
+
originalState = await readFile14(statePath, "utf-8");
|
|
9247
|
+
state = JSON.parse(originalState);
|
|
9248
|
+
} catch {
|
|
9249
|
+
return invalidArg(
|
|
9250
|
+
options,
|
|
9251
|
+
"unable to read .mancode/state.json for decision"
|
|
9252
|
+
);
|
|
9253
|
+
}
|
|
9254
|
+
if (state.currentTask !== taskId) {
|
|
9255
|
+
return invalidArg(
|
|
9256
|
+
options,
|
|
9257
|
+
"current workflow state does not match the decision task"
|
|
9258
|
+
);
|
|
9259
|
+
}
|
|
9260
|
+
const activeSoloPlan = readActiveSoloPlan(state.activeSoloPlan);
|
|
9261
|
+
if (activeSoloPlan) {
|
|
9262
|
+
return invalidArg(
|
|
9263
|
+
options,
|
|
9264
|
+
`another solo plan is active: ${activeSoloPlan.taskId}; resolve it before plan-only`
|
|
9265
|
+
);
|
|
9266
|
+
}
|
|
9267
|
+
const nextState = {
|
|
9268
|
+
...state,
|
|
9269
|
+
currentMode: "solo",
|
|
9270
|
+
lastMode: typeof state.currentMode === "string" ? state.currentMode : meta.mode,
|
|
9271
|
+
currentTask: null,
|
|
9272
|
+
currentWorkflowMode: null,
|
|
9273
|
+
skippedSteps: [],
|
|
9274
|
+
activeSoloPlan: null
|
|
9275
|
+
};
|
|
9276
|
+
const transitionError = await commitPlanningTransition({
|
|
9277
|
+
rootDir,
|
|
9278
|
+
taskId,
|
|
9279
|
+
statePath,
|
|
9280
|
+
originalState,
|
|
9281
|
+
nextState,
|
|
9282
|
+
workflowPatch: { status: "planned", planDecision: "plan_only" },
|
|
9283
|
+
activePlanStatus: "planned",
|
|
9284
|
+
planVersion: meta.planVersion ?? 1
|
|
9285
|
+
});
|
|
9286
|
+
if (transitionError) return invalidArg(options, transitionError);
|
|
9287
|
+
const result = await readWorkflow(rootDir, taskId);
|
|
9288
|
+
if (options.json) {
|
|
9289
|
+
console.log(
|
|
9290
|
+
JSON.stringify({ workflow: result, state: nextState }, null, 2)
|
|
9291
|
+
);
|
|
9292
|
+
} else {
|
|
9293
|
+
console.log(`Saved plan without execution: ${taskId}`);
|
|
9294
|
+
}
|
|
9295
|
+
return EXIT_OK9;
|
|
9296
|
+
}
|
|
9297
|
+
async function commitPlanningTransition(args) {
|
|
9298
|
+
const metadataPath2 = path28.join(
|
|
9299
|
+
args.rootDir,
|
|
9300
|
+
".mancode",
|
|
9301
|
+
"workflows",
|
|
9302
|
+
args.taskId,
|
|
9303
|
+
"metadata.json"
|
|
9304
|
+
);
|
|
9305
|
+
const specPath = path28.join(args.rootDir, ".mancode", "memory", "spec.md");
|
|
9306
|
+
let originalMetadata;
|
|
9307
|
+
let originalSpec;
|
|
9308
|
+
try {
|
|
9309
|
+
originalMetadata = await readFile14(metadataPath2, "utf-8");
|
|
9310
|
+
originalSpec = await readOptionalText2(specPath);
|
|
9311
|
+
} catch (error) {
|
|
9312
|
+
return error instanceof Error ? `unable to prepare planning transition: ${error.message}` : "unable to prepare planning transition";
|
|
9313
|
+
}
|
|
9314
|
+
try {
|
|
9315
|
+
await updateWorkflow(args.rootDir, args.taskId, args.workflowPatch);
|
|
9316
|
+
await writeFile15(
|
|
9317
|
+
args.statePath,
|
|
9318
|
+
`${JSON.stringify(args.nextState, null, 2)}
|
|
9319
|
+
`,
|
|
9320
|
+
"utf-8"
|
|
9321
|
+
);
|
|
9322
|
+
await upsertActivePlan(args.rootDir, {
|
|
9323
|
+
taskId: args.taskId,
|
|
9324
|
+
status: args.activePlanStatus,
|
|
9325
|
+
planVersion: args.planVersion
|
|
9326
|
+
});
|
|
9327
|
+
return null;
|
|
9328
|
+
} catch (error) {
|
|
9329
|
+
const rollback = await Promise.allSettled([
|
|
9330
|
+
writeFile15(metadataPath2, originalMetadata, "utf-8"),
|
|
9331
|
+
writeFile15(args.statePath, args.originalState, "utf-8"),
|
|
9332
|
+
originalSpec === null ? rm6(specPath, { force: true }) : writeFile15(specPath, originalSpec, "utf-8")
|
|
9333
|
+
]);
|
|
9334
|
+
const message = error instanceof Error ? error.message : "planning transition failed";
|
|
9335
|
+
return rollback.some((result) => result.status === "rejected") ? `${message}; rollback was incomplete` : message;
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
async function readOptionalText2(filePath) {
|
|
9339
|
+
try {
|
|
9340
|
+
return await readFile14(filePath, "utf-8");
|
|
9341
|
+
} catch (error) {
|
|
9342
|
+
if (isNodeError10(error) && error.code === "ENOENT") return null;
|
|
9343
|
+
throw error;
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
async function restoreOptionalText(filePath, content) {
|
|
9347
|
+
if (content === null) {
|
|
9348
|
+
await rm6(filePath, { force: true });
|
|
9349
|
+
return;
|
|
9350
|
+
}
|
|
9351
|
+
await writeFile15(filePath, content, "utf-8");
|
|
9352
|
+
}
|
|
9353
|
+
function readActiveSoloPlan(value) {
|
|
9354
|
+
if (!value || typeof value !== "object") return null;
|
|
9355
|
+
const plan = value;
|
|
9356
|
+
if (typeof plan.taskId !== "string" || !Number.isInteger(plan.planVersion)) {
|
|
9357
|
+
return null;
|
|
9358
|
+
}
|
|
9359
|
+
return { taskId: plan.taskId, planVersion: plan.planVersion };
|
|
9360
|
+
}
|
|
9361
|
+
function isNodeError10(error) {
|
|
9362
|
+
return error instanceof Error && "code" in error;
|
|
9363
|
+
}
|
|
8007
9364
|
async function workflowList(rootDir, options) {
|
|
8008
9365
|
const workflows = await listWorkflows(rootDir);
|
|
8009
9366
|
const views = attachActiveChildren(workflows);
|
|
@@ -8075,6 +9432,11 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
8075
9432
|
if (meta.parentTaskId) console.log(`Parent: ${meta.parentTaskId}`);
|
|
8076
9433
|
if (meta.planVersion !== void 0)
|
|
8077
9434
|
console.log(`Plan version: ${meta.planVersion}`);
|
|
9435
|
+
if (meta.requirementsStatus)
|
|
9436
|
+
console.log(`Requirements: ${meta.requirementsStatus}`);
|
|
9437
|
+
if (meta.verificationStatus)
|
|
9438
|
+
console.log(`Verification: ${meta.verificationStatus}`);
|
|
9439
|
+
if (meta.planDecision) console.log(`Plan choice: ${meta.planDecision}`);
|
|
8078
9440
|
if (meta.outcome) console.log(`Outcome: ${meta.outcome}`);
|
|
8079
9441
|
if (meta.blockingReason) console.log(`Blocked: ${meta.blockingReason}`);
|
|
8080
9442
|
if (children !== void 0) {
|
|
@@ -8193,6 +9555,11 @@ function parseExactPositiveInteger(value) {
|
|
|
8193
9555
|
const parsed = Number(value);
|
|
8194
9556
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
8195
9557
|
}
|
|
9558
|
+
function parseExactInteger(value) {
|
|
9559
|
+
if (!value || !/^-?\d+$/.test(value)) return null;
|
|
9560
|
+
const parsed = Number(value);
|
|
9561
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
9562
|
+
}
|
|
8196
9563
|
function parseOlderThan(value) {
|
|
8197
9564
|
if (!value) return void 0;
|
|
8198
9565
|
const match = /^(\d+)([dhm])$/.exec(value);
|
|
@@ -8267,13 +9634,22 @@ program.command("uninstall [platform]").description("Remove platform adapter or
|
|
|
8267
9634
|
program.command("workflow <subcommand> [args...]").description("Manage mancode workflows").option("--dry-run", "Preview clean without deleting").option("--older-than <duration>", "Clean workflows older than (e.g. 30d)").option("--step <n>", "Update workflow current step").option("--status <status>", "Update workflow status").option(
|
|
8268
9635
|
"--parent-task <taskId>",
|
|
8269
9636
|
"Parent /man or /manteam workflow for manba"
|
|
8270
|
-
).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set manba outcome").option("--plan-version <n>", "Set the next man/manteam plan revision").option(
|
|
9637
|
+
).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set manba outcome").option("--plan-version <n>", "Set the next man/manteam plan revision").option(
|
|
9638
|
+
"--requirements-status <status>",
|
|
9639
|
+
"Planning readiness: ready or needs_clarification"
|
|
9640
|
+
).option(
|
|
9641
|
+
"--plan-decision <decision>",
|
|
9642
|
+
"Plan gate choice: plan_only or governed_execution"
|
|
9643
|
+
).option("--to <mode>", "Workflow handoff target (solo)").option("--complete", "Complete an active solo handoff").option(
|
|
9644
|
+
"--skipped <steps>",
|
|
9645
|
+
"Policy v2: clarification only; use workflow review skip for review"
|
|
9646
|
+
).option("--review-depth <depth>", "Review depth: targeted or full").option("--review-domain <domain>", "Review domain: quality or security").option(
|
|
8271
9647
|
"--report <path>",
|
|
8272
9648
|
"Relative Markdown report path for a review domain"
|
|
8273
9649
|
).option("--blockers <ids>", "Comma-separated blocker ids found by a review").option(
|
|
8274
9650
|
"--resolved <ids>",
|
|
8275
9651
|
"Comma-separated blocker ids resolved in remediation"
|
|
8276
|
-
).option("--json", "Output as JSON (for scripts)").action(async (subcommand, args, options) => {
|
|
9652
|
+
).option("--file <path>", "Structured requirements JSON input file").option("--acceptance <id>", "Acceptance criterion id (for example AC-1)").option("--method <method>", "Verification method: automated or manual").option("--result <result>", "Verification result").option("--evidence <text>", "Verification evidence or user confirmation").option("--command <command>", "Command used for automated verification").option("--exit-code <code>", "Exit code from automated verification").option("--evidence-file <path>", "Existing verification report or artifact").option("--reason <reason>", "Reason for an explicit review skip").option("--json", "Output as JSON (for scripts)").action(async (subcommand, args, options) => {
|
|
8277
9653
|
const code = await workflow(process.cwd(), subcommand, args ?? [], options);
|
|
8278
9654
|
process.exitCode = code;
|
|
8279
9655
|
});
|