@lovrozagar/grunt 0.5.0 → 0.6.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/grunt.md +2 -23
- package/.agents/agents/orchestrator.md +5 -45
- package/.agents/skills/ask/SKILL.md +11 -0
- package/.agents/skills/auto/SKILL.md +13 -0
- package/.agents/skills/browser/SKILL.md +4 -7
- package/.agents/skills/clasp/SKILL.md +11 -0
- package/.agents/skills/commit/SKILL.md +3 -1
- package/.agents/skills/commit-push/SKILL.md +2 -0
- package/.agents/skills/commit-push-deploy/SKILL.md +2 -0
- package/.agents/skills/commit-push-release/SKILL.md +2 -0
- package/.agents/skills/explain/SKILL.md +7 -13
- package/.agents/skills/google-workspace/SKILL.md +13 -0
- package/.agents/skills/handoff/SKILL.md +7 -11
- package/.agents/skills/implement-plan/SKILL.md +5 -108
- package/.agents/skills/listen/SKILL.md +15 -0
- package/.agents/skills/pickup/SKILL.md +6 -12
- package/.agents/skills/speak/SKILL.md +13 -0
- package/.agents/skills/tmp/SKILL.md +54 -0
- package/.agents/skills/write-plan/SKILL.md +5 -74
- package/.claude/agents/grunt.md +2 -25
- package/.claude/agents/orchestrator.md +12 -45
- package/.claude/skills/ask/SKILL.md +11 -0
- package/.claude/skills/auto/SKILL.md +13 -0
- package/.claude/skills/browser/SKILL.md +4 -7
- package/.claude/skills/clasp/SKILL.md +11 -0
- package/.claude/skills/commit/SKILL.md +3 -1
- package/.claude/skills/commit-push/SKILL.md +2 -0
- package/.claude/skills/commit-push-deploy/SKILL.md +2 -0
- package/.claude/skills/commit-push-release/SKILL.md +2 -0
- package/.claude/skills/explain/SKILL.md +7 -13
- package/.claude/skills/google-workspace/SKILL.md +13 -0
- package/.claude/skills/handoff/SKILL.md +7 -11
- package/.claude/skills/implement-plan/SKILL.md +5 -108
- package/.claude/skills/listen/SKILL.md +15 -0
- package/.claude/skills/pickup/SKILL.md +6 -12
- package/.claude/skills/speak/SKILL.md +13 -0
- package/.claude/skills/tmp/SKILL.md +54 -0
- package/.claude/skills/write-plan/SKILL.md +5 -74
- package/.codex/agents/grunt.toml +2 -25
- package/.codex/agents/orchestrator.toml +4 -45
- package/.grok/agents/grunt.md +2 -23
- package/.grok/agents/orchestrator.md +4 -43
- package/.grok/global-settings.toml +1 -3
- package/.grok/hooks/orchestrate-parent.js +278 -228
- package/.grok/skills/ask/SKILL.md +11 -0
- package/.grok/skills/auto/SKILL.md +13 -0
- package/.grok/skills/browser/SKILL.md +4 -7
- package/.grok/skills/clasp/SKILL.md +11 -0
- package/.grok/skills/commit/SKILL.md +3 -1
- package/.grok/skills/commit-push/SKILL.md +2 -0
- package/.grok/skills/commit-push-deploy/SKILL.md +2 -0
- package/.grok/skills/commit-push-release/SKILL.md +2 -0
- package/.grok/skills/explain/SKILL.md +7 -13
- package/.grok/skills/google-workspace/SKILL.md +13 -0
- package/.grok/skills/handoff/SKILL.md +7 -11
- package/.grok/skills/implement-plan/SKILL.md +5 -108
- package/.grok/skills/listen/SKILL.md +15 -0
- package/.grok/skills/pickup/SKILL.md +6 -12
- package/.grok/skills/speak/SKILL.md +13 -0
- package/.grok/skills/tmp/SKILL.md +54 -0
- package/.grok/skills/write-plan/SKILL.md +5 -74
- package/.rulesync/hooks.jsonc +1 -1
- package/.rulesync/mcp-policy.jsonc +0 -2
- package/.rulesync/reference/INDEX.md +30 -17
- package/.rulesync/reference/browser.md +13 -6
- package/.rulesync/reference/cascade.md +6 -84
- package/.rulesync/reference/clasp.md +21 -0
- package/.rulesync/reference/google-workspace.md +79 -0
- package/.rulesync/reference/hooks.md +7 -9
- package/.rulesync/reference/implementation-format.md +113 -0
- package/.rulesync/reference/law.md +5 -3
- package/.rulesync/reference/listen.md +80 -0
- package/.rulesync/reference/map.md +39 -31
- package/.rulesync/reference/output.md +3 -41
- package/.rulesync/reference/plan-format.md +14 -12
- package/.rulesync/reference/refs-map.md +12 -5
- package/.rulesync/reference/rtk.md +3 -2
- package/.rulesync/reference/scope.md +37 -0
- package/.rulesync/reference/setup.md +27 -0
- package/.rulesync/reference/skills-map.md +13 -9
- package/.rulesync/reference/speak.md +59 -0
- package/.rulesync/rules/CLAUDE.md +3 -44
- package/.rulesync/rules/overview.md +9 -42
- package/.rulesync/skills/ask/SKILL.md +11 -0
- package/.rulesync/skills/auto/SKILL.md +11 -0
- package/.rulesync/skills/browser/SKILL.md +4 -8
- package/.rulesync/skills/clasp/SKILL.md +9 -0
- package/.rulesync/skills/commit/SKILL.md +3 -1
- package/.rulesync/skills/commit-push/SKILL.md +2 -0
- package/.rulesync/skills/commit-push-deploy/SKILL.md +2 -0
- package/.rulesync/skills/commit-push-release/SKILL.md +2 -0
- package/.rulesync/skills/explain/SKILL.md +7 -14
- package/.rulesync/skills/google-workspace/SKILL.md +11 -0
- package/.rulesync/skills/handoff/SKILL.md +7 -11
- package/.rulesync/skills/implement-plan/SKILL.md +5 -116
- package/.rulesync/skills/listen/SKILL.md +13 -0
- package/.rulesync/skills/pickup/SKILL.md +6 -12
- package/.rulesync/skills/speak/SKILL.md +13 -0
- package/.rulesync/skills/tmp/SKILL.md +54 -0
- package/.rulesync/skills/write-plan/SKILL.md +5 -80
- package/.rulesync/subagents/grunt.md +2 -23
- package/.rulesync/subagents/orchestrator.md +14 -54
- package/AGENTS.md +9 -42
- package/CHANGELOG.md +89 -0
- package/CLAUDE.md +1 -44
- package/README.md +97 -88
- package/cli/grunt.mjs +29 -13
- package/cli/grunt.test.ts +62 -20
- package/cli/init.mjs +218 -31
- package/cli/init.test.ts +669 -46
- package/cli/prompt.mjs +8 -0
- package/cli/prompt.test.ts +14 -0
- package/package.json +13 -1
- package/scripts/browser.mjs +98 -5
- package/scripts/doctor.mjs +82 -1
- package/scripts/emit-gemini.mjs +1 -1
- package/scripts/emit-mcp-policy.mjs +1 -1
- package/scripts/gate-fat-tools.mjs +223 -173
- package/scripts/google-workspace.mjs +713 -0
- package/scripts/grunt-job.mjs +453 -69
- package/scripts/listen.mjs +829 -0
- package/scripts/parse-need.mjs +21 -2
- package/scripts/persist-implementation.mjs +278 -0
- package/scripts/persist-plan.mjs +17 -2
- package/scripts/persist-tmp.mjs +123 -0
- package/scripts/scrub-spawn-prompt.mjs +4 -51
- package/scripts/setup.mjs +746 -0
- package/scripts/speak.mjs +291 -0
- package/.agents/agents/implementer.md +0 -32
- package/.agents/agents/thinker.md +0 -28
- package/.agents/skills/cascade/SKILL.md +0 -26
- package/.agents/skills/parent/SKILL.md +0 -6
- package/.agents/skills/solo/SKILL.md +0 -23
- package/.claude/agents/implementer.md +0 -37
- package/.claude/agents/thinker.md +0 -35
- package/.claude/skills/cascade/SKILL.md +0 -26
- package/.claude/skills/parent/SKILL.md +0 -6
- package/.claude/skills/solo/SKILL.md +0 -23
- package/.codex/agents/implementer.toml +0 -26
- package/.codex/agents/thinker.toml +0 -25
- package/.grok/agents/implementer.md +0 -27
- package/.grok/agents/thinker.md +0 -26
- package/.grok/parent.md +0 -1
- package/.grok/roles/implementer.toml +0 -5
- package/.grok/roles/thinker.toml +0 -5
- package/.grok/skills/cascade/SKILL.md +0 -26
- package/.grok/skills/parent/SKILL.md +0 -6
- package/.grok/skills/solo/SKILL.md +0 -23
- package/.rulesync/skills/cascade/SKILL.md +0 -26
- package/.rulesync/skills/parent/SKILL.md +0 -6
- package/.rulesync/skills/solo/SKILL.md +0 -23
- package/.rulesync/subagents/implementer.md +0 -49
- package/.rulesync/subagents/thinker.md +0 -48
package/cli/init.test.ts
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
6
|
import {
|
|
6
7
|
MAX_GUARDED_MARKDOWN_BYTES,
|
|
7
8
|
composeGuardedMarkdown,
|
|
8
9
|
destAlreadyInited,
|
|
9
10
|
extractGruntBody,
|
|
11
|
+
extractUserMarkdown,
|
|
10
12
|
GUARDED_ROOT_FILES,
|
|
13
|
+
SENTINEL_BEGIN,
|
|
14
|
+
SENTINEL_END,
|
|
11
15
|
guardedMarkdownDrift,
|
|
12
16
|
healGuardedRootFile,
|
|
13
17
|
init,
|
|
18
|
+
pruneRetired,
|
|
19
|
+
RETIRED_SKILLS,
|
|
20
|
+
RETIRED_AGENTS,
|
|
21
|
+
RETIRED_SCRIPTS,
|
|
22
|
+
RETIRED_PATHS,
|
|
23
|
+
RESERVED_SKILLS,
|
|
24
|
+
LAUNCH_SCRIPTS,
|
|
14
25
|
mergeClaudeSettings,
|
|
15
26
|
mergeGitignore,
|
|
16
27
|
mergeGuardedContent,
|
|
@@ -20,6 +31,7 @@ import {
|
|
|
20
31
|
samePath,
|
|
21
32
|
shouldAutoSkipGlobals,
|
|
22
33
|
snapshotGuardedRoots,
|
|
34
|
+
toGruntScriptName,
|
|
23
35
|
withGuardedCheckInteriors,
|
|
24
36
|
writeMergedGuardedFile,
|
|
25
37
|
} from "./init.mjs";
|
|
@@ -53,12 +65,18 @@ const PRODUCT_FILES = [
|
|
|
53
65
|
"grunt-job.mjs",
|
|
54
66
|
"parse-need.mjs",
|
|
55
67
|
"persist-handoff.mjs",
|
|
68
|
+
"persist-implementation.mjs",
|
|
69
|
+
"persist-tmp.mjs",
|
|
56
70
|
"persist-plan.mjs",
|
|
57
71
|
"purge-global-mcps.mjs",
|
|
58
72
|
"scrub-spawn-prompt.mjs",
|
|
59
73
|
"scrub-text-lib.mjs",
|
|
60
74
|
"sync-global-settings.mjs",
|
|
61
75
|
"browser.mjs",
|
|
76
|
+
"speak.mjs",
|
|
77
|
+
"listen.mjs",
|
|
78
|
+
"google-workspace.mjs",
|
|
79
|
+
"setup.mjs",
|
|
62
80
|
"doctor.mjs",
|
|
63
81
|
"skill-conflicts.mjs",
|
|
64
82
|
];
|
|
@@ -154,18 +172,22 @@ describe("samePath", () => {
|
|
|
154
172
|
});
|
|
155
173
|
});
|
|
156
174
|
|
|
175
|
+
const GI_TMP = ".tmp/\n";
|
|
176
|
+
|
|
157
177
|
describe("mergeGitignore", () => {
|
|
158
178
|
it("creates .tmp/ when missing", () => {
|
|
159
179
|
const dest = tmp("gi-miss-");
|
|
160
180
|
mergeGitignore(dest);
|
|
161
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
181
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(GI_TMP);
|
|
162
182
|
});
|
|
163
183
|
|
|
164
|
-
it("noop when .tmp/ present", () => {
|
|
184
|
+
it("noop when .tmp/ already present", () => {
|
|
165
185
|
const dest = tmp("gi-slash-");
|
|
166
186
|
fs.writeFileSync(path.join(dest, ".gitignore"), "foo\n.tmp/\nbar\n");
|
|
167
187
|
mergeGitignore(dest);
|
|
168
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
188
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
189
|
+
"foo\n.tmp/\nbar\n",
|
|
190
|
+
);
|
|
169
191
|
});
|
|
170
192
|
|
|
171
193
|
it("noop when .tmp present", () => {
|
|
@@ -175,12 +197,21 @@ describe("mergeGitignore", () => {
|
|
|
175
197
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(".tmp\n");
|
|
176
198
|
});
|
|
177
199
|
|
|
200
|
+
it("noop when .tmp/ present in mixed file", () => {
|
|
201
|
+
const dest = tmp("gi-both-");
|
|
202
|
+
fs.writeFileSync(path.join(dest, ".gitignore"), `foo\n${GI_TMP}bar\n`);
|
|
203
|
+
mergeGitignore(dest);
|
|
204
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
205
|
+
`foo\n${GI_TMP}bar\n`,
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
178
209
|
it("appends after trailing newline", () => {
|
|
179
210
|
const dest = tmp("gi-nl-");
|
|
180
211
|
fs.writeFileSync(path.join(dest, ".gitignore"), "node_modules/\n");
|
|
181
212
|
mergeGitignore(dest);
|
|
182
213
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
183
|
-
|
|
214
|
+
`node_modules/\n${GI_TMP}`,
|
|
184
215
|
);
|
|
185
216
|
});
|
|
186
217
|
|
|
@@ -189,7 +220,7 @@ describe("mergeGitignore", () => {
|
|
|
189
220
|
fs.writeFileSync(path.join(dest, ".gitignore"), "node_modules/");
|
|
190
221
|
mergeGitignore(dest);
|
|
191
222
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
192
|
-
|
|
223
|
+
`node_modules/\n${GI_TMP}`,
|
|
193
224
|
);
|
|
194
225
|
});
|
|
195
226
|
|
|
@@ -197,7 +228,7 @@ describe("mergeGitignore", () => {
|
|
|
197
228
|
const dest = tmp("gi-empty-");
|
|
198
229
|
fs.writeFileSync(path.join(dest, ".gitignore"), "");
|
|
199
230
|
mergeGitignore(dest);
|
|
200
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
231
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(GI_TMP);
|
|
201
232
|
});
|
|
202
233
|
});
|
|
203
234
|
|
|
@@ -290,6 +321,67 @@ describe("mergeGuardedMarkdown", () => {
|
|
|
290
321
|
);
|
|
291
322
|
});
|
|
292
323
|
|
|
324
|
+
it("already-guarded package src stays begin=1 end=1", () => {
|
|
325
|
+
const pkgRoot = stubPkgRoot();
|
|
326
|
+
fs.writeFileSync(
|
|
327
|
+
path.join(pkgRoot, "CLAUDE.md"),
|
|
328
|
+
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n",
|
|
329
|
+
);
|
|
330
|
+
const dest = tmp("md-wrapped-src-");
|
|
331
|
+
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
332
|
+
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
333
|
+
expect(out).toBe("<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n");
|
|
334
|
+
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
335
|
+
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("merge heals dest with trailing orphan grunt:end", () => {
|
|
339
|
+
const pkgRoot = stubPkgRoot();
|
|
340
|
+
const dest = tmp("md-orphan-end-");
|
|
341
|
+
fs.writeFileSync(
|
|
342
|
+
path.join(dest, "CLAUDE.md"),
|
|
343
|
+
"<!-- grunt:begin -->\nold\n<!-- grunt:end -->\n<!-- grunt:end -->\n",
|
|
344
|
+
);
|
|
345
|
+
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
346
|
+
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
347
|
+
expect(out).toBe("<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n");
|
|
348
|
+
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("dest stacked researcher shape heals to exactly one pair", () => {
|
|
352
|
+
const pkgRoot = stubPkgRoot();
|
|
353
|
+
fs.writeFileSync(
|
|
354
|
+
path.join(pkgRoot, "CLAUDE.md"),
|
|
355
|
+
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n",
|
|
356
|
+
);
|
|
357
|
+
const dest = tmp("md-stacked-");
|
|
358
|
+
fs.writeFileSync(
|
|
359
|
+
path.join(dest, "CLAUDE.md"),
|
|
360
|
+
"<!-- grunt:begin -->\n<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n<!-- grunt:end -->\n",
|
|
361
|
+
);
|
|
362
|
+
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
363
|
+
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
364
|
+
expect(out).toBe("<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\n");
|
|
365
|
+
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
366
|
+
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("dest stacked keeps user prefix/suffix outside the pair", () => {
|
|
370
|
+
const pkgRoot = stubPkgRoot();
|
|
371
|
+
const dest = tmp("md-stacked-user-");
|
|
372
|
+
fs.writeFileSync(
|
|
373
|
+
path.join(dest, "CLAUDE.md"),
|
|
374
|
+
"prefix\n<!-- grunt:begin -->\n<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n<!-- grunt:end -->\nsuffix\n",
|
|
375
|
+
);
|
|
376
|
+
mergeGuardedMarkdown(dest, pkgRoot, "CLAUDE.md");
|
|
377
|
+
const out = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
378
|
+
expect(out).toBe(
|
|
379
|
+
"<!-- grunt:begin -->\nCLAUDE.md content\n<!-- grunt:end -->\nprefix\nsuffix\n",
|
|
380
|
+
);
|
|
381
|
+
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
382
|
+
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
383
|
+
});
|
|
384
|
+
|
|
293
385
|
it("does not fuzzy-dedupe unmarked protocol-looking user text", () => {
|
|
294
386
|
const pkgRoot = stubPkgRoot();
|
|
295
387
|
const dest = tmp("md-proto-");
|
|
@@ -363,6 +455,23 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
363
455
|
);
|
|
364
456
|
});
|
|
365
457
|
|
|
458
|
+
it("remerge after wrapped-src merge+generate does not reattach orphan end", () => {
|
|
459
|
+
const pkgRoot = stubPkgRoot();
|
|
460
|
+
fs.writeFileSync(
|
|
461
|
+
path.join(pkgRoot, "AGENTS.md"),
|
|
462
|
+
"<!-- grunt:begin -->\nAGENTS.md content\n<!-- grunt:end -->\n",
|
|
463
|
+
);
|
|
464
|
+
const dest = tmp("snap-wrap-src-");
|
|
465
|
+
mergeGuardedMarkdown(dest, pkgRoot, "AGENTS.md");
|
|
466
|
+
const snap = snapshotGuardedRoots(dest);
|
|
467
|
+
fs.writeFileSync(path.join(dest, "AGENTS.md"), "generated agents\n");
|
|
468
|
+
remergeGuardedRoots(dest, snap);
|
|
469
|
+
const out = fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8");
|
|
470
|
+
expect(out).toBe("<!-- grunt:begin -->\ngenerated agents\n<!-- grunt:end -->\n");
|
|
471
|
+
expect(out.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
472
|
+
expect(out.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
473
|
+
});
|
|
474
|
+
|
|
366
475
|
it("withGuardedCheckInteriors is restored even if check throws", () => {
|
|
367
476
|
const dest = tmp("check-int-");
|
|
368
477
|
const live =
|
|
@@ -378,6 +487,22 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
378
487
|
});
|
|
379
488
|
|
|
380
489
|
it("compose/extract/drift/write helpers cover remaining branches", () => {
|
|
490
|
+
expect(
|
|
491
|
+
extractUserMarkdown(
|
|
492
|
+
"<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n<!-- grunt:end -->\n",
|
|
493
|
+
),
|
|
494
|
+
).toBe("");
|
|
495
|
+
expect(
|
|
496
|
+
extractUserMarkdown(
|
|
497
|
+
"<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\nkeep\n<!-- grunt:end -->\n",
|
|
498
|
+
),
|
|
499
|
+
).toBe("keep\n");
|
|
500
|
+
expect(
|
|
501
|
+
composeGuardedMarkdown(
|
|
502
|
+
"<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n",
|
|
503
|
+
"<!-- grunt:end -->\n",
|
|
504
|
+
),
|
|
505
|
+
).toBe("<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n");
|
|
381
506
|
expect(composeGuardedMarkdown("body", null)).toBe(
|
|
382
507
|
"<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n",
|
|
383
508
|
);
|
|
@@ -402,7 +527,9 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
402
527
|
expect(mergeGuardedContent("keep\n", "g\r\n")).toBe(
|
|
403
528
|
"<!-- grunt:begin -->\ng\n<!-- grunt:end -->\nkeep\n",
|
|
404
529
|
);
|
|
405
|
-
expect(mergeGuardedContent("<!-- grunt:begin --> only", "g")).
|
|
530
|
+
expect(mergeGuardedContent("<!-- grunt:begin --> only", "g")).toBe(
|
|
531
|
+
"<!-- grunt:begin -->\ng\n<!-- grunt:end -->\n",
|
|
532
|
+
);
|
|
406
533
|
const dest = tmp("md-helpers-");
|
|
407
534
|
const missing = path.join(dest, "nope.md");
|
|
408
535
|
expect(guardedMarkdownDrift(missing, "g")).toBe(true);
|
|
@@ -423,6 +550,73 @@ describe("snapshot/remerge guarded roots", () => {
|
|
|
423
550
|
expect(writeMergedGuardedFile(huge, "g")).toBe("aborted-unsafe");
|
|
424
551
|
});
|
|
425
552
|
|
|
553
|
+
it("unclosed begin unwraps interior; inline orphan end is stripped", () => {
|
|
554
|
+
expect(extractGruntBody("<!-- grunt:begin -->\nhello")).toBe("hello");
|
|
555
|
+
const composed = composeGuardedMarkdown("<!-- grunt:begin -->\nhello", "");
|
|
556
|
+
expect(composed).toBe("<!-- grunt:begin -->\nhello\n<!-- grunt:end -->\n");
|
|
557
|
+
expect(composed.split(SENTINEL_BEGIN)).toHaveLength(2);
|
|
558
|
+
expect(composed.split(SENTINEL_END)).toHaveLength(2);
|
|
559
|
+
expect(composed).not.toContain(`${SENTINEL_BEGIN}\n${SENTINEL_BEGIN}`);
|
|
560
|
+
expect(mergeGuardedContent("note\n<!-- grunt:begin -->\nstale", "new")).toBe(
|
|
561
|
+
"<!-- grunt:begin -->\nnew\n<!-- grunt:end -->\nnote\n",
|
|
562
|
+
);
|
|
563
|
+
expect(extractUserMarkdown("keep <!-- grunt:end --> me")).not.toContain(SENTINEL_END);
|
|
564
|
+
expect(extractUserMarkdown("keep <!-- grunt:end --> me")).toBe("keep me");
|
|
565
|
+
expect(extractUserMarkdown("keep\n<!-- grunt:end -->\nme")).toBe("keep\nme");
|
|
566
|
+
expect(extractUserMarkdown("keep\n <!-- grunt:end --> \nme")).toBe("keep\nme");
|
|
567
|
+
expect(extractGruntBody("<!-- grunt:begin -->\r\nhello")).toBe("hello");
|
|
568
|
+
expect(extractGruntBody("<!-- grunt:begin -->")).toBe("");
|
|
569
|
+
const emptyWrap = composeGuardedMarkdown("<!-- grunt:begin -->", "");
|
|
570
|
+
expect(emptyWrap.split(SENTINEL_BEGIN)).toHaveLength(2);
|
|
571
|
+
expect(emptyWrap).toBe("<!-- grunt:begin -->\n\n<!-- grunt:end -->\n");
|
|
572
|
+
const mixed =
|
|
573
|
+
"<!-- grunt:begin -->\nok\n<!-- grunt:end -->\nuser bit\n<!-- grunt:begin -->\nstale";
|
|
574
|
+
expect(extractGruntBody(mixed)).toBe("ok");
|
|
575
|
+
expect(extractUserMarkdown(mixed)).toBe("user bit\n");
|
|
576
|
+
expect(
|
|
577
|
+
composeGuardedMarkdown("<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n", ""),
|
|
578
|
+
).toBe("<!-- grunt:begin -->\nbody\n<!-- grunt:end -->\n");
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("extract/compose peel stacked dest and trailing orphan end", () => {
|
|
582
|
+
const stacked =
|
|
583
|
+
"<!-- grunt:begin -->\n<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n<!-- grunt:end -->\n";
|
|
584
|
+
expect(extractGruntBody(stacked)).toBe("BODY");
|
|
585
|
+
const stackedOut = composeGuardedMarkdown(stacked, "");
|
|
586
|
+
expect(stackedOut).toBe("<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n");
|
|
587
|
+
expect(stackedOut.split(SENTINEL_BEGIN)).toHaveLength(2);
|
|
588
|
+
expect(stackedOut.split(SENTINEL_END)).toHaveLength(2);
|
|
589
|
+
expect(composeGuardedMarkdown("BODY\n<!-- grunt:end -->\n", "")).toBe(
|
|
590
|
+
"<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\n",
|
|
591
|
+
);
|
|
592
|
+
expect(extractGruntBody("BODY\n\n<!-- grunt:end -->")).toBe("BODY");
|
|
593
|
+
expect(mergeGuardedContent(stacked, "SRC")).toBe(
|
|
594
|
+
"<!-- grunt:begin -->\nSRC\n<!-- grunt:end -->\n",
|
|
595
|
+
);
|
|
596
|
+
expect(mergeGuardedContent("keep\n<!-- grunt:end -->\n", "SRC")).toBe(
|
|
597
|
+
"<!-- grunt:begin -->\nSRC\n<!-- grunt:end -->\nkeep\n",
|
|
598
|
+
);
|
|
599
|
+
expect(
|
|
600
|
+
extractGruntBody("<!-- grunt:begin --> \nBODY\n<!-- grunt:end --> \n"),
|
|
601
|
+
).toBe("BODY");
|
|
602
|
+
expect(
|
|
603
|
+
extractGruntBody(" <!-- grunt:begin -->\nBODY\n <!-- grunt:end -->\n"),
|
|
604
|
+
).toBe("BODY");
|
|
605
|
+
expect(extractGruntBody("<!-- grunt:begin -->\n<!-- grunt:end -->")).toBe("");
|
|
606
|
+
expect(
|
|
607
|
+
extractGruntBody(
|
|
608
|
+
"<!-- grunt:begin -->\nsee <!-- grunt:end --> here\n<!-- grunt:end -->\n",
|
|
609
|
+
),
|
|
610
|
+
).toBe("see <!-- grunt:end --> here");
|
|
611
|
+
const midLine =
|
|
612
|
+
"<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\nstill user\n<!-- grunt:end -->\n";
|
|
613
|
+
expect(extractGruntBody(midLine)).toBe("BODY");
|
|
614
|
+
expect(extractUserMarkdown(midLine)).toBe("still user\n");
|
|
615
|
+
expect(composeGuardedMarkdown(extractGruntBody(midLine) ?? "", extractUserMarkdown(midLine))).toBe(
|
|
616
|
+
"<!-- grunt:begin -->\nBODY\n<!-- grunt:end -->\nstill user\n",
|
|
617
|
+
);
|
|
618
|
+
});
|
|
619
|
+
|
|
426
620
|
it("remerge restores unsafe, missing, binary current, and created files", () => {
|
|
427
621
|
const dest = tmp("remerge-edges-");
|
|
428
622
|
const agents = path.join(dest, "AGENTS.md");
|
|
@@ -786,6 +980,13 @@ describe("mergeClaudeSettings", () => {
|
|
|
786
980
|
});
|
|
787
981
|
});
|
|
788
982
|
|
|
983
|
+
describe("toGruntScriptName", () => {
|
|
984
|
+
it("prefixes unprefixed keys and does not double-prefix", () => {
|
|
985
|
+
expect(toGruntScriptName("rulesync:generate")).toBe("grunt:rulesync:generate");
|
|
986
|
+
expect(toGruntScriptName("grunt:doctor")).toBe("grunt:doctor");
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
|
|
789
990
|
describe("mergePackageJson", () => {
|
|
790
991
|
it("creates {} dest and merges scripts skipping test, injects deps, sorts keys", () => {
|
|
791
992
|
const pkgRoot = stubPkgRoot();
|
|
@@ -793,11 +994,25 @@ describe("mergePackageJson", () => {
|
|
|
793
994
|
mergePackageJson(dest, pkgRoot);
|
|
794
995
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
795
996
|
expect(out.scripts).toEqual({
|
|
796
|
-
alpha: "a",
|
|
797
|
-
"
|
|
798
|
-
|
|
997
|
+
"grunt:alpha": "a",
|
|
998
|
+
"grunt:antigravity": LAUNCH_SCRIPTS.antigravity,
|
|
999
|
+
"grunt:claude": LAUNCH_SCRIPTS.claude,
|
|
1000
|
+
"grunt:codex": LAUNCH_SCRIPTS.codex,
|
|
1001
|
+
"grunt:gemini": LAUNCH_SCRIPTS.gemini,
|
|
1002
|
+
"grunt:grok": LAUNCH_SCRIPTS.grok,
|
|
1003
|
+
"grunt:rulesync:generate": "gen",
|
|
1004
|
+
"grunt:zeta": "z",
|
|
799
1005
|
});
|
|
800
|
-
expect(Object.keys(out.scripts)).toEqual([
|
|
1006
|
+
expect(Object.keys(out.scripts)).toEqual([
|
|
1007
|
+
"grunt:alpha",
|
|
1008
|
+
"grunt:antigravity",
|
|
1009
|
+
"grunt:claude",
|
|
1010
|
+
"grunt:codex",
|
|
1011
|
+
"grunt:gemini",
|
|
1012
|
+
"grunt:grok",
|
|
1013
|
+
"grunt:rulesync:generate",
|
|
1014
|
+
"grunt:zeta",
|
|
1015
|
+
]);
|
|
801
1016
|
expect(out.devDependencies).toEqual({
|
|
802
1017
|
rulesync: "latest",
|
|
803
1018
|
"smol-toml": "^1.8.0",
|
|
@@ -817,14 +1032,21 @@ describe("mergePackageJson", () => {
|
|
|
817
1032
|
devDependencies: { lodash: "4" },
|
|
818
1033
|
}),
|
|
819
1034
|
);
|
|
1035
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
820
1036
|
mergePackageJson(dest, pkgRoot);
|
|
821
1037
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
822
1038
|
expect(out.scripts.test).toBe("jest");
|
|
823
1039
|
expect(out.scripts.alpha).toBe("old");
|
|
1040
|
+
expect(out.scripts["grunt:alpha"]).toBe("a");
|
|
1041
|
+
expect(out.scripts["grunt:zeta"]).toBe("z");
|
|
824
1042
|
expect(out.scripts.foo).toBe("bar");
|
|
825
1043
|
expect(out.devDependencies.lodash).toBe("4");
|
|
826
1044
|
expect(out.devDependencies["smol-toml"]).toBe("^1.8.0");
|
|
827
1045
|
expect(out.devDependencies.rulesync).toBe("latest");
|
|
1046
|
+
expect(warnSpy.mock.calls.some((c) => String(c[0]).includes("alpha") && String(c[0]).includes("untouched"))).toBe(
|
|
1047
|
+
true,
|
|
1048
|
+
);
|
|
1049
|
+
warnSpy.mockRestore();
|
|
828
1050
|
});
|
|
829
1051
|
|
|
830
1052
|
it("preserves dest suffix after an upgraded grunt script prefix", () => {
|
|
@@ -844,9 +1066,10 @@ describe("mergePackageJson", () => {
|
|
|
844
1066
|
);
|
|
845
1067
|
mergePackageJson(dest, pkgRoot);
|
|
846
1068
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
847
|
-
expect(out.scripts["rulesync:generate"]).toBe(
|
|
1069
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe(
|
|
848
1070
|
"rulesync generate -t bar && node scripts/codex-sync.mjs",
|
|
849
1071
|
);
|
|
1072
|
+
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
850
1073
|
});
|
|
851
1074
|
|
|
852
1075
|
it("preserves suffix after a multi-command grunt script upgrade", () => {
|
|
@@ -869,7 +1092,7 @@ describe("mergePackageJson", () => {
|
|
|
869
1092
|
);
|
|
870
1093
|
mergePackageJson(dest, pkgRoot);
|
|
871
1094
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
872
|
-
expect(out.scripts["rulesync:generate"]).toBe(
|
|
1095
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe(
|
|
873
1096
|
"rulesync generate -t bar && node scripts/emit.mjs && node scripts/codex-sync.mjs",
|
|
874
1097
|
);
|
|
875
1098
|
});
|
|
@@ -889,6 +1112,7 @@ describe("mergePackageJson", () => {
|
|
|
889
1112
|
mergePackageJson(dest, pkgRoot);
|
|
890
1113
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
891
1114
|
expect(out.scripts.alpha).toBe("echo custom && extra");
|
|
1115
|
+
expect(out.scripts["grunt:alpha"]).toBe("a");
|
|
892
1116
|
expect(warnSpy).toHaveBeenCalled();
|
|
893
1117
|
warnSpy.mockRestore();
|
|
894
1118
|
});
|
|
@@ -910,6 +1134,7 @@ describe("mergePackageJson", () => {
|
|
|
910
1134
|
mergePackageJson(dest, pkgRoot);
|
|
911
1135
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
912
1136
|
expect(out.scripts["rulesync:generate"]).toBe("echo custom;echo extra");
|
|
1137
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe("rulesync generate -t bar");
|
|
913
1138
|
expect(warnSpy).toHaveBeenCalled();
|
|
914
1139
|
warnSpy.mockRestore();
|
|
915
1140
|
});
|
|
@@ -927,7 +1152,8 @@ describe("mergePackageJson", () => {
|
|
|
927
1152
|
);
|
|
928
1153
|
mergePackageJson(dest, pkgRoot);
|
|
929
1154
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
930
|
-
expect(out.scripts["rulesync:generate"]).toBe("rulesync generate -t bar");
|
|
1155
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe("rulesync generate -t bar");
|
|
1156
|
+
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
931
1157
|
});
|
|
932
1158
|
|
|
933
1159
|
it("keeps dest when it already starts with the new src value plus suffix", () => {
|
|
@@ -941,7 +1167,8 @@ describe("mergePackageJson", () => {
|
|
|
941
1167
|
fs.writeFileSync(path.join(dest, "package.json"), JSON.stringify({ scripts: { "rulesync:generate": cur } }));
|
|
942
1168
|
mergePackageJson(dest, pkgRoot);
|
|
943
1169
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
944
|
-
expect(out.scripts["rulesync:generate"]).toBe(cur);
|
|
1170
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe(cur);
|
|
1171
|
+
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
945
1172
|
});
|
|
946
1173
|
|
|
947
1174
|
it("keeps unrelated custom script values and dest-only keys, warns on skip", () => {
|
|
@@ -957,9 +1184,11 @@ describe("mergePackageJson", () => {
|
|
|
957
1184
|
mergePackageJson(dest, pkgRoot);
|
|
958
1185
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
959
1186
|
expect(out.scripts.alpha).toBe("echo custom");
|
|
1187
|
+
expect(out.scripts["grunt:alpha"]).toBe("a");
|
|
960
1188
|
expect(out.scripts.foo).toBe("bar");
|
|
961
1189
|
expect(out.scripts.test).toBe("jest");
|
|
962
|
-
expect(out.scripts
|
|
1190
|
+
expect(out.scripts["grunt:zeta"]).toBe("z");
|
|
1191
|
+
expect(out.scripts.zeta).toBeUndefined();
|
|
963
1192
|
expect(warnSpy.mock.calls.some((c) => String(c[0]).includes("alpha") && String(c[0]).includes("untouched"))).toBe(
|
|
964
1193
|
true,
|
|
965
1194
|
);
|
|
@@ -980,7 +1209,8 @@ describe("mergePackageJson", () => {
|
|
|
980
1209
|
const dest = tmp("pj-raw-src-");
|
|
981
1210
|
mergePackageJson(dest, pkgRoot);
|
|
982
1211
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
983
|
-
expect(out.scripts["rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
|
|
1212
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
|
|
1213
|
+
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
984
1214
|
expect(out.scripts["rulesync:generate:raw"]).toBeUndefined();
|
|
985
1215
|
expect(out.scripts["rulesync:check:raw"]).toBeUndefined();
|
|
986
1216
|
expect(out.scripts["rulesync:watch:raw"]).toBeUndefined();
|
|
@@ -1001,7 +1231,7 @@ describe("mergePackageJson", () => {
|
|
|
1001
1231
|
);
|
|
1002
1232
|
mergePackageJson(dest, pkgRoot);
|
|
1003
1233
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1004
|
-
expect(out.scripts["rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
|
|
1234
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
|
|
1005
1235
|
expect(out.scripts["rulesync:generate:raw"]).toBe("echo consumer-raw");
|
|
1006
1236
|
});
|
|
1007
1237
|
|
|
@@ -1020,7 +1250,9 @@ describe("mergePackageJson", () => {
|
|
|
1020
1250
|
);
|
|
1021
1251
|
mergePackageJson(dest, pkgRoot);
|
|
1022
1252
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1023
|
-
expect(out.scripts["rulesync:generate"]).toBe("rulesync generate -t bar");
|
|
1253
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe("rulesync generate -t bar");
|
|
1254
|
+
expect(out.scripts["grunt:sync:globals:apply"]).toBe("node scripts/sync-global-settings.mjs --apply");
|
|
1255
|
+
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
1024
1256
|
});
|
|
1025
1257
|
|
|
1026
1258
|
it("early return when name is @lovrozagar/grunt", () => {
|
|
@@ -1040,9 +1272,257 @@ describe("mergePackageJson", () => {
|
|
|
1040
1272
|
const dest = tmp("pj-noscripts-");
|
|
1041
1273
|
mergePackageJson(dest, pkgRoot);
|
|
1042
1274
|
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1043
|
-
expect(out.scripts).toEqual({
|
|
1275
|
+
expect(out.scripts).toEqual({
|
|
1276
|
+
"grunt:antigravity": LAUNCH_SCRIPTS.antigravity,
|
|
1277
|
+
"grunt:claude": LAUNCH_SCRIPTS.claude,
|
|
1278
|
+
"grunt:codex": LAUNCH_SCRIPTS.codex,
|
|
1279
|
+
"grunt:gemini": LAUNCH_SCRIPTS.gemini,
|
|
1280
|
+
"grunt:grok": LAUNCH_SCRIPTS.grok,
|
|
1281
|
+
});
|
|
1044
1282
|
expect(out.devDependencies).toEqual({ rulesync: "2", "smol-toml": "1" });
|
|
1045
1283
|
});
|
|
1284
|
+
|
|
1285
|
+
it("golden autorun-apps: suffix keep, doctor move, check npm run rewrite, old keys gone", () => {
|
|
1286
|
+
const pkgRoot = stubPkgRoot({
|
|
1287
|
+
name: "fixture-pkg",
|
|
1288
|
+
scripts: {
|
|
1289
|
+
"rulesync:generate": "node ./scripts/guarded-roots.mjs generate",
|
|
1290
|
+
"rulesync:watch": "node ./scripts/guarded-roots.mjs watch",
|
|
1291
|
+
"rulesync:check": "node ./scripts/guarded-roots.mjs check",
|
|
1292
|
+
"hooks:union": "node scripts/hooks-union.mjs",
|
|
1293
|
+
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
1294
|
+
doctor: "node ./scripts/doctor.mjs",
|
|
1295
|
+
"rulesync:doctor": "rulesync doctor",
|
|
1296
|
+
"sync:globals": "node scripts/sync-global-settings.mjs",
|
|
1297
|
+
"sync:globals:check": "node scripts/check-globals.mjs",
|
|
1298
|
+
"sync:globals:apply": "node scripts/sync-global-settings.mjs --apply",
|
|
1299
|
+
"purge:global-mcps": "node scripts/purge-global-mcps.mjs",
|
|
1300
|
+
"purge:global-mcps:apply": "node scripts/purge-global-mcps.mjs --apply",
|
|
1301
|
+
test: "vitest run --coverage",
|
|
1302
|
+
},
|
|
1303
|
+
devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
|
|
1304
|
+
});
|
|
1305
|
+
const dest = tmp("pj-autorun-");
|
|
1306
|
+
fs.writeFileSync(
|
|
1307
|
+
path.join(dest, "package.json"),
|
|
1308
|
+
JSON.stringify({
|
|
1309
|
+
scripts: {
|
|
1310
|
+
"apps:doctor": "node scripts/audit.mjs",
|
|
1311
|
+
"apps:mental-model:viewer:build":
|
|
1312
|
+
"npm --prefix scripts/excalidraw/viewer install && npm --prefix scripts/excalidraw/viewer run build",
|
|
1313
|
+
check:
|
|
1314
|
+
"node scripts/portability-check.mjs && npm run rulesync:check && npm run lint && npm test && node scripts/media-fixtures.mjs --check",
|
|
1315
|
+
"codex:sync": "node scripts/codex-sync.mjs",
|
|
1316
|
+
doctor: "node ./scripts/doctor.mjs",
|
|
1317
|
+
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
1318
|
+
"hooks:union": "node scripts/hooks-union.mjs",
|
|
1319
|
+
lint: "eslint scripts tests eslint.config.mjs",
|
|
1320
|
+
"purge:global-mcps": "node scripts/purge-global-mcps.mjs",
|
|
1321
|
+
"purge:global-mcps:apply": "node scripts/purge-global-mcps.mjs --apply",
|
|
1322
|
+
"rulesync:check": "node ./scripts/guarded-roots.mjs check && node scripts/codex-sync.mjs --check",
|
|
1323
|
+
"rulesync:doctor": "rulesync doctor",
|
|
1324
|
+
"rulesync:generate": "node ./scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
1325
|
+
"rulesync:watch": "node ./scripts/guarded-roots.mjs watch",
|
|
1326
|
+
"sync:globals": "node scripts/sync-global-settings.mjs",
|
|
1327
|
+
"sync:globals:apply": "node scripts/sync-global-settings.mjs --apply",
|
|
1328
|
+
"sync:globals:check": "node scripts/check-globals.mjs",
|
|
1329
|
+
test: "node scripts/run-tests.mjs",
|
|
1330
|
+
},
|
|
1331
|
+
}),
|
|
1332
|
+
);
|
|
1333
|
+
mergePackageJson(dest, pkgRoot);
|
|
1334
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1335
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe(
|
|
1336
|
+
"node ./scripts/guarded-roots.mjs generate && node scripts/codex-sync.mjs",
|
|
1337
|
+
);
|
|
1338
|
+
expect(out.scripts["grunt:rulesync:check"]).toBe(
|
|
1339
|
+
"node ./scripts/guarded-roots.mjs check && node scripts/codex-sync.mjs --check",
|
|
1340
|
+
);
|
|
1341
|
+
expect(out.scripts["grunt:rulesync:watch"]).toBe("node ./scripts/guarded-roots.mjs watch");
|
|
1342
|
+
expect(out.scripts["grunt:rulesync:doctor"]).toBe("rulesync doctor");
|
|
1343
|
+
expect(out.scripts["grunt:hooks:union"]).toBe("node scripts/hooks-union.mjs");
|
|
1344
|
+
expect(out.scripts["grunt:hooks:check"]).toBe("node scripts/hooks-union.mjs --check");
|
|
1345
|
+
expect(out.scripts["grunt:sync:globals"]).toBe("node scripts/sync-global-settings.mjs");
|
|
1346
|
+
expect(out.scripts["grunt:sync:globals:apply"]).toBe("node scripts/sync-global-settings.mjs --apply");
|
|
1347
|
+
expect(out.scripts["grunt:sync:globals:check"]).toBe("node scripts/check-globals.mjs");
|
|
1348
|
+
expect(out.scripts["grunt:purge:global-mcps"]).toBe("node scripts/purge-global-mcps.mjs");
|
|
1349
|
+
expect(out.scripts["grunt:purge:global-mcps:apply"]).toBe("node scripts/purge-global-mcps.mjs --apply");
|
|
1350
|
+
expect(out.scripts["grunt:doctor"]).toBe("node ./scripts/doctor.mjs");
|
|
1351
|
+
expect(out.scripts.doctor).toBeUndefined();
|
|
1352
|
+
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
1353
|
+
expect(out.scripts["rulesync:check"]).toBeUndefined();
|
|
1354
|
+
expect(out.scripts["rulesync:watch"]).toBeUndefined();
|
|
1355
|
+
expect(out.scripts["hooks:union"]).toBeUndefined();
|
|
1356
|
+
expect(out.scripts["sync:globals"]).toBeUndefined();
|
|
1357
|
+
expect(out.scripts.check).toBe(
|
|
1358
|
+
"node scripts/portability-check.mjs && npm run grunt:rulesync:check && npm run lint && npm test && node scripts/media-fixtures.mjs --check",
|
|
1359
|
+
);
|
|
1360
|
+
expect(out.scripts["apps:doctor"]).toBe("node scripts/audit.mjs");
|
|
1361
|
+
expect(out.scripts["codex:sync"]).toBe("node scripts/codex-sync.mjs");
|
|
1362
|
+
expect(out.scripts.lint).toBe("eslint scripts tests eslint.config.mjs");
|
|
1363
|
+
expect(out.scripts.test).toBe("node scripts/run-tests.mjs");
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
it("leaves custom doctor and writes grunt:doctor from SoT", () => {
|
|
1367
|
+
const pkgRoot = stubPkgRoot({
|
|
1368
|
+
name: "fixture-pkg",
|
|
1369
|
+
scripts: { doctor: "node ./scripts/doctor.mjs" },
|
|
1370
|
+
devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
|
|
1371
|
+
});
|
|
1372
|
+
const dest = tmp("pj-custom-doctor-");
|
|
1373
|
+
fs.writeFileSync(
|
|
1374
|
+
path.join(dest, "package.json"),
|
|
1375
|
+
JSON.stringify({ scripts: { doctor: "node scripts/audit.mjs" } }),
|
|
1376
|
+
);
|
|
1377
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1378
|
+
mergePackageJson(dest, pkgRoot);
|
|
1379
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1380
|
+
expect(out.scripts.doctor).toBe("node scripts/audit.mjs");
|
|
1381
|
+
expect(out.scripts["grunt:doctor"]).toBe("node ./scripts/doctor.mjs");
|
|
1382
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
1383
|
+
warnSpy.mockRestore();
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
it("merges destKey when both prefixed and owned legacy exist, deletes legacy", () => {
|
|
1387
|
+
const pkgRoot = stubPkgRoot({
|
|
1388
|
+
name: "fixture-pkg",
|
|
1389
|
+
scripts: { "rulesync:generate": "rulesync generate -t bar" },
|
|
1390
|
+
devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
|
|
1391
|
+
});
|
|
1392
|
+
const dest = tmp("pj-both-");
|
|
1393
|
+
fs.writeFileSync(
|
|
1394
|
+
path.join(dest, "package.json"),
|
|
1395
|
+
JSON.stringify({
|
|
1396
|
+
scripts: {
|
|
1397
|
+
"rulesync:generate": "rulesync generate -t foo && node scripts/codex-sync.mjs",
|
|
1398
|
+
"grunt:rulesync:generate": "rulesync generate -t old && node scripts/other.mjs",
|
|
1399
|
+
},
|
|
1400
|
+
}),
|
|
1401
|
+
);
|
|
1402
|
+
mergePackageJson(dest, pkgRoot);
|
|
1403
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1404
|
+
expect(out.scripts["grunt:rulesync:generate"]).toBe(
|
|
1405
|
+
"rulesync generate -t bar && node scripts/other.mjs",
|
|
1406
|
+
);
|
|
1407
|
+
expect(out.scripts["rulesync:generate"]).toBeUndefined();
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
it("does not rewrite npm run rulesync:check:raw when only rulesync:check migrated", () => {
|
|
1411
|
+
const pkgRoot = stubPkgRoot({
|
|
1412
|
+
name: "fixture-pkg",
|
|
1413
|
+
scripts: { "rulesync:check": "node ./scripts/guarded-roots.mjs check" },
|
|
1414
|
+
devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
|
|
1415
|
+
});
|
|
1416
|
+
const dest = tmp("pj-raw-rewrite-");
|
|
1417
|
+
fs.writeFileSync(
|
|
1418
|
+
path.join(dest, "package.json"),
|
|
1419
|
+
JSON.stringify({
|
|
1420
|
+
scripts: {
|
|
1421
|
+
"rulesync:check": "node ./scripts/guarded-roots.mjs check",
|
|
1422
|
+
"rulesync:check:raw": "echo consumer-raw",
|
|
1423
|
+
check: "npm run rulesync:check && npm run rulesync:check:raw",
|
|
1424
|
+
},
|
|
1425
|
+
}),
|
|
1426
|
+
);
|
|
1427
|
+
mergePackageJson(dest, pkgRoot);
|
|
1428
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1429
|
+
expect(out.scripts["grunt:rulesync:check"]).toBe("node ./scripts/guarded-roots.mjs check");
|
|
1430
|
+
expect(out.scripts["rulesync:check"]).toBeUndefined();
|
|
1431
|
+
expect(out.scripts["rulesync:check:raw"]).toBe("echo consumer-raw");
|
|
1432
|
+
expect(out.scripts.check).toBe("npm run grunt:rulesync:check && npm run rulesync:check:raw");
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
it("skips non-string dest scripts when rewriting npm run refs", () => {
|
|
1436
|
+
const pkgRoot = stubPkgRoot({
|
|
1437
|
+
name: "fixture-pkg",
|
|
1438
|
+
scripts: { "rulesync:check": "node ./scripts/guarded-roots.mjs check" },
|
|
1439
|
+
devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
|
|
1440
|
+
});
|
|
1441
|
+
const dest = tmp("pj-nonstr-");
|
|
1442
|
+
fs.writeFileSync(
|
|
1443
|
+
path.join(dest, "package.json"),
|
|
1444
|
+
JSON.stringify({
|
|
1445
|
+
scripts: {
|
|
1446
|
+
extra: 1,
|
|
1447
|
+
check: "npm run rulesync:check",
|
|
1448
|
+
},
|
|
1449
|
+
}),
|
|
1450
|
+
);
|
|
1451
|
+
mergePackageJson(dest, pkgRoot);
|
|
1452
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1453
|
+
expect(out.scripts.extra).toBe(1);
|
|
1454
|
+
expect(out.scripts.check).toBe("npm run grunt:rulesync:check");
|
|
1455
|
+
expect(out.scripts["grunt:rulesync:check"]).toBe("node ./scripts/guarded-roots.mjs check");
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
it("emits grunt:claude into a consumer package.json", () => {
|
|
1459
|
+
const pkgRoot = stubPkgRoot();
|
|
1460
|
+
const dest = tmp("pj-launch-emit-");
|
|
1461
|
+
mergePackageJson(dest, pkgRoot);
|
|
1462
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1463
|
+
expect(out.scripts["grunt:claude"]).toBe(LAUNCH_SCRIPTS.claude);
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
it("leaves a consumer's bare claude launcher untouched, no warn emitted", () => {
|
|
1467
|
+
const pkgRoot = stubPkgRoot();
|
|
1468
|
+
const dest = tmp("pj-consumer-claude-");
|
|
1469
|
+
fs.writeFileSync(
|
|
1470
|
+
path.join(dest, "package.json"),
|
|
1471
|
+
JSON.stringify({ scripts: { claude: "npm run build && claude" } }),
|
|
1472
|
+
);
|
|
1473
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1474
|
+
mergePackageJson(dest, pkgRoot);
|
|
1475
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1476
|
+
expect(out.scripts.claude).toBe("npm run build && claude");
|
|
1477
|
+
expect(out.scripts["grunt:claude"]).toBe(LAUNCH_SCRIPTS.claude);
|
|
1478
|
+
expect(warn).not.toHaveBeenCalled();
|
|
1479
|
+
warn.mockRestore();
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
it("does not rewrite npm run claude inside a consumer script", () => {
|
|
1483
|
+
const pkgRoot = stubPkgRoot();
|
|
1484
|
+
const dest = tmp("pj-npmrun-claude-");
|
|
1485
|
+
fs.writeFileSync(
|
|
1486
|
+
path.join(dest, "package.json"),
|
|
1487
|
+
JSON.stringify({
|
|
1488
|
+
scripts: { claude: "npm run build && claude", start: "npm run claude" },
|
|
1489
|
+
}),
|
|
1490
|
+
);
|
|
1491
|
+
mergePackageJson(dest, pkgRoot);
|
|
1492
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1493
|
+
expect(out.scripts.start).toBe("npm run claude");
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
it("purges a stale grunt:yolo:claude with the exact old value; an edited variant survives", () => {
|
|
1497
|
+
const pkgRoot = stubPkgRoot();
|
|
1498
|
+
const dest = tmp("pj-purge-stale-");
|
|
1499
|
+
fs.writeFileSync(
|
|
1500
|
+
path.join(dest, "package.json"),
|
|
1501
|
+
JSON.stringify({
|
|
1502
|
+
scripts: {
|
|
1503
|
+
"grunt:yolo:claude": LAUNCH_SCRIPTS.claude,
|
|
1504
|
+
"grunt:yolo:codex": `${LAUNCH_SCRIPTS.codex} --extra`,
|
|
1505
|
+
},
|
|
1506
|
+
}),
|
|
1507
|
+
);
|
|
1508
|
+
mergePackageJson(dest, pkgRoot);
|
|
1509
|
+
const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
|
|
1510
|
+
expect(out.scripts["grunt:yolo:claude"]).toBeUndefined();
|
|
1511
|
+
expect(out.scripts["grunt:claude"]).toBe(LAUNCH_SCRIPTS.claude);
|
|
1512
|
+
expect(out.scripts["grunt:yolo:codex"]).toBe(`${LAUNCH_SCRIPTS.codex} --extra`);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
it("drift: every bare launcher key in grunt's package.json equals LAUNCH_SCRIPTS[key]", () => {
|
|
1516
|
+
const pkg = JSON.parse(
|
|
1517
|
+
fs.readFileSync(
|
|
1518
|
+
path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json"),
|
|
1519
|
+
"utf8",
|
|
1520
|
+
),
|
|
1521
|
+
);
|
|
1522
|
+
for (const [k, v] of Object.entries(LAUNCH_SCRIPTS)) {
|
|
1523
|
+
expect(pkg.scripts[k]).toBe(v);
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1046
1526
|
});
|
|
1047
1527
|
|
|
1048
1528
|
describe("init", () => {
|
|
@@ -1076,6 +1556,9 @@ describe("init", () => {
|
|
|
1076
1556
|
if (d === ".claude") continue;
|
|
1077
1557
|
expect(fs.readFileSync(path.join(dest, d, "marker"), "utf8")).toBe(d);
|
|
1078
1558
|
}
|
|
1559
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1560
|
+
expect(PRODUCT_FILES).not.toContain("grunt-config.mjs");
|
|
1561
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1079
1562
|
expect(fs.readFileSync(path.join(dest, ".claude", "marker"), "utf8")).toBe(".claude");
|
|
1080
1563
|
const settings = JSON.parse(
|
|
1081
1564
|
fs.readFileSync(path.join(dest, ".claude", "settings.json"), "utf8"),
|
|
@@ -1103,9 +1586,9 @@ describe("init", () => {
|
|
|
1103
1586
|
|
|
1104
1587
|
expect(exec.mock.calls.map((c) => [c[0], c[1]])).toEqual([
|
|
1105
1588
|
["npm", ["install"]],
|
|
1106
|
-
["npm", ["run", "rulesync:generate"]],
|
|
1107
|
-
["npm", ["run", "sync:globals:apply"]],
|
|
1108
|
-
["npm", ["run", "rulesync:check"]],
|
|
1589
|
+
["npm", ["run", "grunt:rulesync:generate"]],
|
|
1590
|
+
["npm", ["run", "grunt:sync:globals:apply"]],
|
|
1591
|
+
["npm", ["run", "grunt:rulesync:check"]],
|
|
1109
1592
|
]);
|
|
1110
1593
|
for (const call of exec.mock.calls) {
|
|
1111
1594
|
expect(call[2]).toEqual({ cwd: dest, stdio: "inherit" });
|
|
@@ -1149,18 +1632,132 @@ describe("init", () => {
|
|
|
1149
1632
|
expect(fs.readFileSync(path.join(pkgRoot, ".gitignore"), "utf8")).toBe(".tmp/\n");
|
|
1150
1633
|
});
|
|
1151
1634
|
|
|
1635
|
+
it("prunes retired 0.5 skills and agents on dest", () => {
|
|
1636
|
+
const pkgRoot = stubPkgRoot();
|
|
1637
|
+
const dest = tmp("grunt-prune-");
|
|
1638
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "solo"), { recursive: true });
|
|
1639
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "solo", "SKILL.md"), "old");
|
|
1640
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "parent"), { recursive: true });
|
|
1641
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "cascade"), { recursive: true });
|
|
1642
|
+
fs.mkdirSync(path.join(dest, ".claude", "agents"), { recursive: true });
|
|
1643
|
+
fs.writeFileSync(path.join(dest, ".claude", "agents", "implementer.md"), "old");
|
|
1644
|
+
fs.writeFileSync(path.join(dest, ".claude", "agents", "thinker.md"), "old");
|
|
1645
|
+
expect(RETIRED_SKILLS).toEqual(["parent", "solo", "cascade"]);
|
|
1646
|
+
expect(RETIRED_AGENTS).toEqual(["implementer", "thinker"]);
|
|
1647
|
+
expect(RETIRED_SCRIPTS).toEqual(["telemetry.mjs", "grunt-config.mjs"]);
|
|
1648
|
+
expect(RETIRED_PATHS).toEqual([
|
|
1649
|
+
".grok/parent.md",
|
|
1650
|
+
".grok/skills/shared",
|
|
1651
|
+
".rulesync/grunt.config.jsonc",
|
|
1652
|
+
".rulesync/grunt.config.local.jsonc",
|
|
1653
|
+
".rulesync/grunt.config.local.jsonc.example",
|
|
1654
|
+
]);
|
|
1655
|
+
expect(RESERVED_SKILLS).toEqual([
|
|
1656
|
+
"ask",
|
|
1657
|
+
"auto",
|
|
1658
|
+
"browser",
|
|
1659
|
+
"clasp",
|
|
1660
|
+
"commit",
|
|
1661
|
+
"commit-and-push",
|
|
1662
|
+
"commit-push",
|
|
1663
|
+
"commit-push-deploy",
|
|
1664
|
+
"commit-push-release",
|
|
1665
|
+
"explain",
|
|
1666
|
+
"google-workspace",
|
|
1667
|
+
"handoff",
|
|
1668
|
+
"implement-plan",
|
|
1669
|
+
"listen",
|
|
1670
|
+
"pickup",
|
|
1671
|
+
"speak",
|
|
1672
|
+
"tmp",
|
|
1673
|
+
"write-plan",
|
|
1674
|
+
]);
|
|
1675
|
+
expect(RESERVED_SKILLS).not.toEqual(expect.arrayContaining(RETIRED_SKILLS));
|
|
1676
|
+
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
1677
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "solo"))).toBe(false);
|
|
1678
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "parent"))).toBe(false);
|
|
1679
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "cascade"))).toBe(false);
|
|
1680
|
+
expect(fs.existsSync(path.join(dest, ".claude", "agents", "implementer.md"))).toBe(
|
|
1681
|
+
false,
|
|
1682
|
+
);
|
|
1683
|
+
expect(fs.existsSync(path.join(dest, ".claude", "agents", "thinker.md"))).toBe(false);
|
|
1684
|
+
pruneRetired(dest, { pkgRoot });
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
it("prunes retired scripts/paths, reserved-not-packaged skills, and grok roles; keeps consumer extras", () => {
|
|
1688
|
+
const pkgRoot = stubPkgRoot();
|
|
1689
|
+
fs.mkdirSync(path.join(pkgRoot, ".rulesync", "skills", "tmp"), { recursive: true });
|
|
1690
|
+
fs.writeFileSync(path.join(pkgRoot, ".rulesync", "skills", "tmp", "SKILL.md"), "pkg tmp");
|
|
1691
|
+
const dest = tmp("grunt-prune-extra-");
|
|
1692
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "tmp"), { recursive: true });
|
|
1693
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "tmp", "SKILL.md"), "old tmp");
|
|
1694
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "browser"), { recursive: true });
|
|
1695
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "browser", "SKILL.md"), "stale reserved");
|
|
1696
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "acme"), { recursive: true });
|
|
1697
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "acme", "SKILL.md"), "consumer");
|
|
1698
|
+
fs.mkdirSync(path.join(dest, ".grok", "roles"), { recursive: true });
|
|
1699
|
+
fs.writeFileSync(path.join(dest, ".grok", "roles", "implementer.toml"), "old");
|
|
1700
|
+
fs.writeFileSync(path.join(dest, ".grok", "roles", "thinker.toml"), "old");
|
|
1701
|
+
fs.writeFileSync(path.join(dest, ".grok", "parent.md"), "pointer");
|
|
1702
|
+
fs.mkdirSync(path.join(dest, ".grok", "skills", "shared"), { recursive: true });
|
|
1703
|
+
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true });
|
|
1704
|
+
fs.writeFileSync(path.join(dest, "scripts", "telemetry.mjs"), "old telemetry");
|
|
1705
|
+
fs.writeFileSync(path.join(dest, "scripts", "grunt-config.mjs"), "old config");
|
|
1706
|
+
fs.writeFileSync(path.join(dest, "scripts", "mine.mjs"), "keep");
|
|
1707
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "grunt.config.jsonc"), '{"version":1}\n');
|
|
1708
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc"), '{"sessionGate":"ask"}\n');
|
|
1709
|
+
fs.writeFileSync(
|
|
1710
|
+
path.join(dest, ".rulesync", "grunt.config.local.jsonc.example"),
|
|
1711
|
+
'{"sessionGate":"ask"}\n',
|
|
1712
|
+
);
|
|
1713
|
+
fs.mkdirSync(path.join(dest, ".gemini", "agents", "implementer"), { recursive: true });
|
|
1714
|
+
fs.writeFileSync(path.join(dest, ".gemini", "agents", "implementer", "agent.md"), "old");
|
|
1715
|
+
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
1716
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "skills", "tmp", "SKILL.md"), "utf8")).toBe(
|
|
1717
|
+
"pkg tmp",
|
|
1718
|
+
);
|
|
1719
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "browser"))).toBe(false);
|
|
1720
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "skills", "acme", "SKILL.md"), "utf8")).toBe(
|
|
1721
|
+
"consumer",
|
|
1722
|
+
);
|
|
1723
|
+
expect(fs.existsSync(path.join(dest, ".grok", "roles", "implementer.toml"))).toBe(false);
|
|
1724
|
+
expect(fs.existsSync(path.join(dest, ".grok", "roles", "thinker.toml"))).toBe(false);
|
|
1725
|
+
expect(fs.existsSync(path.join(dest, ".grok", "parent.md"))).toBe(false);
|
|
1726
|
+
expect(fs.existsSync(path.join(dest, ".grok", "skills", "shared"))).toBe(false);
|
|
1727
|
+
expect(fs.existsSync(path.join(dest, "scripts", "telemetry.mjs"))).toBe(false);
|
|
1728
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1729
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1730
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc"))).toBe(false);
|
|
1731
|
+
expect(
|
|
1732
|
+
fs.existsSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc.example")),
|
|
1733
|
+
).toBe(false);
|
|
1734
|
+
expect(fs.readFileSync(path.join(dest, "scripts", "mine.mjs"), "utf8")).toBe("keep");
|
|
1735
|
+
expect(fs.existsSync(path.join(dest, ".gemini", "agents", "implementer"))).toBe(false);
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1152
1738
|
it("omitted pkgRoot uses built-in PKG_ROOT; exec mocked", () => {
|
|
1153
1739
|
const dest = tmp("grunt-realroot-");
|
|
1154
1740
|
const exec = vi.fn();
|
|
1155
1741
|
init(dest, { execFileSync: exec });
|
|
1156
1742
|
expect(fs.existsSync(path.join(dest, "AGENTS.md"))).toBe(true);
|
|
1743
|
+
const agents = fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8");
|
|
1744
|
+
const claude = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
1745
|
+
expect(agents.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
1746
|
+
expect(agents.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
1747
|
+
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
1748
|
+
expect(claude.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
1157
1749
|
expect(fs.existsSync(path.join(dest, "scripts", "scrub-text"))).toBe(true);
|
|
1750
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1751
|
+
expect(fs.existsSync(path.join(dest, "scripts", "speak.mjs"))).toBe(true);
|
|
1752
|
+
expect(fs.existsSync(path.join(dest, "scripts", "listen.mjs"))).toBe(true);
|
|
1753
|
+
expect(fs.existsSync(path.join(dest, "scripts", "google-workspace.mjs"))).toBe(true);
|
|
1158
1754
|
expect(fs.existsSync(path.join(dest, ".claude", "settings.json"))).toBe(true);
|
|
1755
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1159
1756
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1160
1757
|
["install"],
|
|
1161
|
-
["run", "rulesync:generate"],
|
|
1162
|
-
["run", "sync:globals:apply"],
|
|
1163
|
-
["run", "rulesync:check"],
|
|
1758
|
+
["run", "grunt:rulesync:generate"],
|
|
1759
|
+
["run", "grunt:sync:globals:apply"],
|
|
1760
|
+
["run", "grunt:rulesync:check"],
|
|
1164
1761
|
]);
|
|
1165
1762
|
});
|
|
1166
1763
|
|
|
@@ -1171,8 +1768,8 @@ describe("init", () => {
|
|
|
1171
1768
|
init(dest, { pkgRoot, execFileSync: exec, skipGlobals: true });
|
|
1172
1769
|
expect(exec.mock.calls.map((c) => [c[0], c[1]])).toEqual([
|
|
1173
1770
|
["npm", ["install"]],
|
|
1174
|
-
["npm", ["run", "rulesync:generate"]],
|
|
1175
|
-
["npm", ["run", "rulesync:check"]],
|
|
1771
|
+
["npm", ["run", "grunt:rulesync:generate"]],
|
|
1772
|
+
["npm", ["run", "grunt:rulesync:check"]],
|
|
1176
1773
|
]);
|
|
1177
1774
|
});
|
|
1178
1775
|
|
|
@@ -1187,9 +1784,9 @@ describe("init", () => {
|
|
|
1187
1784
|
init(dest, { pkgRoot, execFileSync: exec, applyGlobals: true });
|
|
1188
1785
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1189
1786
|
["install"],
|
|
1190
|
-
["run", "rulesync:generate"],
|
|
1191
|
-
["run", "sync:globals:apply"],
|
|
1192
|
-
["run", "rulesync:check"],
|
|
1787
|
+
["run", "grunt:rulesync:generate"],
|
|
1788
|
+
["run", "grunt:sync:globals:apply"],
|
|
1789
|
+
["run", "grunt:rulesync:check"],
|
|
1193
1790
|
]);
|
|
1194
1791
|
});
|
|
1195
1792
|
|
|
@@ -1204,8 +1801,8 @@ describe("init", () => {
|
|
|
1204
1801
|
init(dest, { pkgRoot, execFileSync: exec });
|
|
1205
1802
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1206
1803
|
["install"],
|
|
1207
|
-
["run", "rulesync:generate"],
|
|
1208
|
-
["run", "rulesync:check"],
|
|
1804
|
+
["run", "grunt:rulesync:generate"],
|
|
1805
|
+
["run", "grunt:rulesync:check"],
|
|
1209
1806
|
]);
|
|
1210
1807
|
});
|
|
1211
1808
|
|
|
@@ -1242,9 +1839,9 @@ describe("init", () => {
|
|
|
1242
1839
|
expect(fs.existsSync(path.join(dest, "AGENTS.grunt.md"))).toBe(false);
|
|
1243
1840
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1244
1841
|
["install"],
|
|
1245
|
-
["run", "rulesync:generate"],
|
|
1246
|
-
["run", "sync:globals:apply"],
|
|
1247
|
-
["run", "rulesync:check"],
|
|
1842
|
+
["run", "grunt:rulesync:generate"],
|
|
1843
|
+
["run", "grunt:sync:globals:apply"],
|
|
1844
|
+
["run", "grunt:rulesync:check"],
|
|
1248
1845
|
]);
|
|
1249
1846
|
});
|
|
1250
1847
|
|
|
@@ -1255,7 +1852,7 @@ describe("init", () => {
|
|
|
1255
1852
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "consumer claude\n");
|
|
1256
1853
|
fs.writeFileSync(path.join(dest, "GEMINI.md"), "consumer gemini\n");
|
|
1257
1854
|
const exec = vi.fn((_cmd, args) => {
|
|
1258
|
-
if (Array.isArray(args) && args.includes("rulesync:generate")) {
|
|
1855
|
+
if (Array.isArray(args) && args.includes("grunt:rulesync:generate")) {
|
|
1259
1856
|
fs.writeFileSync(path.join(dest, "AGENTS.md"), "generated agents\n");
|
|
1260
1857
|
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "generated claude\n");
|
|
1261
1858
|
fs.writeFileSync(path.join(dest, "GEMINI.md"), "@AGENTS.md\n");
|
|
@@ -1280,6 +1877,32 @@ describe("init", () => {
|
|
|
1280
1877
|
);
|
|
1281
1878
|
});
|
|
1282
1879
|
|
|
1880
|
+
it("wrapped package src + generate clobber stays begin=1 end=1", () => {
|
|
1881
|
+
const pkgRoot = stubPkgRoot();
|
|
1882
|
+
for (const f of GUARDED_MD_FILES) {
|
|
1883
|
+
fs.writeFileSync(
|
|
1884
|
+
path.join(pkgRoot, f),
|
|
1885
|
+
`<!-- grunt:begin -->\n${f} content\n<!-- grunt:end -->\n`,
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
const dest = tmp("grunt-wrap-gen-");
|
|
1889
|
+
const exec = vi.fn((_cmd, args) => {
|
|
1890
|
+
if (Array.isArray(args) && args.includes("grunt:rulesync:generate")) {
|
|
1891
|
+
fs.writeFileSync(path.join(dest, "AGENTS.md"), "generated agents\n");
|
|
1892
|
+
fs.writeFileSync(path.join(dest, "CLAUDE.md"), "generated claude\n");
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
init(dest, { pkgRoot, execFileSync: exec });
|
|
1896
|
+
const agents = fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8");
|
|
1897
|
+
const claude = fs.readFileSync(path.join(dest, "CLAUDE.md"), "utf8");
|
|
1898
|
+
expect(agents).toBe("<!-- grunt:begin -->\ngenerated agents\n<!-- grunt:end -->\n");
|
|
1899
|
+
expect(claude).toBe("<!-- grunt:begin -->\ngenerated claude\n<!-- grunt:end -->\n");
|
|
1900
|
+
expect(agents.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
1901
|
+
expect(agents.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
1902
|
+
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
1903
|
+
expect(claude.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1283
1906
|
it("onPhase merge then npm phases stop before exec", () => {
|
|
1284
1907
|
const pkgRoot = stubPkgRoot();
|
|
1285
1908
|
const dest = tmp("grunt-onphase-");
|
|
@@ -1300,9 +1923,9 @@ describe("init", () => {
|
|
|
1300
1923
|
]);
|
|
1301
1924
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1302
1925
|
["install"],
|
|
1303
|
-
["run", "rulesync:generate"],
|
|
1304
|
-
["run", "sync:globals:apply"],
|
|
1305
|
-
["run", "rulesync:check"],
|
|
1926
|
+
["run", "grunt:rulesync:generate"],
|
|
1927
|
+
["run", "grunt:sync:globals:apply"],
|
|
1928
|
+
["run", "grunt:rulesync:check"],
|
|
1306
1929
|
]);
|
|
1307
1930
|
});
|
|
1308
1931
|
|
|
@@ -1318,25 +1941,25 @@ describe("init", () => {
|
|
|
1318
1941
|
|
|
1319
1942
|
it("warns when dest skill differs from packaged then force-overwrites; keeps extras", () => {
|
|
1320
1943
|
const pkgRoot = stubPkgRoot();
|
|
1321
|
-
const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "
|
|
1944
|
+
const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "explain");
|
|
1322
1945
|
fs.mkdirSync(pkgSkill, { recursive: true });
|
|
1323
|
-
fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-
|
|
1946
|
+
fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-explain\n");
|
|
1324
1947
|
const dest = tmp("grunt-sk-conflict-");
|
|
1325
|
-
const destSkill = path.join(dest, ".rulesync", "skills", "
|
|
1948
|
+
const destSkill = path.join(dest, ".rulesync", "skills", "explain");
|
|
1326
1949
|
const destExtra = path.join(dest, ".rulesync", "skills", "my-extra");
|
|
1327
1950
|
fs.mkdirSync(destSkill, { recursive: true });
|
|
1328
1951
|
fs.mkdirSync(destExtra, { recursive: true });
|
|
1329
|
-
fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-
|
|
1952
|
+
fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-explain\n");
|
|
1330
1953
|
fs.writeFileSync(path.join(destExtra, "SKILL.md"), "keep-me\n");
|
|
1331
1954
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1332
1955
|
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true });
|
|
1333
1956
|
expect(
|
|
1334
1957
|
warnSpy.mock.calls.some(
|
|
1335
|
-
(c) => String(c[0]).includes("
|
|
1958
|
+
(c) => String(c[0]).includes("explain") && String(c[0]).includes("re-init overwrites"),
|
|
1336
1959
|
),
|
|
1337
1960
|
).toBe(true);
|
|
1338
1961
|
warnSpy.mockRestore();
|
|
1339
|
-
expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-
|
|
1962
|
+
expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-explain\n");
|
|
1340
1963
|
expect(fs.readFileSync(path.join(destExtra, "SKILL.md"), "utf8")).toBe("keep-me\n");
|
|
1341
1964
|
});
|
|
1342
1965
|
});
|