@lovrozagar/grunt 0.9.4 → 0.10.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/skills/jev/SKILL.md +18 -0
- package/.claude/skills/jev/SKILL.md +18 -0
- package/.grok/hooks/orchestrate-parent.js +9 -1
- package/.grok/skills/jev/SKILL.md +18 -0
- package/.rulesync/reference/INDEX.md +4 -2
- package/.rulesync/reference/hooks.md +1 -1
- package/.rulesync/reference/jev.md +56 -0
- package/.rulesync/reference/law.md +2 -2
- package/.rulesync/reference/refs-map.md +1 -0
- package/.rulesync/reference/setup.md +3 -1
- package/.rulesync/reference/skills-map.md +1 -0
- package/.rulesync/skills/jev/SKILL.md +15 -0
- package/CHANGELOG.md +15 -0
- package/README.md +19 -7
- package/cli/grunt.mjs +60 -8
- package/cli/grunt.test.ts +122 -1
- package/cli/init.mjs +118 -10
- package/cli/init.test.ts +302 -5
- package/cli/prompt.mjs +1 -0
- package/cli/prompt.test.ts +29 -0
- package/package.json +4 -1
- package/scripts/board.mjs +390 -0
- package/scripts/doctor.mjs +53 -15
- package/scripts/feature-packs.mjs +123 -0
- package/scripts/guarded-md.mjs +28 -0
- package/scripts/jev.mjs +355 -0
- package/scripts/listen.mjs +2 -1
- package/scripts/pipeline.mjs +84 -2
- package/scripts/prompt.mjs +4 -0
- package/scripts/session-map.mjs +4 -2
- package/scripts/setup.mjs +85 -47
- package/scripts/speak.mjs +2 -0
package/cli/grunt.test.ts
CHANGED
|
@@ -14,7 +14,9 @@ const detectPackageManager = vi.hoisted(() => vi.fn());
|
|
|
14
14
|
const actualDetect = vi.hoisted(() => ({ fn: null as null | ((...args: unknown[]) => unknown) }));
|
|
15
15
|
const isInteractive = vi.hoisted(() => vi.fn(() => false));
|
|
16
16
|
const select = vi.hoisted(() => vi.fn());
|
|
17
|
+
const multiselect = vi.hoisted(() => vi.fn(async () => []));
|
|
17
18
|
const confirm = vi.hoisted(() => vi.fn());
|
|
19
|
+
const selectedPacks = vi.hoisted(() => vi.fn(() => [] as string[]));
|
|
18
20
|
const spinner = vi.hoisted(() =>
|
|
19
21
|
vi.fn(() => ({ start: vi.fn(), stop: vi.fn() })),
|
|
20
22
|
);
|
|
@@ -46,10 +48,15 @@ vi.mock("../scripts/package-manager.mjs", async (importOriginal) => {
|
|
|
46
48
|
vi.mock("./prompt.mjs", () => ({
|
|
47
49
|
isInteractive,
|
|
48
50
|
select,
|
|
51
|
+
multiselect,
|
|
49
52
|
confirm,
|
|
50
53
|
spinner,
|
|
51
54
|
bailIfCancel,
|
|
52
55
|
}));
|
|
56
|
+
vi.mock("../scripts/feature-packs.mjs", async (importOriginal) => {
|
|
57
|
+
const actual = await importOriginal<typeof import("../scripts/feature-packs.mjs")>();
|
|
58
|
+
return { ...actual, selectedPacks };
|
|
59
|
+
});
|
|
53
60
|
|
|
54
61
|
import {
|
|
55
62
|
PACKAGE_MANAGER_ASK,
|
|
@@ -68,7 +75,7 @@ Commands:
|
|
|
68
75
|
sync-globals run grunt:sync:globals (dry-run; --apply to write)
|
|
69
76
|
purge-mcps run grunt:purge:global-mcps (dry-run; --apply to write)
|
|
70
77
|
doctor run grunt:doctor
|
|
71
|
-
setup run grunt:setup — handheld keys/OAuth (speak, listen, google-workspace, browser)
|
|
78
|
+
setup run grunt:setup — handheld keys/OAuth (speak, listen, google-workspace, browser, jev)
|
|
72
79
|
upgrade Self-update to the latest grunt, then re-init: copy owned files, prune retired names, print reserved skills
|
|
73
80
|
map [dir] Code-only folder map (git-tracked, no files); dir for depth
|
|
74
81
|
help Show this help
|
|
@@ -82,6 +89,7 @@ Flags:
|
|
|
82
89
|
--apply Write for sync-globals / purge-mcps
|
|
83
90
|
--host <id> sync-globals host
|
|
84
91
|
--pm <name> npm | yarn | pnpm | bun (else lockfile, then how grunt was launched, then ask)
|
|
92
|
+
--packs <list> Optional packs: google-workspace,listen,speak,clasp or none
|
|
85
93
|
`;
|
|
86
94
|
|
|
87
95
|
const pkgRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -125,6 +133,10 @@ describe("start", () => {
|
|
|
125
133
|
spinner.mockImplementation(() => ({ start: vi.fn(), stop: vi.fn() }));
|
|
126
134
|
bailIfCancel.mockReset();
|
|
127
135
|
bailIfCancel.mockImplementation((v) => v);
|
|
136
|
+
multiselect.mockReset();
|
|
137
|
+
multiselect.mockResolvedValue([]);
|
|
138
|
+
selectedPacks.mockReset();
|
|
139
|
+
selectedPacks.mockReturnValue([]);
|
|
128
140
|
});
|
|
129
141
|
|
|
130
142
|
afterEach(() => {
|
|
@@ -659,6 +671,46 @@ describe("parseArgv --pm", () => {
|
|
|
659
671
|
});
|
|
660
672
|
});
|
|
661
673
|
|
|
674
|
+
it("TTY upgrade asks packs only", async () => {
|
|
675
|
+
isInteractive.mockReturnValue(true);
|
|
676
|
+
confirm.mockClear();
|
|
677
|
+
multiselect.mockClear();
|
|
678
|
+
init.mockClear();
|
|
679
|
+
multiselect.mockResolvedValue(["listen", "speak"]);
|
|
680
|
+
process.argv = ["node", "grunt", "upgrade", "--no-self-update"];
|
|
681
|
+
await start();
|
|
682
|
+
expect(confirm).not.toHaveBeenCalled();
|
|
683
|
+
expect(multiselect).toHaveBeenCalledWith(
|
|
684
|
+
expect.objectContaining({ message: "Optional packs", required: false, initialValues: [] }),
|
|
685
|
+
);
|
|
686
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
687
|
+
skipGlobals: false,
|
|
688
|
+
packageManager: "npm",
|
|
689
|
+
packs: ["listen", "speak"],
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("TTY init pre-checks a saved features file and skips the pack prompt when --packs is set", async () => {
|
|
694
|
+
isInteractive.mockReturnValue(true);
|
|
695
|
+
selectedPacks.mockReturnValue(["speak"]);
|
|
696
|
+
confirm.mockResolvedValue(true);
|
|
697
|
+
process.argv = ["node", "grunt", "init"];
|
|
698
|
+
await start();
|
|
699
|
+
expect(multiselect).toHaveBeenCalledWith(
|
|
700
|
+
expect.objectContaining({ initialValues: ["speak"], required: false }),
|
|
701
|
+
);
|
|
702
|
+
multiselect.mockClear();
|
|
703
|
+
confirm.mockClear();
|
|
704
|
+
init.mockClear();
|
|
705
|
+
process.argv = ["node", "grunt", "init", "--packs", "listen"];
|
|
706
|
+
await start();
|
|
707
|
+
expect(multiselect).not.toHaveBeenCalled();
|
|
708
|
+
expect(init).toHaveBeenCalledWith(
|
|
709
|
+
process.cwd(),
|
|
710
|
+
expect.objectContaining({ packs: ["listen"], applyGlobals: true }),
|
|
711
|
+
);
|
|
712
|
+
});
|
|
713
|
+
|
|
662
714
|
it("missing, empty, flag, and unknown are pmError", () => {
|
|
663
715
|
expect(parseArgv(["generate", "--pm"])).toMatchObject({ pmError: true });
|
|
664
716
|
expect(parseArgv(["generate", "--pm="])).toMatchObject({ pmError: true });
|
|
@@ -666,3 +718,72 @@ describe("parseArgv --pm", () => {
|
|
|
666
718
|
expect(parseArgv(["generate", "--pm", "deno"])).toMatchObject({ pmError: true });
|
|
667
719
|
});
|
|
668
720
|
});
|
|
721
|
+
|
|
722
|
+
describe("parseArgv --packs", () => {
|
|
723
|
+
it("pair, equals, and none", () => {
|
|
724
|
+
expect(parseArgv(["init", "--packs", "speak,listen"])).toMatchObject({
|
|
725
|
+
cmd: "init",
|
|
726
|
+
packs: ["listen", "speak"],
|
|
727
|
+
packsError: false,
|
|
728
|
+
});
|
|
729
|
+
expect(parseArgv(["init", "--packs=google-workspace,clasp"])).toMatchObject({
|
|
730
|
+
packs: ["google-workspace", "clasp"],
|
|
731
|
+
packsError: false,
|
|
732
|
+
});
|
|
733
|
+
expect(parseArgv(["init", "--packs", "none"])).toMatchObject({ packs: [], packsError: false });
|
|
734
|
+
expect(parseArgv(["init", "--packs="])).toMatchObject({ packs: [], packsError: false });
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("missing, flag, and unknown are packsError", () => {
|
|
738
|
+
expect(parseArgv(["init", "--packs"])).toMatchObject({ packsError: true });
|
|
739
|
+
expect(parseArgv(["init", "--packs", "--yes"])).toMatchObject({ packsError: true });
|
|
740
|
+
expect(parseArgv(["init", "--packs", "nope"])).toMatchObject({ packsError: true });
|
|
741
|
+
expect(parseArgv(["init", "--packs=nope"])).toMatchObject({ packsError: true });
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
it("unknown --packs writes usage", async () => {
|
|
745
|
+
const lines: string[] = [];
|
|
746
|
+
const stdoutWrite = process.stdout.write;
|
|
747
|
+
const exitCode = process.exitCode;
|
|
748
|
+
const argv = process.argv.slice();
|
|
749
|
+
process.stdout.write = ((buf: string | Uint8Array) => {
|
|
750
|
+
lines.push(String(buf));
|
|
751
|
+
return true;
|
|
752
|
+
}) as typeof process.stdout.write;
|
|
753
|
+
process.exitCode = 0;
|
|
754
|
+
process.argv = ["node", "grunt", "init", "--packs", "nope"];
|
|
755
|
+
init.mockClear();
|
|
756
|
+
try {
|
|
757
|
+
await start();
|
|
758
|
+
expect(lines.join("")).toBe(USAGE);
|
|
759
|
+
expect(process.exitCode).toBe(1);
|
|
760
|
+
expect(init).not.toHaveBeenCalled();
|
|
761
|
+
} finally {
|
|
762
|
+
process.stdout.write = stdoutWrite;
|
|
763
|
+
process.exitCode = exitCode;
|
|
764
|
+
process.argv = argv;
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it("TTY upgrade with --packs skips the pack prompt", async () => {
|
|
769
|
+
isInteractive.mockReturnValue(true);
|
|
770
|
+
multiselect.mockClear();
|
|
771
|
+
confirm.mockClear();
|
|
772
|
+
init.mockClear();
|
|
773
|
+
process.argv = ["node", "grunt", "upgrade", "--no-self-update", "--packs", "listen"];
|
|
774
|
+
const stdoutWrite = process.stdout.write;
|
|
775
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
776
|
+
try {
|
|
777
|
+
await start();
|
|
778
|
+
expect(multiselect).not.toHaveBeenCalled();
|
|
779
|
+
expect(confirm).not.toHaveBeenCalled();
|
|
780
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
781
|
+
skipGlobals: false,
|
|
782
|
+
packageManager: "npm",
|
|
783
|
+
packs: ["listen"],
|
|
784
|
+
});
|
|
785
|
+
} finally {
|
|
786
|
+
process.stdout.write = stdoutWrite;
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
});
|
package/cli/init.mjs
CHANGED
|
@@ -15,7 +15,13 @@ import {
|
|
|
15
15
|
snapshotGuardedRoots,
|
|
16
16
|
remergeGuardedRoots,
|
|
17
17
|
withGuardedCheckInteriors,
|
|
18
|
+
mergeReferenceMarkdown,
|
|
18
19
|
} from "../scripts/guarded-md.mjs"
|
|
20
|
+
import {
|
|
21
|
+
COMMANDS as PIPELINE_COMMANDS,
|
|
22
|
+
PIPELINE_CONFIG_REL,
|
|
23
|
+
inferPipelineSkip,
|
|
24
|
+
} from "../scripts/pipeline.mjs"
|
|
19
25
|
import {
|
|
20
26
|
PACKAGE_MANAGERS,
|
|
21
27
|
UNKNOWN_PACKAGE_MANAGER,
|
|
@@ -25,6 +31,14 @@ import {
|
|
|
25
31
|
runScriptArgs,
|
|
26
32
|
} from "../scripts/package-manager.mjs"
|
|
27
33
|
import { LAW_REL, emitMaps } from "../scripts/emit-maps.mjs"
|
|
34
|
+
import {
|
|
35
|
+
isUnselectedPackPath,
|
|
36
|
+
resolvePackSelection,
|
|
37
|
+
unselectedPackRels,
|
|
38
|
+
unselectedRefNames,
|
|
39
|
+
unselectedScriptNames,
|
|
40
|
+
writePacks,
|
|
41
|
+
} from "../scripts/feature-packs.mjs"
|
|
28
42
|
|
|
29
43
|
export {
|
|
30
44
|
GUARDED_ROOT_FILES,
|
|
@@ -41,6 +55,8 @@ export {
|
|
|
41
55
|
remergeGuardedRoots,
|
|
42
56
|
healGuardedRootFile,
|
|
43
57
|
withGuardedCheckInteriors,
|
|
58
|
+
extraH2Sections,
|
|
59
|
+
mergeReferenceMarkdown,
|
|
44
60
|
} from "../scripts/guarded-md.mjs"
|
|
45
61
|
|
|
46
62
|
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
@@ -70,9 +86,12 @@ export const PRODUCT_SCRIPTS = [
|
|
|
70
86
|
"scrub-text-lib.mjs",
|
|
71
87
|
"session-map.mjs",
|
|
72
88
|
"sync-global-settings.mjs",
|
|
89
|
+
"board.mjs",
|
|
73
90
|
"browser.mjs",
|
|
91
|
+
"feature-packs.mjs",
|
|
74
92
|
"speak.mjs",
|
|
75
93
|
"listen.mjs",
|
|
94
|
+
"jev.mjs",
|
|
76
95
|
"google-workspace.mjs",
|
|
77
96
|
"interactive.mjs",
|
|
78
97
|
"prompt.mjs",
|
|
@@ -112,6 +131,7 @@ export const RESERVED_SKILLS = [
|
|
|
112
131
|
"google-workspace",
|
|
113
132
|
"handoff",
|
|
114
133
|
"implement-plan",
|
|
134
|
+
"jev",
|
|
115
135
|
"listen",
|
|
116
136
|
"pickup",
|
|
117
137
|
"speak",
|
|
@@ -147,6 +167,23 @@ export const LAUNCH_SCRIPTS = {
|
|
|
147
167
|
gemini: "gemini --yolo",
|
|
148
168
|
grok: "grok --yolo",
|
|
149
169
|
}
|
|
170
|
+
/** Exact prior grunt defaults. A dest script equal to one of these migrates to the current value. */
|
|
171
|
+
export const HISTORICAL_SCRIPT_DEFAULTS = {
|
|
172
|
+
antigravity: ["antigravity"],
|
|
173
|
+
claude: ["claude"],
|
|
174
|
+
codex: ["codex"],
|
|
175
|
+
gemini: ["gemini"],
|
|
176
|
+
grok: ["grok"],
|
|
177
|
+
}
|
|
178
|
+
export const E2E_VITEST_CONFIGS = [
|
|
179
|
+
"vitest.e2e.config.ts",
|
|
180
|
+
"vitest.e2e.config.mts",
|
|
181
|
+
"vitest.e2e.config.js",
|
|
182
|
+
"vitest.e2e.config.mjs",
|
|
183
|
+
"vitest.e2e.config.cjs",
|
|
184
|
+
]
|
|
185
|
+
export const REFERENCE_REL = ".rulesync/reference"
|
|
186
|
+
export { PIPELINE_CONFIG_REL }
|
|
150
187
|
const OWNED_HOOK_FILES = [
|
|
151
188
|
"scrub-spawn-prompt.mjs",
|
|
152
189
|
"gate-fat-tools.mjs",
|
|
@@ -333,11 +370,62 @@ function extraOwnedSuffix(cur, newSrc) {
|
|
|
333
370
|
return null
|
|
334
371
|
}
|
|
335
372
|
|
|
336
|
-
function mergeScriptValue(newSrc, cur) {
|
|
373
|
+
function mergeScriptValue(newSrc, cur, historical = []) {
|
|
337
374
|
if (cur.startsWith(newSrc)) return cur
|
|
338
375
|
const suffix = extraOwnedSuffix(cur, newSrc)
|
|
339
376
|
if (suffix != null) return newSrc + suffix
|
|
340
|
-
return newSrc
|
|
377
|
+
if (historical.includes(cur)) return newSrc
|
|
378
|
+
if (looksGruntOwnedPrefix(cur)) return newSrc
|
|
379
|
+
return cur
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function historicalFor(key) {
|
|
383
|
+
return HISTORICAL_SCRIPT_DEFAULTS[key] || []
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function hasE2eConfig(dest) {
|
|
387
|
+
return E2E_VITEST_CONFIGS.some((name) => fs.existsSync(path.join(dest, name)))
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function snapshotReferenceDocs(dest) {
|
|
391
|
+
const dir = path.join(dest, REFERENCE_REL)
|
|
392
|
+
const snap = {}
|
|
393
|
+
if (!fs.existsSync(dir)) return snap
|
|
394
|
+
for (const name of fs.readdirSync(dir)) {
|
|
395
|
+
if (!name.endsWith(".md") || GENERATED_MAP_FILES.has(name)) continue
|
|
396
|
+
const abs = path.join(dir, name)
|
|
397
|
+
if (!fs.statSync(abs).isFile()) continue
|
|
398
|
+
snap[name] = fs.readFileSync(abs, "utf8")
|
|
399
|
+
}
|
|
400
|
+
return snap
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function remergeReferenceDocs(dest, snap, pkgRoot, skip = new Set()) {
|
|
404
|
+
const pkgDir = path.join(pkgRoot, REFERENCE_REL)
|
|
405
|
+
const destDir = path.join(dest, REFERENCE_REL)
|
|
406
|
+
for (const [name, existing] of Object.entries(snap)) {
|
|
407
|
+
if (skip.has(name)) continue
|
|
408
|
+
const pkgFile = path.join(pkgDir, name)
|
|
409
|
+
if (!fs.existsSync(pkgFile)) continue
|
|
410
|
+
const incoming = fs.readFileSync(pkgFile, "utf8")
|
|
411
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
412
|
+
fs.writeFileSync(path.join(destDir, name), mergeReferenceMarkdown(existing, incoming))
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function seedPipelineConfig(dest, destPipelineSrc) {
|
|
417
|
+
if (!destPipelineSrc || !/export const COMMANDS/.test(destPipelineSrc)) return false
|
|
418
|
+
const abs = path.join(dest, PIPELINE_CONFIG_REL)
|
|
419
|
+
if (fs.existsSync(abs)) return false
|
|
420
|
+
const overlay = {}
|
|
421
|
+
for (const mode of Object.keys(PIPELINE_COMMANDS)) {
|
|
422
|
+
const skip = inferPipelineSkip(destPipelineSrc, mode)
|
|
423
|
+
if (skip.length) overlay[mode] = { skip }
|
|
424
|
+
}
|
|
425
|
+
if (!Object.keys(overlay).length) return false
|
|
426
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true })
|
|
427
|
+
fs.writeFileSync(abs, `${JSON.stringify(overlay, null, 2)}\n`)
|
|
428
|
+
return true
|
|
341
429
|
}
|
|
342
430
|
|
|
343
431
|
function rewritePackageManagerRunRefs(scripts, srcKeys) {
|
|
@@ -394,17 +482,19 @@ export function mergePackageJson(dest, pkgRoot) {
|
|
|
394
482
|
const srcKeys = []
|
|
395
483
|
for (const [k, v] of Object.entries(srcPkg.scripts || {})) {
|
|
396
484
|
if (k === "test" || k.endsWith(":raw") || k in LAUNCH_SCRIPTS) continue
|
|
397
|
-
srcKeys.push(k)
|
|
398
485
|
const destKey = toGruntScriptName(k)
|
|
399
486
|
const current = destPkg.scripts[destKey]
|
|
400
487
|
const legacy = destPkg.scripts[k]
|
|
488
|
+
if (k === "test:e2e" && current == null && legacy == null && !hasE2eConfig(dest)) continue
|
|
489
|
+
srcKeys.push(k)
|
|
490
|
+
const historical = historicalFor(k)
|
|
401
491
|
const ownedLegacy =
|
|
402
492
|
legacy != null &&
|
|
403
493
|
(legacy.startsWith(v) || extraOwnedSuffix(legacy, v) != null || looksGruntOwnedPrefix(legacy))
|
|
404
494
|
if (current != null) {
|
|
405
|
-
destPkg.scripts[destKey] = mergeScriptValue(v, current)
|
|
495
|
+
destPkg.scripts[destKey] = mergeScriptValue(v, current, historical)
|
|
406
496
|
} else if (ownedLegacy) {
|
|
407
|
-
destPkg.scripts[destKey] = mergeScriptValue(v, legacy)
|
|
497
|
+
destPkg.scripts[destKey] = mergeScriptValue(v, legacy, historical)
|
|
408
498
|
} else {
|
|
409
499
|
destPkg.scripts[destKey] = v
|
|
410
500
|
}
|
|
@@ -419,7 +509,7 @@ export function mergePackageJson(dest, pkgRoot) {
|
|
|
419
509
|
for (const [k, v] of Object.entries(LAUNCH_SCRIPTS)) {
|
|
420
510
|
const destKey = toGruntScriptName(k)
|
|
421
511
|
const current = destPkg.scripts[destKey]
|
|
422
|
-
destPkg.scripts[destKey] = current != null ? mergeScriptValue(v, current) : v
|
|
512
|
+
destPkg.scripts[destKey] = current != null ? mergeScriptValue(v, current, historicalFor(k)) : v
|
|
423
513
|
const staleKey = `grunt:yolo:${k}`
|
|
424
514
|
if (destPkg.scripts[staleKey] === v) delete destPkg.scripts[staleKey]
|
|
425
515
|
}
|
|
@@ -478,7 +568,7 @@ export function resolveInitPackageManager(dest, { packageManager, env = process.
|
|
|
478
568
|
return detected.manager
|
|
479
569
|
}
|
|
480
570
|
|
|
481
|
-
export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileSync, skipGlobals = false, applyGlobals, onPhase, packageManager, env = process.env } = {}) {
|
|
571
|
+
export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileSync, skipGlobals = false, applyGlobals, onPhase, packageManager, env = process.env, packs } = {}) {
|
|
482
572
|
dest = path.resolve(dest)
|
|
483
573
|
const pkgRoot = path.resolve(pkgRootOpt ?? PKG_ROOT)
|
|
484
574
|
const skipGlobalsApply =
|
|
@@ -494,6 +584,9 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
494
584
|
}
|
|
495
585
|
|
|
496
586
|
const self = samePath(dest, pkgRoot)
|
|
587
|
+
const selection = self ? { packs: [], answered: false } : resolvePackSelection(dest, packs)
|
|
588
|
+
const selected = new Set(selection.packs)
|
|
589
|
+
const allowPack = (src) => !isUnselectedPackPath(path.relative(pkgRoot, src), selected)
|
|
497
590
|
|
|
498
591
|
phase("merge", () => {
|
|
499
592
|
const wsSkills = path.join(dest, WORKSPACE_SKILLS_REL)
|
|
@@ -507,18 +600,25 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
507
600
|
}
|
|
508
601
|
}
|
|
509
602
|
|
|
603
|
+
const refSnap = self ? {} : snapshotReferenceDocs(dest)
|
|
604
|
+
let destPipelineSrc = ""
|
|
605
|
+
if (!self) {
|
|
606
|
+
const destPipeline = path.join(dest, "scripts", "pipeline.mjs")
|
|
607
|
+
if (fs.existsSync(destPipeline)) destPipelineSrc = fs.readFileSync(destPipeline, "utf8")
|
|
608
|
+
}
|
|
609
|
+
|
|
510
610
|
for (const dir of COPY_DIRS) {
|
|
511
611
|
const src = path.join(pkgRoot, dir)
|
|
512
612
|
const d = path.join(dest, dir)
|
|
513
613
|
fs.mkdirSync(d, { recursive: true })
|
|
514
614
|
if (samePath(src, d)) continue
|
|
515
615
|
if (dir === ".claude") {
|
|
516
|
-
copyTree(src, d, (s) => path.basename(s) !== "settings.json")
|
|
616
|
+
copyTree(src, d, (s) => path.basename(s) !== "settings.json" && allowPack(s))
|
|
517
617
|
mergeClaudeSettings(dest, pkgRoot)
|
|
518
618
|
} else if (dir === ".rulesync") {
|
|
519
|
-
copyTree(src, d, (s) => !GENERATED_MAP_FILES.has(path.basename(s)))
|
|
619
|
+
copyTree(src, d, (s) => !GENERATED_MAP_FILES.has(path.basename(s)) && allowPack(s))
|
|
520
620
|
} else {
|
|
521
|
-
copyTree(src, d)
|
|
621
|
+
copyTree(src, d, allowPack)
|
|
522
622
|
}
|
|
523
623
|
}
|
|
524
624
|
|
|
@@ -529,7 +629,9 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
529
629
|
}
|
|
530
630
|
|
|
531
631
|
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true })
|
|
632
|
+
const skipScripts = unselectedScriptNames(selected)
|
|
532
633
|
for (const name of PRODUCT_SCRIPTS) {
|
|
634
|
+
if (skipScripts.has(name)) continue
|
|
533
635
|
const src = path.join(pkgRoot, "scripts", name)
|
|
534
636
|
const d = path.join(dest, "scripts", name)
|
|
535
637
|
if (samePath(src, d)) continue
|
|
@@ -540,8 +642,14 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
540
642
|
mergeGitignore(dest)
|
|
541
643
|
|
|
542
644
|
if (!self) {
|
|
645
|
+
remergeReferenceDocs(dest, refSnap, pkgRoot, unselectedRefNames(selected))
|
|
646
|
+
seedPipelineConfig(dest, destPipelineSrc)
|
|
543
647
|
mergePackageJson(dest, pkgRoot)
|
|
544
648
|
copyRootIfMissing(dest, pkgRoot)
|
|
649
|
+
for (const rel of unselectedPackRels(selected)) {
|
|
650
|
+
fs.rmSync(path.join(dest, rel), { recursive: true, force: true })
|
|
651
|
+
}
|
|
652
|
+
if (selection.answered) writePacks(dest, selection.packs)
|
|
545
653
|
emitDestMaps(dest)
|
|
546
654
|
}
|
|
547
655
|
})
|