feat(retro): absorb inline git/awk metrics into bin/gstack-retro-metrics + carve report format

RETRO_METRICS_PROTO: 1 contract, local git reads only (fetch stays in the
skill prose), degraded path documented in the skeleton.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-25 17:47:32 +00:00
co-authored by Claude Fable 5
parent b007814be0
commit e0250aa128
10 changed files with 1245 additions and 705 deletions
+51 -12
View File
@@ -6,7 +6,7 @@ import {
logCost, recordE2E,
createEvalCollector, finalizeEvalCollector,
} from './helpers/e2e-helpers';
import { extractSkillSections, RETRO_E2E_SECTIONS } from './helpers/skill-fixture';
import { extractSkillSections } from './helpers/skill-fixture';
import { spawnSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
@@ -14,6 +14,53 @@ import * as os from 'os';
const evalCollector = createEvalCollector('e2e-retro');
// Carved-skill fixture (retro wave): the repo-scoped retro flow lives in the
// skeleton's H2 sections below, and the narrative report format lives in
// retro/sections/report-format.md (the skeleton Step 14 is a STOP-Read
// pointer). The fixture ships skeleton + section + bin/gstack-retro-metrics —
// still an extraction, not a full-file copy (sections ARE the minimal
// on-demand units; same pattern as skill-e2e-review-army.test.ts).
const RETRO_SKELETON_SECTIONS = [
'When to invoke this skill',
'Step 0: Detect platform and base branch',
'User-invocable',
'Arguments',
'Instructions',
'Prior Learnings',
'Capture Learnings',
'Tone',
'Important Rules',
];
/** Write retro/SKILL.md + sections + the metrics script into a fixture dir. */
function buildRetroFixture(dir: string): void {
let skillMd = extractSkillSections(path.join(ROOT, 'retro'), RETRO_SKELETON_SECTIONS);
// The skeleton's STOP-Read points at the installed absolute section path
// (~/.claude/skills/gstack/retro/sections/...), which doesn't exist under
// the hermetic temp HOME — repoint it at the fixture copy.
skillMd = skillMd.replace(
/[^\s`]*\/retro\/sections\/report-format\.md/g,
path.join(dir, 'retro', 'sections', 'report-format.md'),
);
fs.mkdirSync(path.join(dir, 'retro', 'sections'), { recursive: true });
fs.writeFileSync(path.join(dir, 'retro', 'SKILL.md'), skillMd);
fs.copyFileSync(
path.join(ROOT, 'retro', 'sections', 'report-format.md'),
path.join(dir, 'retro', 'sections', 'report-format.md'),
);
// The Step 1 fence resolves bin/gstack-retro-metrics via
// $HOME/.claude/skills/gstack/bin first (absent in the hermetic HOME), then
// the cwd-relative .claude/skills/gstack/bin fallback — satisfy the fallback
// so the run exercises the real script instead of the degraded path.
const binDir = path.join(dir, '.claude', 'skills', 'gstack', 'bin');
fs.mkdirSync(binDir, { recursive: true });
fs.copyFileSync(
path.join(ROOT, 'bin', 'gstack-retro-metrics'),
path.join(binDir, 'gstack-retro-metrics'),
);
fs.chmodSync(path.join(binDir, 'gstack-retro-metrics'), 0o755);
}
// --- Retro base branch detection smoke test ---
describeIfSelected('Base branch detection', ['retro-base-branch'], () => {
@@ -52,11 +99,7 @@ describeIfSelected('Base branch detection', ['retro-base-branch'], () => {
// Retro skill — extract the repo-scoped retro flow only (drops the shared
// preamble + global/compare modes; CLAUDE.md: "extract, don't copy").
fs.mkdirSync(path.join(dir, 'retro'), { recursive: true });
fs.writeFileSync(
path.join(dir, 'retro', 'SKILL.md'),
extractSkillSections(path.join(ROOT, 'retro'), RETRO_E2E_SECTIONS),
);
buildRetroFixture(dir);
const result = await runSkillTest({
prompt: `Read retro/SKILL.md for instructions on how to run a retrospective.
@@ -137,12 +180,8 @@ describeIfSelected('Retro E2E', ['retro'], () => {
run('git', ['add', 'README.md']);
run('git', ['commit', '-m', 'docs: add README', '--date', '2026-03-12T16:00:00']);
// Retro skill — extracted repo-scoped flow, not the full 1820-line file.
fs.mkdirSync(path.join(retroDir, 'retro'), { recursive: true });
fs.writeFileSync(
path.join(retroDir, 'retro', 'SKILL.md'),
extractSkillSections(path.join(ROOT, 'retro'), RETRO_E2E_SECTIONS),
);
// Retro skill — extracted repo-scoped flow, not the full file.
buildRetroFixture(retroDir);
});
afterAll(() => {