mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* feat(dx): agent-ready error contract for the DevEx review skills Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(dx): gstack-error-capture runs a CLI with no terminal and prints a redacted record Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(dx): CI-clean fences, runtime-root-safe contract location, path-agnostic launch-check test Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(dx): preregistered behavior case for the agent-ready error contract; README skill rows Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(dx): grader items come from the contract; paid-file count includes the new case Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(dx): outside DX voice uses the agent-ready contract; DX_CAPTURE_* codes join RESULT_CODES with troubleshooting anchors Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(dx): review fixes for gstack-error-capture and the paid grader - every helper diagnostic passes the record's redaction, fail-closed - forwarded values are masked before and after sanitizing, plus JSON-escaped forms - the final secret check reads the record's string fields with byte-based scan limits - mise/asdf config, trust and env-loading variables need --pass-env; only data locations pass by default - launch reports whether the target started; --launch-check's probe result is exit_code - Step 3 captures --help first; help states the containment lifetime limit - the paid grader validates the bound result with parseReviewResult Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(dx): mask forwarded values before argument validation; protocol strings never match literals; Step 3 captures stay read-only Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * v1.91.114.0 feat(dx): score every error for an AI agent reader (agent-ready error contract + gstack-error-capture) 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>
89 lines
4.8 KiB
TypeScript
89 lines
4.8 KiB
TypeScript
import { afterEach, describe, expect, test } from 'bun:test';
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import { createSnapshot, prepareMethodology } from '../bin/gstack-autoplan-snapshot';
|
|
import { writePrompts } from '../lib/autoplan-prompts';
|
|
import { DX_ERROR_COMPACT, DX_ERROR_CONTRACT, DX_ERROR_ITEMS, DX_ERROR_RUBRIC } from '../lib/dx-error-contract';
|
|
|
|
const ROOT = resolve(import.meta.dir, '..');
|
|
const read = (path: string) => readFileSync(join(ROOT, path), 'utf8');
|
|
const owned: string[] = [];
|
|
afterEach(() => { for (const dir of owned.splice(0)) rmSync(dir, { recursive: true, force: true }); });
|
|
|
|
describe('agent-ready error contract reaches every DX reviewer', () => {
|
|
test('the contract names all eight items, both caps and the failure-class rule', () => {
|
|
for (const item of DX_ERROR_ITEMS) {
|
|
expect(DX_ERROR_CONTRACT).toContain(`**${item}**`);
|
|
expect(DX_ERROR_COMPACT).toContain(`${item} (`);
|
|
}
|
|
for (const text of [DX_ERROR_CONTRACT, DX_ERROR_COMPACT]) {
|
|
expect(text).toContain('CAP4:<path>:<item>');
|
|
expect(text).toContain('CAP6:<path>:<item>');
|
|
expect(text).toContain('4/10');
|
|
expect(text).toContain('6/10');
|
|
expect(text).toMatch(/[Uu]sage (and|or) validation error/);
|
|
expect(text).toMatch(/unexpected failures only/i);
|
|
}
|
|
});
|
|
|
|
test('the snapshot literal equals the compact form, and the built native DX prompt carries it', () => {
|
|
const snapshotSource = read('bin/gstack-autoplan-snapshot.ts');
|
|
expect(snapshotSource).toContain(`3. Error handling: ${DX_ERROR_COMPACT}`);
|
|
const dir = mkdtempSync(join(tmpdir(), 'gstack-dx-contract-')); owned.push(dir);
|
|
const active = join(dir, 'active.md');
|
|
const restore = join(dir, 'restore.md');
|
|
writeFileSync(active, '# Plan\n\n## Implementation plan\nShip a CLI.\n## Review record\n');
|
|
writeFileSync(restore, 'Ship a CLI.\n');
|
|
const method = prepareMethodology('dx', join(ROOT, 'plan-devex-review/SKILL.md'), restore);
|
|
const methodology = readFileSync(method.methodologyPath, 'utf8');
|
|
expect(methodology).toContain(DX_ERROR_COMPACT);
|
|
const snapshot = createSnapshot('dx', active, restore, method.methodologyPath);
|
|
expect(snapshot.nativePrompt).toContain(DX_ERROR_COMPACT);
|
|
const prompts = writePrompts({ phase: 'dx', snapshot, outDir: dir, priorConsensus: [] });
|
|
expect(readFileSync(prompts.native, 'utf8')).toContain(DX_ERROR_COMPACT);
|
|
expect(readFileSync(prompts.outside, 'utf8')).toContain(DX_ERROR_COMPACT);
|
|
});
|
|
|
|
test('rendered skills carry the contract where each consumer reads it', () => {
|
|
expect(read('plan-devex-review/sections/review-sections.md').includes(DX_ERROR_COMPACT)).toBe(true);
|
|
expect(read('plan-devex-review/dx-hall-of-fame.md').includes(DX_ERROR_CONTRACT)).toBe(true);
|
|
expect(read('autoplan/sections/dx-phase.md')).toContain('2. Errors, agent-ready (what, fix, recovery, code, context, trace, redaction, channel)');
|
|
const devex = read('devex-review/SKILL.md');
|
|
expect(devex).toContain(DX_ERROR_ITEMS.join(', '));
|
|
expect(devex).toContain('plan-devex-review/dx-hall-of-fame.md');
|
|
expect(devex).toContain('gstack-error-capture');
|
|
expect(devex).toContain('secret_findings');
|
|
expect(read('plan-devex-review/sections/checklist.md')).toContain('CAP4');
|
|
});
|
|
|
|
test('the public doc is generated from the contract', () => {
|
|
const body = DX_ERROR_CONTRACT.slice(DX_ERROR_CONTRACT.indexOf('| Item |'));
|
|
expect(read('docs/agent-ready-errors.md')).toContain(body);
|
|
});
|
|
|
|
test('review-log rows record the rubric version', () => {
|
|
for (const path of ['plan-devex-review/sections/review-sections.md', 'devex-review/SKILL.md']) {
|
|
const text = read(path);
|
|
expect(text.includes(`"dx_rubric":"${DX_ERROR_RUBRIC}"`), path).toBe(true);
|
|
expect(text.includes('"dimension_scores":{"getting_started":N'), path).toBe(true);
|
|
expect(text.includes('rubric changed, not comparable'), path).toBe(true);
|
|
}
|
|
expect(read('autoplan/SKILL.md').includes(`"unresolved":N,"dx_rubric":"${DX_ERROR_RUBRIC}","via":"autoplan"`)).toBe(true);
|
|
});
|
|
|
|
test('no DX consumer keeps the old error formula', () => {
|
|
const consumers = [
|
|
'scripts/resolvers/dx.ts', 'bin/gstack-autoplan-snapshot.ts', 'lib/autoplan-prompts.ts', 'autoplan/sections/dx-phase.md',
|
|
'plan-devex-review/SKILL.md', 'plan-devex-review/sections/review-sections.md',
|
|
'plan-devex-review/sections/checklist.md', 'plan-devex-review/dx-hall-of-fame.md', 'devex-review/SKILL.md',
|
|
];
|
|
for (const path of consumers) {
|
|
const text = read(path);
|
|
for (const old of ['problem + cause + fix', 'What happened + Why + How to fix', 'three-tier model', 'explain the cause, show the fix']) {
|
|
expect(text.includes(old), `${path} still says "${old}"`).toBe(false);
|
|
}
|
|
}
|
|
});
|
|
});
|