feat(ai-integration): API-coverage verify:pre gate (#1562)
Full API Coverage by Default — Opt Out, Never Opt In. A phase that integrates an external API/SDK/service can no longer seal without a decided coverage matrix. - src/api-coverage.cts: deterministic detector (compound verb+noun signal + <Service> API/SDK surface; stopword-guarded; strips fenced code) + matrix parse/validate/render with field-length caps. - check api-coverage.verify-pre: blocking seal-time gate; phase arg resolved as a token under .planning/phases/ only (traversal-neutralized); validates COVERAGE.md or blocks iff a strong integration signal is detected and no matrix exists; fail-closed when phases tree exists but phase unresolvable. - capabilities/ai-integration: workflow.api_coverage_gate config key (default true), plan:pre contribution, blocking verify:pre gate. Data-driven. - gsd-core/workflows/verify-work.md: generic verify:pre gate dispatch. - Tests: detector FP/FN + matrix validation + fast-check bijection; gate e2e. Code+security review findings fixed (stopword FP, scope containment, pipe/cap rejection, prompt-injection message hygiene). - Regenerated registry/matrix/loop-host-contract/goldens/baseline + docs. Closes #1562
This commit is contained in:
273
tests/api-coverage-gate-e2e.test.cjs
Normal file
273
tests/api-coverage-gate-e2e.test.cjs
Normal file
@@ -0,0 +1,273 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* E2E capability-wiring tests for the API-coverage gate (#1562).
|
||||
*
|
||||
* Drives the real CLI subprocess (`loop render-hooks verify:pre` and
|
||||
* `check api-coverage.verify-pre`) against temp projects to prove:
|
||||
* - the gate is data-driven (activates/deactivates by config) — acceptance #5
|
||||
* - the seal contract (block / pass) — acceptance #1, #2, #4
|
||||
* - the matrix persists on disk and is read at seal time — acceptance #6
|
||||
*
|
||||
* CONTENT/E2E only: every test drives a real CLI subprocess. No readFileSync
|
||||
* source-grep. Genuine assertions: each case asserts the SPECIFIC differing
|
||||
* value (block true/false, capId presence), not a count.
|
||||
*/
|
||||
|
||||
const { describe, test, afterEach } = 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 { execFileSync } = require('node:child_process');
|
||||
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
|
||||
const TOOLS_PATH = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs');
|
||||
|
||||
const TEST_ENV_BASE = {
|
||||
GSD_SESSION_KEY: '',
|
||||
CODEX_THREAD_ID: '',
|
||||
CLAUDE_SESSION_ID: '',
|
||||
CLAUDE_CODE_SSE_PORT: '',
|
||||
OPENCODE_SESSION_ID: '',
|
||||
GEMINI_SESSION_ID: '',
|
||||
CURSOR_SESSION_ID: '',
|
||||
WINDSURF_SESSION_ID: '',
|
||||
TERM_SESSION: '',
|
||||
WT_SESSION: '',
|
||||
TMUX_PANE: '',
|
||||
ZELLIJ_SESSION_NAME: '',
|
||||
TTY: '',
|
||||
SSH_TTY: '',
|
||||
};
|
||||
|
||||
function runTools(args, cwd) {
|
||||
const argv = Array.isArray(args)
|
||||
? args
|
||||
: (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [])
|
||||
.map((t) => t.replace(/"([^"]*)"/g, '$1').replace(/'([^']*)'/g, '$1'));
|
||||
try {
|
||||
const stdout = execFileSync(process.execPath, [TOOLS_PATH, ...argv], {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
env: { ...process.env, ...TEST_ENV_BASE },
|
||||
timeout: 60000,
|
||||
});
|
||||
return { success: true, output: stdout.trim(), exitCode: 0, error: '' };
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
output: err.stdout?.toString().trim() || '',
|
||||
error: err.stderr?.toString().trim() || err.message,
|
||||
exitCode: err.status ?? 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function makeProject(workflow) {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-apicov-'));
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'config.json'),
|
||||
JSON.stringify({ workflow }),
|
||||
'utf8'
|
||||
);
|
||||
return tmpDir;
|
||||
}
|
||||
|
||||
function makePhaseDir(projectDir, phaseSlug) {
|
||||
const dir = path.join(projectDir, '.planning', 'phases', phaseSlug);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
return dir;
|
||||
}
|
||||
|
||||
function writePlan(phaseDir, planFile, body) {
|
||||
fs.writeFileSync(path.join(phaseDir, planFile), body, 'utf8');
|
||||
}
|
||||
|
||||
function writeCoverage(phaseDir, body) {
|
||||
fs.writeFileSync(path.join(phaseDir, 'COVERAGE.md'), body, 'utf8');
|
||||
}
|
||||
|
||||
function verifyPreHooks(cwd) {
|
||||
const result = runTools('loop render-hooks verify:pre --raw', cwd);
|
||||
assert.ok(result.success, `render-hooks verify:pre should succeed. stderr: ${result.error}`);
|
||||
const envelope = JSON.parse(result.output);
|
||||
assert.strictEqual(envelope.point, 'verify:pre', 'point field must be verify:pre');
|
||||
assert.ok(Array.isArray(envelope.activeHooks), 'activeHooks must be an array');
|
||||
return envelope;
|
||||
}
|
||||
|
||||
function findCap(envelope, capId) {
|
||||
return envelope.activeHooks.find((h) => h.capId === capId) || null;
|
||||
}
|
||||
|
||||
function runGate(cwd, phaseDir) {
|
||||
return runTools(['check', 'api-coverage.verify-pre', phaseDir, '--raw'], cwd);
|
||||
}
|
||||
|
||||
// ─── Capability wiring: data-driven activation (acceptance #5) ───────────────
|
||||
|
||||
describe('api-coverage verify:pre gate — capability wiring (#1562 acceptance #5)', () => {
|
||||
let tmpDir;
|
||||
afterEach(() => { if (tmpDir) { cleanup(tmpDir); tmpDir = null; } });
|
||||
|
||||
test('gate is ACTIVE when workflow.api_coverage_gate is true', () => {
|
||||
tmpDir = makeProject({ api_coverage_gate: true });
|
||||
const env = verifyPreHooks(tmpDir);
|
||||
const hook = findCap(env, 'ai-integration');
|
||||
assert.ok(hook, 'ai-integration gate must register at verify:pre when enabled');
|
||||
assert.strictEqual(hook.kind, 'gate');
|
||||
assert.strictEqual(hook.blocking, true);
|
||||
assert.strictEqual(hook.check.query, 'api-coverage.verify-pre');
|
||||
});
|
||||
|
||||
test('gate is ABSENT when workflow.api_coverage_gate is false', () => {
|
||||
tmpDir = makeProject({ api_coverage_gate: false });
|
||||
const env = verifyPreHooks(tmpDir);
|
||||
assert.strictEqual(findCap(env, 'ai-integration'), null, 'gate must not register when disabled');
|
||||
});
|
||||
|
||||
test('gate is ACTIVE by default when the key is absent (opt-out, not opt-in)', () => {
|
||||
tmpDir = makeProject({});
|
||||
const env = verifyPreHooks(tmpDir);
|
||||
assert.ok(findCap(env, 'ai-integration'), 'gate must default ON (full-coverage-by-default)');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Seal contract: block / pass (acceptance #1, #2, #4, #6) ──────────────────
|
||||
|
||||
describe('api-coverage.verify-pre — seal contract (#1562 acceptance #1,#2,#4,#6)', () => {
|
||||
let tmpDir;
|
||||
let phaseDir;
|
||||
afterEach(() => { if (tmpDir) { cleanup(tmpDir); tmpDir = null; } });
|
||||
|
||||
function fresh() {
|
||||
tmpDir = makeProject({ api_coverage_gate: true });
|
||||
phaseDir = makePhaseDir(tmpDir, '01-pay');
|
||||
return phaseDir;
|
||||
}
|
||||
|
||||
test('#1 API phase without a matrix → BLOCKS the seal', () => {
|
||||
fresh();
|
||||
writePlan(phaseDir, '01-PLAN.md', '# Plan\nIntegrate the Stripe API for payment processing.');
|
||||
const r = runGate(tmpDir, phaseDir);
|
||||
assert.ok(r.success, `gate should succeed (JSON). stderr: ${r.error}`);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, true, 'must block when API integration has no matrix');
|
||||
assert.strictEqual(j.detected, true);
|
||||
assert.strictEqual(j.coverage_present, false);
|
||||
});
|
||||
|
||||
test('#4 non-API phase without a matrix → does NOT block', () => {
|
||||
fresh();
|
||||
writePlan(phaseDir, '01-PLAN.md', '# Plan\nRefactor the auth helper to use bcrypt.');
|
||||
const r = runGate(tmpDir, phaseDir);
|
||||
assert.ok(r.success, `gate should succeed. stderr: ${r.error}`);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, false, 'must not block a non-API phase');
|
||||
assert.strictEqual(j.detected, false);
|
||||
});
|
||||
|
||||
test('#1/#6 API phase WITH a valid matrix → passes (matrix persists on disk)', () => {
|
||||
fresh();
|
||||
writePlan(phaseDir, '01-PLAN.md', '# Plan\nIntegrate the Stripe API for payment processing.');
|
||||
writeCoverage(
|
||||
phaseDir,
|
||||
'| capability | decision | reason |\n|---|---|---|\n' +
|
||||
'| charge | INTEGRATE | |\n| refund | OPT-OUT | not needed yet |\n'
|
||||
);
|
||||
const r = runGate(tmpDir, phaseDir);
|
||||
assert.ok(r.success, `gate should succeed. stderr: ${r.error}`);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, false);
|
||||
assert.strictEqual(j.coverage_present, true);
|
||||
assert.strictEqual(j.counts.surface, 2);
|
||||
assert.strictEqual(j.counts.optout, 1);
|
||||
});
|
||||
|
||||
test('#2 OPT-OUT without a reason → BLOCKS (un-decided hole)', () => {
|
||||
fresh();
|
||||
writePlan(phaseDir, '01-PLAN.md', '# Plan\nIntegrate the Stripe API.');
|
||||
writeCoverage(
|
||||
phaseDir,
|
||||
'| capability | decision | reason |\n|---|---|---|\n| refund | OPT-OUT | |\n'
|
||||
);
|
||||
const r = runGate(tmpDir, phaseDir);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, true, 'opt-out without reason must block');
|
||||
assert.ok(j.errors.some((e) => /missing reason/i.test(e)));
|
||||
});
|
||||
|
||||
test('#2 empty matrix → BLOCKS (surface must be enumerated)', () => {
|
||||
fresh();
|
||||
writePlan(phaseDir, '01-PLAN.md', '# Plan\nIntegrate the Stripe API.');
|
||||
writeCoverage(phaseDir, '| capability | decision | reason |\n|---|---|---|\n');
|
||||
const r = runGate(tmpDir, phaseDir);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, true);
|
||||
assert.ok(j.errors.some((e) => /empty/i.test(e)));
|
||||
});
|
||||
|
||||
test('#3 a second platform with full-coverage baseline is accepted (no asymmetry)', () => {
|
||||
fresh();
|
||||
writePlan(phaseDir, '01-PLAN.md', '# Plan\nAdd a YouTube SDK as a second media platform.');
|
||||
// Full-coverage baseline for the second platform: every capability decided.
|
||||
writeCoverage(
|
||||
phaseDir,
|
||||
'| capability | decision | reason |\n|---|---|---|\n' +
|
||||
'| search | INTEGRATE | |\n| playlists | INTEGRATE | |\n| skip | INTEGRATE | |\n'
|
||||
);
|
||||
const r = runGate(tmpDir, phaseDir);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, false, 'a fully-decided second platform seals clean');
|
||||
assert.strictEqual(j.counts.surface, 3);
|
||||
});
|
||||
|
||||
test('JSON-fenced matrix is accepted (machine-generated form)', () => {
|
||||
fresh();
|
||||
writePlan(phaseDir, '01-PLAN.md', '# Plan\nIntegrate the Stripe API.');
|
||||
writeCoverage(
|
||||
phaseDir,
|
||||
'```coverage\n[{"capability":"charge","decision":"INTEGRATE","reason":""}]\n```\n'
|
||||
);
|
||||
const r = runGate(tmpDir, phaseDir);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, false);
|
||||
assert.strictEqual(j.counts.surface, 1);
|
||||
});
|
||||
|
||||
// ── Security (#1562 security review S1/S2): the phase arg is taken only as a
|
||||
// token resolved under .planning/phases/. Traversal / unresolvable args must
|
||||
// NOT read files outside the phase dir, and — since the phases tree exists —
|
||||
// must fail CLOSED (a blocking gate must not silently bypass on a bad arg).
|
||||
test('path-traversal arg is contained and fails CLOSED (phases tree exists)', () => {
|
||||
fresh(); // creates .planning/phases/01-pay
|
||||
const r = runTools(['check', 'api-coverage.verify-pre', '../../etc', '--raw'], tmpDir);
|
||||
assert.ok(r.success, `gate should succeed (JSON). stderr: ${r.error}`);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, true, 'unresolvable phase under an existing phases tree must block');
|
||||
assert.strictEqual(j.phase_lookup_failed, true);
|
||||
});
|
||||
|
||||
test('no .planning/phases at all → fail-open (genuine non-GSD project)', () => {
|
||||
// A project with .planning/config.json but no phases directory.
|
||||
const noPhases = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-apicov-nophase-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(noPhases, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(noPhases, '.planning', 'config.json'),
|
||||
JSON.stringify({ workflow: { api_coverage_gate: true } }),
|
||||
'utf8'
|
||||
);
|
||||
const r = runTools(['check', 'api-coverage.verify-pre', '01-pay', '--raw'], noPhases);
|
||||
assert.ok(r.success);
|
||||
const j = JSON.parse(r.output);
|
||||
assert.strictEqual(j.block, false, 'no phases tree → pass (not a GSD project)');
|
||||
} finally {
|
||||
cleanup(noPhases);
|
||||
}
|
||||
});
|
||||
});
|
||||
410
tests/api-coverage.test.cjs
Normal file
410
tests/api-coverage.test.cjs
Normal file
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Tests for the API-coverage detector + matrix validator (#1562).
|
||||
*
|
||||
* Two pure functions under test, both returning typed IR (asserted, never
|
||||
* text-matched on prose):
|
||||
* - detectApiIntegration(text) -> { detected, signals, terms }
|
||||
* - validateCoverageMatrix(text) -> { valid, errors, counts }
|
||||
*
|
||||
* Acceptance-criterion mapping:
|
||||
* #2 (opt-out needs reason; un-enumerated blocks) → validateCoverageMatrix suite
|
||||
* #4 (non-API phases unaffected, low false-positive) → false-positive suite
|
||||
* Matrix parse/render bijectivity → fast-check property
|
||||
*/
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
const fc = require('fast-check');
|
||||
|
||||
const MODULE_PATH = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'api-coverage.cjs');
|
||||
|
||||
describe('detectApiIntegration — pure detector (#1562)', () => {
|
||||
let mod;
|
||||
try {
|
||||
mod = require(MODULE_PATH);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Could not require ${MODULE_PATH}. Run "npm run build:lib" first. Underlying: ${err.message}`
|
||||
);
|
||||
}
|
||||
const { detectApiIntegration, DEFAULT_API_COVERAGE_TERMS } = mod;
|
||||
|
||||
test('result shape — always carries detected, signals[], terms', () => {
|
||||
const r = detectApiIntegration('refactor the login function');
|
||||
assert.strictEqual(r.detected, false);
|
||||
assert(Array.isArray(r.signals));
|
||||
assert.strictEqual(r.signals.length, 0);
|
||||
assert.ok(r.terms && Array.isArray(r.terms.verbs));
|
||||
assert.ok(Array.isArray(r.terms.nouns));
|
||||
});
|
||||
|
||||
test('non-string input degrades to {detected:false} without throwing', () => {
|
||||
assert.strictEqual(detectApiIntegration(undefined).detected, false);
|
||||
assert.strictEqual(detectApiIntegration(null).detected, false);
|
||||
assert.strictEqual(detectApiIntegration(42).detected, false);
|
||||
assert.strictEqual(detectApiIntegration({}).detected, false);
|
||||
});
|
||||
|
||||
test('empty / whitespace-only input does not fire', () => {
|
||||
assert.strictEqual(detectApiIntegration('').detected, false);
|
||||
assert.strictEqual(detectApiIntegration(' \n\t ').detected, false);
|
||||
});
|
||||
|
||||
test('terms echo is the effective set actually used', () => {
|
||||
const r = detectApiIntegration('nothing relevant here');
|
||||
assert.deepStrictEqual(r.terms.verbs, [...DEFAULT_API_COVERAGE_TERMS.verbs]);
|
||||
assert.deepStrictEqual(r.terms.nouns, [...DEFAULT_API_COVERAGE_TERMS.nouns]);
|
||||
});
|
||||
|
||||
test('terms override — explicit verbs+nouns replace defaults', () => {
|
||||
const r = detectApiIntegration('xyzzy the frobninator', { verbs: ['xyzzy'], nouns: ['frobninator'] });
|
||||
assert.deepStrictEqual(r.terms.verbs, ['xyzzy']);
|
||||
assert.deepStrictEqual(r.terms.nouns, ['frobninator']);
|
||||
assert.strictEqual(r.detected, true);
|
||||
});
|
||||
|
||||
// ── Positive: compound verb+noun (acceptance #1 trigger) ─────────────────
|
||||
for (const scope of [
|
||||
'Integrate the Stripe API for payment processing',
|
||||
'Wrap the GitHub GraphQL API for issue triage',
|
||||
'Connect to the SendGrid REST endpoint for transactional email',
|
||||
'Consume the billing service over gRPC',
|
||||
'Wire up the Slack webhook for deploy notifications',
|
||||
'Onboard the Twilio SDK for SMS',
|
||||
'integrate oauth for login',
|
||||
]) {
|
||||
test(`POSITIVE fires on: "${scope}"`, () => {
|
||||
const r = detectApiIntegration(scope);
|
||||
assert.strictEqual(r.detected, true, `expected detection for: ${scope}`);
|
||||
assert.ok(r.signals.length > 0);
|
||||
assert.ok(r.signals.every((s) => s.snippet.length > 0));
|
||||
});
|
||||
}
|
||||
|
||||
// ── Positive: explicit <Service> API|SDK surface (no verb needed) ────────
|
||||
for (const scope of ['Add a Spotify API client', 'Ship the Notion SDK helper']) {
|
||||
test(`POSITIVE (surface) fires on: "${scope}"`, () => {
|
||||
const r = detectApiIntegration(scope);
|
||||
assert.strictEqual(r.detected, true);
|
||||
assert.ok(r.signals.some((s) => s.verb === '(surface)'));
|
||||
});
|
||||
}
|
||||
|
||||
// ── Negative: false-positive guards (acceptance #4 — the crux) ───────────
|
||||
// Each of these is a phase that is NOT an external-API integration. The
|
||||
// detector must stay silent. The "public API of UserController" case is the
|
||||
// canonical FP trap: "api" is present but there is no integration verb.
|
||||
for (const [label, scope] of [
|
||||
['internal API mention', 'The public API of the UserController should accept pagination params'],
|
||||
['refactor', 'Refactor the authentication module to use bcrypt'],
|
||||
['feature toggle', 'Add a dark mode toggle to the settings page'],
|
||||
['bug fix', 'Fix the off-by-one error in the pagination helper'],
|
||||
['docs', 'Update the README to document the config options'],
|
||||
['internal client code', 'Add a client-side helper to debounce input'],
|
||||
['bare noun no verb', 'We expose a REST-ish JSON shape already'],
|
||||
['bare verb no noun', 'We will integrate the new design system tokens'],
|
||||
]) {
|
||||
test(`NEGATIVE does not fire (${label}): "${scope}"`, () => {
|
||||
const r = detectApiIntegration(scope);
|
||||
assert.strictEqual(r.detected, false, `unexpected detection for [${label}]: ${scope}`);
|
||||
});
|
||||
}
|
||||
|
||||
test('fenced code blocks are stripped — trigger inside a code fence does not fire', () => {
|
||||
const scope = [
|
||||
'Refactor the helpers.',
|
||||
'',
|
||||
'```bash',
|
||||
'# integrate the Stripe API (example command in docs)',
|
||||
'```',
|
||||
'',
|
||||
'No integration in this phase.',
|
||||
].join('\n');
|
||||
assert.strictEqual(detectApiIntegration(scope).detected, false);
|
||||
});
|
||||
|
||||
// ── H1 fix (#1562 code review): capitalized sentence starters must not fire
|
||||
// the <Service> API/SDK surface rule. These are common English, not services.
|
||||
for (const [label, scope] of [
|
||||
['The API', 'The API documentation needs updating.'],
|
||||
['An SDK', 'An SDK is already present in the repo.'],
|
||||
['Our REST', 'Our REST endpoints return JSON.'],
|
||||
['This GraphQL', 'This GraphQL schema is internal.'],
|
||||
['New API', 'New API surface was added by the refactor.'],
|
||||
]) {
|
||||
test(`NEGATIVE (stopword) does not fire (${label}): "${scope}"`, () => {
|
||||
assert.strictEqual(detectApiIntegration(scope).detected, false);
|
||||
});
|
||||
}
|
||||
|
||||
test('compound signal fires when verb and noun are on the same line only', () => {
|
||||
const sameLine = 'Phase A integrates things.\nLater we mention an api.';
|
||||
const splitLine = 'Phase A integrates things.\nLater we mention an api here too.';
|
||||
assert.strictEqual(detectApiIntegration(sameLine).detected, false);
|
||||
assert.strictEqual(detectApiIntegration(splitLine).detected, false);
|
||||
assert.strictEqual(detectApiIntegration('integrates the api').detected, true);
|
||||
});
|
||||
});
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// Matrix parse / validate / render
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('coverage matrix — parse / validate (#1562 acceptance #2)', () => {
|
||||
let mod;
|
||||
try {
|
||||
mod = require(MODULE_PATH);
|
||||
} catch (err) {
|
||||
throw new Error(`Could not require ${MODULE_PATH}. Run "npm run build:lib". Underlying: ${err.message}`);
|
||||
}
|
||||
const { parseCoverageMatrix, validateCoverageMatrix, renderCoverageMatrix } = mod;
|
||||
|
||||
test('parse markdown table — header + 2 rows', () => {
|
||||
const md = [
|
||||
'| capability | decision | reason |',
|
||||
'|---|---|---|',
|
||||
'| search | INTEGRATE | |',
|
||||
'| playlists | OPT-OUT | not needed yet |',
|
||||
].join('\n');
|
||||
const p = parseCoverageMatrix(md);
|
||||
assert.strictEqual(p.format, 'table');
|
||||
assert.strictEqual(p.rows.length, 2);
|
||||
assert.strictEqual(p.rows[0].capability, 'search');
|
||||
assert.strictEqual(p.rows[0].decision, 'INTEGRATE');
|
||||
assert.strictEqual(p.rows[1].decision, 'OPT-OUT');
|
||||
assert.strictEqual(p.rows[1].reason, 'not needed yet');
|
||||
assert.strictEqual(p.errors.length, 0);
|
||||
});
|
||||
|
||||
test('parse fenced ```coverage JSON block', () => {
|
||||
const md = [
|
||||
'Some prose.',
|
||||
'',
|
||||
'```coverage',
|
||||
'[{"capability":"search","decision":"INTEGRATE","reason":""},',
|
||||
' {"capability":"skip","decision":"OPT-OUT","reason":"out of scope"}]',
|
||||
'```',
|
||||
].join('\n');
|
||||
const p = parseCoverageMatrix(md);
|
||||
assert.strictEqual(p.format, 'json');
|
||||
assert.strictEqual(p.rows.length, 2);
|
||||
assert.strictEqual(p.errors.length, 0);
|
||||
});
|
||||
|
||||
test('parse empty / non-matrix input → format none, no rows, no errors', () => {
|
||||
const p = parseCoverageMatrix('# Notes\n\nNothing here.');
|
||||
assert.strictEqual(p.format, 'none');
|
||||
assert.strictEqual(p.rows.length, 0);
|
||||
assert.strictEqual(p.errors.length, 0);
|
||||
});
|
||||
|
||||
test('parse non-string → empty result, no throw', () => {
|
||||
const p = parseCoverageMatrix(undefined);
|
||||
assert.strictEqual(p.rows.length, 0);
|
||||
});
|
||||
|
||||
test('parse rejects malformed fenced JSON with an error', () => {
|
||||
const md = '```coverage\n{not json}\n```';
|
||||
const p = parseCoverageMatrix(md);
|
||||
assert.strictEqual(p.format, 'json');
|
||||
assert.ok(p.errors.length > 0);
|
||||
assert.strictEqual(p.rows.length, 0);
|
||||
});
|
||||
|
||||
// ── validate: boundaries 0 / 1 / 2 rows (limit-1, limit, limit+1) ────────
|
||||
test('validate — empty matrix is invalid (acceptance #1: surface must be enumerated)', () => {
|
||||
const v = validateCoverageMatrix('| capability | decision | reason |\n|---|---|---|');
|
||||
assert.strictEqual(v.valid, false);
|
||||
assert.ok(v.errors.some((e) => /empty/i.test(e)));
|
||||
assert.strictEqual(v.counts.surface, 0);
|
||||
});
|
||||
|
||||
test('validate — single INTEGRATE row is valid (limit)', () => {
|
||||
const md = '| capability | decision | reason |\n|---|---|---|\n| search | INTEGRATE | |';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, true);
|
||||
assert.strictEqual(v.counts.surface, 1);
|
||||
assert.strictEqual(v.counts.integrate, 1);
|
||||
assert.strictEqual(v.counts.optout, 0);
|
||||
});
|
||||
|
||||
test('validate — two rows valid (limit+1)', () => {
|
||||
const md = [
|
||||
'| capability | decision | reason |',
|
||||
'|---|---|---|',
|
||||
'| search | INTEGRATE | |',
|
||||
'| playlists | OPT-OUT | not needed |',
|
||||
].join('\n');
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, true);
|
||||
assert.strictEqual(v.counts.surface, 2);
|
||||
assert.strictEqual(v.counts.optout, 1);
|
||||
});
|
||||
|
||||
// ── acceptance #2: every OPT-OUT must carry a reason ─────────────────────
|
||||
test('validate — OPT-OUT without reason is INVALID (acceptance #2)', () => {
|
||||
const md = '| capability | decision | reason |\n|---|---|---|\n| skip | OPT-OUT | |';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, false);
|
||||
assert.ok(v.errors.some((e) => /missing reason/i.test(e)));
|
||||
});
|
||||
|
||||
test('validate — OPT-OUT with one-char reason is valid (boundary)', () => {
|
||||
const md = '| capability | decision | reason |\n|---|---|---|\n| skip | OPT-OUT | x |';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, true);
|
||||
});
|
||||
|
||||
test('validate — duplicate capability is invalid (matrix is a set of decisions)', () => {
|
||||
const md = [
|
||||
'| capability | decision | reason |',
|
||||
'|---|---|---|',
|
||||
'| search | INTEGRATE | |',
|
||||
'| Search | OPT-OUT | dup |',
|
||||
].join('\n');
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, false);
|
||||
assert.ok(v.errors.some((e) => /duplicate/i.test(e)));
|
||||
});
|
||||
|
||||
test('validate — empty capability name is invalid', () => {
|
||||
const md = '| capability | decision | reason |\n|---|---|---|\n| | INTEGRATE | |';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, false);
|
||||
assert.ok(v.errors.some((e) => /empty capability/i.test(e)));
|
||||
});
|
||||
|
||||
// ── review-fix coverage: parser robustness (#1562 code review M1/M2/L1/L2) ──
|
||||
test('validate — invalid decision cell in a table row is an error, not silently dropped (M1)', () => {
|
||||
const md = '| capability | decision | reason |\n|---|---|---|\n| x | INTEGRAT | |';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, false);
|
||||
assert.ok(v.errors.some((e) => /not in \{INTEGRATE, OPT-OUT\}/i.test(e)));
|
||||
});
|
||||
|
||||
test('validate — a pipe in a cell adds extra columns → invalid (M2)', () => {
|
||||
const md = '| capability | decision | reason |\n|---|---|---|\n| x | OPT-OUT | a|b |';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, false);
|
||||
assert.ok(v.errors.some((e) => /columns|pipe/i.test(e)));
|
||||
});
|
||||
|
||||
test('validate — a pipe in a JSON-fence reason → invalid (M2)', () => {
|
||||
const md = '```coverage\n[{"capability":"x","decision":"OPT-OUT","reason":"a|b"}]\n```';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, false);
|
||||
});
|
||||
|
||||
test('validate — capability over the length cap → invalid (S3 bound)', () => {
|
||||
const longCap = 'x'.repeat(81);
|
||||
const md = `| capability | decision | reason |\n|---|---|---|\n| ${longCap} | INTEGRATE | |`;
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.valid, false);
|
||||
assert.ok(v.errors.some((e) => /exceeds/i.test(e)));
|
||||
});
|
||||
|
||||
test('parse — case-insensitive ```Coverage fence is accepted (L1)', () => {
|
||||
const md = '```Coverage\n[{"capability":"a","decision":"INTEGRATE","reason":""}]\n```';
|
||||
const p = parseCoverageMatrix(md);
|
||||
assert.strictEqual(p.format, 'json');
|
||||
assert.strictEqual(p.rows.length, 1);
|
||||
});
|
||||
|
||||
test('parse — a single-dash cell is not mistaken for a separator (L2)', () => {
|
||||
const md = '| capability | decision | reason |\n|---|---|---|\n| - | INTEGRATE | |';
|
||||
const v = validateCoverageMatrix(md);
|
||||
assert.strictEqual(v.counts.surface, 1);
|
||||
assert.ok(v.valid, 'a capability named "-" is legal');
|
||||
});
|
||||
|
||||
// ── render round-trip (manual) ───────────────────────────────────────────
|
||||
test('render → parse round-trips a valid matrix', () => {
|
||||
const rows = [
|
||||
{ capability: 'search', decision: 'INTEGRATE', reason: '' },
|
||||
{ capability: 'playlists', decision: 'OPT-OUT', reason: 'not needed yet' },
|
||||
];
|
||||
const rendered = renderCoverageMatrix(rows);
|
||||
const v = validateCoverageMatrix(rendered);
|
||||
assert.strictEqual(v.valid, true);
|
||||
assert.strictEqual(v.counts.surface, 2);
|
||||
});
|
||||
});
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// Property test: parse/render bijectivity (RULESET.TESTS property-based)
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('coverage matrix — parse/render bijection (fast-check)', () => {
|
||||
let mod;
|
||||
try {
|
||||
mod = require(MODULE_PATH);
|
||||
} catch (err) {
|
||||
throw new Error(`Could not require ${MODULE_PATH}. Run "npm run build:lib". Underlying: ${err.message}`);
|
||||
}
|
||||
const { renderCoverageMatrix, validateCoverageMatrix } = mod;
|
||||
|
||||
test('any valid row set renders and re-validates to the same counts', () => {
|
||||
const capabilityGen = fc.stringMatching(/^[a-z][a-z0-9-]{0,14}$/);
|
||||
const rowGen = fc.record({
|
||||
capability: capabilityGen,
|
||||
decision: fc.constantFrom('INTEGRATE', 'OPT-OUT'),
|
||||
// Reasons are short prose (e.g. "not needed yet"). The matrix is a
|
||||
// markdown table, so cell text is format-safe: no pipes / newlines.
|
||||
reason: fc.stringMatching(/^[a-z0-9 ,.\-!?]{0,20}$/),
|
||||
});
|
||||
// OPT-OUT rows must carry a non-empty reason for the round-trip to validate.
|
||||
const validRowGen = rowGen.map((r) =>
|
||||
r.decision === 'OPT-OUT' && r.reason.trim() === ''
|
||||
? { ...r, reason: 'because' }
|
||||
: { ...r, reason: r.reason.trim() }
|
||||
);
|
||||
const matrixGen = fc.uniqueArray(validRowGen, {
|
||||
minLength: 1,
|
||||
maxLength: 8,
|
||||
selector: (r) => r.capability.toLowerCase(),
|
||||
});
|
||||
fc.assert(
|
||||
fc.property(matrixGen, (rows) => {
|
||||
const rendered = renderCoverageMatrix(rows);
|
||||
const v = validateCoverageMatrix(rendered);
|
||||
// Injected reason guarantees validity; any invalid result is a parser bug.
|
||||
assert.strictEqual(v.valid, true);
|
||||
assert.strictEqual(v.counts.surface, rows.length);
|
||||
return true;
|
||||
}),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// CLI entry point (STDIN → exit codes mirror grep, like assumption-delta)
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('api-coverage CLI — STDIN + exit codes', () => {
|
||||
const CLI = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'api-coverage.cjs');
|
||||
|
||||
function runCli(stdin) {
|
||||
const r = spawnSync(process.execPath, [CLI, '--json'], {
|
||||
input: stdin,
|
||||
encoding: 'utf-8',
|
||||
timeout: 15000,
|
||||
});
|
||||
return { exitCode: r.status, stdout: r.stdout, stderr: r.stderr };
|
||||
}
|
||||
|
||||
test('exit 0 + JSON IR when integration detected', () => {
|
||||
const r = runCli('Integrate the Stripe API for payments');
|
||||
assert.strictEqual(r.exitCode, 0);
|
||||
const body = JSON.parse(r.stdout);
|
||||
assert.strictEqual(body.detected, true);
|
||||
assert.ok(Array.isArray(body.signals));
|
||||
});
|
||||
|
||||
test('exit 1 when no integration', () => {
|
||||
const r = runCli('Refactor the login helper');
|
||||
assert.strictEqual(r.exitCode, 1);
|
||||
});
|
||||
});
|
||||
@@ -81,6 +81,7 @@ describe('config-field-docs', () => {
|
||||
verifier: 'workflow.verifier',
|
||||
nyquist_validation: 'workflow.nyquist_validation',
|
||||
ai_integration_phase: 'workflow.ai_integration_phase',
|
||||
api_coverage_gate: 'workflow.api_coverage_gate',
|
||||
text_mode: 'workflow.text_mode',
|
||||
subagent_timeout: 'workflow.subagent_timeout',
|
||||
branching_strategy: 'git.branching_strategy',
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "51b5d0ba5b1e98d9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "425dd69c629230e7",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "e176817364a7cbf4",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "efd80aca449032ad",
|
||||
"gsd-core/references/checkpoints.md": "2de680837faa9752",
|
||||
@@ -108,7 +109,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "b0e55e9d585f90c1",
|
||||
"gsd-core/references/planning-config.md": "2e2f418328e52f6a",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -308,7 +309,7 @@
|
||||
"gsd-core/workflows/update.md": "2c58df5e21c41c31",
|
||||
"gsd-core/workflows/validate-phase.md": "6c0ab739d15709fa",
|
||||
"gsd-core/workflows/verify-phase.md": "0eefbb6bb1b0bed6",
|
||||
"gsd-core/workflows/verify-work.md": "be699ed7920f61b0",
|
||||
"gsd-core/workflows/verify-work.md": "59276d94999ee022",
|
||||
"hooks/gsd-check-update-worker.js": "fa301e6366270d5f",
|
||||
"hooks/gsd-check-update.js": "4617a98bf529e4c3",
|
||||
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
@@ -179,7 +180,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/planning-config.md": "ac409835e8260a3e",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -379,7 +380,7 @@
|
||||
"gsd-core/workflows/update.md": "fd160e13f8b7e83c",
|
||||
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
|
||||
"gsd-core/workflows/verify-phase.md": "22f18492581f1da5",
|
||||
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
|
||||
"gsd-core/workflows/verify-work.md": "63b3f680d8f0a6f3",
|
||||
"hooks/gsd-check-update-worker.js": "cc1ef5f840f9dfc9",
|
||||
"hooks/gsd-check-update.js": "7b3a7983d5f1f5d3",
|
||||
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
@@ -107,7 +108,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "99c46b7d318adf1a",
|
||||
"gsd-core/references/planning-config.md": "b025429fc72f9285",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -307,7 +308,7 @@
|
||||
"gsd-core/workflows/update.md": "f9e7d8a760d0d3c8",
|
||||
"gsd-core/workflows/validate-phase.md": "2ac231dc541441c2",
|
||||
"gsd-core/workflows/verify-phase.md": "e0957e153788a222",
|
||||
"gsd-core/workflows/verify-work.md": "efe57bdbbb3af03f",
|
||||
"gsd-core/workflows/verify-work.md": "c8ffee621e7319de",
|
||||
"hooks/gsd-check-update-worker.js": "a530efdb5fdc0da3",
|
||||
"hooks/gsd-check-update.js": "25cde66a12d6b886",
|
||||
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "59f782556e4e611f",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "9a7ba3a17ece1698",
|
||||
@@ -111,7 +112,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "a0dc2dddb953b22c",
|
||||
"gsd-core/references/planning-config.md": "5a40b2934db6a321",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -311,7 +312,7 @@
|
||||
"gsd-core/workflows/update.md": "165beec33490bd28",
|
||||
"gsd-core/workflows/validate-phase.md": "5b4ae14c87859bd2",
|
||||
"gsd-core/workflows/verify-phase.md": "a4f918c92c927268",
|
||||
"gsd-core/workflows/verify-work.md": "6fa216623778c541",
|
||||
"gsd-core/workflows/verify-work.md": "3fb282f2bce34a79",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
@@ -179,7 +180,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/planning-config.md": "ac409835e8260a3e",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -379,7 +380,7 @@
|
||||
"gsd-core/workflows/update.md": "5ff1f77222977648",
|
||||
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
|
||||
"gsd-core/workflows/verify-phase.md": "22f18492581f1da5",
|
||||
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
|
||||
"gsd-core/workflows/verify-work.md": "63b3f680d8f0a6f3",
|
||||
"hooks/gsd-check-update-worker.js": "bdc9324a2f080ddd",
|
||||
"hooks/gsd-check-update.js": "b7669f605631e506",
|
||||
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "51b5d0ba5b1e98d9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "425dd69c629230e7",
|
||||
"gsd-core/references/api-coverage.md": "524382216a8e713f",
|
||||
"gsd-core/references/artifact-types.md": "3218cafb0c92dc32",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "4156025334411073",
|
||||
"gsd-core/references/checkpoints.md": "9feb961f644afa96",
|
||||
@@ -143,7 +144,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "7889bfa28e82156b",
|
||||
"gsd-core/references/planner-revision.md": "2ebf1a714d1ec4bf",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "40d4abb1ed0f3723",
|
||||
"gsd-core/references/planning-config.md": "b01856d7a63e73a2",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -343,7 +344,7 @@
|
||||
"gsd-core/workflows/update.md": "5c35c0ec0f462ea6",
|
||||
"gsd-core/workflows/validate-phase.md": "020201a41049679f",
|
||||
"gsd-core/workflows/verify-phase.md": "2e12c3cb97a9122a",
|
||||
"gsd-core/workflows/verify-work.md": "69e27f6f419d0bba",
|
||||
"gsd-core/workflows/verify-work.md": "5b348e73fc0d0829",
|
||||
"hooks/gsd-check-update.js": "ef48957eb6ac6a10",
|
||||
"hooks/gsd-context-monitor.js": "76fecaaa2babd6c1",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "51b5d0ba5b1e98d9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "425dd69c629230e7",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "f992de8b2b1a4420",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "efd80aca449032ad",
|
||||
"gsd-core/references/checkpoints.md": "c70b323dcb1583d5",
|
||||
@@ -109,7 +110,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "fd81dd276828eab4",
|
||||
"gsd-core/references/planning-config.md": "309a566a0e13e43e",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -309,7 +310,7 @@
|
||||
"gsd-core/workflows/update.md": "f444a7cfcd246cfb",
|
||||
"gsd-core/workflows/validate-phase.md": "2f705775a4b76d42",
|
||||
"gsd-core/workflows/verify-phase.md": "5c72780e34214e27",
|
||||
"gsd-core/workflows/verify-work.md": "58e9b1b16f773b53",
|
||||
"gsd-core/workflows/verify-work.md": "c966971a3cbd1d71",
|
||||
"hooks/gsd-session.json": "0a462834f2a28fee",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "3d62d178004db5cc",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "273b371c5751f35a",
|
||||
"gsd-core/references/checkpoints.md": "3001eeccb319781b",
|
||||
@@ -179,7 +180,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "8af05de88771e9f4",
|
||||
"gsd-core/references/planning-config.md": "a3f1b4aca291db59",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -379,7 +380,7 @@
|
||||
"gsd-core/workflows/update.md": "23e294ba707c3580",
|
||||
"gsd-core/workflows/validate-phase.md": "2df0c6e298a5f249",
|
||||
"gsd-core/workflows/verify-phase.md": "e0957e153788a222",
|
||||
"gsd-core/workflows/verify-work.md": "145596b2542c457a",
|
||||
"gsd-core/workflows/verify-work.md": "e7e7e900c4874490",
|
||||
"hooks/gsd-cursor-post-tool.js": "019d503aee8b4a3f",
|
||||
"hooks/gsd-cursor-session-start.js": "c6e04ed597ea7020",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "892f086e846cd8e4",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "db8a7425ed808e24",
|
||||
@@ -108,7 +109,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "1358a14bded944f5",
|
||||
"gsd-core/references/planning-config.md": "831d626c214d92e4",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -308,7 +309,7 @@
|
||||
"gsd-core/workflows/update.md": "7499bb4cb2a3ce6f",
|
||||
"gsd-core/workflows/validate-phase.md": "75e8971d3981d06b",
|
||||
"gsd-core/workflows/verify-phase.md": "5c8d1305b47fbef4",
|
||||
"gsd-core/workflows/verify-work.md": "e56e07475d5eb51e",
|
||||
"gsd-core/workflows/verify-work.md": "7a9c9541d2d73fdc",
|
||||
"hooks/gsd-check-update-worker.js": "7989cc2bedd1138d",
|
||||
"hooks/gsd-check-update.js": "25f5ad726f76fc11",
|
||||
"hooks/gsd-config-reload.js": "880b696458e85e9b",
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "51b5d0ba5b1e98d9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "425dd69c629230e7",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "3986d58011bf9006",
|
||||
"gsd-core/references/checkpoints.md": "9feb961f644afa96",
|
||||
@@ -179,7 +180,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "aad463ddda23dbaf",
|
||||
"gsd-core/references/planning-config.md": "37ab69107a2f7dc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -379,7 +380,7 @@
|
||||
"gsd-core/workflows/update.md": "07dc2fba78ad1865",
|
||||
"gsd-core/workflows/validate-phase.md": "557e3251e3b9349a",
|
||||
"gsd-core/workflows/verify-phase.md": "e0957e153788a222",
|
||||
"gsd-core/workflows/verify-work.md": "b68ac37f6301a3b5",
|
||||
"gsd-core/workflows/verify-work.md": "9fc717845828c6e3",
|
||||
"kilo.json": "13151e97ff23c1aa",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
@@ -144,7 +145,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/planning-config.md": "ac409835e8260a3e",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -344,7 +345,7 @@
|
||||
"gsd-core/workflows/update.md": "6718e0632bba26ca",
|
||||
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
|
||||
"gsd-core/workflows/verify-phase.md": "22f18492581f1da5",
|
||||
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
|
||||
"gsd-core/workflows/verify-work.md": "63b3f680d8f0a6f3",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "51b5d0ba5b1e98d9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "425dd69c629230e7",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "218c55caf8aff6df",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "3986d58011bf9006",
|
||||
"gsd-core/references/checkpoints.md": "9feb961f644afa96",
|
||||
@@ -179,7 +180,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "aad463ddda23dbaf",
|
||||
"gsd-core/references/planning-config.md": "37ab69107a2f7dc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -379,7 +380,7 @@
|
||||
"gsd-core/workflows/update.md": "71b6cd852f38b4bc",
|
||||
"gsd-core/workflows/validate-phase.md": "abcdbc1b56780565",
|
||||
"gsd-core/workflows/verify-phase.md": "126be1d026900102",
|
||||
"gsd-core/workflows/verify-work.md": "f0d205568abfaf74",
|
||||
"gsd-core/workflows/verify-work.md": "ae07b3fa8b6f864e",
|
||||
"hooks/gsd-check-update-worker.js": "385fb7c67810baf6",
|
||||
"hooks/gsd-check-update.js": "4549451414ffa7d7",
|
||||
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "be09755fed7ad856",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "046171320f816346",
|
||||
@@ -108,7 +109,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "0037314fe38ca55c",
|
||||
"gsd-core/references/planning-config.md": "4c6de9b6d66aca73",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -308,7 +309,7 @@
|
||||
"gsd-core/workflows/update.md": "b34cb866152d4de3",
|
||||
"gsd-core/workflows/validate-phase.md": "e989cbaa4228564c",
|
||||
"gsd-core/workflows/verify-phase.md": "0dc52b9e629a5f5a",
|
||||
"gsd-core/workflows/verify-work.md": "b5afb65fdf311301",
|
||||
"gsd-core/workflows/verify-work.md": "3355ddc14f052fff",
|
||||
"hooks/gsd-check-update-worker.js": "4bb354044e0dff91",
|
||||
"hooks/gsd-check-update.js": "d2065cb3e725a42a",
|
||||
"hooks/gsd-config-reload.js": "4f52b8a0120bb1b8",
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "18d1ff7c7fa0bab1",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "4b8c645ffa695067",
|
||||
@@ -108,7 +109,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "e47cdb2337e10dac",
|
||||
"gsd-core/references/planning-config.md": "a686c1363ae626c5",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -308,7 +309,7 @@
|
||||
"gsd-core/workflows/update.md": "1f935251fca1f276",
|
||||
"gsd-core/workflows/validate-phase.md": "1c0ebe56d96a14d1",
|
||||
"gsd-core/workflows/verify-phase.md": "a151ed36eb51813b",
|
||||
"gsd-core/workflows/verify-work.md": "d3a0970205acc6a5",
|
||||
"gsd-core/workflows/verify-work.md": "dde2a42a56c27c4e",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "849977da771f2b06",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "273b371c5751f35a",
|
||||
"gsd-core/references/checkpoints.md": "808e4fcaa2fda15c",
|
||||
@@ -108,7 +109,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "aafbfc62a3bb84b1",
|
||||
"gsd-core/references/planning-config.md": "1fc70920778d7c8e",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -308,7 +309,7 @@
|
||||
"gsd-core/workflows/update.md": "79aaf4b8f1f83045",
|
||||
"gsd-core/workflows/validate-phase.md": "2db47bf5547d7b9d",
|
||||
"gsd-core/workflows/verify-phase.md": "f961cdb3ef03ff05",
|
||||
"gsd-core/workflows/verify-work.md": "5ad63a5edfb6acac",
|
||||
"gsd-core/workflows/verify-work.md": "cce8ae44b30957f1",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
@@ -179,7 +180,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/planning-config.md": "ac409835e8260a3e",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -379,7 +380,7 @@
|
||||
"gsd-core/workflows/update.md": "dc580dee13f881a6",
|
||||
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
|
||||
"gsd-core/workflows/verify-phase.md": "22f18492581f1da5",
|
||||
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
|
||||
"gsd-core/workflows/verify-work.md": "63b3f680d8f0a6f3",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
* AC5: first-party caps unaffected; writer still rejects truly-unknown ids.
|
||||
*/
|
||||
|
||||
const { describe, test, before, after } = require('node:test');
|
||||
const { describe, test, after } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
* Hook points tested: discuss:pre, discuss:post, execute:pre, execute:wave:pre,
|
||||
* verify:pre, ship:post
|
||||
*
|
||||
* All 6 points have zero hooks in the real registry by design.
|
||||
* 5 of these points have zero hooks in the real registry by design.
|
||||
* verify:pre graduated out of the empty set in #1562 (it now carries the
|
||||
* ai-integration `api-coverage.verify-pre` blocking gate); SECTION 5 pins the
|
||||
* new contract + retains synthetic extension-point-mechanics coverage.
|
||||
* Tests pin: exact envelope shape, placeholder string contract (Hyrum's Law),
|
||||
* resolver-filter mechanics (schema default / config-override / capabilityStatesById),
|
||||
* CLI contract (missing-arg, invalid-point), and Postel-leniency (malformed config).
|
||||
@@ -481,8 +484,13 @@ describe('execute:wave:pre — real registry empty-resolution + synthetic mechan
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// SECTION 5: verify:pre
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// NOTE: verify:pre was an empty extension point until #1562 added the
|
||||
// ai-integration `api-coverage.verify-pre` blocking gate (default-on, opt-out
|
||||
// via workflow.api_coverage_gate=false). The real-registry assertions below pin
|
||||
// the NEW contract; the synthetic BVA tests retain extension-point-mechanics
|
||||
// coverage. Empty-point coverage for the other 5 points is unaffected.
|
||||
|
||||
describe('verify:pre — real registry empty-resolution + synthetic extension-point readiness', () => {
|
||||
describe('verify:pre — real registry carries the api-coverage gate (#1562); synthetic mechanics', () => {
|
||||
let tmpEmptyProjectDir;
|
||||
let tmpProjectDirAllOn;
|
||||
before(() => {
|
||||
@@ -494,29 +502,47 @@ describe('verify:pre — real registry empty-resolution + synthetic extension-po
|
||||
cleanup(tmpProjectDirAllOn);
|
||||
});
|
||||
|
||||
it('[empty-resolution] verify:pre with real registry and no config yields empty activeHooks and exact placeholder (Gall\'s Law)', () => {
|
||||
it('[default-on] verify:pre with real registry and no config yields the api-coverage blocking gate (full-coverage-by-default)', () => {
|
||||
const resolved = resolveLoopHooks({ point: 'verify:pre', registry: realRegistry, config: {} });
|
||||
assert.strictEqual(resolved.activeHooks.length, 0);
|
||||
assert.strictEqual(renderLoopHooks(resolved), '_No active hooks at verify:pre._');
|
||||
const gate = resolved.activeHooks.find((h) => h.capId === 'ai-integration' && h.kind === 'gate');
|
||||
assert.ok(gate, 'ai-integration api-coverage gate must register at verify:pre by default');
|
||||
assert.strictEqual(gate.blocking, true);
|
||||
assert.strictEqual(gate.check.query, 'api-coverage.verify-pre');
|
||||
});
|
||||
|
||||
it('[happy] verify:pre E2E subprocess returns well-formed 3-key JSON envelope with empty activeHooks (Hyrum\'s Law contract pin)', () => {
|
||||
it('[happy] verify:pre E2E subprocess returns a well-formed envelope carrying the gate (Hyrum\'s Law contract pin)', () => {
|
||||
const result = spawnGsd(['loop', 'render-hooks', 'verify:pre', '--cwd', tmpEmptyProjectDir, '--raw'], tmpEmptyProjectDir);
|
||||
assert.strictEqual(result.status, 0, `expected exit 0. stderr: ${result.stderr}`);
|
||||
const envelope = JSON.parse(result.stdout.trim());
|
||||
assert.strictEqual(envelope.point, 'verify:pre');
|
||||
assert.deepEqual(envelope.activeHooks, []);
|
||||
assert.strictEqual(envelope.rendered, '_No active hooks at verify:pre._');
|
||||
assert.ok(Array.isArray(envelope.activeHooks));
|
||||
assert.ok(envelope.activeHooks.some((h) => h.capId === 'ai-integration' && h.kind === 'gate'));
|
||||
assert.deepEqual(Object.keys(envelope).sort(), ['activeHooks', 'point', 'rendered']);
|
||||
});
|
||||
|
||||
it('[negative] verify:pre with all capability config keys set to true still yields empty activeHooks — no leakage from other points', () => {
|
||||
it('[opt-out] verify:pre with workflow.api_coverage_gate=false yields NO ai-integration gate — config opt-out empties the point', () => {
|
||||
const resolved = resolveLoopHooks({
|
||||
point: 'verify:pre',
|
||||
registry: realRegistry,
|
||||
config: { workflow: { api_coverage_gate: false } },
|
||||
});
|
||||
assert.strictEqual(
|
||||
resolved.activeHooks.find((h) => h.capId === 'ai-integration'),
|
||||
undefined,
|
||||
'opting out api_coverage_gate must remove the gate from verify:pre'
|
||||
);
|
||||
});
|
||||
|
||||
it('[negative] verify:pre with unrelated capability keys on does not bleed non-verify:pre hooks into the point', () => {
|
||||
const resolved = resolveLoopHooks({
|
||||
point: 'verify:pre',
|
||||
registry: realRegistry,
|
||||
config: { workflow: { ui_phase: true, ui_review: true, ui_safety_gate: true } },
|
||||
});
|
||||
assert.strictEqual(resolved.activeHooks.length, 0, 'UI and other capabilities must not bleed through to verify:pre');
|
||||
// The only verify:pre hook is the api-coverage gate; UI/other hooks must not bleed in.
|
||||
for (const h of resolved.activeHooks) {
|
||||
assert.notStrictEqual(h.capId, 'ui', 'UI capability must not bleed through to verify:pre');
|
||||
}
|
||||
});
|
||||
|
||||
it('[bva] Synthetic step at verify:pre with configSchema default=true fires correctly — extension point readiness', () => {
|
||||
@@ -547,7 +573,10 @@ describe('verify:pre — real registry empty-resolution + synthetic extension-po
|
||||
const result = spawnGsd(['loop', 'render-hooks', 'verify:pre', '--cwd', malformedDir, '--raw'], malformedDir);
|
||||
assert.strictEqual(result.status, 0, `must not crash on malformed config. stderr: ${result.stderr}`);
|
||||
const envelope = JSON.parse(result.stdout.trim());
|
||||
assert.deepEqual(envelope.activeHooks, []);
|
||||
// Malformed config degrades to defaults (gate default-on); the contract
|
||||
// here is leniency (exit 0, well-formed envelope), not emptiness.
|
||||
assert.strictEqual(envelope.point, 'verify:pre');
|
||||
assert.ok(Array.isArray(envelope.activeHooks));
|
||||
} finally {
|
||||
cleanup(malformedDir);
|
||||
}
|
||||
@@ -720,13 +749,12 @@ describe('CLI contract — missing/invalid point argument (shared across all 6 e
|
||||
// SECTION 8: Parametric empty-point sweep across all 6 points (E2E regression guard)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Parametric E2E sweep — 5 empty points return correct envelope shape via real registry (ship:post excluded — mempalace registers 1 step there)', () => {
|
||||
describe('Parametric E2E sweep — 4 empty points return correct envelope shape via real registry (ship:post excluded — mempalace registers 1 step there; verify:pre excluded since #1562 added the api-coverage gate)', () => {
|
||||
const EMPTY_POINTS = [
|
||||
'discuss:pre',
|
||||
'discuss:post',
|
||||
'execute:pre',
|
||||
'execute:wave:pre',
|
||||
'verify:pre',
|
||||
];
|
||||
|
||||
for (const point of EMPTY_POINTS) {
|
||||
|
||||
@@ -246,6 +246,7 @@ describe('plan:pre all-off — empty resolution', () => {
|
||||
tmpDir = makeProject({
|
||||
workflow: {
|
||||
ai_integration_phase: false,
|
||||
api_coverage_gate: false,
|
||||
tdd_mode: false,
|
||||
security_enforcement: false,
|
||||
ui_phase: false,
|
||||
|
||||
@@ -89,5 +89,5 @@
|
||||
"update.md": 20914,
|
||||
"validate-phase.md": 10789,
|
||||
"verify-phase.md": 40923,
|
||||
"verify-work.md": 38267
|
||||
"verify-work.md": 40247
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user