@monte3l/groundwork 1.0.0-rc.2 → 1.0.0-rc.4

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.
Files changed (170) hide show
  1. package/README.md +13 -5
  2. package/bin/m3l-groundwork.mjs +36 -2
  3. package/dist/assets.d.ts +10 -0
  4. package/dist/assets.js +14 -0
  5. package/dist/baseline-stage.d.ts +173 -0
  6. package/dist/baseline-stage.js +215 -0
  7. package/dist/caps.d.ts +4 -1
  8. package/dist/caps.js +17 -3
  9. package/dist/conflicts.d.ts +23 -2
  10. package/dist/conflicts.js +89 -11
  11. package/dist/customize-paths.d.ts +92 -0
  12. package/dist/customize-paths.js +115 -0
  13. package/dist/emit.d.ts +50 -1
  14. package/dist/emit.js +121 -13
  15. package/dist/fatal.d.ts +70 -0
  16. package/dist/fatal.js +132 -0
  17. package/dist/format-error.d.ts +113 -0
  18. package/dist/format-error.js +553 -0
  19. package/dist/fs-guard.d.ts +142 -0
  20. package/dist/fs-guard.js +222 -0
  21. package/dist/git.js +10 -1
  22. package/dist/harness/conformance.js +2 -0
  23. package/dist/harness/frontmatter.js +2 -13
  24. package/dist/harness/grade.js +37 -8
  25. package/dist/harness/rules.d.ts +20 -3
  26. package/dist/harness/rules.js +108 -6
  27. package/dist/harness/types.d.ts +13 -0
  28. package/dist/harness/types.js +3 -0
  29. package/dist/inventory.d.ts +77 -3
  30. package/dist/inventory.js +103 -12
  31. package/dist/jsonc.d.ts +49 -2
  32. package/dist/jsonc.js +140 -7
  33. package/dist/main.d.ts +62 -3
  34. package/dist/main.js +574 -67
  35. package/dist/merge-json.d.ts +47 -4
  36. package/dist/merge-json.js +120 -8
  37. package/dist/mode.js +12 -2
  38. package/dist/pack-stage.d.ts +210 -0
  39. package/dist/pack-stage.js +287 -0
  40. package/dist/packs.d.ts +21 -13
  41. package/dist/packs.js +241 -28
  42. package/dist/palette.d.ts +23 -0
  43. package/dist/palette.js +22 -0
  44. package/dist/plugin.d.ts +122 -6
  45. package/dist/plugin.js +687 -47
  46. package/dist/report.d.ts +21 -2
  47. package/dist/report.js +93 -5
  48. package/dist/staging.d.ts +176 -0
  49. package/dist/staging.js +375 -0
  50. package/dist/survey/fs-walk.d.ts +34 -2
  51. package/dist/survey/fs-walk.js +70 -5
  52. package/dist/survey/internal/blocked-path.d.ts +27 -0
  53. package/dist/survey/internal/blocked-path.js +129 -0
  54. package/dist/survey/internal/package-json.d.ts +13 -0
  55. package/dist/survey/internal/package-json.js +37 -0
  56. package/dist/survey/internal/read-guard.d.ts +178 -0
  57. package/dist/survey/internal/read-guard.js +276 -0
  58. package/dist/survey/survey-docs.d.ts +17 -2
  59. package/dist/survey/survey-docs.js +61 -21
  60. package/dist/survey/survey-harness.d.ts +20 -2
  61. package/dist/survey/survey-harness.js +87 -46
  62. package/dist/survey/survey-shape.d.ts +17 -2
  63. package/dist/survey/survey-shape.js +60 -46
  64. package/dist/survey/survey-toolchain.d.ts +16 -1
  65. package/dist/survey/survey-toolchain.js +69 -66
  66. package/dist/survey/survey.js +6 -4
  67. package/dist/survey/types.d.ts +79 -0
  68. package/dist/survey/types.js +2 -6
  69. package/dist/term.d.ts +80 -0
  70. package/dist/term.js +145 -0
  71. package/dist/tokens.js +2 -0
  72. package/dist/toolchain/conformance.js +2 -0
  73. package/dist/toolchain/grade.js +26 -8
  74. package/dist/toolchain/rules.d.ts +13 -4
  75. package/dist/toolchain/rules.js +16 -0
  76. package/dist/toolchain/tsconfig-chain.d.ts +2 -0
  77. package/dist/toolchain/tsconfig-chain.js +32 -8
  78. package/dist/toolchain/types.d.ts +16 -4
  79. package/dist/toolchain/types.js +3 -0
  80. package/package.json +4 -3
  81. package/plugin/skills/customize/SKILL.md +292 -18
  82. package/plugin/src/domain-map.ts +39 -14
  83. package/plugin/src/index.ts +5 -1
  84. package/plugin/src/kind-facet-map.ts +25 -8
  85. package/plugin/src/pack-map.ts +147 -25
  86. package/plugin/src/plugin-map.ts +236 -0
  87. package/templates/core/.claude/agents/Explore.md +0 -1
  88. package/templates/core/.claude/agents/code-implementer.md +4 -4
  89. package/templates/core/.claude/agents/code-reviewer.md +4 -4
  90. package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
  91. package/templates/core/.claude/agents/test-author.md +7 -5
  92. package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
  93. package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
  94. package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
  95. package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
  96. package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
  97. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
  98. package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
  99. package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
  100. package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
  101. package/templates/core/.claude/rules/agent-dispatch.md +7 -0
  102. package/templates/core/.claude/rules/tests.md +2 -2
  103. package/templates/core/.claude/settings.json +5 -0
  104. package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
  105. package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
  106. package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
  107. package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
  108. package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
  109. package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
  110. package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
  111. package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
  112. package/templates/core/.github/dependabot.yml +18 -0
  113. package/templates/core/.github/workflows/ci.yml +15 -15
  114. package/templates/core/.github/workflows/dependency-review.yml +2 -2
  115. package/templates/core/.github/workflows/security-audit.yml +10 -4
  116. package/templates/core/.prettierignore +4 -0
  117. package/templates/core/CLAUDE.md +53 -3
  118. package/templates/core/README.md +30 -10
  119. package/templates/core/_gitignore +17 -0
  120. package/templates/core/bin/check-exports.mjs +11 -5
  121. package/templates/core/bin/lib/agent-roster.mjs +1 -1
  122. package/templates/core/bin/lib/frontmatter.mjs +4 -2
  123. package/templates/core/bin/lib/harness-rules.mjs +169 -20
  124. package/templates/core/bin/lib/protected-paths.mjs +93 -5
  125. package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
  126. package/templates/core/bin/lib/verify-steps.mjs +29 -11
  127. package/templates/core/bin/verify.mjs +12 -0
  128. package/templates/core/eslint.config.js +5 -0
  129. package/templates/core/package.json +7 -7
  130. package/templates/core/vitest.config.ts +8 -1
  131. package/templates/packs/README.md +44 -24
  132. package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
  133. package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
  134. package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
  135. package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
  136. package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
  137. package/templates/packs/github/pack.json +19 -0
  138. package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
  139. package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
  140. package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
  141. package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
  142. package/templates/packs/harness-extras/pack.json +18 -13
  143. package/templates/packs/publishing/files/.changeset/README.md +25 -0
  144. package/templates/packs/publishing/files/.changeset/config.json +7 -0
  145. package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
  146. package/templates/packs/publishing/files/.github/workflows/release.yml +295 -0
  147. package/templates/packs/publishing/files/REUSE.toml +28 -0
  148. package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
  149. package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
  150. package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
  151. package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
  152. package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
  153. package/templates/packs/publishing/pack.json +41 -0
  154. package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
  155. package/templates/packs/quality/pack.json +29 -0
  156. package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
  157. package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
  158. package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
  159. package/templates/packs/supply-chain/pack.json +19 -0
  160. package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
  161. package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
  162. package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
  163. package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
  164. package/templates/packs/worktrees/files/.worktreeinclude +11 -0
  165. package/templates/packs/worktrees/pack.json +64 -0
  166. package/templates/packs/statusline/pack.json +0 -31
  167. /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
  168. /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
  169. /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
  170. /package/templates/packs/{harness-extras → quality}/files/bin/file-budget-baseline.json +0 -0
