mancode 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/README.zh-CN.md +18 -6
- package/dist/cli.js +1365 -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,122 @@ async function detectSystemDeps(env = process3.env) {
|
|
|
2963
2967
|
}
|
|
2964
2968
|
}
|
|
2965
2969
|
|
|
2970
|
+
// src/system/init-onboarding.ts
|
|
2971
|
+
import { promises as fs } from "fs";
|
|
2972
|
+
import path11 from "path";
|
|
2973
|
+
import process4 from "process";
|
|
2974
|
+
import { stdin, stdout } from "process";
|
|
2975
|
+
import { createInterface } from "readline/promises";
|
|
2976
|
+
var ALL_PLATFORMS = Object.keys(PLATFORM_INSTALLERS);
|
|
2977
|
+
function detectInitLocale(override, environment = process4.env, systemLocale = Intl.DateTimeFormat().resolvedOptions().locale) {
|
|
2978
|
+
if (override) return parseLocale(override);
|
|
2979
|
+
const environmentLocale = environment.LC_ALL ?? environment.LC_MESSAGES ?? environment.LANG;
|
|
2980
|
+
return parseLocale(environmentLocale) ?? parseLocale(systemLocale) ?? "en";
|
|
2981
|
+
}
|
|
2982
|
+
function parseLocale(value) {
|
|
2983
|
+
if (!value) return null;
|
|
2984
|
+
const normalized = value.toLowerCase().replace("_", "-");
|
|
2985
|
+
if (normalized === "zh" || normalized.startsWith("zh-")) return "zh-CN";
|
|
2986
|
+
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
|
2987
|
+
return null;
|
|
2988
|
+
}
|
|
2989
|
+
function parsePlatformSelection(value) {
|
|
2990
|
+
const normalized = value.trim().toLowerCase();
|
|
2991
|
+
if (normalized === "all" || normalized === "\u5168\u90E8") return [...ALL_PLATFORMS];
|
|
2992
|
+
const choices = normalized.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2993
|
+
if (choices.length === 0) return null;
|
|
2994
|
+
if (!choices.every((item) => item in PLATFORM_INSTALLERS)) {
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
return [...new Set(choices)];
|
|
2998
|
+
}
|
|
2999
|
+
async function detectPlatformHints(rootDir, environment = process4.env) {
|
|
3000
|
+
const hints = /* @__PURE__ */ new Set();
|
|
3001
|
+
if (environment.CLAUDECODE || environment.CLAUDE_CODE)
|
|
3002
|
+
hints.add("claude-code");
|
|
3003
|
+
if (environment.CODEX_HOME) hints.add("codex");
|
|
3004
|
+
if (environment.CURSOR_TRACE_ID) hints.add("cursor");
|
|
3005
|
+
if (environment.COPILOT_AGENT || environment.GITHUB_COPILOT)
|
|
3006
|
+
hints.add("copilot");
|
|
3007
|
+
const exists = async (relative) => {
|
|
3008
|
+
try {
|
|
3009
|
+
await fs.access(path11.join(rootDir, relative));
|
|
3010
|
+
return true;
|
|
3011
|
+
} catch {
|
|
3012
|
+
return false;
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
if (await exists(".claude")) hints.add("claude-code");
|
|
3016
|
+
if (await exists(".cursor")) hints.add("cursor");
|
|
3017
|
+
if (await exists(".github/copilot-instructions.md")) hints.add("copilot");
|
|
3018
|
+
return ALL_PLATFORMS.filter((platform) => hints.has(platform));
|
|
3019
|
+
}
|
|
3020
|
+
function createTerminalPrompter() {
|
|
3021
|
+
return {
|
|
3022
|
+
async confirmGenericProject({ rootDir, locale }) {
|
|
3023
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
3024
|
+
try {
|
|
3025
|
+
if (locale === "zh-CN") {
|
|
3026
|
+
console.log("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\u3002");
|
|
3027
|
+
console.log(`\u76EE\u5F55\uFF1A${rootDir}`);
|
|
3028
|
+
console.log("\u8FD9\u662F\u4E00\u4E2A\u65B0\u9879\u76EE\u5417\uFF1F");
|
|
3029
|
+
console.log("[y] \u521D\u59CB\u5316\u4E3A\u901A\u7528\u9879\u76EE");
|
|
3030
|
+
console.log("[n] \u9000\u51FA");
|
|
3031
|
+
const answer2 = await rl.question("\u8BF7\u9009\u62E9 [y/N]: ");
|
|
3032
|
+
return ["y", "yes", "\u662F"].includes(answer2.trim().toLowerCase());
|
|
3033
|
+
}
|
|
3034
|
+
console.log("No project files were detected in the current directory.");
|
|
3035
|
+
console.log(`Directory: ${rootDir}`);
|
|
3036
|
+
console.log("Is this a new project?");
|
|
3037
|
+
console.log("[y] Initialize as a generic project");
|
|
3038
|
+
console.log("[n] Exit");
|
|
3039
|
+
const answer = await rl.question("Choose [y/N]: ");
|
|
3040
|
+
return ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
3041
|
+
} finally {
|
|
3042
|
+
rl.close();
|
|
3043
|
+
}
|
|
3044
|
+
},
|
|
3045
|
+
async selectPlatforms({ locale, detected }) {
|
|
3046
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
3047
|
+
try {
|
|
3048
|
+
const names = ALL_PLATFORMS.map((platform, index) => {
|
|
3049
|
+
const suffix = detected.includes(platform) ? locale === "zh-CN" ? "\uFF08\u5DF2\u68C0\u6D4B\u5230\uFF09" : " (detected)" : "";
|
|
3050
|
+
return `${index + 1}. ${PLATFORM_INSTALLERS[platform].displayName}${suffix}`;
|
|
3051
|
+
});
|
|
3052
|
+
console.log(
|
|
3053
|
+
locale === "zh-CN" ? "\n\u9009\u62E9\u8981\u521D\u59CB\u5316\u7684\u5E73\u53F0\uFF1A" : "\nChoose platforms to initialize:"
|
|
3054
|
+
);
|
|
3055
|
+
console.log(names.join("\n"));
|
|
3056
|
+
console.log(locale === "zh-CN" ? "a. \u5168\u90E8\u5E73\u53F0" : "a. All platforms");
|
|
3057
|
+
console.log(
|
|
3058
|
+
locale === "zh-CN" ? "\u53EF\u8F93\u5165\u7F16\u53F7\uFF08\u4F8B\u5982 1,3\uFF09\u6216 a\u3002" : "Enter numbers (for example 1,3) or a."
|
|
3059
|
+
);
|
|
3060
|
+
const answer = (await rl.question(locale === "zh-CN" ? "\u9009\u62E9: " : "Selection: ")).trim().toLowerCase();
|
|
3061
|
+
if (answer === "a" || answer === "all" || answer === "\u5168\u90E8")
|
|
3062
|
+
return [...ALL_PLATFORMS];
|
|
3063
|
+
const indexes = answer.split(",").map((item) => Number(item.trim()));
|
|
3064
|
+
if (!indexes.length || indexes.some(
|
|
3065
|
+
(index) => !Number.isInteger(index) || index < 1 || index > ALL_PLATFORMS.length
|
|
3066
|
+
)) {
|
|
3067
|
+
return null;
|
|
3068
|
+
}
|
|
3069
|
+
const selected = [];
|
|
3070
|
+
for (const index of indexes) {
|
|
3071
|
+
const platform = ALL_PLATFORMS[index - 1];
|
|
3072
|
+
if (!platform) return null;
|
|
3073
|
+
selected.push(platform);
|
|
3074
|
+
}
|
|
3075
|
+
return [...new Set(selected)];
|
|
3076
|
+
} finally {
|
|
3077
|
+
rl.close();
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
|
|
2966
3083
|
// src/system/project-profile.ts
|
|
2967
3084
|
import { readFile as readFile7, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
2968
|
-
import
|
|
3085
|
+
import path12 from "path";
|
|
2969
3086
|
var PROJECT_MANIFESTS = [
|
|
2970
3087
|
"package.json",
|
|
2971
3088
|
"pyproject.toml",
|
|
@@ -2998,12 +3115,12 @@ async function detectProjectProfile(projectRoot) {
|
|
|
2998
3115
|
"data",
|
|
2999
3116
|
"notebooks"
|
|
3000
3117
|
]);
|
|
3001
|
-
const packageJson = await readJson2(
|
|
3118
|
+
const packageJson = await readJson2(path12.join(projectRoot, "package.json"));
|
|
3002
3119
|
const [pubspec, pythonManifests] = await Promise.all([
|
|
3003
|
-
readText(
|
|
3120
|
+
readText(path12.join(projectRoot, "pubspec.yaml")),
|
|
3004
3121
|
Promise.all([
|
|
3005
|
-
readText(
|
|
3006
|
-
readText(
|
|
3122
|
+
readText(path12.join(projectRoot, "pyproject.toml")),
|
|
3123
|
+
readText(path12.join(projectRoot, "requirements.txt"))
|
|
3007
3124
|
]).then((parts) => parts.filter(Boolean).join("\n"))
|
|
3008
3125
|
]);
|
|
3009
3126
|
const deps = Object.keys({
|
|
@@ -3012,7 +3129,7 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3012
3129
|
...packageJson?.peerDependencies
|
|
3013
3130
|
});
|
|
3014
3131
|
const flutter = isFlutterProject(pubspec);
|
|
3015
|
-
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(
|
|
3132
|
+
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path12.join(projectRoot, "src", "components", "ui"));
|
|
3016
3133
|
const languages = inferLanguages(manifests, entries);
|
|
3017
3134
|
const frameworks = inferFrameworks(
|
|
3018
3135
|
deps,
|
|
@@ -3055,7 +3172,7 @@ async function existingDirs(root, candidates) {
|
|
|
3055
3172
|
const found = [];
|
|
3056
3173
|
for (const candidate of candidates) {
|
|
3057
3174
|
try {
|
|
3058
|
-
if ((await stat2(
|
|
3175
|
+
if ((await stat2(path12.join(root, candidate))).isDirectory())
|
|
3059
3176
|
found.push(candidate);
|
|
3060
3177
|
} catch {
|
|
3061
3178
|
}
|
|
@@ -3229,8 +3346,8 @@ function isFlutterProject(pubspec) {
|
|
|
3229
3346
|
}
|
|
3230
3347
|
|
|
3231
3348
|
// src/system/scan-aesthetics.ts
|
|
3232
|
-
import { promises as
|
|
3233
|
-
import
|
|
3349
|
+
import { promises as fs2 } from "fs";
|
|
3350
|
+
import path13 from "path";
|
|
3234
3351
|
var MAX_COMPONENT_SCAN_DEPTH = 12;
|
|
3235
3352
|
var MAX_COMPONENT_FILES = 2e3;
|
|
3236
3353
|
async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
@@ -3243,7 +3360,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3243
3360
|
const cssScan = await scanCssVariables(projectRoot);
|
|
3244
3361
|
if (configResult) {
|
|
3245
3362
|
sourceFiles.push(configResult.relPath);
|
|
3246
|
-
const content = await
|
|
3363
|
+
const content = await fs2.readFile(configResult.absPath, "utf-8");
|
|
3247
3364
|
const inspectableContent = stripJsComments(content);
|
|
3248
3365
|
const themeBlock = findKeyBlock(inspectableContent, "theme");
|
|
3249
3366
|
if (themeBlock) {
|
|
@@ -3266,7 +3383,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3266
3383
|
} else if (await hasTailwindDep(projectRoot) || uiLibrary) {
|
|
3267
3384
|
matchLevel = "low";
|
|
3268
3385
|
}
|
|
3269
|
-
if (uiLibrary && await pathExists2(
|
|
3386
|
+
if (uiLibrary && await pathExists2(path13.join(projectRoot, "package.json"))) {
|
|
3270
3387
|
sourceFiles.push("package.json");
|
|
3271
3388
|
}
|
|
3272
3389
|
sourceFiles.push(...cssScan.sourceFiles);
|
|
@@ -3291,7 +3408,7 @@ async function findTailwindConfig(projectRoot) {
|
|
|
3291
3408
|
"tailwind.config.mjs"
|
|
3292
3409
|
];
|
|
3293
3410
|
for (const name of candidates) {
|
|
3294
|
-
const absPath =
|
|
3411
|
+
const absPath = path13.join(projectRoot, name);
|
|
3295
3412
|
if (await pathExists2(absPath)) {
|
|
3296
3413
|
return { absPath, relPath: name };
|
|
3297
3414
|
}
|
|
@@ -3518,7 +3635,7 @@ async function scanComponents(projectRoot) {
|
|
|
3518
3635
|
const names = /* @__PURE__ */ new Set();
|
|
3519
3636
|
let visitedFiles = 0;
|
|
3520
3637
|
for (const relRoot of roots) {
|
|
3521
|
-
const absRoot =
|
|
3638
|
+
const absRoot = path13.join(projectRoot, relRoot);
|
|
3522
3639
|
if (!await pathExists2(absRoot)) continue;
|
|
3523
3640
|
visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
|
|
3524
3641
|
if (visitedFiles >= MAX_COMPONENT_FILES) break;
|
|
@@ -3532,16 +3649,16 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3532
3649
|
let fileCount = visitedFiles;
|
|
3533
3650
|
let entries;
|
|
3534
3651
|
try {
|
|
3535
|
-
entries = await
|
|
3652
|
+
entries = await fs2.readdir(dir);
|
|
3536
3653
|
} catch {
|
|
3537
3654
|
return fileCount;
|
|
3538
3655
|
}
|
|
3539
3656
|
for (const entry of entries) {
|
|
3540
3657
|
if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
|
|
3541
|
-
const abs =
|
|
3658
|
+
const abs = path13.join(dir, entry);
|
|
3542
3659
|
let info;
|
|
3543
3660
|
try {
|
|
3544
|
-
info = await
|
|
3661
|
+
info = await fs2.lstat(abs);
|
|
3545
3662
|
} catch {
|
|
3546
3663
|
continue;
|
|
3547
3664
|
}
|
|
@@ -3559,7 +3676,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3559
3676
|
""
|
|
3560
3677
|
);
|
|
3561
3678
|
if (base === "index") {
|
|
3562
|
-
base =
|
|
3679
|
+
base = path13.basename(dir);
|
|
3563
3680
|
}
|
|
3564
3681
|
if (base.startsWith(".")) continue;
|
|
3565
3682
|
const componentName = toPascalCase(base);
|
|
@@ -3581,9 +3698,9 @@ async function scanCssVariables(projectRoot) {
|
|
|
3581
3698
|
const variables = {};
|
|
3582
3699
|
const sourceFiles = [];
|
|
3583
3700
|
for (const relPath of candidates) {
|
|
3584
|
-
const absPath =
|
|
3701
|
+
const absPath = path13.join(projectRoot, relPath);
|
|
3585
3702
|
if (!await pathExists2(absPath)) continue;
|
|
3586
|
-
const content = await
|
|
3703
|
+
const content = await fs2.readFile(absPath, "utf-8");
|
|
3587
3704
|
const found = extractCssVariables(content);
|
|
3588
3705
|
if (Object.keys(found).length === 0) continue;
|
|
3589
3706
|
Object.assign(variables, found);
|
|
@@ -3626,8 +3743,8 @@ function toPascalCase(value) {
|
|
|
3626
3743
|
}
|
|
3627
3744
|
async function hasTailwindDep(projectRoot) {
|
|
3628
3745
|
try {
|
|
3629
|
-
const raw = await
|
|
3630
|
-
|
|
3746
|
+
const raw = await fs2.readFile(
|
|
3747
|
+
path13.join(projectRoot, "package.json"),
|
|
3631
3748
|
"utf-8"
|
|
3632
3749
|
);
|
|
3633
3750
|
const pkg = JSON.parse(raw);
|
|
@@ -3643,7 +3760,7 @@ function escapeRegex(s) {
|
|
|
3643
3760
|
}
|
|
3644
3761
|
async function pathExists2(p) {
|
|
3645
3762
|
try {
|
|
3646
|
-
await
|
|
3763
|
+
await fs2.access(p);
|
|
3647
3764
|
return true;
|
|
3648
3765
|
} catch {
|
|
3649
3766
|
return false;
|
|
@@ -3654,65 +3771,104 @@ async function pathExists2(p) {
|
|
|
3654
3771
|
var EXIT_OK = 0;
|
|
3655
3772
|
var EXIT_ALREADY_INITIALIZED = 1;
|
|
3656
3773
|
var EXIT_NOT_A_PROJECT_DIR = 2;
|
|
3774
|
+
var EXIT_USER_CANCEL = 3;
|
|
3657
3775
|
var EXIT_INIT_FAILED = 5;
|
|
3658
3776
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
3659
|
-
async function init(rootDir =
|
|
3660
|
-
const mancodeDir =
|
|
3661
|
-
const stateFile =
|
|
3777
|
+
async function init(rootDir = process5.cwd(), options = {}) {
|
|
3778
|
+
const mancodeDir = path14.join(rootDir, ".mancode");
|
|
3779
|
+
const stateFile = path14.join(mancodeDir, "state.json");
|
|
3662
3780
|
const wasInitialized = await pathExists3(stateFile);
|
|
3663
|
-
let
|
|
3781
|
+
let mutationSnapshots = [];
|
|
3782
|
+
let directorySnapshots = [];
|
|
3783
|
+
const locale = detectInitLocale(options.lang);
|
|
3784
|
+
if (!locale) {
|
|
3785
|
+
console.error(`\u2717 Unsupported init language: ${options.lang}`);
|
|
3786
|
+
console.error(" Supported values: zh-CN, en");
|
|
3787
|
+
return EXIT_INIT_FAILED;
|
|
3788
|
+
}
|
|
3664
3789
|
if (wasInitialized) {
|
|
3665
3790
|
if (!options.force) {
|
|
3666
|
-
console.log(
|
|
3791
|
+
console.log(
|
|
3792
|
+
localize(
|
|
3793
|
+
locale,
|
|
3794
|
+
"\u2139\uFE0F mancode \u5DF2\u7ECF\u521D\u59CB\u5316\u3002",
|
|
3795
|
+
"\u2139\uFE0F mancode already initialized."
|
|
3796
|
+
)
|
|
3797
|
+
);
|
|
3667
3798
|
console.log(` ${stateFile}`);
|
|
3668
|
-
console.log(
|
|
3799
|
+
console.log(
|
|
3800
|
+
localize(
|
|
3801
|
+
locale,
|
|
3802
|
+
" \u8FD0\u884C `mancode init --force` \u91CD\u65B0\u5B89\u88C5\u3002",
|
|
3803
|
+
" Run `mancode init --force` to reinstall."
|
|
3804
|
+
)
|
|
3805
|
+
);
|
|
3669
3806
|
return EXIT_ALREADY_INITIALIZED;
|
|
3670
3807
|
}
|
|
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...");
|
|
3808
|
+
console.log(
|
|
3809
|
+
localize(
|
|
3810
|
+
locale,
|
|
3811
|
+
"\u26A0\uFE0F \u6B63\u5728\u4F7F\u7528 --force \u91CD\u65B0\u5B89\u88C5...",
|
|
3812
|
+
"\u26A0\uFE0F Reinstalling with --force..."
|
|
3813
|
+
)
|
|
3814
|
+
);
|
|
3684
3815
|
}
|
|
3685
3816
|
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
3817
|
console.error(
|
|
3701
|
-
|
|
3818
|
+
localize(
|
|
3819
|
+
locale,
|
|
3820
|
+
`\u2717 \u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${rootDir}`,
|
|
3821
|
+
`\u2717 Target directory does not exist: ${rootDir}`
|
|
3822
|
+
)
|
|
3702
3823
|
);
|
|
3703
3824
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3704
3825
|
}
|
|
3826
|
+
const isGitRepo = await pathExists3(path14.join(rootDir, ".git"));
|
|
3827
|
+
const hasManifest = await hasProjectManifest(rootDir);
|
|
3828
|
+
let isGenericProject = false;
|
|
3829
|
+
if (!isGitRepo && !hasManifest) {
|
|
3830
|
+
const genericSafety = await canInitializeGenericProject(rootDir);
|
|
3831
|
+
if (!genericSafety.ok) {
|
|
3832
|
+
printNotProjectDirectory(rootDir, locale, genericSafety.reason);
|
|
3833
|
+
return EXIT_NOT_A_PROJECT_DIR;
|
|
3834
|
+
}
|
|
3835
|
+
const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
|
|
3836
|
+
const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
|
|
3837
|
+
if (!confirmed) {
|
|
3838
|
+
if (options.interactive) {
|
|
3839
|
+
console.log(
|
|
3840
|
+
locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
|
|
3841
|
+
);
|
|
3842
|
+
return EXIT_USER_CANCEL;
|
|
3843
|
+
}
|
|
3844
|
+
printNotProjectDirectory(rootDir, locale, "empty");
|
|
3845
|
+
return EXIT_NOT_A_PROJECT_DIR;
|
|
3846
|
+
}
|
|
3847
|
+
isGenericProject = true;
|
|
3848
|
+
}
|
|
3705
3849
|
try {
|
|
3706
|
-
console.log(
|
|
3850
|
+
console.log(
|
|
3851
|
+
localize(
|
|
3852
|
+
locale,
|
|
3853
|
+
"\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...",
|
|
3854
|
+
"\u2713 Checking system dependencies..."
|
|
3855
|
+
)
|
|
3856
|
+
);
|
|
3707
3857
|
const deps = await detectSystemDeps();
|
|
3708
3858
|
if (!deps.git) {
|
|
3709
3859
|
console.log(
|
|
3710
|
-
|
|
3860
|
+
localize(
|
|
3861
|
+
locale,
|
|
3862
|
+
"\u26A0\uFE0F \u672A\u627E\u5230 Git\uFF08\u53EF\u9009\uFF09\u3002\u56E2\u961F\u81EA\u52A8\u68C0\u6D4B\u5C06\u4F7F\u7528 solo \u9ED8\u8BA4\u503C\u3002",
|
|
3863
|
+
"\u26A0\uFE0F Git not found (optional). Team auto-detection will use solo defaults."
|
|
3864
|
+
)
|
|
3711
3865
|
);
|
|
3712
3866
|
} else {
|
|
3713
3867
|
console.log(" git \u2713");
|
|
3714
3868
|
}
|
|
3715
|
-
console.log(
|
|
3869
|
+
console.log(
|
|
3870
|
+
localize(locale, "\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...", "\u2713 Detecting project type...")
|
|
3871
|
+
);
|
|
3716
3872
|
const profile = await detectProjectProfile(rootDir);
|
|
3717
3873
|
const profileStack = [...profile.languages, ...profile.frameworks];
|
|
3718
3874
|
const techStackStr = profileStack.join(" + ") || "Unknown";
|
|
@@ -3724,37 +3880,100 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3724
3880
|
console.log(` UI: ${uiLibraryStr}`);
|
|
3725
3881
|
}
|
|
3726
3882
|
} else if (hasManifest) {
|
|
3727
|
-
console.log(
|
|
3883
|
+
console.log(
|
|
3884
|
+
localize(
|
|
3885
|
+
locale,
|
|
3886
|
+
" \u5DF2\u53D1\u73B0\u9879\u76EE manifest\uFF0C\u672A\u8BC6\u522B\u5230\u6846\u67B6\u4F9D\u8D56",
|
|
3887
|
+
" Project manifest found; no known framework dependencies"
|
|
3888
|
+
)
|
|
3889
|
+
);
|
|
3728
3890
|
} else {
|
|
3729
3891
|
console.log(
|
|
3730
|
-
|
|
3892
|
+
localize(
|
|
3893
|
+
locale,
|
|
3894
|
+
" \uFF08\u672A\u53D1\u73B0\u5DF2\u8BC6\u522B\u7684 manifest\uFF0C\u9879\u76EE\u753B\u50CF\u7F6E\u4FE1\u5EA6\u8F83\u4F4E\uFF09",
|
|
3895
|
+
" (No recognized manifest found; profile confidence is low)"
|
|
3896
|
+
)
|
|
3731
3897
|
);
|
|
3732
3898
|
}
|
|
3733
3899
|
const existingPlatform = wasInitialized ? await readExistingInitPlatform(stateFile) : null;
|
|
3734
|
-
const
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3737
|
-
|
|
3900
|
+
const platformHints = await detectPlatformHints(rootDir);
|
|
3901
|
+
const selectedPlatforms = await selectInitPlatforms({
|
|
3902
|
+
option: options.platform,
|
|
3903
|
+
existingPlatform,
|
|
3904
|
+
hints: platformHints,
|
|
3905
|
+
interactive: options.interactive,
|
|
3906
|
+
prompter: options.prompter,
|
|
3907
|
+
locale,
|
|
3908
|
+
yes: options.yes
|
|
3909
|
+
});
|
|
3910
|
+
if (!selectedPlatforms) {
|
|
3911
|
+
console.error(
|
|
3912
|
+
locale === "zh-CN" ? "\u2717 \u672A\u9009\u62E9\u5E73\u53F0\u3002\u8BF7\u91CD\u65B0\u8FD0\u884C\u5E76\u9009\u62E9\u5E73\u53F0\uFF0C\u6216\u4F7F\u7528 --platform codex,cursor / --platform all\u3002" : "\u2717 No platform selected. Choose one interactively or pass --platform codex,cursor / --platform all."
|
|
3913
|
+
);
|
|
3914
|
+
return EXIT_INIT_FAILED;
|
|
3915
|
+
}
|
|
3916
|
+
if (selectedPlatforms.length === 0) {
|
|
3917
|
+
console.error("\u2717 No platform selected.");
|
|
3738
3918
|
return EXIT_INIT_FAILED;
|
|
3739
3919
|
}
|
|
3920
|
+
const firstPlatform = selectedPlatforms[0];
|
|
3921
|
+
if (!firstPlatform) {
|
|
3922
|
+
console.error("\u2717 No platform selected.");
|
|
3923
|
+
return EXIT_INIT_FAILED;
|
|
3924
|
+
}
|
|
3925
|
+
const installers = selectedPlatforms.map(getPlatformInstaller);
|
|
3926
|
+
if (installers.some((installer) => !installer)) {
|
|
3927
|
+
console.error(`\u2717 Unsupported platform: ${options.platform}`);
|
|
3928
|
+
return EXIT_INIT_FAILED;
|
|
3929
|
+
}
|
|
3930
|
+
const typedInstallers = installers.filter(
|
|
3931
|
+
(installer) => installer !== null
|
|
3932
|
+
);
|
|
3933
|
+
const onlyPlatformHint = platformHints.length === 1 ? platformHints[0] : null;
|
|
3934
|
+
const detectedPrimary = onlyPlatformHint && selectedPlatforms.includes(onlyPlatformHint) ? onlyPlatformHint : null;
|
|
3935
|
+
const primaryPlatform = existingPlatform && selectedPlatforms.includes(existingPlatform) ? existingPlatform : detectedPrimary ?? firstPlatform;
|
|
3936
|
+
const managedFiles = getInitManagedFilePaths(rootDir, selectedPlatforms);
|
|
3937
|
+
mutationSnapshots = await snapshotFiles(managedFiles);
|
|
3938
|
+
directorySnapshots = await snapshotDirectories(managedFiles, [
|
|
3939
|
+
path14.join(mancodeDir, "workflows"),
|
|
3940
|
+
path14.join(mancodeDir, "preseason-reports")
|
|
3941
|
+
]);
|
|
3740
3942
|
const team = await detectTeamStatus(rootDir);
|
|
3741
|
-
const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir,
|
|
3943
|
+
const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, primaryPlatform) : {};
|
|
3742
3944
|
const initialMinimal = existingPreferences.minimal ?? false;
|
|
3743
|
-
const
|
|
3945
|
+
const existingTeamMode = existingPreferences.forceTeamMode === true ? "on" : existingPreferences.teamMode ?? "auto";
|
|
3946
|
+
const teamModeEnabled = options.team ?? (existingTeamMode === "on" ? true : existingTeamMode === "off" ? false : team.isTeam);
|
|
3744
3947
|
if (options.team === true) {
|
|
3745
|
-
console.log(
|
|
3948
|
+
console.log(
|
|
3949
|
+
localize(
|
|
3950
|
+
locale,
|
|
3951
|
+
" \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5F00\u542F\uFF08--team\uFF09",
|
|
3952
|
+
" team: forced on (--team)"
|
|
3953
|
+
)
|
|
3954
|
+
);
|
|
3746
3955
|
} else if (options.team === false) {
|
|
3747
|
-
console.log(
|
|
3956
|
+
console.log(
|
|
3957
|
+
localize(
|
|
3958
|
+
locale,
|
|
3959
|
+
" \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5173\u95ED\uFF08--no-team\uFF09",
|
|
3960
|
+
" team: forced off (--no-team)"
|
|
3961
|
+
)
|
|
3962
|
+
);
|
|
3748
3963
|
} else if (team.isTeam) {
|
|
3749
3964
|
console.log(
|
|
3750
|
-
|
|
3965
|
+
localize(
|
|
3966
|
+
locale,
|
|
3967
|
+
` \u56E2\u961F\u6A21\u5F0F\uFF1A${team.contributors} \u4F4D\u8D21\u732E\u8005\uFF08\u53EF\u4F7F\u7528 /manteam\uFF09`,
|
|
3968
|
+
` team: ${team.contributors} contributors (/manteam available)`
|
|
3969
|
+
)
|
|
3751
3970
|
);
|
|
3752
3971
|
}
|
|
3753
3972
|
const state = {
|
|
3754
3973
|
version: VERSION,
|
|
3755
3974
|
currentMode: "solo",
|
|
3756
3975
|
lastMode: "solo",
|
|
3757
|
-
platform:
|
|
3976
|
+
platform: primaryPlatform,
|
|
3758
3977
|
initializedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3759
3978
|
techStack: techStackStr,
|
|
3760
3979
|
uiLibrary: uiLibraryStr,
|
|
@@ -3762,18 +3981,19 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3762
3981
|
currentWorkflowMode: null,
|
|
3763
3982
|
skippedSteps: [],
|
|
3764
3983
|
teamModeAutoDetected: teamModeEnabled,
|
|
3765
|
-
contributors: team.contributors
|
|
3984
|
+
contributors: team.contributors,
|
|
3985
|
+
projectMode: isGenericProject ? "generic" : "detected"
|
|
3766
3986
|
};
|
|
3767
|
-
if (
|
|
3987
|
+
if (selectedPlatforms.includes("claude-code")) {
|
|
3768
3988
|
await validateClaudeCodeSettings(rootDir);
|
|
3769
3989
|
}
|
|
3770
3990
|
await installMancodeCore(rootDir);
|
|
3771
|
-
await
|
|
3991
|
+
await fs3.mkdir(mancodeDir, { recursive: true });
|
|
3772
3992
|
const stateContent = `${JSON.stringify(state, null, 2)}
|
|
3773
3993
|
`;
|
|
3774
|
-
await
|
|
3775
|
-
await
|
|
3776
|
-
|
|
3994
|
+
await fs3.writeFile(stateFile, stateContent, "utf-8");
|
|
3995
|
+
await fs3.writeFile(
|
|
3996
|
+
path14.join(mancodeDir, "project-profile.json"),
|
|
3777
3997
|
`${JSON.stringify(profile, null, 2)}
|
|
3778
3998
|
`,
|
|
3779
3999
|
"utf-8"
|
|
@@ -3782,24 +4002,40 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3782
4002
|
mancodeDir,
|
|
3783
4003
|
{
|
|
3784
4004
|
forceTeamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true,
|
|
4005
|
+
teamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true ? "on" : options.team === false ? "off" : "auto",
|
|
3785
4006
|
defaultStyle: options.style === void 0 && wasInitialized ? void 0 : options.style ?? null,
|
|
3786
|
-
platforms:
|
|
4007
|
+
platforms: selectedPlatforms,
|
|
3787
4008
|
platformOptions: {
|
|
3788
|
-
|
|
4009
|
+
...Object.fromEntries(
|
|
4010
|
+
selectedPlatforms.map((platform) => [
|
|
4011
|
+
platform,
|
|
4012
|
+
{ minimal: initialMinimal }
|
|
4013
|
+
])
|
|
4014
|
+
)
|
|
3789
4015
|
}
|
|
3790
4016
|
},
|
|
3791
4017
|
wasInitialized
|
|
3792
4018
|
);
|
|
3793
|
-
let styleLine =
|
|
4019
|
+
let styleLine = localize(
|
|
4020
|
+
locale,
|
|
4021
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u7A7A\uFF09",
|
|
4022
|
+
" .mancode/aesthetics/ # style-tokens.json (empty)"
|
|
4023
|
+
);
|
|
3794
4024
|
if (profile.uiAssets === "detected") {
|
|
3795
|
-
console.log(
|
|
4025
|
+
console.log(
|
|
4026
|
+
localize(
|
|
4027
|
+
locale,
|
|
4028
|
+
"\u2713 \u626B\u63CF\u5BA1\u7F8E token...",
|
|
4029
|
+
"\u2713 Scanning design tokens..."
|
|
4030
|
+
)
|
|
4031
|
+
);
|
|
3796
4032
|
const tokens = await scanAesthetics(rootDir, uiLibrary);
|
|
3797
|
-
const tokensPath =
|
|
4033
|
+
const tokensPath = path14.join(
|
|
3798
4034
|
mancodeDir,
|
|
3799
4035
|
"aesthetics",
|
|
3800
4036
|
"style-tokens.json"
|
|
3801
4037
|
);
|
|
3802
|
-
await
|
|
4038
|
+
await fs3.writeFile(
|
|
3803
4039
|
tokensPath,
|
|
3804
4040
|
`${JSON.stringify(tokens, null, 2)}
|
|
3805
4041
|
`,
|
|
@@ -3809,70 +4045,146 @@ async function init(rootDir = process4.cwd(), options = {}) {
|
|
|
3809
4045
|
const colorCount = Object.keys(tokens.colors).length;
|
|
3810
4046
|
const fontCount = Object.keys(tokens.fonts).length;
|
|
3811
4047
|
console.log(
|
|
3812
|
-
|
|
4048
|
+
localize(
|
|
4049
|
+
locale,
|
|
4050
|
+
` ${colorCount} \u4E2A\u989C\u8272\uFF0C${fontCount} \u4E2A\u5B57\u4F53\uFF08\u5339\u914D\u5EA6\uFF1A\u9AD8\uFF09`,
|
|
4051
|
+
` ${colorCount} colors, ${fontCount} fonts (match: high)`
|
|
4052
|
+
)
|
|
4053
|
+
);
|
|
4054
|
+
styleLine = localize(
|
|
4055
|
+
locale,
|
|
4056
|
+
` .mancode/aesthetics/ # style-tokens.json\uFF08${colorCount} \u4E2A\u989C\u8272\uFF09`,
|
|
4057
|
+
` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`
|
|
3813
4058
|
);
|
|
3814
|
-
styleLine = ` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`;
|
|
3815
4059
|
} else if (tokens.matchLevel === "low") {
|
|
3816
4060
|
console.log(
|
|
3817
|
-
|
|
4061
|
+
localize(
|
|
4062
|
+
locale,
|
|
4063
|
+
" \u5DF2\u68C0\u6D4B\u5230 UI \u8D44\u6E90\uFF0C\u672A\u627E\u5230\u53EF\u590D\u7528 token\uFF08\u5339\u914D\u5EA6\uFF1A\u4F4E\uFF09",
|
|
4064
|
+
" UI assets detected; no reusable tokens found (match: low)"
|
|
4065
|
+
)
|
|
4066
|
+
);
|
|
4067
|
+
styleLine = localize(
|
|
4068
|
+
locale,
|
|
4069
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u4F4E\u5339\u914D\uFF09",
|
|
4070
|
+
" .mancode/aesthetics/ # style-tokens.json (low match)"
|
|
3818
4071
|
);
|
|
3819
|
-
styleLine = " .mancode/aesthetics/ # style-tokens.json (low match)";
|
|
3820
4072
|
} else {
|
|
3821
|
-
console.log(
|
|
3822
|
-
|
|
4073
|
+
console.log(
|
|
4074
|
+
localize(
|
|
4075
|
+
locale,
|
|
4076
|
+
" \u672A\u627E\u5230\u8BBE\u8BA1 token\uFF08\u65E0\u5339\u914D\uFF09",
|
|
4077
|
+
" No design tokens found (match: none)"
|
|
4078
|
+
)
|
|
4079
|
+
);
|
|
4080
|
+
styleLine = localize(
|
|
4081
|
+
locale,
|
|
4082
|
+
" .mancode/aesthetics/ # style-tokens.json\uFF08\u65E0 token\uFF09",
|
|
4083
|
+
" .mancode/aesthetics/ # style-tokens.json (no tokens)"
|
|
4084
|
+
);
|
|
3823
4085
|
}
|
|
3824
4086
|
}
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
4087
|
+
for (const installer of typedInstallers) {
|
|
4088
|
+
console.log(
|
|
4089
|
+
localize(
|
|
4090
|
+
locale,
|
|
4091
|
+
`\u2713 \u5B89\u88C5 ${installer.displayName} \u9002\u914D\u5668...`,
|
|
4092
|
+
`\u2713 Installing ${installer.displayName} adapter...`
|
|
4093
|
+
)
|
|
4094
|
+
);
|
|
4095
|
+
await installer.install(rootDir, {
|
|
4096
|
+
techStack: profileStack,
|
|
4097
|
+
uiLibrary,
|
|
4098
|
+
projectProfile: profile,
|
|
4099
|
+
minimal: initialMinimal,
|
|
4100
|
+
force: options.force
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
3833
4103
|
console.log("");
|
|
3834
|
-
console.log(
|
|
4104
|
+
console.log(
|
|
4105
|
+
localize(locale, "\u2713 mancode \u521D\u59CB\u5316\u5B8C\u6210\u3002", "\u2713 mancode initialized.")
|
|
4106
|
+
);
|
|
3835
4107
|
console.log("");
|
|
3836
|
-
console.log("Created:");
|
|
3837
|
-
console.log(
|
|
3838
|
-
|
|
3839
|
-
|
|
4108
|
+
console.log(localize(locale, "\u5DF2\u521B\u5EFA\uFF1A", "Created:"));
|
|
4109
|
+
console.log(
|
|
4110
|
+
localize(
|
|
4111
|
+
locale,
|
|
4112
|
+
" .mancode/state.json # \u9879\u76EE\u72B6\u6001",
|
|
4113
|
+
" .mancode/state.json # project state"
|
|
4114
|
+
)
|
|
4115
|
+
);
|
|
3840
4116
|
console.log(
|
|
3841
|
-
|
|
4117
|
+
localize(
|
|
4118
|
+
locale,
|
|
4119
|
+
" .mancode/project-profile.json # \u68C0\u6D4B\u5230\u7684\u9879\u76EE\u4E8B\u5B9E",
|
|
4120
|
+
" .mancode/project-profile.json # detected project facts"
|
|
4121
|
+
)
|
|
4122
|
+
);
|
|
4123
|
+
console.log(
|
|
4124
|
+
localize(
|
|
4125
|
+
locale,
|
|
4126
|
+
" .mancode/config.json # \u914D\u7F6E",
|
|
4127
|
+
" .mancode/config.json # configuration"
|
|
4128
|
+
)
|
|
4129
|
+
);
|
|
4130
|
+
console.log(
|
|
4131
|
+
localize(
|
|
4132
|
+
locale,
|
|
4133
|
+
" .mancode/hooks/ # SessionStart + UserPromptSubmit",
|
|
4134
|
+
" .mancode/hooks/ # SessionStart + UserPromptSubmit"
|
|
4135
|
+
)
|
|
3842
4136
|
);
|
|
3843
4137
|
console.log(styleLine);
|
|
3844
4138
|
console.log(" .mancode/logs/ # hooks.log");
|
|
3845
|
-
|
|
4139
|
+
for (const platform of selectedPlatforms)
|
|
4140
|
+
printPlatformCreatedFiles(platform, locale);
|
|
3846
4141
|
console.log("");
|
|
3847
|
-
console.log("Next:");
|
|
3848
|
-
console.log(
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4142
|
+
console.log(localize(locale, "\u4E0B\u4E00\u6B65\uFF1A", "Next:"));
|
|
4143
|
+
console.log(
|
|
4144
|
+
localize(
|
|
4145
|
+
locale,
|
|
4146
|
+
" mancode status # \u663E\u793A\u9879\u76EE\u72B6\u6001",
|
|
4147
|
+
" mancode status # Show project state"
|
|
4148
|
+
)
|
|
4149
|
+
);
|
|
4150
|
+
if (selectedPlatforms.includes("claude-code")) {
|
|
3852
4151
|
console.log(
|
|
3853
|
-
|
|
4152
|
+
localize(
|
|
4153
|
+
locale,
|
|
4154
|
+
" \uFF08\u91CD\u542F Claude Code \u4EE5\u52A0\u8F7D hooks\uFF09",
|
|
4155
|
+
" (Restart Claude Code to load hooks)"
|
|
4156
|
+
)
|
|
4157
|
+
);
|
|
4158
|
+
}
|
|
4159
|
+
if (selectedPlatforms.includes("codex")) {
|
|
4160
|
+
console.log(
|
|
4161
|
+
localize(
|
|
4162
|
+
locale,
|
|
4163
|
+
" \uFF08\u5982\u679C skills \u672A\u51FA\u73B0\uFF0C\u8BF7\u91CD\u542F ChatGPT \u684C\u9762\u5E94\u7528\u6216 Codex \u4F1A\u8BDD\uFF09",
|
|
4164
|
+
" (If skills do not appear, restart the ChatGPT desktop app or Codex session)"
|
|
4165
|
+
)
|
|
3854
4166
|
);
|
|
3855
4167
|
}
|
|
3856
4168
|
return EXIT_OK;
|
|
3857
4169
|
} 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
|
-
}
|
|
4170
|
+
await restoreFiles(mutationSnapshots);
|
|
4171
|
+
await removeNewEmptyDirectories(directorySnapshots);
|
|
3866
4172
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3867
|
-
console.error(
|
|
4173
|
+
console.error(
|
|
4174
|
+
localize(
|
|
4175
|
+
locale,
|
|
4176
|
+
`\u2717 mancode \u521D\u59CB\u5316\u5931\u8D25\uFF1A${msg}`,
|
|
4177
|
+
`\u2717 mancode init failed: ${msg}`
|
|
4178
|
+
)
|
|
4179
|
+
);
|
|
3868
4180
|
return EXIT_INIT_FAILED;
|
|
3869
4181
|
}
|
|
3870
4182
|
}
|
|
3871
4183
|
async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms) {
|
|
3872
|
-
const configPath =
|
|
4184
|
+
const configPath = path14.join(mancodeDir, "config.json");
|
|
3873
4185
|
let config = {};
|
|
3874
4186
|
try {
|
|
3875
|
-
config = JSON.parse(await
|
|
4187
|
+
config = JSON.parse(await fs3.readFile(configPath, "utf-8"));
|
|
3876
4188
|
} catch {
|
|
3877
4189
|
}
|
|
3878
4190
|
const existingPlatforms = Array.isArray(config.platforms) ? config.platforms.filter(
|
|
@@ -3892,7 +4204,7 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
3892
4204
|
...patch.platformOptions
|
|
3893
4205
|
}
|
|
3894
4206
|
} : definedPatch;
|
|
3895
|
-
await
|
|
4207
|
+
await fs3.writeFile(
|
|
3896
4208
|
configPath,
|
|
3897
4209
|
`${JSON.stringify({ ...config, ...mergedPatch }, null, 2)}
|
|
3898
4210
|
`,
|
|
@@ -3902,36 +4214,182 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
3902
4214
|
async function readExistingInitPreferences(mancodeDir, platform) {
|
|
3903
4215
|
try {
|
|
3904
4216
|
const config = JSON.parse(
|
|
3905
|
-
await
|
|
4217
|
+
await fs3.readFile(path14.join(mancodeDir, "config.json"), "utf-8")
|
|
3906
4218
|
);
|
|
3907
4219
|
const preferences = {};
|
|
3908
4220
|
if (typeof config.forceTeamMode === "boolean") {
|
|
3909
4221
|
preferences.forceTeamMode = config.forceTeamMode;
|
|
3910
4222
|
}
|
|
3911
|
-
if (
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4223
|
+
if (config.teamMode === "auto" || config.teamMode === "on" || config.teamMode === "off") {
|
|
4224
|
+
preferences.teamMode = config.teamMode;
|
|
4225
|
+
}
|
|
4226
|
+
if (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
|
|
4227
|
+
const platformOptions = config.platformOptions[platform];
|
|
4228
|
+
if (isRecord3(platformOptions) && platformOptions.minimal === true) {
|
|
4229
|
+
preferences.minimal = true;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
return preferences;
|
|
4233
|
+
} catch {
|
|
4234
|
+
return {};
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
async function readExistingInitPlatform(stateFile) {
|
|
4238
|
+
try {
|
|
4239
|
+
const state = JSON.parse(await fs3.readFile(stateFile, "utf-8"));
|
|
4240
|
+
return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
|
|
4241
|
+
} catch {
|
|
4242
|
+
return null;
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
async function selectInitPlatforms(input) {
|
|
4246
|
+
if (input.option !== void 0) {
|
|
4247
|
+
return parsePlatformSelection(input.option);
|
|
4248
|
+
}
|
|
4249
|
+
if (input.existingPlatform) return [input.existingPlatform];
|
|
4250
|
+
if (input.yes && input.interactive !== void 0) {
|
|
4251
|
+
return input.hints.length === 1 ? input.hints : null;
|
|
4252
|
+
}
|
|
4253
|
+
if (input.interactive) {
|
|
4254
|
+
const prompter = input.prompter ?? createTerminalPrompter();
|
|
4255
|
+
return prompter.selectPlatforms({
|
|
4256
|
+
locale: input.locale,
|
|
4257
|
+
detected: input.hints
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
if (input.interactive === false) {
|
|
4261
|
+
return input.hints.length === 1 ? input.hints : null;
|
|
4262
|
+
}
|
|
4263
|
+
return [DEFAULT_INIT_PLATFORM];
|
|
4264
|
+
}
|
|
4265
|
+
async function hasProjectManifest(rootDir) {
|
|
4266
|
+
for (const name of PROJECT_MANIFESTS) {
|
|
4267
|
+
if (await pathExists3(path14.join(rootDir, name))) return true;
|
|
4268
|
+
}
|
|
4269
|
+
return false;
|
|
4270
|
+
}
|
|
4271
|
+
async function canInitializeGenericProject(rootDir) {
|
|
4272
|
+
const resolved = path14.resolve(rootDir);
|
|
4273
|
+
if (resolved === path14.parse(resolved).root || resolved === path14.resolve(os.homedir())) {
|
|
4274
|
+
return { ok: false, reason: "unsafe" };
|
|
4275
|
+
}
|
|
4276
|
+
try {
|
|
4277
|
+
const entries = await fs3.readdir(resolved);
|
|
4278
|
+
const meaningfulEntries = entries.filter(
|
|
4279
|
+
(entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
|
|
4280
|
+
);
|
|
4281
|
+
return meaningfulEntries.length === 0 ? { ok: true } : { ok: false, reason: "nonempty" };
|
|
4282
|
+
} catch {
|
|
4283
|
+
return { ok: false, reason: "unsafe" };
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
function printNotProjectDirectory(rootDir, locale, reason) {
|
|
4287
|
+
if (locale === "zh-CN") {
|
|
4288
|
+
console.error(`\u2717 \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u53EF\u521D\u59CB\u5316\u7684\u9879\u76EE\u76EE\u5F55\uFF1A${rootDir}`);
|
|
4289
|
+
if (reason === "unsafe") {
|
|
4290
|
+
console.error(" \u4E3A\u907F\u514D\u8BEF\u5199\uFF0C\u4E0D\u80FD\u5728 Home \u76EE\u5F55\u6216\u78C1\u76D8\u6839\u76EE\u5F55\u521D\u59CB\u5316\u3002");
|
|
4291
|
+
} else if (reason === "nonempty") {
|
|
4292
|
+
console.error(
|
|
4293
|
+
" \u672A\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\uFF0C\u4E14\u76EE\u5F55\u4E2D\u5DF2\u6709\u6587\u4EF6\u3002\u8BF7\u5148\u8FDB\u5165\u9879\u76EE\u76EE\u5F55\u3002"
|
|
4294
|
+
);
|
|
4295
|
+
} else {
|
|
4296
|
+
console.error(
|
|
4297
|
+
" \u672A\u53D1\u73B0 .git \u6216\u9879\u76EE\u6587\u4EF6\u3002\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u53EF\u786E\u8BA4\u901A\u7528\u9879\u76EE\uFF0C\u6216\u4F7F\u7528 --empty\u3002"
|
|
4298
|
+
);
|
|
4299
|
+
}
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4302
|
+
console.error(`\u2717 Not a project directory: ${rootDir}`);
|
|
4303
|
+
if (reason === "unsafe") {
|
|
4304
|
+
console.error(
|
|
4305
|
+
" Refusing to initialize a home directory or filesystem root."
|
|
4306
|
+
);
|
|
4307
|
+
} else if (reason === "nonempty") {
|
|
4308
|
+
console.error(
|
|
4309
|
+
" No project files were detected and the directory is not empty."
|
|
4310
|
+
);
|
|
4311
|
+
} else {
|
|
4312
|
+
console.error(
|
|
4313
|
+
" No .git or recognized project manifest found. Use an interactive terminal or --empty for a new project."
|
|
4314
|
+
);
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
function getInitManagedFilePaths(rootDir, platforms) {
|
|
4318
|
+
const files = [
|
|
4319
|
+
".mancode/state.json",
|
|
4320
|
+
".mancode/config.json",
|
|
4321
|
+
".mancode/project-profile.json",
|
|
4322
|
+
".mancode/aesthetics/style-tokens.json",
|
|
4323
|
+
".mancode/hooks/session-start.mjs",
|
|
4324
|
+
".mancode/hooks/user-prompt-submit.mjs",
|
|
4325
|
+
".mancode/logs/hooks.log",
|
|
4326
|
+
".mancode/memory/prd.md",
|
|
4327
|
+
".mancode/memory/spec.md",
|
|
4328
|
+
".mancode/memory/decisions.md"
|
|
4329
|
+
];
|
|
4330
|
+
if (platforms.includes("claude-code")) {
|
|
4331
|
+
files.push(
|
|
4332
|
+
".mancode/hooks/session-start.sh",
|
|
4333
|
+
".mancode/hooks/user-prompt-submit.sh",
|
|
4334
|
+
".claude/settings.json",
|
|
4335
|
+
".claude/skills/man8/SKILL.md",
|
|
4336
|
+
".claude/skills/solo/SKILL.md",
|
|
4337
|
+
".claude/skills/mancode-solo.md",
|
|
4338
|
+
".claude/skills/mancode-man8.md"
|
|
4339
|
+
);
|
|
4340
|
+
for (const mode of MODE_NAMES) {
|
|
4341
|
+
files.push(
|
|
4342
|
+
`.claude/skills/${mode}/SKILL.md`,
|
|
4343
|
+
`.claude/skills/mancode-${mode}.md`
|
|
4344
|
+
);
|
|
4345
|
+
}
|
|
4346
|
+
for (const agent of ALL_AGENTS) {
|
|
4347
|
+
files.push(`.claude/agents/${agent.name}.md`);
|
|
3916
4348
|
}
|
|
3917
|
-
return preferences;
|
|
3918
|
-
} catch {
|
|
3919
|
-
return {};
|
|
3920
4349
|
}
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
4350
|
+
if (platforms.includes("cursor")) {
|
|
4351
|
+
files.push(".cursor/rules/mancode-man8.mdc", ".cursor/commands/man8.md");
|
|
4352
|
+
for (const fileName of MANCODE_CURSOR_RULE_FILES) {
|
|
4353
|
+
files.push(`.cursor/rules/${fileName}`);
|
|
4354
|
+
}
|
|
4355
|
+
for (const mode of MODE_NAMES) {
|
|
4356
|
+
files.push(`.cursor/commands/${mode}.md`);
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
if (platforms.includes("codex") || platforms.includes("zcode")) {
|
|
4360
|
+
files.push("AGENTS.md", ".agents/skills/man8/SKILL.md");
|
|
4361
|
+
for (const mode of MODE_NAMES) {
|
|
4362
|
+
files.push(`.agents/skills/${mode}/SKILL.md`);
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
if (platforms.includes("codex")) {
|
|
4366
|
+
files.push(".codex/skills/man8/SKILL.md");
|
|
4367
|
+
for (const mode of MODE_NAMES) {
|
|
4368
|
+
files.push(`.codex/skills/${mode}/SKILL.md`);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
if (platforms.includes("zcode")) {
|
|
4372
|
+
files.push(".zcode/skills/man8/SKILL.md");
|
|
4373
|
+
for (const mode of MODE_NAMES) {
|
|
4374
|
+
files.push(`.zcode/skills/${mode}/SKILL.md`);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
if (platforms.includes("copilot")) {
|
|
4378
|
+
files.push(
|
|
4379
|
+
".github/copilot-instructions.md",
|
|
4380
|
+
".github/prompts/man8.prompt.md"
|
|
4381
|
+
);
|
|
4382
|
+
for (const mode of MODE_NAMES) {
|
|
4383
|
+
files.push(`.github/prompts/${mode}.prompt.md`);
|
|
4384
|
+
}
|
|
3928
4385
|
}
|
|
4386
|
+
return [...new Set(files.map((file) => path14.join(rootDir, file)))];
|
|
3929
4387
|
}
|
|
3930
4388
|
async function snapshotFiles(filePaths) {
|
|
3931
4389
|
return Promise.all(
|
|
3932
4390
|
filePaths.map(async (filePath) => {
|
|
3933
4391
|
try {
|
|
3934
|
-
return { filePath, content: await
|
|
4392
|
+
return { filePath, content: await fs3.readFile(filePath, "utf-8") };
|
|
3935
4393
|
} catch (error) {
|
|
3936
4394
|
if (isNodeError3(error) && error.code === "ENOENT") {
|
|
3937
4395
|
return { filePath, content: null };
|
|
@@ -3941,14 +4399,39 @@ async function snapshotFiles(filePaths) {
|
|
|
3941
4399
|
})
|
|
3942
4400
|
);
|
|
3943
4401
|
}
|
|
4402
|
+
async function snapshotDirectories(filePaths, additionalDirectories = []) {
|
|
4403
|
+
const directories = new Set(additionalDirectories);
|
|
4404
|
+
for (const filePath of filePaths) {
|
|
4405
|
+
let current = path14.dirname(filePath);
|
|
4406
|
+
while (current !== path14.dirname(current)) {
|
|
4407
|
+
directories.add(current);
|
|
4408
|
+
current = path14.dirname(current);
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
return Promise.all(
|
|
4412
|
+
[...directories].map(async (dirPath) => ({
|
|
4413
|
+
dirPath,
|
|
4414
|
+
existed: await pathExists3(dirPath)
|
|
4415
|
+
}))
|
|
4416
|
+
);
|
|
4417
|
+
}
|
|
3944
4418
|
async function restoreFiles(snapshots) {
|
|
3945
4419
|
for (const snapshot of snapshots) {
|
|
3946
4420
|
if (snapshot.content === null) {
|
|
3947
|
-
await
|
|
4421
|
+
await fs3.rm(snapshot.filePath, { force: true });
|
|
3948
4422
|
continue;
|
|
3949
4423
|
}
|
|
3950
|
-
await
|
|
3951
|
-
await
|
|
4424
|
+
await fs3.mkdir(path14.dirname(snapshot.filePath), { recursive: true });
|
|
4425
|
+
await fs3.writeFile(snapshot.filePath, snapshot.content, "utf-8");
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
async function removeNewEmptyDirectories(snapshots) {
|
|
4429
|
+
const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
|
|
4430
|
+
for (const { dirPath } of candidates) {
|
|
4431
|
+
try {
|
|
4432
|
+
await fs3.rmdir(dirPath);
|
|
4433
|
+
} catch {
|
|
4434
|
+
}
|
|
3952
4435
|
}
|
|
3953
4436
|
}
|
|
3954
4437
|
function isRecord3(value) {
|
|
@@ -3957,10 +4440,22 @@ function isRecord3(value) {
|
|
|
3957
4440
|
function isNodeError3(error) {
|
|
3958
4441
|
return error instanceof Error && "code" in error;
|
|
3959
4442
|
}
|
|
3960
|
-
function printPlatformCreatedFiles(platform) {
|
|
4443
|
+
function printPlatformCreatedFiles(platform, locale) {
|
|
3961
4444
|
if (platform === "claude-code") {
|
|
3962
|
-
console.log(
|
|
3963
|
-
|
|
4445
|
+
console.log(
|
|
4446
|
+
localize(
|
|
4447
|
+
locale,
|
|
4448
|
+
" .claude/settings.json # hook \u6CE8\u518C",
|
|
4449
|
+
" .claude/settings.json # hook registration"
|
|
4450
|
+
)
|
|
4451
|
+
);
|
|
4452
|
+
console.log(
|
|
4453
|
+
localize(
|
|
4454
|
+
locale,
|
|
4455
|
+
" .claude/skills/ # solo + MVP-2 slash skills",
|
|
4456
|
+
" .claude/skills/ # solo + MVP-2 slash skills"
|
|
4457
|
+
)
|
|
4458
|
+
);
|
|
3964
4459
|
return;
|
|
3965
4460
|
}
|
|
3966
4461
|
if (platform === "cursor") {
|
|
@@ -3979,9 +4474,12 @@ function printPlatformCreatedFiles(platform) {
|
|
|
3979
4474
|
}
|
|
3980
4475
|
console.log(" .github/copilot-instructions.md # Copilot instructions");
|
|
3981
4476
|
}
|
|
4477
|
+
function localize(locale, chinese, english) {
|
|
4478
|
+
return locale === "zh-CN" ? chinese : english;
|
|
4479
|
+
}
|
|
3982
4480
|
async function pathExists3(p) {
|
|
3983
4481
|
try {
|
|
3984
|
-
await
|
|
4482
|
+
await fs3.access(p);
|
|
3985
4483
|
return true;
|
|
3986
4484
|
} catch {
|
|
3987
4485
|
return false;
|
|
@@ -3989,13 +4487,13 @@ async function pathExists3(p) {
|
|
|
3989
4487
|
}
|
|
3990
4488
|
|
|
3991
4489
|
// src/commands/install.ts
|
|
3992
|
-
import { promises as
|
|
3993
|
-
import
|
|
3994
|
-
import
|
|
4490
|
+
import { promises as fs5 } from "fs";
|
|
4491
|
+
import path16 from "path";
|
|
4492
|
+
import process6 from "process";
|
|
3995
4493
|
|
|
3996
4494
|
// src/installers/platform-status.ts
|
|
3997
|
-
import { promises as
|
|
3998
|
-
import
|
|
4495
|
+
import { promises as fs4 } from "fs";
|
|
4496
|
+
import path15 from "path";
|
|
3999
4497
|
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
4000
4498
|
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
4001
4499
|
return {
|
|
@@ -4009,13 +4507,13 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4009
4507
|
if (platform === "claude-code") {
|
|
4010
4508
|
const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
|
|
4011
4509
|
fileMatches(
|
|
4012
|
-
|
|
4510
|
+
path15.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
|
|
4013
4511
|
(content) => isGeneratedClaudeSkill(content, "solo")
|
|
4014
4512
|
),
|
|
4015
4513
|
claudeHooksRegistered(rootDir),
|
|
4016
4514
|
pathsExist([
|
|
4017
|
-
|
|
4018
|
-
|
|
4515
|
+
path15.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
4516
|
+
path15.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
4019
4517
|
])
|
|
4020
4518
|
]);
|
|
4021
4519
|
const present = hasSoloSkill && registered && hasHookFiles;
|
|
@@ -4031,7 +4529,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4031
4529
|
if (platform === "cursor") {
|
|
4032
4530
|
const hasCoreRules = await allManagedSkills(
|
|
4033
4531
|
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
4034
|
-
(file) =>
|
|
4532
|
+
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4035
4533
|
),
|
|
4036
4534
|
[CURSOR_RULE_MANAGED_MARKER]
|
|
4037
4535
|
);
|
|
@@ -4046,13 +4544,13 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4046
4544
|
const [hasRules, hasCommands] = await Promise.all([
|
|
4047
4545
|
allManagedSkills(
|
|
4048
4546
|
MANCODE_CURSOR_RULE_FILES.map(
|
|
4049
|
-
(file) =>
|
|
4547
|
+
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4050
4548
|
),
|
|
4051
4549
|
[CURSOR_RULE_MANAGED_MARKER]
|
|
4052
4550
|
),
|
|
4053
4551
|
allManagedSkills(
|
|
4054
4552
|
MODE_NAMES.map(
|
|
4055
|
-
(mode) =>
|
|
4553
|
+
(mode) => path15.join(rootDir, ".cursor", "commands", `${mode}.md`)
|
|
4056
4554
|
),
|
|
4057
4555
|
[MODE_FILE_MANAGED_MARKER]
|
|
4058
4556
|
)
|
|
@@ -4065,7 +4563,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4065
4563
|
};
|
|
4066
4564
|
}
|
|
4067
4565
|
if (platform === "codex") {
|
|
4068
|
-
const hasBlock2 = await fileHasManagedBlock(
|
|
4566
|
+
const hasBlock2 = await fileHasManagedBlock(path15.join(rootDir, "AGENTS.md"));
|
|
4069
4567
|
if (!hasBlock2) {
|
|
4070
4568
|
return {
|
|
4071
4569
|
present: false,
|
|
@@ -4082,9 +4580,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4082
4580
|
readyDetail: "managed block present"
|
|
4083
4581
|
};
|
|
4084
4582
|
}
|
|
4085
|
-
const skillsDir =
|
|
4583
|
+
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4086
4584
|
const hasSkills = await allManagedSkills(
|
|
4087
|
-
MODE_NAMES.map((mode) =>
|
|
4585
|
+
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4088
4586
|
MANCODE_AGENT_SKILL_MARKERS
|
|
4089
4587
|
);
|
|
4090
4588
|
return {
|
|
@@ -4096,7 +4594,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4096
4594
|
}
|
|
4097
4595
|
if (platform === "zcode") {
|
|
4098
4596
|
const hasBlock2 = await fileHasManagedBlock(
|
|
4099
|
-
|
|
4597
|
+
path15.join(rootDir, "AGENTS.md"),
|
|
4100
4598
|
ZCODE_MANCODE_START_MARKER,
|
|
4101
4599
|
ZCODE_MANCODE_END_MARKER
|
|
4102
4600
|
);
|
|
@@ -4116,9 +4614,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4116
4614
|
readyDetail: "managed block present"
|
|
4117
4615
|
};
|
|
4118
4616
|
}
|
|
4119
|
-
const skillsDir =
|
|
4617
|
+
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4120
4618
|
const hasSkills = await allManagedSkills(
|
|
4121
|
-
MODE_NAMES.map((mode) =>
|
|
4619
|
+
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4122
4620
|
MANCODE_AGENT_SKILL_MARKERS
|
|
4123
4621
|
);
|
|
4124
4622
|
return {
|
|
@@ -4129,7 +4627,7 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4129
4627
|
};
|
|
4130
4628
|
}
|
|
4131
4629
|
const hasBlock = await fileHasManagedBlock(
|
|
4132
|
-
|
|
4630
|
+
path15.join(rootDir, ".github", "copilot-instructions.md")
|
|
4133
4631
|
);
|
|
4134
4632
|
if (!hasBlock) {
|
|
4135
4633
|
return {
|
|
@@ -4140,9 +4638,9 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4140
4638
|
};
|
|
4141
4639
|
}
|
|
4142
4640
|
if (!await isPlatformMinimal(rootDir, "copilot")) {
|
|
4143
|
-
const promptsDir =
|
|
4641
|
+
const promptsDir = path15.join(rootDir, ".github", "prompts");
|
|
4144
4642
|
const hasPrompts = await allManagedSkills(
|
|
4145
|
-
MODE_NAMES.map((mode) =>
|
|
4643
|
+
MODE_NAMES.map((mode) => path15.join(promptsDir, `${mode}.prompt.md`)),
|
|
4146
4644
|
[MODE_FILE_MANAGED_MARKER]
|
|
4147
4645
|
);
|
|
4148
4646
|
return {
|
|
@@ -4175,13 +4673,13 @@ async function allManagedSkills(paths, markers) {
|
|
|
4175
4673
|
async function claudeFullContentReady(rootDir) {
|
|
4176
4674
|
const skillChecks = MVP2_SKILLS.map(
|
|
4177
4675
|
(skill) => fileMatches(
|
|
4178
|
-
|
|
4676
|
+
path15.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
|
|
4179
4677
|
(content) => isGeneratedClaudeSkill(content, skill.name)
|
|
4180
4678
|
)
|
|
4181
4679
|
);
|
|
4182
4680
|
const agentChecks = ALL_AGENTS.map(
|
|
4183
4681
|
(agent) => fileMatches(
|
|
4184
|
-
|
|
4682
|
+
path15.join(rootDir, ".claude", "agents", `${agent.name}.md`),
|
|
4185
4683
|
(content) => isGeneratedClaudeAgent(content, agent.name)
|
|
4186
4684
|
)
|
|
4187
4685
|
);
|
|
@@ -4190,7 +4688,7 @@ async function claudeFullContentReady(rootDir) {
|
|
|
4190
4688
|
}
|
|
4191
4689
|
async function fileMatches(filePath, predicate) {
|
|
4192
4690
|
try {
|
|
4193
|
-
return predicate(await
|
|
4691
|
+
return predicate(await fs4.readFile(filePath, "utf-8"));
|
|
4194
4692
|
} catch {
|
|
4195
4693
|
return false;
|
|
4196
4694
|
}
|
|
@@ -4200,7 +4698,7 @@ async function pathsExist(paths) {
|
|
|
4200
4698
|
}
|
|
4201
4699
|
async function fileHasAnyMarker(filePath, needles) {
|
|
4202
4700
|
try {
|
|
4203
|
-
const content = await
|
|
4701
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
4204
4702
|
return needles.some((needle) => content.includes(needle));
|
|
4205
4703
|
} catch {
|
|
4206
4704
|
return false;
|
|
@@ -4208,8 +4706,8 @@ async function fileHasAnyMarker(filePath, needles) {
|
|
|
4208
4706
|
}
|
|
4209
4707
|
async function isPlatformMinimal(rootDir, platform) {
|
|
4210
4708
|
try {
|
|
4211
|
-
const raw = await
|
|
4212
|
-
|
|
4709
|
+
const raw = await fs4.readFile(
|
|
4710
|
+
path15.join(rootDir, ".mancode", "config.json"),
|
|
4213
4711
|
"utf-8"
|
|
4214
4712
|
);
|
|
4215
4713
|
const config = JSON.parse(raw);
|
|
@@ -4222,7 +4720,7 @@ async function isPlatformMinimal(rootDir, platform) {
|
|
|
4222
4720
|
}
|
|
4223
4721
|
async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
4224
4722
|
try {
|
|
4225
|
-
const content = await
|
|
4723
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
4226
4724
|
return hasManagedBlock(content, startMarker, endMarker);
|
|
4227
4725
|
} catch {
|
|
4228
4726
|
return false;
|
|
@@ -4230,8 +4728,8 @@ async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START
|
|
|
4230
4728
|
}
|
|
4231
4729
|
async function claudeHooksRegistered(rootDir) {
|
|
4232
4730
|
try {
|
|
4233
|
-
const raw = await
|
|
4234
|
-
|
|
4731
|
+
const raw = await fs4.readFile(
|
|
4732
|
+
path15.join(rootDir, ".claude", "settings.json"),
|
|
4235
4733
|
"utf-8"
|
|
4236
4734
|
);
|
|
4237
4735
|
const settings = JSON.parse(raw);
|
|
@@ -4261,7 +4759,7 @@ function hasHookCommand(value, needle) {
|
|
|
4261
4759
|
}
|
|
4262
4760
|
async function pathExists4(p) {
|
|
4263
4761
|
try {
|
|
4264
|
-
await
|
|
4762
|
+
await fs4.access(p);
|
|
4265
4763
|
return true;
|
|
4266
4764
|
} catch {
|
|
4267
4765
|
return false;
|
|
@@ -4276,8 +4774,8 @@ var EXIT_OK2 = 0;
|
|
|
4276
4774
|
var EXIT_NOT_INITIALIZED = 1;
|
|
4277
4775
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|
|
4278
4776
|
var EXIT_INSTALL_FAILED = 3;
|
|
4279
|
-
async function install(rootDir =
|
|
4280
|
-
const stateFile =
|
|
4777
|
+
async function install(rootDir = process6.cwd(), platform = "claude-code", options = {}) {
|
|
4778
|
+
const stateFile = path16.join(rootDir, ".mancode", "state.json");
|
|
4281
4779
|
if (!await pathExists5(stateFile)) {
|
|
4282
4780
|
console.error("\u2717 mancode not initialized.");
|
|
4283
4781
|
console.error(" Run `mancode init` first.");
|
|
@@ -4363,9 +4861,9 @@ async function install(rootDir = process5.cwd(), platform = "claude-code", optio
|
|
|
4363
4861
|
return EXIT_OK2;
|
|
4364
4862
|
}
|
|
4365
4863
|
async function readConfig(rootDir) {
|
|
4366
|
-
const configPath =
|
|
4864
|
+
const configPath = path16.join(rootDir, ".mancode", "config.json");
|
|
4367
4865
|
try {
|
|
4368
|
-
const raw = await
|
|
4866
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
4369
4867
|
return { config: JSON.parse(raw), valid: true };
|
|
4370
4868
|
} catch (err) {
|
|
4371
4869
|
if (isNodeError4(err) && err.code === "ENOENT") {
|
|
@@ -4378,10 +4876,10 @@ function isNodeError4(err) {
|
|
|
4378
4876
|
return err instanceof Error && "code" in err;
|
|
4379
4877
|
}
|
|
4380
4878
|
async function updateConfig(rootDir, config) {
|
|
4381
|
-
const configPath =
|
|
4879
|
+
const configPath = path16.join(rootDir, ".mancode", "config.json");
|
|
4382
4880
|
const content = `${JSON.stringify(config, null, 2)}
|
|
4383
4881
|
`;
|
|
4384
|
-
await
|
|
4882
|
+
await fs5.writeFile(configPath, content, "utf-8");
|
|
4385
4883
|
}
|
|
4386
4884
|
function withDefaultConfig(config, fallbackPlatform) {
|
|
4387
4885
|
const fallback = fallbackPlatform ?? DEFAULT_CONFIG.platforms[0];
|
|
@@ -4413,8 +4911,8 @@ function readConfiguredMinimal(value, platform) {
|
|
|
4413
4911
|
}
|
|
4414
4912
|
async function readStatePlatform(rootDir) {
|
|
4415
4913
|
try {
|
|
4416
|
-
const raw = await
|
|
4417
|
-
|
|
4914
|
+
const raw = await fs5.readFile(
|
|
4915
|
+
path16.join(rootDir, ".mancode", "state.json"),
|
|
4418
4916
|
"utf-8"
|
|
4419
4917
|
);
|
|
4420
4918
|
const state = JSON.parse(raw);
|
|
@@ -4425,7 +4923,7 @@ async function readStatePlatform(rootDir) {
|
|
|
4425
4923
|
}
|
|
4426
4924
|
async function pathExists5(p) {
|
|
4427
4925
|
try {
|
|
4428
|
-
await
|
|
4926
|
+
await fs5.access(p);
|
|
4429
4927
|
return true;
|
|
4430
4928
|
} catch {
|
|
4431
4929
|
return false;
|
|
@@ -4436,11 +4934,11 @@ function isRecord5(value) {
|
|
|
4436
4934
|
}
|
|
4437
4935
|
|
|
4438
4936
|
// src/commands/list-platforms.ts
|
|
4439
|
-
import { promises as
|
|
4440
|
-
import
|
|
4441
|
-
import
|
|
4937
|
+
import { promises as fs6 } from "fs";
|
|
4938
|
+
import path17 from "path";
|
|
4939
|
+
import process7 from "process";
|
|
4442
4940
|
var EXIT_OK3 = 0;
|
|
4443
|
-
async function listPlatforms(rootDir =
|
|
4941
|
+
async function listPlatforms(rootDir = process7.cwd()) {
|
|
4444
4942
|
const installed = new Set(await readInstalledPlatforms(rootDir));
|
|
4445
4943
|
const platforms = getPlatformInstallers();
|
|
4446
4944
|
console.log("");
|
|
@@ -4453,8 +4951,8 @@ async function listPlatforms(rootDir = process6.cwd()) {
|
|
|
4453
4951
|
}
|
|
4454
4952
|
async function readInstalledPlatforms(rootDir) {
|
|
4455
4953
|
try {
|
|
4456
|
-
const raw = await
|
|
4457
|
-
|
|
4954
|
+
const raw = await fs6.readFile(
|
|
4955
|
+
path17.join(rootDir, ".mancode", "config.json"),
|
|
4458
4956
|
"utf-8"
|
|
4459
4957
|
);
|
|
4460
4958
|
const config = JSON.parse(raw);
|
|
@@ -4479,8 +4977,8 @@ function describePlatform(platform) {
|
|
|
4479
4977
|
|
|
4480
4978
|
// src/commands/manps.ts
|
|
4481
4979
|
import { access as access4 } from "fs/promises";
|
|
4482
|
-
import
|
|
4483
|
-
import { createInterface } from "readline/promises";
|
|
4980
|
+
import path19 from "path";
|
|
4981
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
4484
4982
|
|
|
4485
4983
|
// src/system/preseason.ts
|
|
4486
4984
|
import { execFile as execFile3 } from "child_process";
|
|
@@ -4495,7 +4993,7 @@ import {
|
|
|
4495
4993
|
rename,
|
|
4496
4994
|
writeFile as writeFile9
|
|
4497
4995
|
} from "fs/promises";
|
|
4498
|
-
import
|
|
4996
|
+
import path18 from "path";
|
|
4499
4997
|
var PRESEASON_AREAS = [
|
|
4500
4998
|
"all",
|
|
4501
4999
|
"deps",
|
|
@@ -4575,9 +5073,9 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4575
5073
|
const needsFiles = normalizedArea === "all" || normalizedArea === "dead-code" || normalizedArea === "config";
|
|
4576
5074
|
const files = needsFiles ? await listProjectFiles(projectRoot) : [];
|
|
4577
5075
|
const issues = (await scanArea(projectRoot, normalizedArea, pkg, files)).slice(0, 20);
|
|
4578
|
-
const reportDir =
|
|
5076
|
+
const reportDir = path18.join(projectRoot, ".mancode", "preseason-reports");
|
|
4579
5077
|
await mkdir7(reportDir, { recursive: true });
|
|
4580
|
-
const issueDbPath =
|
|
5078
|
+
const issueDbPath = path18.join(
|
|
4581
5079
|
projectRoot,
|
|
4582
5080
|
".mancode",
|
|
4583
5081
|
"preseason-issues.json"
|
|
@@ -4597,7 +5095,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4597
5095
|
const database = await buildIssueDatabase(projectRoot, report);
|
|
4598
5096
|
await writeFile9(reportPath, renderPreseasonReport(report), "utf-8");
|
|
4599
5097
|
await writeFile9(
|
|
4600
|
-
|
|
5098
|
+
path18.join(projectRoot, ".mancode", "preseason-report.md"),
|
|
4601
5099
|
renderPreseasonReport(report),
|
|
4602
5100
|
"utf-8"
|
|
4603
5101
|
);
|
|
@@ -4605,7 +5103,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
4605
5103
|
return report;
|
|
4606
5104
|
}
|
|
4607
5105
|
async function runPreseasonRemediation(projectRoot, issues, options = {}) {
|
|
4608
|
-
const issueDbPath =
|
|
5106
|
+
const issueDbPath = path18.join(
|
|
4609
5107
|
projectRoot,
|
|
4610
5108
|
".mancode",
|
|
4611
5109
|
"preseason-issues.json"
|
|
@@ -4719,7 +5217,7 @@ async function scanArea(projectRoot, area, pkg, files) {
|
|
|
4719
5217
|
async function allocateReportPath(reportDir, baseName) {
|
|
4720
5218
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
4721
5219
|
const suffix = attempt === 0 ? "" : `-${attempt + 1}`;
|
|
4722
|
-
const candidate =
|
|
5220
|
+
const candidate = path18.join(reportDir, `${baseName}${suffix}.md`);
|
|
4723
5221
|
if (!existsSync(candidate)) return candidate;
|
|
4724
5222
|
}
|
|
4725
5223
|
throw new Error(`unable to allocate preseason report path: ${baseName}`);
|
|
@@ -4768,8 +5266,8 @@ async function walk(root, current, results, depth) {
|
|
|
4768
5266
|
}
|
|
4769
5267
|
for (const entry of entries) {
|
|
4770
5268
|
if (IGNORE_DIRS.has(entry)) continue;
|
|
4771
|
-
const abs =
|
|
4772
|
-
const rel =
|
|
5269
|
+
const abs = path18.join(current, entry);
|
|
5270
|
+
const rel = path18.relative(root, abs);
|
|
4773
5271
|
let info;
|
|
4774
5272
|
try {
|
|
4775
5273
|
info = await lstat(abs);
|
|
@@ -4779,7 +5277,7 @@ async function walk(root, current, results, depth) {
|
|
|
4779
5277
|
if (info.isSymbolicLink()) continue;
|
|
4780
5278
|
if (info.isDirectory()) {
|
|
4781
5279
|
await walk(root, abs, results, depth + 1);
|
|
4782
|
-
} else if (SOURCE_EXTENSIONS.has(
|
|
5280
|
+
} else if (SOURCE_EXTENSIONS.has(path18.extname(entry)) || entry === "package.json") {
|
|
4783
5281
|
results.push(rel);
|
|
4784
5282
|
if (results.length >= MAX_PROJECT_FILES) return;
|
|
4785
5283
|
}
|
|
@@ -4787,7 +5285,7 @@ async function walk(root, current, results, depth) {
|
|
|
4787
5285
|
}
|
|
4788
5286
|
async function readPackageJson(projectRoot) {
|
|
4789
5287
|
try {
|
|
4790
|
-
const raw = await readFile8(
|
|
5288
|
+
const raw = await readFile8(path18.join(projectRoot, "package.json"), "utf-8");
|
|
4791
5289
|
return JSON.parse(raw);
|
|
4792
5290
|
} catch {
|
|
4793
5291
|
return null;
|
|
@@ -4880,7 +5378,7 @@ function scanTodos(projectRoot, files) {
|
|
|
4880
5378
|
const matches = [];
|
|
4881
5379
|
for (const file of files) {
|
|
4882
5380
|
if (matches.length >= 7) break;
|
|
4883
|
-
const abs =
|
|
5381
|
+
const abs = path18.join(projectRoot, file);
|
|
4884
5382
|
matches.push(...readTodoIssues(abs, file, matches.length));
|
|
4885
5383
|
}
|
|
4886
5384
|
return matches.slice(0, 7);
|
|
@@ -4917,7 +5415,7 @@ function scanTestGaps(files) {
|
|
|
4917
5415
|
if (sourceFiles.length === 0) return [];
|
|
4918
5416
|
const tests = new Set(files.filter((file) => file.startsWith("tests/")));
|
|
4919
5417
|
const missing = sourceFiles.filter((file) => {
|
|
4920
|
-
const base =
|
|
5418
|
+
const base = path18.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
4921
5419
|
return !Array.from(tests).some((test) => test.includes(base));
|
|
4922
5420
|
}).slice(0, 4);
|
|
4923
5421
|
return missing.map((file, index) => ({
|
|
@@ -4926,13 +5424,13 @@ function scanTestGaps(files) {
|
|
|
4926
5424
|
type: "tests",
|
|
4927
5425
|
title: "Core source file has no obvious test",
|
|
4928
5426
|
file,
|
|
4929
|
-
detail: `No matching test file was found for ${
|
|
5427
|
+
detail: `No matching test file was found for ${path18.basename(file)}.`,
|
|
4930
5428
|
recommendation: "Add focused coverage for the public behavior or document why this module is exercised indirectly."
|
|
4931
5429
|
}));
|
|
4932
5430
|
}
|
|
4933
5431
|
function scanConfig(projectRoot, files) {
|
|
4934
5432
|
const issues = [];
|
|
4935
|
-
if (!files.includes(".gitignore") && !pathExistsSync(
|
|
5433
|
+
if (!files.includes(".gitignore") && !pathExistsSync(path18.join(projectRoot, ".gitignore"))) {
|
|
4936
5434
|
issues.push({
|
|
4937
5435
|
id: "config-gitignore",
|
|
4938
5436
|
severity: "P1",
|
|
@@ -4943,7 +5441,7 @@ function scanConfig(projectRoot, files) {
|
|
|
4943
5441
|
recommendation: "Add a .gitignore that excludes dependencies, build output, coverage, and local env files."
|
|
4944
5442
|
});
|
|
4945
5443
|
}
|
|
4946
|
-
if (!files.includes(".editorconfig") && !pathExistsSync(
|
|
5444
|
+
if (!files.includes(".editorconfig") && !pathExistsSync(path18.join(projectRoot, ".editorconfig"))) {
|
|
4947
5445
|
issues.push({
|
|
4948
5446
|
id: "config-editorconfig",
|
|
4949
5447
|
severity: "P2",
|
|
@@ -4962,7 +5460,7 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
4962
5460
|
for (const file of frontendFiles) {
|
|
4963
5461
|
let content;
|
|
4964
5462
|
try {
|
|
4965
|
-
content = readFileSyncSafe(
|
|
5463
|
+
content = readFileSyncSafe(path18.join(projectRoot, file));
|
|
4966
5464
|
} catch {
|
|
4967
5465
|
continue;
|
|
4968
5466
|
}
|
|
@@ -4982,7 +5480,7 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
4982
5480
|
return issues;
|
|
4983
5481
|
}
|
|
4984
5482
|
async function scanArchitecture(projectRoot) {
|
|
4985
|
-
const localBinary =
|
|
5483
|
+
const localBinary = path18.join(
|
|
4986
5484
|
projectRoot,
|
|
4987
5485
|
"node_modules",
|
|
4988
5486
|
".bin",
|
|
@@ -5030,7 +5528,7 @@ async function hasDependencyCruiserConfig(projectRoot) {
|
|
|
5030
5528
|
"dependency-cruiser.config.mjs"
|
|
5031
5529
|
];
|
|
5032
5530
|
const results = await Promise.all(
|
|
5033
|
-
files.map((file) => pathExists6(
|
|
5531
|
+
files.map((file) => pathExists6(path18.join(projectRoot, file)))
|
|
5034
5532
|
);
|
|
5035
5533
|
return results.some(Boolean);
|
|
5036
5534
|
}
|
|
@@ -5056,11 +5554,11 @@ function runDepcruise(binary, projectRoot) {
|
|
|
5056
5554
|
maxBuffer: DEPCRUISE_MAX_BUFFER,
|
|
5057
5555
|
shell: needsShell
|
|
5058
5556
|
},
|
|
5059
|
-
(error,
|
|
5557
|
+
(error, stdout2, stderr) => {
|
|
5060
5558
|
if (!error) {
|
|
5061
5559
|
resolve({
|
|
5062
5560
|
status: "ok",
|
|
5063
|
-
stdout,
|
|
5561
|
+
stdout: stdout2,
|
|
5064
5562
|
stderr,
|
|
5065
5563
|
exitCode: 0,
|
|
5066
5564
|
timedOut: false
|
|
@@ -5080,7 +5578,7 @@ function runDepcruise(binary, projectRoot) {
|
|
|
5080
5578
|
}
|
|
5081
5579
|
resolve({
|
|
5082
5580
|
status: "failed",
|
|
5083
|
-
stdout: nodeError.stdout ??
|
|
5581
|
+
stdout: nodeError.stdout ?? stdout2,
|
|
5084
5582
|
stderr: nodeError.stderr ?? stderr,
|
|
5085
5583
|
exitCode: typeof nodeError.code === "number" ? nodeError.code : null,
|
|
5086
5584
|
timedOut: Boolean(nodeError.killed || nodeError.signal === "SIGTERM")
|
|
@@ -5136,9 +5634,9 @@ function inferCommands(pkg) {
|
|
|
5136
5634
|
return ["lint", "test", "build"].filter((name) => scripts[name]).map((name) => `npm run ${name}`);
|
|
5137
5635
|
}
|
|
5138
5636
|
async function buildIssueDatabase(projectRoot, report) {
|
|
5139
|
-
const reportRef =
|
|
5637
|
+
const reportRef = path18.relative(projectRoot, report.reportPath);
|
|
5140
5638
|
const run = {
|
|
5141
|
-
id:
|
|
5639
|
+
id: path18.basename(report.reportPath, ".md"),
|
|
5142
5640
|
generatedAt: report.generatedAt,
|
|
5143
5641
|
area: report.area,
|
|
5144
5642
|
reportPath: reportRef,
|
|
@@ -5244,7 +5742,7 @@ function compareIssueRecords(a, b) {
|
|
|
5244
5742
|
}
|
|
5245
5743
|
async function applySafeRemediation(projectRoot, issue) {
|
|
5246
5744
|
if (issue.id === "config-gitignore" && issue.file === ".gitignore") {
|
|
5247
|
-
const gitignorePath =
|
|
5745
|
+
const gitignorePath = path18.join(projectRoot, ".gitignore");
|
|
5248
5746
|
if (pathExistsSync(gitignorePath)) {
|
|
5249
5747
|
return { applied: false };
|
|
5250
5748
|
}
|
|
@@ -5253,7 +5751,7 @@ async function applySafeRemediation(projectRoot, issue) {
|
|
|
5253
5751
|
return { applied: true, action: "created .gitignore" };
|
|
5254
5752
|
}
|
|
5255
5753
|
if (issue.id === "config-editorconfig" && issue.file === ".editorconfig") {
|
|
5256
|
-
const editorconfigPath =
|
|
5754
|
+
const editorconfigPath = path18.join(projectRoot, ".editorconfig");
|
|
5257
5755
|
if (pathExistsSync(editorconfigPath)) {
|
|
5258
5756
|
return { applied: false };
|
|
5259
5757
|
}
|
|
@@ -5293,7 +5791,7 @@ async function inferSafePackageScript(projectRoot, scriptName) {
|
|
|
5293
5791
|
}
|
|
5294
5792
|
}
|
|
5295
5793
|
async function addPackageScript(projectRoot, scriptName, script) {
|
|
5296
|
-
const packagePath =
|
|
5794
|
+
const packagePath = path18.join(projectRoot, "package.json");
|
|
5297
5795
|
let pkg;
|
|
5298
5796
|
try {
|
|
5299
5797
|
pkg = JSON.parse(await readFile8(packagePath, "utf-8"));
|
|
@@ -5406,7 +5904,7 @@ var EXIT_NOT_INITIALIZED2 = 1;
|
|
|
5406
5904
|
var EXIT_SCAN_FAILED = 2;
|
|
5407
5905
|
var EXIT_INVALID_ARG = 3;
|
|
5408
5906
|
async function manps(rootDir, area = "all", options = {}) {
|
|
5409
|
-
if (!await pathExists7(
|
|
5907
|
+
if (!await pathExists7(path19.join(rootDir, ".mancode", "state.json"))) {
|
|
5410
5908
|
if (options.json) {
|
|
5411
5909
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
5412
5910
|
} else {
|
|
@@ -5485,8 +5983,8 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
5485
5983
|
console.log(
|
|
5486
5984
|
`Issues: ${report.issues.length} total (P0 ${p0}, P1 ${p1}, P2 ${p2})`
|
|
5487
5985
|
);
|
|
5488
|
-
console.log(`Report: ${
|
|
5489
|
-
console.log(`Issue DB: ${
|
|
5986
|
+
console.log(`Report: ${path19.relative(rootDir, report.reportPath)}`);
|
|
5987
|
+
console.log(`Issue DB: ${path19.relative(rootDir, report.issueDbPath)}`);
|
|
5490
5988
|
if (report.issues.length > 0) {
|
|
5491
5989
|
console.log("");
|
|
5492
5990
|
for (const issue of report.issues.slice(0, 7)) {
|
|
@@ -5502,7 +6000,7 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
5502
6000
|
console.log(` Skipped: ${remediation.skipped}`);
|
|
5503
6001
|
console.log(` Fixed: ${remediation.fixed}`);
|
|
5504
6002
|
console.log(
|
|
5505
|
-
` Issue DB: ${
|
|
6003
|
+
` Issue DB: ${path19.relative(rootDir, remediation.issueDbPath)}`
|
|
5506
6004
|
);
|
|
5507
6005
|
}
|
|
5508
6006
|
return EXIT_OK4;
|
|
@@ -5528,7 +6026,7 @@ async function runRemediation(rootDir, report, options, silent = false) {
|
|
|
5528
6026
|
write
|
|
5529
6027
|
});
|
|
5530
6028
|
}
|
|
5531
|
-
const rl =
|
|
6029
|
+
const rl = createInterface2({
|
|
5532
6030
|
input: process.stdin,
|
|
5533
6031
|
output: process.stdout
|
|
5534
6032
|
});
|
|
@@ -5557,23 +6055,246 @@ async function pathExists7(p) {
|
|
|
5557
6055
|
}
|
|
5558
6056
|
}
|
|
5559
6057
|
|
|
5560
|
-
// src/commands/refresh-
|
|
5561
|
-
import {
|
|
5562
|
-
import
|
|
5563
|
-
import
|
|
6058
|
+
// src/commands/refresh-project.ts
|
|
6059
|
+
import { randomUUID } from "crypto";
|
|
6060
|
+
import { promises as fs7 } from "fs";
|
|
6061
|
+
import path20 from "path";
|
|
6062
|
+
import process8 from "process";
|
|
5564
6063
|
var EXIT_OK5 = 0;
|
|
5565
6064
|
var EXIT_NOT_INITIALIZED3 = 1;
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
6065
|
+
var EXIT_CORRUPT_STATE = 2;
|
|
6066
|
+
var EXIT_REFRESH_FAILED = 3;
|
|
6067
|
+
async function refreshProject(rootDir = process8.cwd()) {
|
|
6068
|
+
const mancodeDir = path20.join(rootDir, ".mancode");
|
|
6069
|
+
const statePath = path20.join(mancodeDir, "state.json");
|
|
6070
|
+
if (!await pathExists8(statePath)) {
|
|
5569
6071
|
console.error("\u2717 mancode not initialized.");
|
|
5570
6072
|
console.error(" Run `mancode init` first.");
|
|
5571
6073
|
return EXIT_NOT_INITIALIZED3;
|
|
5572
6074
|
}
|
|
6075
|
+
const state = await readRequiredState(statePath);
|
|
6076
|
+
if (!state) {
|
|
6077
|
+
console.error("\u2717 .mancode/state.json is corrupt or incomplete.");
|
|
6078
|
+
console.error(" Run `mancode init --force` to repair it.");
|
|
6079
|
+
return EXIT_CORRUPT_STATE;
|
|
6080
|
+
}
|
|
6081
|
+
let factsWritten = false;
|
|
6082
|
+
try {
|
|
6083
|
+
const [profile, team, hasGit, hasManifest] = await Promise.all([
|
|
6084
|
+
detectProjectProfile(rootDir),
|
|
6085
|
+
detectTeamStatus(rootDir),
|
|
6086
|
+
pathExists8(path20.join(rootDir, ".git")),
|
|
6087
|
+
hasProjectManifest2(rootDir)
|
|
6088
|
+
]);
|
|
6089
|
+
const uiLibrary = primaryUiLibrary(profile);
|
|
6090
|
+
const stack = [...profile.languages, ...profile.frameworks];
|
|
6091
|
+
const config = await readJson3(path20.join(mancodeDir, "config.json"));
|
|
6092
|
+
const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : team.isTeam;
|
|
6093
|
+
const nextState = {
|
|
6094
|
+
...state,
|
|
6095
|
+
techStack: stack.join(" + ") || profile.projectKind,
|
|
6096
|
+
uiLibrary: uiLibrary ?? "None",
|
|
6097
|
+
projectMode: hasGit || hasManifest ? "detected" : "generic",
|
|
6098
|
+
teamModeAutoDetected: configuredTeam,
|
|
6099
|
+
contributors: team.contributors
|
|
6100
|
+
};
|
|
6101
|
+
await writeProjectFacts(
|
|
6102
|
+
statePath,
|
|
6103
|
+
path20.join(mancodeDir, "project-profile.json"),
|
|
6104
|
+
`${JSON.stringify(nextState, null, 2)}
|
|
6105
|
+
`,
|
|
6106
|
+
`${JSON.stringify(profile, null, 2)}
|
|
6107
|
+
`
|
|
6108
|
+
);
|
|
6109
|
+
factsWritten = true;
|
|
6110
|
+
const refreshedPlatforms = await refreshStaticPlatforms(
|
|
6111
|
+
rootDir,
|
|
6112
|
+
config,
|
|
6113
|
+
state.platform,
|
|
6114
|
+
stack,
|
|
6115
|
+
uiLibrary,
|
|
6116
|
+
profile
|
|
6117
|
+
);
|
|
6118
|
+
console.log("\u2713 Project facts refreshed.");
|
|
6119
|
+
console.log(
|
|
6120
|
+
` ${hasGit ? "Git detected" : "No Git repository"} | ${hasManifest ? "project manifest detected" : "generic project"}`
|
|
6121
|
+
);
|
|
6122
|
+
console.log(
|
|
6123
|
+
` Stack: ${nextState.techStack} | UI: ${nextState.uiLibrary}`
|
|
6124
|
+
);
|
|
6125
|
+
if (refreshedPlatforms.length > 0) {
|
|
6126
|
+
console.log(` Refreshed adapters: ${refreshedPlatforms.join(", ")}`);
|
|
6127
|
+
}
|
|
6128
|
+
console.log(
|
|
6129
|
+
" Run `mancode refresh-style` if UI files or dependencies changed."
|
|
6130
|
+
);
|
|
6131
|
+
return EXIT_OK5;
|
|
6132
|
+
} catch (error) {
|
|
6133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6134
|
+
console.error(`\u2717 Project refresh failed: ${message}`);
|
|
6135
|
+
if (factsWritten) {
|
|
6136
|
+
console.error(
|
|
6137
|
+
" Project facts were saved, but one or more static adapters remain stale."
|
|
6138
|
+
);
|
|
6139
|
+
}
|
|
6140
|
+
return EXIT_REFRESH_FAILED;
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
async function hasProjectManifest2(rootDir) {
|
|
6144
|
+
for (const manifest of PROJECT_MANIFESTS) {
|
|
6145
|
+
if (await pathExists8(path20.join(rootDir, manifest))) return true;
|
|
6146
|
+
}
|
|
6147
|
+
return false;
|
|
6148
|
+
}
|
|
6149
|
+
async function writeProjectFacts(statePath, profilePath, stateContent, profileContent) {
|
|
6150
|
+
await Promise.all([
|
|
6151
|
+
ensureReplaceableFile(statePath),
|
|
6152
|
+
ensureReplaceableFile(profilePath)
|
|
6153
|
+
]);
|
|
6154
|
+
const previousProfile = await readOptionalText(profilePath);
|
|
6155
|
+
const stateTemp = temporaryPath(statePath);
|
|
6156
|
+
const profileTemp = temporaryPath(profilePath);
|
|
6157
|
+
let profileReplaced = false;
|
|
6158
|
+
try {
|
|
6159
|
+
await Promise.all([
|
|
6160
|
+
fs7.writeFile(stateTemp, stateContent, "utf-8"),
|
|
6161
|
+
fs7.writeFile(profileTemp, profileContent, "utf-8")
|
|
6162
|
+
]);
|
|
6163
|
+
await fs7.rename(profileTemp, profilePath);
|
|
6164
|
+
profileReplaced = true;
|
|
6165
|
+
await fs7.rename(stateTemp, statePath);
|
|
6166
|
+
} catch (error) {
|
|
6167
|
+
if (profileReplaced) {
|
|
6168
|
+
if (previousProfile === null) {
|
|
6169
|
+
await fs7.rm(profilePath, { force: true });
|
|
6170
|
+
} else {
|
|
6171
|
+
await replaceTextFile(profilePath, previousProfile);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
throw error;
|
|
6175
|
+
} finally {
|
|
6176
|
+
await Promise.all([
|
|
6177
|
+
fs7.rm(stateTemp, { force: true }),
|
|
6178
|
+
fs7.rm(profileTemp, { force: true })
|
|
6179
|
+
]);
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
async function ensureReplaceableFile(filePath) {
|
|
6183
|
+
try {
|
|
6184
|
+
const entry = await fs7.lstat(filePath);
|
|
6185
|
+
if (!entry.isFile()) {
|
|
6186
|
+
throw new Error(`cannot replace non-file path: ${filePath}`);
|
|
6187
|
+
}
|
|
6188
|
+
} catch (error) {
|
|
6189
|
+
if (isNodeError6(error) && error.code === "ENOENT") return;
|
|
6190
|
+
throw error;
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
async function replaceTextFile(filePath, content) {
|
|
6194
|
+
const tempPath = temporaryPath(filePath);
|
|
6195
|
+
try {
|
|
6196
|
+
await fs7.writeFile(tempPath, content, "utf-8");
|
|
6197
|
+
await fs7.rename(tempPath, filePath);
|
|
6198
|
+
} finally {
|
|
6199
|
+
await fs7.rm(tempPath, { force: true });
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
async function readOptionalText(filePath) {
|
|
6203
|
+
try {
|
|
6204
|
+
return await fs7.readFile(filePath, "utf-8");
|
|
6205
|
+
} catch (error) {
|
|
6206
|
+
if (isNodeError6(error) && error.code === "ENOENT") return null;
|
|
6207
|
+
throw error;
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
function temporaryPath(filePath) {
|
|
6211
|
+
return path20.join(
|
|
6212
|
+
path20.dirname(filePath),
|
|
6213
|
+
`.${path20.basename(filePath)}.${process8.pid}.${randomUUID()}.tmp`
|
|
6214
|
+
);
|
|
6215
|
+
}
|
|
6216
|
+
async function refreshStaticPlatforms(rootDir, config, fallbackPlatform, stack, uiLibrary, profile) {
|
|
6217
|
+
const platforms = configuredPlatforms(config, fallbackPlatform).filter(
|
|
6218
|
+
(platform) => platform !== "claude-code"
|
|
6219
|
+
);
|
|
6220
|
+
const refreshed = [];
|
|
6221
|
+
for (const platform of platforms) {
|
|
6222
|
+
const installer = getPlatformInstaller(platform);
|
|
6223
|
+
if (!installer) continue;
|
|
6224
|
+
await installer.install(rootDir, {
|
|
6225
|
+
techStack: stack,
|
|
6226
|
+
uiLibrary,
|
|
6227
|
+
projectProfile: profile,
|
|
6228
|
+
minimal: platformIsMinimal(config, platform),
|
|
6229
|
+
force: true
|
|
6230
|
+
});
|
|
6231
|
+
refreshed.push(installer.displayName);
|
|
6232
|
+
}
|
|
6233
|
+
return refreshed;
|
|
6234
|
+
}
|
|
6235
|
+
function configuredPlatforms(config, fallbackPlatform) {
|
|
6236
|
+
const configured = Array.isArray(config.platforms) ? config.platforms : [fallbackPlatform];
|
|
6237
|
+
return configured.filter(
|
|
6238
|
+
(platform) => typeof platform === "string" && getPlatformInstaller(platform) !== null
|
|
6239
|
+
);
|
|
6240
|
+
}
|
|
6241
|
+
function platformIsMinimal(config, platform) {
|
|
6242
|
+
if (!isRecord7(config.platformOptions)) return false;
|
|
6243
|
+
const options = config.platformOptions[platform];
|
|
6244
|
+
return isRecord7(options) && options.minimal === true;
|
|
6245
|
+
}
|
|
6246
|
+
async function readJson3(filePath) {
|
|
6247
|
+
try {
|
|
6248
|
+
const value = JSON.parse(await fs7.readFile(filePath, "utf-8"));
|
|
6249
|
+
return isRecord7(value) ? value : {};
|
|
6250
|
+
} catch {
|
|
6251
|
+
return {};
|
|
6252
|
+
}
|
|
6253
|
+
}
|
|
6254
|
+
async function readRequiredState(filePath) {
|
|
6255
|
+
let state;
|
|
6256
|
+
try {
|
|
6257
|
+
const raw = await fs7.readFile(filePath, "utf-8");
|
|
6258
|
+
const parsed = JSON.parse(raw);
|
|
6259
|
+
if (!isRecord7(parsed)) return null;
|
|
6260
|
+
state = parsed;
|
|
6261
|
+
} catch {
|
|
6262
|
+
return null;
|
|
6263
|
+
}
|
|
6264
|
+
return typeof state.version === "string" && typeof state.currentMode === "string" && typeof state.platform === "string" ? state : null;
|
|
6265
|
+
}
|
|
6266
|
+
function isRecord7(value) {
|
|
6267
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6268
|
+
}
|
|
6269
|
+
function isNodeError6(error) {
|
|
6270
|
+
return error instanceof Error && "code" in error;
|
|
6271
|
+
}
|
|
6272
|
+
async function pathExists8(filePath) {
|
|
6273
|
+
try {
|
|
6274
|
+
await fs7.access(filePath);
|
|
6275
|
+
return true;
|
|
6276
|
+
} catch {
|
|
6277
|
+
return false;
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
// src/commands/refresh-style.ts
|
|
6282
|
+
import { promises as fs8 } from "fs";
|
|
6283
|
+
import path21 from "path";
|
|
6284
|
+
import process9 from "process";
|
|
6285
|
+
var EXIT_OK6 = 0;
|
|
6286
|
+
var EXIT_NOT_INITIALIZED4 = 1;
|
|
6287
|
+
async function refreshStyle(rootDir = process9.cwd()) {
|
|
6288
|
+
const stateFile = path21.join(rootDir, ".mancode", "state.json");
|
|
6289
|
+
if (!await pathExists9(stateFile)) {
|
|
6290
|
+
console.error("\u2717 mancode not initialized.");
|
|
6291
|
+
console.error(" Run `mancode init` first.");
|
|
6292
|
+
return EXIT_NOT_INITIALIZED4;
|
|
6293
|
+
}
|
|
5573
6294
|
console.log("\u2713 \u5237\u65B0\u9879\u76EE profile...");
|
|
5574
6295
|
const profile = await detectProjectProfile(rootDir);
|
|
5575
|
-
const profilePath =
|
|
5576
|
-
await
|
|
6296
|
+
const profilePath = path21.join(rootDir, ".mancode", "project-profile.json");
|
|
6297
|
+
await fs8.writeFile(
|
|
5577
6298
|
profilePath,
|
|
5578
6299
|
`${JSON.stringify(profile, null, 2)}
|
|
5579
6300
|
`,
|
|
@@ -5590,18 +6311,18 @@ async function refreshStyle(rootDir = process7.cwd()) {
|
|
|
5590
6311
|
);
|
|
5591
6312
|
console.log(" Updated .mancode/project-profile.json.");
|
|
5592
6313
|
await printStaticPlatformRefreshHint(rootDir);
|
|
5593
|
-
return
|
|
6314
|
+
return EXIT_OK6;
|
|
5594
6315
|
}
|
|
5595
6316
|
console.log("\u2713 \u626B\u63CF\u9879\u76EE\u8BBE\u8BA1 token...");
|
|
5596
6317
|
const tokens = await scanAesthetics(rootDir, uiLibraryHint);
|
|
5597
|
-
const tokensPath =
|
|
6318
|
+
const tokensPath = path21.join(
|
|
5598
6319
|
rootDir,
|
|
5599
6320
|
".mancode",
|
|
5600
6321
|
"aesthetics",
|
|
5601
6322
|
"style-tokens.json"
|
|
5602
6323
|
);
|
|
5603
|
-
await
|
|
5604
|
-
await
|
|
6324
|
+
await fs8.mkdir(path21.dirname(tokensPath), { recursive: true });
|
|
6325
|
+
await fs8.writeFile(
|
|
5605
6326
|
tokensPath,
|
|
5606
6327
|
`${JSON.stringify(tokens, null, 2)}
|
|
5607
6328
|
`,
|
|
@@ -5649,7 +6370,7 @@ async function refreshStyle(rootDir = process7.cwd()) {
|
|
|
5649
6370
|
"\u5DF2\u66F4\u65B0 .mancode/project-profile.json \u548C .mancode/aesthetics/style-tokens.json"
|
|
5650
6371
|
);
|
|
5651
6372
|
await printStaticPlatformRefreshHint(rootDir);
|
|
5652
|
-
return
|
|
6373
|
+
return EXIT_OK6;
|
|
5653
6374
|
}
|
|
5654
6375
|
async function printStaticPlatformRefreshHint(rootDir) {
|
|
5655
6376
|
const platforms = await readInstalledPlatforms2(rootDir);
|
|
@@ -5666,11 +6387,11 @@ async function printStaticPlatformRefreshHint(rootDir) {
|
|
|
5666
6387
|
);
|
|
5667
6388
|
}
|
|
5668
6389
|
async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
|
|
5669
|
-
const statePath =
|
|
6390
|
+
const statePath = path21.join(rootDir, ".mancode", "state.json");
|
|
5670
6391
|
try {
|
|
5671
|
-
const state = JSON.parse(await
|
|
6392
|
+
const state = JSON.parse(await fs8.readFile(statePath, "utf-8"));
|
|
5672
6393
|
const stack = [...profile.languages, ...profile.frameworks];
|
|
5673
|
-
await
|
|
6394
|
+
await fs8.writeFile(
|
|
5674
6395
|
statePath,
|
|
5675
6396
|
`${JSON.stringify(
|
|
5676
6397
|
{
|
|
@@ -5689,8 +6410,8 @@ async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
|
|
|
5689
6410
|
}
|
|
5690
6411
|
async function readInstalledPlatforms2(rootDir) {
|
|
5691
6412
|
try {
|
|
5692
|
-
const raw = await
|
|
5693
|
-
|
|
6413
|
+
const raw = await fs8.readFile(
|
|
6414
|
+
path21.join(rootDir, ".mancode", "config.json"),
|
|
5694
6415
|
"utf-8"
|
|
5695
6416
|
);
|
|
5696
6417
|
const config = JSON.parse(raw);
|
|
@@ -5701,9 +6422,9 @@ async function readInstalledPlatforms2(rootDir) {
|
|
|
5701
6422
|
return [];
|
|
5702
6423
|
}
|
|
5703
6424
|
}
|
|
5704
|
-
async function
|
|
6425
|
+
async function pathExists9(p) {
|
|
5705
6426
|
try {
|
|
5706
|
-
await
|
|
6427
|
+
await fs8.access(p);
|
|
5707
6428
|
return true;
|
|
5708
6429
|
} catch {
|
|
5709
6430
|
return false;
|
|
@@ -5712,9 +6433,9 @@ async function pathExists8(p) {
|
|
|
5712
6433
|
|
|
5713
6434
|
// src/commands/status.ts
|
|
5714
6435
|
import { spawn } from "child_process";
|
|
5715
|
-
import { promises as
|
|
5716
|
-
import
|
|
5717
|
-
import
|
|
6436
|
+
import { promises as fs9 } from "fs";
|
|
6437
|
+
import path24 from "path";
|
|
6438
|
+
import process10 from "process";
|
|
5718
6439
|
|
|
5719
6440
|
// src/system/workflow.ts
|
|
5720
6441
|
import {
|
|
@@ -5725,11 +6446,11 @@ import {
|
|
|
5725
6446
|
stat as stat4,
|
|
5726
6447
|
writeFile as writeFile11
|
|
5727
6448
|
} from "fs/promises";
|
|
5728
|
-
import
|
|
6449
|
+
import path23 from "path";
|
|
5729
6450
|
|
|
5730
6451
|
// src/system/review-ledger.ts
|
|
5731
6452
|
import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
|
|
5732
|
-
import
|
|
6453
|
+
import path22 from "path";
|
|
5733
6454
|
var REVIEW_FILE = "review-ledger.json";
|
|
5734
6455
|
var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
5735
6456
|
var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
|
|
@@ -5845,8 +6566,8 @@ async function readReviewLedger(projectRoot, taskId) {
|
|
|
5845
6566
|
}
|
|
5846
6567
|
}
|
|
5847
6568
|
function isReviewLedger(value) {
|
|
5848
|
-
if (!
|
|
5849
|
-
if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !
|
|
6569
|
+
if (!isRecord8(value)) return false;
|
|
6570
|
+
if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord8(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
|
|
5850
6571
|
return false;
|
|
5851
6572
|
}
|
|
5852
6573
|
const requiredDomains = value.requiredDomains;
|
|
@@ -5862,13 +6583,13 @@ function isReviewLedger(value) {
|
|
|
5862
6583
|
}
|
|
5863
6584
|
const blockerIds = /* @__PURE__ */ new Set();
|
|
5864
6585
|
return blockers.every(
|
|
5865
|
-
(blocker) =>
|
|
6586
|
+
(blocker) => isRecord8(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
|
|
5866
6587
|
);
|
|
5867
6588
|
}
|
|
5868
6589
|
function isDomainArray(value) {
|
|
5869
6590
|
return Array.isArray(value) && value.every(isReviewDomain);
|
|
5870
6591
|
}
|
|
5871
|
-
function
|
|
6592
|
+
function isRecord8(value) {
|
|
5872
6593
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5873
6594
|
}
|
|
5874
6595
|
async function requireReviewLedger(projectRoot, taskId) {
|
|
@@ -5878,17 +6599,17 @@ async function requireReviewLedger(projectRoot, taskId) {
|
|
|
5878
6599
|
return ledger;
|
|
5879
6600
|
}
|
|
5880
6601
|
async function writeReviewLedger(projectRoot, taskId, ledger) {
|
|
5881
|
-
const dir =
|
|
6602
|
+
const dir = path22.join(projectRoot, ".mancode", "workflows", taskId);
|
|
5882
6603
|
await mkdir8(dir, { recursive: true });
|
|
5883
6604
|
await writeFile10(
|
|
5884
|
-
|
|
6605
|
+
path22.join(dir, REVIEW_FILE),
|
|
5885
6606
|
`${JSON.stringify(ledger, null, 2)}
|
|
5886
6607
|
`,
|
|
5887
6608
|
"utf-8"
|
|
5888
6609
|
);
|
|
5889
6610
|
}
|
|
5890
6611
|
function reviewPath(projectRoot, taskId) {
|
|
5891
|
-
return
|
|
6612
|
+
return path22.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
|
|
5892
6613
|
}
|
|
5893
6614
|
function assertValidTaskId(taskId) {
|
|
5894
6615
|
if (!TASK_ID_PATTERN.test(taskId)) {
|
|
@@ -5901,7 +6622,7 @@ function assertSafeReportPath(report) {
|
|
|
5901
6622
|
}
|
|
5902
6623
|
}
|
|
5903
6624
|
async function assertReportExists(projectRoot, taskId, report) {
|
|
5904
|
-
const reportPath =
|
|
6625
|
+
const reportPath = path22.join(
|
|
5905
6626
|
projectRoot,
|
|
5906
6627
|
".mancode",
|
|
5907
6628
|
"workflows",
|
|
@@ -5915,9 +6636,9 @@ async function assertReportExists(projectRoot, taskId, report) {
|
|
|
5915
6636
|
throw new Error(`review report not found: ${report}`);
|
|
5916
6637
|
}
|
|
5917
6638
|
function isSafeReportPath(report) {
|
|
5918
|
-
const normalized =
|
|
6639
|
+
const normalized = path22.posix.normalize(report.replaceAll("\\", "/"));
|
|
5919
6640
|
return Boolean(
|
|
5920
|
-
report.trim() && !
|
|
6641
|
+
report.trim() && !path22.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
|
|
5921
6642
|
);
|
|
5922
6643
|
}
|
|
5923
6644
|
|
|
@@ -5965,7 +6686,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
|
|
|
5965
6686
|
await mkdir9(workflowDir(projectRoot, taskId));
|
|
5966
6687
|
return taskId;
|
|
5967
6688
|
} catch (err) {
|
|
5968
|
-
if (
|
|
6689
|
+
if (isNodeError7(err) && err.code === "EEXIST") {
|
|
5969
6690
|
continue;
|
|
5970
6691
|
}
|
|
5971
6692
|
throw err;
|
|
@@ -6041,19 +6762,19 @@ async function deleteWorkflow(projectRoot, taskId) {
|
|
|
6041
6762
|
return true;
|
|
6042
6763
|
}
|
|
6043
6764
|
function workflowsRoot(projectRoot) {
|
|
6044
|
-
return
|
|
6765
|
+
return path23.join(projectRoot, ".mancode", "workflows");
|
|
6045
6766
|
}
|
|
6046
6767
|
function workflowDir(projectRoot, taskId) {
|
|
6047
6768
|
assertValidTaskId2(taskId);
|
|
6048
|
-
return
|
|
6769
|
+
return path23.join(workflowsRoot(projectRoot), taskId);
|
|
6049
6770
|
}
|
|
6050
6771
|
function metadataPath(projectRoot, taskId) {
|
|
6051
|
-
return
|
|
6772
|
+
return path23.join(workflowDir(projectRoot, taskId), METADATA_FILE);
|
|
6052
6773
|
}
|
|
6053
6774
|
async function writeMetadata(dir, meta) {
|
|
6054
6775
|
const content = `${JSON.stringify(meta, null, 2)}
|
|
6055
6776
|
`;
|
|
6056
|
-
await writeFile11(
|
|
6777
|
+
await writeFile11(path23.join(dir, METADATA_FILE), content, "utf-8");
|
|
6057
6778
|
}
|
|
6058
6779
|
function parseWorkflowMeta(raw, taskId) {
|
|
6059
6780
|
try {
|
|
@@ -6259,7 +6980,7 @@ async function propagateChildStatus(projectRoot, child) {
|
|
|
6259
6980
|
});
|
|
6260
6981
|
}
|
|
6261
6982
|
}
|
|
6262
|
-
function
|
|
6983
|
+
function isNodeError7(err) {
|
|
6263
6984
|
return err instanceof Error && "code" in err;
|
|
6264
6985
|
}
|
|
6265
6986
|
function assertValidTaskId2(taskId) {
|
|
@@ -6270,19 +6991,19 @@ function assertValidTaskId2(taskId) {
|
|
|
6270
6991
|
|
|
6271
6992
|
// src/commands/status.ts
|
|
6272
6993
|
var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
|
|
6273
|
-
var
|
|
6274
|
-
var
|
|
6275
|
-
var
|
|
6276
|
-
async function status(rootDir =
|
|
6277
|
-
const stateFile =
|
|
6278
|
-
if (!await
|
|
6994
|
+
var EXIT_OK7 = 0;
|
|
6995
|
+
var EXIT_NOT_INITIALIZED5 = 1;
|
|
6996
|
+
var EXIT_CORRUPT_STATE2 = 2;
|
|
6997
|
+
async function status(rootDir = process10.cwd(), options = {}) {
|
|
6998
|
+
const stateFile = path24.join(rootDir, ".mancode", "state.json");
|
|
6999
|
+
if (!await pathExists10(stateFile)) {
|
|
6279
7000
|
console.error("\u2717 mancode not initialized.");
|
|
6280
7001
|
console.error(" Run `mancode init` to get started.");
|
|
6281
|
-
return
|
|
7002
|
+
return EXIT_NOT_INITIALIZED5;
|
|
6282
7003
|
}
|
|
6283
7004
|
let state;
|
|
6284
7005
|
try {
|
|
6285
|
-
const raw = await
|
|
7006
|
+
const raw = await fs9.readFile(stateFile, "utf-8");
|
|
6286
7007
|
state = JSON.parse(raw);
|
|
6287
7008
|
} catch (err) {
|
|
6288
7009
|
if (err instanceof SyntaxError) {
|
|
@@ -6293,7 +7014,7 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6293
7014
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6294
7015
|
console.error(`\u2717 Failed to read .mancode/state.json: ${msg}`);
|
|
6295
7016
|
}
|
|
6296
|
-
return
|
|
7017
|
+
return EXIT_CORRUPT_STATE2;
|
|
6297
7018
|
}
|
|
6298
7019
|
const [
|
|
6299
7020
|
project,
|
|
@@ -6301,14 +7022,16 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6301
7022
|
config,
|
|
6302
7023
|
hookInjection,
|
|
6303
7024
|
teamStatus,
|
|
6304
|
-
currentWorkflow
|
|
7025
|
+
currentWorkflow,
|
|
7026
|
+
projectRefreshRecommended
|
|
6305
7027
|
] = await Promise.all([
|
|
6306
7028
|
getProjectName(rootDir),
|
|
6307
7029
|
checkHooks(rootDir),
|
|
6308
7030
|
readConfig2(rootDir),
|
|
6309
7031
|
estimateHookInjection(rootDir),
|
|
6310
7032
|
detectTeamStatus(rootDir),
|
|
6311
|
-
getCurrentWorkflow(rootDir, state.currentTask ?? null)
|
|
7033
|
+
getCurrentWorkflow(rootDir, state.currentTask ?? null),
|
|
7034
|
+
shouldRefreshProject(rootDir, state)
|
|
6312
7035
|
]);
|
|
6313
7036
|
const effectiveTeam = getEffectiveTeamStatus(state, config, teamStatus);
|
|
6314
7037
|
const result = {
|
|
@@ -6323,7 +7046,8 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6323
7046
|
hookInjection,
|
|
6324
7047
|
team: effectiveTeam,
|
|
6325
7048
|
currentWorkflow,
|
|
6326
|
-
platformStatus: {}
|
|
7049
|
+
platformStatus: {},
|
|
7050
|
+
projectRefreshRecommended
|
|
6327
7051
|
};
|
|
6328
7052
|
result.platformStatus = await checkPlatformStatus2(rootDir, result.platforms);
|
|
6329
7053
|
if (options.json) {
|
|
@@ -6331,7 +7055,16 @@ async function status(rootDir = process8.cwd(), options = {}) {
|
|
|
6331
7055
|
} else {
|
|
6332
7056
|
printText(result);
|
|
6333
7057
|
}
|
|
6334
|
-
return
|
|
7058
|
+
return EXIT_OK7;
|
|
7059
|
+
}
|
|
7060
|
+
async function shouldRefreshProject(rootDir, state) {
|
|
7061
|
+
if (state.projectMode !== "generic") return false;
|
|
7062
|
+
const hasGit = await pathExists10(path24.join(rootDir, ".git"));
|
|
7063
|
+
if (hasGit) return true;
|
|
7064
|
+
for (const manifest of PROJECT_MANIFESTS) {
|
|
7065
|
+
if (await pathExists10(path24.join(rootDir, manifest))) return true;
|
|
7066
|
+
}
|
|
7067
|
+
return false;
|
|
6335
7068
|
}
|
|
6336
7069
|
async function checkPlatformStatus2(rootDir, installedPlatforms) {
|
|
6337
7070
|
const installed = new Set(installedPlatforms);
|
|
@@ -6373,19 +7106,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
|
|
|
6373
7106
|
}
|
|
6374
7107
|
async function getProjectName(rootDir) {
|
|
6375
7108
|
try {
|
|
6376
|
-
const raw = await
|
|
7109
|
+
const raw = await fs9.readFile(path24.join(rootDir, "package.json"), "utf-8");
|
|
6377
7110
|
const pkg = JSON.parse(raw);
|
|
6378
7111
|
if (pkg.name && typeof pkg.name === "string") {
|
|
6379
7112
|
return pkg.name;
|
|
6380
7113
|
}
|
|
6381
7114
|
} catch {
|
|
6382
7115
|
}
|
|
6383
|
-
return
|
|
7116
|
+
return path24.basename(rootDir);
|
|
6384
7117
|
}
|
|
6385
7118
|
async function readConfig2(rootDir) {
|
|
6386
7119
|
try {
|
|
6387
|
-
const raw = await
|
|
6388
|
-
|
|
7120
|
+
const raw = await fs9.readFile(
|
|
7121
|
+
path24.join(rootDir, ".mancode", "config.json"),
|
|
6389
7122
|
"utf-8"
|
|
6390
7123
|
);
|
|
6391
7124
|
return JSON.parse(raw);
|
|
@@ -6400,21 +7133,22 @@ function getInstalledPlatforms(config, fallback) {
|
|
|
6400
7133
|
return fallback ? [fallback] : [];
|
|
6401
7134
|
}
|
|
6402
7135
|
function getEffectiveTeamStatus(state, config, detected) {
|
|
6403
|
-
const configuredTeam = config.forceTeamMode === true ? true : state.teamModeAutoDetected ?? detected.isTeam;
|
|
6404
|
-
const forced = config.forceTeamMode === true;
|
|
7136
|
+
const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
|
|
7137
|
+
const forced = config.teamMode === "on" || config.forceTeamMode === true;
|
|
7138
|
+
const autoDetected = config.forceTeamMode === true || config.teamMode === "on" || config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
|
|
6405
7139
|
return {
|
|
6406
7140
|
...detected,
|
|
6407
7141
|
isTeam: configuredTeam,
|
|
6408
7142
|
contributors: Math.max(detected.contributors, state.contributors ?? 0, 1),
|
|
6409
|
-
autoDetected
|
|
7143
|
+
autoDetected,
|
|
6410
7144
|
forced
|
|
6411
7145
|
};
|
|
6412
7146
|
}
|
|
6413
7147
|
async function checkHooks(rootDir) {
|
|
6414
7148
|
const [sessionStart, userPromptSubmit, registered] = await Promise.all([
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
7149
|
+
pathExists10(path24.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
|
|
7150
|
+
pathExists10(
|
|
7151
|
+
path24.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
6418
7152
|
),
|
|
6419
7153
|
isRegistered(rootDir)
|
|
6420
7154
|
]);
|
|
@@ -6422,8 +7156,8 @@ async function checkHooks(rootDir) {
|
|
|
6422
7156
|
}
|
|
6423
7157
|
async function isRegistered(rootDir) {
|
|
6424
7158
|
try {
|
|
6425
|
-
const raw = await
|
|
6426
|
-
|
|
7159
|
+
const raw = await fs9.readFile(
|
|
7160
|
+
path24.join(rootDir, ".claude", "settings.json"),
|
|
6427
7161
|
"utf-8"
|
|
6428
7162
|
);
|
|
6429
7163
|
const settings = JSON.parse(raw);
|
|
@@ -6433,9 +7167,9 @@ async function isRegistered(rootDir) {
|
|
|
6433
7167
|
}
|
|
6434
7168
|
}
|
|
6435
7169
|
function hooksRegistered2(settings) {
|
|
6436
|
-
if (!
|
|
7170
|
+
if (!isRecord9(settings)) return false;
|
|
6437
7171
|
const hooks = settings.hooks;
|
|
6438
|
-
if (!
|
|
7172
|
+
if (!isRecord9(hooks)) return false;
|
|
6439
7173
|
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
|
|
6440
7174
|
hooks.UserPromptSubmit,
|
|
6441
7175
|
".mancode/hooks/user-prompt-submit.mjs"
|
|
@@ -6444,21 +7178,21 @@ function hooksRegistered2(settings) {
|
|
|
6444
7178
|
function hasHookCommand2(value, needle) {
|
|
6445
7179
|
if (!Array.isArray(value)) return false;
|
|
6446
7180
|
return value.some((group) => {
|
|
6447
|
-
if (!
|
|
7181
|
+
if (!isRecord9(group) || !Array.isArray(group.hooks)) return false;
|
|
6448
7182
|
return group.hooks.some((hook) => {
|
|
6449
|
-
if (!
|
|
7183
|
+
if (!isRecord9(hook) || typeof hook.command !== "string") return false;
|
|
6450
7184
|
return hook.command.includes(needle);
|
|
6451
7185
|
});
|
|
6452
7186
|
});
|
|
6453
7187
|
}
|
|
6454
7188
|
async function estimateHookInjection(rootDir) {
|
|
6455
|
-
const hookPath =
|
|
7189
|
+
const hookPath = path24.join(
|
|
6456
7190
|
rootDir,
|
|
6457
7191
|
".mancode",
|
|
6458
7192
|
"hooks",
|
|
6459
7193
|
"user-prompt-submit.mjs"
|
|
6460
7194
|
);
|
|
6461
|
-
if (!await
|
|
7195
|
+
if (!await pathExists10(hookPath)) {
|
|
6462
7196
|
return { tokens: 0, cap: 800 };
|
|
6463
7197
|
}
|
|
6464
7198
|
try {
|
|
@@ -6473,7 +7207,7 @@ async function estimateHookInjection(rootDir) {
|
|
|
6473
7207
|
}
|
|
6474
7208
|
function runHookEstimate(rootDir, hookPath) {
|
|
6475
7209
|
return new Promise((resolve, reject) => {
|
|
6476
|
-
const child = spawn(
|
|
7210
|
+
const child = spawn(process10.execPath, [hookPath], {
|
|
6477
7211
|
cwd: rootDir,
|
|
6478
7212
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6479
7213
|
});
|
|
@@ -6488,15 +7222,15 @@ function runHookEstimate(rootDir, hookPath) {
|
|
|
6488
7222
|
child.kill();
|
|
6489
7223
|
finish(() => reject(new Error("hook estimate timed out")));
|
|
6490
7224
|
}, HOOK_ESTIMATE_TIMEOUT_MS);
|
|
6491
|
-
let
|
|
7225
|
+
let stdout2 = "";
|
|
6492
7226
|
child.stdout.setEncoding("utf-8");
|
|
6493
7227
|
child.stdout.on("data", (chunk) => {
|
|
6494
|
-
|
|
7228
|
+
stdout2 += chunk;
|
|
6495
7229
|
});
|
|
6496
7230
|
child.on("error", (err) => finish(() => reject(err)));
|
|
6497
7231
|
child.on("close", (code) => {
|
|
6498
7232
|
finish(() => {
|
|
6499
|
-
if (code === 0) resolve(
|
|
7233
|
+
if (code === 0) resolve(stdout2);
|
|
6500
7234
|
else reject(new Error(`hook exited with ${code}`));
|
|
6501
7235
|
});
|
|
6502
7236
|
});
|
|
@@ -6515,6 +7249,11 @@ function printText(r) {
|
|
|
6515
7249
|
console.log(`Style: ${r.uiLibrary}`);
|
|
6516
7250
|
console.log(`Initialized: ${r.initializedAt}`);
|
|
6517
7251
|
console.log(`Team: ${formatTeamStatus(r.team)}`);
|
|
7252
|
+
if (r.projectRefreshRecommended) {
|
|
7253
|
+
console.log(
|
|
7254
|
+
"Project: new Git or project files detected; run `mancode refresh-project`."
|
|
7255
|
+
);
|
|
7256
|
+
}
|
|
6518
7257
|
if (r.currentWorkflow) {
|
|
6519
7258
|
const stepMax = workflowStepMax(r.currentWorkflow.mode);
|
|
6520
7259
|
console.log(
|
|
@@ -6578,12 +7317,12 @@ function formatTeamStatus(team) {
|
|
|
6578
7317
|
function workflowStepMax(mode) {
|
|
6579
7318
|
return maxWorkflowStep(mode);
|
|
6580
7319
|
}
|
|
6581
|
-
function
|
|
7320
|
+
function isRecord9(value) {
|
|
6582
7321
|
return typeof value === "object" && value !== null;
|
|
6583
7322
|
}
|
|
6584
|
-
async function
|
|
7323
|
+
async function pathExists10(p) {
|
|
6585
7324
|
try {
|
|
6586
|
-
await
|
|
7325
|
+
await fs9.access(p);
|
|
6587
7326
|
return true;
|
|
6588
7327
|
} catch {
|
|
6589
7328
|
return false;
|
|
@@ -6592,17 +7331,17 @@ async function pathExists9(p) {
|
|
|
6592
7331
|
|
|
6593
7332
|
// src/commands/uninstall.ts
|
|
6594
7333
|
import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
|
|
6595
|
-
import
|
|
6596
|
-
import
|
|
6597
|
-
var
|
|
6598
|
-
var
|
|
7334
|
+
import path25 from "path";
|
|
7335
|
+
import process11 from "process";
|
|
7336
|
+
var EXIT_OK8 = 0;
|
|
7337
|
+
var EXIT_NOT_INITIALIZED6 = 1;
|
|
6599
7338
|
var EXIT_UNSUPPORTED_PLATFORM2 = 2;
|
|
6600
|
-
async function uninstall(rootDir =
|
|
6601
|
-
const stateFile =
|
|
6602
|
-
if (!await
|
|
7339
|
+
async function uninstall(rootDir = process11.cwd(), platform, options = {}) {
|
|
7340
|
+
const stateFile = path25.join(rootDir, ".mancode", "state.json");
|
|
7341
|
+
if (!await pathExists11(stateFile)) {
|
|
6603
7342
|
console.error("\u2717 mancode not initialized.");
|
|
6604
7343
|
console.error(" Run `mancode init` first.");
|
|
6605
|
-
return
|
|
7344
|
+
return EXIT_NOT_INITIALIZED6;
|
|
6606
7345
|
}
|
|
6607
7346
|
const removeAll = !platform || options.all;
|
|
6608
7347
|
if (platform) {
|
|
@@ -6627,7 +7366,7 @@ async function uninstall(rootDir = process9.cwd(), platform, options = {}) {
|
|
|
6627
7366
|
await uninstallPlatform(rootDir, platform ?? "claude-code");
|
|
6628
7367
|
}
|
|
6629
7368
|
console.log("\u2713 Uninstall complete.");
|
|
6630
|
-
return
|
|
7369
|
+
return EXIT_OK8;
|
|
6631
7370
|
}
|
|
6632
7371
|
async function uninstallPlatform(rootDir, platform) {
|
|
6633
7372
|
console.log(`\u2713 Removing ${formatPlatformName(platform)} adapter...`);
|
|
@@ -6650,7 +7389,7 @@ async function uninstallAll(rootDir) {
|
|
|
6650
7389
|
await uninstallPlatform(rootDir, p);
|
|
6651
7390
|
}
|
|
6652
7391
|
console.log("\u2713 Removing .mancode/ directory...");
|
|
6653
|
-
await rm5(
|
|
7392
|
+
await rm5(path25.join(rootDir, ".mancode"), {
|
|
6654
7393
|
recursive: true,
|
|
6655
7394
|
force: true
|
|
6656
7395
|
});
|
|
@@ -6660,7 +7399,7 @@ async function uninstallClaudeCode(rootDir) {
|
|
|
6660
7399
|
await cleanClaudeSettings(rootDir);
|
|
6661
7400
|
}
|
|
6662
7401
|
async function cleanClaudeSettings(rootDir) {
|
|
6663
|
-
const settingsPath =
|
|
7402
|
+
const settingsPath = path25.join(rootDir, ".claude", "settings.json");
|
|
6664
7403
|
let content;
|
|
6665
7404
|
try {
|
|
6666
7405
|
content = await readFile11(settingsPath, "utf-8");
|
|
@@ -6674,14 +7413,14 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
6674
7413
|
return;
|
|
6675
7414
|
}
|
|
6676
7415
|
const cleanedHooks = {};
|
|
6677
|
-
if (
|
|
7416
|
+
if (isRecord10(settings.hooks)) {
|
|
6678
7417
|
for (const [event, value] of Object.entries(settings.hooks)) {
|
|
6679
7418
|
const cleaned = cleanClaudeHookValue(value);
|
|
6680
7419
|
if (cleaned !== void 0) cleanedHooks[event] = cleaned;
|
|
6681
7420
|
}
|
|
6682
7421
|
settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
|
|
6683
7422
|
}
|
|
6684
|
-
if (
|
|
7423
|
+
if (isRecord10(settings.skills)) {
|
|
6685
7424
|
const retainedSkills = Object.fromEntries(
|
|
6686
7425
|
Object.entries(settings.skills).filter(
|
|
6687
7426
|
([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
|
|
@@ -6701,7 +7440,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6701
7440
|
const entries2 = value.flatMap(cleanClaudeHookEntry);
|
|
6702
7441
|
return entries2.length > 0 ? entries2 : void 0;
|
|
6703
7442
|
}
|
|
6704
|
-
if (!
|
|
7443
|
+
if (!isRecord10(value)) return value;
|
|
6705
7444
|
if (Array.isArray(value.hooks) || typeof value.command === "string") {
|
|
6706
7445
|
const entries2 = cleanClaudeHookEntry(value);
|
|
6707
7446
|
return entries2.length > 0 ? entries2 : void 0;
|
|
@@ -6715,7 +7454,7 @@ function cleanClaudeHookValue(value) {
|
|
|
6715
7454
|
}
|
|
6716
7455
|
function cleanClaudeHookEntry(entry) {
|
|
6717
7456
|
if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
|
|
6718
|
-
if (!
|
|
7457
|
+
if (!isRecord10(entry)) return [entry];
|
|
6719
7458
|
if (Array.isArray(entry.hooks)) {
|
|
6720
7459
|
const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
|
|
6721
7460
|
return hooks.length > 0 ? [{ ...entry, hooks }] : [];
|
|
@@ -6723,11 +7462,11 @@ function cleanClaudeHookEntry(entry) {
|
|
|
6723
7462
|
return isMancodeHookEntry(entry) ? [] : [entry];
|
|
6724
7463
|
}
|
|
6725
7464
|
function isMancodeHookEntry(value) {
|
|
6726
|
-
return
|
|
7465
|
+
return isRecord10(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
|
|
6727
7466
|
}
|
|
6728
7467
|
function containsLegacyMancodeHookPath2(value) {
|
|
6729
7468
|
if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
|
|
6730
|
-
if (!
|
|
7469
|
+
if (!isRecord10(value)) return false;
|
|
6731
7470
|
if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
|
|
6732
7471
|
return true;
|
|
6733
7472
|
}
|
|
@@ -6738,7 +7477,7 @@ async function uninstallCursor(rootDir) {
|
|
|
6738
7477
|
await removeCursorCommands(rootDir);
|
|
6739
7478
|
}
|
|
6740
7479
|
async function uninstallCodex(rootDir) {
|
|
6741
|
-
const agentsPath =
|
|
7480
|
+
const agentsPath = path25.join(rootDir, "AGENTS.md");
|
|
6742
7481
|
try {
|
|
6743
7482
|
const content = await readFile11(agentsPath, "utf-8");
|
|
6744
7483
|
const cleaned = removeManagedBlock(content);
|
|
@@ -6753,7 +7492,7 @@ async function uninstallCodex(rootDir) {
|
|
|
6753
7492
|
await removeCodexSkills(rootDir);
|
|
6754
7493
|
}
|
|
6755
7494
|
async function uninstallCopilot(rootDir) {
|
|
6756
|
-
const instructionsPath =
|
|
7495
|
+
const instructionsPath = path25.join(
|
|
6757
7496
|
rootDir,
|
|
6758
7497
|
".github",
|
|
6759
7498
|
"copilot-instructions.md"
|
|
@@ -6772,7 +7511,7 @@ async function uninstallCopilot(rootDir) {
|
|
|
6772
7511
|
await removeCopilotPrompts(rootDir);
|
|
6773
7512
|
}
|
|
6774
7513
|
async function uninstallZcode(rootDir) {
|
|
6775
|
-
const agentsPath =
|
|
7514
|
+
const agentsPath = path25.join(rootDir, "AGENTS.md");
|
|
6776
7515
|
try {
|
|
6777
7516
|
const content = await readFile11(agentsPath, "utf-8");
|
|
6778
7517
|
const cleaned = removeManagedBlock(
|
|
@@ -6791,13 +7530,13 @@ async function uninstallZcode(rootDir) {
|
|
|
6791
7530
|
await removeZcodeSkills(rootDir);
|
|
6792
7531
|
}
|
|
6793
7532
|
async function removeFromConfig(rootDir, platform) {
|
|
6794
|
-
const configPath =
|
|
7533
|
+
const configPath = path25.join(rootDir, ".mancode", "config.json");
|
|
6795
7534
|
try {
|
|
6796
7535
|
const raw = await readFile11(configPath, "utf-8");
|
|
6797
7536
|
const config = JSON.parse(raw);
|
|
6798
7537
|
if (Array.isArray(config.platforms)) {
|
|
6799
7538
|
config.platforms = config.platforms.filter((p) => p !== platform);
|
|
6800
|
-
if (
|
|
7539
|
+
if (isRecord10(config.platformOptions)) {
|
|
6801
7540
|
const platformOptions = Object.fromEntries(
|
|
6802
7541
|
Object.entries(config.platformOptions).filter(
|
|
6803
7542
|
([name]) => name !== platform
|
|
@@ -6815,10 +7554,10 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
6815
7554
|
} catch {
|
|
6816
7555
|
}
|
|
6817
7556
|
}
|
|
6818
|
-
function
|
|
7557
|
+
function isRecord10(value) {
|
|
6819
7558
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6820
7559
|
}
|
|
6821
|
-
async function
|
|
7560
|
+
async function pathExists11(p) {
|
|
6822
7561
|
try {
|
|
6823
7562
|
await access5(p);
|
|
6824
7563
|
return true;
|
|
@@ -6828,10 +7567,10 @@ async function pathExists10(p) {
|
|
|
6828
7567
|
}
|
|
6829
7568
|
|
|
6830
7569
|
// src/commands/version.ts
|
|
6831
|
-
import
|
|
7570
|
+
import process12 from "process";
|
|
6832
7571
|
function version() {
|
|
6833
|
-
const nodeVersion =
|
|
6834
|
-
const platform = `${
|
|
7572
|
+
const nodeVersion = process12.version;
|
|
7573
|
+
const platform = `${process12.platform}/${process12.arch}`;
|
|
6835
7574
|
console.log(`mancode/${VERSION}`);
|
|
6836
7575
|
console.log(`node/${nodeVersion.replace("v", "")}`);
|
|
6837
7576
|
console.log(platform);
|
|
@@ -6839,19 +7578,19 @@ function version() {
|
|
|
6839
7578
|
|
|
6840
7579
|
// src/commands/workflow.ts
|
|
6841
7580
|
import { access as access6 } from "fs/promises";
|
|
6842
|
-
import
|
|
6843
|
-
var
|
|
6844
|
-
var
|
|
7581
|
+
import path26 from "path";
|
|
7582
|
+
var EXIT_OK9 = 0;
|
|
7583
|
+
var EXIT_NOT_INITIALIZED7 = 1;
|
|
6845
7584
|
var EXIT_INVALID_ARG2 = 2;
|
|
6846
7585
|
async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
6847
|
-
if (!await
|
|
7586
|
+
if (!await pathExists12(path26.join(rootDir, ".mancode", "state.json"))) {
|
|
6848
7587
|
if (options.json) {
|
|
6849
7588
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
6850
7589
|
} else {
|
|
6851
7590
|
console.error("\u2717 mancode not initialized.");
|
|
6852
7591
|
console.error(" Run `mancode init` to get started.");
|
|
6853
7592
|
}
|
|
6854
|
-
return
|
|
7593
|
+
return EXIT_NOT_INITIALIZED7;
|
|
6855
7594
|
}
|
|
6856
7595
|
switch (subcommand) {
|
|
6857
7596
|
case "create":
|
|
@@ -6900,7 +7639,7 @@ async function workflowReview(rootDir, args, options) {
|
|
|
6900
7639
|
if (!ledger)
|
|
6901
7640
|
return invalidArg(options, `review not initialized: ${taskId}`);
|
|
6902
7641
|
outputReviewLedger(ledger, options);
|
|
6903
|
-
return
|
|
7642
|
+
return EXIT_OK9;
|
|
6904
7643
|
}
|
|
6905
7644
|
if (meta.status !== "in_progress" || meta.currentStep < 6) {
|
|
6906
7645
|
return invalidArg(
|
|
@@ -6957,7 +7696,7 @@ async function workflowReview(rootDir, args, options) {
|
|
|
6957
7696
|
);
|
|
6958
7697
|
}
|
|
6959
7698
|
outputReviewLedger(ledger, options);
|
|
6960
|
-
return
|
|
7699
|
+
return EXIT_OK9;
|
|
6961
7700
|
} catch (error) {
|
|
6962
7701
|
return invalidArg(
|
|
6963
7702
|
options,
|
|
@@ -7013,7 +7752,7 @@ async function workflowCreate(rootDir, args, options) {
|
|
|
7013
7752
|
error instanceof Error ? error.message : "unable to create workflow"
|
|
7014
7753
|
);
|
|
7015
7754
|
}
|
|
7016
|
-
return
|
|
7755
|
+
return EXIT_OK9;
|
|
7017
7756
|
}
|
|
7018
7757
|
async function workflowUpdate(rootDir, taskId, options) {
|
|
7019
7758
|
if (!taskId) {
|
|
@@ -7111,18 +7850,18 @@ async function workflowUpdate(rootDir, taskId, options) {
|
|
|
7111
7850
|
} else {
|
|
7112
7851
|
console.log(`Updated workflow: ${taskId}`);
|
|
7113
7852
|
}
|
|
7114
|
-
return
|
|
7853
|
+
return EXIT_OK9;
|
|
7115
7854
|
}
|
|
7116
7855
|
async function workflowList(rootDir, options) {
|
|
7117
7856
|
const workflows = await listWorkflows(rootDir);
|
|
7118
7857
|
const views = attachActiveChildren(workflows);
|
|
7119
7858
|
if (options.json) {
|
|
7120
7859
|
console.log(JSON.stringify(views, null, 2));
|
|
7121
|
-
return
|
|
7860
|
+
return EXIT_OK9;
|
|
7122
7861
|
}
|
|
7123
7862
|
if (workflows.length === 0) {
|
|
7124
7863
|
console.log("No workflows.");
|
|
7125
|
-
return
|
|
7864
|
+
return EXIT_OK9;
|
|
7126
7865
|
}
|
|
7127
7866
|
const inProgress = workflows.filter((w) => w.status === "in_progress").length;
|
|
7128
7867
|
console.log(
|
|
@@ -7132,7 +7871,7 @@ async function workflowList(rootDir, options) {
|
|
|
7132
7871
|
for (const meta of views) {
|
|
7133
7872
|
console.log(formatWorkflowRow(meta));
|
|
7134
7873
|
}
|
|
7135
|
-
return
|
|
7874
|
+
return EXIT_OK9;
|
|
7136
7875
|
}
|
|
7137
7876
|
async function workflowShow(rootDir, taskId, options) {
|
|
7138
7877
|
if (!taskId) {
|
|
@@ -7169,7 +7908,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7169
7908
|
2
|
|
7170
7909
|
)
|
|
7171
7910
|
);
|
|
7172
|
-
return
|
|
7911
|
+
return EXIT_OK9;
|
|
7173
7912
|
}
|
|
7174
7913
|
console.log(`Workflow: ${meta.taskId}`);
|
|
7175
7914
|
console.log(`Task: ${meta.task}`);
|
|
@@ -7196,7 +7935,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7196
7935
|
}
|
|
7197
7936
|
}
|
|
7198
7937
|
}
|
|
7199
|
-
return
|
|
7938
|
+
return EXIT_OK9;
|
|
7200
7939
|
}
|
|
7201
7940
|
async function workflowClean(rootDir, options) {
|
|
7202
7941
|
const workflows = await listWorkflows(rootDir);
|
|
@@ -7264,7 +8003,7 @@ async function workflowClean(rootDir, options) {
|
|
|
7264
8003
|
} else {
|
|
7265
8004
|
console.log(`Removed ${removed.length} workflow(s).`);
|
|
7266
8005
|
}
|
|
7267
|
-
return
|
|
8006
|
+
return EXIT_OK9;
|
|
7268
8007
|
}
|
|
7269
8008
|
function formatWorkflowRow(meta) {
|
|
7270
8009
|
const stepMax = maxWorkflowStep(meta.mode);
|
|
@@ -7323,7 +8062,7 @@ function ago(iso) {
|
|
|
7323
8062
|
if (hours < 24) return `${hours}h ago`;
|
|
7324
8063
|
return `${Math.floor(hours / 24)}d ago`;
|
|
7325
8064
|
}
|
|
7326
|
-
async function
|
|
8065
|
+
async function pathExists12(p) {
|
|
7327
8066
|
try {
|
|
7328
8067
|
await access6(p);
|
|
7329
8068
|
return true;
|
|
@@ -7336,8 +8075,11 @@ async function pathExists11(p) {
|
|
|
7336
8075
|
program.name("mancode").description(
|
|
7337
8076
|
"AI coding agent harness. Modes: solo, man, mamba, manteam, manps."
|
|
7338
8077
|
).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(),
|
|
8078
|
+
program.command("init").description("Initialize mancode in the current project").option("--force", "Reinstall even if already initialized").option("--yes", "Skip all confirmations (CI mode)").option("--team", "Force enable team mode (MVP-2)").option("--no-team", "Force disable team mode (MVP-2)").option("--style <name>", "Specify aesthetic style (MVP-2)").option("--platform <platforms>", "Adapters: comma-separated names or all").option("--empty", "Initialize a safe empty directory as a generic project").option("--lang <locale>", "Initialization language: zh-CN or en").action(async (options) => {
|
|
8079
|
+
const code = await init(process.cwd(), {
|
|
8080
|
+
...options,
|
|
8081
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
8082
|
+
});
|
|
7341
8083
|
process.exitCode = code;
|
|
7342
8084
|
});
|
|
7343
8085
|
program.command("install [platform]").description(
|
|
@@ -7383,6 +8125,12 @@ program.command("refresh-style").description("Refresh project profile and rescan
|
|
|
7383
8125
|
const code = await refreshStyle(process.cwd());
|
|
7384
8126
|
process.exitCode = code;
|
|
7385
8127
|
});
|
|
8128
|
+
program.command("refresh-project").description(
|
|
8129
|
+
"Refresh detected project facts after adding Git or project files"
|
|
8130
|
+
).action(async () => {
|
|
8131
|
+
const code = await refreshProject(process.cwd());
|
|
8132
|
+
process.exitCode = code;
|
|
8133
|
+
});
|
|
7386
8134
|
program.command("version").description("Show version, node version, and platform").action(() => {
|
|
7387
8135
|
version();
|
|
7388
8136
|
});
|