@monte3l/groundwork 1.0.0-rc.2 → 1.0.0-rc.3
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/README.md +13 -5
- package/bin/m3l-groundwork.mjs +36 -2
- package/dist/assets.d.ts +10 -0
- package/dist/assets.js +14 -0
- package/dist/baseline-stage.d.ts +173 -0
- package/dist/baseline-stage.js +215 -0
- package/dist/caps.d.ts +4 -1
- package/dist/caps.js +17 -3
- package/dist/conflicts.d.ts +23 -2
- package/dist/conflicts.js +89 -11
- package/dist/customize-paths.d.ts +92 -0
- package/dist/customize-paths.js +115 -0
- package/dist/emit.d.ts +50 -1
- package/dist/emit.js +121 -13
- package/dist/fatal.d.ts +70 -0
- package/dist/fatal.js +132 -0
- package/dist/format-error.d.ts +113 -0
- package/dist/format-error.js +553 -0
- package/dist/fs-guard.d.ts +142 -0
- package/dist/fs-guard.js +222 -0
- package/dist/git.js +10 -1
- package/dist/harness/conformance.js +2 -0
- package/dist/harness/frontmatter.js +2 -13
- package/dist/harness/grade.js +37 -8
- package/dist/harness/rules.d.ts +20 -3
- package/dist/harness/rules.js +108 -6
- package/dist/harness/types.d.ts +13 -0
- package/dist/harness/types.js +3 -0
- package/dist/inventory.d.ts +77 -3
- package/dist/inventory.js +103 -12
- package/dist/jsonc.d.ts +49 -2
- package/dist/jsonc.js +140 -7
- package/dist/main.d.ts +62 -3
- package/dist/main.js +538 -67
- package/dist/merge-json.d.ts +47 -4
- package/dist/merge-json.js +120 -8
- package/dist/mode.js +12 -2
- package/dist/pack-stage.d.ts +210 -0
- package/dist/pack-stage.js +287 -0
- package/dist/packs.d.ts +19 -13
- package/dist/packs.js +231 -28
- package/dist/palette.d.ts +23 -0
- package/dist/palette.js +22 -0
- package/dist/plugin.d.ts +122 -6
- package/dist/plugin.js +687 -47
- package/dist/report.d.ts +21 -2
- package/dist/report.js +93 -5
- package/dist/staging.d.ts +176 -0
- package/dist/staging.js +375 -0
- package/dist/survey/fs-walk.d.ts +34 -2
- package/dist/survey/fs-walk.js +70 -5
- package/dist/survey/internal/blocked-path.d.ts +27 -0
- package/dist/survey/internal/blocked-path.js +129 -0
- package/dist/survey/internal/package-json.d.ts +13 -0
- package/dist/survey/internal/package-json.js +37 -0
- package/dist/survey/internal/read-guard.d.ts +178 -0
- package/dist/survey/internal/read-guard.js +276 -0
- package/dist/survey/survey-docs.d.ts +17 -2
- package/dist/survey/survey-docs.js +61 -21
- package/dist/survey/survey-harness.d.ts +20 -2
- package/dist/survey/survey-harness.js +87 -46
- package/dist/survey/survey-shape.d.ts +17 -2
- package/dist/survey/survey-shape.js +60 -46
- package/dist/survey/survey-toolchain.d.ts +16 -1
- package/dist/survey/survey-toolchain.js +69 -66
- package/dist/survey/survey.js +6 -4
- package/dist/survey/types.d.ts +79 -0
- package/dist/survey/types.js +2 -6
- package/dist/term.d.ts +80 -0
- package/dist/term.js +145 -0
- package/dist/tokens.js +2 -0
- package/dist/toolchain/conformance.js +2 -0
- package/dist/toolchain/grade.js +26 -8
- package/dist/toolchain/rules.d.ts +13 -4
- package/dist/toolchain/rules.js +16 -0
- package/dist/toolchain/tsconfig-chain.d.ts +2 -0
- package/dist/toolchain/tsconfig-chain.js +32 -8
- package/dist/toolchain/types.d.ts +16 -4
- package/dist/toolchain/types.js +3 -0
- package/package.json +4 -3
- package/plugin/skills/customize/SKILL.md +292 -18
- package/plugin/src/domain-map.ts +39 -14
- package/plugin/src/index.ts +5 -1
- package/plugin/src/kind-facet-map.ts +25 -8
- package/plugin/src/pack-map.ts +147 -25
- package/plugin/src/plugin-map.ts +236 -0
- package/templates/core/.claude/agents/Explore.md +0 -1
- package/templates/core/.claude/agents/code-implementer.md +4 -4
- package/templates/core/.claude/agents/code-reviewer.md +4 -4
- package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
- package/templates/core/.claude/agents/test-author.md +7 -5
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
- package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
- package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
- package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
- package/templates/core/.claude/rules/agent-dispatch.md +7 -0
- package/templates/core/.claude/rules/tests.md +2 -2
- package/templates/core/.claude/settings.json +5 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
- package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
- package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
- package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
- package/templates/core/.github/dependabot.yml +18 -0
- package/templates/core/.github/workflows/ci.yml +15 -15
- package/templates/core/.github/workflows/dependency-review.yml +2 -2
- package/templates/core/.github/workflows/security-audit.yml +10 -4
- package/templates/core/.prettierignore +4 -0
- package/templates/core/CLAUDE.md +53 -3
- package/templates/core/README.md +30 -10
- package/templates/core/_gitignore +17 -0
- package/templates/core/bin/check-exports.mjs +11 -5
- package/templates/core/bin/lib/agent-roster.mjs +1 -1
- package/templates/core/bin/lib/frontmatter.mjs +4 -2
- package/templates/core/bin/lib/harness-rules.mjs +169 -20
- package/templates/core/bin/lib/protected-paths.mjs +93 -5
- package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
- package/templates/core/bin/lib/verify-steps.mjs +29 -11
- package/templates/core/bin/verify.mjs +12 -0
- package/templates/core/eslint.config.js +5 -0
- package/templates/core/package.json +7 -7
- package/templates/core/vitest.config.ts +8 -1
- package/templates/packs/README.md +35 -24
- package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
- package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
- package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
- package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
- package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
- package/templates/packs/github/pack.json +19 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
- package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
- package/templates/packs/harness-extras/pack.json +18 -13
- package/templates/packs/publishing/files/.changeset/README.md +25 -0
- package/templates/packs/publishing/files/.changeset/config.json +7 -0
- package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
- package/templates/packs/publishing/files/.github/workflows/release.yml +293 -0
- package/templates/packs/publishing/files/REUSE.toml +28 -0
- package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
- package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
- package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
- package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
- package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
- package/templates/packs/publishing/pack.json +35 -0
- package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
- package/templates/packs/quality/pack.json +29 -0
- package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
- package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
- package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
- package/templates/packs/supply-chain/pack.json +19 -0
- package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
- package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
- package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
- package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
- package/templates/packs/worktrees/files/.worktreeinclude +11 -0
- package/templates/packs/worktrees/pack.json +64 -0
- package/templates/packs/statusline/pack.json +0 -31
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
- /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
- /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
|
-
/**
|
|
3
|
-
|
|
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(`${
|
|
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
|
-
/**
|
|
279
|
-
|
|
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
|
-
|
|
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
|