@lovrozagar/grunt 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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/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/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/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 +9 -9
- 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/refs-map.md +3 -3
- 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 +5 -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/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 +7 -5
- package/CHANGELOG.md +21 -3
- 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 +67 -17
- package/cli/init.test.ts +321 -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", () => {
|
|
@@ -1775,6 +1794,17 @@ describe("mergePackageJson", () => {
|
|
|
1775
1794
|
expect(out.scripts["grunt:test:e2e"]).toBe("vitest run -c vitest.e2e.config.ts");
|
|
1776
1795
|
});
|
|
1777
1796
|
|
|
1797
|
+
it("leaves a repo's own test:e2e alone when it runs grunt's command after one of its own", () => {
|
|
1798
|
+
const dest = tmp("pj-e2e-own-");
|
|
1799
|
+
const own = "bun run build && vitest run -c vitest.e2e.config.ts";
|
|
1800
|
+
fs.writeFileSync(path.join(dest, "package.json"), JSON.stringify({ name: "app", scripts: { "test:e2e": own } }));
|
|
1801
|
+
fs.writeFileSync(path.join(dest, E2E_VITEST_CONFIGS[0]), "export default {}\n");
|
|
1802
|
+
mergePackageJson(dest, PKG_ROOT);
|
|
1803
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1804
|
+
expect(out.scripts["test:e2e"]).toBe(own);
|
|
1805
|
+
expect(out.scripts["grunt:test:e2e"]).toBe("vitest run -c vitest.e2e.config.ts");
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1778
1808
|
it("keeps an existing grunt:test:e2e even without an e2e config", () => {
|
|
1779
1809
|
const dest = tmp("pj-e2e-keep-");
|
|
1780
1810
|
fs.writeFileSync(
|
|
@@ -1887,7 +1917,7 @@ describe("init", () => {
|
|
|
1887
1917
|
expect(pkg.name).toBe("fixture-pkg");
|
|
1888
1918
|
expect(fs.existsSync(path.join(pkgRoot, ".tmp"))).toBe(true);
|
|
1889
1919
|
expect(fs.readFileSync(path.join(pkgRoot, ".gitignore"), "utf8")).toBe(".tmp/\n");
|
|
1890
|
-
expect(fs.readFileSync(path.join(pkgRoot, "scripts", "grunt-job.mjs"), "utf8")).toBe(
|
|
1920
|
+
expect(fs.readFileSync(path.join(pkgRoot, ".rulesync", "scripts", "grunt-job.mjs"), "utf8")).toBe(
|
|
1891
1921
|
"grunt-job.mjs",
|
|
1892
1922
|
);
|
|
1893
1923
|
// self-skip: markdown/settings stay exactly as-is, no sentinel wrap
|
|
@@ -1910,7 +1940,7 @@ describe("init", () => {
|
|
|
1910
1940
|
}
|
|
1911
1941
|
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1912
1942
|
expect(PRODUCT_FILES).not.toContain("grunt-config.mjs");
|
|
1913
|
-
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1943
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1914
1944
|
expect(fs.readFileSync(path.join(dest, ".claude", "marker"), "utf8")).toBe(".claude");
|
|
1915
1945
|
const settings = JSON.parse(
|
|
1916
1946
|
fs.readFileSync(path.join(dest, ".claude", "settings.json"), "utf8"),
|
|
@@ -1919,17 +1949,17 @@ describe("init", () => {
|
|
|
1919
1949
|
|
|
1920
1950
|
for (const f of GUARDED_MD_FILES) {
|
|
1921
1951
|
expect(fs.readFileSync(path.join(dest, f), "utf8")).toBe(
|
|
1922
|
-
`<!-- grunt:begin -->\n${f} content\n<!-- grunt:end -->\n`,
|
|
1952
|
+
`<!-- grunt:begin -->\n\n${f} content\n\n<!-- grunt:end -->\n`,
|
|
1923
1953
|
);
|
|
1924
1954
|
}
|
|
1925
1955
|
expect(fs.existsSync(path.join(dest, ".mcp.json"))).toBe(false);
|
|
1926
1956
|
|
|
1927
1957
|
for (const name of PRODUCT_FILES) {
|
|
1928
1958
|
const optional = ["speak.mjs", "listen.mjs", "google-workspace.mjs"].includes(name);
|
|
1929
|
-
expect(fs.existsSync(path.join(dest, "scripts", name)), name).toBe(!optional);
|
|
1959
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", name)), name).toBe(!optional);
|
|
1930
1960
|
}
|
|
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(
|
|
1961
|
+
expect(fs.statSync(path.join(dest, ".rulesync", "scripts", "scrub-text")).isDirectory()).toBe(true);
|
|
1962
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "scripts", "scrub-text", "inside"), "utf8")).toBe(
|
|
1933
1963
|
"dir",
|
|
1934
1964
|
);
|
|
1935
1965
|
|
|
@@ -2119,11 +2149,11 @@ describe("init", () => {
|
|
|
2119
2149
|
expect(agents.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
2120
2150
|
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
2121
2151
|
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);
|
|
2152
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "scrub-text"))).toBe(true);
|
|
2153
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "grunt-config.mjs"))).toBe(false);
|
|
2154
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "speak.mjs"))).toBe(false);
|
|
2155
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "listen.mjs"))).toBe(false);
|
|
2156
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "google-workspace.mjs"))).toBe(false);
|
|
2127
2157
|
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "clasp"))).toBe(false);
|
|
2128
2158
|
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "browser"))).toBe(true);
|
|
2129
2159
|
expect(fs.existsSync(path.join(dest, ".rulesync", "reference", "listen.md"))).toBe(false);
|
|
@@ -2174,8 +2204,8 @@ describe("init", () => {
|
|
|
2174
2204
|
expect(fs.existsSync(path.join(dest, ".claude", "skills", "clasp"))).toBe(false);
|
|
2175
2205
|
expect(fs.existsSync(path.join(dest, ".agents", "skills", "listen"))).toBe(false);
|
|
2176
2206
|
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);
|
|
2207
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "listen.mjs"))).toBe(false);
|
|
2208
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "speak.mjs"))).toBe(false);
|
|
2179
2209
|
expect(fs.existsSync(path.join(dest, ".rulesync", "reference", "listen.md"))).toBe(false);
|
|
2180
2210
|
expect(fs.readFileSync(path.join(dest, ".rulesync", "reference", "law.md"), "utf8")).toContain("## Mine");
|
|
2181
2211
|
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.features.jsonc"))).toBe(false);
|
|
@@ -2183,10 +2213,10 @@ describe("init", () => {
|
|
|
2183
2213
|
const picked = tmp("packs-listen-");
|
|
2184
2214
|
init(picked, { pkgRoot, execFileSync: exec, packageManager: "npm", packs: ["listen"] });
|
|
2185
2215
|
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");
|
|
2216
|
+
expect(fs.readFileSync(path.join(picked, ".rulesync", "scripts", "listen.mjs"), "utf8")).toBe("listen.mjs");
|
|
2187
2217
|
expect(fs.existsSync(path.join(picked, ".rulesync", "reference", "listen.md"))).toBe(true);
|
|
2188
2218
|
expect(fs.existsSync(path.join(picked, ".rulesync", "skills", "speak"))).toBe(false);
|
|
2189
|
-
expect(fs.existsSync(path.join(picked, "scripts", "speak.mjs"))).toBe(false);
|
|
2219
|
+
expect(fs.existsSync(path.join(picked, ".rulesync", "scripts", "speak.mjs"))).toBe(false);
|
|
2190
2220
|
expect(JSON.parse(fs.readFileSync(path.join(picked, ".rulesync", "grunt.features.jsonc"), "utf8"))).toEqual({
|
|
2191
2221
|
packs: ["listen"],
|
|
2192
2222
|
});
|
|
@@ -2199,17 +2229,17 @@ describe("init", () => {
|
|
|
2199
2229
|
);
|
|
2200
2230
|
init(saved, { pkgRoot, execFileSync: exec, packageManager: "npm" });
|
|
2201
2231
|
expect(fs.existsSync(path.join(saved, ".rulesync", "skills", "speak"))).toBe(true);
|
|
2202
|
-
expect(fs.existsSync(path.join(saved, "scripts", "listen.mjs"))).toBe(false);
|
|
2232
|
+
expect(fs.existsSync(path.join(saved, ".rulesync", "scripts", "listen.mjs"))).toBe(false);
|
|
2203
2233
|
expect(fs.readFileSync(path.join(saved, ".rulesync", "grunt.features.jsonc"), "utf8")).toBe(
|
|
2204
2234
|
'{"packs":["speak"]}\n',
|
|
2205
2235
|
);
|
|
2206
2236
|
|
|
2207
2237
|
const pkgSelf = stubPkgRoot();
|
|
2208
2238
|
writeSkill(pkgSelf, "clasp");
|
|
2209
|
-
fs.writeFileSync(path.join(pkgSelf, "scripts", "speak.mjs"), "keep-speak\n");
|
|
2239
|
+
fs.writeFileSync(path.join(pkgSelf, ".rulesync", "scripts", "speak.mjs"), "keep-speak\n");
|
|
2210
2240
|
init(pkgSelf, { pkgRoot: pkgSelf, execFileSync: exec, packs: [] });
|
|
2211
2241
|
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");
|
|
2242
|
+
expect(fs.readFileSync(path.join(pkgSelf, ".rulesync", "scripts", "speak.mjs"), "utf8")).toBe("keep-speak\n");
|
|
2213
2243
|
expect(fs.existsSync(path.join(pkgSelf, ".rulesync", "grunt.features.jsonc"))).toBe(false);
|
|
2214
2244
|
expect(() => init(tmp("packs-bad-"), { pkgRoot, execFileSync: exec, packageManager: "npm", packs: ["nope"] })).toThrow(
|
|
2215
2245
|
/unknown pack/,
|
|
@@ -2271,10 +2301,10 @@ describe("init", () => {
|
|
|
2271
2301
|
const exec = vi.fn();
|
|
2272
2302
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2273
2303
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
2274
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
2304
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
2275
2305
|
);
|
|
2276
2306
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
2277
|
-
"<!-- grunt:begin -->\nAGENTS.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
2307
|
+
"<!-- grunt:begin -->\n\nAGENTS.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
2278
2308
|
);
|
|
2279
2309
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.grunt.md"), "utf8")).toBe("stale side");
|
|
2280
2310
|
expect(fs.existsSync(path.join(dest, "AGENTS.grunt.md"))).toBe(false);
|
|
@@ -2288,7 +2318,7 @@ describe("init", () => {
|
|
|
2288
2318
|
const exec = vi.fn();
|
|
2289
2319
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2290
2320
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
2291
|
-
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nhand-written consumer doc\n",
|
|
2321
|
+
"<!-- grunt:begin -->\n\nCLAUDE.md content\n\n<!-- grunt:end -->\n\nhand-written consumer doc\n",
|
|
2292
2322
|
);
|
|
2293
2323
|
expect(fs.existsSync(path.join(dest, "CLAUDE.grunt.md"))).toBe(false);
|
|
2294
2324
|
expect(fs.existsSync(path.join(dest, "AGENTS.grunt.md"))).toBe(false);
|
|
@@ -2315,17 +2345,17 @@ describe("init", () => {
|
|
|
2315
2345
|
});
|
|
2316
2346
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2317
2347
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
2318
|
-
"<!-- grunt:begin -->\ngenerated agents\n<!-- grunt:end -->\nconsumer agents\n",
|
|
2348
|
+
"<!-- grunt:begin -->\n\ngenerated agents\n\n<!-- grunt:end -->\n\nconsumer agents\n",
|
|
2319
2349
|
);
|
|
2320
2350
|
expect(fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8")).toBe(
|
|
2321
|
-
"<!-- grunt:begin -->\ngenerated claude\n<!-- grunt:end -->\nconsumer claude\n",
|
|
2351
|
+
"<!-- grunt:begin -->\n\ngenerated claude\n\n<!-- grunt:end -->\n\nconsumer claude\n",
|
|
2322
2352
|
);
|
|
2323
2353
|
expect(fs.readFileSync(path.join(dest, "GEMINI.md"), "utf8")).toBe(
|
|
2324
|
-
"<!-- grunt:begin -->\n@AGENTS.md\n<!-- grunt:end -->\nconsumer gemini\n",
|
|
2354
|
+
"<!-- grunt:begin -->\n\n@AGENTS.md\n\n<!-- grunt:end -->\n\nconsumer gemini\n",
|
|
2325
2355
|
);
|
|
2326
2356
|
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
2327
2357
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
|
|
2328
|
-
"<!-- grunt:begin -->\nAGENTS.md content\n<!-- grunt:end -->\nconsumer agents\n",
|
|
2358
|
+
"<!-- grunt:begin -->\n\nAGENTS.md content\n\n<!-- grunt:end -->\n\nconsumer agents\n",
|
|
2329
2359
|
);
|
|
2330
2360
|
expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8").split("consumer agents").length).toBe(
|
|
2331
2361
|
2,
|
|
@@ -2350,8 +2380,8 @@ describe("init", () => {
|
|
|
2350
2380
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
2351
2381
|
const agents = fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8");
|
|
2352
2382
|
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");
|
|
2383
|
+
expect(agents).toBe("<!-- grunt:begin -->\n\ngenerated agents\n\n<!-- grunt:end -->\n");
|
|
2384
|
+
expect(claude).toBe("<!-- grunt:begin -->\n\ngenerated claude\n\n<!-- grunt:end -->\n");
|
|
2355
2385
|
expect(agents.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
2356
2386
|
expect(agents.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
2357
2387
|
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
@@ -2483,23 +2513,26 @@ describe("init", () => {
|
|
|
2483
2513
|
expect(index).toMatch(/push → go-live\//);
|
|
2484
2514
|
});
|
|
2485
2515
|
|
|
2486
|
-
it("seeds grunt.pipeline.jsonc from
|
|
2516
|
+
it("seeds grunt.pipeline.jsonc from a legacy root pipeline, then moves grunt scripts out of root", () => {
|
|
2487
2517
|
const dest = tmp("grunt-pipe-init-");
|
|
2488
2518
|
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true });
|
|
2519
|
+
const legacy = (c: string) => c.replace(".rulesync/scripts/", "scripts/");
|
|
2489
2520
|
const skipped = PIPELINE_COMMANDS.check.find((c) => c.includes("subagents"));
|
|
2490
2521
|
const src = `export const COMMANDS = {
|
|
2491
2522
|
check: [
|
|
2492
|
-
${PIPELINE_COMMANDS.check.filter((c) => c !== skipped).map((c) => JSON.stringify(c)).join(",\n ")}
|
|
2523
|
+
${PIPELINE_COMMANDS.check.filter((c) => c !== skipped).map((c) => JSON.stringify(legacy(c))).join(",\n ")}
|
|
2493
2524
|
],
|
|
2494
2525
|
};
|
|
2495
2526
|
`;
|
|
2496
2527
|
fs.writeFileSync(path.join(dest, "scripts", "pipeline.mjs"), src);
|
|
2528
|
+
fs.writeFileSync(path.join(dest, "scripts", "gate-fat-tools.mjs"), "old gate");
|
|
2497
2529
|
init(dest, { execFileSync: vi.fn(), skipGlobals: true, packageManager: "npm" });
|
|
2498
2530
|
const cfg = JSON.parse(fs.readFileSync(path.join(dest, PIPELINE_CONFIG_REL), "utf8"));
|
|
2499
2531
|
expect(cfg.check.skip).toEqual([skipped]);
|
|
2500
|
-
expect(fs.readFileSync(path.join(dest, "scripts", "pipeline.mjs"), "utf8")).toContain(
|
|
2532
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "scripts", "pipeline.mjs"), "utf8")).toContain(
|
|
2501
2533
|
"subagents --check",
|
|
2502
2534
|
);
|
|
2535
|
+
expect(fs.existsSync(path.join(dest, "scripts"))).toBe(false);
|
|
2503
2536
|
});
|
|
2504
2537
|
|
|
2505
2538
|
it("does not stamp package INDEX over dest; re-emits union", () => {
|
|
@@ -2634,10 +2667,13 @@ function relativeSpecs(text: string, re: RegExp) {
|
|
|
2634
2667
|
}
|
|
2635
2668
|
|
|
2636
2669
|
describe("product scripts are consumer-safe", () => {
|
|
2637
|
-
it("package.json files
|
|
2670
|
+
it("package.json files ships every PRODUCT_SCRIPTS entry and no tests or fixtures", () => {
|
|
2638
2671
|
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
|
|
2672
|
+
expect(pkg.files).toContain(".rulesync");
|
|
2673
|
+
expect(pkg.files).toContain("!.rulesync/scripts/**/*.test.ts");
|
|
2674
|
+
expect(pkg.files).toContain("!.rulesync/scripts/fixtures");
|
|
2639
2675
|
for (const name of PRODUCT_SCRIPTS) {
|
|
2640
|
-
expect(
|
|
2676
|
+
expect(fs.existsSync(path.join(PKG_ROOT, SCRIPTS_REL, name)), name).toBe(true);
|
|
2641
2677
|
}
|
|
2642
2678
|
});
|
|
2643
2679
|
|
|
@@ -2660,25 +2696,25 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2660
2696
|
}
|
|
2661
2697
|
};
|
|
2662
2698
|
walk(path.join(PKG_ROOT, "bin", "grunt.js"));
|
|
2663
|
-
expect(seen.has(path.join(PKG_ROOT, "scripts", "folder-map.mjs"))).toBe(true);
|
|
2699
|
+
expect(seen.has(path.join(PKG_ROOT, ".rulesync", "scripts", "folder-map.mjs"))).toBe(true);
|
|
2664
2700
|
expect(bad).toEqual([]);
|
|
2665
2701
|
});
|
|
2666
2702
|
|
|
2667
|
-
it("do not parent-import (from or import()) — init copies scripts/ only", () => {
|
|
2703
|
+
it("do not parent-import (from or import()) — init copies .rulesync/scripts/ only", () => {
|
|
2668
2704
|
for (const name of PRODUCT_FILES) {
|
|
2669
|
-
const text = fs.readFileSync(path.join(PKG_ROOT, "scripts", name), "utf8");
|
|
2705
|
+
const text = fs.readFileSync(path.join(PKG_ROOT, ".rulesync", "scripts", name), "utf8");
|
|
2670
2706
|
expect(relativeSpecs(text, PARENT_IMPORT_RE), name).toEqual([]);
|
|
2671
2707
|
}
|
|
2672
2708
|
});
|
|
2673
2709
|
|
|
2674
|
-
it("repro: dest node
|
|
2710
|
+
it("repro: dest node ./.rulesync/scripts/guarded-roots.mjs generate misses ../cli/init.mjs", () => {
|
|
2675
2711
|
const dest = tmp("repro-cli-miss-");
|
|
2676
|
-
fs.mkdirSync(path.join(dest, "scripts"));
|
|
2712
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "scripts"), { recursive: true });
|
|
2677
2713
|
fs.writeFileSync(
|
|
2678
|
-
path.join(dest, "scripts", "broken.mjs"),
|
|
2679
|
-
'import { snapshotGuardedRoots } from "
|
|
2714
|
+
path.join(dest, ".rulesync", "scripts", "broken.mjs"),
|
|
2715
|
+
'import { snapshotGuardedRoots } from "../../cli/init.mjs";\n',
|
|
2680
2716
|
);
|
|
2681
|
-
const r = spawnDest(dest, ["
|
|
2717
|
+
const r = spawnDest(dest, ["./.rulesync/scripts/broken.mjs", "generate"]);
|
|
2682
2718
|
expect(r.status).not.toBe(0);
|
|
2683
2719
|
expect(r.stderr).toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2684
2720
|
expect(r.stderr).toMatch(/cli\/init\.mjs/);
|
|
@@ -2693,11 +2729,11 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2693
2729
|
applyGlobals: false,
|
|
2694
2730
|
});
|
|
2695
2731
|
expect(fs.existsSync(path.join(dest, "cli"))).toBe(false);
|
|
2696
|
-
expect(fs.existsSync(path.join(dest, "scripts", "guarded-roots.mjs"))).toBe(true);
|
|
2732
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "scripts", "guarded-roots.mjs"))).toBe(true);
|
|
2697
2733
|
|
|
2698
2734
|
const optionalScripts = new Set(["speak.mjs", "listen.mjs", "google-workspace.mjs"]);
|
|
2699
2735
|
for (const name of PRODUCT_FILES) {
|
|
2700
|
-
const file = path.join(dest, "scripts", name);
|
|
2736
|
+
const file = path.join(dest, ".rulesync", "scripts", name);
|
|
2701
2737
|
if (optionalScripts.has(name)) {
|
|
2702
2738
|
expect(fs.existsSync(file), name).toBe(false);
|
|
2703
2739
|
continue;
|
|
@@ -2711,7 +2747,7 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2711
2747
|
}
|
|
2712
2748
|
}
|
|
2713
2749
|
|
|
2714
|
-
const setupRun = spawnDest(dest, ["
|
|
2750
|
+
const setupRun = spawnDest(dest, ["./.rulesync/scripts/setup.mjs"], {
|
|
2715
2751
|
...process.env,
|
|
2716
2752
|
CI: "1",
|
|
2717
2753
|
PATH: ["/usr/bin", "/bin"].join(path.delimiter),
|
|
@@ -2720,12 +2756,12 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2720
2756
|
expect(setupRun.status).toBe(1);
|
|
2721
2757
|
expect(`${setupRun.stdout}${setupRun.stderr}`).toContain("usage: setup");
|
|
2722
2758
|
|
|
2723
|
-
const loaded = spawnDest(dest, ["
|
|
2759
|
+
const loaded = spawnDest(dest, ["./.rulesync/scripts/guarded-roots.mjs"]);
|
|
2724
2760
|
expect(loaded.status).toBe(1);
|
|
2725
2761
|
expect(loaded.stderr).toBe("usage: guarded-roots.mjs generate|check|watch\n");
|
|
2726
2762
|
expect(loaded.stderr).not.toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2727
2763
|
|
|
2728
|
-
const gen = spawnDest(dest, ["
|
|
2764
|
+
const gen = spawnDest(dest, ["./.rulesync/scripts/guarded-roots.mjs", "generate"], {
|
|
2729
2765
|
...process.env,
|
|
2730
2766
|
PATH: ["/usr/bin", "/bin"].join(path.delimiter),
|
|
2731
2767
|
});
|
|
@@ -2737,7 +2773,7 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2737
2773
|
const r = spawnDest(dest, [
|
|
2738
2774
|
"--input-type=module",
|
|
2739
2775
|
"-e",
|
|
2740
|
-
`import(${JSON.stringify(
|
|
2776
|
+
`import(${JSON.stringify(`./.rulesync/scripts/${name}`)})`,
|
|
2741
2777
|
]);
|
|
2742
2778
|
expect(r.status, `${name} load\n${r.stderr}`).toBe(0);
|
|
2743
2779
|
expect(r.stderr, name).not.toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
@@ -2745,3 +2781,125 @@ describe("product scripts are consumer-safe", () => {
|
|
|
2745
2781
|
});
|
|
2746
2782
|
});
|
|
2747
2783
|
|
|
2784
|
+
|
|
2785
|
+
describe("legacy root scripts migration", () => {
|
|
2786
|
+
function legacyDir(names: string[]) {
|
|
2787
|
+
const dest = tmp("legacy-scripts-");
|
|
2788
|
+
fs.mkdirSync(path.join(dest, LEGACY_SCRIPTS_REL));
|
|
2789
|
+
for (const n of names) fs.writeFileSync(path.join(dest, LEGACY_SCRIPTS_REL, n), n);
|
|
2790
|
+
return dest;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
it("drops grunt names and keeps consumer files when a prior grunt install is detected", () => {
|
|
2794
|
+
const dest = legacyDir(["gate-fat-tools.mjs", "doctor.mjs", "mine.mjs"]);
|
|
2795
|
+
pruneLegacyScripts(dest);
|
|
2796
|
+
expect(fs.readdirSync(path.join(dest, LEGACY_SCRIPTS_REL))).toEqual(["mine.mjs"]);
|
|
2797
|
+
});
|
|
2798
|
+
|
|
2799
|
+
it("removes the root dir when only grunt names were in it", () => {
|
|
2800
|
+
const dest = legacyDir(["guarded-roots.mjs", "pipeline.mjs"]);
|
|
2801
|
+
pruneLegacyScripts(dest);
|
|
2802
|
+
expect(fs.existsSync(path.join(dest, LEGACY_SCRIPTS_REL))).toBe(false);
|
|
2803
|
+
});
|
|
2804
|
+
|
|
2805
|
+
it("leaves a consumer's own same-named scripts alone without a grunt marker", () => {
|
|
2806
|
+
const dest = legacyDir(["setup.mjs", "doctor.mjs"]);
|
|
2807
|
+
pruneLegacyScripts(dest);
|
|
2808
|
+
expect(fs.readdirSync(path.join(dest, LEGACY_SCRIPTS_REL)).sort()).toEqual(["doctor.mjs", "setup.mjs"]);
|
|
2809
|
+
const none = tmp("legacy-none-");
|
|
2810
|
+
pruneLegacyScripts(none);
|
|
2811
|
+
expect(fs.existsSync(path.join(none, LEGACY_SCRIPTS_REL))).toBe(false);
|
|
2812
|
+
});
|
|
2813
|
+
|
|
2814
|
+
it("rewrites only grunt-owned legacy refs", () => {
|
|
2815
|
+
expect(rewriteLegacyScriptRefs("node ./scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs")).toBe(
|
|
2816
|
+
"node ./.rulesync/scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
2817
|
+
);
|
|
2818
|
+
expect(rewriteLegacyScriptRefs('node "${ROOT}/scripts/gate-fat-tools.mjs"')).toBe(
|
|
2819
|
+
'node "${ROOT}/.rulesync/scripts/gate-fat-tools.mjs"',
|
|
2820
|
+
);
|
|
2821
|
+
expect(rewriteLegacyScriptRefs('["node scripts/emit-maps.mjs --check"]')).toBe(
|
|
2822
|
+
'["node .rulesync/scripts/emit-maps.mjs --check"]',
|
|
2823
|
+
);
|
|
2824
|
+
for (const keep of [
|
|
2825
|
+
"node scripts/lib/env.mjs",
|
|
2826
|
+
"node tools/scripts/doctor.mjs",
|
|
2827
|
+
"node scripts/doctor.mjsx",
|
|
2828
|
+
"node .rulesync/scripts/doctor.mjs",
|
|
2829
|
+
]) {
|
|
2830
|
+
expect(rewriteLegacyScriptRefs(keep)).toBe(keep);
|
|
2831
|
+
}
|
|
2832
|
+
});
|
|
2833
|
+
|
|
2834
|
+
it("init migrates grunt:* package scripts and the pipeline overlay; consumer refs stay", () => {
|
|
2835
|
+
const pkgRoot = stubPkgRoot({
|
|
2836
|
+
name: "fixture-pkg",
|
|
2837
|
+
version: "1.0.0",
|
|
2838
|
+
scripts: { "rulesync:generate": "node ./.rulesync/scripts/guarded-roots.mjs generate" },
|
|
2839
|
+
dependencies: { "smol-toml": "^1.8.0" },
|
|
2840
|
+
devDependencies: { rulesync: "latest" },
|
|
2841
|
+
});
|
|
2842
|
+
const dest = tmp("legacy-init-");
|
|
2843
|
+
fs.writeFileSync(
|
|
2844
|
+
path.join(dest, "package.json"),
|
|
2845
|
+
JSON.stringify({
|
|
2846
|
+
scripts: {
|
|
2847
|
+
"grunt:rulesync:generate": "node ./scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
2848
|
+
lint: "node scripts/lint.mjs",
|
|
2849
|
+
},
|
|
2850
|
+
}),
|
|
2851
|
+
);
|
|
2852
|
+
fs.mkdirSync(path.join(dest, ".rulesync"), { recursive: true });
|
|
2853
|
+
fs.writeFileSync(
|
|
2854
|
+
path.join(dest, PIPELINE_CONFIG_REL),
|
|
2855
|
+
JSON.stringify({
|
|
2856
|
+
check: { skip: ["node scripts/emit-maps.mjs --check"], extra: ["node scripts/codex-sync.mjs --check"] },
|
|
2857
|
+
}),
|
|
2858
|
+
);
|
|
2859
|
+
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true, packageManager: "npm" });
|
|
2860
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
2861
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe(
|
|
2862
|
+
"node ./.rulesync/scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
2863
|
+
);
|
|
2864
|
+
expect(out.scripts.lint).toBe("node scripts/lint.mjs");
|
|
2865
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(dest, PIPELINE_CONFIG_REL), "utf8"));
|
|
2866
|
+
expect(cfg.check).toEqual({
|
|
2867
|
+
skip: ["node .rulesync/scripts/emit-maps.mjs --check"],
|
|
2868
|
+
extra: ["node scripts/codex-sync.mjs --check"],
|
|
2869
|
+
});
|
|
2870
|
+
});
|
|
2871
|
+
|
|
2872
|
+
it("init copies product scripts to .rulesync/scripts and moves them out of a legacy root", () => {
|
|
2873
|
+
const pkgRoot = stubPkgRoot();
|
|
2874
|
+
fs.writeFileSync(path.join(pkgRoot, SCRIPTS_REL, "x.test.ts"), "test");
|
|
2875
|
+
const dest = legacyDir(["gate-fat-tools.mjs", "board.mjs", "mine.mjs"]);
|
|
2876
|
+
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true, packageManager: "npm" });
|
|
2877
|
+
expect(fs.readFileSync(path.join(dest, SCRIPTS_REL, "gate-fat-tools.mjs"), "utf8")).toBe("gate-fat-tools.mjs");
|
|
2878
|
+
expect(fs.existsSync(path.join(dest, SCRIPTS_REL, "x.test.ts"))).toBe(false);
|
|
2879
|
+
expect(fs.readdirSync(path.join(dest, LEGACY_SCRIPTS_REL))).toEqual(["mine.mjs"]);
|
|
2880
|
+
});
|
|
2881
|
+
|
|
2882
|
+
it("mergeClaudeSettings replaces legacy-path grunt hooks without duplicating; consumer hook kept", () => {
|
|
2883
|
+
const pkgRoot = stubPkgRoot();
|
|
2884
|
+
const destRoot = tmp("settings-legacy-");
|
|
2885
|
+
fs.mkdirSync(path.join(destRoot, ".claude"), { recursive: true });
|
|
2886
|
+
fs.writeFileSync(
|
|
2887
|
+
path.join(destRoot, ".claude", "settings.json"),
|
|
2888
|
+
JSON.stringify({
|
|
2889
|
+
hooks: {
|
|
2890
|
+
PreToolUse: [
|
|
2891
|
+
{ matcher: "spawn", hooks: [{ type: "command", command: 'node "${ROOT}/scripts/scrub-spawn-prompt.mjs"' }] },
|
|
2892
|
+
{ matcher: "Write", hooks: [{ type: "command", command: "node scripts/my-custom-hook.mjs" }] },
|
|
2893
|
+
],
|
|
2894
|
+
},
|
|
2895
|
+
}),
|
|
2896
|
+
);
|
|
2897
|
+
mergeClaudeSettings(destRoot, pkgRoot);
|
|
2898
|
+
const out = JSON.parse(fs.readFileSync(path.join(destRoot, ".claude", "settings.json"), "utf8"));
|
|
2899
|
+
const cmds = out.hooks.PreToolUse.flatMap((g: { hooks: { command: string }[] }) => g.hooks.map((h) => h.command));
|
|
2900
|
+
expect(cmds.filter((c: string) => c.includes("scrub-spawn-prompt.mjs"))).toEqual([
|
|
2901
|
+
'node "${ROOT}/.rulesync/scripts/scrub-spawn-prompt.mjs"',
|
|
2902
|
+
]);
|
|
2903
|
+
expect(cmds).toContain("node scripts/my-custom-hook.mjs");
|
|
2904
|
+
});
|
|
2905
|
+
});
|