package/dist/report.d.ts CHANGED
@@ -1,4 +1,23 @@
1
1
  import type { Inventory } from "./inventory.js";
2
- /** Renders the full adoption report as Markdown. */
3
- export declare function renderReport(inventory: Inventory): string;
2
+ /**
3
+ * Renders the full adoption report as Markdown.
4
+ *
5
+ * @param inventory - The inventory this report describes.
6
+ * @param nextStep - The sentence that leads the `## Next step` paragraph in
7
+ * place of the generic "Open this project in Claude Code and run
8
+ * `/customize`." -- a leading `Next: ` is removed, the rest trimmed and its
9
+ * first character upper-cased; the rest of the paragraph is kept. When
10
+ * omitted, or blank once the `Next: ` prefix is removed and the rest
11
+ * trimmed, the output is the generic sentence, unchanged.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { renderReport } from "./report.js";
16
+ *
17
+ * // inventory: the value buildInventory (inventory.ts) returned for this run
18
+ * const markdown = renderReport(inventory, "Next: run the plugin's own /customize.");
19
+ * // "...## Next step\n\nRun the plugin's own /customize. It reads this report..."
20
+ * ```
21
+ */
22
+ export declare function renderReport(inventory: Inventory, nextStep?: string): string;
4
23
  //# sourceMappingURL=report.d.ts.map
