mancode 0.3.3 → 0.3.5
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 +22 -9
- package/README.zh-CN.md +18 -6
- package/dist/cli.js +1434 -617
- package/dist/cli.js.map +1 -1
- package/package.json +1 -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 {
|
|
@@ -1385,6 +1386,7 @@ var DEFAULT_CONFIG = {
|
|
|
1385
1386
|
platforms: ["claude-code"],
|
|
1386
1387
|
cliCommand: "mancode",
|
|
1387
1388
|
cliArgs: [],
|
|
1389
|
+
teamMode: "auto",
|
|
1388
1390
|
forceTeamMode: false,
|
|
1389
1391
|
defaultStyle: null,
|
|
1390
1392
|
hooks: {
|
|
@@ -1773,100 +1775,10 @@ function isNodeError2(err) {
|
|
|
1773
1775
|
return err instanceof Error && "code" in err;
|
|
1774
1776
|
}
|
|
1775
1777
|
|
|
1776
|
-
// src/installers/
|
|
1777
|
-
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";
|
|
1778
1780
|
import path6 from "path";
|
|
1779
1781
|
|
|
1780
|
-
// src/installers/managed-block.ts
|
|
1781
|
-
var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
|
|
1782
|
-
var DEFAULT_MANCODE_END_MARKER = "<!-- mancode:end -->";
|
|
1783
|
-
function removeManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
1784
|
-
const start = findMarkerLine(existing, startMarker);
|
|
1785
|
-
const end = findMarkerLine(existing, endMarker);
|
|
1786
|
-
if (start === null && end === null) return existing;
|
|
1787
|
-
if (start === null || end === null) return existing;
|
|
1788
|
-
if (end.start < start.start) return existing;
|
|
1789
|
-
const before = existing.slice(0, start.start);
|
|
1790
|
-
const after = existing.slice(end.end);
|
|
1791
|
-
const merged = `${before}${after}`;
|
|
1792
|
-
return cleanUpOrphanedNewlines(merged);
|
|
1793
|
-
}
|
|
1794
|
-
function hasManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
1795
|
-
const start = findMarkerLine(existing, startMarker);
|
|
1796
|
-
const end = findMarkerLine(existing, endMarker);
|
|
1797
|
-
return start !== null && end !== null && end.start > start.start;
|
|
1798
|
-
}
|
|
1799
|
-
function cleanUpOrphanedNewlines(content) {
|
|
1800
|
-
const trimmed = content.replace(/\n{3,}/gu, "\n\n").replace(/\n+$/u, "\n");
|
|
1801
|
-
return trimmed || "";
|
|
1802
|
-
}
|
|
1803
|
-
function replaceManagedBlock(existing, block, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
1804
|
-
const normalizedBlock = normalizeManagedBlock(block, startMarker, endMarker);
|
|
1805
|
-
const start = findMarkerLine(existing, startMarker);
|
|
1806
|
-
const end = findMarkerLine(existing, endMarker);
|
|
1807
|
-
if (start === null !== (end === null)) {
|
|
1808
|
-
throw new Error("managed block is malformed: missing start or end marker");
|
|
1809
|
-
}
|
|
1810
|
-
if (start === null && end === null) {
|
|
1811
|
-
const trimmedExisting = trimTrailingNewlines(existing);
|
|
1812
|
-
if (!trimmedExisting) return `${normalizedBlock}
|
|
1813
|
-
`;
|
|
1814
|
-
return `${trimmedExisting}
|
|
1815
|
-
|
|
1816
|
-
${normalizedBlock}
|
|
1817
|
-
`;
|
|
1818
|
-
}
|
|
1819
|
-
if (!start || !end) {
|
|
1820
|
-
throw new Error("managed block is malformed: missing start or end marker");
|
|
1821
|
-
}
|
|
1822
|
-
if (end.start < start.start) {
|
|
1823
|
-
throw new Error("managed block is malformed: end marker precedes start");
|
|
1824
|
-
}
|
|
1825
|
-
return `${existing.slice(0, start.start)}${normalizedBlock}${existing.slice(
|
|
1826
|
-
end.end
|
|
1827
|
-
)}`;
|
|
1828
|
-
}
|
|
1829
|
-
function normalizeManagedBlock(block, startMarker, endMarker) {
|
|
1830
|
-
const trimmedBlock = block.trim();
|
|
1831
|
-
const hasStart = trimmedBlock.startsWith(startMarker);
|
|
1832
|
-
const hasEnd = trimmedBlock.endsWith(endMarker);
|
|
1833
|
-
if (hasStart && hasEnd) return trimmedBlock;
|
|
1834
|
-
if (hasStart || hasEnd) {
|
|
1835
|
-
throw new Error("managed block content includes only one marker");
|
|
1836
|
-
}
|
|
1837
|
-
return `${startMarker}
|
|
1838
|
-
${trimmedBlock}
|
|
1839
|
-
${endMarker}`;
|
|
1840
|
-
}
|
|
1841
|
-
function trimTrailingNewlines(value) {
|
|
1842
|
-
return value.replace(/\n+$/u, "");
|
|
1843
|
-
}
|
|
1844
|
-
function findMarkerLine(content, marker) {
|
|
1845
|
-
let offset = 0;
|
|
1846
|
-
let inFence = null;
|
|
1847
|
-
for (const lineWithBreak of content.matchAll(/[^\n]*(?:\n|$)/gu)) {
|
|
1848
|
-
const rawLine = lineWithBreak[0];
|
|
1849
|
-
if (!rawLine) break;
|
|
1850
|
-
const line = rawLine.replace(/\n$/u, "").replace(/\r$/u, "");
|
|
1851
|
-
const fence = line.match(/^(`{3,}|~{3,})/u)?.[1];
|
|
1852
|
-
if (fence) {
|
|
1853
|
-
const char = fence[0];
|
|
1854
|
-
if (!inFence) {
|
|
1855
|
-
inFence = { char, length: fence.length };
|
|
1856
|
-
} else if (inFence.char === char && fence.length >= inFence.length) {
|
|
1857
|
-
inFence = null;
|
|
1858
|
-
}
|
|
1859
|
-
} else if (!inFence && line === marker) {
|
|
1860
|
-
return {
|
|
1861
|
-
start: offset,
|
|
1862
|
-
end: offset + marker.length
|
|
1863
|
-
};
|
|
1864
|
-
}
|
|
1865
|
-
offset += rawLine.length;
|
|
1866
|
-
}
|
|
1867
|
-
return null;
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
1782
|
// src/installers/mode-skills.ts
|
|
1871
1783
|
import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, rm as rm2, writeFile as writeFile4 } from "fs/promises";
|
|
1872
1784
|
import path4 from "path";
|
|
@@ -2452,99 +2364,7 @@ function formatValue(value) {
|
|
|
2452
2364
|
return "";
|
|
2453
2365
|
}
|
|
2454
2366
|
|
|
2455
|
-
// src/installers/codex.ts
|
|
2456
|
-
async function installCodex(projectRoot, options) {
|
|
2457
|
-
await installMancodeCore(projectRoot);
|
|
2458
|
-
const agentsPath = path6.join(projectRoot, "AGENTS.md");
|
|
2459
|
-
const existing = await readTextIfExists(agentsPath);
|
|
2460
|
-
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2461
|
-
platform: "codex",
|
|
2462
|
-
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
2463
|
-
capabilities: {
|
|
2464
|
-
slashCommands: "partial",
|
|
2465
|
-
subagents: false,
|
|
2466
|
-
hooks: false,
|
|
2467
|
-
skills: "agents-skills"
|
|
2468
|
-
},
|
|
2469
|
-
minimal: options.minimal,
|
|
2470
|
-
techStack: options.techStack,
|
|
2471
|
-
uiLibrary: options.uiLibrary,
|
|
2472
|
-
projectProfile: options.projectProfile
|
|
2473
|
-
});
|
|
2474
|
-
const block = [
|
|
2475
|
-
DEFAULT_MANCODE_START_MARKER,
|
|
2476
|
-
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2477
|
-
"",
|
|
2478
|
-
"# mancode Configuration",
|
|
2479
|
-
"",
|
|
2480
|
-
sharedContent.trim(),
|
|
2481
|
-
DEFAULT_MANCODE_END_MARKER
|
|
2482
|
-
].join("\n");
|
|
2483
|
-
await writeFile5(agentsPath, replaceManagedBlock(existing, block), "utf-8");
|
|
2484
|
-
await installCodexSkills(projectRoot, options.minimal ?? false);
|
|
2485
|
-
}
|
|
2486
|
-
|
|
2487
|
-
// src/installers/copilot.ts
|
|
2488
|
-
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
2489
|
-
import path7 from "path";
|
|
2490
|
-
async function installCopilot(projectRoot, options) {
|
|
2491
|
-
await installMancodeCore(projectRoot);
|
|
2492
|
-
const githubDir = path7.join(projectRoot, ".github");
|
|
2493
|
-
await mkdir5(githubDir, { recursive: true });
|
|
2494
|
-
const instructionsPath = path7.join(githubDir, "copilot-instructions.md");
|
|
2495
|
-
const existing = await readTextIfExists(instructionsPath);
|
|
2496
|
-
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2497
|
-
platform: "copilot",
|
|
2498
|
-
displayName: "GitHub Copilot",
|
|
2499
|
-
capabilities: {
|
|
2500
|
-
slashCommands: "none",
|
|
2501
|
-
subagents: false,
|
|
2502
|
-
hooks: false,
|
|
2503
|
-
skills: "instructions"
|
|
2504
|
-
},
|
|
2505
|
-
minimal: true,
|
|
2506
|
-
techStack: options.techStack,
|
|
2507
|
-
uiLibrary: options.uiLibrary,
|
|
2508
|
-
projectProfile: options.projectProfile
|
|
2509
|
-
});
|
|
2510
|
-
const sections = [
|
|
2511
|
-
DEFAULT_MANCODE_START_MARKER,
|
|
2512
|
-
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2513
|
-
"",
|
|
2514
|
-
"# mancode for GitHub Copilot",
|
|
2515
|
-
"",
|
|
2516
|
-
sharedContent.trim()
|
|
2517
|
-
];
|
|
2518
|
-
if (!options.minimal) {
|
|
2519
|
-
sections.push("", renderCopilotPromptConventions());
|
|
2520
|
-
}
|
|
2521
|
-
sections.push(DEFAULT_MANCODE_END_MARKER);
|
|
2522
|
-
await writeFile6(
|
|
2523
|
-
instructionsPath,
|
|
2524
|
-
replaceManagedBlock(existing, sections.join("\n")),
|
|
2525
|
-
"utf-8"
|
|
2526
|
-
);
|
|
2527
|
-
await installCopilotPrompts(projectRoot, options.minimal ?? false);
|
|
2528
|
-
}
|
|
2529
|
-
function renderCopilotPromptConventions() {
|
|
2530
|
-
return [
|
|
2531
|
-
"## mancode Prompt Conventions",
|
|
2532
|
-
"",
|
|
2533
|
-
"GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
|
|
2534
|
-
"",
|
|
2535
|
-
"- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
|
|
2536
|
-
"- mamba: diagnose bugs and validate real user flows or regressions.",
|
|
2537
|
-
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
2538
|
-
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
2539
|
-
"- mansolo: exit any active mode and return to default solo behavior.",
|
|
2540
|
-
"",
|
|
2541
|
-
"These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
|
|
2542
|
-
].join("\n");
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
2367
|
// src/installers/cursor.ts
|
|
2546
|
-
import { mkdir as mkdir6, readFile as readFile6, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
2547
|
-
import path8 from "path";
|
|
2548
2368
|
var MANCODE_CURSOR_CORE_RULE_FILES = [
|
|
2549
2369
|
"mancode-context.mdc",
|
|
2550
2370
|
"mancode-practice.mdc",
|
|
@@ -2563,8 +2383,8 @@ var MANCODE_CURSOR_RULE_FILES = [
|
|
|
2563
2383
|
];
|
|
2564
2384
|
async function installCursor(projectRoot, options) {
|
|
2565
2385
|
await installMancodeCore(projectRoot);
|
|
2566
|
-
const rulesDir =
|
|
2567
|
-
await
|
|
2386
|
+
const rulesDir = path6.join(projectRoot, ".cursor", "rules");
|
|
2387
|
+
await mkdir5(rulesDir, { recursive: true });
|
|
2568
2388
|
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2569
2389
|
platform: "cursor",
|
|
2570
2390
|
displayName: "Cursor",
|
|
@@ -2652,14 +2472,14 @@ async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
|
|
|
2652
2472
|
|
|
2653
2473
|
${body.trim()}
|
|
2654
2474
|
`;
|
|
2655
|
-
const rulePath =
|
|
2475
|
+
const rulePath = path6.join(rulesDir, fileName);
|
|
2656
2476
|
const existing = await readTextIfExists4(rulePath);
|
|
2657
2477
|
if (existing !== null && !isGeneratedCursorRule(existing, fileName)) {
|
|
2658
2478
|
throw new Error(
|
|
2659
2479
|
`refusing to overwrite user-authored Cursor rule: ${rulePath}`
|
|
2660
2480
|
);
|
|
2661
2481
|
}
|
|
2662
|
-
await
|
|
2482
|
+
await writeFile5(
|
|
2663
2483
|
rulePath,
|
|
2664
2484
|
content.replace("---\n\n", `---
|
|
2665
2485
|
|
|
@@ -2673,17 +2493,17 @@ async function removeAdvancedRules(rulesDir) {
|
|
|
2673
2493
|
for (const fileName of MANCODE_CURSOR_ADVANCED_RULE_FILES) {
|
|
2674
2494
|
await removeGeneratedCursorRule(rulesDir, fileName);
|
|
2675
2495
|
}
|
|
2676
|
-
await removeLegacyCursorRules(
|
|
2496
|
+
await removeLegacyCursorRules(path6.dirname(path6.dirname(rulesDir)));
|
|
2677
2497
|
}
|
|
2678
2498
|
async function removeCursorGeneratedRules(projectRoot) {
|
|
2679
|
-
const rulesDir =
|
|
2499
|
+
const rulesDir = path6.join(projectRoot, ".cursor", "rules");
|
|
2680
2500
|
for (const fileName of MANCODE_CURSOR_RULE_FILES) {
|
|
2681
2501
|
await removeGeneratedCursorRule(rulesDir, fileName);
|
|
2682
2502
|
}
|
|
2683
2503
|
await removeLegacyCursorRules(projectRoot);
|
|
2684
2504
|
}
|
|
2685
2505
|
async function removeLegacyCursorRules(projectRoot) {
|
|
2686
|
-
const legacyPath =
|
|
2506
|
+
const legacyPath = path6.join(
|
|
2687
2507
|
projectRoot,
|
|
2688
2508
|
".cursor",
|
|
2689
2509
|
"rules",
|
|
@@ -2698,7 +2518,7 @@ async function removeLegacyCursorRules(projectRoot) {
|
|
|
2698
2518
|
}
|
|
2699
2519
|
}
|
|
2700
2520
|
async function removeGeneratedCursorRule(rulesDir, fileName) {
|
|
2701
|
-
const rulePath =
|
|
2521
|
+
const rulePath = path6.join(rulesDir, fileName);
|
|
2702
2522
|
const content = await readTextIfExists4(rulePath);
|
|
2703
2523
|
if (content && isGeneratedCursorRule(content, fileName)) {
|
|
2704
2524
|
await rm3(rulePath, { force: true });
|
|
@@ -2768,78 +2588,262 @@ function renderManpsRule() {
|
|
|
2768
2588
|
return renderModeSkill("manps", "/");
|
|
2769
2589
|
}
|
|
2770
2590
|
|
|
2771
|
-
// src/installers/
|
|
2772
|
-
import { writeFile as
|
|
2773
|
-
import
|
|
2774
|
-
var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
|
|
2775
|
-
var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
|
|
2776
|
-
async function installZcode(projectRoot, options) {
|
|
2777
|
-
await installMancodeCore(projectRoot);
|
|
2778
|
-
const agentsPath = path9.join(projectRoot, "AGENTS.md");
|
|
2779
|
-
const existing = await readTextIfExists(agentsPath);
|
|
2780
|
-
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2781
|
-
platform: "zcode",
|
|
2782
|
-
displayName: "ZCode",
|
|
2783
|
-
capabilities: {
|
|
2784
|
-
slashCommands: "partial",
|
|
2785
|
-
subagents: false,
|
|
2786
|
-
hooks: false,
|
|
2787
|
-
skills: "agents-skills"
|
|
2788
|
-
},
|
|
2789
|
-
minimal: options.minimal,
|
|
2790
|
-
techStack: options.techStack,
|
|
2791
|
-
uiLibrary: options.uiLibrary,
|
|
2792
|
-
projectProfile: options.projectProfile
|
|
2793
|
-
});
|
|
2794
|
-
const block = [
|
|
2795
|
-
ZCODE_MANCODE_START_MARKER,
|
|
2796
|
-
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2797
|
-
"",
|
|
2798
|
-
"# mancode Configuration",
|
|
2799
|
-
"",
|
|
2800
|
-
sharedContent.trim(),
|
|
2801
|
-
ZCODE_MANCODE_END_MARKER
|
|
2802
|
-
].join("\n");
|
|
2803
|
-
await writeFile8(
|
|
2804
|
-
agentsPath,
|
|
2805
|
-
replaceManagedBlock(
|
|
2806
|
-
existing,
|
|
2807
|
-
block,
|
|
2808
|
-
ZCODE_MANCODE_START_MARKER,
|
|
2809
|
-
ZCODE_MANCODE_END_MARKER
|
|
2810
|
-
),
|
|
2811
|
-
"utf-8"
|
|
2812
|
-
);
|
|
2813
|
-
await installZcodeSkills(projectRoot, options.minimal ?? false);
|
|
2814
|
-
}
|
|
2591
|
+
// src/installers/codex.ts
|
|
2592
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2593
|
+
import path7 from "path";
|
|
2815
2594
|
|
|
2816
|
-
// src/installers/
|
|
2817
|
-
var
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
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, {
|
|
2785
|
+
platform: "zcode",
|
|
2786
|
+
displayName: "ZCode",
|
|
2787
|
+
capabilities: {
|
|
2788
|
+
slashCommands: "partial",
|
|
2789
|
+
subagents: false,
|
|
2790
|
+
hooks: false,
|
|
2791
|
+
skills: "agents-skills"
|
|
2792
|
+
},
|
|
2793
|
+
minimal: options.minimal,
|
|
2794
|
+
techStack: options.techStack,
|
|
2795
|
+
uiLibrary: options.uiLibrary,
|
|
2796
|
+
projectProfile: options.projectProfile
|
|
2797
|
+
});
|
|
2798
|
+
const block = [
|
|
2799
|
+
ZCODE_MANCODE_START_MARKER,
|
|
2800
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2801
|
+
"",
|
|
2802
|
+
"# mancode Configuration",
|
|
2803
|
+
"",
|
|
2804
|
+
sharedContent.trim(),
|
|
2805
|
+
ZCODE_MANCODE_END_MARKER
|
|
2806
|
+
].join("\n");
|
|
2807
|
+
await writeFile8(
|
|
2808
|
+
agentsPath,
|
|
2809
|
+
replaceManagedBlock(
|
|
2810
|
+
existing,
|
|
2811
|
+
block,
|
|
2812
|
+
ZCODE_MANCODE_START_MARKER,
|
|
2813
|
+
ZCODE_MANCODE_END_MARKER
|
|
2814
|
+
),
|
|
2815
|
+
"utf-8"
|
|
2816
|
+
);
|
|
2817
|
+
await installZcodeSkills(projectRoot, options.minimal ?? false);
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
// src/installers/registry.ts
|
|
2821
|
+
var PLATFORM_INSTALLERS = {
|
|
2822
|
+
"claude-code": {
|
|
2823
|
+
name: "claude-code",
|
|
2824
|
+
displayName: "Claude Code",
|
|
2825
|
+
capabilities: {
|
|
2826
|
+
slashCommands: "native",
|
|
2827
|
+
subagents: true,
|
|
2828
|
+
hooks: true,
|
|
2829
|
+
skills: "native"
|
|
2830
|
+
},
|
|
2831
|
+
install: installClaudeCode
|
|
2832
|
+
},
|
|
2833
|
+
cursor: {
|
|
2834
|
+
name: "cursor",
|
|
2835
|
+
displayName: "Cursor",
|
|
2836
|
+
capabilities: {
|
|
2837
|
+
slashCommands: "native",
|
|
2838
|
+
subagents: false,
|
|
2839
|
+
hooks: false,
|
|
2840
|
+
skills: "rules"
|
|
2841
|
+
},
|
|
2842
|
+
install: installCursor
|
|
2843
|
+
},
|
|
2844
|
+
codex: {
|
|
2845
|
+
name: "codex",
|
|
2846
|
+
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
2843
2847
|
capabilities: {
|
|
2844
2848
|
slashCommands: "partial",
|
|
2845
2849
|
subagents: false,
|
|
@@ -2932,13 +2936,13 @@ async function detectTeamStatus(projectRoot) {
|
|
|
2932
2936
|
}
|
|
2933
2937
|
}
|
|
2934
2938
|
async function runGit(cwd, args) {
|
|
2935
|
-
const { stdout } = await execFileAsync("git", args, {
|
|
2939
|
+
const { stdout: stdout2 } = await execFileAsync("git", args, {
|
|
2936
2940
|
cwd,
|
|
2937
2941
|
encoding: "utf-8",
|
|
2938
2942
|
env: GIT_ENV,
|
|
2939
2943
|
windowsHide: true
|
|
2940
2944
|
});
|
|
2941
|
-
return
|
|
2945
|
+
return stdout2;
|
|
2942
2946
|
}
|
|
2943
2947
|
function countUniqueLines(output) {
|
|
2944
2948
|
return new Set(
|
|
@@ -2963,9 +2967,191 @@ async function detectSystemDeps(env = process3.env) {
|
|
|
2963
2967
|
}
|
|
2964
2968
|
}
|
|
2965
2969
|
|
|
2970
|
+
// src/system/init-onboarding.ts
|
|
2971
|
+
import { execFileSync } from "child_process";
|
|
2972
|
+
import { promises as fs } from "fs";
|
|
2973
|
+
import path11 from "path";
|
|
2974
|
+
import process4 from "process";
|
|
2975
|
+
import { stdin, stdout } from "process";
|
|
2976
|
+
import { createInterface } from "readline/promises";
|
|
2977
|
+
var ALL_PLATFORMS = Object.keys(PLATFORM_INSTALLERS);
|
|
2978
|
+
var cachedNativeSystemLocale;
|
|
2979
|
+
function detectInitLocale(override, environment = process4.env, systemLocale = Intl.DateTimeFormat().resolvedOptions().locale, nativeLocale) {
|
|
2980
|
+
if (override) return parseLocale(override);
|
|
2981
|
+
const resolvedNativeLocale = nativeLocale === void 0 ? getNativeSystemLocale() : nativeLocale;
|
|
2982
|
+
return parseLocale(resolvedNativeLocale) ?? detectEnvironmentLocale(environment) ?? parseLocale(systemLocale) ?? "en";
|
|
2983
|
+
}
|
|
2984
|
+
function detectNativeSystemLocale(platform = process4.platform, runCommand = runLocaleCommand) {
|
|
2985
|
+
if (platform === "darwin") {
|
|
2986
|
+
const languages = runCommand("defaults", ["read", "-g", "AppleLanguages"]);
|
|
2987
|
+
const primaryLanguage = parsePrimaryAppleLanguage(languages);
|
|
2988
|
+
if (primaryLanguage) return primaryLanguage;
|
|
2989
|
+
return cleanLocaleOutput(
|
|
2990
|
+
runCommand("defaults", ["read", "-g", "AppleLocale"])
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2993
|
+
if (platform === "win32") {
|
|
2994
|
+
return cleanLocaleOutput(
|
|
2995
|
+
runCommand("powershell.exe", [
|
|
2996
|
+
"-NoLogo",
|
|
2997
|
+
"-NoProfile",
|
|
2998
|
+
"-NonInteractive",
|
|
2999
|
+
"-Command",
|
|
3000
|
+
"[System.Globalization.CultureInfo]::CurrentUICulture.Name"
|
|
3001
|
+
])
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
return null;
|
|
3005
|
+
}
|
|
3006
|
+
function getNativeSystemLocale() {
|
|
3007
|
+
if (cachedNativeSystemLocale === void 0) {
|
|
3008
|
+
cachedNativeSystemLocale = detectNativeSystemLocale();
|
|
3009
|
+
}
|
|
3010
|
+
return cachedNativeSystemLocale;
|
|
3011
|
+
}
|
|
3012
|
+
function detectEnvironmentLocale(environment) {
|
|
3013
|
+
const candidates = [
|
|
3014
|
+
environment.LANGUAGE,
|
|
3015
|
+
environment.LC_ALL,
|
|
3016
|
+
environment.LC_MESSAGES,
|
|
3017
|
+
environment.LANG
|
|
3018
|
+
];
|
|
3019
|
+
for (const candidate of candidates) {
|
|
3020
|
+
for (const locale of candidate?.split(":") ?? []) {
|
|
3021
|
+
const parsed = parseLocale(locale);
|
|
3022
|
+
if (parsed) return parsed;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return null;
|
|
3026
|
+
}
|
|
3027
|
+
function runLocaleCommand(command, args) {
|
|
3028
|
+
try {
|
|
3029
|
+
const output = execFileSync(command, [...args], {
|
|
3030
|
+
encoding: "utf8",
|
|
3031
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3032
|
+
timeout: 2e3,
|
|
3033
|
+
windowsHide: true
|
|
3034
|
+
});
|
|
3035
|
+
return cleanLocaleOutput(output);
|
|
3036
|
+
} catch {
|
|
3037
|
+
return null;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
function parsePrimaryAppleLanguage(output) {
|
|
3041
|
+
const cleaned = cleanLocaleOutput(output);
|
|
3042
|
+
if (!cleaned) return null;
|
|
3043
|
+
const quoted = cleaned.match(/"([^"]+)"/)?.[1];
|
|
3044
|
+
if (quoted) return quoted;
|
|
3045
|
+
return cleaned.split(/[\s(),]+/).find((value) => /^[a-z]{2,3}(?:[-_][a-z0-9]+)*$/i.test(value)) ?? null;
|
|
3046
|
+
}
|
|
3047
|
+
function cleanLocaleOutput(output) {
|
|
3048
|
+
const cleaned = output?.replace(/^\uFEFF/, "").trim();
|
|
3049
|
+
return cleaned || null;
|
|
3050
|
+
}
|
|
3051
|
+
function parseLocale(value) {
|
|
3052
|
+
if (!value) return null;
|
|
3053
|
+
const normalized = value.toLowerCase().replace("_", "-");
|
|
3054
|
+
if (normalized === "zh" || normalized.startsWith("zh-")) return "zh-CN";
|
|
3055
|
+
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
function parsePlatformSelection(value) {
|
|
3059
|
+
const normalized = value.trim().toLowerCase();
|
|
3060
|
+
if (normalized === "all" || normalized === "\u5168\u90E8") return [...ALL_PLATFORMS];
|
|
3061
|
+
const choices = normalized.split(",").map((item) => item.trim()).filter(Boolean);
|
|
3062
|
+
if (choices.length === 0) return null;
|
|
3063
|
+
if (!choices.every((item) => item in PLATFORM_INSTALLERS)) {
|
|
3064
|
+
return null;
|
|
3065
|
+
}
|
|
3066
|
+
return [...new Set(choices)];
|
|
3067
|
+
}
|
|
3068
|
+
async function detectPlatformHints(rootDir, environment = process4.env) {
|
|
3069
|
+
const hints = /* @__PURE__ */ new Set();
|
|
3070
|
+
if (environment.CLAUDECODE || environment.CLAUDE_CODE)
|
|
3071
|
+
hints.add("claude-code");
|
|
3072
|
+
if (environment.CODEX_HOME) hints.add("codex");
|
|
3073
|
+
if (environment.CURSOR_TRACE_ID) hints.add("cursor");
|
|
3074
|
+
if (environment.COPILOT_AGENT || environment.GITHUB_COPILOT)
|
|
3075
|
+
hints.add("copilot");
|
|
3076
|
+
const exists = async (relative) => {
|
|
3077
|
+
try {
|
|
3078
|
+
await fs.access(path11.join(rootDir, relative));
|
|
3079
|
+
return true;
|
|
3080
|
+
} catch {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
if (await exists(".claude")) hints.add("claude-code");
|
|
3085
|
+
if (await exists(".cursor")) hints.add("cursor");
|
|
3086
|
+
if (await exists(".github/copilot-instructions.md")) hints.add("copilot");
|
|
3087
|
+
return ALL_PLATFORMS.filter((platform) => hints.has(platform));
|
|
3088
|
+
}
|
|
3089
|
+
function createTerminalPrompter() {
|
|
3090
|
+
return {
|
|
3091
|
+
async confirmGenericProject({ rootDir, locale }) {
|
|
3092
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
3093
|
+
try {
|
|
3094
|
+
if (locale === "zh-CN") {
|
|
3095
|
+
console.log("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\u3002");
|
|
3096
|
+
console.log(`\u76EE\u5F55\uFF1A${rootDir}`);
|
|
3097
|
+
console.log("\u8FD9\u662F\u4E00\u4E2A\u65B0\u9879\u76EE\u5417\uFF1F");
|
|
3098
|
+
console.log("[y] \u521D\u59CB\u5316\u4E3A\u901A\u7528\u9879\u76EE");
|
|
3099
|
+
console.log("[n] \u9000\u51FA");
|
|
3100
|
+
const answer2 = await rl.question("\u8BF7\u9009\u62E9 [y/N]: ");
|
|
3101
|
+
return ["y", "yes", "\u662F"].includes(answer2.trim().toLowerCase());
|
|
3102
|
+
}
|
|
3103
|
+
console.log("No project files were detected in the current directory.");
|
|
3104
|
+
console.log(`Directory: ${rootDir}`);
|
|
3105
|
+
console.log("Is this a new project?");
|
|
3106
|
+
console.log("[y] Initialize as a generic project");
|
|
3107
|
+
console.log("[n] Exit");
|
|
3108
|
+
const answer = await rl.question("Choose [y/N]: ");
|
|
3109
|
+
return ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
3110
|
+
} finally {
|
|
3111
|
+
rl.close();
|
|
3112
|
+
}
|
|
3113
|
+
},
|
|
3114
|
+
async selectPlatforms({ locale, detected }) {
|
|
3115
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
3116
|
+
try {
|
|
3117
|
+
const names = ALL_PLATFORMS.map((platform, index) => {
|
|
3118
|
+
const suffix = detected.includes(platform) ? locale === "zh-CN" ? "\uFF08\u5DF2\u68C0\u6D4B\u5230\uFF09" : " (detected)" : "";
|
|
3119
|
+
return `${index + 1}. ${PLATFORM_INSTALLERS[platform].displayName}${suffix}`;
|
|
3120
|
+
});
|
|
3121
|
+
console.log(
|
|
3122
|
+
locale === "zh-CN" ? "\n\u9009\u62E9\u8981\u521D\u59CB\u5316\u7684\u5E73\u53F0\uFF1A" : "\nChoose platforms to initialize:"
|
|
3123
|
+
);
|
|
3124
|
+
console.log(names.join("\n"));
|
|
3125
|
+
console.log(locale === "zh-CN" ? "a. \u5168\u90E8\u5E73\u53F0" : "a. All platforms");
|
|
3126
|
+
console.log(
|
|
3127
|
+
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."
|
|
3128
|
+
);
|
|
3129
|
+
const answer = (await rl.question(locale === "zh-CN" ? "\u9009\u62E9: " : "Selection: ")).trim().toLowerCase();
|
|
3130
|
+
if (answer === "a" || answer === "all" || answer === "\u5168\u90E8")
|
|
3131
|
+
return [...ALL_PLATFORMS];
|
|
3132
|
+
const indexes = answer.split(",").map((item) => Number(item.trim()));
|
|
3133
|
+
if (!indexes.length || indexes.some(
|
|
3134
|
+
(index) => !Number.isInteger(index) || index < 1 || index > ALL_PLATFORMS.length
|
|
3135
|
+
)) {
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
const selected = [];
|
|
3139
|
+
for (const index of indexes) {
|
|
3140
|
+
const platform = ALL_PLATFORMS[index - 1];
|
|
3141
|
+
if (!platform) return null;
|
|
3142
|
+
selected.push(platform);
|
|
3143
|
+
}
|
|
3144
|
+
return [...new Set(selected)];
|
|
3145
|
+
} finally {
|
|
3146
|
+
rl.close();
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
|
|
2966
3152
|
// src/system/project-profile.ts
|
|
2967
3153
|
import { readFile as readFile7, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
2968
|
-
import
|
|
3154
|
+
import path12 from "path";
|
|
2969
3155
|
var PROJECT_MANIFESTS = [
|
|
2970
3156
|
"package.json",
|
|
2971
3157
|
"pyproject.toml",
|
|
@@ -2998,12 +3184,12 @@ async function detectProjectProfile(projectRoot) {
|
|
|
2998
3184
|
"data",
|
|
2999
3185
|
"notebooks"
|
|
3000
3186
|
]);
|
|
3001
|
-
const packageJson = await readJson2(
|
|
3187
|
+
const packageJson = await readJson2(path12.join(projectRoot, "package.json"));
|
|
3002
3188
|
const [pubspec, pythonManifests] = await Promise.all([
|
|
3003
|
-
readText(
|
|
3189
|
+
readText(path12.join(projectRoot, "pubspec.yaml")),
|
|
3004
3190
|
Promise.all([
|
|
3005
|
-
readText(
|
|
3006
|
-
readText(
|
|
3191
|
+
readText(path12.join(projectRoot, "pyproject.toml")),
|
|
3192
|
+
readText(path12.join(projectRoot, "requirements.txt"))
|
|
3007
3193
|
]).then((parts) => parts.filter(Boolean).join("\n"))
|
|
3008
3194
|
]);
|
|
3009
3195
|
const deps = Object.keys({
|
|
@@ -3012,7 +3198,7 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3012
3198
|
...packageJson?.peerDependencies
|
|
3013
3199
|
});
|
|
3014
3200
|
const flutter = isFlutterProject(pubspec);
|
|
3015
|
-
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(
|
|
3201
|
+
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path12.join(projectRoot, "src", "components", "ui"));
|
|
3016
3202
|
const languages = inferLanguages(manifests, entries);
|
|
3017
3203
|
const frameworks = inferFrameworks(
|
|
3018
3204
|
deps,
|
|
@@ -3055,7 +3241,7 @@ async function existingDirs(root, candidates) {
|
|
|
3055
3241
|
const found = [];
|
|
3056
3242
|
for (const candidate of candidates) {
|
|
3057
3243
|
try {
|
|
3058
|
-
if ((await stat2(
|
|
3244
|
+
if ((await stat2(path12.join(root, candidate))).isDirectory())
|
|
3059
3245
|
found.push(candidate);
|
|
3060
3246
|
} catch {
|
|
3061
3247
|
}
|
|
@@ -3229,8 +3415,8 @@ function isFlutterProject(pubspec) {
|
|
|
3229
3415
|
}
|
|
3230
3416
|
|
|
3231
3417
|
// src/system/scan-aesthetics.ts
|
|
3232
|
-
import { promises as
|
|
3233
|
-
import
|
|
3418
|
+
import { promises as fs2 } from "fs";
|
|
3419
|
+
import path13 from "path";
|
|
3234
3420
|
var MAX_COMPONENT_SCAN_DEPTH = 12;
|
|
3235
3421
|
var MAX_COMPONENT_FILES = 2e3;
|
|
3236
3422
|
async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
@@ -3243,7 +3429,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3243
3429
|
const cssScan = await scanCssVariables(projectRoot);
|
|
3244
3430
|
if (configResult) {
|
|
3245
3431
|
sourceFiles.push(configResult.relPath);
|
|
3246
|
-
const content = await
|
|
3432
|
+
const content = await fs2.readFile(configResult.absPath, "utf-8");
|
|
3247
3433
|
const inspectableContent = stripJsComments(content);
|
|
3248
3434
|
const themeBlock = findKeyBlock(inspectableContent, "theme");
|
|
3249
3435
|
if (themeBlock) {
|
|
@@ -3266,7 +3452,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3266
3452
|
} else if (await hasTailwindDep(projectRoot) || uiLibrary) {
|
|
3267
3453
|
matchLevel = "low";
|
|
3268
3454
|
}
|
|
3269
|
-
if (uiLibrary && await pathExists2(
|
|
3455
|
+
if (uiLibrary && await pathExists2(path13.join(projectRoot, "package.json"))) {
|
|
3270
3456
|
sourceFiles.push("package.json");
|
|
3271
3457
|
}
|
|
3272
3458
|
sourceFiles.push(...cssScan.sourceFiles);
|
|
@@ -3291,7 +3477,7 @@ async function findTailwindConfig(projectRoot) {
|
|
|
3291
3477
|
"tailwind.config.mjs"
|
|
3292
3478
|
];
|
|
3293
3479
|
for (const name of candidates) {
|
|
3294
|
-
const absPath =
|
|
3480
|
+
const absPath = path13.join(projectRoot, name);
|
|
3295
3481
|
if (await pathExists2(absPath)) {
|
|
3296
3482
|
return { absPath, relPath: name };
|
|
3297
3483
|
}
|
|
@@ -3518,7 +3704,7 @@ async function scanComponents(projectRoot) {
|
|
|
3518
3704
|
const names = /* @__PURE__ */ new Set();
|
|
3519
3705
|
let visitedFiles = 0;
|
|
3520
3706
|
for (const relRoot of roots) {
|
|
3521
|
-
const absRoot =
|
|
3707
|
+
const absRoot = path13.join(projectRoot, relRoot);
|
|
3522
3708
|
if (!await pathExists2(absRoot)) continue;
|
|
3523
3709
|
visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
|
|
3524
3710
|
if (visitedFiles >= MAX_COMPONENT_FILES) break;
|
|
@@ -3532,16 +3718,16 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3532
3718
|
let fileCount = visitedFiles;
|
|
3533
3719
|
let entries;
|
|
3534
3720
|
try {
|
|
3535
|
-
entries = await
|
|
3721
|
+
entries = await fs2.readdir(dir);
|
|
3536
3722
|
} catch {
|
|
3537
3723
|
return fileCount;
|
|
3538
3724
|
}
|
|
3539
3725
|
for (const entry of entries) {
|
|
3540
3726
|
if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
|
|
3541
|
-
const abs =
|
|
3727
|
+
const abs = path13.join(dir, entry);
|
|
3542
3728
|
let info;
|
|
3543
3729
|
try {
|
|
3544
|
-
info = await
|
|
3730
|
+
info = await fs2.lstat(abs);
|
|
3545
3731
|
} catch {
|
|
3546
3732
|
continue;
|
|
3547
3733
|
}
|
|
@@ -3559,7 +3745,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3559
3745
|
""
|
|
3560
3746
|
);
|
|
3561
3747
|
if (base === "index") {
|
|
3562
|
-
base =
|
|
3748
|
+
base = path13.basename(dir);
|
|
3563
3749
|
}
|
|
3564
3750
|
if (base.startsWith(".")) continue;
|
|
3565
3751
|
const componentName = toPascalCase(base);
|
|
@@ -3581,9 +3767,9 @@ async function scanCssVariables(projectRoot) {
|
|
|
3581
3767
|
const variables = {};
|
|
3582
3768
|
const sourceFiles = [];
|
|
3583
3769
|
for (const relPath of candidates) {
|
|
3584
|
-
const absPath =
|
|
3770
|
+
const absPath = path13.join(projectRoot, relPath);
|
|
3585
3771
|
if (!await pathExists2(absPath)) continue;
|
|
3586
|
-
const content = await
|
|
3772
|
+
const content = await fs2.readFile(absPath, "utf-8");
|
|
3587
3773
|
const found = extractCssVariables(content);
|
|
3588
3774
|
if (Object.keys(found).length === 0) continue;
|
|
3589
3775
|
Object.assign(variables, found);
|
|
@@ -3626,8 +3812,8 @@ function toPascalCase(value) {
|
|
|
3626
3812
|
}
|
|
3627
3813
|
async function hasTailwindDep(projectRoot) {
|
|
3628
3814
|
try {
|
|
3629
|
-
const raw = await
|
|
3630
|
-
|
|
3815
|
+
const raw = await fs2.readFile(
|
|
3816
|
+
path13.join(projectRoot, "package.json"),
|
|
3631
3817
|
"utf-8"
|
|
3632
3818
|
);
|
|
3633
3819
|
const pkg = JSON.parse(raw);
|
|
@@ -3643,7 +3829,7 @@ function escapeRegex(s) {
|
|
|
3643
3829
|
}
|
|
3644
3830
|
async function pathExists2(p) {
|
|
3645
3831
|
try {
|
|
3646
|
-
await
|
|
3832
|
+
await fs2.access(p);
|
|
3647
3833
|
return true;
|
|
3648
3834
|
} catch {
|
|
3649
3835
|
return false;
|
|
@@ -3654,65 +3840,104 @@ async function pathExists2(p) {
|
|
|
3654
3840
|
var EXIT_OK = 0;
|
|
3655
3841
|
var EXIT_ALREADY_INITIALIZED = 1;
|
|
3656
3842
|
var EXIT_NOT_A_PROJECT_DIR = 2;
|
|
3843
|
+
var EXIT_USER_CANCEL = 3;
|
|
3657
3844
|
var EXIT_INIT_FAILED = 5;
|
|
3658
3845
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
3659
|
-
async function init(rootDir =
|
|
3660
|
-
const mancodeDir =
|
|
3661
|
-
const stateFile =
|
|
3846
|
+
async function init(rootDir = process5.cwd(), options = {}) {
|
|
3847
|
+
const mancodeDir = path14.join(rootDir, ".mancode");
|
|
3848
|
+
const stateFile = path14.join(mancodeDir, "state.json");
|
|
3662
3849
|
const wasInitialized = await pathExists3(stateFile);
|
|
3663
|
-
let
|
|
3850
|
+
let mutationSnapshots = [];
|
|
3851
|
+
let directorySnapshots = [];
|
|
3852
|
+
const locale = detectInitLocale(options.lang);
|
|
3853
|
+
if (!locale) {
|
|
3854
|
+
console.error(`\u2717 Unsupported init language: ${options.lang}`);
|
|
3855
|
+
console.error(" Supported values: zh-CN, en");
|
|
3856
|
+
return EXIT_INIT_FAILED;
|
|
3857
|
+
}
|
|
3664
3858
|
if (wasInitialized) {
|
|
3665
3859
|
if (!options.force) {
|
|
3666
|
-
console.log(
|
|
3860
|
+
console.log(
|
|
3861
|
+
localize(
|
|
3862
|
+
locale,
|
|
3863
|
+
"\u2139\uFE0F mancode \u5DF2\u7ECF\u521D\u59CB\u5316\u3002",
|
|
3864
|
+
"\u2139\uFE0F mancode already initialized."
|
|
3865
|
+
)
|
|
3866
|
+
);
|
|
3667
3867
|
console.log(` ${stateFile}`);
|
|
3668
|
-
console.log(
|
|
3868
|
+
console.log(
|
|
3869
|
+
localize(
|
|
3870
|
+
locale,
|
|
3871
|
+
" \u8FD0\u884C `mancode init --force` \u91CD\u65B0\u5B89\u88C5\u3002",
|
|
3872
|
+
" Run `mancode init --force` to reinstall."
|
|
3873
|
+
)
|
|
3874
|
+
);
|
|
3669
3875
|
return EXIT_ALREADY_INITIALIZED;
|
|
3670
3876
|
}
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
} catch (error) {
|
|
3679
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3680
|
-
console.error(`\u2717 Cannot snapshot existing mancode state: ${message}`);
|
|
3681
|
-
return EXIT_INIT_FAILED;
|
|
3682
|
-
}
|
|
3683
|
-
console.log("\u26A0\uFE0F Reinstalling with --force...");
|
|
3877
|
+
console.log(
|
|
3878
|
+
localize(
|
|
3879
|
+
locale,
|
|
3880
|
+
"\u26A0\uFE0F \u6B63\u5728\u4F7F\u7528 --force \u91CD\u65B0\u5B89\u88C5...",
|
|
3881
|
+
"\u26A0\uFE0F Reinstalling with --force..."
|
|
3882
|
+
)
|
|
3883
|
+
);
|
|
3684
3884
|
}
|
|
3685
3885
|
if (!await pathExists3(rootDir)) {
|
|
3686
|
-
console.error(`\u2717 Target directory does not exist: ${rootDir}`);
|
|
3687
|
-
return EXIT_NOT_A_PROJECT_DIR;
|
|
3688
|
-
}
|
|
3689
|
-
const isGitRepo = await pathExists3(path13.join(rootDir, ".git"));
|
|
3690
|
-
const hasManifest = await Promise.any(
|
|
3691
|
-
PROJECT_MANIFESTS.map(async (name) => {
|
|
3692
|
-
if (await pathExists3(path13.join(rootDir, name))) return true;
|
|
3693
|
-
throw new Error("manifest missing");
|
|
3694
|
-
})
|
|
3695
|
-
).catch(() => false);
|
|
3696
|
-
if (!isGitRepo && !hasManifest) {
|
|
3697
|
-
console.error(`\u2717 Not a project directory: ${rootDir}`);
|
|
3698
|
-
console.error(" (No .git or recognized project manifest found)");
|
|
3699
|
-
console.error("");
|
|
3700
3886
|
console.error(
|
|
3701
|
-
|
|
3887
|
+
localize(
|
|
3888
|
+
locale,
|
|
3889
|
+
`\u2717 \u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${rootDir}`,
|
|
3890
|
+
`\u2717 Target directory does not exist: ${rootDir}`
|
|
3891
|
+
)
|
|
3702
3892
|
);
|
|
3703
3893
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3704
3894
|
}
|
|
3895
|
+
const isGitRepo = await pathExists3(path14.join(rootDir, ".git"));
|
|
3896
|
+
const hasManifest = await hasProjectManifest(rootDir);
|
|
3897
|
+
let isGenericProject = false;
|
|
3898
|
+
if (!isGitRepo && !hasManifest) {
|
|
3899
|
+
const genericSafety = await canInitializeGenericProject(rootDir);
|
|
3900
|
+
if (!genericSafety.ok) {
|
|
3901
|
+
printNotProjectDirectory(rootDir, locale, genericSafety.reason);
|
|
3902
|
+
return EXIT_NOT_A_PROJECT_DIR;
|
|
3903
|
+
}
|
|
3904
|
+
const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
|
|
3905
|
+
const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
|
|
3906
|
+
if (!confirmed) {
|
|
3907
|
+
if (options.interactive) {
|
|
3908
|
+
console.log(
|
|
3909
|
+
locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
|
|
3910
|
+
);
|
|
3911
|
+
return EXIT_USER_CANCEL;
|
|
3912
|
+
}
|
|
3913
|
+
printNotProjectDirectory(rootDir, locale, "empty");
|
|
3914
|
+
return EXIT_NOT_A_PROJECT_DIR;
|
|
3915
|
+
}
|
|
3916
|
+
isGenericProject = true;
|
|
3917
|
+
}
|
|
3705
3918
|
try {
|
|
3706
|
-
console.log(
|
|
3919
|
+
console.log(
|
|
3920
|
+
localize(
|
|
3921
|
+
locale,
|
|
3922
|
+
"\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...",
|
|
3923
|
+
"\u2713 Checking system dependencies..."
|
|
3924
|
+
)
|
|
3925
|
+
);
|
|
3707
3926
|
const deps = await detectSystemDeps();
|
|
3708
3927
|
if (!deps.git) {
|
|
3709
3928
|
console.log(
|
|
3710
|
-
|
|
3929
|
+
localize(
|
|
3930
|
+
locale,
|
|
3931
|
+
"\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",
|
|
3932
|
+
"\u26A0\uFE0F Git not found (optional). Team auto-detection will use solo defaults."
|
|
3933
|
+
)
|
|
3711
3934
|
);
|
|
3712
3935
|
} else {
|
|
3713
3936
|
console.log(" git \u2713");
|
|
3714
3937
|
}
|
|
3715
|
-
console.log(
|
|
3938
|
+
console.log(
|
|
3939
|
+
localize(locale, "\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...", "\u2713 Detecting project type...")
|
|
3940
|
+
);
|
|
3716
3941
|
const profile = await detectProjectProfile(rootDir);
|
|
3717
3942
|
const profileStack = [...profile.languages, ...profile.frameworks];
|
|
3718
3943
|
const techStackStr = profileStack.join(" + ") || "Unknown";
|
|
@@ -3724,37 +3949,100 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3724
3949
|
console.log(` UI: ${uiLibraryStr}`);
|
|
3725
3950
|
}
|
|
3726
3951
|
} else if (hasManifest) {
|
|
3727
|
-
console.log(
|
|
3952
|
+
console.log(
|
|
3953
|
+
localize(
|
|
3954
|
+
locale,
|
|
3955
|
+
" \u5DF2\u53D1\u73B0\u9879\u76EE manifest\uFF0C\u672A\u8BC6\u522B\u5230\u6846\u67B6\u4F9D\u8D56",
|
|
3956
|
+
" Project manifest found; no known framework dependencies"
|
|
3957
|
+
)
|
|
3958
|
+
);
|
|
3728
3959
|
} else {
|
|
3729
3960
|
console.log(
|
|
3730
|
-
|
|
3961
|
+
localize(
|
|
3962
|
+
locale,
|
|
3963
|
+
" \uFF08\u672A\u53D1\u73B0\u5DF2\u8BC6\u522B\u7684 manifest\uFF0C\u9879\u76EE\u753B\u50CF\u7F6E\u4FE1\u5EA6\u8F83\u4F4E\uFF09",
|
|
3964
|
+
" (No recognized manifest found; profile confidence is low)"
|
|
3965
|
+
)
|
|
3731
3966
|
);
|
|
3732
3967
|
}
|
|
3733
3968
|
const existingPlatform = wasInitialized ? await readExistingInitPlatform(stateFile) : null;
|
|
3734
|
-
const
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3737
|
-
|
|
3969
|
+
const platformHints = await detectPlatformHints(rootDir);
|
|
3970
|
+
const selectedPlatforms = await selectInitPlatforms({
|
|
3971
|
+
option: options.platform,
|
|
3972
|
+
existingPlatform,
|
|
3973
|
+
hints: platformHints,
|
|
3974
|
+
interactive: options.interactive,
|
|
3975
|
+
prompter: options.prompter,
|
|
3976
|
+
locale,
|
|
3977
|
+
yes: options.yes
|
|
3978
|
+
});
|
|
3979
|
+
if (!selectedPlatforms) {
|
|
3980
|
+
console.error(
|
|
3981
|
+
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."
|
|
3982
|
+
);
|
|
3983
|
+
return EXIT_INIT_FAILED;
|
|
3984
|
+
}
|
|
3985
|
+
if (selectedPlatforms.length === 0) {
|
|
3986
|
+
console.error("\u2717 No platform selected.");
|
|
3987
|
+
return EXIT_INIT_FAILED;
|
|
3988
|
+
}
|
|
3989
|
+
const firstPlatform = selectedPlatforms[0];
|
|
3990
|
+
if (!firstPlatform) {
|
|
3991
|
+
console.error("\u2717 No platform selected.");
|
|
3992
|
+
return EXIT_INIT_FAILED;
|
|
3993
|
+
}
|
|
3994
|
+
const installers = selectedPlatforms.map(getPlatformInstaller);
|
|
3995
|
+
if (installers.some((installer) => !installer)) {
|
|
3996
|
+
console.error(`\u2717 Unsupported platform: ${options.platform}`);
|
|
3738
3997
|
return EXIT_INIT_FAILED;
|
|
3739
3998
|
}
|
|
3999
|
+
const typedInstallers = installers.filter(
|
|
4000
|
+
(installer) => installer !== null
|
|
4001
|
+
);
|
|
4002
|
+
const onlyPlatformHint = platformHints.length === 1 ? platformHints[0] : null;
|
|
4003
|
+
const detectedPrimary = onlyPlatformHint && selectedPlatforms.includes(onlyPlatformHint) ? onlyPlatformHint : null;
|
|
4004
|
+
const primaryPlatform = existingPlatform && selectedPlatforms.includes(existingPlatform) ? existingPlatform : detectedPrimary ?? firstPlatform;
|
|
4005
|
+
const managedFiles = getInitManagedFilePaths(rootDir, selectedPlatforms);
|
|
4006
|
+
mutationSnapshots = await snapshotFiles(managedFiles);
|
|
4007
|
+
directorySnapshots = await snapshotDirectories(managedFiles, [
|
|
4008
|
+
path14.join(mancodeDir, "workflows"),
|
|
4009
|
+
path14.join(mancodeDir, "preseason-reports")
|
|
4010
|
+
]);
|
|
3740
4011
|
const team = await detectTeamStatus(rootDir);
|
|
3741
|
-
const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir,
|
|
4012
|
+
const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, primaryPlatform) : {};
|
|
3742
4013
|
const initialMinimal = existingPreferences.minimal ?? false;
|
|
3743
|
-
const
|
|
4014
|
+
const existingTeamMode = existingPreferences.forceTeamMode === true ? "on" : existingPreferences.teamMode ?? "auto";
|
|
4015
|
+
const teamModeEnabled = options.team ?? (existingTeamMode === "on" ? true : existingTeamMode === "off" ? false : team.isTeam);
|
|
3744
4016
|
if (options.team === true) {
|
|
3745
|
-
console.log(
|
|
4017
|
+
console.log(
|
|
4018
|
+
localize(
|
|
4019
|
+
locale,
|
|
4020
|
+
" \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5F00\u542F\uFF08--team\uFF09",
|
|
4021
|
+
" team: forced on (--team)"
|
|
4022
|
+
)
|
|
4023
|
+
);
|
|
3746
4024
|
} else if (options.team === false) {
|
|
3747
|
-
console.log(
|
|
4025
|
+
console.log(
|
|
4026
|
+
localize(
|
|
4027
|
+
locale,
|
|
4028
|
+
" \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5173\u95ED\uFF08--no-team\uFF09",
|
|
4029
|
+
" team: forced off (--no-team)"
|
|
4030
|
+
)
|
|
4031
|
+
);
|
|
3748
4032
|
} else if (team.isTeam) {
|
|
3749
4033
|
console.log(
|
|
3750
|
-
|
|
4034
|
+
localize(
|
|
4035
|
+
locale,
|
|
4036
|
+
` \u56E2\u961F\u6A21\u5F0F\uFF1A${team.contributors} \u4F4D\u8D21\u732E\u8005\uFF08\u53EF\u4F7F\u7528 /manteam\uFF09`,
|
|
4037
|
+
` team: ${team.contributors} contributors (/manteam available)`
|
|
4038
|
+
)
|
|
3751
4039
|
);
|
|
3752
4040
|
}
|
|
3753
4041
|
const state = {
|
|
3754
4042
|
version: VERSION,
|
|
3755
4043
|
currentMode: "solo",
|
|
3756
4044
|
lastMode: "solo",
|
|
3757
|
-
platform:
|
|
4045
|
+
platform: primaryPlatform,
|
|
3758
4046
|
initializedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3759
4047
|
techStack: techStackStr,
|
|
3760
4048
|
uiLibrary: uiLibraryStr,
|
|
@@ -3762,18 +4050,19 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3762
4050
|
currentWorkflowMode: null,
|
|
3763
4051
|
skippedSteps: [],
|
|
3764
4052
|
teamModeAutoDetected: teamModeEnabled,
|
|
3765
|
-
contributors: team.contributors
|
|
4053
|
+
contributors: team.contributors,
|
|
4054
|
+
projectMode: isGenericProject ? "generic" : "detected"
|
|
3766
4055
|
};
|
|
3767
|
-
if (
|
|
4056
|
+
if (selectedPlatforms.includes("claude-code")) {
|
|
3768
4057
|
await validateClaudeCodeSettings(rootDir);
|
|
3769
4058
|
}
|
|
3770
4059
|
await installMancodeCore(rootDir);
|
|
3771
|
-
await
|
|
4060
|
+
await fs3.mkdir(mancodeDir, { recursive: true });
|
|
3772
4061
|
const stateContent = `${JSON.stringify(state, null, 2)}
|
|
3773
4062
|
`;
|
|
3774
|
-
await
|
|
3775
|
-
await
|
|
3776
|
-
|
|
4063
|
+
await fs3.writeFile(stateFile, stateContent, "utf-8");
|
|
4064
|
+
await fs3.writeFile(
|
|
4065
|
+
path14.join(mancodeDir, "project-profile.json"),
|
|
3777
4066
|
`${JSON.stringify(profile, null, 2)}
|
|
3778
4067
|
`,
|
|
3779
4068
|
"utf-8"
|
|
@@ -3782,24 +4071,40 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3782
4071
|
mancodeDir,
|
|
3783
4072
|
{
|
|
3784
4073
|
forceTeamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true,
|
|
4074
|
+
teamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true ? "on" : options.team === false ? "off" : "auto",
|
|
3785
4075
|
defaultStyle: options.style === void 0 && wasInitialized ? void 0 : options.style ?? null,
|
|
3786
|
-
platforms:
|
|
4076
|
+
platforms: selectedPlatforms,
|
|
3787
4077
|
platformOptions: {
|
|
3788
|
-
|
|
4078
|
+
...Object.fromEntries(
|
|
4079
|
+
selectedPlatforms.map((platform) => [
|
|
4080
|
+
platform,
|
|
4081
|
+
{ minimal: initialMinimal }
|
|
4082
|
+
])
|
|
4083
|
+
)
|
|
3789
4084
|
}
|
|
3790
4085
|
},
|
|
3791
4086
|
wasInitialized
|
|
3792
4087
|
);
|
|
3793
|
-
let styleLine =
|
|
4088
|
+
let styleLine = localize(
|
|
4089
|
+
locale,
|
|
4090
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u7A7A\uFF09",
|
|
4091
|
+
" .mancode/aesthetics/ # style-tokens.json (empty)"
|
|
4092
|
+
);
|
|
3794
4093
|
if (profile.uiAssets === "detected") {
|
|
3795
|
-
console.log(
|
|
4094
|
+
console.log(
|
|
4095
|
+
localize(
|
|
4096
|
+
locale,
|
|
4097
|
+
"\u2713 \u626B\u63CF\u5BA1\u7F8E token...",
|
|
4098
|
+
"\u2713 Scanning design tokens..."
|
|
4099
|
+
)
|
|
4100
|
+
);
|
|
3796
4101
|
const tokens = await scanAesthetics(rootDir, uiLibrary);
|
|
3797
|
-
const tokensPath =
|
|
4102
|
+
const tokensPath = path14.join(
|
|
3798
4103
|
mancodeDir,
|
|
3799
4104
|
"aesthetics",
|
|
3800
4105
|
"style-tokens.json"
|
|
3801
4106
|
);
|
|
3802
|
-
await
|
|
4107
|
+
await fs3.writeFile(
|
|
3803
4108
|
tokensPath,
|
|
3804
4109
|
`${JSON.stringify(tokens, null, 2)}
|
|
3805
4110
|
`,
|
|
@@ -3809,70 +4114,146 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3809
4114
|
const colorCount = Object.keys(tokens.colors).length;
|
|
3810
4115
|
const fontCount = Object.keys(tokens.fonts).length;
|
|
3811
4116
|
console.log(
|
|
3812
|
-
|
|
4117
|
+
localize(
|
|
4118
|
+
locale,
|
|
4119
|
+
` ${colorCount} \u4E2A\u989C\u8272\uFF0C${fontCount} \u4E2A\u5B57\u4F53\uFF08\u5339\u914D\u5EA6\uFF1A\u9AD8\uFF09`,
|
|
4120
|
+
` ${colorCount} colors, ${fontCount} fonts (match: high)`
|
|
4121
|
+
)
|
|
4122
|
+
);
|
|
4123
|
+
styleLine = localize(
|
|
4124
|
+
locale,
|
|
4125
|
+
` .mancode/aesthetics/ # style-tokens.json\uFF08${colorCount} \u4E2A\u989C\u8272\uFF09`,
|
|
4126
|
+
` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`
|
|
3813
4127
|
);
|
|
3814
|
-
styleLine = ` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`;
|
|
3815
4128
|
} else if (tokens.matchLevel === "low") {
|
|
3816
4129
|
console.log(
|
|
3817
|
-
|
|
4130
|
+
localize(
|
|
4131
|
+
locale,
|
|
4132
|
+
" \u5DF2\u68C0\u6D4B\u5230 UI \u8D44\u6E90\uFF0C\u672A\u627E\u5230\u53EF\u590D\u7528 token\uFF08\u5339\u914D\u5EA6\uFF1A\u4F4E\uFF09",
|
|
4133
|
+
" UI assets detected; no reusable tokens found (match: low)"
|
|
4134
|
+
)
|
|
4135
|
+
);
|
|
4136
|
+
styleLine = localize(
|
|
4137
|
+
locale,
|
|
4138
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u4F4E\u5339\u914D\uFF09",
|
|
4139
|
+
" .mancode/aesthetics/ # style-tokens.json (low match)"
|
|
3818
4140
|
);
|
|
3819
|
-
styleLine = " .mancode/aesthetics/ # style-tokens.json (low match)";
|
|
3820
4141
|
} else {
|
|
3821
|
-
console.log(
|
|
3822
|
-
|
|
4142
|
+
console.log(
|
|
4143
|
+
localize(
|
|
4144
|
+
locale,
|
|
4145
|
+
" \u672A\u627E\u5230\u8BBE\u8BA1 token\uFF08\u65E0\u5339\u914D\uFF09",
|
|
4146
|
+
" No design tokens found (match: none)"
|
|
4147
|
+
)
|
|
4148
|
+
);
|
|
4149
|
+
styleLine = localize(
|
|
4150
|
+
locale,
|
|
4151
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u65E0 token\uFF09",
|
|
4152
|
+
" .mancode/aesthetics/ # style-tokens.json (no tokens)"
|
|
4153
|
+
);
|
|
3823
4154
|
}
|
|
3824
4155
|
}
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
4156
|
+
for (const installer of typedInstallers) {
|
|
4157
|
+
console.log(
|
|
4158
|
+
localize(
|
|
4159
|
+
locale,
|
|
4160
|
+
`\u2713 \u5B89\u88C5 ${installer.displayName} \u9002\u914D\u5668...`,
|
|
4161
|
+
`\u2713 Installing ${installer.displayName} adapter...`
|
|
4162
|
+
)
|
|
4163
|
+
);
|
|
4164
|
+
await installer.install(rootDir, {
|
|
4165
|
+
techStack: profileStack,
|
|
4166
|
+
uiLibrary,
|
|
4167
|
+
projectProfile: profile,
|
|
4168
|
+
minimal: initialMinimal,
|
|
4169
|
+
force: options.force
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
3833
4172
|
console.log("");
|
|
3834
|
-
console.log(
|
|
4173
|
+
console.log(
|
|
4174
|
+
localize(locale, "\u2713 mancode \u521D\u59CB\u5316\u5B8C\u6210\u3002", "\u2713 mancode initialized.")
|
|
4175
|
+
);
|
|
3835
4176
|
console.log("");
|
|
3836
|
-
console.log("Created:");
|
|
3837
|
-
console.log(" .mancode/state.json # \u9879\u76EE\u72B6\u6001");
|
|
3838
|
-
console.log(" .mancode/project-profile.json # \u68C0\u6D4B\u5230\u7684\u9879\u76EE\u4E8B\u5B9E");
|
|
3839
|
-
console.log(" .mancode/config.json # \u914D\u7F6E");
|
|
4177
|
+
console.log(localize(locale, "\u5DF2\u521B\u5EFA\uFF1A", "Created:"));
|
|
3840
4178
|
console.log(
|
|
3841
|
-
|
|
4179
|
+
localize(
|
|
4180
|
+
locale,
|
|
4181
|
+
" .mancode/state.json # \u9879\u76EE\u72B6\u6001",
|
|
4182
|
+
" .mancode/state.json # project state"
|
|
4183
|
+
)
|
|
4184
|
+
);
|
|
4185
|
+
console.log(
|
|
4186
|
+
localize(
|
|
4187
|
+
locale,
|
|
4188
|
+
" .mancode/project-profile.json # \u68C0\u6D4B\u5230\u7684\u9879\u76EE\u4E8B\u5B9E",
|
|
4189
|
+
" .mancode/project-profile.json # detected project facts"
|
|
4190
|
+
)
|
|
4191
|
+
);
|
|
4192
|
+
console.log(
|
|
4193
|
+
localize(
|
|
4194
|
+
locale,
|
|
4195
|
+
" .mancode/config.json # \u914D\u7F6E",
|
|
4196
|
+
" .mancode/config.json # configuration"
|
|
4197
|
+
)
|
|
4198
|
+
);
|
|
4199
|
+
console.log(
|
|
4200
|
+
localize(
|
|
4201
|
+
locale,
|
|
4202
|
+
" .mancode/hooks/ # SessionStart + UserPromptSubmit",
|
|
4203
|
+
" .mancode/hooks/ # SessionStart + UserPromptSubmit"
|
|
4204
|
+
)
|
|
3842
4205
|
);
|
|
3843
4206
|
console.log(styleLine);
|
|
3844
4207
|
console.log(" .mancode/logs/ # hooks.log");
|
|
3845
|
-
|
|
4208
|
+
for (const platform of selectedPlatforms)
|
|
4209
|
+
printPlatformCreatedFiles(platform, locale);
|
|
3846
4210
|
console.log("");
|
|
3847
|
-
console.log("Next:");
|
|
3848
|
-
console.log(
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4211
|
+
console.log(localize(locale, "\u4E0B\u4E00\u6B65\uFF1A", "Next:"));
|
|
4212
|
+
console.log(
|
|
4213
|
+
localize(
|
|
4214
|
+
locale,
|
|
4215
|
+
" mancode status # \u663E\u793A\u9879\u76EE\u72B6\u6001",
|
|
4216
|
+
" mancode status # Show project state"
|
|
4217
|
+
)
|
|
4218
|
+
);
|
|
4219
|
+
if (selectedPlatforms.includes("claude-code")) {
|
|
3852
4220
|
console.log(
|
|
3853
|
-
|
|
4221
|
+
localize(
|
|
4222
|
+
locale,
|
|
4223
|
+
" \uFF08\u91CD\u542F Claude Code \u4EE5\u52A0\u8F7D hooks\uFF09",
|
|
4224
|
+
" (Restart Claude Code to load hooks)"
|
|
4225
|
+
)
|
|
4226
|
+
);
|
|
4227
|
+
}
|
|
4228
|
+
if (selectedPlatforms.includes("codex")) {
|
|
4229
|
+
console.log(
|
|
4230
|
+
localize(
|
|
4231
|
+
locale,
|
|
4232
|
+
" \uFF08\u5982\u679C skills \u672A\u51FA\u73B0\uFF0C\u8BF7\u91CD\u542F ChatGPT \u684C\u9762\u5E94\u7528\u6216 Codex \u4F1A\u8BDD\uFF09",
|
|
4233
|
+
" (If skills do not appear, restart the ChatGPT desktop app or Codex session)"
|
|
4234
|
+
)
|
|
3854
4235
|
);
|
|
3855
4236
|
}
|
|
3856
4237
|
return EXIT_OK;
|
|
3857
4238
|
} catch (err) {
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
} else {
|
|
3861
|
-
await fs2.rm(stateFile, { force: true });
|
|
3862
|
-
await fs2.rm(path13.join(mancodeDir, "project-profile.json"), {
|
|
3863
|
-
force: true
|
|
3864
|
-
});
|
|
3865
|
-
}
|
|
4239
|
+
await restoreFiles(mutationSnapshots);
|
|
4240
|
+
await removeNewEmptyDirectories(directorySnapshots);
|
|
3866
4241
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3867
|
-
console.error(
|
|
4242
|
+
console.error(
|
|
4243
|
+
localize(
|
|
4244
|
+
locale,
|
|
4245
|
+
`\u2717 mancode \u521D\u59CB\u5316\u5931\u8D25\uFF1A${msg}`,
|
|
4246
|
+
`\u2717 mancode init failed: ${msg}`
|
|
4247
|
+
)
|
|
4248
|
+
);
|
|
3868
4249
|
return EXIT_INIT_FAILED;
|
|
3869
4250
|
}
|
|
3870
4251
|
}
|
|
3871
4252
|
async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms) {
|
|
3872
|
-
const configPath =
|
|
4253
|
+
const configPath = path14.join(mancodeDir, "config.json");
|
|
3873
4254
|
let config = {};
|
|
3874
4255
|
try {
|
|
3875
|
-
config = JSON.parse(await
|
|
4256
|
+
config = JSON.parse(await fs3.readFile(configPath, "utf-8"));
|
|
3876
4257
|
} catch {
|
|
3877
4258
|
}
|
|
3878
4259
|
const existingPlatforms = Array.isArray(config.platforms) ? config.platforms.filter(
|
|
@@ -3892,7 +4273,7 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
3892
4273
|
...patch.platformOptions
|
|
3893
4274
|
}
|
|
3894
4275
|
} : definedPatch;
|
|
3895
|
-
await
|
|
4276
|
+
await fs3.writeFile(
|
|
3896
4277
|
configPath,
|
|
3897
4278
|
`${JSON.stringify({ ...config, ...mergedPatch }, null, 2)}
|
|
3898
4279
|
`,
|
|
@@ -3902,36 +4283,182 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
3902
4283
|
async function readExistingInitPreferences(mancodeDir, platform) {
|
|
3903
4284
|
try {
|
|
3904
4285
|
const config = JSON.parse(
|
|
3905
|
-
await
|
|
4286
|
+
await fs3.readFile(path14.join(mancodeDir, "config.json"), "utf-8")
|
|
3906
4287
|
);
|
|
3907
4288
|
const preferences = {};
|
|
3908
4289
|
if (typeof config.forceTeamMode === "boolean") {
|
|
3909
4290
|
preferences.forceTeamMode = config.forceTeamMode;
|
|
3910
4291
|
}
|
|
3911
|
-
if (
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4292
|
+
if (config.teamMode === "auto" || config.teamMode === "on" || config.teamMode === "off") {
|
|
4293
|
+
preferences.teamMode = config.teamMode;
|
|
4294
|
+
}
|
|
4295
|
+
if (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
|
|
4296
|
+
const platformOptions = config.platformOptions[platform];
|
|
4297
|
+
if (isRecord3(platformOptions) && platformOptions.minimal === true) {
|
|
4298
|
+
preferences.minimal = true;
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
return preferences;
|
|
4302
|
+
} catch {
|
|
4303
|
+
return {};
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
async function readExistingInitPlatform(stateFile) {
|
|
4307
|
+
try {
|
|
4308
|
+
const state = JSON.parse(await fs3.readFile(stateFile, "utf-8"));
|
|
4309
|
+
return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
|
|
4310
|
+
} catch {
|
|
4311
|
+
return null;
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
async function selectInitPlatforms(input) {
|
|
4315
|
+
if (input.option !== void 0) {
|
|
4316
|
+
return parsePlatformSelection(input.option);
|
|
4317
|
+
}
|
|
4318
|
+
if (input.existingPlatform) return [input.existingPlatform];
|
|
4319
|
+
if (input.yes && input.interactive !== void 0) {
|
|
4320
|
+
return input.hints.length === 1 ? input.hints : null;
|
|
4321
|
+
}
|
|
4322
|
+
if (input.interactive) {
|
|
4323
|
+
const prompter = input.prompter ?? createTerminalPrompter();
|
|
4324
|
+
return prompter.selectPlatforms({
|
|
4325
|
+
locale: input.locale,
|
|
4326
|
+
detected: input.hints
|
|
4327
|
+
});
|
|
4328
|
+
}
|
|
4329
|
+
if (input.interactive === false) {
|
|
4330
|
+
return input.hints.length === 1 ? input.hints : null;
|
|
4331
|
+
}
|
|
4332
|
+
return [DEFAULT_INIT_PLATFORM];
|
|
4333
|
+
}
|
|
4334
|
+
async function hasProjectManifest(rootDir) {
|
|
4335
|
+
for (const name of PROJECT_MANIFESTS) {
|
|
4336
|
+
if (await pathExists3(path14.join(rootDir, name))) return true;
|
|
4337
|
+
}
|
|
4338
|
+
return false;
|
|
4339
|
+
}
|
|
4340
|
+
async function canInitializeGenericProject(rootDir) {
|
|
4341
|
+
const resolved = path14.resolve(rootDir);
|
|
4342
|
+
if (resolved === path14.parse(resolved).root || resolved === path14.resolve(os.homedir())) {
|
|
4343
|
+
return { ok: false, reason: "unsafe" };
|
|
4344
|
+
}
|
|
4345
|
+
try {
|
|
4346
|
+
const entries = await fs3.readdir(resolved);
|
|
4347
|
+
const meaningfulEntries = entries.filter(
|
|
4348
|
+
(entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
|
|
4349
|
+
);
|
|
4350
|
+
return meaningfulEntries.length === 0 ? { ok: true } : { ok: false, reason: "nonempty" };
|
|
4351
|
+
} catch {
|
|
4352
|
+
return { ok: false, reason: "unsafe" };
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
function printNotProjectDirectory(rootDir, locale, reason) {
|
|
4356
|
+
if (locale === "zh-CN") {
|
|
4357
|
+
console.error(`\u2717 \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u53EF\u521D\u59CB\u5316\u7684\u9879\u76EE\u76EE\u5F55\uFF1A${rootDir}`);
|
|
4358
|
+
if (reason === "unsafe") {
|
|
4359
|
+
console.error(" \u4E3A\u907F\u514D\u8BEF\u5199\uFF0C\u4E0D\u80FD\u5728 Home \u76EE\u5F55\u6216\u78C1\u76D8\u6839\u76EE\u5F55\u521D\u59CB\u5316\u3002");
|
|
4360
|
+
} else if (reason === "nonempty") {
|
|
4361
|
+
console.error(
|
|
4362
|
+
" \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"
|
|
4363
|
+
);
|
|
4364
|
+
} else {
|
|
4365
|
+
console.error(
|
|
4366
|
+
" \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"
|
|
4367
|
+
);
|
|
4368
|
+
}
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
console.error(`\u2717 Not a project directory: ${rootDir}`);
|
|
4372
|
+
if (reason === "unsafe") {
|
|
4373
|
+
console.error(
|
|
4374
|
+
" Refusing to initialize a home directory or filesystem root."
|
|
4375
|
+
);
|
|
4376
|
+
} else if (reason === "nonempty") {
|
|
4377
|
+
console.error(
|
|
4378
|
+
" No project files were detected and the directory is not empty."
|
|
4379
|
+
);
|
|
4380
|
+
} else {
|
|
4381
|
+
console.error(
|
|
4382
|
+
" No .git or recognized project manifest found. Use an interactive terminal or --empty for a new project."
|
|
4383
|
+
);
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
function getInitManagedFilePaths(rootDir, platforms) {
|
|
4387
|
+
const files = [
|
|
4388
|
+
".mancode/state.json",
|
|
4389
|
+
".mancode/config.json",
|
|
4390
|
+
".mancode/project-profile.json",
|
|
4391
|
+
".mancode/aesthetics/style-tokens.json",
|
|
4392
|
+
".mancode/hooks/session-start.mjs",
|
|
4393
|
+
".mancode/hooks/user-prompt-submit.mjs",
|
|
4394
|
+
".mancode/logs/hooks.log",
|
|
4395
|
+
".mancode/memory/prd.md",
|
|
4396
|
+
".mancode/memory/spec.md",
|
|
4397
|
+
".mancode/memory/decisions.md"
|
|
4398
|
+
];
|
|
4399
|
+
if (platforms.includes("claude-code")) {
|
|
4400
|
+
files.push(
|
|
4401
|
+
".mancode/hooks/session-start.sh",
|
|
4402
|
+
".mancode/hooks/user-prompt-submit.sh",
|
|
4403
|
+
".claude/settings.json",
|
|
4404
|
+
".claude/skills/man8/SKILL.md",
|
|
4405
|
+
".claude/skills/solo/SKILL.md",
|
|
4406
|
+
".claude/skills/mancode-solo.md",
|
|
4407
|
+
".claude/skills/mancode-man8.md"
|
|
4408
|
+
);
|
|
4409
|
+
for (const mode of MODE_NAMES) {
|
|
4410
|
+
files.push(
|
|
4411
|
+
`.claude/skills/${mode}/SKILL.md`,
|
|
4412
|
+
`.claude/skills/mancode-${mode}.md`
|
|
4413
|
+
);
|
|
4414
|
+
}
|
|
4415
|
+
for (const agent of ALL_AGENTS) {
|
|
4416
|
+
files.push(`.claude/agents/${agent.name}.md`);
|
|
3916
4417
|
}
|
|
3917
|
-
return preferences;
|
|
3918
|
-
} catch {
|
|
3919
|
-
return {};
|
|
3920
4418
|
}
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
4419
|
+
if (platforms.includes("cursor")) {
|
|
4420
|
+
files.push(".cursor/rules/mancode-man8.mdc", ".cursor/commands/man8.md");
|
|
4421
|
+
for (const fileName of MANCODE_CURSOR_RULE_FILES) {
|
|
4422
|
+
files.push(`.cursor/rules/${fileName}`);
|
|
4423
|
+
}
|
|
4424
|
+
for (const mode of MODE_NAMES) {
|
|
4425
|
+
files.push(`.cursor/commands/${mode}.md`);
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
if (platforms.includes("codex") || platforms.includes("zcode")) {
|
|
4429
|
+
files.push("AGENTS.md", ".agents/skills/man8/SKILL.md");
|
|
4430
|
+
for (const mode of MODE_NAMES) {
|
|
4431
|
+
files.push(`.agents/skills/${mode}/SKILL.md`);
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
if (platforms.includes("codex")) {
|
|
4435
|
+
files.push(".codex/skills/man8/SKILL.md");
|
|
4436
|
+
for (const mode of MODE_NAMES) {
|
|
4437
|
+
files.push(`.codex/skills/${mode}/SKILL.md`);
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
if (platforms.includes("zcode")) {
|
|
4441
|
+
files.push(".zcode/skills/man8/SKILL.md");
|
|
4442
|
+
for (const mode of MODE_NAMES) {
|
|
4443
|
+
files.push(`.zcode/skills/${mode}/SKILL.md`);
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
if (platforms.includes("copilot")) {
|
|
4447
|
+
files.push(
|
|
4448
|
+
".github/copilot-instructions.md",
|
|
4449
|
+
".github/prompts/man8.prompt.md"
|
|
4450
|
+
);
|
|
4451
|
+
for (const mode of MODE_NAMES) {
|
|
4452
|
+
files.push(`.github/prompts/${mode}.prompt.md`);
|
|
4453
|
+
}
|
|
3928
4454
|
}
|
|
4455
|
+
return [...new Set(files.map((file) => path14.join(rootDir, file)))];
|
|
3929
4456
|
}
|
|
3930
4457
|
async function snapshotFiles(filePaths) {
|
|
3931
4458
|
return Promise.all(
|
|
3932
4459
|
filePaths.map(async (filePath) => {
|
|
3933
4460
|
try {
|
|
3934
|
-
return { filePath, content: await
|
|
4461
|
+
return { filePath, content: await fs3.readFile(filePath, "utf-8") };
|
|
3935
4462
|
} catch (error) {
|
|
3936
4463
|
if (isNodeError3(error) && error.code === "ENOENT") {
|
|
3937
4464
|
return { filePath, content: null };
|
|
@@ -3941,14 +4468,39 @@ async function snapshotFiles(filePaths) {
|
|
|
3941
4468
|
})
|
|
3942
4469
|
);
|
|
3943
4470
|
}
|
|
4471
|
+
async function snapshotDirectories(filePaths, additionalDirectories = []) {
|
|
4472
|
+
const directories = new Set(additionalDirectories);
|
|
4473
|
+
for (const filePath of filePaths) {
|
|
4474
|
+
let current = path14.dirname(filePath);
|
|
4475
|
+
while (current !== path14.dirname(current)) {
|
|
4476
|
+
directories.add(current);
|
|
4477
|
+
current = path14.dirname(current);
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
return Promise.all(
|
|
4481
|
+
[...directories].map(async (dirPath) => ({
|
|
4482
|
+
dirPath,
|
|
4483
|
+
existed: await pathExists3(dirPath)
|
|
4484
|
+
}))
|
|
4485
|
+
);
|
|
4486
|
+
}
|
|
3944
4487
|
async function restoreFiles(snapshots) {
|
|
3945
4488
|
for (const snapshot of snapshots) {
|
|
3946
4489
|
if (snapshot.content === null) {
|
|
3947
|
-
await
|
|
4490
|
+
await fs3.rm(snapshot.filePath, { force: true });
|
|
3948
4491
|
continue;
|
|
3949
4492
|
}
|
|
3950
|
-
await
|
|
3951
|
-
await
|
|
4493
|
+
await fs3.mkdir(path14.dirname(snapshot.filePath), { recursive: true });
|
|
4494
|
+
await fs3.writeFile(snapshot.filePath, snapshot.content, "utf-8");
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
async function removeNewEmptyDirectories(snapshots) {
|
|
4498
|
+
const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
|
|
4499
|
+
for (const { dirPath } of candidates) {
|
|
4500
|
+
try {
|
|
4501
|
+
await fs3.rmdir(dirPath);
|
|
4502
|
+
} catch {
|
|
4503
|
+
}
|
|
3952
4504
|
}
|
|
3953
4505
|
}
|
|
3954
4506
|
function isRecord3(value) {
|
|
@@ -3957,10 +4509,22 @@ function isRecord3(value) {
|
|
|
3957
4509
|
function isNodeError3(error) {
|
|
3958
4510
|
return error instanceof Error && "code" in error;
|
|
3959
4511
|
}
|
|
3960
|
-
function printPlatformCreatedFiles(platform) {
|
|
4512
|
+
function printPlatformCreatedFiles(platform, locale) {
|
|
3961
4513
|
if (platform === "claude-code") {
|
|
3962
|
-
console.log(
|
|
3963
|
-
|
|
4514
|
+
console.log(
|
|
4515
|
+
localize(
|
|
4516
|
+
locale,
|
|
4517
|
+
" .claude/settings.json # hook \u6CE8\u518C",
|
|
4518
|
+
" .claude/settings.json # hook registration"
|
|
4519
|
+
)
|
|
4520
|
+
);
|
|
4521
|
+
console.log(
|
|
4522
|
+
localize(
|
|
4523
|
+
locale,
|
|
4524
|
+
" .claude/skills/ # solo + MVP-2 slash skills",
|
|
4525
|
+
" .claude/skills/ # solo + MVP-2 slash skills"
|
|
4526
|
+
)
|
|
4527
|
+
);
|
|
3964
4528
|
return;
|
|
3965
4529
|
}
|
|
3966
4530
|
if (platform === "cursor") {
|
|
@@ -3979,9 +4543,12 @@ function printPlatformCreatedFiles(platform) {
|
|
|
3979
4543
|
}
|
|
3980
4544
|
console.log(" .github/copilot-instructions.md # Copilot instructions");
|
|
3981
4545
|
}
|
|
4546
|
+
function localize(locale, chinese, english) {
|
|
4547
|
+
return locale === "zh-CN" ? chinese : english;
|
|
4548
|
+
}
|
|
3982
4549
|
async function pathExists3(p) {
|
|
3983
4550
|
try {
|
|
3984
|
-
await
|
|
4551
|
+
await fs3.access(p);
|
|
3985
4552
|
return true;
|
|
3986
4553
|
} catch {
|
|
3987
4554
|
return false;
|
|
@@ -3989,13 +4556,13 @@ async function pathExists3(p) {
|
|
|
3989
4556
|
}
|
|
3990
4557
|
|
|
3991
4558
|
// src/commands/install.ts
|
|
3992
|
-
import { promises as
|
|
3993
|
-
import
|
|
3994
|
-
import
|
|
4559
|
+
import { promises as fs5 } from "fs";
|
|
4560
|
+
import path16 from "path";
|
|
4561
|
+
import process6 from "process";
|
|
3995
4562
|
|
|
3996
4563
|
// src/installers/platform-status.ts
|
|
3997
|
-
import { promises as
|
|
3998
|
-
import
|
|
4564
|
+
import { promises as fs4 } from "fs";
|
|
4565
|
+
import path15 from "path";
|
|
3999
4566
|
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
4000
4567
|
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
4001
4568
|
return {
|
|
@@ -4009,13 +4576,13 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4009
4576
|
if (platform === "claude-code") {
|
|
4010
4577
|
const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
|
|
4011
4578
|
fileMatches(
|
|
4012
|
-
|
|
4579
|
+
path15.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
|
|
4013
4580
|
(content) => isGeneratedClaudeSkill(content, "solo")
|
|
4014
4581
|
),
|
|
4015
4582
|
claudeHooksRegistered(rootDir),
|
|
4016
4583
|
pathsExist([
|
|
4017
|
-
|
|
4018
|
-
|
|
4584
|
+
path15.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
4585
|
+
path15.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
4019
4586
|
])
|
|
4020
4587
|
]);
|
|
4021
4588
|
const present = hasSoloSkill && registered && hasHookFiles;
|
|
@@ -4031,7 +4598,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4031
4598
|
if (platform === "cursor") {
|
|
4032
4599
|
const hasCoreRules = await allManagedSkills(
|
|
4033
4600
|
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
4034
|
-
(file) =>
|
|
4601
|
+
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4035
4602
|
),
|
|
4036
4603
|
[CURSOR_RULE_MANAGED_MARKER]
|
|
4037
4604
|
);
|
|
@@ -4046,13 +4613,13 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4046
4613
|
const [hasRules, hasCommands] = await Promise.all([
|
|
4047
4614
|
allManagedSkills(
|
|
4048
4615
|
MANCODE_CURSOR_RULE_FILES.map(
|
|
4049
|
-
(file) =>
|
|
4616
|
+
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4050
4617
|
),
|
|
4051
4618
|
[CURSOR_RULE_MANAGED_MARKER]
|
|
4052
4619
|
),
|
|
4053
4620
|
allManagedSkills(
|
|
4054
4621
|
MODE_NAMES.map(
|
|
4055
|
-
(mode) =>
|
|
4622
|
+
(mode) => path15.join(rootDir, ".cursor", "commands", `${mode}.md`)
|
|
4056
4623
|
),
|
|
4057
4624
|
[MODE_FILE_MANAGED_MARKER]
|
|
4058
4625
|
)
|
|
@@ -4065,7 +4632,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4065
4632
|
};
|
|
4066
4633
|
}
|
|
4067
4634
|
if (platform === "codex") {
|
|
4068
|
-
const hasBlock2 = await fileHasManagedBlock(
|
|
4635
|
+
const hasBlock2 = await fileHasManagedBlock(path15.join(rootDir, "AGENTS.md"));
|
|
4069
4636
|
if (!hasBlock2) {
|
|
4070
4637
|
return {
|
|
4071
4638
|
present: false,
|
|
@@ -4082,9 +4649,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4082
4649
|
readyDetail: "managed block present"
|
|
4083
4650
|
};
|
|
4084
4651
|
}
|
|
4085
|
-
const skillsDir =
|
|
4652
|
+
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4086
4653
|
const hasSkills = await allManagedSkills(
|
|
4087
|
-
MODE_NAMES.map((mode) =>
|
|
4654
|
+
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4088
4655
|
MANCODE_AGENT_SKILL_MARKERS
|
|
4089
4656
|
);
|
|
4090
4657
|
return {
|
|
@@ -4096,7 +4663,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4096
4663
|
}
|
|
4097
4664
|
if (platform === "zcode") {
|
|
4098
4665
|
const hasBlock2 = await fileHasManagedBlock(
|
|
4099
|
-
|
|
4666
|
+
path15.join(rootDir, "AGENTS.md"),
|
|
4100
4667
|
ZCODE_MANCODE_START_MARKER,
|
|
4101
4668
|
ZCODE_MANCODE_END_MARKER
|
|
4102
4669
|
);
|
|
@@ -4116,9 +4683,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4116
4683
|
readyDetail: "managed block present"
|
|
4117
4684
|
};
|
|
4118
4685
|
}
|
|
4119
|
-
const skillsDir =
|
|
4686
|
+
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4120
4687
|
const hasSkills = await allManagedSkills(
|
|
4121
|
-
MODE_NAMES.map((mode) =>
|
|
4688
|
+
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4122
4689
|
MANCODE_AGENT_SKILL_MARKERS
|
|
4123
4690
|
);
|
|
4124
4691
|
return {
|
|
@@ -4129,7 +4696,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4129
4696
|
};
|
|
4130
4697
|
}
|
|
4131
4698
|
const hasBlock = await fileHasManagedBlock(
|
|
4132
|
-
|
|
4699
|
+
path15.join(rootDir, ".github", "copilot-instructions.md")
|
|
4133
4700
|
);
|
|
4134
4701
|
if (!hasBlock) {
|
|
4135
4702
|
return {
|
|
@@ -4140,9 +4707,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4140
4707
|
};
|
|
4141
4708
|
}
|
|
4142
4709
|
if (!await isPlatformMinimal(rootDir, "copilot")) {
|
|
4143
|
-
const promptsDir =
|
|
4710
|
+
const promptsDir = path15.join(rootDir, ".github", "prompts");
|
|
4144
4711
|
const hasPrompts = await allManagedSkills(
|
|
4145
|
-
MODE_NAMES.map((mode) =>
|
|
4712
|
+
MODE_NAMES.map((mode) => path15.join(promptsDir, `${mode}.prompt.md`)),
|
|
4146
4713
|
[MODE_FILE_MANAGED_MARKER]
|
|
4147
4714
|
);
|
|
4148
4715
|
return {
|
|
@@ -4175,13 +4742,13 @@ async function allManagedSkills(paths, markers) {
|
|
|
4175
4742
|
async function claudeFullContentReady(rootDir) {
|
|
4176
4743
|
const skillChecks = MVP2_SKILLS.map(
|
|
4177
4744
|
(skill) => fileMatches(
|
|
4178
|
-
|
|
4745
|
+
path15.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
|
|
4179
4746
|
(content) => isGeneratedClaudeSkill(content, skill.name)
|
|
4180
4747
|
)
|
|
4181
4748
|
);
|
|
4182
4749
|
const agentChecks = ALL_AGENTS.map(
|
|
4183
4750
|
(agent) => fileMatches(
|
|
4184
|
-
|
|
4751
|
+
path15.join(rootDir, ".claude", "agents", `${agent.name}.md`),
|
|
4185
4752
|
(content) => isGeneratedClaudeAgent(content, agent.name)
|
|
4186
4753
|
)
|
|
4187
4754
|
);
|
|
@@ -4190,7 +4757,7 @@ async function claudeFullContentReady(rootDir) {
|
|
|
4190
4757
|
}
|
|
4191
4758
|
async function fileMatches(filePath, predicate) {
|
|
4192
4759
|
try {
|
|
4193
|
-
return predicate(await
|
|
4760
|
+
return predicate(await fs4.readFile(filePath, "utf-8"));
|
|
4194
4761
|
} catch {
|
|
4195
4762
|
return false;
|
|
4196
4763
|
}
|
|
@@ -4200,7 +4767,7 @@ async function pathsExist(paths) {
|
|
|
4200
4767
|
}
|
|
4201
4768
|
async function fileHasAnyMarker(filePath, needles) {
|
|
4202
4769
|
try {
|
|
4203
|
-
const content = await
|
|
4770
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
4204
4771
|
return needles.some((needle) => content.includes(needle));
|
|
4205
4772
|
} catch {
|
|
4206
4773
|
return false;
|
|
@@ -4208,8 +4775,8 @@ async function fileHasAnyMarker(filePath, needles) {
|
|
|
4208
4775
|
}
|
|
4209
4776
|
async function isPlatformMinimal(rootDir, platform) {
|
|
4210
4777
|
try {
|
|
4211
|
-
const raw = await
|
|
4212
|
-
|
|
4778
|
+
const raw = await fs4.readFile(
|
|
4779
|
+
path15.join(rootDir, ".mancode", "config.json"),
|
|
4213
4780
|
"utf-8"
|
|
4214
4781
|
);
|
|
4215
4782
|
const config = JSON.parse(raw);
|
|
@@ -4222,7 +4789,7 @@ async function isPlatformMinimal(rootDir, platform) {
|
|
|
4222
4789
|
}
|
|
4223
4790
|
async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
4224
4791
|
try {
|
|
4225
|
-
const content = await
|
|
4792
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
4226
4793
|
return hasManagedBlock(content, startMarker, endMarker);
|
|
4227
4794
|
} catch {
|
|
4228
4795
|
return false;
|
|
@@ -4230,8 +4797,8 @@ async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START
|
|
|
4230
4797
|
}
|
|
4231
4798
|
async function claudeHooksRegistered(rootDir) {
|
|
4232
4799
|
try {
|
|
4233
|
-
const raw = await
|
|
4234
|
-
|
|
4800
|
+
const raw = await fs4.readFile(
|
|
4801
|
+
path15.join(rootDir, ".claude", "settings.json"),
|
|
4235
4802
|
"utf-8"
|
|
4236
4803
|
);
|
|
4237
4804
|
const settings = JSON.parse(raw);
|
|
@@ -4261,7 +4828,7 @@ function hasHookCommand(value, needle) {
|
|
|
4261
4828
|
}
|
|
4262
4829
|
async function pathExists4(p) {
|
|
4263
4830
|
try {
|
|
4264
|
-
await
|
|
4831
|
+
await fs4.access(p);
|
|
4265
4832
|
return true;
|
|
4266
4833
|
} catch {
|
|
4267
4834
|
return false;
|
|
@@ -4276,8 +4843,8 @@ var EXIT_OK2 = 0;
|
|
|
4276
4843
|
var EXIT_NOT_INITIALIZED = 1;
|
|
4277
4844
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|
|
4278
4845
|
var EXIT_INSTALL_FAILED = 3;
|
|
4279
|
-
async function install(rootDir =
|
|
4280
|
-
const stateFile =
|
|
4846
|
+
async function install(rootDir = process6.cwd(), platform = "claude-code", options = {}) {
|
|
4847
|
+
const stateFile = path16.join(rootDir, ".mancode", "state.json");
|
|
4281
4848
|
if (!await pathExists5(stateFile)) {
|
|
4282
4849
|
console.error("\u2717 mancode not initialized.");
|
|
4283
4850
|
console.error(" Run `mancode init` first.");
|
|
@@ -4363,9 +4930,9 @@ async function install(rootDir = process5.cwd(), platform = "claude-code", optio
|
|
|
4363
4930
|
return EXIT_OK2;
|
|
4364
4931
|
}
|
|
4365
4932
|
async function readConfig(rootDir) {
|
|
4366
|
-
const configPath =
|
|
4933
|
+
const configPath = path16.join(rootDir, ".mancode", "config.json");
|
|
4367
4934
|
try {
|
|
4368
|
-
const raw = await
|
|
4935
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
4369
4936
|
return { config: JSON.parse(raw), valid: true };
|
|
4370
4937
|
} catch (err) {
|
|
4371
4938
|
if (isNodeError4(err) && err.code === "ENOENT") {
|
|
@@ -4378,10 +4945,10 @@ function isNodeError4(err) {
|
|
|
4378
4945
|
return err instanceof Error && "code" in err;
|
|
4379
4946
|
}
|
|
4380
4947
|
async function updateConfig(rootDir, config) {
|
|
4381
|
-
const configPath =
|
|
4948
|
+
const configPath = path16.join(rootDir, ".mancode", "config.json");
|
|
4382
4949
|
const content = `${JSON.stringify(config, null, 2)}
|
|
4383
4950
|
`;
|
|
4384
|
-
await
|
|
4951
|
+
await fs5.writeFile(configPath, content, "utf-8");
|
|
4385
4952
|
}
|
|
4386
4953
|
function withDefaultConfig(config, fallbackPlatform) {
|
|
4387
4954
|
const fallback = fallbackPlatform ?? DEFAULT_CONFIG.platforms[0];
|
|
@@ -4413,8 +4980,8 @@ function readConfiguredMinimal(value, platform) {
|
|
|
4413
4980
|
}
|
|
4414
4981
|
async function readStatePlatform(rootDir) {
|
|
4415
4982
|
try {
|
|
4416
|
-
const raw = await
|
|
4417
|
-
|
|
4983
|
+
const raw = await fs5.readFile(
|
|
4984
|
+
path16.join(rootDir, ".mancode", "state.json"),
|
|
4418
4985
|
"utf-8"
|
|
4419
4986
|
);
|
|
4420
4987
|
const state = JSON.parse(raw);
|
|
@@ -4425,7 +4992,7 @@ async function readStatePlatform(rootDir) {
|
|
|
4425
4992
|
}
|
|
4426
4993
|
async function pathExists5(p) {
|
|
4427
4994
|
try {
|
|
4428
|
-
await
|
|
4995
|
+
await fs5.access(p);
|
|
4429
4996
|
return true;
|
|
4430
4997
|
} catch {
|
|
4431
4998
|
return false;
|
|
@@ -4436,11 +5003,11 @@ function isRecord5(value) {
|
|
|
4436
5003
|
}
|
|
4437
5004
|
|
|
4438
5005
|
// src/commands/list-platforms.ts
|
|
4439
|
-
import { promises as
|
|
4440
|
-
import
|
|
4441
|
-
import
|
|
5006
|
+
import { promises as fs6 } from "fs";
|
|
5007
|
+
import path17 from "path";
|
|
5008
|
+
import process7 from "process";
|
|
4442
5009
|
var EXIT_OK3 = 0;
|
|
4443
|
-
async function listPlatforms(rootDir =
|
|
5010
|
+
async function listPlatforms(rootDir = process7.cwd()) {
|
|
4444
5011
|
const installed = new Set(await readInstalledPlatforms(rootDir));
|
|
4445
5012
|
const platforms = getPlatformInstallers();
|
|
4446
5013
|
console.log("");
|
|
@@ -4453,8 +5020,8 @@ async function listPlatforms(rootDir = process6.cwd()) {
|
|
|
4453
5020
|
}
|
|
4454
5021
|
async function readInstalledPlatforms(rootDir) {
|
|
4455
5022
|
try {
|
|
4456
|
-
const raw = await
|
|
4457
|
-
|
|
5023
|
+
const raw = await fs6.readFile(
|
|
5024
|
+
path17.join(rootDir, ".mancode", "config.json"),
|
|
4458
5025
|
"utf-8"
|
|
4459
5026
|
);
|
|
4460
5027
|
const config = JSON.parse(raw);
|
|
@@ -4479,8 +5046,8 @@ function describePlatform(platform) {
|
|
|
4479
5046
|
|
|
4480
5047
|
// src/commands/manps.ts
|
|
4481
5048
|
import { access as access4 } from "fs/promises";
|
|
4482
|
-
import
|
|
4483
|
-
import { createInterface } from "readline/promises";
|
|
5049
|
+
import path19 from "path";
|
|
5050
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
4484
5051
|
|
|
4485
5052
|
// src/system/preseason.ts
|
|
4486
5053
|
import { execFile as execFile3 } from "child_process";
|
|
@@ -4495,7 +5062,7 @@ import {
|
|
|
4495
5062
|
rename,
|
|
4496
5063
|
writeFile as writeFile9
|
|
4497
5064
|
} from "fs/promises";
|
|
4498
|
-
import
|
|
5065
|
+
import path18 from "path";
|
|
4499
5066
|
var PRESEASON_AREAS = [
|
|
4500
5067
|
"all",
|
|
4501
5068
|
"deps",
|
|
@@ -4575,9 +5142,9 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4575
5142
|
const needsFiles = normalizedArea === "all" || normalizedArea === "dead-code" || normalizedArea === "config";
|
|
4576
5143
|
const files = needsFiles ? await listProjectFiles(projectRoot) : [];
|
|
4577
5144
|
const issues = (await scanArea(projectRoot, normalizedArea, pkg, files)).slice(0, 20);
|
|
4578
|
-
const reportDir =
|
|
5145
|
+
const reportDir = path18.join(projectRoot, ".mancode", "preseason-reports");
|
|
4579
5146
|
await mkdir7(reportDir, { recursive: true });
|
|
4580
|
-
const issueDbPath =
|
|
5147
|
+
const issueDbPath = path18.join(
|
|
4581
5148
|
projectRoot,
|
|
4582
5149
|
".mancode",
|
|
4583
5150
|
"preseason-issues.json"
|
|
@@ -4597,7 +5164,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4597
5164
|
const database = await buildIssueDatabase(projectRoot, report);
|
|
4598
5165
|
await writeFile9(reportPath, renderPreseasonReport(report), "utf-8");
|
|
4599
5166
|
await writeFile9(
|
|
4600
|
-
|
|
5167
|
+
path18.join(projectRoot, ".mancode", "preseason-report.md"),
|
|
4601
5168
|
renderPreseasonReport(report),
|
|
4602
5169
|
"utf-8"
|
|
4603
5170
|
);
|
|
@@ -4605,7 +5172,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4605
5172
|
return report;
|
|
4606
5173
|
}
|
|
4607
5174
|
async function runPreseasonRemediation(projectRoot, issues, options = {}) {
|
|
4608
|
-
const issueDbPath =
|
|
5175
|
+
const issueDbPath = path18.join(
|
|
4609
5176
|
projectRoot,
|
|
4610
5177
|
".mancode",
|
|
4611
5178
|
"preseason-issues.json"
|
|
@@ -4719,7 +5286,7 @@ async function scanArea(projectRoot, area, pkg, files) {
|
|
|
4719
5286
|
async function allocateReportPath(reportDir, baseName) {
|
|
4720
5287
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
4721
5288
|
const suffix = attempt === 0 ? "" : `-${attempt + 1}`;
|
|
4722
|
-
const candidate =
|
|
5289
|
+
const candidate = path18.join(reportDir, `${baseName}${suffix}.md`);
|
|
4723
5290
|
if (!existsSync(candidate)) return candidate;
|
|
4724
5291
|
}
|
|
4725
5292
|
throw new Error(`unable to allocate preseason report path: ${baseName}`);
|
|
@@ -4768,8 +5335,8 @@ async function walk(root, current, results, depth) {
|
|
|
4768
5335
|
}
|
|
4769
5336
|
for (const entry of entries) {
|
|
4770
5337
|
if (IGNORE_DIRS.has(entry)) continue;
|
|
4771
|
-
const abs =
|
|
4772
|
-
const rel =
|
|
5338
|
+
const abs = path18.join(current, entry);
|
|
5339
|
+
const rel = path18.relative(root, abs);
|
|
4773
5340
|
let info;
|
|
4774
5341
|
try {
|
|
4775
5342
|
info = await lstat(abs);
|
|
@@ -4779,7 +5346,7 @@ async function walk(root, current, results, depth) {
|
|
|
4779
5346
|
if (info.isSymbolicLink()) continue;
|
|
4780
5347
|
if (info.isDirectory()) {
|
|
4781
5348
|
await walk(root, abs, results, depth + 1);
|
|
4782
|
-
} else if (SOURCE_EXTENSIONS.has(
|
|
5349
|
+
} else if (SOURCE_EXTENSIONS.has(path18.extname(entry)) || entry === "package.json") {
|
|
4783
5350
|
results.push(rel);
|
|
4784
5351
|
if (results.length >= MAX_PROJECT_FILES) return;
|
|
4785
5352
|
}
|
|
@@ -4787,7 +5354,7 @@ async function walk(root, current, results, depth) {
|
|
|
4787
5354
|
}
|
|
4788
5355
|
async function readPackageJson(projectRoot) {
|
|
4789
5356
|
try {
|
|
4790
|
-
const raw = await readFile8(
|
|
5357
|
+
const raw = await readFile8(path18.join(projectRoot, "package.json"), "utf-8");
|
|
4791
5358
|
return JSON.parse(raw);
|
|
4792
5359
|
} catch {
|
|
4793
5360
|
return null;
|
|
@@ -4880,7 +5447,7 @@ function scanTodos(projectRoot, files) {
|
|
|
4880
5447
|
const matches = [];
|
|
4881
5448
|
for (const file of files) {
|
|
4882
5449
|
if (matches.length >= 7) break;
|
|
4883
|
-
const abs =
|
|
5450
|
+
const abs = path18.join(projectRoot, file);
|
|
4884
5451
|
matches.push(...readTodoIssues(abs, file, matches.length));
|
|
4885
5452
|
}
|
|
4886
5453
|
return matches.slice(0, 7);
|
|
@@ -4917,7 +5484,7 @@ function scanTestGaps(files) {
|
|
|
4917
5484
|
if (sourceFiles.length === 0) return [];
|
|
4918
5485
|
const tests = new Set(files.filter((file) => file.startsWith("tests/")));
|
|
4919
5486
|
const missing = sourceFiles.filter((file) => {
|
|
4920
|
-
const base =
|
|
5487
|
+
const base = path18.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
4921
5488
|
return !Array.from(tests).some((test) => test.includes(base));
|
|
4922
5489
|
}).slice(0, 4);
|
|
4923
5490
|
return missing.map((file, index) => ({
|
|
@@ -4926,13 +5493,13 @@ function scanTestGaps(files) {
|
|
|
4926
5493
|
type: "tests",
|
|
4927
5494
|
title: "Core source file has no obvious test",
|
|
4928
5495
|
file,
|
|
4929
|
-
detail: `No matching test file was found for ${
|
|
5496
|
+
detail: `No matching test file was found for ${path18.basename(file)}.`,
|
|
4930
5497
|
recommendation: "Add focused coverage for the public behavior or document why this module is exercised indirectly."
|
|
4931
5498
|
}));
|
|
4932
5499
|
}
|
|
4933
5500
|
function scanConfig(projectRoot, files) {
|
|
4934
5501
|
const issues = [];
|
|
4935
|
-
if (!files.includes(".gitignore") && !pathExistsSync(
|
|
5502
|
+
if (!files.includes(".gitignore") && !pathExistsSync(path18.join(projectRoot, ".gitignore"))) {
|
|
4936
5503
|
issues.push({
|
|
4937
5504
|
id: "config-gitignore",
|
|
4938
5505
|
severity: "P1",
|
|
@@ -4943,7 +5510,7 @@ function scanConfig(projectRoot, files) {
|
|
|
4943
5510
|
recommendation: "Add a .gitignore that excludes dependencies, build output, coverage, and local env files."
|
|
4944
5511
|
});
|
|
4945
5512
|
}
|
|
4946
|
-
if (!files.includes(".editorconfig") && !pathExistsSync(
|
|
5513
|
+
if (!files.includes(".editorconfig") && !pathExistsSync(path18.join(projectRoot, ".editorconfig"))) {
|
|
4947
5514
|
issues.push({
|
|
4948
5515
|
id: "config-editorconfig",
|
|
4949
5516
|
severity: "P2",
|
|
@@ -4962,7 +5529,7 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
4962
5529
|
for (const file of frontendFiles) {
|
|
4963
5530
|
let content;
|
|
4964
5531
|
try {
|
|
4965
|
-
content = readFileSyncSafe(
|
|
5532
|
+
content = readFileSyncSafe(path18.join(projectRoot, file));
|
|
4966
5533
|
} catch {
|
|
4967
5534
|
continue;
|
|
4968
5535
|
}
|
|
@@ -4982,7 +5549,7 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
4982
5549
|
return issues;
|
|
4983
5550
|
}
|
|
4984
5551
|
async function scanArchitecture(projectRoot) {
|
|
4985
|
-
const localBinary =
|
|
5552
|
+
const localBinary = path18.join(
|
|
4986
5553
|
projectRoot,
|
|
4987
5554
|
"node_modules",
|
|
4988
5555
|
".bin",
|
|
@@ -5030,7 +5597,7 @@ async function hasDependencyCruiserConfig(projectRoot) {
|
|
|
5030
5597
|
"dependency-cruiser.config.mjs"
|
|
5031
5598
|
];
|
|
5032
5599
|
const results = await Promise.all(
|
|
5033
|
-
files.map((file) => pathExists6(
|
|
5600
|
+
files.map((file) => pathExists6(path18.join(projectRoot, file)))
|
|
5034
5601
|
);
|
|
5035
5602
|
return results.some(Boolean);
|
|
5036
5603
|
}
|
|
@@ -5056,11 +5623,11 @@ function runDepcruise(binary, projectRoot) {
|
|
|
5056
5623
|
maxBuffer: DEPCRUISE_MAX_BUFFER,
|
|
5057
5624
|
shell: needsShell
|
|
5058
5625
|
},
|
|
5059
|
-
(error,
|
|
5626
|
+
(error, stdout2, stderr) => {
|
|
5060
5627
|
if (!error) {
|
|
5061
5628
|
resolve({
|
|
5062
5629
|
status: "ok",
|
|
5063
|
-
stdout,
|
|
5630
|
+
stdout: stdout2,
|
|
5064
5631
|
stderr,
|
|
5065
5632
|
exitCode: 0,
|
|
5066
5633
|
timedOut: false
|
|
@@ -5080,7 +5647,7 @@ function runDepcruise(binary, projectRoot) {
|
|
|
5080
5647
|
}
|
|
5081
5648
|
resolve({
|
|
5082
5649
|
status: "failed",
|
|
5083
|
-
stdout: nodeError.stdout ??
|
|
5650
|
+
stdout: nodeError.stdout ?? stdout2,
|
|
5084
5651
|
stderr: nodeError.stderr ?? stderr,
|
|
5085
5652
|
exitCode: typeof nodeError.code === "number" ? nodeError.code : null,
|
|
5086
5653
|
timedOut: Boolean(nodeError.killed || nodeError.signal === "SIGTERM")
|
|
@@ -5136,9 +5703,9 @@ function inferCommands(pkg) {
|
|
|
5136
5703
|
return ["lint", "test", "build"].filter((name) => scripts[name]).map((name) => `npm run ${name}`);
|
|
5137
5704
|
}
|
|
5138
5705
|
async function buildIssueDatabase(projectRoot, report) {
|
|
5139
|
-
const reportRef =
|
|
5706
|
+
const reportRef = path18.relative(projectRoot, report.reportPath);
|
|
5140
5707
|
const run = {
|
|
5141
|
-
id:
|
|
5708
|
+
id: path18.basename(report.reportPath, ".md"),
|
|
5142
5709
|
generatedAt: report.generatedAt,
|
|
5143
5710
|
area: report.area,
|
|
5144
5711
|
reportPath: reportRef,
|
|
@@ -5244,7 +5811,7 @@ function compareIssueRecords(a, b) {
|
|
|
5244
5811
|
}
|
|
5245
5812
|
async function applySafeRemediation(projectRoot, issue) {
|
|
5246
5813
|
if (issue.id === "config-gitignore" && issue.file === ".gitignore") {
|
|
5247
|
-
const gitignorePath =
|
|
5814
|
+
const gitignorePath = path18.join(projectRoot, ".gitignore");
|
|
5248
5815
|
if (pathExistsSync(gitignorePath)) {
|
|
5249
5816
|
return { applied: false };
|
|
5250
5817
|
}
|
|
@@ -5253,7 +5820,7 @@ async function applySafeRemediation(projectRoot, issue) {
|
|
|
5253
5820
|
return { applied: true, action: "created .gitignore" };
|
|
5254
5821
|
}
|
|
5255
5822
|
if (issue.id === "config-editorconfig" && issue.file === ".editorconfig") {
|
|
5256
|
-
const editorconfigPath =
|
|
5823
|
+
const editorconfigPath = path18.join(projectRoot, ".editorconfig");
|
|
5257
5824
|
if (pathExistsSync(editorconfigPath)) {
|
|
5258
5825
|
return { applied: false };
|
|
5259
5826
|
}
|
|
@@ -5293,7 +5860,7 @@ async function inferSafePackageScript(projectRoot, scriptName) {
|
|
|
5293
5860
|
}
|
|
5294
5861
|
}
|
|
5295
5862
|
async function addPackageScript(projectRoot, scriptName, script) {
|
|
5296
|
-
const packagePath =
|
|
5863
|
+
const packagePath = path18.join(projectRoot, "package.json");
|
|
5297
5864
|
let pkg;
|
|
5298
5865
|
try {
|
|
5299
5866
|
pkg = JSON.parse(await readFile8(packagePath, "utf-8"));
|
|
@@ -5406,7 +5973,7 @@ var EXIT_NOT_INITIALIZED2 = 1;
|
|
|
5406
5973
|
var EXIT_SCAN_FAILED = 2;
|
|
5407
5974
|
var EXIT_INVALID_ARG = 3;
|
|
5408
5975
|
async function manps(rootDir, area = "all", options = {}) {
|
|
5409
|
-
if (!await pathExists7(
|
|
5976
|
+
if (!await pathExists7(path19.join(rootDir, ".mancode", "state.json"))) {
|
|
5410
5977
|
if (options.json) {
|
|
5411
5978
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
5412
5979
|
} else {
|
|
@@ -5485,8 +6052,8 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
5485
6052
|
console.log(
|
|
5486
6053
|
`Issues: ${report.issues.length} total (P0 ${p0}, P1 ${p1}, P2 ${p2})`
|
|
5487
6054
|
);
|
|
5488
|
-
console.log(`Report: ${
|
|
5489
|
-
console.log(`Issue DB: ${
|
|
6055
|
+
console.log(`Report: ${path19.relative(rootDir, report.reportPath)}`);
|
|
6056
|
+
console.log(`Issue DB: ${path19.relative(rootDir, report.issueDbPath)}`);
|
|
5490
6057
|
if (report.issues.length > 0) {
|
|
5491
6058
|
console.log("");
|
|
5492
6059
|
for (const issue of report.issues.slice(0, 7)) {
|
|
@@ -5502,7 +6069,7 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
5502
6069
|
console.log(` Skipped: ${remediation.skipped}`);
|
|
5503
6070
|
console.log(` Fixed: ${remediation.fixed}`);
|
|
5504
6071
|
console.log(
|
|
5505
|
-
` Issue DB: ${
|
|
6072
|
+
` Issue DB: ${path19.relative(rootDir, remediation.issueDbPath)}`
|
|
5506
6073
|
);
|
|
5507
6074
|
}
|
|
5508
6075
|
return EXIT_OK4;
|
|
@@ -5528,7 +6095,7 @@ async function runRemediation(rootDir, report, options, silent = false) {
|
|
|
5528
6095
|
write
|
|
5529
6096
|
});
|
|
5530
6097
|
}
|
|
5531
|
-
const rl =
|
|
6098
|
+
const rl = createInterface2({
|
|
5532
6099
|
input: process.stdin,
|
|
5533
6100
|
output: process.stdout
|
|
5534
6101
|
});
|
|
@@ -5557,23 +6124,246 @@ async function pathExists7(p) {
|
|
|
5557
6124
|
}
|
|
5558
6125
|
}
|
|
5559
6126
|
|
|
5560
|
-
// src/commands/refresh-
|
|
5561
|
-
import {
|
|
5562
|
-
import
|
|
5563
|
-
import
|
|
6127
|
+
// src/commands/refresh-project.ts
|
|
6128
|
+
import { randomUUID } from "crypto";
|
|
6129
|
+
import { promises as fs7 } from "fs";
|
|
6130
|
+
import path20 from "path";
|
|
6131
|
+
import process8 from "process";
|
|
5564
6132
|
var EXIT_OK5 = 0;
|
|
5565
6133
|
var EXIT_NOT_INITIALIZED3 = 1;
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
6134
|
+
var EXIT_CORRUPT_STATE = 2;
|
|
6135
|
+
var EXIT_REFRESH_FAILED = 3;
|
|
6136
|
+
async function refreshProject(rootDir = process8.cwd()) {
|
|
6137
|
+
const mancodeDir = path20.join(rootDir, ".mancode");
|
|
6138
|
+
const statePath = path20.join(mancodeDir, "state.json");
|
|
6139
|
+
if (!await pathExists8(statePath)) {
|
|
5569
6140
|
console.error("\u2717 mancode not initialized.");
|
|
5570
6141
|
console.error(" Run `mancode init` first.");
|
|
5571
6142
|
return EXIT_NOT_INITIALIZED3;
|
|
5572
6143
|
}
|
|
6144
|
+
const state = await readRequiredState(statePath);
|
|
6145
|
+
if (!state) {
|
|
6146
|
+
console.error("\u2717 .mancode/state.json is corrupt or incomplete.");
|
|
6147
|
+
console.error(" Run `mancode init --force` to repair it.");
|
|
6148
|
+
return EXIT_CORRUPT_STATE;
|
|
6149
|
+
}
|
|
6150
|
+
let factsWritten = false;
|
|
6151
|
+
try {
|
|
6152
|
+
const [profile, team, hasGit, hasManifest] = await Promise.all([
|
|
6153
|
+
detectProjectProfile(rootDir),
|
|
6154
|
+
detectTeamStatus(rootDir),
|
|
6155
|
+
pathExists8(path20.join(rootDir, ".git")),
|
|
6156
|
+
hasProjectManifest2(rootDir)
|
|
6157
|
+
]);
|
|
6158
|
+
const uiLibrary = primaryUiLibrary(profile);
|
|
6159
|
+
const stack = [...profile.languages, ...profile.frameworks];
|
|
6160
|
+
const config = await readJson3(path20.join(mancodeDir, "config.json"));
|
|
6161
|
+
const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : team.isTeam;
|
|
6162
|
+
const nextState = {
|
|
6163
|
+
...state,
|
|
6164
|
+
techStack: stack.join(" + ") || profile.projectKind,
|
|
6165
|
+
uiLibrary: uiLibrary ?? "None",
|
|
6166
|
+
projectMode: hasGit || hasManifest ? "detected" : "generic",
|
|
6167
|
+
teamModeAutoDetected: configuredTeam,
|
|
6168
|
+
contributors: team.contributors
|
|
6169
|
+
};
|
|
6170
|
+
await writeProjectFacts(
|
|
6171
|
+
statePath,
|
|
6172
|
+
path20.join(mancodeDir, "project-profile.json"),
|
|
6173
|
+
`${JSON.stringify(nextState, null, 2)}
|
|
6174
|
+
`,
|
|
6175
|
+
`${JSON.stringify(profile, null, 2)}
|
|
6176
|
+
`
|
|
6177
|
+
);
|
|
6178
|
+
factsWritten = true;
|
|
6179
|
+
const refreshedPlatforms = await refreshStaticPlatforms(
|
|
6180
|
+
rootDir,
|
|
6181
|
+
config,
|
|
6182
|
+
state.platform,
|
|
6183
|
+
stack,
|
|
6184
|
+
uiLibrary,
|
|
6185
|
+
profile
|
|
6186
|
+
);
|
|
6187
|
+
console.log("\u2713 Project facts refreshed.");
|
|
6188
|
+
console.log(
|
|
6189
|
+
` ${hasGit ? "Git detected" : "No Git repository"} | ${hasManifest ? "project manifest detected" : "generic project"}`
|
|
6190
|
+
);
|
|
6191
|
+
console.log(
|
|
6192
|
+
` Stack: ${nextState.techStack} | UI: ${nextState.uiLibrary}`
|
|
6193
|
+
);
|
|
6194
|
+
if (refreshedPlatforms.length > 0) {
|
|
6195
|
+
console.log(` Refreshed adapters: ${refreshedPlatforms.join(", ")}`);
|
|
6196
|
+
}
|
|
6197
|
+
console.log(
|
|
6198
|
+
" Run `mancode refresh-style` if UI files or dependencies changed."
|
|
6199
|
+
);
|
|
6200
|
+
return EXIT_OK5;
|
|
6201
|
+
} catch (error) {
|
|
6202
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6203
|
+
console.error(`\u2717 Project refresh failed: ${message}`);
|
|
6204
|
+
if (factsWritten) {
|
|
6205
|
+
console.error(
|
|
6206
|
+
" Project facts were saved, but one or more static adapters remain stale."
|
|
6207
|
+
);
|
|
6208
|
+
}
|
|
6209
|
+
return EXIT_REFRESH_FAILED;
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6212
|
+
async function hasProjectManifest2(rootDir) {
|
|
6213
|
+
for (const manifest of PROJECT_MANIFESTS) {
|
|
6214
|
+
if (await pathExists8(path20.join(rootDir, manifest))) return true;
|
|
6215
|
+
}
|
|
6216
|
+
return false;
|
|
6217
|
+
}
|
|
6218
|
+
async function writeProjectFacts(statePath, profilePath, stateContent, profileContent) {
|
|
6219
|
+
await Promise.all([
|
|
6220
|
+
ensureReplaceableFile(statePath),
|
|
6221
|
+
ensureReplaceableFile(profilePath)
|
|
6222
|
+
]);
|
|
6223
|
+
const previousProfile = await readOptionalText(profilePath);
|
|
6224
|
+
const stateTemp = temporaryPath(statePath);
|
|
6225
|
+
const profileTemp = temporaryPath(profilePath);
|
|
6226
|
+
let profileReplaced = false;
|
|
6227
|
+
try {
|
|
6228
|
+
await Promise.all([
|
|
6229
|
+
fs7.writeFile(stateTemp, stateContent, "utf-8"),
|
|
6230
|
+
fs7.writeFile(profileTemp, profileContent, "utf-8")
|
|
6231
|
+
]);
|
|
6232
|
+
await fs7.rename(profileTemp, profilePath);
|
|
6233
|
+
profileReplaced = true;
|
|
6234
|
+
await fs7.rename(stateTemp, statePath);
|
|
6235
|
+
} catch (error) {
|
|
6236
|
+
if (profileReplaced) {
|
|
6237
|
+
if (previousProfile === null) {
|
|
6238
|
+
await fs7.rm(profilePath, { force: true });
|
|
6239
|
+
} else {
|
|
6240
|
+
await replaceTextFile(profilePath, previousProfile);
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6243
|
+
throw error;
|
|
6244
|
+
} finally {
|
|
6245
|
+
await Promise.all([
|
|
6246
|
+
fs7.rm(stateTemp, { force: true }),
|
|
6247
|
+
fs7.rm(profileTemp, { force: true })
|
|
6248
|
+
]);
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
async function ensureReplaceableFile(filePath) {
|
|
6252
|
+
try {
|
|
6253
|
+
const entry = await fs7.lstat(filePath);
|
|
6254
|
+
if (!entry.isFile()) {
|
|
6255
|
+
throw new Error(`cannot replace non-file path: ${filePath}`);
|
|
6256
|
+
}
|
|
6257
|
+
} catch (error) {
|
|
6258
|
+
if (isNodeError6(error) && error.code === "ENOENT") return;
|
|
6259
|
+
throw error;
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
async function replaceTextFile(filePath, content) {
|
|
6263
|
+
const tempPath = temporaryPath(filePath);
|
|
6264
|
+
try {
|
|
6265
|
+
await fs7.writeFile(tempPath, content, "utf-8");
|
|
6266
|
+
await fs7.rename(tempPath, filePath);
|
|
6267
|
+
} finally {
|
|
6268
|
+
await fs7.rm(tempPath, { force: true });
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
async function readOptionalText(filePath) {
|
|
6272
|
+
try {
|
|
6273
|
+
return await fs7.readFile(filePath, "utf-8");
|
|
6274
|
+
} catch (error) {
|
|
6275
|
+
if (isNodeError6(error) && error.code === "ENOENT") return null;
|
|
6276
|
+
throw error;
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
function temporaryPath(filePath) {
|
|
6280
|
+
return path20.join(
|
|
6281
|
+
path20.dirname(filePath),
|
|
6282
|
+
`.${path20.basename(filePath)}.${process8.pid}.${randomUUID()}.tmp`
|
|
6283
|
+
);
|
|
6284
|
+
}
|
|
6285
|
+
async function refreshStaticPlatforms(rootDir, config, fallbackPlatform, stack, uiLibrary, profile) {
|
|
6286
|
+
const platforms = configuredPlatforms(config, fallbackPlatform).filter(
|
|
6287
|
+
(platform) => platform !== "claude-code"
|
|
6288
|
+
);
|
|
6289
|
+
const refreshed = [];
|
|
6290
|
+
for (const platform of platforms) {
|
|
6291
|
+
const installer = getPlatformInstaller(platform);
|
|
6292
|
+
if (!installer) continue;
|
|
6293
|
+
await installer.install(rootDir, {
|
|
6294
|
+
techStack: stack,
|
|
6295
|
+
uiLibrary,
|
|
6296
|
+
projectProfile: profile,
|
|
6297
|
+
minimal: platformIsMinimal(config, platform),
|
|
6298
|
+
force: true
|
|
6299
|
+
});
|
|
6300
|
+
refreshed.push(installer.displayName);
|
|
6301
|
+
}
|
|
6302
|
+
return refreshed;
|
|
6303
|
+
}
|
|
6304
|
+
function configuredPlatforms(config, fallbackPlatform) {
|
|
6305
|
+
const configured = Array.isArray(config.platforms) ? config.platforms : [fallbackPlatform];
|
|
6306
|
+
return configured.filter(
|
|
6307
|
+
(platform) => typeof platform === "string" && getPlatformInstaller(platform) !== null
|
|
6308
|
+
);
|
|
6309
|
+
}
|
|
6310
|
+
function platformIsMinimal(config, platform) {
|
|
6311
|
+
if (!isRecord7(config.platformOptions)) return false;
|
|
6312
|
+
const options = config.platformOptions[platform];
|
|
6313
|
+
return isRecord7(options) && options.minimal === true;
|
|
6314
|
+
}
|
|
6315
|
+
async function readJson3(filePath) {
|
|
6316
|
+
try {
|
|
6317
|
+
const value = JSON.parse(await fs7.readFile(filePath, "utf-8"));
|
|
6318
|
+
return isRecord7(value) ? value : {};
|
|
6319
|
+
} catch {
|
|
6320
|
+
return {};
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
async function readRequiredState(filePath) {
|
|
6324
|
+
let state;
|
|
6325
|
+
try {
|
|
6326
|
+
const raw = await fs7.readFile(filePath, "utf-8");
|
|
6327
|
+
const parsed = JSON.parse(raw);
|
|
6328
|
+
if (!isRecord7(parsed)) return null;
|
|
6329
|
+
state = parsed;
|
|
6330
|
+
} catch {
|
|
6331
|
+
return null;
|
|
6332
|
+
}
|
|
6333
|
+
return typeof state.version === "string" && typeof state.currentMode === "string" && typeof state.platform === "string" ? state : null;
|
|
6334
|
+
}
|
|
6335
|
+
function isRecord7(value) {
|
|
6336
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6337
|
+
}
|
|
6338
|
+
function isNodeError6(error) {
|
|
6339
|
+
return error instanceof Error && "code" in error;
|
|
6340
|
+
}
|
|
6341
|
+
async function pathExists8(filePath) {
|
|
6342
|
+
try {
|
|
6343
|
+
await fs7.access(filePath);
|
|
6344
|
+
return true;
|
|
6345
|
+
} catch {
|
|
6346
|
+
return false;
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
|
|
6350
|
+
// src/commands/refresh-style.ts
|
|
6351
|
+
import { promises as fs8 } from "fs";
|
|
6352
|
+
import path21 from "path";
|
|
6353
|
+
import process9 from "process";
|
|
6354
|
+
var EXIT_OK6 = 0;
|
|
6355
|
+
var EXIT_NOT_INITIALIZED4 = 1;
|
|
6356
|
+
async function refreshStyle(rootDir = process9.cwd()) {
|
|
6357
|
+
const stateFile = path21.join(rootDir, ".mancode", "state.json");
|
|
6358
|
+
if (!await pathExists9(stateFile)) {
|
|
6359
|
+
console.error("\u2717 mancode not initialized.");
|
|
6360
|
+
console.error(" Run `mancode init` first.");
|
|
6361
|
+
return EXIT_NOT_INITIALIZED4;
|
|
6362
|
+
}
|
|
5573
6363
|
console.log("\u2713 \u5237\u65B0\u9879\u76EE profile...");
|
|
5574
6364
|
const profile = await detectProjectProfile(rootDir);
|
|
5575
|
-
const profilePath =
|
|
5576
|
-
await
|
|
6365
|
+
const profilePath = path21.join(rootDir, ".mancode", "project-profile.json");
|
|
6366
|
+
await fs8.writeFile(
|
|
5577
6367
|
profilePath,
|
|
5578
6368
|
`${JSON.stringify(profile, null, 2)}
|
|
5579
6369
|
`,
|
|
@@ -5590,18 +6380,18 @@ async function refreshStyle(rootDir = process7.cwd()) {
|
|
|
5590
6380
|
);
|
|
5591
6381
|
console.log(" Updated .mancode/project-profile.json.");
|
|
5592
6382
|
await printStaticPlatformRefreshHint(rootDir);
|
|
5593
|
-
return
|
|
6383
|
+
return EXIT_OK6;
|
|
5594
6384
|
}
|
|
5595
6385
|
console.log("\u2713 \u626B\u63CF\u9879\u76EE\u8BBE\u8BA1 token...");
|
|
5596
6386
|
const tokens = await scanAesthetics(rootDir, uiLibraryHint);
|
|
5597
|
-
const tokensPath =
|
|
6387
|
+
const tokensPath = path21.join(
|
|
5598
6388
|
rootDir,
|
|
5599
6389
|
".mancode",
|
|
5600
6390
|
"aesthetics",
|
|
5601
6391
|
"style-tokens.json"
|
|
5602
6392
|
);
|
|
5603
|
-
await
|
|
5604
|
-
await
|
|
6393
|
+
await fs8.mkdir(path21.dirname(tokensPath), { recursive: true });
|
|
6394
|
+
await fs8.writeFile(
|
|
5605
6395
|
tokensPath,
|
|
5606
6396
|
`${JSON.stringify(tokens, null, 2)}
|
|
5607
6397
|
`,
|
|
@@ -5649,7 +6439,7 @@ async function refreshStyle(rootDir = process7.cwd()) {
|
|
|
5649
6439
|
"\u5DF2\u66F4\u65B0 .mancode/project-profile.json \u548C .mancode/aesthetics/style-tokens.json"
|
|
5650
6440
|
);
|
|
5651
6441
|
await printStaticPlatformRefreshHint(rootDir);
|
|
5652
|
-
return
|
|
6442
|
+
return EXIT_OK6;
|
|
5653
6443
|
}
|
|
5654
6444
|
async function printStaticPlatformRefreshHint(rootDir) {
|
|
5655
6445
|
const platforms = await readInstalledPlatforms2(rootDir);
|
|
@@ -5666,11 +6456,11 @@ async function printStaticPlatformRefreshHint(rootDir) {
|
|
|
5666
6456
|
);
|
|
5667
6457
|
}
|
|
5668
6458
|
async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
|
|
5669
|
-
const statePath =
|
|
6459
|
+
const statePath = path21.join(rootDir, ".mancode", "state.json");
|
|
5670
6460
|
try {
|
|
5671
|
-
const state = JSON.parse(await
|
|
6461
|
+
const state = JSON.parse(await fs8.readFile(statePath, "utf-8"));
|
|
5672
6462
|
const stack = [...profile.languages, ...profile.frameworks];
|
|
5673
|
-
await
|
|
6463
|
+
await fs8.writeFile(
|
|
5674
6464
|
statePath,
|
|
5675
6465
|
`${JSON.stringify(
|
|
5676
6466
|
{
|
|
@@ -5689,8 +6479,8 @@ async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
|
|
|
5689
6479
|
}
|
|
5690
6480
|
async function readInstalledPlatforms2(rootDir) {
|
|
5691
6481
|
try {
|
|
5692
|
-
const raw = await
|
|
5693
|
-
|
|
6482
|
+
const raw = await fs8.readFile(
|
|
6483
|
+
path21.join(rootDir, ".mancode", "config.json"),
|
|
5694
6484
|
"utf-8"
|
|
5695
6485
|
);
|
|
5696
6486
|
const config = JSON.parse(raw);
|
|
@@ -5701,9 +6491,9 @@ async function readInstalledPlatforms2(rootDir) {
|
|
|
5701
6491
|
return [];
|
|
5702
6492
|
}
|
|
5703
6493
|
}
|
|
5704
|
-
async function
|
|
6494
|
+
async function pathExists9(p) {
|
|
5705
6495
|
try {
|
|
5706
|
-
await
|
|
6496
|
+
await fs8.access(p);
|
|
5707
6497
|
return true;
|
|
5708
6498
|
} catch {
|
|
5709
6499
|
return false;
|
|
@@ -5712,9 +6502,9 @@ async function pathExists8(p) {
|
|
|
5712
6502
|
|
|
5713
6503
|
// src/commands/status.ts
|
|
5714
6504
|
import { spawn } from "child_process";
|
|
5715
|
-
import { promises as
|
|
5716
|
-
import
|
|
5717
|
-
import
|
|
6505
|
+
import { promises as fs9 } from "fs";
|
|
6506
|
+
import path24 from "path";
|
|
6507
|
+
import process10 from "process";
|
|
5718
6508
|
|
|
5719
6509
|
// src/system/workflow.ts
|
|
5720
6510
|
import {
|
|
@@ -5725,11 +6515,11 @@ import {
|
|
|
5725
6515
|
stat as stat4,
|
|
5726
6516
|
writeFile as writeFile11
|
|
5727
6517
|
} from "fs/promises";
|
|
5728
|
-
import
|
|
6518
|
+
import path23 from "path";
|
|
5729
6519
|
|
|
5730
6520
|
// src/system/review-ledger.ts
|
|
5731
6521
|
import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
|
|
5732
|
-
import
|
|
6522
|
+
import path22 from "path";
|
|
5733
6523
|
var REVIEW_FILE = "review-ledger.json";
|
|
5734
6524
|
var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
5735
6525
|
var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
|
|
@@ -5845,8 +6635,8 @@ async function readReviewLedger(projectRoot, taskId) {
|
|
|
5845
6635
|
}
|
|
5846
6636
|
}
|
|
5847
6637
|
function isReviewLedger(value) {
|
|
5848
|
-
if (!
|
|
5849
|
-
if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !
|
|
6638
|
+
if (!isRecord8(value)) return false;
|
|
6639
|
+
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) {
|
|
5850
6640
|
return false;
|
|
5851
6641
|
}
|
|
5852
6642
|
const requiredDomains = value.requiredDomains;
|
|
@@ -5862,13 +6652,13 @@ function isReviewLedger(value) {
|
|
|
5862
6652
|
}
|
|
5863
6653
|
const blockerIds = /* @__PURE__ */ new Set();
|
|
5864
6654
|
return blockers.every(
|
|
5865
|
-
(blocker) =>
|
|
6655
|
+
(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")
|
|
5866
6656
|
);
|
|
5867
6657
|
}
|
|
5868
6658
|
function isDomainArray(value) {
|
|
5869
6659
|
return Array.isArray(value) && value.every(isReviewDomain);
|
|
5870
6660
|
}
|
|
5871
|
-
function
|
|
6661
|
+
function isRecord8(value) {
|
|
5872
6662
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5873
6663
|
}
|
|
5874
6664
|
async function requireReviewLedger(projectRoot, taskId) {
|
|
@@ -5878,17 +6668,17 @@ async function requireReviewLedger(projectRoot, taskId) {
|
|
|
5878
6668
|
return ledger;
|
|
5879
6669
|
}
|
|
5880
6670
|
async function writeReviewLedger(projectRoot, taskId, ledger) {
|
|
5881
|
-
const dir =
|
|
6671
|
+
const dir = path22.join(projectRoot, ".mancode", "workflows", taskId);
|
|
5882
6672
|
await mkdir8(dir, { recursive: true });
|
|
5883
6673
|
await writeFile10(
|
|
5884
|
-
|
|
6674
|
+
path22.join(dir, REVIEW_FILE),
|
|
5885
6675
|
`${JSON.stringify(ledger, null, 2)}
|
|
5886
6676
|
`,
|
|
5887
6677
|
"utf-8"
|
|
5888
6678
|
);
|
|
5889
6679
|
}
|
|
5890
6680
|
function reviewPath(projectRoot, taskId) {
|
|
5891
|
-
return
|
|
6681
|
+
return path22.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
|
|
5892
6682
|
}
|
|
5893
6683
|
function assertValidTaskId(taskId) {
|
|
5894
6684
|
if (!TASK_ID_PATTERN.test(taskId)) {
|
|
@@ -5901,7 +6691,7 @@ function assertSafeReportPath(report) {
|
|
|
5901
6691
|
}
|
|
5902
6692
|
}
|
|
5903
6693
|
async function assertReportExists(projectRoot, taskId, report) {
|
|
5904
|
-
const reportPath =
|
|
6694
|
+
const reportPath = path22.join(
|
|
5905
6695
|
projectRoot,
|
|
5906
6696
|
".mancode",
|
|
5907
6697
|
"workflows",
|
|
@@ -5915,9 +6705,9 @@ async function assertReportExists(projectRoot, taskId, report) {
|
|
|
5915
6705
|
throw new Error(`review report not found: ${report}`);
|
|
5916
6706
|
}
|
|
5917
6707
|
function isSafeReportPath(report) {
|
|
5918
|
-
const normalized =
|
|
6708
|
+
const normalized = path22.posix.normalize(report.replaceAll("\\", "/"));
|
|
5919
6709
|
return Boolean(
|
|
5920
|
-
report.trim() && !
|
|
6710
|
+
report.trim() && !path22.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
|
|
5921
6711
|
);
|
|
5922
6712
|
}
|
|
5923
6713
|
|
|
@@ -5965,7 +6755,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
|
|
|
5965
6755
|
await mkdir9(workflowDir(projectRoot, taskId));
|
|
5966
6756
|
return taskId;
|
|
5967
6757
|
} catch (err) {
|
|
5968
|
-
if (
|
|
6758
|
+
if (isNodeError7(err) && err.code === "EEXIST") {
|
|
5969
6759
|
continue;
|
|
5970
6760
|
}
|
|
5971
6761
|
throw err;
|
|
@@ -6041,19 +6831,19 @@ async function deleteWorkflow(projectRoot, taskId) {
|
|
|
6041
6831
|
return true;
|
|
6042
6832
|
}
|
|
6043
6833
|
function workflowsRoot(projectRoot) {
|
|
6044
|
-
return
|
|
6834
|
+
return path23.join(projectRoot, ".mancode", "workflows");
|
|
6045
6835
|
}
|
|
6046
6836
|
function workflowDir(projectRoot, taskId) {
|
|
6047
6837
|
assertValidTaskId2(taskId);
|
|
6048
|
-
return
|
|
6838
|
+
return path23.join(workflowsRoot(projectRoot), taskId);
|
|
6049
6839
|
}
|
|
6050
6840
|
function metadataPath(projectRoot, taskId) {
|
|
6051
|
-
return
|
|
6841
|
+
return path23.join(workflowDir(projectRoot, taskId), METADATA_FILE);
|
|
6052
6842
|
}
|
|
6053
6843
|
async function writeMetadata(dir, meta) {
|
|
6054
6844
|
const content = `${JSON.stringify(meta, null, 2)}
|
|
6055
6845
|
`;
|
|
6056
|
-
await writeFile11(
|
|
6846
|
+
await writeFile11(path23.join(dir, METADATA_FILE), content, "utf-8");
|
|
6057
6847
|
}
|
|
6058
6848
|
function parseWorkflowMeta(raw, taskId) {
|
|
6059
6849
|
try {
|
|
@@ -6259,7 +7049,7 @@ async function propagateChildStatus(projectRoot, child) {
|
|
|
6259
7049
|
});
|
|
6260
7050
|
}
|
|
6261
7051
|
}
|
|
6262
|
-
function
|
|
7052
|
+
function isNodeError7(err) {
|
|
6263
7053
|
return err instanceof Error && "code" in err;
|
|
6264
7054
|
}
|
|
6265
7055
|
function assertValidTaskId2(taskId) {
|
|
@@ -6270,19 +7060,19 @@ function assertValidTaskId2(taskId) {
|
|
|
6270
7060
|
|
|
6271
7061
|
// src/commands/status.ts
|
|
6272
7062
|
var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
|
|
6273
|
-
var
|
|
6274
|
-
var
|
|
6275
|
-
var
|
|
6276
|
-
async function status(rootDir =
|
|
6277
|
-
const stateFile =
|
|
6278
|
-
if (!await
|
|
7063
|
+
var EXIT_OK7 = 0;
|
|
7064
|
+
var EXIT_NOT_INITIALIZED5 = 1;
|
|
7065
|
+
var EXIT_CORRUPT_STATE2 = 2;
|
|
7066
|
+
async function status(rootDir = process10.cwd(), options = {}) {
|
|
7067
|
+
const stateFile = path24.join(rootDir, ".mancode", "state.json");
|
|
7068
|
+
if (!await pathExists10(stateFile)) {
|
|
6279
7069
|
console.error("\u2717 mancode not initialized.");
|
|
6280
7070
|
console.error(" Run `mancode init` to get started.");
|
|
6281
|
-
return
|
|
7071
|
+
return EXIT_NOT_INITIALIZED5;
|
|
6282
7072
|
}
|
|
6283
7073
|
let state;
|
|
6284
7074
|
try {
|
|
6285
|
-
const raw = await
|
|
7075
|
+
const raw = await fs9.readFile(stateFile, "utf-8");
|
|
6286
7076
|
state = JSON.parse(raw);
|
|
6287
7077
|
} catch (err) {
|
|
6288
7078
|
if (err instanceof SyntaxError) {
|
|
@@ -6293,7 +7083,7 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6293
7083
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6294
7084
|
console.error(`\u2717 Failed to read .mancode/state.json: ${msg}`);
|
|
6295
7085
|
}
|
|
6296
|
-
return
|
|
7086
|
+
return EXIT_CORRUPT_STATE2;
|
|
6297
7087
|
}
|
|
6298
7088
|
const [
|
|
6299
7089
|
project,
|
|
@@ -6301,14 +7091,16 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6301
7091
|
config,
|
|
6302
7092
|
hookInjection,
|
|
6303
7093
|
teamStatus,
|
|
6304
|
-
currentWorkflow
|
|
7094
|
+
currentWorkflow,
|
|
7095
|
+
projectRefreshRecommended
|
|
6305
7096
|
] = await Promise.all([
|
|
6306
7097
|
getProjectName(rootDir),
|
|
6307
7098
|
checkHooks(rootDir),
|
|
6308
7099
|
readConfig2(rootDir),
|
|
6309
7100
|
estimateHookInjection(rootDir),
|
|
6310
7101
|
detectTeamStatus(rootDir),
|
|
6311
|
-
getCurrentWorkflow(rootDir, state.currentTask ?? null)
|
|
7102
|
+
getCurrentWorkflow(rootDir, state.currentTask ?? null),
|
|
7103
|
+
shouldRefreshProject(rootDir, state)
|
|
6312
7104
|
]);
|
|
6313
7105
|
const effectiveTeam = getEffectiveTeamStatus(state, config, teamStatus);
|
|
6314
7106
|
const result = {
|
|
@@ -6323,7 +7115,8 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6323
7115
|
hookInjection,
|
|
6324
7116
|
team: effectiveTeam,
|
|
6325
7117
|
currentWorkflow,
|
|
6326
|
-
platformStatus: {}
|
|
7118
|
+
platformStatus: {},
|
|
7119
|
+
projectRefreshRecommended
|
|
6327
7120
|
};
|
|
6328
7121
|
result.platformStatus = await checkPlatformStatus2(rootDir, result.platforms);
|
|
6329
7122
|
if (options.json) {
|
|
@@ -6331,7 +7124,16 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6331
7124
|
} else {
|
|
6332
7125
|
printText(result);
|
|
6333
7126
|
}
|
|
6334
|
-
return
|
|
7127
|
+
return EXIT_OK7;
|
|
7128
|
+
}
|
|
7129
|
+
async function shouldRefreshProject(rootDir, state) {
|
|
7130
|
+
if (state.projectMode !== "generic") return false;
|
|
7131
|
+
const hasGit = await pathExists10(path24.join(rootDir, ".git"));
|
|
7132
|
+
if (hasGit) return true;
|
|
7133
|
+
for (const manifest of PROJECT_MANIFESTS) {
|
|
7134
|
+
if (await pathExists10(path24.join(rootDir, manifest))) return true;
|
|
7135
|
+
}
|
|
7136
|
+
return false;
|
|
6335
7137
|
}
|
|
6336
7138
|
async function checkPlatformStatus2(rootDir, installedPlatforms) {
|
|
6337
7139
|
const installed = new Set(installedPlatforms);
|
|
@@ -6373,19 +7175,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
|
|
|
6373
7175
|
}
|
|
6374
7176
|
async function getProjectName(rootDir) {
|
|
6375
7177
|
try {
|
|
6376
|
-
const raw = await
|
|
7178
|
+
const raw = await fs9.readFile(path24.join(rootDir, "package.json"), "utf-8");
|
|
6377
7179
|
const pkg = JSON.parse(raw);
|
|
6378
7180
|
if (pkg.name && typeof pkg.name === "string") {
|
|
6379
7181
|
return pkg.name;
|
|
6380
7182
|
}
|
|
6381
7183
|
} catch {
|
|
6382
7184
|
}
|
|
6383
|
-
return
|
|
7185
|
+
return path24.basename(rootDir);
|
|
6384
7186
|
}
|
|
6385
7187
|
async function readConfig2(rootDir) {
|
|
6386
7188
|
try {
|
|
6387
|
-
const raw = await
|
|
6388
|
-
|
|
7189
|
+
const raw = await fs9.readFile(
|
|
7190
|
+
path24.join(rootDir, ".mancode", "config.json"),
|
|
6389
7191
|
"utf-8"
|
|
6390
7192
|
);
|
|
6391
7193
|
return JSON.parse(raw);
|
|
@@ -6400,21 +7202,22 @@ function getInstalledPlatforms(config, fallback) {
|
|
|
6400
7202
|
return fallback ? [fallback] : [];
|
|
6401
7203
|
}
|
|
6402
7204
|
function getEffectiveTeamStatus(state, config, detected) {
|
|
6403
|
-
const configuredTeam = config.forceTeamMode === true ? true : state.teamModeAutoDetected ?? detected.isTeam;
|
|
6404
|
-
const forced = config.forceTeamMode === true;
|
|
7205
|
+
const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
|
|
7206
|
+
const forced = config.teamMode === "on" || config.forceTeamMode === true;
|
|
7207
|
+
const autoDetected = config.forceTeamMode === true || config.teamMode === "on" || config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
|
|
6405
7208
|
return {
|
|
6406
7209
|
...detected,
|
|
6407
7210
|
isTeam: configuredTeam,
|
|
6408
7211
|
contributors: Math.max(detected.contributors, state.contributors ?? 0, 1),
|
|
6409
|
-
autoDetected
|
|
7212
|
+
autoDetected,
|
|
6410
7213
|
forced
|
|
6411
7214
|
};
|
|
6412
7215
|
}
|
|
6413
7216
|
async function checkHooks(rootDir) {
|
|
6414
7217
|
const [sessionStart, userPromptSubmit, registered] = await Promise.all([
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
7218
|
+
pathExists10(path24.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
|
|
7219
|
+
pathExists10(
|
|
7220
|
+
path24.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
6418
7221
|
),
|
|
6419
7222
|
isRegistered(rootDir)
|
|
6420
7223
|
]);
|
|
@@ -6422,8 +7225,8 @@ async function checkHooks(rootDir) {
|
|
|
6422
7225
|
}
|
|
6423
7226
|
async function isRegistered(rootDir) {
|
|
6424
7227
|
try {
|
|
6425
|
-
const raw = await
|
|
6426
|
-
|
|
7228
|
+
const raw = await fs9.readFile(
|
|
7229
|
+
path24.join(rootDir, ".claude", "settings.json"),
|
|
6427
7230
|
"utf-8"
|
|
6428
7231
|
);
|
|
6429
7232
|
const settings = JSON.parse(raw);
|
|
@@ -6433,9 +7236,9 @@ async function isRegistered(rootDir) {
|
|
|
6433
7236
|
}
|
|
6434
7237
|
}
|
|
6435
7238
|
function hooksRegistered2(settings) {
|
|
6436
|
-
if (!
|
|
7239
|
+
if (!isRecord9(settings)) return false;
|
|
6437
7240
|
const hooks = settings.hooks;
|
|
6438
|
-
if (!
|
|
7241
|
+
if (!isRecord9(hooks)) return false;
|
|
6439
7242
|
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
|
|
6440
7243
|
hooks.UserPromptSubmit,
|
|
6441
7244
|
".mancode/hooks/user-prompt-submit.mjs"
|
|
@@ -6444,21 +7247,21 @@ function hooksRegistered2(settings) {
|
|
|
6444
7247
|
function hasHookCommand2(value, needle) {
|
|
6445
7248
|
if (!Array.isArray(value)) return false;
|
|
6446
7249
|
return value.some((group) => {
|
|
6447
|
-
if (!
|
|
7250
|
+
if (!isRecord9(group) || !Array.isArray(group.hooks)) return false;
|
|
6448
7251
|
return group.hooks.some((hook) => {
|
|
6449
|
-
if (!
|
|
7252
|
+
if (!isRecord9(hook) || typeof hook.command !== "string") return false;
|
|
6450
7253
|
return hook.command.includes(needle);
|
|
6451
7254
|
});
|
|
6452
7255
|
});
|
|
6453
7256
|
}
|
|
6454
7257
|
async function estimateHookInjection(rootDir) {
|
|
6455
|
-
const hookPath =
|
|
7258
|
+
const hookPath = path24.join(
|
|
6456
7259
|
rootDir,
|
|
6457
7260
|
".mancode",
|
|
6458
7261
|
"hooks",
|
|
6459
7262
|
"user-prompt-submit.mjs"
|
|
6460
7263
|
);
|
|
6461
|
-
if (!await
|
|
7264
|
+
if (!await pathExists10(hookPath)) {
|
|
6462
7265
|
return { tokens: 0, cap: 800 };
|
|
6463
7266
|
}
|
|
6464
7267
|
try {
|
|
@@ -6473,7 +7276,7 @@ async function estimateHookInjection(rootDir) {
|
|
|
6473
7276
|
}
|
|
6474
7277
|
function runHookEstimate(rootDir, hookPath) {
|
|
6475
7278
|
return new Promise((resolve, reject) => {
|
|
6476
|
-
const child = spawn(
|
|
7279
|
+
const child = spawn(process10.execPath, [hookPath], {
|
|
6477
7280
|
cwd: rootDir,
|
|
6478
7281
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6479
7282
|
});
|
|
@@ -6488,15 +7291,15 @@ function runHookEstimate(rootDir, hookPath) {
|
|
|
6488
7291
|
child.kill();
|
|
6489
7292
|
finish(() => reject(new Error("hook estimate timed out")));
|
|
6490
7293
|
}, HOOK_ESTIMATE_TIMEOUT_MS);
|
|
6491
|
-
let
|
|
7294
|
+
let stdout2 = "";
|
|
6492
7295
|
child.stdout.setEncoding("utf-8");
|
|
6493
7296
|
child.stdout.on("data", (chunk) => {
|
|
6494
|
-
|
|
7297
|
+
stdout2 += chunk;
|
|
6495
7298
|
});
|
|
6496
7299
|
child.on("error", (err) => finish(() => reject(err)));
|
|
6497
7300
|
child.on("close", (code) => {
|
|
6498
7301
|
finish(() => {
|
|
6499
|
-
if (code === 0) resolve(
|
|
7302
|
+
if (code === 0) resolve(stdout2);
|
|
6500
7303
|
else reject(new Error(`hook exited with ${code}`));
|
|
6501
7304
|
});
|
|
6502
7305
|
});
|
|
@@ -6515,6 +7318,11 @@ function printText(r) {
|
|
|
6515
7318
|
console.log(`Style: ${r.uiLibrary}`);
|
|
6516
7319
|
console.log(`Initialized: ${r.initializedAt}`);
|
|
6517
7320
|
console.log(`Team: ${formatTeamStatus(r.team)}`);
|
|
7321
|
+
if (r.projectRefreshRecommended) {
|
|
7322
|
+
console.log(
|
|
7323
|
+
"Project: new Git or project files detected; run `mancode refresh-project`."
|
|
7324
|
+
);
|
|
7325
|
+
}
|
|
6518
7326
|
if (r.currentWorkflow) {
|
|
6519
7327
|
const stepMax = workflowStepMax(r.currentWorkflow.mode);
|
|
6520
7328
|
console.log(
|
|
@@ -6578,12 +7386,12 @@ function formatTeamStatus(team) {
|
|
|
6578
7386
|
function workflowStepMax(mode) {
|
|
6579
7387
|
return maxWorkflowStep(mode);
|
|
6580
7388
|
}
|
|
6581
|
-
function
|
|
7389
|
+
function isRecord9(value) {
|
|
6582
7390
|
return typeof value === "object" && value !== null;
|
|
6583
7391
|
}
|
|
6584
|
-
async function
|
|
7392
|
+
async function pathExists10(p) {
|
|
6585
7393
|
try {
|
|
6586
|
-
await
|
|
7394
|
+
await fs9.access(p);
|
|
6587
7395
|
return true;
|
|
6588
7396
|
} catch {
|
|
6589
7397
|
return false;
|
|
@@ -6592,17 +7400,17 @@ async function pathExists9(p) {
|
|
|
6592
7400
|
|
|
6593
7401
|
// src/commands/uninstall.ts
|
|
6594
7402
|
import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
|
|
6595
|
-
import
|
|
6596
|
-
import
|
|
6597
|
-
var
|
|
6598
|
-
var
|
|
7403
|
+
import path25 from "path";
|
|
7404
|
+
import process11 from "process";
|
|
7405
|
+
var EXIT_OK8 = 0;
|
|
7406
|
+
var EXIT_NOT_INITIALIZED6 = 1;
|
|
6599
7407
|
var EXIT_UNSUPPORTED_PLATFORM2 = 2;
|
|
6600
|
-
async function uninstall(rootDir =
|
|
6601
|
-
const stateFile =
|
|
6602
|
-
if (!await
|
|
7408
|
+
async function uninstall(rootDir = process11.cwd(), platform, options = {}) {
|
|
7409
|
+
const stateFile = path25.join(rootDir, ".mancode", "state.json");
|
|
7410
|
+
if (!await pathExists11(stateFile)) {
|
|
6603
7411
|
console.error("\u2717 mancode not initialized.");
|
|
6604
7412
|
console.error(" Run `mancode init` first.");
|
|
6605
|
-
return
|
|
7413
|
+
return EXIT_NOT_INITIALIZED6;
|
|
6606
7414
|
}
|
|
6607
7415
|
const removeAll = !platform || options.all;
|
|
6608
7416
|
if (platform) {
|
|
@@ -6627,7 +7435,7 @@ async function uninstall(rootDir = process9.cwd(), platform, options = {}) {
|
|
|
6627
7435
|
await uninstallPlatform(rootDir, platform ?? "claude-code");
|
|
6628
7436
|
}
|
|
6629
7437
|
console.log("\u2713 Uninstall complete.");
|
|
6630
|
-
return
|
|
7438
|
+
return EXIT_OK8;
|
|
6631
7439
|
}
|
|
6632
7440
|
async function uninstallPlatform(rootDir, platform) {
|
|
6633
7441
|
console.log(`\u2713 Removing ${formatPlatformName(platform)} adapter...`);
|
|
@@ -6650,7 +7458,7 @@ async function uninstallAll(rootDir) {
|
|
|
6650
7458
|
await uninstallPlatform(rootDir, p);
|
|
6651
7459
|
}
|
|
6652
7460
|
console.log("\u2713 Removing .mancode/ directory...");
|
|
6653
|
-
await rm5(
|
|
7461
|
+
await rm5(path25.join(rootDir, ".mancode"), {
|
|
6654
7462
|
recursive: true,
|
|
6655
7463
|
force: true
|
|
6656
7464
|
});
|
|
@@ -6660,7 +7468,7 @@ async function uninstallClaudeCode(rootDir) {
|
|
|
6660
7468
|
await cleanClaudeSettings(rootDir);
|
|
6661
7469
|
}
|
|
6662
7470
|
async function cleanClaudeSettings(rootDir) {
|
|
6663
|
-
const settingsPath =
|
|
7471
|
+
const settingsPath = path25.join(rootDir, ".claude", "settings.json");
|
|
6664
7472
|
let content;
|
|
6665
7473
|
try {
|
|
6666
7474
|
content = await readFile11(settingsPath, "utf-8");
|
|
@@ -6674,14 +7482,14 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
6674
7482
|
return;
|
|
6675
7483
|
}
|
|
6676
7484
|
const cleanedHooks = {};
|
|
6677
|
-
if (
|
|
7485
|
+
if (isRecord10(settings.hooks)) {
|
|
6678
7486
|
for (const [event, value] of Object.entries(settings.hooks)) {
|
|
6679
7487
|
const cleaned = cleanClaudeHookValue(value);
|
|
6680
7488
|
if (cleaned !== void 0) cleanedHooks[event] = cleaned;
|
|
6681
7489
|
}
|
|
6682
7490
|
settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
|
|
6683
7491
|
}
|
|
6684
|
-
if (
|
|
7492
|
+
if (isRecord10(settings.skills)) {
|
|
6685
7493
|
const retainedSkills = Object.fromEntries(
|
|
6686
7494
|
Object.entries(settings.skills).filter(
|
|
6687
7495
|
([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
|
|
@@ -6701,7 +7509,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6701
7509
|
const entries2 = value.flatMap(cleanClaudeHookEntry);
|
|
6702
7510
|
return entries2.length > 0 ? entries2 : void 0;
|
|
6703
7511
|
}
|
|
6704
|
-
if (!
|
|
7512
|
+
if (!isRecord10(value)) return value;
|
|
6705
7513
|
if (Array.isArray(value.hooks) || typeof value.command === "string") {
|
|
6706
7514
|
const entries2 = cleanClaudeHookEntry(value);
|
|
6707
7515
|
return entries2.length > 0 ? entries2 : void 0;
|
|
@@ -6715,7 +7523,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6715
7523
|
}
|
|
6716
7524
|
function cleanClaudeHookEntry(entry) {
|
|
6717
7525
|
if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
|
|
6718
|
-
if (!
|
|
7526
|
+
if (!isRecord10(entry)) return [entry];
|
|
6719
7527
|
if (Array.isArray(entry.hooks)) {
|
|
6720
7528
|
const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
|
|
6721
7529
|
return hooks.length > 0 ? [{ ...entry, hooks }] : [];
|
|
@@ -6723,11 +7531,11 @@ function cleanClaudeHookEntry(entry) {
|
|
|
6723
7531
|
return isMancodeHookEntry(entry) ? [] : [entry];
|
|
6724
7532
|
}
|
|
6725
7533
|
function isMancodeHookEntry(value) {
|
|
6726
|
-
return
|
|
7534
|
+
return isRecord10(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
|
|
6727
7535
|
}
|
|
6728
7536
|
function containsLegacyMancodeHookPath2(value) {
|
|
6729
7537
|
if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
|
|
6730
|
-
if (!
|
|
7538
|
+
if (!isRecord10(value)) return false;
|
|
6731
7539
|
if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
|
|
6732
7540
|
return true;
|
|
6733
7541
|
}
|
|
@@ -6738,7 +7546,7 @@ async function uninstallCursor(rootDir) {
|
|
|
6738
7546
|
await removeCursorCommands(rootDir);
|
|
6739
7547
|
}
|
|
6740
7548
|
async function uninstallCodex(rootDir) {
|
|
6741
|
-
const agentsPath =
|
|
7549
|
+
const agentsPath = path25.join(rootDir, "AGENTS.md");
|
|
6742
7550
|
try {
|
|
6743
7551
|
const content = await readFile11(agentsPath, "utf-8");
|
|
6744
7552
|
const cleaned = removeManagedBlock(content);
|
|
@@ -6753,7 +7561,7 @@ async function uninstallCodex(rootDir) {
|
|
|
6753
7561
|
await removeCodexSkills(rootDir);
|
|
6754
7562
|
}
|
|
6755
7563
|
async function uninstallCopilot(rootDir) {
|
|
6756
|
-
const instructionsPath =
|
|
7564
|
+
const instructionsPath = path25.join(
|
|
6757
7565
|
rootDir,
|
|
6758
7566
|
".github",
|
|
6759
7567
|
"copilot-instructions.md"
|
|
@@ -6772,7 +7580,7 @@ async function uninstallCopilot(rootDir) {
|
|
|
6772
7580
|
await removeCopilotPrompts(rootDir);
|
|
6773
7581
|
}
|
|
6774
7582
|
async function uninstallZcode(rootDir) {
|
|
6775
|
-
const agentsPath =
|
|
7583
|
+
const agentsPath = path25.join(rootDir, "AGENTS.md");
|
|
6776
7584
|
try {
|
|
6777
7585
|
const content = await readFile11(agentsPath, "utf-8");
|
|
6778
7586
|
const cleaned = removeManagedBlock(
|
|
@@ -6791,13 +7599,13 @@ async function uninstallZcode(rootDir) {
|
|
|
6791
7599
|
await removeZcodeSkills(rootDir);
|
|
6792
7600
|
}
|
|
6793
7601
|
async function removeFromConfig(rootDir, platform) {
|
|
6794
|
-
const configPath =
|
|
7602
|
+
const configPath = path25.join(rootDir, ".mancode", "config.json");
|
|
6795
7603
|
try {
|
|
6796
7604
|
const raw = await readFile11(configPath, "utf-8");
|
|
6797
7605
|
const config = JSON.parse(raw);
|
|
6798
7606
|
if (Array.isArray(config.platforms)) {
|
|
6799
7607
|
config.platforms = config.platforms.filter((p) => p !== platform);
|
|
6800
|
-
if (
|
|
7608
|
+
if (isRecord10(config.platformOptions)) {
|
|
6801
7609
|
const platformOptions = Object.fromEntries(
|
|
6802
7610
|
Object.entries(config.platformOptions).filter(
|
|
6803
7611
|
([name]) => name !== platform
|
|
@@ -6815,10 +7623,10 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
6815
7623
|
} catch {
|
|
6816
7624
|
}
|
|
6817
7625
|
}
|
|
6818
|
-
function
|
|
7626
|
+
function isRecord10(value) {
|
|
6819
7627
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6820
7628
|
}
|
|
6821
|
-
async function
|
|
7629
|
+
async function pathExists11(p) {
|
|
6822
7630
|
try {
|
|
6823
7631
|
await access5(p);
|
|
6824
7632
|
return true;
|
|
@@ -6828,10 +7636,10 @@ async function pathExists10(p) {
|
|
|
6828
7636
|
}
|
|
6829
7637
|
|
|
6830
7638
|
// src/commands/version.ts
|
|
6831
|
-
import
|
|
7639
|
+
import process12 from "process";
|
|
6832
7640
|
function version() {
|
|
6833
|
-
const nodeVersion =
|
|
6834
|
-
const platform = `${
|
|
7641
|
+
const nodeVersion = process12.version;
|
|
7642
|
+
const platform = `${process12.platform}/${process12.arch}`;
|
|
6835
7643
|
console.log(`mancode/${VERSION}`);
|
|
6836
7644
|
console.log(`node/${nodeVersion.replace("v", "")}`);
|
|
6837
7645
|
console.log(platform);
|
|
@@ -6839,19 +7647,19 @@ function version() {
|
|
|
6839
7647
|
|
|
6840
7648
|
// src/commands/workflow.ts
|
|
6841
7649
|
import { access as access6 } from "fs/promises";
|
|
6842
|
-
import
|
|
6843
|
-
var
|
|
6844
|
-
var
|
|
7650
|
+
import path26 from "path";
|
|
7651
|
+
var EXIT_OK9 = 0;
|
|
7652
|
+
var EXIT_NOT_INITIALIZED7 = 1;
|
|
6845
7653
|
var EXIT_INVALID_ARG2 = 2;
|
|
6846
7654
|
async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
6847
|
-
if (!await
|
|
7655
|
+
if (!await pathExists12(path26.join(rootDir, ".mancode", "state.json"))) {
|
|
6848
7656
|
if (options.json) {
|
|
6849
7657
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
6850
7658
|
} else {
|
|
6851
7659
|
console.error("\u2717 mancode not initialized.");
|
|
6852
7660
|
console.error(" Run `mancode init` to get started.");
|
|
6853
7661
|
}
|
|
6854
|
-
return
|
|
7662
|
+
return EXIT_NOT_INITIALIZED7;
|
|
6855
7663
|
}
|
|
6856
7664
|
switch (subcommand) {
|
|
6857
7665
|
case "create":
|
|
@@ -6900,7 +7708,7 @@ async function workflowReview(rootDir, args, options) {
|
|
|
6900
7708
|
if (!ledger)
|
|
6901
7709
|
return invalidArg(options, `review not initialized: ${taskId}`);
|
|
6902
7710
|
outputReviewLedger(ledger, options);
|
|
6903
|
-
return
|
|
7711
|
+
return EXIT_OK9;
|
|
6904
7712
|
}
|
|
6905
7713
|
if (meta.status !== "in_progress" || meta.currentStep < 6) {
|
|
6906
7714
|
return invalidArg(
|
|
@@ -6957,7 +7765,7 @@ async function workflowReview(rootDir, args, options) {
|
|
|
6957
7765
|
);
|
|
6958
7766
|
}
|
|
6959
7767
|
outputReviewLedger(ledger, options);
|
|
6960
|
-
return
|
|
7768
|
+
return EXIT_OK9;
|
|
6961
7769
|
} catch (error) {
|
|
6962
7770
|
return invalidArg(
|
|
6963
7771
|
options,
|
|
@@ -7013,7 +7821,7 @@ async function workflowCreate(rootDir, args, options) {
|
|
|
7013
7821
|
error instanceof Error ? error.message : "unable to create workflow"
|
|
7014
7822
|
);
|
|
7015
7823
|
}
|
|
7016
|
-
return
|
|
7824
|
+
return EXIT_OK9;
|
|
7017
7825
|
}
|
|
7018
7826
|
async function workflowUpdate(rootDir, taskId, options) {
|
|
7019
7827
|
if (!taskId) {
|
|
@@ -7111,18 +7919,18 @@ async function workflowUpdate(rootDir, taskId, options) {
|
|
|
7111
7919
|
} else {
|
|
7112
7920
|
console.log(`Updated workflow: ${taskId}`);
|
|
7113
7921
|
}
|
|
7114
|
-
return
|
|
7922
|
+
return EXIT_OK9;
|
|
7115
7923
|
}
|
|
7116
7924
|
async function workflowList(rootDir, options) {
|
|
7117
7925
|
const workflows = await listWorkflows(rootDir);
|
|
7118
7926
|
const views = attachActiveChildren(workflows);
|
|
7119
7927
|
if (options.json) {
|
|
7120
7928
|
console.log(JSON.stringify(views, null, 2));
|
|
7121
|
-
return
|
|
7929
|
+
return EXIT_OK9;
|
|
7122
7930
|
}
|
|
7123
7931
|
if (workflows.length === 0) {
|
|
7124
7932
|
console.log("No workflows.");
|
|
7125
|
-
return
|
|
7933
|
+
return EXIT_OK9;
|
|
7126
7934
|
}
|
|
7127
7935
|
const inProgress = workflows.filter((w) => w.status === "in_progress").length;
|
|
7128
7936
|
console.log(
|
|
@@ -7132,7 +7940,7 @@ async function workflowList(rootDir, options) {
|
|
|
7132
7940
|
for (const meta of views) {
|
|
7133
7941
|
console.log(formatWorkflowRow(meta));
|
|
7134
7942
|
}
|
|
7135
|
-
return
|
|
7943
|
+
return EXIT_OK9;
|
|
7136
7944
|
}
|
|
7137
7945
|
async function workflowShow(rootDir, taskId, options) {
|
|
7138
7946
|
if (!taskId) {
|
|
@@ -7169,7 +7977,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7169
7977
|
2
|
|
7170
7978
|
)
|
|
7171
7979
|
);
|
|
7172
|
-
return
|
|
7980
|
+
return EXIT_OK9;
|
|
7173
7981
|
}
|
|
7174
7982
|
console.log(`Workflow: ${meta.taskId}`);
|
|
7175
7983
|
console.log(`Task: ${meta.task}`);
|
|
@@ -7196,7 +8004,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7196
8004
|
}
|
|
7197
8005
|
}
|
|
7198
8006
|
}
|
|
7199
|
-
return
|
|
8007
|
+
return EXIT_OK9;
|
|
7200
8008
|
}
|
|
7201
8009
|
async function workflowClean(rootDir, options) {
|
|
7202
8010
|
const workflows = await listWorkflows(rootDir);
|
|
@@ -7264,7 +8072,7 @@ async function workflowClean(rootDir, options) {
|
|
|
7264
8072
|
} else {
|
|
7265
8073
|
console.log(`Removed ${removed.length} workflow(s).`);
|
|
7266
8074
|
}
|
|
7267
|
-
return
|
|
8075
|
+
return EXIT_OK9;
|
|
7268
8076
|
}
|
|
7269
8077
|
function formatWorkflowRow(meta) {
|
|
7270
8078
|
const stepMax = maxWorkflowStep(meta.mode);
|
|
@@ -7323,7 +8131,7 @@ function ago(iso) {
|
|
|
7323
8131
|
if (hours < 24) return `${hours}h ago`;
|
|
7324
8132
|
return `${Math.floor(hours / 24)}d ago`;
|
|
7325
8133
|
}
|
|
7326
|
-
async function
|
|
8134
|
+
async function pathExists12(p) {
|
|
7327
8135
|
try {
|
|
7328
8136
|
await access6(p);
|
|
7329
8137
|
return true;
|
|
@@ -7336,8 +8144,11 @@ async function pathExists11(p) {
|
|
|
7336
8144
|
program.name("mancode").description(
|
|
7337
8145
|
"AI coding agent harness. Modes: solo, man, mamba, manteam, manps."
|
|
7338
8146
|
).version(VERSION);
|
|
7339
|
-
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 <
|
|
7340
|
-
const code = await init(process.cwd(),
|
|
8147
|
+
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) => {
|
|
8148
|
+
const code = await init(process.cwd(), {
|
|
8149
|
+
...options,
|
|
8150
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
8151
|
+
});
|
|
7341
8152
|
process.exitCode = code;
|
|
7342
8153
|
});
|
|
7343
8154
|
program.command("install [platform]").description(
|
|
@@ -7383,6 +8194,12 @@ program.command("refresh-style").description("Refresh project profile and rescan
|
|
|
7383
8194
|
const code = await refreshStyle(process.cwd());
|
|
7384
8195
|
process.exitCode = code;
|
|
7385
8196
|
});
|
|
8197
|
+
program.command("refresh-project").description(
|
|
8198
|
+
"Refresh detected project facts after adding Git or project files"
|
|
8199
|
+
).action(async () => {
|
|
8200
|
+
const code = await refreshProject(process.cwd());
|
|
8201
|
+
process.exitCode = code;
|
|
8202
|
+
});
|
|
7386
8203
|
program.command("version").description("Show version, node version, and platform").action(() => {
|
|
7387
8204
|
version();
|
|
7388
8205
|
});
|