mancode 0.1.0 → 0.2.1

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
@@ -8,11 +8,11 @@ import { program } from "commander";
8
8
 
9
9
  // src/commands/init.ts
10
10
  import { promises as fs2 } from "fs";
11
- import path11 from "path";
11
+ import path13 from "path";
12
12
  import process3 from "process";
13
13
 
14
14
  // src/installers/claude-code.ts
15
- import { mkdir as mkdir3, readFile as readFile3, rm, writeFile as writeFile3 } from "fs/promises";
15
+ import { access, mkdir as mkdir3, readFile as readFile2, rm, writeFile as writeFile3 } from "fs/promises";
16
16
  import path3 from "path";
17
17
 
18
18
  // src/templates/agents/scout.ts
@@ -836,6 +836,15 @@ AskUserQuestion({
836
836
  \u6536\u5230 \`/man8\` \u6216\u81EA\u52A8\u89E6\u53D1\u540E\u7ACB\u5373\u5F00\u59CB Step 1\uFF0C\u4E0D\u8981\u7B49\u7528\u6237\u786E\u8BA4\u3002`
837
837
  };
838
838
 
839
+ // src/templates/skills/principles.ts
840
+ var CORE_CODING_PRINCIPLES = `## \u94C1\u5F8B\uFF08\u6C38\u4E0D\u8FDD\u53CD\uFF09
841
+
842
+ 1. **\u4E0D\u505A\u65E0\u5173\u4FEE\u6539** \u2014 \u53EA\u6539 plan \u8303\u56F4\u5185\u7684
843
+ 2. **\u5148\u9A8C\u8BC1\u518D\u58F0\u79F0\u5B8C\u6210** \u2014 build/lint/test \u5FC5\u987B\u5B9E\u9645\u8DD1
844
+ 3. **\u5931\u8D25\u4E24\u6B21\u5FC5\u987B\u505C\u4E0B** \u2014 \u4E0D\u76F2\u8BD5
845
+ 4. **\u4E0D\u53EF\u9006\u64CD\u4F5C\u5148\u95EE** \u2014 \u5220\u9664\u3001force push\u3001worktree \u5408\u5E76
846
+ 5. **\u53EA\u89E3\u51B3\u88AB\u95EE\u5230\u7684\u95EE\u9898** \u2014 \u4E0D\u52A0\u63A8\u6D4B\u6027\u529F\u80FD`;
847
+
839
848
  // src/templates/skills/man.ts
840
849
  var MAN_SKILL = {
841
850
  name: "man",
@@ -987,13 +996,7 @@ metadata.json\uFF1A\`currentStep: 8\`
987
996
  - diff \u5927\u65F6\u53EA\u8D34 hunk \u6458\u8981\uFF0C\u8BA9 agent \u81EA\u5DF1\u7528 Read \u770B\u5B8C\u6574 diff
988
997
  - \u4E0D\u8981 dump \u6574\u4E2A\u6E90\u6587\u4EF6\u5230\u5BF9\u8BDD
989
998
 
990
- ## \u94C1\u5F8B\uFF08\u6C38\u4E0D\u8FDD\u53CD\uFF09
991
-
992
- 1. **\u4E0D\u505A\u65E0\u5173\u4FEE\u6539** \u2014 \u53EA\u6539 plan \u8303\u56F4\u5185\u7684
993
- 2. **\u5148\u9A8C\u8BC1\u518D\u58F0\u79F0\u5B8C\u6210** \u2014 build/lint/test \u5FC5\u987B\u5B9E\u9645\u8DD1
994
- 3. **\u5931\u8D25\u4E24\u6B21\u5FC5\u987B\u505C\u4E0B** \u2014 \u4E0D\u76F2\u8BD5
995
- 4. **\u4E0D\u53EF\u9006\u64CD\u4F5C\u5148\u95EE** \u2014 \u5220\u9664\u3001force push\u3001worktree \u5408\u5E76
996
- 5. **\u53EA\u89E3\u51B3\u88AB\u95EE\u5230\u7684\u95EE\u9898** \u2014 \u4E0D\u52A0\u63A8\u6D4B\u6027\u529F\u80FD
999
+ ${CORE_CODING_PRINCIPLES}
997
1000
 
998
1001
  \u6536\u5230 \`/man\` \u89E6\u53D1\u540E\u7ACB\u5373\u5F00\u59CB Step 1\u3002`
999
1002
  };
@@ -1234,7 +1237,6 @@ var MANTEAM_SKILL = {
1234
1237
  - \`.mancode/memory/spec.md\`
1235
1238
  - \`.mancode/memory/decisions.md\`
1236
1239
  - \`.mancode/team/commit-template.txt\`
1237
- - \`.mancode/team/commit-msg.sh\`\uFF08\u4EC5\u5F53\u56E2\u961F\u542F\u7528 \`mancode install claude-code --commit-hook\`\uFF09
1238
1240
  - \`.github/PULL_REQUEST_TEMPLATE.md\`
1239
1241
  2. \u5982\u679C memory \u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u5148\u521B\u5EFA\u4E0A\u8FF0 3 \u4E2A\u6587\u4EF6\uFF0C\u4F7F\u7528\u7B80\u77ED\u6807\u9898\u548C\u7A7A\u6A21\u677F\uFF0C\u4E0D\u8981\u8986\u76D6\u5DF2\u6709\u5185\u5BB9\u3002
1240
1242
  3. \u6536\u96C6\u56E2\u961F\u4E0A\u4E0B\u6587\uFF1A
@@ -1296,16 +1298,16 @@ var MANTEAM_SKILL = {
1296
1298
  - workflow: <taskId>
1297
1299
  - validation: <commands>
1298
1300
  \`\`\`
1299
- 4. \u5982\u679C\u56E2\u961F\u9700\u8981\u5F3A\u5236 commit \u89C4\u8303\uFF0C\u63D0\u9192\u53EF\u8FD0\u884C \`mancode install claude-code --commit-hook\` \u5B89\u88C5\u53EF\u9009 Git hook\uFF1B\u4E0D\u8981\u8986\u76D6\u5DF2\u6709\u81EA\u5B9A\u4E49 hook\u3002
1301
+ 4. \u5982\u679C\u56E2\u961F\u9700\u8981\u5F3A\u5236 commit \u89C4\u8303\uFF0C\u5EFA\u8BAE\u5728\u9879\u76EE\u5C42\u9762\u914D\u7F6E Git hooks\uFF08\u5982 husky \u6216 native git hooks\uFF09\uFF1B\u4E0D\u8981\u8986\u76D6\u5DF2\u6709\u81EA\u5B9A\u4E49 hook\u3002
1300
1302
  5. \u5982\u679C\u7528\u6237\u8981\u5F00 PR\uFF0C\u53C2\u8003 \`.github/PULL_REQUEST_TEMPLATE.md\` \u8F93\u51FA PR \u63CF\u8FF0\u3002
1301
1303
 
1302
- ## \u94C1\u5F8B
1304
+ ${CORE_CODING_PRINCIPLES}
1303
1305
 
1304
- 1. \u4E0D\u8986\u76D6\u672A\u786E\u8BA4\u7684\u7528\u6237/\u961F\u53CB\u6539\u52A8
1305
- 2. \u4E0D\u6539 plan \u5916\u6587\u4EF6
1306
- 3. \u5171\u4EAB\u63A5\u53E3\u53D8\u66F4\u5FC5\u987B\u5199\u517C\u5BB9\u6027\u8BF4\u660E
1307
- 4. \u5931\u8D25\u4E24\u6B21\u505C\u4E0B\u8BCA\u65AD
1308
- 5. \u4E0D\u81EA\u52A8 commit / merge / push
1306
+ ## \u56E2\u961F\u6269\u5C55\u7EAA\u5F8B
1307
+
1308
+ 1. **\u4E0D\u8986\u76D6\u672A\u786E\u8BA4\u7684\u7528\u6237/\u961F\u53CB\u6539\u52A8** \u2014 \u6539\u52A8\u524D\u540E\u90FD\u68C0\u67E5\u5DE5\u4F5C\u533A\u72B6\u6001
1309
+ 2. **\u5171\u4EAB\u63A5\u53E3\u53D8\u66F4\u5FC5\u987B\u5199\u517C\u5BB9\u6027\u8BF4\u660E** \u2014 \u5305\u542B\u8FC1\u79FB\u3001\u56DE\u6EDA\u548C\u8C03\u7528\u65B9\u5F71\u54CD
1310
+ 3. **\u4E0D\u81EA\u52A8 commit / merge / push** \u2014 \u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42
1309
1311
 
1310
1312
  \u6536\u5230 \`/manteam\` \u89E6\u53D1\u540E\u7ACB\u5373\u5F00\u59CB Step 0\u3002`
1311
1313
  };
@@ -1329,11 +1331,11 @@ ${spec.body}
1329
1331
  }
1330
1332
 
1331
1333
  // src/installers/common.ts
