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/README.md +36 -21
- package/README.zh-CN.md +31 -18
- package/dist/cli.js +1067 -304
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
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
|
|
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
|
|
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
|
-
|
|
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\
|
|
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
|
-
|
|
1304
|
+
${CORE_CODING_PRINCIPLES}
|
|
1303
1305
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
|
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 {
|
|
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
|
|
1388
|
-
} catch {
|
|
1389
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
1623
|
-
|
|
1624
|
-
|
|
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
|
|
1664
|
-
import
|
|
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
|
|
2096
|
+
import path5 from "path";
|
|
1754
2097
|
async function generateSharedContent(projectRoot, options) {
|
|
1755
2098
|
const [state, tokens] = await Promise.all([
|
|
1756
2099
|
readJson(
|
|
1757
|
-
|
|
2100
|
+
path5.join(projectRoot, ".mancode", "state.json")
|
|
1758
2101
|
),
|
|
1759
2102
|
readJson(
|
|
1760
|
-
|
|
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
|
-
|
|
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 =
|
|
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: "
|
|
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
|
|
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
|
|
1929
|
-
import
|
|
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 =
|
|
1933
|
-
await
|
|
1934
|
-
const instructionsPath =
|
|
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
|
|
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
|
|
1982
|
-
import
|
|
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 =
|
|
2001
|
-
await
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
2516
|
+
"# mancode Configuration",
|
|
2167
2517
|
"",
|
|
2168
|
-
|
|
2169
|
-
|
|
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: "
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
2718
|
+
const pkgPath = path11.join(projectRoot, "package.json");
|
|
2348
2719
|
try {
|
|
2349
|
-
await
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
2821
|
-
const stateFile =
|
|
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(
|
|
2837
|
-
const hasPackageJson = await pathExists4(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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:
|
|
3069
|
-
|
|
3070
|
-
|
|
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:
|
|
3075
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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)}
|
|
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, {
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
3316
|
-
import
|
|
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
|
|
3839
|
+
mkdir as mkdir7,
|
|
3325
3840
|
readFile as readFile6,
|
|
3326
|
-
readdir,
|
|
3841
|
+
readdir as readdir2,
|
|
3327
3842
|
rename,
|
|
3328
|
-
writeFile as
|
|
3843
|
+
writeFile as writeFile9
|
|
3329
3844
|
} from "fs/promises";
|
|
3330
|
-
import
|
|
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 =
|
|
3367
|
-
await
|
|
3368
|
-
const issueDbPath =
|
|
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
|
|
3387
|
-
await
|
|
3388
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
3576
|
-
const rel =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 ${
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
4486
|
+
const reportRef = path17.relative(projectRoot, report.reportPath);
|
|
3799
4487
|
const run = {
|
|
3800
|
-
id:
|
|
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
|
|
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 =
|
|
4594
|
+
const gitignorePath = path17.join(projectRoot, ".gitignore");
|
|
3907
4595
|
if (pathExistsSync(gitignorePath)) {
|
|
3908
4596
|
return { applied: false };
|
|
3909
4597
|
}
|
|
3910
|
-
await
|
|
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 =
|
|
4603
|
+
const editorconfigPath = path17.join(projectRoot, ".editorconfig");
|
|
3916
4604
|
if (pathExistsSync(editorconfigPath)) {
|
|
3917
4605
|
return { applied: false };
|
|
3918
4606
|
}
|
|
3919
|
-
await
|
|
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 =
|
|
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
|
|
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
|
|
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: ${
|
|
4137
|
-
console.log(`Issue DB: ${
|
|
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: ${
|
|
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
|
|
4898
|
+
async function pathExists8(p) {
|
|
4200
4899
|
try {
|
|
4201
|
-
await
|
|
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
|
|
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 =
|
|
4216
|
-
if (!await
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
5025
|
+
mkdir as mkdir8,
|
|
4327
5026
|
readFile as readFile7,
|
|
4328
|
-
readdir as
|
|
4329
|
-
rm as
|
|
5027
|
+
readdir as readdir3,
|
|
5028
|
+
rm as rm4,
|
|
4330
5029
|
stat as stat2,
|
|
4331
|
-
writeFile as
|
|
5030
|
+
writeFile as writeFile10
|
|
4332
5031
|
} from "fs/promises";
|
|
4333
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5130
|
+
await rm4(dir, { recursive: true, force: true });
|
|
4432
5131
|
return true;
|
|
4433
5132
|
}
|
|
4434
5133
|
function workflowsRoot(projectRoot) {
|
|
4435
|
-
return
|
|
5134
|
+
return path20.join(projectRoot, ".mancode", "workflows");
|
|
4436
5135
|
}
|
|
4437
5136
|
function workflowDir(projectRoot, taskId) {
|
|
4438
5137
|
assertValidTaskId(taskId);
|
|
4439
|
-
return
|
|
5138
|
+
return path20.join(workflowsRoot(projectRoot), taskId);
|
|
4440
5139
|
}
|
|
4441
5140
|
function metadataPath(projectRoot, taskId) {
|
|
4442
|
-
return
|
|
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
|
|
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 =
|
|
4475
|
-
if (!await
|
|
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(
|
|
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
|
|
5268
|
+
return path21.basename(rootDir);
|
|
4569
5269
|
}
|
|
4570
5270
|
async function readConfig2(rootDir) {
|
|
4571
5271
|
try {
|
|
4572
5272
|
const raw = await fs7.readFile(
|
|
4573
|
-
|
|
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
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
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
|
-
|
|
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 (!
|
|
5321
|
+
if (!isRecord5(settings)) return false;
|
|
4622
5322
|
const hooks = settings.hooks;
|
|
4623
|
-
if (!
|
|
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 (!
|
|
5332
|
+
if (!isRecord5(group) || !Array.isArray(group.hooks)) return false;
|
|
4633
5333
|
return group.hooks.some((hook) => {
|
|
4634
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
-
|
|
4673
|
-
|
|
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
|
|
5449
|
+
function isRecord5(value) {
|
|
4737
5450
|
return typeof value === "object" && value !== null;
|
|
4738
5451
|
}
|
|
4739
|
-
async function
|
|
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
|
|
4750
|
-
import
|
|
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 =
|
|
4772
|
-
if (!await
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
4828
|
-
const skillsDir =
|
|
4829
|
-
const agentsDir =
|
|
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
|
|
5546
|
+
await rm5(path22.join(skillsDir, skillDir), { recursive: true, force: true });
|
|
4832
5547
|
}
|
|
4833
5548
|
for (const agentFile of CLAUDE_AGENT_FILES) {
|
|
4834
|
-
await
|
|
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 =
|
|
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 (!
|
|
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.
|
|
4860
|
-
if (!
|
|
4861
|
-
|
|
4862
|
-
(hook) =>
|
|
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
|
|
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 =
|
|
5598
|
+
const rulesDir = path22.join(rootDir, ".cursor", "rules");
|
|
4883
5599
|
for (const file of MANCODE_CURSOR_RULE_FILES) {
|
|
4884
|
-
await
|
|
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 =
|
|
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
|
|
5610
|
+
await writeFile11(agentsPath, `${cleaned}
|
|
4894
5611
|
`, "utf-8");
|
|
4895
5612
|
} else {
|
|
4896
|
-
await
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
5686
|
+
function isRecord6(value) {
|
|
4937
5687
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4938
5688
|
}
|
|
4939
|
-
async function
|
|
5689
|
+
async function pathExists11(p) {
|
|
4940
5690
|
try {
|
|
4941
|
-
await
|
|
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
|
|
4960
|
-
import
|
|
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
|
|
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
|
|
5986
|
+
async function pathExists12(p) {
|
|
5223
5987
|
try {
|
|
5224
|
-
await
|
|
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(
|
|
5238
|
-
"
|
|
5239
|
-
|
|
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",
|