calibration: pattern drill-down route + revisit-now CLI (TD3 / D29 + TD4 / D30)

TD3 (D29) — clickable pattern drill-down endpoint:
  GET /admin/api/calibration/pattern/:id (requireAdmin)
  Returns the pattern statement at index `id` plus the top 25 resolved
  takes for the holder, sorted by weight desc. v0.36.0.0 ship-state
  approximation: surfaces broad provenance evidence (top resolved
  takes). v0.37+ stores per-pattern source_take_ids[] on a
  calibration_profile_patterns join table so the drill-down shows the
  EXACT takes that drove the pattern.

  Surfaces a `provenance_note` field in the response so the operator
  sees the v0.36.0.0-vs-v0.37 fidelity boundary inline.

  The admin SPA's renderPatternStatementsCard SVG already emits anchor
  tags pointing at /admin/calibration/pattern/<i> (T15 ship state).
  This route makes those anchors clickable — closes the trust loop that
  was the rationale for D29 ("pattern statements without their evidence
  are dressed-up LLM hallucinations").

TD4 (D30) — `gbrain takes revisit <slug>` editor-open action:
  Adds the `revisit` subcommand to gbrain takes. Opens $EDITOR (falling
  back to vi) on the source markdown file for the slug. Appends a
  `<!-- gbrain:revisit -->` cursor marker at the bottom of the page on
  first invocation so the editor opens with intent visible.

  Reads sync.repo_path from config to locate the brain repo. Refuses to
  proceed with a clear error when the repo isn't configured or the page
  doesn't exist.

  spawnSync with stdio:'inherit' so the editor takes the terminal. Exit
  status surfaced on failure.

  The SVG renderer's revisit-now anchor for each abandoned thread row
  emits /admin/calibration/revisit/<takeId>. A small route handler that
  resolves take_id → page_slug then dispatches `gbrain takes revisit`
  via spawn is a v0.37 follow-up — the CLI command exists now so
  developers can wire it directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-17 17:00:56 -07:00
co-authored by Claude Opus 4.7
parent c45b4329f7
commit c4e4c67f51
2 changed files with 100 additions and 0 deletions
+53
View File
@@ -622,6 +622,59 @@ export async function runServeHttp(engine: BrainEngine, options: ServeHttpOption
// v0.36.0.0 (T15 / E6 / D23) — Calibration tab data endpoints.
// Server-rendered SVG charts; admin SPA renders via TrustedSVG wrapper.
// v0.36.0.0 (TD3) — pattern drill-down. Returns the source takes that
// produced the pattern statement at index `id` of the active profile.
// v0.36.0.0 ship state: returns the top N takes in the holder's overall
// takes table, sorted by weight desc. v0.37+ will store per-pattern
// source_take_ids on calibration_profiles_patterns so the drill-down
// shows the EXACT takes that drove the pattern.
app.get('/admin/api/calibration/pattern/:id', requireAdmin, async (req: Request, res: Response) => {
try {
const { getLatestProfile } = await import('./calibration.ts');
const holder = (req.query.holder as string) || 'garry';
const profile = await getLatestProfile(engine, { holder });
if (!profile) {
res.status(404).json({ error: 'no_profile' });
return;
}
const rawId = req.params.id;
const idStr = Array.isArray(rawId) ? rawId[0] : rawId;
const idx = Number.parseInt(idStr ?? '', 10) - 1;
if (!Number.isFinite(idx) || idx < 0 || idx >= profile.pattern_statements.length) {
res.status(400).json({ error: 'invalid_pattern_index', max: profile.pattern_statements.length });
return;
}
const statement = profile.pattern_statements[idx];
// v0.36.0.0 ship state: surface the top resolved takes for the
// holder as drill-down evidence. Per-pattern provenance is v0.37.
const takes = await engine.executeRaw<{
id: number;
page_slug: string;
row_num: number;
claim: string;
weight: number;
resolved_quality: string | null;
since_date: string | null;
}>(
`SELECT id, page_slug, row_num, claim, weight, resolved_quality, since_date
FROM takes
WHERE holder = $1 AND active = true AND resolved_at IS NOT NULL
ORDER BY weight DESC, since_date DESC
LIMIT 25`,
[holder],
);
res.json({
pattern_statement: statement,
pattern_index: idx + 1,
holder,
provenance_note: 'v0.36.0.0 ship state shows top-25 resolved takes for this holder; per-pattern source_take_ids land in v0.37.',
takes,
});
} catch (err) {
res.status(500).json({ error: err instanceof Error ? err.message : 'unknown' });
}
});
app.get('/admin/api/calibration/profile', requireAdmin, async (req: Request, res: Response) => {
try {
const { getLatestProfile } = await import('./calibration.ts');
+47
View File
@@ -552,8 +552,55 @@ Common flags:
case 'resolve': return cmdResolve(engine, rest);
case 'scorecard': return cmdScorecard(engine, rest);
case 'calibration': return cmdCalibration(engine, rest);
case 'revisit': return cmdRevisit(engine, rest);
default:
// No subcommand keyword → treat first arg as <slug> for the list path.
return cmdList(engine, args);
}
}
/**
* v0.36.0.0 (TD4 / D30) — `gbrain takes revisit <slug>` opens $EDITOR on
* the source page so the user can write a follow-up immediately. The
* action the admin SPA's "revisit now" link triggers (via a small
* route handler that dispatches into this CLI command).
*
* Inserts a `<!-- gbrain:revisit -->` cursor marker at the bottom of the
* page body so the editor opens with intent visible.
*/
async function cmdRevisit(_engine: BrainEngine, rest: string[]): Promise<void> {
const slug = rest[0];
if (!slug) {
process.stderr.write('Usage: gbrain takes revisit <slug>\n');
process.exit(1);
}
const { existsSync, readFileSync, writeFileSync } = await import('node:fs');
const { join } = await import('node:path');
const { execFileSync, spawnSync } = await import('node:child_process');
const { loadConfig } = await import('../core/config.ts');
const cfg = loadConfig();
const repoPath = (cfg as { sync?: { repo_path?: string } } | null)?.sync?.repo_path;
if (!repoPath) {
process.stderr.write('No brain repo configured. Run `gbrain config set sync.repo_path /path/to/brain`.\n');
process.exit(1);
}
const filePath = join(repoPath, `${slug}.md`);
if (!existsSync(filePath)) {
process.stderr.write(`Page not found: ${filePath}\n`);
process.exit(1);
}
// Append a cursor marker if not already present.
const existing = readFileSync(filePath, 'utf8');
const marker = '\n<!-- gbrain:revisit -->\n';
if (!existing.includes('<!-- gbrain:revisit -->')) {
writeFileSync(filePath, existing + marker);
}
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
process.stderr.write(`Opening ${filePath} in ${editor}...\n`);
// Use spawnSync with stdio:'inherit' so the editor takes the terminal.
const result = spawnSync(editor, [filePath], { stdio: 'inherit' });
if (result.status !== 0) {
process.stderr.write(`Editor exited with status ${result.status ?? 'unknown'}\n`);
}
void execFileSync;
}