fix(trekexecute): the checkpoint records the full commit id for the done line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-28 16:06:52 +01:00
commit f9cd08076e
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 94 additions and 3 deletions

View file

@ -1957,6 +1957,10 @@ or 5). Release the step's test lock before stopping
command exactly as written in the plan. (The test files are listed apart command exactly as written in the plan. (The test files are listed apart
from `Files:` in the plan; staging only `Files:` would leave the step's from `Files:` in the plan; staging only `Files:` would leave the step's
test uncommitted and charge it to the next step's diff.) test uncommitted and charge it to the next step's diff.)
`{hash}` is the full commit id: run `git rev-parse HEAD` after the step's
last commit, never the short id `git commit` prints. Read it again after
any later commit of the step (the second commit on the exit-1 path); when
the commit failed, it is the HEAD the done line names.
If the commit fails (nothing to commit, etc.): warn but do NOT fail the If the commit fails (nothing to commit, etc.): warn but do NOT fail the
step. The step's verification already passed — the commit is bookkeeping. step. The step's verification already passed — the commit is bookkeeping.
2. **The done line**, at the step's own commit. A step is done only when a 2. **The done line**, at the step's own commit. A step is done only when a
@ -1995,7 +1999,7 @@ already passed) but is recorded as `checkpoint_drift: {expected_pattern,
actual_message}` in the progress file. Phase 7.5 audit reports drift as actual_message}` in the progress file. Phase 7.5 audit reports drift as
an advisory. an advisory.
Update progress: `steps.{N}.status = "completed"`, `steps.{N}.commit = {hash}`, Update progress: `steps.{N}.status = "completed"`, `steps.{N}.commit = {hash}` (the full commit id),
`steps.{N}.completed_at = now`, `steps.{N}.manifest_audit = "pass"`. `steps.{N}.completed_at = now`, `steps.{N}.manifest_audit = "pass"`.
### Step mode exit ### Step mode exit

View file

@ -369,7 +369,7 @@ Rule B is stated by the reviewers and recorded by the gate from round 1 (`lowere
| `attempts` | number | 1..N | | `attempts` | number | 1..N |
| `error` | string \| null | | | `error` | string \| null | |
| `completed_at` | ISO string \| null | | | `completed_at` | ISO string \| null | |
| `commit` | string \| null | git sha after Checkpoint | | `commit` | string \| null | full commit id (`git rev-parse HEAD`) after Checkpoint |
| `manifest_audit` | string | `pass \| fail \| pass-with-note \| n/a` | | `manifest_audit` | string | `pass \| fail \| pass-with-note \| n/a` |
| `note` | string | optional human-readable annotation | | `note` | string | optional human-readable annotation |
| `start_sha` | string \| null | optional (wave 1) — git sha the step started from; every check of the step reads `git diff {start_sha}` | | `start_sha` | string \| null | optional (wave 1) — git sha the step started from; every check of the step reads `git diff {start_sha}` |

View file

@ -22,7 +22,7 @@
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1141-1165 | profile, phase_models, profile_source | none | | trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1141-1165 | profile, phase_models, profile_source | none |
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:829-862 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none | | trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:829-862 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1307-1335 | profile, phase_models, parallel_agents, profile_source | none | | trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1307-1335 | profile, phase_models, parallel_agents, profile_source | none |
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:2986-3008 | profile, phase_models, profile_source | none | | trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:2990-3012 | profile, phase_models, profile_source | none |
| trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none | | trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none |
| trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) | | trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) |
| trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:643-693 | profile, phase_models, profile_source | none | | trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:643-693 | profile, phase_models, profile_source | none |

View file