1332
- import { chmod, mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
1334
+ import { chmod, mkdir as mkdir2, readFile, stat, writeFile as writeFile2 } from "fs/promises";
1333
1335
  import path2 from "path";
1334
1336
 
1335
1337
  // src/system/team-memory.ts
1336
- import { mkdir, readFile, writeFile } from "fs/promises";
1338
+ import { appendFile, mkdir, writeFile } from "fs/promises";
1337
1339
  import path from "path";
1338
1340
  var MEMORY_FILES = {
1339
1341
  prd: {
@@ -1384,9 +1386,9 @@ function memoryDir(projectRoot) {
1384
1386
  }
1385
1387
  async function writeIfMissing(file, content) {
1386
1388
  try {
1387
- await readFile(file, "utf-8");
1388
- } catch {
1389
- await writeFile(file, content, "utf-8");
1389
+ await writeFile(file, content, { flag: "wx" });
1390
+ } catch (err) {
1391
+ if (err.code !== "EEXIST") throw err;
1390
1392
  }
1391
1393
  }
1392
1394
  function renderMemoryFile(title, body) {
@@ -1461,7 +1463,7 @@ async function installMancodeCore(projectRoot) {
1461
1463
  }
1462
1464
  async function readTextIfExists(filePath) {
1463
1465
  try {
1464
- return await readFile2(filePath, "utf-8");
1466
+ return await readFile(filePath, "utf-8");
1465
1467
  } catch (err) {
1466
1468
  if (isNodeError(err) && err.code === "ENOENT") return "";
1467
1469
  throw err;
@@ -1494,18 +1496,19 @@ async function validateClaudeCodeSettings(projectRoot) {
1494
1496
  async function installClaudeCode(projectRoot, options) {
1495
1497
  const claudeDir = path3.join(projectRoot, ".claude");
1496
1498
  const settings = await readClaudeSettings(claudeDir);
1499
+ const force = options.force ?? false;
1497
1500
  await installMancodeCore(projectRoot);
1498
1501
  await mkdir3(path3.join(claudeDir, "skills"), { recursive: true });
1499
1502
  await installSoloSkill(path3.join(claudeDir, "skills"));
1500
1503
  if (options.minimal) {
1501
1504
  await uninstallMvp2Skills(path3.join(claudeDir, "skills"));
1502
1505
  } else {
1503
- await installMvp2Skills(path3.join(claudeDir, "skills"));
1506
+ await installMvp2Skills(path3.join(claudeDir, "skills"), force);
1504
1507
  }
1505
1508
  if (options.minimal) {
1506
1509
  await uninstallAgents(path3.join(claudeDir, "agents"));
1507
1510
  } else {
1508
- await installAgents(path3.join(claudeDir, "agents"));
1511
+ await installAgents(path3.join(claudeDir, "agents"), force);
1509
1512
  }
1510
1513
  await updateClaudeSettings(claudeDir, settings);
1511
1514
  }
@@ -1517,9 +1520,9 @@ async function installSoloSkill(skillsDir) {
1517
1520
  });
1518
1521
  await removeLegacyFlatSkill(skillsDir, "mancode-solo");
1519
1522
  }
1520
- async function installMvp2Skills(skillsDir) {
1523
+ async function installMvp2Skills(skillsDir, force) {
1521
1524
  for (const skill of MVP2_SKILLS) {
1522
- await installProjectSkill(skillsDir, skill);
1525
+ await installProjectSkill(skillsDir, skill, force);
1523
1526
  await removeLegacyFlatSkill(skillsDir, `mancode-${skill.name}`);
1524
1527
  }
1525
1528
  }
@@ -1532,19 +1535,35 @@ async function uninstallMvp2Skills(skillsDir) {
1532
1535
  await removeLegacyFlatSkill(skillsDir, `mancode-${skill.name}`);
1533
1536
  }
1534
1537
  }
1535
- async function installProjectSkill(skillsDir, skill) {
1538
+ async function installProjectSkill(skillsDir, skill, force = true) {
1536
1539
  const skillDir = path3.join(skillsDir, skill.name);
1540
+ const skillPath = path3.join(skillDir, "SKILL.md");
1541
+ if (!force) {
1542
+ try {
1543
+ await access(skillPath);
1544
+ return;
1545
+ } catch {
1546
+ }
1547
+ }
1537
1548
  await mkdir3(skillDir, { recursive: true });
1538
- await writeFile3(path3.join(skillDir, "SKILL.md"), renderSkill(skill), "utf-8");
1549
+ await writeFile3(skillPath, renderSkill(skill), "utf-8");
1539
1550
  }
1540
1551
  async function removeLegacyFlatSkill(skillsDir, legacyName) {
1541
1552
  await rm(path3.join(skillsDir, `${legacyName}.md`), { force: true });
1542
1553
  }
1543
- async function installAgents(agentsDir) {
1554
+ async function installAgents(agentsDir, force) {
1544
1555
  await mkdir3(agentsDir, { recursive: true });
1545
1556
  for (const agent of ALL_AGENTS) {
1557
+ const agentPath = path3.join(agentsDir, `${agent.name}.md`);
1558
+ if (!force) {
1559
+ try {
1560
+ await access(agentPath);
1561
+ continue;
1562
+ } catch {
1563
+ }
1564
+ }
1546
1565
  const content = renderAgent(agent);
1547
- await writeFile3(path3.join(agentsDir, `${agent.name}.md`), content, "utf-8");
1566
+ await writeFile3(agentPath, content, "utf-8");
1548
1567
  }
1549
1568
  }
1550
1569
  async function uninstallAgents(agentsDir) {
@@ -1555,7 +1574,7 @@ async function uninstallAgents(agentsDir) {
1555
1574
  async function readClaudeSettings(claudeDir) {
1556
1575
  const settingsPath = path3.join(claudeDir, "settings.json");
1557
1576
  try {
1558
- const raw = await readFile3(settingsPath, "utf-8");
1577
+ const raw = await readFile2(settingsPath, "utf-8");
1559
1578
  return JSON.parse(raw);
1560
1579
  } catch (err) {
1561
1580
  if (isNodeError2(err) && err.code === "ENOENT") {
@@ -1610,19 +1629,20 @@ function createCommandGroup(command) {
1610
1629
  }
1611
1630
  function normalizeHookGroups(value) {
1612
1631
  if (Array.isArray(value)) {
1613
- return value.flatMap((entry) => {
1614
- const group = normalizeMatcherGroup(entry);
1615
- if (group) return [group];
1616
- const hook = normalizeHookItem(entry);
1617
- if (hook && !isMancodeHook(hook)) return [{ hooks: [hook] }];
1618
- return [];
1619
- });
1632
+ return value.flatMap(normalizeHookGroupEntry);
1620
1633
  }
1621
1634
  if (isRecord(value)) {
1622
- return Object.values(value).flatMap((entry) => {
1623
- const group = normalizeMatcherGroup(entry);
1624
- return group ? [group] : [];
1625
- });
1635
+ return Object.values(value).flatMap(normalizeHookGroupEntry);
1636
+ }
1637
+ return [];
1638
+ }
1639
+ function normalizeHookGroupEntry(entry) {
1640
+ const group = normalizeMatcherGroup(entry);
1641
+ if (group) return [group];
1642
+ const hook = normalizeHookItem(entry);
1643
+ if (hook && !isMancodeHook(hook)) return [{ hooks: [hook] }];
1644
+ if (Array.isArray(entry)) {
1645
+ return entry.flatMap(normalizeHookGroupEntry);
1626
1646
  }
1627
1647
  return [];
1628
1648
  }
@@ -1660,8 +1680,8 @@ function isNodeError2(err) {
1660
1680
  }
1661
1681
 
1662
1682
  // src/installers/codex.ts
1663
- import { writeFile as writeFile4 } from "fs/promises";
1664
- import path5 from "path";
1683
+ import { writeFile as writeFile5 } from "fs/promises";
1684
+ import path6 from "path";
1665
1685
 
1666
1686
  // src/installers/managed-block.ts
1667
1687
  var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
@@ -1677,6 +1697,11 @@ function removeManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER
1677
1697
  const merged = `${before}${after}`;
1678
1698
  return cleanUpOrphanedNewlines(merged);
1679
1699
  }
1700
+ function hasManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
1701
+ const start = findMarkerLine(existing, startMarker);
1702
+ const end = findMarkerLine(existing, endMarker);
1703
+ return start !== null && end !== null && end.start > start.start;
1704
+ }
1680
1705
  function cleanUpOrphanedNewlines(content) {
1681
1706
  const trimmed = content.replace(/\n{3,}/gu, "\n\n").replace(/\n+$/u, "\n");
1682
1707
  return trimmed || "";
@@ -1748,16 +1773,334 @@ function findMarkerLine(content, marker) {
1748
1773
  return null;
1749
1774
  }
1750
1775
 
1776
+ // src/installers/mode-skills.ts
1777
+ import { mkdir as mkdir4, readFile as readFile3, readdir, rm as rm2, writeFile as writeFile4 } from "fs/promises";
1778
+ import path4 from "path";
1779
+ var MODE_NAMES = [
1780
+ "man8",
1781
+ "man",
1782
+ "manteam",
1783
+ "manps",
1784
+ "mansolo"
1785
+ ];
1786
+ var CODEX_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:codex-skill. Do not edit this file manually. -->";
1787
+ var ZCODE_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:zcode-skill. Do not edit this file manually. -->";
1788
+ var MANCODE_AGENT_SKILL_MARKERS = [
1789
+ CODEX_SKILL_MANAGED_MARKER,
1790
+ ZCODE_SKILL_MANAGED_MARKER
1791
+ ];
1792
+ function renderModeSkill(mode, commandPrefix = "$") {
1793
+ const meta = MODE_META[mode];
1794
+ const stateMode = mode === "mansolo" ? "solo" : mode;
1795
+ const modesList = MODE_NAMES.map((m) => `${commandPrefix}${m}`).join(", ");
1796
+ return [
1797
+ meta.intro,
1798
+ "",
1799
+ "## Mode Persistence",
1800
+ "",
1801
+ "Continue following these instructions for ALL subsequent tasks in this",
1802
+ `conversation until the user switches modes (${modesList}).`,
1803
+ "",
1804
+ "When entering this mode, update `.mancode/state.json` and set",
1805
+ `"currentMode" to "${stateMode}" so the mode persists across session restarts.`,
1806
+ "If unsure whether a mode is active, read `.mancode/state.json` to verify.",
1807
+ "",
1808
+ "## Practice",
1809
+ "",
1810
+ "Before writing new code, check the YAGNI ladder:",
1811
+ "",
1812
+ "1. Reuse existing project code.",
1813
+ "2. Use the standard library.",
1814
+ "3. Use platform-native behavior.",
1815
+ "4. Use already installed dependencies.",
1816
+ "5. Prefer a one-line fix when it is enough.",
1817
+ "6. Only then write the smallest new implementation.",
1818
+ "",
1819
+ "For every task, consider: why this change, what already exists, and what",
1820
+ "is the smallest useful diff?",
1821
+ "",
1822
+ "For UI tasks, read `.mancode/aesthetics/style-tokens.json` for current",
1823
+ "design tokens before inventing new styles.",
1824
+ "",
1825
+ meta.workflow
1826
+ ].join("\n");
1827
+ }
1828
+ async function installCodexSkills(projectRoot, minimal) {
1829
+ if (minimal) {
1830
+ await removeCodexSkills(projectRoot);
1831
+ return;
1832
+ }
1833
+ const skillsDir = path4.join(projectRoot, ".agents", "skills");
1834
+ for (const mode of MODE_NAMES) {
1835
+ const modeDir = path4.join(skillsDir, mode);
1836
+ await mkdir4(modeDir, { recursive: true });
1837
+ const meta = MODE_META[mode];
1838
+ const content = [
1839
+ "---",
1840
+ `name: ${mode}`,
1841
+ `description: ${JSON.stringify(meta.description)}`,
1842
+ "---",
1843
+ "",
1844
+ CODEX_SKILL_MANAGED_MARKER,
1845
+ "",
1846
+ renderModeSkill(mode, "$"),
1847
+ ""
1848
+ ].join("\n");
1849
+ await writeManagedSkill(
1850
+ path4.join(modeDir, "SKILL.md"),
1851
+ content,
1852
+ CODEX_SKILL_MANAGED_MARKER,
1853
+ "Codex",
1854
+ MANCODE_AGENT_SKILL_MARKERS
1855
+ );
1856
+ }
1857
+ }
1858
+ async function installZcodeSkills(projectRoot, minimal) {
1859
+ if (minimal) {
1860
+ await removeZcodeSkills(projectRoot);
1861
+ return;
1862
+ }
1863
+ const skillsDir = path4.join(projectRoot, ".agents", "skills");
1864
+ for (const mode of MODE_NAMES) {
1865
+ const modeDir = path4.join(skillsDir, mode);
1866
+ await mkdir4(modeDir, { recursive: true });
1867
+ const meta = MODE_META[mode];
1868
+ const content = [
1869
+ "---",
1870
+ `name: ${mode}`,
1871
+ `description: ${JSON.stringify(meta.description)}`,
1872
+ "---",
1873
+ "",
1874
+ ZCODE_SKILL_MANAGED_MARKER,
1875
+ "",
1876
+ renderModeSkill(mode, "$"),
1877
+ ""
1878
+ ].join("\n");
1879
+ await writeManagedSkill(
1880
+ path4.join(modeDir, "SKILL.md"),
1881
+ content,
1882
+ ZCODE_SKILL_MANAGED_MARKER,
1883
+ "ZCode",
1884
+ MANCODE_AGENT_SKILL_MARKERS
1885
+ );
1886
+ }
1887
+ }
1888
+ async function installCursorCommands(projectRoot, minimal) {
1889
+ if (minimal) {
1890
+ await removeCursorCommands(projectRoot);
1891
+ return;
1892
+ }
1893
+ const commandsDir = path4.join(projectRoot, ".cursor", "commands");
1894
+ await mkdir4(commandsDir, { recursive: true });
1895
+ for (const mode of MODE_NAMES) {
1896
+ const meta = MODE_META[mode];
1897
+ const content = [
1898
+ "---",
1899
+ `description: ${JSON.stringify(meta.description)}`,
1900
+ "---",
1901
+ "",
1902
+ renderModeSkill(mode, "/"),
1903
+ ""
1904
+ ].join("\n");
1905
+ await writeFile4(path4.join(commandsDir, `${mode}.md`), content, "utf-8");
1906
+ }
1907
+ }
1908
+ async function installCopilotPrompts(projectRoot, minimal) {
1909
+ if (minimal) {
1910
+ await removeCopilotPrompts(projectRoot);
1911
+ return;
1912
+ }
1913
+ const promptsDir = path4.join(projectRoot, ".github", "prompts");
1914
+ await mkdir4(promptsDir, { recursive: true });
1915
+ for (const mode of MODE_NAMES) {
1916
+ const meta = MODE_META[mode];
1917
+ const content = [
1918
+ "---",
1919
+ "agent: 'agent'",
1920
+ `description: ${JSON.stringify(meta.description)}`,
1921
+ "---",
1922
+ "",
1923
+ renderModeSkill(mode, ""),
1924
+ ""
1925
+ ].join("\n");
1926
+ await writeFile4(
1927
+ path4.join(promptsDir, `${mode}.prompt.md`),
1928
+ content,
1929
+ "utf-8"
1930
+ );
1931
+ }
1932
+ }
1933
+ async function removeCodexSkills(projectRoot) {
1934
+ await cleanCodexManagedSkills(path4.join(projectRoot, ".agents", "skills"));
1935
+ await cleanCodexManagedSkills(path4.join(projectRoot, ".codex", "skills"));
1936
+ }
1937
+ async function cleanCodexManagedSkills(skillsDir) {
1938
+ for (const mode of MODE_NAMES) {
1939
+ const modeDir = path4.join(skillsDir, mode);
1940
+ const skillPath = path4.join(modeDir, "SKILL.md");
1941
+ if (await isManagedSkillFile(skillPath, CODEX_SKILL_MANAGED_MARKER)) {
1942
+ await rm2(skillPath, { force: true });
1943
+ await removeIfEmpty(modeDir);
1944
+ }
1945
+ }
1946
+ await removeIfEmpty(skillsDir);
1947
+ }
1948
+ async function removeZcodeSkills(projectRoot) {
1949
+ await cleanZcodeManagedSkills(path4.join(projectRoot, ".agents", "skills"));
1950
+ await cleanZcodeManagedSkills(path4.join(projectRoot, ".zcode", "skills"));
1951
+ }
1952
+ async function cleanZcodeManagedSkills(skillsDir) {
1953
+ for (const mode of MODE_NAMES) {
1954
+ const modeDir = path4.join(skillsDir, mode);
1955
+ const skillPath = path4.join(modeDir, "SKILL.md");
1956
+ if (await isManagedSkillFile(skillPath, ZCODE_SKILL_MANAGED_MARKER)) {
1957
+ await rm2(skillPath, { force: true });
1958
+ await removeIfEmpty(modeDir);
1959
+ }
1960
+ }
1961
+ await removeIfEmpty(skillsDir);
1962
+ }
1963
+ async function removeCursorCommands(projectRoot) {
1964
+ for (const mode of MODE_NAMES) {
1965
+ await rm2(path4.join(projectRoot, ".cursor", "commands", `${mode}.md`), {
1966
+ force: true
1967
+ });
1968
+ }
1969
+ }
1970
+ async function removeCopilotPrompts(projectRoot) {
1971
+ for (const mode of MODE_NAMES) {
1972
+ await rm2(
1973
+ path4.join(projectRoot, ".github", "prompts", `${mode}.prompt.md`),
1974
+ { force: true }
1975
+ );
1976
+ }
1977
+ }
1978
+ async function removeIfEmpty(dir) {
1979
+ try {
1980
+ const entries = await readdir(dir);
1981
+ if (entries.length === 0) {
1982
+ await rm2(dir, { recursive: true, force: true });
1983
+ }
1984
+ } catch {
1985
+ }
1986
+ }
1987
+ async function writeManagedSkill(skillPath, content, marker, platformLabel, recognizedMarkers = [marker]) {
1988
+ const existing = await readTextIfExists2(skillPath);
1989
+ if (existing !== null && !recognizedMarkers.some((m) => existing.includes(m))) {
1990
+ throw new Error(
1991
+ `refusing to overwrite user-authored ${platformLabel} skill: ${skillPath}`
1992
+ );
1993
+ }
1994
+ await writeFile4(skillPath, content, "utf-8");
1995
+ }
1996
+ async function isManagedSkillFile(skillPath, marker) {
1997
+ const content = await readTextIfExists2(skillPath);
1998
+ return content?.includes(marker) ?? false;
1999
+ }
2000
+ async function readTextIfExists2(filePath) {
2001
+ try {
2002
+ return await readFile3(filePath, "utf-8");
2003
+ } catch {
2004
+ return null;
2005
+ }
2006
+ }
2007
+ var MODE_META = {
2008
+ man8: {
2009
+ description: "Investigate first, then produce a plan before implementation. Use when the user asks for man8, planning, architecture, migration, integration, or risk assessment.",
2010
+ intro: "# mancode man8 \u2014 Investigate and Plan",
2011
+ workflow: [
2012
+ "## Workflow (simulate in single conversation)",
2013
+ "",
2014
+ "This platform does not provide isolated subagents. Simulate the coaching",
2015
+ "staff in sequence:",
2016
+ "",
2017
+ "1. **Scout**: Investigate the relevant code and summarize constraints,",
2018
+ " existing patterns, and dependencies.",
2019
+ "2. **Head Coach**: Write a concrete implementation plan based on the",
2020
+ " Scout findings.",
2021
+ "3. **Stop for user approval** before implementing. Do not write code",
2022
+ " until the user confirms.",
2023
+ "",
2024
+ "After user approval, switch to solo mode (update state.json currentMode",
2025
+ 'to "solo") and implement the plan.'
2026
+ ].join("\n")
2027
+ },
2028
+ man: {
2029
+ description: "Full high-risk workflow with plan, implementation, verification, and review. Use when the user asks for man or a high-risk production change.",
2030
+ intro: "# mancode man \u2014 Full Review Workflow",
2031
+ workflow: [
2032
+ "## Workflow (simulate in single conversation)",
2033
+ "",
2034
+ "This platform does not provide isolated subagents. Simulate the full",
2035
+ "coaching staff workflow in sequence:",
2036
+ "",
2037
+ "1. **Scout**: Investigate the codebase and summarize constraints.",
2038
+ "2. **Head Coach**: Propose a concrete implementation plan.",
2039
+ "3. **Wait for approval** when the task is high-risk.",
2040
+ "4. **Implement** the plan and self-test.",
2041
+ "5. **Film Analyst Offense**: Review readability, DRY, and YAGNI.",
2042
+ "6. **Fix** review findings.",
2043
+ "7. **Film Analyst Defense**: Review auth, injection, concurrency, and",
2044
+ " resource risks.",
2045
+ "8. **Summarize** verification results and remaining risk."
2046
+ ].join("\n")
2047
+ },
2048
+ manteam: {
2049
+ description: "Team-aware workflow with handoff-friendly summaries. Use when the user asks for manteam or when the task affects shared team context.",
2050
+ intro: "# mancode manteam \u2014 Team Workflow",
2051
+ workflow: [
2052
+ "## Workflow",
2053
+ "",
2054
+ "1. Read `.mancode/memory/prd.md`, `.mancode/memory/spec.md`, and",
2055
+ " `.mancode/memory/decisions.md` when relevant.",
2056
+ "2. Preserve decisions and leave handoff-friendly summaries.",
2057
+ "3. Prefer Conventional Commit style when suggesting commits.",
2058
+ "4. Simulate the coaching staff in sequence (same as man mode) but",
2059
+ " with extra emphasis on documentation and handoff clarity."
2060
+ ].join("\n")
2061
+ },
2062
+ manps: {
2063
+ description: "Run project health and cleanup scans before remediation. Use when the user asks for manps or project health cleanup.",
2064
+ intro: "# mancode manps \u2014 Project Health Scan",
2065
+ workflow: [
2066
+ "## Workflow",
2067
+ "",
2068
+ "1. Run `mancode manps [area]` to scan for tech debt, unused dependencies,",
2069
+ " security issues, dead code, and config drift.",
2070
+ "2. Supported areas: all, deps, security, dead-code, config.",
2071
+ "3. Treat scan output as triage data \u2014 do not remediate without clear",
2072
+ " user approval for each item.",
2073
+ "4. Present findings as a prioritized list with severity and recommendation."
2074
+ ].join("\n")
2075
+ },
2076
+ mansolo: {
2077
+ description: "Exit any active mancode mode and return to default solo behavior.",
2078
+ intro: "# mancode mansolo \u2014 Return to Solo",
2079
+ workflow: [
2080
+ "## Solo Behavior",
2081
+ "",
2082
+ "You are now in solo (default) mode.",
2083
+ "",
2084
+ 'Update `.mancode/state.json` and set `currentMode` to "solo".',
2085
+ "",
2086
+ "- Keep the diff narrow.",
2087
+ "- Reuse existing functions, components, styles, and dependencies.",
2088
+ "- For UI work, read `.mancode/aesthetics/style-tokens.json`.",
2089
+ "- Verify with the narrowest meaningful test, lint, build, or smoke check."
2090
+ ].join("\n")
2091
+ }
2092
+ };
2093
+
1751
2094
  // src/installers/shared-content.ts
1752
2095
  import { readFile as readFile4 } from "fs/promises";
1753
- import path4 from "path";
2096
+ import path5 from "path";
1754
2097
  async function generateSharedContent(projectRoot, options) {
1755
2098
  const [state, tokens] = await Promise.all([
1756
2099
  readJson(
1757
- path4.join(projectRoot, ".mancode", "state.json")
2100
+ path5.join(projectRoot, ".mancode", "state.json")
1758
2101
  ),
1759
2102
  readJson(
1760
- path4.join(projectRoot, ".mancode", "aesthetics", "style-tokens.json")
2103
+ path5.join(projectRoot, ".mancode", "aesthetics", "style-tokens.json")
1761
2104
  )
1762
2105
  ]);
1763
2106
  const sections = [
@@ -1833,7 +2176,14 @@ function renderAesthetics(tokens) {
1833
2176
  return lines.join("\n");
1834
2177
  }
1835
2178
  function renderModes(options) {
1836
- const commandLabel = options.capabilities.slashCommands === "native" ? "Use the named commands directly when available." : "Treat these as prompt conventions when native commands are unavailable.";
2179
+ let commandLabel;
2180
+ if (options.capabilities.skills === "agents-skills") {
2181
+ commandLabel = "Invoke mode skills with `$man8`, `$man`, `$manteam`, `$manps`, `$mansolo`.";
2182
+ } else if (options.capabilities.slashCommands === "native") {
2183
+ commandLabel = "Use the named commands directly when available.";
2184
+ } else {
2185
+ commandLabel = "Treat these as prompt conventions when native commands are unavailable.";
2186
+ }
1837
2187
  return [
1838
2188
  "## mancode Modes",
1839
2189
  "",
@@ -1897,7 +2247,7 @@ function formatValue(value) {
1897
2247
  // src/installers/codex.ts
1898
2248
  async function installCodex(projectRoot, options) {
1899
2249
  await installMancodeCore(projectRoot);
1900
- const agentsPath = path5.join(projectRoot, "AGENTS.md");
2250
+ const agentsPath = path6.join(projectRoot, "AGENTS.md");
1901
2251
  const existing = await readTextIfExists(agentsPath);
1902
2252
  const sharedContent = await generateSharedContent(projectRoot, {
1903
2253
  platform: "codex",
@@ -1906,7 +2256,7 @@ async function installCodex(projectRoot, options) {
1906
2256
  slashCommands: "partial",
1907
2257
  subagents: false,
1908
2258
  hooks: false,
1909
- skills: "single-file"
2259
+ skills: "agents-skills"
1910
2260
  },
1911
2261
  minimal: options.minimal,
1912
2262
  techStack: options.techStack,
@@ -1921,17 +2271,18 @@ async function installCodex(projectRoot, options) {
1921
2271
  sharedContent.trim(),
1922
2272
  DEFAULT_MANCODE_END_MARKER
1923
2273
  ].join("\n");
1924
- await writeFile4(agentsPath, replaceManagedBlock(existing, block), "utf-8");
2274
+ await writeFile5(agentsPath, replaceManagedBlock(existing, block), "utf-8");
2275
+ await installCodexSkills(projectRoot, options.minimal ?? false);
1925
2276
  }
1926
2277
 
1927
2278
  // src/installers/copilot.ts
1928
- import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
1929
- import path6 from "path";
2279
+ import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
2280
+ import path7 from "path";
1930
2281
  async function installCopilot(projectRoot, options) {
1931
2282
  await installMancodeCore(projectRoot);
1932
- const githubDir = path6.join(projectRoot, ".github");
1933
- await mkdir4(githubDir, { recursive: true });
1934
- const instructionsPath = path6.join(githubDir, "copilot-instructions.md");
2283
+ const githubDir = path7.join(projectRoot, ".github");
2284
+ await mkdir5(githubDir, { recursive: true });
2285
+ const instructionsPath = path7.join(githubDir, "copilot-instructions.md");
1935
2286
  const existing = await readTextIfExists(instructionsPath);
1936
2287
  const sharedContent = await generateSharedContent(projectRoot, {
1937
2288
  platform: "copilot",
@@ -1958,11 +2309,12 @@ async function installCopilot(projectRoot, options) {
1958
2309
  sections.push("", renderCopilotPromptConventions());
1959
2310
  }
1960
2311
  sections.push(DEFAULT_MANCODE_END_MARKER);
1961
- await writeFile5(
2312
+ await writeFile6(
1962
2313
  instructionsPath,
1963
2314
  replaceManagedBlock(existing, sections.join("\n")),
1964
2315
  "utf-8"
1965
2316
  );
2317
+ await installCopilotPrompts(projectRoot, options.minimal ?? false);
1966
2318
  }
1967
2319
  function renderCopilotPromptConventions() {
1968
2320
  return [
@@ -1973,13 +2325,16 @@ function renderCopilotPromptConventions() {
1973
2325
  "- man8: investigate first and produce a plan before implementation.",
1974
2326
  "- man: use a careful plan, implementation, verification, and review loop.",
1975
2327
  "- manteam: read team memory and write handoff-friendly summaries.",
1976
- "- manps: prefer `mancode manps [area]` before cleanup."
2328
+ "- manps: prefer `mancode manps [area]` before cleanup.",
2329
+ "- mansolo: exit any active mode and return to default solo behavior.",
2330
+ "",
2331
+ "These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
1977
2332
  ].join("\n");
1978
2333
  }
1979
2334
 
1980
2335
  // src/installers/cursor.ts
1981
- import { mkdir as mkdir5, rm as rm2, writeFile as writeFile6 } from "fs/promises";
1982
- import path7 from "path";
2336
+ import { mkdir as mkdir6, rm as rm3, writeFile as writeFile7 } from "fs/promises";
2337
+ import path8 from "path";
1983
2338
  var MANCODE_CURSOR_CORE_RULE_FILES = [
1984
2339
  "mancode-context.mdc",
1985
2340
  "mancode-practice.mdc",
@@ -1997,8 +2352,8 @@ var MANCODE_CURSOR_RULE_FILES = [
1997
2352
  ];
1998
2353
  async function installCursor(projectRoot, options) {
1999
2354
  await installMancodeCore(projectRoot);
2000
- const rulesDir = path7.join(projectRoot, ".cursor", "rules");
2001
- await mkdir5(rulesDir, { recursive: true });
2355
+ const rulesDir = path8.join(projectRoot, ".cursor", "rules");
2356
+ await mkdir6(rulesDir, { recursive: true });
2002
2357
  const sharedContent = await generateSharedContent(projectRoot, {
2003
2358
  platform: "cursor",
2004
2359
  displayName: "Cursor",
@@ -2037,6 +2392,7 @@ async function installCursor(projectRoot, options) {
2037
2392
  );
2038
2393
  if (options.minimal) {
2039
2394
  await removeAdvancedRules(rulesDir);
2395
+ await installCursorCommands(projectRoot, true);
2040
2396
  return;
2041
2397
  }
2042
2398
  await writeRule(
@@ -2067,6 +2423,7 @@ async function installCursor(projectRoot, options) {
2067
2423
  false,
2068
2424
  renderManpsRule()
2069
2425
  );
2426
+ await installCursorCommands(projectRoot, options.minimal ?? false);
2070
2427
  }
2071
2428
  async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
2072
2429
  const frontmatter = [
@@ -2082,11 +2439,11 @@ async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
2082
2439
 
2083
2440
  ${body.trim()}
2084
2441
  `;
2085
- await writeFile6(path7.join(rulesDir, fileName), content, "utf-8");
2442
+ await writeFile7(path8.join(rulesDir, fileName), content, "utf-8");
2086
2443
  }
2087
2444
  async function removeAdvancedRules(rulesDir) {
2088
2445
  for (const fileName of MANCODE_CURSOR_ADVANCED_RULE_FILES) {
2089
- await rm2(path7.join(rulesDir, fileName), { force: true });
2446
+ await rm3(path8.join(rulesDir, fileName), { force: true });
2090
2447
  }
2091
2448
  }
2092
2449
  function renderPracticeRule() {
@@ -2118,57 +2475,60 @@ function renderSoloRule() {
2118
2475
  ].join("\n");
2119
2476
  }
2120
2477
  function renderMan8Rule() {
2121
- return [
2122
- "# mancode man8",
2123
- "",
2124
- "Use this when the user asks for /man8, planning, investigation, architecture, migration, integration, or risk assessment.",
2125
- "",
2126
- "Cursor does not provide isolated mancode subagents. Simulate the flow in sequence:",
2127
- "",
2128
- "1. Scout: inspect the relevant code and summarize constraints.",
2129
- "2. Head Coach: write a concrete implementation plan.",
2130
- "3. Stop for user approval when the user asked for planning only."
2131
- ].join("\n");
2478
+ return renderModeSkill("man8", "/");
2132
2479
  }
2133
2480
  function renderManRule() {
2134
- return [
2135
- "# mancode man",
2136
- "",
2137
- "Use this when the user asks for /man or a high-risk production change.",
2138
- "",
2139
- "Simulate the full workflow in one conversation:",
2140
- "",
2141
- "1. Scout investigates the codebase.",
2142
- "2. Head Coach proposes the plan.",
2143
- "3. Wait for approval when required.",
2144
- "4. Implement and self-test.",
2145
- "5. Film Analyst Offense reviews readability, DRY, and YAGNI.",
2146
- "6. Fix review findings.",
2147
- "7. Film Analyst Defense reviews auth, injection, concurrency, and resource risks.",
2148
- "8. Summarize verification and remaining risk."
2149
- ].join("\n");
2481
+ return renderModeSkill("man", "/");
2150
2482
  }
2151
2483
  function renderManteamRule() {
2152
- return [
2153
- "# mancode manteam",
2154
- "",
2155
- "Use this when the user asks for /manteam or when the task affects shared team context.",
2156
- "",
2157
- "- Read `.mancode/memory/prd.md`, `.mancode/memory/spec.md`, and `.mancode/memory/decisions.md` when relevant.",
2158
- "- Preserve decisions and leave handoff-friendly summaries.",
2159
- "- Prefer Conventional Commit style when suggesting commits."
2160
- ].join("\n");
2484
+ return renderModeSkill("manteam", "/");
2161
2485
  }
2162
2486
  function renderManpsRule() {
2163
- return [
2164
- "# mancode manps",
2487
+ return renderModeSkill("manps", "/");
2488
+ }
2489
+
2490
+ // src/installers/zcode.ts
2491
+ import { writeFile as writeFile8 } from "fs/promises";
2492
+ import path9 from "path";
2493
+ var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
2494
+ var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
2495
+ async function installZcode(projectRoot, options) {
2496
+ await installMancodeCore(projectRoot);
2497
+ const agentsPath = path9.join(projectRoot, "AGENTS.md");
2498
+ const existing = await readTextIfExists(agentsPath);
2499
+ const sharedContent = await generateSharedContent(projectRoot, {
2500
+ platform: "zcode",
2501
+ displayName: "ZCode",
2502
+ capabilities: {
2503
+ slashCommands: "partial",
2504
+ subagents: false,
2505
+ hooks: false,
2506
+ skills: "agents-skills"
2507
+ },
2508
+ minimal: options.minimal,
2509
+ techStack: options.techStack,
2510
+ uiLibrary: options.uiLibrary
2511
+ });
2512
+ const block = [
2513
+ ZCODE_MANCODE_START_MARKER,
2514
+ "<!-- Managed by mancode. Do not edit this block manually. -->",
2165
2515
  "",
2166
- "Use this when the user asks for /manps or project health cleanup.",
2516
+ "# mancode Configuration",
2167
2517
  "",
2168
- "- Prefer `mancode manps [area]` before manual cleanup.",
2169
- "- Supported areas: all, deps, security, dead-code, config.",
2170
- "- Treat scan output as triage data; do not remediate without clear user approval."
2518
+ sharedContent.trim(),
2519
+ ZCODE_MANCODE_END_MARKER
2171
2520
  ].join("\n");
2521
+ await writeFile8(
2522
+ agentsPath,
2523
+ replaceManagedBlock(
2524
+ existing,
2525
+ block,
2526
+ ZCODE_MANCODE_START_MARKER,
2527
+ ZCODE_MANCODE_END_MARKER
2528
+ ),
2529
+ "utf-8"
2530
+ );
2531
+ await installZcodeSkills(projectRoot, options.minimal ?? false);
2172
2532
  }
2173
2533
 
2174
2534
  // src/installers/registry.ts
@@ -2202,7 +2562,7 @@ var PLATFORM_INSTALLERS = {
2202
2562
  slashCommands: "partial",
2203
2563
  subagents: false,
2204
2564
  hooks: false,
2205
- skills: "single-file"
2565
+ skills: "agents-skills"
2206
2566
  },
2207
2567
  install: installCodex
2208
2568
  },
@@ -2216,6 +2576,17 @@ var PLATFORM_INSTALLERS = {
2216
2576
  skills: "instructions"
2217
2577
  },
2218
2578
  install: installCopilot
2579
+ },
2580
+ zcode: {
2581
+ name: "zcode",
2582
+ displayName: "ZCode",
2583
+ capabilities: {
2584
+ slashCommands: "partial",
2585
+ subagents: false,
2586
+ hooks: false,
2587
+ skills: "agents-skills"
2588
+ },
2589
+ install: installZcode
2219
2590
  }
2220
2591
  };
2221
2592
  function getPlatformInstaller(platform) {
@@ -2234,7 +2605,7 @@ function isPlatformName(platform) {
2234
2605
 
2235
2606
  // src/system/detect-team.ts
2236
2607
  import { exec } from "child_process";
2237
- import path8 from "path";
2608
+ import path10 from "path";
2238
2609
  import process2 from "process";
2239
2610
  import { promisify } from "util";
2240
2611
  var execAsync = promisify(exec);
@@ -2256,7 +2627,7 @@ var EXEC_OPTS = {
2256
2627
  }
2257
2628
  };
2258
2629
  async function detectTeamStatus(projectRoot) {
2259
- const gitDir = path8.join(projectRoot, ".git");
2630
+ const gitDir = path10.join(projectRoot, ".git");
2260
2631
  try {
2261
2632
  await execAsync("git rev-parse --is-inside-work-tree", {
2262
2633
  cwd: projectRoot,
@@ -2318,8 +2689,8 @@ async function checkRemote(cwd) {
2318
2689
 
2319
2690
  // src/system/detect.ts
2320
2691
  import { exec as exec2 } from "child_process";
2321
- import { access, readFile as readFile5 } from "fs/promises";
2322
- import path9 from "path";
2692
+ import { access as access2, readFile as readFile5 } from "fs/promises";
2693
+ import path11 from "path";
2323
2694
  import { promisify as promisify2 } from "util";
2324
2695
  var execAsync2 = promisify2(exec2);
2325
2696
  async function detectSystemDeps() {
@@ -2344,9 +2715,9 @@ async function detectSystemDeps() {
2344
2715
  return { bash, git, jq, node };
2345
2716
  }
2346
2717
  async function detectProjectType(projectRoot) {
2347
- const pkgPath = path9.join(projectRoot, "package.json");
2718
+ const pkgPath = path11.join(projectRoot, "package.json");
2348
2719
  try {
2349
- await access(pkgPath);
2720
+ await access2(pkgPath);
2350
2721
  } catch (err) {
2351
2722
  if (err.code === "ENOENT") {
2352
2723
  return {
@@ -2378,7 +2749,7 @@ async function detectProjectType(projectRoot) {
2378
2749
  if (deps.includes("tailwindcss")) techStack.push("Tailwind CSS");
2379
2750
  let uiLibrary = null;
2380
2751
  const hasRadix = deps.some((d) => d.startsWith("@radix-ui/"));
2381
- const uiDir = path9.join(projectRoot, "src", "components", "ui");
2752
+ const uiDir = path11.join(projectRoot, "src", "components", "ui");
2382
2753
  const hasUiDir = await pathExists2(uiDir);
2383
2754
  if (hasRadix && hasUiDir) {
2384
2755
  uiLibrary = "shadcn/ui";
@@ -2393,7 +2764,7 @@ async function detectProjectType(projectRoot) {
2393
2764
  }
2394
2765
  async function pathExists2(p) {
2395
2766
  try {
2396
- await access(p);
2767
+ await access2(p);
2397
2768
  return true;
2398
2769
  } catch {
2399
2770
  return false;
@@ -2402,7 +2773,7 @@ async function pathExists2(p) {
2402
2773
 
2403
2774
  // src/system/scan-aesthetics.ts
2404
2775
  import { promises as fs } from "fs";
2405
- import path10 from "path";
2776
+ import path12 from "path";
2406
2777
  var MAX_COMPONENT_SCAN_DEPTH = 12;
2407
2778
  var MAX_COMPONENT_FILES = 2e3;
2408
2779
  async function scanAesthetics(projectRoot, uiLibrary = null) {
@@ -2462,7 +2833,7 @@ async function findTailwindConfig(projectRoot) {
2462
2833
  "tailwind.config.mjs"
2463
2834
  ];
2464
2835
  for (const name of candidates) {
2465
- const absPath = path10.join(projectRoot, name);
2836
+ const absPath = path12.join(projectRoot, name);
2466
2837
  if (await pathExists3(absPath)) {
2467
2838
  return { absPath, relPath: name };
2468
2839
  }
@@ -2682,7 +3053,7 @@ async function scanComponents(projectRoot) {
2682
3053
  const names = /* @__PURE__ */ new Set();
2683
3054
  let visitedFiles = 0;
2684
3055
  for (const relRoot of roots) {
2685
- const absRoot = path10.join(projectRoot, relRoot);
3056
+ const absRoot = path12.join(projectRoot, relRoot);
2686
3057
  if (!await pathExists3(absRoot)) continue;
2687
3058
  visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
2688
3059
  if (visitedFiles >= MAX_COMPONENT_FILES) break;
@@ -2702,7 +3073,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
2702
3073
  }
2703
3074
  for (const entry of entries) {
2704
3075
  if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
2705
- const abs = path10.join(dir, entry);
3076
+ const abs = path12.join(dir, entry);
2706
3077
  let info;
2707
3078
  try {
2708
3079
  info = await fs.lstat(abs);
@@ -2719,7 +3090,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
2719
3090
  if (isNonComponentFile(entry)) continue;
2720
3091
  let base = entry.replace(/\.(tsx|jsx|ts|js|vue|svelte)$/, "");
2721
3092
  if (base === "index") {
2722
- base = path10.basename(dir);
3093
+ base = path12.basename(dir);
2723
3094
  }
2724
3095
  if (base.startsWith(".")) continue;
2725
3096
  const componentName = toPascalCase(base);
@@ -2741,7 +3112,7 @@ async function scanCssVariables(projectRoot) {
2741
3112
  const variables = {};
2742
3113
  const sourceFiles = [];
2743
3114
  for (const relPath of candidates) {
2744
- const absPath = path10.join(projectRoot, relPath);
3115
+ const absPath = path12.join(projectRoot, relPath);
2745
3116
  if (!await pathExists3(absPath)) continue;
2746
3117
  const content = await fs.readFile(absPath, "utf-8");
2747
3118
  const found = extractCssVariables(content);
@@ -2787,7 +3158,7 @@ function toPascalCase(value) {
2787
3158
  async function hasTailwindDep(projectRoot) {
2788
3159
  try {
2789
3160
  const raw = await fs.readFile(
2790
- path10.join(projectRoot, "package.json"),
3161
+ path12.join(projectRoot, "package.json"),
2791
3162
  "utf-8"
2792
3163
  );
2793
3164
  const pkg = JSON.parse(raw);
@@ -2817,8 +3188,8 @@ var EXIT_NOT_A_PROJECT_DIR = 2;
2817
3188
  var EXIT_INIT_FAILED = 5;
2818
3189
  var DEFAULT_INIT_PLATFORM = "claude-code";
2819
3190
  async function init(rootDir = process3.cwd(), options = {}) {
2820
- const mancodeDir = path11.join(rootDir, ".mancode");
2821
- const stateFile = path11.join(mancodeDir, "state.json");
3191
+ const mancodeDir = path13.join(rootDir, ".mancode");
3192
+ const stateFile = path13.join(mancodeDir, "state.json");
2822
3193
  const wasInitialized = await pathExists4(stateFile);
2823
3194
  if (wasInitialized) {
2824
3195
  if (!options.force) {
@@ -2833,8 +3204,8 @@ async function init(rootDir = process3.cwd(), options = {}) {
2833
3204
  console.error(`\u2717 Target directory does not exist: ${rootDir}`);
2834
3205
  return EXIT_NOT_A_PROJECT_DIR;
2835
3206
  }
2836
- const isGitRepo = await pathExists4(path11.join(rootDir, ".git"));
2837
- const hasPackageJson = await pathExists4(path11.join(rootDir, "package.json"));
3207
+ const isGitRepo = await pathExists4(path13.join(rootDir, ".git"));
3208
+ const hasPackageJson = await pathExists4(path13.join(rootDir, "package.json"));
2838
3209
  if (!isGitRepo && !hasPackageJson) {
2839
3210
  console.error(`\u2717 Not a project directory: ${rootDir}`);
2840
3211
  console.error(" (No .git or package.json found)");
@@ -2918,13 +3289,16 @@ async function init(rootDir = process3.cwd(), options = {}) {
2918
3289
  await updateConfigOptions(mancodeDir, {
2919
3290
  forceTeamMode: options.team === true,
2920
3291
  defaultStyle: options.style ?? null,
2921
- platforms: [installer.name]
3292
+ platforms: [installer.name],
3293
+ platformOptions: {
3294
+ [installer.name]: { minimal: false }
3295
+ }
2922
3296
  });
2923
3297
  let styleLine = " .mancode/aesthetics/ # style-tokens.json (\u7A7A)";
2924
3298
  if (project.hasFrontend) {
2925
3299
  console.log("\u2713 \u626B\u63CF\u5BA1\u7F8E token...");
2926
3300
  const tokens = await scanAesthetics(rootDir, project.uiLibrary);
2927
- const tokensPath = path11.join(
3301
+ const tokensPath = path13.join(
2928
3302
  mancodeDir,
2929
3303
  "aesthetics",
2930
3304
  "style-tokens.json"
@@ -2984,7 +3358,7 @@ async function init(rootDir = process3.cwd(), options = {}) {
2984
3358
  }
2985
3359
  }
2986
3360
  async function updateConfigOptions(mancodeDir, patch) {
2987
- const configPath = path11.join(mancodeDir, "config.json");
3361
+ const configPath = path13.join(mancodeDir, "config.json");
2988
3362
  let config = {};
2989
3363
  try {
2990
3364
  config = JSON.parse(await fs2.readFile(configPath, "utf-8"));
@@ -3009,6 +3383,12 @@ function printPlatformCreatedFiles(platform) {
3009
3383
  }
3010
3384
  if (platform === "codex") {
3011
3385
  console.log(" AGENTS.md # Codex managed block");
3386
+ console.log(" .agents/skills/ # Codex mode skills");
3387
+ return;
3388
+ }
3389
+ if (platform === "zcode") {
3390
+ console.log(" AGENTS.md # ZCode managed block");
3391
+ console.log(" .agents/skills/ # ZCode mode skills");
3012
3392
  return;
3013
3393
  }
3014
3394
  console.log(" .github/copilot-instructions.md # Copilot instructions");
@@ -3024,17 +3404,17 @@ async function pathExists4(p) {
3024
3404
 
3025
3405
  // src/commands/install.ts
3026
3406
  import { promises as fs4 } from "fs";
3027
- import path13 from "path";
3407
+ import path15 from "path";
3028
3408
  import process4 from "process";
3029
3409
 
3030
3410
  // src/installers/platform-status.ts
3031
3411
  import { promises as fs3 } from "fs";
3032
- import path12 from "path";
3412
+ import path14 from "path";
3033
3413
  async function checkPlatformStatus(rootDir, platform, installed) {
3034
3414
  const readiness = await checkPlatformReadiness(rootDir, platform);
3035
3415
  return {
3036
3416
  installed,
3037
- ready: installed && readiness.present,
3417
+ ready: installed && readiness.ready,
3038
3418
  target: readiness.target,
3039
3419
  detail: describeStatus(installed, readiness)
3040
3420
  };
@@ -3042,11 +3422,13 @@ async function checkPlatformStatus(rootDir, platform, installed) {
3042
3422
  async function checkPlatformReadiness(rootDir, platform) {
3043
3423
  if (platform === "claude-code") {
3044
3424
  const hasSoloSkill = await pathExists5(
3045
- path12.join(rootDir, ".claude", "skills", "solo", "SKILL.md")
3425
+ path14.join(rootDir, ".claude", "skills", "solo", "SKILL.md")
3046
3426
  );
3047
3427
  const registered = await claudeHooksRegistered(rootDir);
3428
+ const present = hasSoloSkill && registered;
3048
3429
  return {
3049
- present: hasSoloSkill && registered,
3430
+ present,
3431
+ ready: present,
3050
3432
  target: ".claude/",
3051
3433
  readyDetail: "skills and hooks registered"
3052
3434
  };
@@ -3054,26 +3436,104 @@ async function checkPlatformReadiness(rootDir, platform) {
3054
3436
  if (platform === "cursor") {
3055
3437
  const present = await allPathsExist(
3056
3438
  MANCODE_CURSOR_CORE_RULE_FILES.map(
3057
- (file) => path12.join(rootDir, ".cursor", "rules", file)
3439
+ (file) => path14.join(rootDir, ".cursor", "rules", file)
3058
3440
  )
3059
3441
  );
3060
3442
  return {
3061
3443
  present,
3444
+ ready: present,
3062
3445
  target: ".cursor/rules/",
3063
3446
  readyDetail: "mancode rules present"
3064
3447
  };
3065
3448
  }
3066
3449
  if (platform === "codex") {
3450
+ const hasBlock2 = await fileHasManagedBlock(path14.join(rootDir, "AGENTS.md"));
3451
+ if (!hasBlock2) {
3452
+ return {
3453
+ present: false,
3454
+ ready: false,
3455
+ target: "AGENTS.md",
3456
+ readyDetail: "managed block missing"
3457
+ };
3458
+ }
3459
+ if (await isPlatformMinimal(rootDir, "codex")) {
3460
+ return {
3461
+ present: true,
3462
+ ready: true,
3463
+ target: "AGENTS.md",
3464
+ readyDetail: "managed block present"
3465
+ };
3466
+ }
3467
+ const skillsDir = path14.join(rootDir, ".agents", "skills");
3468
+ const hasSkills = await allManagedSkills(
3469
+ MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
3470
+ MANCODE_AGENT_SKILL_MARKERS
3471
+ );
3472
+ return {
3473
+ present: true,
3474
+ ready: hasSkills,
3475
+ target: "AGENTS.md + .agents/skills/",
3476
+ readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
3477
+ };
3478
+ }
3479
+ if (platform === "zcode") {
3480
+ const hasBlock2 = await fileHasManagedBlock(
3481
+ path14.join(rootDir, "AGENTS.md"),
3482
+ ZCODE_MANCODE_START_MARKER,
3483
+ ZCODE_MANCODE_END_MARKER
3484
+ );
3485
+ if (!hasBlock2) {
3486
+ return {
3487
+ present: false,
3488
+ ready: false,
3489
+ target: "AGENTS.md",
3490
+ readyDetail: "managed block missing"
3491
+ };
3492
+ }
3493
+ if (await isPlatformMinimal(rootDir, "zcode")) {
3494
+ return {
3495
+ present: true,
3496
+ ready: true,
3497
+ target: "AGENTS.md",
3498
+ readyDetail: "managed block present"
3499
+ };
3500
+ }
3501
+ const skillsDir = path14.join(rootDir, ".agents", "skills");
3502
+ const hasSkills = await allManagedSkills(
3503
+ MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
3504
+ MANCODE_AGENT_SKILL_MARKERS
3505
+ );
3506
+ return {
3507
+ present: true,
3508
+ ready: hasSkills,
3509
+ target: "AGENTS.md + .agents/skills/",
3510
+ readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
3511
+ };
3512
+ }
3513
+ const hasBlock = await fileHasManagedBlock(
3514
+ path14.join(rootDir, ".github", "copilot-instructions.md")
3515
+ );
3516
+ if (!hasBlock) {
3517
+ return {
3518
+ present: false,
3519
+ ready: false,
3520
+ target: ".github/copilot-instructions.md",
3521
+ readyDetail: "managed block missing"
3522
+ };
3523
+ }
3524
+ const promptsDir = path14.join(rootDir, ".github", "prompts");
3525
+ if (await pathExists5(promptsDir)) {
3526
+ const hasPrompt = await pathExists5(path14.join(promptsDir, "man8.prompt.md"));
3067
3527
  return {
3068
- present: await fileHasManagedBlock(path12.join(rootDir, "AGENTS.md")),
3069
- target: "AGENTS.md",
3070
- readyDetail: "managed block present"
3528
+ present: hasPrompt,
3529
+ ready: hasPrompt,
3530
+ target: ".github/copilot-instructions.md + .github/prompts/",
3531
+ readyDetail: hasPrompt ? "managed block and prompts present" : "prompts directory exists but man8 prompt missing"
3071
3532
  };
3072
3533
  }
3073
3534
  return {
3074
- present: await fileHasManagedBlock(
3075
- path12.join(rootDir, ".github", "copilot-instructions.md")
3076
- ),
3535
+ present: true,
3536
+ ready: true,
3077
3537
  target: ".github/copilot-instructions.md",
3078
3538
  readyDetail: "managed block present"
3079
3539
  };
@@ -3089,10 +3549,38 @@ async function allPathsExist(paths) {
3089
3549
  const results = await Promise.all(paths.map(pathExists5));
3090
3550
  return results.every(Boolean);
3091
3551
  }
3092
- async function fileHasManagedBlock(filePath) {
3552
+ async function allManagedSkills(paths, markers) {
3553
+ const results = await Promise.all(
3554
+ paths.map((filePath) => fileHasAnyMarker(filePath, markers))
3555
+ );
3556
+ return results.every(Boolean);
3557
+ }
3558
+ async function fileHasAnyMarker(filePath, needles) {
3559
+ try {
3560
+ const content = await fs3.readFile(filePath, "utf-8");
3561
+ return needles.some((needle) => content.includes(needle));
3562
+ } catch {
3563
+ return false;
3564
+ }
3565
+ }
3566
+ async function isPlatformMinimal(rootDir, platform) {
3567
+ try {
3568
+ const raw = await fs3.readFile(
3569
+ path14.join(rootDir, ".mancode", "config.json"),
3570
+ "utf-8"
3571
+ );
3572
+ const config = JSON.parse(raw);
3573
+ if (!isRecord2(config.platformOptions)) return false;
3574
+ const options = config.platformOptions[platform];
3575
+ return isRecord2(options) && options.minimal === true;
3576
+ } catch {
3577
+ return false;
3578
+ }
3579
+ }
3580
+ async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
3093
3581
  try {
3094
3582
  const content = await fs3.readFile(filePath, "utf-8");
3095
- return content.includes(DEFAULT_MANCODE_START_MARKER) && content.includes(DEFAULT_MANCODE_END_MARKER);
3583
+ return hasManagedBlock(content, startMarker, endMarker);
3096
3584
  } catch {
3097
3585
  return false;
3098
3586
  }
@@ -3100,7 +3588,7 @@ async function fileHasManagedBlock(filePath) {
3100
3588
  async function claudeHooksRegistered(rootDir) {
3101
3589
  try {
3102
3590
  const raw = await fs3.readFile(
3103
- path12.join(rootDir, ".claude", "settings.json"),
3591
+ path14.join(rootDir, ".claude", "settings.json"),
3104
3592
  "utf-8"
3105
3593
  );
3106
3594
  const settings = JSON.parse(raw);
@@ -3146,7 +3634,7 @@ var EXIT_NOT_INITIALIZED = 1;
3146
3634
  var EXIT_UNSUPPORTED_PLATFORM = 2;
3147
3635
  var EXIT_INSTALL_FAILED = 3;
3148
3636
  async function install(rootDir = process4.cwd(), platform = "claude-code", options = {}) {
3149
- const stateFile = path13.join(rootDir, ".mancode", "state.json");
3637
+ const stateFile = path15.join(rootDir, ".mancode", "state.json");
3150
3638
  if (!await pathExists6(stateFile)) {
3151
3639
  console.error("\u2717 mancode not initialized.");
3152
3640
  console.error(" Run `mancode init` first.");
@@ -3177,15 +3665,22 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
3177
3665
  if (alreadyInstalled && !options.force) {
3178
3666
  const status2 = await checkPlatformStatus(rootDir, platform, true);
3179
3667
  if (status2.ready) {
3668
+ if (!options.minimal) {
3669
+ console.log(
3670
+ `\u2139\uFE0F ${formatPlatformName(platform)} adapter already installed.`
3671
+ );
3672
+ console.log(" Run with --force to reinstall.");
3673
+ return EXIT_OK2;
3674
+ }
3675
+ console.log(
3676
+ `\u2139\uFE0F ${formatPlatformName(platform)} adapter already installed; switching to minimal install.`
3677
+ );
3678
+ console.log(" Run with --force to fully reinstall.");
3679
+ } else {
3180
3680
  console.log(
3181
- `\u2139\uFE0F ${formatPlatformName(platform)} adapter already installed.`
3681
+ `\u2139\uFE0F ${formatPlatformName(platform)} is recorded in config but not ready; repairing generated files.`
3182
3682
  );
3183
- console.log(" Run with --force to reinstall.");
3184
- return EXIT_OK2;
3185
3683
  }
3186
- console.log(
3187
- `\u2139\uFE0F ${formatPlatformName(platform)} is recorded in config but not ready; repairing generated files.`
3188
- );
3189
3684
  }
3190
3685
  console.log(`\u2713 Installing ${formatPlatformName(platform)} adapter...`);
3191
3686
  const project = await detectProjectType(rootDir);
@@ -3193,7 +3688,8 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
3193
3688
  await installer.install(rootDir, {
3194
3689
  techStack: project.techStack,
3195
3690
  uiLibrary: project.uiLibrary,
3196
- minimal: options.minimal
3691
+ minimal: options.minimal,
3692
+ force: options.force
3197
3693
  });
3198
3694
  } catch (err) {
3199
3695
  const msg = err instanceof Error ? err.message : String(err);
@@ -3206,12 +3702,20 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
3206
3702
  if (!platforms.includes(platform)) {
3207
3703
  platforms.push(platform);
3208
3704
  }
3209
- await updateConfig(rootDir, { ...config, platforms });
3705
+ await updateConfig(rootDir, {
3706
+ ...config,
3707
+ platforms,
3708
+ platformOptions: updatePlatformOptions(
3709
+ config.platformOptions,
3710
+ platform,
3711
+ options
3712
+ )
3713
+ });
3210
3714
  console.log(`\u2713 ${formatPlatformName(platform)} adapter installed.`);
3211
3715
  return EXIT_OK2;
3212
3716
  }
3213
3717
  async function readConfig(rootDir) {
3214
- const configPath = path13.join(rootDir, ".mancode", "config.json");
3718
+ const configPath = path15.join(rootDir, ".mancode", "config.json");
3215
3719
  try {
3216
3720
  const raw = await fs4.readFile(configPath, "utf-8");
3217
3721
  return { config: JSON.parse(raw), valid: true };
@@ -3226,7 +3730,7 @@ function isNodeError3(err) {
3226
3730
  return err instanceof Error && "code" in err;
3227
3731
  }
3228
3732
  async function updateConfig(rootDir, config) {
3229
- const configPath = path13.join(rootDir, ".mancode", "config.json");
3733
+ const configPath = path15.join(rootDir, ".mancode", "config.json");
3230
3734
  const content = `${JSON.stringify(config, null, 2)}
3231
3735
  `;
3232
3736
  await fs4.writeFile(configPath, content, "utf-8");
@@ -3245,10 +3749,19 @@ function withDefaultConfig(config, fallbackPlatform) {
3245
3749
  logging: isRecord3(config.logging) ? config.logging : DEFAULT_CONFIG.logging
3246
3750
  };
3247
3751
  }
3752
+ function updatePlatformOptions(value, platform, options) {
3753
+ const platformOptions = isRecord3(value) ? { ...value } : {};
3754
+ const existing = platformOptions[platform];
3755
+ platformOptions[platform] = {
3756
+ ...isRecord3(existing) ? existing : {},
3757
+ minimal: options.minimal === true
3758
+ };
3759
+ return platformOptions;
3760
+ }
3248
3761
  async function readStatePlatform(rootDir) {
3249
3762
  try {
3250
3763
  const raw = await fs4.readFile(
3251
- path13.join(rootDir, ".mancode", "state.json"),
3764
+ path15.join(rootDir, ".mancode", "state.json"),
3252
3765
  "utf-8"
3253
3766
  );
3254
3767
  const state = JSON.parse(raw);
@@ -3271,7 +3784,7 @@ function isRecord3(value) {
3271
3784
 
3272
3785
  // src/commands/list-platforms.ts
3273
3786
  import { promises as fs5 } from "fs";
3274
- import path14 from "path";
3787
+ import path16 from "path";
3275
3788
  import process5 from "process";
3276
3789
  var EXIT_OK3 = 0;
3277
3790
  async function listPlatforms(rootDir = process5.cwd()) {
@@ -3288,7 +3801,7 @@ async function listPlatforms(rootDir = process5.cwd()) {
3288
3801
  async function readInstalledPlatforms(rootDir) {
3289
3802
  try {
3290
3803
  const raw = await fs5.readFile(
3291
- path14.join(rootDir, ".mancode", "config.json"),
3804
+ path16.join(rootDir, ".mancode", "config.json"),
3292
3805
  "utf-8"
3293
3806
  );
3294
3807
  const config = JSON.parse(raw);
@@ -3312,22 +3825,24 @@ function describePlatform(platform) {
3312
3825
  }
3313
3826
 
3314
3827
  // src/commands/manps.ts
3315
- import { access as access2 } from "fs/promises";
3316
- import path16 from "path";
3828
+ import { access as access4 } from "fs/promises";
3829
+ import path18 from "path";
3317
3830
  import { createInterface } from "readline/promises";
3318
3831
 
3319
3832
  // src/system/preseason.ts
3833
+ import { execFile } from "child_process";
3320
3834
  import { createHash } from "crypto";
3321
3835
  import { existsSync, readFileSync } from "fs";
3322
3836
  import {
3837
+ access as access3,
3323
3838
  lstat,
3324
- mkdir as mkdir6,
3839
+ mkdir as mkdir7,
3325
3840
  readFile as readFile6,
3326
- readdir,
3841
+ readdir as readdir2,
3327
3842
  rename,
3328
- writeFile as writeFile7
3843
+ writeFile as writeFile9
3329
3844
  } from "fs/promises";
3330
- import path15 from "path";
3845
+ import path17 from "path";
3331
3846
  var PRESEASON_AREAS = [
3332
3847
  "all",
3333
3848
  "deps",
@@ -3356,16 +3871,60 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
3356
3871
  ]);
3357
3872
  var MAX_SCAN_DEPTH = 20;
3358
3873
  var MAX_PROJECT_FILES = 5e3;
3874
+ var DEPCRUISE_TIMEOUT_MS = 15e3;
3875
+ var DEPCRUISE_MAX_BUFFER = 4 * 1024 * 1024;
3876
+ var SCANNERS = [
3877
+ {
3878
+ name: "scripts",
3879
+ areas: ["all", "config"],
3880
+ scan: ({ pkg }) => scanScripts(pkg)
3881
+ },
3882
+ {
3883
+ name: "dependency-overlap",
3884
+ areas: ["all", "deps"],
3885
+ scan: ({ pkg }) => scanDependencyOverlap(pkg)
3886
+ },
3887
+ {
3888
+ name: "security",
3889
+ areas: ["all", "security"],
3890
+ scan: ({ pkg }) => scanSecurity(pkg)
3891
+ },
3892
+ {
3893
+ name: "todos",
3894
+ areas: ["all", "dead-code"],
3895
+ scan: ({ projectRoot, files }) => scanTodos(projectRoot, files)
3896
+ },
3897
+ {
3898
+ name: "test-gaps",
3899
+ areas: ["all", "dead-code"],
3900
+ scan: ({ files }) => scanTestGaps(files)
3901
+ },
3902
+ {
3903
+ name: "config",
3904
+ areas: ["all", "config"],
3905
+ scan: ({ projectRoot, files }) => scanConfig(projectRoot, files)
3906
+ },
3907
+ {
3908
+ name: "aesthetic-drift",
3909
+ areas: ["all"],
3910
+ scan: ({ projectRoot, files }) => scanAestheticDrift(projectRoot, files)
3911
+ },
3912
+ {
3913
+ name: "architecture",
3914
+ areas: ["all"],
3915
+ scan: ({ projectRoot }) => scanArchitecture(projectRoot)
3916
+ }
3917
+ ];
3359
3918
  async function runPreseasonScan(projectRoot, area = "all") {
3360
3919
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
3361
3920
  const normalizedArea = normalizeArea(area);
3362
3921
  const pkg = await readPackageJson(projectRoot);
3363
3922
  const needsFiles = normalizedArea === "all" || normalizedArea === "dead-code" || normalizedArea === "config";
3364
3923
  const files = needsFiles ? await listProjectFiles(projectRoot) : [];
3365
- const issues = scanArea(projectRoot, normalizedArea, pkg, files).slice(0, 20);
3366
- const reportDir = path15.join(projectRoot, ".mancode", "preseason-reports");
3367
- await mkdir6(reportDir, { recursive: true });
3368
- const issueDbPath = path15.join(
3924
+ const issues = (await scanArea(projectRoot, normalizedArea, pkg, files)).slice(0, 20);
3925
+ const reportDir = path17.join(projectRoot, ".mancode", "preseason-reports");
3926
+ await mkdir7(reportDir, { recursive: true });
3927
+ const issueDbPath = path17.join(
3369
3928
  projectRoot,
3370
3929
  ".mancode",
3371
3930
  "preseason-issues.json"
@@ -3383,9 +3942,9 @@ async function runPreseasonScan(projectRoot, area = "all") {
3383
3942
  issueDbPath
3384
3943
  };
3385
3944
  const database = await buildIssueDatabase(projectRoot, report);
3386
- await writeFile7(reportPath, renderPreseasonReport(report), "utf-8");
3387
- await writeFile7(
3388
- path15.join(projectRoot, ".mancode", "preseason-report.md"),
3945
+ await writeFile9(reportPath, renderPreseasonReport(report), "utf-8");
3946
+ await writeFile9(
3947
+ path17.join(projectRoot, ".mancode", "preseason-report.md"),
3389
3948
  renderPreseasonReport(report),
3390
3949
  "utf-8"
3391
3950
  );
@@ -3393,7 +3952,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
3393
3952
  return report;
3394
3953
  }
3395
3954
  async function runPreseasonRemediation(projectRoot, issues, options = {}) {
3396
- const issueDbPath = path15.join(
3955
+ const issueDbPath = path17.join(
3397
3956
  projectRoot,
3398
3957
  ".mancode",
3399
3958
  "preseason-issues.json"
@@ -3475,7 +4034,7 @@ async function runPreseasonRemediation(projectRoot, issues, options = {}) {
3475
4034
  write(" Decision: skipped.");
3476
4035
  }
3477
4036
  database.updatedAt = now;
3478
- await writeFile7(
4037
+ await writeFile9(
3479
4038
  issueDbPath,
3480
4039
  `${JSON.stringify(database, null, 2)}
3481
4040
  `,
@@ -3498,32 +4057,16 @@ function normalizeArea(area) {
3498
4057
  `invalid area: ${area}. Supported areas: ${PRESEASON_AREAS.join(", ")}`
3499
4058
  );
3500
4059
  }
3501
- function scanArea(projectRoot, area, pkg, files) {
3502
- switch (area) {
3503
- case "deps":
3504
- return scanDependencyOverlap(pkg);
3505
- case "security":
3506
- return scanSecurity(pkg);
3507
- case "dead-code":
3508
- return [...scanTodos(projectRoot, files), ...scanTestGaps(files)];
3509
- case "config":
3510
- return [...scanScripts(pkg), ...scanConfig(projectRoot, files)];
3511
- case "all":
3512
- return [
3513
- ...scanScripts(pkg),
3514
- ...scanDependencyOverlap(pkg),
3515
- ...scanSecurity(pkg),
3516
- ...scanTodos(projectRoot, files),
3517
- ...scanTestGaps(files),
3518
- ...scanConfig(projectRoot, files),
3519
- ...scanAestheticDrift(projectRoot, files)
3520
- ];
3521
- }
4060
+ async function scanArea(projectRoot, area, pkg, files) {
4061
+ const ctx = { projectRoot, area, pkg, files };
4062
+ const active = SCANNERS.filter((scanner) => scanner.areas.includes(area));
4063
+ const chunks = await Promise.all(active.map((scanner) => scanner.scan(ctx)));
4064
+ return chunks.flat();
3522
4065
  }
3523
4066
  async function allocateReportPath(reportDir, baseName) {
3524
4067
  for (let attempt = 0; attempt < 1e3; attempt++) {
3525
4068
  const suffix = attempt === 0 ? "" : `-${attempt + 1}`;
3526
- const candidate = path15.join(reportDir, `${baseName}${suffix}.md`);
4069
+ const candidate = path17.join(reportDir, `${baseName}${suffix}.md`);
3527
4070
  if (!existsSync(candidate)) return candidate;
3528
4071
  }
3529
4072
  throw new Error(`unable to allocate preseason report path: ${baseName}`);
@@ -3566,14 +4109,14 @@ async function walk(root, current, results, depth) {
3566
4109
  if (depth > MAX_SCAN_DEPTH || results.length >= MAX_PROJECT_FILES) return;
3567
4110
  let entries;
3568
4111
  try {
3569
- entries = await readdir(current);
4112
+ entries = await readdir2(current);
3570
4113
  } catch {
3571
4114
  return;
3572
4115
  }
3573
4116
  for (const entry of entries) {
3574
4117
  if (IGNORE_DIRS.has(entry)) continue;
3575
- const abs = path15.join(current, entry);
3576
- const rel = path15.relative(root, abs);
4118
+ const abs = path17.join(current, entry);
4119
+ const rel = path17.relative(root, abs);
3577
4120
  let info;
3578
4121
  try {
3579
4122
  info = await lstat(abs);
@@ -3583,7 +4126,7 @@ async function walk(root, current, results, depth) {
3583
4126
  if (info.isSymbolicLink()) continue;
3584
4127
  if (info.isDirectory()) {
3585
4128
  await walk(root, abs, results, depth + 1);
3586
- } else if (SOURCE_EXTENSIONS.has(path15.extname(entry)) || entry === "package.json") {
4129
+ } else if (SOURCE_EXTENSIONS.has(path17.extname(entry)) || entry === "package.json") {
3587
4130
  results.push(rel);
3588
4131
  if (results.length >= MAX_PROJECT_FILES) return;
3589
4132
  }
@@ -3591,7 +4134,7 @@ async function walk(root, current, results, depth) {
3591
4134
  }
3592
4135
  async function readPackageJson(projectRoot) {
3593
4136
  try {
3594
- const raw = await readFile6(path15.join(projectRoot, "package.json"), "utf-8");
4137
+ const raw = await readFile6(path17.join(projectRoot, "package.json"), "utf-8");
3595
4138
  return JSON.parse(raw);
3596
4139
  } catch {
3597
4140
  return null;
@@ -3684,7 +4227,7 @@ function scanTodos(projectRoot, files) {
3684
4227
  const matches = [];
3685
4228
  for (const file of files) {
3686
4229
  if (matches.length >= 7) break;
3687
- const abs = path15.join(projectRoot, file);
4230
+ const abs = path17.join(projectRoot, file);
3688
4231
  matches.push(...readTodoIssues(abs, file, matches.length));
3689
4232
  }
3690
4233
  return matches.slice(0, 7);
@@ -3721,7 +4264,7 @@ function scanTestGaps(files) {
3721
4264
  if (sourceFiles.length === 0) return [];
3722
4265
  const tests = new Set(files.filter((file) => file.startsWith("tests/")));
3723
4266
  const missing = sourceFiles.filter((file) => {
3724
- const base = path15.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
4267
+ const base = path17.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
3725
4268
  return !Array.from(tests).some((test) => test.includes(base));
3726
4269
  }).slice(0, 4);
3727
4270
  return missing.map((file, index) => ({
@@ -3730,13 +4273,13 @@ function scanTestGaps(files) {
3730
4273
  type: "tests",
3731
4274
  title: "Core source file has no obvious test",
3732
4275
  file,
3733
- detail: `No matching test file was found for ${path15.basename(file)}.`,
4276
+ detail: `No matching test file was found for ${path17.basename(file)}.`,
3734
4277
  recommendation: "Add focused coverage for the public behavior or document why this module is exercised indirectly."
3735
4278
  }));
3736
4279
  }
3737
4280
  function scanConfig(projectRoot, files) {
3738
4281
  const issues = [];
3739
- if (!files.includes(".gitignore") && !pathExistsSync(path15.join(projectRoot, ".gitignore"))) {
4282
+ if (!files.includes(".gitignore") && !pathExistsSync(path17.join(projectRoot, ".gitignore"))) {
3740
4283
  issues.push({
3741
4284
  id: "config-gitignore",
3742
4285
  severity: "P1",
@@ -3747,7 +4290,7 @@ function scanConfig(projectRoot, files) {
3747
4290
  recommendation: "Add a .gitignore that excludes dependencies, build output, coverage, and local env files."
3748
4291
  });
3749
4292
  }
3750
- if (!files.includes(".editorconfig") && !pathExistsSync(path15.join(projectRoot, ".editorconfig"))) {
4293
+ if (!files.includes(".editorconfig") && !pathExistsSync(path17.join(projectRoot, ".editorconfig"))) {
3751
4294
  issues.push({
3752
4295
  id: "config-editorconfig",
3753
4296
  severity: "P2",
@@ -3766,7 +4309,7 @@ function scanAestheticDrift(projectRoot, files) {
3766
4309
  for (const file of frontendFiles) {
3767
4310
  let content;
3768
4311
  try {
3769
- content = readFileSyncSafe(path15.join(projectRoot, file));
4312
+ content = readFileSyncSafe(path17.join(projectRoot, file));
3770
4313
  } catch {
3771
4314
  continue;
3772
4315
  }
@@ -3785,6 +4328,151 @@ function scanAestheticDrift(projectRoot, files) {
3785
4328
  }
3786
4329
  return issues;
3787
4330
  }
4331
+ async function scanArchitecture(projectRoot) {
4332
+ const localBinary = path17.join(
4333
+ projectRoot,
4334
+ "node_modules",
4335
+ ".bin",
4336
+ process.platform === "win32" ? "depcruise.cmd" : "depcruise"
4337
+ );
4338
+ const binary = pathExistsSync(localBinary) ? localBinary : "depcruise";
4339
+ const result = await runDepcruise(binary, projectRoot);
4340
+ if (result.status === "not-found") {
4341
+ if (!await hasDependencyCruiserConfig(projectRoot)) return [];
4342
+ return [architectureScannerUnavailableIssue()];
4343
+ }
4344
+ const parsed = parseDepcruiseJson(result.stdout);
4345
+ if (!parsed) {
4346
+ return [
4347
+ {
4348
+ id: "architecture-scan-skipped",
4349
+ severity: "P2",
4350
+ type: "architecture",
4351
+ title: "Architecture scan skipped",
4352
+ detail: summarizeProcessFailure(result),
4353
+ recommendation: "Check the dependency-cruiser configuration or run depcruise manually to inspect the failure."
4354
+ }
4355
+ ];
4356
+ }
4357
+ const violations = extractDepcruiseViolations(parsed);
4358
+ if (violations.length === 0) return [];
4359
+ return violations.slice(0, 5).map((violation, index) => ({
4360
+ id: `architecture-${index + 1}`,
4361
+ severity: depcruiseSeverityToPreseason(violation.severity),
4362
+ type: "architecture",
4363
+ title: `Architecture rule violation: ${violation.ruleName}`,
4364
+ file: violation.from,
4365
+ detail: `${violation.from}${violation.to ? ` -> ${violation.to}` : ""}${violation.comment ? `: ${violation.comment}` : ""}`,
4366
+ recommendation: "Respect the configured dependency boundary or update the dependency-cruiser rule if the architecture decision changed."
4367
+ }));
4368
+ }
4369
+ async function hasDependencyCruiserConfig(projectRoot) {
4370
+ const files = [
4371
+ ".dependency-cruiser.js",
4372
+ ".dependency-cruiser.cjs",
4373
+ ".dependency-cruiser.mjs",
4374
+ ".dependency-cruiser.json",
4375
+ "dependency-cruiser.config.js",
4376
+ "dependency-cruiser.config.cjs",
4377
+ "dependency-cruiser.config.mjs"
4378
+ ];
4379
+ const results = await Promise.all(
4380
+ files.map((file) => pathExists7(path17.join(projectRoot, file)))
4381
+ );
4382
+ return results.some(Boolean);
4383
+ }
4384
+ function architectureScannerUnavailableIssue() {
4385
+ return {
4386
+ id: "architecture-scanner-unavailable",
4387
+ severity: "P2",
4388
+ type: "architecture",
4389
+ title: "Architecture scanner not installed",
4390
+ detail: "dependency-cruiser config was found, but dependency-cruiser was not found in node_modules/.bin or on PATH. Architecture dependency rules cannot be checked.",
4391
+ recommendation: "Install dependency-cruiser (npm i -D dependency-cruiser) to enable architecture scanning."
4392
+ };
4393
+ }
4394
+ function runDepcruise(binary, projectRoot) {
4395
+ return new Promise((resolve) => {
4396
+ const needsShell = process.platform === "win32";
4397
+ execFile(
4398
+ binary,
4399
+ ["--output-type", "json", "."],
4400
+ {
4401
+ cwd: projectRoot,
4402
+ timeout: DEPCRUISE_TIMEOUT_MS,
4403
+ maxBuffer: DEPCRUISE_MAX_BUFFER,
4404
+ shell: needsShell
4405
+ },
4406
+ (error, stdout, stderr) => {
4407
+ if (!error) {
4408
+ resolve({
4409
+ status: "ok",
4410
+ stdout,
4411
+ stderr,
4412
+ exitCode: 0,
4413
+ timedOut: false
4414
+ });
4415
+ return;
4416
+ }
4417
+ const nodeError = error;
4418
+ if (nodeError.code === "ENOENT") {
4419
+ resolve({
4420
+ status: "not-found",
4421
+ stdout: "",
4422
+ stderr: "",
4423
+ exitCode: null,
4424
+ timedOut: false
4425
+ });
4426
+ return;
4427
+ }
4428
+ resolve({
4429
+ status: "failed",
4430
+ stdout: nodeError.stdout ?? stdout,
4431
+ stderr: nodeError.stderr ?? stderr,
4432
+ exitCode: typeof nodeError.code === "number" ? nodeError.code : null,
4433
+ timedOut: Boolean(nodeError.killed || nodeError.signal === "SIGTERM")
4434
+ });
4435
+ }
4436
+ );
4437
+ });
4438
+ }
4439
+ function parseDepcruiseJson(output) {
4440
+ if (!output.trim()) return null;
4441
+ try {
4442
+ return JSON.parse(output);
4443
+ } catch {
4444
+ return null;
4445
+ }
4446
+ }
4447
+ function extractDepcruiseViolations(value) {
4448
+ if (!isRecord4(value)) return [];
4449
+ const rawViolations = Array.isArray(value.violations) ? value.violations : isRecord4(value.summary) && Array.isArray(value.summary.violations) ? value.summary.violations : [];
4450
+ return rawViolations.map((violation) => normalizeDepcruiseViolation(violation)).filter(
4451
+ (violation) => violation !== null
4452
+ );
4453
+ }
4454
+ function normalizeDepcruiseViolation(value) {
4455
+ if (!isRecord4(value)) return null;
4456
+ const rule = isRecord4(value.rule) ? value.rule : {};
4457
+ const from = typeof value.from === "string" ? value.from : "";
4458
+ const to = typeof value.to === "string" ? value.to : void 0;
4459
+ const ruleName = typeof rule.name === "string" ? rule.name : typeof value.name === "string" ? value.name : "unnamed-rule";
4460
+ const severity = typeof rule.severity === "string" ? rule.severity : typeof value.severity === "string" ? value.severity : "warn";
4461
+ const comment = typeof value.comment === "string" ? value.comment : void 0;
4462
+ if (!from) return null;
4463
+ return { ruleName, severity, from, to, comment };
4464
+ }
4465
+ function depcruiseSeverityToPreseason(severity) {
4466
+ return severity.toLowerCase() === "error" ? "P1" : "P2";
4467
+ }
4468
+ function summarizeProcessFailure(result) {
4469
+ if (result.timedOut) {
4470
+ return `dependency-cruiser timed out after ${DEPCRUISE_TIMEOUT_MS}ms.`;
4471
+ }
4472
+ const output = `${result.stderr || result.stdout}`.trim();
4473
+ const summary = output.length > 0 ? output.slice(0, 240) : "No output.";
4474
+ return `dependency-cruiser exited with ${result.exitCode ?? "unknown"}: ${summary}`;
4475
+ }
3788
4476
  function containsHardcodedColor(content) {
3789
4477
  return content.split("\n").some(
3790
4478
  (line) => /#[0-9a-fA-F]{3,8}\b/.test(line) && !/^\s*--[a-zA-Z0-9-_]+\s*:/.test(line)
@@ -3795,9 +4483,9 @@ function inferCommands(pkg) {
3795
4483
  return ["lint", "test", "build"].filter((name) => scripts[name]).map((name) => `npm run ${name}`);
3796
4484
  }
3797
4485
  async function buildIssueDatabase(projectRoot, report) {
3798
- const reportRef = path15.relative(projectRoot, report.reportPath);
4486
+ const reportRef = path17.relative(projectRoot, report.reportPath);
3799
4487
  const run = {
3800
- id: path15.basename(report.reportPath, ".md"),
4488
+ id: path17.basename(report.reportPath, ".md"),
3801
4489
  generatedAt: report.generatedAt,
3802
4490
  area: report.area,
3803
4491
  reportPath: reportRef,
@@ -3860,7 +4548,7 @@ async function readIssueDatabase(issueDbPath) {
3860
4548
  }
3861
4549
  async function writeJsonAtomic(file, value) {
3862
4550
  const tmp = `${file}.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
3863
- await writeFile7(tmp, `${JSON.stringify(value, null, 2)}
4551
+ await writeFile9(tmp, `${JSON.stringify(value, null, 2)}
3864
4552
  `, "utf-8");
3865
4553
  await rename(tmp, file);
3866
4554
  }
@@ -3903,20 +4591,20 @@ function compareIssueRecords(a, b) {
3903
4591
  }
3904
4592
  async function applySafeRemediation(projectRoot, issue) {
3905
4593
  if (issue.id === "config-gitignore" && issue.file === ".gitignore") {
3906
- const gitignorePath = path15.join(projectRoot, ".gitignore");
4594
+ const gitignorePath = path17.join(projectRoot, ".gitignore");
3907
4595
  if (pathExistsSync(gitignorePath)) {
3908
4596
  return { applied: false };
3909
4597
  }
3910
- await writeFile7(gitignorePath, `${DEFAULT_GITIGNORE}
4598
+ await writeFile9(gitignorePath, `${DEFAULT_GITIGNORE}
3911
4599
  `, "utf-8");
3912
4600
  return { applied: true, action: "created .gitignore" };
3913
4601
  }
3914
4602
  if (issue.id === "config-editorconfig" && issue.file === ".editorconfig") {
3915
- const editorconfigPath = path15.join(projectRoot, ".editorconfig");
4603
+ const editorconfigPath = path17.join(projectRoot, ".editorconfig");
3916
4604
  if (pathExistsSync(editorconfigPath)) {
3917
4605
  return { applied: false };
3918
4606
  }
3919
- await writeFile7(editorconfigPath, `${DEFAULT_EDITORCONFIG}
4607
+ await writeFile9(editorconfigPath, `${DEFAULT_EDITORCONFIG}
3920
4608
  `, "utf-8");
3921
4609
  return { applied: true, action: "created .editorconfig" };
3922
4610
  }
@@ -3952,7 +4640,7 @@ async function inferSafePackageScript(projectRoot, scriptName) {
3952
4640
  }
3953
4641
  }
3954
4642
  async function addPackageScript(projectRoot, scriptName, script) {
3955
- const packagePath = path15.join(projectRoot, "package.json");
4643
+ const packagePath = path17.join(projectRoot, "package.json");
3956
4644
  let pkg;
3957
4645
  try {
3958
4646
  pkg = JSON.parse(await readFile6(packagePath, "utf-8"));
@@ -3965,7 +4653,7 @@ async function addPackageScript(projectRoot, scriptName, script) {
3965
4653
  ...scripts,
3966
4654
  [scriptName]: script
3967
4655
  };
3968
- await writeFile7(packagePath, `${JSON.stringify(pkg, null, 2)}
4656
+ await writeFile9(packagePath, `${JSON.stringify(pkg, null, 2)}
3969
4657
  `, "utf-8");
3970
4658
  return true;
3971
4659
  }
@@ -4044,9 +4732,20 @@ function readFileSyncSafe(file) {
4044
4732
  function pathExistsSync(file) {
4045
4733
  return existsSync(file);
4046
4734
  }
4735
+ async function pathExists7(file) {
4736
+ try {
4737
+ await access3(file);
4738
+ return true;
4739
+ } catch {
4740
+ return false;
4741
+ }
4742
+ }
4047
4743
  function isNodeError4(err) {
4048
4744
  return err instanceof Error && "code" in err;
4049
4745
  }
4746
+ function isRecord4(value) {
4747
+ return typeof value === "object" && value !== null;
4748
+ }
4050
4749
 
4051
4750
  // src/commands/manps.ts
4052
4751
  var EXIT_OK4 = 0;
@@ -4054,7 +4753,7 @@ var EXIT_NOT_INITIALIZED2 = 1;
4054
4753
  var EXIT_SCAN_FAILED = 2;
4055
4754
  var EXIT_INVALID_ARG = 3;
4056
4755
  async function manps(rootDir, area = "all", options = {}) {
4057
- if (!await pathExists7(path16.join(rootDir, ".mancode", "state.json"))) {
4756
+ if (!await pathExists8(path18.join(rootDir, ".mancode", "state.json"))) {
4058
4757
  if (options.json) {
4059
4758
  console.log(JSON.stringify({ error: "not initialized" }, null, 2));
4060
4759
  } else {
@@ -4133,8 +4832,8 @@ async function manps(rootDir, area = "all", options = {}) {
4133
4832
  console.log(
4134
4833
  `Issues: ${report.issues.length} total (P0 ${p0}, P1 ${p1}, P2 ${p2})`
4135
4834
  );
4136
- console.log(`Report: ${path16.relative(rootDir, report.reportPath)}`);
4137
- console.log(`Issue DB: ${path16.relative(rootDir, report.issueDbPath)}`);
4835
+ console.log(`Report: ${path18.relative(rootDir, report.reportPath)}`);
4836
+ console.log(`Issue DB: ${path18.relative(rootDir, report.issueDbPath)}`);
4138
4837
  if (report.issues.length > 0) {
4139
4838
  console.log("");
4140
4839
  for (const issue of report.issues.slice(0, 7)) {
@@ -4150,7 +4849,7 @@ async function manps(rootDir, area = "all", options = {}) {
4150
4849
  console.log(` Skipped: ${remediation.skipped}`);
4151
4850
  console.log(` Fixed: ${remediation.fixed}`);
4152
4851
  console.log(
4153
- ` Issue DB: ${path16.relative(rootDir, remediation.issueDbPath)}`
4852
+ ` Issue DB: ${path18.relative(rootDir, remediation.issueDbPath)}`
4154
4853
  );
4155
4854
  }
4156
4855
  return EXIT_OK4;
@@ -4196,9 +4895,9 @@ async function readStdinLines() {
4196
4895
  }
4197
4896
  return Buffer.concat(chunks).toString("utf-8").split(/\r?\n/).filter((line) => line.length > 0);
4198
4897
  }
4199
- async function pathExists7(p) {
4898
+ async function pathExists8(p) {
4200
4899
  try {
4201
- await access2(p);
4900
+ await access4(p);
4202
4901
  return true;
4203
4902
  } catch {
4204
4903
  return false;
@@ -4207,13 +4906,13 @@ async function pathExists7(p) {
4207
4906
 
4208
4907
  // src/commands/refresh-style.ts
4209
4908
  import { promises as fs6 } from "fs";
4210
- import path17 from "path";
4909
+ import path19 from "path";
4211
4910
  import process6 from "process";
4212
4911
  var EXIT_OK5 = 0;
4213
4912
  var EXIT_NOT_INITIALIZED3 = 1;
4214
4913
  async function refreshStyle(rootDir = process6.cwd()) {
4215
- const stateFile = path17.join(rootDir, ".mancode", "state.json");
4216
- if (!await pathExists8(stateFile)) {
4914
+ const stateFile = path19.join(rootDir, ".mancode", "state.json");
4915
+ if (!await pathExists9(stateFile)) {
4217
4916
  console.error("\u2717 mancode not initialized.");
4218
4917
  console.error(" Run `mancode init` first.");
4219
4918
  return EXIT_NOT_INITIALIZED3;
@@ -4225,13 +4924,13 @@ async function refreshStyle(rootDir = process6.cwd()) {
4225
4924
  }
4226
4925
  console.log("\u2713 \u626B\u63CF\u9879\u76EE\u8BBE\u8BA1 token...");
4227
4926
  const tokens = await scanAesthetics(rootDir, project.uiLibrary);
4228
- const tokensPath = path17.join(
4927
+ const tokensPath = path19.join(
4229
4928
  rootDir,
4230
4929
  ".mancode",
4231
4930
  "aesthetics",
4232
4931
  "style-tokens.json"
4233
4932
  );
4234
- await fs6.mkdir(path17.dirname(tokensPath), { recursive: true });
4933
+ await fs6.mkdir(path19.dirname(tokensPath), { recursive: true });
4235
4934
  await fs6.writeFile(
4236
4935
  tokensPath,
4237
4936
  `${JSON.stringify(tokens, null, 2)}
@@ -4295,7 +4994,7 @@ async function printStaticPlatformRefreshHint(rootDir) {
4295
4994
  async function readInstalledPlatforms2(rootDir) {
4296
4995
  try {
4297
4996
  const raw = await fs6.readFile(
4298
- path17.join(rootDir, ".mancode", "config.json"),
4997
+ path19.join(rootDir, ".mancode", "config.json"),
4299
4998
  "utf-8"
4300
4999
  );
4301
5000
  const config = JSON.parse(raw);
@@ -4306,7 +5005,7 @@ async function readInstalledPlatforms2(rootDir) {
4306
5005
  return [];
4307
5006
  }
4308
5007
  }
4309
- async function pathExists8(p) {
5008
+ async function pathExists9(p) {
4310
5009
  try {
4311
5010
  await fs6.access(p);
4312
5011
  return true;
@@ -4318,19 +5017,19 @@ async function pathExists8(p) {
4318
5017
  // src/commands/status.ts
4319
5018
  import { spawn } from "child_process";
4320
5019
  import { promises as fs7 } from "fs";
4321
- import path19 from "path";
5020
+ import path21 from "path";
4322
5021
  import process7 from "process";
4323
5022
 
4324
5023
  // src/system/workflow.ts
4325
5024
  import {
4326
- mkdir as mkdir7,
5025
+ mkdir as mkdir8,
4327
5026
  readFile as readFile7,
4328
- readdir as readdir2,
4329
- rm as rm3,
5027
+ readdir as readdir3,
5028
+ rm as rm4,
4330
5029
  stat as stat2,
4331
- writeFile as writeFile8
5030
+ writeFile as writeFile10
4332
5031
  } from "fs/promises";
4333
- import path18 from "path";
5032
+ import path20 from "path";
4334
5033
  var METADATA_FILE = "metadata.json";
4335
5034
  var SLUG_MAX = 30;
4336
5035
  var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
@@ -4347,7 +5046,7 @@ function slugify(text) {
4347
5046
  return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4348
5047
  }
4349
5048
  async function createWorkflow(projectRoot, task, mode) {
4350
- await mkdir7(workflowsRoot(projectRoot), { recursive: true });
5049
+ await mkdir8(workflowsRoot(projectRoot), { recursive: true });
4351
5050
  const taskId = await allocateTaskId(projectRoot, generateTaskId(task));
4352
5051
  const dir = workflowDir(projectRoot, taskId);
4353
5052
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4368,7 +5067,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
4368
5067
  for (let attempt = 0; attempt < 1e3; attempt++) {
4369
5068
  const taskId = attempt === 0 ? baseTaskId : `${baseTaskId}-${attempt + 1}`;
4370
5069
  try {
4371
- await mkdir7(workflowDir(projectRoot, taskId));
5070
+ await mkdir8(workflowDir(projectRoot, taskId));
4372
5071
  return taskId;
4373
5072
  } catch (err) {
4374
5073
  if (isNodeError5(err) && err.code === "EEXIST") {
@@ -4408,7 +5107,7 @@ async function listWorkflows(projectRoot) {
4408
5107
  const root = workflowsRoot(projectRoot);
4409
5108
  let entries;
4410
5109
  try {
4411
- entries = await readdir2(root);
5110
+ entries = await readdir3(root);
4412
5111
  } catch {
4413
5112
  return [];
4414
5113
  }
@@ -4428,23 +5127,23 @@ async function deleteWorkflow(projectRoot, taskId) {
4428
5127
  } catch {
4429
5128
  return false;
4430
5129
  }
4431
- await rm3(dir, { recursive: true, force: true });
5130
+ await rm4(dir, { recursive: true, force: true });
4432
5131
  return true;
4433
5132
  }
4434
5133
  function workflowsRoot(projectRoot) {
4435
- return path18.join(projectRoot, ".mancode", "workflows");
5134
+ return path20.join(projectRoot, ".mancode", "workflows");
4436
5135
  }
4437
5136
  function workflowDir(projectRoot, taskId) {
4438
5137
  assertValidTaskId(taskId);
4439
- return path18.join(workflowsRoot(projectRoot), taskId);
5138
+ return path20.join(workflowsRoot(projectRoot), taskId);
4440
5139
  }
4441
5140
  function metadataPath(projectRoot, taskId) {
4442
- return path18.join(workflowDir(projectRoot, taskId), METADATA_FILE);
5141
+ return path20.join(workflowDir(projectRoot, taskId), METADATA_FILE);
4443
5142
  }
4444
5143
  async function writeMetadata(dir, meta) {
4445
5144
  const content = `${JSON.stringify(meta, null, 2)}
4446
5145
  `;
4447
- await writeFile8(path18.join(dir, METADATA_FILE), content, "utf-8");
5146
+ await writeFile10(path20.join(dir, METADATA_FILE), content, "utf-8");
4448
5147
  }
4449
5148
  function parseWorkflowMeta(raw, taskId) {
4450
5149
  try {
@@ -4467,12 +5166,13 @@ function assertValidTaskId(taskId) {
4467
5166
  }
4468
5167
 
4469
5168
  // src/commands/status.ts
5169
+ var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
4470
5170
  var EXIT_OK6 = 0;
4471
5171
  var EXIT_NOT_INITIALIZED4 = 1;
4472
5172
  var EXIT_CORRUPT_STATE = 2;
4473
5173
  async function status(rootDir = process7.cwd(), options = {}) {
4474
- const stateFile = path19.join(rootDir, ".mancode", "state.json");
4475
- if (!await pathExists9(stateFile)) {
5174
+ const stateFile = path21.join(rootDir, ".mancode", "state.json");
5175
+ if (!await pathExists10(stateFile)) {
4476
5176
  console.error("\u2717 mancode not initialized.");
4477
5177
  console.error(" Run `mancode init` to get started.");
4478
5178
  return EXIT_NOT_INITIALIZED4;
@@ -4558,19 +5258,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
4558
5258
  }
4559
5259
  async function getProjectName(rootDir) {
4560
5260
  try {
4561
- const raw = await fs7.readFile(path19.join(rootDir, "package.json"), "utf-8");
5261
+ const raw = await fs7.readFile(path21.join(rootDir, "package.json"), "utf-8");
4562
5262
  const pkg = JSON.parse(raw);
4563
5263
  if (pkg.name && typeof pkg.name === "string") {
4564
5264
  return pkg.name;
4565
5265
  }
4566
5266
  } catch {
4567
5267
  }
4568
- return path19.basename(rootDir);
5268
+ return path21.basename(rootDir);
4569
5269
  }
4570
5270
  async function readConfig2(rootDir) {
4571
5271
  try {
4572
5272
  const raw = await fs7.readFile(
4573
- path19.join(rootDir, ".mancode", "config.json"),
5273
+ path21.join(rootDir, ".mancode", "config.json"),
4574
5274
  "utf-8"
4575
5275
  );
4576
5276
  return JSON.parse(raw);
@@ -4597,9 +5297,9 @@ function getEffectiveTeamStatus(state, config, detected) {
4597
5297
  }
4598
5298
  async function checkHooks(rootDir) {
4599
5299
  const [sessionStart, userPromptSubmit, registered] = await Promise.all([
4600
- pathExists9(path19.join(rootDir, ".mancode", "hooks", "session-start.sh")),
4601
- pathExists9(
4602
- path19.join(rootDir, ".mancode", "hooks", "user-prompt-submit.sh")
5300
+ pathExists10(path21.join(rootDir, ".mancode", "hooks", "session-start.sh")),
5301
+ pathExists10(
5302
+ path21.join(rootDir, ".mancode", "hooks", "user-prompt-submit.sh")
4603
5303
  ),
4604
5304
  isRegistered(rootDir)
4605
5305
  ]);
@@ -4608,7 +5308,7 @@ async function checkHooks(rootDir) {
4608
5308
  async function isRegistered(rootDir) {
4609
5309
  try {
4610
5310
  const raw = await fs7.readFile(
4611
- path19.join(rootDir, ".claude", "settings.json"),
5311
+ path21.join(rootDir, ".claude", "settings.json"),
4612
5312
  "utf-8"
4613
5313
  );
4614
5314
  const settings = JSON.parse(raw);
@@ -4618,9 +5318,9 @@ async function isRegistered(rootDir) {
4618
5318
  }
4619
5319
  }
4620
5320
  function hooksRegistered2(settings) {
4621
- if (!isRecord4(settings)) return false;
5321
+ if (!isRecord5(settings)) return false;
4622
5322
  const hooks = settings.hooks;
4623
- if (!isRecord4(hooks)) return false;
5323
+ if (!isRecord5(hooks)) return false;
4624
5324
  return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.sh") && hasHookCommand2(
4625
5325
  hooks.UserPromptSubmit,
4626
5326
  ".mancode/hooks/user-prompt-submit.sh"
@@ -4629,21 +5329,21 @@ function hooksRegistered2(settings) {
4629
5329
  function hasHookCommand2(value, needle) {
4630
5330
  if (!Array.isArray(value)) return false;
4631
5331
  return value.some((group) => {
4632
- if (!isRecord4(group) || !Array.isArray(group.hooks)) return false;
5332
+ if (!isRecord5(group) || !Array.isArray(group.hooks)) return false;
4633
5333
  return group.hooks.some((hook) => {
4634
- if (!isRecord4(hook) || typeof hook.command !== "string") return false;
5334
+ if (!isRecord5(hook) || typeof hook.command !== "string") return false;
4635
5335
  return hook.command.includes(needle);
4636
5336
  });
4637
5337
  });
4638
5338
  }
4639
5339
  async function estimateHookInjection(rootDir) {
4640
- const hookPath = path19.join(
5340
+ const hookPath = path21.join(
4641
5341
  rootDir,
4642
5342
  ".mancode",
4643
5343
  "hooks",
4644
5344
  "user-prompt-submit.sh"
4645
5345
  );
4646
- if (!await pathExists9(hookPath)) {
5346
+ if (!await pathExists10(hookPath)) {
4647
5347
  return { tokens: 0, cap: 800 };
4648
5348
  }
4649
5349
  try {
@@ -4662,15 +5362,28 @@ function runHookEstimate(rootDir, hookPath) {
4662
5362
  cwd: rootDir,
4663
5363
  stdio: ["pipe", "pipe", "ignore"]
4664
5364
  });
5365
+ let settled = false;
5366
+ const finish = (fn) => {
5367
+ if (settled) return;
5368
+ settled = true;
5369
+ clearTimeout(timeout);
5370
+ fn();
5371
+ };
5372
+ const timeout = setTimeout(() => {
5373
+ child.kill();
5374
+ finish(() => reject(new Error("hook estimate timed out")));
5375
+ }, HOOK_ESTIMATE_TIMEOUT_MS);
4665
5376
  let stdout = "";
4666
5377
  child.stdout.setEncoding("utf-8");
4667
5378
  child.stdout.on("data", (chunk) => {
4668
5379
  stdout += chunk;
4669
5380
  });
4670
- child.on("error", reject);
5381
+ child.on("error", (err) => finish(() => reject(err)));
4671
5382
  child.on("close", (code) => {
4672
- if (code === 0) resolve(stdout);
4673
- else reject(new Error(`hook exited with ${code}`));
5383
+ finish(() => {
5384
+ if (code === 0) resolve(stdout);
5385
+ else reject(new Error(`hook exited with ${code}`));
5386
+ });
4674
5387
  });
4675
5388
  child.stdin.end(
4676
5389
  JSON.stringify({ prompt: "design a button component with tailwind css" })
@@ -4733,10 +5446,10 @@ function formatTeamStatus(team) {
4733
5446
  function workflowStepMax(mode) {
4734
5447
  return mode === "man8" ? 3 : 8;
4735
5448
  }
4736
- function isRecord4(value) {
5449
+ function isRecord5(value) {
4737
5450
  return typeof value === "object" && value !== null;
4738
5451
  }
4739
- async function pathExists9(p) {
5452
+ async function pathExists10(p) {
4740
5453
  try {
4741
5454
  await fs7.access(p);
4742
5455
  return true;
@@ -4746,8 +5459,8 @@ async function pathExists9(p) {
4746
5459
  }
4747
5460
 
4748
5461
  // src/commands/uninstall.ts
4749
- import { access as access3, readFile as readFile8, rm as rm4, writeFile as writeFile9 } from "fs/promises";
4750
- import path20 from "path";
5462
+ import { access as access5, readFile as readFile8, rm as rm5, writeFile as writeFile11 } from "fs/promises";
5463
+ import path22 from "path";
4751
5464
  import process8 from "process";
4752
5465
  var EXIT_OK7 = 0;
4753
5466
  var EXIT_NOT_INITIALIZED5 = 1;
@@ -4768,8 +5481,8 @@ var CLAUDE_AGENT_FILES = [
4768
5481
  "plan-coach.md"
4769
5482
  ];
4770
5483
  async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
4771
- const stateFile = path20.join(rootDir, ".mancode", "state.json");
4772
- if (!await pathExists10(stateFile)) {
5484
+ const stateFile = path22.join(rootDir, ".mancode", "state.json");
5485
+ if (!await pathExists11(stateFile)) {
4773
5486
  console.error("\u2717 mancode not initialized.");
4774
5487
  console.error(" Run `mancode init` first.");
4775
5488
  return EXIT_NOT_INITIALIZED5;
@@ -4780,7 +5493,7 @@ async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
4780
5493
  if (!installer) {
4781
5494
  console.error(`\u2717 Unsupported platform: ${platform}`);
4782
5495
  console.error(
4783
- " Supported platforms: claude-code, cursor, codex, copilot"
5496
+ ` Supported platforms: ${getPlatformInstallers().map((item) => item.name).join(", ")}`
4784
5497
  );
4785
5498
  return EXIT_UNSUPPORTED_PLATFORM2;
4786
5499
  }
@@ -4809,34 +5522,36 @@ async function uninstallPlatform(rootDir, platform) {
4809
5522
  await uninstallCodex(rootDir);
4810
5523
  } else if (platform === "copilot") {
4811
5524
  await uninstallCopilot(rootDir);
5525
+ } else if (platform === "zcode") {
5526
+ await uninstallZcode(rootDir);
4812
5527
  }
4813
5528
  await removeFromConfig(rootDir, platform);
4814
5529
  }
4815
5530
  async function uninstallAll(rootDir) {
4816
5531
  console.log("\u2713 Removing all platform adapters...");
4817
- for (const p of ["claude-code", "cursor", "codex", "copilot"]) {
5532
+ for (const p of getPlatformInstallers().map((platform) => platform.name)) {
4818
5533
  await uninstallPlatform(rootDir, p);
4819
5534
  }
4820
5535
  console.log("\u2713 Removing .mancode/ directory...");
4821
- await rm4(path20.join(rootDir, ".mancode"), {
5536
+ await rm5(path22.join(rootDir, ".mancode"), {
4822
5537
  recursive: true,
4823
5538
  force: true
4824
5539
  });
4825
5540
  }
4826
5541
  async function uninstallClaudeCode(rootDir) {
4827
- const claudeDir = path20.join(rootDir, ".claude");
4828
- const skillsDir = path20.join(claudeDir, "skills");
4829
- const agentsDir = path20.join(claudeDir, "agents");
5542
+ const claudeDir = path22.join(rootDir, ".claude");
5543
+ const skillsDir = path22.join(claudeDir, "skills");
5544
+ const agentsDir = path22.join(claudeDir, "agents");
4830
5545
  for (const skillDir of CLAUDE_SKILL_DIRS) {
4831
- await rm4(path20.join(skillsDir, skillDir), { recursive: true, force: true });
5546
+ await rm5(path22.join(skillsDir, skillDir), { recursive: true, force: true });
4832
5547
  }
4833
5548
  for (const agentFile of CLAUDE_AGENT_FILES) {
4834
- await rm4(path20.join(agentsDir, agentFile), { force: true });
5549
+ await rm5(path22.join(agentsDir, agentFile), { force: true });
4835
5550
  }
4836
5551
  await cleanClaudeSettings(rootDir);
4837
5552
  }
4838
5553
  async function cleanClaudeSettings(rootDir) {
4839
- const settingsPath = path20.join(rootDir, ".claude", "settings.json");
5554
+ const settingsPath = path22.join(rootDir, ".claude", "settings.json");
4840
5555
  let content;
4841
5556
  try {
4842
5557
  content = await readFile8(settingsPath, "utf-8");
@@ -4849,18 +5564,19 @@ async function cleanClaudeSettings(rootDir) {
4849
5564
  } catch {
4850
5565
  return;
4851
5566
  }
4852
- if (!isRecord5(settings.hooks)) return;
5567
+ if (!isRecord6(settings.hooks)) return;
4853
5568
  const cleanedHooks = {};
4854
5569
  for (const [event, groups] of Object.entries(settings.hooks)) {
4855
5570
  if (!Array.isArray(groups)) {
4856
5571
  cleanedHooks[event] = groups;
4857
5572
  continue;
4858
5573
  }
4859
- const filtered = groups.filter((group) => {
4860
- if (!isRecord5(group) || !Array.isArray(group.hooks)) return true;
4861
- return !group.hooks.some(
4862
- (hook) => isRecord5(hook) && typeof hook.command === "string" && hook.command.includes(".mancode/hooks/")
5574
+ const filtered = groups.flatMap((group) => {
5575
+ if (!isRecord6(group) || !Array.isArray(group.hooks)) return [group];
5576
+ const hooks = group.hooks.filter(
5577
+ (hook) => !(isRecord6(hook) && typeof hook.command === "string" && hook.command.includes(".mancode/hooks/"))
4863
5578
  );
5579
+ return hooks.length > 0 ? [{ ...group, hooks }] : [];
4864
5580
  });
4865
5581
  if (filtered.length > 0) {
4866
5582
  cleanedHooks[event] = filtered;
@@ -4871,7 +5587,7 @@ async function cleanClaudeSettings(rootDir) {
4871
5587
  } else {
4872
5588
  settings.hooks = void 0;
4873
5589
  }
4874
- await writeFile9(
5590
+ await writeFile11(
4875
5591
  settingsPath,
4876
5592
  `${JSON.stringify(settings, null, 2)}
4877
5593
  `,
@@ -4879,27 +5595,31 @@ async function cleanClaudeSettings(rootDir) {
4879
5595
  );
4880
5596
  }
4881
5597
  async function uninstallCursor(rootDir) {
4882
- const rulesDir = path20.join(rootDir, ".cursor", "rules");
5598
+ const rulesDir = path22.join(rootDir, ".cursor", "rules");
4883
5599
  for (const file of MANCODE_CURSOR_RULE_FILES) {
4884
- await rm4(path20.join(rulesDir, file), { force: true });
5600
+ await rm5(path22.join(rulesDir, file), { force: true });
4885
5601
  }
5602
+ await removeCursorCommands(rootDir);
4886
5603
  }
4887
5604
  async function uninstallCodex(rootDir) {
4888
- const agentsPath = path20.join(rootDir, "AGENTS.md");
5605
+ const agentsPath = path22.join(rootDir, "AGENTS.md");
4889
5606
  try {
4890
5607
  const content = await readFile8(agentsPath, "utf-8");
4891
5608
  const cleaned = removeManagedBlock(content);
4892
5609
  if (cleaned.trim()) {
4893
- await writeFile9(agentsPath, `${cleaned}
5610
+ await writeFile11(agentsPath, `${cleaned}
4894
5611
  `, "utf-8");
4895
5612
  } else {
4896
- await rm4(agentsPath, { force: true });
5613
+ await rm5(agentsPath, { force: true });
4897
5614
  }
4898
5615
  } catch {
4899
5616
  }
5617
+ if (!await otherAgentSkillsPlatformActive(rootDir, "codex")) {
5618
+ await removeCodexSkills(rootDir);
5619
+ }
4900
5620
  }
4901
5621
  async function uninstallCopilot(rootDir) {
4902
- const instructionsPath = path20.join(
5622
+ const instructionsPath = path22.join(
4903
5623
  rootDir,
4904
5624
  ".github",
4905
5625
  "copilot-instructions.md"
@@ -4908,22 +5628,52 @@ async function uninstallCopilot(rootDir) {
4908
5628
  const content = await readFile8(instructionsPath, "utf-8");
4909
5629
  const cleaned = removeManagedBlock(content);
4910
5630
  if (cleaned.trim()) {
4911
- await writeFile9(instructionsPath, `${cleaned}
5631
+ await writeFile11(instructionsPath, `${cleaned}
5632
+ `, "utf-8");
5633
+ } else {
5634
+ await rm5(instructionsPath, { force: true });
5635
+ }
5636
+ } catch {
5637
+ }
5638
+ await removeCopilotPrompts(rootDir);
5639
+ }
5640
+ async function uninstallZcode(rootDir) {
5641
+ const agentsPath = path22.join(rootDir, "AGENTS.md");
5642
+ try {
5643
+ const content = await readFile8(agentsPath, "utf-8");
5644
+ const cleaned = removeManagedBlock(
5645
+ content,
5646
+ ZCODE_MANCODE_START_MARKER,
5647
+ ZCODE_MANCODE_END_MARKER
5648
+ );
5649
+ if (cleaned.trim()) {
5650
+ await writeFile11(agentsPath, `${cleaned}
4912
5651
  `, "utf-8");
4913
5652
  } else {
4914
- await rm4(instructionsPath, { force: true });
5653
+ await rm5(agentsPath, { force: true });
4915
5654
  }
4916
5655
  } catch {
4917
5656
  }
5657
+ if (!await otherAgentSkillsPlatformActive(rootDir, "zcode")) {
5658
+ await removeZcodeSkills(rootDir);
5659
+ }
4918
5660
  }
4919
5661
  async function removeFromConfig(rootDir, platform) {
4920
- const configPath = path20.join(rootDir, ".mancode", "config.json");
5662
+ const configPath = path22.join(rootDir, ".mancode", "config.json");
4921
5663
  try {
4922
5664
  const raw = await readFile8(configPath, "utf-8");
4923
5665
  const config = JSON.parse(raw);
4924
5666
  if (Array.isArray(config.platforms)) {
4925
5667
  config.platforms = config.platforms.filter((p) => p !== platform);
4926
- await writeFile9(
5668
+ if (isRecord6(config.platformOptions)) {
5669
+ const platformOptions = Object.fromEntries(
5670
+ Object.entries(config.platformOptions).filter(
5671
+ ([name]) => name !== platform
5672
+ )
5673
+ );
5674
+ config.platformOptions = Object.keys(platformOptions).length > 0 ? platformOptions : void 0;
5675
+ }
5676
+ await writeFile11(
4927
5677
  configPath,
4928
5678
  `${JSON.stringify(config, null, 2)}
4929
5679
  `,
@@ -4933,17 +5683,31 @@ async function removeFromConfig(rootDir, platform) {
4933
5683
  } catch {
4934
5684
  }
4935
5685
  }
4936
- function isRecord5(value) {
5686
+ function isRecord6(value) {
4937
5687
  return typeof value === "object" && value !== null && !Array.isArray(value);
4938
5688
  }
4939
- async function pathExists10(p) {
5689
+ async function pathExists11(p) {
4940
5690
  try {
4941
- await access3(p);
5691
+ await access5(p);
4942
5692
  return true;
4943
5693
  } catch {
4944
5694
  return false;
4945
5695
  }
4946
5696
  }
5697
+ async function otherAgentSkillsPlatformActive(rootDir, excluding) {
5698
+ try {
5699
+ const raw = await readFile8(
5700
+ path22.join(rootDir, ".mancode", "config.json"),
5701
+ "utf-8"
5702
+ );
5703
+ const config = JSON.parse(raw);
5704
+ const platforms = Array.isArray(config.platforms) ? config.platforms : [];
5705
+ const peers = ["codex", "zcode"].filter((p) => p !== excluding);
5706
+ return platforms.some((p) => peers.includes(p));
5707
+ } catch {
5708
+ return false;
5709
+ }
5710
+ }
4947
5711
 
4948
5712
  // src/commands/version.ts
4949
5713
  import process9 from "process";
@@ -4956,13 +5720,13 @@ function version() {
4956
5720
  }
4957
5721
 
4958
5722
  // src/commands/workflow.ts
4959
- import { access as access4 } from "fs/promises";
4960
- import path21 from "path";
5723
+ import { access as access6 } from "fs/promises";
5724
+ import path23 from "path";
4961
5725
  var EXIT_OK8 = 0;
4962
5726
  var EXIT_NOT_INITIALIZED6 = 1;
4963
5727
  var EXIT_INVALID_ARG2 = 2;
4964
5728
  async function workflow(rootDir, subcommand, args = [], options = {}) {
4965
- if (!await pathExists11(path21.join(rootDir, ".mancode", "state.json"))) {
5729
+ if (!await pathExists12(path23.join(rootDir, ".mancode", "state.json"))) {
4966
5730
  if (options.json) {
4967
5731
  console.log(JSON.stringify({ error: "not initialized" }, null, 2));
4968
5732
  } else {
@@ -5219,9 +5983,9 @@ function ago(iso) {
5219
5983
  if (hours < 24) return `${hours}h ago`;
5220
5984
  return `${Math.floor(hours / 24)}d ago`;
5221
5985
  }
5222
- async function pathExists11(p) {
5986
+ async function pathExists12(p) {
5223
5987
  try {
5224
- await access4(p);
5988
+ await access6(p);
5225
5989
  return true;
5226
5990
  } catch {
5227
5991
  return false;
@@ -5234,10 +5998,9 @@ program.command("init").description("Initialize mancode in the current project")
5234
5998
  const code = await init(process.cwd(), options);
5235
5999
  process.exitCode = code;
5236
6000
  });
5237
- program.command("install [platform]").description("Install platform adapter (claude-code, cursor, codex, copilot)").option("--force", "Reinstall even if already installed").option("--minimal", "Minimal install (MVP-2)").option(
5238
- "--commit-hook",
5239
- "Install optional /manteam Conventional Commit enforcement hook"
5240
- ).action(async (platform, options) => {
6001
+ program.command("install [platform]").description(
6002
+ "Install platform adapter (claude-code, cursor, codex, copilot, zcode)"
6003
+ ).option("--force", "Reinstall even if already installed").option("--minimal", "Minimal install (MVP-2)").action(async (platform, options) => {
5241
6004
  const code = await install(
5242
6005
  process.cwd(),
5243
6006
  platform ?? "claude-code",