mancode 0.3.2 → 0.3.4
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 +51 -16
- package/README.zh-CN.md +45 -13
- package/dist/cli.js +1606 -872
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
import { program } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
|
-
import { promises as
|
|
11
|
-
import
|
|
12
|
-
import
|
|
10
|
+
import { promises as fs3 } from "fs";
|
|
11
|
+
import os from "os";
|
|
12
|
+
import path14 from "path";
|
|
13
|
+
import process5 from "process";
|
|
13
14
|
|
|
14
15
|
// src/installers/claude-code.ts
|
|
15
16
|
import {
|
|
@@ -510,215 +511,229 @@ function yamlString(s) {
|
|
|
510
511
|
}
|
|
511
512
|
|
|
512
513
|
// src/templates/inline.ts
|
|
513
|
-
var SESSION_START_HOOK = `#!/bin/
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
514
|
+
var SESSION_START_HOOK = String.raw`#!/usr/bin/env node
|
|
515
|
+
// .mancode/hooks/session-start.mjs
|
|
516
|
+
// mancode SessionStart hook - cross-platform project context
|
|
517
|
+
import { readFileSync } from 'node:fs';
|
|
518
|
+
import path from 'node:path';
|
|
519
|
+
import { fileURLToPath } from 'node:url';
|
|
520
|
+
|
|
521
|
+
const projectRoot = path.resolve(
|
|
522
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
523
|
+
'../..',
|
|
524
|
+
);
|
|
525
|
+
const state = readJson(path.join(projectRoot, '.mancode', 'state.json'));
|
|
526
|
+
const profile =
|
|
527
|
+
readJson(path.join(projectRoot, '.mancode', 'project-profile.json')) || {};
|
|
528
|
+
|
|
529
|
+
if (!state) {
|
|
530
|
+
console.log('ℹ️ mancode 未初始化。运行 mancode init 开始。');
|
|
531
|
+
process.exit(0);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const mode = text(state.currentMode) || 'solo';
|
|
535
|
+
const techStack = sanitize(state.techStack);
|
|
536
|
+
const uiLibrary = sanitize(state.uiLibrary);
|
|
537
|
+
const output = [
|
|
538
|
+
'mancode_mode: ' + mode,
|
|
539
|
+
'project_type: ' + techStack,
|
|
540
|
+
'ui_library: ' + uiLibrary,
|
|
541
|
+
'',
|
|
542
|
+
'## mancode · ' + mode + ' mode',
|
|
543
|
+
'',
|
|
544
|
+
'你正在使用 mancode ' + mode + ' 模式。',
|
|
545
|
+
'',
|
|
546
|
+
'### 核心原则',
|
|
547
|
+
'1. **优先复用项目已有代码**',
|
|
548
|
+
' - 检查已检测到的源码目录和已有类似实现',
|
|
549
|
+
' - 复用现有组件、函数、样式',
|
|
550
|
+
'',
|
|
551
|
+
];
|
|
552
|
+
|
|
553
|
+
if (profile.uiAssets === 'detected') {
|
|
554
|
+
output.push(
|
|
555
|
+
'2. **应用项目审美 token**(仅在项目 profile 确认有 UI 资产且任务涉及 UI 时)',
|
|
556
|
+
' - UI library: ' + uiLibrary,
|
|
557
|
+
' - 使用项目已有的设计 token',
|
|
558
|
+
);
|
|
559
|
+
} else {
|
|
560
|
+
output.push(
|
|
561
|
+
'2. **按项目能力工作**',
|
|
562
|
+
' - 不假定存在 UI、浏览器或特定技术栈',
|
|
563
|
+
' - 先读取 project-profile 与项目现有验证方式',
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
output.push(
|
|
568
|
+
'',
|
|
569
|
+
'3. **最小改动**',
|
|
570
|
+
' - 只改用户要求的部分',
|
|
571
|
+
' - 不重构无关代码',
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
if (state.teamModeAutoDetected === true && mode === 'solo') {
|
|
575
|
+
output.push(
|
|
576
|
+
'',
|
|
577
|
+
'### 团队协作提醒',
|
|
578
|
+
'检测到团队项目(contributors: ' +
|
|
579
|
+
(Number.isFinite(state.contributors) ? state.contributors : 2) +
|
|
580
|
+
')。',
|
|
581
|
+
'- 涉及多人协作、交接、PR、共享模块时,优先使用 /manteam <task>。',
|
|
582
|
+
'- 只做个人小改动时,可以继续 solo;需要退出流程用 /mansolo。',
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
console.log(output.join('\n'));
|
|
587
|
+
|
|
588
|
+
function readJson(filePath) {
|
|
589
|
+
try {
|
|
590
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function text(value) {
|
|
597
|
+
return typeof value === 'string' ? value : '';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function sanitize(value) {
|
|
601
|
+
return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
|
|
602
|
+
}
|
|
599
603
|
`;
|
|
600
|
-
var USER_PROMPT_SUBMIT_HOOK = `#!/bin/
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
604
|
+
var USER_PROMPT_SUBMIT_HOOK = String.raw`#!/usr/bin/env node
|
|
605
|
+
// .mancode/hooks/user-prompt-submit.mjs
|
|
606
|
+
// mancode UserPromptSubmit hook - cross-platform prompt and style context
|
|
607
|
+
import { readFileSync } from 'node:fs';
|
|
608
|
+
import path from 'node:path';
|
|
609
|
+
import { fileURLToPath } from 'node:url';
|
|
610
|
+
|
|
611
|
+
const projectRoot = path.resolve(
|
|
612
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
613
|
+
'../..',
|
|
614
|
+
);
|
|
615
|
+
const mancodeDir = path.join(projectRoot, '.mancode');
|
|
616
|
+
const state = readJson(path.join(mancodeDir, 'state.json')) || {};
|
|
617
|
+
const profile = readJson(path.join(mancodeDir, 'project-profile.json')) || {};
|
|
618
|
+
const mode = typeof state.currentMode === 'string' ? state.currentMode : '';
|
|
619
|
+
const output = [];
|
|
620
|
+
|
|
621
|
+
if (mode === 'solo') {
|
|
622
|
+
output.push(
|
|
623
|
+
'## 动手前,先想六个问题:',
|
|
624
|
+
'',
|
|
625
|
+
'1. **为什么做?**',
|
|
626
|
+
' - 这个改动解决什么问题?',
|
|
627
|
+
'',
|
|
628
|
+
'2. **已经有什么?**',
|
|
629
|
+
' - 项目里有没有类似的实现可以复用?',
|
|
630
|
+
'',
|
|
631
|
+
'3. **最少改多少?**',
|
|
632
|
+
' - 能用一行解决吗?能复用现有代码吗?',
|
|
633
|
+
'',
|
|
634
|
+
'4. **能不能不拆新系统?**',
|
|
635
|
+
' - 不新建文件或模块能完成吗?',
|
|
636
|
+
'',
|
|
637
|
+
'5. **非平凡逻辑怎样最小运行验证?**',
|
|
638
|
+
'',
|
|
639
|
+
'6. **有什么没把握的?**',
|
|
640
|
+
' - 先自行查代码或文档,最多 2 次工具调用;仍不确定再问用户。',
|
|
641
|
+
'',
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const rawInput = readFileSync(0, 'utf8');
|
|
646
|
+
const userPrompt = readPrompt(rawInput);
|
|
647
|
+
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;
|
|
648
|
+
|
|
649
|
+
if (mode === 'solo' && planningPattern.test(userPrompt)) {
|
|
650
|
+
output.push(
|
|
651
|
+
'## mancode 自动路由',
|
|
652
|
+
'',
|
|
653
|
+
'这个请求是规划/调研类任务。不要直接进入 solo 实施。',
|
|
654
|
+
"必须先调用 Skill tool,skill='man',把用户原始请求作为 task,执行 Scout 调研、澄清和 Plan Coach plan。",
|
|
655
|
+
'用户只要计划时,在 Step 4 选择“只要计划”;不要切到另一个命令。',
|
|
656
|
+
'',
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
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;
|
|
661
|
+
|
|
662
|
+
if (profile.uiAssets === 'detected' && uiPattern.test(userPrompt)) {
|
|
663
|
+
appendAestheticSummary(
|
|
664
|
+
output,
|
|
665
|
+
readJson(path.join(mancodeDir, 'aesthetics', 'style-tokens.json')),
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (output.length > 0) console.log(output.join('\n'));
|
|
670
|
+
|
|
671
|
+
function appendAestheticSummary(lines, tokens) {
|
|
672
|
+
if (!tokens || tokens.matchLevel !== 'high') return;
|
|
673
|
+
|
|
674
|
+
const colors = safeEntries(tokens.colors, 8).map(
|
|
675
|
+
([key, value]) => key + '=' + String(value),
|
|
676
|
+
);
|
|
677
|
+
const fonts = safeEntries(tokens.fonts, 4)
|
|
678
|
+
.filter(([, value]) => Array.isArray(value) && value.length > 0)
|
|
679
|
+
.map(([key, value]) => key + '=' + String(value[0]));
|
|
680
|
+
const components = Array.isArray(tokens.components)
|
|
681
|
+
? tokens.components
|
|
682
|
+
.filter(
|
|
683
|
+
(value) =>
|
|
684
|
+
typeof value === 'string' && /^[A-Z][A-Za-z0-9]{0,79}$/.test(value),
|
|
685
|
+
)
|
|
686
|
+
.slice(0, 8)
|
|
687
|
+
: [];
|
|
688
|
+
const cssVariables = safeEntries(tokens.cssVariables, 8).map(
|
|
689
|
+
([key, value]) => '--' + key + '=' + String(value),
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
lines.push('## 审美 token 摘要');
|
|
693
|
+
appendValue(lines, 'UI', tokens.uiLibrary);
|
|
694
|
+
appendValue(lines, 'Dark', tokens.darkMode);
|
|
695
|
+
appendValue(lines, 'Match', tokens.matchLevel);
|
|
696
|
+
appendValue(lines, 'Colors (前 8)', colors.join(', '));
|
|
697
|
+
appendValue(lines, 'Fonts (前 4)', fonts.join(', '));
|
|
698
|
+
appendValue(lines, 'Components (前 8)', components.join(', '));
|
|
699
|
+
appendValue(lines, 'CSS variables (前 8)', cssVariables.join(', '));
|
|
700
|
+
lines.push('完整 token: .mancode/aesthetics/style-tokens.json', '');
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function safeEntries(value, limit) {
|
|
704
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return [];
|
|
705
|
+
return Object.entries(value)
|
|
706
|
+
.filter(([key]) => /^[A-Za-z0-9_-]{1,80}$/.test(key))
|
|
707
|
+
.slice(0, limit);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function appendValue(lines, label, value) {
|
|
711
|
+
const clean = sanitize(value);
|
|
712
|
+
if (clean) lines.push(label + ': ' + clean);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function readPrompt(input) {
|
|
716
|
+
try {
|
|
717
|
+
const parsed = JSON.parse(input);
|
|
718
|
+
return typeof parsed.prompt === 'string' && parsed.prompt
|
|
719
|
+
? parsed.prompt
|
|
720
|
+
: input;
|
|
721
|
+
} catch {
|
|
722
|
+
return input;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function readJson(filePath) {
|
|
727
|
+
try {
|
|
728
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
729
|
+
} catch {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function sanitize(value) {
|
|
735
|
+
return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
|
|
736
|
+
}
|
|
722
737
|
`;
|
|
723
738
|
var SOLO_SKILL = `# mancode \xB7 solo mode
|
|
724
739
|
|
|
@@ -1247,7 +1262,7 @@ ${spec.body}
|
|
|
1247
1262
|
}
|
|
1248
1263
|
|
|
1249
1264
|
// src/installers/common.ts
|
|
1250
|
-
import {
|
|
1265
|
+
import { mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
1251
1266
|
import path2 from "path";
|
|
1252
1267
|
|
|
1253
1268
|
// src/system/team-memory.ts
|
|
@@ -1371,6 +1386,7 @@ var DEFAULT_CONFIG = {
|
|
|
1371
1386
|
platforms: ["claude-code"],
|
|
1372
1387
|
cliCommand: "mancode",
|
|
1373
1388
|
cliArgs: [],
|
|
1389
|
+
teamMode: "auto",
|
|
1374
1390
|
forceTeamMode: false,
|
|
1375
1391
|
defaultStyle: null,
|
|
1376
1392
|
hooks: {
|
|
@@ -1437,12 +1453,10 @@ async function readTextIfExists(filePath) {
|
|
|
1437
1453
|
}
|
|
1438
1454
|
}
|
|
1439
1455
|
async function installHooks(hooksDir) {
|
|
1440
|
-
const sessionStartDst = path2.join(hooksDir, "session-start.
|
|
1456
|
+
const sessionStartDst = path2.join(hooksDir, "session-start.mjs");
|
|
1441
1457
|
await writeFile2(sessionStartDst, SESSION_START_HOOK, "utf-8");
|
|
1442
|
-
|
|
1443
|
-
const userPromptDst = path2.join(hooksDir, "user-prompt-submit.sh");
|
|
1458
|
+
const userPromptDst = path2.join(hooksDir, "user-prompt-submit.mjs");
|
|
1444
1459
|
await writeFile2(userPromptDst, USER_PROMPT_SUBMIT_HOOK, "utf-8");
|
|
1445
|
-
await chmod(userPromptDst, 493);
|
|
1446
1460
|
}
|
|
1447
1461
|
async function pathExists(p) {
|
|
1448
1462
|
try {
|
|
@@ -1460,12 +1474,18 @@ function isNodeError(err) {
|
|
|
1460
1474
|
var CLAUDE_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:claude-skill. Do not edit this marker. -->";
|
|
1461
1475
|
var CLAUDE_AGENT_MANAGED_MARKER = "<!-- Managed by mancode:claude-agent. Do not edit this marker. -->";
|
|
1462
1476
|
var MANCODE_HOOK_COMMANDS = [
|
|
1477
|
+
'node ".mancode/hooks/session-start.mjs"',
|
|
1478
|
+
'node ".mancode/hooks/user-prompt-submit.mjs"'
|
|
1479
|
+
];
|
|
1480
|
+
var LEGACY_MANCODE_HOOK_COMMANDS = [
|
|
1463
1481
|
"bash .mancode/hooks/session-start.sh",
|
|
1464
1482
|
"bash .mancode/hooks/user-prompt-submit.sh"
|
|
1465
1483
|
];
|
|
1466
1484
|
function isGeneratedMancodeHookCommand(command) {
|
|
1467
1485
|
const normalized = command.trim();
|
|
1468
|
-
return MANCODE_HOOK_COMMANDS.some(
|
|
1486
|
+
return [...MANCODE_HOOK_COMMANDS, ...LEGACY_MANCODE_HOOK_COMMANDS].some(
|
|
1487
|
+
(item) => item === normalized
|
|
1488
|
+
);
|
|
1469
1489
|
}
|
|
1470
1490
|
var LEGACY_CLAUDE_SKILL_SETTINGS = {
|
|
1471
1491
|
solo: ".claude/skills/mancode-solo.md",
|
|
@@ -1494,6 +1514,14 @@ async function installClaudeCode(projectRoot, options) {
|
|
|
1494
1514
|
await installAgents(path3.join(claudeDir, "agents"), force);
|
|
1495
1515
|
}
|
|
1496
1516
|
await updateClaudeSettings(claudeDir, settings);
|
|
1517
|
+
await removeLegacyHookFiles(projectRoot);
|
|
1518
|
+
}
|
|
1519
|
+
async function removeLegacyHookFiles(projectRoot) {
|
|
1520
|
+
const hooksDir = path3.join(projectRoot, ".mancode", "hooks");
|
|
1521
|
+
await Promise.all([
|
|
1522
|
+
rm(path3.join(hooksDir, "session-start.sh"), { force: true }),
|
|
1523
|
+
rm(path3.join(hooksDir, "user-prompt-submit.sh"), { force: true })
|
|
1524
|
+
]);
|
|
1497
1525
|
}
|
|
1498
1526
|
async function installSoloSkill(skillsDir, force) {
|
|
1499
1527
|
await installProjectSkill(
|
|
@@ -1747,100 +1775,10 @@ function isNodeError2(err) {
|
|
|
1747
1775
|
return err instanceof Error && "code" in err;
|
|
1748
1776
|
}
|
|
1749
1777
|
|
|
1750
|
-
// src/installers/
|
|
1751
|
-
import { writeFile as writeFile5 } from "fs/promises";
|
|
1778
|
+
// src/installers/cursor.ts
|
|
1779
|
+
import { mkdir as mkdir5, readFile as readFile6, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
1752
1780
|
import path6 from "path";
|
|
1753
1781
|
|
|
1754
|
-
// src/installers/managed-block.ts
|
|
1755
|
-
var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
|
|
1756
|
-
var DEFAULT_MANCODE_END_MARKER = "<!-- mancode:end -->";
|
|
1757
|
-
function removeManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
1758
|
-
const start = findMarkerLine(existing, startMarker);
|
|
1759
|
-
const end = findMarkerLine(existing, endMarker);
|
|
1760
|
-
if (start === null && end === null) return existing;
|
|
1761
|
-
if (start === null || end === null) return existing;
|
|
1762
|
-
if (end.start < start.start) return existing;
|
|
1763
|
-
const before = existing.slice(0, start.start);
|
|
1764
|
-
const after = existing.slice(end.end);
|
|
1765
|
-
const merged = `${before}${after}`;
|
|
1766
|
-
return cleanUpOrphanedNewlines(merged);
|
|
1767
|
-
}
|
|
1768
|
-
function hasManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
1769
|
-
const start = findMarkerLine(existing, startMarker);
|
|
1770
|
-
const end = findMarkerLine(existing, endMarker);
|
|
1771
|
-
return start !== null && end !== null && end.start > start.start;
|
|
1772
|
-
}
|
|
1773
|
-
function cleanUpOrphanedNewlines(content) {
|
|
1774
|
-
const trimmed = content.replace(/\n{3,}/gu, "\n\n").replace(/\n+$/u, "\n");
|
|
1775
|
-
return trimmed || "";
|
|
1776
|
-
}
|
|
1777
|
-
function replaceManagedBlock(existing, block, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
1778
|
-
const normalizedBlock = normalizeManagedBlock(block, startMarker, endMarker);
|
|
1779
|
-
const start = findMarkerLine(existing, startMarker);
|
|
1780
|
-
const end = findMarkerLine(existing, endMarker);
|
|
1781
|
-
if (start === null !== (end === null)) {
|
|
1782
|
-
throw new Error("managed block is malformed: missing start or end marker");
|
|
1783
|
-
}
|
|
1784
|
-
if (start === null && end === null) {
|
|
1785
|
-
const trimmedExisting = trimTrailingNewlines(existing);
|
|
1786
|
-
if (!trimmedExisting) return `${normalizedBlock}
|
|
1787
|
-
`;
|
|
1788
|
-
return `${trimmedExisting}
|
|
1789
|
-
|
|
1790
|
-
${normalizedBlock}
|
|
1791
|
-
`;
|
|
1792
|
-
}
|
|
1793
|
-
if (!start || !end) {
|
|
1794
|
-
throw new Error("managed block is malformed: missing start or end marker");
|
|
1795
|
-
}
|
|
1796
|
-
if (end.start < start.start) {
|
|
1797
|
-
throw new Error("managed block is malformed: end marker precedes start");
|
|
1798
|
-
}
|
|
1799
|
-
return `${existing.slice(0, start.start)}${normalizedBlock}${existing.slice(
|
|
1800
|
-
end.end
|
|
1801
|
-
)}`;
|
|
1802
|
-
}
|
|
1803
|
-
function normalizeManagedBlock(block, startMarker, endMarker) {
|
|
1804
|
-
const trimmedBlock = block.trim();
|
|
1805
|
-
const hasStart = trimmedBlock.startsWith(startMarker);
|
|
1806
|
-
const hasEnd = trimmedBlock.endsWith(endMarker);
|
|
1807
|
-
if (hasStart && hasEnd) return trimmedBlock;
|
|
1808
|
-
if (hasStart || hasEnd) {
|
|
1809
|
-
throw new Error("managed block content includes only one marker");
|
|
1810
|
-
}
|
|
1811
|
-
return `${startMarker}
|
|
1812
|
-
${trimmedBlock}
|
|
1813
|
-
${endMarker}`;
|
|
1814
|
-
}
|
|
1815
|
-
function trimTrailingNewlines(value) {
|
|
1816
|
-
return value.replace(/\n+$/u, "");
|
|
1817
|
-
}
|
|
1818
|
-
function findMarkerLine(content, marker) {
|
|
1819
|
-
let offset = 0;
|
|
1820
|
-
let inFence = null;
|
|
1821
|
-
for (const lineWithBreak of content.matchAll(/[^\n]*(?:\n|$)/gu)) {
|
|
1822
|
-
const rawLine = lineWithBreak[0];
|
|
1823
|
-
if (!rawLine) break;
|
|
1824
|
-
const line = rawLine.replace(/\n$/u, "").replace(/\r$/u, "");
|
|
1825
|
-
const fence = line.match(/^(`{3,}|~{3,})/u)?.[1];
|
|
1826
|
-
if (fence) {
|
|
1827
|
-
const char = fence[0];
|
|
1828
|
-
if (!inFence) {
|
|
1829
|
-
inFence = { char, length: fence.length };
|
|
1830
|
-
} else if (inFence.char === char && fence.length >= inFence.length) {
|
|
1831
|
-
inFence = null;
|
|
1832
|
-
}
|
|
1833
|
-
} else if (!inFence && line === marker) {
|
|
1834
|
-
return {
|
|
1835
|
-
start: offset,
|
|
1836
|
-
end: offset + marker.length
|
|
1837
|
-
};
|
|
1838
|
-
}
|
|
1839
|
-
offset += rawLine.length;
|
|
1840
|
-
}
|
|
1841
|
-
return null;
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
1782
|
// src/installers/mode-skills.ts
|
|
1845
1783
|
import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, rm as rm2, writeFile as writeFile4 } from "fs/promises";
|
|
1846
1784
|
import path4 from "path";
|
|
@@ -2426,99 +2364,7 @@ function formatValue(value) {
|
|
|
2426
2364
|
return "";
|
|
2427
2365
|
}
|
|
2428
2366
|
|
|
2429
|
-
// src/installers/codex.ts
|
|
2430
|
-
async function installCodex(projectRoot, options) {
|
|
2431
|
-
await installMancodeCore(projectRoot);
|
|
2432
|
-
const agentsPath = path6.join(projectRoot, "AGENTS.md");
|
|
2433
|
-
const existing = await readTextIfExists(agentsPath);
|
|
2434
|
-
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2435
|
-
platform: "codex",
|
|
2436
|
-
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
2437
|
-
capabilities: {
|
|
2438
|
-
slashCommands: "partial",
|
|
2439
|
-
subagents: false,
|
|
2440
|
-
hooks: false,
|
|
2441
|
-
skills: "agents-skills"
|
|
2442
|
-
},
|
|
2443
|
-
minimal: options.minimal,
|
|
2444
|
-
techStack: options.techStack,
|
|
2445
|
-
uiLibrary: options.uiLibrary,
|
|
2446
|
-
projectProfile: options.projectProfile
|
|
2447
|
-
});
|
|
2448
|
-
const block = [
|
|
2449
|
-
DEFAULT_MANCODE_START_MARKER,
|
|
2450
|
-
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2451
|
-
"",
|
|
2452
|
-
"# mancode Configuration",
|
|
2453
|
-
"",
|
|
2454
|
-
sharedContent.trim(),
|
|
2455
|
-
DEFAULT_MANCODE_END_MARKER
|
|
2456
|
-
].join("\n");
|
|
2457
|
-
await writeFile5(agentsPath, replaceManagedBlock(existing, block), "utf-8");
|
|
2458
|
-
await installCodexSkills(projectRoot, options.minimal ?? false);
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
// src/installers/copilot.ts
|
|
2462
|
-
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
2463
|
-
import path7 from "path";
|
|
2464
|
-
async function installCopilot(projectRoot, options) {
|
|
2465
|
-
await installMancodeCore(projectRoot);
|
|
2466
|
-
const githubDir = path7.join(projectRoot, ".github");
|
|
2467
|
-
await mkdir5(githubDir, { recursive: true });
|
|
2468
|
-
const instructionsPath = path7.join(githubDir, "copilot-instructions.md");
|
|
2469
|
-
const existing = await readTextIfExists(instructionsPath);
|
|
2470
|
-
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2471
|
-
platform: "copilot",
|
|
2472
|
-
displayName: "GitHub Copilot",
|
|
2473
|
-
capabilities: {
|
|
2474
|
-
slashCommands: "none",
|
|
2475
|
-
subagents: false,
|
|
2476
|
-
hooks: false,
|
|
2477
|
-
skills: "instructions"
|
|
2478
|
-
},
|
|
2479
|
-
minimal: true,
|
|
2480
|
-
techStack: options.techStack,
|
|
2481
|
-
uiLibrary: options.uiLibrary,
|
|
2482
|
-
projectProfile: options.projectProfile
|
|
2483
|
-
});
|
|
2484
|
-
const sections = [
|
|
2485
|
-
DEFAULT_MANCODE_START_MARKER,
|
|
2486
|
-
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2487
|
-
"",
|
|
2488
|
-
"# mancode for GitHub Copilot",
|
|
2489
|
-
"",
|
|
2490
|
-
sharedContent.trim()
|
|
2491
|
-
];
|
|
2492
|
-
if (!options.minimal) {
|
|
2493
|
-
sections.push("", renderCopilotPromptConventions());
|
|
2494
|
-
}
|
|
2495
|
-
sections.push(DEFAULT_MANCODE_END_MARKER);
|
|
2496
|
-
await writeFile6(
|
|
2497
|
-
instructionsPath,
|
|
2498
|
-
replaceManagedBlock(existing, sections.join("\n")),
|
|
2499
|
-
"utf-8"
|
|
2500
|
-
);
|
|
2501
|
-
await installCopilotPrompts(projectRoot, options.minimal ?? false);
|
|
2502
|
-
}
|
|
2503
|
-
function renderCopilotPromptConventions() {
|
|
2504
|
-
return [
|
|
2505
|
-
"## mancode Prompt Conventions",
|
|
2506
|
-
"",
|
|
2507
|
-
"GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
|
|
2508
|
-
"",
|
|
2509
|
-
"- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
|
|
2510
|
-
"- mamba: diagnose bugs and validate real user flows or regressions.",
|
|
2511
|
-
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
2512
|
-
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
2513
|
-
"- mansolo: exit any active mode and return to default solo behavior.",
|
|
2514
|
-
"",
|
|
2515
|
-
"These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
|
|
2516
|
-
].join("\n");
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
2367
|
// src/installers/cursor.ts
|
|
2520
|
-
import { mkdir as mkdir6, readFile as readFile6, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
2521
|
-
import path8 from "path";
|
|
2522
2368
|
var MANCODE_CURSOR_CORE_RULE_FILES = [
|
|
2523
2369
|
"mancode-context.mdc",
|
|
2524
2370
|
"mancode-practice.mdc",
|
|
@@ -2537,8 +2383,8 @@ var MANCODE_CURSOR_RULE_FILES = [
|
|
|
2537
2383
|
];
|
|
2538
2384
|
async function installCursor(projectRoot, options) {
|
|
2539
2385
|
await installMancodeCore(projectRoot);
|
|
2540
|
-
const rulesDir =
|
|
2541
|
-
await
|
|
2386
|
+
const rulesDir = path6.join(projectRoot, ".cursor", "rules");
|
|
2387
|
+
await mkdir5(rulesDir, { recursive: true });
|
|
2542
2388
|
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2543
2389
|
platform: "cursor",
|
|
2544
2390
|
displayName: "Cursor",
|
|
@@ -2626,14 +2472,14 @@ async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
|
|
|
2626
2472
|
|
|
2627
2473
|
${body.trim()}
|
|
2628
2474
|
`;
|
|
2629
|
-
const rulePath =
|
|
2475
|
+
const rulePath = path6.join(rulesDir, fileName);
|
|
2630
2476
|
const existing = await readTextIfExists4(rulePath);
|
|
2631
2477
|
if (existing !== null && !isGeneratedCursorRule(existing, fileName)) {
|
|
2632
2478
|
throw new Error(
|
|
2633
2479
|
`refusing to overwrite user-authored Cursor rule: ${rulePath}`
|
|
2634
2480
|
);
|
|
2635
2481
|
}
|
|
2636
|
-
await
|
|
2482
|
+
await writeFile5(
|
|
2637
2483
|
rulePath,
|
|
2638
2484
|
content.replace("---\n\n", `---
|
|
2639
2485
|
|
|
@@ -2647,17 +2493,17 @@ async function removeAdvancedRules(rulesDir) {
|
|
|
2647
2493
|
for (const fileName of MANCODE_CURSOR_ADVANCED_RULE_FILES) {
|
|
2648
2494
|
await removeGeneratedCursorRule(rulesDir, fileName);
|
|
2649
2495
|
}
|
|
2650
|
-
await removeLegacyCursorRules(
|
|
2496
|
+
await removeLegacyCursorRules(path6.dirname(path6.dirname(rulesDir)));
|
|
2651
2497
|
}
|
|
2652
2498
|
async function removeCursorGeneratedRules(projectRoot) {
|
|
2653
|
-
const rulesDir =
|
|
2499
|
+
const rulesDir = path6.join(projectRoot, ".cursor", "rules");
|
|
2654
2500
|
for (const fileName of MANCODE_CURSOR_RULE_FILES) {
|
|
2655
2501
|
await removeGeneratedCursorRule(rulesDir, fileName);
|
|
2656
2502
|
}
|
|
2657
2503
|
await removeLegacyCursorRules(projectRoot);
|
|
2658
2504
|
}
|
|
2659
2505
|
async function removeLegacyCursorRules(projectRoot) {
|
|
2660
|
-
const legacyPath =
|
|
2506
|
+
const legacyPath = path6.join(
|
|
2661
2507
|
projectRoot,
|
|
2662
2508
|
".cursor",
|
|
2663
2509
|
"rules",
|
|
@@ -2672,7 +2518,7 @@ async function removeLegacyCursorRules(projectRoot) {
|
|
|
2672
2518
|
}
|
|
2673
2519
|
}
|
|
2674
2520
|
async function removeGeneratedCursorRule(rulesDir, fileName) {
|
|
2675
|
-
const rulePath =
|
|
2521
|
+
const rulePath = path6.join(rulesDir, fileName);
|
|
2676
2522
|
const content = await readTextIfExists4(rulePath);
|
|
2677
2523
|
if (content && isGeneratedCursorRule(content, fileName)) {
|
|
2678
2524
|
await rm3(rulePath, { force: true });
|
|
@@ -2742,16 +2588,200 @@ function renderManpsRule() {
|
|
|
2742
2588
|
return renderModeSkill("manps", "/");
|
|
2743
2589
|
}
|
|
2744
2590
|
|
|
2745
|
-
// src/installers/
|
|
2746
|
-
import { writeFile as
|
|
2747
|
-
import
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
const
|
|
2754
|
-
const
|
|
2591
|
+
// src/installers/codex.ts
|
|
2592
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2593
|
+
import path7 from "path";
|
|
2594
|
+
|
|
2595
|
+
// src/installers/managed-block.ts
|
|
2596
|
+
var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
|
|
2597
|
+
var DEFAULT_MANCODE_END_MARKER = "<!-- mancode:end -->";
|
|
2598
|
+
function removeManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
2599
|
+
const start = findMarkerLine(existing, startMarker);
|
|
2600
|
+
const end = findMarkerLine(existing, endMarker);
|
|
2601
|
+
if (start === null && end === null) return existing;
|
|
2602
|
+
if (start === null || end === null) return existing;
|
|
2603
|
+
if (end.start < start.start) return existing;
|
|
2604
|
+
const before = existing.slice(0, start.start);
|
|
2605
|
+
const after = existing.slice(end.end);
|
|
2606
|
+
const merged = `${before}${after}`;
|
|
2607
|
+
return cleanUpOrphanedNewlines(merged);
|
|
2608
|
+
}
|
|
2609
|
+
function hasManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
2610
|
+
const start = findMarkerLine(existing, startMarker);
|
|
2611
|
+
const end = findMarkerLine(existing, endMarker);
|
|
2612
|
+
return start !== null && end !== null && end.start > start.start;
|
|
2613
|
+
}
|
|
2614
|
+
function cleanUpOrphanedNewlines(content) {
|
|
2615
|
+
const trimmed = content.replace(/\n{3,}/gu, "\n\n").replace(/\n+$/u, "\n");
|
|
2616
|
+
return trimmed || "";
|
|
2617
|
+
}
|
|
2618
|
+
function replaceManagedBlock(existing, block, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
2619
|
+
const normalizedBlock = normalizeManagedBlock(block, startMarker, endMarker);
|
|
2620
|
+
const start = findMarkerLine(existing, startMarker);
|
|
2621
|
+
const end = findMarkerLine(existing, endMarker);
|
|
2622
|
+
if (start === null !== (end === null)) {
|
|
2623
|
+
throw new Error("managed block is malformed: missing start or end marker");
|
|
2624
|
+
}
|
|
2625
|
+
if (start === null && end === null) {
|
|
2626
|
+
const trimmedExisting = trimTrailingNewlines(existing);
|
|
2627
|
+
if (!trimmedExisting) return `${normalizedBlock}
|
|
2628
|
+
`;
|
|
2629
|
+
return `${trimmedExisting}
|
|
2630
|
+
|
|
2631
|
+
${normalizedBlock}
|
|
2632
|
+
`;
|
|
2633
|
+
}
|
|
2634
|
+
if (!start || !end) {
|
|
2635
|
+
throw new Error("managed block is malformed: missing start or end marker");
|
|
2636
|
+
}
|
|
2637
|
+
if (end.start < start.start) {
|
|
2638
|
+
throw new Error("managed block is malformed: end marker precedes start");
|
|
2639
|
+
}
|
|
2640
|
+
return `${existing.slice(0, start.start)}${normalizedBlock}${existing.slice(
|
|
2641
|
+
end.end
|
|
2642
|
+
)}`;
|
|
2643
|
+
}
|
|
2644
|
+
function normalizeManagedBlock(block, startMarker, endMarker) {
|
|
2645
|
+
const trimmedBlock = block.trim();
|
|
2646
|
+
const hasStart = trimmedBlock.startsWith(startMarker);
|
|
2647
|
+
const hasEnd = trimmedBlock.endsWith(endMarker);
|
|
2648
|
+
if (hasStart && hasEnd) return trimmedBlock;
|
|
2649
|
+
if (hasStart || hasEnd) {
|
|
2650
|
+
throw new Error("managed block content includes only one marker");
|
|
2651
|
+
}
|
|
2652
|
+
return `${startMarker}
|
|
2653
|
+
${trimmedBlock}
|
|
2654
|
+
${endMarker}`;
|
|
2655
|
+
}
|
|
2656
|
+
function trimTrailingNewlines(value) {
|
|
2657
|
+
return value.replace(/\n+$/u, "");
|
|
2658
|
+
}
|
|
2659
|
+
function findMarkerLine(content, marker) {
|
|
2660
|
+
let offset = 0;
|
|
2661
|
+
let inFence = null;
|
|
2662
|
+
for (const lineWithBreak of content.matchAll(/[^\n]*(?:\n|$)/gu)) {
|
|
2663
|
+
const rawLine = lineWithBreak[0];
|
|
2664
|
+
if (!rawLine) break;
|
|
2665
|
+
const line = rawLine.replace(/\n$/u, "").replace(/\r$/u, "");
|
|
2666
|
+
const fence = line.match(/^(`{3,}|~{3,})/u)?.[1];
|
|
2667
|
+
if (fence) {
|
|
2668
|
+
const char = fence[0];
|
|
2669
|
+
if (!inFence) {
|
|
2670
|
+
inFence = { char, length: fence.length };
|
|
2671
|
+
} else if (inFence.char === char && fence.length >= inFence.length) {
|
|
2672
|
+
inFence = null;
|
|
2673
|
+
}
|
|
2674
|
+
} else if (!inFence && line === marker) {
|
|
2675
|
+
return {
|
|
2676
|
+
start: offset,
|
|
2677
|
+
end: offset + marker.length
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
offset += rawLine.length;
|
|
2681
|
+
}
|
|
2682
|
+
return null;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
// src/installers/codex.ts
|
|
2686
|
+
async function installCodex(projectRoot, options) {
|
|
2687
|
+
await installMancodeCore(projectRoot);
|
|
2688
|
+
const agentsPath = path7.join(projectRoot, "AGENTS.md");
|
|
2689
|
+
const existing = await readTextIfExists(agentsPath);
|
|
2690
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2691
|
+
platform: "codex",
|
|
2692
|
+
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
2693
|
+
capabilities: {
|
|
2694
|
+
slashCommands: "partial",
|
|
2695
|
+
subagents: false,
|
|
2696
|
+
hooks: false,
|
|
2697
|
+
skills: "agents-skills"
|
|
2698
|
+
},
|
|
2699
|
+
minimal: options.minimal,
|
|
2700
|
+
techStack: options.techStack,
|
|
2701
|
+
uiLibrary: options.uiLibrary,
|
|
2702
|
+
projectProfile: options.projectProfile
|
|
2703
|
+
});
|
|
2704
|
+
const block = [
|
|
2705
|
+
DEFAULT_MANCODE_START_MARKER,
|
|
2706
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2707
|
+
"",
|
|
2708
|
+
"# mancode Configuration",
|
|
2709
|
+
"",
|
|
2710
|
+
sharedContent.trim(),
|
|
2711
|
+
DEFAULT_MANCODE_END_MARKER
|
|
2712
|
+
].join("\n");
|
|
2713
|
+
await writeFile6(agentsPath, replaceManagedBlock(existing, block), "utf-8");
|
|
2714
|
+
await installCodexSkills(projectRoot, options.minimal ?? false);
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
// src/installers/copilot.ts
|
|
2718
|
+
import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
|
|
2719
|
+
import path8 from "path";
|
|
2720
|
+
async function installCopilot(projectRoot, options) {
|
|
2721
|
+
await installMancodeCore(projectRoot);
|
|
2722
|
+
const githubDir = path8.join(projectRoot, ".github");
|
|
2723
|
+
await mkdir6(githubDir, { recursive: true });
|
|
2724
|
+
const instructionsPath = path8.join(githubDir, "copilot-instructions.md");
|
|
2725
|
+
const existing = await readTextIfExists(instructionsPath);
|
|
2726
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2727
|
+
platform: "copilot",
|
|
2728
|
+
displayName: "GitHub Copilot",
|
|
2729
|
+
capabilities: {
|
|
2730
|
+
slashCommands: "none",
|
|
2731
|
+
subagents: false,
|
|
2732
|
+
hooks: false,
|
|
2733
|
+
skills: "instructions"
|
|
2734
|
+
},
|
|
2735
|
+
minimal: true,
|
|
2736
|
+
techStack: options.techStack,
|
|
2737
|
+
uiLibrary: options.uiLibrary,
|
|
2738
|
+
projectProfile: options.projectProfile
|
|
2739
|
+
});
|
|
2740
|
+
const sections = [
|
|
2741
|
+
DEFAULT_MANCODE_START_MARKER,
|
|
2742
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2743
|
+
"",
|
|
2744
|
+
"# mancode for GitHub Copilot",
|
|
2745
|
+
"",
|
|
2746
|
+
sharedContent.trim()
|
|
2747
|
+
];
|
|
2748
|
+
if (!options.minimal) {
|
|
2749
|
+
sections.push("", renderCopilotPromptConventions());
|
|
2750
|
+
}
|
|
2751
|
+
sections.push(DEFAULT_MANCODE_END_MARKER);
|
|
2752
|
+
await writeFile7(
|
|
2753
|
+
instructionsPath,
|
|
2754
|
+
replaceManagedBlock(existing, sections.join("\n")),
|
|
2755
|
+
"utf-8"
|
|
2756
|
+
);
|
|
2757
|
+
await installCopilotPrompts(projectRoot, options.minimal ?? false);
|
|
2758
|
+
}
|
|
2759
|
+
function renderCopilotPromptConventions() {
|
|
2760
|
+
return [
|
|
2761
|
+
"## mancode Prompt Conventions",
|
|
2762
|
+
"",
|
|
2763
|
+
"GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
|
|
2764
|
+
"",
|
|
2765
|
+
"- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
|
|
2766
|
+
"- mamba: diagnose bugs and validate real user flows or regressions.",
|
|
2767
|
+
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
2768
|
+
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
2769
|
+
"- mansolo: exit any active mode and return to default solo behavior.",
|
|
2770
|
+
"",
|
|
2771
|
+
"These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
|
|
2772
|
+
].join("\n");
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// src/installers/zcode.ts
|
|
2776
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
2777
|
+
import path9 from "path";
|
|
2778
|
+
var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
|
|
2779
|
+
var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
|
|
2780
|
+
async function installZcode(projectRoot, options) {
|
|
2781
|
+
await installMancodeCore(projectRoot);
|
|
2782
|
+
const agentsPath = path9.join(projectRoot, "AGENTS.md");
|
|
2783
|
+
const existing = await readTextIfExists(agentsPath);
|
|
2784
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2755
2785
|
platform: "zcode",
|
|
2756
2786
|
displayName: "ZCode",
|
|
2757
2787
|
capabilities: {
|
|
@@ -2860,11 +2890,12 @@ function isPlatformName(platform) {
|
|
|
2860
2890
|
}
|
|
2861
2891
|
|
|
2862
2892
|
// src/system/detect-team.ts
|
|
2863
|
-
import {
|
|
2893
|
+
import { execFile } from "child_process";
|
|
2894
|
+
import { access as access2 } from "fs/promises";
|
|
2864
2895
|
import path10 from "path";
|
|
2865
2896
|
import process2 from "process";
|
|
2866
2897
|
import { promisify } from "util";
|
|
2867
|
-
var
|
|
2898
|
+
var execFileAsync = promisify(execFile);
|
|
2868
2899
|
var NO_TEAM = {
|
|
2869
2900
|
isTeam: false,
|
|
2870
2901
|
contributors: 1,
|
|
@@ -2872,106 +2903,186 @@ var NO_TEAM = {
|
|
|
2872
2903
|
hasRemote: false
|
|
2873
2904
|
};
|
|
2874
2905
|
var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
|
|
2875
|
-
var
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
GIT_PAGER: "cat",
|
|
2881
|
-
PAGER: "cat",
|
|
2882
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
2883
|
-
}
|
|
2906
|
+
var GIT_ENV = {
|
|
2907
|
+
...process2.env,
|
|
2908
|
+
GIT_PAGER: "cat",
|
|
2909
|
+
PAGER: "cat",
|
|
2910
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
2884
2911
|
};
|
|
2885
2912
|
async function detectTeamStatus(projectRoot) {
|
|
2886
|
-
const gitDir = path10.join(projectRoot, ".git");
|
|
2887
2913
|
try {
|
|
2888
|
-
await
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
return NO_TEAM;
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2914
|
+
await access2(path10.join(projectRoot, ".git"));
|
|
2915
|
+
const inside = await runGit(projectRoot, [
|
|
2916
|
+
"rev-parse",
|
|
2917
|
+
"--is-inside-work-tree"
|
|
2918
|
+
]);
|
|
2919
|
+
if (inside.trim() !== "true") return NO_TEAM;
|
|
2920
|
+
const [allEmails, recentEmails, remotes] = await Promise.all([
|
|
2921
|
+
runGit(projectRoot, ["log", "--all", "--format=%ae"]),
|
|
2922
|
+
runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
|
|
2923
|
+
runGit(projectRoot, ["remote", "-v"])
|
|
2924
|
+
]);
|
|
2925
|
+
const contributors = countUniqueLines(allEmails);
|
|
2926
|
+
const recentActive = countUniqueLines(recentEmails);
|
|
2927
|
+
const hasRemote = REMOTE_PATTERN.test(remotes);
|
|
2928
|
+
return {
|
|
2929
|
+
isTeam: contributors > 1 && hasRemote && recentActive > 1,
|
|
2930
|
+
contributors: contributors || 1,
|
|
2931
|
+
recentActive: recentActive || 1,
|
|
2932
|
+
hasRemote
|
|
2933
|
+
};
|
|
2900
2934
|
} catch {
|
|
2901
2935
|
return NO_TEAM;
|
|
2902
2936
|
}
|
|
2903
|
-
const contributors = await countUniqueEmails(
|
|
2904
|
-
"git log --all --format='%ae'",
|
|
2905
|
-
projectRoot
|
|
2906
|
-
);
|
|
2907
|
-
const recentActive = await countUniqueEmails(
|
|
2908
|
-
"git log --since='30 days ago' --format='%ae'",
|
|
2909
|
-
projectRoot
|
|
2910
|
-
);
|
|
2911
|
-
const hasRemote = await checkRemote(projectRoot);
|
|
2912
|
-
const safeContributors = Number.isFinite(contributors) ? contributors : 0;
|
|
2913
|
-
const safeRecent = Number.isFinite(recentActive) ? recentActive : 0;
|
|
2914
|
-
const isTeam = safeContributors > 1 && hasRemote && safeRecent > 1;
|
|
2915
|
-
return {
|
|
2916
|
-
isTeam,
|
|
2917
|
-
contributors: safeContributors || 1,
|
|
2918
|
-
recentActive: safeRecent || 1,
|
|
2919
|
-
hasRemote
|
|
2920
|
-
};
|
|
2921
2937
|
}
|
|
2922
|
-
async function
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
} catch {
|
|
2931
|
-
return 0;
|
|
2932
|
-
}
|
|
2938
|
+
async function runGit(cwd, args) {
|
|
2939
|
+
const { stdout: stdout2 } = await execFileAsync("git", args, {
|
|
2940
|
+
cwd,
|
|
2941
|
+
encoding: "utf-8",
|
|
2942
|
+
env: GIT_ENV,
|
|
2943
|
+
windowsHide: true
|
|
2944
|
+
});
|
|
2945
|
+
return stdout2;
|
|
2933
2946
|
}
|
|
2934
|
-
|
|
2947
|
+
function countUniqueLines(output) {
|
|
2948
|
+
return new Set(
|
|
2949
|
+
output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
2950
|
+
).size;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
// src/system/detect.ts
|
|
2954
|
+
import { execFile as execFile2 } from "child_process";
|
|
2955
|
+
import process3 from "process";
|
|
2956
|
+
import { promisify as promisify2 } from "util";
|
|
2957
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
2958
|
+
async function detectSystemDeps(env = process3.env) {
|
|
2935
2959
|
try {
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2960
|
+
await execFileAsync2("git", ["--version"], {
|
|
2961
|
+
env,
|
|
2962
|
+
windowsHide: true
|
|
2939
2963
|
});
|
|
2940
|
-
return
|
|
2964
|
+
return { git: true };
|
|
2941
2965
|
} catch {
|
|
2942
|
-
return false;
|
|
2966
|
+
return { git: false };
|
|
2943
2967
|
}
|
|
2944
2968
|
}
|
|
2945
2969
|
|
|
2946
|
-
// src/system/
|
|
2947
|
-
import {
|
|
2948
|
-
import
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2970
|
+
// src/system/init-onboarding.ts
|
|
2971
|
+
import { promises as fs } from "fs";
|
|
2972
|
+
import path11 from "path";
|
|
2973
|
+
import process4 from "process";
|
|
2974
|
+
import { stdin, stdout } from "process";
|
|
2975
|
+
import { createInterface } from "readline/promises";
|
|
2976
|
+
var ALL_PLATFORMS = Object.keys(PLATFORM_INSTALLERS);
|
|
2977
|
+
function detectInitLocale(override, environment = process4.env, systemLocale = Intl.DateTimeFormat().resolvedOptions().locale) {
|
|
2978
|
+
if (override) return parseLocale(override);
|
|
2979
|
+
const environmentLocale = environment.LC_ALL ?? environment.LC_MESSAGES ?? environment.LANG;
|
|
2980
|
+
return parseLocale(environmentLocale) ?? parseLocale(systemLocale) ?? "en";
|
|
2981
|
+
}
|
|
2982
|
+
function parseLocale(value) {
|
|
2983
|
+
if (!value) return null;
|
|
2984
|
+
const normalized = value.toLowerCase().replace("_", "-");
|
|
2985
|
+
if (normalized === "zh" || normalized.startsWith("zh-")) return "zh-CN";
|
|
2986
|
+
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
|
2987
|
+
return null;
|
|
2988
|
+
}
|
|
2989
|
+
function parsePlatformSelection(value) {
|
|
2990
|
+
const normalized = value.trim().toLowerCase();
|
|
2991
|
+
if (normalized === "all" || normalized === "\u5168\u90E8") return [...ALL_PLATFORMS];
|
|
2992
|
+
const choices = normalized.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2993
|
+
if (choices.length === 0) return null;
|
|
2994
|
+
if (!choices.every((item) => item in PLATFORM_INSTALLERS)) {
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
return [...new Set(choices)];
|
|
2998
|
+
}
|
|
2999
|
+
async function detectPlatformHints(rootDir, environment = process4.env) {
|
|
3000
|
+
const hints = /* @__PURE__ */ new Set();
|
|
3001
|
+
if (environment.CLAUDECODE || environment.CLAUDE_CODE)
|
|
3002
|
+
hints.add("claude-code");
|
|
3003
|
+
if (environment.CODEX_HOME) hints.add("codex");
|
|
3004
|
+
if (environment.CURSOR_TRACE_ID) hints.add("cursor");
|
|
3005
|
+
if (environment.COPILOT_AGENT || environment.GITHUB_COPILOT)
|
|
3006
|
+
hints.add("copilot");
|
|
3007
|
+
const exists = async (relative) => {
|
|
2956
3008
|
try {
|
|
2957
|
-
await
|
|
3009
|
+
await fs.access(path11.join(rootDir, relative));
|
|
2958
3010
|
return true;
|
|
2959
3011
|
} catch {
|
|
2960
3012
|
return false;
|
|
2961
3013
|
}
|
|
2962
3014
|
};
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
return {
|
|
3015
|
+
if (await exists(".claude")) hints.add("claude-code");
|
|
3016
|
+
if (await exists(".cursor")) hints.add("cursor");
|
|
3017
|
+
if (await exists(".github/copilot-instructions.md")) hints.add("copilot");
|
|
3018
|
+
return ALL_PLATFORMS.filter((platform) => hints.has(platform));
|
|
3019
|
+
}
|
|
3020
|
+
function createTerminalPrompter() {
|
|
3021
|
+
return {
|
|
3022
|
+
async confirmGenericProject({ rootDir, locale }) {
|
|
3023
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
3024
|
+
try {
|
|
3025
|
+
if (locale === "zh-CN") {
|
|
3026
|
+
console.log("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\u3002");
|
|
3027
|
+
console.log(`\u76EE\u5F55\uFF1A${rootDir}`);
|
|
3028
|
+
console.log("\u8FD9\u662F\u4E00\u4E2A\u65B0\u9879\u76EE\u5417\uFF1F");
|
|
3029
|
+
console.log("[y] \u521D\u59CB\u5316\u4E3A\u901A\u7528\u9879\u76EE");
|
|
3030
|
+
console.log("[n] \u9000\u51FA");
|
|
3031
|
+
const answer2 = await rl.question("\u8BF7\u9009\u62E9 [y/N]: ");
|
|
3032
|
+
return ["y", "yes", "\u662F"].includes(answer2.trim().toLowerCase());
|
|
3033
|
+
}
|
|
3034
|
+
console.log("No project files were detected in the current directory.");
|
|
3035
|
+
console.log(`Directory: ${rootDir}`);
|
|
3036
|
+
console.log("Is this a new project?");
|
|
3037
|
+
console.log("[y] Initialize as a generic project");
|
|
3038
|
+
console.log("[n] Exit");
|
|
3039
|
+
const answer = await rl.question("Choose [y/N]: ");
|
|
3040
|
+
return ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
3041
|
+
} finally {
|
|
3042
|
+
rl.close();
|
|
3043
|
+
}
|
|
3044
|
+
},
|
|
3045
|
+
async selectPlatforms({ locale, detected }) {
|
|
3046
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
3047
|
+
try {
|
|
3048
|
+
const names = ALL_PLATFORMS.map((platform, index) => {
|
|
3049
|
+
const suffix = detected.includes(platform) ? locale === "zh-CN" ? "\uFF08\u5DF2\u68C0\u6D4B\u5230\uFF09" : " (detected)" : "";
|
|
3050
|
+
return `${index + 1}. ${PLATFORM_INSTALLERS[platform].displayName}${suffix}`;
|
|
3051
|
+
});
|
|
3052
|
+
console.log(
|
|
3053
|
+
locale === "zh-CN" ? "\n\u9009\u62E9\u8981\u521D\u59CB\u5316\u7684\u5E73\u53F0\uFF1A" : "\nChoose platforms to initialize:"
|
|
3054
|
+
);
|
|
3055
|
+
console.log(names.join("\n"));
|
|
3056
|
+
console.log(locale === "zh-CN" ? "a. \u5168\u90E8\u5E73\u53F0" : "a. All platforms");
|
|
3057
|
+
console.log(
|
|
3058
|
+
locale === "zh-CN" ? "\u53EF\u8F93\u5165\u7F16\u53F7\uFF08\u4F8B\u5982 1,3\uFF09\u6216 a\u3002" : "Enter numbers (for example 1,3) or a."
|
|
3059
|
+
);
|
|
3060
|
+
const answer = (await rl.question(locale === "zh-CN" ? "\u9009\u62E9: " : "Selection: ")).trim().toLowerCase();
|
|
3061
|
+
if (answer === "a" || answer === "all" || answer === "\u5168\u90E8")
|
|
3062
|
+
return [...ALL_PLATFORMS];
|
|
3063
|
+
const indexes = answer.split(",").map((item) => Number(item.trim()));
|
|
3064
|
+
if (!indexes.length || indexes.some(
|
|
3065
|
+
(index) => !Number.isInteger(index) || index < 1 || index > ALL_PLATFORMS.length
|
|
3066
|
+
)) {
|
|
3067
|
+
return null;
|
|
3068
|
+
}
|
|
3069
|
+
const selected = [];
|
|
3070
|
+
for (const index of indexes) {
|
|
3071
|
+
const platform = ALL_PLATFORMS[index - 1];
|
|
3072
|
+
if (!platform) return null;
|
|
3073
|
+
selected.push(platform);
|
|
3074
|
+
}
|
|
3075
|
+
return [...new Set(selected)];
|
|
3076
|
+
} finally {
|
|
3077
|
+
rl.close();
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
2970
3081
|
}
|
|
2971
3082
|
|
|
2972
3083
|
// src/system/project-profile.ts
|
|
2973
3084
|
import { readFile as readFile7, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
2974
|
-
import
|
|
3085
|
+
import path12 from "path";
|
|
2975
3086
|
var PROJECT_MANIFESTS = [
|
|
2976
3087
|
"package.json",
|
|
2977
3088
|
"pyproject.toml",
|
|
@@ -3004,12 +3115,12 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3004
3115
|
"data",
|
|
3005
3116
|
"notebooks"
|
|
3006
3117
|
]);
|
|
3007
|
-
const packageJson = await readJson2(
|
|
3118
|
+
const packageJson = await readJson2(path12.join(projectRoot, "package.json"));
|
|
3008
3119
|
const [pubspec, pythonManifests] = await Promise.all([
|
|
3009
|
-
readText(
|
|
3120
|
+
readText(path12.join(projectRoot, "pubspec.yaml")),
|
|
3010
3121
|
Promise.all([
|
|
3011
|
-
readText(
|
|
3012
|
-
readText(
|
|
3122
|
+
readText(path12.join(projectRoot, "pyproject.toml")),
|
|
3123
|
+
readText(path12.join(projectRoot, "requirements.txt"))
|
|
3013
3124
|
]).then((parts) => parts.filter(Boolean).join("\n"))
|
|
3014
3125
|
]);
|
|
3015
3126
|
const deps = Object.keys({
|
|
@@ -3018,7 +3129,7 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3018
3129
|
...packageJson?.peerDependencies
|
|
3019
3130
|
});
|
|
3020
3131
|
const flutter = isFlutterProject(pubspec);
|
|
3021
|
-
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(
|
|
3132
|
+
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path12.join(projectRoot, "src", "components", "ui"));
|
|
3022
3133
|
const languages = inferLanguages(manifests, entries);
|
|
3023
3134
|
const frameworks = inferFrameworks(
|
|
3024
3135
|
deps,
|
|
@@ -3061,7 +3172,7 @@ async function existingDirs(root, candidates) {
|
|
|
3061
3172
|
const found = [];
|
|
3062
3173
|
for (const candidate of candidates) {
|
|
3063
3174
|
try {
|
|
3064
|
-
if ((await stat2(
|
|
3175
|
+
if ((await stat2(path12.join(root, candidate))).isDirectory())
|
|
3065
3176
|
found.push(candidate);
|
|
3066
3177
|
} catch {
|
|
3067
3178
|
}
|
|
@@ -3235,8 +3346,8 @@ function isFlutterProject(pubspec) {
|
|
|
3235
3346
|
}
|
|
3236
3347
|
|
|
3237
3348
|
// src/system/scan-aesthetics.ts
|
|
3238
|
-
import { promises as
|
|
3239
|
-
import
|
|
3349
|
+
import { promises as fs2 } from "fs";
|
|
3350
|
+
import path13 from "path";
|
|
3240
3351
|
var MAX_COMPONENT_SCAN_DEPTH = 12;
|
|
3241
3352
|
var MAX_COMPONENT_FILES = 2e3;
|
|
3242
3353
|
async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
@@ -3249,7 +3360,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3249
3360
|
const cssScan = await scanCssVariables(projectRoot);
|
|
3250
3361
|
if (configResult) {
|
|
3251
3362
|
sourceFiles.push(configResult.relPath);
|
|
3252
|
-
const content = await
|
|
3363
|
+
const content = await fs2.readFile(configResult.absPath, "utf-8");
|
|
3253
3364
|
const inspectableContent = stripJsComments(content);
|
|
3254
3365
|
const themeBlock = findKeyBlock(inspectableContent, "theme");
|
|
3255
3366
|
if (themeBlock) {
|
|
@@ -3272,7 +3383,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3272
3383
|
} else if (await hasTailwindDep(projectRoot) || uiLibrary) {
|
|
3273
3384
|
matchLevel = "low";
|
|
3274
3385
|
}
|
|
3275
|
-
if (uiLibrary && await pathExists2(
|
|
3386
|
+
if (uiLibrary && await pathExists2(path13.join(projectRoot, "package.json"))) {
|
|
3276
3387
|
sourceFiles.push("package.json");
|
|
3277
3388
|
}
|
|
3278
3389
|
sourceFiles.push(...cssScan.sourceFiles);
|
|
@@ -3297,7 +3408,7 @@ async function findTailwindConfig(projectRoot) {
|
|
|
3297
3408
|
"tailwind.config.mjs"
|
|
3298
3409
|
];
|
|
3299
3410
|
for (const name of candidates) {
|
|
3300
|
-
const absPath =
|
|
3411
|
+
const absPath = path13.join(projectRoot, name);
|
|
3301
3412
|
if (await pathExists2(absPath)) {
|
|
3302
3413
|
return { absPath, relPath: name };
|
|
3303
3414
|
}
|
|
@@ -3524,7 +3635,7 @@ async function scanComponents(projectRoot) {
|
|
|
3524
3635
|
const names = /* @__PURE__ */ new Set();
|
|
3525
3636
|
let visitedFiles = 0;
|
|
3526
3637
|
for (const relRoot of roots) {
|
|
3527
|
-
const absRoot =
|
|
3638
|
+
const absRoot = path13.join(projectRoot, relRoot);
|
|
3528
3639
|
if (!await pathExists2(absRoot)) continue;
|
|
3529
3640
|
visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
|
|
3530
3641
|
if (visitedFiles >= MAX_COMPONENT_FILES) break;
|
|
@@ -3538,16 +3649,16 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3538
3649
|
let fileCount = visitedFiles;
|
|
3539
3650
|
let entries;
|
|
3540
3651
|
try {
|
|
3541
|
-
entries = await
|
|
3652
|
+
entries = await fs2.readdir(dir);
|
|
3542
3653
|
} catch {
|
|
3543
3654
|
return fileCount;
|
|
3544
3655
|
}
|
|
3545
3656
|
for (const entry of entries) {
|
|
3546
3657
|
if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
|
|
3547
|
-
const abs =
|
|
3658
|
+
const abs = path13.join(dir, entry);
|
|
3548
3659
|
let info;
|
|
3549
3660
|
try {
|
|
3550
|
-
info = await
|
|
3661
|
+
info = await fs2.lstat(abs);
|
|
3551
3662
|
} catch {
|
|
3552
3663
|
continue;
|
|
3553
3664
|
}
|
|
@@ -3565,7 +3676,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3565
3676
|
""
|
|
3566
3677
|
);
|
|
3567
3678
|
if (base === "index") {
|
|
3568
|
-
base =
|
|
3679
|
+
base = path13.basename(dir);
|
|
3569
3680
|
}
|
|
3570
3681
|
if (base.startsWith(".")) continue;
|
|
3571
3682
|
const componentName = toPascalCase(base);
|
|
@@ -3587,9 +3698,9 @@ async function scanCssVariables(projectRoot) {
|
|
|
3587
3698
|
const variables = {};
|
|
3588
3699
|
const sourceFiles = [];
|
|
3589
3700
|
for (const relPath of candidates) {
|
|
3590
|
-
const absPath =
|
|
3701
|
+
const absPath = path13.join(projectRoot, relPath);
|
|
3591
3702
|
if (!await pathExists2(absPath)) continue;
|
|
3592
|
-
const content = await
|
|
3703
|
+
const content = await fs2.readFile(absPath, "utf-8");
|
|
3593
3704
|
const found = extractCssVariables(content);
|
|
3594
3705
|
if (Object.keys(found).length === 0) continue;
|
|
3595
3706
|
Object.assign(variables, found);
|
|
@@ -3632,8 +3743,8 @@ function toPascalCase(value) {
|
|
|
3632
3743
|
}
|
|
3633
3744
|
async function hasTailwindDep(projectRoot) {
|
|
3634
3745
|
try {
|
|
3635
|
-
const raw = await
|
|
3636
|
-
|
|
3746
|
+
const raw = await fs2.readFile(
|
|
3747
|
+
path13.join(projectRoot, "package.json"),
|
|
3637
3748
|
"utf-8"
|
|
3638
3749
|
);
|
|
3639
3750
|
const pkg = JSON.parse(raw);
|
|
@@ -3649,7 +3760,7 @@ function escapeRegex(s) {
|
|
|
3649
3760
|
}
|
|
3650
3761
|
async function pathExists2(p) {
|
|
3651
3762
|
try {
|
|
3652
|
-
await
|
|
3763
|
+
await fs2.access(p);
|
|
3653
3764
|
return true;
|
|
3654
3765
|
} catch {
|
|
3655
3766
|
return false;
|
|
@@ -3660,73 +3771,104 @@ async function pathExists2(p) {
|
|
|
3660
3771
|
var EXIT_OK = 0;
|
|
3661
3772
|
var EXIT_ALREADY_INITIALIZED = 1;
|
|
3662
3773
|
var EXIT_NOT_A_PROJECT_DIR = 2;
|
|
3774
|
+
var EXIT_USER_CANCEL = 3;
|
|
3663
3775
|
var EXIT_INIT_FAILED = 5;
|
|
3664
3776
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
3665
|
-
async function init(rootDir =
|
|
3666
|
-
const mancodeDir =
|
|
3667
|
-
const stateFile =
|
|
3777
|
+
async function init(rootDir = process5.cwd(), options = {}) {
|
|
3778
|
+
const mancodeDir = path14.join(rootDir, ".mancode");
|
|
3779
|
+
const stateFile = path14.join(mancodeDir, "state.json");
|
|
3668
3780
|
const wasInitialized = await pathExists3(stateFile);
|
|
3669
|
-
let
|
|
3781
|
+
let mutationSnapshots = [];
|
|
3782
|
+
let directorySnapshots = [];
|
|
3783
|
+
const locale = detectInitLocale(options.lang);
|
|
3784
|
+
if (!locale) {
|
|
3785
|
+
console.error(`\u2717 Unsupported init language: ${options.lang}`);
|
|
3786
|
+
console.error(" Supported values: zh-CN, en");
|
|
3787
|
+
return EXIT_INIT_FAILED;
|
|
3788
|
+
}
|
|
3670
3789
|
if (wasInitialized) {
|
|
3671
3790
|
if (!options.force) {
|
|
3672
|
-
console.log(
|
|
3791
|
+
console.log(
|
|
3792
|
+
localize(
|
|
3793
|
+
locale,
|
|
3794
|
+
"\u2139\uFE0F mancode \u5DF2\u7ECF\u521D\u59CB\u5316\u3002",
|
|
3795
|
+
"\u2139\uFE0F mancode already initialized."
|
|
3796
|
+
)
|
|
3797
|
+
);
|
|
3673
3798
|
console.log(` ${stateFile}`);
|
|
3674
|
-
console.log(
|
|
3799
|
+
console.log(
|
|
3800
|
+
localize(
|
|
3801
|
+
locale,
|
|
3802
|
+
" \u8FD0\u884C `mancode init --force` \u91CD\u65B0\u5B89\u88C5\u3002",
|
|
3803
|
+
" Run `mancode init --force` to reinstall."
|
|
3804
|
+
)
|
|
3805
|
+
);
|
|
3675
3806
|
return EXIT_ALREADY_INITIALIZED;
|
|
3676
3807
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
} catch (error) {
|
|
3685
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3686
|
-
console.error(`\u2717 Cannot snapshot existing mancode state: ${message}`);
|
|
3687
|
-
return EXIT_INIT_FAILED;
|
|
3688
|
-
}
|
|
3689
|
-
console.log("\u26A0\uFE0F Reinstalling with --force...");
|
|
3808
|
+
console.log(
|
|
3809
|
+
localize(
|
|
3810
|
+
locale,
|
|
3811
|
+
"\u26A0\uFE0F \u6B63\u5728\u4F7F\u7528 --force \u91CD\u65B0\u5B89\u88C5...",
|
|
3812
|
+
"\u26A0\uFE0F Reinstalling with --force..."
|
|
3813
|
+
)
|
|
3814
|
+
);
|
|
3690
3815
|
}
|
|
3691
3816
|
if (!await pathExists3(rootDir)) {
|
|
3692
|
-
console.error(`\u2717 Target directory does not exist: ${rootDir}`);
|
|
3693
|
-
return EXIT_NOT_A_PROJECT_DIR;
|
|
3694
|
-
}
|
|
3695
|
-
const isGitRepo = await pathExists3(path13.join(rootDir, ".git"));
|
|
3696
|
-
const hasManifest = await Promise.any(
|
|
3697
|
-
PROJECT_MANIFESTS.map(async (name) => {
|
|
3698
|
-
if (await pathExists3(path13.join(rootDir, name))) return true;
|
|
3699
|
-
throw new Error("manifest missing");
|
|
3700
|
-
})
|
|
3701
|
-
).catch(() => false);
|
|
3702
|
-
if (!isGitRepo && !hasManifest) {
|
|
3703
|
-
console.error(`\u2717 Not a project directory: ${rootDir}`);
|
|
3704
|
-
console.error(" (No .git or recognized project manifest found)");
|
|
3705
|
-
console.error("");
|
|
3706
3817
|
console.error(
|
|
3707
|
-
|
|
3818
|
+
localize(
|
|
3819
|
+
locale,
|
|
3820
|
+
`\u2717 \u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${rootDir}`,
|
|
3821
|
+
`\u2717 Target directory does not exist: ${rootDir}`
|
|
3822
|
+
)
|
|
3708
3823
|
);
|
|
3709
3824
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3710
3825
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3826
|
+
const isGitRepo = await pathExists3(path14.join(rootDir, ".git"));
|
|
3827
|
+
const hasManifest = await hasProjectManifest(rootDir);
|
|
3828
|
+
let isGenericProject = false;
|
|
3829
|
+
if (!isGitRepo && !hasManifest) {
|
|
3830
|
+
const genericSafety = await canInitializeGenericProject(rootDir);
|
|
3831
|
+
if (!genericSafety.ok) {
|
|
3832
|
+
printNotProjectDirectory(rootDir, locale, genericSafety.reason);
|
|
3833
|
+
return EXIT_NOT_A_PROJECT_DIR;
|
|
3834
|
+
}
|
|
3835
|
+
const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
|
|
3836
|
+
const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
|
|
3837
|
+
if (!confirmed) {
|
|
3838
|
+
if (options.interactive) {
|
|
3839
|
+
console.log(
|
|
3840
|
+
locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
|
|
3841
|
+
);
|
|
3842
|
+
return EXIT_USER_CANCEL;
|
|
3843
|
+
}
|
|
3844
|
+
printNotProjectDirectory(rootDir, locale, "empty");
|
|
3720
3845
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3721
3846
|
}
|
|
3722
|
-
|
|
3847
|
+
isGenericProject = true;
|
|
3848
|
+
}
|
|
3849
|
+
try {
|
|
3850
|
+
console.log(
|
|
3851
|
+
localize(
|
|
3852
|
+
locale,
|
|
3853
|
+
"\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...",
|
|
3854
|
+
"\u2713 Checking system dependencies..."
|
|
3855
|
+
)
|
|
3856
|
+
);
|
|
3857
|
+
const deps = await detectSystemDeps();
|
|
3858
|
+
if (!deps.git) {
|
|
3723
3859
|
console.log(
|
|
3724
|
-
|
|
3860
|
+
localize(
|
|
3861
|
+
locale,
|
|
3862
|
+
"\u26A0\uFE0F \u672A\u627E\u5230 Git\uFF08\u53EF\u9009\uFF09\u3002\u56E2\u961F\u81EA\u52A8\u68C0\u6D4B\u5C06\u4F7F\u7528 solo \u9ED8\u8BA4\u503C\u3002",
|
|
3863
|
+
"\u26A0\uFE0F Git not found (optional). Team auto-detection will use solo defaults."
|
|
3864
|
+
)
|
|
3725
3865
|
);
|
|
3726
3866
|
} else {
|
|
3727
|
-
console.log("
|
|
3867
|
+
console.log(" git \u2713");
|
|
3728
3868
|
}
|
|
3729
|
-
console.log(
|
|
3869
|
+
console.log(
|
|
3870
|
+
localize(locale, "\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...", "\u2713 Detecting project type...")
|
|
3871
|
+
);
|
|
3730
3872
|
const profile = await detectProjectProfile(rootDir);
|
|
3731
3873
|
const profileStack = [...profile.languages, ...profile.frameworks];
|
|
3732
3874
|
const techStackStr = profileStack.join(" + ") || "Unknown";
|
|
@@ -3738,37 +3880,100 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
3738
3880
|
console.log(` UI: ${uiLibraryStr}`);
|
|
3739
3881
|
}
|
|
3740
3882
|
} else if (hasManifest) {
|
|
3741
|
-
console.log(
|
|
3883
|
+
console.log(
|
|
3884
|
+
localize(
|
|
3885
|
+
locale,
|
|
3886
|
+
" \u5DF2\u53D1\u73B0\u9879\u76EE manifest\uFF0C\u672A\u8BC6\u522B\u5230\u6846\u67B6\u4F9D\u8D56",
|
|
3887
|
+
" Project manifest found; no known framework dependencies"
|
|
3888
|
+
)
|
|
3889
|
+
);
|
|
3742
3890
|
} else {
|
|
3743
3891
|
console.log(
|
|
3744
|
-
|
|
3892
|
+
localize(
|
|
3893
|
+
locale,
|
|
3894
|
+
" \uFF08\u672A\u53D1\u73B0\u5DF2\u8BC6\u522B\u7684 manifest\uFF0C\u9879\u76EE\u753B\u50CF\u7F6E\u4FE1\u5EA6\u8F83\u4F4E\uFF09",
|
|
3895
|
+
" (No recognized manifest found; profile confidence is low)"
|
|
3896
|
+
)
|
|
3745
3897
|
);
|
|
3746
3898
|
}
|
|
3747
3899
|
const existingPlatform = wasInitialized ? await readExistingInitPlatform(stateFile) : null;
|
|
3748
|
-
const
|
|
3749
|
-
const
|
|
3750
|
-
|
|
3751
|
-
|
|
3900
|
+
const platformHints = await detectPlatformHints(rootDir);
|
|
3901
|
+
const selectedPlatforms = await selectInitPlatforms({
|
|
3902
|
+
option: options.platform,
|
|
3903
|
+
existingPlatform,
|
|
3904
|
+
hints: platformHints,
|
|
3905
|
+
interactive: options.interactive,
|
|
3906
|
+
prompter: options.prompter,
|
|
3907
|
+
locale,
|
|
3908
|
+
yes: options.yes
|
|
3909
|
+
});
|
|
3910
|
+
if (!selectedPlatforms) {
|
|
3911
|
+
console.error(
|
|
3912
|
+
locale === "zh-CN" ? "\u2717 \u672A\u9009\u62E9\u5E73\u53F0\u3002\u8BF7\u91CD\u65B0\u8FD0\u884C\u5E76\u9009\u62E9\u5E73\u53F0\uFF0C\u6216\u4F7F\u7528 --platform codex,cursor / --platform all\u3002" : "\u2717 No platform selected. Choose one interactively or pass --platform codex,cursor / --platform all."
|
|
3913
|
+
);
|
|
3914
|
+
return EXIT_INIT_FAILED;
|
|
3915
|
+
}
|
|
3916
|
+
if (selectedPlatforms.length === 0) {
|
|
3917
|
+
console.error("\u2717 No platform selected.");
|
|
3918
|
+
return EXIT_INIT_FAILED;
|
|
3919
|
+
}
|
|
3920
|
+
const firstPlatform = selectedPlatforms[0];
|
|
3921
|
+
if (!firstPlatform) {
|
|
3922
|
+
console.error("\u2717 No platform selected.");
|
|
3923
|
+
return EXIT_INIT_FAILED;
|
|
3924
|
+
}
|
|
3925
|
+
const installers = selectedPlatforms.map(getPlatformInstaller);
|
|
3926
|
+
if (installers.some((installer) => !installer)) {
|
|
3927
|
+
console.error(`\u2717 Unsupported platform: ${options.platform}`);
|
|
3752
3928
|
return EXIT_INIT_FAILED;
|
|
3753
3929
|
}
|
|
3930
|
+
const typedInstallers = installers.filter(
|
|
3931
|
+
(installer) => installer !== null
|
|
3932
|
+
);
|
|
3933
|
+
const onlyPlatformHint = platformHints.length === 1 ? platformHints[0] : null;
|
|
3934
|
+
const detectedPrimary = onlyPlatformHint && selectedPlatforms.includes(onlyPlatformHint) ? onlyPlatformHint : null;
|
|
3935
|
+
const primaryPlatform = existingPlatform && selectedPlatforms.includes(existingPlatform) ? existingPlatform : detectedPrimary ?? firstPlatform;
|
|
3936
|
+
const managedFiles = getInitManagedFilePaths(rootDir, selectedPlatforms);
|
|
3937
|
+
mutationSnapshots = await snapshotFiles(managedFiles);
|
|
3938
|
+
directorySnapshots = await snapshotDirectories(managedFiles, [
|
|
3939
|
+
path14.join(mancodeDir, "workflows"),
|
|
3940
|
+
path14.join(mancodeDir, "preseason-reports")
|
|
3941
|
+
]);
|
|
3754
3942
|
const team = await detectTeamStatus(rootDir);
|
|
3755
|
-
const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir,
|
|
3943
|
+
const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, primaryPlatform) : {};
|
|
3756
3944
|
const initialMinimal = existingPreferences.minimal ?? false;
|
|
3757
|
-
const
|
|
3945
|
+
const existingTeamMode = existingPreferences.forceTeamMode === true ? "on" : existingPreferences.teamMode ?? "auto";
|
|
3946
|
+
const teamModeEnabled = options.team ?? (existingTeamMode === "on" ? true : existingTeamMode === "off" ? false : team.isTeam);
|
|
3758
3947
|
if (options.team === true) {
|
|
3759
|
-
console.log(
|
|
3948
|
+
console.log(
|
|
3949
|
+
localize(
|
|
3950
|
+
locale,
|
|
3951
|
+
" \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5F00\u542F\uFF08--team\uFF09",
|
|
3952
|
+
" team: forced on (--team)"
|
|
3953
|
+
)
|
|
3954
|
+
);
|
|
3760
3955
|
} else if (options.team === false) {
|
|
3761
|
-
console.log(
|
|
3956
|
+
console.log(
|
|
3957
|
+
localize(
|
|
3958
|
+
locale,
|
|
3959
|
+
" \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5173\u95ED\uFF08--no-team\uFF09",
|
|
3960
|
+
" team: forced off (--no-team)"
|
|
3961
|
+
)
|
|
3962
|
+
);
|
|
3762
3963
|
} else if (team.isTeam) {
|
|
3763
3964
|
console.log(
|
|
3764
|
-
|
|
3965
|
+
localize(
|
|
3966
|
+
locale,
|
|
3967
|
+
` \u56E2\u961F\u6A21\u5F0F\uFF1A${team.contributors} \u4F4D\u8D21\u732E\u8005\uFF08\u53EF\u4F7F\u7528 /manteam\uFF09`,
|
|
3968
|
+
` team: ${team.contributors} contributors (/manteam available)`
|
|
3969
|
+
)
|
|
3765
3970
|
);
|
|
3766
3971
|
}
|
|
3767
3972
|
const state = {
|
|
3768
3973
|
version: VERSION,
|
|
3769
3974
|
currentMode: "solo",
|
|
3770
3975
|
lastMode: "solo",
|
|
3771
|
-
platform:
|
|
3976
|
+
platform: primaryPlatform,
|
|
3772
3977
|
initializedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3773
3978
|
techStack: techStackStr,
|
|
3774
3979
|
uiLibrary: uiLibraryStr,
|
|
@@ -3776,18 +3981,19 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
3776
3981
|
currentWorkflowMode: null,
|
|
3777
3982
|
skippedSteps: [],
|
|
3778
3983
|
teamModeAutoDetected: teamModeEnabled,
|
|
3779
|
-
contributors: team.contributors
|
|
3984
|
+
contributors: team.contributors,
|
|
3985
|
+
projectMode: isGenericProject ? "generic" : "detected"
|
|
3780
3986
|
};
|
|
3781
|
-
if (
|
|
3987
|
+
if (selectedPlatforms.includes("claude-code")) {
|
|
3782
3988
|
await validateClaudeCodeSettings(rootDir);
|
|
3783
3989
|
}
|
|
3784
3990
|
await installMancodeCore(rootDir);
|
|
3785
|
-
await
|
|
3991
|
+
await fs3.mkdir(mancodeDir, { recursive: true });
|
|
3786
3992
|
const stateContent = `${JSON.stringify(state, null, 2)}
|
|
3787
3993
|
`;
|
|
3788
|
-
await
|
|
3789
|
-
await
|
|
3790
|
-
|
|
3994
|
+
await fs3.writeFile(stateFile, stateContent, "utf-8");
|
|
3995
|
+
await fs3.writeFile(
|
|
3996
|
+
path14.join(mancodeDir, "project-profile.json"),
|
|
3791
3997
|
`${JSON.stringify(profile, null, 2)}
|
|
3792
3998
|
`,
|
|
3793
3999
|
"utf-8"
|
|
@@ -3796,24 +4002,40 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
3796
4002
|
mancodeDir,
|
|
3797
4003
|
{
|
|
3798
4004
|
forceTeamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true,
|
|
4005
|
+
teamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true ? "on" : options.team === false ? "off" : "auto",
|
|
3799
4006
|
defaultStyle: options.style === void 0 && wasInitialized ? void 0 : options.style ?? null,
|
|
3800
|
-
platforms:
|
|
4007
|
+
platforms: selectedPlatforms,
|
|
3801
4008
|
platformOptions: {
|
|
3802
|
-
|
|
4009
|
+
...Object.fromEntries(
|
|
4010
|
+
selectedPlatforms.map((platform) => [
|
|
4011
|
+
platform,
|
|
4012
|
+
{ minimal: initialMinimal }
|
|
4013
|
+
])
|
|
4014
|
+
)
|
|
3803
4015
|
}
|
|
3804
4016
|
},
|
|
3805
4017
|
wasInitialized
|
|
3806
4018
|
);
|
|
3807
|
-
let styleLine =
|
|
4019
|
+
let styleLine = localize(
|
|
4020
|
+
locale,
|
|
4021
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u7A7A\uFF09",
|
|
4022
|
+
" .mancode/aesthetics/ # style-tokens.json (empty)"
|
|
4023
|
+
);
|
|
3808
4024
|
if (profile.uiAssets === "detected") {
|
|
3809
|
-
console.log(
|
|
4025
|
+
console.log(
|
|
4026
|
+
localize(
|
|
4027
|
+
locale,
|
|
4028
|
+
"\u2713 \u626B\u63CF\u5BA1\u7F8E token...",
|
|
4029
|
+
"\u2713 Scanning design tokens..."
|
|
4030
|
+
)
|
|
4031
|
+
);
|
|
3810
4032
|
const tokens = await scanAesthetics(rootDir, uiLibrary);
|
|
3811
|
-
const tokensPath =
|
|
4033
|
+
const tokensPath = path14.join(
|
|
3812
4034
|
mancodeDir,
|
|
3813
4035
|
"aesthetics",
|
|
3814
4036
|
"style-tokens.json"
|
|
3815
4037
|
);
|
|
3816
|
-
await
|
|
4038
|
+
await fs3.writeFile(
|
|
3817
4039
|
tokensPath,
|
|
3818
4040
|
`${JSON.stringify(tokens, null, 2)}
|
|
3819
4041
|
`,
|
|
@@ -3823,70 +4045,146 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
3823
4045
|
const colorCount = Object.keys(tokens.colors).length;
|
|
3824
4046
|
const fontCount = Object.keys(tokens.fonts).length;
|
|
3825
4047
|
console.log(
|
|
3826
|
-
|
|
4048
|
+
localize(
|
|
4049
|
+
locale,
|
|
4050
|
+
` ${colorCount} \u4E2A\u989C\u8272\uFF0C${fontCount} \u4E2A\u5B57\u4F53\uFF08\u5339\u914D\u5EA6\uFF1A\u9AD8\uFF09`,
|
|
4051
|
+
` ${colorCount} colors, ${fontCount} fonts (match: high)`
|
|
4052
|
+
)
|
|
4053
|
+
);
|
|
4054
|
+
styleLine = localize(
|
|
4055
|
+
locale,
|
|
4056
|
+
` .mancode/aesthetics/ # style-tokens.json\uFF08${colorCount} \u4E2A\u989C\u8272\uFF09`,
|
|
4057
|
+
` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`
|
|
3827
4058
|
);
|
|
3828
|
-
styleLine = ` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`;
|
|
3829
4059
|
} else if (tokens.matchLevel === "low") {
|
|
3830
4060
|
console.log(
|
|
3831
|
-
|
|
4061
|
+
localize(
|
|
4062
|
+
locale,
|
|
4063
|
+
" \u5DF2\u68C0\u6D4B\u5230 UI \u8D44\u6E90\uFF0C\u672A\u627E\u5230\u53EF\u590D\u7528 token\uFF08\u5339\u914D\u5EA6\uFF1A\u4F4E\uFF09",
|
|
4064
|
+
" UI assets detected; no reusable tokens found (match: low)"
|
|
4065
|
+
)
|
|
4066
|
+
);
|
|
4067
|
+
styleLine = localize(
|
|
4068
|
+
locale,
|
|
4069
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u4F4E\u5339\u914D\uFF09",
|
|
4070
|
+
" .mancode/aesthetics/ # style-tokens.json (low match)"
|
|
3832
4071
|
);
|
|
3833
|
-
styleLine = " .mancode/aesthetics/ # style-tokens.json (low match)";
|
|
3834
4072
|
} else {
|
|
3835
|
-
console.log(
|
|
3836
|
-
|
|
4073
|
+
console.log(
|
|
4074
|
+
localize(
|
|
4075
|
+
locale,
|
|
4076
|
+
" \u672A\u627E\u5230\u8BBE\u8BA1 token\uFF08\u65E0\u5339\u914D\uFF09",
|
|
4077
|
+
" No design tokens found (match: none)"
|
|
4078
|
+
)
|
|
4079
|
+
);
|
|
4080
|
+
styleLine = localize(
|
|
4081
|
+
locale,
|
|
4082
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u65E0 token\uFF09",
|
|
4083
|
+
" .mancode/aesthetics/ # style-tokens.json (no tokens)"
|
|
4084
|
+
);
|
|
3837
4085
|
}
|
|
3838
4086
|
}
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
4087
|
+
for (const installer of typedInstallers) {
|
|
4088
|
+
console.log(
|
|
4089
|
+
localize(
|
|
4090
|
+
locale,
|
|
4091
|
+
`\u2713 \u5B89\u88C5 ${installer.displayName} \u9002\u914D\u5668...`,
|
|
4092
|
+
`\u2713 Installing ${installer.displayName} adapter...`
|
|
4093
|
+
)
|
|
4094
|
+
);
|
|
4095
|
+
await installer.install(rootDir, {
|
|
4096
|
+
techStack: profileStack,
|
|
4097
|
+
uiLibrary,
|
|
4098
|
+
projectProfile: profile,
|
|
4099
|
+
minimal: initialMinimal,
|
|
4100
|
+
force: options.force
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
3847
4103
|
console.log("");
|
|
3848
|
-
console.log(
|
|
4104
|
+
console.log(
|
|
4105
|
+
localize(locale, "\u2713 mancode \u521D\u59CB\u5316\u5B8C\u6210\u3002", "\u2713 mancode initialized.")
|
|
4106
|
+
);
|
|
3849
4107
|
console.log("");
|
|
3850
|
-
console.log("Created:");
|
|
3851
|
-
console.log(
|
|
3852
|
-
|
|
3853
|
-
|
|
4108
|
+
console.log(localize(locale, "\u5DF2\u521B\u5EFA\uFF1A", "Created:"));
|
|
4109
|
+
console.log(
|
|
4110
|
+
localize(
|
|
4111
|
+
locale,
|
|
4112
|
+
" .mancode/state.json # \u9879\u76EE\u72B6\u6001",
|
|
4113
|
+
" .mancode/state.json # project state"
|
|
4114
|
+
)
|
|
4115
|
+
);
|
|
3854
4116
|
console.log(
|
|
3855
|
-
|
|
4117
|
+
localize(
|
|
4118
|
+
locale,
|
|
4119
|
+
" .mancode/project-profile.json # \u68C0\u6D4B\u5230\u7684\u9879\u76EE\u4E8B\u5B9E",
|
|
4120
|
+
" .mancode/project-profile.json # detected project facts"
|
|
4121
|
+
)
|
|
4122
|
+
);
|
|
4123
|
+
console.log(
|
|
4124
|
+
localize(
|
|
4125
|
+
locale,
|
|
4126
|
+
" .mancode/config.json # \u914D\u7F6E",
|
|
4127
|
+
" .mancode/config.json # configuration"
|
|
4128
|
+
)
|
|
4129
|
+
);
|
|
4130
|
+
console.log(
|
|
4131
|
+
localize(
|
|
4132
|
+
locale,
|
|
4133
|
+
" .mancode/hooks/ # SessionStart + UserPromptSubmit",
|
|
4134
|
+
" .mancode/hooks/ # SessionStart + UserPromptSubmit"
|
|
4135
|
+
)
|
|
3856
4136
|
);
|
|
3857
4137
|
console.log(styleLine);
|
|
3858
4138
|
console.log(" .mancode/logs/ # hooks.log");
|
|
3859
|
-
|
|
4139
|
+
for (const platform of selectedPlatforms)
|
|
4140
|
+
printPlatformCreatedFiles(platform, locale);
|
|
3860
4141
|
console.log("");
|
|
3861
|
-
console.log("Next:");
|
|
3862
|
-
console.log(
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
4142
|
+
console.log(localize(locale, "\u4E0B\u4E00\u6B65\uFF1A", "Next:"));
|
|
4143
|
+
console.log(
|
|
4144
|
+
localize(
|
|
4145
|
+
locale,
|
|
4146
|
+
" mancode status # \u663E\u793A\u9879\u76EE\u72B6\u6001",
|
|
4147
|
+
" mancode status # Show project state"
|
|
4148
|
+
)
|
|
4149
|
+
);
|
|
4150
|
+
if (selectedPlatforms.includes("claude-code")) {
|
|
4151
|
+
console.log(
|
|
4152
|
+
localize(
|
|
4153
|
+
locale,
|
|
4154
|
+
" \uFF08\u91CD\u542F Claude Code \u4EE5\u52A0\u8F7D hooks\uFF09",
|
|
4155
|
+
" (Restart Claude Code to load hooks)"
|
|
4156
|
+
)
|
|
4157
|
+
);
|
|
4158
|
+
}
|
|
4159
|
+
if (selectedPlatforms.includes("codex")) {
|
|
3866
4160
|
console.log(
|
|
3867
|
-
|
|
4161
|
+
localize(
|
|
4162
|
+
locale,
|
|
4163
|
+
" \uFF08\u5982\u679C skills \u672A\u51FA\u73B0\uFF0C\u8BF7\u91CD\u542F ChatGPT \u684C\u9762\u5E94\u7528\u6216 Codex \u4F1A\u8BDD\uFF09",
|
|
4164
|
+
" (If skills do not appear, restart the ChatGPT desktop app or Codex session)"
|
|
4165
|
+
)
|
|
3868
4166
|
);
|
|
3869
4167
|
}
|
|
3870
4168
|
return EXIT_OK;
|
|
3871
4169
|
} catch (err) {
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
} else {
|
|
3875
|
-
await fs2.rm(stateFile, { force: true });
|
|
3876
|
-
await fs2.rm(path13.join(mancodeDir, "project-profile.json"), {
|
|
3877
|
-
force: true
|
|
3878
|
-
});
|
|
3879
|
-
}
|
|
4170
|
+
await restoreFiles(mutationSnapshots);
|
|
4171
|
+
await removeNewEmptyDirectories(directorySnapshots);
|
|
3880
4172
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3881
|
-
console.error(
|
|
4173
|
+
console.error(
|
|
4174
|
+
localize(
|
|
4175
|
+
locale,
|
|
4176
|
+
`\u2717 mancode \u521D\u59CB\u5316\u5931\u8D25\uFF1A${msg}`,
|
|
4177
|
+
`\u2717 mancode init failed: ${msg}`
|
|
4178
|
+
)
|
|
4179
|
+
);
|
|
3882
4180
|
return EXIT_INIT_FAILED;
|
|
3883
4181
|
}
|
|
3884
4182
|
}
|
|
3885
4183
|
async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms) {
|
|
3886
|
-
const configPath =
|
|
4184
|
+
const configPath = path14.join(mancodeDir, "config.json");
|
|
3887
4185
|
let config = {};
|
|
3888
4186
|
try {
|
|
3889
|
-
config = JSON.parse(await
|
|
4187
|
+
config = JSON.parse(await fs3.readFile(configPath, "utf-8"));
|
|
3890
4188
|
} catch {
|
|
3891
4189
|
}
|
|
3892
4190
|
const existingPlatforms = Array.isArray(config.platforms) ? config.platforms.filter(
|
|
@@ -3906,7 +4204,7 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
3906
4204
|
...patch.platformOptions
|
|
3907
4205
|
}
|
|
3908
4206
|
} : definedPatch;
|
|
3909
|
-
await
|
|
4207
|
+
await fs3.writeFile(
|
|
3910
4208
|
configPath,
|
|
3911
4209
|
`${JSON.stringify({ ...config, ...mergedPatch }, null, 2)}
|
|
3912
4210
|
`,
|
|
@@ -3916,36 +4214,182 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
3916
4214
|
async function readExistingInitPreferences(mancodeDir, platform) {
|
|
3917
4215
|
try {
|
|
3918
4216
|
const config = JSON.parse(
|
|
3919
|
-
await
|
|
4217
|
+
await fs3.readFile(path14.join(mancodeDir, "config.json"), "utf-8")
|
|
3920
4218
|
);
|
|
3921
4219
|
const preferences = {};
|
|
3922
4220
|
if (typeof config.forceTeamMode === "boolean") {
|
|
3923
4221
|
preferences.forceTeamMode = config.forceTeamMode;
|
|
3924
4222
|
}
|
|
3925
|
-
if (
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
4223
|
+
if (config.teamMode === "auto" || config.teamMode === "on" || config.teamMode === "off") {
|
|
4224
|
+
preferences.teamMode = config.teamMode;
|
|
4225
|
+
}
|
|
4226
|
+
if (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
|
|
4227
|
+
const platformOptions = config.platformOptions[platform];
|
|
4228
|
+
if (isRecord3(platformOptions) && platformOptions.minimal === true) {
|
|
4229
|
+
preferences.minimal = true;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
return preferences;
|
|
4233
|
+
} catch {
|
|
4234
|
+
return {};
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
async function readExistingInitPlatform(stateFile) {
|
|
4238
|
+
try {
|
|
4239
|
+
const state = JSON.parse(await fs3.readFile(stateFile, "utf-8"));
|
|
4240
|
+
return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
|
|
4241
|
+
} catch {
|
|
4242
|
+
return null;
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
async function selectInitPlatforms(input) {
|
|
4246
|
+
if (input.option !== void 0) {
|
|
4247
|
+
return parsePlatformSelection(input.option);
|
|
4248
|
+
}
|
|
4249
|
+
if (input.existingPlatform) return [input.existingPlatform];
|
|
4250
|
+
if (input.yes && input.interactive !== void 0) {
|
|
4251
|
+
return input.hints.length === 1 ? input.hints : null;
|
|
4252
|
+
}
|
|
4253
|
+
if (input.interactive) {
|
|
4254
|
+
const prompter = input.prompter ?? createTerminalPrompter();
|
|
4255
|
+
return prompter.selectPlatforms({
|
|
4256
|
+
locale: input.locale,
|
|
4257
|
+
detected: input.hints
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
if (input.interactive === false) {
|
|
4261
|
+
return input.hints.length === 1 ? input.hints : null;
|
|
4262
|
+
}
|
|
4263
|
+
return [DEFAULT_INIT_PLATFORM];
|
|
4264
|
+
}
|
|
4265
|
+
async function hasProjectManifest(rootDir) {
|
|
4266
|
+
for (const name of PROJECT_MANIFESTS) {
|
|
4267
|
+
if (await pathExists3(path14.join(rootDir, name))) return true;
|
|
4268
|
+
}
|
|
4269
|
+
return false;
|
|
4270
|
+
}
|
|
4271
|
+
async function canInitializeGenericProject(rootDir) {
|
|
4272
|
+
const resolved = path14.resolve(rootDir);
|
|
4273
|
+
if (resolved === path14.parse(resolved).root || resolved === path14.resolve(os.homedir())) {
|
|
4274
|
+
return { ok: false, reason: "unsafe" };
|
|
4275
|
+
}
|
|
4276
|
+
try {
|
|
4277
|
+
const entries = await fs3.readdir(resolved);
|
|
4278
|
+
const meaningfulEntries = entries.filter(
|
|
4279
|
+
(entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
|
|
4280
|
+
);
|
|
4281
|
+
return meaningfulEntries.length === 0 ? { ok: true } : { ok: false, reason: "nonempty" };
|
|
4282
|
+
} catch {
|
|
4283
|
+
return { ok: false, reason: "unsafe" };
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
function printNotProjectDirectory(rootDir, locale, reason) {
|
|
4287
|
+
if (locale === "zh-CN") {
|
|
4288
|
+
console.error(`\u2717 \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u53EF\u521D\u59CB\u5316\u7684\u9879\u76EE\u76EE\u5F55\uFF1A${rootDir}`);
|
|
4289
|
+
if (reason === "unsafe") {
|
|
4290
|
+
console.error(" \u4E3A\u907F\u514D\u8BEF\u5199\uFF0C\u4E0D\u80FD\u5728 Home \u76EE\u5F55\u6216\u78C1\u76D8\u6839\u76EE\u5F55\u521D\u59CB\u5316\u3002");
|
|
4291
|
+
} else if (reason === "nonempty") {
|
|
4292
|
+
console.error(
|
|
4293
|
+
" \u672A\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\uFF0C\u4E14\u76EE\u5F55\u4E2D\u5DF2\u6709\u6587\u4EF6\u3002\u8BF7\u5148\u8FDB\u5165\u9879\u76EE\u76EE\u5F55\u3002"
|
|
4294
|
+
);
|
|
4295
|
+
} else {
|
|
4296
|
+
console.error(
|
|
4297
|
+
" \u672A\u53D1\u73B0 .git \u6216\u9879\u76EE\u6587\u4EF6\u3002\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u53EF\u786E\u8BA4\u901A\u7528\u9879\u76EE\uFF0C\u6216\u4F7F\u7528 --empty\u3002"
|
|
4298
|
+
);
|
|
4299
|
+
}
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4302
|
+
console.error(`\u2717 Not a project directory: ${rootDir}`);
|
|
4303
|
+
if (reason === "unsafe") {
|
|
4304
|
+
console.error(
|
|
4305
|
+
" Refusing to initialize a home directory or filesystem root."
|
|
4306
|
+
);
|
|
4307
|
+
} else if (reason === "nonempty") {
|
|
4308
|
+
console.error(
|
|
4309
|
+
" No project files were detected and the directory is not empty."
|
|
4310
|
+
);
|
|
4311
|
+
} else {
|
|
4312
|
+
console.error(
|
|
4313
|
+
" No .git or recognized project manifest found. Use an interactive terminal or --empty for a new project."
|
|
4314
|
+
);
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
function getInitManagedFilePaths(rootDir, platforms) {
|
|
4318
|
+
const files = [
|
|
4319
|
+
".mancode/state.json",
|
|
4320
|
+
".mancode/config.json",
|
|
4321
|
+
".mancode/project-profile.json",
|
|
4322
|
+
".mancode/aesthetics/style-tokens.json",
|
|
4323
|
+
".mancode/hooks/session-start.mjs",
|
|
4324
|
+
".mancode/hooks/user-prompt-submit.mjs",
|
|
4325
|
+
".mancode/logs/hooks.log",
|
|
4326
|
+
".mancode/memory/prd.md",
|
|
4327
|
+
".mancode/memory/spec.md",
|
|
4328
|
+
".mancode/memory/decisions.md"
|
|
4329
|
+
];
|
|
4330
|
+
if (platforms.includes("claude-code")) {
|
|
4331
|
+
files.push(
|
|
4332
|
+
".mancode/hooks/session-start.sh",
|
|
4333
|
+
".mancode/hooks/user-prompt-submit.sh",
|
|
4334
|
+
".claude/settings.json",
|
|
4335
|
+
".claude/skills/man8/SKILL.md",
|
|
4336
|
+
".claude/skills/solo/SKILL.md",
|
|
4337
|
+
".claude/skills/mancode-solo.md",
|
|
4338
|
+
".claude/skills/mancode-man8.md"
|
|
4339
|
+
);
|
|
4340
|
+
for (const mode of MODE_NAMES) {
|
|
4341
|
+
files.push(
|
|
4342
|
+
`.claude/skills/${mode}/SKILL.md`,
|
|
4343
|
+
`.claude/skills/mancode-${mode}.md`
|
|
4344
|
+
);
|
|
4345
|
+
}
|
|
4346
|
+
for (const agent of ALL_AGENTS) {
|
|
4347
|
+
files.push(`.claude/agents/${agent.name}.md`);
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
if (platforms.includes("cursor")) {
|
|
4351
|
+
files.push(".cursor/rules/mancode-man8.mdc", ".cursor/commands/man8.md");
|
|
4352
|
+
for (const fileName of MANCODE_CURSOR_RULE_FILES) {
|
|
4353
|
+
files.push(`.cursor/rules/${fileName}`);
|
|
4354
|
+
}
|
|
4355
|
+
for (const mode of MODE_NAMES) {
|
|
4356
|
+
files.push(`.cursor/commands/${mode}.md`);
|
|
3930
4357
|
}
|
|
3931
|
-
return preferences;
|
|
3932
|
-
} catch {
|
|
3933
|
-
return {};
|
|
3934
4358
|
}
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
}
|
|
3941
|
-
|
|
4359
|
+
if (platforms.includes("codex") || platforms.includes("zcode")) {
|
|
4360
|
+
files.push("AGENTS.md", ".agents/skills/man8/SKILL.md");
|
|
4361
|
+
for (const mode of MODE_NAMES) {
|
|
4362
|
+
files.push(`.agents/skills/${mode}/SKILL.md`);
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
if (platforms.includes("codex")) {
|
|
4366
|
+
files.push(".codex/skills/man8/SKILL.md");
|
|
4367
|
+
for (const mode of MODE_NAMES) {
|
|
4368
|
+
files.push(`.codex/skills/${mode}/SKILL.md`);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
if (platforms.includes("zcode")) {
|
|
4372
|
+
files.push(".zcode/skills/man8/SKILL.md");
|
|
4373
|
+
for (const mode of MODE_NAMES) {
|
|
4374
|
+
files.push(`.zcode/skills/${mode}/SKILL.md`);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
if (platforms.includes("copilot")) {
|
|
4378
|
+
files.push(
|
|
4379
|
+
".github/copilot-instructions.md",
|
|
4380
|
+
".github/prompts/man8.prompt.md"
|
|
4381
|
+
);
|
|
4382
|
+
for (const mode of MODE_NAMES) {
|
|
4383
|
+
files.push(`.github/prompts/${mode}.prompt.md`);
|
|
4384
|
+
}
|
|
3942
4385
|
}
|
|
4386
|
+
return [...new Set(files.map((file) => path14.join(rootDir, file)))];
|
|
3943
4387
|
}
|
|
3944
4388
|
async function snapshotFiles(filePaths) {
|
|
3945
4389
|
return Promise.all(
|
|
3946
4390
|
filePaths.map(async (filePath) => {
|
|
3947
4391
|
try {
|
|
3948
|
-
return { filePath, content: await
|
|
4392
|
+
return { filePath, content: await fs3.readFile(filePath, "utf-8") };
|
|
3949
4393
|
} catch (error) {
|
|
3950
4394
|
if (isNodeError3(error) && error.code === "ENOENT") {
|
|
3951
4395
|
return { filePath, content: null };
|
|
@@ -3955,14 +4399,39 @@ async function snapshotFiles(filePaths) {
|
|
|
3955
4399
|
})
|
|
3956
4400
|
);
|
|
3957
4401
|
}
|
|
4402
|
+
async function snapshotDirectories(filePaths, additionalDirectories = []) {
|
|
4403
|
+
const directories = new Set(additionalDirectories);
|
|
4404
|
+
for (const filePath of filePaths) {
|
|
4405
|
+
let current = path14.dirname(filePath);
|
|
4406
|
+
while (current !== path14.dirname(current)) {
|
|
4407
|
+
directories.add(current);
|
|
4408
|
+
current = path14.dirname(current);
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
return Promise.all(
|
|
4412
|
+
[...directories].map(async (dirPath) => ({
|
|
4413
|
+
dirPath,
|
|
4414
|
+
existed: await pathExists3(dirPath)
|
|
4415
|
+
}))
|
|
4416
|
+
);
|
|
4417
|
+
}
|
|
3958
4418
|
async function restoreFiles(snapshots) {
|
|
3959
4419
|
for (const snapshot of snapshots) {
|
|
3960
4420
|
if (snapshot.content === null) {
|
|
3961
|
-
await
|
|
4421
|
+
await fs3.rm(snapshot.filePath, { force: true });
|
|
3962
4422
|
continue;
|
|
3963
4423
|
}
|
|
3964
|
-
await
|
|
3965
|
-
await
|
|
4424
|
+
await fs3.mkdir(path14.dirname(snapshot.filePath), { recursive: true });
|
|
4425
|
+
await fs3.writeFile(snapshot.filePath, snapshot.content, "utf-8");
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
async function removeNewEmptyDirectories(snapshots) {
|
|
4429
|
+
const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
|
|
4430
|
+
for (const { dirPath } of candidates) {
|
|
4431
|
+
try {
|
|
4432
|
+
await fs3.rmdir(dirPath);
|
|
4433
|
+
} catch {
|
|
4434
|
+
}
|
|
3966
4435
|
}
|
|
3967
4436
|
}
|
|
3968
4437
|
function isRecord3(value) {
|
|
@@ -3971,10 +4440,22 @@ function isRecord3(value) {
|
|
|
3971
4440
|
function isNodeError3(error) {
|
|
3972
4441
|
return error instanceof Error && "code" in error;
|
|
3973
4442
|
}
|
|
3974
|
-
function printPlatformCreatedFiles(platform) {
|
|
4443
|
+
function printPlatformCreatedFiles(platform, locale) {
|
|
3975
4444
|
if (platform === "claude-code") {
|
|
3976
|
-
console.log(
|
|
3977
|
-
|
|
4445
|
+
console.log(
|
|
4446
|
+
localize(
|
|
4447
|
+
locale,
|
|
4448
|
+
" .claude/settings.json # hook \u6CE8\u518C",
|
|
4449
|
+
" .claude/settings.json # hook registration"
|
|
4450
|
+
)
|
|
4451
|
+
);
|
|
4452
|
+
console.log(
|
|
4453
|
+
localize(
|
|
4454
|
+
locale,
|
|
4455
|
+
" .claude/skills/ # solo + MVP-2 slash skills",
|
|
4456
|
+
" .claude/skills/ # solo + MVP-2 slash skills"
|
|
4457
|
+
)
|
|
4458
|
+
);
|
|
3978
4459
|
return;
|
|
3979
4460
|
}
|
|
3980
4461
|
if (platform === "cursor") {
|
|
@@ -3993,9 +4474,12 @@ function printPlatformCreatedFiles(platform) {
|
|
|
3993
4474
|
}
|
|
3994
4475
|
console.log(" .github/copilot-instructions.md # Copilot instructions");
|
|
3995
4476
|
}
|
|
4477
|
+
function localize(locale, chinese, english) {
|
|
4478
|
+
return locale === "zh-CN" ? chinese : english;
|
|
4479
|
+
}
|
|
3996
4480
|
async function pathExists3(p) {
|
|
3997
4481
|
try {
|
|
3998
|
-
await
|
|
4482
|
+
await fs3.access(p);
|
|
3999
4483
|
return true;
|
|
4000
4484
|
} catch {
|
|
4001
4485
|
return false;
|
|
@@ -4003,13 +4487,13 @@ async function pathExists3(p) {
|
|
|
4003
4487
|
}
|
|
4004
4488
|
|
|
4005
4489
|
// src/commands/install.ts
|
|
4006
|
-
import { promises as
|
|
4007
|
-
import
|
|
4008
|
-
import
|
|
4490
|
+
import { promises as fs5 } from "fs";
|
|
4491
|
+
import path16 from "path";
|
|
4492
|
+
import process6 from "process";
|
|
4009
4493
|
|
|
4010
4494
|
// src/installers/platform-status.ts
|
|
4011
|
-
import { promises as
|
|
4012
|
-
import
|
|
4495
|
+
import { promises as fs4 } from "fs";
|
|
4496
|
+
import path15 from "path";
|
|
4013
4497
|
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
4014
4498
|
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
4015
4499
|
return {
|
|
@@ -4023,13 +4507,13 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4023
4507
|
if (platform === "claude-code") {
|
|
4024
4508
|
const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
|
|
4025
4509
|
fileMatches(
|
|
4026
|
-
|
|
4510
|
+
path15.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
|
|
4027
4511
|
(content) => isGeneratedClaudeSkill(content, "solo")
|
|
4028
4512
|
),
|
|
4029
4513
|
claudeHooksRegistered(rootDir),
|
|
4030
4514
|
pathsExist([
|
|
4031
|
-
|
|
4032
|
-
|
|
4515
|
+
path15.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
4516
|
+
path15.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
4033
4517
|
])
|
|
4034
4518
|
]);
|
|
4035
4519
|
const present = hasSoloSkill && registered && hasHookFiles;
|
|
@@ -4045,7 +4529,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4045
4529
|
if (platform === "cursor") {
|
|
4046
4530
|
const hasCoreRules = await allManagedSkills(
|
|
4047
4531
|
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
4048
|
-
(file) =>
|
|
4532
|
+
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4049
4533
|
),
|
|
4050
4534
|
[CURSOR_RULE_MANAGED_MARKER]
|
|
4051
4535
|
);
|
|
@@ -4060,13 +4544,13 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4060
4544
|
const [hasRules, hasCommands] = await Promise.all([
|
|
4061
4545
|
allManagedSkills(
|
|
4062
4546
|
MANCODE_CURSOR_RULE_FILES.map(
|
|
4063
|
-
(file) =>
|
|
4547
|
+
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4064
4548
|
),
|
|
4065
4549
|
[CURSOR_RULE_MANAGED_MARKER]
|
|
4066
4550
|
),
|
|
4067
4551
|
allManagedSkills(
|
|
4068
4552
|
MODE_NAMES.map(
|
|
4069
|
-
(mode) =>
|
|
4553
|
+
(mode) => path15.join(rootDir, ".cursor", "commands", `${mode}.md`)
|
|
4070
4554
|
),
|
|
4071
4555
|
[MODE_FILE_MANAGED_MARKER]
|
|
4072
4556
|
)
|
|
@@ -4079,7 +4563,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4079
4563
|
};
|
|
4080
4564
|
}
|
|
4081
4565
|
if (platform === "codex") {
|
|
4082
|
-
const hasBlock2 = await fileHasManagedBlock(
|
|
4566
|
+
const hasBlock2 = await fileHasManagedBlock(path15.join(rootDir, "AGENTS.md"));
|
|
4083
4567
|
if (!hasBlock2) {
|
|
4084
4568
|
return {
|
|
4085
4569
|
present: false,
|
|
@@ -4096,9 +4580,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4096
4580
|
readyDetail: "managed block present"
|
|
4097
4581
|
};
|
|
4098
4582
|
}
|
|
4099
|
-
const skillsDir =
|
|
4583
|
+
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4100
4584
|
const hasSkills = await allManagedSkills(
|
|
4101
|
-
MODE_NAMES.map((mode) =>
|
|
4585
|
+
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4102
4586
|
MANCODE_AGENT_SKILL_MARKERS
|
|
4103
4587
|
);
|
|
4104
4588
|
return {
|
|
@@ -4110,7 +4594,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4110
4594
|
}
|
|
4111
4595
|
if (platform === "zcode") {
|
|
4112
4596
|
const hasBlock2 = await fileHasManagedBlock(
|
|
4113
|
-
|
|
4597
|
+
path15.join(rootDir, "AGENTS.md"),
|
|
4114
4598
|
ZCODE_MANCODE_START_MARKER,
|
|
4115
4599
|
ZCODE_MANCODE_END_MARKER
|
|
4116
4600
|
);
|
|
@@ -4130,9 +4614,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4130
4614
|
readyDetail: "managed block present"
|
|
4131
4615
|
};
|
|
4132
4616
|
}
|
|
4133
|
-
const skillsDir =
|
|
4617
|
+
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4134
4618
|
const hasSkills = await allManagedSkills(
|
|
4135
|
-
MODE_NAMES.map((mode) =>
|
|
4619
|
+
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4136
4620
|
MANCODE_AGENT_SKILL_MARKERS
|
|
4137
4621
|
);
|
|
4138
4622
|
return {
|
|
@@ -4143,7 +4627,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4143
4627
|
};
|
|
4144
4628
|
}
|
|
4145
4629
|
const hasBlock = await fileHasManagedBlock(
|
|
4146
|
-
|
|
4630
|
+
path15.join(rootDir, ".github", "copilot-instructions.md")
|
|
4147
4631
|
);
|
|
4148
4632
|
if (!hasBlock) {
|
|
4149
4633
|
return {
|
|
@@ -4154,9 +4638,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4154
4638
|
};
|
|
4155
4639
|
}
|
|
4156
4640
|
if (!await isPlatformMinimal(rootDir, "copilot")) {
|
|
4157
|
-
const promptsDir =
|
|
4641
|
+
const promptsDir = path15.join(rootDir, ".github", "prompts");
|
|
4158
4642
|
const hasPrompts = await allManagedSkills(
|
|
4159
|
-
MODE_NAMES.map((mode) =>
|
|
4643
|
+
MODE_NAMES.map((mode) => path15.join(promptsDir, `${mode}.prompt.md`)),
|
|
4160
4644
|
[MODE_FILE_MANAGED_MARKER]
|
|
4161
4645
|
);
|
|
4162
4646
|
return {
|
|
@@ -4189,13 +4673,13 @@ async function allManagedSkills(paths, markers) {
|
|
|
4189
4673
|
async function claudeFullContentReady(rootDir) {
|
|
4190
4674
|
const skillChecks = MVP2_SKILLS.map(
|
|
4191
4675
|
(skill) => fileMatches(
|
|
4192
|
-
|
|
4676
|
+
path15.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
|
|
4193
4677
|
(content) => isGeneratedClaudeSkill(content, skill.name)
|
|
4194
4678
|
)
|
|
4195
4679
|
);
|
|
4196
4680
|
const agentChecks = ALL_AGENTS.map(
|
|
4197
4681
|
(agent) => fileMatches(
|
|
4198
|
-
|
|
4682
|
+
path15.join(rootDir, ".claude", "agents", `${agent.name}.md`),
|
|
4199
4683
|
(content) => isGeneratedClaudeAgent(content, agent.name)
|
|
4200
4684
|
)
|
|
4201
4685
|
);
|
|
@@ -4204,7 +4688,7 @@ async function claudeFullContentReady(rootDir) {
|
|
|
4204
4688
|
}
|
|
4205
4689
|
async function fileMatches(filePath, predicate) {
|
|
4206
4690
|
try {
|
|
4207
|
-
return predicate(await
|
|
4691
|
+
return predicate(await fs4.readFile(filePath, "utf-8"));
|
|
4208
4692
|
} catch {
|
|
4209
4693
|
return false;
|
|
4210
4694
|
}
|
|
@@ -4214,7 +4698,7 @@ async function pathsExist(paths) {
|
|
|
4214
4698
|
}
|
|
4215
4699
|
async function fileHasAnyMarker(filePath, needles) {
|
|
4216
4700
|
try {
|
|
4217
|
-
const content = await
|
|
4701
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
4218
4702
|
return needles.some((needle) => content.includes(needle));
|
|
4219
4703
|
} catch {
|
|
4220
4704
|
return false;
|
|
@@ -4222,8 +4706,8 @@ async function fileHasAnyMarker(filePath, needles) {
|
|
|
4222
4706
|
}
|
|
4223
4707
|
async function isPlatformMinimal(rootDir, platform) {
|
|
4224
4708
|
try {
|
|
4225
|
-
const raw = await
|
|
4226
|
-
|
|
4709
|
+
const raw = await fs4.readFile(
|
|
4710
|
+
path15.join(rootDir, ".mancode", "config.json"),
|
|
4227
4711
|
"utf-8"
|
|
4228
4712
|
);
|
|
4229
4713
|
const config = JSON.parse(raw);
|
|
@@ -4236,7 +4720,7 @@ async function isPlatformMinimal(rootDir, platform) {
|
|
|
4236
4720
|
}
|
|
4237
4721
|
async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
4238
4722
|
try {
|
|
4239
|
-
const content = await
|
|
4723
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
4240
4724
|
return hasManagedBlock(content, startMarker, endMarker);
|
|
4241
4725
|
} catch {
|
|
4242
4726
|
return false;
|
|
@@ -4244,8 +4728,8 @@ async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START
|
|
|
4244
4728
|
}
|
|
4245
4729
|
async function claudeHooksRegistered(rootDir) {
|
|
4246
4730
|
try {
|
|
4247
|
-
const raw = await
|
|
4248
|
-
|
|
4731
|
+
const raw = await fs4.readFile(
|
|
4732
|
+
path15.join(rootDir, ".claude", "settings.json"),
|
|
4249
4733
|
"utf-8"
|
|
4250
4734
|
);
|
|
4251
4735
|
const settings = JSON.parse(raw);
|
|
@@ -4258,9 +4742,9 @@ function hooksRegistered(settings) {
|
|
|
4258
4742
|
if (!isRecord4(settings)) return false;
|
|
4259
4743
|
const hooks = settings.hooks;
|
|
4260
4744
|
if (!isRecord4(hooks)) return false;
|
|
4261
|
-
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.
|
|
4745
|
+
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
|
|
4262
4746
|
hooks.UserPromptSubmit,
|
|
4263
|
-
".mancode/hooks/user-prompt-submit.
|
|
4747
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
4264
4748
|
);
|
|
4265
4749
|
}
|
|
4266
4750
|
function hasHookCommand(value, needle) {
|
|
@@ -4275,7 +4759,7 @@ function hasHookCommand(value, needle) {
|
|
|
4275
4759
|
}
|
|
4276
4760
|
async function pathExists4(p) {
|
|
4277
4761
|
try {
|
|
4278
|
-
await
|
|
4762
|
+
await fs4.access(p);
|
|
4279
4763
|
return true;
|
|
4280
4764
|
} catch {
|
|
4281
4765
|
return false;
|
|
@@ -4290,8 +4774,8 @@ var EXIT_OK2 = 0;
|
|
|
4290
4774
|
var EXIT_NOT_INITIALIZED = 1;
|
|
4291
4775
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|
|
4292
4776
|
var EXIT_INSTALL_FAILED = 3;
|
|
4293
|
-
async function install(rootDir =
|
|
4294
|
-
const stateFile =
|
|
4777
|
+
async function install(rootDir = process6.cwd(), platform = "claude-code", options = {}) {
|
|
4778
|
+
const stateFile = path16.join(rootDir, ".mancode", "state.json");
|
|
4295
4779
|
if (!await pathExists5(stateFile)) {
|
|
4296
4780
|
console.error("\u2717 mancode not initialized.");
|
|
4297
4781
|
console.error(" Run `mancode init` first.");
|
|
@@ -4377,9 +4861,9 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
|
|
|
4377
4861
|
return EXIT_OK2;
|
|
4378
4862
|
}
|
|
4379
4863
|
async function readConfig(rootDir) {
|
|
4380
|
-
const configPath =
|
|
4864
|
+
const configPath = path16.join(rootDir, ".mancode", "config.json");
|
|
4381
4865
|
try {
|
|
4382
|
-
const raw = await
|
|
4866
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
4383
4867
|
return { config: JSON.parse(raw), valid: true };
|
|
4384
4868
|
} catch (err) {
|
|
4385
4869
|
if (isNodeError4(err) && err.code === "ENOENT") {
|
|
@@ -4392,10 +4876,10 @@ function isNodeError4(err) {
|
|
|
4392
4876
|
return err instanceof Error && "code" in err;
|
|
4393
4877
|
}
|
|
4394
4878
|
async function updateConfig(rootDir, config) {
|
|
4395
|
-
const configPath =
|
|
4879
|
+
const configPath = path16.join(rootDir, ".mancode", "config.json");
|
|
4396
4880
|
const content = `${JSON.stringify(config, null, 2)}
|
|
4397
4881
|
`;
|
|
4398
|
-
await
|
|
4882
|
+
await fs5.writeFile(configPath, content, "utf-8");
|
|
4399
4883
|
}
|
|
4400
4884
|
function withDefaultConfig(config, fallbackPlatform) {
|
|
4401
4885
|
const fallback = fallbackPlatform ?? DEFAULT_CONFIG.platforms[0];
|
|
@@ -4427,8 +4911,8 @@ function readConfiguredMinimal(value, platform) {
|
|
|
4427
4911
|
}
|
|
4428
4912
|
async function readStatePlatform(rootDir) {
|
|
4429
4913
|
try {
|
|
4430
|
-
const raw = await
|
|
4431
|
-
|
|
4914
|
+
const raw = await fs5.readFile(
|
|
4915
|
+
path16.join(rootDir, ".mancode", "state.json"),
|
|
4432
4916
|
"utf-8"
|
|
4433
4917
|
);
|
|
4434
4918
|
const state = JSON.parse(raw);
|
|
@@ -4439,7 +4923,7 @@ async function readStatePlatform(rootDir) {
|
|
|
4439
4923
|
}
|
|
4440
4924
|
async function pathExists5(p) {
|
|
4441
4925
|
try {
|
|
4442
|
-
await
|
|
4926
|
+
await fs5.access(p);
|
|
4443
4927
|
return true;
|
|
4444
4928
|
} catch {
|
|
4445
4929
|
return false;
|
|
@@ -4450,11 +4934,11 @@ function isRecord5(value) {
|
|
|
4450
4934
|
}
|
|
4451
4935
|
|
|
4452
4936
|
// src/commands/list-platforms.ts
|
|
4453
|
-
import { promises as
|
|
4454
|
-
import
|
|
4455
|
-
import
|
|
4937
|
+
import { promises as fs6 } from "fs";
|
|
4938
|
+
import path17 from "path";
|
|
4939
|
+
import process7 from "process";
|
|
4456
4940
|
var EXIT_OK3 = 0;
|
|
4457
|
-
async function listPlatforms(rootDir =
|
|
4941
|
+
async function listPlatforms(rootDir = process7.cwd()) {
|
|
4458
4942
|
const installed = new Set(await readInstalledPlatforms(rootDir));
|
|
4459
4943
|
const platforms = getPlatformInstallers();
|
|
4460
4944
|
console.log("");
|
|
@@ -4467,8 +4951,8 @@ async function listPlatforms(rootDir = process5.cwd()) {
|
|
|
4467
4951
|
}
|
|
4468
4952
|
async function readInstalledPlatforms(rootDir) {
|
|
4469
4953
|
try {
|
|
4470
|
-
const raw = await
|
|
4471
|
-
|
|
4954
|
+
const raw = await fs6.readFile(
|
|
4955
|
+
path17.join(rootDir, ".mancode", "config.json"),
|
|
4472
4956
|
"utf-8"
|
|
4473
4957
|
);
|
|
4474
4958
|
const config = JSON.parse(raw);
|
|
@@ -4492,16 +4976,16 @@ function describePlatform(platform) {
|
|
|
4492
4976
|
}
|
|
4493
4977
|
|
|
4494
4978
|
// src/commands/manps.ts
|
|
4495
|
-
import { access as
|
|
4496
|
-
import
|
|
4497
|
-
import { createInterface } from "readline/promises";
|
|
4979
|
+
import { access as access4 } from "fs/promises";
|
|
4980
|
+
import path19 from "path";
|
|
4981
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
4498
4982
|
|
|
4499
4983
|
// src/system/preseason.ts
|
|
4500
|
-
import { execFile } from "child_process";
|
|
4984
|
+
import { execFile as execFile3 } from "child_process";
|
|
4501
4985
|
import { createHash } from "crypto";
|
|
4502
4986
|
import { existsSync, readFileSync } from "fs";
|
|
4503
4987
|
import {
|
|
4504
|
-
access as
|
|
4988
|
+
access as access3,
|
|
4505
4989
|
lstat,
|
|
4506
4990
|
mkdir as mkdir7,
|
|
4507
4991
|
readFile as readFile8,
|
|
@@ -4509,7 +4993,7 @@ import {
|
|
|
4509
4993
|
rename,
|
|
4510
4994
|
writeFile as writeFile9
|
|
4511
4995
|
} from "fs/promises";
|
|
4512
|
-
import
|
|
4996
|
+
import path18 from "path";
|
|
4513
4997
|
var PRESEASON_AREAS = [
|
|
4514
4998
|
"all",
|
|
4515
4999
|
"deps",
|
|
@@ -4589,9 +5073,9 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4589
5073
|
const needsFiles = normalizedArea === "all" || normalizedArea === "dead-code" || normalizedArea === "config";
|
|
4590
5074
|
const files = needsFiles ? await listProjectFiles(projectRoot) : [];
|
|
4591
5075
|
const issues = (await scanArea(projectRoot, normalizedArea, pkg, files)).slice(0, 20);
|
|
4592
|
-
const reportDir =
|
|
5076
|
+
const reportDir = path18.join(projectRoot, ".mancode", "preseason-reports");
|
|
4593
5077
|
await mkdir7(reportDir, { recursive: true });
|
|
4594
|
-
const issueDbPath =
|
|
5078
|
+
const issueDbPath = path18.join(
|
|
4595
5079
|
projectRoot,
|
|
4596
5080
|
".mancode",
|
|
4597
5081
|
"preseason-issues.json"
|
|
@@ -4611,7 +5095,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4611
5095
|
const database = await buildIssueDatabase(projectRoot, report);
|
|
4612
5096
|
await writeFile9(reportPath, renderPreseasonReport(report), "utf-8");
|
|
4613
5097
|
await writeFile9(
|
|
4614
|
-
|
|
5098
|
+
path18.join(projectRoot, ".mancode", "preseason-report.md"),
|
|
4615
5099
|
renderPreseasonReport(report),
|
|
4616
5100
|
"utf-8"
|
|
4617
5101
|
);
|
|
@@ -4619,7 +5103,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4619
5103
|
return report;
|
|
4620
5104
|
}
|
|
4621
5105
|
async function runPreseasonRemediation(projectRoot, issues, options = {}) {
|
|
4622
|
-
const issueDbPath =
|
|
5106
|
+
const issueDbPath = path18.join(
|
|
4623
5107
|
projectRoot,
|
|
4624
5108
|
".mancode",
|
|
4625
5109
|
"preseason-issues.json"
|
|
@@ -4733,7 +5217,7 @@ async function scanArea(projectRoot, area, pkg, files) {
|
|
|
4733
5217
|
async function allocateReportPath(reportDir, baseName) {
|
|
4734
5218
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
4735
5219
|
const suffix = attempt === 0 ? "" : `-${attempt + 1}`;
|
|
4736
|
-
const candidate =
|
|
5220
|
+
const candidate = path18.join(reportDir, `${baseName}${suffix}.md`);
|
|
4737
5221
|
if (!existsSync(candidate)) return candidate;
|
|
4738
5222
|
}
|
|
4739
5223
|
throw new Error(`unable to allocate preseason report path: ${baseName}`);
|
|
@@ -4782,8 +5266,8 @@ async function walk(root, current, results, depth) {
|
|
|
4782
5266
|
}
|
|
4783
5267
|
for (const entry of entries) {
|
|
4784
5268
|
if (IGNORE_DIRS.has(entry)) continue;
|
|
4785
|
-
const abs =
|
|
4786
|
-
const rel =
|
|
5269
|
+
const abs = path18.join(current, entry);
|
|
5270
|
+
const rel = path18.relative(root, abs);
|
|
4787
5271
|
let info;
|
|
4788
5272
|
try {
|
|
4789
5273
|
info = await lstat(abs);
|
|
@@ -4793,7 +5277,7 @@ async function walk(root, current, results, depth) {
|
|
|
4793
5277
|
if (info.isSymbolicLink()) continue;
|
|
4794
5278
|
if (info.isDirectory()) {
|
|
4795
5279
|
await walk(root, abs, results, depth + 1);
|
|
4796
|
-
} else if (SOURCE_EXTENSIONS.has(
|
|
5280
|
+
} else if (SOURCE_EXTENSIONS.has(path18.extname(entry)) || entry === "package.json") {
|
|
4797
5281
|
results.push(rel);
|
|
4798
5282
|
if (results.length >= MAX_PROJECT_FILES) return;
|
|
4799
5283
|
}
|
|
@@ -4801,7 +5285,7 @@ async function walk(root, current, results, depth) {
|
|
|
4801
5285
|
}
|
|
4802
5286
|
async function readPackageJson(projectRoot) {
|
|
4803
5287
|
try {
|
|
4804
|
-
const raw = await readFile8(
|
|
5288
|
+
const raw = await readFile8(path18.join(projectRoot, "package.json"), "utf-8");
|
|
4805
5289
|
return JSON.parse(raw);
|
|
4806
5290
|
} catch {
|
|
4807
5291
|
return null;
|
|
@@ -4894,7 +5378,7 @@ function scanTodos(projectRoot, files) {
|
|
|
4894
5378
|
const matches = [];
|
|
4895
5379
|
for (const file of files) {
|
|
4896
5380
|
if (matches.length >= 7) break;
|
|
4897
|
-
const abs =
|
|
5381
|
+
const abs = path18.join(projectRoot, file);
|
|
4898
5382
|
matches.push(...readTodoIssues(abs, file, matches.length));
|
|
4899
5383
|
}
|
|
4900
5384
|
return matches.slice(0, 7);
|
|
@@ -4931,7 +5415,7 @@ function scanTestGaps(files) {
|
|
|
4931
5415
|
if (sourceFiles.length === 0) return [];
|
|
4932
5416
|
const tests = new Set(files.filter((file) => file.startsWith("tests/")));
|
|
4933
5417
|
const missing = sourceFiles.filter((file) => {
|
|
4934
|
-
const base =
|
|
5418
|
+
const base = path18.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
4935
5419
|
return !Array.from(tests).some((test) => test.includes(base));
|
|
4936
5420
|
}).slice(0, 4);
|
|
4937
5421
|
return missing.map((file, index) => ({
|
|
@@ -4940,13 +5424,13 @@ function scanTestGaps(files) {
|
|
|
4940
5424
|
type: "tests",
|
|
4941
5425
|
title: "Core source file has no obvious test",
|
|
4942
5426
|
file,
|
|
4943
|
-
detail: `No matching test file was found for ${
|
|
5427
|
+
detail: `No matching test file was found for ${path18.basename(file)}.`,
|
|
4944
5428
|
recommendation: "Add focused coverage for the public behavior or document why this module is exercised indirectly."
|
|
4945
5429
|
}));
|
|
4946
5430
|
}
|
|
4947
5431
|
function scanConfig(projectRoot, files) {
|
|
4948
5432
|
const issues = [];
|
|
4949
|
-
if (!files.includes(".gitignore") && !pathExistsSync(
|
|
5433
|
+
if (!files.includes(".gitignore") && !pathExistsSync(path18.join(projectRoot, ".gitignore"))) {
|
|
4950
5434
|
issues.push({
|
|
4951
5435
|
id: "config-gitignore",
|
|
4952
5436
|
severity: "P1",
|
|
@@ -4957,7 +5441,7 @@ function scanConfig(projectRoot, files) {
|
|
|
4957
5441
|
recommendation: "Add a .gitignore that excludes dependencies, build output, coverage, and local env files."
|
|
4958
5442
|
});
|
|
4959
5443
|
}
|
|
4960
|
-
if (!files.includes(".editorconfig") && !pathExistsSync(
|
|
5444
|
+
if (!files.includes(".editorconfig") && !pathExistsSync(path18.join(projectRoot, ".editorconfig"))) {
|
|
4961
5445
|
issues.push({
|
|
4962
5446
|
id: "config-editorconfig",
|
|
4963
5447
|
severity: "P2",
|
|
@@ -4976,7 +5460,7 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
4976
5460
|
for (const file of frontendFiles) {
|
|
4977
5461
|
let content;
|
|
4978
5462
|
try {
|
|
4979
|
-
content = readFileSyncSafe(
|
|
5463
|
+
content = readFileSyncSafe(path18.join(projectRoot, file));
|
|
4980
5464
|
} catch {
|
|
4981
5465
|
continue;
|
|
4982
5466
|
}
|
|
@@ -4996,7 +5480,7 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
4996
5480
|
return issues;
|
|
4997
5481
|
}
|
|
4998
5482
|
async function scanArchitecture(projectRoot) {
|
|
4999
|
-
const localBinary =
|
|
5483
|
+
const localBinary = path18.join(
|
|
5000
5484
|
projectRoot,
|
|
5001
5485
|
"node_modules",
|
|
5002
5486
|
".bin",
|
|
@@ -5044,7 +5528,7 @@ async function hasDependencyCruiserConfig(projectRoot) {
|
|
|
5044
5528
|
"dependency-cruiser.config.mjs"
|
|
5045
5529
|
];
|
|
5046
5530
|
const results = await Promise.all(
|
|
5047
|
-
files.map((file) => pathExists6(
|
|
5531
|
+
files.map((file) => pathExists6(path18.join(projectRoot, file)))
|
|
5048
5532
|
);
|
|
5049
5533
|
return results.some(Boolean);
|
|
5050
5534
|
}
|
|
@@ -5061,7 +5545,7 @@ function architectureScannerUnavailableIssue() {
|
|
|
5061
5545
|
function runDepcruise(binary, projectRoot) {
|
|
5062
5546
|
return new Promise((resolve) => {
|
|
5063
5547
|
const needsShell = process.platform === "win32";
|
|
5064
|
-
|
|
5548
|
+
execFile3(
|
|
5065
5549
|
binary,
|
|
5066
5550
|
["--output-type", "json", "."],
|
|
5067
5551
|
{
|
|
@@ -5070,11 +5554,11 @@ function runDepcruise(binary, projectRoot) {
|
|
|
5070
5554
|
maxBuffer: DEPCRUISE_MAX_BUFFER,
|
|
5071
5555
|
shell: needsShell
|
|
5072
5556
|
},
|
|
5073
|
-
(error,
|
|
5557
|
+
(error, stdout2, stderr) => {
|
|
5074
5558
|
if (!error) {
|
|
5075
5559
|
resolve({
|
|
5076
5560
|
status: "ok",
|
|
5077
|
-
stdout,
|
|
5561
|
+
stdout: stdout2,
|
|
5078
5562
|
stderr,
|
|
5079
5563
|
exitCode: 0,
|
|
5080
5564
|
timedOut: false
|
|
@@ -5094,7 +5578,7 @@ function runDepcruise(binary, projectRoot) {
|
|
|
5094
5578
|
}
|
|
5095
5579
|
resolve({
|
|
5096
5580
|
status: "failed",
|
|
5097
|
-
stdout: nodeError.stdout ??
|
|
5581
|
+
stdout: nodeError.stdout ?? stdout2,
|
|
5098
5582
|
stderr: nodeError.stderr ?? stderr,
|
|
5099
5583
|
exitCode: typeof nodeError.code === "number" ? nodeError.code : null,
|
|
5100
5584
|
timedOut: Boolean(nodeError.killed || nodeError.signal === "SIGTERM")
|
|
@@ -5150,9 +5634,9 @@ function inferCommands(pkg) {
|
|
|
5150
5634
|
return ["lint", "test", "build"].filter((name) => scripts[name]).map((name) => `npm run ${name}`);
|
|
5151
5635
|
}
|
|
5152
5636
|
async function buildIssueDatabase(projectRoot, report) {
|
|
5153
|
-
const reportRef =
|
|
5637
|
+
const reportRef = path18.relative(projectRoot, report.reportPath);
|
|
5154
5638
|
const run = {
|
|
5155
|
-
id:
|
|
5639
|
+
id: path18.basename(report.reportPath, ".md"),
|
|
5156
5640
|
generatedAt: report.generatedAt,
|
|
5157
5641
|
area: report.area,
|
|
5158
5642
|
reportPath: reportRef,
|
|
@@ -5258,7 +5742,7 @@ function compareIssueRecords(a, b) {
|
|
|
5258
5742
|
}
|
|
5259
5743
|
async function applySafeRemediation(projectRoot, issue) {
|
|
5260
5744
|
if (issue.id === "config-gitignore" && issue.file === ".gitignore") {
|
|
5261
|
-
const gitignorePath =
|
|
5745
|
+
const gitignorePath = path18.join(projectRoot, ".gitignore");
|
|
5262
5746
|
if (pathExistsSync(gitignorePath)) {
|
|
5263
5747
|
return { applied: false };
|
|
5264
5748
|
}
|
|
@@ -5267,7 +5751,7 @@ async function applySafeRemediation(projectRoot, issue) {
|
|
|
5267
5751
|
return { applied: true, action: "created .gitignore" };
|
|
5268
5752
|
}
|
|
5269
5753
|
if (issue.id === "config-editorconfig" && issue.file === ".editorconfig") {
|
|
5270
|
-
const editorconfigPath =
|
|
5754
|
+
const editorconfigPath = path18.join(projectRoot, ".editorconfig");
|
|
5271
5755
|
if (pathExistsSync(editorconfigPath)) {
|
|
5272
5756
|
return { applied: false };
|
|
5273
5757
|
}
|
|
@@ -5307,7 +5791,7 @@ async function inferSafePackageScript(projectRoot, scriptName) {
|
|
|
5307
5791
|
}
|
|
5308
5792
|
}
|
|
5309
5793
|
async function addPackageScript(projectRoot, scriptName, script) {
|
|
5310
|
-
const packagePath =
|
|
5794
|
+
const packagePath = path18.join(projectRoot, "package.json");
|
|
5311
5795
|
let pkg;
|
|
5312
5796
|
try {
|
|
5313
5797
|
pkg = JSON.parse(await readFile8(packagePath, "utf-8"));
|
|
@@ -5401,7 +5885,7 @@ function pathExistsSync(file) {
|
|
|
5401
5885
|
}
|
|
5402
5886
|
async function pathExists6(file) {
|
|
5403
5887
|
try {
|
|
5404
|
-
await
|
|
5888
|
+
await access3(file);
|
|
5405
5889
|
return true;
|
|
5406
5890
|
} catch {
|
|
5407
5891
|
return false;
|
|
@@ -5420,7 +5904,7 @@ var EXIT_NOT_INITIALIZED2 = 1;
|
|
|
5420
5904
|
var EXIT_SCAN_FAILED = 2;
|
|
5421
5905
|
var EXIT_INVALID_ARG = 3;
|
|
5422
5906
|
async function manps(rootDir, area = "all", options = {}) {
|
|
5423
|
-
if (!await pathExists7(
|
|
5907
|
+
if (!await pathExists7(path19.join(rootDir, ".mancode", "state.json"))) {
|
|
5424
5908
|
if (options.json) {
|
|
5425
5909
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
5426
5910
|
} else {
|
|
@@ -5499,8 +5983,8 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
5499
5983
|
console.log(
|
|
5500
5984
|
`Issues: ${report.issues.length} total (P0 ${p0}, P1 ${p1}, P2 ${p2})`
|
|
5501
5985
|
);
|
|
5502
|
-
console.log(`Report: ${
|
|
5503
|
-
console.log(`Issue DB: ${
|
|
5986
|
+
console.log(`Report: ${path19.relative(rootDir, report.reportPath)}`);
|
|
5987
|
+
console.log(`Issue DB: ${path19.relative(rootDir, report.issueDbPath)}`);
|
|
5504
5988
|
if (report.issues.length > 0) {
|
|
5505
5989
|
console.log("");
|
|
5506
5990
|
for (const issue of report.issues.slice(0, 7)) {
|
|
@@ -5516,7 +6000,7 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
5516
6000
|
console.log(` Skipped: ${remediation.skipped}`);
|
|
5517
6001
|
console.log(` Fixed: ${remediation.fixed}`);
|
|
5518
6002
|
console.log(
|
|
5519
|
-
` Issue DB: ${
|
|
6003
|
+
` Issue DB: ${path19.relative(rootDir, remediation.issueDbPath)}`
|
|
5520
6004
|
);
|
|
5521
6005
|
}
|
|
5522
6006
|
return EXIT_OK4;
|
|
@@ -5542,7 +6026,7 @@ async function runRemediation(rootDir, report, options, silent = false) {
|
|
|
5542
6026
|
write
|
|
5543
6027
|
});
|
|
5544
6028
|
}
|
|
5545
|
-
const rl =
|
|
6029
|
+
const rl = createInterface2({
|
|
5546
6030
|
input: process.stdin,
|
|
5547
6031
|
output: process.stdout
|
|
5548
6032
|
});
|
|
@@ -5564,30 +6048,253 @@ async function readStdinLines() {
|
|
|
5564
6048
|
}
|
|
5565
6049
|
async function pathExists7(p) {
|
|
5566
6050
|
try {
|
|
5567
|
-
await
|
|
6051
|
+
await access4(p);
|
|
5568
6052
|
return true;
|
|
5569
6053
|
} catch {
|
|
5570
6054
|
return false;
|
|
5571
6055
|
}
|
|
5572
6056
|
}
|
|
5573
6057
|
|
|
5574
|
-
// src/commands/refresh-
|
|
5575
|
-
import {
|
|
5576
|
-
import
|
|
5577
|
-
import
|
|
6058
|
+
// src/commands/refresh-project.ts
|
|
6059
|
+
import { randomUUID } from "crypto";
|
|
6060
|
+
import { promises as fs7 } from "fs";
|
|
6061
|
+
import path20 from "path";
|
|
6062
|
+
import process8 from "process";
|
|
5578
6063
|
var EXIT_OK5 = 0;
|
|
5579
6064
|
var EXIT_NOT_INITIALIZED3 = 1;
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
6065
|
+
var EXIT_CORRUPT_STATE = 2;
|
|
6066
|
+
var EXIT_REFRESH_FAILED = 3;
|
|
6067
|
+
async function refreshProject(rootDir = process8.cwd()) {
|
|
6068
|
+
const mancodeDir = path20.join(rootDir, ".mancode");
|
|
6069
|
+
const statePath = path20.join(mancodeDir, "state.json");
|
|
6070
|
+
if (!await pathExists8(statePath)) {
|
|
5583
6071
|
console.error("\u2717 mancode not initialized.");
|
|
5584
6072
|
console.error(" Run `mancode init` first.");
|
|
5585
6073
|
return EXIT_NOT_INITIALIZED3;
|
|
5586
6074
|
}
|
|
6075
|
+
const state = await readRequiredState(statePath);
|
|
6076
|
+
if (!state) {
|
|
6077
|
+
console.error("\u2717 .mancode/state.json is corrupt or incomplete.");
|
|
6078
|
+
console.error(" Run `mancode init --force` to repair it.");
|
|
6079
|
+
return EXIT_CORRUPT_STATE;
|
|
6080
|
+
}
|
|
6081
|
+
let factsWritten = false;
|
|
6082
|
+
try {
|
|
6083
|
+
const [profile, team, hasGit, hasManifest] = await Promise.all([
|
|
6084
|
+
detectProjectProfile(rootDir),
|
|
6085
|
+
detectTeamStatus(rootDir),
|
|
6086
|
+
pathExists8(path20.join(rootDir, ".git")),
|
|
6087
|
+
hasProjectManifest2(rootDir)
|
|
6088
|
+
]);
|
|
6089
|
+
const uiLibrary = primaryUiLibrary(profile);
|
|
6090
|
+
const stack = [...profile.languages, ...profile.frameworks];
|
|
6091
|
+
const config = await readJson3(path20.join(mancodeDir, "config.json"));
|
|
6092
|
+
const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : team.isTeam;
|
|
6093
|
+
const nextState = {
|
|
6094
|
+
...state,
|
|
6095
|
+
techStack: stack.join(" + ") || profile.projectKind,
|
|
6096
|
+
uiLibrary: uiLibrary ?? "None",
|
|
6097
|
+
projectMode: hasGit || hasManifest ? "detected" : "generic",
|
|
6098
|
+
teamModeAutoDetected: configuredTeam,
|
|
6099
|
+
contributors: team.contributors
|
|
6100
|
+
};
|
|
6101
|
+
await writeProjectFacts(
|
|
6102
|
+
statePath,
|
|
6103
|
+
path20.join(mancodeDir, "project-profile.json"),
|
|
6104
|
+
`${JSON.stringify(nextState, null, 2)}
|
|
6105
|
+
`,
|
|
6106
|
+
`${JSON.stringify(profile, null, 2)}
|
|
6107
|
+
`
|
|
6108
|
+
);
|
|
6109
|
+
factsWritten = true;
|
|
6110
|
+
const refreshedPlatforms = await refreshStaticPlatforms(
|
|
6111
|
+
rootDir,
|
|
6112
|
+
config,
|
|
6113
|
+
state.platform,
|
|
6114
|
+
stack,
|
|
6115
|
+
uiLibrary,
|
|
6116
|
+
profile
|
|
6117
|
+
);
|
|
6118
|
+
console.log("\u2713 Project facts refreshed.");
|
|
6119
|
+
console.log(
|
|
6120
|
+
` ${hasGit ? "Git detected" : "No Git repository"} | ${hasManifest ? "project manifest detected" : "generic project"}`
|
|
6121
|
+
);
|
|
6122
|
+
console.log(
|
|
6123
|
+
` Stack: ${nextState.techStack} | UI: ${nextState.uiLibrary}`
|
|
6124
|
+
);
|
|
6125
|
+
if (refreshedPlatforms.length > 0) {
|
|
6126
|
+
console.log(` Refreshed adapters: ${refreshedPlatforms.join(", ")}`);
|
|
6127
|
+
}
|
|
6128
|
+
console.log(
|
|
6129
|
+
" Run `mancode refresh-style` if UI files or dependencies changed."
|
|
6130
|
+
);
|
|
6131
|
+
return EXIT_OK5;
|
|
6132
|
+
} catch (error) {
|
|
6133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6134
|
+
console.error(`\u2717 Project refresh failed: ${message}`);
|
|
6135
|
+
if (factsWritten) {
|
|
6136
|
+
console.error(
|
|
6137
|
+
" Project facts were saved, but one or more static adapters remain stale."
|
|
6138
|
+
);
|
|
6139
|
+
}
|
|
6140
|
+
return EXIT_REFRESH_FAILED;
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
async function hasProjectManifest2(rootDir) {
|
|
6144
|
+
for (const manifest of PROJECT_MANIFESTS) {
|
|
6145
|
+
if (await pathExists8(path20.join(rootDir, manifest))) return true;
|
|
6146
|
+
}
|
|
6147
|
+
return false;
|
|
6148
|
+
}
|
|
6149
|
+
async function writeProjectFacts(statePath, profilePath, stateContent, profileContent) {
|
|
6150
|
+
await Promise.all([
|
|
6151
|
+
ensureReplaceableFile(statePath),
|
|
6152
|
+
ensureReplaceableFile(profilePath)
|
|
6153
|
+
]);
|
|
6154
|
+
const previousProfile = await readOptionalText(profilePath);
|
|
6155
|
+
const stateTemp = temporaryPath(statePath);
|
|
6156
|
+
const profileTemp = temporaryPath(profilePath);
|
|
6157
|
+
let profileReplaced = false;
|
|
6158
|
+
try {
|
|
6159
|
+
await Promise.all([
|
|
6160
|
+
fs7.writeFile(stateTemp, stateContent, "utf-8"),
|
|
6161
|
+
fs7.writeFile(profileTemp, profileContent, "utf-8")
|
|
6162
|
+
]);
|
|
6163
|
+
await fs7.rename(profileTemp, profilePath);
|
|
6164
|
+
profileReplaced = true;
|
|
6165
|
+
await fs7.rename(stateTemp, statePath);
|
|
6166
|
+
} catch (error) {
|
|
6167
|
+
if (profileReplaced) {
|
|
6168
|
+
if (previousProfile === null) {
|
|
6169
|
+
await fs7.rm(profilePath, { force: true });
|
|
6170
|
+
} else {
|
|
6171
|
+
await replaceTextFile(profilePath, previousProfile);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
throw error;
|
|
6175
|
+
} finally {
|
|
6176
|
+
await Promise.all([
|
|
6177
|
+
fs7.rm(stateTemp, { force: true }),
|
|
6178
|
+
fs7.rm(profileTemp, { force: true })
|
|
6179
|
+
]);
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
async function ensureReplaceableFile(filePath) {
|
|
6183
|
+
try {
|
|
6184
|
+
const entry = await fs7.lstat(filePath);
|
|
6185
|
+
if (!entry.isFile()) {
|
|
6186
|
+
throw new Error(`cannot replace non-file path: ${filePath}`);
|
|
6187
|
+
}
|
|
6188
|
+
} catch (error) {
|
|
6189
|
+
if (isNodeError6(error) && error.code === "ENOENT") return;
|
|
6190
|
+
throw error;
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
async function replaceTextFile(filePath, content) {
|
|
6194
|
+
const tempPath = temporaryPath(filePath);
|
|
6195
|
+
try {
|
|
6196
|
+
await fs7.writeFile(tempPath, content, "utf-8");
|
|
6197
|
+
await fs7.rename(tempPath, filePath);
|
|
6198
|
+
} finally {
|
|
6199
|
+
await fs7.rm(tempPath, { force: true });
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
async function readOptionalText(filePath) {
|
|
6203
|
+
try {
|
|
6204
|
+
return await fs7.readFile(filePath, "utf-8");
|
|
6205
|
+
} catch (error) {
|
|
6206
|
+
if (isNodeError6(error) && error.code === "ENOENT") return null;
|
|
6207
|
+
throw error;
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
function temporaryPath(filePath) {
|
|
6211
|
+
return path20.join(
|
|
6212
|
+
path20.dirname(filePath),
|
|
6213
|
+
`.${path20.basename(filePath)}.${process8.pid}.${randomUUID()}.tmp`
|
|
6214
|
+
);
|
|
6215
|
+
}
|
|
6216
|
+
async function refreshStaticPlatforms(rootDir, config, fallbackPlatform, stack, uiLibrary, profile) {
|
|
6217
|
+
const platforms = configuredPlatforms(config, fallbackPlatform).filter(
|
|
6218
|
+
(platform) => platform !== "claude-code"
|
|
6219
|
+
);
|
|
6220
|
+
const refreshed = [];
|
|
6221
|
+
for (const platform of platforms) {
|
|
6222
|
+
const installer = getPlatformInstaller(platform);
|
|
6223
|
+
if (!installer) continue;
|
|
6224
|
+
await installer.install(rootDir, {
|
|
6225
|
+
techStack: stack,
|
|
6226
|
+
uiLibrary,
|
|
6227
|
+
projectProfile: profile,
|
|
6228
|
+
minimal: platformIsMinimal(config, platform),
|
|
6229
|
+
force: true
|
|
6230
|
+
});
|
|
6231
|
+
refreshed.push(installer.displayName);
|
|
6232
|
+
}
|
|
6233
|
+
return refreshed;
|
|
6234
|
+
}
|
|
6235
|
+
function configuredPlatforms(config, fallbackPlatform) {
|
|
6236
|
+
const configured = Array.isArray(config.platforms) ? config.platforms : [fallbackPlatform];
|
|
6237
|
+
return configured.filter(
|
|
6238
|
+
(platform) => typeof platform === "string" && getPlatformInstaller(platform) !== null
|
|
6239
|
+
);
|
|
6240
|
+
}
|
|
6241
|
+
function platformIsMinimal(config, platform) {
|
|
6242
|
+
if (!isRecord7(config.platformOptions)) return false;
|
|
6243
|
+
const options = config.platformOptions[platform];
|
|
6244
|
+
return isRecord7(options) && options.minimal === true;
|
|
6245
|
+
}
|
|
6246
|
+
async function readJson3(filePath) {
|
|
6247
|
+
try {
|
|
6248
|
+
const value = JSON.parse(await fs7.readFile(filePath, "utf-8"));
|
|
6249
|
+
return isRecord7(value) ? value : {};
|
|
6250
|
+
} catch {
|
|
6251
|
+
return {};
|
|
6252
|
+
}
|
|
6253
|
+
}
|
|
6254
|
+
async function readRequiredState(filePath) {
|
|
6255
|
+
let state;
|
|
6256
|
+
try {
|
|
6257
|
+
const raw = await fs7.readFile(filePath, "utf-8");
|
|
6258
|
+
const parsed = JSON.parse(raw);
|
|
6259
|
+
if (!isRecord7(parsed)) return null;
|
|
6260
|
+
state = parsed;
|
|
6261
|
+
} catch {
|
|
6262
|
+
return null;
|
|
6263
|
+
}
|
|
6264
|
+
return typeof state.version === "string" && typeof state.currentMode === "string" && typeof state.platform === "string" ? state : null;
|
|
6265
|
+
}
|
|
6266
|
+
function isRecord7(value) {
|
|
6267
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6268
|
+
}
|
|
6269
|
+
function isNodeError6(error) {
|
|
6270
|
+
return error instanceof Error && "code" in error;
|
|
6271
|
+
}
|
|
6272
|
+
async function pathExists8(filePath) {
|
|
6273
|
+
try {
|
|
6274
|
+
await fs7.access(filePath);
|
|
6275
|
+
return true;
|
|
6276
|
+
} catch {
|
|
6277
|
+
return false;
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
// src/commands/refresh-style.ts
|
|
6282
|
+
import { promises as fs8 } from "fs";
|
|
6283
|
+
import path21 from "path";
|
|
6284
|
+
import process9 from "process";
|
|
6285
|
+
var EXIT_OK6 = 0;
|
|
6286
|
+
var EXIT_NOT_INITIALIZED4 = 1;
|
|
6287
|
+
async function refreshStyle(rootDir = process9.cwd()) {
|
|
6288
|
+
const stateFile = path21.join(rootDir, ".mancode", "state.json");
|
|
6289
|
+
if (!await pathExists9(stateFile)) {
|
|
6290
|
+
console.error("\u2717 mancode not initialized.");
|
|
6291
|
+
console.error(" Run `mancode init` first.");
|
|
6292
|
+
return EXIT_NOT_INITIALIZED4;
|
|
6293
|
+
}
|
|
5587
6294
|
console.log("\u2713 \u5237\u65B0\u9879\u76EE profile...");
|
|
5588
6295
|
const profile = await detectProjectProfile(rootDir);
|
|
5589
|
-
const profilePath =
|
|
5590
|
-
await
|
|
6296
|
+
const profilePath = path21.join(rootDir, ".mancode", "project-profile.json");
|
|
6297
|
+
await fs8.writeFile(
|
|
5591
6298
|
profilePath,
|
|
5592
6299
|
`${JSON.stringify(profile, null, 2)}
|
|
5593
6300
|
`,
|
|
@@ -5604,18 +6311,18 @@ async function refreshStyle(rootDir = process6.cwd()) {
|
|
|
5604
6311
|
);
|
|
5605
6312
|
console.log(" Updated .mancode/project-profile.json.");
|
|
5606
6313
|
await printStaticPlatformRefreshHint(rootDir);
|
|
5607
|
-
return
|
|
6314
|
+
return EXIT_OK6;
|
|
5608
6315
|
}
|
|
5609
6316
|
console.log("\u2713 \u626B\u63CF\u9879\u76EE\u8BBE\u8BA1 token...");
|
|
5610
6317
|
const tokens = await scanAesthetics(rootDir, uiLibraryHint);
|
|
5611
|
-
const tokensPath =
|
|
6318
|
+
const tokensPath = path21.join(
|
|
5612
6319
|
rootDir,
|
|
5613
6320
|
".mancode",
|
|
5614
6321
|
"aesthetics",
|
|
5615
6322
|
"style-tokens.json"
|
|
5616
6323
|
);
|
|
5617
|
-
await
|
|
5618
|
-
await
|
|
6324
|
+
await fs8.mkdir(path21.dirname(tokensPath), { recursive: true });
|
|
6325
|
+
await fs8.writeFile(
|
|
5619
6326
|
tokensPath,
|
|
5620
6327
|
`${JSON.stringify(tokens, null, 2)}
|
|
5621
6328
|
`,
|
|
@@ -5663,7 +6370,7 @@ async function refreshStyle(rootDir = process6.cwd()) {
|
|
|
5663
6370
|
"\u5DF2\u66F4\u65B0 .mancode/project-profile.json \u548C .mancode/aesthetics/style-tokens.json"
|
|
5664
6371
|
);
|
|
5665
6372
|
await printStaticPlatformRefreshHint(rootDir);
|
|
5666
|
-
return
|
|
6373
|
+
return EXIT_OK6;
|
|
5667
6374
|
}
|
|
5668
6375
|
async function printStaticPlatformRefreshHint(rootDir) {
|
|
5669
6376
|
const platforms = await readInstalledPlatforms2(rootDir);
|
|
@@ -5680,11 +6387,11 @@ async function printStaticPlatformRefreshHint(rootDir) {
|
|
|
5680
6387
|
);
|
|
5681
6388
|
}
|
|
5682
6389
|
async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
|
|
5683
|
-
const statePath =
|
|
6390
|
+
const statePath = path21.join(rootDir, ".mancode", "state.json");
|
|
5684
6391
|
try {
|
|
5685
|
-
const state = JSON.parse(await
|
|
6392
|
+
const state = JSON.parse(await fs8.readFile(statePath, "utf-8"));
|
|
5686
6393
|
const stack = [...profile.languages, ...profile.frameworks];
|
|
5687
|
-
await
|
|
6394
|
+
await fs8.writeFile(
|
|
5688
6395
|
statePath,
|
|
5689
6396
|
`${JSON.stringify(
|
|
5690
6397
|
{
|
|
@@ -5703,8 +6410,8 @@ async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
|
|
|
5703
6410
|
}
|
|
5704
6411
|
async function readInstalledPlatforms2(rootDir) {
|
|
5705
6412
|
try {
|
|
5706
|
-
const raw = await
|
|
5707
|
-
|
|
6413
|
+
const raw = await fs8.readFile(
|
|
6414
|
+
path21.join(rootDir, ".mancode", "config.json"),
|
|
5708
6415
|
"utf-8"
|
|
5709
6416
|
);
|
|
5710
6417
|
const config = JSON.parse(raw);
|
|
@@ -5715,9 +6422,9 @@ async function readInstalledPlatforms2(rootDir) {
|
|
|
5715
6422
|
return [];
|
|
5716
6423
|
}
|
|
5717
6424
|
}
|
|
5718
|
-
async function
|
|
6425
|
+
async function pathExists9(p) {
|
|
5719
6426
|
try {
|
|
5720
|
-
await
|
|
6427
|
+
await fs8.access(p);
|
|
5721
6428
|
return true;
|
|
5722
6429
|
} catch {
|
|
5723
6430
|
return false;
|
|
@@ -5726,9 +6433,9 @@ async function pathExists8(p) {
|
|
|
5726
6433
|
|
|
5727
6434
|
// src/commands/status.ts
|
|
5728
6435
|
import { spawn } from "child_process";
|
|
5729
|
-
import { promises as
|
|
5730
|
-
import
|
|
5731
|
-
import
|
|
6436
|
+
import { promises as fs9 } from "fs";
|
|
6437
|
+
import path24 from "path";
|
|
6438
|
+
import process10 from "process";
|
|
5732
6439
|
|
|
5733
6440
|
// src/system/workflow.ts
|
|
5734
6441
|
import {
|
|
@@ -5739,11 +6446,11 @@ import {
|
|
|
5739
6446
|
stat as stat4,
|
|
5740
6447
|
writeFile as writeFile11
|
|
5741
6448
|
} from "fs/promises";
|
|
5742
|
-
import
|
|
6449
|
+
import path23 from "path";
|
|
5743
6450
|
|
|
5744
6451
|
// src/system/review-ledger.ts
|
|
5745
6452
|
import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
|
|
5746
|
-
import
|
|
6453
|
+
import path22 from "path";
|
|
5747
6454
|
var REVIEW_FILE = "review-ledger.json";
|
|
5748
6455
|
var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
5749
6456
|
var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
|
|
@@ -5859,8 +6566,8 @@ async function readReviewLedger(projectRoot, taskId) {
|
|
|
5859
6566
|
}
|
|
5860
6567
|
}
|
|
5861
6568
|
function isReviewLedger(value) {
|
|
5862
|
-
if (!
|
|
5863
|
-
if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !
|
|
6569
|
+
if (!isRecord8(value)) return false;
|
|
6570
|
+
if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord8(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
|
|
5864
6571
|
return false;
|
|
5865
6572
|
}
|
|
5866
6573
|
const requiredDomains = value.requiredDomains;
|
|
@@ -5876,13 +6583,13 @@ function isReviewLedger(value) {
|
|
|
5876
6583
|
}
|
|
5877
6584
|
const blockerIds = /* @__PURE__ */ new Set();
|
|
5878
6585
|
return blockers.every(
|
|
5879
|
-
(blocker) =>
|
|
6586
|
+
(blocker) => isRecord8(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
|
|
5880
6587
|
);
|
|
5881
6588
|
}
|
|
5882
6589
|
function isDomainArray(value) {
|
|
5883
6590
|
return Array.isArray(value) && value.every(isReviewDomain);
|
|
5884
6591
|
}
|
|
5885
|
-
function
|
|
6592
|
+
function isRecord8(value) {
|
|
5886
6593
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5887
6594
|
}
|
|
5888
6595
|
async function requireReviewLedger(projectRoot, taskId) {
|
|
@@ -5892,17 +6599,17 @@ async function requireReviewLedger(projectRoot, taskId) {
|
|
|
5892
6599
|
return ledger;
|
|
5893
6600
|
}
|
|
5894
6601
|
async function writeReviewLedger(projectRoot, taskId, ledger) {
|
|
5895
|
-
const dir =
|
|
6602
|
+
const dir = path22.join(projectRoot, ".mancode", "workflows", taskId);
|
|
5896
6603
|
await mkdir8(dir, { recursive: true });
|
|
5897
6604
|
await writeFile10(
|
|
5898
|
-
|
|
6605
|
+
path22.join(dir, REVIEW_FILE),
|
|
5899
6606
|
`${JSON.stringify(ledger, null, 2)}
|
|
5900
6607
|
`,
|
|
5901
6608
|
"utf-8"
|
|
5902
6609
|
);
|
|
5903
6610
|
}
|
|
5904
6611
|
function reviewPath(projectRoot, taskId) {
|
|
5905
|
-
return
|
|
6612
|
+
return path22.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
|
|
5906
6613
|
}
|
|
5907
6614
|
function assertValidTaskId(taskId) {
|
|
5908
6615
|
if (!TASK_ID_PATTERN.test(taskId)) {
|
|
@@ -5915,7 +6622,7 @@ function assertSafeReportPath(report) {
|
|
|
5915
6622
|
}
|
|
5916
6623
|
}
|
|
5917
6624
|
async function assertReportExists(projectRoot, taskId, report) {
|
|
5918
|
-
const reportPath =
|
|
6625
|
+
const reportPath = path22.join(
|
|
5919
6626
|
projectRoot,
|
|
5920
6627
|
".mancode",
|
|
5921
6628
|
"workflows",
|
|
@@ -5929,9 +6636,9 @@ async function assertReportExists(projectRoot, taskId, report) {
|
|
|
5929
6636
|
throw new Error(`review report not found: ${report}`);
|
|
5930
6637
|
}
|
|
5931
6638
|
function isSafeReportPath(report) {
|
|
5932
|
-
const normalized =
|
|
6639
|
+
const normalized = path22.posix.normalize(report.replaceAll("\\", "/"));
|
|
5933
6640
|
return Boolean(
|
|
5934
|
-
report.trim() && !
|
|
6641
|
+
report.trim() && !path22.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
|
|
5935
6642
|
);
|
|
5936
6643
|
}
|
|
5937
6644
|
|
|
@@ -5979,7 +6686,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
|
|
|
5979
6686
|
await mkdir9(workflowDir(projectRoot, taskId));
|
|
5980
6687
|
return taskId;
|
|
5981
6688
|
} catch (err) {
|
|
5982
|
-
if (
|
|
6689
|
+
if (isNodeError7(err) && err.code === "EEXIST") {
|
|
5983
6690
|
continue;
|
|
5984
6691
|
}
|
|
5985
6692
|
throw err;
|
|
@@ -6055,19 +6762,19 @@ async function deleteWorkflow(projectRoot, taskId) {
|
|
|
6055
6762
|
return true;
|
|
6056
6763
|
}
|
|
6057
6764
|
function workflowsRoot(projectRoot) {
|
|
6058
|
-
return
|
|
6765
|
+
return path23.join(projectRoot, ".mancode", "workflows");
|
|
6059
6766
|
}
|
|
6060
6767
|
function workflowDir(projectRoot, taskId) {
|
|
6061
6768
|
assertValidTaskId2(taskId);
|
|
6062
|
-
return
|
|
6769
|
+
return path23.join(workflowsRoot(projectRoot), taskId);
|
|
6063
6770
|
}
|
|
6064
6771
|
function metadataPath(projectRoot, taskId) {
|
|
6065
|
-
return
|
|
6772
|
+
return path23.join(workflowDir(projectRoot, taskId), METADATA_FILE);
|
|
6066
6773
|
}
|
|
6067
6774
|
async function writeMetadata(dir, meta) {
|
|
6068
6775
|
const content = `${JSON.stringify(meta, null, 2)}
|
|
6069
6776
|
`;
|
|
6070
|
-
await writeFile11(
|
|
6777
|
+
await writeFile11(path23.join(dir, METADATA_FILE), content, "utf-8");
|
|
6071
6778
|
}
|
|
6072
6779
|
function parseWorkflowMeta(raw, taskId) {
|
|
6073
6780
|
try {
|
|
@@ -6273,7 +6980,7 @@ async function propagateChildStatus(projectRoot, child) {
|
|
|
6273
6980
|
});
|
|
6274
6981
|
}
|
|
6275
6982
|
}
|
|
6276
|
-
function
|
|
6983
|
+
function isNodeError7(err) {
|
|
6277
6984
|
return err instanceof Error && "code" in err;
|
|
6278
6985
|
}
|
|
6279
6986
|
function assertValidTaskId2(taskId) {
|
|
@@ -6284,19 +6991,19 @@ function assertValidTaskId2(taskId) {
|
|
|
6284
6991
|
|
|
6285
6992
|
// src/commands/status.ts
|
|
6286
6993
|
var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
|
|
6287
|
-
var
|
|
6288
|
-
var
|
|
6289
|
-
var
|
|
6290
|
-
async function status(rootDir =
|
|
6291
|
-
const stateFile =
|
|
6292
|
-
if (!await
|
|
6994
|
+
var EXIT_OK7 = 0;
|
|
6995
|
+
var EXIT_NOT_INITIALIZED5 = 1;
|
|
6996
|
+
var EXIT_CORRUPT_STATE2 = 2;
|
|
6997
|
+
async function status(rootDir = process10.cwd(), options = {}) {
|
|
6998
|
+
const stateFile = path24.join(rootDir, ".mancode", "state.json");
|
|
6999
|
+
if (!await pathExists10(stateFile)) {
|
|
6293
7000
|
console.error("\u2717 mancode not initialized.");
|
|
6294
7001
|
console.error(" Run `mancode init` to get started.");
|
|
6295
|
-
return
|
|
7002
|
+
return EXIT_NOT_INITIALIZED5;
|
|
6296
7003
|
}
|
|
6297
7004
|
let state;
|
|
6298
7005
|
try {
|
|
6299
|
-
const raw = await
|
|
7006
|
+
const raw = await fs9.readFile(stateFile, "utf-8");
|
|
6300
7007
|
state = JSON.parse(raw);
|
|
6301
7008
|
} catch (err) {
|
|
6302
7009
|
if (err instanceof SyntaxError) {
|
|
@@ -6307,7 +7014,7 @@ async function status(rootDir = process7.cwd(), options = {}) {
|
|
|
6307
7014
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6308
7015
|
console.error(`\u2717 Failed to read .mancode/state.json: ${msg}`);
|
|
6309
7016
|
}
|
|
6310
|
-
return
|
|
7017
|
+
return EXIT_CORRUPT_STATE2;
|
|
6311
7018
|
}
|
|
6312
7019
|
const [
|
|
6313
7020
|
project,
|
|
@@ -6315,14 +7022,16 @@ async function status(rootDir = process7.cwd(), options = {}) {
|
|
|
6315
7022
|
config,
|
|
6316
7023
|
hookInjection,
|
|
6317
7024
|
teamStatus,
|
|
6318
|
-
currentWorkflow
|
|
7025
|
+
currentWorkflow,
|
|
7026
|
+
projectRefreshRecommended
|
|
6319
7027
|
] = await Promise.all([
|
|
6320
7028
|
getProjectName(rootDir),
|
|
6321
7029
|
checkHooks(rootDir),
|
|
6322
7030
|
readConfig2(rootDir),
|
|
6323
7031
|
estimateHookInjection(rootDir),
|
|
6324
7032
|
detectTeamStatus(rootDir),
|
|
6325
|
-
getCurrentWorkflow(rootDir, state.currentTask ?? null)
|
|
7033
|
+
getCurrentWorkflow(rootDir, state.currentTask ?? null),
|
|
7034
|
+
shouldRefreshProject(rootDir, state)
|
|
6326
7035
|
]);
|
|
6327
7036
|
const effectiveTeam = getEffectiveTeamStatus(state, config, teamStatus);
|
|
6328
7037
|
const result = {
|
|
@@ -6337,7 +7046,8 @@ async function status(rootDir = process7.cwd(), options = {}) {
|
|
|
6337
7046
|
hookInjection,
|
|
6338
7047
|
team: effectiveTeam,
|
|
6339
7048
|
currentWorkflow,
|
|
6340
|
-
platformStatus: {}
|
|
7049
|
+
platformStatus: {},
|
|
7050
|
+
projectRefreshRecommended
|
|
6341
7051
|
};
|
|
6342
7052
|
result.platformStatus = await checkPlatformStatus2(rootDir, result.platforms);
|
|
6343
7053
|
if (options.json) {
|
|
@@ -6345,7 +7055,16 @@ async function status(rootDir = process7.cwd(), options = {}) {
|
|
|
6345
7055
|
} else {
|
|
6346
7056
|
printText(result);
|
|
6347
7057
|
}
|
|
6348
|
-
return
|
|
7058
|
+
return EXIT_OK7;
|
|
7059
|
+
}
|
|
7060
|
+
async function shouldRefreshProject(rootDir, state) {
|
|
7061
|
+
if (state.projectMode !== "generic") return false;
|
|
7062
|
+
const hasGit = await pathExists10(path24.join(rootDir, ".git"));
|
|
7063
|
+
if (hasGit) return true;
|
|
7064
|
+
for (const manifest of PROJECT_MANIFESTS) {
|
|
7065
|
+
if (await pathExists10(path24.join(rootDir, manifest))) return true;
|
|
7066
|
+
}
|
|
7067
|
+
return false;
|
|
6349
7068
|
}
|
|
6350
7069
|
async function checkPlatformStatus2(rootDir, installedPlatforms) {
|
|
6351
7070
|
const installed = new Set(installedPlatforms);
|
|
@@ -6387,19 +7106,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
|
|
|
6387
7106
|
}
|
|
6388
7107
|
async function getProjectName(rootDir) {
|
|
6389
7108
|
try {
|
|
6390
|
-
const raw = await
|
|
7109
|
+
const raw = await fs9.readFile(path24.join(rootDir, "package.json"), "utf-8");
|
|
6391
7110
|
const pkg = JSON.parse(raw);
|
|
6392
7111
|
if (pkg.name && typeof pkg.name === "string") {
|
|
6393
7112
|
return pkg.name;
|
|
6394
7113
|
}
|
|
6395
7114
|
} catch {
|
|
6396
7115
|
}
|
|
6397
|
-
return
|
|
7116
|
+
return path24.basename(rootDir);
|
|
6398
7117
|
}
|
|
6399
7118
|
async function readConfig2(rootDir) {
|
|
6400
7119
|
try {
|
|
6401
|
-
const raw = await
|
|
6402
|
-
|
|
7120
|
+
const raw = await fs9.readFile(
|
|
7121
|
+
path24.join(rootDir, ".mancode", "config.json"),
|
|
6403
7122
|
"utf-8"
|
|
6404
7123
|
);
|
|
6405
7124
|
return JSON.parse(raw);
|
|
@@ -6414,21 +7133,22 @@ function getInstalledPlatforms(config, fallback) {
|
|
|
6414
7133
|
return fallback ? [fallback] : [];
|
|
6415
7134
|
}
|
|
6416
7135
|
function getEffectiveTeamStatus(state, config, detected) {
|
|
6417
|
-
const configuredTeam = config.forceTeamMode === true ? true : state.teamModeAutoDetected ?? detected.isTeam;
|
|
6418
|
-
const forced = config.forceTeamMode === true;
|
|
7136
|
+
const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
|
|
7137
|
+
const forced = config.teamMode === "on" || config.forceTeamMode === true;
|
|
7138
|
+
const autoDetected = config.forceTeamMode === true || config.teamMode === "on" || config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
|
|
6419
7139
|
return {
|
|
6420
7140
|
...detected,
|
|
6421
7141
|
isTeam: configuredTeam,
|
|
6422
7142
|
contributors: Math.max(detected.contributors, state.contributors ?? 0, 1),
|
|
6423
|
-
autoDetected
|
|
7143
|
+
autoDetected,
|
|
6424
7144
|
forced
|
|
6425
7145
|
};
|
|
6426
7146
|
}
|
|
6427
7147
|
async function checkHooks(rootDir) {
|
|
6428
7148
|
const [sessionStart, userPromptSubmit, registered] = await Promise.all([
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
7149
|
+
pathExists10(path24.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
|
|
7150
|
+
pathExists10(
|
|
7151
|
+
path24.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
6432
7152
|
),
|
|
6433
7153
|
isRegistered(rootDir)
|
|
6434
7154
|
]);
|
|
@@ -6436,8 +7156,8 @@ async function checkHooks(rootDir) {
|
|
|
6436
7156
|
}
|
|
6437
7157
|
async function isRegistered(rootDir) {
|
|
6438
7158
|
try {
|
|
6439
|
-
const raw = await
|
|
6440
|
-
|
|
7159
|
+
const raw = await fs9.readFile(
|
|
7160
|
+
path24.join(rootDir, ".claude", "settings.json"),
|
|
6441
7161
|
"utf-8"
|
|
6442
7162
|
);
|
|
6443
7163
|
const settings = JSON.parse(raw);
|
|
@@ -6447,32 +7167,32 @@ async function isRegistered(rootDir) {
|
|
|
6447
7167
|
}
|
|
6448
7168
|
}
|
|
6449
7169
|
function hooksRegistered2(settings) {
|
|
6450
|
-
if (!
|
|
7170
|
+
if (!isRecord9(settings)) return false;
|
|
6451
7171
|
const hooks = settings.hooks;
|
|
6452
|
-
if (!
|
|
6453
|
-
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.
|
|
7172
|
+
if (!isRecord9(hooks)) return false;
|
|
7173
|
+
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
|
|
6454
7174
|
hooks.UserPromptSubmit,
|
|
6455
|
-
".mancode/hooks/user-prompt-submit.
|
|
7175
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
6456
7176
|
);
|
|
6457
7177
|
}
|
|
6458
7178
|
function hasHookCommand2(value, needle) {
|
|
6459
7179
|
if (!Array.isArray(value)) return false;
|
|
6460
7180
|
return value.some((group) => {
|
|
6461
|
-
if (!
|
|
7181
|
+
if (!isRecord9(group) || !Array.isArray(group.hooks)) return false;
|
|
6462
7182
|
return group.hooks.some((hook) => {
|
|
6463
|
-
if (!
|
|
7183
|
+
if (!isRecord9(hook) || typeof hook.command !== "string") return false;
|
|
6464
7184
|
return hook.command.includes(needle);
|
|
6465
7185
|
});
|
|
6466
7186
|
});
|
|
6467
7187
|
}
|
|
6468
7188
|
async function estimateHookInjection(rootDir) {
|
|
6469
|
-
const hookPath =
|
|
7189
|
+
const hookPath = path24.join(
|
|
6470
7190
|
rootDir,
|
|
6471
7191
|
".mancode",
|
|
6472
7192
|
"hooks",
|
|
6473
|
-
"user-prompt-submit.
|
|
7193
|
+
"user-prompt-submit.mjs"
|
|
6474
7194
|
);
|
|
6475
|
-
if (!await
|
|
7195
|
+
if (!await pathExists10(hookPath)) {
|
|
6476
7196
|
return { tokens: 0, cap: 800 };
|
|
6477
7197
|
}
|
|
6478
7198
|
try {
|
|
@@ -6487,7 +7207,7 @@ async function estimateHookInjection(rootDir) {
|
|
|
6487
7207
|
}
|
|
6488
7208
|
function runHookEstimate(rootDir, hookPath) {
|
|
6489
7209
|
return new Promise((resolve, reject) => {
|
|
6490
|
-
const child = spawn(
|
|
7210
|
+
const child = spawn(process10.execPath, [hookPath], {
|
|
6491
7211
|
cwd: rootDir,
|
|
6492
7212
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6493
7213
|
});
|
|
@@ -6502,15 +7222,15 @@ function runHookEstimate(rootDir, hookPath) {
|
|
|
6502
7222
|
child.kill();
|
|
6503
7223
|
finish(() => reject(new Error("hook estimate timed out")));
|
|
6504
7224
|
}, HOOK_ESTIMATE_TIMEOUT_MS);
|
|
6505
|
-
let
|
|
7225
|
+
let stdout2 = "";
|
|
6506
7226
|
child.stdout.setEncoding("utf-8");
|
|
6507
7227
|
child.stdout.on("data", (chunk) => {
|
|
6508
|
-
|
|
7228
|
+
stdout2 += chunk;
|
|
6509
7229
|
});
|
|
6510
7230
|
child.on("error", (err) => finish(() => reject(err)));
|
|
6511
7231
|
child.on("close", (code) => {
|
|
6512
7232
|
finish(() => {
|
|
6513
|
-
if (code === 0) resolve(
|
|
7233
|
+
if (code === 0) resolve(stdout2);
|
|
6514
7234
|
else reject(new Error(`hook exited with ${code}`));
|
|
6515
7235
|
});
|
|
6516
7236
|
});
|
|
@@ -6529,6 +7249,11 @@ function printText(r) {
|
|
|
6529
7249
|
console.log(`Style: ${r.uiLibrary}`);
|
|
6530
7250
|
console.log(`Initialized: ${r.initializedAt}`);
|
|
6531
7251
|
console.log(`Team: ${formatTeamStatus(r.team)}`);
|
|
7252
|
+
if (r.projectRefreshRecommended) {
|
|
7253
|
+
console.log(
|
|
7254
|
+
"Project: new Git or project files detected; run `mancode refresh-project`."
|
|
7255
|
+
);
|
|
7256
|
+
}
|
|
6532
7257
|
if (r.currentWorkflow) {
|
|
6533
7258
|
const stepMax = workflowStepMax(r.currentWorkflow.mode);
|
|
6534
7259
|
console.log(
|
|
@@ -6571,9 +7296,9 @@ function printText(r) {
|
|
|
6571
7296
|
console.log("");
|
|
6572
7297
|
if (r.platforms.includes("claude-code")) {
|
|
6573
7298
|
console.log("Hooks:");
|
|
6574
|
-
console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.
|
|
7299
|
+
console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.mjs`);
|
|
6575
7300
|
console.log(
|
|
6576
|
-
` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.
|
|
7301
|
+
` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.mjs`
|
|
6577
7302
|
);
|
|
6578
7303
|
console.log(
|
|
6579
7304
|
` ${r.hooks.registered ? "\u2713" : "\u2717"} registered in .claude/settings.json`
|
|
@@ -6592,12 +7317,12 @@ function formatTeamStatus(team) {
|
|
|
6592
7317
|
function workflowStepMax(mode) {
|
|
6593
7318
|
return maxWorkflowStep(mode);
|
|
6594
7319
|
}
|
|
6595
|
-
function
|
|
7320
|
+
function isRecord9(value) {
|
|
6596
7321
|
return typeof value === "object" && value !== null;
|
|
6597
7322
|
}
|
|
6598
|
-
async function
|
|
7323
|
+
async function pathExists10(p) {
|
|
6599
7324
|
try {
|
|
6600
|
-
await
|
|
7325
|
+
await fs9.access(p);
|
|
6601
7326
|
return true;
|
|
6602
7327
|
} catch {
|
|
6603
7328
|
return false;
|
|
@@ -6605,18 +7330,18 @@ async function pathExists9(p) {
|
|
|
6605
7330
|
}
|
|
6606
7331
|
|
|
6607
7332
|
// src/commands/uninstall.ts
|
|
6608
|
-
import { access as
|
|
6609
|
-
import
|
|
6610
|
-
import
|
|
6611
|
-
var
|
|
6612
|
-
var
|
|
7333
|
+
import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
|
|
7334
|
+
import path25 from "path";
|
|
7335
|
+
import process11 from "process";
|
|
7336
|
+
var EXIT_OK8 = 0;
|
|
7337
|
+
var EXIT_NOT_INITIALIZED6 = 1;
|
|
6613
7338
|
var EXIT_UNSUPPORTED_PLATFORM2 = 2;
|
|
6614
|
-
async function uninstall(rootDir =
|
|
6615
|
-
const stateFile =
|
|
6616
|
-
if (!await
|
|
7339
|
+
async function uninstall(rootDir = process11.cwd(), platform, options = {}) {
|
|
7340
|
+
const stateFile = path25.join(rootDir, ".mancode", "state.json");
|
|
7341
|
+
if (!await pathExists11(stateFile)) {
|
|
6617
7342
|
console.error("\u2717 mancode not initialized.");
|
|
6618
7343
|
console.error(" Run `mancode init` first.");
|
|
6619
|
-
return
|
|
7344
|
+
return EXIT_NOT_INITIALIZED6;
|
|
6620
7345
|
}
|
|
6621
7346
|
const removeAll = !platform || options.all;
|
|
6622
7347
|
if (platform) {
|
|
@@ -6641,7 +7366,7 @@ async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
|
|
|
6641
7366
|
await uninstallPlatform(rootDir, platform ?? "claude-code");
|
|
6642
7367
|
}
|
|
6643
7368
|
console.log("\u2713 Uninstall complete.");
|
|
6644
|
-
return
|
|
7369
|
+
return EXIT_OK8;
|
|
6645
7370
|
}
|
|
6646
7371
|
async function uninstallPlatform(rootDir, platform) {
|
|
6647
7372
|
console.log(`\u2713 Removing ${formatPlatformName(platform)} adapter...`);
|
|
@@ -6664,7 +7389,7 @@ async function uninstallAll(rootDir) {
|
|
|
6664
7389
|
await uninstallPlatform(rootDir, p);
|
|
6665
7390
|
}
|
|
6666
7391
|
console.log("\u2713 Removing .mancode/ directory...");
|
|
6667
|
-
await rm5(
|
|
7392
|
+
await rm5(path25.join(rootDir, ".mancode"), {
|
|
6668
7393
|
recursive: true,
|
|
6669
7394
|
force: true
|
|
6670
7395
|
});
|
|
@@ -6674,7 +7399,7 @@ async function uninstallClaudeCode(rootDir) {
|
|
|
6674
7399
|
await cleanClaudeSettings(rootDir);
|
|
6675
7400
|
}
|
|
6676
7401
|
async function cleanClaudeSettings(rootDir) {
|
|
6677
|
-
const settingsPath =
|
|
7402
|
+
const settingsPath = path25.join(rootDir, ".claude", "settings.json");
|
|
6678
7403
|
let content;
|
|
6679
7404
|
try {
|
|
6680
7405
|
content = await readFile11(settingsPath, "utf-8");
|
|
@@ -6688,14 +7413,14 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
6688
7413
|
return;
|
|
6689
7414
|
}
|
|
6690
7415
|
const cleanedHooks = {};
|
|
6691
|
-
if (
|
|
7416
|
+
if (isRecord10(settings.hooks)) {
|
|
6692
7417
|
for (const [event, value] of Object.entries(settings.hooks)) {
|
|
6693
7418
|
const cleaned = cleanClaudeHookValue(value);
|
|
6694
7419
|
if (cleaned !== void 0) cleanedHooks[event] = cleaned;
|
|
6695
7420
|
}
|
|
6696
7421
|
settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
|
|
6697
7422
|
}
|
|
6698
|
-
if (
|
|
7423
|
+
if (isRecord10(settings.skills)) {
|
|
6699
7424
|
const retainedSkills = Object.fromEntries(
|
|
6700
7425
|
Object.entries(settings.skills).filter(
|
|
6701
7426
|
([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
|
|
@@ -6715,7 +7440,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6715
7440
|
const entries2 = value.flatMap(cleanClaudeHookEntry);
|
|
6716
7441
|
return entries2.length > 0 ? entries2 : void 0;
|
|
6717
7442
|
}
|
|
6718
|
-
if (!
|
|
7443
|
+
if (!isRecord10(value)) return value;
|
|
6719
7444
|
if (Array.isArray(value.hooks) || typeof value.command === "string") {
|
|
6720
7445
|
const entries2 = cleanClaudeHookEntry(value);
|
|
6721
7446
|
return entries2.length > 0 ? entries2 : void 0;
|
|
@@ -6729,7 +7454,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6729
7454
|
}
|
|
6730
7455
|
function cleanClaudeHookEntry(entry) {
|
|
6731
7456
|
if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
|
|
6732
|
-
if (!
|
|
7457
|
+
if (!isRecord10(entry)) return [entry];
|
|
6733
7458
|
if (Array.isArray(entry.hooks)) {
|
|
6734
7459
|
const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
|
|
6735
7460
|
return hooks.length > 0 ? [{ ...entry, hooks }] : [];
|
|
@@ -6737,11 +7462,11 @@ function cleanClaudeHookEntry(entry) {
|
|
|
6737
7462
|
return isMancodeHookEntry(entry) ? [] : [entry];
|
|
6738
7463
|
}
|
|
6739
7464
|
function isMancodeHookEntry(value) {
|
|
6740
|
-
return
|
|
7465
|
+
return isRecord10(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
|
|
6741
7466
|
}
|
|
6742
7467
|
function containsLegacyMancodeHookPath2(value) {
|
|
6743
7468
|
if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
|
|
6744
|
-
if (!
|
|
7469
|
+
if (!isRecord10(value)) return false;
|
|
6745
7470
|
if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
|
|
6746
7471
|
return true;
|
|
6747
7472
|
}
|
|
@@ -6752,7 +7477,7 @@ async function uninstallCursor(rootDir) {
|
|
|
6752
7477
|
await removeCursorCommands(rootDir);
|
|
6753
7478
|
}
|
|
6754
7479
|
async function uninstallCodex(rootDir) {
|
|
6755
|
-
const agentsPath =
|
|
7480
|
+
const agentsPath = path25.join(rootDir, "AGENTS.md");
|
|
6756
7481
|
try {
|
|
6757
7482
|
const content = await readFile11(agentsPath, "utf-8");
|
|
6758
7483
|
const cleaned = removeManagedBlock(content);
|
|
@@ -6767,7 +7492,7 @@ async function uninstallCodex(rootDir) {
|
|
|
6767
7492
|
await removeCodexSkills(rootDir);
|
|
6768
7493
|
}
|
|
6769
7494
|
async function uninstallCopilot(rootDir) {
|
|
6770
|
-
const instructionsPath =
|
|
7495
|
+
const instructionsPath = path25.join(
|
|
6771
7496
|
rootDir,
|
|
6772
7497
|
".github",
|
|
6773
7498
|
"copilot-instructions.md"
|
|
@@ -6786,7 +7511,7 @@ async function uninstallCopilot(rootDir) {
|
|
|
6786
7511
|
await removeCopilotPrompts(rootDir);
|
|
6787
7512
|
}
|
|
6788
7513
|
async function uninstallZcode(rootDir) {
|
|
6789
|
-
const agentsPath =
|
|
7514
|
+
const agentsPath = path25.join(rootDir, "AGENTS.md");
|
|
6790
7515
|
try {
|
|
6791
7516
|
const content = await readFile11(agentsPath, "utf-8");
|
|
6792
7517
|
const cleaned = removeManagedBlock(
|
|
@@ -6805,13 +7530,13 @@ async function uninstallZcode(rootDir) {
|
|
|
6805
7530
|
await removeZcodeSkills(rootDir);
|
|
6806
7531
|
}
|
|
6807
7532
|
async function removeFromConfig(rootDir, platform) {
|
|
6808
|
-
const configPath =
|
|
7533
|
+
const configPath = path25.join(rootDir, ".mancode", "config.json");
|
|
6809
7534
|
try {
|
|
6810
7535
|
const raw = await readFile11(configPath, "utf-8");
|
|
6811
7536
|
const config = JSON.parse(raw);
|
|
6812
7537
|
if (Array.isArray(config.platforms)) {
|
|
6813
7538
|
config.platforms = config.platforms.filter((p) => p !== platform);
|
|
6814
|
-
if (
|
|
7539
|
+
if (isRecord10(config.platformOptions)) {
|
|
6815
7540
|
const platformOptions = Object.fromEntries(
|
|
6816
7541
|
Object.entries(config.platformOptions).filter(
|
|
6817
7542
|
([name]) => name !== platform
|
|
@@ -6829,12 +7554,12 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
6829
7554
|
} catch {
|
|
6830
7555
|
}
|
|
6831
7556
|
}
|
|
6832
|
-
function
|
|
7557
|
+
function isRecord10(value) {
|
|
6833
7558
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6834
7559
|
}
|
|
6835
|
-
async function
|
|
7560
|
+
async function pathExists11(p) {
|
|
6836
7561
|
try {
|
|
6837
|
-
await
|
|
7562
|
+
await access5(p);
|
|
6838
7563
|
return true;
|
|
6839
7564
|
} catch {
|
|
6840
7565
|
return false;
|
|
@@ -6842,30 +7567,30 @@ async function pathExists10(p) {
|
|
|
6842
7567
|
}
|
|
6843
7568
|
|
|
6844
7569
|
// src/commands/version.ts
|
|
6845
|
-
import
|
|
7570
|
+
import process12 from "process";
|
|
6846
7571
|
function version() {
|
|
6847
|
-
const nodeVersion =
|
|
6848
|
-
const platform = `${
|
|
7572
|
+
const nodeVersion = process12.version;
|
|
7573
|
+
const platform = `${process12.platform}/${process12.arch}`;
|
|
6849
7574
|
console.log(`mancode/${VERSION}`);
|
|
6850
7575
|
console.log(`node/${nodeVersion.replace("v", "")}`);
|
|
6851
7576
|
console.log(platform);
|
|
6852
7577
|
}
|
|
6853
7578
|
|
|
6854
7579
|
// src/commands/workflow.ts
|
|
6855
|
-
import { access as
|
|
6856
|
-
import
|
|
6857
|
-
var
|
|
6858
|
-
var
|
|
7580
|
+
import { access as access6 } from "fs/promises";
|
|
7581
|
+
import path26 from "path";
|
|
7582
|
+
var EXIT_OK9 = 0;
|
|
7583
|
+
var EXIT_NOT_INITIALIZED7 = 1;
|
|
6859
7584
|
var EXIT_INVALID_ARG2 = 2;
|
|
6860
7585
|
async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
6861
|
-
if (!await
|
|
7586
|
+
if (!await pathExists12(path26.join(rootDir, ".mancode", "state.json"))) {
|
|
6862
7587
|
if (options.json) {
|
|
6863
7588
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
6864
7589
|
} else {
|
|
6865
7590
|
console.error("\u2717 mancode not initialized.");
|
|
6866
7591
|
console.error(" Run `mancode init` to get started.");
|
|
6867
7592
|
}
|
|
6868
|
-
return
|
|
7593
|
+
return EXIT_NOT_INITIALIZED7;
|
|
6869
7594
|
}
|
|
6870
7595
|
switch (subcommand) {
|
|
6871
7596
|
case "create":
|
|
@@ -6914,7 +7639,7 @@ async function workflowReview(rootDir, args, options) {
|
|
|
6914
7639
|
if (!ledger)
|
|
6915
7640
|
return invalidArg(options, `review not initialized: ${taskId}`);
|
|
6916
7641
|
outputReviewLedger(ledger, options);
|
|
6917
|
-
return
|
|
7642
|
+
return EXIT_OK9;
|
|
6918
7643
|
}
|
|
6919
7644
|
if (meta.status !== "in_progress" || meta.currentStep < 6) {
|
|
6920
7645
|
return invalidArg(
|
|
@@ -6971,7 +7696,7 @@ async function workflowReview(rootDir, args, options) {
|
|
|
6971
7696
|
);
|
|
6972
7697
|
}
|
|
6973
7698
|
outputReviewLedger(ledger, options);
|
|
6974
|
-
return
|
|
7699
|
+
return EXIT_OK9;
|
|
6975
7700
|
} catch (error) {
|
|
6976
7701
|
return invalidArg(
|
|
6977
7702
|
options,
|
|
@@ -7027,7 +7752,7 @@ async function workflowCreate(rootDir, args, options) {
|
|
|
7027
7752
|
error instanceof Error ? error.message : "unable to create workflow"
|
|
7028
7753
|
);
|
|
7029
7754
|
}
|
|
7030
|
-
return
|
|
7755
|
+
return EXIT_OK9;
|
|
7031
7756
|
}
|
|
7032
7757
|
async function workflowUpdate(rootDir, taskId, options) {
|
|
7033
7758
|
if (!taskId) {
|
|
@@ -7125,18 +7850,18 @@ async function workflowUpdate(rootDir, taskId, options) {
|
|
|
7125
7850
|
} else {
|
|
7126
7851
|
console.log(`Updated workflow: ${taskId}`);
|
|
7127
7852
|
}
|
|
7128
|
-
return
|
|
7853
|
+
return EXIT_OK9;
|
|
7129
7854
|
}
|
|
7130
7855
|
async function workflowList(rootDir, options) {
|
|
7131
7856
|
const workflows = await listWorkflows(rootDir);
|
|
7132
7857
|
const views = attachActiveChildren(workflows);
|
|
7133
7858
|
if (options.json) {
|
|
7134
7859
|
console.log(JSON.stringify(views, null, 2));
|
|
7135
|
-
return
|
|
7860
|
+
return EXIT_OK9;
|
|
7136
7861
|
}
|
|
7137
7862
|
if (workflows.length === 0) {
|
|
7138
7863
|
console.log("No workflows.");
|
|
7139
|
-
return
|
|
7864
|
+
return EXIT_OK9;
|
|
7140
7865
|
}
|
|
7141
7866
|
const inProgress = workflows.filter((w) => w.status === "in_progress").length;
|
|
7142
7867
|
console.log(
|
|
@@ -7146,7 +7871,7 @@ async function workflowList(rootDir, options) {
|
|
|
7146
7871
|
for (const meta of views) {
|
|
7147
7872
|
console.log(formatWorkflowRow(meta));
|
|
7148
7873
|
}
|
|
7149
|
-
return
|
|
7874
|
+
return EXIT_OK9;
|
|
7150
7875
|
}
|
|
7151
7876
|
async function workflowShow(rootDir, taskId, options) {
|
|
7152
7877
|
if (!taskId) {
|
|
@@ -7183,7 +7908,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7183
7908
|
2
|
|
7184
7909
|
)
|
|
7185
7910
|
);
|
|
7186
|
-
return
|
|
7911
|
+
return EXIT_OK9;
|
|
7187
7912
|
}
|
|
7188
7913
|
console.log(`Workflow: ${meta.taskId}`);
|
|
7189
7914
|
console.log(`Task: ${meta.task}`);
|
|
@@ -7210,7 +7935,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7210
7935
|
}
|
|
7211
7936
|
}
|
|
7212
7937
|
}
|
|
7213
|
-
return
|
|
7938
|
+
return EXIT_OK9;
|
|
7214
7939
|
}
|
|
7215
7940
|
async function workflowClean(rootDir, options) {
|
|
7216
7941
|
const workflows = await listWorkflows(rootDir);
|
|
@@ -7278,7 +8003,7 @@ async function workflowClean(rootDir, options) {
|
|
|
7278
8003
|
} else {
|
|
7279
8004
|
console.log(`Removed ${removed.length} workflow(s).`);
|
|
7280
8005
|
}
|
|
7281
|
-
return
|
|
8006
|
+
return EXIT_OK9;
|
|
7282
8007
|
}
|
|
7283
8008
|
function formatWorkflowRow(meta) {
|
|
7284
8009
|
const stepMax = maxWorkflowStep(meta.mode);
|
|
@@ -7337,9 +8062,9 @@ function ago(iso) {
|
|
|
7337
8062
|
if (hours < 24) return `${hours}h ago`;
|
|
7338
8063
|
return `${Math.floor(hours / 24)}d ago`;
|
|
7339
8064
|
}
|
|
7340
|
-
async function
|
|
8065
|
+
async function pathExists12(p) {
|
|
7341
8066
|
try {
|
|
7342
|
-
await
|
|
8067
|
+
await access6(p);
|
|
7343
8068
|
return true;
|
|
7344
8069
|
} catch {
|
|
7345
8070
|
return false;
|
|
@@ -7350,8 +8075,11 @@ async function pathExists11(p) {
|
|
|
7350
8075
|
program.name("mancode").description(
|
|
7351
8076
|
"AI coding agent harness. Modes: solo, man, mamba, manteam, manps."
|
|
7352
8077
|
).version(VERSION);
|
|
7353
|
-
program.command("init").description("Initialize mancode in the current project").option("--force", "Reinstall even if already initialized").option("--yes", "Skip all confirmations (CI mode)").option("--team", "Force enable team mode (MVP-2)").option("--no-team", "Force disable team mode (MVP-2)").option("--style <name>", "Specify aesthetic style (MVP-2)").option("--platform <
|
|
7354
|
-
const code = await init(process.cwd(),
|
|
8078
|
+
program.command("init").description("Initialize mancode in the current project").option("--force", "Reinstall even if already initialized").option("--yes", "Skip all confirmations (CI mode)").option("--team", "Force enable team mode (MVP-2)").option("--no-team", "Force disable team mode (MVP-2)").option("--style <name>", "Specify aesthetic style (MVP-2)").option("--platform <platforms>", "Adapters: comma-separated names or all").option("--empty", "Initialize a safe empty directory as a generic project").option("--lang <locale>", "Initialization language: zh-CN or en").action(async (options) => {
|
|
8079
|
+
const code = await init(process.cwd(), {
|
|
8080
|
+
...options,
|
|
8081
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
8082
|
+
});
|
|
7355
8083
|
process.exitCode = code;
|
|
7356
8084
|
});
|
|
7357
8085
|
program.command("install [platform]").description(
|
|
@@ -7397,6 +8125,12 @@ program.command("refresh-style").description("Refresh project profile and rescan
|
|
|
7397
8125
|
const code = await refreshStyle(process.cwd());
|
|
7398
8126
|
process.exitCode = code;
|
|
7399
8127
|
});
|
|
8128
|
+
program.command("refresh-project").description(
|
|
8129
|
+
"Refresh detected project facts after adding Git or project files"
|
|
8130
|
+
).action(async () => {
|
|
8131
|
+
const code = await refreshProject(process.cwd());
|
|
8132
|
+
process.exitCode = code;
|
|
8133
|
+
});
|
|
7400
8134
|
program.command("version").description("Show version, node version, and platform").action(() => {
|
|
7401
8135
|
version();
|
|
7402
8136
|
});
|