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/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 fs2 } from "fs";
11
- import path13 from "path";
12
- import process4 from "process";
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/codex.ts
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 = path8.join(projectRoot, ".cursor", "rules");
2567
- await mkdir6(rulesDir, { recursive: true });
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 = path8.join(rulesDir, fileName);
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 writeFile7(
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(path8.dirname(path8.dirname(rulesDir)));
2496
+ await removeLegacyCursorRules(path6.dirname(path6.dirname(rulesDir)));
2677
2497
  }
2678
2498
  async function removeCursorGeneratedRules(projectRoot) {
2679
- const rulesDir = path8.join(projectRoot, ".cursor", "rules");
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 = path8.join(
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 = path8.join(rulesDir, fileName);
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/zcode.ts
2772
- import { writeFile as writeFile8 } from "fs/promises";
2773
- import path9 from "path";
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/registry.ts
2817
- var PLATFORM_INSTALLERS = {
2818
- "claude-code": {
2819
- name: "claude-code",
2820
- displayName: "Claude Code",
2821
- capabilities: {
2822
- slashCommands: "native",
2823
- subagents: true,
2824
- hooks: true,
2825
- skills: "native"
2826
- },
2827
- install: installClaudeCode
2828
- },
2829
- cursor: {
2830
- name: "cursor",
2831
- displayName: "Cursor",
2832
- capabilities: {
2833
- slashCommands: "native",
2834
- subagents: false,
2835
- hooks: false,
2836
- skills: "rules"
2837
- },
2838
- install: installCursor
2839
- },
2840
- codex: {
2841
- name: "codex",
2842
- displayName: "Codex (ChatGPT desktop/CLI)",
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 stdout;
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 path11 from "path";
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(path11.join(projectRoot, "package.json"));
3118
+ const packageJson = await readJson2(path12.join(projectRoot, "package.json"));
3002
3119
  const [pubspec, pythonManifests] = await Promise.all([
3003
- readText(path11.join(projectRoot, "pubspec.yaml")),
3120
+ readText(path12.join(projectRoot, "pubspec.yaml")),
3004
3121
  Promise.all([
3005
- readText(path11.join(projectRoot, "pyproject.toml")),
3006
- readText(path11.join(projectRoot, "requirements.txt"))
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(path11.join(projectRoot, "src", "components", "ui"));
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(path11.join(root, candidate))).isDirectory())
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 fs } from "fs";
3233
- import path12 from "path";
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 fs.readFile(configResult.absPath, "utf-8");
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(path12.join(projectRoot, "package.json"))) {
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 = path12.join(projectRoot, name);
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 = path12.join(projectRoot, relRoot);
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 fs.readdir(dir);
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 = path12.join(dir, entry);
3658
+ const abs = path13.join(dir, entry);
3542
3659
  let info;
3543
3660
  try {
3544
- info = await fs.lstat(abs);
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 = path12.basename(dir);
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 = path12.join(projectRoot, relPath);
3701
+ const absPath = path13.join(projectRoot, relPath);
3585
3702
  if (!await pathExists2(absPath)) continue;
3586
- const content = await fs.readFile(absPath, "utf-8");
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 fs.readFile(
3630
- path12.join(projectRoot, "package.json"),
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 fs.access(p);
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 = process4.cwd(), options = {}) {
3660
- const mancodeDir = path13.join(rootDir, ".mancode");
3661
- const stateFile = path13.join(mancodeDir, "state.json");
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 reinstallSnapshots = [];
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("\u2139\uFE0F mancode already initialized.");
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(" Run `mancode init --force` to reinstall.");
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
- try {
3672
- reinstallSnapshots = await snapshotFiles([
3673
- stateFile,
3674
- path13.join(mancodeDir, "config.json"),
3675
- path13.join(mancodeDir, "project-profile.json"),
3676
- path13.join(mancodeDir, "aesthetics", "style-tokens.json")
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
- " Run mancode init in a git repository or a recognized project directory."
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("\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...");
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
- "\u26A0\uFE0F Git not found (optional). Team auto-detection will use solo defaults."
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("\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...");
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(" project manifest found, no known framework dependencies");
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
- " (No recognized manifest found, profile confidence is low)"
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 initPlatformName = options.platform ?? existingPlatform ?? DEFAULT_INIT_PLATFORM;
3735
- const installer = getPlatformInstaller(initPlatformName);
3736
- if (!installer) {
3737
- console.error(`\u2717 Unsupported platform: ${initPlatformName}`);
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, installer.name) : {};
3943
+ const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, primaryPlatform) : {};
3742
3944
  const initialMinimal = existingPreferences.minimal ?? false;
3743
- const teamModeEnabled = options.team ?? existingPreferences.forceTeamMode ?? team.isTeam;
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(" team: forced on (--team)");
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(" team: forced off (--no-team)");
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
- ` team: ${team.contributors} contributors (/manteam available)`
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: installer.name,
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 (installer.name === "claude-code") {
3987
+ if (selectedPlatforms.includes("claude-code")) {
3768
3988
  await validateClaudeCodeSettings(rootDir);
3769
3989
  }
3770
3990
  await installMancodeCore(rootDir);
3771
- await fs2.mkdir(mancodeDir, { recursive: true });
3991
+ await fs3.mkdir(mancodeDir, { recursive: true });
3772
3992
  const stateContent = `${JSON.stringify(state, null, 2)}
3773
3993
  `;
3774
- await fs2.writeFile(stateFile, stateContent, "utf-8");
3775
- await fs2.writeFile(
3776
- path13.join(mancodeDir, "project-profile.json"),
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: [installer.name],
4007
+ platforms: selectedPlatforms,
3787
4008
  platformOptions: {
3788
- [installer.name]: { minimal: initialMinimal }
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 = " .mancode/aesthetics/ # style-tokens.json (\u7A7A)";
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("\u2713 \u626B\u63CF\u5BA1\u7F8E token...");
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 = path13.join(
4033
+ const tokensPath = path14.join(
3798
4034
  mancodeDir,
3799
4035
  "aesthetics",
3800
4036
  "style-tokens.json"
3801
4037
  );
3802
- await fs2.writeFile(
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
- ` ${colorCount} colors, ${fontCount} fonts (match: high)`
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
- " UI assets detected, no reusable tokens found (match: low)"
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(" No design tokens found (match: none)");
3822
- styleLine = " .mancode/aesthetics/ # style-tokens.json (no tokens)";
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
- console.log(`\u2713 \u5B89\u88C5 ${installer.displayName} \u9002\u914D...`);
3826
- await installer.install(rootDir, {
3827
- techStack: profileStack,
3828
- uiLibrary,
3829
- projectProfile: profile,
3830
- minimal: initialMinimal,
3831
- force: options.force
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("\u2713 mancode initialized.");
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(" .mancode/state.json # \u9879\u76EE\u72B6\u6001");
3838
- console.log(" .mancode/project-profile.json # \u68C0\u6D4B\u5230\u7684\u9879\u76EE\u4E8B\u5B9E");
3839
- console.log(" .mancode/config.json # \u914D\u7F6E");
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
- " .mancode/hooks/ # SessionStart + UserPromptSubmit"
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
- printPlatformCreatedFiles(installer.name);
4139
+ for (const platform of selectedPlatforms)
4140
+ printPlatformCreatedFiles(platform, locale);
3846
4141
  console.log("");
3847
- console.log("Next:");
3848
- console.log(" mancode status # Show project state");
3849
- if (installer.name === "claude-code") {
3850
- console.log(" (Restart Claude Code to load hooks)");
3851
- } else if (installer.name === "codex") {
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
- " (If skills do not appear, restart the ChatGPT desktop app or Codex session)"
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
- if (wasInitialized) {
3859
- await restoreFiles(reinstallSnapshots);
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(`\u2717 mancode init failed: ${msg}`);
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 = path13.join(mancodeDir, "config.json");
4184
+ const configPath = path14.join(mancodeDir, "config.json");
3873
4185
  let config = {};
3874
4186
  try {
3875
- config = JSON.parse(await fs2.readFile(configPath, "utf-8"));
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 fs2.writeFile(
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 fs2.readFile(path13.join(mancodeDir, "config.json"), "utf-8")
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 (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
3912
- const platformOptions = config.platformOptions[platform];
3913
- if (isRecord3(platformOptions) && platformOptions.minimal === true) {
3914
- preferences.minimal = true;
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
- async function readExistingInitPlatform(stateFile) {
3923
- try {
3924
- const state = JSON.parse(await fs2.readFile(stateFile, "utf-8"));
3925
- return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
3926
- } catch {
3927
- return null;
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 fs2.readFile(filePath, "utf-8") };
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 fs2.rm(snapshot.filePath, { force: true });
4421
+ await fs3.rm(snapshot.filePath, { force: true });
3948
4422
  continue;
3949
4423
  }
3950
- await fs2.mkdir(path13.dirname(snapshot.filePath), { recursive: true });
3951
- await fs2.writeFile(snapshot.filePath, snapshot.content, "utf-8");
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(" .claude/settings.json # hook \u6CE8\u518C");
3963
- console.log(" .claude/skills/ # solo + MVP-2 slash skills");
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 fs2.access(p);
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 fs4 } from "fs";
3993
- import path15 from "path";
3994
- import process5 from "process";
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 fs3 } from "fs";
3998
- import path14 from "path";
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
- path14.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
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
- path14.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
4018
- path14.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
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) => path14.join(rootDir, ".cursor", "rules", 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) => path14.join(rootDir, ".cursor", "rules", 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) => path14.join(rootDir, ".cursor", "commands", `${mode}.md`)
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(path14.join(rootDir, "AGENTS.md"));
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 = path14.join(rootDir, ".agents", "skills");
4583
+ const skillsDir = path15.join(rootDir, ".agents", "skills");
4086
4584
  const hasSkills = await allManagedSkills(
4087
- MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
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
- path14.join(rootDir, "AGENTS.md"),
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 = path14.join(rootDir, ".agents", "skills");
4617
+ const skillsDir = path15.join(rootDir, ".agents", "skills");
4120
4618
  const hasSkills = await allManagedSkills(
4121
- MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
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
- path14.join(rootDir, ".github", "copilot-instructions.md")
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 = path14.join(rootDir, ".github", "prompts");
4641
+ const promptsDir = path15.join(rootDir, ".github", "prompts");
4144
4642
  const hasPrompts = await allManagedSkills(
4145
- MODE_NAMES.map((mode) => path14.join(promptsDir, `${mode}.prompt.md`)),
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
- path14.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
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
- path14.join(rootDir, ".claude", "agents", `${agent.name}.md`),
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 fs3.readFile(filePath, "utf-8"));
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 fs3.readFile(filePath, "utf-8");
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 fs3.readFile(
4212
- path14.join(rootDir, ".mancode", "config.json"),
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 fs3.readFile(filePath, "utf-8");
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 fs3.readFile(
4234
- path14.join(rootDir, ".claude", "settings.json"),
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 fs3.access(p);
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 = process5.cwd(), platform = "claude-code", options = {}) {
4280
- const stateFile = path15.join(rootDir, ".mancode", "state.json");
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 = path15.join(rootDir, ".mancode", "config.json");
4864
+ const configPath = path16.join(rootDir, ".mancode", "config.json");
4367
4865
  try {
4368
- const raw = await fs4.readFile(configPath, "utf-8");
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 = path15.join(rootDir, ".mancode", "config.json");
4879
+ const configPath = path16.join(rootDir, ".mancode", "config.json");
4382
4880
  const content = `${JSON.stringify(config, null, 2)}
4383
4881
  `;
4384
- await fs4.writeFile(configPath, content, "utf-8");
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 fs4.readFile(
4417
- path15.join(rootDir, ".mancode", "state.json"),
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 fs4.access(p);
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 fs5 } from "fs";
4440
- import path16 from "path";
4441
- import process6 from "process";
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 = process6.cwd()) {
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 fs5.readFile(
4457
- path16.join(rootDir, ".mancode", "config.json"),
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 path18 from "path";
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 path17 from "path";
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 = path17.join(projectRoot, ".mancode", "preseason-reports");
5076
+ const reportDir = path18.join(projectRoot, ".mancode", "preseason-reports");
4579
5077
  await mkdir7(reportDir, { recursive: true });
4580
- const issueDbPath = path17.join(
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
- path17.join(projectRoot, ".mancode", "preseason-report.md"),
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 = path17.join(
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 = path17.join(reportDir, `${baseName}${suffix}.md`);
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 = path17.join(current, entry);
4772
- const rel = path17.relative(root, abs);
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(path17.extname(entry)) || entry === "package.json") {
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(path17.join(projectRoot, "package.json"), "utf-8");
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 = path17.join(projectRoot, file);
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 = path17.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
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 ${path17.basename(file)}.`,
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(path17.join(projectRoot, ".gitignore"))) {
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(path17.join(projectRoot, ".editorconfig"))) {
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(path17.join(projectRoot, file));
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 = path17.join(
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(path17.join(projectRoot, file)))
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, stdout, stderr) => {
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 ?? 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 = path17.relative(projectRoot, report.reportPath);
5637
+ const reportRef = path18.relative(projectRoot, report.reportPath);
5140
5638
  const run = {
5141
- id: path17.basename(report.reportPath, ".md"),
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 = path17.join(projectRoot, ".gitignore");
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 = path17.join(projectRoot, ".editorconfig");
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 = path17.join(projectRoot, "package.json");
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(path18.join(rootDir, ".mancode", "state.json"))) {
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: ${path18.relative(rootDir, report.reportPath)}`);
5489
- console.log(`Issue DB: ${path18.relative(rootDir, report.issueDbPath)}`);
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: ${path18.relative(rootDir, remediation.issueDbPath)}`
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 = createInterface({
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-style.ts
5561
- import { promises as fs6 } from "fs";
5562
- import path19 from "path";
5563
- import process7 from "process";
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
- async function refreshStyle(rootDir = process7.cwd()) {
5567
- const stateFile = path19.join(rootDir, ".mancode", "state.json");
5568
- if (!await pathExists8(stateFile)) {
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 = path19.join(rootDir, ".mancode", "project-profile.json");
5576
- await fs6.writeFile(
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 EXIT_OK5;
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 = path19.join(
6318
+ const tokensPath = path21.join(
5598
6319
  rootDir,
5599
6320
  ".mancode",
5600
6321
  "aesthetics",
5601
6322
  "style-tokens.json"
5602
6323
  );
5603
- await fs6.mkdir(path19.dirname(tokensPath), { recursive: true });
5604
- await fs6.writeFile(
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 EXIT_OK5;
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 = path19.join(rootDir, ".mancode", "state.json");
6390
+ const statePath = path21.join(rootDir, ".mancode", "state.json");
5670
6391
  try {
5671
- const state = JSON.parse(await fs6.readFile(statePath, "utf-8"));
6392
+ const state = JSON.parse(await fs8.readFile(statePath, "utf-8"));
5672
6393
  const stack = [...profile.languages, ...profile.frameworks];
5673
- await fs6.writeFile(
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 fs6.readFile(
5693
- path19.join(rootDir, ".mancode", "config.json"),
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 pathExists8(p) {
6425
+ async function pathExists9(p) {
5705
6426
  try {
5706
- await fs6.access(p);
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 fs7 } from "fs";
5716
- import path22 from "path";
5717
- import process8 from "process";
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 path21 from "path";
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 path20 from "path";
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 (!isRecord7(value)) return false;
5849
- if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord7(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
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) => isRecord7(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
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 isRecord7(value) {
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 = path20.join(projectRoot, ".mancode", "workflows", taskId);
6602
+ const dir = path22.join(projectRoot, ".mancode", "workflows", taskId);
5882
6603
  await mkdir8(dir, { recursive: true });
5883
6604
  await writeFile10(
5884
- path20.join(dir, REVIEW_FILE),
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 path20.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
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 = path20.join(
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 = path20.posix.normalize(report.replaceAll("\\", "/"));
6639
+ const normalized = path22.posix.normalize(report.replaceAll("\\", "/"));
5919
6640
  return Boolean(
5920
- report.trim() && !path20.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
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 (isNodeError6(err) && err.code === "EEXIST") {
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 path21.join(projectRoot, ".mancode", "workflows");
6765
+ return path23.join(projectRoot, ".mancode", "workflows");
6045
6766
  }
6046
6767
  function workflowDir(projectRoot, taskId) {
6047
6768
  assertValidTaskId2(taskId);
6048
- return path21.join(workflowsRoot(projectRoot), taskId);
6769
+ return path23.join(workflowsRoot(projectRoot), taskId);
6049
6770
  }
6050
6771
  function metadataPath(projectRoot, taskId) {
6051
- return path21.join(workflowDir(projectRoot, taskId), METADATA_FILE);
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(path21.join(dir, METADATA_FILE), content, "utf-8");
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 isNodeError6(err) {
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 EXIT_OK6 = 0;
6274
- var EXIT_NOT_INITIALIZED4 = 1;
6275
- var EXIT_CORRUPT_STATE = 2;
6276
- async function status(rootDir = process8.cwd(), options = {}) {
6277
- const stateFile = path22.join(rootDir, ".mancode", "state.json");
6278
- if (!await pathExists9(stateFile)) {
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 EXIT_NOT_INITIALIZED4;
7002
+ return EXIT_NOT_INITIALIZED5;
6282
7003
  }
6283
7004
  let state;
6284
7005
  try {
6285
- const raw = await fs7.readFile(stateFile, "utf-8");
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 EXIT_CORRUPT_STATE;
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 EXIT_OK6;
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 fs7.readFile(path22.join(rootDir, "package.json"), "utf-8");
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 path22.basename(rootDir);
7116
+ return path24.basename(rootDir);
6384
7117
  }
6385
7118
  async function readConfig2(rootDir) {
6386
7119
  try {
6387
- const raw = await fs7.readFile(
6388
- path22.join(rootDir, ".mancode", "config.json"),
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: forced ? false : state.teamModeAutoDetected ?? detected.isTeam,
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
- pathExists9(path22.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
6416
- pathExists9(
6417
- path22.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
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 fs7.readFile(
6426
- path22.join(rootDir, ".claude", "settings.json"),
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 (!isRecord8(settings)) return false;
7170
+ if (!isRecord9(settings)) return false;
6437
7171
  const hooks = settings.hooks;
6438
- if (!isRecord8(hooks)) return false;
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 (!isRecord8(group) || !Array.isArray(group.hooks)) return false;
7181
+ if (!isRecord9(group) || !Array.isArray(group.hooks)) return false;
6448
7182
  return group.hooks.some((hook) => {
6449
- if (!isRecord8(hook) || typeof hook.command !== "string") return false;
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 = path22.join(
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 pathExists9(hookPath)) {
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(process8.execPath, [hookPath], {
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 stdout = "";
7225
+ let stdout2 = "";
6492
7226
  child.stdout.setEncoding("utf-8");
6493
7227
  child.stdout.on("data", (chunk) => {
6494
- stdout += chunk;
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(stdout);
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 isRecord8(value) {
7320
+ function isRecord9(value) {
6582
7321
  return typeof value === "object" && value !== null;
6583
7322
  }
6584
- async function pathExists9(p) {
7323
+ async function pathExists10(p) {
6585
7324
  try {
6586
- await fs7.access(p);
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 path23 from "path";
6596
- import process9 from "process";
6597
- var EXIT_OK7 = 0;
6598
- var EXIT_NOT_INITIALIZED5 = 1;
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 = process9.cwd(), platform, options = {}) {
6601
- const stateFile = path23.join(rootDir, ".mancode", "state.json");
6602
- if (!await pathExists10(stateFile)) {
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 EXIT_NOT_INITIALIZED5;
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 EXIT_OK7;
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(path23.join(rootDir, ".mancode"), {
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 = path23.join(rootDir, ".claude", "settings.json");
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 (isRecord9(settings.hooks)) {
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 (isRecord9(settings.skills)) {
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 (!isRecord9(value)) return value;
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 (!isRecord9(entry)) return [entry];
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 isRecord9(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
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 (!isRecord9(value)) return false;
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 = path23.join(rootDir, "AGENTS.md");
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 = path23.join(
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 = path23.join(rootDir, "AGENTS.md");
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 = path23.join(rootDir, ".mancode", "config.json");
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 (isRecord9(config.platformOptions)) {
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 isRecord9(value) {
7557
+ function isRecord10(value) {
6819
7558
  return typeof value === "object" && value !== null && !Array.isArray(value);
6820
7559
  }
6821
- async function pathExists10(p) {
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 process10 from "process";
7570
+ import process12 from "process";
6832
7571
  function version() {
6833
- const nodeVersion = process10.version;
6834
- const platform = `${process10.platform}/${process10.arch}`;
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 path24 from "path";
6843
- var EXIT_OK8 = 0;
6844
- var EXIT_NOT_INITIALIZED6 = 1;
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 pathExists11(path24.join(rootDir, ".mancode", "state.json"))) {
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 EXIT_NOT_INITIALIZED6;
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 EXIT_OK8;
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 EXIT_OK8;
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 EXIT_OK8;
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 EXIT_OK8;
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 EXIT_OK8;
7860
+ return EXIT_OK9;
7122
7861
  }
7123
7862
  if (workflows.length === 0) {
7124
7863
  console.log("No workflows.");
7125
- return EXIT_OK8;
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 EXIT_OK8;
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 EXIT_OK8;
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 EXIT_OK8;
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 EXIT_OK8;
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 pathExists11(p) {
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 <platform>", "Initial adapter platform (MVP-3)").action(async (options) => {
7340
- const code = await init(process.cwd(), options);
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
  });