@bensandee/tooling 0.87.2 → 0.88.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/README.md +1 -1
- package/dist/bin.mjs +16 -160
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -332,7 +332,7 @@ The feature has two halves: a **CI history** that captures one record per commit
|
|
|
332
332
|
|
|
333
333
|
#### How it works
|
|
334
334
|
|
|
335
|
-
`repo:sync` enables coverage tracking automatically when a `vitest.config.ts` is present. The generated `vitest.config.ts` includes the `json-summary` reporter so each `pnpm test:coverage` writes `coverage/coverage-summary.json` alongside the human-readable HTML report.
|
|
335
|
+
`repo:sync` enables coverage tracking automatically when a `vitest.config.ts` is present. The generated `vitest.config.ts` includes the `json-summary` reporter so each `pnpm test:coverage` writes `coverage/coverage-summary.json` alongside the human-readable HTML report. The generated CI workflow (`ci.yml`, or `check.yml` for a non-changesets strategy) runs the coverage steps on push to the default branch and pipes:
|
|
336
336
|
|
|
337
337
|
```text
|
|
338
338
|
pnpm test:coverage -> coverage:summary -> coverage:record -> coverage:status
|
package/dist/bin.mjs
CHANGED
|
@@ -269,7 +269,7 @@ function loadEnvFileForBin(rawArgs, bin, io = processIo) {
|
|
|
269
269
|
*/
|
|
270
270
|
const envFileLoad = loadEnvFileForBin(process.argv.slice(2), {
|
|
271
271
|
name: "bst",
|
|
272
|
-
version: "0.
|
|
272
|
+
version: "0.88.0"
|
|
273
273
|
});
|
|
274
274
|
//#endregion
|
|
275
275
|
//#region ../cli-kit/src/logger.ts
|
|
@@ -7029,8 +7029,6 @@ const MANAGED_SCRIPTS = {
|
|
|
7029
7029
|
function matchesManagedScript(scriptValue, expectedFragment) {
|
|
7030
7030
|
return scriptValue.includes(expectedFragment);
|
|
7031
7031
|
}
|
|
7032
|
-
/** Deprecated scripts to remove during migration. */
|
|
7033
|
-
const DEPRECATED_SCRIPTS = ["tooling:init", "tooling:update"];
|
|
7034
7032
|
/**
|
|
7035
7033
|
* DevDeps for each project/package (single repo) or per-package (monorepo),
|
|
7036
7034
|
* filtered by what the stack actually uses. `typescript` is dropped (not just
|
|
@@ -7360,10 +7358,6 @@ function mergeManagedScripts(pkg, allScripts, changes) {
|
|
|
7360
7358
|
changes.push(`updated script: ${key}`);
|
|
7361
7359
|
}
|
|
7362
7360
|
}
|
|
7363
|
-
for (const key of DEPRECATED_SCRIPTS) if (key in existingScripts) {
|
|
7364
|
-
delete existingScripts[key];
|
|
7365
|
-
changes.push(`removed deprecated script: ${key}`);
|
|
7366
|
-
}
|
|
7367
7361
|
pkg.scripts = existingScripts;
|
|
7368
7362
|
}
|
|
7369
7363
|
/**
|
|
@@ -7593,9 +7587,9 @@ function buildManagedDevDeps(ctx) {
|
|
|
7593
7587
|
const isMonorepo = ctx.config.structure === "monorepo";
|
|
7594
7588
|
const devDeps = { ...rootDevDeps(ctx.config) };
|
|
7595
7589
|
if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
|
|
7596
|
-
if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.
|
|
7597
|
-
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.
|
|
7598
|
-
if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.
|
|
7590
|
+
if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.15.0";
|
|
7591
|
+
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.88.0";
|
|
7592
|
+
if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.17.0";
|
|
7599
7593
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
7600
7594
|
"@changesets/cli": "3.0.3",
|
|
7601
7595
|
"@release-it/bumper": "8.0.1",
|
|
@@ -10579,41 +10573,6 @@ export default {${ignoreBinariesField(ignoreBinaries)}
|
|
|
10579
10573
|
} satisfies KnipConfig;
|
|
10580
10574
|
`;
|
|
10581
10575
|
}
|
|
10582
|
-
/**
|
|
10583
|
-
* What earlier versions of this generator wrote. A repo that still carries one verbatim
|
|
10584
|
-
* is carrying our redundant ignores, and its `knip` step still reports the two config
|
|
10585
|
-
* hints -- so we rewrite it to the current template. Only a byte-identical match (modulo
|
|
10586
|
-
* the repo's formatter) qualifies: a config the user has edited is theirs, hints or not.
|
|
10587
|
-
*/
|
|
10588
|
-
function buildLegacyKnipConfigSingle(ignoreBinaries) {
|
|
10589
|
-
return `import type { KnipConfig } from "knip";
|
|
10590
|
-
|
|
10591
|
-
export default {
|
|
10592
|
-
entry: ["src/index.ts"],
|
|
10593
|
-
project: ["src/**/*.ts"],
|
|
10594
|
-
ignore: ["dist/**", ".claude/hooks/**"],${ignoreBinariesField(ignoreBinaries)}
|
|
10595
|
-
} satisfies KnipConfig;
|
|
10596
|
-
`;
|
|
10597
|
-
}
|
|
10598
|
-
function buildLegacyKnipConfigMonorepo(ignoreBinaries) {
|
|
10599
|
-
return `import type { KnipConfig } from "knip";
|
|
10600
|
-
|
|
10601
|
-
export default {
|
|
10602
|
-
ignore: [".claude/hooks/**"],${ignoreBinariesField(ignoreBinaries)}
|
|
10603
|
-
workspaces: {
|
|
10604
|
-
".": {
|
|
10605
|
-
entry: [],
|
|
10606
|
-
project: [],
|
|
10607
|
-
},
|
|
10608
|
-
"packages/*": {
|
|
10609
|
-
entry: ["src/index.ts", "src/bin.ts"],
|
|
10610
|
-
project: ["src/**/*.ts"],
|
|
10611
|
-
ignore: ["dist/**"],
|
|
10612
|
-
},
|
|
10613
|
-
},
|
|
10614
|
-
} satisfies KnipConfig;
|
|
10615
|
-
`;
|
|
10616
|
-
}
|
|
10617
10576
|
const REQUIRED_IGNORE = ".claude/hooks/**";
|
|
10618
10577
|
/** The file bst deploys into the consumer's repo, and the only thing REQUIRED_IGNORE defends. */
|
|
10619
10578
|
const HOOK_FILE = ".claude/hooks/bst-hook.mjs";
|
|
@@ -10774,14 +10733,6 @@ async function generateKnip(ctx) {
|
|
|
10774
10733
|
const buildConfig = isMonorepo ? buildKnipConfigMonorepo : buildKnipConfigSingle;
|
|
10775
10734
|
const existingPath = KNIP_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
10776
10735
|
if (existingPath) {
|
|
10777
|
-
if (existingPath === filePath && isLegacyTemplate(ctx, filePath, isMonorepo, ignoreBinaries)) {
|
|
10778
|
-
ctx.write(filePath, formattedTemplate(ctx, filePath, buildConfig(ignoreBinaries)));
|
|
10779
|
-
return {
|
|
10780
|
-
filePath,
|
|
10781
|
-
action: "updated",
|
|
10782
|
-
description: "Dropped ignore patterns that are already outside knip's project scope"
|
|
10783
|
-
};
|
|
10784
|
-
}
|
|
10785
10736
|
const content = ctx.read(existingPath);
|
|
10786
10737
|
if (content === void 0) return {
|
|
10787
10738
|
filePath: existingPath,
|
|
@@ -10819,21 +10770,9 @@ async function generateKnip(ctx) {
|
|
|
10819
10770
|
description: createdDescription(filePath)
|
|
10820
10771
|
};
|
|
10821
10772
|
}
|
|
10822
|
-
/**
|
|
10823
|
-
* Whether the config on disk is one this generator wrote before the redundant ignores were
|
|
10824
|
-
* dropped. Both the currently-detected `ignoreBinaries` and the empty form are candidates:
|
|
10825
|
-
* a repo that gained a `composer.json` after its last sync carries the older, binary-less
|
|
10826
|
-
* variant, and it deserves the same cleanup.
|
|
10827
|
-
*/
|
|
10828
|
-
function isLegacyTemplate(ctx, filePath, isMonorepo, ignoreBinaries) {
|
|
10829
|
-
const build = isMonorepo ? buildLegacyKnipConfigMonorepo : buildLegacyKnipConfigSingle;
|
|
10830
|
-
return (ignoreBinaries.length > 0 ? [build(ignoreBinaries), build([])] : [build([])]).some((legacy) => isGeneratedTemplate(ctx, filePath, legacy));
|
|
10831
|
-
}
|
|
10832
10773
|
//#endregion
|
|
10833
10774
|
//#region src/generators/renovate.ts
|
|
10834
10775
|
const SHARED_PRESET = "local>bensandee/tooling";
|
|
10835
|
-
/** Deprecated npm-based preset to migrate away from. */
|
|
10836
|
-
const LEGACY_PRESET = "@bensandee/config";
|
|
10837
10776
|
/** All known renovate config file locations, in priority order. */
|
|
10838
10777
|
const RENOVATE_CONFIG_PATHS = [
|
|
10839
10778
|
"renovate.json",
|
|
@@ -10856,17 +10795,6 @@ async function generateRenovate(ctx) {
|
|
|
10856
10795
|
if (existing) {
|
|
10857
10796
|
const parsed = parseRenovateJson(existing);
|
|
10858
10797
|
const existingExtends = parsed.extends ?? [];
|
|
10859
|
-
const legacyIndex = existingExtends.indexOf(LEGACY_PRESET);
|
|
10860
|
-
if (legacyIndex !== -1) {
|
|
10861
|
-
existingExtends[legacyIndex] = SHARED_PRESET;
|
|
10862
|
-
parsed.extends = existingExtends;
|
|
10863
|
-
ctx.write(existingPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
10864
|
-
return {
|
|
10865
|
-
filePath: existingPath,
|
|
10866
|
-
action: "updated",
|
|
10867
|
-
description: `Migrated extends: ${LEGACY_PRESET} -> ${SHARED_PRESET}`
|
|
10868
|
-
};
|
|
10869
|
-
}
|
|
10870
10798
|
if (!existingExtends.includes(SHARED_PRESET)) {
|
|
10871
10799
|
existingExtends.unshift(SHARED_PRESET);
|
|
10872
10800
|
parsed.extends = existingExtends;
|
|
@@ -11088,22 +11016,6 @@ function parseClaudeSettings(raw) {
|
|
|
11088
11016
|
return;
|
|
11089
11017
|
}
|
|
11090
11018
|
}
|
|
11091
|
-
/**
|
|
11092
|
-
* Instructions previously emitted to `.claude/settings.json#instructions`.
|
|
11093
|
-
* Migrated to a managed block in CLAUDE.md (see `claude-md.ts`); on sync we
|
|
11094
|
-
* strip any existing entry that matches (by either-direction prefix, mirroring
|
|
11095
|
-
* the upgrade-in-place semantics the merge logic used to apply on add).
|
|
11096
|
-
*/
|
|
11097
|
-
const MIGRATED_INSTRUCTIONS = [
|
|
11098
|
-
"Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`.",
|
|
11099
|
-
"No typecasts (as/any). Use zod schemas, type guards, or narrowing instead.",
|
|
11100
|
-
"Fix lint violations instead of suppressing them. Only add disable comments when suppression is genuinely the best option. For no-empty-function: add a `{ /* no-op */ }` comment body instead of a disable comment.",
|
|
11101
|
-
"Prefer extensionless imports; if an extension is required, use .ts over .js.",
|
|
11102
|
-
"The bst-hook manages Bash/file permissions. See .claude/hooks/bst-hook-guide.md for auto-approved, denied, and passthrough commands."
|
|
11103
|
-
];
|
|
11104
|
-
function isMigratedInstruction(s) {
|
|
11105
|
-
return MIGRATED_INSTRUCTIONS.some((c) => s.startsWith(c) || c.startsWith(s));
|
|
11106
|
-
}
|
|
11107
11019
|
function buildSettings(ctx) {
|
|
11108
11020
|
const allow = [];
|
|
11109
11021
|
const enabledPlugins = { "code-simplifier@claude-plugins-official": true };
|
|
@@ -11127,15 +11039,6 @@ function buildSettings(ctx) {
|
|
|
11127
11039
|
};
|
|
11128
11040
|
}
|
|
11129
11041
|
const BST_HOOK_MARKER = "bst-hook.mjs";
|
|
11130
|
-
/** Remove old per-tool bst-hook entries that are now covered by a consolidated matcher. */
|
|
11131
|
-
function removeObsoleteBstHookEntries(matchers, consolidatedMatcher) {
|
|
11132
|
-
const consolidatedTools = new Set(consolidatedMatcher.split("|"));
|
|
11133
|
-
return matchers.filter((m) => {
|
|
11134
|
-
if (m.matcher === void 0 || !consolidatedTools.has(m.matcher)) return true;
|
|
11135
|
-
if (!m.hooks.some((h) => h.command?.includes(BST_HOOK_MARKER) === true)) return true;
|
|
11136
|
-
return false;
|
|
11137
|
-
});
|
|
11138
|
-
}
|
|
11139
11042
|
/**
|
|
11140
11043
|
* Merge a single generated matcher into `mergedHooks` for the given event,
|
|
11141
11044
|
* returning the number of changes made (for change tracking).
|
|
@@ -11156,15 +11059,8 @@ function mergeGeneratedMatcher(mergedHooks, existingMatchers, event, gm) {
|
|
|
11156
11059
|
}
|
|
11157
11060
|
return 0;
|
|
11158
11061
|
}
|
|
11159
|
-
let changes = 0;
|
|
11160
|
-
if (gm.matcher !== void 0 && gm.matcher.includes("|")) {
|
|
11161
|
-
const before = (mergedHooks[event] ?? []).length;
|
|
11162
|
-
mergedHooks[event] = removeObsoleteBstHookEntries(mergedHooks[event] ?? [], gm.matcher);
|
|
11163
|
-
changes += before - (mergedHooks[event] ?? []).length;
|
|
11164
|
-
}
|
|
11165
11062
|
mergedHooks[event] = [...mergedHooks[event] ?? [], gm];
|
|
11166
|
-
|
|
11167
|
-
return changes;
|
|
11063
|
+
return 1;
|
|
11168
11064
|
}
|
|
11169
11065
|
/** Remove empty optional fields to keep the file clean. */
|
|
11170
11066
|
function serializeSettings$1(settings) {
|
|
@@ -11188,9 +11084,6 @@ function writeOrMergeSettings(ctx, filePath, generated) {
|
|
|
11188
11084
|
const { settings, rawJson } = parsed;
|
|
11189
11085
|
const missingAllow = generated.permissions.allow.filter((rule) => !settings.permissions.allow.includes(rule));
|
|
11190
11086
|
const missingDeny = generated.permissions.deny.filter((rule) => !settings.permissions.deny.includes(rule));
|
|
11191
|
-
const filteredInstructions = settings.instructions.filter((s) => !isMigratedInstruction(s));
|
|
11192
|
-
const instructionChanges = settings.instructions.length - filteredInstructions.length;
|
|
11193
|
-
const mergedInstructions = filteredInstructions;
|
|
11194
11087
|
let hookChanges = 0;
|
|
11195
11088
|
const mergedHooks = { ...settings.hooks };
|
|
11196
11089
|
for (const [event, generatedMatchers] of Object.entries(generated.hooks)) {
|
|
@@ -11199,11 +11092,11 @@ function writeOrMergeSettings(ctx, filePath, generated) {
|
|
|
11199
11092
|
}
|
|
11200
11093
|
const missingPlugins = Object.entries(generated.enabledPlugins).filter(([key]) => !(key in settings.enabledPlugins));
|
|
11201
11094
|
const missingMarketplaces = Object.entries(generated.extraKnownMarketplaces).filter(([key]) => !(key in settings.extraKnownMarketplaces));
|
|
11202
|
-
const changed = missingAllow.length + missingDeny.length + hookChanges +
|
|
11095
|
+
const changed = missingAllow.length + missingDeny.length + hookChanges + missingPlugins.length + missingMarketplaces.length;
|
|
11203
11096
|
if (changed === 0) return {
|
|
11204
11097
|
filePath,
|
|
11205
11098
|
action: "skipped",
|
|
11206
|
-
description: "Already has all rules and
|
|
11099
|
+
description: "Already has all rules and hooks"
|
|
11207
11100
|
};
|
|
11208
11101
|
rawJson["permissions"] = {
|
|
11209
11102
|
...settings.permissions,
|
|
@@ -11211,7 +11104,7 @@ function writeOrMergeSettings(ctx, filePath, generated) {
|
|
|
11211
11104
|
deny: [...settings.permissions.deny, ...missingDeny]
|
|
11212
11105
|
};
|
|
11213
11106
|
if (Object.keys(mergedHooks).length > 0) rawJson["hooks"] = mergedHooks;
|
|
11214
|
-
if (
|
|
11107
|
+
if (settings.instructions.length > 0) rawJson["instructions"] = settings.instructions;
|
|
11215
11108
|
else delete rawJson["instructions"];
|
|
11216
11109
|
const updatedPlugins = { ...settings.enabledPlugins };
|
|
11217
11110
|
for (const [key, value] of missingPlugins) updatedPlugins[key] = value;
|
|
@@ -11225,7 +11118,7 @@ function writeOrMergeSettings(ctx, filePath, generated) {
|
|
|
11225
11118
|
return {
|
|
11226
11119
|
filePath,
|
|
11227
11120
|
action: "updated",
|
|
11228
|
-
description: `Updated ${String(changed)} rules/
|
|
11121
|
+
description: `Updated ${String(changed)} rules/hooks`
|
|
11229
11122
|
};
|
|
11230
11123
|
}
|
|
11231
11124
|
ctx.write(filePath, serializeSettings$1(generated));
|
|
@@ -11628,46 +11521,6 @@ async function generateVscodeSettings(ctx) {
|
|
|
11628
11521
|
return results;
|
|
11629
11522
|
}
|
|
11630
11523
|
//#endregion
|
|
11631
|
-
//#region src/generators/coverage-ci.ts
|
|
11632
|
-
/**
|
|
11633
|
-
* Cleanup-only generator: coverage steps now run inline in the CI workflow (see generateCi), so
|
|
11634
|
-
* any legacy `coverage.yml` left over from an older sync is removed.
|
|
11635
|
-
*
|
|
11636
|
-
* The result is `removed`, not `archived`: `repo:sync --check` counts only the former as drift,
|
|
11637
|
-
* and the agent's workflow sync sweep acts only on drift the check reports. An `archived` result
|
|
11638
|
-
* let a plain `repo:sync` delete a file while `--check` called the repo up to date.
|
|
11639
|
-
*
|
|
11640
|
-
* The ignore comment is the only opt-out here, and there is deliberately no managed-header guard:
|
|
11641
|
-
* this file predates the header, so no legacy `coverage.yml` in a synced repo carries one. Requiring
|
|
11642
|
-
* it would leave every file this generator exists to remove exactly where it is.
|
|
11643
|
-
*/
|
|
11644
|
-
async function generateCoverageCi(ctx) {
|
|
11645
|
-
const filePath = "coverage-ci";
|
|
11646
|
-
if (ctx.config.ci === "none") return {
|
|
11647
|
-
filePath,
|
|
11648
|
-
action: "skipped",
|
|
11649
|
-
description: "CI workflow not requested"
|
|
11650
|
-
};
|
|
11651
|
-
const workflowPath = ctx.config.ci === "github" ? ".github/workflows/coverage.yml" : ".forgejo/workflows/coverage.yml";
|
|
11652
|
-
const existing = ctx.read(workflowPath);
|
|
11653
|
-
if (existing === void 0) return {
|
|
11654
|
-
filePath,
|
|
11655
|
-
action: "skipped",
|
|
11656
|
-
description: "Coverage runs inline in ci.yml"
|
|
11657
|
-
};
|
|
11658
|
-
if (isToolingIgnored(existing)) return {
|
|
11659
|
-
filePath: workflowPath,
|
|
11660
|
-
action: "skipped",
|
|
11661
|
-
description: "coverage.yml has ignore comment"
|
|
11662
|
-
};
|
|
11663
|
-
ctx.remove(workflowPath);
|
|
11664
|
-
return {
|
|
11665
|
-
filePath: workflowPath,
|
|
11666
|
-
action: "removed",
|
|
11667
|
-
description: `Moved to ${ARCHIVE_DIR$1}/${workflowPath} (coverage now runs inline in ci.yml)`
|
|
11668
|
-
};
|
|
11669
|
-
}
|
|
11670
|
-
//#endregion
|
|
11671
11524
|
//#region src/docs-check/blocks.ts
|
|
11672
11525
|
/**
|
|
11673
11526
|
* A line that opens something other than a labelled-line continuation: a `Key:`-style
|
|
@@ -12396,6 +12249,7 @@ const COMMENTS_GUIDE_BODY = [
|
|
|
12396
12249
|
"- Default to none. A comment earns its place by carrying what the code cannot -- a rejected alternative, the bug that forced this shape, an invariant enforced elsewhere -- and earns it one decision at a time, in a sentence or two each: what was rejected, and what it cost. Five decisions is five sentences, not five paragraphs. Recoverable from the code? Leave it unwritten.",
|
|
12397
12250
|
"- Present tense, about what is. No `now`, `previously`, `changed to`: diff narration is stale on the next edit, and the commit message carries it. No running ledgers or changelogs: when adjusting a threshold, constant, or baseline, state the active invariant and prune past deltas.",
|
|
12398
12251
|
"- Never cite a lifecycle-managed doc, PR, or issue from code: no PR or issue numbers (`PR #123`, `closes #456`), no path under `docs/superpowers/` or `docs/design/`, none carrying a `retired/` or `completed/` segment, no `docs/plan-*.md`, and no bare decision or task ID (`D3`, `T2`). They move, retire, and renumber, nothing checks the reference, and the reader has no way to tell a dead one from a live one. State the constraint itself, or cite a maintained reference doc instead. A `docs/` path that is a glob or an elided example rather than a reference (`docs/*.md`) is vocabulary, not a citation, and is fine.",
|
|
12252
|
+
"- Code that exists only to accept an older shape -- a file, field, flag, environment variable, label or comment format -- carries `// @compat <id> until <expiry>: <reason>` on the line above it. The expiry is a date (`YYYY-MM-DD`) when the old shape lives with consumers who upgrade on their own schedule; a version of the code's own package (`v1.4.0`) when its consumer upgrades with each release; or `forge-clean` for old data on the forge, which has neither. Remove the code and every test that carries the same id together.",
|
|
12399
12253
|
"- **The code is the fact.** When a comment disagrees with it, fix it in one line or delete it -- never grow one to argue the code is right."
|
|
12400
12254
|
].join("\n");
|
|
12401
12255
|
/**
|
|
@@ -12424,7 +12278,10 @@ const TESTING_GUIDE_BODY = [
|
|
|
12424
12278
|
"- Never assert that a constant equals its own literal (a default, threshold, model name, label). Test the logic that reads it.",
|
|
12425
12279
|
"- Never pin prose with `toContain`/`toBe`. Snapshot it, or assert a property: ordering, absence, output that changes with an input, a character set or length.",
|
|
12426
12280
|
"- One case per branch. A new literal down a path already tested, or the same call through a one-line wrapper, is a duplicate.",
|
|
12427
|
-
"- Never assert what the test set up: a mock's stub, a type the compiler guarantees, a call count no caller relies on."
|
|
12281
|
+
"- Never assert what the test set up: a mock's stub, a type the compiler guarantees, a call count no caller relies on.",
|
|
12282
|
+
"- A test that exists only for a compatibility path carries the same `@compat` marker as the code.",
|
|
12283
|
+
"- Test a branch in the module that owns it. A wrapper gets one test that it is wired to that module, not a re-run of the module's own cases.",
|
|
12284
|
+
"- A fixture used in three or more cases goes into a builder, in the test file or in a shared test helper, not written out again in each case."
|
|
12428
12285
|
].join("\n");
|
|
12429
12286
|
/**
|
|
12430
12287
|
* Worktrees, issue linking, commit subjects, and merging the default branch in. The issue-linking
|
|
@@ -13260,7 +13117,6 @@ async function runGenerators(ctx) {
|
|
|
13260
13117
|
results.push(await generateReleaseCi(ctx));
|
|
13261
13118
|
results.push(...await generateStaleCiWorkflows(ctx));
|
|
13262
13119
|
results.push(await generateDeployCi(ctx));
|
|
13263
|
-
results.push(await generateCoverageCi(ctx));
|
|
13264
13120
|
results.push(...await generateClaudeMd(ctx));
|
|
13265
13121
|
results.push(...await generateVitest(ctx));
|
|
13266
13122
|
results.push(...await generateVscodeSettings(ctx));
|
|
@@ -13291,7 +13147,7 @@ function reportFailures(failed, command) {
|
|
|
13291
13147
|
function generateMigratePrompt(results, config, detected) {
|
|
13292
13148
|
const sections = [];
|
|
13293
13149
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13294
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
13150
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.88.0 repo:sync\` on ${timestamp}_\n`);
|
|
13295
13151
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
13296
13152
|
if (changed.length > 0) {
|
|
13297
13153
|
const items = changed.map((r) => {
|
|
@@ -33418,7 +33274,7 @@ setForgejoLogger(log$1);
|
|
|
33418
33274
|
* `local <sha>[-dirty]` otherwise -- so an artifact built from the working tree
|
|
33419
33275
|
* can't pass itself off as the published release whose version it carries.
|
|
33420
33276
|
*/
|
|
33421
|
-
const displayVersion = "0.
|
|
33277
|
+
const displayVersion = "0.88.0";
|
|
33422
33278
|
/**
|
|
33423
33279
|
* The registered `bst` subcommand map, exported so both the JSON entry path below (resolving a
|
|
33424
33280
|
* command name before citty parses anything) and the coverage test (enumerating every
|