* enhance(#4406): the lazily-read remainder and the artifact templates ADR-4139 Decision 3, Phase 6 of the #4139 Compact Content epic. Covers stream 1b (gsd-core/workflows/<name>/{modes,steps,templates}/*.md) and stream 4 (gsd-core/templates/**) with a variant-swap mechanism, confirmed with the user: two independent, complete files per covered path (canonical + .compact.md sibling), with the gate picking which one gets Read at the call site. This is a different shape from Phase 5's spine+detail partition, and is safe here specifically because these files are already reached only by a runtime Read — a missed Read already means zero overlay content today, with or without workflow.compact_content, so selecting between two independently-complete files introduces no new failure mode (documented in gsd-core/references/compact-content-gate.md's new "Streams 1b and 4" section). Disposition, after inspecting every candidate rather than trusting a byte-size threshold (same rigor Phase 5 applied to review.md): - Stream 1b: 1 of 78 files compacted (help/modes/full.md, a user-facing reference doc emitted verbatim, not orchestrator instruction). The other 9 size-threshold candidates are dominated by fail-closed guards, exact CLI invocations, or output-format contracts (AskUserQuestion blocks) — recorded not-worth-compacting, same reasoning as Phase 5's review.md. - Stream 4: a ground-truth reachability audit replaced the initial size-only candidate list. Two files (summary.md, user-setup.md) got compact variants; a third (spec.md) was drafted, then dropped after discovering its only two call sites are eager @-includes, not a runtime Read — stream-1 material hiding under gsd-core/templates/, not stream-4's actual mechanism. summary.md itself has 3 eager call sites and only 1 genuine runtime-Read call site (execute-plan.md); only that one was wired, so the compact variant's savings apply to the sequential single-plan execution path only. - Discovered while auditing reachability: 12 gsd-core/templates/** files with zero references anywhere in workflow/agent/command prose, compiled source, or tests — dead scaffolding predating this phase. Deleted in this same PR per this repo's no-defer policy, after re-verifying against a computed path.join(...) pattern (not just a plain-string search) that nearly caused two genuinely load-bearing templates (user-profile.md, dev-preferences.md) to be misclassified as dead. New checker (tests/helpers/compact-content-variant.cjs): registration, reachability, protected-content-preserved, size-smaller — replacing Phase 3/5's disjointness/completeness checks, which assume a partition rather than two deliberately-overlapping documents. The reachability check's own "unprefixed match" guard had a real bug (rejected the repo's own `~/.claude/gsd-core/...` convention), caught by running it against the already-wired help/modes/full.compact.md pair rather than only synthetic fixtures — fixed to anchor on the nearest `gsd-core` path segment instead. Template consumer parity (tests/compact-content-template-variant-parity.test.cjs): proves each compact variant's `## File Template` fenced block — the actual output-format contract a generated SUMMARY.md/USER-SETUP.md is parsed against — is byte-identical to the canonical file, then runs the one real deterministic consumer (gsd-core/bin/lib/coverage.cjs's classifyContent, backing `gsd-tools uat classify-coverage`) against content built from that shared contract. Added a sibling benchmark script (scripts/benchmark-compact-content-variants.cjs) rather than extending the existing spine/detail one — different data shape, and the existing script's own contract deliberately isolates it from a test-only helper's shape changing. Emitted-drift acknowledgement: not needed. Every changed/added path in this diff is hand-authored and present in the diff itself, so diffEmitted's attribution loop resolves `via` to the path's own source before reaching the ack-lookup branch (same reasoning Phase 5 verified for its own diff). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * enhance(#4406): address code-review findings on the variant-swap gate - docs/CONFIGURATION.md and gsd-core/references/planning-config.md's workflow.compact_content entries described only the spine+detail mechanism (Phase 5) and were missing this phase's variant-swap mechanism and its benchmark:compact-content-variants script entirely — required since this PR's changeset is type Added (CLAUDE.md's "Missing Docs for Changesets" rule). Both now describe both mechanisms and which call sites are wired. - Added the missing RED^-1/no-op fixture for checkProtectedContentPreserved: a canonical file with zero <!-- gsd:protected --> blocks must be a no-op, not a violation — the only branch of that function the existing fixtures didn't exercise. - Collapsed findCompactFiles/findMarkdownFiles in tests/helpers/compact-content-variant.cjs into one findFilesWithSuffix helper — the two were identical recursive walks differing only in the extension predicate (minor Duplicated-Code finding). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#4406): restore copilot-instructions.md, a false-positive dead-template classification gsd-test caught this, not static analysis: 10 real failures in tests/copilot-install.test.cjs, tests/installer-migration-install.integration.test.cjs, and tests/repo-layout.test.cjs — all downstream of bin/install.js's Copilot install path, which does fs.readFileSync(path.join(targetDir, 'gsd-core', 'templates', 'copilot-instructions.md')) after copying gsd-core/templates/** into the target project, then merges it into both .github/copilot-instructions.md and (local installs) AGENTS.md. The reachability audit that flagged this file as dead checked src/*.cts and gsd-core/bin/*.cjs but never the repo-root bin/install.js — a separately maintained installer bundle outside the src/-to-gsd-core/bin/lib/ compiled-output convention. The fs.existsSync guard around that read degrades to a silent skip rather than a crash when the template is missing, which is why this surfaced only once the real E2E install test ran, not from any static check. Re-verified the remaining 11 deleted filenames against bin/install.js specifically (plain substring and quoted-filename search) before trusting that list — all 11 have zero hits there, confirmed dead by the same standard this one file failed. Regenerated the installer emitted-tree goldens (tests/fixtures/install-tree/*.json) to reflect the restored file, and corrected the "Removed" changeset (jolly-lynx-sprint.md) and the phase design doc from 12 to 11 deleted files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Emitted-Drift-Ack-Growth: execute-plan.md — call-site wiring for the summary.md and user-setup.md .compact.md variants Emitted-Drift-Ack-Growth: help.md — call-site wiring for full.compact.md, same variant-resolution rule * docs(#4406): backfill changeset PR numbers Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#4406): resolve removed-but-needed lint findings on the dead-template deletion CI's own full-test matrix (not gsd-test's matrix, which does not run this check) caught 4 more false-positive dead-template classifications via tests/removed-but-needed-lint.test.cjs / scripts/lint-removed-but-needed.cjs — a literal, word-boundary basename check across .github/workflows/, gsd-core/, and docs/ (excluding docs/adr/** and docs/research/**) for every file a PR deletes. It has no semantic awareness, so a deleted template's basename colliding with something else entirely still fires: - claude-md.md: gsd-core/templates/README.md had a stale table row claiming /gsd-profile reads this template to generate CLAUDE.md. Verified false (no code reads it anywhere, same search that already covered bin/install.js) — fixed the row to *(inline)*, matching every other command-generated artifact in that table. File stays deleted. - codebase/testing.md: collided with docs/guides/testing.md, an illustrative example row in docs-update.md's sample output table (an unrelated real generated-docs path). Swapped the example topic to "contributing" — the row is illustrative, any topic works. File stays deleted. - codebase/architecture.md, codebase/stack.md: collided with docs/reference/ planning-artifacts.md's directory listing of a user's own generated .planning/codebase/architecture.md and stack.md output — the same semantic mismatch already investigated and dismissed as unrelated earlier in this phase's audit, now caught by a gate instead of judgment. That listing repeats across 5 locale copies of the doc. - continue-here.md: collided with the real .continue-here.md pause-work artifact, referenced across 15+ locale and workflow files. For the last two, the lint's own error message offers "restore the file or update every consumer in the same commit." Rewording 15+ files across languages I cannot verify translation quality for, to shave 2 already-tiny templates that were merely presumed dead, is disproportionate to this PR's actual scope — restored codebase/architecture.md, codebase/stack.md, and continue-here.md instead, and corrected docs/ARCHITECTURE.md's Templates section accordingly. Final confirmed-dead set: claude-md.md, codebase/concerns.md, codebase/conventions.md, codebase/integrations.md, codebase/structure.md, codebase/testing.md, debug-subagent-prompt.md, discovery.md — 8 files, down from the original 12. Verified locally: GSD_REMOVED_BUT_NEEDED_BASE=next node scripts/lint-removed-but-needed.cjs now passes clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Emitted-Drift-Ack-Growth: docs-update.md — swapped an illustrative example-table topic (testing -> contributing) to avoid a removed-but-needed basename collision with the deleted codebase/testing.md template; net +10 bytes * fix(#4406): split codex-config.test.cjs to fix a genuine Windows CI timeout Root cause of the `full test (windows-latest, 24, shard 2/3)` failure the user asked to be actually fixed, not just re-run past: PR #4497 (landed 2026-09-07, one day before this PR's CI run) isolated tests/codex-config.test.cjs into its own dedicated chunk because its measured weight (17.87, ~45% of the post-cut Windows budget) made it unsafe to share a chunk with any other file. That isolation was necessary but not sufficient — even alone, with zero companion-file contention, the file's real Windows execution time sits right at the 600s per-chunk ceiling. Two independent CI runs on two unrelated PRs (this one and #4154) were both killed within ~1.4s of the identical 600000ms mark — not random contention, a deterministic near-miss the isolation fix couldn't address because it never reduced the file's own cost, only removed the risk of a companion file's cost stacking on top of it (which the PR #4497 comment explicitly anticipated: "if a future profiling pass genuinely speeds up codex-config.test.cjs itself, this isolation can be revisited"). The file itself explains why it's this heavy: 11,262 lines / 433 tests / 79 describe blocks, accumulated over dozens of bug-fix PRs (#2695, #2760, #3245, #3285, #3346, #3426, #3427, #3562, #3566, #3582, #3808, and more), several of which are explicitly documented as "folded" in from separate files that were never actually split back out ("Verified non-duplicate against both the pre-existing target and the other three folded sources"). Split into 4 files by top-level AST statement boundaries (never a naive column-0 regex — an early attempt at that overcounted 79 apparent "describe(" matches when only 21 are genuinely top-level; the rest are nested inside a handful of large folded-in blocks, which a regex can't tell apart from real top-level statements). Verified lossless twice: the split script asserts byte-for-byte reconstruction of every source character, and independently, total test()/describe() call counts match exactly between the original file and the sum across all 4 new files (433/79 both sides). Each new file carries the complete original shared header (imports/helpers) for safety; per-file unused-import warnings from that duplication are resolved via ESLint-precise alias renames (`{ foo: _foo }`, the standard form for an intentionally-unused destructured binding — never a bare `{ _foo }`, which would destructure a different, nonexistent property). No change needed to scripts/run-tests.cjs's ISOLATED_HEAVY_FILES or its pinned test in tests/run-tests-harness.test.cjs: the file that keeps the original name (tests/codex-config.test.cjs) is now only ~28% of the original's size and safely isolated in its own chunk as before; the other three new files re-enter normal weight-balanced packing, none individually close to disproportionate. Confirmed no other file hardcodes the hardcoded filename anywhere that would silently stop these tests from running (the CI test-selection scripts determine scope algorithmically, not by literal filename). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2350
tests/codex-config-agents.test.cjs
Normal file
2350
tests/codex-config-agents.test.cjs
Normal file
File diff suppressed because it is too large
Load Diff
3629
tests/codex-config-hooks.test.cjs
Normal file
3629
tests/codex-config-hooks.test.cjs
Normal file
File diff suppressed because it is too large
Load Diff
2894
tests/codex-config-install.test.cjs
Normal file
2894
tests/codex-config-install.test.cjs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
142
tests/compact-content-template-variant-parity.test.cjs
Normal file
142
tests/compact-content-template-variant-parity.test.cjs
Normal file
@@ -0,0 +1,142 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* tests/compact-content-template-variant-parity.test.cjs — ADR-4139, epic #4139, Phase 6 (#4406).
|
||||
*
|
||||
* Check 5 of `.gsd/phase/enhance-4406-lazy-remainder/40-design.md`'s variant-pair checklist:
|
||||
* template consumer parity. Only two `gsd-core/templates/**` files got a `.compact.md` variant
|
||||
* wired to a genuine runtime `Read` this phase — `summary.md` and `user-setup.md` (see
|
||||
* `40-design.md`'s "a size-only candidate list was also the wrong test here" for why `spec.md`
|
||||
* was dropped and why `summary.md`'s wiring is scoped to one call site only).
|
||||
*
|
||||
* The actual guarantee that makes compacting these templates safe: downstream consumers parse
|
||||
* the GENERATED artifact (a real SUMMARY.md / USER-SETUP.md an agent wrote), never the template
|
||||
* file itself. So the only way a compact variant could silently break a real consumer is if it
|
||||
* changed the artifact's OUTPUT-FORMAT CONTRACT — the `## File Template` fenced block — relative
|
||||
* to the canonical file. Both compact variants were authored to leave that block byte-identical
|
||||
* and compact only the surrounding illustrative material (examples, guidelines prose). This test
|
||||
* proves that invariant directly, rather than assuming it from the authoring process, and then
|
||||
* proves the shared contract really is what the one real deterministic consumer
|
||||
* (`gsd-core/bin/lib/coverage.cjs`'s `classifyContent`, backing `gsd-tools uat classify-coverage`)
|
||||
* accepts and classifies correctly.
|
||||
*
|
||||
* `user-setup.md` has no deterministic content parser anywhere in this repo (confirmed by
|
||||
* repo-wide search — the only "consumption" beyond an agent `Read` is a human `grep -r
|
||||
* "USER-SETUP" .planning/` search over filenames, insensitive to internal structure). Its parity
|
||||
* check is therefore limited to the File Template identity assertion; there is no real parser
|
||||
* round trip to run against it.
|
||||
*/
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const { classifyContent } = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'coverage.cjs'));
|
||||
|
||||
/**
|
||||
* Extract the fenced code block immediately following a `## File Template` heading.
|
||||
* Returns the block's inner content (without the opening/closing fences), or null if
|
||||
* the heading or a following fence isn't found.
|
||||
* @param {string} content
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function extractFileTemplateBlock(content) {
|
||||
const headingIdx = content.indexOf('## File Template');
|
||||
if (headingIdx === -1) return null;
|
||||
const fenceStart = content.indexOf('```', headingIdx);
|
||||
if (fenceStart === -1) return null;
|
||||
const afterOpenFence = content.indexOf('\n', fenceStart) + 1;
|
||||
const fenceEnd = content.indexOf('\n```', afterOpenFence);
|
||||
if (fenceEnd === -1) return null;
|
||||
return content.slice(afterOpenFence, fenceEnd);
|
||||
}
|
||||
|
||||
describe('template consumer parity — File Template contract identity', () => {
|
||||
test('summary.md and summary.compact.md share a byte-identical File Template block', () => {
|
||||
const canonical = fs.readFileSync(path.join(ROOT, 'gsd-core', 'templates', 'summary.md'), 'utf8');
|
||||
const compact = fs.readFileSync(path.join(ROOT, 'gsd-core', 'templates', 'summary.compact.md'), 'utf8');
|
||||
const canonicalBlock = extractFileTemplateBlock(canonical);
|
||||
const compactBlock = extractFileTemplateBlock(compact);
|
||||
assert.ok(canonicalBlock, 'canonical summary.md must have an extractable File Template block');
|
||||
assert.ok(compactBlock, 'summary.compact.md must have an extractable File Template block');
|
||||
assert.strictEqual(
|
||||
compactBlock,
|
||||
canonicalBlock,
|
||||
'summary.compact.md must not alter the output-format contract any downstream consumer parses',
|
||||
);
|
||||
});
|
||||
|
||||
test('user-setup.md and user-setup.compact.md share a byte-identical File Template block', () => {
|
||||
const canonical = fs.readFileSync(path.join(ROOT, 'gsd-core', 'templates', 'user-setup.md'), 'utf8');
|
||||
const compact = fs.readFileSync(path.join(ROOT, 'gsd-core', 'templates', 'user-setup.compact.md'), 'utf8');
|
||||
const canonicalBlock = extractFileTemplateBlock(canonical);
|
||||
const compactBlock = extractFileTemplateBlock(compact);
|
||||
assert.ok(canonicalBlock, 'canonical user-setup.md must have an extractable File Template block');
|
||||
assert.ok(compactBlock, 'user-setup.compact.md must have an extractable File Template block');
|
||||
assert.strictEqual(
|
||||
compactBlock,
|
||||
canonicalBlock,
|
||||
'user-setup.compact.md must not alter the output-format contract',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('template consumer parity — real classify-coverage round trip (summary.md)', () => {
|
||||
const REALISTIC_SUMMARY = `---
|
||||
phase: 07-example
|
||||
plan: 01
|
||||
subsystem: testing
|
||||
tags: [example]
|
||||
requires: []
|
||||
provides: []
|
||||
affects: []
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "Deterministic deliverable"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "tests/example.test.cjs#does the thing"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "Deliverable needing a human"
|
||||
verification: []
|
||||
human_judgment: true
|
||||
rationale: "UI screenshot review"
|
||||
duration: 5min
|
||||
completed: 2026-01-01
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 7: Example Summary
|
||||
|
||||
**A deterministic deliverable and a human-judgment deliverable.**
|
||||
`;
|
||||
|
||||
test('a SUMMARY.md built from the shared File Template coverage schema classifies correctly', () => {
|
||||
const result = classifyContent(REALISTIC_SUMMARY, '07-example-01-SUMMARY.md');
|
||||
assert.strictEqual(result.mode, 'coverage');
|
||||
assert.strictEqual(result.errors.length, 0, `expected no validation errors: ${JSON.stringify(result.errors)}`);
|
||||
assert.strictEqual(result.auto_passed.length, 1, 'D1 (human_judgment:false, all verification pass) must auto-pass');
|
||||
assert.strictEqual(result.present.length, 1, 'D2 (human_judgment:true) must route to a human');
|
||||
assert.strictEqual(result.all_auto_covered, false);
|
||||
});
|
||||
|
||||
test('boundary: a legacy SUMMARY.md with no coverage block still classifies (byte-identical fallback)', () => {
|
||||
const legacy = REALISTIC_SUMMARY.replace(/coverage:[\s\S]*?rationale: "UI screenshot review"\n/, '');
|
||||
const result = classifyContent(legacy, '07-example-01-SUMMARY.md');
|
||||
assert.strictEqual(result.mode, 'legacy');
|
||||
});
|
||||
|
||||
test('negative: a coverage block that fails validation is presented to a human, never silently auto-passed', () => {
|
||||
const malformed = REALISTIC_SUMMARY.replace('human_judgment: false', '');
|
||||
const result = classifyContent(malformed, '07-example-01-SUMMARY.md');
|
||||
assert.strictEqual(result.mode, 'coverage');
|
||||
assert.ok(
|
||||
result.present.some((p) => p.id === 'D1'),
|
||||
'D1 missing human_judgment must route to present, never auto-pass',
|
||||
);
|
||||
});
|
||||
});
|
||||
293
tests/compact-content-variant-guard.test.cjs
Normal file
293
tests/compact-content-variant-guard.test.cjs
Normal file
@@ -0,0 +1,293 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* tests/compact-content-variant-guard.test.cjs — ADR-4139, epic #4139, Phase 6 (#4406).
|
||||
*
|
||||
* Implements the four mechanical checks `.gsd/phase/enhance-4406-lazy-remainder/40-design.md`
|
||||
* describes for the variant-swap shape (two independent, complete files; the gate picks which
|
||||
* one gets `Read`) covering `gsd-core/workflows/<name>/{modes,steps,templates}/*.compact.md`
|
||||
* and `gsd-core/templates/**\/*.compact.md`. This is a DIFFERENT shape from
|
||||
* `tests/compact-content-partition-guard.test.cjs` (stream 1's spine+detail partition) — see
|
||||
* `tests/helpers/compact-content-variant.cjs` for why disjointness/completeness do not apply
|
||||
* here. Template consumer parity (the fifth check) lives in its own file,
|
||||
* `tests/compact-content-template-variant-parity.test.cjs`, because it needs a real
|
||||
* artifact-generation + real-parser round trip per template rather than a generic file-shape
|
||||
* check.
|
||||
*
|
||||
* Each check gets a RED (deliberately broken) and GREEN (fixed) fixture pair, built against
|
||||
* synthetic temp files — never against this repo's own real variants — per this repo's rule
|
||||
* that a guard nobody has seen go red is not yet a guard.
|
||||
*/
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
const {
|
||||
discoverRegisteredVariants,
|
||||
checkRegistration,
|
||||
checkReachability,
|
||||
checkProtectedContentPreserved,
|
||||
checkSizeSmaller,
|
||||
} = require('./helpers/compact-content-variant.cjs');
|
||||
|
||||
describe('compact-content variant guard — real repo state (ADR-4139, Phase 6 #4406)', () => {
|
||||
test('check 1 (registration): every .compact.md file has a canonical sibling', () => {
|
||||
const pairs = discoverRegisteredVariants();
|
||||
const violations = checkRegistration(pairs);
|
||||
assert.deepStrictEqual(violations, [], `registration violations: ${JSON.stringify(violations, null, 2)}`);
|
||||
});
|
||||
|
||||
test('check 2 (reachability): every registered compact variant is named by at least one spine', () => {
|
||||
const pairs = discoverRegisteredVariants();
|
||||
const violations = checkReachability(pairs);
|
||||
assert.deepStrictEqual(violations, [], `reachability violations: ${JSON.stringify(violations, null, 2)}`);
|
||||
});
|
||||
|
||||
test('check 3 (protected content preserved): every protected block in a canonical file survives in its compact sibling', () => {
|
||||
const pairs = discoverRegisteredVariants();
|
||||
const violations = checkProtectedContentPreserved(pairs);
|
||||
assert.deepStrictEqual(violations, [], `protected-content violations: ${JSON.stringify(violations, null, 2)}`);
|
||||
});
|
||||
|
||||
test('check 4 (size smaller): every compact file is strictly smaller than its canonical sibling', () => {
|
||||
const pairs = discoverRegisteredVariants();
|
||||
const violations = checkSizeSmaller(pairs);
|
||||
assert.deepStrictEqual(violations, [], `size violations: ${JSON.stringify(violations, null, 2)}`);
|
||||
});
|
||||
|
||||
test('non-vacuity: this repo actually has registered variant pairs to check', () => {
|
||||
const pairs = discoverRegisteredVariants();
|
||||
assert.ok(pairs.length > 0, 'expected at least one registered .compact.md pair — an empty result proves nothing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('failing-first fixture: check 1 (registration)', () => {
|
||||
test('RED — a .compact.md file exists with no canonical sibling (orphan)', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-orphan-'));
|
||||
try {
|
||||
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(workflowsDir, 'bar.compact.md'), 'Compact content with no canonical pair.\n');
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkRegistration(pairs);
|
||||
assert.ok(violations.length > 0, 'expected at least one registration violation');
|
||||
assert.ok(
|
||||
violations.some((v) => v.kind === 'orphan_compact_file' && v.compactPath.endsWith('bar.compact.md')),
|
||||
`expected the orphan file to be named: ${JSON.stringify(violations)}`,
|
||||
);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
|
||||
test('GREEN — the canonical sibling is added', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-orphan-ok-'));
|
||||
try {
|
||||
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(workflowsDir, 'bar.compact.md'), 'Terser.\n');
|
||||
fs.writeFileSync(path.join(workflowsDir, 'bar.md'), 'Canonical, longer content.\n');
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkRegistration(pairs);
|
||||
assert.deepStrictEqual(violations, []);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('failing-first fixture: check 2 (reachability)', () => {
|
||||
test('RED — a registered pair whose compact path is never named by any spine', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-unreached-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows');
|
||||
const stepsDir = path.join(workflowsRoot, 'foo', 'steps');
|
||||
fs.mkdirSync(stepsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(stepsDir, 'bar.md'), 'Canonical, longer content.\n');
|
||||
fs.writeFileSync(path.join(stepsDir, 'bar.compact.md'), 'Terser.\n');
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'foo.md'), 'Spine with entirely unrelated content and no path reference of any kind.\n');
|
||||
|
||||
const pairs = discoverRegisteredVariants([workflowsRoot]);
|
||||
const violations = checkReachability(pairs, [workflowsRoot]);
|
||||
assert.ok(violations.length > 0, 'expected at least one reachability violation');
|
||||
assert.ok(
|
||||
violations.some((v) => v.kind === 'unreachable_compact_file' && v.compactPath.endsWith('bar.compact.md')),
|
||||
`expected the unreached file to be named: ${JSON.stringify(violations)}`,
|
||||
);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
|
||||
test('GREEN — the spine names the compact path via the variant-resolution rule', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-unreached-ok-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows');
|
||||
const stepsDir = path.join(workflowsRoot, 'foo', 'steps');
|
||||
fs.mkdirSync(stepsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(stepsDir, 'bar.md'), 'Canonical, longer content.\n');
|
||||
fs.writeFileSync(path.join(stepsDir, 'bar.compact.md'), 'Terser.\n');
|
||||
fs.writeFileSync(
|
||||
path.join(workflowsRoot, 'foo.md'),
|
||||
'Read and execute `gsd-core/workflows/foo/steps/bar.md` (or its `gsd-core/workflows/foo/steps/bar.compact.md` variant per the shared gate).\n',
|
||||
);
|
||||
|
||||
const pairs = discoverRegisteredVariants([workflowsRoot]);
|
||||
const violations = checkReachability(pairs, [workflowsRoot]);
|
||||
assert.deepStrictEqual(violations, []);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
|
||||
test('a vendored path ending in the same filename does not grant false reachability', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-unreached-prefix-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows');
|
||||
const stepsDir = path.join(workflowsRoot, 'foo', 'steps');
|
||||
fs.mkdirSync(stepsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(stepsDir, 'bar.md'), 'Canonical, longer content.\n');
|
||||
fs.writeFileSync(path.join(stepsDir, 'bar.compact.md'), 'Terser.\n');
|
||||
fs.writeFileSync(
|
||||
path.join(workflowsRoot, 'foo.md'),
|
||||
'This mentions vendor/foo/steps/bar.compact.md, a different tree entirely.\n',
|
||||
);
|
||||
|
||||
const pairs = discoverRegisteredVariants([workflowsRoot]);
|
||||
const violations = checkReachability(pairs, [workflowsRoot]);
|
||||
assert.ok(
|
||||
violations.some((v) => v.kind === 'unreachable_compact_file'),
|
||||
'a prefixed match must not count as reachability',
|
||||
);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('failing-first fixture: check 3 (protected content preserved)', () => {
|
||||
test('RED — a protected block in the canonical file is absent from the compact sibling', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-protected-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(workflowsRoot, 'bar.md'),
|
||||
'Intro.\n\n<!-- gsd:protected -->\nNever weaken this exact guardrail sentence.\n\nMore filler text that is safe to shorten elsewhere in this file to pad it out longer than the compact sibling.\n',
|
||||
);
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.compact.md'), 'Terser intro with the guardrail dropped.\n');
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkProtectedContentPreserved(pairs);
|
||||
assert.ok(violations.length > 0, 'expected at least one protected-content violation');
|
||||
assert.ok(
|
||||
violations.some((v) => v.missing.includes('Never weaken this exact guardrail sentence.')),
|
||||
`expected the dropped sentence to be named: ${JSON.stringify(violations)}`,
|
||||
);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
|
||||
test('GREEN — the guardrail sentence is preserved verbatim in the compact sibling', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-protected-ok-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(workflowsRoot, 'bar.md'),
|
||||
'Intro.\n\n<!-- gsd:protected -->\nNever weaken this exact guardrail sentence.\n\nMore filler text that is safe to shorten elsewhere in this file to pad it out longer than the compact sibling.\n',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(workflowsRoot, 'bar.compact.md'),
|
||||
'Terser intro.\n\nNever weaken this exact guardrail sentence.\n',
|
||||
);
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkProtectedContentPreserved(pairs);
|
||||
assert.deepStrictEqual(violations, []);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
|
||||
test('a canonical file with no protected blocks is a correct no-op (nothing to preserve)', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-protected-none-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsRoot, { recursive: true });
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.md'), 'Plain canonical content with no protected sentinel at all.\n');
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.compact.md'), 'Terser.\n');
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkProtectedContentPreserved(pairs);
|
||||
assert.deepStrictEqual(
|
||||
violations,
|
||||
[],
|
||||
'a canonical file with zero <!-- gsd:protected --> blocks has nothing to check — never a violation',
|
||||
);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('failing-first fixture: check 4 (size smaller)', () => {
|
||||
test('RED — a "compact" file the same size as its canonical sibling', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-size-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsRoot, { recursive: true });
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.md'), 'x'.repeat(100));
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.compact.md'), 'y'.repeat(100));
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkSizeSmaller(pairs);
|
||||
assert.ok(violations.length > 0, 'expected at least one size violation');
|
||||
assert.ok(
|
||||
violations.some((v) => v.kind === 'compact_not_smaller' && v.canonicalSize === 100 && v.compactSize === 100),
|
||||
`expected the equal-size pair to be named: ${JSON.stringify(violations)}`,
|
||||
);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
|
||||
test('RED — a "compact" file one byte LARGER than its canonical sibling (boundary point)', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-size-over-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsRoot, { recursive: true });
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.md'), 'x'.repeat(100));
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.compact.md'), 'y'.repeat(101));
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkSizeSmaller(pairs);
|
||||
assert.ok(violations.length > 0, 'expected at least one size violation');
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
|
||||
test('GREEN — a compact file exactly one byte smaller (boundary point)', () => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-variant-size-ok-'));
|
||||
try {
|
||||
const workflowsRoot = path.join(tmpRoot, 'gsd-core', 'workflows', 'foo', 'steps');
|
||||
fs.mkdirSync(workflowsRoot, { recursive: true });
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.md'), 'x'.repeat(100));
|
||||
fs.writeFileSync(path.join(workflowsRoot, 'bar.compact.md'), 'y'.repeat(99));
|
||||
|
||||
const pairs = discoverRegisteredVariants([path.join(tmpRoot, 'gsd-core', 'workflows')]);
|
||||
const violations = checkSizeSmaller(pairs);
|
||||
assert.deepStrictEqual(violations, []);
|
||||
} finally {
|
||||
cleanup(tmpRoot);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -13,8 +13,8 @@
|
||||
"reductionPct": 36.67
|
||||
},
|
||||
"docs-update": {
|
||||
"offTokens": 14231,
|
||||
"onTokens": 11881,
|
||||
"offTokens": 14232,
|
||||
"onTokens": 11882,
|
||||
"reductionPct": 16.51
|
||||
},
|
||||
"execute-phase": {
|
||||
@@ -39,8 +39,8 @@
|
||||
}
|
||||
},
|
||||
"aggregate": {
|
||||
"offTokens": 106922,
|
||||
"onTokens": 90274,
|
||||
"offTokens": 106923,
|
||||
"onTokens": 90275,
|
||||
"reductionPct": 15.57
|
||||
}
|
||||
}
|
||||
|
||||
31
tests/fixtures/compact-content-variant-benchmark-baseline.json
vendored
Normal file
31
tests/fixtures/compact-content-variant-benchmark-baseline.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"generated_by": "scripts/benchmark-compact-content-variants.cjs",
|
||||
"tokenizer": {
|
||||
"name": "gpt-tokenizer",
|
||||
"version": "4.0.0"
|
||||
},
|
||||
"label": "PROXY-TOKENIZER DELTA — gpt-tokenizer is a stand-in; Anthropic publishes no tokenizer for Claude 3+. The on/off COMPARISON is exact under this pinned tokenizer; absolute counts are not Claude's real token counts.",
|
||||
"pairs": {
|
||||
"gsd-core/templates/summary.md": {
|
||||
"offTokens": 2938,
|
||||
"onTokens": 2299,
|
||||
"reductionPct": 21.75
|
||||
},
|
||||
"gsd-core/templates/user-setup.md": {
|
||||
"offTokens": 2061,
|
||||
"onTokens": 1363,
|
||||
"reductionPct": 33.87
|
||||
},
|
||||
"gsd-core/workflows/help/modes/full.md": {
|
||||
"offTokens": 9950,
|
||||
"onTokens": 6579,
|
||||
"reductionPct": 33.88
|
||||
}
|
||||
},
|
||||
"aggregate": {
|
||||
"offTokens": 14949,
|
||||
"onTokens": 10241,
|
||||
"reductionPct": 31.49
|
||||
}
|
||||
}
|
||||
11
tests/fixtures/install-tree/antigravity.json
vendored
11
tests/fixtures/install-tree/antigravity.json
vendored
@@ -296,21 +296,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -331,8 +323,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -412,6 +406,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/augment.json
vendored
11
tests/fixtures/install-tree/augment.json
vendored
@@ -368,21 +368,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -403,8 +395,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -484,6 +478,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/claude-local.json
vendored
11
tests/fixtures/install-tree/claude-local.json
vendored
@@ -261,21 +261,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -296,8 +288,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -377,6 +371,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/claude.json
vendored
11
tests/fixtures/install-tree/claude.json
vendored
@@ -296,21 +296,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -331,8 +323,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -412,6 +406,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/cline.json
vendored
11
tests/fixtures/install-tree/cline.json
vendored
@@ -298,21 +298,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -333,8 +325,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -414,6 +408,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/codebuddy.json
vendored
11
tests/fixtures/install-tree/codebuddy.json
vendored
@@ -368,21 +368,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -403,8 +395,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -484,6 +478,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/codex.json
vendored
11
tests/fixtures/install-tree/codex.json
vendored
@@ -332,21 +332,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -367,8 +359,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -448,6 +442,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/copilot.json
vendored
11
tests/fixtures/install-tree/copilot.json
vendored
@@ -297,21 +297,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -332,8 +324,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -413,6 +407,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/cursor.json
vendored
11
tests/fixtures/install-tree/cursor.json
vendored
@@ -296,21 +296,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -331,8 +323,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -412,6 +406,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/hermes.json
vendored
11
tests/fixtures/install-tree/hermes.json
vendored
@@ -296,21 +296,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -331,8 +323,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -412,6 +406,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/kilo.json
vendored
11
tests/fixtures/install-tree/kilo.json
vendored
@@ -368,21 +368,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -403,8 +395,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -484,6 +478,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/kimi-code.json
vendored
11
tests/fixtures/install-tree/kimi-code.json
vendored
@@ -297,21 +297,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -332,8 +324,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -413,6 +407,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/kimi.json
vendored
11
tests/fixtures/install-tree/kimi.json
vendored
@@ -333,21 +333,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -368,8 +360,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -449,6 +443,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/opencode.json
vendored
11
tests/fixtures/install-tree/opencode.json
vendored
@@ -368,21 +368,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -403,8 +395,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -484,6 +478,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/pi.json
vendored
11
tests/fixtures/install-tree/pi.json
vendored
@@ -156,21 +156,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -191,8 +183,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -272,6 +266,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/qwen.json
vendored
11
tests/fixtures/install-tree/qwen.json
vendored
@@ -296,21 +296,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -331,8 +323,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -412,6 +406,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/trae.json
vendored
11
tests/fixtures/install-tree/trae.json
vendored
@@ -296,21 +296,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -331,8 +323,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -412,6 +406,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/windsurf.json
vendored
11
tests/fixtures/install-tree/windsurf.json
vendored
@@ -224,21 +224,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -259,8 +251,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -340,6 +334,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
11
tests/fixtures/install-tree/zcode.json
vendored
11
tests/fixtures/install-tree/zcode.json
vendored
@@ -368,21 +368,13 @@
|
||||
"gsd-core/templates/UAT.md",
|
||||
"gsd-core/templates/UI-SPEC.md",
|
||||
"gsd-core/templates/VALIDATION.md",
|
||||
"gsd-core/templates/claude-md.md",
|
||||
"gsd-core/templates/codebase/architecture.md",
|
||||
"gsd-core/templates/codebase/concerns.md",
|
||||
"gsd-core/templates/codebase/conventions.md",
|
||||
"gsd-core/templates/codebase/integrations.md",
|
||||
"gsd-core/templates/codebase/stack.md",
|
||||
"gsd-core/templates/codebase/structure.md",
|
||||
"gsd-core/templates/codebase/testing.md",
|
||||
"gsd-core/templates/config.json",
|
||||
"gsd-core/templates/context.md",
|
||||
"gsd-core/templates/continue-here.md",
|
||||
"gsd-core/templates/copilot-instructions.md",
|
||||
"gsd-core/templates/debug-subagent-prompt.md",
|
||||
"gsd-core/templates/dev-preferences.md",
|
||||
"gsd-core/templates/discovery.md",
|
||||
"gsd-core/templates/discussion-log.md",
|
||||
"gsd-core/templates/milestone-archive.md",
|
||||
"gsd-core/templates/milestone.md",
|
||||
@@ -403,8 +395,10 @@
|
||||
"gsd-core/templates/summary-complex.md",
|
||||
"gsd-core/templates/summary-minimal.md",
|
||||
"gsd-core/templates/summary-standard.md",
|
||||
"gsd-core/templates/summary.compact.md",
|
||||
"gsd-core/templates/summary.md",
|
||||
"gsd-core/templates/user-profile.md",
|
||||
"gsd-core/templates/user-setup.compact.md",
|
||||
"gsd-core/templates/user-setup.md",
|
||||
"gsd-core/templates/verification-report.md",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh",
|
||||
@@ -484,6 +478,7 @@
|
||||
"gsd-core/workflows/help.md",
|
||||
"gsd-core/workflows/help/modes/brief.md",
|
||||
"gsd-core/workflows/help/modes/default.md",
|
||||
"gsd-core/workflows/help/modes/full.compact.md",
|
||||
"gsd-core/workflows/help/modes/full.md",
|
||||
"gsd-core/workflows/help/modes/topic.md",
|
||||
"gsd-core/workflows/import.md",
|
||||
|
||||
293
tests/helpers/compact-content-variant.cjs
Normal file
293
tests/helpers/compact-content-variant.cjs
Normal file
@@ -0,0 +1,293 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Shared library for the compact-content VARIANT guard (ADR-4139, epic #4139,
|
||||
* Phase 6 #4406). See `gsd-core/references/compact-content-gate.md` §"Streams
|
||||
* 1b and 4 — variant resolution" for the operational rule this module checks;
|
||||
* this file is the mechanics, not the source of truth for behavior.
|
||||
*
|
||||
* This is a DIFFERENT shape from `compact-content-split.cjs` (Phase 3, stream
|
||||
* 1's spine+detail partition). A partition is one document split into two
|
||||
* halves that must never overlap (disjointness) and whose union must equal
|
||||
* the original (completeness). A variant pair is two INDEPENDENT, complete
|
||||
* documents that are EXPECTED to overlap heavily — the compact file is a
|
||||
* hand-terser rewrite of the same content, not an extracted remainder. So
|
||||
* this module has no disjointness check and no completeness-at-split-time
|
||||
* check; it has the five checks `40-design.md` (Phase 6) describes instead:
|
||||
*
|
||||
* 1. Registration — `discoverRegisteredVariants` (a `.compact.md`
|
||||
* file with no canonical sibling is not a registered pair; the guard
|
||||
* test reports it as an orphan).
|
||||
* 2. Reachability — `checkReachability` (a registered pair whose
|
||||
* compact path is never named by any spine's "Read ... variant
|
||||
* resolution" call site is unwired dead weight).
|
||||
* 3. Protected content preserved — `checkProtectedContentPreserved` (a
|
||||
* `<!-- gsd:protected -->` block's lines must appear verbatim in BOTH
|
||||
* files, since nothing is "moved" in a variant pair — it is duplicated).
|
||||
* 4. Size smaller — `checkSizeSmaller`.
|
||||
* 5. Template consumer parity — NOT implemented here; it needs a real
|
||||
* artifact-generation + real-parser round trip per template, which is
|
||||
* the domain of `tests/compact-content-template-variant-parity.test.cjs`
|
||||
* directly, not a generic file-shape check.
|
||||
*
|
||||
* This module only reads (filesystem + a search of markdown source for
|
||||
* literal path substrings). No writes, no network, no git.
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { extractProtectedBlocks, normalizeNonTrivialLines } = require('./compact-content-split.cjs');
|
||||
|
||||
/** Default scan roots: everywhere a `.compact.md` sibling can legally live. */
|
||||
const DEFAULT_VARIANT_ROOTS = [
|
||||
path.join(__dirname, '..', '..', 'gsd-core', 'workflows'),
|
||||
path.join(__dirname, '..', '..', 'gsd-core', 'templates'),
|
||||
];
|
||||
|
||||
/** Every markdown-source root a spine/fragment might name a variant path from. */
|
||||
const DEFAULT_SEARCH_ROOTS = [
|
||||
path.join(__dirname, '..', '..', 'gsd-core', 'workflows'),
|
||||
];
|
||||
|
||||
const COMPACT_SUFFIX = '.compact.md';
|
||||
|
||||
/**
|
||||
* Recursively list every file under `dir` whose name ends with `suffix`.
|
||||
* Shared by both file-discovery needs this module has — `.compact.md` files
|
||||
* (`findCompactFiles`) and general `.md` files to search for reachability
|
||||
* (`findMarkdownFiles`) — which otherwise duplicated the same walk with only
|
||||
* the extension predicate differing.
|
||||
* @param {string} dir
|
||||
* @param {string} suffix
|
||||
* @returns {string[]} absolute paths
|
||||
*/
|
||||
function findFilesWithSuffix(dir, suffix) {
|
||||
const results = [];
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return results;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
results.push(...findFilesWithSuffix(full, suffix));
|
||||
} else if (entry.isFile() && entry.name.endsWith(suffix)) {
|
||||
results.push(full);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively list every `*.compact.md` file under `dir`.
|
||||
* @param {string} dir
|
||||
* @returns {string[]} absolute paths
|
||||
*/
|
||||
function findCompactFiles(dir) {
|
||||
return findFilesWithSuffix(dir, COMPACT_SUFFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover every registered compact/canonical variant pair under `roots`.
|
||||
*
|
||||
* A pair is registered by a `<dir>/<stem>.compact.md` file existing on disk —
|
||||
* there is no separate registry. Its canonical sibling is `<dir>/<stem>.md`
|
||||
* in the SAME directory. A `.compact.md` file with no canonical sibling is
|
||||
* still returned (with `canonicalExists: false`) so the registration check
|
||||
* can report it as an orphan by name, rather than silently skipping it.
|
||||
*
|
||||
* @param {string[]} roots
|
||||
* @returns {{compactPath: string, canonicalPath: string, canonicalExists: boolean}[]}
|
||||
*/
|
||||
function discoverRegisteredVariants(roots = DEFAULT_VARIANT_ROOTS) {
|
||||
const pairs = [];
|
||||
for (const root of roots) {
|
||||
for (const compactPath of findCompactFiles(root)) {
|
||||
const dir = path.dirname(compactPath);
|
||||
const stem = path.basename(compactPath, COMPACT_SUFFIX);
|
||||
const canonicalPath = path.join(dir, `${stem}.md`);
|
||||
pairs.push({
|
||||
compactPath,
|
||||
canonicalPath,
|
||||
canonicalExists: fs.existsSync(canonicalPath),
|
||||
});
|
||||
}
|
||||
}
|
||||
return pairs.sort((a, b) => a.compactPath.localeCompare(b.compactPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 1 — registration. A `.compact.md` file must have a canonical sibling.
|
||||
* @param {ReturnType<typeof discoverRegisteredVariants>} pairs
|
||||
*/
|
||||
function checkRegistration(pairs) {
|
||||
const violations = [];
|
||||
for (const pair of pairs) {
|
||||
if (!pair.canonicalExists) {
|
||||
violations.push({ kind: 'orphan_compact_file', compactPath: pair.compactPath });
|
||||
}
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 2 — reachability. A registered pair's compact path must be named by
|
||||
* at least one markdown file under `searchRoots` (a spine's "Read ... variant
|
||||
* resolution" call site). Three needle forms, matched differently, because
|
||||
* this corpus has two live conventions for naming these paths (found by
|
||||
* walking up from the compact file itself to its nearest `gsd-core` ancestor,
|
||||
* so this works the same way against the real repo and against a fixture
|
||||
* that builds its own `<tmp>/gsd-core/...` tree):
|
||||
*
|
||||
* - The `gsd-core/<rest>` form (e.g. `gsd-core/workflows/autonomous/steps/
|
||||
* converge-fail-fast.md`'s own convention) is unambiguous on its own — a
|
||||
* different, longer path coincidentally ending in this exact multi-segment
|
||||
* suffix is not a realistic false positive, so a plain substring match is
|
||||
* sufficient without the "unprefixed" guard below.
|
||||
* - The `<rest>` form without the leading `gsd-core/` (e.g. `workflows/help/
|
||||
* modes/full.compact.md`, `help.md`'s own dispatch-table convention) is
|
||||
* equally unambiguous for the same reason.
|
||||
* - The bare `<stem>.compact.md` form has no such guarantee — a same-named
|
||||
* file under an unrelated nested directory (the exact class of bug already
|
||||
* hit once this phase: `discuss-phase/templates/context.md` vs. the root
|
||||
* `templates/context.md`) could grant it a false reachability. This form
|
||||
* keeps the `isUnprefixedMatch` guard from `namesFragmentAsEntryPoint`
|
||||
* (`scripts/lint-response-language-coverage.cjs`): a path character
|
||||
* immediately before the match means this is the tail of some longer,
|
||||
* different path, not the fragment itself.
|
||||
*
|
||||
* @param {ReturnType<typeof discoverRegisteredVariants>} pairs
|
||||
* @param {string[]} searchRoots
|
||||
*/
|
||||
function checkReachability(pairs, searchRoots = DEFAULT_SEARCH_ROOTS) {
|
||||
const violations = [];
|
||||
const haystacks = [];
|
||||
for (const root of searchRoots) {
|
||||
for (const file of findMarkdownFiles(root)) {
|
||||
haystacks.push(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
}
|
||||
for (const pair of pairs) {
|
||||
if (!pair.canonicalExists) continue; // already reported by checkRegistration
|
||||
const gsdCoreRelative = relativeToNearestGsdCore(pair.compactPath);
|
||||
const stem = path.basename(pair.compactPath, COMPACT_SUFFIX);
|
||||
const bareNeedle = `${stem}${COMPACT_SUFFIX}`;
|
||||
const reached = haystacks.some((text) => {
|
||||
if (gsdCoreRelative && (text.includes(`gsd-core/${gsdCoreRelative}`) || text.includes(gsdCoreRelative))) {
|
||||
return true;
|
||||
}
|
||||
return isUnprefixedMatch(text, bareNeedle);
|
||||
});
|
||||
if (!reached) {
|
||||
violations.push({ kind: 'unreachable_compact_file', compactPath: pair.compactPath });
|
||||
}
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk up from `filePath` to the nearest ancestor directory literally named
|
||||
* `gsd-core`, and return the path from there to `filePath` (POSIX-separated).
|
||||
* Returns `null` if no such ancestor exists. Anchoring on the literal
|
||||
* `gsd-core` segment — rather than a hardcoded repo-root constant — is what
|
||||
* lets this match both the real repo and a fixture built under its own
|
||||
* `<tmp>/gsd-core/...` tree the same way.
|
||||
* @param {string} filePath
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function relativeToNearestGsdCore(filePath) {
|
||||
const segments = filePath.split(path.sep);
|
||||
const idx = segments.lastIndexOf('gsd-core');
|
||||
if (idx === -1) return null;
|
||||
return segments.slice(idx + 1).join('/');
|
||||
}
|
||||
|
||||
/** Is `needle` present in `text` with no path character immediately before it (any line)? */
|
||||
function isUnprefixedMatch(text, needle) {
|
||||
return text.split(/\r?\n/).some((line) => {
|
||||
const at = line.indexOf(needle);
|
||||
if (at === -1) return false;
|
||||
const before = at > 0 ? line[at - 1] : '';
|
||||
return !/[A-Za-z0-9_\-./]/.test(before);
|
||||
});
|
||||
}
|
||||
|
||||
function findMarkdownFiles(dir) {
|
||||
return findFilesWithSuffix(dir, '.md');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 3 — protected content preserved. Every protected block's non-trivial
|
||||
* lines in the canonical file must also appear (verbatim, after the same
|
||||
* normalization the partition guard uses) somewhere in the compact sibling.
|
||||
* Unlike the partition guard, this is NOT a sentinel-presence check on the
|
||||
* compact file itself — the compact file need not carry `<!-- gsd:protected -->`
|
||||
* markers of its own, since it is not itself audited for content it might
|
||||
* shed later; it only must not have DROPPED the protected wording.
|
||||
*
|
||||
* @param {ReturnType<typeof discoverRegisteredVariants>} pairs
|
||||
*/
|
||||
function checkProtectedContentPreserved(pairs) {
|
||||
const violations = [];
|
||||
for (const pair of pairs) {
|
||||
if (!pair.canonicalExists) continue;
|
||||
const canonical = fs.readFileSync(pair.canonicalPath, 'utf8');
|
||||
const compact = fs.readFileSync(pair.compactPath, 'utf8');
|
||||
const { blocks } = extractProtectedBlocks(canonical);
|
||||
if (blocks.length === 0) continue;
|
||||
const compactLines = new Set(normalizeNonTrivialLines(compact));
|
||||
for (const block of blocks) {
|
||||
const missing = block.lines
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l.length > 0)
|
||||
.filter((l) => !compactLines.has(l));
|
||||
if (missing.length > 0) {
|
||||
violations.push({
|
||||
kind: 'protected_content_dropped',
|
||||
canonicalPath: pair.canonicalPath,
|
||||
compactPath: pair.compactPath,
|
||||
missing,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 4 — size smaller. The compact file must be strictly smaller than its
|
||||
* canonical sibling; a same-size-or-larger "compact" file is not one.
|
||||
* @param {ReturnType<typeof discoverRegisteredVariants>} pairs
|
||||
*/
|
||||
function checkSizeSmaller(pairs) {
|
||||
const violations = [];
|
||||
for (const pair of pairs) {
|
||||
if (!pair.canonicalExists) continue;
|
||||
const canonicalSize = fs.statSync(pair.canonicalPath).size;
|
||||
const compactSize = fs.statSync(pair.compactPath).size;
|
||||
if (!(compactSize < canonicalSize)) {
|
||||
violations.push({
|
||||
kind: 'compact_not_smaller',
|
||||
canonicalPath: pair.canonicalPath,
|
||||
compactPath: pair.compactPath,
|
||||
canonicalSize,
|
||||
compactSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_VARIANT_ROOTS,
|
||||
DEFAULT_SEARCH_ROOTS,
|
||||
COMPACT_SUFFIX,
|
||||
discoverRegisteredVariants,
|
||||
checkRegistration,
|
||||
checkReachability,
|
||||
checkProtectedContentPreserved,
|
||||
checkSizeSmaller,
|
||||
};
|
||||
Reference in New Issue
Block a user