@ -0,0 +1,87 @@
// tests/lib/fix-round-4-5b-part-2-prose.test.mjs
// Prose pins for fix round 4-5b, part 2 (plan
// .claude/projects/2026-10-27-fix-round-4-5b-part-2, decision 88): the fixes
// that are text a session reads. The tags in brackets are the part-2 ids from
// the round's brief; each tagged test fails on an assertion at 52c382c, the
// round's start point. The keep-behaviour checks sit inside the tagged test as
// "Control:" assertions.
// DLC /trekexecute's Sub-step G says what `{hash}` is: the full commit id
// from `git rev-parse HEAD` after the step's last commit, never the short
// id `git commit` prints (done-line --check refuses a short id with
// exit 2). The progress line stores that full id, and Handover 5's
// `commit` row says so.
// No static import of a new export: the file must load at 52c382c.
// Fix the SOURCE a pin guards, not the pin.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const readCache = new Map();
/** A repo file's text; the file must exist. Read from disk once per run. */
function read(rel) {
if (!readCache.has(rel)) {
assert.ok(existsSync(join(ROOT, rel)), `missing file ${rel}`);
readCache.set(rel, readFileSync(join(ROOT, rel), 'utf8'));
}
return readCache.get(rel);
}
/** Whitespace (line breaks too) collapsed to one space. */
const flat = (s) => s.replace(/\s+/g, ' ').trim();
/** The text from `heading` up to the next heading of the same or a higher level; the heading must exist. */
function section(text, heading) {
const at = text.indexOf(heading);
assert.ok(at >= 0, `missing heading "${heading}"`);
const level = heading.match(/^#+/)[0].length;
const rest = text.slice(at + heading.length);
const next = rest.search(new RegExp(`\\n#{1,${level}} `));
return heading + (next >= 0 ? rest.slice(0, next) : rest);
}
const EXEC = 'commands/trekexecute.md';
const CONTRACTS = 'docs/HANDOVER-CONTRACTS.md';
// --- DLC ---------------------------------------------------------------------------
test('[DLC] Sub-step G records the full commit id from git rev-parse HEAD after the checkpoint', () => {
const g = section(read(EXEC), '#### Sub-step G — Checkpoint, done line, unlock');
const gFlat = flat(g);
// Red: the checkpoint says how {hash} is read, before the done line runs.
const checkpointAt = g.indexOf('**Checkpoint.**');
const doneLineAt = g.indexOf('**The done line**');
assert.ok(checkpointAt >= 0 && doneLineAt > checkpointAt, 'Sub-step G must hold **Checkpoint.** before **The done line**');
const revParseAt = g.indexOf('`git rev-parse HEAD`');
assert.ok(revParseAt > checkpointAt && revParseAt < doneLineAt,
'Sub-step G must name `git rev-parse HEAD` in the checkpoint item, after **Checkpoint.** and before the done-line check');
assert.ok(gFlat.includes('full commit id'), 'Sub-step G must say {hash} is the full commit id');
assert.ok(gFlat.includes('never the short id'), 'Sub-step G must say {hash} is never the short id `git commit` prints');
assert.ok(gFlat.includes('Read it again after any later commit of the step'),
'Sub-step G must say the id is read again after a later commit of the step');
const commitLines = g.split('\n').filter((l) => l.includes('`steps.{N}.commit = {hash}`'));
assert.ok(commitLines.length > 0, 'Sub-step G must hold the `steps.{N}.commit = {hash}` progress line');
for (const l of commitLines) {
assert.ok(l.includes('(the full commit id)'), `the progress line must say it stores the full commit id: ${l}`);
}
const handover5 = section(read(CONTRACTS), '## Handover 5 ');
const rows = handover5.split('\n').filter((l) => l.startsWith('| `commit` |'));
assert.equal(rows.length, 1, `Handover 5 must hold exactly one \`commit\` row; found ${rows.length}`);
assert.ok(rows[0].includes('full commit id'), `the Handover 5 \`commit\` row must say "full commit id": ${rows[0]}`);
// Control: the done-line check keeps its literal, Phase 7.5 keeps reading the
// stored id, and Sub-step G asks for no abbreviated id anywhere.
assert.ok(g.includes('done-line.mjs --check --step {N} --report {report_path} --commit {hash}'),
'Control: Sub-step G keeps the done-line --check literal with --commit {hash}');
const p75 = section(read(EXEC), '## Phase 7.5 — Manifest audit (independent)');
assert.ok(p75.includes('--commit {steps.N.commit}'), 'Control: Phase 7.5 keeps --commit {steps.N.commit}');
assert.doesNotMatch(g, /--short|%h|--abbrev/, 'Control: Sub-step G must not ask for an abbreviated commit id');
});