@lovrozagar/grunt 0.10.0 → 0.11.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 +1 -1
- package/.agents/hooks.json +2 -2
- package/.agents/skills/browser/SKILL.md +3 -3
- package/.agents/skills/clasp/SKILL.md +1 -1
- package/.agents/skills/explain/SKILL.md +1 -1
- package/.agents/skills/google-workspace/SKILL.md +3 -3
- package/.agents/skills/handoff/SKILL.md +2 -2
- package/.agents/skills/implement-plan/SKILL.md +2 -2
- package/.agents/skills/jev/SKILL.md +3 -3
- package/.agents/skills/listen/SKILL.md +3 -3
- package/.agents/skills/pickup/SKILL.md +1 -1
- package/.agents/skills/speak/SKILL.md +3 -3
- package/.agents/skills/tmp/SKILL.md +2 -2
- package/.agents/skills/write-plan/SKILL.md +4 -2
- package/.claude/agents/orchestrator.md +1 -1
- package/.claude/settings.json +3 -3
- package/.claude/skills/browser/SKILL.md +3 -3
- package/.claude/skills/clasp/SKILL.md +1 -1
- package/.claude/skills/explain/SKILL.md +1 -1
- package/.claude/skills/google-workspace/SKILL.md +3 -3
- package/.claude/skills/handoff/SKILL.md +2 -2
- package/.claude/skills/implement-plan/SKILL.md +2 -2
- package/.claude/skills/jev/SKILL.md +3 -3
- package/.claude/skills/listen/SKILL.md +3 -3
- package/.claude/skills/pickup/SKILL.md +1 -1
- package/.claude/skills/speak/SKILL.md +3 -3
- package/.claude/skills/tmp/SKILL.md +2 -2
- package/.claude/skills/write-plan/SKILL.md +4 -2
- package/.codex/agents/orchestrator.toml +1 -1
- package/.codex/hooks.json +3 -3
- package/.grok/agents/orchestrator.md +1 -1
- package/.grok/hooks/orchestrate-parent.js +9 -9
- package/.grok/skills/browser/SKILL.md +3 -3
- package/.grok/skills/clasp/SKILL.md +1 -1
- package/.grok/skills/explain/SKILL.md +1 -1
- package/.grok/skills/google-workspace/SKILL.md +3 -3
- package/.grok/skills/handoff/SKILL.md +2 -2
- package/.grok/skills/implement-plan/SKILL.md +2 -2
- package/.grok/skills/jev/SKILL.md +3 -3
- package/.grok/skills/listen/SKILL.md +3 -3
- package/.grok/skills/pickup/SKILL.md +1 -1
- package/.grok/skills/speak/SKILL.md +3 -3
- package/.grok/skills/tmp/SKILL.md +2 -2
- package/.grok/skills/write-plan/SKILL.md +4 -2
- package/.rulesync/hooks.jsonc +3 -3
- package/.rulesync/reference/INDEX.md +10 -10
- package/.rulesync/reference/browser.md +4 -4
- package/.rulesync/reference/cascade.md +2 -2
- package/.rulesync/reference/clasp.md +1 -1
- package/.rulesync/reference/google-workspace.md +29 -29
- package/.rulesync/reference/hooks.md +3 -3
- package/.rulesync/reference/implementation-format.md +2 -2
- package/.rulesync/reference/jev.md +3 -3
- package/.rulesync/reference/law.md +1 -1
- package/.rulesync/reference/listen.md +12 -12
- package/.rulesync/reference/map.md +25 -25
- package/.rulesync/reference/output.md +9 -1
- package/.rulesync/reference/refs-map.md +4 -4
- package/.rulesync/reference/rtk.md +1 -1
- package/.rulesync/reference/scope.md +12 -14
- package/.rulesync/reference/setup.md +7 -7
- package/.rulesync/reference/skills-map.md +5 -5
- package/.rulesync/reference/speak.md +6 -6
- package/.rulesync/rules/overview.md +9 -5
- package/{scripts → .rulesync/scripts}/doctor.mjs +2 -2
- package/{scripts → .rulesync/scripts}/feature-packs.mjs +1 -1
- package/{scripts → .rulesync/scripts}/folder-map.mjs +7 -3
- package/{scripts → .rulesync/scripts}/gate-fat-tools.mjs +3 -3
- package/{scripts → .rulesync/scripts}/google-workspace.mjs +2 -2
- package/{scripts → .rulesync/scripts}/grunt-job.mjs +2 -0
- package/{scripts → .rulesync/scripts}/guarded-md.mjs +10 -4
- package/{scripts → .rulesync/scripts}/hooks-union.mjs +13 -8
- package/{scripts → .rulesync/scripts}/jev.mjs +2 -2
- package/{scripts → .rulesync/scripts}/listen.mjs +6 -6
- package/{scripts → .rulesync/scripts}/pipeline.mjs +14 -14
- package/{scripts → .rulesync/scripts}/session-map.mjs +1 -1
- package/{scripts → .rulesync/scripts}/setup.mjs +4 -4
- package/{scripts → .rulesync/scripts}/speak.mjs +4 -4
- package/.rulesync/skills/browser/SKILL.md +3 -3
- package/.rulesync/skills/clasp/SKILL.md +1 -1
- package/.rulesync/skills/explain/SKILL.md +1 -1
- package/.rulesync/skills/google-workspace/SKILL.md +3 -3
- package/.rulesync/skills/handoff/SKILL.md +2 -2
- package/.rulesync/skills/implement-plan/SKILL.md +2 -2
- package/.rulesync/skills/jev/SKILL.md +3 -3
- package/.rulesync/skills/listen/SKILL.md +3 -3
- package/.rulesync/skills/pickup/SKILL.md +1 -1
- package/.rulesync/skills/speak/SKILL.md +3 -3
- package/.rulesync/skills/tmp/SKILL.md +2 -2
- package/.rulesync/skills/write-plan/SKILL.md +4 -2
- package/.rulesync/subagents/orchestrator.md +1 -1
- package/AGENTS.md +11 -5
- package/CHANGELOG.md +22 -0
- package/CLAUDE.md +2 -0
- package/README.md +23 -23
- package/cli/grunt.mjs +3 -3
- package/cli/grunt.test.ts +6 -6
- package/cli/init.mjs +64 -16
- package/cli/init.test.ts +310 -163
- package/cli/prompt.mjs +1 -1
- package/cli/self-update.mjs +1 -1
- package/package.json +15 -50
- /package/{scripts → .rulesync/scripts}/board.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/browser.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/check-globals.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-agent-shell-tools.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-gemini.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-maps.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-mcp-policy.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/guarded-roots.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/interactive.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/jsonc.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/package-manager.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/parse-need.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-handoff.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-implementation.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-plan.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-tmp.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/prompt.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/purge-global-mcps.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/scrub-spawn-prompt.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/scrub-text +0 -0
- /package/{scripts → .rulesync/scripts}/scrub-text-lib.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/skill-conflicts.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/sync-global-settings.mjs +0 -0
package/cli/init.test.ts
CHANGED
|
@@ -8,8 +8,8 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
8
8
|
const emitMaps = vi.hoisted(() => vi.fn());
|
|
9
9
|
const actualEmitMaps = vi.hoisted(() => ({ fn: null as null | ((...args: unknown[]) => unknown) }));
|
|
10
10
|
|
|
11
|
-
vi.mock("
|
|
12
|
-
const actual = await importOriginal<typeof import("
|
|
11
|
+
vi.mock("../.rulesync/scripts/emit-maps.mjs", async (importOriginal) => {
|
|
12
|
+
const actual = await importOriginal<typeof import("../.rulesync/scripts/emit-maps.mjs")>();
|
|
13
13
|
actualEmitMaps.fn = actual.emitMaps;
|
|
14
14
|
emitMaps.mockImplementation((...args: unknown[]) =>
|
|
15
15
|
actual.emitMaps(...(args as Parameters<typeof actual.emitMaps>)),
|
|
@@ -19,6 +19,10 @@ vi.mock("../scripts/emit-maps.mjs", async (importOriginal) => {
|
|
|
19
19
|
import {
|
|
20
20
|
MAX_GUARDED_MARKDOWN_BYTES,
|
|
21
21
|
PRODUCT_SCRIPTS,
|
|
22
|
+
SCRIPTS_REL,
|
|
23
|
+
LEGACY_SCRIPTS_REL,
|
|
24
|
+
pruneLegacyScripts,
|
|
25
|
+
rewriteLegacyScriptRefs,
|
|
22
26
|
composeGuardedMarkdown,
|
|
23
27
|
copyTree,
|
|
24
28
|
destAlreadyInited,
|
|
@@ -61,9 +65,9 @@ import {
|
|
|
61
65
|
withGuardedCheckInteriors,
|
|
62
66
|
writeMergedGuardedFile,
|
|
63
67
|
} from "./init.mjs";
|
|
64
|
-
import { UNKNOWN_PACKAGE_MANAGER } from "
|
|
65
|
-
import { runGuardedRoots } from "
|
|
66
|
-
import { COMMANDS as PIPELINE_COMMANDS } from "
|
|
68
|
+
import { UNKNOWN_PACKAGE_MANAGER } from "../.rulesync/scripts/package-manager.mjs";
|
|
69
|
+
import { runGuardedRoots } from "../.rulesync/scripts/guarded-roots.mjs";
|
|
70
|
+
import { COMMANDS as PIPELINE_COMMANDS } from "../.rulesync/scripts/pipeline.mjs";
|
|
67
71
|
|
|
68
72
|
const tmpDirs: string[] = [];
|
|
69
73
|
afterEach(() => {
|
|
@@ -97,7 +101,7 @@ const SRC_CLAUDE_SETTINGS = {
|
|
|
97
101
|
hooks: [
|
|
98
102
|
{
|
|
99
103
|
type: "command",
|
|
100
|
-
command: 'node "${ROOT}/scripts/session-map.mjs"',
|
|
104
|
+
command: 'node "${ROOT}/.rulesync/scripts/session-map.mjs"',
|
|
101
105
|
timeout: 5,
|
|
102
106
|
},
|
|
103
107
|
],
|
|
@@ -109,7 +113,7 @@ const SRC_CLAUDE_SETTINGS = {
|
|
|
109
113
|
hooks: [
|
|
110
114
|
{
|
|
111
115
|
type: "command",
|
|
112
|
-
command: 'node "${ROOT}/scripts/scrub-spawn-prompt.mjs"',
|
|
116
|
+
command: 'node "${ROOT}/.rulesync/scripts/scrub-spawn-prompt.mjs"',
|
|
113
117
|
},
|
|
114
118
|
],
|
|
115
119
|
},
|
|
@@ -118,7 +122,7 @@ const SRC_CLAUDE_SETTINGS = {
|
|
|
118
122
|
hooks: [
|
|
119
123
|
{
|
|
120
124
|
type: "command",
|
|
121
|
-
command: 'node "${ROOT}/scripts/gate-fat-tools.mjs"',
|
|
125
|
+
command: 'node "${ROOT}/.rulesync/scripts/gate-fat-tools.mjs"',
|
|
122
126
|
},
|
|
123
127
|
],
|
|
124
128
|
},
|
|
@@ -166,12 +170,12 @@ function stubPkgRoot(pkg: Record<string, unknown> = {
|
|
|
166
170
|
for (const f of GUARDED_MD_FILES) {
|
|
167
171
|
fs.writeFileSync(path.join(root, f), `${f} content`);
|
|
168
172
|
}
|
|
169
|
-
fs.mkdirSync(path.join(root, "scripts"));
|
|
173
|
+
fs.mkdirSync(path.join(root, ".rulesync", "scripts"));
|
|
170
174
|
for (const name of PRODUCT_FILES) {
|
|
171
|
-
fs.writeFileSync(path.join(root, "scripts", name), name);
|
|
175
|
+
fs.writeFileSync(path.join(root, ".rulesync", "scripts", name), name);
|
|
172
176
|
}
|
|
173
|
-
fs.mkdirSync(path.join(root, "scripts", "scrub-text"));
|
|
174
|
-
fs.writeFileSync(path.join(root, "scripts", "scrub-text", "inside"), "dir");
|
|
177
|
+
fs.mkdirSync(path.join(root, ".rulesync", "scripts", "scrub-text"));
|
|
178
|
+
fs.writeFileSync(path.join(root, ".rulesync", "scripts", "scrub-text", "inside"), "dir");
|
|
175
179
|
fs.writeFileSync(path.join(root, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
176
180
|
return root;
|
|
177
181
|
}
|
|
@@ -264,7 +268,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
264
268
|
const dest = tmp("md-absent-");
|
|
265
269
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
266
270
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
267
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n",
|
|
271
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n",
|
|
268
272
|
);
|
|
269
273
|
});
|
|
270
274
|
|
|
@@ -274,7 +278,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
274
278
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "hand-written consumer doc\n");
|
|
275
279
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
276
280
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
277
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
281
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
278
282
|
);
|
|
279
283
|
expect(fs.existsSync(path.join(dest, "CLAUDE.grunt.md"))).toBe(false);
|
|
280
284
|
});
|
|
@@ -285,7 +289,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
285
289
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "hand-written consumer doc\n");
|
|
286
290
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md", { alreadyInited: true });
|
|
287
291
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
288
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
292
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
289
293
|
);
|
|
290
294
|
expect(fs.existsSync(path.join(dest, "CLAUDE.grunt.md"))).toBe(false);
|
|
291
295
|
});
|
|
@@ -296,7 +300,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
296
300
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "");
|
|
297
301
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
298
302
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
299
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n",
|
|
303
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n",
|
|
300
304
|
);
|
|
301
305
|
});
|
|
302
306
|
|
|
@@ -309,7 +313,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
309
313
|
);
|
|
310
314
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
311
315
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
312
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nbefore\nafter\n",
|
|
316
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nbefore\nafter\n",
|
|
313
317
|
);
|
|
314
318
|
expect(fs.existsSync(path.join(dest, "CLAUDE.grunt.md"))).toBe(false);
|
|
315
319
|
});
|
|
@@ -324,7 +328,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
324
328
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
325
329
|
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
326
330
|
expect(out).toBe(
|
|
327
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nkeep-a\nkeep-b\n",
|
|
331
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nkeep-a\nkeep-b\n",
|
|
328
332
|
);
|
|
329
333
|
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
330
334
|
});
|
|
@@ -336,7 +340,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
336
340
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
337
341
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
338
342
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
339
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
343
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
340
344
|
);
|
|
341
345
|
});
|
|
342
346
|
|
|
@@ -349,7 +353,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
349
353
|
const dest = tmp("md-wrapped-src-");
|
|
350
354
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
351
355
|
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
352
|
-
expect(out).toBe("<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n");
|
|
356
|
+
expect(out).toBe("<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n");
|
|
353
357
|
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
354
358
|
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
355
359
|
});
|
|
@@ -363,7 +367,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
363
367
|
);
|
|
364
368
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
365
369
|
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
366
|
-
expect(out).toBe("<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n");
|
|
370
|
+
expect(out).toBe("<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n");
|
|
367
371
|
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
368
372
|
});
|
|
369
373
|
|
|
@@ -380,7 +384,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
380
384
|
);
|
|
381
385
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
382
386
|
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
383
|
-
expect(out).toBe("<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n");
|
|
387
|
+
expect(out).toBe("<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n");
|
|
384
388
|
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
385
389
|
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
386
390
|
});
|
|
@@ -395,7 +399,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
395
399
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
396
400
|
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
397
401
|
expect(out).toBe(
|
|
398
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nprefix\nsuffix\n",
|
|
402
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nprefix\nsuffix\n",
|
|
399
403
|
);
|
|
400
404
|
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
401
405
|
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
@@ -408,7 +412,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
408
412
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), user);
|
|
409
413
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
410
414
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
411
|
-
`<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n${user}`,
|
|
415
|
+
`<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\n${user}`,
|
|
412
416
|
);
|
|
413
417
|
});
|
|
414
418
|
|
|
@@ -418,7 +422,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
418
422
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "user line\r\n");
|
|
419
423
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
420
424
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
421
|
-
"<!-- grunt:begin -->\r\nCLAUDE.md content\r\n<!-- grunt:end -->\r\nuser line\r\n",
|
|
425
|
+
"<!-- grunt:begin -->\r\n\r\nCLAUDE.md content\r\n\r\n<!-- grunt:end -->\r\n\r\nuser line\r\n",
|
|
422
426
|
);
|
|
423
427
|
});
|
|
424
428
|
|
|
@@ -428,7 +432,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
428
432
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "CLAUDE.md content\n");
|
|
429
433
|
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
430
434
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
431
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n",
|
|
435
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n",
|
|
432
436
|
);
|
|
433
437
|
});
|
|
434
438
|
|
|
@@ -449,7 +453,7 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
449
453
|
mergeGuardedMarkdown(dest, pkgRoot, "AGENTS.md");
|
|
450
454
|
expect(fs.readFileSync(path.join(dest, "pkg", "AGENTS.md"), "utf8")).toBe("nested user\n");
|
|
451
455
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
452
|
-
"<!-- grunt:begin -->\nAGENTS.md content\n<!-- grunt:end -->\n",
|
|
456
|
+
"<!-- grunt:begin -->\n\nAGENTS.md content\n\n<!-- grunt:end -->\n",
|
|
453
457
|
);
|
|
454
458
|
});
|
|
455
459
|
});
|
|
@@ -467,10 +471,10 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
467
471
|
fs.writeFileSync(path.join(dest, "GEMINI.md"), "@AGENTS.md\n");
|
|
468
472
|
remergeGuardedRoots(dest, snap);
|
|
469
473
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
470
|
-
"<!-- grunt:begin -->\ngenerated agents\n<!-- grunt:end -->\nconsumer agents\n",
|
|
474
|
+
"<!-- grunt:begin -->\n\ngenerated agents\n\n<!-- grunt:end -->\n\nconsumer agents\n",
|
|
471
475
|
);
|
|
472
476
|
expect(fs.readFileSync(path.join(dest, "GEMINI.md"), "utf8")).toBe(
|
|
473
|
-
"<!-- grunt:begin -->\n@AGENTS.md\n<!-- grunt:end -->\nuser gemini\n",
|
|
477
|
+
"<!-- grunt:begin -->\n\n@AGENTS.md\n\n<!-- grunt:end -->\n\nuser gemini\n",
|
|
474
478
|
);
|
|
475
479
|
});
|
|
476
480
|
|
|
@@ -486,7 +490,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
486
490
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "generated agents\n");
|
|
487
491
|
remergeGuardedRoots(dest, snap);
|
|
488
492
|
const out = fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8");
|
|
489
|
-
expect(out).toBe("<!-- grunt:begin -->\ngenerated agents\n<!-- grunt:end -->\n");
|
|
493
|
+
expect(out).toBe("<!-- grunt:begin -->\n\ngenerated agents\n\n<!-- grunt:end -->\n");
|
|
490
494
|
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
491
495
|
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
492
496
|
});
|
|
@@ -521,12 +525,12 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
521
525
|
"<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n",
|
|
522
526
|
"<!-- grunt:end -->\n",
|
|
523
527
|
),
|
|
524
|
-
).toBe("<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n");
|
|
528
|
+
).toBe("<!-- grunt:begin -->\n\nbody\n\n<!-- grunt:end -->\n");
|
|
525
529
|
expect(composeGuardedMarkdown("body", null)).toBe(
|
|
526
|
-
"<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n",
|
|
530
|
+
"<!-- grunt:begin -->\n\nbody\n\n<!-- grunt:end -->\n",
|
|
527
531
|
);
|
|
528
532
|
expect(composeGuardedMarkdown("body", "\n\n")).toBe(
|
|
529
|
-
"<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n",
|
|
533
|
+
"<!-- grunt:begin -->\n\nbody\n\n<!-- grunt:end -->\n",
|
|
530
534
|
);
|
|
531
535
|
expect(extractGruntBody("no markers")).toBeNull();
|
|
532
536
|
expect(
|
|
@@ -535,26 +539,26 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
535
539
|
),
|
|
536
540
|
).toBe("a\nb");
|
|
537
541
|
expect(mergeGuardedContent(null, "g")).toBe(
|
|
538
|
-
"<!-- grunt:begin -->\ng\n<!-- grunt:end -->\n",
|
|
542
|
+
"<!-- grunt:begin -->\n\ng\n\n<!-- grunt:end -->\n",
|
|
539
543
|
);
|
|
540
544
|
expect(mergeGuardedContent("", "a\r\nb")).toBe(
|
|
541
|
-
"<!-- grunt:begin -->\r\na\r\nb\r\n<!-- grunt:end -->\r\n",
|
|
545
|
+
"<!-- grunt:begin -->\r\n\r\na\r\nb\r\n\r\n<!-- grunt:end -->\r\n",
|
|
542
546
|
);
|
|
543
547
|
expect(composeGuardedMarkdown("b", "\r\n")).toBe(
|
|
544
|
-
"<!-- grunt:begin -->\nb\n<!-- grunt:end -->\n",
|
|
548
|
+
"<!-- grunt:begin -->\n\nb\n\n<!-- grunt:end -->\n",
|
|
545
549
|
);
|
|
546
550
|
expect(mergeGuardedContent("keep\n", "g\r\n")).toBe(
|
|
547
|
-
"<!-- grunt:begin -->\ng\n<!-- grunt:end -->\nkeep\n",
|
|
551
|
+
"<!-- grunt:begin -->\n\ng\n\n<!-- grunt:end -->\n\nkeep\n",
|
|
548
552
|
);
|
|
549
553
|
expect(mergeGuardedContent("<!-- grunt:begin --> only", "g")).toBe(
|
|
550
|
-
"<!-- grunt:begin -->\ng\n<!-- grunt:end -->\n",
|
|
554
|
+
"<!-- grunt:begin -->\n\ng\n\n<!-- grunt:end -->\n",
|
|
551
555
|
);
|
|
552
556
|
const dest = tmp("md-helpers-");
|
|
553
557
|
const missing = path.join(dest, "nope.md");
|
|
554
558
|
expect(guardedMarkdownDrift(missing, "g")).toBe(true);
|
|
555
559
|
writeMergedGuardedFile(missing, "g");
|
|
556
560
|
expect(fs.readFileSync(missing, "utf8")).toBe(
|
|
557
|
-
"<!-- grunt:begin -->\ng\n<!-- grunt:end -->\n",
|
|
561
|
+
"<!-- grunt:begin -->\n\ng\n\n<!-- grunt:end -->\n",
|
|
558
562
|
);
|
|
559
563
|
expect(guardedMarkdownDrift(missing, "g")).toBe(false);
|
|
560
564
|
expect(guardedMarkdownDrift(missing, "other")).toBe(true);
|
|
@@ -569,15 +573,30 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
569
573
|
expect(writeMergedGuardedFile(huge, "g")).toBe("aborted-unsafe");
|
|
570
574
|
});
|
|
571
575
|
|
|
576
|
+
it("formatter-padded sentinels are a fixed point and do not drift", () => {
|
|
577
|
+
const padded = "<!-- grunt:begin -->\n\n@AGENTS.md\n\n<!-- grunt:end -->\n";
|
|
578
|
+
const withUser = "<!-- grunt:begin -->\n\nbody\n\n<!-- grunt:end -->\n\n# Mine\n";
|
|
579
|
+
expect(extractGruntBody(padded)).toBe("@AGENTS.md");
|
|
580
|
+
expect(mergeGuardedContent(padded, "@AGENTS.md\n")).toBe(padded);
|
|
581
|
+
expect(mergeGuardedContent(withUser, "body")).toBe(withUser);
|
|
582
|
+
expect(extractUserMarkdown(withUser)).toBe("\n# Mine\n");
|
|
583
|
+
expect(
|
|
584
|
+
mergeGuardedContent("<!-- grunt:begin -->\n@AGENTS.md\n<!-- grunt:end -->\nuser\n", "@AGENTS.md"),
|
|
585
|
+
).toBe("<!-- grunt:begin -->\n\n@AGENTS.md\n\n<!-- grunt:end -->\n\nuser\n");
|
|
586
|
+
const file = path.join(tmp("md-padded-"), "GEMINI.md");
|
|
587
|
+
fs.writeFileSync(file, padded);
|
|
588
|
+
expect(guardedMarkdownDrift(file, "@AGENTS.md")).toBe(false);
|
|
589
|
+
});
|
|
590
|
+
|
|
572
591
|
it("unclosed begin unwraps interior; inline orphan end is stripped", () => {
|
|
573
592
|
expect(extractGruntBody("<!-- grunt:begin -->\nhello")).toBe("hello");
|
|
574
593
|
const composed = composeGuardedMarkdown("<!-- grunt:begin -->\nhello", "");
|
|
575
|
-
expect(composed).toBe("<!-- grunt:begin -->\nhello\n<!-- grunt:end -->\n");
|
|
594
|
+
expect(composed).toBe("<!-- grunt:begin -->\n\nhello\n\n<!-- grunt:end -->\n");
|
|
576
595
|
expect(composed.split(SENTINEL_BEGIN)).toHaveLength(2);
|
|
577
596
|
expect(composed.split(SENTINEL_END)).toHaveLength(2);
|
|
578
597
|
expect(composed).not.toContain(`${SENTINEL_BEGIN}\n${SENTINEL_BEGIN}`);
|
|
579
598
|
expect(mergeGuardedContent("note\n<!-- grunt:begin -->\nstale", "new")).toBe(
|
|
580
|
-
"<!-- grunt:begin -->\nnew\n<!-- grunt:end -->\nnote\n",
|
|
599
|
+
"<!-- grunt:begin -->\n\nnew\n\n<!-- grunt:end -->\n\nnote\n",
|
|
581
600
|
);
|
|
582
601
|
expect(extractUserMarkdown("keep <!-- grunt:end --> me")).not.toContain(SENTINEL_END);
|
|
583
602
|
expect(extractUserMarkdown("keep <!-- grunt:end --> me")).toBe("keep me");
|
|
@@ -594,7 +613,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
594
613
|
expect(extractUserMarkdown(mixed)).toBe("user bit\n");
|
|
595
614
|
expect(
|
|
596
615
|
composeGuardedMarkdown("<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n", ""),
|
|
597
|
-
).toBe("<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n");
|
|
616
|
+
).toBe("<!-- grunt:begin -->\n\nbody\n\n<!-- grunt:end -->\n");
|
|
598
617
|
});
|
|
599
618
|
|
|
600
619
|
it("extract/compose peel stacked dest and trailing orphan end", () => {
|
|
@@ -602,18 +621,18 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
602
621
|
"<!-- grunt:begin -->\n<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n<!-- grunt:end -->\n";
|
|
603
622
|
expect(extractGruntBody(stacked)).toBe("BODY");
|
|
604
623
|
const stackedOut = composeGuardedMarkdown(stacked, "");
|
|
605
|
-
expect(stackedOut).toBe("<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n");
|
|
624
|
+
expect(stackedOut).toBe("<!-- grunt:begin -->\n\nBODY\n\n<!-- grunt:end -->\n");
|
|
606
625
|
expect(stackedOut.split(SENTINEL_BEGIN)).toHaveLength(2);
|
|
607
626
|
expect(stackedOut.split(SENTINEL_END)).toHaveLength(2);
|
|
608
627
|
expect(composeGuardedMarkdown("BODY\n<!-- grunt:end -->\n", "")).toBe(
|
|
609
|
-
"<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n",
|
|
628
|
+
"<!-- grunt:begin -->\n\nBODY\n\n<!-- grunt:end -->\n",
|
|
610
629
|
);
|
|
611
630
|
expect(extractGruntBody("BODY\n\n<!-- grunt:end -->")).toBe("BODY");
|
|
612
631
|
expect(mergeGuardedContent(stacked, "SRC")).toBe(
|
|
613
|
-
"<!-- grunt:begin -->\nSRC\n<!-- grunt:end -->\n",
|
|
632
|
+
"<!-- grunt:begin -->\n\nSRC\n\n<!-- grunt:end -->\n",
|
|
614
633
|
);
|
|
615
634
|
expect(mergeGuardedContent("keep\n<!-- grunt:end -->\n", "SRC")).toBe(
|
|
616
|
-
"<!-- grunt:begin -->\nSRC\n<!-- grunt:end -->\nkeep\n",
|
|
635
|
+
"<!-- grunt:begin -->\n\nSRC\n\n<!-- grunt:end -->\n\nkeep\n",
|
|
617
636
|
);
|
|
618
637
|
expect(
|
|
619
638
|
extractGruntBody("<!-- grunt:begin --> \nBODY\n<!-- grunt:end --> \n"),
|
|
@@ -632,7 +651,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
632
651
|
expect(extractGruntBody(midLine)).toBe("BODY");
|
|
633
652
|
expect(extractUserMarkdown(midLine)).toBe("still user\n");
|
|
634
653
|
expect(composeGuardedMarkdown(extractGruntBody(midLine) ?? "", extractUserMarkdown(midLine))).toBe(
|
|
635
|
-
"<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\nstill user\n",
|
|
654
|
+
"<!-- grunt:begin -->\n\nBODY\n\n<!-- grunt:end -->\n\nstill user\n",
|
|
636
655
|
);
|
|
637
656
|
});
|
|
638
657
|
|
|
@@ -654,7 +673,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
654
673
|
expect(fs.readFileSync(agents)).toEqual(rawBuf);
|
|
655
674
|
expect(fs.readFileSync(claude, "utf8")).toBe("keep claude\n");
|
|
656
675
|
expect(fs.readFileSync(gemini, "utf8")).toBe(
|
|
657
|
-
"<!-- grunt:begin -->\n@AGENTS.md\n<!-- grunt:end -->\n",
|
|
676
|
+
"<!-- grunt:begin -->\n\n@AGENTS.md\n\n<!-- grunt:end -->\n",
|
|
658
677
|
);
|
|
659
678
|
fs.writeFileSync(claude, Buffer.from([0x00]));
|
|
660
679
|
remergeGuardedRoots(dest, snap);
|
|
@@ -681,7 +700,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
681
700
|
);
|
|
682
701
|
remergeGuardedRoots(dest, snap);
|
|
683
702
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
684
|
-
"<!-- grunt:begin -->\nsame\n<!-- grunt:end -->\n",
|
|
703
|
+
"<!-- grunt:begin -->\n\nsame\n\n<!-- grunt:end -->\n",
|
|
685
704
|
);
|
|
686
705
|
});
|
|
687
706
|
|
|
@@ -714,14 +733,14 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
714
733
|
expect(exec.mock.calls.map((c) => c[0])).toEqual([
|
|
715
734
|
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills",
|
|
716
735
|
"rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks",
|
|
717
|
-
"node scripts/emit-mcp-policy.mjs",
|
|
718
|
-
"node scripts/emit-gemini.mjs",
|
|
719
|
-
"node scripts/emit-agent-shell-tools.mjs",
|
|
720
|
-
"node scripts/emit-maps.mjs",
|
|
721
|
-
"node scripts/hooks-union.mjs",
|
|
736
|
+
"node .rulesync/scripts/emit-mcp-policy.mjs",
|
|
737
|
+
"node .rulesync/scripts/emit-gemini.mjs",
|
|
738
|
+
"node .rulesync/scripts/emit-agent-shell-tools.mjs",
|
|
739
|
+
"node .rulesync/scripts/emit-maps.mjs",
|
|
740
|
+
"node .rulesync/scripts/hooks-union.mjs",
|
|
722
741
|
]);
|
|
723
742
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
724
|
-
"<!-- grunt:begin -->\nfresh ssot\n<!-- grunt:end -->\nkeep me\n",
|
|
743
|
+
"<!-- grunt:begin -->\n\nfresh ssot\n\n<!-- grunt:end -->\n\nkeep me\n",
|
|
725
744
|
);
|
|
726
745
|
});
|
|
727
746
|
|
|
@@ -736,7 +755,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
736
755
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "watch gen 1\n");
|
|
737
756
|
healGuardedRootFile(dest, "AGENTS.md", snap);
|
|
738
757
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
739
|
-
"<!-- grunt:begin -->\nwatch gen 1\n<!-- grunt:end -->\nkeep me\n",
|
|
758
|
+
"<!-- grunt:begin -->\n\nwatch gen 1\n\n<!-- grunt:end -->\n\nkeep me\n",
|
|
740
759
|
);
|
|
741
760
|
fs.writeFileSync(
|
|
742
761
|
path.join(dest, "AGENTS.md"),
|
|
@@ -746,12 +765,12 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
746
765
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "watch gen 2\n");
|
|
747
766
|
healGuardedRootFile(dest, "AGENTS.md", snap);
|
|
748
767
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
749
|
-
"<!-- grunt:begin -->\nwatch gen 2\n<!-- grunt:end -->\nedited bottom\n",
|
|
768
|
+
"<!-- grunt:begin -->\n\nwatch gen 2\n\n<!-- grunt:end -->\n\nedited bottom\n",
|
|
750
769
|
);
|
|
751
770
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "watch claude\n");
|
|
752
771
|
healGuardedRootFile(dest, "CLAUDE.md", snap);
|
|
753
772
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
754
|
-
"<!-- grunt:begin -->\nwatch claude\n<!-- grunt:end -->\nuser claude\n",
|
|
773
|
+
"<!-- grunt:begin -->\n\nwatch claude\n\n<!-- grunt:end -->\n\nuser claude\n",
|
|
755
774
|
);
|
|
756
775
|
healGuardedRootFile(dest, "README.md", snap);
|
|
757
776
|
expect(GUARDED_ROOT_FILES).toEqual(["AGENTS.md", "CLAUDE.md", "GEMINI.md"]);
|
|
@@ -763,12 +782,12 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
763
782
|
fs.writeFileSync(path.join(eq, "AGENTS.md"), "same");
|
|
764
783
|
healGuardedRootFile(eq, "AGENTS.md", eqSnap);
|
|
765
784
|
expect(fs.readFileSync(path.join(eq, "AGENTS.md"), "utf8")).toBe(
|
|
766
|
-
"<!-- grunt:begin -->\nsame\n<!-- grunt:end -->\n",
|
|
785
|
+
"<!-- grunt:begin -->\n\nsame\n\n<!-- grunt:end -->\n",
|
|
767
786
|
);
|
|
768
787
|
fs.writeFileSync(path.join(eq, "GEMINI.md"), "@AGENTS.md\n");
|
|
769
788
|
healGuardedRootFile(eq, "GEMINI.md", {});
|
|
770
789
|
expect(fs.readFileSync(path.join(eq, "GEMINI.md"), "utf8")).toBe(
|
|
771
|
-
"<!-- grunt:begin -->\n@AGENTS.md\n<!-- grunt:end -->\n",
|
|
790
|
+
"<!-- grunt:begin -->\n\n@AGENTS.md\n\n<!-- grunt:end -->\n",
|
|
772
791
|
);
|
|
773
792
|
});
|
|
774
793
|
|
|
@@ -804,7 +823,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
804
823
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "watch1\n");
|
|
805
824
|
heal("AGENTS.md");
|
|
806
825
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
807
|
-
"<!-- grunt:begin -->\nwatch1\n<!-- grunt:end -->\nkeep me\n",
|
|
826
|
+
"<!-- grunt:begin -->\n\nwatch1\n\n<!-- grunt:end -->\n\nkeep me\n",
|
|
808
827
|
);
|
|
809
828
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "watch2\n");
|
|
810
829
|
heal("AGENTS.md");
|
|
@@ -819,14 +838,14 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
819
838
|
},
|
|
820
839
|
});
|
|
821
840
|
expect(exec.mock.calls.map((c) => c[0])).toEqual([
|
|
822
|
-
"node scripts/emit-mcp-policy.mjs",
|
|
823
|
-
"node scripts/emit-gemini.mjs",
|
|
824
|
-
"node scripts/emit-agent-shell-tools.mjs",
|
|
841
|
+
"node .rulesync/scripts/emit-mcp-policy.mjs",
|
|
842
|
+
"node .rulesync/scripts/emit-gemini.mjs",
|
|
843
|
+
"node .rulesync/scripts/emit-agent-shell-tools.mjs",
|
|
825
844
|
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
|
|
826
845
|
]);
|
|
827
846
|
expect(stop).toHaveBeenCalledTimes(1);
|
|
828
847
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
829
|
-
"<!-- grunt:begin -->\nwatch2\n<!-- grunt:end -->\nkeep me\n",
|
|
848
|
+
"<!-- grunt:begin -->\n\nwatch2\n\n<!-- grunt:end -->\n\nkeep me\n",
|
|
830
849
|
);
|
|
831
850
|
});
|
|
832
851
|
|
|
@@ -841,7 +860,7 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
841
860
|
});
|
|
842
861
|
runGuardedRoots("watch", { cwd: dest, exec });
|
|
843
862
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
844
|
-
"<!-- grunt:begin -->\nfrom-watch\n<!-- grunt:end -->\nkeep me\n",
|
|
863
|
+
"<!-- grunt:begin -->\n\nfrom-watch\n\n<!-- grunt:end -->\n\nkeep me\n",
|
|
845
864
|
);
|
|
846
865
|
expect(() => runGuardedRoots("nope")).toThrow(/generate\|check\|watch/);
|
|
847
866
|
});
|
|
@@ -938,7 +957,7 @@ describe("mergeClaudeSettings", () => {
|
|
|
938
957
|
hooks: [
|
|
939
958
|
{
|
|
940
959
|
type: "command",
|
|
941
|
-
command: 'node "${OLD_ROOT}/scripts/scrub-spawn-prompt.mjs"',
|
|
960
|
+
command: 'node "${OLD_ROOT}/.rulesync/scripts/scrub-spawn-prompt.mjs"',
|
|
942
961
|
},
|
|
943
962
|
],
|
|
944
963
|
},
|
|
@@ -989,7 +1008,7 @@ describe("mergeClaudeSettings", () => {
|
|
|
989
1008
|
const mixedGroup = {
|
|
990
1009
|
matcher: "mixed",
|
|
991
1010
|
hooks: [
|
|
992
|
-
{ type: "command", command: 'node "${OLD_ROOT}/scripts/gate-fat-tools.mjs"' },
|
|
1011
|
+
{ type: "command", command: 'node "${OLD_ROOT}/.rulesync/scripts/gate-fat-tools.mjs"' },
|
|
993
1012
|
{ type: "command", command: "node scripts/my-custom-hook.mjs" },
|
|
994
1013
|
],
|
|
995
1014
|
};
|
|
@@ -1278,7 +1297,7 @@ describe("mergePackageJson", () => {
|
|
|
1278
1297
|
const pkgRoot = stubPkgRoot({
|
|
1279
1298
|
name: "fixture-pkg",
|
|
1280
1299
|
scripts: {
|
|
1281
|
-
"rulesync:generate": "node
|
|
1300
|
+
"rulesync:generate": "node ./.rulesync/scripts/guarded-roots.mjs generate",
|
|
1282
1301
|
"rulesync:generate:raw": "echo raw",
|
|
1283
1302
|
"rulesync:check:raw": "echo check-raw",
|
|
1284
1303
|
"rulesync:watch:raw": "echo watch-raw",
|
|
@@ -1289,7 +1308,7 @@ describe("mergePackageJson", () => {
|
|
|
1289
1308
|
const dest = tmp("pj-raw-src-");
|
|
1290
1309
|
mergePackageJson(dest, pkgRoot);
|
|
1291
1310
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1292
|
-
expect(out.scripts["grunt:rulesync:generate"]).toBe("node
|
|
1311
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe("node ./.rulesync/scripts/guarded-roots.mjs generate");
|
|
1293
1312
|
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
1294
1313
|
expect(out.scripts["rulesync:generate:raw"]).toBeUndefined();
|
|
1295
1314
|
expect(out.scripts["rulesync:check:raw"]).toBeUndefined();
|
|
@@ -1299,7 +1318,7 @@ describe("mergePackageJson", () => {
|
|
|
1299
1318
|
it("leaves dest-only :raw keys", () => {
|
|
1300
1319
|
const pkgRoot = stubPkgRoot({
|
|
1301
1320
|
name: "fixture-pkg",
|
|
1302
|
-
scripts: { "rulesync:generate": "node
|
|
1321
|
+
scripts: { "rulesync:generate": "node ./.rulesync/scripts/guarded-roots.mjs generate" },
|
|
1303
1322
|
dependencies: { "smol-toml": "^1.8.0" },
|
|
1304
1323
|
devDependencies: { rulesync: "latest" },
|
|
1305
1324
|
});
|
|
@@ -1312,14 +1331,14 @@ describe("mergePackageJson", () => {
|
|
|
1312
1331
|
);
|
|
1313
1332
|
mergePackageJson(dest, pkgRoot);
|
|
1314
1333
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1315
|
-
expect(out.scripts["grunt:rulesync:generate"]).toBe("node
|
|
1334
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe("node ./.rulesync/scripts/guarded-roots.mjs generate");
|
|
1316
1335
|
expect(out.scripts["rulesync:generate:raw"]).toBe("echo consumer-raw");
|
|
1317
1336
|
});
|
|
1318
1337
|
|
|
1319
1338
|
it("replaces a grunt-owned script with no suffix", () => {
|
|
1320
1339
|
const pkgRoot = stubPkgRoot({
|
|
1321
1340
|
name: "fixture-pkg",
|
|
1322
|
-
scripts: { "rulesync:generate": "rulesync generate -t bar", "sync:globals:apply": "node scripts/sync-global-settings.mjs --apply" },
|
|
1341
|
+
scripts: { "rulesync:generate": "rulesync generate -t bar", "sync:globals:apply": "node .rulesync/scripts/sync-global-settings.mjs --apply" },
|
|
1323
1342
|
dependencies: { "smol-toml": "^1.8.0" },
|
|
1324
1343
|
devDependencies: { rulesync: "latest" },
|
|
1325
1344
|
});
|
|
@@ -1333,7 +1352,7 @@ describe("mergePackageJson", () => {
|
|
|
1333
1352
|
mergePackageJson(dest, pkgRoot);
|
|
1334
1353
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1335
1354
|
expect(out.scripts["grunt:rulesync:generate"]).toBe("rulesync generate -t bar");
|
|
1336
|
-
expect(out.scripts["grunt:sync:globals:apply"]).toBe("node scripts/sync-global-settings.mjs --apply");
|
|
1355
|
+
expect(out.scripts["grunt:sync:globals:apply"]).toBe("node .rulesync/scripts/sync-global-settings.mjs --apply");
|
|
1337
1356
|
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
1338
1357
|
});
|
|
1339
1358
|
|
|
@@ -1369,18 +1388,18 @@ describe("mergePackageJson", () => {
|
|
|
1369
1388
|
const pkgRoot = stubPkgRoot({
|
|
1370
1389
|
name: "fixture-pkg",
|
|
1371
1390
|
scripts: {
|
|
1372
|
-
"rulesync:generate": "node
|
|
1373
|
-
"rulesync:watch": "node
|
|
1374
|
-
"rulesync:check": "node
|
|
1375
|
-
"hooks:union": "node scripts/hooks-union.mjs",
|
|
1376
|
-
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
1377
|
-
doctor: "node
|
|
1391
|
+
"rulesync:generate": "node ./.rulesync/scripts/guarded-roots.mjs generate",
|
|
1392
|
+
"rulesync:watch": "node ./.rulesync/scripts/guarded-roots.mjs watch",
|
|
1393
|
+
"rulesync:check": "node ./.rulesync/scripts/guarded-roots.mjs check",
|
|
1394
|
+
"hooks:union": "node .rulesync/scripts/hooks-union.mjs",
|
|
1395
|
+
"hooks:check": "node .rulesync/scripts/hooks-union.mjs --check",
|
|
1396
|
+
doctor: "node ./.rulesync/scripts/doctor.mjs",
|
|
1378
1397
|
"rulesync:doctor": "rulesync doctor",
|
|
1379
|
-
"sync:globals": "node scripts/sync-global-settings.mjs",
|
|
1380
|
-
"sync:globals:check": "node scripts/check-globals.mjs",
|
|
1381
|
-
"sync:globals:apply": "node scripts/sync-global-settings.mjs --apply",
|
|
1382
|
-
"purge:global-mcps": "node scripts/purge-global-mcps.mjs",
|
|
1383
|
-
"purge:global-mcps:apply": "node scripts/purge-global-mcps.mjs --apply",
|
|
1398
|
+
"sync:globals": "node .rulesync/scripts/sync-global-settings.mjs",
|
|
1399
|
+
"sync:globals:check": "node .rulesync/scripts/check-globals.mjs",
|
|
1400
|
+
"sync:globals:apply": "node .rulesync/scripts/sync-global-settings.mjs --apply",
|
|
1401
|
+
"purge:global-mcps": "node .rulesync/scripts/purge-global-mcps.mjs",
|
|
1402
|
+
"purge:global-mcps:apply": "node .rulesync/scripts/purge-global-mcps.mjs --apply",
|
|
1384
1403
|
test: "vitest run --coverage",
|
|
1385
1404
|
},
|
|
1386
1405
|
dependencies: { "smol-toml": "^1.8.0" },
|
|
@@ -1397,19 +1416,19 @@ describe("mergePackageJson", () => {
|
|
|
1397
1416
|
check:
|
|
1398
1417
|
"node scripts/portability-check.mjs && npm run rulesync:check && npm run lint && npm test && node scripts/media-fixtures.mjs --check",
|
|
1399
1418
|
"codex:sync": "node scripts/codex-sync.mjs",
|
|
1400
|
-
doctor: "node
|
|
1401
|
-
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
1402
|
-
"hooks:union": "node scripts/hooks-union.mjs",
|
|
1419
|
+
doctor: "node ./.rulesync/scripts/doctor.mjs",
|
|
1420
|
+
"hooks:check": "node .rulesync/scripts/hooks-union.mjs --check",
|
|
1421
|
+
"hooks:union": "node .rulesync/scripts/hooks-union.mjs",
|
|
1403
1422
|
lint: "eslint scripts tests eslint.config.mjs",
|
|
1404
|
-
"purge:global-mcps": "node scripts/purge-global-mcps.mjs",
|
|
1405
|
-
"purge:global-mcps:apply": "node scripts/purge-global-mcps.mjs --apply",
|
|
1406
|
-
"rulesync:check": "node
|
|
1423
|
+
"purge:global-mcps": "node .rulesync/scripts/purge-global-mcps.mjs",
|
|
1424
|
+
"purge:global-mcps:apply": "node .rulesync/scripts/purge-global-mcps.mjs --apply",
|
|
1425
|
+
"rulesync:check": "node ./.rulesync/scripts/guarded-roots.mjs check && node scripts/codex-sync.mjs --check",
|
|
1407
1426
|
"rulesync:doctor": "rulesync doctor",
|
|
1408
|
-
"rulesync:generate": "node
|
|
1409
|
-
"rulesync:watch": "node
|
|
1410
|
-
"sync:globals": "node scripts/sync-global-settings.mjs",
|
|
1411
|
-
"sync:globals:apply": "node scripts/sync-global-settings.mjs --apply",
|
|
1412
|
-
"sync:globals:check": "node scripts/check-globals.mjs",
|
|
1427
|
+
"rulesync:generate": "node ./.rulesync/scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
1428
|
+
"rulesync:watch": "node ./.rulesync/scripts/guarded-roots.mjs watch",
|
|
1429
|
+
"sync:globals": "node .rulesync/scripts/sync-global-settings.mjs",
|
|
1430
|
+
"sync:globals:apply": "node .rulesync/scripts/sync-global-settings.mjs --apply",
|
|
1431
|
+
"sync:globals:check": "node .rulesync/scripts/check-globals.mjs",
|
|
1413
1432
|
test: "node scripts/run-tests.mjs",
|
|
1414
1433
|
},
|
|
1415
1434
|
}),
|
|
@@ -1417,21 +1436,21 @@ describe("mergePackageJson", () => {
|
|
|
1417
1436
|
mergePackageJson(dest, pkgRoot);
|
|
1418
1437
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1419
1438
|
expect(out.scripts["grunt:rulesync:generate"]).toBe(
|
|
1420
|
-
"node
|
|
1439
|
+
"node ./.rulesync/scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
1421
1440
|
);
|
|
1422
1441
|
expect(out.scripts["grunt:rulesync:check"]).toBe(
|
|
1423
|
-
"node
|
|
1442
|
+
"node ./.rulesync/scripts/guarded-roots.mjs check && node scripts/codex-sync.mjs --check",
|
|
1424
1443
|
);
|
|
1425
|
-
expect(out.scripts["grunt:rulesync:watch"]).toBe("node
|
|
1444
|
+
expect(out.scripts["grunt:rulesync:watch"]).toBe("node ./.rulesync/scripts/guarded-roots.mjs watch");
|
|
1426
1445
|
expect(out.scripts["grunt:rulesync:doctor"]).toBe("rulesync doctor");
|
|
1427
|
-
expect(out.scripts["grunt:hooks:union"]).toBe("node scripts/hooks-union.mjs");
|
|
1428
|
-
expect(out.scripts["grunt:hooks:check"]).toBe("node scripts/hooks-union.mjs --check");
|
|
1429
|
-
expect(out.scripts["grunt:sync:globals"]).toBe("node scripts/sync-global-settings.mjs");
|
|
1430
|
-
expect(out.scripts["grunt:sync:globals:apply"]).toBe("node scripts/sync-global-settings.mjs --apply");
|
|
1431
|
-
expect(out.scripts["grunt:sync:globals:check"]).toBe("node scripts/check-globals.mjs");
|
|
1432
|
-
expect(out.scripts["grunt:purge:global-mcps"]).toBe("node scripts/purge-global-mcps.mjs");
|
|
1433
|
-
expect(out.scripts["grunt:purge:global-mcps:apply"]).toBe("node scripts/purge-global-mcps.mjs --apply");
|
|
1434
|
-
expect(out.scripts["grunt:doctor"]).toBe("node
|
|
1446
|
+
expect(out.scripts["grunt:hooks:union"]).toBe("node .rulesync/scripts/hooks-union.mjs");
|
|
1447
|
+
expect(out.scripts["grunt:hooks:check"]).toBe("node .rulesync/scripts/hooks-union.mjs --check");
|
|
1448
|
+
expect(out.scripts["grunt:sync:globals"]).toBe("node .rulesync/scripts/sync-global-settings.mjs");
|
|
1449
|
+
expect(out.scripts["grunt:sync:globals:apply"]).toBe("node .rulesync/scripts/sync-global-settings.mjs --apply");
|
|
1450
|
+
expect(out.scripts["grunt:sync:globals:check"]).toBe("node .rulesync/scripts/check-globals.mjs");
|
|
1451
|
+
expect(out.scripts["grunt:purge:global-mcps"]).toBe("node .rulesync/scripts/purge-global-mcps.mjs");
|
|
1452
|
+
expect(out.scripts["grunt:purge:global-mcps:apply"]).toBe("node .rulesync/scripts/purge-global-mcps.mjs --apply");
|
|
1453
|
+
expect(out.scripts["grunt:doctor"]).toBe("node ./.rulesync/scripts/doctor.mjs");
|
|
1435
1454
|
expect(out.scripts.doctor).toBeUndefined();
|
|
1436
1455
|
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
1437
1456
|
expect(out.scripts["rulesync:check"]).toBeUndefined();
|
|
@@ -1450,7 +1469,7 @@ describe("mergePackageJson", () => {
|
|
|
1450
1469
|
it("leaves custom doctor and writes grunt:doctor from SoT", () => {
|
|
1451
1470
|
const pkgRoot = stubPkgRoot({
|
|
1452
1471
|
name: "fixture-pkg",
|
|
1453
|
-
scripts: { doctor: "node
|
|
1472
|
+
scripts: { doctor: "node ./.rulesync/scripts/doctor.mjs" },
|
|
1454
1473
|
dependencies: { "smol-toml": "^1.8.0" },
|
|
1455
1474
|
devDependencies: { rulesync: "latest" },
|
|
1456
1475
|
});
|
|
@@ -1463,7 +1482,7 @@ describe("mergePackageJson", () => {
|
|
|
1463
1482
|
mergePackageJson(dest, pkgRoot);
|
|
1464
1483
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1465
1484
|
expect(out.scripts.doctor).toBe("node scripts/audit.mjs");
|
|
1466
|
-
expect(out.scripts["grunt:doctor"]).toBe("node
|
|
1485
|
+
expect(out.scripts["grunt:doctor"]).toBe("node ./.rulesync/scripts/doctor.mjs");
|
|
1467
1486
|
expect(warnSpy).toHaveBeenCalled();
|
|
1468
1487
|
warnSpy.mockRestore();
|
|
1469
1488
|
});
|
|
@@ -1496,7 +1515,7 @@ describe("mergePackageJson", () => {
|
|
|
1496
1515
|
it("does not rewrite npm run rulesync:check:raw when only rulesync:check migrated", () => {
|
|
1497
1516
|
const pkgRoot = stubPkgRoot({
|
|
1498
1517
|
name: "fixture-pkg",
|
|
1499
|
-
scripts: { "rulesync:check": "node
|
|
1518
|
+
scripts: { "rulesync:check": "node ./.rulesync/scripts/guarded-roots.mjs check" },
|
|
1500
1519
|
dependencies: { "smol-toml": "^1.8.0" },
|
|
1501
1520
|
devDependencies: { rulesync: "latest" },
|
|
1502
1521
|
});
|
|
@@ -1505,7 +1524,7 @@ describe("mergePackageJson", () => {
|
|
|
1505
1524
|
path.join(dest, "package.json"),
|
|
1506
1525
|
JSON.stringify({
|
|
1507
1526
|
scripts: {
|
|
1508
|
-
"rulesync:check": "node
|
|
1527
|
+
"rulesync:check": "node ./.rulesync/scripts/guarded-roots.mjs check",
|
|
1509
1528
|
"rulesync:check:raw": "echo consumer-raw",
|
|
1510
1529
|
check: "npm run rulesync:check && npm run rulesync:check:raw",
|
|
1511
1530
|
},
|
|
@@ -1513,7 +1532,7 @@ describe("mergePackageJson", () => {
|
|
|
1513
1532
|
);
|
|
1514
1533
|
mergePackageJson(dest, pkgRoot);
|
|
1515
1534
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1516
|
-
expect(out.scripts["grunt:rulesync:check"]).toBe("node
|
|
1535
|
+
expect(out.scripts["grunt:rulesync:check"]).toBe("node ./.rulesync/scripts/guarded-roots.mjs check");
|
|
1517
1536
|
expect(out.scripts["rulesync:check"]).toBeUndefined();
|
|
1518
1537
|
expect(out.scripts["rulesync:check:raw"]).toBe("echo consumer-raw");
|
|
1519
1538
|
expect(out.scripts.check).toBe("npm run grunt:rulesync:check && npm run rulesync:check:raw");
|
|
@@ -1522,7 +1541,7 @@ describe("mergePackageJson", () => {
|
|
|
1522
1541
|
it("rewrites yarn/pnpm/bun run refs the same way as npm run", () => {
|
|
1523
1542
|
const pkgRoot = stubPkgRoot({
|
|
1524
1543
|
name: "fixture-pkg",
|
|
1525
|
-
scripts: { "rulesync:check": "node
|
|
1544
|
+
scripts: { "rulesync:check": "node ./.rulesync/scripts/guarded-roots.mjs check" },
|
|
1526
1545
|
dependencies: { "smol-toml": "^1.8.0" },
|
|
1527
1546
|
devDependencies: { rulesync: "latest" },
|
|
1528
1547
|
});
|
|
@@ -1531,7 +1550,7 @@ describe("mergePackageJson", () => {
|
|
|
1531
1550
|
path.join(dest, "package.json"),
|
|
1532
1551
|
JSON.stringify({
|
|
1533
1552
|
scripts: {
|
|
1534
|
-
"rulesync:check": "node
|
|
1553
|
+
"rulesync:check": "node ./.rulesync/scripts/guarded-roots.mjs check",
|
|
1535
1554
|
check: "pnpm run rulesync:check && yarn run rulesync:check && bun run rulesync:check",
|
|
1536
1555
|
},
|
|
1537
1556
|
}),
|
|
@@ -1634,7 +1653,7 @@ describe("mergePackageJson", () => {
|
|
|
1634
1653
|
it("skips non-string dest scripts when rewriting npm run refs", () => {
|
|
1635
1654
|
const pkgRoot = stubPkgRoot({
|
|
1636
1655
|
name: "fixture-pkg",
|
|
1637
|
-
scripts: { "rulesync:check": "node
|
|
1656
|
+
scripts: { "rulesync:check": "node ./.rulesync/scripts/guarded-roots.mjs check" },
|
|
1638
1657
|
dependencies: { "smol-toml": "^1.8.0" },
|
|
1639
1658
|
devDependencies: { rulesync: "latest" },
|
|
1640
1659
|
});
|
|
@@ -1652,7 +1671,7 @@ describe("mergePackageJson", () => {
|
|
|
1652
1671
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1653
1672
|
expect(out.scripts.extra).toBe(1);
|
|
1654
1673
|
expect(out.scripts.check).toBe("npm run grunt:rulesync:check");
|
|
1655
|
-
expect(out.scripts["grunt:rulesync:check"]).toBe("node
|
|
1674
|
+
expect(out.scripts["grunt:rulesync:check"]).toBe("node ./.rulesync/scripts/guarded-roots.mjs check");
|
|
1656
1675
|
});
|
|
1657
1676
|
|
|
1658
1677
|
it("emits grunt:claude into a consumer package.json", () => {
|
|
@@ -1763,7 +1782,7 @@ describe("mergePackageJson", () => {
|
|
|
1763
1782
|
mergePackageJson(dest, PKG_ROOT);
|
|
1764
1783
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1765
1784
|
expect(out.scripts["grunt:test:e2e"]).toBeUndefined();
|
|
1766
|
-
expect(out.scripts["grunt:rulesync:check"]).toBe("node
|
|
1785
|
+
expect(out.scripts["grunt:rulesync:check"]).toBe("node ./.rulesync/scripts/guarded-roots.mjs check");
|
|
1767
1786
|
});
|
|
1768
1787
|
|
|
1769
1788
|
it("adds grunt:test:e2e when dest has a vitest e2e config", () => {
|
|
@@ -1887,7 +1906,7 @@ describe("init", () => {
|
|
|
1887
1906
|
expect(pkg.name).toBe("fixture-pkg");
|
|
1888
1907
|
expect(fs.existsSync(path.join(pkgRoot, ".tmp"))).toBe(true);
|
|
1889
1908
|
expect(fs.readFileSync(path.join(pkgRoot, ".gitignore"), "utf8")).toBe(".tmp/\n");
|
|
1890
|
-
expect(fs.readFileSync(path.join(pkgRoot, "scripts", "grunt-job.mjs"), "utf8")).toBe(
|
|
1909
|
+
expect(fs.readFileSync(path.join(pkgRoot, ".rulesync", "scripts", "grunt-job.mjs"), "utf8")).toBe(
|
|
1891
1910
|
"grunt-job.mjs",
|
|
1892
1911
|
);
|
|
1893
1912
|
// self-skip: markdown/settings stay exactly as-is, no sentinel wrap
|
|
@@ -1910,7 +1929,7 @@ describe("init", () => {
|
|
|
1910
1929
|
}
|
|
1911
1930
|
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1912
1931
|
expect(PRODUCT_FILES).not.toContain("grunt-config.mjs");
|
|
1913
|
-
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1932
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1914
1933
|
expect(fs.readFileSync(path.join(dest, ".claude", "marker"), "utf8")).toBe(".claude");
|
|
1915
1934
|
const settings = JSON.parse(
|
|
1916
1935
|
fs.readFileSync(path.join(dest, ".claude", "settings.json"), "utf8"),
|
|
@@ -1919,17 +1938,17 @@ describe("init", () => {
|
|
|
1919
1938
|
|
|
1920
1939
|
for (const f of GUARDED_MD_FILES) {
|
|
1921
1940
|
expect(fs.readFileSync(path.join(dest, f), "utf8")).toBe(
|
|
1922
|
-
`<!-- grunt:begin -->\n${f} content\n<!-- grunt:end -->\n`,
|
|
1941
|
+
`<!-- grunt:begin -->\n\n${f} content\n\n<!-- grunt:end -->\n`,
|
|
1923
1942
|
);
|
|
1924
1943
|
}
|
|
1925
1944
|
expect(fs.existsSync(path.join(dest, ".mcp.json"))).toBe(false);
|
|
1926
1945
|
|
|
1927
1946
|
for (const name of PRODUCT_FILES) {
|
|
1928
1947
|
const optional = ["speak.mjs", "listen.mjs", "google-workspace.mjs"].includes(name);
|
|
1929
|
-
expect(fs.existsSync(path.join(dest, "scripts", name)), name).toBe(!optional);
|
|
1948
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", name)), name).toBe(!optional);
|
|
1930
1949
|
}
|
|
1931
|
-
expect(fs.statSync(path.join(dest, "scripts", "scrub-text")).isDirectory()).toBe(true);
|
|
1932
|
-
expect(fs.readFileSync(path.join(dest, "scripts", "scrub-text", "inside"), "utf8")).toBe(
|
|
1950
|
+
expect(fs.statSync(path.join(dest, ".rulesync", "scripts", "scrub-text")).isDirectory()).toBe(true);
|
|
1951
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "scripts", "scrub-text", "inside"), "utf8")).toBe(
|
|
1933
1952
|
"dir",
|
|
1934
1953
|
);
|
|
1935
1954
|
|
|
@@ -2119,11 +2138,11 @@ describe("init", () => {
|
|
|
2119
2138
|
expect(agents.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
2120
2139
|
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
2121
2140
|
expect(claude.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
2122
|
-
expect(fs.existsSync(path.join(dest, "scripts", "scrub-text"))).toBe(true);
|
|
2123
|
-
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
2124
|
-
expect(fs.existsSync(path.join(dest, "scripts", "speak.mjs"))).toBe(false);
|
|
2125
|
-
expect(fs.existsSync(path.join(dest, "scripts", "listen.mjs"))).toBe(false);
|
|
2126
|
-
expect(fs.existsSync(path.join(dest, "scripts", "google-workspace.mjs"))).toBe(false);
|
|
2141
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "scrub-text"))).toBe(true);
|
|
2142
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "grunt-config.mjs"))).toBe(false);
|
|
2143
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "speak.mjs"))).toBe(false);
|
|
2144
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "listen.mjs"))).toBe(false);
|
|
2145
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "google-workspace.mjs"))).toBe(false);
|
|
2127
2146
|
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "clasp"))).toBe(false);
|
|
2128
2147
|
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "browser"))).toBe(true);
|
|
2129
2148
|
expect(fs.existsSync(path.join(dest, ".rulesync", "reference", "listen.md"))).toBe(false);
|
|
@@ -2174,8 +2193,8 @@ describe("init", () => {
|
|
|
2174
2193
|
expect(fs.existsSync(path.join(dest, ".claude", "skills", "clasp"))).toBe(false);
|
|
2175
2194
|
expect(fs.existsSync(path.join(dest, ".agents", "skills", "listen"))).toBe(false);
|
|
2176
2195
|
expect(fs.existsSync(path.join(dest, ".grok", "skills", "clasp"))).toBe(false);
|
|
2177
|
-
expect(fs.existsSync(path.join(dest, "scripts", "listen.mjs"))).toBe(false);
|
|
2178
|
-
expect(fs.existsSync(path.join(dest, "scripts", "speak.mjs"))).toBe(false);
|
|
2196
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "listen.mjs"))).toBe(false);
|
|
2197
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "speak.mjs"))).toBe(false);
|
|
2179
2198
|
expect(fs.existsSync(path.join(dest, ".rulesync", "reference", "listen.md"))).toBe(false);
|
|
2180
2199
|
expect(fs.readFileSync(path.join(dest, ".rulesync", "reference", "law.md"), "utf8")).toContain("## Mine");
|
|
2181
2200
|
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.features.jsonc"))).toBe(false);
|
|
@@ -2183,10 +2202,10 @@ describe("init", () => {
|
|
|
2183
2202
|
const picked = tmp("packs-listen-");
|
|
2184
2203
|
init(picked, { pkgRoot, execFileSync: exec, packageManager: "npm", packs: ["listen"] });
|
|
2185
2204
|
expect(fs.readFileSync(path.join(picked, ".rulesync", "skills", "listen", "SKILL.md"), "utf8")).toBe("listen\n");
|
|
2186
|
-
expect(fs.readFileSync(path.join(picked, "scripts", "listen.mjs"), "utf8")).toBe("listen.mjs");
|
|
2205
|
+
expect(fs.readFileSync(path.join(picked, ".rulesync", "scripts", "listen.mjs"), "utf8")).toBe("listen.mjs");
|
|
2187
2206
|
expect(fs.existsSync(path.join(picked, ".rulesync", "reference", "listen.md"))).toBe(true);
|
|
2188
2207
|
expect(fs.existsSync(path.join(picked, ".rulesync", "skills", "speak"))).toBe(false);
|
|
2189
|
-
expect(fs.existsSync(path.join(picked, "scripts", "speak.mjs"))).toBe(false);
|
|
2208
|
+
expect(fs.existsSync(path.join(picked, ".rulesync", "scripts", "speak.mjs"))).toBe(false);
|
|
2190
2209
|
expect(JSON.parse(fs.readFileSync(path.join(picked, ".rulesync", "grunt.features.jsonc"), "utf8"))).toEqual({
|
|
2191
2210
|
packs: ["listen"],
|
|
2192
2211
|
});
|
|
@@ -2199,17 +2218,17 @@ describe("init", () => {
|
|
|
2199
2218
|
);
|
|
2200
2219
|
init(saved, { pkgRoot, execFileSync: exec, packageManager: "npm" });
|
|
2201
2220
|
expect(fs.existsSync(path.join(saved, ".rulesync", "skills", "speak"))).toBe(true);
|
|
2202
|
-
expect(fs.existsSync(path.join(saved, "scripts", "listen.mjs"))).toBe(false);
|
|
2221
|
+
expect(fs.existsSync(path.join(saved, ".rulesync", "scripts", "listen.mjs"))).toBe(false);
|
|
2203
2222
|
expect(fs.readFileSync(path.join(saved, ".rulesync", "grunt.features.jsonc"), "utf8")).toBe(
|
|
2204
2223
|
'{"packs":["speak"]}\n',
|
|
2205
2224
|
);
|
|
2206
2225
|
|
|
2207
2226
|
const pkgSelf = stubPkgRoot();
|
|
2208
2227
|
writeSkill(pkgSelf, "clasp");
|
|
2209
|
-
fs.writeFileSync(path.join(pkgSelf, "scripts", "speak.mjs"), "keep-speak\n");
|
|
2228
|
+
fs.writeFileSync(path.join(pkgSelf, ".rulesync", "scripts", "speak.mjs"), "keep-speak\n");
|
|
2210
2229
|
init(pkgSelf, { pkgRoot: pkgSelf, execFileSync: exec, packs: [] });
|
|
2211
2230
|
expect(fs.readFileSync(path.join(pkgSelf, ".rulesync", "skills", "clasp", "SKILL.md"), "utf8")).toBe("clasp\n");
|
|
2212
|
-
expect(fs.readFileSync(path.join(pkgSelf, "scripts", "speak.mjs"), "utf8")).toBe("keep-speak\n");
|
|
2231
|
+
expect(fs.readFileSync(path.join(pkgSelf, ".rulesync", "scripts", "speak.mjs"), "utf8")).toBe("keep-speak\n");
|
|
2213
2232
|
expect(fs.existsSync(path.join(pkgSelf, ".rulesync", "grunt.features.jsonc"))).toBe(false);
|
|
2214
2233
|
expect(() => init(tmp("packs-bad-"), { pkgRoot, execFileSync: exec, packageManager: "npm", packs: ["nope"] })).toThrow(
|
|
2215
2234
|
/unknown pack/,
|
|
@@ -2271,10 +2290,10 @@ describe("init", () => {
|
|
|
2271
2290
|
const exec = vi.fn();
|
|
2272
2291
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2273
2292
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
2274
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
2293
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
2275
2294
|
);
|
|
2276
2295
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
2277
|
-
"<!-- grunt:begin -->\nAGENTS.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
2296
|
+
"<!-- grunt:begin -->\n\nAGENTS.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
2278
2297
|
);
|
|
2279
2298
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.grunt.md"), "utf8")).toBe("stale side");
|
|
2280
2299
|
expect(fs.existsSync(path.join(dest, "AGENTS.grunt.md"))).toBe(false);
|
|
@@ -2288,7 +2307,7 @@ describe("init", () => {
|
|
|
2288
2307
|
const exec = vi.fn();
|
|
2289
2308
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2290
2309
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
2291
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
2310
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
2292
2311
|
);
|
|
2293
2312
|
expect(fs.existsSync(path.join(dest, "CLAUDE.grunt.md"))).toBe(false);
|
|
2294
2313
|
expect(fs.existsSync(path.join(dest, "AGENTS.grunt.md"))).toBe(false);
|
|
@@ -2315,17 +2334,17 @@ describe("init", () => {
|
|
|
2315
2334
|
});
|
|
2316
2335
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2317
2336
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
2318
|
-
"<!-- grunt:begin -->\ngenerated agents\n<!-- grunt:end -->\nconsumer agents\n",
|
|
2337
|
+
"<!-- grunt:begin -->\n\ngenerated agents\n\n<!-- grunt:end -->\n\nconsumer agents\n",
|
|
2319
2338
|
);
|
|
2320
2339
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
2321
|
-
"<!-- grunt:begin -->\ngenerated claude\n<!-- grunt:end -->\nconsumer claude\n",
|
|
2340
|
+
"<!-- grunt:begin -->\n\ngenerated claude\n\n<!-- grunt:end -->\n\nconsumer claude\n",
|
|
2322
2341
|
);
|
|
2323
2342
|
expect(fs.readFileSync(path.join(dest, "GEMINI.md"), "utf8")).toBe(
|
|
2324
|
-
"<!-- grunt:begin -->\n@AGENTS.md\n<!-- grunt:end -->\nconsumer gemini\n",
|
|
2343
|
+
"<!-- grunt:begin -->\n\n@AGENTS.md\n\n<!-- grunt:end -->\n\nconsumer gemini\n",
|
|
2325
2344
|
);
|
|
2326
2345
|
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
2327
2346
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
2328
|
-
"<!-- grunt:begin -->\nAGENTS.md content\n<!-- grunt:end -->\nconsumer agents\n",
|
|
2347
|
+
"<!-- grunt:begin -->\n\nAGENTS.md content\n\n<!-- grunt:end -->\n\nconsumer agents\n",
|
|
2329
2348
|
);
|
|
2330
2349
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8").split("consumer agents").length).toBe(
|
|
2331
2350
|
2,
|
|
@@ -2350,8 +2369,8 @@ describe("init", () => {
|
|
|
2350
2369
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2351
2370
|
const agents = fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8");
|
|
2352
2371
|
const claude = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
2353
|
-
expect(agents).toBe("<!-- grunt:begin -->\ngenerated agents\n<!-- grunt:end -->\n");
|
|
2354
|
-
expect(claude).toBe("<!-- grunt:begin -->\ngenerated claude\n<!-- grunt:end -->\n");
|
|
2372
|
+
expect(agents).toBe("<!-- grunt:begin -->\n\ngenerated agents\n\n<!-- grunt:end -->\n");
|
|
2373
|
+
expect(claude).toBe("<!-- grunt:begin -->\n\ngenerated claude\n\n<!-- grunt:end -->\n");
|
|
2355
2374
|
expect(agents.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
2356
2375
|
expect(agents.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
2357
2376
|
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
@@ -2483,23 +2502,26 @@ describe("init", () => {
|
|
|
2483
2502
|
expect(index).toMatch(/push → go-live\//);
|
|
2484
2503
|
});
|
|
2485
2504
|
|
|
2486
|
-
it("seeds grunt.pipeline.jsonc from
|
|
2505
|
+
it("seeds grunt.pipeline.jsonc from a legacy root pipeline, then moves grunt scripts out of root", () => {
|
|
2487
2506
|
const dest = tmp("grunt-pipe-init-");
|
|
2488
2507
|
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true });
|
|
2508
|
+
const legacy = (c: string) => c.replace(".rulesync/scripts/", "scripts/");
|
|
2489
2509
|
const skipped = PIPELINE_COMMANDS.check.find((c) => c.includes("subagents"));
|
|
2490
2510
|
const src = `export const COMMANDS = {
|
|
2491
2511
|
check: [
|
|
2492
|
-
${PIPELINE_COMMANDS.check.filter((c) => c !== skipped).map((c) => JSON.stringify(c)).join(",\n ")}
|
|
2512
|
+
${PIPELINE_COMMANDS.check.filter((c) => c !== skipped).map((c) => JSON.stringify(legacy(c))).join(",\n ")}
|
|
2493
2513
|
],
|
|
2494
2514
|
};
|
|
2495
2515
|
`;
|
|
2496
2516
|
fs.writeFileSync(path.join(dest, "scripts", "pipeline.mjs"), src);
|
|
2517
|
+
fs.writeFileSync(path.join(dest, "scripts", "gate-fat-tools.mjs"), "old gate");
|
|
2497
2518
|
init(dest, { execFileSync: vi.fn(), skipGlobals: true, packageManager: "npm" });
|
|
2498
2519
|
const cfg = JSON.parse(fs.readFileSync(path.join(dest, PIPELINE_CONFIG_REL), "utf8"));
|
|
2499
2520
|
expect(cfg.check.skip).toEqual([skipped]);
|
|
2500
|
-
expect(fs.readFileSync(path.join(dest, "scripts", "pipeline.mjs"), "utf8")).toContain(
|
|
2521
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "scripts", "pipeline.mjs"), "utf8")).toContain(
|
|
2501
2522
|
"subagents --check",
|
|
2502
2523
|
);
|
|
2524
|
+
expect(fs.existsSync(path.join(dest, "scripts"))).toBe(false);
|
|
2503
2525
|
});
|
|
2504
2526
|
|
|
2505
2527
|
it("does not stamp package INDEX over dest; re-emits union", () => {
|
|
@@ -2634,10 +2656,13 @@ function relativeSpecs(text: string, re: RegExp) {
|
|
|
2634
2656
|
}
|
|
2635
2657
|
|
|
2636
2658
|
describe("product scripts are consumer-safe", () => {
|
|
2637
|
-
it("package.json files
|
|
2659
|
+
it("package.json files ships every PRODUCT_SCRIPTS entry and no tests or fixtures", () => {
|
|
2638
2660
|
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
|
|
2661
|
+
expect(pkg.files).toContain(".rulesync");
|
|
2662
|
+
expect(pkg.files).toContain("!.rulesync/scripts/**/*.test.ts");
|
|
2663
|
+
expect(pkg.files).toContain("!.rulesync/scripts/fixtures");
|
|
2639
2664
|
for (const name of PRODUCT_SCRIPTS) {
|
|
2640
|
-
expect(
|
|
2665
|
+
expect(fs.existsSync(path.join(PKG_ROOT, SCRIPTS_REL, name)), name).toBe(true);
|
|
2641
2666
|
}
|
|
2642
2667
|
});
|
|
2643
2668
|
|
|
@@ -2660,25 +2685,25 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2660
2685
|
}
|
|
2661
2686
|
};
|
|
2662
2687
|
walk(path.join(PKG_ROOT, "bin", "grunt.js"));
|
|
2663
|
-
expect(seen.has(path.join(PKG_ROOT, "scripts", "folder-map.mjs"))).toBe(true);
|
|
2688
|
+
expect(seen.has(path.join(PKG_ROOT, ".rulesync", "scripts", "folder-map.mjs"))).toBe(true);
|
|
2664
2689
|
expect(bad).toEqual([]);
|
|
2665
2690
|
});
|
|
2666
2691
|
|
|
2667
|
-
it("do not parent-import (from or import()) — init copies scripts/ only", () => {
|
|
2692
|
+
it("do not parent-import (from or import()) — init copies .rulesync/scripts/ only", () => {
|
|
2668
2693
|
for (const name of PRODUCT_FILES) {
|
|
2669
|
-
const text = fs.readFileSync(path.join(PKG_ROOT, "scripts", name), "utf8");
|
|
2694
|
+
const text = fs.readFileSync(path.join(PKG_ROOT, ".rulesync", "scripts", name), "utf8");
|
|
2670
2695
|
expect(relativeSpecs(text, PARENT_IMPORT_RE), name).toEqual([]);
|
|
2671
2696
|
}
|
|
2672
2697
|
});
|
|
2673
2698
|
|
|
2674
|
-
it("repro: dest node
|
|
2699
|
+
it("repro: dest node ./.rulesync/scripts/guarded-roots.mjs generate misses ../cli/init.mjs", () => {
|
|
2675
2700
|
const dest = tmp("repro-cli-miss-");
|
|
2676
|
-
fs.mkdirSync(path.join(dest, "scripts"));
|
|
2701
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "scripts"), { recursive: true });
|
|
2677
2702
|
fs.writeFileSync(
|
|
2678
|
-
path.join(dest, "scripts", "broken.mjs"),
|
|
2679
|
-
'import { snapshotGuardedRoots } from "
|
|
2703
|
+
path.join(dest, ".rulesync", "scripts", "broken.mjs"),
|
|
2704
|
+
'import { snapshotGuardedRoots } from "../../cli/init.mjs";\n',
|
|
2680
2705
|
);
|
|
2681
|
-
const r = spawnDest(dest, ["
|
|
2706
|
+
const r = spawnDest(dest, ["./.rulesync/scripts/broken.mjs", "generate"]);
|
|
2682
2707
|
expect(r.status).not.toBe(0);
|
|
2683
2708
|
expect(r.stderr).toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2684
2709
|
expect(r.stderr).toMatch(/cli\/init\.mjs/);
|
|
@@ -2693,11 +2718,11 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2693
2718
|
applyGlobals: false,
|
|
2694
2719
|
});
|
|
2695
2720
|
expect(fs.existsSync(path.join(dest, "cli"))).toBe(false);
|
|
2696
|
-
expect(fs.existsSync(path.join(dest, "scripts", "guarded-roots.mjs"))).toBe(true);
|
|
2721
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "guarded-roots.mjs"))).toBe(true);
|
|
2697
2722
|
|
|
2698
2723
|
const optionalScripts = new Set(["speak.mjs", "listen.mjs", "google-workspace.mjs"]);
|
|
2699
2724
|
for (const name of PRODUCT_FILES) {
|
|
2700
|
-
const file = path.join(dest, "scripts", name);
|
|
2725
|
+
const file = path.join(dest, ".rulesync", "scripts", name);
|
|
2701
2726
|
if (optionalScripts.has(name)) {
|
|
2702
2727
|
expect(fs.existsSync(file), name).toBe(false);
|
|
2703
2728
|
continue;
|
|
@@ -2711,7 +2736,7 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2711
2736
|
}
|
|
2712
2737
|
}
|
|
2713
2738
|
|
|
2714
|
-
const setupRun = spawnDest(dest, ["
|
|
2739
|
+
const setupRun = spawnDest(dest, ["./.rulesync/scripts/setup.mjs"], {
|
|
2715
2740
|
...process.env,
|
|
2716
2741
|
CI: "1",
|
|
2717
2742
|
PATH: ["/usr/bin", "/bin"].join(path.delimiter),
|
|
@@ -2720,12 +2745,12 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2720
2745
|
expect(setupRun.status).toBe(1);
|
|
2721
2746
|
expect(`${setupRun.stdout}${setupRun.stderr}`).toContain("usage: setup");
|
|
2722
2747
|
|
|
2723
|
-
const loaded = spawnDest(dest, ["
|
|
2748
|
+
const loaded = spawnDest(dest, ["./.rulesync/scripts/guarded-roots.mjs"]);
|
|
2724
2749
|
expect(loaded.status).toBe(1);
|
|
2725
2750
|
expect(loaded.stderr).toBe("usage: guarded-roots.mjs generate|check|watch\n");
|
|
2726
2751
|
expect(loaded.stderr).not.toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2727
2752
|
|
|
2728
|
-
const gen = spawnDest(dest, ["
|
|
2753
|
+
const gen = spawnDest(dest, ["./.rulesync/scripts/guarded-roots.mjs", "generate"], {
|
|
2729
2754
|
...process.env,
|
|
2730
2755
|
PATH: ["/usr/bin", "/bin"].join(path.delimiter),
|
|
2731
2756
|
});
|
|
@@ -2737,7 +2762,7 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2737
2762
|
const r = spawnDest(dest, [
|
|
2738
2763
|
"--input-type=module",
|
|
2739
2764
|
"-e",
|
|
2740
|
-
`import(${JSON.stringify(
|
|
2765
|
+
`import(${JSON.stringify(`./.rulesync/scripts/${name}`)})`,
|
|
2741
2766
|
]);
|
|
2742
2767
|
expect(r.status, `${name} load\n${r.stderr}`).toBe(0);
|
|
2743
2768
|
expect(r.stderr, name).not.toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
@@ -2745,3 +2770,125 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2745
2770
|
});
|
|
2746
2771
|
});
|
|
2747
2772
|
|
|
2773
|
+
|
|
2774
|
+
describe("legacy root scripts migration", () => {
|
|
2775
|
+
function legacyDir(names: string[]) {
|
|
2776
|
+
const dest = tmp("legacy-scripts-");
|
|
2777
|
+
fs.mkdirSync(path.join(dest, LEGACY_SCRIPTS_REL));
|
|
2778
|
+
for (const n of names) fs.writeFileSync(path.join(dest, LEGACY_SCRIPTS_REL, n), n);
|
|
2779
|
+
return dest;
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
it("drops grunt names and keeps consumer files when a prior grunt install is detected", () => {
|
|
2783
|
+
const dest = legacyDir(["gate-fat-tools.mjs", "doctor.mjs", "mine.mjs"]);
|
|
2784
|
+
pruneLegacyScripts(dest);
|
|
2785
|
+
expect(fs.readdirSync(path.join(dest, LEGACY_SCRIPTS_REL))).toEqual(["mine.mjs"]);
|
|
2786
|
+
});
|
|
2787
|
+
|
|
2788
|
+
it("removes the root dir when only grunt names were in it", () => {
|
|
2789
|
+
const dest = legacyDir(["guarded-roots.mjs", "pipeline.mjs"]);
|
|
2790
|
+
pruneLegacyScripts(dest);
|
|
2791
|
+
expect(fs.existsSync(path.join(dest, LEGACY_SCRIPTS_REL))).toBe(false);
|
|
2792
|
+
});
|
|
2793
|
+
|
|
2794
|
+
it("leaves a consumer's own same-named scripts alone without a grunt marker", () => {
|
|
2795
|
+
const dest = legacyDir(["setup.mjs", "doctor.mjs"]);
|
|
2796
|
+
pruneLegacyScripts(dest);
|
|
2797
|
+
expect(fs.readdirSync(path.join(dest, LEGACY_SCRIPTS_REL)).sort()).toEqual(["doctor.mjs", "setup.mjs"]);
|
|
2798
|
+
const none = tmp("legacy-none-");
|
|
2799
|
+
pruneLegacyScripts(none);
|
|
2800
|
+
expect(fs.existsSync(path.join(none, LEGACY_SCRIPTS_REL))).toBe(false);
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
it("rewrites only grunt-owned legacy refs", () => {
|
|
2804
|
+
expect(rewriteLegacyScriptRefs("node ./scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs")).toBe(
|
|
2805
|
+
"node ./.rulesync/scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
2806
|
+
);
|
|
2807
|
+
expect(rewriteLegacyScriptRefs('node "${ROOT}/scripts/gate-fat-tools.mjs"')).toBe(
|
|
2808
|
+
'node "${ROOT}/.rulesync/scripts/gate-fat-tools.mjs"',
|
|
2809
|
+
);
|
|
2810
|
+
expect(rewriteLegacyScriptRefs('["node scripts/emit-maps.mjs --check"]')).toBe(
|
|
2811
|
+
'["node .rulesync/scripts/emit-maps.mjs --check"]',
|
|
2812
|
+
);
|
|
2813
|
+
for (const keep of [
|
|
2814
|
+
"node scripts/lib/env.mjs",
|
|
2815
|
+
"node tools/scripts/doctor.mjs",
|
|
2816
|
+
"node scripts/doctor.mjsx",
|
|
2817
|
+
"node .rulesync/scripts/doctor.mjs",
|
|
2818
|
+
]) {
|
|
2819
|
+
expect(rewriteLegacyScriptRefs(keep)).toBe(keep);
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
it("init migrates grunt:* package scripts and the pipeline overlay; consumer refs stay", () => {
|
|
2824
|
+
const pkgRoot = stubPkgRoot({
|
|
2825
|
+
name: "fixture-pkg",
|
|
2826
|
+
version: "1.0.0",
|
|
2827
|
+
scripts: { "rulesync:generate": "node ./.rulesync/scripts/guarded-roots.mjs generate" },
|
|
2828
|
+
dependencies: { "smol-toml": "^1.8.0" },
|
|
2829
|
+
devDependencies: { rulesync: "latest" },
|
|
2830
|
+
});
|
|
2831
|
+
const dest = tmp("legacy-init-");
|
|
2832
|
+
fs.writeFileSync(
|
|
2833
|
+
path.join(dest, "package.json"),
|
|
2834
|
+
JSON.stringify({
|
|
2835
|
+
scripts: {
|
|
2836
|
+
"grunt:rulesync:generate": "node ./scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
2837
|
+
lint: "node scripts/lint.mjs",
|
|
2838
|
+
},
|
|
2839
|
+
}),
|
|
2840
|
+
);
|
|
2841
|
+
fs.mkdirSync(path.join(dest, ".rulesync"), { recursive: true });
|
|
2842
|
+
fs.writeFileSync(
|
|
2843
|
+
path.join(dest, PIPELINE_CONFIG_REL),
|
|
2844
|
+
JSON.stringify({
|
|
2845
|
+
check: { skip: ["node scripts/emit-maps.mjs --check"], extra: ["node scripts/codex-sync.mjs --check"] },
|
|
2846
|
+
}),
|
|
2847
|
+
);
|
|
2848
|
+
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true, packageManager: "npm" });
|
|
2849
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
2850
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe(
|
|
2851
|
+
"node ./.rulesync/scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
2852
|
+
);
|
|
2853
|
+
expect(out.scripts.lint).toBe("node scripts/lint.mjs");
|
|
2854
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(dest, PIPELINE_CONFIG_REL), "utf8"));
|
|
2855
|
+
expect(cfg.check).toEqual({
|
|
2856
|
+
skip: ["node .rulesync/scripts/emit-maps.mjs --check"],
|
|
2857
|
+
extra: ["node scripts/codex-sync.mjs --check"],
|
|
2858
|
+
});
|
|
2859
|
+
});
|
|
2860
|
+
|
|
2861
|
+
it("init copies product scripts to .rulesync/scripts and moves them out of a legacy root", () => {
|
|
2862
|
+
const pkgRoot = stubPkgRoot();
|
|
2863
|
+
fs.writeFileSync(path.join(pkgRoot, SCRIPTS_REL, "x.test.ts"), "test");
|
|
2864
|
+
const dest = legacyDir(["gate-fat-tools.mjs", "board.mjs", "mine.mjs"]);
|
|
2865
|
+
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true, packageManager: "npm" });
|
|
2866
|
+
expect(fs.readFileSync(path.join(dest, SCRIPTS_REL, "gate-fat-tools.mjs"), "utf8")).toBe("gate-fat-tools.mjs");
|
|
2867
|
+
expect(fs.existsSync(path.join(dest, SCRIPTS_REL, "x.test.ts"))).toBe(false);
|
|
2868
|
+
expect(fs.readdirSync(path.join(dest, LEGACY_SCRIPTS_REL))).toEqual(["mine.mjs"]);
|
|
2869
|
+
});
|
|
2870
|
+
|
|
2871
|
+
it("mergeClaudeSettings replaces legacy-path grunt hooks without duplicating; consumer hook kept", () => {
|
|
2872
|
+
const pkgRoot = stubPkgRoot();
|
|
2873
|
+
const destRoot = tmp("settings-legacy-");
|
|
2874
|
+
fs.mkdirSync(path.join(destRoot, ".claude"), { recursive: true });
|
|
2875
|
+
fs.writeFileSync(
|
|
2876
|
+
path.join(destRoot, ".claude", "settings.json"),
|
|
2877
|
+
JSON.stringify({
|
|
2878
|
+
hooks: {
|
|
2879
|
+
PreToolUse: [
|
|
2880
|
+
{ matcher: "spawn", hooks: [{ type: "command", command: 'node "${ROOT}/scripts/scrub-spawn-prompt.mjs"' }] },
|
|
2881
|
+
{ matcher: "Write", hooks: [{ type: "command", command: "node scripts/my-custom-hook.mjs" }] },
|
|
2882
|
+
],
|
|
2883
|
+
},
|
|
2884
|
+
}),
|
|
2885
|
+
);
|
|
2886
|
+
mergeClaudeSettings(destRoot, pkgRoot);
|
|
2887
|
+
const out = JSON.parse(fs.readFileSync(path.join(destRoot, ".claude", "settings.json"), "utf8"));
|
|
2888
|
+
const cmds = out.hooks.PreToolUse.flatMap((g: { hooks: { command: string }[] }) => g.hooks.map((h) => h.command));
|
|
2889
|
+
expect(cmds.filter((c: string) => c.includes("scrub-spawn-prompt.mjs"))).toEqual([
|
|
2890
|
+
'node "${ROOT}/.rulesync/scripts/scrub-spawn-prompt.mjs"',
|
|
2891
|
+
]);
|
|
2892
|
+
expect(cmds).toContain("node scripts/my-custom-hook.mjs");
|
|
2893
|
+
});
|
|
2894
|
+
});
|