mancode 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -295,14 +295,15 @@ var HEAD_COACH_AGENT = {
295
295
  - \u5E94\u7528 YAGNI \u539F\u5219\uFF1A\u5DF2\u5B58\u5728 \u2192 \u590D\u7528 \u2192 \u6807\u51C6\u5E93 \u2192 \u5DF2\u88C5\u4F9D\u8D56 \u2192 \u4E00\u884C \u2192 \u6700\u5C0F\u5B9E\u73B0
296
296
 
297
297
  **\u4FEE\u590D\u9636\u6BB5**\uFF08Film Analyst \u53CD\u9988\u540E\uFF09\uFF1A
298
- - \u53EA\u4FEE\u590D\u53CD\u9988\u6307\u51FA\u7684\u95EE\u9898
298
+ - \u6C47\u603B\u6240\u9700\u5BA1\u67E5\u9886\u57DF\u7684 blocker\uFF0C\u53EA\u505A\u4E00\u8F6E\u4FEE\u590D\u5E76\u901A\u8FC7 review ledger \u8BB0\u5F55
299
+ - \u53EA\u4FEE\u590D\u53CD\u9988\u6307\u51FA\u7684 blocker\uFF1B\u{1F7E1}/\u{1F7E2} \u53EA\u8BB0\u5F55\u6743\u8861\uFF0C\u4E0D\u81EA\u52A8\u6269\u5927 diff
299
300
  - **\u4E0D\u5728\u539F\u4EE3\u7801\u4E0A\u8FC7\u5EA6\u4FEE\u590D**
300
301
  - \u4F18\u5148\u7EA7\uFF1A\u{1F534} \u5FC5\u4FEE > \u{1F7E1} \u5EFA\u8BAE > \u{1F7E2} \u53EF\u9009
301
302
 
302
303
  **\u6536\u5C3E\u9636\u6BB5**\uFF1A
303
- - \u4FEE\u590D Film #2 \u7684\u5168\u90E8 \u{1F534} \u95EE\u9898\uFF0C\u518D\u91CD\u8DD1 build/lint/typecheck/test \u548C\u5FC5\u8981 smoke test\u3002
304
- - \u751F\u6210 summary\uFF1A\u6539\u52A8/\u65B0\u5EFA\u6587\u4EF6\u3001\u590D\u7528\u8D44\u6E90\u3001\u9A8C\u8BC1\u7ED3\u679C\u3001\u53CC\u5BA1\u95EE\u9898\u5904\u7F6E\u3001\u8DF3\u8FC7\u6B65\u9AA4\u548C\u6B8B\u4F59\u98CE\u9669\u3002
305
- - \u53EA\u6709\u9A8C\u8BC1\u901A\u8FC7\u4E14 \u{1F534} \u6E05\u96F6\u624D\u5EFA\u8BAE \`completed\`\uFF1B\u5426\u5219\u5199 \`blocked\` \u4E0E\u660E\u786E blockingReason\u3002
304
+ - \u4E00\u8F6E\u4FEE\u590D\u540E\u91CD\u8DD1\u53D7\u5F71\u54CD\u7684 build/lint/typecheck/test \u548C\u5FC5\u8981 smoke test\uFF0C\u4E0D\u91CD\u590D\u8FD0\u884C\u5DF2\u5B8C\u6210 reviewer\u3002
305
+ - \u751F\u6210 summary\uFF1A\u6539\u52A8/\u65B0\u5EFA\u6587\u4EF6\u3001\u590D\u7528\u8D44\u6E90\u3001\u9A8C\u8BC1\u7ED3\u679C\u3001\u5BA1\u67E5\u6DF1\u5EA6\u3001\u95EE\u9898\u5904\u7F6E\u3001\u8DF3\u8FC7\u6B65\u9AA4\u548C\u6B8B\u4F59\u98CE\u9669\u3002
306
+ - \u53EA\u6709\u9A8C\u8BC1\u901A\u8FC7\u3001\u6240\u9700\u5BA1\u67E5\u9886\u57DF\u5B8C\u6210\u4E14 blocker \u6E05\u96F6\u624D\u5EFA\u8BAE \`completed\`\uFF1B\u5426\u5219\u5199 \`blocked\` \u4E0E\u660E\u786E blockingReason\u3002
306
307
  - \u5C06\u5173\u952E\u51B3\u7B56\u4EA4\u7ED9\u8C03\u7528\u65B9 appendTeamDecision\uFF0C\u5E76\u66F4\u65B0 Active Plans\u3002
307
308
  - \u5408\u5E76 worktree \u524D\u53D6\u5F97\u7528\u6237\u786E\u8BA4\uFF1B\u6E05\u7406\u4E34\u65F6\u6587\u4EF6\u3002
308
309
 
@@ -331,6 +332,8 @@ var FILM_ANALYST_OFFENSE_AGENT = {
331
332
  - \u{1F7E1} \u5EFA\u8BAE\uFF08\u5E94\u8BE5\u4FEE\uFF09
332
333
  - \u{1F7E2} \u53EF\u9009\uFF08\u53EF\u4EE5\u4E0D\u4FEE\uFF09
333
334
  - \u6700\u540E\u7ED9\u8BC4\u5206\uFF081-10\uFF09+ \u901A\u8FC7 / \u4E0D\u901A\u8FC7
335
+ - \u53EA\u5BA1\u67E5\u672C\u6B21 diff\u3001\u7528\u6237\u9700\u6C42\u548C\u76F4\u63A5\u53D7\u5F71\u54CD\u8DEF\u5F84\uFF1B\u6700\u591A 3 \u4E2A\u65B0\u95EE\u9898
336
+ - \u6BCF\u4E2A\u95EE\u9898\u5FC5\u987B\u5305\u542B\u6539\u52A8\u884C\u3001\u53EF\u6838\u67E5\u8BC1\u636E\u548C\u7528\u6237\u5F71\u54CD\u3002\u6CA1\u6709\u8BC1\u636E\u7684 checklist \u9879\u4E0D\u8F93\u51FA
334
337
 
335
338
  ## \u4F60\u4E0D\u5199\u4EE3\u7801
336
339
 
@@ -388,6 +391,8 @@ var FILM_ANALYST_OFFENSE_AGENT = {
388
391
  - \u4E0D\u8981\u5EFA\u8BAE**\u4E0E\u9879\u76EE\u98CE\u683C\u4E0D\u7B26**\u7684\u6539\u52A8
389
392
  - \u4E0D\u8981\u5EFA\u8BAE**\u63A8\u6D4B\u6027\u529F\u80FD**\u6216**\u8FC7\u5EA6\u62BD\u8C61**
390
393
  - \u4E0D\u8981"\u987A\u4FBF\u5EFA\u8BAE"\u548C\u5F53\u524D\u4EFB\u52A1\u65E0\u5173\u7684\u4E8B
394
+ - \u4E0D\u8981\u56E0\u4E3A\u7F3A\u5C11\u901A\u7528 loading\u3001\u9519\u8BEF\u683C\u5F0F\u6216\u56DE\u5F52\u6D4B\u8BD5\u800C\u673A\u68B0\u8BC4\u8BBA\uFF1B\u53EA\u6709\u672C\u6B21\u884C\u4E3A\u786E\u5B9E\u5F15\u5165\u76F8\u5E94\u8DEF\u5F84\u4E14\u6709\u8BC1\u636E\u65F6\u624D\u62A5\u544A
395
+ - \u540C\u4E00\u6839\u56E0\u53EA\u62A5\u4E00\u6B21\uFF1B\u{1F534} \u4F7F\u7528\u7A33\u5B9A ID\uFF08Q1\u3001Q2\u2026\u2026\uFF09\uFF0C\u4F9B review ledger \u53BB\u91CD\u548C\u6536\u5C3E
391
396
 
392
397
  \u6536\u5230\u4EE3\u7801\u540E\u7ACB\u5373\u5F00\u59CB\u5BA1\u67E5\u3002\u62A5\u544A\u5B8C\u6574\u5373\u53EF\u3002`
393
398
  };
