Files
gstack/bin/gstack-owner-brief
T
Garry Tanandcapy-ai[bot] 5d647455f2 v1.91.79.0 feat(eval-plan): /eval-plan tier 1, lane-check, evidence verify, coordinator contract, owner brief, contributor-PR mode, banned terms (multi-agent wave PR E) (#3123)
* v1.91.79.0 feat(eval-plan): /eval-plan tier 1, lane-check, evidence verify, coordinator contract, owner brief, contributor-PR mode, banned terms (multi-agent wave PR E)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(spec,review): fail closed when the coordinator contract cannot be read; restore the omitted-Army sentence

The spec --execute spawn captures `gstack-autoplan contract` before piping
the archive, so a lane never starts without the contract; the C9 fixture
installs its gstack-like checkout (bin, lib, a contract stub) at every
host's global root. Review Step 5.8 keeps the pinned
'without claiming specialist coverage' wording.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-11 03:10:06 +00:00

74 lines
4.6 KiB
TypeScript
Executable File

#!/usr/bin/env bun
/**
* gstack-owner-brief — the one-page owner brief at the autoplan gate (plan E3).
*
* gstack-owner-brief render --out <dir> [--page N] [--summary <file>] [--write] [--json]
* gstack-owner-brief close --out <dir> --plan <file> [--date YYYY-MM-DD] [--json]
*
* `render` prints the plain-language summary (--summary <file>, else <dir>/summary.md
* when present, else counted from the artifacts), the URGENT block and the numbered
* decisions (gate list, seven per page, stable ids; auto items show the default
* taken if unanswered, approval items stay pending) and points at review-record.md
* for the full review; --write also saves brief.md in the run directory.
* `close` rewrites every `draft direction stands until the owner decides` line in
* the plan to the decided option and date; pending lines stay and are reported
* (DRAFT_DIRECTION_UNRESOLVED, exit 1). Logic: lib/owner-brief.ts.
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { EXIT, exitTableHelp } from '../lib/headless-artifacts';
import { BRIEF_FILE, closeDraftDirectionsFile, loadBriefInputs, renderBrief } from '../lib/owner-brief';
import { resultLine } from '../lib/result-codes';
const HELP = `gstack-owner-brief — one page for the owner: summary, URGENT block, numbered decisions (docs/unattended.md#owner-brief)
render --out <dir> [--page N] [--summary <file>] [--write] [--json]
close --out <dir> --plan <file> [--date YYYY-MM-DD] [--json]
${exitTableHelp()}`;
function flag(args: string[], name: string): string | undefined {
const i = args.indexOf(name);
return i >= 0 ? args[i + 1] : undefined;
}
function main(argv: string[]): number {
const [cmd, ...rest] = argv;
if (!cmd || cmd === '--help' || cmd === '-h') { process.stdout.write(HELP + '\n'); return cmd ? EXIT.ok : EXIT.usage; }
const out = flag(rest, '--out');
if (!out) { process.stderr.write(`gstack-owner-brief: ${cmd} needs --out <dir>\n${HELP}\n`); return EXIT.usage; }
const loaded = loadBriefInputs(path.resolve(out));
if ('error' in loaded) { process.stderr.write(resultLine(`gstack-owner-brief ${cmd}`, loaded.error.code, loaded.error.message) + '\n'); return loaded.error.code === 'BRIEF_INPUT_MISSING' ? EXIT.refused : EXIT.fail; }
const json = rest.includes('--json');
if (cmd === 'render') {
const pageText = flag(rest, '--page');
if (pageText !== undefined && !/^[0-9]+$/.test(pageText)) { process.stderr.write('gstack-owner-brief: --page needs a number\n'); return EXIT.usage; }
const summaryFile = flag(rest, '--summary') ?? (fs.existsSync(path.join(path.resolve(out), 'summary.md')) ? path.join(path.resolve(out), 'summary.md') : undefined);
const summary = summaryFile ? fs.readFileSync(summaryFile, 'utf8') : undefined;
const brief = renderBrief(loaded.inputs, { page: pageText ? Number(pageText) : 1, summary, outDir: path.resolve(out) });
if (rest.includes('--write')) fs.writeFileSync(path.join(path.resolve(out), BRIEF_FILE), brief);
if (json) process.stdout.write(JSON.stringify({ run: loaded.inputs.gate.run, gate_rev: loaded.inputs.gate.gate_rev, items: loaded.inputs.gate.items.length, brief }) + '\n');
else process.stdout.write(brief);
return EXIT.ok;
}
if (cmd === 'close') {
const plan = flag(rest, '--plan');
if (!plan || !fs.existsSync(plan)) { process.stderr.write(`gstack-owner-brief: close needs --plan <existing file>\n`); return EXIT.usage; }
const date = flag(rest, '--date');
if (date !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(date)) { process.stderr.write('gstack-owner-brief: --date needs YYYY-MM-DD\n'); return EXIT.usage; }
const result = closeDraftDirectionsFile(plan, loaded.inputs, date);
if (json) process.stdout.write(JSON.stringify({ plan, rewrites: result.rewrites, unresolved: result.unresolved.length }) + '\n');
else {
for (const r of result.rewrites) process.stdout.write(`DRAFT_DIRECTION: line ${r.line} ${r.id ?? '(no id)'} ${r.status}\n`);
process.stdout.write(`DRAFT_DIRECTIONS: rewritten=${result.rewrites.length - result.unresolved.length} unresolved=${result.unresolved.length} plan=${plan}\n`);
}
for (const r of result.unresolved) process.stderr.write(resultLine('gstack-owner-brief close', 'DRAFT_DIRECTION_UNRESOLVED', `line ${r.line}: ${r.status === 'unknown_id' ? 'no decision id on the line and no plan approval item' : `${r.id} pending`}`) + '\n');
return result.unresolved.length ? EXIT.fail : EXIT.ok;
}
process.stderr.write(`gstack-owner-brief: unknown command ${cmd}\n${HELP}\n`);
return EXIT.usage;
}
process.exitCode = main(process.argv.slice(2));