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:
parent
1b9d5fd459
commit
f9cd08076e
4 changed files with 94 additions and 3 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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}` |
|
||||||
|
|
|
||||||
2
tests/fixtures/jsonl-schemas.md
vendored
2
tests/fixtures/jsonl-schemas.md
vendored
|
|
@ -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 |
|
||||||
|
|
|
||||||
87
tests/lib/fix-round-4-5b-part-2-prose.test.mjs
Normal file
87
tests/lib/fix-round-4-5b-part-2-prose.test.mjs
Normal 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');
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue