feat(brief): ceremony by size, one question round, logged and committed approvals
P1 (order 2, wave 1). /trekbrief puts the task in a size class (ceremony: small|bounded|architectural, K1; heaviest in doubt, said aloud, overridable, only moved up) that sets the question budget and the review cap, and asks one round of questions: facts looked up, never asked; recommendation first; ranked by impact x uncertainty with a visible Deferred list; stop when the next three answers can be predicted. "høres bra ut", "gjør som du vil" and an empty answer are not approval. K2: lib/util/approval-log.mjs, an append-only hash-chained log in the target repo (.claude/voyage/approvals.jsonl) bound to the approved content's sha256; append/verify (0/1/2), answer, digest. Committed by lib/util/artifact-commit.mjs: the log always, the brief and plan only with artifact_store: git (operator decision 2026-10-06). /trekplan Phase 1 halts on a broken, diverged, missing or mismatched log entry; Phase 11 logs and commits an explicit plan approval before any handoff. Both new brief fields are optional: no brief_version bump. The main-module guard in intent-approval.mjs and brief-validator.mjs now compares real paths. Borrowings credited in the prose and module headers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c89a6b580
commit
5244d2e8e8
16 changed files with 1527 additions and 85 deletions
|
|
@ -39,8 +39,9 @@
|
|||
// what `/trekbrief --approve` approves: {brief_path, project_dir, plan_command}; exit 1 = no brief
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import { existsSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { existsSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { basename, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { parseDocument } from '../util/frontmatter.mjs';
|
||||
import { issue } from '../util/result.mjs';
|
||||
import { emit } from '../stats/event-emit.mjs';
|
||||
|
|
@ -233,7 +234,18 @@ export function resolveApproveTarget(arg) {
|
|||
return { brief_path: briefPath, project_dir: projectDir, plan_command: planCommand };
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
// Main-module guard on real paths: comparing against `file://` + argv[1] fails
|
||||
// when the plugin sits behind a symlink or in a path with a space; the CLI then
|
||||
// exits 0 having checked nothing.
|
||||
function isMain() {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMain()) {
|
||||
const args = process.argv.slice(2);
|
||||
const file = args.find((a) => !a.startsWith('--'));
|
||||
if (args.includes('--resolve')) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue