Files
gstack/test/honesty-kernel.test.ts
T
Garry Tanandcapy-ai[bot] b9222a57b7 v1.91.247.0 fix(plan-eng-review): define the three references its judged text left dangling (GSTA-91) (#3421)
* fix(plan-eng-review): define the three references its judged text left dangling (GSTA-91)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: ship coverage test, unattended doc and CHANGELOG for review fixes

test/plan-reality.test.ts asserts each phase's gstack-plan-reality check line names PLAN_REALITY (Step 7 gap). docs/unattended.md names the unattended-session block the skill prose now cites (Step 14.5 audit). CHANGELOG [Unreleased] covers the hook pointer fix and the new tests.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): drop the hook directive claim (GSTA-91)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: keep destructive options pending in the unattended AUQ fallback (GSTA-91)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: trim the unattended pointers so every size pin goes down (GSTA-91)

The failure fallback's unattended branch now says a consent, destructive or irreversible option, unrecommended question or approval gate stays pending, keeping the carve-out. Item 1 drops its dangling '(per its printed unattended-session block)' pointer. CQ3 now asserts no section or block pointer remains in item 1; CQ4 is retitled to the block and hook; the plan-reality pointer loop drops devex, which renders no reality rows. Every host and the /ship goldens are regenerated and all 89 pins go down versus 4814b3d8a (78 by 62, 11 by 38).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(unattended): drop the claim that skill prose cites the block id (GSTA-91)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: item 1 names destructive or irreversible options as pending in unattended runs (GSTA-91)

Dropping item 1's block pointer also dropped its only route to the printed block's destructive/irreversible carve-out. Item 1 now lists them in its unattended pending set, matching the failure-fallback branch, the hook and the printed block; the shared Exception is unchanged. CQ3 requires destructive and irreversible. Regenerated every host and the /ship goldens; every pin still goes down versus 4814b3d8a (78 by 32, 11 by 8). Includes the /ship Step 7 test that docs/unattended.md names the printed block header and END.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 13:36:43 +00:00

77 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The preamble's Claims Need Evidence kernel (docs/designs/HONEST_WORK_GATE_INTEGRITY.md W4).
*
* Pins where the kernel renders (tiers 2–4, default and terse; never tier 1),
* its byte budget, and the machine-read markers each bullet carries; never the
* sentences (docs/test-value-bar.md). The behavior claim is carried by the
* selected prompt evals, not here.
*/
import { describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { generatePreamble } from '../scripts/resolvers/preamble';
import {
EVIDENCE_KERNEL_BULLETS, EVIDENCE_KERNEL_HEADING, KERNEL_MAX_BYTES, generateEvidenceDirective,
} from '../scripts/resolvers/preamble/generate-evidence-directive';
import { generateCompletionStatus } from '../scripts/resolvers/preamble/generate-completion-status';
import { HOST_PATHS, type TemplateContext } from '../scripts/resolvers/types';
import { between, expectMentions, expectTokens } from './helpers/prompt-structure';
const ROOT = path.resolve(import.meta.dir, '..');
const ctx = (tier: number, explainLevel?: 'default' | 'terse'): TemplateContext => ({
skillName: 'ship', tmplPath: 'ship/SKILL.md.tmpl', host: 'claude', paths: HOST_PATHS.claude, preambleTier: tier, explainLevel,
} as TemplateContext);
describe('Claims Need Evidence kernel', () => {
test('renders five bullets within the byte budget, with the stderr rule and no cross-skill step pointer', () => {
const rendered = generateEvidenceDirective();
expect(rendered.startsWith(`${EVIDENCE_KERNEL_HEADING}\n`)).toBe(true);
const bullets = rendered.split('\n').filter(line => line.startsWith('- '));
expect(bullets).toHaveLength(5);
expect(EVIDENCE_KERNEL_BULLETS).toHaveLength(5);
expect(Buffer.byteLength(bullets.join('\n'), 'utf8')).toBeLessThanOrEqual(KERNEL_MAX_BYTES);
expectTokens(rendered, ['stderr', '"done, unverified"'], 'kernel');
expect(rendered).not.toMatch(/\bStep \d/);
expectMentions(rendered, [
['limitation', 'verbatim error'],
['execution', 'command', 'fingerprint'],
['mock', 'live check'],
['disclose', 'failure', 'conclusion'],
['null result', 'success'],
], 'kernel');
expect(rendered).not.toContain('Claimed Limitations Need Evidence');
});
test('composes for tiers 2–4 in default and terse builds, never for tier 1', () => {
for (const tier of [2, 3, 4]) {
for (const level of ['default', 'terse'] as const) {
const out = generatePreamble(ctx(tier, level));
expect(out, `tier ${tier} ${level} lacks the kernel`).toContain(generateEvidenceDirective());
expect(out.indexOf(EVIDENCE_KERNEL_HEADING)).toBeLessThan(out.indexOf('## Completion Status Protocol'));
}
}
for (const level of ['default', 'terse'] as const) {
expect(generatePreamble(ctx(1, level))).not.toContain(EVIDENCE_KERNEL_HEADING);
}
});
test('the DONE line binds evidence to the final consumed inputs and names reuse (every tier)', () => {
const status = between(generateCompletionStatus(ctx(1)), '## Completion Status Protocol', '## Operational Self-Improvement');
const done = status.split('\n').find(line => line.startsWith('- **DONE**'))!;
expectTokens(done, ['**DONE**', 'final consumed inputs', 'reuse'], 'DONE line');
expect(done).not.toBe('- **DONE** — completed with evidence.');
for (const tier of [1, 2, 3, 4]) expect(generatePreamble(ctx(tier))).toContain(done);
});
test('generated SKILL.md files carry the kernel at tier 2+ and the old heading nowhere', () => {
const skills = fs.readdirSync(ROOT).filter(dir => fs.existsSync(path.join(ROOT, dir, 'SKILL.md.tmpl')) && fs.existsSync(path.join(ROOT, dir, 'SKILL.md')));
expect(skills.length).toBeGreaterThan(20);
for (const skill of skills) {
const tier = Number(/preamble-tier:\s*(\d)/.exec(fs.readFileSync(path.join(ROOT, skill, 'SKILL.md.tmpl'), 'utf8'))?.[1]);
const rendered = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md'), 'utf8');
expect(rendered, `${skill} still carries the retired heading`).not.toContain('Claimed Limitations Need Evidence');
if (tier >= 2) expect(rendered, `${skill} (tier ${tier}) lacks the kernel; run bun run gen:skill-docs`).toContain(EVIDENCE_KERNEL_HEADING);
}
});
});