mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* docs(designs): honest work and gate integrity plan (autoplan-reviewed) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(e2e): review-gate-integrity rule case registered in touchfiles and the PR profile Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(honesty): claims-need-evidence kernel, no_claim card field, CEO value test, implementer dispatch rules, ACCEPTANCE_EDITED, Honest Work ethos Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(gate-diff): gate-edit detector lib + CLI, patch fixtures, calibration script, tests_ran in gstack-evidence Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(gate-diff): RH-15? only on CI/config floors after calibration Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(review,ship): Gate Integrity category, GATE_SCAN_BLOCK, one-way gate disposition, actor stamping, ZERO-RUN lanes, draft-while-open Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: re-measure parity and context budgets on the integrated tree Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(touchfiles): gate-diff fixtures and helpers select review-gate-integrity Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: archaeology lint wording, paid-file census, harness-version non-behavioral record Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pin review Step 3.5 rename, plan-path placeholder, mktemp template, per-stream evidence tail Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: ACCEPTANCE_EDITED fence substitutes the <plan-path> placeholder Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(gate-diff): shallow boundary commits report no_base instead of a whole-tree inventory; recalibrate 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>
84 lines
4.8 KiB
TypeScript
84 lines
4.8 KiB
TypeScript
/**
|
|
* Implementer dispatch rules (docs/designs/HONEST_WORK_GATE_INTEGRITY.md W7).
|
|
*
|
|
* One constant renders into /spec's issue body (the archived spec that
|
|
* `/spec --execute` pipes to `claude -p`) and into /ship Step 7's child prompt,
|
|
* before the LAST-line JSON protocol. These checks pin the rendering sites, the
|
|
* machine-read tokens, the untouched JSON field list and the spawn env var
|
|
* (docs/test-value-bar.md: structure and markers, never sentences).
|
|
*/
|
|
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import {
|
|
IMPLEMENTER_DISPATCH_RULES, IMPLEMENTER_DISPATCH_RULES_HEADING, IMPLEMENTER_DISPATCH_RULES_ITEMS, generateImplementerDispatchRules,
|
|
} from '../scripts/resolvers/dispatch-rules';
|
|
import { RESOLVERS } from '../scripts/resolvers/index';
|
|
import { between, expectAbsent, expectMentions, expectOrdered, expectTokens } from './helpers/prompt-structure';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const read = (relative: string) => fs.readFileSync(path.join(ROOT, relative), 'utf8');
|
|
|
|
describe('the rules constant', () => {
|
|
test('is a numbered list in the card vocabulary that forbids the forbidden dispatch', () => {
|
|
expect(IMPLEMENTER_DISPATCH_RULES_ITEMS).toHaveLength(5);
|
|
expect(IMPLEMENTER_DISPATCH_RULES.split('\n').map(line => line.replace(/\..*$/, ''))).toEqual(['1', '2', '3', '4', '5']);
|
|
expectTokens(IMPLEMENTER_DISPATCH_RULES, ['`protects / fails_when / no_claim`', '"make the tests pass"'], 'rules');
|
|
expectMentions(IMPLEMENTER_DISPATCH_RULES, [
|
|
['never', 'make the tests pass'],
|
|
['real code', 'real tests', 'same change'],
|
|
['test', 'timeout', 'suppression', 'own line', 'justification'],
|
|
['command', 'observed result'],
|
|
['claim', 'parent'],
|
|
], 'rules');
|
|
expect(generateImplementerDispatchRules()).toBe(IMPLEMENTER_DISPATCH_RULES);
|
|
expect(RESOLVERS.IMPLEMENTER_DISPATCH_RULES).toBe(generateImplementerDispatchRules);
|
|
expect(IMPLEMENTER_DISPATCH_RULES_HEADING).toBe('Rules for the implementing agent');
|
|
});
|
|
});
|
|
|
|
describe('/spec carries the rules inside the archived spec body', () => {
|
|
const tmpl = read('spec/SKILL.md.tmpl');
|
|
const rendered = read('spec/SKILL.md');
|
|
|
|
test('the Standard Issues template renders the rules under its own heading and drops "Tests written and passing"', () => {
|
|
const template = between(tmpl, '### Standard Issues', '### Epics');
|
|
expectOrdered(template, ['## Acceptance Criteria', '## Testing Plan', `## ${IMPLEMENTER_DISPATCH_RULES_HEADING}`, '{{IMPLEMENTER_DISPATCH_RULES}}', '## Rollback Plan'], 'spec issue template');
|
|
expectAbsent(tmpl, ['Tests written and passing'], 'spec template');
|
|
expectAbsent(rendered, ['Tests written and passing', '{{IMPLEMENTER_DISPATCH_RULES}}'], 'spec SKILL.md');
|
|
expect(between(rendered, '### Standard Issues', '### Epics')).toContain(IMPLEMENTER_DISPATCH_RULES);
|
|
});
|
|
|
|
test('acceptance criteria are written in the card vocabulary', () => {
|
|
const criteria = between(tmpl, '## Acceptance Criteria', '## Testing Plan');
|
|
expectTokens(criteria, ['protects=', 'fails_when=', 'no_claim=', 'none beyond protects'], 'spec acceptance criteria');
|
|
});
|
|
|
|
test('the --execute spawn sets GSTACK_SESSION_KIND=spawned on the claude -p line', () => {
|
|
for (const file of ['spec/sections/gate-and-file.md.tmpl', 'spec/sections/gate-and-file.md']) {
|
|
const spawn = read(file).split('\n').filter(line => line.includes('claude -p 2>&1'));
|
|
expect(spawn, `${file} spawn line`).toHaveLength(1);
|
|
expect(spawn[0]).toMatch(/GSTACK_SESSION_KIND=spawned claude -p/);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('/ship Step 7 child prompt carries the rules before an unchanged JSON protocol', () => {
|
|
const JSON_LINE_PREFIX = '{"coverage_pct":N,';
|
|
const FIELDS = ['coverage_pct', 'gaps', 'diagram', 'tests_added', 'coverage_pct_value', 'weak_gaps', 'tests_extended', 'tests_rejected', 'regression_proof'];
|
|
|
|
test('template order: audit, rules, JSON protocol', () => {
|
|
const child = between(read('ship/sections/test-coverage.md.tmpl'), '````text', '````\n\n**Parent processing:**');
|
|
expectOrdered(child, ['{{TEST_COVERAGE_AUDIT_SHIP}}', `${IMPLEMENTER_DISPATCH_RULES_HEADING}:`, '{{IMPLEMENTER_DISPATCH_RULES}}', 'output a single JSON object on the LAST LINE', JSON_LINE_PREFIX], 'ship child prompt');
|
|
});
|
|
|
|
test('rendered prompt carries the list and the LAST-line field set is unchanged', () => {
|
|
const child = between(read('ship/sections/test-coverage.md'), '````text', '````\n\n**Parent processing:**');
|
|
expect(child).toContain(IMPLEMENTER_DISPATCH_RULES);
|
|
expect(child.indexOf(IMPLEMENTER_DISPATCH_RULES)).toBeLessThan(child.indexOf(JSON_LINE_PREFIX));
|
|
const line = child.split('\n').find(l => l.startsWith(JSON_LINE_PREFIX))!;
|
|
const keys = Object.keys(JSON.parse(line.replace(/:N([,}])/g, ':0$1').replaceAll(',...', '')));
|
|
expect(keys).toEqual(FIELDS);
|
|
});
|
|
});
|