@monte3l/groundwork 1.0.0-rc.3 → 1.0.0-rc.5
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/dist/customize-paths.d.ts +51 -15
- package/dist/customize-paths.js +32 -13
- package/dist/fs-guard.d.ts +2 -2
- package/dist/harness/rules.d.ts +2 -1
- package/dist/harness/rules.js +3 -1
- package/dist/main.js +36 -0
- package/dist/packs.d.ts +3 -1
- package/dist/packs.js +11 -1
- package/dist/plugin.d.ts +12 -12
- package/dist/plugin.js +34 -26
- package/package.json +1 -1
- package/plugin/skills/customize/SKILL.md +33 -403
- package/plugin/skills/customize/step-0-reconcile.md +261 -0
- package/plugin/skills/customize/step-3-round-1.md +170 -0
- package/plugin/src/plugin-map.ts +131 -10
- package/templates/core/.claude/agents/Explore.md +1 -1
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +3 -2
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +7 -7
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +2 -2
- package/templates/core/.claude/skills/writing-commits/SKILL.md +4 -4
- package/templates/core/.prettierignore +1 -0
- package/templates/core/_gitignore +1 -0
- package/templates/core/bin/check-exports.mjs +5 -3
- package/templates/core/bin/lib/harness-rules.mjs +3 -1
- package/templates/core/tsconfig.base.json +10 -6
- package/templates/packs/README.md +15 -0
- package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +1 -1
- package/templates/packs/github/files/.github/workflows/claude.yml +1 -1
- package/templates/packs/github/pack.json +1 -1
- package/templates/packs/harness-extras/files/.claude/hooks/subagent-statusline.mjs +29 -14
- package/templates/packs/harness-extras/pack.json +1 -1
- package/templates/packs/publishing/files/.github/release-tools/package.json +1 -1
- package/templates/packs/publishing/files/.github/workflows/release.yml +8 -6
- package/templates/packs/publishing/pack.json +7 -1
- package/templates/packs/quality/files/.claude/agents/type-design-analyzer.md +1 -1
- package/templates/packs/quality/pack.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The skill's entry file:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The skill's entry file: it lives under the plugin source's
|
|
3
|
+
* `skills/customize/` beside {@link CUSTOMIZE_SKILL_STEP_FILE_NAMES} (the
|
|
4
|
+
* backing-data files live under its `src/`), and it is the one payload file
|
|
5
|
+
* whose presence makes Claude Code load the skill -- which is why
|
|
5
6
|
* {@link CUSTOMIZE_SKILL_WRITE_ORDER} writes it last.
|
|
6
7
|
*
|
|
7
8
|
* @example
|
|
@@ -12,6 +13,24 @@
|
|
|
12
13
|
* ```
|
|
13
14
|
*/
|
|
14
15
|
export declare const CUSTOMIZE_SKILL_ENTRY_FILE = "SKILL.md";
|
|
16
|
+
/**
|
|
17
|
+
* The step files `SKILL.md` delegates to (Step 0's adopt-mode reconcile and
|
|
18
|
+
* Step 3's Round 1), sourced from the plugin's `skills/customize/` beside
|
|
19
|
+
* {@link CUSTOMIZE_SKILL_ENTRY_FILE}. Like the backing-data files, they are
|
|
20
|
+
* written before `SKILL.md`, so a loadable `SKILL.md` never sits beside a
|
|
21
|
+
* missing step file.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { CUSTOMIZE_SKILL_STEP_FILE_NAMES } from "./customize-paths.js";
|
|
26
|
+
*
|
|
27
|
+
* CUSTOMIZE_SKILL_STEP_FILE_NAMES[0]; // "step-0-reconcile.md"
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const CUSTOMIZE_SKILL_STEP_FILE_NAMES: readonly [
|
|
31
|
+
"step-0-reconcile.md",
|
|
32
|
+
"step-3-round-1.md"
|
|
33
|
+
];
|
|
15
34
|
/**
|
|
16
35
|
* Every payload file's name inside an installed copy of the skill.
|
|
17
36
|
*
|
|
@@ -22,19 +41,28 @@ export declare const CUSTOMIZE_SKILL_ENTRY_FILE = "SKILL.md";
|
|
|
22
41
|
* CUSTOMIZE_SKILL_FILE_NAMES.includes("SKILL.md"); // true
|
|
23
42
|
* ```
|
|
24
43
|
*/
|
|
25
|
-
export declare const CUSTOMIZE_SKILL_FILE_NAMES: readonly [
|
|
44
|
+
export declare const CUSTOMIZE_SKILL_FILE_NAMES: readonly [
|
|
45
|
+
"SKILL.md",
|
|
46
|
+
"kind-facet-map.ts",
|
|
47
|
+
"domain-map.ts",
|
|
48
|
+
"pack-map.ts",
|
|
49
|
+
"plugin-map.ts",
|
|
50
|
+
"step-0-reconcile.md",
|
|
51
|
+
"step-3-round-1.md"
|
|
52
|
+
];
|
|
26
53
|
/**
|
|
27
|
-
* The order an install writes the payload in: every backing-data file
|
|
28
|
-
*
|
|
29
|
-
* (and whose rollback cannot remove everything) therefore never
|
|
30
|
-
* loadable `SKILL.md` beside missing or stale data files --
|
|
31
|
-
* precondition that no `SKILL.md` already sits at the destination.
|
|
32
|
-
* an install replaces existing entries -- fresh mode's
|
|
54
|
+
* The order an install writes the payload in: every backing-data file, then
|
|
55
|
+
* every step file, {@link CUSTOMIZE_SKILL_ENTRY_FILE} last. A run that fails
|
|
56
|
+
* part-way (and whose rollback cannot remove everything) therefore never
|
|
57
|
+
* leaves a loadable `SKILL.md` beside missing or stale data or step files --
|
|
58
|
+
* given the precondition that no `SKILL.md` already sits at the destination.
|
|
59
|
+
* Wherever an install replaces existing entries -- fresh mode's
|
|
33
60
|
* `.claude/skills/customize/` (a `--force` re-run over an earlier install)
|
|
34
61
|
* and the CLI-owned `.groundwork/customize/` -- `plugin.ts` establishes that
|
|
35
62
|
* precondition by removing any existing `SKILL.md` entry before the first
|
|
36
|
-
* data file is rewritten. Adopt mode's additive
|
|
37
|
-
* install only ever writes there when no
|
|
63
|
+
* data or step file is rewritten. Adopt mode's additive
|
|
64
|
+
* `.claude/skills/customize/` install only ever writes there when no
|
|
65
|
+
* `SKILL.md` entry exists.
|
|
38
66
|
*
|
|
39
67
|
* @example
|
|
40
68
|
* ```ts
|
|
@@ -43,7 +71,15 @@ export declare const CUSTOMIZE_SKILL_FILE_NAMES: readonly ["SKILL.md", "kind-fac
|
|
|
43
71
|
* CUSTOMIZE_SKILL_WRITE_ORDER.at(-1); // "SKILL.md"
|
|
44
72
|
* ```
|
|
45
73
|
*/
|
|
46
|
-
export declare const CUSTOMIZE_SKILL_WRITE_ORDER: readonly [
|
|
74
|
+
export declare const CUSTOMIZE_SKILL_WRITE_ORDER: readonly [
|
|
75
|
+
"kind-facet-map.ts",
|
|
76
|
+
"domain-map.ts",
|
|
77
|
+
"pack-map.ts",
|
|
78
|
+
"plugin-map.ts",
|
|
79
|
+
"step-0-reconcile.md",
|
|
80
|
+
"step-3-round-1.md",
|
|
81
|
+
"SKILL.md"
|
|
82
|
+
];
|
|
47
83
|
/**
|
|
48
84
|
* The fresh-mode destination -- and adopt mode's, when nothing is there yet
|
|
49
85
|
* or an earlier install was interrupted before its `SKILL.md` -- as path
|
|
@@ -63,7 +99,7 @@ export declare const CLAUDE_DEST_SEGMENTS: readonly [".claude", "skills", "custo
|
|
|
63
99
|
* project root. Used whenever {@link CLAUDE_DEST_SEGMENTS} cannot be written
|
|
64
100
|
* additively: a project-owned entry under any of the skill's payload names
|
|
65
101
|
* that is not this CLI's current copy (a differing file, a symlink, a
|
|
66
|
-
* directory, a `SKILL.md` without its data), or a component of
|
|
102
|
+
* directory, a `SKILL.md` without its data or step files), or a component of
|
|
67
103
|
* `.claude/skills/customize` that is a symlink or not a directory.
|
|
68
104
|
*
|
|
69
105
|
* @example
|
|
@@ -85,7 +121,7 @@ export declare const GROUNDWORK_DEST_SEGMENTS: readonly [".groundwork", "customi
|
|
|
85
121
|
* import { plannedCustomizeSkillPaths } from "./customize-paths.js";
|
|
86
122
|
*
|
|
87
123
|
* plannedCustomizeSkillPaths();
|
|
88
|
-
* // [".claude/skills/customize/SKILL.md", ..., ".groundwork/customize/
|
|
124
|
+
* // [".claude/skills/customize/SKILL.md", ..., ".groundwork/customize/step-3-round-1.md"]
|
|
89
125
|
* ```
|
|
90
126
|
*/
|
|
91
127
|
export declare function plannedCustomizeSkillPaths(): readonly string[];
|
package/dist/customize-paths.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
/**
|
|
10
|
-
* The skill's entry file:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The skill's entry file: it lives under the plugin source's
|
|
11
|
+
* `skills/customize/` beside {@link CUSTOMIZE_SKILL_STEP_FILE_NAMES} (the
|
|
12
|
+
* backing-data files live under its `src/`), and it is the one payload file
|
|
13
|
+
* whose presence makes Claude Code load the skill -- which is why
|
|
13
14
|
* {@link CUSTOMIZE_SKILL_WRITE_ORDER} writes it last.
|
|
14
15
|
*
|
|
15
16
|
* @example
|
|
@@ -27,6 +28,21 @@ const CUSTOMIZE_SKILL_DATA_FILE_NAMES = [
|
|
|
27
28
|
"pack-map.ts",
|
|
28
29
|
"plugin-map.ts",
|
|
29
30
|
];
|
|
31
|
+
/**
|
|
32
|
+
* The step files `SKILL.md` delegates to (Step 0's adopt-mode reconcile and
|
|
33
|
+
* Step 3's Round 1), sourced from the plugin's `skills/customize/` beside
|
|
34
|
+
* {@link CUSTOMIZE_SKILL_ENTRY_FILE}. Like the backing-data files, they are
|
|
35
|
+
* written before `SKILL.md`, so a loadable `SKILL.md` never sits beside a
|
|
36
|
+
* missing step file.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { CUSTOMIZE_SKILL_STEP_FILE_NAMES } from "./customize-paths.js";
|
|
41
|
+
*
|
|
42
|
+
* CUSTOMIZE_SKILL_STEP_FILE_NAMES[0]; // "step-0-reconcile.md"
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const CUSTOMIZE_SKILL_STEP_FILE_NAMES = ["step-0-reconcile.md", "step-3-round-1.md"];
|
|
30
46
|
/**
|
|
31
47
|
* Every payload file's name inside an installed copy of the skill.
|
|
32
48
|
*
|
|
@@ -40,19 +56,21 @@ const CUSTOMIZE_SKILL_DATA_FILE_NAMES = [
|
|
|
40
56
|
export const CUSTOMIZE_SKILL_FILE_NAMES = [
|
|
41
57
|
CUSTOMIZE_SKILL_ENTRY_FILE,
|
|
42
58
|
...CUSTOMIZE_SKILL_DATA_FILE_NAMES,
|
|
59
|
+
...CUSTOMIZE_SKILL_STEP_FILE_NAMES,
|
|
43
60
|
];
|
|
44
61
|
/**
|
|
45
|
-
* The order an install writes the payload in: every backing-data file
|
|
46
|
-
*
|
|
47
|
-
* (and whose rollback cannot remove everything) therefore never
|
|
48
|
-
* loadable `SKILL.md` beside missing or stale data files --
|
|
49
|
-
* precondition that no `SKILL.md` already sits at the destination.
|
|
50
|
-
* an install replaces existing entries -- fresh mode's
|
|
62
|
+
* The order an install writes the payload in: every backing-data file, then
|
|
63
|
+
* every step file, {@link CUSTOMIZE_SKILL_ENTRY_FILE} last. A run that fails
|
|
64
|
+
* part-way (and whose rollback cannot remove everything) therefore never
|
|
65
|
+
* leaves a loadable `SKILL.md` beside missing or stale data or step files --
|
|
66
|
+
* given the precondition that no `SKILL.md` already sits at the destination.
|
|
67
|
+
* Wherever an install replaces existing entries -- fresh mode's
|
|
51
68
|
* `.claude/skills/customize/` (a `--force` re-run over an earlier install)
|
|
52
69
|
* and the CLI-owned `.groundwork/customize/` -- `plugin.ts` establishes that
|
|
53
70
|
* precondition by removing any existing `SKILL.md` entry before the first
|
|
54
|
-
* data file is rewritten. Adopt mode's additive
|
|
55
|
-
* install only ever writes there when no
|
|
71
|
+
* data or step file is rewritten. Adopt mode's additive
|
|
72
|
+
* `.claude/skills/customize/` install only ever writes there when no
|
|
73
|
+
* `SKILL.md` entry exists.
|
|
56
74
|
*
|
|
57
75
|
* @example
|
|
58
76
|
* ```ts
|
|
@@ -63,6 +81,7 @@ export const CUSTOMIZE_SKILL_FILE_NAMES = [
|
|
|
63
81
|
*/
|
|
64
82
|
export const CUSTOMIZE_SKILL_WRITE_ORDER = [
|
|
65
83
|
...CUSTOMIZE_SKILL_DATA_FILE_NAMES,
|
|
84
|
+
...CUSTOMIZE_SKILL_STEP_FILE_NAMES,
|
|
66
85
|
CUSTOMIZE_SKILL_ENTRY_FILE,
|
|
67
86
|
];
|
|
68
87
|
/**
|
|
@@ -84,7 +103,7 @@ export const CLAUDE_DEST_SEGMENTS = [".claude", "skills", "customize"];
|
|
|
84
103
|
* project root. Used whenever {@link CLAUDE_DEST_SEGMENTS} cannot be written
|
|
85
104
|
* additively: a project-owned entry under any of the skill's payload names
|
|
86
105
|
* that is not this CLI's current copy (a differing file, a symlink, a
|
|
87
|
-
* directory, a `SKILL.md` without its data), or a component of
|
|
106
|
+
* directory, a `SKILL.md` without its data or step files), or a component of
|
|
88
107
|
* `.claude/skills/customize` that is a symlink or not a directory.
|
|
89
108
|
*
|
|
90
109
|
* @example
|
|
@@ -106,7 +125,7 @@ export const GROUNDWORK_DEST_SEGMENTS = [".groundwork", "customize"];
|
|
|
106
125
|
* import { plannedCustomizeSkillPaths } from "./customize-paths.js";
|
|
107
126
|
*
|
|
108
127
|
* plannedCustomizeSkillPaths();
|
|
109
|
-
* // [".claude/skills/customize/SKILL.md", ..., ".groundwork/customize/
|
|
128
|
+
* // [".claude/skills/customize/SKILL.md", ..., ".groundwork/customize/step-3-round-1.md"]
|
|
110
129
|
* ```
|
|
111
130
|
*/
|
|
112
131
|
export function plannedCustomizeSkillPaths() {
|
package/dist/fs-guard.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* endsWithRerunAdvice(message); // true
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
-
export declare const FIX_AND_RERUN_ADVICE
|
|
15
|
+
export declare const FIX_AND_RERUN_ADVICE: string;
|
|
16
16
|
/**
|
|
17
17
|
* Whether `message` already ENDS with "re-run the CLI" advice, in any
|
|
18
18
|
* wording that ends that way (e.g. {@link assertNotSymlink}'s "remove it and
|
|
@@ -138,5 +138,5 @@ export declare function assertNotDirectory(path: string, advice: string): void;
|
|
|
138
138
|
* assertNotSymlink("/work/app/package.json", FRESH_SYMLINK_ADVICE);
|
|
139
139
|
* ```
|
|
140
140
|
*/
|
|
141
|
-
export declare const FRESH_SYMLINK_ADVICE
|
|
141
|
+
export declare const FRESH_SYMLINK_ADVICE: string;
|
|
142
142
|
//# sourceMappingURL=fs-guard.d.ts.map
|
package/dist/harness/rules.d.ts
CHANGED
|
@@ -59,7 +59,8 @@ export interface HarnessRule {
|
|
|
59
59
|
* Model ids and aliases the rubric accepts: the current ids and aliases, plus
|
|
60
60
|
* ids that were once listed here, kept until Anthropic deprecates them. The
|
|
61
61
|
* ids follow Anthropic's models overview and model-deprecations pages
|
|
62
|
-
* (retrieved 2026-10-
|
|
62
|
+
* (retrieved 2026-10-08): Haiku 5.5 (released 2026-10-07) is current, and
|
|
63
|
+
* Haiku 4.5 is legacy but still active. A legacy id that was never listed here is
|
|
63
64
|
* deliberately not added, so the rule keeps nudging pins toward current
|
|
64
65
|
* models. Bump alongside the `harness-guidance` refresh sweep;
|
|
65
66
|
* `templates/core/bin/lib/harness-rules.mjs` carries the same list and the
|
package/dist/harness/rules.js
CHANGED
|
@@ -20,7 +20,8 @@ import { fieldList, fieldText, parseFrontmatter } from "./frontmatter.js";
|
|
|
20
20
|
* Model ids and aliases the rubric accepts: the current ids and aliases, plus
|
|
21
21
|
* ids that were once listed here, kept until Anthropic deprecates them. The
|
|
22
22
|
* ids follow Anthropic's models overview and model-deprecations pages
|
|
23
|
-
* (retrieved 2026-10-
|
|
23
|
+
* (retrieved 2026-10-08): Haiku 5.5 (released 2026-10-07) is current, and
|
|
24
|
+
* Haiku 4.5 is legacy but still active. A legacy id that was never listed here is
|
|
24
25
|
* deliberately not added, so the rule keeps nudging pins toward current
|
|
25
26
|
* models. Bump alongside the `harness-guidance` refresh sweep;
|
|
26
27
|
* `templates/core/bin/lib/harness-rules.mjs` carries the same list and the
|
|
@@ -37,6 +38,7 @@ export const CURRENT_MODELS = [
|
|
|
37
38
|
"claude-sonnet-5",
|
|
38
39
|
"claude-sonnet-5-5",
|
|
39
40
|
"claude-fable-5-1",
|
|
41
|
+
"claude-haiku-5-5",
|
|
40
42
|
"claude-haiku-4-5",
|
|
41
43
|
"claude-haiku-4-5-20251001",
|
|
42
44
|
];
|
package/dist/main.js
CHANGED
|
@@ -242,6 +242,27 @@ export function formatCapsSummary(baseline, installed) {
|
|
|
242
242
|
lines.push(`= ${formatCounts(total)}${overCap.length > 0 ? ` ⚠ over cap: ${overCap.join(", ")}` : ""}`);
|
|
243
243
|
return { text: lines.join("\n"), overCap: overCap.length > 0 };
|
|
244
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Fresh mode's closing "setup required" block: one heading, then each pack
|
|
247
|
+
* that declares `setupSteps` (in `packs` order) followed by its commands,
|
|
248
|
+
* each indented two spaces. `undefined` when no pack declares any, so the
|
|
249
|
+
* caller prints nothing extra.
|
|
250
|
+
*/
|
|
251
|
+
function formatSetupBlock(targetDir, packs) {
|
|
252
|
+
const lines = [];
|
|
253
|
+
for (const { manifest } of packs) {
|
|
254
|
+
const steps = manifest.setupSteps ?? [];
|
|
255
|
+
if (steps.length === 0)
|
|
256
|
+
continue;
|
|
257
|
+
lines.push(`pack "${manifest.name}":`, ...steps.map((step) => ` ${step}`));
|
|
258
|
+
}
|
|
259
|
+
if (lines.length === 0)
|
|
260
|
+
return undefined;
|
|
261
|
+
return [
|
|
262
|
+
`\nthe installed pack(s) need setup before the first \`pnpm verify\` -- run these in ${targetDir}:`,
|
|
263
|
+
...lines,
|
|
264
|
+
].join("\n");
|
|
265
|
+
}
|
|
245
266
|
/**
|
|
246
267
|
* Fresh mode's pre-flight over the `/customize` skill's own destination
|
|
247
268
|
* (`.claude/skills/customize/`): each directory component must be missing
|
|
@@ -295,6 +316,18 @@ function runFresh(options, platform) {
|
|
|
295
316
|
: summary.text);
|
|
296
317
|
}
|
|
297
318
|
const { targetDir, skipInstall, projectName } = options;
|
|
319
|
+
// Built once, now that the project and its packs are on disk, and printed
|
|
320
|
+
// exactly once per run: at the very end on success, or just before the
|
|
321
|
+
// throw when git init / pnpm install fails -- a plain re-run of a
|
|
322
|
+
// non-empty target adopts it, and adopt mode never prints setupSteps. The
|
|
323
|
+
// /customize-skill failure below deliberately does not print it: its
|
|
324
|
+
// message says to re-run with --fresh --force, which prints it on success.
|
|
325
|
+
const setupBlock = formatSetupBlock(targetDir, packs);
|
|
326
|
+
const printSetupBlock = () => {
|
|
327
|
+
if (setupBlock !== undefined) {
|
|
328
|
+
console.log(paint(process.stdout, "warning", setupBlock));
|
|
329
|
+
}
|
|
330
|
+
};
|
|
298
331
|
let pluginResult;
|
|
299
332
|
try {
|
|
300
333
|
pluginResult = installCustomizeSkill(targetDir);
|
|
@@ -314,6 +347,7 @@ function runFresh(options, platform) {
|
|
|
314
347
|
gitInit(targetDir);
|
|
315
348
|
}
|
|
316
349
|
catch (error) {
|
|
350
|
+
printSetupBlock();
|
|
317
351
|
throw new Error(`git init failed, but the project was written to ${targetDir}; run \`git init\`${skipInstall ? "" : " and `pnpm install`"} there yourself`, { cause: error });
|
|
318
352
|
}
|
|
319
353
|
console.log("initialized git repository");
|
|
@@ -323,11 +357,13 @@ function runFresh(options, platform) {
|
|
|
323
357
|
}
|
|
324
358
|
catch (error) {
|
|
325
359
|
console.log(paint(process.stdout, "warning", `\n${projectName} written to ${targetDir}, but dependencies are not installed`));
|
|
360
|
+
printSetupBlock();
|
|
326
361
|
throw new Error(`${describeInstallFailure(error)}; the project was written to ${targetDir} -- run \`pnpm install\` there yourself to finish`, { cause: error });
|
|
327
362
|
}
|
|
328
363
|
console.log("installed dependencies");
|
|
329
364
|
}
|
|
330
365
|
console.log(paint(process.stdout, "success", `\n✓ ${projectName} is ready at ${targetDir}`));
|
|
366
|
+
printSetupBlock();
|
|
331
367
|
}
|
|
332
368
|
/**
|
|
333
369
|
* Explains why the post-emission `pnpm install` failed: a missing binary
|
package/dist/packs.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface PackManifest {
|
|
|
19
19
|
} | undefined;
|
|
20
20
|
wiring: PackWiring;
|
|
21
21
|
adoptNotes: string | undefined;
|
|
22
|
+
/** Shell commands, run in the target directory, that a fresh install needs before its first `pnpm verify` (fresh mode prints them; see main.ts). Optional: absent means no setup. */
|
|
23
|
+
setupSteps?: string[] | undefined;
|
|
22
24
|
}
|
|
23
25
|
export interface Pack {
|
|
24
26
|
manifest: PackManifest;
|
|
@@ -28,7 +30,7 @@ export interface Pack {
|
|
|
28
30
|
export declare function packsRootDir(): string;
|
|
29
31
|
/** Names of every pack directory that has a `pack.json` under `root`, sorted for deterministic install order. `root` defaults to `templates/packs`, overridable for tests. */
|
|
30
32
|
export declare function listPackNames(root?: string): string[];
|
|
31
|
-
/** Loads and validates one pack's manifest from under `root` (default `templates/packs`, overridable for tests). Throws, naming the available packs, if unknown; throws naming the pack and the problem if malformed, including a prototype-sensitive key in its wiring (see {@link assertValidWiringShape}). */
|
|
33
|
+
/** Loads and validates one pack's manifest from under `root` (default `templates/packs`, overridable for tests). Throws, naming the available packs, if unknown; throws naming the pack and the problem if malformed, including a prototype-sensitive key in its wiring (see {@link assertValidWiringShape}) or a `setupSteps` that is present but not a non-empty array of single-line, non-empty strings. */
|
|
32
34
|
export declare function loadPack(name: string, root?: string): Pack;
|
|
33
35
|
export interface PackInstallResult {
|
|
34
36
|
filesWritten: string[];
|
package/dist/packs.js
CHANGED
|
@@ -132,7 +132,13 @@ function assertValidWiringShape(name, wiring) {
|
|
|
132
132
|
assertValidVerifyStep(name, step, index);
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
-
/**
|
|
135
|
+
/** True when `steps` is a non-empty array of non-empty strings, none containing a line break -- each is printed as one indented line. */
|
|
136
|
+
function isValidSetupSteps(steps) {
|
|
137
|
+
return (Array.isArray(steps) &&
|
|
138
|
+
steps.length > 0 &&
|
|
139
|
+
steps.every((step) => typeof step === "string" && step !== "" && !/[\r\n]/.test(step)));
|
|
140
|
+
}
|
|
141
|
+
/** Loads and validates one pack's manifest from under `root` (default `templates/packs`, overridable for tests). Throws, naming the available packs, if unknown; throws naming the pack and the problem if malformed, including a prototype-sensitive key in its wiring (see {@link assertValidWiringShape}) or a `setupSteps` that is present but not a non-empty array of single-line, non-empty strings. */
|
|
136
142
|
export function loadPack(name, root = packsRootDir()) {
|
|
137
143
|
const packDir = join(root, name);
|
|
138
144
|
const manifestPath = join(packDir, "pack.json");
|
|
@@ -176,6 +182,10 @@ export function loadPack(name, root = packsRootDir()) {
|
|
|
176
182
|
if (!isValidBudget(budget)) {
|
|
177
183
|
throw new Error(`pack "${name}": pack.json's budget must set ${CAP_KEYS.join(", ")} to non-negative integers`);
|
|
178
184
|
}
|
|
185
|
+
const setupSteps = manifest.setupSteps;
|
|
186
|
+
if (setupSteps !== undefined && !isValidSetupSteps(setupSteps)) {
|
|
187
|
+
throw new Error(`pack "${name}": pack.json's setupSteps must be a non-empty array of single-line, non-empty strings`);
|
|
188
|
+
}
|
|
179
189
|
// pack-stage.ts's stagePacks keys each pack's staging directory on
|
|
180
190
|
// manifest.name, so a mismatch would let two pack directories collide.
|
|
181
191
|
if (manifestName !== name) {
|
package/dist/plugin.d.ts
CHANGED
|
@@ -89,20 +89,20 @@ export interface GuardedInstallResult {
|
|
|
89
89
|
* `fallbackCause` is `"component"` -- the entry (and any link target) is
|
|
90
90
|
* left untouched.
|
|
91
91
|
* - Otherwise, if every payload file already there is a regular file
|
|
92
|
-
* matching what this CLI ships byte-for-byte: with
|
|
93
|
-
* result is `"already-present"` (nothing written); with no
|
|
94
|
-
* (none at all, or an install interrupted before writing
|
|
95
|
-
* files are written into `.claude/skills/customize/`,
|
|
96
|
-
* never rewriting or removing the correct ones.
|
|
92
|
+
* matching what this CLI ships byte-for-byte: with every payload file
|
|
93
|
+
* present the result is `"already-present"` (nothing written); with no
|
|
94
|
+
* `SKILL.md` file (none at all, or an install interrupted before writing
|
|
95
|
+
* it) the missing files are written into `.claude/skills/customize/`,
|
|
96
|
+
* `SKILL.md` last, never rewriting or removing the correct ones.
|
|
97
97
|
* - Anything else under a payload name (a differing file, a regular file
|
|
98
98
|
* that cannot be read, a symlink -- dangling or not -- a directory, a
|
|
99
|
-
* `SKILL.md` without its data; detected by `lstat`) is kept
|
|
100
|
-
* project's own: the skill is written to `.groundwork/customize/`
|
|
101
|
-
* `fallbackReason` names that entry (saying so when it could not
|
|
102
|
-
* and `fallbackCause` is `"entry"`. Adopt mode never guesses at a
|
|
103
|
-
* entry it cannot compare, and the fallback does not guess either:
|
|
104
|
-
* leaves that entry exactly as it was. Claude Code does not load a
|
|
105
|
-
* from there; the caller must say so.
|
|
99
|
+
* `SKILL.md` without its data or step files; detected by `lstat`) is kept
|
|
100
|
+
* as the project's own: the skill is written to `.groundwork/customize/`
|
|
101
|
+
* instead, `fallbackReason` names that entry (saying so when it could not
|
|
102
|
+
* be read) and `fallbackCause` is `"entry"`. Adopt mode never guesses at a
|
|
103
|
+
* project entry it cannot compare, and the fallback does not guess either:
|
|
104
|
+
* it leaves that entry exactly as it was. Claude Code does not load a
|
|
105
|
+
* skill from there; the caller must say so.
|
|
106
106
|
*
|
|
107
107
|
* Writes into `.claude/skills/customize/` use `"wx"` only, so an entry that
|
|
108
108
|
* appears there mid-install fails the run (rolled back) rather than being
|
package/dist/plugin.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Installs the `/customize` skill into a bootstrapped or adopted project.
|
|
5
5
|
* Claude Code's marketplace-based plugin installation is an interactive,
|
|
6
6
|
* network-involving flow this offline CLI can't drive; instead this copies
|
|
7
|
-
* the skill's SKILL.md
|
|
8
|
-
* destination directory, so `/customize` works immediately with no
|
|
9
|
-
* setup step.
|
|
7
|
+
* the skill's SKILL.md, its step files and its deterministic backing data
|
|
8
|
+
* into a destination directory, so `/customize` works immediately with no
|
|
9
|
+
* further setup step.
|
|
10
10
|
*/
|
|
11
11
|
import { lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { resolveAsset } from "./assets.js";
|
|
14
|
-
import { CLAUDE_DEST_SEGMENTS, CUSTOMIZE_SKILL_ENTRY_FILE, CUSTOMIZE_SKILL_WRITE_ORDER, GROUNDWORK_DEST_SEGMENTS, } from "./customize-paths.js";
|
|
14
|
+
import { CLAUDE_DEST_SEGMENTS, CUSTOMIZE_SKILL_ENTRY_FILE, CUSTOMIZE_SKILL_STEP_FILE_NAMES, CUSTOMIZE_SKILL_WRITE_ORDER, GROUNDWORK_DEST_SEGMENTS, } from "./customize-paths.js";
|
|
15
15
|
import { assertNotSymlink, endsWithRerunAdvice, FIX_AND_RERUN_ADVICE, FRESH_RETRY, FRESH_SYMLINK_ADVICE, } from "./fs-guard.js";
|
|
16
16
|
/** Resolves the plugin payload for a source checkout (`packages/plugin`) or a published tarball (`plugin/`). */
|
|
17
17
|
function pluginDir() {
|
|
@@ -107,6 +107,14 @@ function errnoField(error, field) {
|
|
|
107
107
|
const value = Reflect.get(error, field);
|
|
108
108
|
return typeof value === "string" ? value : undefined;
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* The payload files sourced from the plugin's `skills/customize/` (the
|
|
112
|
+
* entry file and its step files); every other one comes from its `src/`.
|
|
113
|
+
*/
|
|
114
|
+
const SKILL_DIR_FILE_NAMES = new Set([
|
|
115
|
+
CUSTOMIZE_SKILL_ENTRY_FILE,
|
|
116
|
+
...CUSTOMIZE_SKILL_STEP_FILE_NAMES,
|
|
117
|
+
]);
|
|
110
118
|
/**
|
|
111
119
|
* Reads every payload file from the plugin source, in
|
|
112
120
|
* {@link CUSTOMIZE_SKILL_WRITE_ORDER} (`SKILL.md` last), before anything is
|
|
@@ -117,7 +125,7 @@ function errnoField(error, field) {
|
|
|
117
125
|
*/
|
|
118
126
|
function readCustomizeSkillPayload(sourceDir) {
|
|
119
127
|
return CUSTOMIZE_SKILL_WRITE_ORDER.map((name) => {
|
|
120
|
-
const from = name
|
|
128
|
+
const from = SKILL_DIR_FILE_NAMES.has(name)
|
|
121
129
|
? join(sourceDir, "skills", "customize", name)
|
|
122
130
|
: join(sourceDir, "src", name);
|
|
123
131
|
try {
|
|
@@ -201,13 +209,13 @@ function assertNoDirectoryAtPayloadNames(destDir, payload, alreadyCurrent, polic
|
|
|
201
209
|
/**
|
|
202
210
|
* For every policy that replaces existing entries: removes an existing
|
|
203
211
|
* `SKILL.md` entry (a file or a symlink, unlinked, never followed) BEFORE any
|
|
204
|
-
* data file is rewritten, so a later failure never leaves a stale
|
|
205
|
-
* loadable beside missing or half-rewritten data
|
|
206
|
-
* {@link assertNoDirectoryAtPayloadNames}, so a directory there
|
|
207
|
-
* been refused (one raced in since makes `rmSync` throw). Returns
|
|
208
|
-
* removed path, if any; throws (wrapped) before anything is written when
|
|
209
|
-
* removal fails. `label` is how the entry is described: `"existing"` for
|
|
210
|
-
* project's own `.claude/` copy, `"stale"` for the CLI-owned staging.
|
|
212
|
+
* data or step file is rewritten, so a later failure never leaves a stale
|
|
213
|
+
* `SKILL.md` loadable beside missing or half-rewritten data or step files.
|
|
214
|
+
* Runs after {@link assertNoDirectoryAtPayloadNames}, so a directory there
|
|
215
|
+
* has already been refused (one raced in since makes `rmSync` throw). Returns
|
|
216
|
+
* the removed path, if any; throws (wrapped) before anything is written when
|
|
217
|
+
* the removal fails. `label` is how the entry is described: `"existing"` for
|
|
218
|
+
* a project's own `.claude/` copy, `"stale"` for the CLI-owned staging.
|
|
211
219
|
*/
|
|
212
220
|
function removeStaleSkillEntry(destDir, label) {
|
|
213
221
|
const staleEntry = join(destDir, CUSTOMIZE_SKILL_ENTRY_FILE);
|
|
@@ -323,8 +331,8 @@ function replacedClause(policy, skillRemoved, replaced, skillMaybeLeft) {
|
|
|
323
331
|
if (skillRemoved === undefined && others === "") {
|
|
324
332
|
return "";
|
|
325
333
|
}
|
|
326
|
-
// One sentence for SKILL.md and the
|
|
327
|
-
// them was removed and none is restored.
|
|
334
|
+
// One sentence for SKILL.md and the other payload files alike: every
|
|
335
|
+
// one of them was removed and none is restored.
|
|
328
336
|
const removedPaths = [
|
|
329
337
|
...(skillRemoved === undefined
|
|
330
338
|
? []
|
|
@@ -668,20 +676,20 @@ function installGuarded(targetDir, sourceDir) {
|
|
|
668
676
|
* `fallbackCause` is `"component"` -- the entry (and any link target) is
|
|
669
677
|
* left untouched.
|
|
670
678
|
* - Otherwise, if every payload file already there is a regular file
|
|
671
|
-
* matching what this CLI ships byte-for-byte: with
|
|
672
|
-
* result is `"already-present"` (nothing written); with no
|
|
673
|
-
* (none at all, or an install interrupted before writing
|
|
674
|
-
* files are written into `.claude/skills/customize/`,
|
|
675
|
-
* never rewriting or removing the correct ones.
|
|
679
|
+
* matching what this CLI ships byte-for-byte: with every payload file
|
|
680
|
+
* present the result is `"already-present"` (nothing written); with no
|
|
681
|
+
* `SKILL.md` file (none at all, or an install interrupted before writing
|
|
682
|
+
* it) the missing files are written into `.claude/skills/customize/`,
|
|
683
|
+
* `SKILL.md` last, never rewriting or removing the correct ones.
|
|
676
684
|
* - Anything else under a payload name (a differing file, a regular file
|
|
677
685
|
* that cannot be read, a symlink -- dangling or not -- a directory, a
|
|
678
|
-
* `SKILL.md` without its data; detected by `lstat`) is kept
|
|
679
|
-
* project's own: the skill is written to `.groundwork/customize/`
|
|
680
|
-
* `fallbackReason` names that entry (saying so when it could not
|
|
681
|
-
* and `fallbackCause` is `"entry"`. Adopt mode never guesses at a
|
|
682
|
-
* entry it cannot compare, and the fallback does not guess either:
|
|
683
|
-
* leaves that entry exactly as it was. Claude Code does not load a
|
|
684
|
-
* from there; the caller must say so.
|
|
686
|
+
* `SKILL.md` without its data or step files; detected by `lstat`) is kept
|
|
687
|
+
* as the project's own: the skill is written to `.groundwork/customize/`
|
|
688
|
+
* instead, `fallbackReason` names that entry (saying so when it could not
|
|
689
|
+
* be read) and `fallbackCause` is `"entry"`. Adopt mode never guesses at a
|
|
690
|
+
* project entry it cannot compare, and the fallback does not guess either:
|
|
691
|
+
* it leaves that entry exactly as it was. Claude Code does not load a
|
|
692
|
+
* skill from there; the caller must say so.
|
|
685
693
|
*
|
|
686
694
|
* Writes into `.claude/skills/customize/` use `"wx"` only, so an entry that
|
|
687
695
|
* appears there mid-install fails the run (rolled back) rather than being
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monte3l/groundwork",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.5",
|
|
4
4
|
"description": "CLI that writes a TypeScript toolchain and Claude Code setup into an empty directory, or -- against an existing project -- read-only surveys it and reports the differences. No prompts, and no network access beyond the package install.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|