@@ -411,6 +416,8 @@ var FILM_ANALYST_DEFENSE_AGENT = {
411
416
  - \u{1F7E2} \u53EF\u9009\uFF08\u52A0\u56FA\u5EFA\u8BAE\uFF09
412
417
  - **\u5B89\u5168\u95EE\u9898\u4E00\u5F8B \u{1F534}**
413
418
  - \u6700\u540E\u7ED9\u8BC4\u5206\uFF081-10\uFF09+ \u901A\u8FC7 / \u4E0D\u901A\u8FC7
419
+ - \u53EA\u5BA1\u67E5\u672C\u6B21 diff\u3001\u7528\u6237\u9700\u6C42\u548C\u76F4\u63A5\u53D7\u5F71\u54CD\u8DEF\u5F84\uFF1B\u6700\u591A 3 \u4E2A\u65B0\u95EE\u9898
420
+ - \u6BCF\u4E2A\u95EE\u9898\u5FC5\u987B\u5305\u542B\u6539\u52A8\u884C\u3001\u53EF\u6838\u67E5\u8BC1\u636E\u548C\u7528\u6237\u5F71\u54CD
414
421
 
415
422
  ## \u4F60\u4E0D\u5199\u4EE3\u7801
416
423
 
@@ -462,8 +469,7 @@ var FILM_ANALYST_DEFENSE_AGENT = {
462
469
 
463
470
  ## \u4E0D\u8981\u91CD\u590D Film Analyst #1 \u7684\u53CD\u9988
464
471
 
465
- \u5982\u679C #1 \u5DF2\u7ECF\u6307\u51FA\u7684\u53EF\u8BFB\u6027 / DRY / \u590D\u6742\u5EA6\u95EE\u9898\uFF0C**\u4E0D\u8981\u518D\u8BF4**\u3002
466
- \u4F60\u4E13\u6CE8\u8FB9\u754C\u3001\u5B89\u5168\u3001\u6027\u80FD\u3001\u8D44\u6E90\u3002
472
+ \u5F00\u59CB\u524D\u5FC5\u987B\u8BFB\u53D6 \`review-scope.md\` \u548C \`film-report-1.md\`\u3002\u5982\u679C #1 \u5DF2\u7ECF\u6307\u51FA\u76F8\u540C\u6839\u56E0\uFF0C\u6807\u8BB0\u4E3A \`duplicate\` \u5E76\u4E14\u4E0D\u8981\u91CD\u65B0\u8F93\u51FA\u4E3A finding\u3002\u4F60\u53EA\u4E13\u6CE8\u8FB9\u754C\u3001\u5B89\u5168\u3001\u6027\u80FD\u3001\u8D44\u6E90\uFF1B\u{1F534} \u4F7F\u7528\u7A33\u5B9A ID\uFF08D1\u3001D2\u2026\u2026\uFF09\u3002
467
473
 
468
474
  ## \u7EA2\u7EBF
469
475
 
@@ -757,23 +763,19 @@ var SOLO_SKILL = `# mancode \xB7 solo mode
757
763
  4. \u4EE5\u9879\u76EE\u65E2\u6709\u7684\u65E0\u969C\u788D\u3001\u54CD\u5E94\u5F0F\u4E0E\u53CD\u9988\u65B9\u5F0F\u4E3A\u51C6
758
764
 
759
765
  ### \u754C\u9762\u8D28\u91CF\u68C0\u67E5\uFF08\u6761\u4EF6\u6267\u884C\uFF09
760
- UI \u8F93\u51FA\u524D\u81EA\u68C0\uFF1A
761
- - \u662F\u5426\u590D\u7528\u4E86\u9879\u76EE\u5B9E\u9645\u7684\u5C42\u7EA7\u3001\u95F4\u8DDD\u3001\u989C\u8272\u3001\u5B57\u4F53\u4E0E\u4EA4\u4E92\u53CD\u9988\uFF1F
762
- - \u4FE1\u606F\u5C42\u7EA7\u3001\u7A7A/\u52A0\u8F7D/\u9519\u8BEF\u72B6\u6001\u548C\u952E\u76D8/\u8BFB\u5C4F\u53EF\u8FBE\u6027\u662F\u5426\u6E05\u695A\uFF1F
763
- - \u662F\u5426\u4E3A\u4E86\u201C\u597D\u770B\u201D\u5F15\u5165\u4E0E\u73B0\u6709\u4EA7\u54C1\u65E0\u5173\u7684\u88C5\u9970\u3001\u52A8\u753B\u6216\u7EC4\u4EF6\uFF1F
766
+ \u53EA\u68C0\u67E5\u672C\u6B21 diff \u65B0\u589E\u6216\u6539\u53D8\u7684\u754C\u9762\u884C\u4E3A\uFF1A\u590D\u7528\u5DF2\u6709\u5C42\u7EA7\u3001token \u548C\u7EC4\u4EF6\uFF1B\u65B0\u589E\u5F02\u6B65\u6216\u4EA4\u4E92\u8DEF\u5F84\u65F6\u624D\u68C0\u67E5\u5BF9\u5E94\u7684\u52A0\u8F7D\u3001\u5931\u8D25\u548C\u53EF\u8FBE\u6027\u72B6\u6001\u3002\u4E0D\u8981\u501F UI \u81EA\u68C0\u5DE1\u67E5\u672A\u6539\u52A8\u9875\u9762\u6216\u8865\u9F50\u63A8\u6D4B\u6027\u72B6\u6001\u3002
764
767
 
765
768
  ### \u9A8C\u8BC1
766
769
  \u4EE3\u7801\u5B8C\u6210\u540E\uFF1A
767
- 1. \u8FD0\u884C build\uFF08\u5982\u6709\uFF09
768
- 2. \u8FD0\u884C lint\uFF08\u5982\u6709\uFF09
769
- 3. \u8FD0\u884C test\uFF08\u5982\u6709\uFF09
770
- 4. \u786E\u8BA4\u901A\u8FC7\u540E\u624D\u58F0\u79F0\u5B8C\u6210
770
+ 1. \u9009\u62E9\u4E0E\u672C\u6B21\u6539\u52A8\u76F4\u63A5\u76F8\u5173\u7684\u6700\u7A84 test\u3001lint\u3001typecheck\u3001build \u6216 smoke check
771
+ 2. \u6587\u6848\u3001\u6CE8\u91CA\u6216\u7EAF\u9759\u6001\u8D44\u6E90\u6539\u52A8\u4E0D\u5F3A\u5236\u8FD0\u884C\u5B8C\u6574\u6D4B\u8BD5\u77E9\u9635
772
+ 3. \u786E\u8BA4\u5B9E\u9645\u6267\u884C\u7ED3\u679C\u540E\u624D\u58F0\u79F0\u5B8C\u6210
771
773
 
772
- ### \u5B8C\u6210\u540E\u81EA\u5BA1\u4E0E\u6A21\u5F0F\u63A8\u8350
773
- - \u9A8C\u8BC1\u7C7B\u95EE\u9898\uFF08build/lint/test \u5931\u8D25\u3001\u9057\u7559 debug\uFF09\u76F4\u63A5\u4FEE\u590D\u5E76\u590D\u9A8C\u3002
774
- - \u98CE\u683C\u7C7B\u95EE\u9898\uFF08\u547D\u540D\u3001\u53EF\u8BFB\u6027\u3001DRY\uFF09\u53EA\u63D0\u793A\uFF0C\u4E0D\u4E3A\u6B64\u6269\u5927\u65E0\u5173\u6539\u52A8\u3002
775
- - \u52A8\u624B\u524D\u82E5\u6D89\u53CA 3 \u4E2A\u4EE5\u4E0A\u6587\u4EF6\u3001\u65B0\u6A21\u5757\u6216\u67B6\u6784\u53D8\u52A8\uFF0C\u7528\u4E00\u53E5\u8BDD\u5EFA\u8BAE\u7528\u6237\u5148\u7528 \`/man\`\uFF1B\u7528\u6237\u8BF4\u201C\u7EE7\u7EED solo\u201D\u5373\u53EF\u8DF3\u8FC7\u3002
776
- - \u81EA\u5BA1\u53D1\u73B0 3 \u4E2A\u4EE5\u4E0A\u95EE\u9898\u65F6\uFF0C\u5EFA\u8BAE \`/manps\` \u6216 \`/man\`\u3002
774
+ ### \u5B8C\u6210\u540E\u7684\u4E00\u6B21\u53D7\u9650\u81EA\u68C0
775
+ - \u53EA\u505A\u4E00\u6B21\uFF0C\u53EA\u770B\u672C\u6B21 diff\u3001\u9700\u6C42\u548C\u76F4\u63A5\u53D7\u5F71\u54CD\u8DEF\u5F84\uFF1B\u4E0D\u8C03\u7528\u989D\u5916 reviewer\uFF0C\u4E0D\u751F\u6210\u5BA1\u67E5\u62A5\u544A\uFF0C\u4E0D\u91CD\u590D\u5DE1\u68C0\u3002
776
+ - \u9A8C\u8BC1\u5931\u8D25\u3001\u884C\u4E3A\u56DE\u5F52\u6216\u9057\u7559 debug \u53EF\u76F4\u63A5\u4FEE\u590D\u5E76\u590D\u9A8C\uFF1B\u590D\u9A8C\u4E0D\u662F\u65B0\u4E00\u8F6E review\u3002
777
+ - \u547D\u540D\u3001\u53EF\u8BFB\u6027\u3001DRY\u3001loading/error \u5F62\u5F0F\u7B49\u5EFA\u8BAE\u4E0D\u81EA\u52A8\u6269\u5927\u6539\u52A8\uFF1B\u4E0E\u9700\u6C42\u65E0\u5173\u65F6\u4E0D\u8F93\u51FA\u3002
778
+ - \u53EA\u6709\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\u6216\u57FA\u7840\u8BBE\u65BD\u7B49\u786C\u98CE\u9669\u51FA\u73B0\u65F6\uFF0C\u624D\u7528\u4E00\u53E5\u8BDD\u5EFA\u8BAE \`/man\`\uFF1B\u7528\u6237\u8BF4\u201C\u7EE7\u7EED solo\u201D\u5373\u53EF\u7EE7\u7EED\u3002
777
779
 
778
780
  ## \u4F60\u7684\u98CE\u683C
779
781
 
@@ -833,21 +835,24 @@ var MAN_SKILL = {
833
835
 
834
836
  \u8FD0\u884C\u5B9E\u9645 build/lint/typecheck/test \u548C smoke test\u3002\u76F8\u540C\u4EE3\u7801\u3001\u73AF\u5883\u3001\u547D\u4EE4\u4E0B\u76F8\u540C\u9519\u8BEF\u7B7E\u540D\u5931\u8D25\u4E24\u6B21\uFF0C\u505C\u6B62\u76F2\u8BD5\u5E76\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u6D4F\u89C8\u5668\u3001\u590D\u6742\u590D\u73B0\u6216\u56DE\u5F52\u65F6\uFF0C\u7528 \`mancode workflow create mamba "<\u95EE\u9898>" --parent-task <taskId> --json\` \u521B\u5EFA\u5B50 workflow\uFF1B\u7236\u4EFB\u52A1\u4FDD\u6301 Step 6\u3002\u5B50\u4EFB\u52A1 fixed/verified/no_repro \u540E\u6062\u590D\u672C\u4EFB\u52A1\uFF1B\u82E5\u7236\u66FE\u56E0\u8BE5\u5B50\u4EFB\u52A1 blocked\uFF0C\u5148\u901A\u8FC7 \`workflow update --status in_progress\` \u6062\u590D\uFF0C\u518D\u66F4\u65B0\u81F3 Step 7\u3002blocked \u6216 manual_test_required \u4F1A\u7531 CLI \u81EA\u52A8\u963B\u585E\u7236\u4EFB\u52A1\uFF0C\u4E0D\u5F97\u624B\u6539\u7236 metadata\uFF0C\u4E5F\u4E0D\u5F97\u81EA\u52A8\u8D8A\u8FC7\u4EBA\u5DE5\u9A8C\u8BC1\u8981\u6C42\u3002
835
837
 
838
+ \u9A8C\u8BC1\u540E\u57FA\u4E8E**\u5B9E\u9645 diff**\u5199 \`review-scope.md\`\uFF1Abase\u3001\u6539\u52A8\u6587\u4EF6\u3001\u9700\u6C42\u3001\u5DF2\u8DD1\u9A8C\u8BC1\u3001\u786C\u98CE\u9669\u548C\u5BA1\u67E5\u6DF1\u5EA6\u3002\u9274\u6743\u3001\u652F\u4ED8\u3001\u654F\u611F\u6570\u636E\u3001\u8FC1\u79FB/\u5220\u9664\u3001\u516C\u5F00 API\u3001\u672A\u53EF\u4FE1\u8F93\u5165\u3001\u5E76\u53D1\u3001\u8DE8\u670D\u52A1\u6216\u57FA\u7840\u8BBE\u65BD\u547D\u4E2D\u4EFB\u4E00\u9879\u65F6\u7528\u5B8C\u6574\u5BA1\u67E5 \`full\`\uFF1B\u5426\u5219\u7528\u5B9A\u5411\u5BA1\u67E5 \`targeted\`\u3002\u8FD0\u884C \`mancode workflow review <taskId> init --review-depth targeted --review-domain quality\` \u6216 \`--review-depth full\`\u3002\u7528\u6237\u660E\u786E\u8DF3\u8FC7\u5BA1\u67E5\u65F6\u624D\u628A \`review\` \u5199\u5165\u7D2F\u8BA1 skippedSteps\uFF0C\u5E76\u8BB0\u5F55\u6B8B\u4F59\u98CE\u9669\u3002
839
+
836
840
  ### Step 7: Film #1 \u4EE3\u7801\u8D28\u91CF\u5BA1\u67E5\u4E0E\u4FEE\u590D
837
841
 
838
- \u8C03\u7528 \`film-analyst-offense\` \u5BA1\u67E5\u8D28\u91CF\u3001\u590D\u7528\u3001\u590D\u6742\u5EA6\u548C\u6D4B\u8BD5\uFF0C\u5199 \`film-report-1.md\`\uFF1BHead Coach \u4FEE\u590D \u{1F534}\uFF0C\u5BF9 \u{1F7E1} \u5199\u4FEE\u590D\u6216\u6743\u8861\u3002\u5B8C\u6210\u540E\u66F4\u65B0\u81F3 Step 8\u3002
842
+ \u672A\u8DF3\u8FC7 review \u65F6\u8C03\u7528 \`film-analyst-offense\`\uFF0C\u53EA\u5BA1\u67E5\u672C\u6B21 diff \u7684\u884C\u4E3A\u6B63\u786E\u6027\u3001\u590D\u7528\u3001\u590D\u6742\u5EA6\u548C\u6D4B\u8BD5\uFF0C\u5199 \`film-report-1.md\`\u3002\u6BCF\u6761 finding \u5FC5\u987B\u5F15\u7528\u6539\u52A8\u884C\u3001\u7ED9\u51FA\u8BC1\u636E\u548C\u7528\u6237\u5F71\u54CD\uFF1B\u6700\u591A 3 \u4E2A\u65B0 finding\u3002\u7528\u7A33\u5B9A ID\uFF08\u5982 Q1\uFF09\u6807\u8BB0 \u{1F534} blocker\uFF0C\u5E76\u8FD0\u884C \`mancode workflow review <taskId> complete --review-domain quality --report film-report-1.md --blockers Q1,Q2\`\uFF1B\u6CA1\u6709 blocker \u65F6\u4F20\u7A7A\u5B57\u7B26\u4E32\u3002\u6B64\u65F6\u4E0D\u4FEE\u590D\uFF0C\u5148\u5B8C\u6210\u6240\u9700\u5BA1\u67E5\u9886\u57DF\uFF0C\u518D\u66F4\u65B0\u81F3 Step 8\u3002
839
843
 
840
844
  ### Step 8: Film #2 \u5B89\u5168\u4E0E\u8FB9\u754C\u5BA1\u67E5
841
845
 
842
- \u8C03\u7528 \`film-analyst-defense\` \u5BA1\u67E5\u5B89\u5168\u3001\u6743\u9650\u3001\u9519\u8BEF\u8DEF\u5F84\u3001\u517C\u5BB9\u6027\u548C\u8FB9\u754C\uFF0C\u5199 \`film-report-2.md\`\u3002\u53CC\u5BA1\u9ED8\u8BA4\u6267\u884C\uFF1B\u7528\u6237\u660E\u786E\u8981\u6C42\u624D\u53EF\u8DF3\u8FC7\uFF0C\u901A\u8FC7 CLI \u5199\u5165\u7D2F\u8BA1 skippedSteps \u548C\u6B8B\u4F59\u98CE\u9669\u3002\u5B8C\u6210\u540E\u66F4\u65B0\u81F3 Step 9\u3002
846
+ \`full\` \u624D\u8C03\u7528 \`film-analyst-defense\`\uFF1B\u5B83\u5FC5\u987B\u5148\u8BFB \`review-scope.md\` \u548C \`film-report-1.md\`\uFF0C\u53EA\u5BA1\u67E5\u5B89\u5168\u3001\u6743\u9650\u3001\u9519\u8BEF\u8DEF\u5F84\u3001\u517C\u5BB9\u6027\u3001\u8D44\u6E90\u548C\u8FB9\u754C\u3002\u76F8\u540C\u6839\u56E0\u6807\u8BB0 \`duplicate\`\uFF0C\u4E0D\u5F97\u91CD\u65B0\u62A5\u544A\u3002\u5199 \`film-report-2.md\` \u540E\u4EE5 D1 \u7B49\u7A33\u5B9A blocker ID \u8FD0\u884C \`mancode workflow review <taskId> complete --review-domain security --report film-report-2.md --blockers D1\`\u3002\`targeted\` \u4E0D\u6267\u884C\u7B2C\u4E8C\u5BA1\uFF0C\u4E5F\u4E0D\u628A\u5B83\u63CF\u8FF0\u6210\u201C\u8DF3\u8FC7\u201D\uFF1B\u5B8C\u6210\u540E\u66F4\u65B0\u81F3 Step 9\u3002
843
847
 
844
848
  ### Step 9: \u589E\u5F3A\u6536\u5C3E
845
849
 
846
- 1. \u4FEE\u590D Film #2 \u7684 \u{1F534} \u5E76\u91CD\u8DD1\u9A8C\u8BC1\u3002
847
- 2. \u5199 \`summary.md\`\uFF1A\u6539\u52A8\u3001\u65B0\u5EFA\u3001\u590D\u7528\u3001\u9A8C\u8BC1\u3001\u53CC\u5BA1\u548C\u8DF3\u8FC7\u6B65\u9AA4\u3002
848
- 3. \u9A8C\u8BC1\u901A\u8FC7\u4E14 \u{1F534} \u6E05\u96F6\u624D\u7528 CLI \u5199 \`completed\`\uFF1B\u5426\u5219\u7528 \`--status blocked --blocking-reason "<\u539F\u56E0>"\`\u3002
849
- 4. \u5173\u952E\u51B3\u7B56 appendTeamDecision \u5230 \`decisions.md\`\uFF0C\u66F4\u65B0 Active Plans\u3002
850
- 5. worktree \u5408\u5E76\u524D\u53D6\u5F97\u7528\u6237\u786E\u8BA4\uFF1B\u7EC8\u6001\u5199\u5165\u6210\u529F\u540E state \u56DE solo \u5E76\u6E05\u7A7A workflow \u6307\u9488\u3002
850
+ 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
851
+ 2. \u91CD\u8DD1\u53D7\u5F71\u54CD\u9A8C\u8BC1\uFF0C\u4E0D\u91CD\u65B0\u8FD0\u884C\u5DF2\u5B8C\u6210\u7684 reviewer\u3002\u4FEE\u590D\u82E5\u5F15\u5165\u65B0\u7684\u9AD8\u98CE\u9669\u9762\u5219\u6807\u8BB0 blocked\uFF0C\u4E0D\u80FD\u5F00\u542F\u65E0\u754C review \u5FAA\u73AF\u3002
852
+ 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
853
+ 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
854
+ 5. \u5173\u952E\u51B3\u7B56 appendTeamDecision \u5230 \`decisions.md\`\uFF0C\u66F4\u65B0 Active Plans\u3002
855
+ 6. worktree \u5408\u5E76\u524D\u53D6\u5F97\u7528\u6237\u786E\u8BA4\uFF1B\u7EC8\u6001\u5199\u5165\u6210\u529F\u540E state \u56DE solo \u5E76\u6E05\u7A7A workflow \u6307\u9488\u3002
851
856
 
852
857
  \u4EFB\u4F55 step/status/outcome/planVersion \u53D8\u5316\u90FD\u5FC5\u987B\u7ECF\u8FC7 workflow CLI\uFF1BCLI \u62D2\u7EDD\u65F6\u4FDD\u7559\u5F53\u524D state \u5E76\u62A5\u544A\u539F\u56E0\uFF0C\u4E0D\u53EF\u7ED5\u8FC7\u6821\u9A8C\u76F4\u63A5\u6539 metadata.json\u3002
853
858
 
@@ -1174,9 +1179,9 @@ var MANTEAM_SKILL = {
1174
1179
  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
1175
1180
  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
1176
1181
  6. Self-test\uFF1A\u4F18\u5148\u8DD1\u9879\u76EE\u5DF2\u6709\u9A8C\u8BC1\u547D\u4EE4\uFF1B\u5931\u8D25\u4E24\u6B21\u505C\u4E0B\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u9A8C\u8BC1\u65F6\u521B\u5EFA\u5E26 parentTaskId \u7684 /mamba \u5B50\u4EFB\u52A1\u3002\u5B50\u4EFB\u52A1\u4FEE\u590D\u540E\u82E5\u7236\u4EFB\u52A1\u66FE\u56E0\u6B64 blocked\uFF0C\u5148\u7528 workflow CLI \u6062\u590D\u7236\u4EFB\u52A1\u4E3A in_progress\uFF1Bmanual_test_required \u4E0D\u5F97\u81EA\u52A8\u6062\u590D\u3002
1177
- 7. Film #1\uFF1A\u91CD\u70B9\u5BA1\u67E5\u53EF\u7EF4\u62A4\u6027\u3001\u547D\u540D\u4E00\u81F4\u6027\u3001\u56E2\u961F\u98CE\u683C\u4E00\u81F4\u6027\uFF0C\u5E76\u4FEE\u590D \u{1F534}\u3002
1178
- 8. Film #2\uFF1A\u91CD\u70B9\u5BA1\u67E5\u8FB9\u754C\u6761\u4EF6\u3001\u5B89\u5168\u3001\u6027\u80FD\u3001\u5E76\u53D1\u3001\u517C\u5BB9\u6027\u3002\u53CC\u5BA1\u9ED8\u8BA4\u6267\u884C\uFF1B\u7528\u6237\u660E\u786E\u8981\u6C42\u624D\u53EF\u8DF3\u8FC7\uFF0C\u5199\u5165 \`skippedSteps\` \u548C\u6B8B\u4F59\u98CE\u9669\uFF1B\u8DF3\u8FC7\u4E0D\u963B\u6B62 completed\uFF0C\u4F46\u4E0D\u5F97\u58F0\u79F0"\u5DF2\u5B8C\u6210\u53CC\u5BA1"\u3002
1179
- 9. Post-game\uFF1A\u590D\u9A8C\uFF0C\u5199 summary \u4E0E hand-off\uFF1B\u9A8C\u8BC1\u5931\u8D25\u6216\u672A\u4FEE\u590D \u{1F534} \u65F6\u6807\u8BB0 blocked\uFF0C\u4E0D\u5F97\u6807 completed\u3002
1182
+ 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
1183
+ 8. Film #2\uFF1A\u4EC5 full \u6267\u884C\uFF0C\u5148\u8BFB Film #1 \u62A5\u544A\u5E76\u53BB\u91CD\uFF0C\u53EA\u5BA1\u67E5\u8FB9\u754C\u3001\u5B89\u5168\u3001\u6027\u80FD\u3001\u5E76\u53D1\u548C\u517C\u5BB9\u6027\u3002\u7528\u6237\u660E\u786E\u8981\u6C42\u624D\u53EF\u8DF3\u8FC7\u5168\u90E8 review\uFF0C\u5E76\u5199\u5165 \`skippedSteps\` \u548C\u6B8B\u4F59\u98CE\u9669\u3002
1184
+ 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
1180
1185
 
1181
1186
  \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 mamba \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
1182
1187
 
@@ -2194,10 +2199,10 @@ var MODE_META = {
2194
2199
  "Step 3 \u2014 Plan: write `plan.md` with scope, reuse, exclusions, risks, completion criteria, and real validation. First plan is v1; revisions use `--plan-version <current+1>` and Active Plans is updated by the CLI.",
2195
2200
  "Step 4 \u2014 Plan gate: the user chooses plan-only (`status planned`), continue to execution, or revise the plan. Do not implement before this choice.",
2196
2201
  "Step 5 \u2014 Implement the confirmed minimum change.",
2197
- "Step 6 \u2014 Run detected build/lint/typecheck/test and smoke checks. A real or complex diagnosis creates a linked mamba child while the parent remains at Step 6. After a resolved child, restore a parent blocked by that child with `workflow update --status in_progress` before Step 7; never auto-resume manual_test_required.",
2198
- "Step 7 \u2014 Film #1 code-quality review; fix all required findings and explain suggested-item tradeoffs.",
2199
- "Step 8 \u2014 Film #2 security/boundary review; keep it independent from Film #1. The two review passes are skipped only on explicit user instruction and the residual risk is recorded.",
2200
- "Step 9 \u2014 Fix, re-verify, write `summary.md`, archive decisions, and set completed only when verification passes and required findings are zero; otherwise set blocked+blockingReason.",
2202
+ "Step 6 \u2014 Run detected build/lint/typecheck/test and smoke checks. A real or complex diagnosis creates a linked mamba child while the parent remains at Step 6. After a resolved child, restore a parent blocked by that child with `workflow update --status in_progress` before Step 7; never auto-resume manual_test_required. Then write `review-scope.md` from the actual diff, verification evidence, and hard-risk triggers. Initialize a targeted review for routine governed changes or a full review for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, cross-service, or infrastructure changes using `mancode workflow review <taskId> init ...`.",
2203
+ "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.",
2204
+ "Step 8 \u2014 Only full review runs the security/boundary reviewer. It must read Film #1, mark the same root cause duplicate, and stay within security, permissions, recovery, resources, and boundaries. A targeted review ends after Film #1.",
2205
+ "Step 9 \u2014 If open blockers exist, fix them in one remediation round and record resolved IDs through `workflow review ... remediate`; with no blockers, do not create a remediation round. Re-run affected verification without re-running completed reviewers. Write `summary.md` and set completed only when required domains are complete and blockers are zero; otherwise set blocked+blockingReason.",
2201
2206
  "",
2202
2207
  "After planned/completed/abandoned, return state to solo. On blocked, preserve enough state to resume. A CLI rejection is a hard stop, never a reason to bypass metadata validation."
2203
2208
  ].join("\n")
@@ -2214,7 +2219,7 @@ var MODE_META = {
2214
2219
  "shared-module, and handoff risks in `team-context.md`.",
2215
2220
  "",
2216
2221
  "Then execute every /man Step 1 through Step 9, including clarification,",
2217
- "plan gate, validation, two review passes, conditional completion, planVersion,",
2222
+ "plan gate, validation, bounded risk-based review, conditional completion, planVersion,",
2218
2223
  "Active Plans, and linked mamba child behavior. Add compatibility, rollback,",
2219
2224
  "ownership, conflict, and handoff notes at each relevant step.",
2220
2225
  "When a resolved mamba child had blocked the parent, restore the parent through",
@@ -2317,7 +2322,9 @@ function renderPracticeRules() {
2317
2322
  "5. Prefer a one-line fix when it is enough.",
2318
2323
  "6. Only then write the smallest new implementation.",
2319
2324
  "",
2320
- "For every task, consider: why this change, what already exists, and what is the smallest useful diff?"
2325
+ "For every task, consider: why this change, what already exists, and what is the smallest useful diff?",
2326
+ "",
2327
+ "In solo mode, use the narrowest meaningful validation and one bounded self-check limited to the current diff. Do not start another reviewer or repeat the review. Only recommend man for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, or infrastructure risk."
2321
2328
  ].join("\n");
2322
2329
  }
2323
2330
  function renderAesthetics(tokens) {
@@ -2499,7 +2506,7 @@ function renderCopilotPromptConventions() {
2499
2506
  "",
2500
2507
  "GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
2501
2508
  "",
2502
- "- man: use progressive research, planning, implementation, verification, and review.",
2509
+ "- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
2503
2510
  "- mamba: diagnose bugs and validate real user flows or regressions.",
2504
2511
  "- manteam: read team memory and write handoff-friendly summaries.",
2505
2512
  "- manps: prefer `mancode manps [area]` before cleanup.",
@@ -2717,7 +2724,9 @@ function renderSoloRule() {
2717
2724
  "- Keep the diff narrow.",
2718
2725
  "- Reuse existing functions, components, styles, and dependencies.",
2719
2726
  "- Read `.mancode/project-profile.json` before choosing tools or validation. For detected UI assets and UI work, read `.mancode/aesthetics/style-tokens.json` and inspect existing components before inventing new styles.",
2720
- "- Verify with the narrowest meaningful test, lint, build, or smoke check."
2727
+ "- Verify with the narrowest meaningful test, lint, build, or smoke check.",
2728
+ "- Perform one bounded self-check limited to the current diff. Do not start another reviewer, create a review artifact, or repeat the review.",
2729
+ "- Recommend /man only for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, or infrastructure risk."
2721
2730
  ].join("\n");
2722
2731
  }
2723
2732
  function renderMambaRule() {
@@ -5718,24 +5727,220 @@ async function pathExists8(p) {
5718
5727
  // src/commands/status.ts
5719
5728
  import { spawn } from "child_process";
5720
5729
  import { promises as fs7 } from "fs";
5721
- import path21 from "path";
5730
+ import path22 from "path";
5722
5731
  import process7 from "process";
5723
5732
 
5724
5733
  // src/system/workflow.ts
5725
5734
  import {
5726
- mkdir as mkdir8,
5727
- readFile as readFile9,
5735
+ mkdir as mkdir9,
5736
+ readFile as readFile10,
5728
5737
  readdir as readdir5,
5729
5738
  rm as rm4,
5730
- stat as stat3,
5731
- writeFile as writeFile10
5739
+ stat as stat4,
5740
+ writeFile as writeFile11
5732
5741
  } from "fs/promises";
5742
+ import path21 from "path";
5743
+
5744
+ // src/system/review-ledger.ts
5745
+ import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
5733
5746
  import path20 from "path";
5747
+ var REVIEW_FILE = "review-ledger.json";
5748
+ var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
5749
+ var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
5750
+ function isReviewDepth(value) {
5751
+ return value === "targeted" || value === "full";
5752
+ }
5753
+ function isReviewDomain(value) {
5754
+ return value === "quality" || value === "security";
5755
+ }
5756
+ async function initializeReview(projectRoot, taskId, depth, targetDomain) {
5757
+ assertValidTaskId(taskId);
5758
+ const existing = await readReviewLedger(projectRoot, taskId);
5759
+ if (existing) throw new Error(`review already initialized: ${taskId}`);
5760
+ if (depth === "targeted" && !targetDomain) {
5761
+ throw new Error("targeted review requires a review domain");
5762
+ }
5763
+ const ledger = {
5764
+ version: "1.0",
5765
+ depth,
5766
+ requiredDomains: depth === "full" ? ["quality", "security"] : [targetDomain],
5767
+ completedDomains: [],
5768
+ reports: {},
5769
+ blockers: [],
5770
+ remediationRounds: 0
5771
+ };
5772
+ await writeReviewLedger(projectRoot, taskId, ledger);
5773
+ return ledger;
5774
+ }
5775
+ async function completeReviewDomain(projectRoot, taskId, domain, report, blockerIds) {
5776
+ const ledger = await requireReviewLedger(projectRoot, taskId);
5777
+ if (!ledger.requiredDomains.includes(domain)) {
5778
+ throw new Error(`review domain is not required: ${domain}`);
5779
+ }
5780
+ if (ledger.completedDomains.includes(domain)) {
5781
+ throw new Error(`review domain already completed: ${domain}`);
5782
+ }
5783
+ if (domain === "security" && !ledger.completedDomains.includes("quality")) {
5784
+ throw new Error("quality review must complete first");
5785
+ }
5786
+ assertSafeReportPath(report);
5787
+ await assertReportExists(projectRoot, taskId, report);
5788
+ const uniqueIds = new Set(blockerIds);
5789
+ if (uniqueIds.size !== blockerIds.length) {
5790
+ throw new Error("review blocker ids must be unique");
5791
+ }
5792
+ for (const id of blockerIds) {
5793
+ if (!BLOCKER_ID_PATTERN.test(id)) {
5794
+ throw new Error(`invalid review blocker id: ${id}`);
5795
+ }
5796
+ if (ledger.blockers.some((blocker) => blocker.id === id)) {
5797
+ throw new Error(`duplicate review blocker id: ${id}`);
5798
+ }
5799
+ }
5800
+ const updated = {
5801
+ ...ledger,
5802
+ completedDomains: [...ledger.completedDomains, domain],
5803
+ reports: { ...ledger.reports, [domain]: report },
5804
+ blockers: [
5805
+ ...ledger.blockers,
5806
+ ...blockerIds.map((id) => ({
5807
+ id,
5808
+ domain,
5809
+ status: "open"
5810
+ }))
5811
+ ]
5812
+ };
5813
+ await writeReviewLedger(projectRoot, taskId, updated);
5814
+ return updated;
5815
+ }
5816
+ async function remediateReviewBlockers(projectRoot, taskId, resolvedIds) {
5817
+ const ledger = await requireReviewLedger(projectRoot, taskId);
5818
+ if (ledger.remediationRounds >= 1) {
5819
+ throw new Error("review allows only one remediation round");
5820
+ }
5821
+ if (resolvedIds.length === 0) {
5822
+ throw new Error("review remediation requires resolved blocker ids");
5823
+ }
5824
+ const uniqueIds = new Set(resolvedIds);
5825
+ if (uniqueIds.size !== resolvedIds.length) {
5826
+ throw new Error("resolved review blocker ids must be unique");
5827
+ }
5828
+ for (const id of resolvedIds) {
5829
+ const blocker = ledger.blockers.find((item) => item.id === id);
5830
+ if (!blocker || blocker.status !== "open") {
5831
+ throw new Error(`review blocker is not open: ${id}`);
5832
+ }
5833
+ }
5834
+ const updated = {
5835
+ ...ledger,
5836
+ remediationRounds: ledger.remediationRounds + 1,
5837
+ blockers: ledger.blockers.map(
5838
+ (blocker) => uniqueIds.has(blocker.id) ? { ...blocker, status: "resolved" } : blocker
5839
+ )
5840
+ };
5841
+ await writeReviewLedger(projectRoot, taskId, updated);
5842
+ return updated;
5843
+ }
5844
+ async function reviewCanComplete(projectRoot, taskId) {
5845
+ const ledger = await readReviewLedger(projectRoot, taskId);
5846
+ if (!ledger) return false;
5847
+ return ledger.requiredDomains.every(
5848
+ (domain) => ledger.completedDomains.includes(domain)
5849
+ ) && ledger.blockers.every((blocker) => blocker.status === "resolved");
5850
+ }
5851
+ async function readReviewLedger(projectRoot, taskId) {
5852
+ if (!TASK_ID_PATTERN.test(taskId)) return null;
5853
+ try {
5854
+ const raw = await readFile9(reviewPath(projectRoot, taskId), "utf-8");
5855
+ const value = JSON.parse(raw);
5856
+ return isReviewLedger(value) ? value : null;
5857
+ } catch {
5858
+ return null;
5859
+ }
5860
+ }
5861
+ function isReviewLedger(value) {
5862
+ if (!isRecord7(value)) return false;
5863
+ if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord7(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
5864
+ return false;
5865
+ }
5866
+ const requiredDomains = value.requiredDomains;
5867
+ const completedDomains = value.completedDomains;
5868
+ const reports = value.reports;
5869
+ const blockers = value.blockers;
5870
+ const expectedDomains = value.depth === "full" ? ["quality", "security"] : requiredDomains;
5871
+ 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) => {
5872
+ const report = reports[domain];
5873
+ return typeof report !== "string" || !isSafeReportPath(report);
5874
+ })) {
5875
+ return false;
5876
+ }
5877
+ const blockerIds = /* @__PURE__ */ new Set();
5878
+ return blockers.every(
5879
+ (blocker) => isRecord7(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")
5880
+ );
5881
+ }
5882
+ function isDomainArray(value) {
5883
+ return Array.isArray(value) && value.every(isReviewDomain);
5884
+ }
5885
+ function isRecord7(value) {
5886
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5887
+ }
5888
+ async function requireReviewLedger(projectRoot, taskId) {
5889
+ assertValidTaskId(taskId);
5890
+ const ledger = await readReviewLedger(projectRoot, taskId);
5891
+ if (!ledger) throw new Error(`review not initialized: ${taskId}`);
5892
+ return ledger;
5893
+ }
5894
+ async function writeReviewLedger(projectRoot, taskId, ledger) {
5895
+ const dir = path20.join(projectRoot, ".mancode", "workflows", taskId);
5896
+ await mkdir8(dir, { recursive: true });
5897
+ await writeFile10(
5898
+ path20.join(dir, REVIEW_FILE),
5899
+ `${JSON.stringify(ledger, null, 2)}
5900
+ `,
5901
+ "utf-8"
5902
+ );
5903
+ }
5904
+ function reviewPath(projectRoot, taskId) {
5905
+ return path20.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
5906
+ }
5907
+ function assertValidTaskId(taskId) {
5908
+ if (!TASK_ID_PATTERN.test(taskId)) {
5909
+ throw new Error(`invalid workflow taskId: ${taskId}`);
5910
+ }
5911
+ }
5912
+ function assertSafeReportPath(report) {
5913
+ if (!isSafeReportPath(report)) {
5914
+ throw new Error(`invalid review report path: ${report}`);
5915
+ }
5916
+ }
5917
+ async function assertReportExists(projectRoot, taskId, report) {
5918
+ const reportPath = path20.join(
5919
+ projectRoot,
5920
+ ".mancode",
5921
+ "workflows",
5922
+ taskId,
5923
+ report
5924
+ );
5925
+ try {
5926
+ if ((await stat3(reportPath)).isFile()) return;
5927
+ } catch {
5928
+ }
5929
+ throw new Error(`review report not found: ${report}`);
5930
+ }
5931
+ function isSafeReportPath(report) {
5932
+ const normalized = path20.posix.normalize(report.replaceAll("\\", "/"));
5933
+ return Boolean(
5934
+ report.trim() && !path20.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
5935
+ );
5936
+ }
5937
+
5938
+ // src/system/workflow.ts
5734
5939
  var METADATA_FILE = "metadata.json";
5735
5940
  var SLUG_MAX = 30;
5736
- var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
5941
+ var TASK_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
5737
5942
  function isValidWorkflowTaskId(taskId) {
5738
- return TASK_ID_PATTERN.test(taskId);
5943
+ return TASK_ID_PATTERN2.test(taskId);
5739
5944
  }
5740
5945
  function generateTaskId(task, now = /* @__PURE__ */ new Date()) {
5741
5946
  const slug = slugify(task).slice(0, SLUG_MAX) || "task";
@@ -5748,7 +5953,7 @@ function slugify(text) {
5748
5953
  }
5749
5954
  async function createWorkflow(projectRoot, task, mode, options = {}) {
5750
5955
  await validateParentTask(projectRoot, mode, options.parentTaskId, true);
5751
- await mkdir8(workflowsRoot(projectRoot), { recursive: true });
5956
+ await mkdir9(workflowsRoot(projectRoot), { recursive: true });
5752
5957
  const taskId = await allocateTaskId(projectRoot, generateTaskId(task));
5753
5958
  const dir = workflowDir(projectRoot, taskId);
5754
5959
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5762,7 +5967,7 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
5762
5967
  updatedAt: now,
5763
5968
  status: "in_progress",
5764
5969
  ...options.parentTaskId ? { parentTaskId: options.parentTaskId } : {},
5765
- ...mode === "man" || mode === "manteam" ? { planVersion: 1 } : {}
5970
+ ...mode === "man" || mode === "manteam" ? { planVersion: 1, reviewPolicyVersion: 1 } : {}
5766
5971
  };
5767
5972
  await writeMetadata(dir, meta);
5768
5973
  return meta;
@@ -5771,7 +5976,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
5771
5976
  for (let attempt = 0; attempt < 1e3; attempt++) {
5772
5977
  const taskId = attempt === 0 ? baseTaskId : `${baseTaskId}-${attempt + 1}`;
5773
5978
  try {
5774
- await mkdir8(workflowDir(projectRoot, taskId));
5979
+ await mkdir9(workflowDir(projectRoot, taskId));
5775
5980
  return taskId;
5776
5981
  } catch (err) {
5777
5982
  if (isNodeError6(err) && err.code === "EEXIST") {
@@ -5786,14 +5991,14 @@ async function readWorkflow(projectRoot, taskId) {
5786
5991
  if (!isValidWorkflowTaskId(taskId)) return null;
5787
5992
  const file = metadataPath(projectRoot, taskId);
5788
5993
  try {
5789
- const raw = await readFile9(file, "utf-8");
5994
+ const raw = await readFile10(file, "utf-8");
5790
5995
  return parseWorkflowMeta(raw, taskId);
5791
5996
  } catch {
5792
5997
  return null;
5793
5998
  }
5794
5999
  }
5795
6000
  async function updateWorkflow(projectRoot, taskId, patch) {
5796
- assertValidTaskId(taskId);
6001
+ assertValidTaskId2(taskId);
5797
6002
  const existing = await readWorkflow(projectRoot, taskId);
5798
6003
  if (!existing) {
5799
6004
  throw new Error(`workflow not found: ${taskId}`);
@@ -5838,7 +6043,7 @@ async function deleteWorkflow(projectRoot, taskId) {
5838
6043
  if (!isValidWorkflowTaskId(taskId)) return false;
5839
6044
  const dir = workflowDir(projectRoot, taskId);
5840
6045
  try {
5841
- await stat3(dir);
6046
+ await stat4(dir);
5842
6047
  } catch {
5843
6048
  return false;
5844
6049
  }
@@ -5850,19 +6055,19 @@ async function deleteWorkflow(projectRoot, taskId) {
5850
6055
  return true;
5851
6056
  }
5852
6057
  function workflowsRoot(projectRoot) {
5853
- return path20.join(projectRoot, ".mancode", "workflows");
6058
+ return path21.join(projectRoot, ".mancode", "workflows");
5854
6059
  }
5855
6060
  function workflowDir(projectRoot, taskId) {
5856
- assertValidTaskId(taskId);
5857
- return path20.join(workflowsRoot(projectRoot), taskId);
6061
+ assertValidTaskId2(taskId);
6062
+ return path21.join(workflowsRoot(projectRoot), taskId);
5858
6063
  }
5859
6064
  function metadataPath(projectRoot, taskId) {
5860
- return path20.join(workflowDir(projectRoot, taskId), METADATA_FILE);
6065
+ return path21.join(workflowDir(projectRoot, taskId), METADATA_FILE);
5861
6066
  }
5862
6067
  async function writeMetadata(dir, meta) {
5863
6068
  const content = `${JSON.stringify(meta, null, 2)}
5864
6069
  `;
5865
- await writeFile10(path20.join(dir, METADATA_FILE), content, "utf-8");
6070
+ await writeFile11(path21.join(dir, METADATA_FILE), content, "utf-8");
5866
6071
  }
5867
6072
  function parseWorkflowMeta(raw, taskId) {
5868
6073
  try {
@@ -5912,6 +6117,9 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
5912
6117
  if (updated.parentTaskId !== existing.parentTaskId) {
5913
6118
  throw new Error("workflow parent task cannot be changed");
5914
6119
  }
6120
+ if (updated.reviewPolicyVersion !== existing.reviewPolicyVersion) {
6121
+ throw new Error("workflow review policy version cannot be changed");
6122
+ }
5915
6123
  if (!Number.isInteger(updated.currentStep) || updated.currentStep < 1 || updated.currentStep > maxWorkflowStep(updated.mode)) {
5916
6124
  throw new Error(`invalid workflow step: ${updated.currentStep}`);
5917
6125
  }
@@ -5962,6 +6170,12 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
5962
6170
  if (updated.mode === "mamba" && updated.planVersion !== void 0) {
5963
6171
  throw new Error("mamba workflows cannot have a plan version");
5964
6172
  }
6173
+ if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1) {
6174
+ throw new Error("invalid workflow review policy version");
6175
+ }
6176
+ if (updated.mode === "mamba" && updated.reviewPolicyVersion !== void 0) {
6177
+ throw new Error("mamba workflows cannot have a review policy version");
6178
+ }
5965
6179
  if (updated.planVersion !== void 0 && (!Number.isInteger(updated.planVersion) || updated.planVersion < 1)) {
5966
6180
  throw new Error("workflow plan version must be a positive integer");
5967
6181
  }
@@ -5975,6 +6189,9 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
5975
6189
  if ((isTerminalWorkflowStatus(updated.status) || updated.status === "planned") && (await listActiveMambaChildren(projectRoot, updated.taskId)).length > 0) {
5976
6190
  throw new Error("cannot finish workflow with an active mamba child");
5977
6191
  }
6192
+ if (updated.status === "completed" && updated.reviewPolicyVersion === 1 && !reviewWasExplicitlySkipped(updated) && !await reviewCanComplete(projectRoot, updated.taskId)) {
6193
+ throw new Error("workflow review is incomplete or still has open blockers");
6194
+ }
5978
6195
  }
5979
6196
  function canTransition(from, to) {
5980
6197
  if (from === to) return true;
@@ -6002,13 +6219,16 @@ function isWorkflowShape(obj) {
6002
6219
  return false;
6003
6220
  if (obj.planVersion !== void 0 && (!Number.isInteger(obj.planVersion) || obj.planVersion < 1))
6004
6221
  return false;
6222
+ if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1)
6223
+ return false;
6005
6224
  if (status2 === "blocked") {
6006
6225
  if (!obj.blockingReason?.trim()) return false;
6007
6226
  } else if (obj.blockingReason !== void 0) {
6008
6227
  return false;
6009
6228
  }
6010
6229
  if (mode === "mamba") {
6011
- if (status2 === "planned" || obj.planVersion !== void 0) return false;
6230
+ if (status2 === "planned" || obj.planVersion !== void 0 || obj.reviewPolicyVersion !== void 0)
6231
+ return false;
6012
6232
  if (obj.parentTaskId !== void 0 && !isValidWorkflowTaskId(obj.parentTaskId))
6013
6233
  return false;
6014
6234
  if (obj.outcome !== void 0 && status2 !== "completed") return false;
@@ -6018,6 +6238,9 @@ function isWorkflowShape(obj) {
6018
6238
  }
6019
6239
  return true;
6020
6240
  }
6241
+ function reviewWasExplicitlySkipped(meta) {
6242
+ return meta.skippedSteps.includes("review") || meta.skippedSteps.includes("film-1") && meta.skippedSteps.includes("film-2");
6243
+ }
6021
6244
  async function listActiveMambaChildren(projectRoot, parentTaskId) {
6022
6245
  return (await listMambaChildren(projectRoot, parentTaskId)).filter(
6023
6246
  (workflow2) => workflow2.status === "in_progress" || workflow2.status === "blocked"
@@ -6053,7 +6276,7 @@ async function propagateChildStatus(projectRoot, child) {
6053
6276
  function isNodeError6(err) {
6054
6277
  return err instanceof Error && "code" in err;
6055
6278
  }
6056
- function assertValidTaskId(taskId) {
6279
+ function assertValidTaskId2(taskId) {
6057
6280
  if (!isValidWorkflowTaskId(taskId)) {
6058
6281
  throw new Error(`invalid workflow taskId: ${taskId}`);
6059
6282
  }
@@ -6065,7 +6288,7 @@ var EXIT_OK6 = 0;
6065
6288
  var EXIT_NOT_INITIALIZED4 = 1;
6066
6289
  var EXIT_CORRUPT_STATE = 2;
6067
6290
  async function status(rootDir = process7.cwd(), options = {}) {
6068
- const stateFile = path21.join(rootDir, ".mancode", "state.json");
6291
+ const stateFile = path22.join(rootDir, ".mancode", "state.json");
6069
6292
  if (!await pathExists9(stateFile)) {
6070
6293
  console.error("\u2717 mancode not initialized.");
6071
6294
  console.error(" Run `mancode init` to get started.");
@@ -6164,19 +6387,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
6164
6387
  }
6165
6388
  async function getProjectName(rootDir) {
6166
6389
  try {
6167
- const raw = await fs7.readFile(path21.join(rootDir, "package.json"), "utf-8");
6390
+ const raw = await fs7.readFile(path22.join(rootDir, "package.json"), "utf-8");
6168
6391
  const pkg = JSON.parse(raw);
6169
6392
  if (pkg.name && typeof pkg.name === "string") {
6170
6393
  return pkg.name;
6171
6394
  }
6172
6395
  } catch {
6173
6396
  }
6174
- return path21.basename(rootDir);
6397
+ return path22.basename(rootDir);
6175
6398
  }
6176
6399
  async function readConfig2(rootDir) {
6177
6400
  try {
6178
6401
  const raw = await fs7.readFile(
6179
- path21.join(rootDir, ".mancode", "config.json"),
6402
+ path22.join(rootDir, ".mancode", "config.json"),
6180
6403
  "utf-8"
6181
6404
  );
6182
6405
  return JSON.parse(raw);
@@ -6203,9 +6426,9 @@ function getEffectiveTeamStatus(state, config, detected) {
6203
6426
  }
6204
6427
  async function checkHooks(rootDir) {
6205
6428
  const [sessionStart, userPromptSubmit, registered] = await Promise.all([
6206
- pathExists9(path21.join(rootDir, ".mancode", "hooks", "session-start.sh")),
6429
+ pathExists9(path22.join(rootDir, ".mancode", "hooks", "session-start.sh")),
6207
6430
  pathExists9(
6208
- path21.join(rootDir, ".mancode", "hooks", "user-prompt-submit.sh")
6431
+ path22.join(rootDir, ".mancode", "hooks", "user-prompt-submit.sh")
6209
6432
  ),
6210
6433
  isRegistered(rootDir)
6211
6434
  ]);
@@ -6214,7 +6437,7 @@ async function checkHooks(rootDir) {
6214
6437
  async function isRegistered(rootDir) {
6215
6438
  try {
6216
6439
  const raw = await fs7.readFile(
6217
- path21.join(rootDir, ".claude", "settings.json"),
6440
+ path22.join(rootDir, ".claude", "settings.json"),
6218
6441
  "utf-8"
6219
6442
  );
6220
6443
  const settings = JSON.parse(raw);
@@ -6224,9 +6447,9 @@ async function isRegistered(rootDir) {
6224
6447
  }
6225
6448
  }
6226
6449
  function hooksRegistered2(settings) {
6227
- if (!isRecord7(settings)) return false;
6450
+ if (!isRecord8(settings)) return false;
6228
6451
  const hooks = settings.hooks;
6229
- if (!isRecord7(hooks)) return false;
6452
+ if (!isRecord8(hooks)) return false;
6230
6453
  return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.sh") && hasHookCommand2(
6231
6454
  hooks.UserPromptSubmit,
6232
6455
  ".mancode/hooks/user-prompt-submit.sh"
@@ -6235,15 +6458,15 @@ function hooksRegistered2(settings) {
6235
6458
  function hasHookCommand2(value, needle) {
6236
6459
  if (!Array.isArray(value)) return false;
6237
6460
  return value.some((group) => {
6238
- if (!isRecord7(group) || !Array.isArray(group.hooks)) return false;
6461
+ if (!isRecord8(group) || !Array.isArray(group.hooks)) return false;
6239
6462
  return group.hooks.some((hook) => {
6240
- if (!isRecord7(hook) || typeof hook.command !== "string") return false;
6463
+ if (!isRecord8(hook) || typeof hook.command !== "string") return false;
6241
6464
  return hook.command.includes(needle);
6242
6465
  });
6243
6466
  });
6244
6467
  }
6245
6468
  async function estimateHookInjection(rootDir) {
6246
- const hookPath = path21.join(
6469
+ const hookPath = path22.join(
6247
6470
  rootDir,
6248
6471
  ".mancode",
6249
6472
  "hooks",
@@ -6369,7 +6592,7 @@ function formatTeamStatus(team) {
6369
6592
  function workflowStepMax(mode) {
6370
6593
  return maxWorkflowStep(mode);
6371
6594
  }
6372
- function isRecord7(value) {
6595
+ function isRecord8(value) {
6373
6596
  return typeof value === "object" && value !== null;
6374
6597
  }
6375
6598
  async function pathExists9(p) {
@@ -6382,14 +6605,14 @@ async function pathExists9(p) {
6382
6605
  }
6383
6606
 
6384
6607
  // src/commands/uninstall.ts
6385
- import { access as access4, readFile as readFile10, rm as rm5, writeFile as writeFile11 } from "fs/promises";
6386
- import path22 from "path";
6608
+ import { access as access4, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
6609
+ import path23 from "path";
6387
6610
  import process8 from "process";
6388
6611
  var EXIT_OK7 = 0;
6389
6612
  var EXIT_NOT_INITIALIZED5 = 1;
6390
6613
  var EXIT_UNSUPPORTED_PLATFORM2 = 2;
6391
6614
  async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
6392
- const stateFile = path22.join(rootDir, ".mancode", "state.json");
6615
+ const stateFile = path23.join(rootDir, ".mancode", "state.json");
6393
6616
  if (!await pathExists10(stateFile)) {
6394
6617
  console.error("\u2717 mancode not initialized.");
6395
6618
  console.error(" Run `mancode init` first.");
@@ -6441,7 +6664,7 @@ async function uninstallAll(rootDir) {
6441
6664
  await uninstallPlatform(rootDir, p);
6442
6665
  }
6443
6666
  console.log("\u2713 Removing .mancode/ directory...");
6444
- await rm5(path22.join(rootDir, ".mancode"), {
6667
+ await rm5(path23.join(rootDir, ".mancode"), {
6445
6668
  recursive: true,
6446
6669
  force: true
6447
6670
  });
@@ -6451,10 +6674,10 @@ async function uninstallClaudeCode(rootDir) {
6451
6674
  await cleanClaudeSettings(rootDir);
6452
6675
  }
6453
6676
  async function cleanClaudeSettings(rootDir) {
6454
- const settingsPath = path22.join(rootDir, ".claude", "settings.json");
6677
+ const settingsPath = path23.join(rootDir, ".claude", "settings.json");
6455
6678
  let content;
6456
6679
  try {
6457
- content = await readFile10(settingsPath, "utf-8");
6680
+ content = await readFile11(settingsPath, "utf-8");
6458
6681
  } catch {
6459
6682
  return;
6460
6683
  }
@@ -6465,14 +6688,14 @@ async function cleanClaudeSettings(rootDir) {
6465
6688
  return;
6466
6689
  }
6467
6690
  const cleanedHooks = {};
6468
- if (isRecord8(settings.hooks)) {
6691
+ if (isRecord9(settings.hooks)) {
6469
6692
  for (const [event, value] of Object.entries(settings.hooks)) {
6470
6693
  const cleaned = cleanClaudeHookValue(value);
6471
6694
  if (cleaned !== void 0) cleanedHooks[event] = cleaned;
6472
6695
  }
6473
6696
  settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
6474
6697
  }
6475
- if (isRecord8(settings.skills)) {
6698
+ if (isRecord9(settings.skills)) {
6476
6699
  const retainedSkills = Object.fromEntries(
6477
6700
  Object.entries(settings.skills).filter(
6478
6701
  ([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
@@ -6480,7 +6703,7 @@ async function cleanClaudeSettings(rootDir) {
6480
6703
  );
6481
6704
  settings.skills = Object.keys(retainedSkills).length > 0 ? retainedSkills : void 0;
6482
6705
  }
6483
- await writeFile11(
6706
+ await writeFile12(
6484
6707
  settingsPath,
6485
6708
  `${JSON.stringify(settings, null, 2)}
6486
6709
  `,
@@ -6492,7 +6715,7 @@ function cleanClaudeHookValue(value) {
6492
6715
  const entries2 = value.flatMap(cleanClaudeHookEntry);
6493
6716
  return entries2.length > 0 ? entries2 : void 0;
6494
6717
  }
6495
- if (!isRecord8(value)) return value;
6718
+ if (!isRecord9(value)) return value;
6496
6719
  if (Array.isArray(value.hooks) || typeof value.command === "string") {
6497
6720
  const entries2 = cleanClaudeHookEntry(value);
6498
6721
  return entries2.length > 0 ? entries2 : void 0;
@@ -6506,7 +6729,7 @@ function cleanClaudeHookValue(value) {
6506
6729
  }
6507
6730
  function cleanClaudeHookEntry(entry) {
6508
6731
  if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
6509
- if (!isRecord8(entry)) return [entry];
6732
+ if (!isRecord9(entry)) return [entry];
6510
6733
  if (Array.isArray(entry.hooks)) {
6511
6734
  const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
6512
6735
  return hooks.length > 0 ? [{ ...entry, hooks }] : [];
@@ -6514,11 +6737,11 @@ function cleanClaudeHookEntry(entry) {
6514
6737
  return isMancodeHookEntry(entry) ? [] : [entry];
6515
6738
  }
6516
6739
  function isMancodeHookEntry(value) {
6517
- return isRecord8(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
6740
+ return isRecord9(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
6518
6741
  }
6519
6742
  function containsLegacyMancodeHookPath2(value) {
6520
6743
  if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
6521
- if (!isRecord8(value)) return false;
6744
+ if (!isRecord9(value)) return false;
6522
6745
  if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
6523
6746
  return true;
6524
6747
  }
@@ -6529,12 +6752,12 @@ async function uninstallCursor(rootDir) {
6529
6752
  await removeCursorCommands(rootDir);
6530
6753
  }
6531
6754
  async function uninstallCodex(rootDir) {
6532
- const agentsPath = path22.join(rootDir, "AGENTS.md");
6755
+ const agentsPath = path23.join(rootDir, "AGENTS.md");
6533
6756
  try {
6534
- const content = await readFile10(agentsPath, "utf-8");
6757
+ const content = await readFile11(agentsPath, "utf-8");
6535
6758
  const cleaned = removeManagedBlock(content);
6536
6759
  if (cleaned.trim()) {
6537
- await writeFile11(agentsPath, `${cleaned}
6760
+ await writeFile12(agentsPath, `${cleaned}
6538
6761
  `, "utf-8");
6539
6762
  } else {
6540
6763
  await rm5(agentsPath, { force: true });
@@ -6544,16 +6767,16 @@ async function uninstallCodex(rootDir) {
6544
6767
  await removeCodexSkills(rootDir);
6545
6768
  }
6546
6769
  async function uninstallCopilot(rootDir) {
6547
- const instructionsPath = path22.join(
6770
+ const instructionsPath = path23.join(
6548
6771
  rootDir,
6549
6772
  ".github",
6550
6773
  "copilot-instructions.md"
6551
6774
  );
6552
6775
  try {
6553
- const content = await readFile10(instructionsPath, "utf-8");
6776
+ const content = await readFile11(instructionsPath, "utf-8");
6554
6777
  const cleaned = removeManagedBlock(content);
6555
6778
  if (cleaned.trim()) {
6556
- await writeFile11(instructionsPath, `${cleaned}
6779
+ await writeFile12(instructionsPath, `${cleaned}
6557
6780
  `, "utf-8");
6558
6781
  } else {
6559
6782
  await rm5(instructionsPath, { force: true });
@@ -6563,16 +6786,16 @@ async function uninstallCopilot(rootDir) {
6563
6786
  await removeCopilotPrompts(rootDir);
6564
6787
  }
6565
6788
  async function uninstallZcode(rootDir) {
6566
- const agentsPath = path22.join(rootDir, "AGENTS.md");
6789
+ const agentsPath = path23.join(rootDir, "AGENTS.md");
6567
6790
  try {
6568
- const content = await readFile10(agentsPath, "utf-8");
6791
+ const content = await readFile11(agentsPath, "utf-8");
6569
6792
  const cleaned = removeManagedBlock(
6570
6793
  content,
6571
6794
  ZCODE_MANCODE_START_MARKER,
6572
6795
  ZCODE_MANCODE_END_MARKER
6573
6796
  );
6574
6797
  if (cleaned.trim()) {
6575
- await writeFile11(agentsPath, `${cleaned}
6798
+ await writeFile12(agentsPath, `${cleaned}
6576
6799
  `, "utf-8");
6577
6800
  } else {
6578
6801
  await rm5(agentsPath, { force: true });
@@ -6582,13 +6805,13 @@ async function uninstallZcode(rootDir) {
6582
6805
  await removeZcodeSkills(rootDir);
6583
6806
  }
6584
6807
  async function removeFromConfig(rootDir, platform) {
6585
- const configPath = path22.join(rootDir, ".mancode", "config.json");
6808
+ const configPath = path23.join(rootDir, ".mancode", "config.json");
6586
6809
  try {
6587
- const raw = await readFile10(configPath, "utf-8");
6810
+ const raw = await readFile11(configPath, "utf-8");
6588
6811
  const config = JSON.parse(raw);
6589
6812
  if (Array.isArray(config.platforms)) {
6590
6813
  config.platforms = config.platforms.filter((p) => p !== platform);
6591
- if (isRecord8(config.platformOptions)) {
6814
+ if (isRecord9(config.platformOptions)) {
6592
6815
  const platformOptions = Object.fromEntries(
6593
6816
  Object.entries(config.platformOptions).filter(
6594
6817
  ([name]) => name !== platform
@@ -6596,7 +6819,7 @@ async function removeFromConfig(rootDir, platform) {
6596
6819
  );
6597
6820
  config.platformOptions = Object.keys(platformOptions).length > 0 ? platformOptions : void 0;
6598
6821
  }
6599
- await writeFile11(
6822
+ await writeFile12(
6600
6823
  configPath,
6601
6824
  `${JSON.stringify(config, null, 2)}
6602
6825
  `,
@@ -6606,7 +6829,7 @@ async function removeFromConfig(rootDir, platform) {
6606
6829
  } catch {
6607
6830
  }
6608
6831
  }
6609
- function isRecord8(value) {
6832
+ function isRecord9(value) {
6610
6833
  return typeof value === "object" && value !== null && !Array.isArray(value);
6611
6834
  }
6612
6835
  async function pathExists10(p) {
@@ -6630,12 +6853,12 @@ function version() {
6630
6853
 
6631
6854
  // src/commands/workflow.ts
6632
6855
  import { access as access5 } from "fs/promises";
6633
- import path23 from "path";
6856
+ import path24 from "path";
6634
6857
  var EXIT_OK8 = 0;
6635
6858
  var EXIT_NOT_INITIALIZED6 = 1;
6636
6859
  var EXIT_INVALID_ARG2 = 2;
6637
6860
  async function workflow(rootDir, subcommand, args = [], options = {}) {
6638
- if (!await pathExists11(path23.join(rootDir, ".mancode", "state.json"))) {
6861
+ if (!await pathExists11(path24.join(rootDir, ".mancode", "state.json"))) {
6639
6862
  if (options.json) {
6640
6863
  console.log(JSON.stringify({ error: "not initialized" }, null, 2));
6641
6864
  } else {
@@ -6653,6 +6876,8 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
6653
6876
  return workflowList(rootDir, options);
6654
6877
  case "show":
6655
6878
  return workflowShow(rootDir, args[0], options);
6879
+ case "review":
6880
+ return workflowReview(rootDir, args, options);
6656
6881
  case "clean":
6657
6882
  return workflowClean(rootDir, options);
6658
6883
  default:
@@ -6667,12 +6892,109 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
6667
6892
  } else {
6668
6893
  console.error(`\u2717 Invalid workflow subcommand: ${subcommand}`);
6669
6894
  console.error(
6670
- " Use: create <man|mamba|manteam> <task> | update <taskId> | list | show <taskId> | clean"
6895
+ " Use: create <man|mamba|manteam> <task> | update <taskId> | review <taskId> <init|complete|remediate|show> | list | show <taskId> | clean"
6671
6896
  );
6672
6897
  }
6673
6898
  return EXIT_INVALID_ARG2;
6674
6899
  }
6675
6900
  }
6901
+ async function workflowReview(rootDir, args, options) {
6902
+ const taskId = args[0];
6903
+ const action = args[1];
6904
+ if (!taskId || !isValidWorkflowTaskId(taskId)) {
6905
+ return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
6906
+ }
6907
+ const meta = await readWorkflow(rootDir, taskId);
6908
+ if (!meta) return invalidArg(options, `workflow not found: ${taskId}`);
6909
+ if (meta.mode !== "man" && meta.mode !== "manteam") {
6910
+ return invalidArg(options, "review is only valid for man or manteam");
6911
+ }
6912
+ if (action === "show") {
6913
+ const ledger = await readReviewLedger(rootDir, taskId);
6914
+ if (!ledger)
6915
+ return invalidArg(options, `review not initialized: ${taskId}`);
6916
+ outputReviewLedger(ledger, options);
6917
+ return EXIT_OK8;
6918
+ }
6919
+ if (meta.status !== "in_progress" || meta.currentStep < 6) {
6920
+ return invalidArg(
6921
+ options,
6922
+ "review requires an in_progress workflow at step 6 or later"
6923
+ );
6924
+ }
6925
+ try {
6926
+ let ledger;
6927
+ if (action === "init") {
6928
+ if (!isReviewDepth(options.reviewDepth)) {
6929
+ return invalidArg(
6930
+ options,
6931
+ `invalid --review-depth: ${options.reviewDepth ?? ""}`
6932
+ );
6933
+ }
6934
+ if (options.reviewDomain !== void 0 && !isReviewDomain(options.reviewDomain)) {
6935
+ return invalidArg(
6936
+ options,
6937
+ `invalid --review-domain: ${options.reviewDomain}`
6938
+ );
6939
+ }
6940
+ ledger = await initializeReview(
6941
+ rootDir,
6942
+ taskId,
6943
+ options.reviewDepth,
6944
+ isReviewDomain(options.reviewDomain) ? options.reviewDomain : void 0
6945
+ );
6946
+ } else if (action === "complete") {
6947
+ if (!isReviewDomain(options.reviewDomain) || !options.report) {
6948
+ return invalidArg(
6949
+ options,
6950
+ "review complete requires --review-domain and --report"
6951
+ );
6952
+ }
6953
+ ledger = await completeReviewDomain(
6954
+ rootDir,
6955
+ taskId,
6956
+ options.reviewDomain,
6957
+ options.report,
6958
+ parseCsv(options.blockers)
6959
+ );
6960
+ } else if (action === "remediate") {
6961
+ ledger = await remediateReviewBlockers(
6962
+ rootDir,
6963
+ taskId,
6964
+ parseCsv(options.resolved)
6965
+ );
6966
+ } else {
6967
+ return invalidArg(
6968
+ options,
6969
+ `invalid review action: ${action ?? ""}`,
6970
+ "Use: mancode workflow review <taskId> <init|complete|remediate|show>"
6971
+ );
6972
+ }
6973
+ outputReviewLedger(ledger, options);
6974
+ return EXIT_OK8;
6975
+ } catch (error) {
6976
+ return invalidArg(
6977
+ options,
6978
+ error instanceof Error ? error.message : "unable to update review"
6979
+ );
6980
+ }
6981
+ }
6982
+ function outputReviewLedger(ledger, options) {
6983
+ if (options.json) {
6984
+ console.log(JSON.stringify(ledger, null, 2));
6985
+ return;
6986
+ }
6987
+ if (!ledger) return;
6988
+ const openBlockers = ledger.blockers.filter(
6989
+ (blocker) => blocker.status === "open"
6990
+ );
6991
+ console.log(
6992
+ `Review: ${ledger.depth}; domains ${ledger.completedDomains.length}/${ledger.requiredDomains.length}; open blockers ${openBlockers.length}; remediation ${ledger.remediationRounds}/1`
6993
+ );
6994
+ }
6995
+ function parseCsv(value) {
6996
+ return (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
6997
+ }
6676
6998
  async function workflowCreate(rootDir, args, options) {
6677
6999
  const mode = args[0];
6678
7000
  const task = args.slice(1).join(" ").trim();
@@ -7057,7 +7379,13 @@ program.command("uninstall [platform]").description("Remove platform adapter or
7057
7379
  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(
7058
7380
  "--parent-task <taskId>",
7059
7381
  "Parent /man or /manteam workflow for mamba"
7060
- ).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set mamba outcome").option("--plan-version <n>", "Set the next man/manteam plan revision").option("--skipped <steps>", "Update skipped steps as comma-separated values").option("--json", "Output as JSON (for scripts)").action(async (subcommand, args, options) => {
7382
+ ).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set mamba outcome").option("--plan-version <n>", "Set the next man/manteam plan revision").option("--skipped <steps>", "Update skipped steps as comma-separated values").option("--review-depth <depth>", "Review depth: targeted or full").option("--review-domain <domain>", "Review domain: quality or security").option(
7383
+ "--report <path>",
7384
+ "Relative Markdown report path for a review domain"
7385
+ ).option("--blockers <ids>", "Comma-separated blocker ids found by a review").option(
7386
+ "--resolved <ids>",
7387
+ "Comma-separated blocker ids resolved in remediation"
7388
+ ).option("--json", "Output as JSON (for scripts)").action(async (subcommand, args, options) => {
7061
7389
  const code = await workflow(process.cwd(), subcommand, args ?? [], options);
7062
7390
  process.exitCode = code;
7063
7391
  });