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/README.md +40 -12
- package/README.zh-CN.md +30 -11
- package/dist/cli.js +429 -101
- package/dist/cli.js.map +1 -1
- package/package.json +3 -3
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\
|
|
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
|
|
304
|
-
- \u751F\u6210 summary\uFF1A\u6539\u52A8/\u65B0\u5EFA\u6587\u4EF6\u3001\u590D\u7528\u8D44\u6E90\u3001\u9A8C\u8BC1\u7ED3\u679C\u3001\
|
|
305
|
-
- \u53EA\u6709\u9A8C\u8BC1\u901A\u8FC7\u4E14
|
|
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\
|
|
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
|
-
|
|
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. \
|
|
768
|
-
2. \u8FD0\u884C
|
|
769
|
-
3. \
|
|
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\
|
|
773
|
-
- \
|
|
774
|
-
- \
|
|
775
|
-
- \
|
|
776
|
-
- \
|
|
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
|
|
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
|
|
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
|
|
847
|
-
2. \
|
|
848
|
-
3. \u9A8C\u8BC1\
|
|
849
|
-
4. \
|
|
850
|
-
5.
|
|
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\
|
|
1178
|
-
8. Film #2\uFF1A\u91CD\
|
|
1179
|
-
9. Post-game\uFF1A\u590D\u9A8C\
|
|
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
|
|
2199
|
-
"Step 8 \u2014
|
|
2200
|
-
"Step 9 \u2014
|
|
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,
|
|
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
|
|
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
|
|
5727
|
-
readFile as
|
|
5735
|
+
mkdir as mkdir9,
|
|
5736
|
+
readFile as readFile10,
|
|
5728
5737
|
readdir as readdir5,
|
|
5729
5738
|
rm as rm4,
|
|
5730
|
-
stat as
|
|
5731
|
-
writeFile as
|
|
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
|
|
5941
|
+
var TASK_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
5737
5942
|
function isValidWorkflowTaskId(taskId) {
|
|
5738
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
6058
|
+
return path21.join(projectRoot, ".mancode", "workflows");
|
|
5854
6059
|
}
|
|
5855
6060
|
function workflowDir(projectRoot, taskId) {
|
|
5856
|
-
|
|
5857
|
-
return
|
|
6061
|
+
assertValidTaskId2(taskId);
|
|
6062
|
+
return path21.join(workflowsRoot(projectRoot), taskId);
|
|
5858
6063
|
}
|
|
5859
6064
|
function metadataPath(projectRoot, taskId) {
|
|
5860
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
6397
|
+
return path22.basename(rootDir);
|
|
6175
6398
|
}
|
|
6176
6399
|
async function readConfig2(rootDir) {
|
|
6177
6400
|
try {
|
|
6178
6401
|
const raw = await fs7.readFile(
|
|
6179
|
-
|
|
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(
|
|
6429
|
+
pathExists9(path22.join(rootDir, ".mancode", "hooks", "session-start.sh")),
|
|
6207
6430
|
pathExists9(
|
|
6208
|
-
|
|
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
|
-
|
|
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 (!
|
|
6450
|
+
if (!isRecord8(settings)) return false;
|
|
6228
6451
|
const hooks = settings.hooks;
|
|
6229
|
-
if (!
|
|
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 (!
|
|
6461
|
+
if (!isRecord8(group) || !Array.isArray(group.hooks)) return false;
|
|
6239
6462
|
return group.hooks.some((hook) => {
|
|
6240
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
6386
|
-
import
|
|
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 =
|
|
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(
|
|
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 =
|
|
6677
|
+
const settingsPath = path23.join(rootDir, ".claude", "settings.json");
|
|
6455
6678
|
let content;
|
|
6456
6679
|
try {
|
|
6457
|
-
content = await
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 =
|
|
6755
|
+
const agentsPath = path23.join(rootDir, "AGENTS.md");
|
|
6533
6756
|
try {
|
|
6534
|
-
const content = await
|
|
6757
|
+
const content = await readFile11(agentsPath, "utf-8");
|
|
6535
6758
|
const cleaned = removeManagedBlock(content);
|
|
6536
6759
|
if (cleaned.trim()) {
|
|
6537
|
-
await
|
|
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 =
|
|
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
|
|
6776
|
+
const content = await readFile11(instructionsPath, "utf-8");
|
|
6554
6777
|
const cleaned = removeManagedBlock(content);
|
|
6555
6778
|
if (cleaned.trim()) {
|
|
6556
|
-
await
|
|
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 =
|
|
6789
|
+
const agentsPath = path23.join(rootDir, "AGENTS.md");
|
|
6567
6790
|
try {
|
|
6568
|
-
const content = await
|
|
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
|
|
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 =
|
|
6808
|
+
const configPath = path23.join(rootDir, ".mancode", "config.json");
|
|
6586
6809
|
try {
|
|
6587
|
-
const raw = await
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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("--
|
|
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
|
});
|