@lovrozagar/grunt 0.4.1 → 0.5.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/.agents/agents/orchestrator.md +8 -7
- package/.agents/agents/thinker.md +6 -5
- package/.agents/skills/commit-and-push/SKILL.md +3 -3
- package/.agents/skills/explain/SKILL.md +1 -0
- package/.agents/skills/implement-plan/SKILL.md +116 -0
- package/.agents/skills/write-plan/SKILL.md +82 -0
- package/.claude/agents/orchestrator.md +8 -7
- package/.claude/agents/thinker.md +6 -5
- package/.claude/skills/commit-and-push/SKILL.md +3 -3
- package/.claude/skills/explain/SKILL.md +1 -0
- package/.claude/skills/implement-plan/SKILL.md +116 -0
- package/.claude/skills/write-plan/SKILL.md +82 -0
- package/.codex/agents/orchestrator.toml +8 -7
- package/.codex/agents/thinker.toml +6 -5
- package/.grok/agents/orchestrator.md +8 -7
- package/.grok/agents/thinker.md +6 -5
- package/.grok/hooks/orchestrate-parent.js +3 -100
- package/.grok/skills/commit-and-push/SKILL.md +3 -3
- package/.grok/skills/explain/SKILL.md +1 -0
- package/.grok/skills/implement-plan/SKILL.md +45 -17
- package/.grok/skills/write-plan/SKILL.md +11 -11
- package/.rulesync/reference/INDEX.md +43 -0
- package/.rulesync/reference/cascade.md +11 -9
- package/.rulesync/reference/hooks.md +4 -4
- package/.rulesync/reference/law.md +9 -0
- package/.rulesync/reference/map.md +12 -8
- package/.rulesync/reference/output.md +13 -8
- package/{.grok/skills/shared → .rulesync/reference}/plan-format.md +2 -1
- package/.rulesync/reference/refs-map.md +14 -0
- package/.rulesync/reference/skills-map.md +20 -0
- package/.rulesync/rules/CLAUDE.md +13 -11
- package/.rulesync/rules/overview.md +13 -11
- package/.rulesync/skills/commit-and-push/SKILL.md +3 -3
- package/.rulesync/skills/explain/SKILL.md +1 -0
- package/.rulesync/skills/implement-plan/SKILL.md +124 -0
- package/.rulesync/skills/write-plan/SKILL.md +88 -0
- package/.rulesync/subagents/orchestrator.md +8 -7
- package/.rulesync/subagents/thinker.md +6 -5
- package/AGENTS.md +13 -11
- package/CHANGELOG.md +210 -0
- package/CLAUDE.md +13 -11
- package/README.md +37 -16
- package/cli/init.mjs +21 -4
- package/cli/init.test.ts +97 -26
- package/package.json +6 -6
- package/scripts/doctor.mjs +64 -2
- package/scripts/emit-maps.mjs +422 -0
- package/scripts/grunt-job.mjs +0 -9
- package/scripts/guarded-roots.mjs +4 -7
- package/scripts/pipeline.mjs +67 -0
- package/scripts/skill-conflicts.mjs +71 -0
- package/scripts/telemetry.mjs +0 -38
package/cli/init.test.ts
CHANGED
|
@@ -44,10 +44,12 @@ const PRODUCT_FILES = [
|
|
|
44
44
|
"check-globals.mjs",
|
|
45
45
|
"emit-agent-shell-tools.mjs",
|
|
46
46
|
"emit-gemini.mjs",
|
|
47
|
+
"emit-maps.mjs",
|
|
47
48
|
"guarded-roots.mjs",
|
|
48
49
|
"emit-mcp-policy.mjs",
|
|
49
50
|
"gate-fat-tools.mjs",
|
|
50
51
|
"hooks-union.mjs",
|
|
52
|
+
"pipeline.mjs",
|
|
51
53
|
"grunt-job.mjs",
|
|
52
54
|
"parse-need.mjs",
|
|
53
55
|
"persist-handoff.mjs",
|
|
@@ -56,9 +58,9 @@ const PRODUCT_FILES = [
|
|
|
56
58
|
"scrub-spawn-prompt.mjs",
|
|
57
59
|
"scrub-text-lib.mjs",
|
|
58
60
|
"sync-global-settings.mjs",
|
|
59
|
-
"telemetry.mjs",
|
|
60
61
|
"browser.mjs",
|
|
61
62
|
"doctor.mjs",
|
|
63
|
+
"skill-conflicts.mjs",
|
|
62
64
|
];
|
|
63
65
|
|
|
64
66
|
const SRC_CLAUDE_SETTINGS = {
|
|
@@ -496,8 +498,15 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
496
498
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "fresh ssot\n");
|
|
497
499
|
});
|
|
498
500
|
runGuardedRoots("generate", { cwd: dest, exec });
|
|
499
|
-
expect(exec).
|
|
500
|
-
|
|
501
|
+
expect(exec.mock.calls.map((c) => c[0])).toEqual([
|
|
502
|
+
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills",
|
|
503
|
+
"rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks",
|
|
504
|
+
"node scripts/emit-mcp-policy.mjs",
|
|
505
|
+
"node scripts/emit-gemini.mjs",
|
|
506
|
+
"node scripts/emit-agent-shell-tools.mjs",
|
|
507
|
+
"node scripts/emit-maps.mjs",
|
|
508
|
+
"node scripts/hooks-union.mjs",
|
|
509
|
+
]);
|
|
501
510
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
502
511
|
"<!-- grunt:begin -->\nfresh ssot\n<!-- grunt:end -->\nkeep me\n",
|
|
503
512
|
);
|
|
@@ -596,8 +605,12 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
596
605
|
return stop;
|
|
597
606
|
},
|
|
598
607
|
});
|
|
599
|
-
expect(exec).
|
|
600
|
-
|
|
608
|
+
expect(exec.mock.calls.map((c) => c[0])).toEqual([
|
|
609
|
+
"node scripts/emit-mcp-policy.mjs",
|
|
610
|
+
"node scripts/emit-gemini.mjs",
|
|
611
|
+
"node scripts/emit-agent-shell-tools.mjs",
|
|
612
|
+
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
|
|
613
|
+
]);
|
|
601
614
|
expect(stop).toHaveBeenCalledTimes(1);
|
|
602
615
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
603
616
|
"<!-- grunt:begin -->\nwatch2\n<!-- grunt:end -->\nkeep me\n",
|
|
@@ -953,6 +966,45 @@ describe("mergePackageJson", () => {
|
|
|
953
966
|
warnSpy.mockRestore();
|
|
954
967
|
});
|
|
955
968
|
|
|
969
|
+
it("does not merge :raw scripts from src", () => {
|
|
970
|
+
const pkgRoot = stubPkgRoot({
|
|
971
|
+
name: "fixture-pkg",
|
|
972
|
+
scripts: {
|
|
973
|
+
"rulesync:generate": "node ./scripts/guarded-roots.mjs generate",
|
|
974
|
+
"rulesync:generate:raw": "echo raw",
|
|
975
|
+
"rulesync:check:raw": "echo check-raw",
|
|
976
|
+
"rulesync:watch:raw": "echo watch-raw",
|
|
977
|
+
},
|
|
978
|
+
devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
|
|
979
|
+
});
|
|
980
|
+
const dest = tmp("pj-raw-src-");
|
|
981
|
+
mergePackageJson(dest, pkgRoot);
|
|
982
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
983
|
+
expect(out.scripts["rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
|
|
984
|
+
expect(out.scripts["rulesync:generate:raw"]).toBeUndefined();
|
|
985
|
+
expect(out.scripts["rulesync:check:raw"]).toBeUndefined();
|
|
986
|
+
expect(out.scripts["rulesync:watch:raw"]).toBeUndefined();
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
it("leaves dest-only :raw keys", () => {
|
|
990
|
+
const pkgRoot = stubPkgRoot({
|
|
991
|
+
name: "fixture-pkg",
|
|
992
|
+
scripts: { "rulesync:generate": "node ./scripts/guarded-roots.mjs generate" },
|
|
993
|
+
devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
|
|
994
|
+
});
|
|
995
|
+
const dest = tmp("pj-raw-dest-");
|
|
996
|
+
fs.writeFileSync(
|
|
997
|
+
path.join(dest, "package.json"),
|
|
998
|
+
JSON.stringify({
|
|
999
|
+
scripts: { "rulesync:generate:raw": "echo consumer-raw" },
|
|
1000
|
+
}),
|
|
1001
|
+
);
|
|
1002
|
+
mergePackageJson(dest, pkgRoot);
|
|
1003
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1004
|
+
expect(out.scripts["rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
|
|
1005
|
+
expect(out.scripts["rulesync:generate:raw"]).toBe("echo consumer-raw");
|
|
1006
|
+
});
|
|
1007
|
+
|
|
956
1008
|
it("replaces a grunt-owned script with no suffix", () => {
|
|
957
1009
|
const pkgRoot = stubPkgRoot({
|
|
958
1010
|
name: "fixture-pkg",
|
|
@@ -1003,8 +1055,8 @@ describe("init", () => {
|
|
|
1003
1055
|
expect(pkg.name).toBe("fixture-pkg");
|
|
1004
1056
|
expect(fs.existsSync(path.join(pkgRoot, ".tmp"))).toBe(true);
|
|
1005
1057
|
expect(fs.readFileSync(path.join(pkgRoot, ".gitignore"), "utf8")).toBe(".tmp/\n");
|
|
1006
|
-
expect(fs.readFileSync(path.join(pkgRoot, "scripts", "
|
|
1007
|
-
"
|
|
1058
|
+
expect(fs.readFileSync(path.join(pkgRoot, "scripts", "grunt-job.mjs"), "utf8")).toBe(
|
|
1059
|
+
"grunt-job.mjs",
|
|
1008
1060
|
);
|
|
1009
1061
|
// self-skip: markdown/settings stay exactly as-is, no sentinel wrap
|
|
1010
1062
|
expect(fs.readFileSync(path.join(pkgRoot, "CLAUDE.md"), "utf8")).toBe("CLAUDE.md content");
|
|
@@ -1124,11 +1176,13 @@ describe("init", () => {
|
|
|
1124
1176
|
]);
|
|
1125
1177
|
});
|
|
1126
1178
|
|
|
1127
|
-
it("applyGlobals true still applies when
|
|
1179
|
+
it("applyGlobals true still applies when sentinel auto-skip would fire", () => {
|
|
1128
1180
|
const pkgRoot = stubPkgRoot();
|
|
1129
1181
|
const dest = tmp("grunt-force-globals-");
|
|
1130
|
-
fs.
|
|
1131
|
-
|
|
1182
|
+
fs.writeFileSync(
|
|
1183
|
+
path.join(dest, "AGENTS.md"),
|
|
1184
|
+
"<!-- grunt:begin -->\nold\n<!-- grunt:end -->\n",
|
|
1185
|
+
);
|
|
1132
1186
|
const exec = vi.fn();
|
|
1133
1187
|
init(dest, { pkgRoot, execFileSync: exec, applyGlobals: true });
|
|
1134
1188
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
@@ -1139,20 +1193,6 @@ describe("init", () => {
|
|
|
1139
1193
|
]);
|
|
1140
1194
|
});
|
|
1141
1195
|
|
|
1142
|
-
it("auto-skip globals when telemetry.mjs exists", () => {
|
|
1143
|
-
const pkgRoot = stubPkgRoot();
|
|
1144
|
-
const dest = tmp("grunt-auto-tel-");
|
|
1145
|
-
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true });
|
|
1146
|
-
fs.writeFileSync(path.join(dest, "scripts", "telemetry.mjs"), "existing");
|
|
1147
|
-
const exec = vi.fn();
|
|
1148
|
-
init(dest, { pkgRoot, execFileSync: exec });
|
|
1149
|
-
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1150
|
-
["install"],
|
|
1151
|
-
["run", "rulesync:generate"],
|
|
1152
|
-
["run", "rulesync:check"],
|
|
1153
|
-
]);
|
|
1154
|
-
});
|
|
1155
|
-
|
|
1156
1196
|
it("auto-skip globals when AGENTS.md already has a grunt sentinel", () => {
|
|
1157
1197
|
const pkgRoot = stubPkgRoot();
|
|
1158
1198
|
const dest = tmp("grunt-auto-sent-");
|
|
@@ -1174,8 +1214,7 @@ describe("init", () => {
|
|
|
1174
1214
|
const dest = tmp("grunt-rerun-nosent-");
|
|
1175
1215
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "hand-written consumer doc\n");
|
|
1176
1216
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "hand-written consumer doc\n");
|
|
1177
|
-
fs.mkdirSync(path.join(dest, "
|
|
1178
|
-
fs.writeFileSync(path.join(dest, "scripts", "telemetry.mjs"), "existing");
|
|
1217
|
+
fs.mkdirSync(path.join(dest, ".rulesync"), { recursive: true });
|
|
1179
1218
|
fs.writeFileSync(path.join(dest, "CLAUDE.grunt.md"), "stale side");
|
|
1180
1219
|
const exec = vi.fn();
|
|
1181
1220
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
@@ -1276,6 +1315,30 @@ describe("init", () => {
|
|
|
1276
1315
|
["merge", "stop"],
|
|
1277
1316
|
]);
|
|
1278
1317
|
});
|
|
1318
|
+
|
|
1319
|
+
it("warns when dest skill differs from packaged then force-overwrites; keeps extras", () => {
|
|
1320
|
+
const pkgRoot = stubPkgRoot();
|
|
1321
|
+
const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "parent");
|
|
1322
|
+
fs.mkdirSync(pkgSkill, { recursive: true });
|
|
1323
|
+
fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-parent\n");
|
|
1324
|
+
const dest = tmp("grunt-sk-conflict-");
|
|
1325
|
+
const destSkill = path.join(dest, ".rulesync", "skills", "parent");
|
|
1326
|
+
const destExtra = path.join(dest, ".rulesync", "skills", "my-extra");
|
|
1327
|
+
fs.mkdirSync(destSkill, { recursive: true });
|
|
1328
|
+
fs.mkdirSync(destExtra, { recursive: true });
|
|
1329
|
+
fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-parent\n");
|
|
1330
|
+
fs.writeFileSync(path.join(destExtra, "SKILL.md"), "keep-me\n");
|
|
1331
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1332
|
+
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true });
|
|
1333
|
+
expect(
|
|
1334
|
+
warnSpy.mock.calls.some(
|
|
1335
|
+
(c) => String(c[0]).includes("parent") && String(c[0]).includes("re-init overwrites"),
|
|
1336
|
+
),
|
|
1337
|
+
).toBe(true);
|
|
1338
|
+
warnSpy.mockRestore();
|
|
1339
|
+
expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-parent\n");
|
|
1340
|
+
expect(fs.readFileSync(path.join(destExtra, "SKILL.md"), "utf8")).toBe("keep-me\n");
|
|
1341
|
+
});
|
|
1279
1342
|
});
|
|
1280
1343
|
|
|
1281
1344
|
describe("destAlreadyInited / shouldAutoSkipGlobals", () => {
|
|
@@ -1292,6 +1355,14 @@ describe("destAlreadyInited / shouldAutoSkipGlobals", () => {
|
|
|
1292
1355
|
expect(shouldAutoSkipGlobals(dest)).toBe(false);
|
|
1293
1356
|
});
|
|
1294
1357
|
|
|
1358
|
+
it(".grok/hooks/orchestrate-parent.js means inited, not auto-skip", () => {
|
|
1359
|
+
const dest = tmp("inspect-hook-");
|
|
1360
|
+
fs.mkdirSync(path.join(dest, ".grok", "hooks"), { recursive: true });
|
|
1361
|
+
fs.writeFileSync(path.join(dest, ".grok", "hooks", "orchestrate-parent.js"), "");
|
|
1362
|
+
expect(destAlreadyInited(dest)).toBe(true);
|
|
1363
|
+
expect(shouldAutoSkipGlobals(dest)).toBe(false);
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1295
1366
|
it("sentinel auto-skips globals", () => {
|
|
1296
1367
|
const dest = tmp("inspect-sent-");
|
|
1297
1368
|
fs.writeFileSync(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovrozagar/grunt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Multi-agent orchestrator SoT — deep-merge rulesync trees, product scripts, and generate pipeline into a consumer repo",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -27,10 +27,12 @@
|
|
|
27
27
|
"scripts/check-globals.mjs",
|
|
28
28
|
"scripts/emit-agent-shell-tools.mjs",
|
|
29
29
|
"scripts/emit-gemini.mjs",
|
|
30
|
+
"scripts/emit-maps.mjs",
|
|
30
31
|
"scripts/guarded-roots.mjs",
|
|
31
32
|
"scripts/emit-mcp-policy.mjs",
|
|
32
33
|
"scripts/gate-fat-tools.mjs",
|
|
33
34
|
"scripts/hooks-union.mjs",
|
|
35
|
+
"scripts/pipeline.mjs",
|
|
34
36
|
"scripts/grunt-job.mjs",
|
|
35
37
|
"scripts/parse-need.mjs",
|
|
36
38
|
"scripts/persist-handoff.mjs",
|
|
@@ -39,9 +41,9 @@
|
|
|
39
41
|
"scripts/scrub-spawn-prompt.mjs",
|
|
40
42
|
"scripts/scrub-text-lib.mjs",
|
|
41
43
|
"scripts/sync-global-settings.mjs",
|
|
42
|
-
"scripts/telemetry.mjs",
|
|
43
44
|
"scripts/browser.mjs",
|
|
44
45
|
"scripts/doctor.mjs",
|
|
46
|
+
"scripts/skill-conflicts.mjs",
|
|
45
47
|
"scripts/scrub-text",
|
|
46
48
|
".rulesync",
|
|
47
49
|
".grok",
|
|
@@ -52,7 +54,8 @@
|
|
|
52
54
|
"CLAUDE.md",
|
|
53
55
|
".mcp.json",
|
|
54
56
|
"README.md",
|
|
55
|
-
"LICENSE"
|
|
57
|
+
"LICENSE",
|
|
58
|
+
"CHANGELOG.md"
|
|
56
59
|
],
|
|
57
60
|
"type": "module",
|
|
58
61
|
"publishConfig": {
|
|
@@ -63,11 +66,8 @@
|
|
|
63
66
|
},
|
|
64
67
|
"scripts": {
|
|
65
68
|
"rulesync:generate": "node ./scripts/guarded-roots.mjs generate",
|
|
66
|
-
"rulesync:generate:raw": "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks && node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && node scripts/hooks-union.mjs",
|
|
67
69
|
"rulesync:watch": "node ./scripts/guarded-roots.mjs watch",
|
|
68
|
-
"rulesync:watch:raw": "node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
|
|
69
70
|
"rulesync:check": "node ./scripts/guarded-roots.mjs check",
|
|
70
|
-
"rulesync:check:raw": "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,skills --check && rulesync generate -t codexcli,antigravity-cli,grokcli -f subagents --check && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks --check && node scripts/emit-mcp-policy.mjs --check && node scripts/emit-gemini.mjs --check && node scripts/emit-agent-shell-tools.mjs --check && node scripts/check-globals.mjs && node scripts/hooks-union.mjs --check",
|
|
71
71
|
"hooks:union": "node scripts/hooks-union.mjs",
|
|
72
72
|
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
73
73
|
"doctor": "node ./scripts/doctor.mjs",
|
package/scripts/doctor.mjs
CHANGED
|
@@ -4,6 +4,12 @@ import { execFileSync } from "node:child_process";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import {
|
|
8
|
+
PACKAGED_SKILLS_REL,
|
|
9
|
+
WORKSPACE_SKILLS_REL,
|
|
10
|
+
findSkillContentConflicts,
|
|
11
|
+
formatSkillConflictWarn,
|
|
12
|
+
} from "./skill-conflicts.mjs";
|
|
7
13
|
|
|
8
14
|
export const CHROMIUM_BINS = [
|
|
9
15
|
"chromium",
|
|
@@ -23,6 +29,12 @@ const RTK_DOCS = "https://www.rtk-ai.app/docs/getting-started/installation/";
|
|
|
23
29
|
const LP_INSTALL_SH = "curl -fsSL https://pkg.lightpanda.io/install.sh | bash";
|
|
24
30
|
const RULESYNC_SCHEMA = "rulesync schema doctor: npm run rulesync:doctor";
|
|
25
31
|
const REQUIRED = ["node", "npm", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
32
|
+
export const REQUIRED_MAP_FILES = [
|
|
33
|
+
{ name: "INDEX.md", rel: ".rulesync/reference/INDEX.md" },
|
|
34
|
+
{ name: "skills-map.md", rel: ".rulesync/reference/skills-map.md" },
|
|
35
|
+
{ name: "refs-map.md", rel: ".rulesync/reference/refs-map.md" },
|
|
36
|
+
{ name: "law.md", rel: ".rulesync/reference/law.md" },
|
|
37
|
+
];
|
|
26
38
|
|
|
27
39
|
export function nodeMajor(version) {
|
|
28
40
|
const m = String(version || "").match(/(\d+)/);
|
|
@@ -158,12 +170,35 @@ export function installHints(id, platform = process.platform) {
|
|
|
158
170
|
}
|
|
159
171
|
|
|
160
172
|
function row(name, status, extra = "") {
|
|
161
|
-
const a = String(name).padEnd(
|
|
173
|
+
const a = String(name).padEnd(14);
|
|
162
174
|
const b = String(status).padEnd(18);
|
|
163
175
|
const c = extra ? String(extra).trim() : "";
|
|
164
176
|
return c ? `${a} ${b} ${c}` : `${a} ${b}`;
|
|
165
177
|
}
|
|
166
178
|
|
|
179
|
+
function mapFilePresent(cwd, rel) {
|
|
180
|
+
try {
|
|
181
|
+
return fs.statSync(path.join(cwd, rel)).isFile();
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isDir(abs) {
|
|
188
|
+
try {
|
|
189
|
+
return fs.statSync(abs).isDirectory();
|
|
190
|
+
} catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Product SoT: reference tree or skills (emit-maps in play). No .rulesync / no those trees → bins-only. */
|
|
196
|
+
export function mapsRequired(cwd) {
|
|
197
|
+
const rs = path.join(cwd || "", ".rulesync");
|
|
198
|
+
if (!isDir(rs)) return false;
|
|
199
|
+
return isDir(path.join(rs, "reference")) || isDir(path.join(rs, "skills"));
|
|
200
|
+
}
|
|
201
|
+
|
|
167
202
|
export function runDoctor({
|
|
168
203
|
cwd = process.cwd(),
|
|
169
204
|
pathEnv = process.env.PATH,
|
|
@@ -232,10 +267,37 @@ export function runDoctor({
|
|
|
232
267
|
}
|
|
233
268
|
}
|
|
234
269
|
|
|
270
|
+
const missingMaps = [];
|
|
271
|
+
if (mapsRequired(cwd)) {
|
|
272
|
+
for (const f of REQUIRED_MAP_FILES) {
|
|
273
|
+
if (mapFilePresent(cwd, f.rel)) {
|
|
274
|
+
lines.push(row(f.name, "ok", f.rel));
|
|
275
|
+
} else {
|
|
276
|
+
missingMaps.push(f);
|
|
277
|
+
lines.push(row(f.name, "missing", f.rel));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (missingMaps.length) {
|
|
281
|
+
lines.push(`maps missing: ${missingMaps.map((f) => f.name).join(", ")}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const skillConflicts = findSkillContentConflicts({
|
|
286
|
+
workspaceSkillsDir: path.join(cwd || "", WORKSPACE_SKILLS_REL),
|
|
287
|
+
packagedSkillsDir: path.join(cwd || "", PACKAGED_SKILLS_REL),
|
|
288
|
+
});
|
|
289
|
+
if (skillConflicts.length) {
|
|
290
|
+
lines.push("");
|
|
291
|
+
lines.push("skill conflicts (warn; re-init overwrites grunt-owned names):");
|
|
292
|
+
for (const c of skillConflicts) {
|
|
293
|
+
lines.push(formatSkillConflictWarn(c.name));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
235
297
|
lines.push("");
|
|
236
298
|
lines.push(RULESYNC_SCHEMA);
|
|
237
299
|
const stdout = `${lines.join("\n")}\n`;
|
|
238
|
-
return { code: missingRequired.length ? 1 : 0, stdout, stderr: "" };
|
|
300
|
+
return { code: missingRequired.length || missingMaps.length ? 1 : 0, stdout, stderr: "" };
|
|
239
301
|
}
|
|
240
302
|
|
|
241
303
|
function main() {
|