mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 06:28:59 +02:00
test: coverage for gstack-shortcut marker grammar and retro harvest joint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c22592ceb0
commit
b1cf2bcdee
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* gstack-shortcut debt-ledger pins (free, static) — WS4.
|
||||
*
|
||||
* The convention has two halves that must stay joined:
|
||||
* - WRITER: the AskUserQuestion Format preamble (generate-ask-user-format.ts)
|
||||
* mandates the marker when a user accepts a Completeness ≤ 7 durable call:
|
||||
* `gstack-shortcut(dec-<id>): <ceiling>, upgrade when <trigger>`.
|
||||
* - HARVESTER: /retro Step 11.5 greps `gstack-shortcut(` into a debt ledger
|
||||
* joined on the decision id.
|
||||
*
|
||||
* Nothing else pins either half (the golden ship fixtures are freshness
|
||||
* checks — a deliberate deletion regenerates them and passes). A drift where
|
||||
* the writer changes the marker shape while the harvester greps the old one
|
||||
* silently empties the ledger, so the joint is asserted here explicitly.
|
||||
* The marker's redaction-guard survival is pinned in redact-engine.test.ts.
|
||||
*/
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const ROOT = path.join(import.meta.dir, '..');
|
||||
const read = (p: string) => fs.readFileSync(path.join(ROOT, p), 'utf-8');
|
||||
|
||||
/** The canonical marker prefix both halves must agree on. */
|
||||
const MARKER = 'gstack-shortcut(';
|
||||
|
||||
describe('writer: AUQ format preamble mandates the marker', () => {
|
||||
const resolver = read('scripts/resolvers/preamble/generate-ask-user-format.ts');
|
||||
|
||||
test('resolver emits the full marker grammar and its guardrails', () => {
|
||||
expect(resolver).toContain('gstack-shortcut(dec-<id>): <ceiling>, upgrade when <trigger>');
|
||||
// User-sovereignty guardrail: the marker is never agent-initiated.
|
||||
expect(resolver).toContain('Never agent-initiated');
|
||||
// The trail is joined to the decision store, not free-floating.
|
||||
expect(resolver).toContain('gstack-decision-log');
|
||||
// Scope guard: turn-level choices never leave debt markers.
|
||||
expect(resolver).toContain('never a turn-level choice');
|
||||
});
|
||||
|
||||
test('rendered tier-2+ skeletons carry the trail rule (always-loaded, like the AUQ format itself)', () => {
|
||||
for (const skill of ['plan-ceo-review', 'ship', 'retro']) {
|
||||
const body = read(`${skill}/SKILL.md`);
|
||||
expect(body).toContain('Accepted shortcuts leave a trail');
|
||||
expect(body).toContain('gstack-shortcut(dec-<id>)');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('harvester: /retro Step 11.5 debt ledger', () => {
|
||||
const tmpl = read('retro/SKILL.md.tmpl');
|
||||
const rendered = read('retro/SKILL.md');
|
||||
|
||||
test('template ships the ledger step, and generation carries it into SKILL.md', () => {
|
||||
for (const body of [tmpl, rendered]) {
|
||||
expect(body).toContain('### Step 11.5: Shortcut Debt Ledger');
|
||||
expect(body).toContain(`grep -rn "${MARKER}"`);
|
||||
// Zero markers is the healthy case — the step must say so, not fail.
|
||||
expect(body).toContain('No shortcut debt. Clean ledger.');
|
||||
expect(body).toContain('N markers, M with no trigger.');
|
||||
}
|
||||
});
|
||||
|
||||
test('the harvest grep excludes convention docs; ledger rows join decisions and name the rot taxonomy', () => {
|
||||
const step = tmpl.split('### Step 11.5')[1]?.split('### Step 12')[0] ?? '';
|
||||
for (const excl of [
|
||||
'--exclude-dir=.git',
|
||||
'--exclude-dir=node_modules',
|
||||
'--exclude-dir=vendor',
|
||||
'--exclude-dir=.claude',
|
||||
'--exclude-dir=dist',
|
||||
'--exclude="SKILL.md"',
|
||||
'--exclude="*.md.tmpl"',
|
||||
]) {
|
||||
expect(step).toContain(excl);
|
||||
}
|
||||
// Zero matches must not fail the pipeline the step runs in.
|
||||
expect(step).toContain('|| true');
|
||||
// Rows join the decision store and tag the two ways a marker rots.
|
||||
expect(step).toContain('gstack-decision-search');
|
||||
expect(step).toContain('`unlinked`');
|
||||
expect(step).toContain('`no-trigger`');
|
||||
expect(step).toContain('never double-count');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the joint: writer grammar matches harvester grep', () => {
|
||||
test('what the harvester greps is a prefix of what the writer mandates', () => {
|
||||
const resolver = read('scripts/resolvers/preamble/generate-ask-user-format.ts');
|
||||
const tmpl = read('retro/SKILL.md.tmpl');
|
||||
// Harvester pattern, extracted from the actual grep line.
|
||||
const grepLine = tmpl.split('\n').find((l) => l.includes('grep -rn'));
|
||||
expect(grepLine).toBeDefined();
|
||||
const m = /grep -rn "([^"]+)"/.exec(grepLine!);
|
||||
expect(m).not.toBeNull();
|
||||
const harvested = m![1];
|
||||
expect(harvested).toBe(MARKER);
|
||||
// Writer's mandated grammar starts with exactly that prefix.
|
||||
expect(resolver).toContain(`\`${MARKER}dec-<id>)`);
|
||||
});
|
||||
|
||||
test('review checklist suppression and simplification specialist both honor the marker (acknowledged debt is not a finding)', () => {
|
||||
expect(read('review/checklist.md')).toContain('gstack-shortcut(dec-*)');
|
||||
expect(read('review/specialists/simplification.md')).toContain('gstack-shortcut(dec-*)');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user