package/dist/report.js CHANGED
@@ -1,4 +1,8 @@
1
+ // SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
2
+ // SPDX-License-Identifier: MIT
1
3
  import { CAP_LIMITS, countBaselineCaps } from "./caps.js";
4
+ import { STAGED_PACKS_DIR, STAGED_PACK_MANIFEST } from "./pack-stage.js";
5
+ import { STAGED_SUFFIX } from "./staging.js";
2
6
  /**
3
7
  * Estimates the post-merge total against each cap: the baseline's own count
4
8
  * plus whatever the existing project already has, on the (approximate)
@@ -187,10 +191,13 @@ function renderCapsSection(inventory) {
187
191
  ? "| --- | --- | --- | --- | --- | --- |"
188
192
  : "| --- | --- | --- | --- | --- |";
189
193
  const row = (label, key, existingCount, cap) => {
194
+ // The shown total (and its over-cap flag) includes the "+ all packs"
195
+ // column, so a pack that tips a cap over is flagged, not just listed.
196
+ const total = postMerge[key] + (hasPacks ? packBudget[key] : 0);
190
197
  const cells = [label, String(baseline[key]), String(existingCount)];
191
198
  if (hasPacks)
192
199
  cells.push(`+${packBudget[key]}`);
193
- cells.push(`${postMerge[key]}${overCap(postMerge[key], cap)}`, String(cap));
200
+ cells.push(`${total}${overCap(total, cap)}`, String(cap));
194
201
  return `| ${cells.join(" | ")} |`;
195
202
  };
196
203
  const lines = [
@@ -201,6 +208,8 @@ function renderCapsSection(inventory) {
201
208
  row("Agents", "agents", inventory.survey.harness.agents.length, CAP_LIMITS.agents),
202
209
  row("Skills", "skills", inventory.survey.harness.skills.length, CAP_LIMITS.skills),
203
210
  row("Hooks", "hooks", inventory.survey.harness.hooks.length, CAP_LIMITS.hooks),
211
+ row("Workflows", "workflows", inventory.survey.toolchain.workflows.files.length, CAP_LIMITS.workflows),
212
+ row("Scripts", "scripts", Object.keys(inventory.survey.toolchain.scripts).length, CAP_LIMITS.scripts),
204
213
  "",
205
214
  "This is an approximate count assuming no name overlap; `/customize`'s Step 0 resolves it for real.",
206
215
  ];
@@ -243,6 +252,14 @@ function renderPacksSection(inventory) {
243
252
  .replace(/\n{3,}/g, "\n\n")
244
253
  .trimEnd();
245
254
  }
255
+ /** The "; N staged for /customize at …" clause, or nothing when no file was actually staged. */
256
+ function stagedClause(stagedBaseline) {
257
+ const { dir, suffix, files } = stagedBaseline;
258
+ if (files.length === 0) {
259
+ return "";
260
+ }
261
+ return `; ${files.length} staged for /customize at ${dir}/ (inert copies, each with a ${suffix} suffix)`;
262
+ }
246
263
  function renderConflictsSection(inventory) {
247
264
  const { conflicts } = inventory;
248
265
  const absent = conflicts.filter((c) => c.status === "absent");
@@ -251,7 +268,7 @@ function renderConflictsSection(inventory) {
251
268
  const lines = [
252
269
  "## What groundwork would change",
253
270
  "",
254
- `- ${absent.length} file(s) would be added cleanly (no collision).`,
271
+ `- ${absent.length} file(s) would be added cleanly (no collision)${stagedClause(inventory.stagedBaseline)}.`,
255
272
  `- ${identical.length} file(s) already match the baseline.`,
256
273
  `- ${divergent.length} file(s) conflict and need a decision.`,
257
274
  ];
@@ -275,8 +292,78 @@ function renderUndeterminedSection(inventory) {
275
292
  }
276
293
  return lines.join("\n");
277
294
  }
278
- /** Renders the full adoption report as Markdown. */
279
- export function renderReport(inventory) {
295
+ /**
296
+ * The closing paragraph (plus its trailing blank line) about the inert staged
297
+ * copies -- the baseline's and the packs' -- or nothing when neither staged
298
+ * anything.
299
+ */
300
+ function renderStagedFilesNote(stagedBaseline, stagedPacks) {
301
+ const parts = [];
302
+ const dirs = [];
303
+ if (stagedBaseline.files.length > 0) {
304
+ parts.push(`the baseline files staged under \`${stagedBaseline.dir}/\``);
305
+ dirs.push(stagedBaseline.dir);
306
+ }
307
+ if (stagedPacks.length > 0) {
308
+ parts.push(`the ${stagedPacks.length} pack(s) staged under \`${STAGED_PACKS_DIR}/\` ` +
309
+ `(each pack's \`${STAGED_PACK_MANIFEST}\` included)`);
310
+ dirs.push(STAGED_PACKS_DIR);
311
+ }
312
+ const subject = parts.join(" and ");
313
+ if (subject === "") {
314
+ return [];
315
+ }
316
+ const ignoreLines = dirs.map((dir) => `\`${dir}/\``).join(" and a ");
317
+ return [
318
+ `${subject.charAt(0).toUpperCase()}${subject.slice(1)} are inert copies, never ` +
319
+ `installed: each carries a \`${STAGED_SUFFIX}\` suffix so no tool in this ` +
320
+ "project picks one up, and `/customize` installs one only after you " +
321
+ `confirm it. Decide whether to commit or ignore those \`${STAGED_SUFFIX}\` ` +
322
+ "files before your next commit: they are verbatim template copies, so " +
323
+ "a strict license-header check, or any gate that runs over every " +
324
+ `tracked file, may flag them. To keep them out of git, add a ` +
325
+ `${ignoreLines} line to \`.gitignore\`.`,
326
+ "",
327
+ ];
328
+ }
329
+ /** The `## Next step` lead sentence used when the caller supplies none. */
330
+ const DEFAULT_NEXT_STEP = "Open this project in Claude Code and run `/customize`.";
331
+ /**
332
+ * `nextStep` with a leading `Next: ` removed, trimmed, and its first
333
+ * character upper-cased; {@link DEFAULT_NEXT_STEP} when omitted or when
334
+ * nothing but whitespace is left.
335
+ */
336
+ function nextStepLead(nextStep) {
337
+ if (nextStep === undefined) {
338
+ return DEFAULT_NEXT_STEP;
339
+ }
340
+ const lead = (nextStep.startsWith("Next: ") ? nextStep.slice("Next: ".length) : nextStep).trim();
341
+ if (lead === "") {
342
+ return DEFAULT_NEXT_STEP;
343
+ }
344
+ return `${lead.charAt(0).toUpperCase()}${lead.slice(1)}`;
345
+ }
346
+ /**
347
+ * Renders the full adoption report as Markdown.
348
+ *
349
+ * @param inventory - The inventory this report describes.
350
+ * @param nextStep - The sentence that leads the `## Next step` paragraph in
351
+ * place of the generic "Open this project in Claude Code and run
352
+ * `/customize`." -- a leading `Next: ` is removed, the rest trimmed and its
353
+ * first character upper-cased; the rest of the paragraph is kept. When
354
+ * omitted, or blank once the `Next: ` prefix is removed and the rest
355
+ * trimmed, the output is the generic sentence, unchanged.
356
+ *
357
+ * @example
358
+ * ```ts
359
+ * import { renderReport } from "./report.js";
360
+ *
361
+ * // inventory: the value buildInventory (inventory.ts) returned for this run
362
+ * const markdown = renderReport(inventory, "Next: run the plugin's own /customize.");
363
+ * // "...## Next step\n\nRun the plugin's own /customize. It reads this report..."
364
+ * ```
365
+ */
366
+ export function renderReport(inventory, nextStep) {
280
367
  const sections = [
281
368
  "# Adoption report",
282
369
  "",
@@ -305,13 +392,14 @@ export function renderReport(inventory) {
305
392
  "",
306
393
  "## Next step",
307
394
  "",
308
- "Open this project in Claude Code and run `/customize`. It reads this " +
395
+ `${nextStepLead(nextStep)} It reads this ` +
309
396
  "report and `.groundwork/inventory.json`, does a deeper read of " +
310
397
  "anything above marked as needing one, and asks you to confirm before " +
311
398
  "changing anything. Did this report miss something about your " +
312
399
  "project? Say so when `/customize` asks -- that confirmation round " +
313
400
  "is the point where it's caught.",
314
401
  "",
402
+ ...renderStagedFilesNote(inventory.stagedBaseline, inventory.stagedPacks),
315
403
  "`.groundwork/` itself was not added to this project's `.gitignore` -- " +
316
404
  "that choice is yours. It's disposable (regenerate it any time by " +
317
405
  "re-running the CLI), so most projects gitignore it; some prefer to " +
@@ -0,0 +1,176 @@
1
+ import type { TokenTable } from "./tokens.js";
2
+ export { toPosixPath } from "./assets.js";
3
+ /**
4
+ * The suffix every staged file carries, so no extension-based glob
5
+ * (`**\/*.ts`, `**\/*.md`, `vitest.config.*`) ever matches a staged copy.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const staged = `eslint.config.js${STAGED_SUFFIX}`; // "eslint.config.js.staged"
10
+ * ```
11
+ */
12
+ export declare const STAGED_SUFFIX = ".staged";
13
+ /**
14
+ * The staged name for a project-relative `path`: `path` + {@link STAGED_SUFFIX}.
15
+ * The single derivation every stager and adopt mode's write-scope check use.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * stagedNameFor("src/index.ts"); // "src/index.ts.staged"
20
+ * ```
21
+ */
22
+ export declare function stagedNameFor(path: string): string;
23
+ /**
24
+ * Finds the first pair of staged paths that would land on the same file on
25
+ * some supported file system: two paths equal once folded -- NFC-normalized
26
+ * (macOS's APFS treats a precomposed `é` and `e` + U+0301 as one name) and
27
+ * case-folded (macOS and Windows default to case-insensitive) -- or one path
28
+ * a proper directory prefix of another once folded (`x.staged` as both a
29
+ * file and the directory holding `x.staged/y.staged`). Either collision
30
+ * makes the second exclusive (`wx`) write fail mid-staging; a plan checks
31
+ * for it first so the defect surfaces as its own error instead. Folding is
32
+ * `toLowerCase()` after `normalize("NFC")`, not a full Unicode case fold, so
33
+ * it is a best-effort approximation of each file system's own rules. Paths
34
+ * are otherwise compared as given -- pass them `/`-separated
35
+ * ({@link toPosixPath}).
36
+ *
37
+ * @returns A description naming both colliding paths, or `undefined` when
38
+ * none collide.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * findStagedPathCollision(["README.md.staged", "readme.md.staged"]); // "README.md.staged and readme.md.staged …"
43
+ * findStagedPathCollision(["a.staged", "b.staged"]); // undefined
44
+ * ```
45
+ */
46
+ export declare function findStagedPathCollision(stagedPaths: readonly string[]): string | undefined;
47
+ /**
48
+ * Maps every file under `root` to its install path (tokens applied, dotfile
49
+ * name restored) -- the same derivation `planConflicts` uses -- keyed by
50
+ * install path, valued by absolute source path.
51
+ *
52
+ * @throws `Error` (no `cause`) naming the install path and both sources when
53
+ * two files map to the same install path (`_gitignore` beside `.gitignore`),
54
+ * rather than silently letting the later one win.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const files = collectTemplateFiles("/repo/templates/core", { PROJECT_NAME: "acme" });
59
+ * files.get(".gitignore"); // "/repo/templates/core/_gitignore"
60
+ * ```
61
+ */
62
+ export declare function collectTemplateFiles(root: string, tokens: TokenTable): Map<string, string>;
63
+ /**
64
+ * Refuses a staging plan computed for a different `.groundwork/` than the
65
+ * one a stager was handed: the plan's scope-checked paths would otherwise
66
+ * not be the paths written. Directories are compared after `path.resolve`,
67
+ * so two spellings of one directory match. A caller defect, so a plain
68
+ * `Error` (never an `AssertionError`), thrown before anything is written.
69
+ *
70
+ * @throws `Error` naming both directories when they differ.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * assertPlanBuiltFor("stagePacks", plan.groundworkDir, groundworkDir);
75
+ * ```
76
+ */
77
+ export declare function assertPlanBuiltFor(caller: string, planGroundworkDir: string, groundworkDir: string): void;
78
+ /**
79
+ * One staging area under `.groundwork/`: the directory `dirName` that
80
+ * {@link stageAtomically} writes, and the `noun` its messages use.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const target: StagingTarget = {
85
+ * groundworkDir: "/work/app/.groundwork",
86
+ * dirName: "packs",
87
+ * noun: "packs",
88
+ * plural: true,
89
+ * };
90
+ * ```
91
+ */
92
+ export interface StagingTarget {
93
+ /** The `.groundwork/` directory the staging area lives in. */
94
+ readonly groundworkDir: string;
95
+ /** The staging directory's name under `groundworkDir`; its work directories are named `.<dirName>-XXXXXX`. */
96
+ readonly dirName: string;
97
+ /** What is being staged, for messages: `"baseline"`, `"packs"`. */
98
+ readonly noun: string;
99
+ /** Whether `noun` takes a plural verb in messages ("the previous packs were", not "was"). */
100
+ readonly plural: boolean;
101
+ }
102
+ /**
103
+ * The first step of every staging run: refuses a symlinked `groundworkDir`
104
+ * or `<groundworkDir>/<dirName>` (before anything is deleted or written),
105
+ * then sweeps the `.<dirName>-*` work directories a crashed earlier run
106
+ * left. The sweep removes **every** entry of the CLI-owned `.groundwork/`
107
+ * whose name matches `.<dirName>-*`, whatever created it -- including a
108
+ * concurrent run's in-progress work directory, so two runs against the same
109
+ * directory can fail each other with the standard incomplete/re-run error;
110
+ * a failure to remove one only warns. Returns the staging directory's path.
111
+ *
112
+ * @throws `Error` naming the path when either directory is a symlink; the
113
+ * standard `.groundwork/ is incomplete -- re-run` `Error`, with `cause`, when
114
+ * `groundworkDir` cannot be listed for the sweep.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * const destDir = prepareStaging({ groundworkDir, dirName: "packs", noun: "packs", plural: true });
119
+ * ```
120
+ */
121
+ export declare function prepareStaging(target: StagingTarget): string;
122
+ /**
123
+ * Removes the staging directory outright -- what a run with nothing to
124
+ * stage does, so no previous staging outlives it.
125
+ *
126
+ * @throws {@link incompleteStagingError}, with the removal failure as `cause`.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * clearStaging({ groundworkDir, dirName: "packs", noun: "packs" });
131
+ * ```
132
+ */
133
+ export declare function clearStaging(target: StagingTarget): void;
134
+ /**
135
+ * Writes `bytes` to `<rootDir>/<stagedName>` with the exclusive `wx` flag
136
+ * (creating parent directories) and returns their lowercase hex sha256.
137
+ * Re-asserts the CWE-22 containment invariant (docs/assurance-case.md)
138
+ * against the directory actually written to; the caller's plan must already
139
+ * have refused an escaping name.
140
+ *
141
+ * @throws `AssertionError` (message prefixed by `caller`) when the staged
142
+ * path escapes `rootDir`; any write error unchanged.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const sha256 = writeStagedBytes("stagePacks", bytes, newDir, "pack.json.staged");
147
+ * ```
148
+ */
149
+ export declare function writeStagedBytes(caller: string, bytes: Uint8Array, rootDir: string, stagedName: string): string;
150
+ /**
151
+ * Runs `write` against a fresh `<groundworkDir>/.<dirName>-XXXXXX/<dirName>`
152
+ * directory and, only once it returns, swaps that directory over
153
+ * `<groundworkDir>/<dirName>` by rename -- so a failure part-way through
154
+ * leaves any previous staging intact, and a previous staging is replaced
155
+ * wholesale. Call {@link prepareStaging} first.
156
+ *
157
+ * - If the swap's final rename fails, the previous staging is renamed back.
158
+ * Only if that restore also fails is the staging directory left absent:
159
+ * the previous staging then survives, parked inside the work directory,
160
+ * which is deliberately not removed (a later run's stale-dir sweep does).
161
+ * - The work directory is otherwise always removed; a failure to remove it
162
+ * only warns, naming its path.
163
+ *
164
+ * @throws {@link ParkedStagingError} when both renames fail; an
165
+ * `AssertionError` unwrapped (a broken invariant is a bug, not something a
166
+ * re-run fixes); otherwise {@link incompleteStagingError} with `cause`.
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * const files = stageAtomically(target, (newDir) =>
171
+ * [writeStagedBytes("stagePacks", bytes, newDir, "a.txt.staged")],
172
+ * );
173
+ * ```
174
+ */
175
+ export declare function stageAtomically<T>(target: StagingTarget, write: (newDir: string) => T): T;
176
+ //# sourceMappingURL=staging.d.ts.map