Files
msd-core/tests/plan-phase-drift-guard.test.cjs
Tom Boucher 05cdec5f47 feat(#22): plan-vs-codebase drift guard (source-grounded reviewer + intel surface) (#487)
* feat(#22): add plan_review.source_grounding + _authority config keys

Two additive opt-out keys for the drift guard: source_grounding (bool,
default true) gates the source-grounded reviewer pass; _authority (enum
grep|intel|treesitter|lsp|scip, default grep) selects the resolver rung.
No existing default changed.

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#22): add intel api-surface renderer + CLI subcommand

Renders .planning/intel/api-map.json into a human-readable API-SURFACE.md
for planner injection. Empty/missing map still writes a surface that
announces itself incomplete (absence = unknown, not 'does not exist').
Gated on intel.enabled like all intel functions.

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#22): add source-grounding pass to plan-review-convergence

Default-on reviewer pass (plan_review.source_grounding) that enumerates
every symbol a plan cites, excludes declared new artifacts, resolves each
against source via the configured authority adapter, and records
three-valued verdicts. rung-0/1 MISSING is needs-acknowledgement, not a
hard block; UNCHECKABLE is logged in a REVIEWS.md coverage section.

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#22): inject API-SURFACE.md into planner + require Artifacts section

When intel.enabled, plan-phase regenerates API-SURFACE.md and injects it
as a HINT (prefer, may be incomplete, absence = unknown), never a hard
rule. Every plan must now emit an 'Artifacts this phase produces' section
so the source-grounding reviewer can separate new symbols from references
to existing code.

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#22): surface drift-guard in setup + settings, add docs

/gsd:new-project asks to enable plan_review.source_grounding (default Y);
/gsd:settings exposes the toggle and authority knob. Documents both config
keys in CONFIGURATION.md, the intel api-surface command in COMMANDS.md,
the drift guard in USER-GUIDE.md, and links ADR 22 from ARCHITECTURE.md.

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#22): respect AskUserQuestion 4-option cap and plan-phase XL line budget

settings drift-guard toggle moved to its own 2-option question; #22
plan-phase additions condensed to bring the file back under the 1810-line
XL budget without dropping the intel gate, the incomplete-surface hint, or
the Artifacts-section requirement.

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#22): use live slash-command forms in drift-guard docs

Doc-parity gate requires every slash-command token in docs/*.md to resolve
to a registered command. Corrected the command form(s) referenced in the
#22 drift-guard / api-surface documentation.

The unresolved token was /gsd-core, matched from the GitHub repo reference
"open-gsd/gsd-core#22" in docs/adr/22-plan-drift-guard.md. This is the
same pattern as the existing 'test-runner' exemption (open-gsd/gsd-test-runner).
Added 'core' to INTERNAL_COMPONENT_SLUGS with a matching explanatory comment.

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#22): add changeset fragment for drift guard (PR #487)

Refs #22

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: CI Rebase Check <ci@gsd-redux>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 17:08:11 -04:00

138 lines
5.7 KiB
JavaScript

/**
* Drift guard for gsd:plan-phase workflow (#22)
*
* Validates that the plan-phase workflow contains the key structural elements
* added for issue #22 Change #1:
*
* (A) intel.enabled gate — when intel.enabled is true, plan-phase regenerates
* API-SURFACE.md via `gsd-tools intel api-surface` and injects it into the
* planner's required reading as a HINT (prefer symbols, may be incomplete,
* absence = unknown, never exhaustive).
*
* (B) "Artifacts this phase produces" section — every PLAN.md must include
* this section so the plan-review-convergence source-grounding pass can
* exclude newly-created symbols from drift verification.
*/
// allow-test-rule: source-text-is-the-product
// The workflow markdown IS the runtime instruction. Testing its text content
// tests the deployed contract — if the intel gate or Artifacts section
// requirement is absent, the drift-guard feature is absent from defenses too.
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const WORKFLOW_PATH = path.join(
__dirname,
'..',
'get-shit-done',
'workflows',
'plan-phase.md'
);
// ─── Fixture ──────────────────────────────────────────────────────────────────
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8');
// ─── (A) intel.enabled gate ───────────────────────────────────────────────────
describe('plan-phase workflow: intel.enabled gate for API-SURFACE injection (#22)', () => {
test('workflow reads intel.enabled config before planner spawn', () => {
assert.ok(
workflow.includes('intel.enabled'),
'workflow must gate API-SURFACE generation on intel.enabled config key'
);
});
test('workflow runs gsd-tools intel api-surface to regenerate surface', () => {
assert.ok(
workflow.includes('intel api-surface'),
'workflow must call `gsd_run intel api-surface` (or equivalent) to regenerate API-SURFACE.md'
);
});
test('workflow injects API-SURFACE.md into planner files_to_read when intel.enabled', () => {
assert.ok(
workflow.includes('API-SURFACE.md') && workflow.includes('API_SURFACE_PATH'),
'workflow must pass API_SURFACE_PATH into the planner prompt files_to_read block'
);
});
test('workflow labels the surface as a HINT (not a hard rule)', () => {
assert.ok(
workflow.includes('HINT') || workflow.includes('intel_surface_hint'),
'API-SURFACE.md must be annotated as a HINT, never a hard rule'
);
});
test('workflow documents that surface absence means unknown not nonexistent', () => {
assert.ok(
workflow.includes("absence means *unknown*, not *nonexistent*") ||
workflow.includes("absence = unknown") ||
workflow.includes("absence means unknown"),
"workflow must state that a symbol's absence from the surface means unknown, not nonexistent"
);
});
test('workflow states the surface may be incomplete', () => {
assert.ok(
workflow.includes('MAY BE INCOMPLETE') || workflow.includes('may be incomplete'),
'workflow must warn that the API surface may be incomplete'
);
});
test('workflow skips surface injection when intel.enabled is false', () => {
// The gate must have an explicit false/skip branch
assert.ok(
workflow.includes("INTEL_CFG") &&
(workflow.includes("'false'") || workflow.includes('"false"') || workflow.includes('false')),
'workflow must skip the intel step when intel.enabled is false (config defaults to false)'
);
});
});
// ─── (B) "Artifacts this phase produces" requirement ─────────────────────────
describe('plan-phase workflow: Artifacts this phase produces section (#22)', () => {
test('downstream_consumer block requires Artifacts this phase produces section', () => {
assert.ok(
workflow.includes('Artifacts this phase produces'),
'downstream_consumer must list "Artifacts this phase produces" as a required plan section'
);
});
test('quality_gate checklist includes Artifacts this phase produces item', () => {
// Find the quality_gate block and confirm the checklist item is there
const qualityGateMatch = workflow.match(/<quality_gate>([\s\S]*?)<\/quality_gate>/);
assert.ok(
qualityGateMatch,
'workflow must have a <quality_gate> block'
);
assert.ok(
qualityGateMatch[1].includes('Artifacts this phase produces'),
'<quality_gate> checklist must include an "Artifacts this phase produces" item'
);
});
test('workflow explains why Artifacts section is needed (source-grounding reviewer)', () => {
assert.ok(
workflow.includes('source-grounding') || workflow.includes('plan-review-convergence'),
'workflow must explain that the Artifacts section is consumed by the source-grounding pass'
);
});
test('workflow lists symbol kinds for Artifacts section (decorators, classes, functions, CLI flags)', () => {
// Must enumerate concrete symbol kinds so planner knows what to list
const hasDecorators = workflow.includes('decorators');
const hasClasses = workflow.includes('classes');
const hasFunctions = workflow.includes('functions');
const hasCliFlags = workflow.includes('CLI flags');
assert.ok(
hasDecorators && hasClasses && hasFunctions && hasCliFlags,
'workflow must enumerate symbol kinds: decorators, classes, functions, CLI flags (needed for Artifacts section guidance)'
);
});
});