mancode 0.3.1 → 0.3.3
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 +70 -20
- package/README.zh-CN.md +58 -19
- package/dist/cli.js +759 -445
- package/dist/cli.js.map +1 -1
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { program } from "commander";
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
10
|
import { promises as fs2 } from "fs";
|
|
11
11
|
import path13 from "path";
|
|
12
|
-
import
|
|
12
|
+
import process4 from "process";
|
|
13
13
|
|
|
14
14
|
// src/installers/claude-code.ts
|
|
15
15
|
import {
|
|
@@ -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
|
|
|
@@ -504,215 +510,229 @@ function yamlString(s) {
|
|
|
504
510
|
}
|
|
505
511
|
|
|
506
512
|
// src/templates/inline.ts
|
|
507
|
-
var SESSION_START_HOOK = `#!/bin/
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
513
|
+
var SESSION_START_HOOK = String.raw`#!/usr/bin/env node
|
|
514
|
+
// .mancode/hooks/session-start.mjs
|
|
515
|
+
// mancode SessionStart hook - cross-platform project context
|
|
516
|
+
import { readFileSync } from 'node:fs';
|
|
517
|
+
import path from 'node:path';
|
|
518
|
+
import { fileURLToPath } from 'node:url';
|
|
519
|
+
|
|
520
|
+
const projectRoot = path.resolve(
|
|
521
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
522
|
+
'../..',
|
|
523
|
+
);
|
|
524
|
+
const state = readJson(path.join(projectRoot, '.mancode', 'state.json'));
|
|
525
|
+
const profile =
|
|
526
|
+
readJson(path.join(projectRoot, '.mancode', 'project-profile.json')) || {};
|
|
527
|
+
|
|
528
|
+
if (!state) {
|
|
529
|
+
console.log('ℹ️ mancode 未初始化。运行 mancode init 开始。');
|
|
530
|
+
process.exit(0);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const mode = text(state.currentMode) || 'solo';
|
|
534
|
+
const techStack = sanitize(state.techStack);
|
|
535
|
+
const uiLibrary = sanitize(state.uiLibrary);
|
|
536
|
+
const output = [
|
|
537
|
+
'mancode_mode: ' + mode,
|
|
538
|
+
'project_type: ' + techStack,
|
|
539
|
+
'ui_library: ' + uiLibrary,
|
|
540
|
+
'',
|
|
541
|
+
'## mancode · ' + mode + ' mode',
|
|
542
|
+
'',
|
|
543
|
+
'你正在使用 mancode ' + mode + ' 模式。',
|
|
544
|
+
'',
|
|
545
|
+
'### 核心原则',
|
|
546
|
+
'1. **优先复用项目已有代码**',
|
|
547
|
+
' - 检查已检测到的源码目录和已有类似实现',
|
|
548
|
+
' - 复用现有组件、函数、样式',
|
|
549
|
+
'',
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
if (profile.uiAssets === 'detected') {
|
|
553
|
+
output.push(
|
|
554
|
+
'2. **应用项目审美 token**(仅在项目 profile 确认有 UI 资产且任务涉及 UI 时)',
|
|
555
|
+
' - UI library: ' + uiLibrary,
|
|
556
|
+
' - 使用项目已有的设计 token',
|
|
557
|
+
);
|
|
558
|
+
} else {
|
|
559
|
+
output.push(
|
|
560
|
+
'2. **按项目能力工作**',
|
|
561
|
+
' - 不假定存在 UI、浏览器或特定技术栈',
|
|
562
|
+
' - 先读取 project-profile 与项目现有验证方式',
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
output.push(
|
|
567
|
+
'',
|
|
568
|
+
'3. **最小改动**',
|
|
569
|
+
' - 只改用户要求的部分',
|
|
570
|
+
' - 不重构无关代码',
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
if (state.teamModeAutoDetected === true && mode === 'solo') {
|
|
574
|
+
output.push(
|
|
575
|
+
'',
|
|
576
|
+
'### 团队协作提醒',
|
|
577
|
+
'检测到团队项目(contributors: ' +
|
|
578
|
+
(Number.isFinite(state.contributors) ? state.contributors : 2) +
|
|
579
|
+
')。',
|
|
580
|
+
'- 涉及多人协作、交接、PR、共享模块时,优先使用 /manteam <task>。',
|
|
581
|
+
'- 只做个人小改动时,可以继续 solo;需要退出流程用 /mansolo。',
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
console.log(output.join('\n'));
|
|
586
|
+
|
|
587
|
+
function readJson(filePath) {
|
|
588
|
+
try {
|
|
589
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
590
|
+
} catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function text(value) {
|
|
596
|
+
return typeof value === 'string' ? value : '';
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function sanitize(value) {
|
|
600
|
+
return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
|
|
601
|
+
}
|
|
593
602
|
`;
|
|
594
|
-
var USER_PROMPT_SUBMIT_HOOK = `#!/bin/
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
603
|
+
var USER_PROMPT_SUBMIT_HOOK = String.raw`#!/usr/bin/env node
|
|
604
|
+
// .mancode/hooks/user-prompt-submit.mjs
|
|
605
|
+
// mancode UserPromptSubmit hook - cross-platform prompt and style context
|
|
606
|
+
import { readFileSync } from 'node:fs';
|
|
607
|
+
import path from 'node:path';
|
|
608
|
+
import { fileURLToPath } from 'node:url';
|
|
609
|
+
|
|
610
|
+
const projectRoot = path.resolve(
|
|
611
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
612
|
+
'../..',
|
|
613
|
+
);
|
|
614
|
+
const mancodeDir = path.join(projectRoot, '.mancode');
|
|
615
|
+
const state = readJson(path.join(mancodeDir, 'state.json')) || {};
|
|
616
|
+
const profile = readJson(path.join(mancodeDir, 'project-profile.json')) || {};
|
|
617
|
+
const mode = typeof state.currentMode === 'string' ? state.currentMode : '';
|
|
618
|
+
const output = [];
|
|
619
|
+
|
|
620
|
+
if (mode === 'solo') {
|
|
621
|
+
output.push(
|
|
622
|
+
'## 动手前,先想六个问题:',
|
|
623
|
+
'',
|
|
624
|
+
'1. **为什么做?**',
|
|
625
|
+
' - 这个改动解决什么问题?',
|
|
626
|
+
'',
|
|
627
|
+
'2. **已经有什么?**',
|
|
628
|
+
' - 项目里有没有类似的实现可以复用?',
|
|
629
|
+
'',
|
|
630
|
+
'3. **最少改多少?**',
|
|
631
|
+
' - 能用一行解决吗?能复用现有代码吗?',
|
|
632
|
+
'',
|
|
633
|
+
'4. **能不能不拆新系统?**',
|
|
634
|
+
' - 不新建文件或模块能完成吗?',
|
|
635
|
+
'',
|
|
636
|
+
'5. **非平凡逻辑怎样最小运行验证?**',
|
|
637
|
+
'',
|
|
638
|
+
'6. **有什么没把握的?**',
|
|
639
|
+
' - 先自行查代码或文档,最多 2 次工具调用;仍不确定再问用户。',
|
|
640
|
+
'',
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const rawInput = readFileSync(0, 'utf8');
|
|
645
|
+
const userPrompt = readPrompt(rawInput);
|
|
646
|
+
const planningPattern = /先(?:别|不要|看|看看|调研|分析|评估)|给.*方案|给.*计划|怎么.*做|如何.*做|怎么.*实现|如何.*实现|应该怎么|怎么.*拆|拆分|只给.*计划|不要.*改代码|别.*改代码|不要.*动代码|别.*动代码|评估.*风险|风险.*评估|设计.*方案|架构|迁移|集成|\b(?:plan|planning|research|investigate|approach|proposal|architecture|risk|migration|integration)\b|how (?:should|would|to)|do not (?:edit|modify|change)|don.t (?:edit|modify|change)|no code changes|without changing code/iu;
|
|
647
|
+
|
|
648
|
+
if (mode === 'solo' && planningPattern.test(userPrompt)) {
|
|
649
|
+
output.push(
|
|
650
|
+
'## mancode 自动路由',
|
|
651
|
+
'',
|
|
652
|
+
'这个请求是规划/调研类任务。不要直接进入 solo 实施。',
|
|
653
|
+
"必须先调用 Skill tool,skill='man',把用户原始请求作为 task,执行 Scout 调研、澄清和 Plan Coach plan。",
|
|
654
|
+
'用户只要计划时,在 Step 4 选择“只要计划”;不要切到另一个命令。',
|
|
655
|
+
'',
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const uiPattern = /\b(?:button|component|page|style|ui|design|layout|css|color|font|theme|card|input|modal|dialog|header|footer|sidebar|dropdown|tooltip|toast|avatar|badge)\b|界面|页面|按钮|样式|颜色|字体|布局|组件|弹窗|导航|卡片|输入框|主题|美化|优化.*界面|调整.*样式/iu;
|
|
660
|
+
|
|
661
|
+
if (profile.uiAssets === 'detected' && uiPattern.test(userPrompt)) {
|
|
662
|
+
appendAestheticSummary(
|
|
663
|
+
output,
|
|
664
|
+
readJson(path.join(mancodeDir, 'aesthetics', 'style-tokens.json')),
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (output.length > 0) console.log(output.join('\n'));
|
|
669
|
+
|
|
670
|
+
function appendAestheticSummary(lines, tokens) {
|
|
671
|
+
if (!tokens || tokens.matchLevel !== 'high') return;
|
|
672
|
+
|
|
673
|
+
const colors = safeEntries(tokens.colors, 8).map(
|
|
674
|
+
([key, value]) => key + '=' + String(value),
|
|
675
|
+
);
|
|
676
|
+
const fonts = safeEntries(tokens.fonts, 4)
|
|
677
|
+
.filter(([, value]) => Array.isArray(value) && value.length > 0)
|
|
678
|
+
.map(([key, value]) => key + '=' + String(value[0]));
|
|
679
|
+
const components = Array.isArray(tokens.components)
|
|
680
|
+
? tokens.components
|
|
681
|
+
.filter(
|
|
682
|
+
(value) =>
|
|
683
|
+
typeof value === 'string' && /^[A-Z][A-Za-z0-9]{0,79}$/.test(value),
|
|
684
|
+
)
|
|
685
|
+
.slice(0, 8)
|
|
686
|
+
: [];
|
|
687
|
+
const cssVariables = safeEntries(tokens.cssVariables, 8).map(
|
|
688
|
+
([key, value]) => '--' + key + '=' + String(value),
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
lines.push('## 审美 token 摘要');
|
|
692
|
+
appendValue(lines, 'UI', tokens.uiLibrary);
|
|
693
|
+
appendValue(lines, 'Dark', tokens.darkMode);
|
|
694
|
+
appendValue(lines, 'Match', tokens.matchLevel);
|
|
695
|
+
appendValue(lines, 'Colors (前 8)', colors.join(', '));
|
|
696
|
+
appendValue(lines, 'Fonts (前 4)', fonts.join(', '));
|
|
697
|
+
appendValue(lines, 'Components (前 8)', components.join(', '));
|
|
698
|
+
appendValue(lines, 'CSS variables (前 8)', cssVariables.join(', '));
|
|
699
|
+
lines.push('完整 token: .mancode/aesthetics/style-tokens.json', '');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function safeEntries(value, limit) {
|
|
703
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return [];
|
|
704
|
+
return Object.entries(value)
|
|
705
|
+
.filter(([key]) => /^[A-Za-z0-9_-]{1,80}$/.test(key))
|
|
706
|
+
.slice(0, limit);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function appendValue(lines, label, value) {
|
|
710
|
+
const clean = sanitize(value);
|
|
711
|
+
if (clean) lines.push(label + ': ' + clean);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function readPrompt(input) {
|
|
715
|
+
try {
|
|
716
|
+
const parsed = JSON.parse(input);
|
|
717
|
+
return typeof parsed.prompt === 'string' && parsed.prompt
|
|
718
|
+
? parsed.prompt
|
|
719
|
+
: input;
|
|
720
|
+
} catch {
|
|
721
|
+
return input;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function readJson(filePath) {
|
|
726
|
+
try {
|
|
727
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
728
|
+
} catch {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function sanitize(value) {
|
|
734
|
+
return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
|
|
735
|
+
}
|
|
716
736
|
`;
|
|
717
737
|
var SOLO_SKILL = `# mancode \xB7 solo mode
|
|
718
738
|
|
|
@@ -757,23 +777,19 @@ var SOLO_SKILL = `# mancode \xB7 solo mode
|
|
|
757
777
|
4. \u4EE5\u9879\u76EE\u65E2\u6709\u7684\u65E0\u969C\u788D\u3001\u54CD\u5E94\u5F0F\u4E0E\u53CD\u9988\u65B9\u5F0F\u4E3A\u51C6
|
|
758
778
|
|
|
759
779
|
### \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
|
|
780
|
+
\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
781
|
|
|
765
782
|
### \u9A8C\u8BC1
|
|
766
783
|
\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
|
|
784
|
+
1. \u9009\u62E9\u4E0E\u672C\u6B21\u6539\u52A8\u76F4\u63A5\u76F8\u5173\u7684\u6700\u7A84 test\u3001lint\u3001typecheck\u3001build \u6216 smoke check
|
|
785
|
+
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
|
|
786
|
+
3. \u786E\u8BA4\u5B9E\u9645\u6267\u884C\u7ED3\u679C\u540E\u624D\u58F0\u79F0\u5B8C\u6210
|
|
771
787
|
|
|
772
|
-
### \u5B8C\u6210\u540E\
|
|
773
|
-
- \
|
|
774
|
-
- \
|
|
775
|
-
- \
|
|
776
|
-
- \
|
|
788
|
+
### \u5B8C\u6210\u540E\u7684\u4E00\u6B21\u53D7\u9650\u81EA\u68C0
|
|
789
|
+
- \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
|
|
790
|
+
- \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
|
|
791
|
+
- \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
|
|
792
|
+
- \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
793
|
|
|
778
794
|
## \u4F60\u7684\u98CE\u683C
|
|
779
795
|
|
|
@@ -833,21 +849,24 @@ var MAN_SKILL = {
|
|
|
833
849
|
|
|
834
850
|
\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
851
|
|
|
852
|
+
\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
|
|
853
|
+
|
|
836
854
|
### Step 7: Film #1 \u4EE3\u7801\u8D28\u91CF\u5BA1\u67E5\u4E0E\u4FEE\u590D
|
|
837
855
|
|
|
838
|
-
\u8C03\u7528 \`film-analyst-offense
|
|
856
|
+
\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
857
|
|
|
840
858
|
### Step 8: Film #2 \u5B89\u5168\u4E0E\u8FB9\u754C\u5BA1\u67E5
|
|
841
859
|
|
|
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
|
|
860
|
+
\`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
861
|
|
|
844
862
|
### Step 9: \u589E\u5F3A\u6536\u5C3E
|
|
845
863
|
|
|
846
|
-
1. \u4FEE\u590D
|
|
847
|
-
2. \
|
|
848
|
-
3. \u9A8C\u8BC1\
|
|
849
|
-
4. \
|
|
850
|
-
5.
|
|
864
|
+
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
|
|
865
|
+
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
|
|
866
|
+
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
|
|
867
|
+
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
|
|
868
|
+
5. \u5173\u952E\u51B3\u7B56 appendTeamDecision \u5230 \`decisions.md\`\uFF0C\u66F4\u65B0 Active Plans\u3002
|
|
869
|
+
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
870
|
|
|
852
871
|
\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
872
|
|
|
@@ -1174,9 +1193,9 @@ var MANTEAM_SKILL = {
|
|
|
1174
1193
|
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
1194
|
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
1195
|
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\
|
|
1196
|
+
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
|
|
1197
|
+
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
|
|
1198
|
+
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
1199
|
|
|
1181
1200
|
\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
1201
|
|
|
@@ -1242,7 +1261,7 @@ ${spec.body}
|
|
|
1242
1261
|
}
|
|
1243
1262
|
|
|
1244
1263
|
// src/installers/common.ts
|
|
1245
|
-
import {
|
|
1264
|
+
import { mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
1246
1265
|
import path2 from "path";
|
|
1247
1266
|
|
|
1248
1267
|
// src/system/team-memory.ts
|
|
@@ -1432,12 +1451,10 @@ async function readTextIfExists(filePath) {
|
|
|
1432
1451
|
}
|
|
1433
1452
|
}
|
|
1434
1453
|
async function installHooks(hooksDir) {
|
|
1435
|
-
const sessionStartDst = path2.join(hooksDir, "session-start.
|
|
1454
|
+
const sessionStartDst = path2.join(hooksDir, "session-start.mjs");
|
|
1436
1455
|
await writeFile2(sessionStartDst, SESSION_START_HOOK, "utf-8");
|
|
1437
|
-
|
|
1438
|
-
const userPromptDst = path2.join(hooksDir, "user-prompt-submit.sh");
|
|
1456
|
+
const userPromptDst = path2.join(hooksDir, "user-prompt-submit.mjs");
|
|
1439
1457
|
await writeFile2(userPromptDst, USER_PROMPT_SUBMIT_HOOK, "utf-8");
|
|
1440
|
-
await chmod(userPromptDst, 493);
|
|
1441
1458
|
}
|
|
1442
1459
|
async function pathExists(p) {
|
|
1443
1460
|
try {
|
|
@@ -1455,12 +1472,18 @@ function isNodeError(err) {
|
|
|
1455
1472
|
var CLAUDE_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:claude-skill. Do not edit this marker. -->";
|
|
1456
1473
|
var CLAUDE_AGENT_MANAGED_MARKER = "<!-- Managed by mancode:claude-agent. Do not edit this marker. -->";
|
|
1457
1474
|
var MANCODE_HOOK_COMMANDS = [
|
|
1475
|
+
'node ".mancode/hooks/session-start.mjs"',
|
|
1476
|
+
'node ".mancode/hooks/user-prompt-submit.mjs"'
|
|
1477
|
+
];
|
|
1478
|
+
var LEGACY_MANCODE_HOOK_COMMANDS = [
|
|
1458
1479
|
"bash .mancode/hooks/session-start.sh",
|
|
1459
1480
|
"bash .mancode/hooks/user-prompt-submit.sh"
|
|
1460
1481
|
];
|
|
1461
1482
|
function isGeneratedMancodeHookCommand(command) {
|
|
1462
1483
|
const normalized = command.trim();
|
|
1463
|
-
return MANCODE_HOOK_COMMANDS.some(
|
|
1484
|
+
return [...MANCODE_HOOK_COMMANDS, ...LEGACY_MANCODE_HOOK_COMMANDS].some(
|
|
1485
|
+
(item) => item === normalized
|
|
1486
|
+
);
|
|
1464
1487
|
}
|
|
1465
1488
|
var LEGACY_CLAUDE_SKILL_SETTINGS = {
|
|
1466
1489
|
solo: ".claude/skills/mancode-solo.md",
|
|
@@ -1489,6 +1512,14 @@ async function installClaudeCode(projectRoot, options) {
|
|
|
1489
1512
|
await installAgents(path3.join(claudeDir, "agents"), force);
|
|
1490
1513
|
}
|
|
1491
1514
|
await updateClaudeSettings(claudeDir, settings);
|
|
1515
|
+
await removeLegacyHookFiles(projectRoot);
|
|
1516
|
+
}
|
|
1517
|
+
async function removeLegacyHookFiles(projectRoot) {
|
|
1518
|
+
const hooksDir = path3.join(projectRoot, ".mancode", "hooks");
|
|
1519
|
+
await Promise.all([
|
|
1520
|
+
rm(path3.join(hooksDir, "session-start.sh"), { force: true }),
|
|
1521
|
+
rm(path3.join(hooksDir, "user-prompt-submit.sh"), { force: true })
|
|
1522
|
+
]);
|
|
1492
1523
|
}
|
|
1493
1524
|
async function installSoloSkill(skillsDir, force) {
|
|
1494
1525
|
await installProjectSkill(
|
|
@@ -2194,10 +2225,10 @@ var MODE_META = {
|
|
|
2194
2225
|
"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
2226
|
"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
2227
|
"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
|
|
2228
|
+
"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 ...`.",
|
|
2229
|
+
"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.",
|
|
2230
|
+
"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.",
|
|
2231
|
+
"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
2232
|
"",
|
|
2202
2233
|
"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
2234
|
].join("\n")
|
|
@@ -2214,7 +2245,7 @@ var MODE_META = {
|
|
|
2214
2245
|
"shared-module, and handoff risks in `team-context.md`.",
|
|
2215
2246
|
"",
|
|
2216
2247
|
"Then execute every /man Step 1 through Step 9, including clarification,",
|
|
2217
|
-
"plan gate, validation,
|
|
2248
|
+
"plan gate, validation, bounded risk-based review, conditional completion, planVersion,",
|
|
2218
2249
|
"Active Plans, and linked mamba child behavior. Add compatibility, rollback,",
|
|
2219
2250
|
"ownership, conflict, and handoff notes at each relevant step.",
|
|
2220
2251
|
"When a resolved mamba child had blocked the parent, restore the parent through",
|
|
@@ -2317,7 +2348,9 @@ function renderPracticeRules() {
|
|
|
2317
2348
|
"5. Prefer a one-line fix when it is enough.",
|
|
2318
2349
|
"6. Only then write the smallest new implementation.",
|
|
2319
2350
|
"",
|
|
2320
|
-
"For every task, consider: why this change, what already exists, and what is the smallest useful diff?"
|
|
2351
|
+
"For every task, consider: why this change, what already exists, and what is the smallest useful diff?",
|
|
2352
|
+
"",
|
|
2353
|
+
"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
2354
|
].join("\n");
|
|
2322
2355
|
}
|
|
2323
2356
|
function renderAesthetics(tokens) {
|
|
@@ -2499,7 +2532,7 @@ function renderCopilotPromptConventions() {
|
|
|
2499
2532
|
"",
|
|
2500
2533
|
"GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
|
|
2501
2534
|
"",
|
|
2502
|
-
"- man: use progressive research, planning, implementation, verification, and review.",
|
|
2535
|
+
"- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
|
|
2503
2536
|
"- mamba: diagnose bugs and validate real user flows or regressions.",
|
|
2504
2537
|
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
2505
2538
|
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
@@ -2717,7 +2750,9 @@ function renderSoloRule() {
|
|
|
2717
2750
|
"- Keep the diff narrow.",
|
|
2718
2751
|
"- Reuse existing functions, components, styles, and dependencies.",
|
|
2719
2752
|
"- 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."
|
|
2753
|
+
"- Verify with the narrowest meaningful test, lint, build, or smoke check.",
|
|
2754
|
+
"- Perform one bounded self-check limited to the current diff. Do not start another reviewer, create a review artifact, or repeat the review.",
|
|
2755
|
+
"- Recommend /man only for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, or infrastructure risk."
|
|
2721
2756
|
].join("\n");
|
|
2722
2757
|
}
|
|
2723
2758
|
function renderMambaRule() {
|
|
@@ -2851,11 +2886,12 @@ function isPlatformName(platform) {
|
|
|
2851
2886
|
}
|
|
2852
2887
|
|
|
2853
2888
|
// src/system/detect-team.ts
|
|
2854
|
-
import {
|
|
2889
|
+
import { execFile } from "child_process";
|
|
2890
|
+
import { access as access2 } from "fs/promises";
|
|
2855
2891
|
import path10 from "path";
|
|
2856
2892
|
import process2 from "process";
|
|
2857
2893
|
import { promisify } from "util";
|
|
2858
|
-
var
|
|
2894
|
+
var execFileAsync = promisify(execFile);
|
|
2859
2895
|
var NO_TEAM = {
|
|
2860
2896
|
isTeam: false,
|
|
2861
2897
|
contributors: 1,
|
|
@@ -2863,101 +2899,68 @@ var NO_TEAM = {
|
|
|
2863
2899
|
hasRemote: false
|
|
2864
2900
|
};
|
|
2865
2901
|
var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
|
|
2866
|
-
var
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
GIT_PAGER: "cat",
|
|
2872
|
-
PAGER: "cat",
|
|
2873
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
2874
|
-
}
|
|
2902
|
+
var GIT_ENV = {
|
|
2903
|
+
...process2.env,
|
|
2904
|
+
GIT_PAGER: "cat",
|
|
2905
|
+
PAGER: "cat",
|
|
2906
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
2875
2907
|
};
|
|
2876
2908
|
async function detectTeamStatus(projectRoot) {
|
|
2877
|
-
const gitDir = path10.join(projectRoot, ".git");
|
|
2878
|
-
try {
|
|
2879
|
-
await execAsync("git rev-parse --is-inside-work-tree", {
|
|
2880
|
-
cwd: projectRoot,
|
|
2881
|
-
...EXEC_OPTS
|
|
2882
|
-
});
|
|
2883
|
-
} catch {
|
|
2884
|
-
return NO_TEAM;
|
|
2885
|
-
}
|
|
2886
2909
|
try {
|
|
2887
|
-
await
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2910
|
+
await access2(path10.join(projectRoot, ".git"));
|
|
2911
|
+
const inside = await runGit(projectRoot, [
|
|
2912
|
+
"rev-parse",
|
|
2913
|
+
"--is-inside-work-tree"
|
|
2914
|
+
]);
|
|
2915
|
+
if (inside.trim() !== "true") return NO_TEAM;
|
|
2916
|
+
const [allEmails, recentEmails, remotes] = await Promise.all([
|
|
2917
|
+
runGit(projectRoot, ["log", "--all", "--format=%ae"]),
|
|
2918
|
+
runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
|
|
2919
|
+
runGit(projectRoot, ["remote", "-v"])
|
|
2920
|
+
]);
|
|
2921
|
+
const contributors = countUniqueLines(allEmails);
|
|
2922
|
+
const recentActive = countUniqueLines(recentEmails);
|
|
2923
|
+
const hasRemote = REMOTE_PATTERN.test(remotes);
|
|
2924
|
+
return {
|
|
2925
|
+
isTeam: contributors > 1 && hasRemote && recentActive > 1,
|
|
2926
|
+
contributors: contributors || 1,
|
|
2927
|
+
recentActive: recentActive || 1,
|
|
2928
|
+
hasRemote
|
|
2929
|
+
};
|
|
2891
2930
|
} catch {
|
|
2892
2931
|
return NO_TEAM;
|
|
2893
2932
|
}
|
|
2894
|
-
const contributors = await countUniqueEmails(
|
|
2895
|
-
"git log --all --format='%ae'",
|
|
2896
|
-
projectRoot
|
|
2897
|
-
);
|
|
2898
|
-
const recentActive = await countUniqueEmails(
|
|
2899
|
-
"git log --since='30 days ago' --format='%ae'",
|
|
2900
|
-
projectRoot
|
|
2901
|
-
);
|
|
2902
|
-
const hasRemote = await checkRemote(projectRoot);
|
|
2903
|
-
const safeContributors = Number.isFinite(contributors) ? contributors : 0;
|
|
2904
|
-
const safeRecent = Number.isFinite(recentActive) ? recentActive : 0;
|
|
2905
|
-
const isTeam = safeContributors > 1 && hasRemote && safeRecent > 1;
|
|
2906
|
-
return {
|
|
2907
|
-
isTeam,
|
|
2908
|
-
contributors: safeContributors || 1,
|
|
2909
|
-
recentActive: safeRecent || 1,
|
|
2910
|
-
hasRemote
|
|
2911
|
-
};
|
|
2912
2933
|
}
|
|
2913
|
-
async function
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
} catch {
|
|
2922
|
-
return 0;
|
|
2923
|
-
}
|
|
2934
|
+
async function runGit(cwd, args) {
|
|
2935
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
2936
|
+
cwd,
|
|
2937
|
+
encoding: "utf-8",
|
|
2938
|
+
env: GIT_ENV,
|
|
2939
|
+
windowsHide: true
|
|
2940
|
+
});
|
|
2941
|
+
return stdout;
|
|
2924
2942
|
}
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
...EXEC_OPTS
|
|
2930
|
-
});
|
|
2931
|
-
return REMOTE_PATTERN.test(stdout);
|
|
2932
|
-
} catch {
|
|
2933
|
-
return false;
|
|
2934
|
-
}
|
|
2943
|
+
function countUniqueLines(output) {
|
|
2944
|
+
return new Set(
|
|
2945
|
+
output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
2946
|
+
).size;
|
|
2935
2947
|
}
|
|
2936
2948
|
|
|
2937
2949
|
// src/system/detect.ts
|
|
2938
|
-
import {
|
|
2950
|
+
import { execFile as execFile2 } from "child_process";
|
|
2951
|
+
import process3 from "process";
|
|
2939
2952
|
import { promisify as promisify2 } from "util";
|
|
2940
|
-
var
|
|
2941
|
-
async function detectSystemDeps() {
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
return false;
|
|
2952
|
-
}
|
|
2953
|
-
};
|
|
2954
|
-
const [bash, git, jq, node] = await Promise.all([
|
|
2955
|
-
check("bash"),
|
|
2956
|
-
check("git"),
|
|
2957
|
-
check("jq"),
|
|
2958
|
-
check("node")
|
|
2959
|
-
]);
|
|
2960
|
-
return { bash, git, jq, node };
|
|
2953
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
2954
|
+
async function detectSystemDeps(env = process3.env) {
|
|
2955
|
+
try {
|
|
2956
|
+
await execFileAsync2("git", ["--version"], {
|
|
2957
|
+
env,
|
|
2958
|
+
windowsHide: true
|
|
2959
|
+
});
|
|
2960
|
+
return { git: true };
|
|
2961
|
+
} catch {
|
|
2962
|
+
return { git: false };
|
|
2963
|
+
}
|
|
2961
2964
|
}
|
|
2962
2965
|
|
|
2963
2966
|
// src/system/project-profile.ts
|
|
@@ -3653,7 +3656,7 @@ var EXIT_ALREADY_INITIALIZED = 1;
|
|
|
3653
3656
|
var EXIT_NOT_A_PROJECT_DIR = 2;
|
|
3654
3657
|
var EXIT_INIT_FAILED = 5;
|
|
3655
3658
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
3656
|
-
async function init(rootDir =
|
|
3659
|
+
async function init(rootDir = process4.cwd(), options = {}) {
|
|
3657
3660
|
const mancodeDir = path13.join(rootDir, ".mancode");
|
|
3658
3661
|
const stateFile = path13.join(mancodeDir, "state.json");
|
|
3659
3662
|
const wasInitialized = await pathExists3(stateFile);
|
|
@@ -3702,20 +3705,12 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
3702
3705
|
try {
|
|
3703
3706
|
console.log("\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...");
|
|
3704
3707
|
const deps = await detectSystemDeps();
|
|
3705
|
-
if (!deps.
|
|
3706
|
-
console.error("\u2717 Missing required dependencies:");
|
|
3707
|
-
if (!deps.bash) console.error(" - bash (required)");
|
|
3708
|
-
if (!deps.git) console.error(" - git (required)");
|
|
3709
|
-
console.error("");
|
|
3710
|
-
console.error(" Install them and try again.");
|
|
3711
|
-
return EXIT_NOT_A_PROJECT_DIR;
|
|
3712
|
-
}
|
|
3713
|
-
if (!deps.jq) {
|
|
3708
|
+
if (!deps.git) {
|
|
3714
3709
|
console.log(
|
|
3715
|
-
"\u26A0\uFE0F
|
|
3710
|
+
"\u26A0\uFE0F Git not found (optional). Team auto-detection will use solo defaults."
|
|
3716
3711
|
);
|
|
3717
3712
|
} else {
|
|
3718
|
-
console.log("
|
|
3713
|
+
console.log(" git \u2713");
|
|
3719
3714
|
}
|
|
3720
3715
|
console.log("\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...");
|
|
3721
3716
|
const profile = await detectProjectProfile(rootDir);
|
|
@@ -3996,7 +3991,7 @@ async function pathExists3(p) {
|
|
|
3996
3991
|
// src/commands/install.ts
|
|
3997
3992
|
import { promises as fs4 } from "fs";
|
|
3998
3993
|
import path15 from "path";
|
|
3999
|
-
import
|
|
3994
|
+
import process5 from "process";
|
|
4000
3995
|
|
|
4001
3996
|
// src/installers/platform-status.ts
|
|
4002
3997
|
import { promises as fs3 } from "fs";
|
|
@@ -4019,8 +4014,8 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4019
4014
|
),
|
|
4020
4015
|
claudeHooksRegistered(rootDir),
|
|
4021
4016
|
pathsExist([
|
|
4022
|
-
path14.join(rootDir, ".mancode", "hooks", "session-start.
|
|
4023
|
-
path14.join(rootDir, ".mancode", "hooks", "user-prompt-submit.
|
|
4017
|
+
path14.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
4018
|
+
path14.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
4024
4019
|
])
|
|
4025
4020
|
]);
|
|
4026
4021
|
const present = hasSoloSkill && registered && hasHookFiles;
|
|
@@ -4249,9 +4244,9 @@ function hooksRegistered(settings) {
|
|
|
4249
4244
|
if (!isRecord4(settings)) return false;
|
|
4250
4245
|
const hooks = settings.hooks;
|
|
4251
4246
|
if (!isRecord4(hooks)) return false;
|
|
4252
|
-
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.
|
|
4247
|
+
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
|
|
4253
4248
|
hooks.UserPromptSubmit,
|
|
4254
|
-
".mancode/hooks/user-prompt-submit.
|
|
4249
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
4255
4250
|
);
|
|
4256
4251
|
}
|
|
4257
4252
|
function hasHookCommand(value, needle) {
|
|
@@ -4281,7 +4276,7 @@ var EXIT_OK2 = 0;
|
|
|
4281
4276
|
var EXIT_NOT_INITIALIZED = 1;
|
|
4282
4277
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|
|
4283
4278
|
var EXIT_INSTALL_FAILED = 3;
|
|
4284
|
-
async function install(rootDir =
|
|
4279
|
+
async function install(rootDir = process5.cwd(), platform = "claude-code", options = {}) {
|
|
4285
4280
|
const stateFile = path15.join(rootDir, ".mancode", "state.json");
|
|
4286
4281
|
if (!await pathExists5(stateFile)) {
|
|
4287
4282
|
console.error("\u2717 mancode not initialized.");
|
|
@@ -4443,9 +4438,9 @@ function isRecord5(value) {
|
|
|
4443
4438
|
// src/commands/list-platforms.ts
|
|
4444
4439
|
import { promises as fs5 } from "fs";
|
|
4445
4440
|
import path16 from "path";
|
|
4446
|
-
import
|
|
4441
|
+
import process6 from "process";
|
|
4447
4442
|
var EXIT_OK3 = 0;
|
|
4448
|
-
async function listPlatforms(rootDir =
|
|
4443
|
+
async function listPlatforms(rootDir = process6.cwd()) {
|
|
4449
4444
|
const installed = new Set(await readInstalledPlatforms(rootDir));
|
|
4450
4445
|
const platforms = getPlatformInstallers();
|
|
4451
4446
|
console.log("");
|
|
@@ -4483,16 +4478,16 @@ function describePlatform(platform) {
|
|
|
4483
4478
|
}
|
|
4484
4479
|
|
|
4485
4480
|
// src/commands/manps.ts
|
|
4486
|
-
import { access as
|
|
4481
|
+
import { access as access4 } from "fs/promises";
|
|
4487
4482
|
import path18 from "path";
|
|
4488
4483
|
import { createInterface } from "readline/promises";
|
|
4489
4484
|
|
|
4490
4485
|
// src/system/preseason.ts
|
|
4491
|
-
import { execFile } from "child_process";
|
|
4486
|
+
import { execFile as execFile3 } from "child_process";
|
|
4492
4487
|
import { createHash } from "crypto";
|
|
4493
4488
|
import { existsSync, readFileSync } from "fs";
|
|
4494
4489
|
import {
|
|
4495
|
-
access as
|
|
4490
|
+
access as access3,
|
|
4496
4491
|
lstat,
|
|
4497
4492
|
mkdir as mkdir7,
|
|
4498
4493
|
readFile as readFile8,
|
|
@@ -5052,7 +5047,7 @@ function architectureScannerUnavailableIssue() {
|
|
|
5052
5047
|
function runDepcruise(binary, projectRoot) {
|
|
5053
5048
|
return new Promise((resolve) => {
|
|
5054
5049
|
const needsShell = process.platform === "win32";
|
|
5055
|
-
|
|
5050
|
+
execFile3(
|
|
5056
5051
|
binary,
|
|
5057
5052
|
["--output-type", "json", "."],
|
|
5058
5053
|
{
|
|
@@ -5392,7 +5387,7 @@ function pathExistsSync(file) {
|
|
|
5392
5387
|
}
|
|
5393
5388
|
async function pathExists6(file) {
|
|
5394
5389
|
try {
|
|
5395
|
-
await
|
|
5390
|
+
await access3(file);
|
|
5396
5391
|
return true;
|
|
5397
5392
|
} catch {
|
|
5398
5393
|
return false;
|
|
@@ -5555,7 +5550,7 @@ async function readStdinLines() {
|
|
|
5555
5550
|
}
|
|
5556
5551
|
async function pathExists7(p) {
|
|
5557
5552
|
try {
|
|
5558
|
-
await
|
|
5553
|
+
await access4(p);
|
|
5559
5554
|
return true;
|
|
5560
5555
|
} catch {
|
|
5561
5556
|
return false;
|
|
@@ -5565,10 +5560,10 @@ async function pathExists7(p) {
|
|
|
5565
5560
|
// src/commands/refresh-style.ts
|
|
5566
5561
|
import { promises as fs6 } from "fs";
|
|
5567
5562
|
import path19 from "path";
|
|
5568
|
-
import
|
|
5563
|
+
import process7 from "process";
|
|
5569
5564
|
var EXIT_OK5 = 0;
|
|
5570
5565
|
var EXIT_NOT_INITIALIZED3 = 1;
|
|
5571
|
-
async function refreshStyle(rootDir =
|
|
5566
|
+
async function refreshStyle(rootDir = process7.cwd()) {
|
|
5572
5567
|
const stateFile = path19.join(rootDir, ".mancode", "state.json");
|
|
5573
5568
|
if (!await pathExists8(stateFile)) {
|
|
5574
5569
|
console.error("\u2717 mancode not initialized.");
|
|
@@ -5718,24 +5713,220 @@ async function pathExists8(p) {
|
|
|
5718
5713
|
// src/commands/status.ts
|
|
5719
5714
|
import { spawn } from "child_process";
|
|
5720
5715
|
import { promises as fs7 } from "fs";
|
|
5721
|
-
import
|
|
5722
|
-
import
|
|
5716
|
+
import path22 from "path";
|
|
5717
|
+
import process8 from "process";
|
|
5723
5718
|
|
|
5724
5719
|
// src/system/workflow.ts
|
|
5725
5720
|
import {
|
|
5726
|
-
mkdir as
|
|
5727
|
-
readFile as
|
|
5721
|
+
mkdir as mkdir9,
|
|
5722
|
+
readFile as readFile10,
|
|
5728
5723
|
readdir as readdir5,
|
|
5729
5724
|
rm as rm4,
|
|
5730
|
-
stat as
|
|
5731
|
-
writeFile as
|
|
5725
|
+
stat as stat4,
|
|
5726
|
+
writeFile as writeFile11
|
|
5732
5727
|
} from "fs/promises";
|
|
5728
|
+
import path21 from "path";
|
|
5729
|
+
|
|
5730
|
+
// src/system/review-ledger.ts
|
|
5731
|
+
import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
|
|
5733
5732
|
import path20 from "path";
|
|
5733
|
+
var REVIEW_FILE = "review-ledger.json";
|
|
5734
|
+
var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
5735
|
+
var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
|
|
5736
|
+
function isReviewDepth(value) {
|
|
5737
|
+
return value === "targeted" || value === "full";
|
|
5738
|
+
}
|
|
5739
|
+
function isReviewDomain(value) {
|
|
5740
|
+
return value === "quality" || value === "security";
|
|
5741
|
+
}
|
|
5742
|
+
async function initializeReview(projectRoot, taskId, depth, targetDomain) {
|
|
5743
|
+
assertValidTaskId(taskId);
|
|
5744
|
+
const existing = await readReviewLedger(projectRoot, taskId);
|
|
5745
|
+
if (existing) throw new Error(`review already initialized: ${taskId}`);
|
|
5746
|
+
if (depth === "targeted" && !targetDomain) {
|
|
5747
|
+
throw new Error("targeted review requires a review domain");
|
|
5748
|
+
}
|
|
5749
|
+
const ledger = {
|
|
5750
|
+
version: "1.0",
|
|
5751
|
+
depth,
|
|
5752
|
+
requiredDomains: depth === "full" ? ["quality", "security"] : [targetDomain],
|
|
5753
|
+
completedDomains: [],
|
|
5754
|
+
reports: {},
|
|
5755
|
+
blockers: [],
|
|
5756
|
+
remediationRounds: 0
|
|
5757
|
+
};
|
|
5758
|
+
await writeReviewLedger(projectRoot, taskId, ledger);
|
|
5759
|
+
return ledger;
|
|
5760
|
+
}
|
|
5761
|
+
async function completeReviewDomain(projectRoot, taskId, domain, report, blockerIds) {
|
|
5762
|
+
const ledger = await requireReviewLedger(projectRoot, taskId);
|
|
5763
|
+
if (!ledger.requiredDomains.includes(domain)) {
|
|
5764
|
+
throw new Error(`review domain is not required: ${domain}`);
|
|
5765
|
+
}
|
|
5766
|
+
if (ledger.completedDomains.includes(domain)) {
|
|
5767
|
+
throw new Error(`review domain already completed: ${domain}`);
|
|
5768
|
+
}
|
|
5769
|
+
if (domain === "security" && !ledger.completedDomains.includes("quality")) {
|
|
5770
|
+
throw new Error("quality review must complete first");
|
|
5771
|
+
}
|
|
5772
|
+
assertSafeReportPath(report);
|
|
5773
|
+
await assertReportExists(projectRoot, taskId, report);
|
|
5774
|
+
const uniqueIds = new Set(blockerIds);
|
|
5775
|
+
if (uniqueIds.size !== blockerIds.length) {
|
|
5776
|
+
throw new Error("review blocker ids must be unique");
|
|
5777
|
+
}
|
|
5778
|
+
for (const id of blockerIds) {
|
|
5779
|
+
if (!BLOCKER_ID_PATTERN.test(id)) {
|
|
5780
|
+
throw new Error(`invalid review blocker id: ${id}`);
|
|
5781
|
+
}
|
|
5782
|
+
if (ledger.blockers.some((blocker) => blocker.id === id)) {
|
|
5783
|
+
throw new Error(`duplicate review blocker id: ${id}`);
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
const updated = {
|
|
5787
|
+
...ledger,
|
|
5788
|
+
completedDomains: [...ledger.completedDomains, domain],
|
|
5789
|
+
reports: { ...ledger.reports, [domain]: report },
|
|
5790
|
+
blockers: [
|
|
5791
|
+
...ledger.blockers,
|
|
5792
|
+
...blockerIds.map((id) => ({
|
|
5793
|
+
id,
|
|
5794
|
+
domain,
|
|
5795
|
+
status: "open"
|
|
5796
|
+
}))
|
|
5797
|
+
]
|
|
5798
|
+
};
|
|
5799
|
+
await writeReviewLedger(projectRoot, taskId, updated);
|
|
5800
|
+
return updated;
|
|
5801
|
+
}
|
|
5802
|
+
async function remediateReviewBlockers(projectRoot, taskId, resolvedIds) {
|
|
5803
|
+
const ledger = await requireReviewLedger(projectRoot, taskId);
|
|
5804
|
+
if (ledger.remediationRounds >= 1) {
|
|
5805
|
+
throw new Error("review allows only one remediation round");
|
|
5806
|
+
}
|
|
5807
|
+
if (resolvedIds.length === 0) {
|
|
5808
|
+
throw new Error("review remediation requires resolved blocker ids");
|
|
5809
|
+
}
|
|
5810
|
+
const uniqueIds = new Set(resolvedIds);
|
|
5811
|
+
if (uniqueIds.size !== resolvedIds.length) {
|
|
5812
|
+
throw new Error("resolved review blocker ids must be unique");
|
|
5813
|
+
}
|
|
5814
|
+
for (const id of resolvedIds) {
|
|
5815
|
+
const blocker = ledger.blockers.find((item) => item.id === id);
|
|
5816
|
+
if (!blocker || blocker.status !== "open") {
|
|
5817
|
+
throw new Error(`review blocker is not open: ${id}`);
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
const updated = {
|
|
5821
|
+
...ledger,
|
|
5822
|
+
remediationRounds: ledger.remediationRounds + 1,
|
|
5823
|
+
blockers: ledger.blockers.map(
|
|
5824
|
+
(blocker) => uniqueIds.has(blocker.id) ? { ...blocker, status: "resolved" } : blocker
|
|
5825
|
+
)
|
|
5826
|
+
};
|
|
5827
|
+
await writeReviewLedger(projectRoot, taskId, updated);
|
|
5828
|
+
return updated;
|
|
5829
|
+
}
|
|
5830
|
+
async function reviewCanComplete(projectRoot, taskId) {
|
|
5831
|
+
const ledger = await readReviewLedger(projectRoot, taskId);
|
|
5832
|
+
if (!ledger) return false;
|
|
5833
|
+
return ledger.requiredDomains.every(
|
|
5834
|
+
(domain) => ledger.completedDomains.includes(domain)
|
|
5835
|
+
) && ledger.blockers.every((blocker) => blocker.status === "resolved");
|
|
5836
|
+
}
|
|
5837
|
+
async function readReviewLedger(projectRoot, taskId) {
|
|
5838
|
+
if (!TASK_ID_PATTERN.test(taskId)) return null;
|
|
5839
|
+
try {
|
|
5840
|
+
const raw = await readFile9(reviewPath(projectRoot, taskId), "utf-8");
|
|
5841
|
+
const value = JSON.parse(raw);
|
|
5842
|
+
return isReviewLedger(value) ? value : null;
|
|
5843
|
+
} catch {
|
|
5844
|
+
return null;
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
function isReviewLedger(value) {
|
|
5848
|
+
if (!isRecord7(value)) return false;
|
|
5849
|
+
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) {
|
|
5850
|
+
return false;
|
|
5851
|
+
}
|
|
5852
|
+
const requiredDomains = value.requiredDomains;
|
|
5853
|
+
const completedDomains = value.completedDomains;
|
|
5854
|
+
const reports = value.reports;
|
|
5855
|
+
const blockers = value.blockers;
|
|
5856
|
+
const expectedDomains = value.depth === "full" ? ["quality", "security"] : requiredDomains;
|
|
5857
|
+
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) => {
|
|
5858
|
+
const report = reports[domain];
|
|
5859
|
+
return typeof report !== "string" || !isSafeReportPath(report);
|
|
5860
|
+
})) {
|
|
5861
|
+
return false;
|
|
5862
|
+
}
|
|
5863
|
+
const blockerIds = /* @__PURE__ */ new Set();
|
|
5864
|
+
return blockers.every(
|
|
5865
|
+
(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")
|
|
5866
|
+
);
|
|
5867
|
+
}
|
|
5868
|
+
function isDomainArray(value) {
|
|
5869
|
+
return Array.isArray(value) && value.every(isReviewDomain);
|
|
5870
|
+
}
|
|
5871
|
+
function isRecord7(value) {
|
|
5872
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5873
|
+
}
|
|
5874
|
+
async function requireReviewLedger(projectRoot, taskId) {
|
|
5875
|
+
assertValidTaskId(taskId);
|
|
5876
|
+
const ledger = await readReviewLedger(projectRoot, taskId);
|
|
5877
|
+
if (!ledger) throw new Error(`review not initialized: ${taskId}`);
|
|
5878
|
+
return ledger;
|
|
5879
|
+
}
|
|
5880
|
+
async function writeReviewLedger(projectRoot, taskId, ledger) {
|
|
5881
|
+
const dir = path20.join(projectRoot, ".mancode", "workflows", taskId);
|
|
5882
|
+
await mkdir8(dir, { recursive: true });
|
|
5883
|
+
await writeFile10(
|
|
5884
|
+
path20.join(dir, REVIEW_FILE),
|
|
5885
|
+
`${JSON.stringify(ledger, null, 2)}
|
|
5886
|
+
`,
|
|
5887
|
+
"utf-8"
|
|
5888
|
+
);
|
|
5889
|
+
}
|
|
5890
|
+
function reviewPath(projectRoot, taskId) {
|
|
5891
|
+
return path20.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
|
|
5892
|
+
}
|
|
5893
|
+
function assertValidTaskId(taskId) {
|
|
5894
|
+
if (!TASK_ID_PATTERN.test(taskId)) {
|
|
5895
|
+
throw new Error(`invalid workflow taskId: ${taskId}`);
|
|
5896
|
+
}
|
|
5897
|
+
}
|
|
5898
|
+
function assertSafeReportPath(report) {
|
|
5899
|
+
if (!isSafeReportPath(report)) {
|
|
5900
|
+
throw new Error(`invalid review report path: ${report}`);
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
async function assertReportExists(projectRoot, taskId, report) {
|
|
5904
|
+
const reportPath = path20.join(
|
|
5905
|
+
projectRoot,
|
|
5906
|
+
".mancode",
|
|
5907
|
+
"workflows",
|
|
5908
|
+
taskId,
|
|
5909
|
+
report
|
|
5910
|
+
);
|
|
5911
|
+
try {
|
|
5912
|
+
if ((await stat3(reportPath)).isFile()) return;
|
|
5913
|
+
} catch {
|
|
5914
|
+
}
|
|
5915
|
+
throw new Error(`review report not found: ${report}`);
|
|
5916
|
+
}
|
|
5917
|
+
function isSafeReportPath(report) {
|
|
5918
|
+
const normalized = path20.posix.normalize(report.replaceAll("\\", "/"));
|
|
5919
|
+
return Boolean(
|
|
5920
|
+
report.trim() && !path20.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
|
|
5921
|
+
);
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
// src/system/workflow.ts
|
|
5734
5925
|
var METADATA_FILE = "metadata.json";
|
|
5735
5926
|
var SLUG_MAX = 30;
|
|
5736
|
-
var
|
|
5927
|
+
var TASK_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
5737
5928
|
function isValidWorkflowTaskId(taskId) {
|
|
5738
|
-
return
|
|
5929
|
+
return TASK_ID_PATTERN2.test(taskId);
|
|
5739
5930
|
}
|
|
5740
5931
|
function generateTaskId(task, now = /* @__PURE__ */ new Date()) {
|
|
5741
5932
|
const slug = slugify(task).slice(0, SLUG_MAX) || "task";
|
|
@@ -5748,7 +5939,7 @@ function slugify(text) {
|
|
|
5748
5939
|
}
|
|
5749
5940
|
async function createWorkflow(projectRoot, task, mode, options = {}) {
|
|
5750
5941
|
await validateParentTask(projectRoot, mode, options.parentTaskId, true);
|
|
5751
|
-
await
|
|
5942
|
+
await mkdir9(workflowsRoot(projectRoot), { recursive: true });
|
|
5752
5943
|
const taskId = await allocateTaskId(projectRoot, generateTaskId(task));
|
|
5753
5944
|
const dir = workflowDir(projectRoot, taskId);
|
|
5754
5945
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5762,7 +5953,7 @@ async function createWorkflow(projectRoot, task, mode, options = {}) {
|
|
|
5762
5953
|
updatedAt: now,
|
|
5763
5954
|
status: "in_progress",
|
|
5764
5955
|
...options.parentTaskId ? { parentTaskId: options.parentTaskId } : {},
|
|
5765
|
-
...mode === "man" || mode === "manteam" ? { planVersion: 1 } : {}
|
|
5956
|
+
...mode === "man" || mode === "manteam" ? { planVersion: 1, reviewPolicyVersion: 1 } : {}
|
|
5766
5957
|
};
|
|
5767
5958
|
await writeMetadata(dir, meta);
|
|
5768
5959
|
return meta;
|
|
@@ -5771,7 +5962,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
|
|
|
5771
5962
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
5772
5963
|
const taskId = attempt === 0 ? baseTaskId : `${baseTaskId}-${attempt + 1}`;
|
|
5773
5964
|
try {
|
|
5774
|
-
await
|
|
5965
|
+
await mkdir9(workflowDir(projectRoot, taskId));
|
|
5775
5966
|
return taskId;
|
|
5776
5967
|
} catch (err) {
|
|
5777
5968
|
if (isNodeError6(err) && err.code === "EEXIST") {
|
|
@@ -5786,14 +5977,14 @@ async function readWorkflow(projectRoot, taskId) {
|
|
|
5786
5977
|
if (!isValidWorkflowTaskId(taskId)) return null;
|
|
5787
5978
|
const file = metadataPath(projectRoot, taskId);
|
|
5788
5979
|
try {
|
|
5789
|
-
const raw = await
|
|
5980
|
+
const raw = await readFile10(file, "utf-8");
|
|
5790
5981
|
return parseWorkflowMeta(raw, taskId);
|
|
5791
5982
|
} catch {
|
|
5792
5983
|
return null;
|
|
5793
5984
|
}
|
|
5794
5985
|
}
|
|
5795
5986
|
async function updateWorkflow(projectRoot, taskId, patch) {
|
|
5796
|
-
|
|
5987
|
+
assertValidTaskId2(taskId);
|
|
5797
5988
|
const existing = await readWorkflow(projectRoot, taskId);
|
|
5798
5989
|
if (!existing) {
|
|
5799
5990
|
throw new Error(`workflow not found: ${taskId}`);
|
|
@@ -5838,7 +6029,7 @@ async function deleteWorkflow(projectRoot, taskId) {
|
|
|
5838
6029
|
if (!isValidWorkflowTaskId(taskId)) return false;
|
|
5839
6030
|
const dir = workflowDir(projectRoot, taskId);
|
|
5840
6031
|
try {
|
|
5841
|
-
await
|
|
6032
|
+
await stat4(dir);
|
|
5842
6033
|
} catch {
|
|
5843
6034
|
return false;
|
|
5844
6035
|
}
|
|
@@ -5850,19 +6041,19 @@ async function deleteWorkflow(projectRoot, taskId) {
|
|
|
5850
6041
|
return true;
|
|
5851
6042
|
}
|
|
5852
6043
|
function workflowsRoot(projectRoot) {
|
|
5853
|
-
return
|
|
6044
|
+
return path21.join(projectRoot, ".mancode", "workflows");
|
|
5854
6045
|
}
|
|
5855
6046
|
function workflowDir(projectRoot, taskId) {
|
|
5856
|
-
|
|
5857
|
-
return
|
|
6047
|
+
assertValidTaskId2(taskId);
|
|
6048
|
+
return path21.join(workflowsRoot(projectRoot), taskId);
|
|
5858
6049
|
}
|
|
5859
6050
|
function metadataPath(projectRoot, taskId) {
|
|
5860
|
-
return
|
|
6051
|
+
return path21.join(workflowDir(projectRoot, taskId), METADATA_FILE);
|
|
5861
6052
|
}
|
|
5862
6053
|
async function writeMetadata(dir, meta) {
|
|
5863
6054
|
const content = `${JSON.stringify(meta, null, 2)}
|
|
5864
6055
|
`;
|
|
5865
|
-
await
|
|
6056
|
+
await writeFile11(path21.join(dir, METADATA_FILE), content, "utf-8");
|
|
5866
6057
|
}
|
|
5867
6058
|
function parseWorkflowMeta(raw, taskId) {
|
|
5868
6059
|
try {
|
|
@@ -5912,6 +6103,9 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
5912
6103
|
if (updated.parentTaskId !== existing.parentTaskId) {
|
|
5913
6104
|
throw new Error("workflow parent task cannot be changed");
|
|
5914
6105
|
}
|
|
6106
|
+
if (updated.reviewPolicyVersion !== existing.reviewPolicyVersion) {
|
|
6107
|
+
throw new Error("workflow review policy version cannot be changed");
|
|
6108
|
+
}
|
|
5915
6109
|
if (!Number.isInteger(updated.currentStep) || updated.currentStep < 1 || updated.currentStep > maxWorkflowStep(updated.mode)) {
|
|
5916
6110
|
throw new Error(`invalid workflow step: ${updated.currentStep}`);
|
|
5917
6111
|
}
|
|
@@ -5962,6 +6156,12 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
5962
6156
|
if (updated.mode === "mamba" && updated.planVersion !== void 0) {
|
|
5963
6157
|
throw new Error("mamba workflows cannot have a plan version");
|
|
5964
6158
|
}
|
|
6159
|
+
if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1) {
|
|
6160
|
+
throw new Error("invalid workflow review policy version");
|
|
6161
|
+
}
|
|
6162
|
+
if (updated.mode === "mamba" && updated.reviewPolicyVersion !== void 0) {
|
|
6163
|
+
throw new Error("mamba workflows cannot have a review policy version");
|
|
6164
|
+
}
|
|
5965
6165
|
if (updated.planVersion !== void 0 && (!Number.isInteger(updated.planVersion) || updated.planVersion < 1)) {
|
|
5966
6166
|
throw new Error("workflow plan version must be a positive integer");
|
|
5967
6167
|
}
|
|
@@ -5975,6 +6175,9 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
5975
6175
|
if ((isTerminalWorkflowStatus(updated.status) || updated.status === "planned") && (await listActiveMambaChildren(projectRoot, updated.taskId)).length > 0) {
|
|
5976
6176
|
throw new Error("cannot finish workflow with an active mamba child");
|
|
5977
6177
|
}
|
|
6178
|
+
if (updated.status === "completed" && updated.reviewPolicyVersion === 1 && !reviewWasExplicitlySkipped(updated) && !await reviewCanComplete(projectRoot, updated.taskId)) {
|
|
6179
|
+
throw new Error("workflow review is incomplete or still has open blockers");
|
|
6180
|
+
}
|
|
5978
6181
|
}
|
|
5979
6182
|
function canTransition(from, to) {
|
|
5980
6183
|
if (from === to) return true;
|
|
@@ -6002,13 +6205,16 @@ function isWorkflowShape(obj) {
|
|
|
6002
6205
|
return false;
|
|
6003
6206
|
if (obj.planVersion !== void 0 && (!Number.isInteger(obj.planVersion) || obj.planVersion < 1))
|
|
6004
6207
|
return false;
|
|
6208
|
+
if (obj.reviewPolicyVersion !== void 0 && obj.reviewPolicyVersion !== 1)
|
|
6209
|
+
return false;
|
|
6005
6210
|
if (status2 === "blocked") {
|
|
6006
6211
|
if (!obj.blockingReason?.trim()) return false;
|
|
6007
6212
|
} else if (obj.blockingReason !== void 0) {
|
|
6008
6213
|
return false;
|
|
6009
6214
|
}
|
|
6010
6215
|
if (mode === "mamba") {
|
|
6011
|
-
if (status2 === "planned" || obj.planVersion !== void 0
|
|
6216
|
+
if (status2 === "planned" || obj.planVersion !== void 0 || obj.reviewPolicyVersion !== void 0)
|
|
6217
|
+
return false;
|
|
6012
6218
|
if (obj.parentTaskId !== void 0 && !isValidWorkflowTaskId(obj.parentTaskId))
|
|
6013
6219
|
return false;
|
|
6014
6220
|
if (obj.outcome !== void 0 && status2 !== "completed") return false;
|
|
@@ -6018,6 +6224,9 @@ function isWorkflowShape(obj) {
|
|
|
6018
6224
|
}
|
|
6019
6225
|
return true;
|
|
6020
6226
|
}
|
|
6227
|
+
function reviewWasExplicitlySkipped(meta) {
|
|
6228
|
+
return meta.skippedSteps.includes("review") || meta.skippedSteps.includes("film-1") && meta.skippedSteps.includes("film-2");
|
|
6229
|
+
}
|
|
6021
6230
|
async function listActiveMambaChildren(projectRoot, parentTaskId) {
|
|
6022
6231
|
return (await listMambaChildren(projectRoot, parentTaskId)).filter(
|
|
6023
6232
|
(workflow2) => workflow2.status === "in_progress" || workflow2.status === "blocked"
|
|
@@ -6053,7 +6262,7 @@ async function propagateChildStatus(projectRoot, child) {
|
|
|
6053
6262
|
function isNodeError6(err) {
|
|
6054
6263
|
return err instanceof Error && "code" in err;
|
|
6055
6264
|
}
|
|
6056
|
-
function
|
|
6265
|
+
function assertValidTaskId2(taskId) {
|
|
6057
6266
|
if (!isValidWorkflowTaskId(taskId)) {
|
|
6058
6267
|
throw new Error(`invalid workflow taskId: ${taskId}`);
|
|
6059
6268
|
}
|
|
@@ -6064,8 +6273,8 @@ var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
|
|
|
6064
6273
|
var EXIT_OK6 = 0;
|
|
6065
6274
|
var EXIT_NOT_INITIALIZED4 = 1;
|
|
6066
6275
|
var EXIT_CORRUPT_STATE = 2;
|
|
6067
|
-
async function status(rootDir =
|
|
6068
|
-
const stateFile =
|
|
6276
|
+
async function status(rootDir = process8.cwd(), options = {}) {
|
|
6277
|
+
const stateFile = path22.join(rootDir, ".mancode", "state.json");
|
|
6069
6278
|
if (!await pathExists9(stateFile)) {
|
|
6070
6279
|
console.error("\u2717 mancode not initialized.");
|
|
6071
6280
|
console.error(" Run `mancode init` to get started.");
|
|
@@ -6164,19 +6373,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
|
|
|
6164
6373
|
}
|
|
6165
6374
|
async function getProjectName(rootDir) {
|
|
6166
6375
|
try {
|
|
6167
|
-
const raw = await fs7.readFile(
|
|
6376
|
+
const raw = await fs7.readFile(path22.join(rootDir, "package.json"), "utf-8");
|
|
6168
6377
|
const pkg = JSON.parse(raw);
|
|
6169
6378
|
if (pkg.name && typeof pkg.name === "string") {
|
|
6170
6379
|
return pkg.name;
|
|
6171
6380
|
}
|
|
6172
6381
|
} catch {
|
|
6173
6382
|
}
|
|
6174
|
-
return
|
|
6383
|
+
return path22.basename(rootDir);
|
|
6175
6384
|
}
|
|
6176
6385
|
async function readConfig2(rootDir) {
|
|
6177
6386
|
try {
|
|
6178
6387
|
const raw = await fs7.readFile(
|
|
6179
|
-
|
|
6388
|
+
path22.join(rootDir, ".mancode", "config.json"),
|
|
6180
6389
|
"utf-8"
|
|
6181
6390
|
);
|
|
6182
6391
|
return JSON.parse(raw);
|
|
@@ -6203,9 +6412,9 @@ function getEffectiveTeamStatus(state, config, detected) {
|
|
|
6203
6412
|
}
|
|
6204
6413
|
async function checkHooks(rootDir) {
|
|
6205
6414
|
const [sessionStart, userPromptSubmit, registered] = await Promise.all([
|
|
6206
|
-
pathExists9(
|
|
6415
|
+
pathExists9(path22.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
|
|
6207
6416
|
pathExists9(
|
|
6208
|
-
|
|
6417
|
+
path22.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
6209
6418
|
),
|
|
6210
6419
|
isRegistered(rootDir)
|
|
6211
6420
|
]);
|
|
@@ -6214,7 +6423,7 @@ async function checkHooks(rootDir) {
|
|
|
6214
6423
|
async function isRegistered(rootDir) {
|
|
6215
6424
|
try {
|
|
6216
6425
|
const raw = await fs7.readFile(
|
|
6217
|
-
|
|
6426
|
+
path22.join(rootDir, ".claude", "settings.json"),
|
|
6218
6427
|
"utf-8"
|
|
6219
6428
|
);
|
|
6220
6429
|
const settings = JSON.parse(raw);
|
|
@@ -6224,30 +6433,30 @@ async function isRegistered(rootDir) {
|
|
|
6224
6433
|
}
|
|
6225
6434
|
}
|
|
6226
6435
|
function hooksRegistered2(settings) {
|
|
6227
|
-
if (!
|
|
6436
|
+
if (!isRecord8(settings)) return false;
|
|
6228
6437
|
const hooks = settings.hooks;
|
|
6229
|
-
if (!
|
|
6230
|
-
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.
|
|
6438
|
+
if (!isRecord8(hooks)) return false;
|
|
6439
|
+
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
|
|
6231
6440
|
hooks.UserPromptSubmit,
|
|
6232
|
-
".mancode/hooks/user-prompt-submit.
|
|
6441
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
6233
6442
|
);
|
|
6234
6443
|
}
|
|
6235
6444
|
function hasHookCommand2(value, needle) {
|
|
6236
6445
|
if (!Array.isArray(value)) return false;
|
|
6237
6446
|
return value.some((group) => {
|
|
6238
|
-
if (!
|
|
6447
|
+
if (!isRecord8(group) || !Array.isArray(group.hooks)) return false;
|
|
6239
6448
|
return group.hooks.some((hook) => {
|
|
6240
|
-
if (!
|
|
6449
|
+
if (!isRecord8(hook) || typeof hook.command !== "string") return false;
|
|
6241
6450
|
return hook.command.includes(needle);
|
|
6242
6451
|
});
|
|
6243
6452
|
});
|
|
6244
6453
|
}
|
|
6245
6454
|
async function estimateHookInjection(rootDir) {
|
|
6246
|
-
const hookPath =
|
|
6455
|
+
const hookPath = path22.join(
|
|
6247
6456
|
rootDir,
|
|
6248
6457
|
".mancode",
|
|
6249
6458
|
"hooks",
|
|
6250
|
-
"user-prompt-submit.
|
|
6459
|
+
"user-prompt-submit.mjs"
|
|
6251
6460
|
);
|
|
6252
6461
|
if (!await pathExists9(hookPath)) {
|
|
6253
6462
|
return { tokens: 0, cap: 800 };
|
|
@@ -6264,7 +6473,7 @@ async function estimateHookInjection(rootDir) {
|
|
|
6264
6473
|
}
|
|
6265
6474
|
function runHookEstimate(rootDir, hookPath) {
|
|
6266
6475
|
return new Promise((resolve, reject) => {
|
|
6267
|
-
const child = spawn(
|
|
6476
|
+
const child = spawn(process8.execPath, [hookPath], {
|
|
6268
6477
|
cwd: rootDir,
|
|
6269
6478
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6270
6479
|
});
|
|
@@ -6348,9 +6557,9 @@ function printText(r) {
|
|
|
6348
6557
|
console.log("");
|
|
6349
6558
|
if (r.platforms.includes("claude-code")) {
|
|
6350
6559
|
console.log("Hooks:");
|
|
6351
|
-
console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.
|
|
6560
|
+
console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.mjs`);
|
|
6352
6561
|
console.log(
|
|
6353
|
-
` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.
|
|
6562
|
+
` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.mjs`
|
|
6354
6563
|
);
|
|
6355
6564
|
console.log(
|
|
6356
6565
|
` ${r.hooks.registered ? "\u2713" : "\u2717"} registered in .claude/settings.json`
|
|
@@ -6369,7 +6578,7 @@ function formatTeamStatus(team) {
|
|
|
6369
6578
|
function workflowStepMax(mode) {
|
|
6370
6579
|
return maxWorkflowStep(mode);
|
|
6371
6580
|
}
|
|
6372
|
-
function
|
|
6581
|
+
function isRecord8(value) {
|
|
6373
6582
|
return typeof value === "object" && value !== null;
|
|
6374
6583
|
}
|
|
6375
6584
|
async function pathExists9(p) {
|
|
@@ -6382,14 +6591,14 @@ async function pathExists9(p) {
|
|
|
6382
6591
|
}
|
|
6383
6592
|
|
|
6384
6593
|
// src/commands/uninstall.ts
|
|
6385
|
-
import { access as
|
|
6386
|
-
import
|
|
6387
|
-
import
|
|
6594
|
+
import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
|
|
6595
|
+
import path23 from "path";
|
|
6596
|
+
import process9 from "process";
|
|
6388
6597
|
var EXIT_OK7 = 0;
|
|
6389
6598
|
var EXIT_NOT_INITIALIZED5 = 1;
|
|
6390
6599
|
var EXIT_UNSUPPORTED_PLATFORM2 = 2;
|
|
6391
|
-
async function uninstall(rootDir =
|
|
6392
|
-
const stateFile =
|
|
6600
|
+
async function uninstall(rootDir = process9.cwd(), platform, options = {}) {
|
|
6601
|
+
const stateFile = path23.join(rootDir, ".mancode", "state.json");
|
|
6393
6602
|
if (!await pathExists10(stateFile)) {
|
|
6394
6603
|
console.error("\u2717 mancode not initialized.");
|
|
6395
6604
|
console.error(" Run `mancode init` first.");
|
|
@@ -6441,7 +6650,7 @@ async function uninstallAll(rootDir) {
|
|
|
6441
6650
|
await uninstallPlatform(rootDir, p);
|
|
6442
6651
|
}
|
|
6443
6652
|
console.log("\u2713 Removing .mancode/ directory...");
|
|
6444
|
-
await rm5(
|
|
6653
|
+
await rm5(path23.join(rootDir, ".mancode"), {
|
|
6445
6654
|
recursive: true,
|
|
6446
6655
|
force: true
|
|
6447
6656
|
});
|
|
@@ -6451,10 +6660,10 @@ async function uninstallClaudeCode(rootDir) {
|
|
|
6451
6660
|
await cleanClaudeSettings(rootDir);
|
|
6452
6661
|
}
|
|
6453
6662
|
async function cleanClaudeSettings(rootDir) {
|
|
6454
|
-
const settingsPath =
|
|
6663
|
+
const settingsPath = path23.join(rootDir, ".claude", "settings.json");
|
|
6455
6664
|
let content;
|
|
6456
6665
|
try {
|
|
6457
|
-
content = await
|
|
6666
|
+
content = await readFile11(settingsPath, "utf-8");
|
|
6458
6667
|
} catch {
|
|
6459
6668
|
return;
|
|
6460
6669
|
}
|
|
@@ -6465,14 +6674,14 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
6465
6674
|
return;
|
|
6466
6675
|
}
|
|
6467
6676
|
const cleanedHooks = {};
|
|
6468
|
-
if (
|
|
6677
|
+
if (isRecord9(settings.hooks)) {
|
|
6469
6678
|
for (const [event, value] of Object.entries(settings.hooks)) {
|
|
6470
6679
|
const cleaned = cleanClaudeHookValue(value);
|
|
6471
6680
|
if (cleaned !== void 0) cleanedHooks[event] = cleaned;
|
|
6472
6681
|
}
|
|
6473
6682
|
settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
|
|
6474
6683
|
}
|
|
6475
|
-
if (
|
|
6684
|
+
if (isRecord9(settings.skills)) {
|
|
6476
6685
|
const retainedSkills = Object.fromEntries(
|
|
6477
6686
|
Object.entries(settings.skills).filter(
|
|
6478
6687
|
([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
|
|
@@ -6480,7 +6689,7 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
6480
6689
|
);
|
|
6481
6690
|
settings.skills = Object.keys(retainedSkills).length > 0 ? retainedSkills : void 0;
|
|
6482
6691
|
}
|
|
6483
|
-
await
|
|
6692
|
+
await writeFile12(
|
|
6484
6693
|
settingsPath,
|
|
6485
6694
|
`${JSON.stringify(settings, null, 2)}
|
|
6486
6695
|
`,
|
|
@@ -6492,7 +6701,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6492
6701
|
const entries2 = value.flatMap(cleanClaudeHookEntry);
|
|
6493
6702
|
return entries2.length > 0 ? entries2 : void 0;
|
|
6494
6703
|
}
|
|
6495
|
-
if (!
|
|
6704
|
+
if (!isRecord9(value)) return value;
|
|
6496
6705
|
if (Array.isArray(value.hooks) || typeof value.command === "string") {
|
|
6497
6706
|
const entries2 = cleanClaudeHookEntry(value);
|
|
6498
6707
|
return entries2.length > 0 ? entries2 : void 0;
|
|
@@ -6506,7 +6715,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6506
6715
|
}
|
|
6507
6716
|
function cleanClaudeHookEntry(entry) {
|
|
6508
6717
|
if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
|
|
6509
|
-
if (!
|
|
6718
|
+
if (!isRecord9(entry)) return [entry];
|
|
6510
6719
|
if (Array.isArray(entry.hooks)) {
|
|
6511
6720
|
const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
|
|
6512
6721
|
return hooks.length > 0 ? [{ ...entry, hooks }] : [];
|
|
@@ -6514,11 +6723,11 @@ function cleanClaudeHookEntry(entry) {
|
|
|
6514
6723
|
return isMancodeHookEntry(entry) ? [] : [entry];
|
|
6515
6724
|
}
|
|
6516
6725
|
function isMancodeHookEntry(value) {
|
|
6517
|
-
return
|
|
6726
|
+
return isRecord9(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
|
|
6518
6727
|
}
|
|
6519
6728
|
function containsLegacyMancodeHookPath2(value) {
|
|
6520
6729
|
if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
|
|
6521
|
-
if (!
|
|
6730
|
+
if (!isRecord9(value)) return false;
|
|
6522
6731
|
if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
|
|
6523
6732
|
return true;
|
|
6524
6733
|
}
|
|
@@ -6529,12 +6738,12 @@ async function uninstallCursor(rootDir) {
|
|
|
6529
6738
|
await removeCursorCommands(rootDir);
|
|
6530
6739
|
}
|
|
6531
6740
|
async function uninstallCodex(rootDir) {
|
|
6532
|
-
const agentsPath =
|
|
6741
|
+
const agentsPath = path23.join(rootDir, "AGENTS.md");
|
|
6533
6742
|
try {
|
|
6534
|
-
const content = await
|
|
6743
|
+
const content = await readFile11(agentsPath, "utf-8");
|
|
6535
6744
|
const cleaned = removeManagedBlock(content);
|
|
6536
6745
|
if (cleaned.trim()) {
|
|
6537
|
-
await
|
|
6746
|
+
await writeFile12(agentsPath, `${cleaned}
|
|
6538
6747
|
`, "utf-8");
|
|
6539
6748
|
} else {
|
|
6540
6749
|
await rm5(agentsPath, { force: true });
|
|
@@ -6544,16 +6753,16 @@ async function uninstallCodex(rootDir) {
|
|
|
6544
6753
|
await removeCodexSkills(rootDir);
|
|
6545
6754
|
}
|
|
6546
6755
|
async function uninstallCopilot(rootDir) {
|
|
6547
|
-
const instructionsPath =
|
|
6756
|
+
const instructionsPath = path23.join(
|
|
6548
6757
|
rootDir,
|
|
6549
6758
|
".github",
|
|
6550
6759
|
"copilot-instructions.md"
|
|
6551
6760
|
);
|
|
6552
6761
|
try {
|
|
6553
|
-
const content = await
|
|
6762
|
+
const content = await readFile11(instructionsPath, "utf-8");
|
|
6554
6763
|
const cleaned = removeManagedBlock(content);
|
|
6555
6764
|
if (cleaned.trim()) {
|
|
6556
|
-
await
|
|
6765
|
+
await writeFile12(instructionsPath, `${cleaned}
|
|
6557
6766
|
`, "utf-8");
|
|
6558
6767
|
} else {
|
|
6559
6768
|
await rm5(instructionsPath, { force: true });
|
|
@@ -6563,16 +6772,16 @@ async function uninstallCopilot(rootDir) {
|
|
|
6563
6772
|
await removeCopilotPrompts(rootDir);
|
|
6564
6773
|
}
|
|
6565
6774
|
async function uninstallZcode(rootDir) {
|
|
6566
|
-
const agentsPath =
|
|
6775
|
+
const agentsPath = path23.join(rootDir, "AGENTS.md");
|
|
6567
6776
|
try {
|
|
6568
|
-
const content = await
|
|
6777
|
+
const content = await readFile11(agentsPath, "utf-8");
|
|
6569
6778
|
const cleaned = removeManagedBlock(
|
|
6570
6779
|
content,
|
|
6571
6780
|
ZCODE_MANCODE_START_MARKER,
|
|
6572
6781
|
ZCODE_MANCODE_END_MARKER
|
|
6573
6782
|
);
|
|
6574
6783
|
if (cleaned.trim()) {
|
|
6575
|
-
await
|
|
6784
|
+
await writeFile12(agentsPath, `${cleaned}
|
|
6576
6785
|
`, "utf-8");
|
|
6577
6786
|
} else {
|
|
6578
6787
|
await rm5(agentsPath, { force: true });
|
|
@@ -6582,13 +6791,13 @@ async function uninstallZcode(rootDir) {
|
|
|
6582
6791
|
await removeZcodeSkills(rootDir);
|
|
6583
6792
|
}
|
|
6584
6793
|
async function removeFromConfig(rootDir, platform) {
|
|
6585
|
-
const configPath =
|
|
6794
|
+
const configPath = path23.join(rootDir, ".mancode", "config.json");
|
|
6586
6795
|
try {
|
|
6587
|
-
const raw = await
|
|
6796
|
+
const raw = await readFile11(configPath, "utf-8");
|
|
6588
6797
|
const config = JSON.parse(raw);
|
|
6589
6798
|
if (Array.isArray(config.platforms)) {
|
|
6590
6799
|
config.platforms = config.platforms.filter((p) => p !== platform);
|
|
6591
|
-
if (
|
|
6800
|
+
if (isRecord9(config.platformOptions)) {
|
|
6592
6801
|
const platformOptions = Object.fromEntries(
|
|
6593
6802
|
Object.entries(config.platformOptions).filter(
|
|
6594
6803
|
([name]) => name !== platform
|
|
@@ -6596,7 +6805,7 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
6596
6805
|
);
|
|
6597
6806
|
config.platformOptions = Object.keys(platformOptions).length > 0 ? platformOptions : void 0;
|
|
6598
6807
|
}
|
|
6599
|
-
await
|
|
6808
|
+
await writeFile12(
|
|
6600
6809
|
configPath,
|
|
6601
6810
|
`${JSON.stringify(config, null, 2)}
|
|
6602
6811
|
`,
|
|
@@ -6606,12 +6815,12 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
6606
6815
|
} catch {
|
|
6607
6816
|
}
|
|
6608
6817
|
}
|
|
6609
|
-
function
|
|
6818
|
+
function isRecord9(value) {
|
|
6610
6819
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6611
6820
|
}
|
|
6612
6821
|
async function pathExists10(p) {
|
|
6613
6822
|
try {
|
|
6614
|
-
await
|
|
6823
|
+
await access5(p);
|
|
6615
6824
|
return true;
|
|
6616
6825
|
} catch {
|
|
6617
6826
|
return false;
|
|
@@ -6619,23 +6828,23 @@ async function pathExists10(p) {
|
|
|
6619
6828
|
}
|
|
6620
6829
|
|
|
6621
6830
|
// src/commands/version.ts
|
|
6622
|
-
import
|
|
6831
|
+
import process10 from "process";
|
|
6623
6832
|
function version() {
|
|
6624
|
-
const nodeVersion =
|
|
6625
|
-
const platform = `${
|
|
6833
|
+
const nodeVersion = process10.version;
|
|
6834
|
+
const platform = `${process10.platform}/${process10.arch}`;
|
|
6626
6835
|
console.log(`mancode/${VERSION}`);
|
|
6627
6836
|
console.log(`node/${nodeVersion.replace("v", "")}`);
|
|
6628
6837
|
console.log(platform);
|
|
6629
6838
|
}
|
|
6630
6839
|
|
|
6631
6840
|
// src/commands/workflow.ts
|
|
6632
|
-
import { access as
|
|
6633
|
-
import
|
|
6841
|
+
import { access as access6 } from "fs/promises";
|
|
6842
|
+
import path24 from "path";
|
|
6634
6843
|
var EXIT_OK8 = 0;
|
|
6635
6844
|
var EXIT_NOT_INITIALIZED6 = 1;
|
|
6636
6845
|
var EXIT_INVALID_ARG2 = 2;
|
|
6637
6846
|
async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
6638
|
-
if (!await pathExists11(
|
|
6847
|
+
if (!await pathExists11(path24.join(rootDir, ".mancode", "state.json"))) {
|
|
6639
6848
|
if (options.json) {
|
|
6640
6849
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
6641
6850
|
} else {
|
|
@@ -6653,6 +6862,8 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
|
6653
6862
|
return workflowList(rootDir, options);
|
|
6654
6863
|
case "show":
|
|
6655
6864
|
return workflowShow(rootDir, args[0], options);
|
|
6865
|
+
case "review":
|
|
6866
|
+
return workflowReview(rootDir, args, options);
|
|
6656
6867
|
case "clean":
|
|
6657
6868
|
return workflowClean(rootDir, options);
|
|
6658
6869
|
default:
|
|
@@ -6667,12 +6878,109 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
|
6667
6878
|
} else {
|
|
6668
6879
|
console.error(`\u2717 Invalid workflow subcommand: ${subcommand}`);
|
|
6669
6880
|
console.error(
|
|
6670
|
-
" Use: create <man|mamba|manteam> <task> | update <taskId> | list | show <taskId> | clean"
|
|
6881
|
+
" Use: create <man|mamba|manteam> <task> | update <taskId> | review <taskId> <init|complete|remediate|show> | list | show <taskId> | clean"
|
|
6671
6882
|
);
|
|
6672
6883
|
}
|
|
6673
6884
|
return EXIT_INVALID_ARG2;
|
|
6674
6885
|
}
|
|
6675
6886
|
}
|
|
6887
|
+
async function workflowReview(rootDir, args, options) {
|
|
6888
|
+
const taskId = args[0];
|
|
6889
|
+
const action = args[1];
|
|
6890
|
+
if (!taskId || !isValidWorkflowTaskId(taskId)) {
|
|
6891
|
+
return invalidArg(options, `invalid taskId: ${taskId ?? ""}`);
|
|
6892
|
+
}
|
|
6893
|
+
const meta = await readWorkflow(rootDir, taskId);
|
|
6894
|
+
if (!meta) return invalidArg(options, `workflow not found: ${taskId}`);
|
|
6895
|
+
if (meta.mode !== "man" && meta.mode !== "manteam") {
|
|
6896
|
+
return invalidArg(options, "review is only valid for man or manteam");
|
|
6897
|
+
}
|
|
6898
|
+
if (action === "show") {
|
|
6899
|
+
const ledger = await readReviewLedger(rootDir, taskId);
|
|
6900
|
+
if (!ledger)
|
|
6901
|
+
return invalidArg(options, `review not initialized: ${taskId}`);
|
|
6902
|
+
outputReviewLedger(ledger, options);
|
|
6903
|
+
return EXIT_OK8;
|
|
6904
|
+
}
|
|
6905
|
+
if (meta.status !== "in_progress" || meta.currentStep < 6) {
|
|
6906
|
+
return invalidArg(
|
|
6907
|
+
options,
|
|
6908
|
+
"review requires an in_progress workflow at step 6 or later"
|
|
6909
|
+
);
|
|
6910
|
+
}
|
|
6911
|
+
try {
|
|
6912
|
+
let ledger;
|
|
6913
|
+
if (action === "init") {
|
|
6914
|
+
if (!isReviewDepth(options.reviewDepth)) {
|
|
6915
|
+
return invalidArg(
|
|
6916
|
+
options,
|
|
6917
|
+
`invalid --review-depth: ${options.reviewDepth ?? ""}`
|
|
6918
|
+
);
|
|
6919
|
+
}
|
|
6920
|
+
if (options.reviewDomain !== void 0 && !isReviewDomain(options.reviewDomain)) {
|
|
6921
|
+
return invalidArg(
|
|
6922
|
+
options,
|
|
6923
|
+
`invalid --review-domain: ${options.reviewDomain}`
|
|
6924
|
+
);
|
|
6925
|
+
}
|
|
6926
|
+
ledger = await initializeReview(
|
|
6927
|
+
rootDir,
|
|
6928
|
+
taskId,
|
|
6929
|
+
options.reviewDepth,
|
|
6930
|
+
isReviewDomain(options.reviewDomain) ? options.reviewDomain : void 0
|
|
6931
|
+
);
|
|
6932
|
+
} else if (action === "complete") {
|
|
6933
|
+
if (!isReviewDomain(options.reviewDomain) || !options.report) {
|
|
6934
|
+
return invalidArg(
|
|
6935
|
+
options,
|
|
6936
|
+
"review complete requires --review-domain and --report"
|
|
6937
|
+
);
|
|
6938
|
+
}
|
|
6939
|
+
ledger = await completeReviewDomain(
|
|
6940
|
+
rootDir,
|
|
6941
|
+
taskId,
|
|
6942
|
+
options.reviewDomain,
|
|
6943
|
+
options.report,
|
|
6944
|
+
parseCsv(options.blockers)
|
|
6945
|
+
);
|
|
6946
|
+
} else if (action === "remediate") {
|
|
6947
|
+
ledger = await remediateReviewBlockers(
|
|
6948
|
+
rootDir,
|
|
6949
|
+
taskId,
|
|
6950
|
+
parseCsv(options.resolved)
|
|
6951
|
+
);
|
|
6952
|
+
} else {
|
|
6953
|
+
return invalidArg(
|
|
6954
|
+
options,
|
|
6955
|
+
`invalid review action: ${action ?? ""}`,
|
|
6956
|
+
"Use: mancode workflow review <taskId> <init|complete|remediate|show>"
|
|
6957
|
+
);
|
|
6958
|
+
}
|
|
6959
|
+
outputReviewLedger(ledger, options);
|
|
6960
|
+
return EXIT_OK8;
|
|
6961
|
+
} catch (error) {
|
|
6962
|
+
return invalidArg(
|
|
6963
|
+
options,
|
|
6964
|
+
error instanceof Error ? error.message : "unable to update review"
|
|
6965
|
+
);
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
function outputReviewLedger(ledger, options) {
|
|
6969
|
+
if (options.json) {
|
|
6970
|
+
console.log(JSON.stringify(ledger, null, 2));
|
|
6971
|
+
return;
|
|
6972
|
+
}
|
|
6973
|
+
if (!ledger) return;
|
|
6974
|
+
const openBlockers = ledger.blockers.filter(
|
|
6975
|
+
(blocker) => blocker.status === "open"
|
|
6976
|
+
);
|
|
6977
|
+
console.log(
|
|
6978
|
+
`Review: ${ledger.depth}; domains ${ledger.completedDomains.length}/${ledger.requiredDomains.length}; open blockers ${openBlockers.length}; remediation ${ledger.remediationRounds}/1`
|
|
6979
|
+
);
|
|
6980
|
+
}
|
|
6981
|
+
function parseCsv(value) {
|
|
6982
|
+
return (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
6983
|
+
}
|
|
6676
6984
|
async function workflowCreate(rootDir, args, options) {
|
|
6677
6985
|
const mode = args[0];
|
|
6678
6986
|
const task = args.slice(1).join(" ").trim();
|
|
@@ -7017,7 +7325,7 @@ function ago(iso) {
|
|
|
7017
7325
|
}
|
|
7018
7326
|
async function pathExists11(p) {
|
|
7019
7327
|
try {
|
|
7020
|
-
await
|
|
7328
|
+
await access6(p);
|
|
7021
7329
|
return true;
|
|
7022
7330
|
} catch {
|
|
7023
7331
|
return false;
|
|
@@ -7057,7 +7365,13 @@ program.command("uninstall [platform]").description("Remove platform adapter or
|
|
|
7057
7365
|
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
7366
|
"--parent-task <taskId>",
|
|
7059
7367
|
"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("--
|
|
7368
|
+
).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(
|
|
7369
|
+
"--report <path>",
|
|
7370
|
+
"Relative Markdown report path for a review domain"
|
|
7371
|
+
).option("--blockers <ids>", "Comma-separated blocker ids found by a review").option(
|
|
7372
|
+
"--resolved <ids>",
|
|
7373
|
+
"Comma-separated blocker ids resolved in remediation"
|
|
7374
|
+
).option("--json", "Output as JSON (for scripts)").action(async (subcommand, args, options) => {
|
|
7061
7375
|
const code = await workflow(process.cwd(), subcommand, args ?? [], options);
|
|
7062
7376
|
process.exitCode = code;
|
|
7063
7377
|
});
|