mancode 0.1.0 → 0.2.0
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 +1037 -304
- package/dist/cli.js.map +1 -1
- package/package.json +5 -4
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}
|
|
1305
|
+
|
|
1306
|
+
## \u56E2\u961F\u6269\u5C55\u7EAA\u5F8B
|
|
1303
1307
|
|
|
1304
|
-
1.
|
|
1305
|
-
2. \
|
|
1306
|
-
3. \
|
|
1307
|
-
4. \u5931\u8D25\u4E24\u6B21\u505C\u4E0B\u8BCA\u65AD
|
|
1308
|
-
5. \u4E0D\u81EA\u52A8 commit / merge / push
|
|
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,322 @@ 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
|
+
function renderModeSkill(mode, commandPrefix = "$") {
|
|
1789
|
+
const meta = MODE_META[mode];
|
|
1790
|
+
const stateMode = mode === "mansolo" ? "solo" : mode;
|
|
1791
|
+
const modesList = MODE_NAMES.map((m) => `${commandPrefix}${m}`).join(", ");
|
|
1792
|
+
return [
|
|
1793
|
+
meta.intro,
|
|
1794
|
+
"",
|
|
1795
|
+
"## Mode Persistence",
|
|
1796
|
+
"",
|
|
1797
|
+
"Continue following these instructions for ALL subsequent tasks in this",
|
|
1798
|
+
`conversation until the user switches modes (${modesList}).`,
|
|
1799
|
+
"",
|
|
1800
|
+
"When entering this mode, update `.mancode/state.json` and set",
|
|
1801
|
+
`"currentMode" to "${stateMode}" so the mode persists across session restarts.`,
|
|
1802
|
+
"If unsure whether a mode is active, read `.mancode/state.json` to verify.",
|
|
1803
|
+
"",
|
|
1804
|
+
"## Practice",
|
|
1805
|
+
"",
|
|
1806
|
+
"Before writing new code, check the YAGNI ladder:",
|
|
1807
|
+
"",
|
|
1808
|
+
"1. Reuse existing project code.",
|
|
1809
|
+
"2. Use the standard library.",
|
|
1810
|
+
"3. Use platform-native behavior.",
|
|
1811
|
+
"4. Use already installed dependencies.",
|
|
1812
|
+
"5. Prefer a one-line fix when it is enough.",
|
|
1813
|
+
"6. Only then write the smallest new implementation.",
|
|
1814
|
+
"",
|
|
1815
|
+
"For every task, consider: why this change, what already exists, and what",
|
|
1816
|
+
"is the smallest useful diff?",
|
|
1817
|
+
"",
|
|
1818
|
+
"For UI tasks, read `.mancode/aesthetics/style-tokens.json` for current",
|
|
1819
|
+
"design tokens before inventing new styles.",
|
|
1820
|
+
"",
|
|
1821
|
+
meta.workflow
|
|
1822
|
+
].join("\n");
|
|
1823
|
+
}
|
|
1824
|
+
async function installCodexSkills(projectRoot, minimal) {
|
|
1825
|
+
if (minimal) {
|
|
1826
|
+
await removeCodexSkills(projectRoot);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const skillsDir = path4.join(projectRoot, ".codex", "skills");
|
|
1830
|
+
for (const mode of MODE_NAMES) {
|
|
1831
|
+
const modeDir = path4.join(skillsDir, mode);
|
|
1832
|
+
await mkdir4(modeDir, { recursive: true });
|
|
1833
|
+
const meta = MODE_META[mode];
|
|
1834
|
+
const content = [
|
|
1835
|
+
"---",
|
|
1836
|
+
`name: ${mode}`,
|
|
1837
|
+
`description: ${JSON.stringify(meta.description)}`,
|
|
1838
|
+
"---",
|
|
1839
|
+
"",
|
|
1840
|
+
CODEX_SKILL_MANAGED_MARKER,
|
|
1841
|
+
"",
|
|
1842
|
+
renderModeSkill(mode, "$"),
|
|
1843
|
+
""
|
|
1844
|
+
].join("\n");
|
|
1845
|
+
await writeManagedSkill(
|
|
1846
|
+
path4.join(modeDir, "SKILL.md"),
|
|
1847
|
+
content,
|
|
1848
|
+
CODEX_SKILL_MANAGED_MARKER,
|
|
1849
|
+
"Codex"
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
async function installZcodeSkills(projectRoot, minimal) {
|
|
1854
|
+
if (minimal) {
|
|
1855
|
+
await removeZcodeSkills(projectRoot);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
const skillsDir = path4.join(projectRoot, ".zcode", "skills");
|
|
1859
|
+
for (const mode of MODE_NAMES) {
|
|
1860
|
+
const modeDir = path4.join(skillsDir, mode);
|
|
1861
|
+
await mkdir4(modeDir, { recursive: true });
|
|
1862
|
+
const meta = MODE_META[mode];
|
|
1863
|
+
const content = [
|
|
1864
|
+
"---",
|
|
1865
|
+
`name: ${mode}`,
|
|
1866
|
+
`description: ${JSON.stringify(meta.description)}`,
|
|
1867
|
+
"---",
|
|
1868
|
+
"",
|
|
1869
|
+
ZCODE_SKILL_MANAGED_MARKER,
|
|
1870
|
+
"",
|
|
1871
|
+
renderModeSkill(mode, "$"),
|
|
1872
|
+
""
|
|
1873
|
+
].join("\n");
|
|
1874
|
+
await writeManagedSkill(
|
|
1875
|
+
path4.join(modeDir, "SKILL.md"),
|
|
1876
|
+
content,
|
|
1877
|
+
ZCODE_SKILL_MANAGED_MARKER,
|
|
1878
|
+
"ZCode"
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
async function installCursorCommands(projectRoot, minimal) {
|
|
1883
|
+
if (minimal) {
|
|
1884
|
+
await removeCursorCommands(projectRoot);
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
const commandsDir = path4.join(projectRoot, ".cursor", "commands");
|
|
1888
|
+
await mkdir4(commandsDir, { recursive: true });
|
|
1889
|
+
for (const mode of MODE_NAMES) {
|
|
1890
|
+
const meta = MODE_META[mode];
|
|
1891
|
+
const content = [
|
|
1892
|
+
"---",
|
|
1893
|
+
`description: ${JSON.stringify(meta.description)}`,
|
|
1894
|
+
"---",
|
|
1895
|
+
"",
|
|
1896
|
+
renderModeSkill(mode, "/"),
|
|
1897
|
+
""
|
|
1898
|
+
].join("\n");
|
|
1899
|
+
await writeFile4(path4.join(commandsDir, `${mode}.md`), content, "utf-8");
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
async function installCopilotPrompts(projectRoot, minimal) {
|
|
1903
|
+
if (minimal) {
|
|
1904
|
+
await removeCopilotPrompts(projectRoot);
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
const promptsDir = path4.join(projectRoot, ".github", "prompts");
|
|
1908
|
+
await mkdir4(promptsDir, { recursive: true });
|
|
1909
|
+
for (const mode of MODE_NAMES) {
|
|
1910
|
+
const meta = MODE_META[mode];
|
|
1911
|
+
const content = [
|
|
1912
|
+
"---",
|
|
1913
|
+
"agent: 'agent'",
|
|
1914
|
+
`description: ${JSON.stringify(meta.description)}`,
|
|
1915
|
+
"---",
|
|
1916
|
+
"",
|
|
1917
|
+
renderModeSkill(mode, ""),
|
|
1918
|
+
""
|
|
1919
|
+
].join("\n");
|
|
1920
|
+
await writeFile4(
|
|
1921
|
+
path4.join(promptsDir, `${mode}.prompt.md`),
|
|
1922
|
+
content,
|
|
1923
|
+
"utf-8"
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
async function removeCodexSkills(projectRoot) {
|
|
1928
|
+
const skillsDir = path4.join(projectRoot, ".codex", "skills");
|
|
1929
|
+
for (const mode of MODE_NAMES) {
|
|
1930
|
+
const modeDir = path4.join(skillsDir, mode);
|
|
1931
|
+
const skillPath = path4.join(modeDir, "SKILL.md");
|
|
1932
|
+
if (await isManagedSkillFile(skillPath, CODEX_SKILL_MANAGED_MARKER)) {
|
|
1933
|
+
await rm2(skillPath, { force: true });
|
|
1934
|
+
await removeIfEmpty(modeDir);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
await removeIfEmpty(skillsDir);
|
|
1938
|
+
}
|
|
1939
|
+
async function removeZcodeSkills(projectRoot) {
|
|
1940
|
+
const skillsDir = path4.join(projectRoot, ".zcode", "skills");
|
|
1941
|
+
for (const mode of MODE_NAMES) {
|
|
1942
|
+
const modeDir = path4.join(skillsDir, mode);
|
|
1943
|
+
const skillPath = path4.join(modeDir, "SKILL.md");
|
|
1944
|
+
if (await isManagedSkillFile(skillPath, ZCODE_SKILL_MANAGED_MARKER)) {
|
|
1945
|
+
await rm2(skillPath, { force: true });
|
|
1946
|
+
await removeIfEmpty(modeDir);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
await removeIfEmpty(skillsDir);
|
|
1950
|
+
}
|
|
1951
|
+
async function removeCursorCommands(projectRoot) {
|
|
1952
|
+
for (const mode of MODE_NAMES) {
|
|
1953
|
+
await rm2(path4.join(projectRoot, ".cursor", "commands", `${mode}.md`), {
|
|
1954
|
+
force: true
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
async function removeCopilotPrompts(projectRoot) {
|
|
1959
|
+
for (const mode of MODE_NAMES) {
|
|
1960
|
+
await rm2(
|
|
1961
|
+
path4.join(projectRoot, ".github", "prompts", `${mode}.prompt.md`),
|
|
1962
|
+
{ force: true }
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
async function removeIfEmpty(dir) {
|
|
1967
|
+
try {
|
|
1968
|
+
const entries = await readdir(dir);
|
|
1969
|
+
if (entries.length === 0) {
|
|
1970
|
+
await rm2(dir, { recursive: true, force: true });
|
|
1971
|
+
}
|
|
1972
|
+
} catch {
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
async function writeManagedSkill(skillPath, content, marker, platformLabel) {
|
|
1976
|
+
const existing = await readTextIfExists2(skillPath);
|
|
1977
|
+
if (existing !== null && !existing.includes(marker)) {
|
|
1978
|
+
throw new Error(
|
|
1979
|
+
`refusing to overwrite user-authored ${platformLabel} skill: ${skillPath}`
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
await writeFile4(skillPath, content, "utf-8");
|
|
1983
|
+
}
|
|
1984
|
+
async function isManagedSkillFile(skillPath, marker) {
|
|
1985
|
+
const content = await readTextIfExists2(skillPath);
|
|
1986
|
+
return content?.includes(marker) ?? false;
|
|
1987
|
+
}
|
|
1988
|
+
async function readTextIfExists2(filePath) {
|
|
1989
|
+
try {
|
|
1990
|
+
return await readFile3(filePath, "utf-8");
|
|
1991
|
+
} catch {
|
|
1992
|
+
return null;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
var MODE_META = {
|
|
1996
|
+
man8: {
|
|
1997
|
+
description: "Investigate first, then produce a plan before implementation. Use when the user asks for man8, planning, architecture, migration, integration, or risk assessment.",
|
|
1998
|
+
intro: "# mancode man8 \u2014 Investigate and Plan",
|
|
1999
|
+
workflow: [
|
|
2000
|
+
"## Workflow (simulate in single conversation)",
|
|
2001
|
+
"",
|
|
2002
|
+
"This platform does not provide isolated subagents. Simulate the coaching",
|
|
2003
|
+
"staff in sequence:",
|
|
2004
|
+
"",
|
|
2005
|
+
"1. **Scout**: Investigate the relevant code and summarize constraints,",
|
|
2006
|
+
" existing patterns, and dependencies.",
|
|
2007
|
+
"2. **Head Coach**: Write a concrete implementation plan based on the",
|
|
2008
|
+
" Scout findings.",
|
|
2009
|
+
"3. **Stop for user approval** before implementing. Do not write code",
|
|
2010
|
+
" until the user confirms.",
|
|
2011
|
+
"",
|
|
2012
|
+
"After user approval, switch to solo mode (update state.json currentMode",
|
|
2013
|
+
'to "solo") and implement the plan.'
|
|
2014
|
+
].join("\n")
|
|
2015
|
+
},
|
|
2016
|
+
man: {
|
|
2017
|
+
description: "Full high-risk workflow with plan, implementation, verification, and review. Use when the user asks for man or a high-risk production change.",
|
|
2018
|
+
intro: "# mancode man \u2014 Full Review Workflow",
|
|
2019
|
+
workflow: [
|
|
2020
|
+
"## Workflow (simulate in single conversation)",
|
|
2021
|
+
"",
|
|
2022
|
+
"This platform does not provide isolated subagents. Simulate the full",
|
|
2023
|
+
"coaching staff workflow in sequence:",
|
|
2024
|
+
"",
|
|
2025
|
+
"1. **Scout**: Investigate the codebase and summarize constraints.",
|
|
2026
|
+
"2. **Head Coach**: Propose a concrete implementation plan.",
|
|
2027
|
+
"3. **Wait for approval** when the task is high-risk.",
|
|
2028
|
+
"4. **Implement** the plan and self-test.",
|
|
2029
|
+
"5. **Film Analyst Offense**: Review readability, DRY, and YAGNI.",
|
|
2030
|
+
"6. **Fix** review findings.",
|
|
2031
|
+
"7. **Film Analyst Defense**: Review auth, injection, concurrency, and",
|
|
2032
|
+
" resource risks.",
|
|
2033
|
+
"8. **Summarize** verification results and remaining risk."
|
|
2034
|
+
].join("\n")
|
|
2035
|
+
},
|
|
2036
|
+
manteam: {
|
|
2037
|
+
description: "Team-aware workflow with handoff-friendly summaries. Use when the user asks for manteam or when the task affects shared team context.",
|
|
2038
|
+
intro: "# mancode manteam \u2014 Team Workflow",
|
|
2039
|
+
workflow: [
|
|
2040
|
+
"## Workflow",
|
|
2041
|
+
"",
|
|
2042
|
+
"1. Read `.mancode/memory/prd.md`, `.mancode/memory/spec.md`, and",
|
|
2043
|
+
" `.mancode/memory/decisions.md` when relevant.",
|
|
2044
|
+
"2. Preserve decisions and leave handoff-friendly summaries.",
|
|
2045
|
+
"3. Prefer Conventional Commit style when suggesting commits.",
|
|
2046
|
+
"4. Simulate the coaching staff in sequence (same as man mode) but",
|
|
2047
|
+
" with extra emphasis on documentation and handoff clarity."
|
|
2048
|
+
].join("\n")
|
|
2049
|
+
},
|
|
2050
|
+
manps: {
|
|
2051
|
+
description: "Run project health and cleanup scans before remediation. Use when the user asks for manps or project health cleanup.",
|
|
2052
|
+
intro: "# mancode manps \u2014 Project Health Scan",
|
|
2053
|
+
workflow: [
|
|
2054
|
+
"## Workflow",
|
|
2055
|
+
"",
|
|
2056
|
+
"1. Run `mancode manps [area]` to scan for tech debt, unused dependencies,",
|
|
2057
|
+
" security issues, dead code, and config drift.",
|
|
2058
|
+
"2. Supported areas: all, deps, security, dead-code, config.",
|
|
2059
|
+
"3. Treat scan output as triage data \u2014 do not remediate without clear",
|
|
2060
|
+
" user approval for each item.",
|
|
2061
|
+
"4. Present findings as a prioritized list with severity and recommendation."
|
|
2062
|
+
].join("\n")
|
|
2063
|
+
},
|
|
2064
|
+
mansolo: {
|
|
2065
|
+
description: "Exit any active mancode mode and return to default solo behavior.",
|
|
2066
|
+
intro: "# mancode mansolo \u2014 Return to Solo",
|
|
2067
|
+
workflow: [
|
|
2068
|
+
"## Solo Behavior",
|
|
2069
|
+
"",
|
|
2070
|
+
"You are now in solo (default) mode.",
|
|
2071
|
+
"",
|
|
2072
|
+
'Update `.mancode/state.json` and set `currentMode` to "solo".',
|
|
2073
|
+
"",
|
|
2074
|
+
"- Keep the diff narrow.",
|
|
2075
|
+
"- Reuse existing functions, components, styles, and dependencies.",
|
|
2076
|
+
"- For UI work, read `.mancode/aesthetics/style-tokens.json`.",
|
|
2077
|
+
"- Verify with the narrowest meaningful test, lint, build, or smoke check."
|
|
2078
|
+
].join("\n")
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
|
|
1751
2082
|
// src/installers/shared-content.ts
|
|
1752
2083
|
import { readFile as readFile4 } from "fs/promises";
|
|
1753
|
-
import
|
|
2084
|
+
import path5 from "path";
|
|
1754
2085
|
async function generateSharedContent(projectRoot, options) {
|
|
1755
2086
|
const [state, tokens] = await Promise.all([
|
|
1756
2087
|
readJson(
|
|
1757
|
-
|
|
2088
|
+
path5.join(projectRoot, ".mancode", "state.json")
|
|
1758
2089
|
),
|
|
1759
2090
|
readJson(
|
|
1760
|
-
|
|
2091
|
+
path5.join(projectRoot, ".mancode", "aesthetics", "style-tokens.json")
|
|
1761
2092
|
)
|
|
1762
2093
|
]);
|
|
1763
2094
|
const sections = [
|
|
@@ -1833,7 +2164,14 @@ function renderAesthetics(tokens) {
|
|
|
1833
2164
|
return lines.join("\n");
|
|
1834
2165
|
}
|
|
1835
2166
|
function renderModes(options) {
|
|
1836
|
-
|
|
2167
|
+
let commandLabel;
|
|
2168
|
+
if (options.capabilities.skills === "agents-skills") {
|
|
2169
|
+
commandLabel = "Invoke mode skills with `$man8`, `$man`, `$manteam`, `$manps`, `$mansolo`.";
|
|
2170
|
+
} else if (options.capabilities.slashCommands === "native") {
|
|
2171
|
+
commandLabel = "Use the named commands directly when available.";
|
|
2172
|
+
} else {
|
|
2173
|
+
commandLabel = "Treat these as prompt conventions when native commands are unavailable.";
|
|
2174
|
+
}
|
|
1837
2175
|
return [
|
|
1838
2176
|
"## mancode Modes",
|
|
1839
2177
|
"",
|
|
@@ -1897,7 +2235,7 @@ function formatValue(value) {
|
|
|
1897
2235
|
// src/installers/codex.ts
|
|
1898
2236
|
async function installCodex(projectRoot, options) {
|
|
1899
2237
|
await installMancodeCore(projectRoot);
|
|
1900
|
-
const agentsPath =
|
|
2238
|
+
const agentsPath = path6.join(projectRoot, "AGENTS.md");
|
|
1901
2239
|
const existing = await readTextIfExists(agentsPath);
|
|
1902
2240
|
const sharedContent = await generateSharedContent(projectRoot, {
|
|
1903
2241
|
platform: "codex",
|
|
@@ -1906,7 +2244,7 @@ async function installCodex(projectRoot, options) {
|
|
|
1906
2244
|
slashCommands: "partial",
|
|
1907
2245
|
subagents: false,
|
|
1908
2246
|
hooks: false,
|
|
1909
|
-
skills: "
|
|
2247
|
+
skills: "agents-skills"
|
|
1910
2248
|
},
|
|
1911
2249
|
minimal: options.minimal,
|
|
1912
2250
|
techStack: options.techStack,
|
|
@@ -1921,17 +2259,18 @@ async function installCodex(projectRoot, options) {
|
|
|
1921
2259
|
sharedContent.trim(),
|
|
1922
2260
|
DEFAULT_MANCODE_END_MARKER
|
|
1923
2261
|
].join("\n");
|
|
1924
|
-
await
|
|
2262
|
+
await writeFile5(agentsPath, replaceManagedBlock(existing, block), "utf-8");
|
|
2263
|
+
await installCodexSkills(projectRoot, options.minimal ?? false);
|
|
1925
2264
|
}
|
|
1926
2265
|
|
|
1927
2266
|
// src/installers/copilot.ts
|
|
1928
|
-
import { mkdir as
|
|
1929
|
-
import
|
|
2267
|
+
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
2268
|
+
import path7 from "path";
|
|
1930
2269
|
async function installCopilot(projectRoot, options) {
|
|
1931
2270
|
await installMancodeCore(projectRoot);
|
|
1932
|
-
const githubDir =
|
|
1933
|
-
await
|
|
1934
|
-
const instructionsPath =
|
|
2271
|
+
const githubDir = path7.join(projectRoot, ".github");
|
|
2272
|
+
await mkdir5(githubDir, { recursive: true });
|
|
2273
|
+
const instructionsPath = path7.join(githubDir, "copilot-instructions.md");
|
|
1935
2274
|
const existing = await readTextIfExists(instructionsPath);
|
|
1936
2275
|
const sharedContent = await generateSharedContent(projectRoot, {
|
|
1937
2276
|
platform: "copilot",
|
|
@@ -1958,11 +2297,12 @@ async function installCopilot(projectRoot, options) {
|
|
|
1958
2297
|
sections.push("", renderCopilotPromptConventions());
|
|
1959
2298
|
}
|
|
1960
2299
|
sections.push(DEFAULT_MANCODE_END_MARKER);
|
|
1961
|
-
await
|
|
2300
|
+
await writeFile6(
|
|
1962
2301
|
instructionsPath,
|
|
1963
2302
|
replaceManagedBlock(existing, sections.join("\n")),
|
|
1964
2303
|
"utf-8"
|
|
1965
2304
|
);
|
|
2305
|
+
await installCopilotPrompts(projectRoot, options.minimal ?? false);
|
|
1966
2306
|
}
|
|
1967
2307
|
function renderCopilotPromptConventions() {
|
|
1968
2308
|
return [
|
|
@@ -1973,13 +2313,16 @@ function renderCopilotPromptConventions() {
|
|
|
1973
2313
|
"- man8: investigate first and produce a plan before implementation.",
|
|
1974
2314
|
"- man: use a careful plan, implementation, verification, and review loop.",
|
|
1975
2315
|
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
1976
|
-
"- manps: prefer `mancode manps [area]` before cleanup."
|
|
2316
|
+
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
2317
|
+
"- mansolo: exit any active mode and return to default solo behavior.",
|
|
2318
|
+
"",
|
|
2319
|
+
"These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
|
|
1977
2320
|
].join("\n");
|
|
1978
2321
|
}
|
|
1979
2322
|
|
|
1980
2323
|
// src/installers/cursor.ts
|
|
1981
|
-
import { mkdir as
|
|
1982
|
-
import
|
|
2324
|
+
import { mkdir as mkdir6, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
2325
|
+
import path8 from "path";
|
|
1983
2326
|
var MANCODE_CURSOR_CORE_RULE_FILES = [
|
|
1984
2327
|
"mancode-context.mdc",
|
|
1985
2328
|
"mancode-practice.mdc",
|
|
@@ -1997,8 +2340,8 @@ var MANCODE_CURSOR_RULE_FILES = [
|
|
|
1997
2340
|
];
|
|
1998
2341
|
async function installCursor(projectRoot, options) {
|
|
1999
2342
|
await installMancodeCore(projectRoot);
|
|
2000
|
-
const rulesDir =
|
|
2001
|
-
await
|
|
2343
|
+
const rulesDir = path8.join(projectRoot, ".cursor", "rules");
|
|
2344
|
+
await mkdir6(rulesDir, { recursive: true });
|
|
2002
2345
|
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2003
2346
|
platform: "cursor",
|
|
2004
2347
|
displayName: "Cursor",
|
|
@@ -2037,6 +2380,7 @@ async function installCursor(projectRoot, options) {
|
|
|
2037
2380
|
);
|
|
2038
2381
|
if (options.minimal) {
|
|
2039
2382
|
await removeAdvancedRules(rulesDir);
|
|
2383
|
+
await installCursorCommands(projectRoot, true);
|
|
2040
2384
|
return;
|
|
2041
2385
|
}
|
|
2042
2386
|
await writeRule(
|
|
@@ -2067,6 +2411,7 @@ async function installCursor(projectRoot, options) {
|
|
|
2067
2411
|
false,
|
|
2068
2412
|
renderManpsRule()
|
|
2069
2413
|
);
|
|
2414
|
+
await installCursorCommands(projectRoot, options.minimal ?? false);
|
|
2070
2415
|
}
|
|
2071
2416
|
async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
|
|
2072
2417
|
const frontmatter = [
|
|
@@ -2082,11 +2427,11 @@ async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
|
|
|
2082
2427
|
|
|
2083
2428
|
${body.trim()}
|
|
2084
2429
|
`;
|
|
2085
|
-
await
|
|
2430
|
+
await writeFile7(path8.join(rulesDir, fileName), content, "utf-8");
|
|
2086
2431
|
}
|
|
2087
2432
|
async function removeAdvancedRules(rulesDir) {
|
|
2088
2433
|
for (const fileName of MANCODE_CURSOR_ADVANCED_RULE_FILES) {
|
|
2089
|
-
await
|
|
2434
|
+
await rm3(path8.join(rulesDir, fileName), { force: true });
|
|
2090
2435
|
}
|
|
2091
2436
|
}
|
|
2092
2437
|
function renderPracticeRule() {
|
|
@@ -2118,57 +2463,60 @@ function renderSoloRule() {
|
|
|
2118
2463
|
].join("\n");
|
|
2119
2464
|
}
|
|
2120
2465
|
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");
|
|
2466
|
+
return renderModeSkill("man8", "/");
|
|
2132
2467
|
}
|
|
2133
2468
|
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");
|
|
2469
|
+
return renderModeSkill("man", "/");
|
|
2150
2470
|
}
|
|
2151
2471
|
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");
|
|
2472
|
+
return renderModeSkill("manteam", "/");
|
|
2161
2473
|
}
|
|
2162
2474
|
function renderManpsRule() {
|
|
2163
|
-
return
|
|
2164
|
-
|
|
2475
|
+
return renderModeSkill("manps", "/");
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// src/installers/zcode.ts
|
|
2479
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
2480
|
+
import path9 from "path";
|
|
2481
|
+
var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
|
|
2482
|
+
var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
|
|
2483
|
+
async function installZcode(projectRoot, options) {
|
|
2484
|
+
await installMancodeCore(projectRoot);
|
|
2485
|
+
const agentsPath = path9.join(projectRoot, "AGENTS.md");
|
|
2486
|
+
const existing = await readTextIfExists(agentsPath);
|
|
2487
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2488
|
+
platform: "zcode",
|
|
2489
|
+
displayName: "ZCode",
|
|
2490
|
+
capabilities: {
|
|
2491
|
+
slashCommands: "partial",
|
|
2492
|
+
subagents: false,
|
|
2493
|
+
hooks: false,
|
|
2494
|
+
skills: "agents-skills"
|
|
2495
|
+
},
|
|
2496
|
+
minimal: options.minimal,
|
|
2497
|
+
techStack: options.techStack,
|
|
2498
|
+
uiLibrary: options.uiLibrary
|
|
2499
|
+
});
|
|
2500
|
+
const block = [
|
|
2501
|
+
ZCODE_MANCODE_START_MARKER,
|
|
2502
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2165
2503
|
"",
|
|
2166
|
-
"
|
|
2504
|
+
"# mancode Configuration",
|
|
2167
2505
|
"",
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
"- Treat scan output as triage data; do not remediate without clear user approval."
|
|
2506
|
+
sharedContent.trim(),
|
|
2507
|
+
ZCODE_MANCODE_END_MARKER
|
|
2171
2508
|
].join("\n");
|
|
2509
|
+
await writeFile8(
|
|
2510
|
+
agentsPath,
|
|
2511
|
+
replaceManagedBlock(
|
|
2512
|
+
existing,
|
|
2513
|
+
block,
|
|
2514
|
+
ZCODE_MANCODE_START_MARKER,
|
|
2515
|
+
ZCODE_MANCODE_END_MARKER
|
|
2516
|
+
),
|
|
2517
|
+
"utf-8"
|
|
2518
|
+
);
|
|
2519
|
+
await installZcodeSkills(projectRoot, options.minimal ?? false);
|
|
2172
2520
|
}
|
|
2173
2521
|
|
|
2174
2522
|
// src/installers/registry.ts
|
|
@@ -2202,7 +2550,7 @@ var PLATFORM_INSTALLERS = {
|
|
|
2202
2550
|
slashCommands: "partial",
|
|
2203
2551
|
subagents: false,
|
|
2204
2552
|
hooks: false,
|
|
2205
|
-
skills: "
|
|
2553
|
+
skills: "agents-skills"
|
|
2206
2554
|
},
|
|
2207
2555
|
install: installCodex
|
|
2208
2556
|
},
|
|
@@ -2216,6 +2564,17 @@ var PLATFORM_INSTALLERS = {
|
|
|
2216
2564
|
skills: "instructions"
|
|
2217
2565
|
},
|
|
2218
2566
|
install: installCopilot
|
|
2567
|
+
},
|
|
2568
|
+
zcode: {
|
|
2569
|
+
name: "zcode",
|
|
2570
|
+
displayName: "ZCode",
|
|
2571
|
+
capabilities: {
|
|
2572
|
+
slashCommands: "partial",
|
|
2573
|
+
subagents: false,
|
|
2574
|
+
hooks: false,
|
|
2575
|
+
skills: "agents-skills"
|
|
2576
|
+
},
|
|
2577
|
+
install: installZcode
|
|
2219
2578
|
}
|
|
2220
2579
|
};
|
|
2221
2580
|
function getPlatformInstaller(platform) {
|
|
@@ -2234,7 +2593,7 @@ function isPlatformName(platform) {
|
|
|
2234
2593
|
|
|
2235
2594
|
// src/system/detect-team.ts
|
|
2236
2595
|
import { exec } from "child_process";
|
|
2237
|
-
import
|
|
2596
|
+
import path10 from "path";
|
|
2238
2597
|
import process2 from "process";
|
|
2239
2598
|
import { promisify } from "util";
|
|
2240
2599
|
var execAsync = promisify(exec);
|
|
@@ -2256,7 +2615,7 @@ var EXEC_OPTS = {
|
|
|
2256
2615
|
}
|
|
2257
2616
|
};
|
|
2258
2617
|
async function detectTeamStatus(projectRoot) {
|
|
2259
|
-
const gitDir =
|
|
2618
|
+
const gitDir = path10.join(projectRoot, ".git");
|
|
2260
2619
|
try {
|
|
2261
2620
|
await execAsync("git rev-parse --is-inside-work-tree", {
|
|
2262
2621
|
cwd: projectRoot,
|
|
@@ -2318,8 +2677,8 @@ async function checkRemote(cwd) {
|
|
|
2318
2677
|
|
|
2319
2678
|
// src/system/detect.ts
|
|
2320
2679
|
import { exec as exec2 } from "child_process";
|
|
2321
|
-
import { access, readFile as readFile5 } from "fs/promises";
|
|
2322
|
-
import
|
|
2680
|
+
import { access as access2, readFile as readFile5 } from "fs/promises";
|
|
2681
|
+
import path11 from "path";
|
|
2323
2682
|
import { promisify as promisify2 } from "util";
|
|
2324
2683
|
var execAsync2 = promisify2(exec2);
|
|
2325
2684
|
async function detectSystemDeps() {
|
|
@@ -2344,9 +2703,9 @@ async function detectSystemDeps() {
|
|
|
2344
2703
|
return { bash, git, jq, node };
|
|
2345
2704
|
}
|
|
2346
2705
|
async function detectProjectType(projectRoot) {
|
|
2347
|
-
const pkgPath =
|
|
2706
|
+
const pkgPath = path11.join(projectRoot, "package.json");
|
|
2348
2707
|
try {
|
|
2349
|
-
await
|
|
2708
|
+
await access2(pkgPath);
|
|
2350
2709
|
} catch (err) {
|
|
2351
2710
|
if (err.code === "ENOENT") {
|
|
2352
2711
|
return {
|
|
@@ -2378,7 +2737,7 @@ async function detectProjectType(projectRoot) {
|
|
|
2378
2737
|
if (deps.includes("tailwindcss")) techStack.push("Tailwind CSS");
|
|
2379
2738
|
let uiLibrary = null;
|
|
2380
2739
|
const hasRadix = deps.some((d) => d.startsWith("@radix-ui/"));
|
|
2381
|
-
const uiDir =
|
|
2740
|
+
const uiDir = path11.join(projectRoot, "src", "components", "ui");
|
|
2382
2741
|
const hasUiDir = await pathExists2(uiDir);
|
|
2383
2742
|
if (hasRadix && hasUiDir) {
|
|
2384
2743
|
uiLibrary = "shadcn/ui";
|
|
@@ -2393,7 +2752,7 @@ async function detectProjectType(projectRoot) {
|
|
|
2393
2752
|
}
|
|
2394
2753
|
async function pathExists2(p) {
|
|
2395
2754
|
try {
|
|
2396
|
-
await
|
|
2755
|
+
await access2(p);
|
|
2397
2756
|
return true;
|
|
2398
2757
|
} catch {
|
|
2399
2758
|
return false;
|
|
@@ -2402,7 +2761,7 @@ async function pathExists2(p) {
|
|
|
2402
2761
|
|
|
2403
2762
|
// src/system/scan-aesthetics.ts
|
|
2404
2763
|
import { promises as fs } from "fs";
|
|
2405
|
-
import
|
|
2764
|
+
import path12 from "path";
|
|
2406
2765
|
var MAX_COMPONENT_SCAN_DEPTH = 12;
|
|
2407
2766
|
var MAX_COMPONENT_FILES = 2e3;
|
|
2408
2767
|
async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
@@ -2462,7 +2821,7 @@ async function findTailwindConfig(projectRoot) {
|
|
|
2462
2821
|
"tailwind.config.mjs"
|
|
2463
2822
|
];
|
|
2464
2823
|
for (const name of candidates) {
|
|
2465
|
-
const absPath =
|
|
2824
|
+
const absPath = path12.join(projectRoot, name);
|
|
2466
2825
|
if (await pathExists3(absPath)) {
|
|
2467
2826
|
return { absPath, relPath: name };
|
|
2468
2827
|
}
|
|
@@ -2682,7 +3041,7 @@ async function scanComponents(projectRoot) {
|
|
|
2682
3041
|
const names = /* @__PURE__ */ new Set();
|
|
2683
3042
|
let visitedFiles = 0;
|
|
2684
3043
|
for (const relRoot of roots) {
|
|
2685
|
-
const absRoot =
|
|
3044
|
+
const absRoot = path12.join(projectRoot, relRoot);
|
|
2686
3045
|
if (!await pathExists3(absRoot)) continue;
|
|
2687
3046
|
visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
|
|
2688
3047
|
if (visitedFiles >= MAX_COMPONENT_FILES) break;
|
|
@@ -2702,7 +3061,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
2702
3061
|
}
|
|
2703
3062
|
for (const entry of entries) {
|
|
2704
3063
|
if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
|
|
2705
|
-
const abs =
|
|
3064
|
+
const abs = path12.join(dir, entry);
|
|
2706
3065
|
let info;
|
|
2707
3066
|
try {
|
|
2708
3067
|
info = await fs.lstat(abs);
|
|
@@ -2719,7 +3078,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
2719
3078
|
if (isNonComponentFile(entry)) continue;
|
|
2720
3079
|
let base = entry.replace(/\.(tsx|jsx|ts|js|vue|svelte)$/, "");
|
|
2721
3080
|
if (base === "index") {
|
|
2722
|
-
base =
|
|
3081
|
+
base = path12.basename(dir);
|
|
2723
3082
|
}
|
|
2724
3083
|
if (base.startsWith(".")) continue;
|
|
2725
3084
|
const componentName = toPascalCase(base);
|
|
@@ -2741,7 +3100,7 @@ async function scanCssVariables(projectRoot) {
|
|
|
2741
3100
|
const variables = {};
|
|
2742
3101
|
const sourceFiles = [];
|
|
2743
3102
|
for (const relPath of candidates) {
|
|
2744
|
-
const absPath =
|
|
3103
|
+
const absPath = path12.join(projectRoot, relPath);
|
|
2745
3104
|
if (!await pathExists3(absPath)) continue;
|
|
2746
3105
|
const content = await fs.readFile(absPath, "utf-8");
|
|
2747
3106
|
const found = extractCssVariables(content);
|
|
@@ -2787,7 +3146,7 @@ function toPascalCase(value) {
|
|
|
2787
3146
|
async function hasTailwindDep(projectRoot) {
|
|
2788
3147
|
try {
|
|
2789
3148
|
const raw = await fs.readFile(
|
|
2790
|
-
|
|
3149
|
+
path12.join(projectRoot, "package.json"),
|
|
2791
3150
|
"utf-8"
|
|
2792
3151
|
);
|
|
2793
3152
|
const pkg = JSON.parse(raw);
|
|
@@ -2817,8 +3176,8 @@ var EXIT_NOT_A_PROJECT_DIR = 2;
|
|
|
2817
3176
|
var EXIT_INIT_FAILED = 5;
|
|
2818
3177
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
2819
3178
|
async function init(rootDir = process3.cwd(), options = {}) {
|
|
2820
|
-
const mancodeDir =
|
|
2821
|
-
const stateFile =
|
|
3179
|
+
const mancodeDir = path13.join(rootDir, ".mancode");
|
|
3180
|
+
const stateFile = path13.join(mancodeDir, "state.json");
|
|
2822
3181
|
const wasInitialized = await pathExists4(stateFile);
|
|
2823
3182
|
if (wasInitialized) {
|
|
2824
3183
|
if (!options.force) {
|
|
@@ -2833,8 +3192,8 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
2833
3192
|
console.error(`\u2717 Target directory does not exist: ${rootDir}`);
|
|
2834
3193
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
2835
3194
|
}
|
|
2836
|
-
const isGitRepo = await pathExists4(
|
|
2837
|
-
const hasPackageJson = await pathExists4(
|
|
3195
|
+
const isGitRepo = await pathExists4(path13.join(rootDir, ".git"));
|
|
3196
|
+
const hasPackageJson = await pathExists4(path13.join(rootDir, "package.json"));
|
|
2838
3197
|
if (!isGitRepo && !hasPackageJson) {
|
|
2839
3198
|
console.error(`\u2717 Not a project directory: ${rootDir}`);
|
|
2840
3199
|
console.error(" (No .git or package.json found)");
|
|
@@ -2918,13 +3277,16 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
2918
3277
|
await updateConfigOptions(mancodeDir, {
|
|
2919
3278
|
forceTeamMode: options.team === true,
|
|
2920
3279
|
defaultStyle: options.style ?? null,
|
|
2921
|
-
platforms: [installer.name]
|
|
3280
|
+
platforms: [installer.name],
|
|
3281
|
+
platformOptions: {
|
|
3282
|
+
[installer.name]: { minimal: false }
|
|
3283
|
+
}
|
|
2922
3284
|
});
|
|
2923
3285
|
let styleLine = " .mancode/aesthetics/ # style-tokens.json (\u7A7A)";
|
|
2924
3286
|
if (project.hasFrontend) {
|
|
2925
3287
|
console.log("\u2713 \u626B\u63CF\u5BA1\u7F8E token...");
|
|
2926
3288
|
const tokens = await scanAesthetics(rootDir, project.uiLibrary);
|
|
2927
|
-
const tokensPath =
|
|
3289
|
+
const tokensPath = path13.join(
|
|
2928
3290
|
mancodeDir,
|
|
2929
3291
|
"aesthetics",
|
|
2930
3292
|
"style-tokens.json"
|
|
@@ -2984,7 +3346,7 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
2984
3346
|
}
|
|
2985
3347
|
}
|
|
2986
3348
|
async function updateConfigOptions(mancodeDir, patch) {
|
|
2987
|
-
const configPath =
|
|
3349
|
+
const configPath = path13.join(mancodeDir, "config.json");
|
|
2988
3350
|
let config = {};
|
|
2989
3351
|
try {
|
|
2990
3352
|
config = JSON.parse(await fs2.readFile(configPath, "utf-8"));
|
|
@@ -3009,6 +3371,12 @@ function printPlatformCreatedFiles(platform) {
|
|
|
3009
3371
|
}
|
|
3010
3372
|
if (platform === "codex") {
|
|
3011
3373
|
console.log(" AGENTS.md # Codex managed block");
|
|
3374
|
+
console.log(" .codex/skills/ # Codex mode skills");
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
if (platform === "zcode") {
|
|
3378
|
+
console.log(" AGENTS.md # ZCode managed block");
|
|
3379
|
+
console.log(" .zcode/skills/ # ZCode mode skills");
|
|
3012
3380
|
return;
|
|
3013
3381
|
}
|
|
3014
3382
|
console.log(" .github/copilot-instructions.md # Copilot instructions");
|
|
@@ -3024,17 +3392,17 @@ async function pathExists4(p) {
|
|
|
3024
3392
|
|
|
3025
3393
|
// src/commands/install.ts
|
|
3026
3394
|
import { promises as fs4 } from "fs";
|
|
3027
|
-
import
|
|
3395
|
+
import path15 from "path";
|
|
3028
3396
|
import process4 from "process";
|
|
3029
3397
|
|
|
3030
3398
|
// src/installers/platform-status.ts
|
|
3031
3399
|
import { promises as fs3 } from "fs";
|
|
3032
|
-
import
|
|
3400
|
+
import path14 from "path";
|
|
3033
3401
|
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
3034
3402
|
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
3035
3403
|
return {
|
|
3036
3404
|
installed,
|
|
3037
|
-
ready: installed && readiness.
|
|
3405
|
+
ready: installed && readiness.ready,
|
|
3038
3406
|
target: readiness.target,
|
|
3039
3407
|
detail: describeStatus(installed, readiness)
|
|
3040
3408
|
};
|
|
@@ -3042,11 +3410,13 @@ async function checkPlatformStatus(rootDir, platform, installed) {
|
|
|
3042
3410
|
async function checkPlatformReadiness(rootDir, platform) {
|
|
3043
3411
|
if (platform === "claude-code") {
|
|
3044
3412
|
const hasSoloSkill = await pathExists5(
|
|
3045
|
-
|
|
3413
|
+
path14.join(rootDir, ".claude", "skills", "solo", "SKILL.md")
|
|
3046
3414
|
);
|
|
3047
3415
|
const registered = await claudeHooksRegistered(rootDir);
|
|
3416
|
+
const present = hasSoloSkill && registered;
|
|
3048
3417
|
return {
|
|
3049
|
-
present
|
|
3418
|
+
present,
|
|
3419
|
+
ready: present,
|
|
3050
3420
|
target: ".claude/",
|
|
3051
3421
|
readyDetail: "skills and hooks registered"
|
|
3052
3422
|
};
|
|
@@ -3054,26 +3424,104 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
3054
3424
|
if (platform === "cursor") {
|
|
3055
3425
|
const present = await allPathsExist(
|
|
3056
3426
|
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
3057
|
-
(file) =>
|
|
3427
|
+
(file) => path14.join(rootDir, ".cursor", "rules", file)
|
|
3058
3428
|
)
|
|
3059
3429
|
);
|
|
3060
3430
|
return {
|
|
3061
3431
|
present,
|
|
3432
|
+
ready: present,
|
|
3062
3433
|
target: ".cursor/rules/",
|
|
3063
3434
|
readyDetail: "mancode rules present"
|
|
3064
3435
|
};
|
|
3065
3436
|
}
|
|
3066
3437
|
if (platform === "codex") {
|
|
3438
|
+
const hasBlock2 = await fileHasManagedBlock(path14.join(rootDir, "AGENTS.md"));
|
|
3439
|
+
if (!hasBlock2) {
|
|
3440
|
+
return {
|
|
3441
|
+
present: false,
|
|
3442
|
+
ready: false,
|
|
3443
|
+
target: "AGENTS.md",
|
|
3444
|
+
readyDetail: "managed block missing"
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
if (await isPlatformMinimal(rootDir, "codex")) {
|
|
3448
|
+
return {
|
|
3449
|
+
present: true,
|
|
3450
|
+
ready: true,
|
|
3451
|
+
target: "AGENTS.md",
|
|
3452
|
+
readyDetail: "managed block present"
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
const skillsDir = path14.join(rootDir, ".codex", "skills");
|
|
3456
|
+
const hasSkills = await allManagedSkills(
|
|
3457
|
+
MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
|
|
3458
|
+
CODEX_SKILL_MANAGED_MARKER
|
|
3459
|
+
);
|
|
3067
3460
|
return {
|
|
3068
|
-
present:
|
|
3069
|
-
|
|
3070
|
-
|
|
3461
|
+
present: true,
|
|
3462
|
+
ready: hasSkills,
|
|
3463
|
+
target: "AGENTS.md + .codex/skills/",
|
|
3464
|
+
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
if (platform === "zcode") {
|
|
3468
|
+
const hasBlock2 = await fileHasManagedBlock(
|
|
3469
|
+
path14.join(rootDir, "AGENTS.md"),
|
|
3470
|
+
ZCODE_MANCODE_START_MARKER,
|
|
3471
|
+
ZCODE_MANCODE_END_MARKER
|
|
3472
|
+
);
|
|
3473
|
+
if (!hasBlock2) {
|
|
3474
|
+
return {
|
|
3475
|
+
present: false,
|
|
3476
|
+
ready: false,
|
|
3477
|
+
target: "AGENTS.md",
|
|
3478
|
+
readyDetail: "managed block missing"
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
if (await isPlatformMinimal(rootDir, "zcode")) {
|
|
3482
|
+
return {
|
|
3483
|
+
present: true,
|
|
3484
|
+
ready: true,
|
|
3485
|
+
target: "AGENTS.md",
|
|
3486
|
+
readyDetail: "managed block present"
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
const skillsDir = path14.join(rootDir, ".zcode", "skills");
|
|
3490
|
+
const hasSkills = await allManagedSkills(
|
|
3491
|
+
MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
|
|
3492
|
+
ZCODE_SKILL_MANAGED_MARKER
|
|
3493
|
+
);
|
|
3494
|
+
return {
|
|
3495
|
+
present: true,
|
|
3496
|
+
ready: hasSkills,
|
|
3497
|
+
target: "AGENTS.md + .zcode/skills/",
|
|
3498
|
+
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
const hasBlock = await fileHasManagedBlock(
|
|
3502
|
+
path14.join(rootDir, ".github", "copilot-instructions.md")
|
|
3503
|
+
);
|
|
3504
|
+
if (!hasBlock) {
|
|
3505
|
+
return {
|
|
3506
|
+
present: false,
|
|
3507
|
+
ready: false,
|
|
3508
|
+
target: ".github/copilot-instructions.md",
|
|
3509
|
+
readyDetail: "managed block missing"
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
const promptsDir = path14.join(rootDir, ".github", "prompts");
|
|
3513
|
+
if (await pathExists5(promptsDir)) {
|
|
3514
|
+
const hasPrompt = await pathExists5(path14.join(promptsDir, "man8.prompt.md"));
|
|
3515
|
+
return {
|
|
3516
|
+
present: hasPrompt,
|
|
3517
|
+
ready: hasPrompt,
|
|
3518
|
+
target: ".github/copilot-instructions.md + .github/prompts/",
|
|
3519
|
+
readyDetail: hasPrompt ? "managed block and prompts present" : "prompts directory exists but man8 prompt missing"
|
|
3071
3520
|
};
|
|
3072
3521
|
}
|
|
3073
3522
|
return {
|
|
3074
|
-
present:
|
|
3075
|
-
|
|
3076
|
-
),
|
|
3523
|
+
present: true,
|
|
3524
|
+
ready: true,
|
|
3077
3525
|
target: ".github/copilot-instructions.md",
|
|
3078
3526
|
readyDetail: "managed block present"
|
|
3079
3527
|
};
|
|
@@ -3089,10 +3537,38 @@ async function allPathsExist(paths) {
|
|
|
3089
3537
|
const results = await Promise.all(paths.map(pathExists5));
|
|
3090
3538
|
return results.every(Boolean);
|
|
3091
3539
|
}
|
|
3092
|
-
async function
|
|
3540
|
+
async function allManagedSkills(paths, marker) {
|
|
3541
|
+
const results = await Promise.all(
|
|
3542
|
+
paths.map((filePath) => fileHasText(filePath, marker))
|
|
3543
|
+
);
|
|
3544
|
+
return results.every(Boolean);
|
|
3545
|
+
}
|
|
3546
|
+
async function fileHasText(filePath, needle) {
|
|
3093
3547
|
try {
|
|
3094
3548
|
const content = await fs3.readFile(filePath, "utf-8");
|
|
3095
|
-
return content.includes(
|
|
3549
|
+
return content.includes(needle);
|
|
3550
|
+
} catch {
|
|
3551
|
+
return false;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
async function isPlatformMinimal(rootDir, platform) {
|
|
3555
|
+
try {
|
|
3556
|
+
const raw = await fs3.readFile(
|
|
3557
|
+
path14.join(rootDir, ".mancode", "config.json"),
|
|
3558
|
+
"utf-8"
|
|
3559
|
+
);
|
|
3560
|
+
const config = JSON.parse(raw);
|
|
3561
|
+
if (!isRecord2(config.platformOptions)) return false;
|
|
3562
|
+
const options = config.platformOptions[platform];
|
|
3563
|
+
return isRecord2(options) && options.minimal === true;
|
|
3564
|
+
} catch {
|
|
3565
|
+
return false;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
3569
|
+
try {
|
|
3570
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
3571
|
+
return hasManagedBlock(content, startMarker, endMarker);
|
|
3096
3572
|
} catch {
|
|
3097
3573
|
return false;
|
|
3098
3574
|
}
|
|
@@ -3100,7 +3576,7 @@ async function fileHasManagedBlock(filePath) {
|
|
|
3100
3576
|
async function claudeHooksRegistered(rootDir) {
|
|
3101
3577
|
try {
|
|
3102
3578
|
const raw = await fs3.readFile(
|
|
3103
|
-
|
|
3579
|
+
path14.join(rootDir, ".claude", "settings.json"),
|
|
3104
3580
|
"utf-8"
|
|
3105
3581
|
);
|
|
3106
3582
|
const settings = JSON.parse(raw);
|
|
@@ -3146,7 +3622,7 @@ var EXIT_NOT_INITIALIZED = 1;
|
|
|
3146
3622
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|
|
3147
3623
|
var EXIT_INSTALL_FAILED = 3;
|
|
3148
3624
|
async function install(rootDir = process4.cwd(), platform = "claude-code", options = {}) {
|
|
3149
|
-
const stateFile =
|
|
3625
|
+
const stateFile = path15.join(rootDir, ".mancode", "state.json");
|
|
3150
3626
|
if (!await pathExists6(stateFile)) {
|
|
3151
3627
|
console.error("\u2717 mancode not initialized.");
|
|
3152
3628
|
console.error(" Run `mancode init` first.");
|
|
@@ -3177,15 +3653,22 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
|
|
|
3177
3653
|
if (alreadyInstalled && !options.force) {
|
|
3178
3654
|
const status2 = await checkPlatformStatus(rootDir, platform, true);
|
|
3179
3655
|
if (status2.ready) {
|
|
3656
|
+
if (!options.minimal) {
|
|
3657
|
+
console.log(
|
|
3658
|
+
`\u2139\uFE0F ${formatPlatformName(platform)} adapter already installed.`
|
|
3659
|
+
);
|
|
3660
|
+
console.log(" Run with --force to reinstall.");
|
|
3661
|
+
return EXIT_OK2;
|
|
3662
|
+
}
|
|
3663
|
+
console.log(
|
|
3664
|
+
`\u2139\uFE0F ${formatPlatformName(platform)} adapter already installed; switching to minimal install.`
|
|
3665
|
+
);
|
|
3666
|
+
console.log(" Run with --force to fully reinstall.");
|
|
3667
|
+
} else {
|
|
3180
3668
|
console.log(
|
|
3181
|
-
`\u2139\uFE0F ${formatPlatformName(platform)}
|
|
3669
|
+
`\u2139\uFE0F ${formatPlatformName(platform)} is recorded in config but not ready; repairing generated files.`
|
|
3182
3670
|
);
|
|
3183
|
-
console.log(" Run with --force to reinstall.");
|
|
3184
|
-
return EXIT_OK2;
|
|
3185
3671
|
}
|
|
3186
|
-
console.log(
|
|
3187
|
-
`\u2139\uFE0F ${formatPlatformName(platform)} is recorded in config but not ready; repairing generated files.`
|
|
3188
|
-
);
|
|
3189
3672
|
}
|
|
3190
3673
|
console.log(`\u2713 Installing ${formatPlatformName(platform)} adapter...`);
|
|
3191
3674
|
const project = await detectProjectType(rootDir);
|
|
@@ -3193,7 +3676,8 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
|
|
|
3193
3676
|
await installer.install(rootDir, {
|
|
3194
3677
|
techStack: project.techStack,
|
|
3195
3678
|
uiLibrary: project.uiLibrary,
|
|
3196
|
-
minimal: options.minimal
|
|
3679
|
+
minimal: options.minimal,
|
|
3680
|
+
force: options.force
|
|
3197
3681
|
});
|
|
3198
3682
|
} catch (err) {
|
|
3199
3683
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3206,12 +3690,20 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
|
|
|
3206
3690
|
if (!platforms.includes(platform)) {
|
|
3207
3691
|
platforms.push(platform);
|
|
3208
3692
|
}
|
|
3209
|
-
await updateConfig(rootDir, {
|
|
3693
|
+
await updateConfig(rootDir, {
|
|
3694
|
+
...config,
|
|
3695
|
+
platforms,
|
|
3696
|
+
platformOptions: updatePlatformOptions(
|
|
3697
|
+
config.platformOptions,
|
|
3698
|
+
platform,
|
|
3699
|
+
options
|
|
3700
|
+
)
|
|
3701
|
+
});
|
|
3210
3702
|
console.log(`\u2713 ${formatPlatformName(platform)} adapter installed.`);
|
|
3211
3703
|
return EXIT_OK2;
|
|
3212
3704
|
}
|
|
3213
3705
|
async function readConfig(rootDir) {
|
|
3214
|
-
const configPath =
|
|
3706
|
+
const configPath = path15.join(rootDir, ".mancode", "config.json");
|
|
3215
3707
|
try {
|
|
3216
3708
|
const raw = await fs4.readFile(configPath, "utf-8");
|
|
3217
3709
|
return { config: JSON.parse(raw), valid: true };
|
|
@@ -3226,7 +3718,7 @@ function isNodeError3(err) {
|
|
|
3226
3718
|
return err instanceof Error && "code" in err;
|
|
3227
3719
|
}
|
|
3228
3720
|
async function updateConfig(rootDir, config) {
|
|
3229
|
-
const configPath =
|
|
3721
|
+
const configPath = path15.join(rootDir, ".mancode", "config.json");
|
|
3230
3722
|
const content = `${JSON.stringify(config, null, 2)}
|
|
3231
3723
|
`;
|
|
3232
3724
|
await fs4.writeFile(configPath, content, "utf-8");
|
|
@@ -3245,10 +3737,19 @@ function withDefaultConfig(config, fallbackPlatform) {
|
|
|
3245
3737
|
logging: isRecord3(config.logging) ? config.logging : DEFAULT_CONFIG.logging
|
|
3246
3738
|
};
|
|
3247
3739
|
}
|
|
3740
|
+
function updatePlatformOptions(value, platform, options) {
|
|
3741
|
+
const platformOptions = isRecord3(value) ? { ...value } : {};
|
|
3742
|
+
const existing = platformOptions[platform];
|
|
3743
|
+
platformOptions[platform] = {
|
|
3744
|
+
...isRecord3(existing) ? existing : {},
|
|
3745
|
+
minimal: options.minimal === true
|
|
3746
|
+
};
|
|
3747
|
+
return platformOptions;
|
|
3748
|
+
}
|
|
3248
3749
|
async function readStatePlatform(rootDir) {
|
|
3249
3750
|
try {
|
|
3250
3751
|
const raw = await fs4.readFile(
|
|
3251
|
-
|
|
3752
|
+
path15.join(rootDir, ".mancode", "state.json"),
|
|
3252
3753
|
"utf-8"
|
|
3253
3754
|
);
|
|
3254
3755
|
const state = JSON.parse(raw);
|
|
@@ -3271,7 +3772,7 @@ function isRecord3(value) {
|
|
|
3271
3772
|
|
|
3272
3773
|
// src/commands/list-platforms.ts
|
|
3273
3774
|
import { promises as fs5 } from "fs";
|
|
3274
|
-
import
|
|
3775
|
+
import path16 from "path";
|
|
3275
3776
|
import process5 from "process";
|
|
3276
3777
|
var EXIT_OK3 = 0;
|
|
3277
3778
|
async function listPlatforms(rootDir = process5.cwd()) {
|
|
@@ -3288,7 +3789,7 @@ async function listPlatforms(rootDir = process5.cwd()) {
|
|
|
3288
3789
|
async function readInstalledPlatforms(rootDir) {
|
|
3289
3790
|
try {
|
|
3290
3791
|
const raw = await fs5.readFile(
|
|
3291
|
-
|
|
3792
|
+
path16.join(rootDir, ".mancode", "config.json"),
|
|
3292
3793
|
"utf-8"
|
|
3293
3794
|
);
|
|
3294
3795
|
const config = JSON.parse(raw);
|
|
@@ -3312,22 +3813,24 @@ function describePlatform(platform) {
|
|
|
3312
3813
|
}
|
|
3313
3814
|
|
|
3314
3815
|
// src/commands/manps.ts
|
|
3315
|
-
import { access as
|
|
3316
|
-
import
|
|
3816
|
+
import { access as access4 } from "fs/promises";
|
|
3817
|
+
import path18 from "path";
|
|
3317
3818
|
import { createInterface } from "readline/promises";
|
|
3318
3819
|
|
|
3319
3820
|
// src/system/preseason.ts
|
|
3821
|
+
import { execFile } from "child_process";
|
|
3320
3822
|
import { createHash } from "crypto";
|
|
3321
3823
|
import { existsSync, readFileSync } from "fs";
|
|
3322
3824
|
import {
|
|
3825
|
+
access as access3,
|
|
3323
3826
|
lstat,
|
|
3324
|
-
mkdir as
|
|
3827
|
+
mkdir as mkdir7,
|
|
3325
3828
|
readFile as readFile6,
|
|
3326
|
-
readdir,
|
|
3829
|
+
readdir as readdir2,
|
|
3327
3830
|
rename,
|
|
3328
|
-
writeFile as
|
|
3831
|
+
writeFile as writeFile9
|
|
3329
3832
|
} from "fs/promises";
|
|
3330
|
-
import
|
|
3833
|
+
import path17 from "path";
|
|
3331
3834
|
var PRESEASON_AREAS = [
|
|
3332
3835
|
"all",
|
|
3333
3836
|
"deps",
|
|
@@ -3356,16 +3859,60 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3356
3859
|
]);
|
|
3357
3860
|
var MAX_SCAN_DEPTH = 20;
|
|
3358
3861
|
var MAX_PROJECT_FILES = 5e3;
|
|
3862
|
+
var DEPCRUISE_TIMEOUT_MS = 15e3;
|
|
3863
|
+
var DEPCRUISE_MAX_BUFFER = 4 * 1024 * 1024;
|
|
3864
|
+
var SCANNERS = [
|
|
3865
|
+
{
|
|
3866
|
+
name: "scripts",
|
|
3867
|
+
areas: ["all", "config"],
|
|
3868
|
+
scan: ({ pkg }) => scanScripts(pkg)
|
|
3869
|
+
},
|
|
3870
|
+
{
|
|
3871
|
+
name: "dependency-overlap",
|
|
3872
|
+
areas: ["all", "deps"],
|
|
3873
|
+
scan: ({ pkg }) => scanDependencyOverlap(pkg)
|
|
3874
|
+
},
|
|
3875
|
+
{
|
|
3876
|
+
name: "security",
|
|
3877
|
+
areas: ["all", "security"],
|
|
3878
|
+
scan: ({ pkg }) => scanSecurity(pkg)
|
|
3879
|
+
},
|
|
3880
|
+
{
|
|
3881
|
+
name: "todos",
|
|
3882
|
+
areas: ["all", "dead-code"],
|
|
3883
|
+
scan: ({ projectRoot, files }) => scanTodos(projectRoot, files)
|
|
3884
|
+
},
|
|
3885
|
+
{
|
|
3886
|
+
name: "test-gaps",
|
|
3887
|
+
areas: ["all", "dead-code"],
|
|
3888
|
+
scan: ({ files }) => scanTestGaps(files)
|
|
3889
|
+
},
|
|
3890
|
+
{
|
|
3891
|
+
name: "config",
|
|
3892
|
+
areas: ["all", "config"],
|
|
3893
|
+
scan: ({ projectRoot, files }) => scanConfig(projectRoot, files)
|
|
3894
|
+
},
|
|
3895
|
+
{
|
|
3896
|
+
name: "aesthetic-drift",
|
|
3897
|
+
areas: ["all"],
|
|
3898
|
+
scan: ({ projectRoot, files }) => scanAestheticDrift(projectRoot, files)
|
|
3899
|
+
},
|
|
3900
|
+
{
|
|
3901
|
+
name: "architecture",
|
|
3902
|
+
areas: ["all"],
|
|
3903
|
+
scan: ({ projectRoot }) => scanArchitecture(projectRoot)
|
|
3904
|
+
}
|
|
3905
|
+
];
|
|
3359
3906
|
async function runPreseasonScan(projectRoot, area = "all") {
|
|
3360
3907
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3361
3908
|
const normalizedArea = normalizeArea(area);
|
|
3362
3909
|
const pkg = await readPackageJson(projectRoot);
|
|
3363
3910
|
const needsFiles = normalizedArea === "all" || normalizedArea === "dead-code" || normalizedArea === "config";
|
|
3364
3911
|
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 =
|
|
3912
|
+
const issues = (await scanArea(projectRoot, normalizedArea, pkg, files)).slice(0, 20);
|
|
3913
|
+
const reportDir = path17.join(projectRoot, ".mancode", "preseason-reports");
|
|
3914
|
+
await mkdir7(reportDir, { recursive: true });
|
|
3915
|
+
const issueDbPath = path17.join(
|
|
3369
3916
|
projectRoot,
|
|
3370
3917
|
".mancode",
|
|
3371
3918
|
"preseason-issues.json"
|
|
@@ -3383,9 +3930,9 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
3383
3930
|
issueDbPath
|
|
3384
3931
|
};
|
|
3385
3932
|
const database = await buildIssueDatabase(projectRoot, report);
|
|
3386
|
-
await
|
|
3387
|
-
await
|
|
3388
|
-
|
|
3933
|
+
await writeFile9(reportPath, renderPreseasonReport(report), "utf-8");
|
|
3934
|
+
await writeFile9(
|
|
3935
|
+
path17.join(projectRoot, ".mancode", "preseason-report.md"),
|
|
3389
3936
|
renderPreseasonReport(report),
|
|
3390
3937
|
"utf-8"
|
|
3391
3938
|
);
|
|
@@ -3393,7 +3940,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
|
|
|
3393
3940
|
return report;
|
|
3394
3941
|
}
|
|
3395
3942
|
async function runPreseasonRemediation(projectRoot, issues, options = {}) {
|
|
3396
|
-
const issueDbPath =
|
|
3943
|
+
const issueDbPath = path17.join(
|
|
3397
3944
|
projectRoot,
|
|
3398
3945
|
".mancode",
|
|
3399
3946
|
"preseason-issues.json"
|
|
@@ -3475,7 +4022,7 @@ async function runPreseasonRemediation(projectRoot, issues, options = {}) {
|
|
|
3475
4022
|
write(" Decision: skipped.");
|
|
3476
4023
|
}
|
|
3477
4024
|
database.updatedAt = now;
|
|
3478
|
-
await
|
|
4025
|
+
await writeFile9(
|
|
3479
4026
|
issueDbPath,
|
|
3480
4027
|
`${JSON.stringify(database, null, 2)}
|
|
3481
4028
|
`,
|
|
@@ -3498,32 +4045,16 @@ function normalizeArea(area) {
|
|
|
3498
4045
|
`invalid area: ${area}. Supported areas: ${PRESEASON_AREAS.join(", ")}`
|
|
3499
4046
|
);
|
|
3500
4047
|
}
|
|
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
|
-
}
|
|
4048
|
+
async function scanArea(projectRoot, area, pkg, files) {
|
|
4049
|
+
const ctx = { projectRoot, area, pkg, files };
|
|
4050
|
+
const active = SCANNERS.filter((scanner) => scanner.areas.includes(area));
|
|
4051
|
+
const chunks = await Promise.all(active.map((scanner) => scanner.scan(ctx)));
|
|
4052
|
+
return chunks.flat();
|
|
3522
4053
|
}
|
|
3523
4054
|
async function allocateReportPath(reportDir, baseName) {
|
|
3524
4055
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
3525
4056
|
const suffix = attempt === 0 ? "" : `-${attempt + 1}`;
|
|
3526
|
-
const candidate =
|
|
4057
|
+
const candidate = path17.join(reportDir, `${baseName}${suffix}.md`);
|
|
3527
4058
|
if (!existsSync(candidate)) return candidate;
|
|
3528
4059
|
}
|
|
3529
4060
|
throw new Error(`unable to allocate preseason report path: ${baseName}`);
|
|
@@ -3566,14 +4097,14 @@ async function walk(root, current, results, depth) {
|
|
|
3566
4097
|
if (depth > MAX_SCAN_DEPTH || results.length >= MAX_PROJECT_FILES) return;
|
|
3567
4098
|
let entries;
|
|
3568
4099
|
try {
|
|
3569
|
-
entries = await
|
|
4100
|
+
entries = await readdir2(current);
|
|
3570
4101
|
} catch {
|
|
3571
4102
|
return;
|
|
3572
4103
|
}
|
|
3573
4104
|
for (const entry of entries) {
|
|
3574
4105
|
if (IGNORE_DIRS.has(entry)) continue;
|
|
3575
|
-
const abs =
|
|
3576
|
-
const rel =
|
|
4106
|
+
const abs = path17.join(current, entry);
|
|
4107
|
+
const rel = path17.relative(root, abs);
|
|
3577
4108
|
let info;
|
|
3578
4109
|
try {
|
|
3579
4110
|
info = await lstat(abs);
|
|
@@ -3583,7 +4114,7 @@ async function walk(root, current, results, depth) {
|
|
|
3583
4114
|
if (info.isSymbolicLink()) continue;
|
|
3584
4115
|
if (info.isDirectory()) {
|
|
3585
4116
|
await walk(root, abs, results, depth + 1);
|
|
3586
|
-
} else if (SOURCE_EXTENSIONS.has(
|
|
4117
|
+
} else if (SOURCE_EXTENSIONS.has(path17.extname(entry)) || entry === "package.json") {
|
|
3587
4118
|
results.push(rel);
|
|
3588
4119
|
if (results.length >= MAX_PROJECT_FILES) return;
|
|
3589
4120
|
}
|
|
@@ -3591,7 +4122,7 @@ async function walk(root, current, results, depth) {
|
|
|
3591
4122
|
}
|
|
3592
4123
|
async function readPackageJson(projectRoot) {
|
|
3593
4124
|
try {
|
|
3594
|
-
const raw = await readFile6(
|
|
4125
|
+
const raw = await readFile6(path17.join(projectRoot, "package.json"), "utf-8");
|
|
3595
4126
|
return JSON.parse(raw);
|
|
3596
4127
|
} catch {
|
|
3597
4128
|
return null;
|
|
@@ -3684,7 +4215,7 @@ function scanTodos(projectRoot, files) {
|
|
|
3684
4215
|
const matches = [];
|
|
3685
4216
|
for (const file of files) {
|
|
3686
4217
|
if (matches.length >= 7) break;
|
|
3687
|
-
const abs =
|
|
4218
|
+
const abs = path17.join(projectRoot, file);
|
|
3688
4219
|
matches.push(...readTodoIssues(abs, file, matches.length));
|
|
3689
4220
|
}
|
|
3690
4221
|
return matches.slice(0, 7);
|
|
@@ -3721,7 +4252,7 @@ function scanTestGaps(files) {
|
|
|
3721
4252
|
if (sourceFiles.length === 0) return [];
|
|
3722
4253
|
const tests = new Set(files.filter((file) => file.startsWith("tests/")));
|
|
3723
4254
|
const missing = sourceFiles.filter((file) => {
|
|
3724
|
-
const base =
|
|
4255
|
+
const base = path17.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
3725
4256
|
return !Array.from(tests).some((test) => test.includes(base));
|
|
3726
4257
|
}).slice(0, 4);
|
|
3727
4258
|
return missing.map((file, index) => ({
|
|
@@ -3730,13 +4261,13 @@ function scanTestGaps(files) {
|
|
|
3730
4261
|
type: "tests",
|
|
3731
4262
|
title: "Core source file has no obvious test",
|
|
3732
4263
|
file,
|
|
3733
|
-
detail: `No matching test file was found for ${
|
|
4264
|
+
detail: `No matching test file was found for ${path17.basename(file)}.`,
|
|
3734
4265
|
recommendation: "Add focused coverage for the public behavior or document why this module is exercised indirectly."
|
|
3735
4266
|
}));
|
|
3736
4267
|
}
|
|
3737
4268
|
function scanConfig(projectRoot, files) {
|
|
3738
4269
|
const issues = [];
|
|
3739
|
-
if (!files.includes(".gitignore") && !pathExistsSync(
|
|
4270
|
+
if (!files.includes(".gitignore") && !pathExistsSync(path17.join(projectRoot, ".gitignore"))) {
|
|
3740
4271
|
issues.push({
|
|
3741
4272
|
id: "config-gitignore",
|
|
3742
4273
|
severity: "P1",
|
|
@@ -3747,7 +4278,7 @@ function scanConfig(projectRoot, files) {
|
|
|
3747
4278
|
recommendation: "Add a .gitignore that excludes dependencies, build output, coverage, and local env files."
|
|
3748
4279
|
});
|
|
3749
4280
|
}
|
|
3750
|
-
if (!files.includes(".editorconfig") && !pathExistsSync(
|
|
4281
|
+
if (!files.includes(".editorconfig") && !pathExistsSync(path17.join(projectRoot, ".editorconfig"))) {
|
|
3751
4282
|
issues.push({
|
|
3752
4283
|
id: "config-editorconfig",
|
|
3753
4284
|
severity: "P2",
|
|
@@ -3766,7 +4297,7 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
3766
4297
|
for (const file of frontendFiles) {
|
|
3767
4298
|
let content;
|
|
3768
4299
|
try {
|
|
3769
|
-
content = readFileSyncSafe(
|
|
4300
|
+
content = readFileSyncSafe(path17.join(projectRoot, file));
|
|
3770
4301
|
} catch {
|
|
3771
4302
|
continue;
|
|
3772
4303
|
}
|
|
@@ -3785,6 +4316,151 @@ function scanAestheticDrift(projectRoot, files) {
|
|
|
3785
4316
|
}
|
|
3786
4317
|
return issues;
|
|
3787
4318
|
}
|
|
4319
|
+
async function scanArchitecture(projectRoot) {
|
|
4320
|
+
const localBinary = path17.join(
|
|
4321
|
+
projectRoot,
|
|
4322
|
+
"node_modules",
|
|
4323
|
+
".bin",
|
|
4324
|
+
process.platform === "win32" ? "depcruise.cmd" : "depcruise"
|
|
4325
|
+
);
|
|
4326
|
+
const binary = pathExistsSync(localBinary) ? localBinary : "depcruise";
|
|
4327
|
+
const result = await runDepcruise(binary, projectRoot);
|
|
4328
|
+
if (result.status === "not-found") {
|
|
4329
|
+
if (!await hasDependencyCruiserConfig(projectRoot)) return [];
|
|
4330
|
+
return [architectureScannerUnavailableIssue()];
|
|
4331
|
+
}
|
|
4332
|
+
const parsed = parseDepcruiseJson(result.stdout);
|
|
4333
|
+
if (!parsed) {
|
|
4334
|
+
return [
|
|
4335
|
+
{
|
|
4336
|
+
id: "architecture-scan-skipped",
|
|
4337
|
+
severity: "P2",
|
|
4338
|
+
type: "architecture",
|
|
4339
|
+
title: "Architecture scan skipped",
|
|
4340
|
+
detail: summarizeProcessFailure(result),
|
|
4341
|
+
recommendation: "Check the dependency-cruiser configuration or run depcruise manually to inspect the failure."
|
|
4342
|
+
}
|
|
4343
|
+
];
|
|
4344
|
+
}
|
|
4345
|
+
const violations = extractDepcruiseViolations(parsed);
|
|
4346
|
+
if (violations.length === 0) return [];
|
|
4347
|
+
return violations.slice(0, 5).map((violation, index) => ({
|
|
4348
|
+
id: `architecture-${index + 1}`,
|
|
4349
|
+
severity: depcruiseSeverityToPreseason(violation.severity),
|
|
4350
|
+
type: "architecture",
|
|
4351
|
+
title: `Architecture rule violation: ${violation.ruleName}`,
|
|
4352
|
+
file: violation.from,
|
|
4353
|
+
detail: `${violation.from}${violation.to ? ` -> ${violation.to}` : ""}${violation.comment ? `: ${violation.comment}` : ""}`,
|
|
4354
|
+
recommendation: "Respect the configured dependency boundary or update the dependency-cruiser rule if the architecture decision changed."
|
|
4355
|
+
}));
|
|
4356
|
+
}
|
|
4357
|
+
async function hasDependencyCruiserConfig(projectRoot) {
|
|
4358
|
+
const files = [
|
|
4359
|
+
".dependency-cruiser.js",
|
|
4360
|
+
".dependency-cruiser.cjs",
|
|
4361
|
+
".dependency-cruiser.mjs",
|
|
4362
|
+
".dependency-cruiser.json",
|
|
4363
|
+
"dependency-cruiser.config.js",
|
|
4364
|
+
"dependency-cruiser.config.cjs",
|
|
4365
|
+
"dependency-cruiser.config.mjs"
|
|
4366
|
+
];
|
|
4367
|
+
const results = await Promise.all(
|
|
4368
|
+
files.map((file) => pathExists7(path17.join(projectRoot, file)))
|
|
4369
|
+
);
|
|
4370
|
+
return results.some(Boolean);
|
|
4371
|
+
}
|
|
4372
|
+
function architectureScannerUnavailableIssue() {
|
|
4373
|
+
return {
|
|
4374
|
+
id: "architecture-scanner-unavailable",
|
|
4375
|
+
severity: "P2",
|
|
4376
|
+
type: "architecture",
|
|
4377
|
+
title: "Architecture scanner not installed",
|
|
4378
|
+
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.",
|
|
4379
|
+
recommendation: "Install dependency-cruiser (npm i -D dependency-cruiser) to enable architecture scanning."
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
function runDepcruise(binary, projectRoot) {
|
|
4383
|
+
return new Promise((resolve) => {
|
|
4384
|
+
const needsShell = process.platform === "win32";
|
|
4385
|
+
execFile(
|
|
4386
|
+
binary,
|
|
4387
|
+
["--output-type", "json", "."],
|
|
4388
|
+
{
|
|
4389
|
+
cwd: projectRoot,
|
|
4390
|
+
timeout: DEPCRUISE_TIMEOUT_MS,
|
|
4391
|
+
maxBuffer: DEPCRUISE_MAX_BUFFER,
|
|
4392
|
+
shell: needsShell
|
|
4393
|
+
},
|
|
4394
|
+
(error, stdout, stderr) => {
|
|
4395
|
+
if (!error) {
|
|
4396
|
+
resolve({
|
|
4397
|
+
status: "ok",
|
|
4398
|
+
stdout,
|
|
4399
|
+
stderr,
|
|
4400
|
+
exitCode: 0,
|
|
4401
|
+
timedOut: false
|
|
4402
|
+
});
|
|
4403
|
+
return;
|
|
4404
|
+
}
|
|
4405
|
+
const nodeError = error;
|
|
4406
|
+
if (nodeError.code === "ENOENT") {
|
|
4407
|
+
resolve({
|
|
4408
|
+
status: "not-found",
|
|
4409
|
+
stdout: "",
|
|
4410
|
+
stderr: "",
|
|
4411
|
+
exitCode: null,
|
|
4412
|
+
timedOut: false
|
|
4413
|
+
});
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
resolve({
|
|
4417
|
+
status: "failed",
|
|
4418
|
+
stdout: nodeError.stdout ?? stdout,
|
|
4419
|
+
stderr: nodeError.stderr ?? stderr,
|
|
4420
|
+
exitCode: typeof nodeError.code === "number" ? nodeError.code : null,
|
|
4421
|
+
timedOut: Boolean(nodeError.killed || nodeError.signal === "SIGTERM")
|
|
4422
|
+
});
|
|
4423
|
+
}
|
|
4424
|
+
);
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
function parseDepcruiseJson(output) {
|
|
4428
|
+
if (!output.trim()) return null;
|
|
4429
|
+
try {
|
|
4430
|
+
return JSON.parse(output);
|
|
4431
|
+
} catch {
|
|
4432
|
+
return null;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
function extractDepcruiseViolations(value) {
|
|
4436
|
+
if (!isRecord4(value)) return [];
|
|
4437
|
+
const rawViolations = Array.isArray(value.violations) ? value.violations : isRecord4(value.summary) && Array.isArray(value.summary.violations) ? value.summary.violations : [];
|
|
4438
|
+
return rawViolations.map((violation) => normalizeDepcruiseViolation(violation)).filter(
|
|
4439
|
+
(violation) => violation !== null
|
|
4440
|
+
);
|
|
4441
|
+
}
|
|
4442
|
+
function normalizeDepcruiseViolation(value) {
|
|
4443
|
+
if (!isRecord4(value)) return null;
|
|
4444
|
+
const rule = isRecord4(value.rule) ? value.rule : {};
|
|
4445
|
+
const from = typeof value.from === "string" ? value.from : "";
|
|
4446
|
+
const to = typeof value.to === "string" ? value.to : void 0;
|
|
4447
|
+
const ruleName = typeof rule.name === "string" ? rule.name : typeof value.name === "string" ? value.name : "unnamed-rule";
|
|
4448
|
+
const severity = typeof rule.severity === "string" ? rule.severity : typeof value.severity === "string" ? value.severity : "warn";
|
|
4449
|
+
const comment = typeof value.comment === "string" ? value.comment : void 0;
|
|
4450
|
+
if (!from) return null;
|
|
4451
|
+
return { ruleName, severity, from, to, comment };
|
|
4452
|
+
}
|
|
4453
|
+
function depcruiseSeverityToPreseason(severity) {
|
|
4454
|
+
return severity.toLowerCase() === "error" ? "P1" : "P2";
|
|
4455
|
+
}
|
|
4456
|
+
function summarizeProcessFailure(result) {
|
|
4457
|
+
if (result.timedOut) {
|
|
4458
|
+
return `dependency-cruiser timed out after ${DEPCRUISE_TIMEOUT_MS}ms.`;
|
|
4459
|
+
}
|
|
4460
|
+
const output = `${result.stderr || result.stdout}`.trim();
|
|
4461
|
+
const summary = output.length > 0 ? output.slice(0, 240) : "No output.";
|
|
4462
|
+
return `dependency-cruiser exited with ${result.exitCode ?? "unknown"}: ${summary}`;
|
|
4463
|
+
}
|
|
3788
4464
|
function containsHardcodedColor(content) {
|
|
3789
4465
|
return content.split("\n").some(
|
|
3790
4466
|
(line) => /#[0-9a-fA-F]{3,8}\b/.test(line) && !/^\s*--[a-zA-Z0-9-_]+\s*:/.test(line)
|
|
@@ -3795,9 +4471,9 @@ function inferCommands(pkg) {
|
|
|
3795
4471
|
return ["lint", "test", "build"].filter((name) => scripts[name]).map((name) => `npm run ${name}`);
|
|
3796
4472
|
}
|
|
3797
4473
|
async function buildIssueDatabase(projectRoot, report) {
|
|
3798
|
-
const reportRef =
|
|
4474
|
+
const reportRef = path17.relative(projectRoot, report.reportPath);
|
|
3799
4475
|
const run = {
|
|
3800
|
-
id:
|
|
4476
|
+
id: path17.basename(report.reportPath, ".md"),
|
|
3801
4477
|
generatedAt: report.generatedAt,
|
|
3802
4478
|
area: report.area,
|
|
3803
4479
|
reportPath: reportRef,
|
|
@@ -3860,7 +4536,7 @@ async function readIssueDatabase(issueDbPath) {
|
|
|
3860
4536
|
}
|
|
3861
4537
|
async function writeJsonAtomic(file, value) {
|
|
3862
4538
|
const tmp = `${file}.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3863
|
-
await
|
|
4539
|
+
await writeFile9(tmp, `${JSON.stringify(value, null, 2)}
|
|
3864
4540
|
`, "utf-8");
|
|
3865
4541
|
await rename(tmp, file);
|
|
3866
4542
|
}
|
|
@@ -3903,20 +4579,20 @@ function compareIssueRecords(a, b) {
|
|
|
3903
4579
|
}
|
|
3904
4580
|
async function applySafeRemediation(projectRoot, issue) {
|
|
3905
4581
|
if (issue.id === "config-gitignore" && issue.file === ".gitignore") {
|
|
3906
|
-
const gitignorePath =
|
|
4582
|
+
const gitignorePath = path17.join(projectRoot, ".gitignore");
|
|
3907
4583
|
if (pathExistsSync(gitignorePath)) {
|
|
3908
4584
|
return { applied: false };
|
|
3909
4585
|
}
|
|
3910
|
-
await
|
|
4586
|
+
await writeFile9(gitignorePath, `${DEFAULT_GITIGNORE}
|
|
3911
4587
|
`, "utf-8");
|
|
3912
4588
|
return { applied: true, action: "created .gitignore" };
|
|
3913
4589
|
}
|
|
3914
4590
|
if (issue.id === "config-editorconfig" && issue.file === ".editorconfig") {
|
|
3915
|
-
const editorconfigPath =
|
|
4591
|
+
const editorconfigPath = path17.join(projectRoot, ".editorconfig");
|
|
3916
4592
|
if (pathExistsSync(editorconfigPath)) {
|
|
3917
4593
|
return { applied: false };
|
|
3918
4594
|
}
|
|
3919
|
-
await
|
|
4595
|
+
await writeFile9(editorconfigPath, `${DEFAULT_EDITORCONFIG}
|
|
3920
4596
|
`, "utf-8");
|
|
3921
4597
|
return { applied: true, action: "created .editorconfig" };
|
|
3922
4598
|
}
|
|
@@ -3952,7 +4628,7 @@ async function inferSafePackageScript(projectRoot, scriptName) {
|
|
|
3952
4628
|
}
|
|
3953
4629
|
}
|
|
3954
4630
|
async function addPackageScript(projectRoot, scriptName, script) {
|
|
3955
|
-
const packagePath =
|
|
4631
|
+
const packagePath = path17.join(projectRoot, "package.json");
|
|
3956
4632
|
let pkg;
|
|
3957
4633
|
try {
|
|
3958
4634
|
pkg = JSON.parse(await readFile6(packagePath, "utf-8"));
|
|
@@ -3965,7 +4641,7 @@ async function addPackageScript(projectRoot, scriptName, script) {
|
|
|
3965
4641
|
...scripts,
|
|
3966
4642
|
[scriptName]: script
|
|
3967
4643
|
};
|
|
3968
|
-
await
|
|
4644
|
+
await writeFile9(packagePath, `${JSON.stringify(pkg, null, 2)}
|
|
3969
4645
|
`, "utf-8");
|
|
3970
4646
|
return true;
|
|
3971
4647
|
}
|
|
@@ -4044,9 +4720,20 @@ function readFileSyncSafe(file) {
|
|
|
4044
4720
|
function pathExistsSync(file) {
|
|
4045
4721
|
return existsSync(file);
|
|
4046
4722
|
}
|
|
4723
|
+
async function pathExists7(file) {
|
|
4724
|
+
try {
|
|
4725
|
+
await access3(file);
|
|
4726
|
+
return true;
|
|
4727
|
+
} catch {
|
|
4728
|
+
return false;
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4047
4731
|
function isNodeError4(err) {
|
|
4048
4732
|
return err instanceof Error && "code" in err;
|
|
4049
4733
|
}
|
|
4734
|
+
function isRecord4(value) {
|
|
4735
|
+
return typeof value === "object" && value !== null;
|
|
4736
|
+
}
|
|
4050
4737
|
|
|
4051
4738
|
// src/commands/manps.ts
|
|
4052
4739
|
var EXIT_OK4 = 0;
|
|
@@ -4054,7 +4741,7 @@ var EXIT_NOT_INITIALIZED2 = 1;
|
|
|
4054
4741
|
var EXIT_SCAN_FAILED = 2;
|
|
4055
4742
|
var EXIT_INVALID_ARG = 3;
|
|
4056
4743
|
async function manps(rootDir, area = "all", options = {}) {
|
|
4057
|
-
if (!await
|
|
4744
|
+
if (!await pathExists8(path18.join(rootDir, ".mancode", "state.json"))) {
|
|
4058
4745
|
if (options.json) {
|
|
4059
4746
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
4060
4747
|
} else {
|
|
@@ -4133,8 +4820,8 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
4133
4820
|
console.log(
|
|
4134
4821
|
`Issues: ${report.issues.length} total (P0 ${p0}, P1 ${p1}, P2 ${p2})`
|
|
4135
4822
|
);
|
|
4136
|
-
console.log(`Report: ${
|
|
4137
|
-
console.log(`Issue DB: ${
|
|
4823
|
+
console.log(`Report: ${path18.relative(rootDir, report.reportPath)}`);
|
|
4824
|
+
console.log(`Issue DB: ${path18.relative(rootDir, report.issueDbPath)}`);
|
|
4138
4825
|
if (report.issues.length > 0) {
|
|
4139
4826
|
console.log("");
|
|
4140
4827
|
for (const issue of report.issues.slice(0, 7)) {
|
|
@@ -4150,7 +4837,7 @@ async function manps(rootDir, area = "all", options = {}) {
|
|
|
4150
4837
|
console.log(` Skipped: ${remediation.skipped}`);
|
|
4151
4838
|
console.log(` Fixed: ${remediation.fixed}`);
|
|
4152
4839
|
console.log(
|
|
4153
|
-
` Issue DB: ${
|
|
4840
|
+
` Issue DB: ${path18.relative(rootDir, remediation.issueDbPath)}`
|
|
4154
4841
|
);
|
|
4155
4842
|
}
|
|
4156
4843
|
return EXIT_OK4;
|
|
@@ -4196,9 +4883,9 @@ async function readStdinLines() {
|
|
|
4196
4883
|
}
|
|
4197
4884
|
return Buffer.concat(chunks).toString("utf-8").split(/\r?\n/).filter((line) => line.length > 0);
|
|
4198
4885
|
}
|
|
4199
|
-
async function
|
|
4886
|
+
async function pathExists8(p) {
|
|
4200
4887
|
try {
|
|
4201
|
-
await
|
|
4888
|
+
await access4(p);
|
|
4202
4889
|
return true;
|
|
4203
4890
|
} catch {
|
|
4204
4891
|
return false;
|
|
@@ -4207,13 +4894,13 @@ async function pathExists7(p) {
|
|
|
4207
4894
|
|
|
4208
4895
|
// src/commands/refresh-style.ts
|
|
4209
4896
|
import { promises as fs6 } from "fs";
|
|
4210
|
-
import
|
|
4897
|
+
import path19 from "path";
|
|
4211
4898
|
import process6 from "process";
|
|
4212
4899
|
var EXIT_OK5 = 0;
|
|
4213
4900
|
var EXIT_NOT_INITIALIZED3 = 1;
|
|
4214
4901
|
async function refreshStyle(rootDir = process6.cwd()) {
|
|
4215
|
-
const stateFile =
|
|
4216
|
-
if (!await
|
|
4902
|
+
const stateFile = path19.join(rootDir, ".mancode", "state.json");
|
|
4903
|
+
if (!await pathExists9(stateFile)) {
|
|
4217
4904
|
console.error("\u2717 mancode not initialized.");
|
|
4218
4905
|
console.error(" Run `mancode init` first.");
|
|
4219
4906
|
return EXIT_NOT_INITIALIZED3;
|
|
@@ -4225,13 +4912,13 @@ async function refreshStyle(rootDir = process6.cwd()) {
|
|
|
4225
4912
|
}
|
|
4226
4913
|
console.log("\u2713 \u626B\u63CF\u9879\u76EE\u8BBE\u8BA1 token...");
|
|
4227
4914
|
const tokens = await scanAesthetics(rootDir, project.uiLibrary);
|
|
4228
|
-
const tokensPath =
|
|
4915
|
+
const tokensPath = path19.join(
|
|
4229
4916
|
rootDir,
|
|
4230
4917
|
".mancode",
|
|
4231
4918
|
"aesthetics",
|
|
4232
4919
|
"style-tokens.json"
|
|
4233
4920
|
);
|
|
4234
|
-
await fs6.mkdir(
|
|
4921
|
+
await fs6.mkdir(path19.dirname(tokensPath), { recursive: true });
|
|
4235
4922
|
await fs6.writeFile(
|
|
4236
4923
|
tokensPath,
|
|
4237
4924
|
`${JSON.stringify(tokens, null, 2)}
|
|
@@ -4295,7 +4982,7 @@ async function printStaticPlatformRefreshHint(rootDir) {
|
|
|
4295
4982
|
async function readInstalledPlatforms2(rootDir) {
|
|
4296
4983
|
try {
|
|
4297
4984
|
const raw = await fs6.readFile(
|
|
4298
|
-
|
|
4985
|
+
path19.join(rootDir, ".mancode", "config.json"),
|
|
4299
4986
|
"utf-8"
|
|
4300
4987
|
);
|
|
4301
4988
|
const config = JSON.parse(raw);
|
|
@@ -4306,7 +4993,7 @@ async function readInstalledPlatforms2(rootDir) {
|
|
|
4306
4993
|
return [];
|
|
4307
4994
|
}
|
|
4308
4995
|
}
|
|
4309
|
-
async function
|
|
4996
|
+
async function pathExists9(p) {
|
|
4310
4997
|
try {
|
|
4311
4998
|
await fs6.access(p);
|
|
4312
4999
|
return true;
|
|
@@ -4318,19 +5005,19 @@ async function pathExists8(p) {
|
|
|
4318
5005
|
// src/commands/status.ts
|
|
4319
5006
|
import { spawn } from "child_process";
|
|
4320
5007
|
import { promises as fs7 } from "fs";
|
|
4321
|
-
import
|
|
5008
|
+
import path21 from "path";
|
|
4322
5009
|
import process7 from "process";
|
|
4323
5010
|
|
|
4324
5011
|
// src/system/workflow.ts
|
|
4325
5012
|
import {
|
|
4326
|
-
mkdir as
|
|
5013
|
+
mkdir as mkdir8,
|
|
4327
5014
|
readFile as readFile7,
|
|
4328
|
-
readdir as
|
|
4329
|
-
rm as
|
|
5015
|
+
readdir as readdir3,
|
|
5016
|
+
rm as rm4,
|
|
4330
5017
|
stat as stat2,
|
|
4331
|
-
writeFile as
|
|
5018
|
+
writeFile as writeFile10
|
|
4332
5019
|
} from "fs/promises";
|
|
4333
|
-
import
|
|
5020
|
+
import path20 from "path";
|
|
4334
5021
|
var METADATA_FILE = "metadata.json";
|
|
4335
5022
|
var SLUG_MAX = 30;
|
|
4336
5023
|
var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
@@ -4347,7 +5034,7 @@ function slugify(text) {
|
|
|
4347
5034
|
return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4348
5035
|
}
|
|
4349
5036
|
async function createWorkflow(projectRoot, task, mode) {
|
|
4350
|
-
await
|
|
5037
|
+
await mkdir8(workflowsRoot(projectRoot), { recursive: true });
|
|
4351
5038
|
const taskId = await allocateTaskId(projectRoot, generateTaskId(task));
|
|
4352
5039
|
const dir = workflowDir(projectRoot, taskId);
|
|
4353
5040
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4368,7 +5055,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
|
|
|
4368
5055
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
4369
5056
|
const taskId = attempt === 0 ? baseTaskId : `${baseTaskId}-${attempt + 1}`;
|
|
4370
5057
|
try {
|
|
4371
|
-
await
|
|
5058
|
+
await mkdir8(workflowDir(projectRoot, taskId));
|
|
4372
5059
|
return taskId;
|
|
4373
5060
|
} catch (err) {
|
|
4374
5061
|
if (isNodeError5(err) && err.code === "EEXIST") {
|
|
@@ -4408,7 +5095,7 @@ async function listWorkflows(projectRoot) {
|
|
|
4408
5095
|
const root = workflowsRoot(projectRoot);
|
|
4409
5096
|
let entries;
|
|
4410
5097
|
try {
|
|
4411
|
-
entries = await
|
|
5098
|
+
entries = await readdir3(root);
|
|
4412
5099
|
} catch {
|
|
4413
5100
|
return [];
|
|
4414
5101
|
}
|
|
@@ -4428,23 +5115,23 @@ async function deleteWorkflow(projectRoot, taskId) {
|
|
|
4428
5115
|
} catch {
|
|
4429
5116
|
return false;
|
|
4430
5117
|
}
|
|
4431
|
-
await
|
|
5118
|
+
await rm4(dir, { recursive: true, force: true });
|
|
4432
5119
|
return true;
|
|
4433
5120
|
}
|
|
4434
5121
|
function workflowsRoot(projectRoot) {
|
|
4435
|
-
return
|
|
5122
|
+
return path20.join(projectRoot, ".mancode", "workflows");
|
|
4436
5123
|
}
|
|
4437
5124
|
function workflowDir(projectRoot, taskId) {
|
|
4438
5125
|
assertValidTaskId(taskId);
|
|
4439
|
-
return
|
|
5126
|
+
return path20.join(workflowsRoot(projectRoot), taskId);
|
|
4440
5127
|
}
|
|
4441
5128
|
function metadataPath(projectRoot, taskId) {
|
|
4442
|
-
return
|
|
5129
|
+
return path20.join(workflowDir(projectRoot, taskId), METADATA_FILE);
|
|
4443
5130
|
}
|
|
4444
5131
|
async function writeMetadata(dir, meta) {
|
|
4445
5132
|
const content = `${JSON.stringify(meta, null, 2)}
|
|
4446
5133
|
`;
|
|
4447
|
-
await
|
|
5134
|
+
await writeFile10(path20.join(dir, METADATA_FILE), content, "utf-8");
|
|
4448
5135
|
}
|
|
4449
5136
|
function parseWorkflowMeta(raw, taskId) {
|
|
4450
5137
|
try {
|
|
@@ -4467,12 +5154,13 @@ function assertValidTaskId(taskId) {
|
|
|
4467
5154
|
}
|
|
4468
5155
|
|
|
4469
5156
|
// src/commands/status.ts
|
|
5157
|
+
var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
|
|
4470
5158
|
var EXIT_OK6 = 0;
|
|
4471
5159
|
var EXIT_NOT_INITIALIZED4 = 1;
|
|
4472
5160
|
var EXIT_CORRUPT_STATE = 2;
|
|
4473
5161
|
async function status(rootDir = process7.cwd(), options = {}) {
|
|
4474
|
-
const stateFile =
|
|
4475
|
-
if (!await
|
|
5162
|
+
const stateFile = path21.join(rootDir, ".mancode", "state.json");
|
|
5163
|
+
if (!await pathExists10(stateFile)) {
|
|
4476
5164
|
console.error("\u2717 mancode not initialized.");
|
|
4477
5165
|
console.error(" Run `mancode init` to get started.");
|
|
4478
5166
|
return EXIT_NOT_INITIALIZED4;
|
|
@@ -4558,19 +5246,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
|
|
|
4558
5246
|
}
|
|
4559
5247
|
async function getProjectName(rootDir) {
|
|
4560
5248
|
try {
|
|
4561
|
-
const raw = await fs7.readFile(
|
|
5249
|
+
const raw = await fs7.readFile(path21.join(rootDir, "package.json"), "utf-8");
|
|
4562
5250
|
const pkg = JSON.parse(raw);
|
|
4563
5251
|
if (pkg.name && typeof pkg.name === "string") {
|
|
4564
5252
|
return pkg.name;
|
|
4565
5253
|
}
|
|
4566
5254
|
} catch {
|
|
4567
5255
|
}
|
|
4568
|
-
return
|
|
5256
|
+
return path21.basename(rootDir);
|
|
4569
5257
|
}
|
|
4570
5258
|
async function readConfig2(rootDir) {
|
|
4571
5259
|
try {
|
|
4572
5260
|
const raw = await fs7.readFile(
|
|
4573
|
-
|
|
5261
|
+
path21.join(rootDir, ".mancode", "config.json"),
|
|
4574
5262
|
"utf-8"
|
|
4575
5263
|
);
|
|
4576
5264
|
return JSON.parse(raw);
|
|
@@ -4597,9 +5285,9 @@ function getEffectiveTeamStatus(state, config, detected) {
|
|
|
4597
5285
|
}
|
|
4598
5286
|
async function checkHooks(rootDir) {
|
|
4599
5287
|
const [sessionStart, userPromptSubmit, registered] = await Promise.all([
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
5288
|
+
pathExists10(path21.join(rootDir, ".mancode", "hooks", "session-start.sh")),
|
|
5289
|
+
pathExists10(
|
|
5290
|
+
path21.join(rootDir, ".mancode", "hooks", "user-prompt-submit.sh")
|
|
4603
5291
|
),
|
|
4604
5292
|
isRegistered(rootDir)
|
|
4605
5293
|
]);
|
|
@@ -4608,7 +5296,7 @@ async function checkHooks(rootDir) {
|
|
|
4608
5296
|
async function isRegistered(rootDir) {
|
|
4609
5297
|
try {
|
|
4610
5298
|
const raw = await fs7.readFile(
|
|
4611
|
-
|
|
5299
|
+
path21.join(rootDir, ".claude", "settings.json"),
|
|
4612
5300
|
"utf-8"
|
|
4613
5301
|
);
|
|
4614
5302
|
const settings = JSON.parse(raw);
|
|
@@ -4618,9 +5306,9 @@ async function isRegistered(rootDir) {
|
|
|
4618
5306
|
}
|
|
4619
5307
|
}
|
|
4620
5308
|
function hooksRegistered2(settings) {
|
|
4621
|
-
if (!
|
|
5309
|
+
if (!isRecord5(settings)) return false;
|
|
4622
5310
|
const hooks = settings.hooks;
|
|
4623
|
-
if (!
|
|
5311
|
+
if (!isRecord5(hooks)) return false;
|
|
4624
5312
|
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.sh") && hasHookCommand2(
|
|
4625
5313
|
hooks.UserPromptSubmit,
|
|
4626
5314
|
".mancode/hooks/user-prompt-submit.sh"
|
|
@@ -4629,21 +5317,21 @@ function hooksRegistered2(settings) {
|
|
|
4629
5317
|
function hasHookCommand2(value, needle) {
|
|
4630
5318
|
if (!Array.isArray(value)) return false;
|
|
4631
5319
|
return value.some((group) => {
|
|
4632
|
-
if (!
|
|
5320
|
+
if (!isRecord5(group) || !Array.isArray(group.hooks)) return false;
|
|
4633
5321
|
return group.hooks.some((hook) => {
|
|
4634
|
-
if (!
|
|
5322
|
+
if (!isRecord5(hook) || typeof hook.command !== "string") return false;
|
|
4635
5323
|
return hook.command.includes(needle);
|
|
4636
5324
|
});
|
|
4637
5325
|
});
|
|
4638
5326
|
}
|
|
4639
5327
|
async function estimateHookInjection(rootDir) {
|
|
4640
|
-
const hookPath =
|
|
5328
|
+
const hookPath = path21.join(
|
|
4641
5329
|
rootDir,
|
|
4642
5330
|
".mancode",
|
|
4643
5331
|
"hooks",
|
|
4644
5332
|
"user-prompt-submit.sh"
|
|
4645
5333
|
);
|
|
4646
|
-
if (!await
|
|
5334
|
+
if (!await pathExists10(hookPath)) {
|
|
4647
5335
|
return { tokens: 0, cap: 800 };
|
|
4648
5336
|
}
|
|
4649
5337
|
try {
|
|
@@ -4662,15 +5350,28 @@ function runHookEstimate(rootDir, hookPath) {
|
|
|
4662
5350
|
cwd: rootDir,
|
|
4663
5351
|
stdio: ["pipe", "pipe", "ignore"]
|
|
4664
5352
|
});
|
|
5353
|
+
let settled = false;
|
|
5354
|
+
const finish = (fn) => {
|
|
5355
|
+
if (settled) return;
|
|
5356
|
+
settled = true;
|
|
5357
|
+
clearTimeout(timeout);
|
|
5358
|
+
fn();
|
|
5359
|
+
};
|
|
5360
|
+
const timeout = setTimeout(() => {
|
|
5361
|
+
child.kill();
|
|
5362
|
+
finish(() => reject(new Error("hook estimate timed out")));
|
|
5363
|
+
}, HOOK_ESTIMATE_TIMEOUT_MS);
|
|
4665
5364
|
let stdout = "";
|
|
4666
5365
|
child.stdout.setEncoding("utf-8");
|
|
4667
5366
|
child.stdout.on("data", (chunk) => {
|
|
4668
5367
|
stdout += chunk;
|
|
4669
5368
|
});
|
|
4670
|
-
child.on("error", reject);
|
|
5369
|
+
child.on("error", (err) => finish(() => reject(err)));
|
|
4671
5370
|
child.on("close", (code) => {
|
|
4672
|
-
|
|
4673
|
-
|
|
5371
|
+
finish(() => {
|
|
5372
|
+
if (code === 0) resolve(stdout);
|
|
5373
|
+
else reject(new Error(`hook exited with ${code}`));
|
|
5374
|
+
});
|
|
4674
5375
|
});
|
|
4675
5376
|
child.stdin.end(
|
|
4676
5377
|
JSON.stringify({ prompt: "design a button component with tailwind css" })
|
|
@@ -4733,10 +5434,10 @@ function formatTeamStatus(team) {
|
|
|
4733
5434
|
function workflowStepMax(mode) {
|
|
4734
5435
|
return mode === "man8" ? 3 : 8;
|
|
4735
5436
|
}
|
|
4736
|
-
function
|
|
5437
|
+
function isRecord5(value) {
|
|
4737
5438
|
return typeof value === "object" && value !== null;
|
|
4738
5439
|
}
|
|
4739
|
-
async function
|
|
5440
|
+
async function pathExists10(p) {
|
|
4740
5441
|
try {
|
|
4741
5442
|
await fs7.access(p);
|
|
4742
5443
|
return true;
|
|
@@ -4746,8 +5447,8 @@ async function pathExists9(p) {
|
|
|
4746
5447
|
}
|
|
4747
5448
|
|
|
4748
5449
|
// src/commands/uninstall.ts
|
|
4749
|
-
import { access as
|
|
4750
|
-
import
|
|
5450
|
+
import { access as access5, readFile as readFile8, rm as rm5, writeFile as writeFile11 } from "fs/promises";
|
|
5451
|
+
import path22 from "path";
|
|
4751
5452
|
import process8 from "process";
|
|
4752
5453
|
var EXIT_OK7 = 0;
|
|
4753
5454
|
var EXIT_NOT_INITIALIZED5 = 1;
|
|
@@ -4768,8 +5469,8 @@ var CLAUDE_AGENT_FILES = [
|
|
|
4768
5469
|
"plan-coach.md"
|
|
4769
5470
|
];
|
|
4770
5471
|
async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
|
|
4771
|
-
const stateFile =
|
|
4772
|
-
if (!await
|
|
5472
|
+
const stateFile = path22.join(rootDir, ".mancode", "state.json");
|
|
5473
|
+
if (!await pathExists11(stateFile)) {
|
|
4773
5474
|
console.error("\u2717 mancode not initialized.");
|
|
4774
5475
|
console.error(" Run `mancode init` first.");
|
|
4775
5476
|
return EXIT_NOT_INITIALIZED5;
|
|
@@ -4780,7 +5481,7 @@ async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
|
|
|
4780
5481
|
if (!installer) {
|
|
4781
5482
|
console.error(`\u2717 Unsupported platform: ${platform}`);
|
|
4782
5483
|
console.error(
|
|
4783
|
-
|
|
5484
|
+
` Supported platforms: ${getPlatformInstallers().map((item) => item.name).join(", ")}`
|
|
4784
5485
|
);
|
|
4785
5486
|
return EXIT_UNSUPPORTED_PLATFORM2;
|
|
4786
5487
|
}
|
|
@@ -4809,34 +5510,36 @@ async function uninstallPlatform(rootDir, platform) {
|
|
|
4809
5510
|
await uninstallCodex(rootDir);
|
|
4810
5511
|
} else if (platform === "copilot") {
|
|
4811
5512
|
await uninstallCopilot(rootDir);
|
|
5513
|
+
} else if (platform === "zcode") {
|
|
5514
|
+
await uninstallZcode(rootDir);
|
|
4812
5515
|
}
|
|
4813
5516
|
await removeFromConfig(rootDir, platform);
|
|
4814
5517
|
}
|
|
4815
5518
|
async function uninstallAll(rootDir) {
|
|
4816
5519
|
console.log("\u2713 Removing all platform adapters...");
|
|
4817
|
-
for (const p of
|
|
5520
|
+
for (const p of getPlatformInstallers().map((platform) => platform.name)) {
|
|
4818
5521
|
await uninstallPlatform(rootDir, p);
|
|
4819
5522
|
}
|
|
4820
5523
|
console.log("\u2713 Removing .mancode/ directory...");
|
|
4821
|
-
await
|
|
5524
|
+
await rm5(path22.join(rootDir, ".mancode"), {
|
|
4822
5525
|
recursive: true,
|
|
4823
5526
|
force: true
|
|
4824
5527
|
});
|
|
4825
5528
|
}
|
|
4826
5529
|
async function uninstallClaudeCode(rootDir) {
|
|
4827
|
-
const claudeDir =
|
|
4828
|
-
const skillsDir =
|
|
4829
|
-
const agentsDir =
|
|
5530
|
+
const claudeDir = path22.join(rootDir, ".claude");
|
|
5531
|
+
const skillsDir = path22.join(claudeDir, "skills");
|
|
5532
|
+
const agentsDir = path22.join(claudeDir, "agents");
|
|
4830
5533
|
for (const skillDir of CLAUDE_SKILL_DIRS) {
|
|
4831
|
-
await
|
|
5534
|
+
await rm5(path22.join(skillsDir, skillDir), { recursive: true, force: true });
|
|
4832
5535
|
}
|
|
4833
5536
|
for (const agentFile of CLAUDE_AGENT_FILES) {
|
|
4834
|
-
await
|
|
5537
|
+
await rm5(path22.join(agentsDir, agentFile), { force: true });
|
|
4835
5538
|
}
|
|
4836
5539
|
await cleanClaudeSettings(rootDir);
|
|
4837
5540
|
}
|
|
4838
5541
|
async function cleanClaudeSettings(rootDir) {
|
|
4839
|
-
const settingsPath =
|
|
5542
|
+
const settingsPath = path22.join(rootDir, ".claude", "settings.json");
|
|
4840
5543
|
let content;
|
|
4841
5544
|
try {
|
|
4842
5545
|
content = await readFile8(settingsPath, "utf-8");
|
|
@@ -4849,18 +5552,19 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
4849
5552
|
} catch {
|
|
4850
5553
|
return;
|
|
4851
5554
|
}
|
|
4852
|
-
if (!
|
|
5555
|
+
if (!isRecord6(settings.hooks)) return;
|
|
4853
5556
|
const cleanedHooks = {};
|
|
4854
5557
|
for (const [event, groups] of Object.entries(settings.hooks)) {
|
|
4855
5558
|
if (!Array.isArray(groups)) {
|
|
4856
5559
|
cleanedHooks[event] = groups;
|
|
4857
5560
|
continue;
|
|
4858
5561
|
}
|
|
4859
|
-
const filtered = groups.
|
|
4860
|
-
if (!
|
|
4861
|
-
|
|
4862
|
-
(hook) =>
|
|
5562
|
+
const filtered = groups.flatMap((group) => {
|
|
5563
|
+
if (!isRecord6(group) || !Array.isArray(group.hooks)) return [group];
|
|
5564
|
+
const hooks = group.hooks.filter(
|
|
5565
|
+
(hook) => !(isRecord6(hook) && typeof hook.command === "string" && hook.command.includes(".mancode/hooks/"))
|
|
4863
5566
|
);
|
|
5567
|
+
return hooks.length > 0 ? [{ ...group, hooks }] : [];
|
|
4864
5568
|
});
|
|
4865
5569
|
if (filtered.length > 0) {
|
|
4866
5570
|
cleanedHooks[event] = filtered;
|
|
@@ -4871,7 +5575,7 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
4871
5575
|
} else {
|
|
4872
5576
|
settings.hooks = void 0;
|
|
4873
5577
|
}
|
|
4874
|
-
await
|
|
5578
|
+
await writeFile11(
|
|
4875
5579
|
settingsPath,
|
|
4876
5580
|
`${JSON.stringify(settings, null, 2)}
|
|
4877
5581
|
`,
|
|
@@ -4879,27 +5583,29 @@ async function cleanClaudeSettings(rootDir) {
|
|
|
4879
5583
|
);
|
|
4880
5584
|
}
|
|
4881
5585
|
async function uninstallCursor(rootDir) {
|
|
4882
|
-
const rulesDir =
|
|
5586
|
+
const rulesDir = path22.join(rootDir, ".cursor", "rules");
|
|
4883
5587
|
for (const file of MANCODE_CURSOR_RULE_FILES) {
|
|
4884
|
-
await
|
|
5588
|
+
await rm5(path22.join(rulesDir, file), { force: true });
|
|
4885
5589
|
}
|
|
5590
|
+
await removeCursorCommands(rootDir);
|
|
4886
5591
|
}
|
|
4887
5592
|
async function uninstallCodex(rootDir) {
|
|
4888
|
-
const agentsPath =
|
|
5593
|
+
const agentsPath = path22.join(rootDir, "AGENTS.md");
|
|
4889
5594
|
try {
|
|
4890
5595
|
const content = await readFile8(agentsPath, "utf-8");
|
|
4891
5596
|
const cleaned = removeManagedBlock(content);
|
|
4892
5597
|
if (cleaned.trim()) {
|
|
4893
|
-
await
|
|
5598
|
+
await writeFile11(agentsPath, `${cleaned}
|
|
4894
5599
|
`, "utf-8");
|
|
4895
5600
|
} else {
|
|
4896
|
-
await
|
|
5601
|
+
await rm5(agentsPath, { force: true });
|
|
4897
5602
|
}
|
|
4898
5603
|
} catch {
|
|
4899
5604
|
}
|
|
5605
|
+
await removeCodexSkills(rootDir);
|
|
4900
5606
|
}
|
|
4901
5607
|
async function uninstallCopilot(rootDir) {
|
|
4902
|
-
const instructionsPath =
|
|
5608
|
+
const instructionsPath = path22.join(
|
|
4903
5609
|
rootDir,
|
|
4904
5610
|
".github",
|
|
4905
5611
|
"copilot-instructions.md"
|
|
@@ -4908,22 +5614,50 @@ async function uninstallCopilot(rootDir) {
|
|
|
4908
5614
|
const content = await readFile8(instructionsPath, "utf-8");
|
|
4909
5615
|
const cleaned = removeManagedBlock(content);
|
|
4910
5616
|
if (cleaned.trim()) {
|
|
4911
|
-
await
|
|
5617
|
+
await writeFile11(instructionsPath, `${cleaned}
|
|
4912
5618
|
`, "utf-8");
|
|
4913
5619
|
} else {
|
|
4914
|
-
await
|
|
5620
|
+
await rm5(instructionsPath, { force: true });
|
|
4915
5621
|
}
|
|
4916
5622
|
} catch {
|
|
4917
5623
|
}
|
|
5624
|
+
await removeCopilotPrompts(rootDir);
|
|
5625
|
+
}
|
|
5626
|
+
async function uninstallZcode(rootDir) {
|
|
5627
|
+
const agentsPath = path22.join(rootDir, "AGENTS.md");
|
|
5628
|
+
try {
|
|
5629
|
+
const content = await readFile8(agentsPath, "utf-8");
|
|
5630
|
+
const cleaned = removeManagedBlock(
|
|
5631
|
+
content,
|
|
5632
|
+
ZCODE_MANCODE_START_MARKER,
|
|
5633
|
+
ZCODE_MANCODE_END_MARKER
|
|
5634
|
+
);
|
|
5635
|
+
if (cleaned.trim()) {
|
|
5636
|
+
await writeFile11(agentsPath, `${cleaned}
|
|
5637
|
+
`, "utf-8");
|
|
5638
|
+
} else {
|
|
5639
|
+
await rm5(agentsPath, { force: true });
|
|
5640
|
+
}
|
|
5641
|
+
} catch {
|
|
5642
|
+
}
|
|
5643
|
+
await removeZcodeSkills(rootDir);
|
|
4918
5644
|
}
|
|
4919
5645
|
async function removeFromConfig(rootDir, platform) {
|
|
4920
|
-
const configPath =
|
|
5646
|
+
const configPath = path22.join(rootDir, ".mancode", "config.json");
|
|
4921
5647
|
try {
|
|
4922
5648
|
const raw = await readFile8(configPath, "utf-8");
|
|
4923
5649
|
const config = JSON.parse(raw);
|
|
4924
5650
|
if (Array.isArray(config.platforms)) {
|
|
4925
5651
|
config.platforms = config.platforms.filter((p) => p !== platform);
|
|
4926
|
-
|
|
5652
|
+
if (isRecord6(config.platformOptions)) {
|
|
5653
|
+
const platformOptions = Object.fromEntries(
|
|
5654
|
+
Object.entries(config.platformOptions).filter(
|
|
5655
|
+
([name]) => name !== platform
|
|
5656
|
+
)
|
|
5657
|
+
);
|
|
5658
|
+
config.platformOptions = Object.keys(platformOptions).length > 0 ? platformOptions : void 0;
|
|
5659
|
+
}
|
|
5660
|
+
await writeFile11(
|
|
4927
5661
|
configPath,
|
|
4928
5662
|
`${JSON.stringify(config, null, 2)}
|
|
4929
5663
|
`,
|
|
@@ -4933,12 +5667,12 @@ async function removeFromConfig(rootDir, platform) {
|
|
|
4933
5667
|
} catch {
|
|
4934
5668
|
}
|
|
4935
5669
|
}
|
|
4936
|
-
function
|
|
5670
|
+
function isRecord6(value) {
|
|
4937
5671
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4938
5672
|
}
|
|
4939
|
-
async function
|
|
5673
|
+
async function pathExists11(p) {
|
|
4940
5674
|
try {
|
|
4941
|
-
await
|
|
5675
|
+
await access5(p);
|
|
4942
5676
|
return true;
|
|
4943
5677
|
} catch {
|
|
4944
5678
|
return false;
|
|
@@ -4956,13 +5690,13 @@ function version() {
|
|
|
4956
5690
|
}
|
|
4957
5691
|
|
|
4958
5692
|
// src/commands/workflow.ts
|
|
4959
|
-
import { access as
|
|
4960
|
-
import
|
|
5693
|
+
import { access as access6 } from "fs/promises";
|
|
5694
|
+
import path23 from "path";
|
|
4961
5695
|
var EXIT_OK8 = 0;
|
|
4962
5696
|
var EXIT_NOT_INITIALIZED6 = 1;
|
|
4963
5697
|
var EXIT_INVALID_ARG2 = 2;
|
|
4964
5698
|
async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
4965
|
-
if (!await
|
|
5699
|
+
if (!await pathExists12(path23.join(rootDir, ".mancode", "state.json"))) {
|
|
4966
5700
|
if (options.json) {
|
|
4967
5701
|
console.log(JSON.stringify({ error: "not initialized" }, null, 2));
|
|
4968
5702
|
} else {
|
|
@@ -5219,9 +5953,9 @@ function ago(iso) {
|
|
|
5219
5953
|
if (hours < 24) return `${hours}h ago`;
|
|
5220
5954
|
return `${Math.floor(hours / 24)}d ago`;
|
|
5221
5955
|
}
|
|
5222
|
-
async function
|
|
5956
|
+
async function pathExists12(p) {
|
|
5223
5957
|
try {
|
|
5224
|
-
await
|
|
5958
|
+
await access6(p);
|
|
5225
5959
|
return true;
|
|
5226
5960
|
} catch {
|
|
5227
5961
|
return false;
|
|
@@ -5234,10 +5968,9 @@ program.command("init").description("Initialize mancode in the current project")
|
|
|
5234
5968
|
const code = await init(process.cwd(), options);
|
|
5235
5969
|
process.exitCode = code;
|
|
5236
5970
|
});
|
|
5237
|
-
program.command("install [platform]").description(
|
|
5238
|
-
"
|
|
5239
|
-
|
|
5240
|
-
).action(async (platform, options) => {
|
|
5971
|
+
program.command("install [platform]").description(
|
|
5972
|
+
"Install platform adapter (claude-code, cursor, codex, copilot, zcode)"
|
|
5973
|
+
).option("--force", "Reinstall even if already installed").option("--minimal", "Minimal install (MVP-2)").action(async (platform, options) => {
|
|
5241
5974
|
const code = await install(
|
|
5242
5975
|
process.cwd(),
|
|
5243
5976
|
platform ?? "claude-code",
|