@juzser/blacksmith 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/{auditor.md → bs-auditor.md} +8 -5
- package/.claude/agents/{coder.md → bs-coder.md} +8 -7
- package/.claude/agents/{grader.md → bs-grader.md} +16 -9
- package/.claude/agents/{merger.md → bs-merger.md} +4 -4
- package/.claude/agents/{planner.md → bs-planner.md} +20 -6
- package/.claude/agents/{researcher.md → bs-researcher.md} +6 -5
- package/.claude/agents/{reviewer.md → bs-reviewer.md} +16 -5
- package/.claude/agents/{scribe.md → bs-scribe.md} +11 -2
- package/.claude/agents/{security-reviewer.md → bs-security-reviewer.md} +9 -6
- package/.claude/agents/{spec-reviewer.md → bs-spec-reviewer.md} +18 -10
- package/.claude/agents/{tester.md → bs-tester.md} +40 -12
- package/.claude/agents/bs-uiux.md +257 -0
- package/.claude/agents/{verifier.md → bs-verifier.md} +7 -4
- package/.claude/agents/{wave-runner.md → bs-wave-runner.md} +14 -5
- package/.claude/skills/bs/SKILL.md +99 -21
- package/.claude/skills/bs/audit.md +45 -31
- package/.claude/skills/bs/dispatch.md +133 -47
- package/.claude/skills/bs/lessons.md +28 -14
- package/.claude/skills/bs/mcp.md +4 -4
- package/.claude/skills/bs/new.md +3 -3
- package/.claude/skills/bs/plan.md +57 -16
- package/.claude/skills/bs/report.md +11 -11
- package/.claude/skills/bs/run.md +190 -52
- package/.claude/skills/bs/status.md +14 -7
- package/.claude/skills/bs/ui.md +21 -5
- package/.claude/skills/bs/waivers.md +78 -2
- package/.claude/skills/bs/wave.md +143 -50
- package/README.md +53 -32
- package/factory/orchestrator/dist/agentNames.js +43 -0
- package/factory/orchestrator/dist/agents-registry.js +11 -4
- package/factory/orchestrator/dist/agentsSync.js +70 -13
- package/factory/orchestrator/dist/artifacts.js +31 -0
- package/factory/orchestrator/dist/attribution.js +4 -0
- package/factory/orchestrator/dist/audit.js +245 -20
- package/factory/orchestrator/dist/autonomy.js +6 -2
- package/factory/orchestrator/dist/budgetAlarm.js +132 -37
- package/factory/orchestrator/dist/budgets.js +302 -84
- package/factory/orchestrator/dist/claims.js +9 -19
- package/factory/orchestrator/dist/cli.js +1087 -116
- package/factory/orchestrator/dist/cliName.js +27 -0
- package/factory/orchestrator/dist/crossFinding.js +20 -0
- package/factory/orchestrator/dist/crosscheck.js +136 -8
- package/factory/orchestrator/dist/daemon.js +352 -67
- package/factory/orchestrator/dist/db/projector.js +327 -54
- package/factory/orchestrator/dist/db/queries.js +1831 -94
- package/factory/orchestrator/dist/db/schema.js +57 -4
- package/factory/orchestrator/dist/declaredArtifactLine.js +18 -0
- package/factory/orchestrator/dist/delegation.js +86 -19
- package/factory/orchestrator/dist/diffstat.js +15 -8
- package/factory/orchestrator/dist/dispatchLint.js +108 -0
- package/factory/orchestrator/dist/effort.js +14 -2
- package/factory/orchestrator/dist/effortTiers.js +13 -0
- package/factory/orchestrator/dist/env.js +26 -0
- package/factory/orchestrator/dist/epic.js +494 -42
- package/factory/orchestrator/dist/epicBudget.js +93 -0
- package/factory/orchestrator/dist/epicWidth.js +22 -5
- package/factory/orchestrator/dist/errorIssues.js +182 -11
- package/factory/orchestrator/dist/escalation.js +2 -2
- package/factory/orchestrator/dist/events.js +164 -14
- package/factory/orchestrator/dist/feedback.js +169 -0
- package/factory/orchestrator/dist/findingContext.js +26 -0
- package/factory/orchestrator/dist/findings.js +108 -14
- package/factory/orchestrator/dist/gate.js +120 -10
- package/factory/orchestrator/dist/gh.js +19 -2
- package/factory/orchestrator/dist/git.js +20 -2
- package/factory/orchestrator/dist/goalCheck.js +2 -2
- package/factory/orchestrator/dist/harness.js +25 -17
- package/factory/orchestrator/dist/hookDecision.js +411 -39
- package/factory/orchestrator/dist/integration.js +20 -8
- package/factory/orchestrator/dist/issueReporter.js +81 -16
- package/factory/orchestrator/dist/judgePreflight.js +25 -4
- package/factory/orchestrator/dist/judgeRoles.js +47 -0
- package/factory/orchestrator/dist/judgeStopHook.js +239 -0
- package/factory/orchestrator/dist/judges.js +419 -21
- package/factory/orchestrator/dist/lessons.js +190 -11
- package/factory/orchestrator/dist/logCache.js +291 -0
- package/factory/orchestrator/dist/mcp.js +6 -6
- package/factory/orchestrator/dist/paths.js +20 -7
- package/factory/orchestrator/dist/plan.js +399 -33
- package/factory/orchestrator/dist/planQuorum.js +5 -2
- package/factory/orchestrator/dist/policy.js +1275 -30
- package/factory/orchestrator/dist/projectName.js +25 -0
- package/factory/orchestrator/dist/projects.js +12 -1
- package/factory/orchestrator/dist/promptCapture.js +306 -0
- package/factory/orchestrator/dist/promptHook.js +33 -0
- package/factory/orchestrator/dist/promptImport.js +281 -0
- package/factory/orchestrator/dist/prompts.js +13 -0
- package/factory/orchestrator/dist/providers/schema-validate.js +2 -2
- package/factory/orchestrator/dist/providers/types.js +1 -1
- package/factory/orchestrator/dist/queue.js +471 -30
- package/factory/orchestrator/dist/quorum.js +18 -5
- package/factory/orchestrator/dist/results.js +25 -7
- package/factory/orchestrator/dist/roadmap.js +10 -5
- package/factory/orchestrator/dist/run-cli.js +15 -10
- package/factory/orchestrator/dist/runner.js +2 -2
- package/factory/orchestrator/dist/scheduler.js +3 -3
- package/factory/orchestrator/dist/schemas.js +56 -8
- package/factory/orchestrator/dist/severity.js +8 -4
- package/factory/orchestrator/dist/spec.js +246 -12
- package/factory/orchestrator/dist/specChange.js +13 -9
- package/factory/orchestrator/dist/stack.js +6 -6
- package/factory/orchestrator/dist/storeRoot.js +90 -0
- package/factory/orchestrator/dist/symbols.js +261 -6
- package/factory/orchestrator/dist/taskEvents.js +192 -31
- package/factory/orchestrator/dist/taskStatus.js +14 -0
- package/factory/orchestrator/dist/taxonomy.js +11 -0
- package/factory/orchestrator/dist/testerAudit.js +31 -12
- package/factory/orchestrator/dist/testgate.js +20 -16
- package/factory/orchestrator/dist/uiuxGate.js +83 -0
- package/factory/orchestrator/dist/usage.js +113 -48
- package/factory/orchestrator/dist/waivers.js +99 -12
- package/factory/orchestrator/dist/waveBudget.js +2 -0
- package/factory/orchestrator/dist/waveConcurrency.js +143 -7
- package/factory/orchestrator/dist/waveSchedule.js +84 -0
- package/factory/orchestrator/dist/workroot.js +1 -1
- package/factory/orchestrator/dist/worktree.js +76 -21
- package/factory/orchestrator/drizzle/0013_operator_feedback.sql +19 -0
- package/factory/orchestrator/drizzle/0014_misty_madripoor.sql +2 -0
- package/factory/orchestrator/drizzle/0015_gifted_omega_flight.sql +1 -0
- package/factory/orchestrator/drizzle/0016_milestones_project_default.sql +30 -0
- package/factory/orchestrator/drizzle/0017_events_raw_cli_session_id.sql +2 -0
- package/factory/orchestrator/drizzle/meta/_journal.json +35 -0
- package/factory/policies/budgets.yml +123 -33
- package/factory/policies/crosscheck.yml +51 -13
- package/factory/policies/delegation.yml +3 -3
- package/factory/policies/effort.yml +18 -10
- package/factory/policies/guardrails.yml +8 -6
- package/factory/policies/harness.yml +5 -5
- package/factory/policies/lessons.md +53 -2
- package/factory/policies/scheduler.yml +4 -4
- package/factory/policies/sensitive-paths.yml +1 -1
- package/factory/policies/severity.yml +13 -1
- package/factory/policies/stack.yml +6 -6
- package/factory/policies/taxonomy.yml +9 -3
- package/factory/policies/worktree.yml +15 -9
- package/factory/specs/roadmap.md +27 -3
- package/factory/specs/schema/event.schema.json +8 -1
- package/factory/specs/schema/result.schema.json +21 -8
- package/factory/specs/schema/task-spec.schema.json +12 -0
- package/package.json +16 -12
- package/.claude/agents/uiux.md +0 -133
|
@@ -8,18 +8,37 @@ import { taskIdsMatch } from './taskId.js';
|
|
|
8
8
|
*/
|
|
9
9
|
const TEST_GATE_EVENT = 'testgate-result';
|
|
10
10
|
/**
|
|
11
|
-
* How a dispatched agent's turn is recorded as over, and which payload
|
|
12
|
-
*
|
|
13
|
-
* nicety: the older half of the log names the role
|
|
14
|
-
* and reading only the payload would report those
|
|
15
|
-
* reported — an `unverifiable` about evidence the
|
|
11
|
+
* How a dispatched agent's turn is recorded as over, and which payload
|
|
12
|
+
* field(s) name the role that ended it, tried in order. Falling back to the
|
|
13
|
+
* record's `actor` is not a nicety: the older half of the log names the role
|
|
14
|
+
* there and nowhere else, and reading only the payload would report those
|
|
15
|
+
* runs as testers that never reported — an `unverifiable` about evidence the
|
|
16
|
+
* log is holding.
|
|
17
|
+
*
|
|
18
|
+
* `error-logged` tries `agent` before `agent_role`: this reader's own
|
|
19
|
+
* historical key first, `agent_role` (the dispatch's) as the fallback for a
|
|
20
|
+
* hand-written event that only carries that one — the same either-key
|
|
21
|
+
* reading delegation.ts and agents-registry.ts already give this event type.
|
|
22
|
+
* Each of the three readers keeps its own historical key first rather than
|
|
23
|
+
* all agreeing on one order, since `agent` and `agent_role` were each the
|
|
24
|
+
* only key some reader ever had for this event type before the other was
|
|
25
|
+
* added as a fallback.
|
|
16
26
|
*/
|
|
17
27
|
const TERMINAL_ROLE_EVENTS = {
|
|
18
|
-
'task-result-recorded': 'agent',
|
|
19
|
-
'error-logged': 'agent',
|
|
20
|
-
'judge-reported': 'agent_role',
|
|
21
|
-
'judge-verdict': 'agent',
|
|
28
|
+
'task-result-recorded': ['agent'],
|
|
29
|
+
'error-logged': ['agent', 'agent_role'],
|
|
30
|
+
'judge-reported': ['agent_role'],
|
|
31
|
+
'judge-verdict': ['agent'],
|
|
22
32
|
};
|
|
33
|
+
/** The first of `keys` that names a non-empty string in `payload`, or null. */
|
|
34
|
+
function firstPayloadString(payload, keys) {
|
|
35
|
+
for (const key of keys) {
|
|
36
|
+
const value = payloadString(payload, key);
|
|
37
|
+
if (value !== null)
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
23
42
|
function payloadString(payload, key) {
|
|
24
43
|
const value = payload[key];
|
|
25
44
|
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
@@ -46,11 +65,11 @@ export function readTerminalRoleRecords(events) {
|
|
|
46
65
|
const records = [];
|
|
47
66
|
for (const stored of events) {
|
|
48
67
|
const record = stored.record;
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
68
|
+
const fields = TERMINAL_ROLE_EVENTS[record.event_type];
|
|
69
|
+
if (!fields)
|
|
51
70
|
continue;
|
|
52
71
|
const payload = (record.payload ?? {});
|
|
53
|
-
const role =
|
|
72
|
+
const role = firstPayloadString(payload, fields) ?? record.actor;
|
|
54
73
|
if (!role)
|
|
55
74
|
continue;
|
|
56
75
|
records.push({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { constants as osConstants } from 'node:os';
|
|
3
|
+
import { BS_PREFIX, LEGACY_PREFIX } from './env.js';
|
|
3
4
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
4
5
|
const TAIL_LINES = 50;
|
|
5
6
|
/**
|
|
@@ -63,27 +64,29 @@ function signalNote(code, signal) {
|
|
|
63
64
|
* factory's own namespace.
|
|
64
65
|
*
|
|
65
66
|
* A command the factory launches on a project's behalf belongs to the
|
|
66
|
-
* project, not to the factory that launched it. Every `
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* factory
|
|
67
|
+
* project, not to the factory that launched it. Every `BS_*` variable — and,
|
|
68
|
+
* for the transition period, every legacy `SMITH_*` one — in this process
|
|
69
|
+
* was set for *this* process, and letting one through silently makes the
|
|
70
|
+
* factory's own invocation part of the project's verdict. `BS_HOME` (nee
|
|
71
|
+
* `SMITH_HOME`) is the one that bites: it moves `WORK_ROOT` off the
|
|
72
|
+
* worktree, so a suite that asserts its own layout fails on the launcher's
|
|
73
|
+
* environment alone, and anything the suite writes to state lands in the
|
|
74
|
+
* factory's live `state/` instead of the worktree's. The verdict then
|
|
75
|
+
* depends on how the factory happened to be invoked, which is not a verdict
|
|
76
|
+
* about the branch.
|
|
74
77
|
*
|
|
75
78
|
* The strip is a rule rather than a roster, deliberately: a roster is a list
|
|
76
|
-
* that goes stale the next time a `SMITH_*` variable is added, and it
|
|
77
|
-
* stale silently. Not every one of them is the factory configuring
|
|
78
|
-
* `
|
|
79
|
-
* says of that one "pass it per command"
|
|
79
|
+
* that goes stale the next time a `BS_*`/`SMITH_*` variable is added, and it
|
|
80
|
+
* goes stale silently. Not every one of them is the factory configuring
|
|
81
|
+
* itself — `BS_CROSSCHECK_OFFLINE` is an operator switch — but the runbook
|
|
82
|
+
* already says of that one "pass it per command"
|
|
80
83
|
* (`docs/runbooks/providers.md`, and it names an ambient copy of that switch
|
|
81
84
|
* as its own hazard). The command the operator means is the one running
|
|
82
85
|
* here, not the factory that spawned it, and stating it inside the command
|
|
83
86
|
* string works: the shell applies it after this strip.
|
|
84
87
|
*/
|
|
85
88
|
export function projectCommandEnv(env) {
|
|
86
|
-
return Object.fromEntries(Object.entries(env).filter(([name]) => !name.startsWith(
|
|
89
|
+
return Object.fromEntries(Object.entries(env).filter(([name]) => !name.startsWith(BS_PREFIX) && !name.startsWith(LEGACY_PREFIX)));
|
|
87
90
|
}
|
|
88
91
|
/**
|
|
89
92
|
* Run one check command in its own process group (POSIX `setsid`, via
|
|
@@ -94,8 +97,9 @@ export function projectCommandEnv(env) {
|
|
|
94
97
|
* jobs, long-lived children) running as orphans after the parent shell
|
|
95
98
|
* dies — a real leak in a 24/7 factory (reproduced: manual kill -9 needed).
|
|
96
99
|
*/
|
|
97
|
-
function runOne(check, cwd,
|
|
100
|
+
function runOne(check, cwd, defaultTimeoutMs) {
|
|
98
101
|
return new Promise((resolve) => {
|
|
102
|
+
const timeoutMs = check.timeout_ms ?? defaultTimeoutMs;
|
|
99
103
|
const child = spawn(check.cmd, {
|
|
100
104
|
cwd,
|
|
101
105
|
shell: true,
|
|
@@ -176,10 +180,10 @@ function runOne(check, cwd, timeoutMs) {
|
|
|
176
180
|
* no test-output parsing beyond the process exit code.
|
|
177
181
|
*/
|
|
178
182
|
export async function run(checks, opts) {
|
|
179
|
-
const
|
|
183
|
+
const defaultTimeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
180
184
|
const results = [];
|
|
181
185
|
for (const check of checks) {
|
|
182
|
-
const result = await runOne(check, opts.cwd,
|
|
186
|
+
const result = await runOne(check, opts.cwd, defaultTimeoutMs);
|
|
183
187
|
results.push(result);
|
|
184
188
|
if (!result.pass && !opts.runAll)
|
|
185
189
|
break;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { resolveArtifactPath } from './artifacts.js';
|
|
2
|
+
import { eventTaskId, readLineageEvents } from './events.js';
|
|
3
|
+
import { foldJudgeTurns, JUDGE_DISPATCH_EVENT_TYPE } from './judges.js';
|
|
4
|
+
import { taskIdsMatch } from './taskId.js';
|
|
5
|
+
/** `judges.ts`'s uiux role, spelled out here rather than imported: the string is the taxonomy value, not an implementation detail of judges.ts. */
|
|
6
|
+
const UIUX_ROLE = 'uiux';
|
|
7
|
+
/** desktop × mobile, light × dark — the four shots a visual pass has to cover. */
|
|
8
|
+
const REQUIRED_COMBOS = [
|
|
9
|
+
['desktop', 'light'],
|
|
10
|
+
['desktop', 'dark'],
|
|
11
|
+
['mobile', 'light'],
|
|
12
|
+
['mobile', 'dark'],
|
|
13
|
+
];
|
|
14
|
+
function latestTesterRecord(events, taskId) {
|
|
15
|
+
let latest = null;
|
|
16
|
+
events.forEach(({ record }, index) => {
|
|
17
|
+
if (record.event_type !== 'artifact-check-result')
|
|
18
|
+
return;
|
|
19
|
+
if (!taskIdsMatch(eventTaskId(record) ?? undefined, taskId))
|
|
20
|
+
return;
|
|
21
|
+
const head = record.payload.head;
|
|
22
|
+
if (typeof head !== 'string')
|
|
23
|
+
return; // an artifact-check-result with no --worktree behind it
|
|
24
|
+
const screenshots = Array.isArray(record.payload.screenshots)
|
|
25
|
+
? record.payload.screenshots
|
|
26
|
+
: [];
|
|
27
|
+
latest = { index, head, screenshots };
|
|
28
|
+
});
|
|
29
|
+
return latest;
|
|
30
|
+
}
|
|
31
|
+
/** The lineage position of the dispatch that opened a judge turn — `JudgeTurn.dispatchedAt` is wall-clock only. */
|
|
32
|
+
function dispatchIndex(events, taskId, role, kind, round) {
|
|
33
|
+
let found = -1;
|
|
34
|
+
events.forEach(({ record }, index) => {
|
|
35
|
+
if (record.event_type !== JUDGE_DISPATCH_EVENT_TYPE)
|
|
36
|
+
return;
|
|
37
|
+
if (!taskIdsMatch(eventTaskId(record) ?? undefined, taskId))
|
|
38
|
+
return;
|
|
39
|
+
if (record.payload.agent_role !== role)
|
|
40
|
+
return;
|
|
41
|
+
if (record.payload.judge_kind !== kind)
|
|
42
|
+
return;
|
|
43
|
+
if (record.payload.round !== round)
|
|
44
|
+
return;
|
|
45
|
+
found = index;
|
|
46
|
+
});
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `checkUiux`: the one function both the gate stage and `bs uiux check`
|
|
51
|
+
* (the preflight verb, D4) call, so the two can never disagree about the same
|
|
52
|
+
* fixture. Reads the lineage once and folds both judge turns and the tester's
|
|
53
|
+
* freshness record out of it — no separate dispatch, no separate store.
|
|
54
|
+
*/
|
|
55
|
+
export async function checkUiux(input, ctx, opts = {}) {
|
|
56
|
+
const events = await readLineageEvents(ctx.sessionId, opts);
|
|
57
|
+
const turns = foldJudgeTurns(events, input.taskId);
|
|
58
|
+
const specTurn = turns.find((t) => t.role === UIUX_ROLE && t.kind === 'spec');
|
|
59
|
+
if (!specTurn?.reported) {
|
|
60
|
+
return { outcome: 'blocked', reason: 'uiux-spec-missing' };
|
|
61
|
+
}
|
|
62
|
+
const tester = latestTesterRecord(events, input.taskId);
|
|
63
|
+
if (tester === null) {
|
|
64
|
+
return { outcome: 'blocked', reason: 'screenshots-missing' };
|
|
65
|
+
}
|
|
66
|
+
const haveCombos = new Set(tester.screenshots.map((s) => `${s.viewport}:${s.theme}`));
|
|
67
|
+
const allCombosPresent = REQUIRED_COMBOS.every(([viewport, theme]) => haveCombos.has(`${viewport}:${theme}`));
|
|
68
|
+
const allOnDisk = tester.screenshots.every((s) => resolveArtifactPath(input.taskId, s.path, input.artifactsDir) !== null);
|
|
69
|
+
if (!allCombosPresent || !allOnDisk) {
|
|
70
|
+
return { outcome: 'blocked', reason: 'screenshots-missing' };
|
|
71
|
+
}
|
|
72
|
+
if (input.head !== undefined && tester.head !== input.head) {
|
|
73
|
+
return { outcome: 'blocked', reason: 'screenshots-stale' };
|
|
74
|
+
}
|
|
75
|
+
const visualTurn = turns.find((t) => t.role === UIUX_ROLE && t.kind === 'visual');
|
|
76
|
+
const visualDispatchIndex = visualTurn === undefined
|
|
77
|
+
? -1
|
|
78
|
+
: dispatchIndex(events, input.taskId, UIUX_ROLE, 'visual', visualTurn.round);
|
|
79
|
+
if (!visualTurn?.reported || !(visualDispatchIndex > tester.index)) {
|
|
80
|
+
return { outcome: 'blocked', reason: 'uiux-visual-missing' };
|
|
81
|
+
}
|
|
82
|
+
return { outcome: 'pass' };
|
|
83
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What `
|
|
2
|
+
* What `bs` can be asked to do, written down once (P9-21).
|
|
3
3
|
*
|
|
4
4
|
* Before this, `smith` and `smith --help` both answered
|
|
5
5
|
* `{"error":{"message":"Unknown command: "}}`, so the only way to learn a
|
|
@@ -31,7 +31,7 @@ const EVENTS_DIR = `${EVENTS} [--state-dir <dir>]`;
|
|
|
31
31
|
*/
|
|
32
32
|
const STATS = '[--db <file>] [--session <id>] [--lineage]';
|
|
33
33
|
/** The scope both `issues` actions accept: a session's lineage, narrowed by epic and ISO since. */
|
|
34
|
-
const ISSUES = '--session <id> [--epic <id>] [--since <iso>] [--state-dir <dir>] [--roadmap-path <file>]';
|
|
34
|
+
const ISSUES = '--session <id> [--epic <id>] [--since <iso>] [--state-dir <dir>] [--roadmap-path <file>] [--specs-dir <dir>]';
|
|
35
35
|
export const COMMANDS = [
|
|
36
36
|
{
|
|
37
37
|
command: 'audit open',
|
|
@@ -66,8 +66,8 @@ export const COMMANDS = [
|
|
|
66
66
|
{
|
|
67
67
|
command: 'audit resolve',
|
|
68
68
|
positionals: '<project-dir>',
|
|
69
|
-
flags: `--epic <epic-id> ${EVENTS_DIR}`,
|
|
70
|
-
summary:
|
|
69
|
+
flags: `--epic <epic-id> [--except <fp[,fp...]>] [--plan <plan.json>] [--specs-dir <dir>] ${EVENTS_DIR}`,
|
|
70
|
+
summary: "Append a fixed line for every carried finding a task in the epic's newest plan still claims, or a superseded task's change already merged; list the rest as deferred. Run from the epic-close step.",
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
command: 'audit close',
|
|
@@ -90,14 +90,14 @@ export const COMMANDS = [
|
|
|
90
90
|
{
|
|
91
91
|
command: 'plan quorum',
|
|
92
92
|
positionals: '',
|
|
93
|
-
flags: '--plan-version <n> --session <id> --causal-parent <event-id> [--epic <id>] [--plan <draft.json>] [--confidence <0-1>] [--out <file>] [--actor <name>] [--specs-dir <dir>] [--state-dir <dir>]',
|
|
93
|
+
flags: '--plan-version <n> --session <id> --causal-parent <event-id> [--epic <id>] [--plan <draft.json>] [--confidence <0-1>] [--project <dir>] [--out <file>] [--actor <name>] [--specs-dir <dir>] [--state-dir <dir>]',
|
|
94
94
|
summary: 'Critique-only review of a drafted plan: --plan <draft.json> before it is filed, or --epic for the filed version. Exit 1 means the operator must look first.',
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
command: 'plan amend',
|
|
98
98
|
positionals: '',
|
|
99
99
|
flags: `--plan <plan.json> --findings <id[,id...]> --rationale <text> --sites <path[,path...]> [--changes <changes.json>] [--specs-dir <dir>] ${EVENTS_DIR}`,
|
|
100
|
-
summary: 'Cut a new plan version against the spec findings that forced it. No finding, no amendment. --sites is every place the shape occurs, not only where it was reported.',
|
|
100
|
+
summary: 'Cut a new plan version against the spec findings that forced it. No finding, no amendment. --sites is every place the shape occurs, not only where it was reported. --specs-dir defaults to the directory --plan already lives in (#219), not factory/specs/active; an explicit --specs-dir that disagrees with it is refused rather than silently preferred.',
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
command: 'plan propose',
|
|
@@ -126,8 +126,11 @@ export const COMMANDS = [
|
|
|
126
126
|
{
|
|
127
127
|
command: 'plan ingest',
|
|
128
128
|
positionals: '<plan.json>',
|
|
129
|
-
flags: EVENTS_DIR
|
|
130
|
-
summary: 'Record every task and dependency the plan declares, before any wave names them.'
|
|
129
|
+
flags: `[--project <dir>] ${EVENTS_DIR}`,
|
|
130
|
+
summary: 'Record every task and dependency the plan declares, before any wave names them. ' +
|
|
131
|
+
'--project resolves the branch prefix a legacy `smith/<epic>/integration` epic keeps ' +
|
|
132
|
+
'using, rather than declaring `bs/...` for a branch `worktree create` will cut as ' +
|
|
133
|
+
'`smith/...`.',
|
|
131
134
|
},
|
|
132
135
|
{
|
|
133
136
|
command: 'wave check',
|
|
@@ -198,7 +201,7 @@ export const COMMANDS = [
|
|
|
198
201
|
{
|
|
199
202
|
command: 'daemon run',
|
|
200
203
|
positionals: '',
|
|
201
|
-
flags: `[--interval <seconds>] [--once] [--dir <dir>] [--project <dir>...] [--no-self] [--db <path>] [--no-db] [--state-dir <dir>]`,
|
|
204
|
+
flags: `[--interval <seconds>] [--once] [--dir <dir>] [--project <dir>...] [--no-self] [--db <path>] [--no-db] [--state-dir <dir>] [--specs-dir <dir>]`,
|
|
202
205
|
summary: 'Watch the event log in the foreground: budgets, stale agents, work that is due. ' +
|
|
203
206
|
'Prints one report per tick, one JSON line each; --once prints one document and exits. ' +
|
|
204
207
|
'Never dispatches.',
|
|
@@ -232,21 +235,21 @@ export const COMMANDS = [
|
|
|
232
235
|
{
|
|
233
236
|
command: 'scheduler run',
|
|
234
237
|
positionals: '',
|
|
235
|
-
flags: `[--dry] [--now <iso>] [--project <dir>...] [--no-self] ${EVENTS_DIR}`,
|
|
238
|
+
flags: `[--dry] [--now <iso>] [--project <dir>...] [--no-self] [--specs-dir <dir>] ${EVENTS_DIR}`,
|
|
236
239
|
summary: 'Propose and enact the next scheduler moves for a session. --dry proposes only.',
|
|
237
240
|
},
|
|
238
241
|
{
|
|
239
242
|
command: 'scheduler admit',
|
|
240
243
|
positionals: '',
|
|
241
244
|
flags: '--session <id> [--state-dir <dir>] [--now <iso>] [--project <dir>...] [--no-self] ' +
|
|
242
|
-
'[--policy <file>] [--crosscheck <file>]',
|
|
245
|
+
'[--specs-dir <dir>] [--policy <file>] [--crosscheck <file>]',
|
|
243
246
|
summary: "Classify what's due into auto or operator per scheduler.yml autonomy. Enacts nothing.",
|
|
244
247
|
},
|
|
245
248
|
{
|
|
246
249
|
command: 'worktree create',
|
|
247
250
|
positionals: '<project-dir> <epic> <task-id>',
|
|
248
|
-
flags: '',
|
|
249
|
-
summary: 'Cut the isolated worktree and task branch one task is allowed to write in.',
|
|
251
|
+
flags: '[--from <predecessor-task-id> --session <id> [--state-dir <dir>]]',
|
|
252
|
+
summary: 'Cut the isolated worktree and task branch one task is allowed to write in. --from cuts it from a predecessor branch instead of integration, and requires --session (which supplies the log --from is checked against): refused unless that session records <epic>/<task-id> as the logged successor of <epic>/<predecessor-task-id>.',
|
|
250
253
|
},
|
|
251
254
|
{
|
|
252
255
|
command: 'worktree rm',
|
|
@@ -275,8 +278,12 @@ export const COMMANDS = [
|
|
|
275
278
|
{
|
|
276
279
|
command: 'queue run',
|
|
277
280
|
positionals: '<epic>',
|
|
278
|
-
flags: '--project <dir> --test-cmd <cmd> --tasks <tasks.json>
|
|
279
|
-
summary: 'Serially rebase, test, and merge task branches into the integration branch.'
|
|
281
|
+
flags: '--project <dir> --test-cmd <cmd> --tasks <tasks.json> --session <id> --causal-parent <event-id> --plan <plan.json> [--plan-version <n>] [--actor <name>] [--select-test-cmd <cmd>] [--batch] [--state-dir <dir>]',
|
|
282
|
+
summary: 'Serially rebase, test, and merge task branches into the integration branch. ' +
|
|
283
|
+
'A merge is a fact the log must carry, so --session/--causal-parent/--plan are required — ' +
|
|
284
|
+
'refuses before any git write without them. ' +
|
|
285
|
+
'--batch (roadmap merge-lanes) groups claim-disjoint tasks with groupForBatch and ' +
|
|
286
|
+
'tests each group once instead of once per task — see queue.ts and the operator guide.',
|
|
280
287
|
},
|
|
281
288
|
{
|
|
282
289
|
command: 'queue adopt',
|
|
@@ -291,8 +298,8 @@ export const COMMANDS = [
|
|
|
291
298
|
{
|
|
292
299
|
command: 'session start',
|
|
293
300
|
positionals: '<session-id>',
|
|
294
|
-
flags: '[--continues <event-id>] [--actor <name>] [--state-dir <dir>]',
|
|
295
|
-
summary: 'Open a session log with the one root event it is allowed. Exit 1 if the session is already open.',
|
|
301
|
+
flags: '[--continues <event-id>] [--prompt <event-id>] [--actor <name>] [--state-dir <dir>]',
|
|
302
|
+
summary: 'Open a session log with the one root event it is allowed; --prompt links the user_prompt that asked for it. Exit 1 if the session is already open.',
|
|
296
303
|
},
|
|
297
304
|
{
|
|
298
305
|
command: 'event append',
|
|
@@ -325,6 +332,36 @@ export const COMMANDS = [
|
|
|
325
332
|
flags: EVENTS_DIR,
|
|
326
333
|
summary: "Put the operator's own words on the timeline verbatim, and print the event id a dispatch hangs off.",
|
|
327
334
|
},
|
|
335
|
+
{
|
|
336
|
+
command: 'prompt capture',
|
|
337
|
+
positionals: '',
|
|
338
|
+
flags: '',
|
|
339
|
+
summary: 'Read one UserPromptSubmit hook payload on stdin and record it where the cwd is managed. Print one line, or nothing; always exit 0.',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
command: 'prompt import',
|
|
343
|
+
positionals: '',
|
|
344
|
+
flags: '--transcript <file> [--dry-run]',
|
|
345
|
+
summary: "Replay a Claude Code transcript's prompts and option answers into prompts-<cli session>, keeping their timestamps. Re-running imports nothing; --dry-run prints each event and writes none. Print a summary line.",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
command: 'feedback record',
|
|
349
|
+
positionals: '',
|
|
350
|
+
flags: `--task <task-id> [--body <text>|--body-file <file>] [--kind <must-fix|nice-to-have>] [--source <dashboard|github|cli>] [--external-id <id>] [--author <name>] ${EVENTS_DIR}`,
|
|
351
|
+
summary: "Record the operator's mid-run comment on a task. Print { event_id, feedback_id, deduped }; a repeated --external-id dedupes rather than appending.",
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
command: 'feedback pending',
|
|
355
|
+
positionals: '',
|
|
356
|
+
flags: '--session <id> [--task <task-id>] [--epic <id>] [--state-dir <dir>]',
|
|
357
|
+
summary: "List this session lineage's unresolved feedback, each with a suggestedAction: follow-up once its task is closed to further work, bounce otherwise.",
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
command: 'feedback resolve',
|
|
361
|
+
positionals: '',
|
|
362
|
+
flags: `--feedback <id> --resolution <bounced|follow-up|dismissed> [--note <text>] [--follow-up-task <task-id>] ${EVENTS_DIR}`,
|
|
363
|
+
summary: 'Resolve one recorded feedback item. Rejects an unknown or already-resolved feedback id.',
|
|
364
|
+
},
|
|
328
365
|
{
|
|
329
366
|
command: 'claims check',
|
|
330
367
|
form: '--roots',
|
|
@@ -359,6 +396,12 @@ export const COMMANDS = [
|
|
|
359
396
|
flags: '[--task <id>] [--policy <path>] [--state-dir <dir>]',
|
|
360
397
|
summary: 'Assert crosscheck.yml’s role asymmetry against the log. Exit 1 on a violation OR an unverifiable answer.',
|
|
361
398
|
},
|
|
399
|
+
{
|
|
400
|
+
command: 'dispatch lint',
|
|
401
|
+
positionals: '<prompt-file>',
|
|
402
|
+
flags: '--role <role> --task <id> --session <id> [--agents-dir <dir>] [--state-dir <dir>]',
|
|
403
|
+
summary: "Check a composed dispatch prompt's stated turn budget against the role template's maxTurns, and a judge prompt's declared-artifact line against the ledger. Exit 1 on a violation OR an unverifiable answer. `-` reads stdin.",
|
|
404
|
+
},
|
|
362
405
|
{
|
|
363
406
|
command: 'tester check',
|
|
364
407
|
positionals: '<session-id>',
|
|
@@ -415,7 +458,7 @@ export const COMMANDS = [
|
|
|
415
458
|
{
|
|
416
459
|
command: 'harness plan',
|
|
417
460
|
positionals: '',
|
|
418
|
-
flags: '--role <role> --task <id> --prompt-file <path> [--harness <name>] [--worktree <dir>] [--policy <file>] [--tier <frontier|mid|small>] [--schema <name>]',
|
|
461
|
+
flags: '--role <role> --task <id> --prompt-file <path> [--harness <name>] [--worktree <dir>] [--policy <file>] [--tier <frontier|mid|small>] [--schema <name>] [--specs-dir <dir>]',
|
|
419
462
|
summary: 'Render the invocation that would run one worker turn, as JSON. Prints it; never starts it.',
|
|
420
463
|
},
|
|
421
464
|
{
|
|
@@ -452,7 +495,7 @@ export const COMMANDS = [
|
|
|
452
495
|
command: 'stack check',
|
|
453
496
|
positionals: '',
|
|
454
497
|
flags: '[--policy <file>]',
|
|
455
|
-
summary: 'Which stack answers the shipped templates honour, which they only record, and which make `
|
|
498
|
+
summary: 'Which stack answers the shipped templates honour, which they only record, and which make `bs new` refuse. Exit 1 on a refusal.',
|
|
456
499
|
},
|
|
457
500
|
{
|
|
458
501
|
command: 'policy check',
|
|
@@ -469,9 +512,21 @@ export const COMMANDS = [
|
|
|
469
512
|
{
|
|
470
513
|
command: 'gate run',
|
|
471
514
|
positionals: '<task-id>',
|
|
472
|
-
flags: `--worktree <dir> --checks <checks.json> --result <result.json> [--base <ref>] [--grader <file>] [--agent <role> --provider <name> --model-tier <tier> --input-tokens <n> --output-tokens <n>] [--evidence <file> --found-by <role> [--found-by-provider <name>]] [--findings <file>] [--no-findings <role>] [--lessons <lessons.md>] [--plan <file>] [--run-all] [--artifacts-dir <dir>] ${EVENTS_DIR}`,
|
|
515
|
+
flags: `--worktree <dir> --checks <checks.json> --result <result.json> [--project <dir>] [--base <ref>] [--grader <file>] [--agent <role> --provider <name> --model-tier <tier> --input-tokens <n> --output-tokens <n>] [--evidence <file> --found-by <role> [--found-by-provider <name>]] [--findings <file>] [--no-findings <role>] [--uiux-visual <file>] [--lessons <lessons.md>] [--plan <file>] [--run-all] [--artifacts-dir <dir>] ${EVENTS_DIR}`,
|
|
473
516
|
summary: 'The per-task gate: schema, tests, coverage, findings. Exit 1 when blocked.',
|
|
474
517
|
},
|
|
518
|
+
{
|
|
519
|
+
command: 'results record',
|
|
520
|
+
positionals: '',
|
|
521
|
+
flags: `--task <task-id> --result <result.json> [--agent <role> --provider <name> --model-tier <tier> [--input-tokens <n> --output-tokens <n>]] [--artifacts-dir <dir>] [--worktree <dir>] ${EVENTS_DIR}`,
|
|
522
|
+
summary: "Project a worker's Result on its own, with no worktree to certify and no tests to run: schema-check, task-result-recorded, artifact-check. Wave.md's tester has no gate to run one through; this is how its screenshots reach the dashboard the moment it finishes. --worktree records that worktree's HEAD and the Result's screenshot artifacts, the freshness evidence the uiux gate stage reads back. Exit 1 when blocked.",
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
command: 'uiux check',
|
|
526
|
+
positionals: '',
|
|
527
|
+
flags: `--task <task-id> --plan <plan.json> [--worktree <dir>] [--artifacts-dir <dir>] ${EVENTS_DIR}`,
|
|
528
|
+
summary: 'Preflight for the gate uiux stage: the same checkUiux a flagged task is gated on, so a dispatcher can ask before running the whole gate. Exit 1 when blocked.',
|
|
529
|
+
},
|
|
475
530
|
{
|
|
476
531
|
command: 'coverage check',
|
|
477
532
|
positionals: '<worktree-dir>',
|
|
@@ -481,7 +536,7 @@ export const COMMANDS = [
|
|
|
481
536
|
{
|
|
482
537
|
command: 'budget alarm',
|
|
483
538
|
positionals: '<session-id>',
|
|
484
|
-
flags: '[--epic <id>] [--policy <path>] [--state-dir <dir>]',
|
|
539
|
+
flags: '[--epic <id>] [--policy <path>] [--specs-dir <dir>] [--state-dir <dir>]',
|
|
485
540
|
summary: 'Epic spend against budgets.yml’s epic.alarm_ratio. Exit 1 on a crossing OR on a record too holey to rule one out.',
|
|
486
541
|
},
|
|
487
542
|
{
|
|
@@ -505,8 +560,8 @@ export const COMMANDS = [
|
|
|
505
560
|
{
|
|
506
561
|
command: 'epic spec-review',
|
|
507
562
|
positionals: '',
|
|
508
|
-
flags: `--epic <id> --project <dir> --plan <plan.json> --reviewed-by <role> [--reviewed-by-provider <name>] [--evidence <file>] ${EVENTS_DIR}`,
|
|
509
|
-
summary: 'Record the closing spec review, pinned to the integration head it was read at.',
|
|
563
|
+
flags: `--epic <id> --project <dir> --plan <plan.json> --reviewed-by <role> [--reviewed-by-provider <name>] [--evidence <file>] [--no-findings] ${EVENTS_DIR}`,
|
|
564
|
+
summary: 'Record the closing spec review, pinned to the integration head it was read at. Exactly one of --evidence or --no-findings; --no-findings attests the clean case.',
|
|
510
565
|
},
|
|
511
566
|
{
|
|
512
567
|
command: 'epic goal',
|
|
@@ -545,8 +600,8 @@ export const COMMANDS = [
|
|
|
545
600
|
{
|
|
546
601
|
command: 'findings for-dispatch',
|
|
547
602
|
positionals: '',
|
|
548
|
-
flags: '--session <id> --plan <plan.json> --task <task-id> [--state-dir <dir>]',
|
|
549
|
-
summary: "Print the open findings a dispatching task's own claims cover, as a prompt block.",
|
|
603
|
+
flags: '--session <id> [--plan <plan.json> --task <task-id>] [--state-dir <dir>]',
|
|
604
|
+
summary: "Print the open findings a dispatching task's own claims cover, as a prompt block; with no --plan/--task, every open finding in the epic.",
|
|
550
605
|
},
|
|
551
606
|
{
|
|
552
607
|
command: 'findings list',
|
|
@@ -587,10 +642,20 @@ export const COMMANDS = [
|
|
|
587
642
|
flags: EVENTS_DIR,
|
|
588
643
|
summary: 'Apply a batch of operator waiver decisions.',
|
|
589
644
|
},
|
|
645
|
+
{
|
|
646
|
+
command: 'waivers approve-task',
|
|
647
|
+
positionals: '<task-id>',
|
|
648
|
+
flags: `--note <why> ${EVENTS_DIR}`,
|
|
649
|
+
summary: 'Record the operator approval of one waived task. Without it a waived task blocks the epic close; an approval counts only for the plan version it was given at or later. Requires --actor operator.',
|
|
650
|
+
},
|
|
590
651
|
{
|
|
591
652
|
command: 'lessons candidates',
|
|
592
653
|
positionals: '',
|
|
593
|
-
|
|
654
|
+
// Only `candidates` takes `--state-dir` alongside STATS: it validates
|
|
655
|
+
// `--session` against the event log the same way `raise`/`approve`/
|
|
656
|
+
// `reject`/`db rebuild` do (P9-28), which is a per-session log, not a
|
|
657
|
+
// per-DB-query concern the other STATS-flagged commands share.
|
|
658
|
+
flags: `${STATS} [--state-dir <dir>]`,
|
|
594
659
|
summary: 'List the lesson candidates awaiting an approve or a reject.',
|
|
595
660
|
},
|
|
596
661
|
{
|
|
@@ -647,20 +712,20 @@ export const COMMANDS = [
|
|
|
647
712
|
{
|
|
648
713
|
command: 'judge dispatch',
|
|
649
714
|
positionals: '',
|
|
650
|
-
flags: `--task <task-id> --role <role> --artifact <
|
|
651
|
-
summary: 'Declare the file a judge owes, so a turn that never came back is visible.',
|
|
715
|
+
flags: `--task <task-id> --role <role> --artifact <absolute-path> --model <id> [--round <n>] [--provider <name>] [--model-tier <tier>] [--kind <spec|visual>] ${EVENTS_DIR}`,
|
|
716
|
+
summary: 'Declare the file a judge owes, so a turn that never came back is visible. --kind only applies to uiux, which opens a spec turn and a visual turn per task.',
|
|
652
717
|
},
|
|
653
718
|
{
|
|
654
719
|
command: 'judge report',
|
|
655
720
|
positionals: '',
|
|
656
|
-
flags: `--task <task-id> --role <role> [--round <n>] [--artifact <file>] [--no-findings] ${EVENTS_DIR}`,
|
|
657
|
-
summary:
|
|
721
|
+
flags: `--task <task-id> --role <role> [--round <n>] [--artifact <file>] [--no-findings] [--kind <spec|visual>] ${EVENTS_DIR}`,
|
|
722
|
+
summary: "Close a judge turn against the artifact it declared, written during this turn. --no-findings attests the clean case. --kind picks which of uiux's two open turns to close when both are open.",
|
|
658
723
|
},
|
|
659
724
|
{
|
|
660
725
|
command: 'judge outstanding',
|
|
661
726
|
positionals: '',
|
|
662
|
-
flags: '--session <id> --task <task-id> [--state-dir <dir>]',
|
|
663
|
-
summary: '
|
|
727
|
+
flags: '--session <id> --task <task-id> [--kind <spec|visual>] [--state-dir <dir>]',
|
|
728
|
+
summary: 'Print { taskId, sessionId, outstanding, count } for the judge turns still owed on a task. Exit 1 while count is non-zero.',
|
|
664
729
|
},
|
|
665
730
|
{
|
|
666
731
|
command: 'judge escalations',
|
|
@@ -671,8 +736,8 @@ export const COMMANDS = [
|
|
|
671
736
|
{
|
|
672
737
|
command: 'judge preflight',
|
|
673
738
|
positionals: '',
|
|
674
|
-
flags: '[--policy <file>]',
|
|
675
|
-
summary: 'Can the enabled providers be called at all, and would a promotion decide anything? Exit 1 on a provider that costs a call it cannot make.',
|
|
739
|
+
flags: '[--policy <file>] [--project <dir>]',
|
|
740
|
+
summary: 'Can the enabled providers be called at all, and would a promotion decide anything? Exit 1 on a provider that costs a call it cannot make. --project merges <dir>/.blacksmith/crosscheck.yml over the policy (OpenRouter substitution/extra judges).',
|
|
676
741
|
},
|
|
677
742
|
{
|
|
678
743
|
command: 'judge run',
|
|
@@ -683,25 +748,25 @@ export const COMMANDS = [
|
|
|
683
748
|
{
|
|
684
749
|
command: 'crossfind request',
|
|
685
750
|
positionals: '',
|
|
686
|
-
flags: '--task <id> --diff <file> --diff-ref <ref> [--criterion <text>...] [--timeout-ms <n>] [--max-output-bytes <n>] [--max-output-tokens <n>] [--policy <file>]',
|
|
751
|
+
flags: '--task <id> --diff <file> --diff-ref <ref> [--criterion <text>...] [--timeout-ms <n>] [--max-output-bytes <n>] [--max-output-tokens <n>] [--policy <file>] [--project <dir>] [--worktree <dir>]',
|
|
687
752
|
summary: 'Print the finder request without sending it — exactly what would leave the machine. Refuses when send_diff is false.',
|
|
688
753
|
},
|
|
689
754
|
{
|
|
690
755
|
command: 'crossfind reconcile',
|
|
691
756
|
positionals: '',
|
|
692
|
-
flags: '--task <id> --native <findings.json> --independent <runs.json> [--policy <file>]',
|
|
757
|
+
flags: '--task <id> --native <findings.json> --independent <runs.json> [--policy <file>] [--project <dir>] [--worktree <dir>]',
|
|
693
758
|
summary: 'Reconcile two saved finding lists offline. No provider, no log. Exit 1 when the result would change a gate.',
|
|
694
759
|
},
|
|
695
760
|
{
|
|
696
761
|
command: 'crossfind run',
|
|
697
762
|
positionals: '',
|
|
698
|
-
flags: `--task <id> --diff <file> --diff-ref <ref> [--criterion <text>...] [--status <status>] [--timeout-ms <n>] [--max-output-bytes <n>] [--max-output-tokens <n>] [--policy <file>] ${EVENTS_DIR}`,
|
|
763
|
+
flags: `--task <id> --diff <file> --diff-ref <ref> [--criterion <text>...] [--status <status>] [--timeout-ms <n>] [--max-output-bytes <n>] [--max-output-tokens <n>] [--policy <file>] [--project <dir>] [--worktree <dir>] ${EVENTS_DIR}`,
|
|
699
764
|
summary: 'Run the independent finder over a diff and reconcile it against the native findings. Exit 1 when the result would change a gate.',
|
|
700
765
|
},
|
|
701
766
|
{
|
|
702
767
|
command: 'ui serve',
|
|
703
768
|
positionals: '',
|
|
704
|
-
flags: '[--port <n>] [--db <file>] [--state-dir <dir>] [--roadmap-path <file>] [--specs-dir <dir>] [--now-iso <iso>]',
|
|
769
|
+
flags: '[--port <n>] [--db <file>] [--state-dir <dir>] [--roadmap-path <file>] [--specs-dir <dir>] [--now-iso <iso>] [--claude-config-dir <dir>] [--store <dir>...]',
|
|
705
770
|
summary: 'Serve the dashboard from the built ui/server.',
|
|
706
771
|
},
|
|
707
772
|
{
|
|
@@ -783,7 +848,7 @@ const COMMAND_NAMES = new Set(COMMANDS.map((doc) => doc.command));
|
|
|
783
848
|
const NAMESPACES = new Set(COMMANDS.map(namespaceOf));
|
|
784
849
|
/** The line that would have worked. Empty parts are dropped, never printed as a double space. */
|
|
785
850
|
export function usageLine(doc) {
|
|
786
|
-
return ['
|
|
851
|
+
return ['bs', doc.command, doc.positionals, doc.flags].filter((part) => part !== '').join(' ');
|
|
787
852
|
}
|
|
788
853
|
/** The positional argument names, in order — what a missing-argument error names. */
|
|
789
854
|
export function positionalNames(doc) {
|
|
@@ -825,7 +890,7 @@ export function flagSpecFor(namespace, action) {
|
|
|
825
890
|
if (namespace === undefined)
|
|
826
891
|
return undefined;
|
|
827
892
|
const key = action === undefined ? namespace : `${namespace} ${action}`;
|
|
828
|
-
// The second arm is what keeps the action-less verbs working: `
|
|
893
|
+
// The second arm is what keeps the action-less verbs working: `bs new
|
|
829
894
|
// my-project` spends its action slot on a positional. Same fallback as
|
|
830
895
|
// isDocumented, deliberately — a command that dispatches must also parse.
|
|
831
896
|
const docs = COMMANDS.filter((doc) => doc.command === key || doc.command === namespace).filter((doc) => doc.command === key || !COMMAND_NAMES.has(key));
|
|
@@ -853,23 +918,23 @@ function renderCommands(docs) {
|
|
|
853
918
|
}
|
|
854
919
|
/**
|
|
855
920
|
* The whole command list, or one namespace's slice of it. An unknown namespace
|
|
856
|
-
* throws rather than printing an empty list: "
|
|
921
|
+
* throws rather than printing an empty list: "bs has no such namespace" and
|
|
857
922
|
* "that namespace has no commands" must not be the same observable answer.
|
|
858
923
|
*/
|
|
859
924
|
export function usageText(namespace) {
|
|
860
925
|
if (namespace !== undefined && !NAMESPACES.has(namespace)) {
|
|
861
|
-
throw new SmithError('usage.unknown-namespace', `No such namespace: ${namespace}. Run "
|
|
926
|
+
throw new SmithError('usage.unknown-namespace', `No such namespace: ${namespace}. Run "bs --help" for the list.`, { namespace });
|
|
862
927
|
}
|
|
863
928
|
const docs = namespace === undefined ? COMMANDS : COMMANDS.filter((doc) => namespaceOf(doc) === namespace);
|
|
864
929
|
const header = namespace === undefined
|
|
865
930
|
? [
|
|
866
|
-
'Usage:
|
|
931
|
+
'Usage: bs <namespace> <action> [args] [--flags]',
|
|
867
932
|
'',
|
|
868
|
-
'
|
|
869
|
-
'
|
|
933
|
+
' bs <namespace> --help list one namespace',
|
|
934
|
+
' bs <command> --help show one command',
|
|
870
935
|
'',
|
|
871
936
|
].join('\n')
|
|
872
|
-
: `Usage:
|
|
937
|
+
: `Usage: bs ${namespace} <action> [args] [--flags]\n`;
|
|
873
938
|
let out = `${header}\n`;
|
|
874
939
|
let current = '';
|
|
875
940
|
for (const doc of docs) {
|
|
@@ -895,7 +960,7 @@ export function helpText(namespace, action) {
|
|
|
895
960
|
const exact = COMMANDS.filter((doc) => doc.command === key);
|
|
896
961
|
if (exact.length > 0)
|
|
897
962
|
return renderCommands(exact);
|
|
898
|
-
// `
|
|
963
|
+
// `bs new my-project --help`: the action slot holds a positional, so the
|
|
899
964
|
// namespace alone is the command.
|
|
900
965
|
const bare = COMMANDS.filter((doc) => doc.command === namespace);
|
|
901
966
|
if (bare.length > 0)
|
|
@@ -908,7 +973,7 @@ export function helpText(namespace, action) {
|
|
|
908
973
|
* Whether cli.ts's dispatcher has a branch for this pair. Checked before
|
|
909
974
|
* dispatch, so an undocumented command cannot run — which is the half of
|
|
910
975
|
* "the table and the dispatcher agree" that a test cannot enforce at runtime.
|
|
911
|
-
* The second arm is what keeps the action-less verbs working: `
|
|
976
|
+
* The second arm is what keeps the action-less verbs working: `bs new
|
|
912
977
|
* my-project` spends its action slot on a positional.
|
|
913
978
|
*/
|
|
914
979
|
export function isDocumented(namespace, action) {
|