muse-crew 0.8.0 → 0.9.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/API.md +3 -0
- package/docs/artifact-ux.md +110 -0
- package/docs/guide.md +2 -2
- package/docs/ooda-report.md +21 -10
- package/docs/terminal-ux.md +110 -0
- package/docs/upgrade-paths.md +105 -0
- package/identities/hazel.md +20 -7
- package/lib/AGENTS.md +1 -0
- package/lib/append-ooda-step.js +25 -5
- package/lib/crew-api.js +37 -6
- package/lib/crew-release.sh +8 -2
- package/lib/publish-npm.sh +24 -6
- package/lib/read-ooda-verdict.js +60 -2
- package/lib/schema.sql +9 -0
- package/lib/update-watch.js +79 -1
- package/lib/ux-doctrine.js +63 -0
- package/package.json +1 -1
- package/workflows/bugfix.js +165 -38
- package/workflows/chore.js +43 -8
- package/workflows/crew-dispatch.js +5 -1
- package/workflows/crew-init.js +20 -3
- package/workflows/docs.js +18 -1
- package/workflows/standard.js +157 -37
package/workflows/bugfix.js
CHANGED
|
@@ -115,6 +115,36 @@ const WORKTREE_HINT = REPO_PATH + "/.worktrees/" + taskId;
|
|
|
115
115
|
const WORKTREE_PRESERVED_HINT = ".worktrees/" + taskId;
|
|
116
116
|
|
|
117
117
|
const PUBLISH_TYPE = projectConfig.deploy_type || "";
|
|
118
|
+
// User-facing surface for experiential QA routing: 'artifact' (a rendered
|
|
119
|
+
// web UI Hazel drives with the see-act browser loop) | 'terminal' (a CLI
|
|
120
|
+
// Hazel drives herself, keeping attempt-scoped transcripts) | null
|
|
121
|
+
// (unclassified — no experiential QA). environment_type is the canonical
|
|
122
|
+
// UX-surface axis; deploy_type names the deployment target, but
|
|
123
|
+
// deploy_type === "artifact" remains a legacy artifact-surface signal so
|
|
124
|
+
// pre-field projects keep today's experiential QA (the migration does not
|
|
125
|
+
// backfill the column).
|
|
126
|
+
const ENV_TYPE = projectConfig.environment_type || null;
|
|
127
|
+
// Surface resolution: artifact wins on contradictory config (the deployed
|
|
128
|
+
// artifact is what users see). Unclassified surface => Capture skips, QA
|
|
129
|
+
// runs the plain code-blind prompt — today's behavior, unchanged.
|
|
130
|
+
const SURFACE_ARTIFACT = (PUBLISH_TYPE === "artifact" || ENV_TYPE === "artifact");
|
|
131
|
+
const SURFACE_TERMINAL = (!SURFACE_ARTIFACT && ENV_TYPE === "terminal");
|
|
132
|
+
const SURFACE_CLASSIFIED = SURFACE_ARTIFACT || SURFACE_TERMINAL;
|
|
133
|
+
// One-line surface description for the Triage prompt, so Sage judges the
|
|
134
|
+
// experiential flag against the project's actual user-facing surface.
|
|
135
|
+
const SURFACE_TRIAGE_DESC = SURFACE_ARTIFACT
|
|
136
|
+
? "This project's user-facing surface is artifact: a rendered web UI."
|
|
137
|
+
: SURFACE_TERMINAL
|
|
138
|
+
? "This project's user-facing surface is terminal: a command-line interface."
|
|
139
|
+
: "This project's user-facing surface is unclassified (environment_type not set): judge by what a user would directly observe.";
|
|
140
|
+
// UX doctrine page: the shared UX bar for this run's surface, resolved
|
|
141
|
+
// mechanically — every phase prompt reads UX_DOCTRINE_PATH, never a
|
|
142
|
+
// hardcoded filename. Canonical map: lib/ux-doctrine.js (mirrored here as a
|
|
143
|
+
// one-liner because the workflow runtime's relative-import support is
|
|
144
|
+
// unverified; tests pin the mirror). Null on unclassified surfaces: no
|
|
145
|
+
// shared page, and prompts say so instead of naming the wrong one.
|
|
146
|
+
const UX_DOCTRINE_PAGE = SURFACE_TERMINAL ? "terminal-ux.md" : (SURFACE_ARTIFACT ? "artifact-ux.md" : null);
|
|
147
|
+
const UX_DOCTRINE_PATH = UX_DOCTRINE_PAGE ? crewHome + "/current/docs/" + UX_DOCTRINE_PAGE : null;
|
|
118
148
|
const PUBLISH_SLUG = projectConfig.deploy_slug || "";
|
|
119
149
|
const PROJECT_DESC = projectConfig.description || "React + TypeScript web dashboard (client/src/, server/src/, drizzle/)";
|
|
120
150
|
const RELEASE_SCRIPT = crewHome + "/crew-release.sh";
|
|
@@ -528,7 +558,7 @@ function extractMarkerLines(workerText) {
|
|
|
528
558
|
var markers = [];
|
|
529
559
|
for (var i = 0; i < lines.length; i++) {
|
|
530
560
|
var line = lines[i].trim();
|
|
531
|
-
if (/^(repo_diff:|release:|version_bump:|VERDICT:|TARGET_VERSION=|published:|experiential:|layer:|capture_targets:|worktree:)/i.test(line)) {
|
|
561
|
+
if (/^(repo_diff:|release:|version_bump:|VERDICT:|TARGET_VERSION=|published:|experiential:|layer:|capture_targets:|terminal_targets:|worktree:)/i.test(line)) {
|
|
532
562
|
markers.push(line);
|
|
533
563
|
}
|
|
534
564
|
}
|
|
@@ -779,6 +809,10 @@ let layerResolved = null;
|
|
|
779
809
|
// notes are immutable within a run, so the lookup runs at most once.
|
|
780
810
|
let experientialResolved = null;
|
|
781
811
|
let captureTargets = "";
|
|
812
|
+
// Sage's terminal_targets marker: the CLI commands/flags exercising the
|
|
813
|
+
// changed surface (terminal-surface tasks). Falls back to the task
|
|
814
|
+
// description when Sage omits it.
|
|
815
|
+
let terminalTargets = "";
|
|
782
816
|
let mapGateBounceCount = 0;
|
|
783
817
|
// Merge-time versioning: the release decision is extracted deterministically
|
|
784
818
|
// from the accepted Build worker report (extractReleaseDecision) so the Publish
|
|
@@ -1083,13 +1117,13 @@ while (i < STEPS.length) {
|
|
|
1083
1117
|
if (step.name === "Capture") {
|
|
1084
1118
|
var capExp = await resolveExperiential();
|
|
1085
1119
|
var bounceSuffix = (mapGateBounceCount > 0 ? "-g" + mapGateBounceCount : "");
|
|
1086
|
-
if (capExp !== "yes" ||
|
|
1087
|
-
log("Capture skipped for task " + taskId + " — " + (capExp !== "yes" ? "not experiential" : "
|
|
1120
|
+
if (capExp !== "yes" || !SURFACE_CLASSIFIED) {
|
|
1121
|
+
log("Capture skipped for task " + taskId + " — " + (capExp !== "yes" ? "not experiential" : "surface unclassified (environment_type=" + (ENV_TYPE || "null") + ")"));
|
|
1088
1122
|
await agent(
|
|
1089
1123
|
"Update the session and log the event.\n" +
|
|
1090
1124
|
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
1091
1125
|
task_id: taskId,
|
|
1092
|
-
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "completed", notes: "Capture skipped — not an experiential
|
|
1126
|
+
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "completed", notes: "Capture skipped — not an experiential task on a classified surface" },
|
|
1093
1127
|
event: { task_id: taskId, type: "completed", identity: step.identity, message: "Capture completed by " + step.identity }
|
|
1094
1128
|
}),
|
|
1095
1129
|
{ key: "record-Capture" + bounceSuffix, label: "Recording Capture result" }
|
|
@@ -1097,6 +1131,34 @@ while (i < STEPS.length) {
|
|
|
1097
1131
|
i++;
|
|
1098
1132
|
continue;
|
|
1099
1133
|
}
|
|
1134
|
+
// Terminal-surface Capture: Hazel runs the CLI herself against the
|
|
1135
|
+
// pre-change tree and archives attempt-scoped transcripts. No parent
|
|
1136
|
+
// protocol exists for terminal projects (there is no see-act loop to
|
|
1137
|
+
// drive), so the baseline is agent-driven in one shot — the Map gate
|
|
1138
|
+
// reads the same "baseline: captured/none" note prefixes either way.
|
|
1139
|
+
if (SURFACE_TERMINAL) {
|
|
1140
|
+
var termBaseDir = crewHome + "/task-evidence/" + taskId + "/baseline";
|
|
1141
|
+
log("Capture: terminal-surface baseline for task " + taskId + " — Hazel drives the CLI herself");
|
|
1142
|
+
await agent(
|
|
1143
|
+
"Run the pre-change terminal baseline yourself — there is no parent capture protocol for terminal-surface projects.\n" +
|
|
1144
|
+
"1. The project's repo is at " + REPO_PATH + " (pre-change state; the task branch does not exist yet). The CLI under test is the project's own command-line interface in that tree — start like a new user with --help. You are code-blind: you may RUN the CLI, never READ its source.\n" +
|
|
1145
|
+
"2. Terminal targets for this task: " + (terminalTargets || "not declared — derive them from --help and the task description") + ".\n" +
|
|
1146
|
+
"3. Run in shell: mkdir -p " + termBaseDir + "\n" +
|
|
1147
|
+
"4. For each target, run the command with stdout AND stderr captured to " + termBaseDir + "/<nn>-<short-slug>.txt (number them 01, 02, ...), appending the exit code as the final line. Pattern: <cmd> > " + termBaseDir + "/01-<slug>.txt 2>&1; echo \"exit=$?\" >> " + termBaseDir + "/01-<slug>.txt\n" +
|
|
1148
|
+
"5. READ every transcript file you wrote — an unread transcript is not evidence.\n" +
|
|
1149
|
+
"6. Log the baseline note — run in shell and return the stdout verbatim:\n" + crewCmd("log-event", { task_id: taskId, type: "note", identity: step.identity, message: "baseline: captured (terminal transcripts: <comma-separated filenames>)" }) + "\n" +
|
|
1150
|
+
" (replace <comma-separated filenames> with the real filenames). If the CLI cannot run from the pre-change tree (will not start, missing dependency), log instead: baseline: none (terminal targets not runnable: <reason>) — never fabricate a transcript.\n" +
|
|
1151
|
+
"7. Record the phase — run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
1152
|
+
task_id: taskId,
|
|
1153
|
+
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "completed", notes: "Terminal baseline captured by the QA agent (transcripts archived)" },
|
|
1154
|
+
event: { task_id: taskId, type: "completed", identity: step.identity, message: "Capture completed by " + step.identity }
|
|
1155
|
+
}) + "\n" +
|
|
1156
|
+
"Report back in plain prose: which commands you ran and what the pre-change baseline looks like.",
|
|
1157
|
+
{ key: "terminal-baseline-" + taskId + bounceSuffix, label: "Capturing terminal baseline" }
|
|
1158
|
+
);
|
|
1159
|
+
i++;
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1100
1162
|
var capStatus = await baselineStatus();
|
|
1101
1163
|
// Stale-decision guard: a "baseline: none (visual protocol unavailable)"
|
|
1102
1164
|
// note is only durable while the protocol is unavailable. When
|
|
@@ -1189,9 +1251,9 @@ while (i < STEPS.length) {
|
|
|
1189
1251
|
var mapBaselineRefs = "";
|
|
1190
1252
|
var mapBaselineNone = false;
|
|
1191
1253
|
if (step.name === "Map") {
|
|
1192
|
-
// Must match Capture's run condition (experiential +
|
|
1254
|
+
// Must match Capture's run condition (experiential + classified surface):
|
|
1193
1255
|
// when Capture skips, no baseline notes exist, so the gate must not apply.
|
|
1194
|
-
if ((await resolveExperiential()) === "yes" &&
|
|
1256
|
+
if ((await resolveExperiential()) === "yes" && SURFACE_CLASSIFIED) {
|
|
1195
1257
|
var gateStatus = await baselineStatus();
|
|
1196
1258
|
if (!gateStatus.baseline_found) {
|
|
1197
1259
|
log("Map gate: no baseline evidence for experiential task " + taskId + " — bouncing to Capture");
|
|
@@ -1215,13 +1277,17 @@ while (i < STEPS.length) {
|
|
|
1215
1277
|
}
|
|
1216
1278
|
}
|
|
1217
1279
|
|
|
1218
|
-
//
|
|
1219
|
-
// to Hazel's own experiential QA instructions
|
|
1220
|
-
//
|
|
1280
|
+
// Experiential verdict routing (2026-09-15): experiential tasks on a
|
|
1281
|
+
// classified surface route to Hazel's own experiential QA instructions
|
|
1282
|
+
// below — she drives the surface herself (see-act loop for artifact,
|
|
1283
|
+
// terminal loop for terminal) and owns the verdict (verdict.json +
|
|
1221
1284
|
// verdicts.jsonl). No parent verdict gate remains.
|
|
1222
|
-
var
|
|
1285
|
+
var qaArtifact = false;
|
|
1286
|
+
var qaTerminal = false;
|
|
1223
1287
|
if (step.name === "QA") {
|
|
1224
|
-
|
|
1288
|
+
var qaExp = (await resolveExperiential()) === "yes";
|
|
1289
|
+
qaArtifact = qaExp && SURFACE_ARTIFACT;
|
|
1290
|
+
qaTerminal = qaExp && SURFACE_TERMINAL;
|
|
1225
1291
|
}
|
|
1226
1292
|
|
|
1227
1293
|
// Reproduce-layer routing (2026-09-16): Sage's Triage classifies the bug's
|
|
@@ -1239,11 +1305,22 @@ while (i < STEPS.length) {
|
|
|
1239
1305
|
var instructions = "";
|
|
1240
1306
|
|
|
1241
1307
|
if (step.name === "Triage") {
|
|
1242
|
-
instructions = "Validate the task against the project's repo at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. If you run git commands, cd " + REPO_PATH + " first.\nCheck clarity, note dependencies, confirm the bugfix workflow assignment.\nIf the task needs decomposition, note that in your assessment.\nReport back in plain prose — what you found.\nEXPERIENTIAL FLAG: does this task change anything
|
|
1308
|
+
instructions = "Validate the task against the project's repo at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. If you run git commands, cd " + REPO_PATH + " first.\nCheck clarity, note dependencies, confirm the bugfix workflow assignment.\nIf the task needs decomposition, note that in your assessment.\nReport back in plain prose — what you found.\nEXPERIENTIAL FLAG: does this task change anything a user can directly observe in the project's user-facing surface? " + SURFACE_TRIAGE_DESC + " For an artifact surface that means rendered and visible — pages, components, styles, layout, copy, visual states. For a terminal surface it means the CLI experience — command output, help text, flags, error messages, defaults. The shared UX bar is " + (UX_DOCTRINE_PATH ? UX_DOCTRINE_PATH + " — flag experiential when the task touches anything it covers." : "not classified for this project — flag experiential when the task touches anything user-observable in the surface described above.") + " If yes it is experiential and gets baseline evidence (plus an experiential verdict where the workflow has a QA phase). For terminal-surface tasks also declare the CLI surface to exercise: end your report with a line `terminal_targets: <comma-separated CLI commands/flags>` (machine-read; optional — falls back to the task description). End your report with exactly one line on its own, lowercase, unrephrased: experiential: yes — or experiential: no. This line is machine-read.\nLAYER FLAG: classify the bug's layer — where the reported misbehavior lives. artifact: user-facing behavior of the project's rendered artifact (something a user sees or clicks). engine: the crew's own machinery — workflows, lib scripts, shell scripts, tests, scheduler. docs: a documentation gap or error. End your report with exactly one line on its own, lowercase, unrephrased: layer: artifact — or layer: engine — or layer: docs. This line is machine-read. If the bug genuinely spans layers, pick the layer where reproduction must happen and note the ambiguity in prose.";
|
|
1243
1309
|
|
|
1244
1310
|
} else if (step.name === "Reproduce") {
|
|
1245
1311
|
if (reproLayer !== "engine" && reproLayer !== "docs") {
|
|
1246
|
-
|
|
1312
|
+
if (SURFACE_TERMINAL) {
|
|
1313
|
+
instructions = "Reproduce the bug from a user's perspective — by USING the CLI, not by reading source code. You are CODE-BLIND — do NOT read source code.\n" +
|
|
1314
|
+
"This project's user-facing surface is a terminal: a command-line interface. Read the shared UX bar FIRST: " + UX_DOCTRINE_PATH + " — it is what correct CLI behavior looks like.\n" +
|
|
1315
|
+
"a. Work in the project checkout at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. Drive the CLI the way a user would: start with --help to discover the surface, then the commands the bug report implicates.\n" +
|
|
1316
|
+
"b. Bounded CLI loop, at most 8 commands: drive the CLI to TRIGGER the reported bug. For EVERY command, save a transcript file under " + crewHome + "/task-evidence/" + taskId + "/repro/ named 01-<slug>.txt, 02-<slug>.txt, ... — each transcript holds the exact command line, its stdout, its stderr, and its exit code. Never invent output: a transcript you did not run is not evidence.\n" +
|
|
1317
|
+
"c. After EVERY command, append it to the OODA report: node " + crewHome + "/current/lib/append-ooda-step.js --log " + crewHome + "/task-evidence/" + taskId + "/repro/ooda-log.jsonl --attempt \"1\" --step <N> --action terminal --exit <exit-code> --transcript <the transcript file you saved> --observation \"<1-2 sentences: what the command showed and what you concluded>\" — the script requires --transcript for terminal actions and rejects --screenshot (there is nothing to see). Steps are strictly monotonic: your first step is --step 1, then 2, 3, ... If you run the loop again (a new idea, after a restart), that run is --attempt \"2\": attempts never overwrite, they accumulate. Your observation is the payload — say what the output showed and what it means, not just what you ran. A command you ran but did not log is invisible to everyone after you. If the CLI will not run at all (missing runtime, broken install), log ONE step with --exit 3, --observation \"NOT POSSIBLE: <reason>\" and a transcript holding the failed invocation, then fall back to the data-level investigation at the end.\n" +
|
|
1318
|
+
"Data-level fallback (only if the CLI loop above is NOT POSSIBLE): run in shell and read the stdout JSON:\n" + crewCmd("get-state", {}) + "\n" +
|
|
1319
|
+
"This returns current sessions, events, and tasks — capture concrete evidence from the data you retrieve.\n" +
|
|
1320
|
+
"Report your reproduction steps and evidence as plain prose.\n" +
|
|
1321
|
+
"End your report with exactly one line: VERDICT: PASS if you reproduced the reported bug (your transcripts show the reported misbehavior), VERDICT: FAIL if you could not. --expected names the bug as reported (its CLI manifestation); --actual names what your commands actually showed. Checks you could not run are evidence gaps, not silent drops: name every one in --missing. First ensure the OODA log exists even if you logged zero steps (touch " + crewHome + "/task-evidence/" + taskId + "/repro/ooda-log.jsonl — an empty log is honest, an absent one is a broken report). Also write the same verdict machine-readably: node " + crewHome + "/current/lib/write-ooda-verdict.js --dir " + crewHome + "/task-evidence/" + taskId + "/repro/ --attempt \"1\" --verdict <PASS|FAIL|NOT_POSSIBLE> --summary \"<one line>\" --expected \"<the bug as reported — its CLI manifestation>\" --actual \"<what your commands actually showed>\" --missing '[\"honest evidence gap, if any\"]' [--reason \"<why it failed — REQUIRED and non-empty when verdict is FAIL or NOT_POSSIBLE; the script rejects a reason-less negative verdict with exit 2>\"] — this writes verdict.json (the latest verdict) and appends to verdicts.jsonl (the append-only ledger: every attempt's verdict is preserved, never overwritten). A FAIL or NOT_POSSIBLE verdict without a machine-readable --reason cannot be written — state the reason.";
|
|
1322
|
+
} else {
|
|
1323
|
+
instructions = "Reproduce the bug from a user's perspective — by USING the artifact, not by reading data. You are CODE-BLIND — do NOT read source code.\n" +
|
|
1247
1324
|
"You have a see-act driver: " + crewHome + "/current/lib/see-act.js (a node script; one browser action per invocation; it prints one JSON line to stdout). It launches its own Chromium through a self-contained loopback proxy — the ONLY url you may give it is the local artifact server you start below. Never point it at any other URL.\n" +
|
|
1248
1325
|
"Actions: aria | shot [--out <png>] [--full] | click [--out <png>] --selector <css> | scroll [--out <png>] --y <pixels|bottom> | type [--out <png>] --selector <css> --text <text>. Add --viewport mobile for a 390x844 frame. The JSON reports console_errors — treat any as a defect signal. Exit code 3 with not_possible set (a \"NOT POSSIBLE: <reason>\" string) means this environment cannot drive a browser: report NOT POSSIBLE: <reason> and fall back to the data-level investigation at the end.\n" +
|
|
1249
1326
|
"a. Verify the built artifact exists: test -d ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/client/dist && test -f ~/workspace/ts-spaces/" + PUBLISH_SLUG + "/server/dist/actions.js — if either is missing, report NOT POSSIBLE: built artifact not present at ~/workspace/ts-spaces/" + PUBLISH_SLUG + " and fall back to the data-level investigation.\n" +
|
|
@@ -1259,6 +1336,7 @@ while (i < STEPS.length) {
|
|
|
1259
1336
|
"This returns current sessions, events, and tasks — capture concrete evidence from the data you retrieve.\n" +
|
|
1260
1337
|
"Report your reproduction steps and evidence as plain prose.\n" +
|
|
1261
1338
|
"End your report with exactly one line: VERDICT: PASS if you reproduced the reported bug (your frames show the reported misbehavior), VERDICT: FAIL if you could not. --expected names the bug as reported (its visible manifestation); --actual names what your frames actually showed. Checks you could not run are evidence gaps, not silent drops: name every one in --missing. First ensure the OODA log exists even if you logged zero steps (touch " + crewHome + "/task-evidence/" + taskId + "/repro/ooda-log.jsonl — an empty log is honest, an absent one is a broken report). Also write the same verdict machine-readably: node " + crewHome + "/current/lib/write-ooda-verdict.js --dir " + crewHome + "/task-evidence/" + taskId + "/repro/ --attempt \"1\" --verdict <PASS|FAIL|NOT_POSSIBLE> --summary \"<one line>\" --expected \"<the bug as reported — its visible manifestation>\" --actual \"<what your frames actually showed>\" --missing '[\"honest evidence gap, if any\"]' [--reason \"<why it failed — REQUIRED and non-empty when verdict is FAIL or NOT_POSSIBLE; the script rejects a reason-less negative verdict with exit 2>\"] — this writes verdict.json (the latest verdict) and appends to verdicts.jsonl (the append-only ledger: every attempt's verdict is preserved, never overwritten). A FAIL or NOT_POSSIBLE verdict without a machine-readable --reason cannot be written — state the reason.";
|
|
1339
|
+
}
|
|
1262
1340
|
} else if (reproLayer === "engine") {
|
|
1263
1341
|
instructions = "REPRODUCE AT THE BUG'S LAYER. Triage classified this bug as layer: engine — it lives in the crew's own machinery (workflows, lib scripts, shell scripts, tests, scheduler), not in the rendered artifact. Reproduce it deterministically with shell commands in the repo checkout at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. You are NOT code-blind here: reading source to find the failing mechanism is expected.\n" +
|
|
1264
1342
|
"Do NOT start an artifact server. Do NOT invoke see-act.js or any browser loop — the experiential loop is for artifact-layer bugs only, and driving it here fails the phase loudly. If you cannot reproduce without a browser, say so and FAIL with a reason naming the layer you tried.\n" +
|
|
@@ -1278,11 +1356,15 @@ while (i < STEPS.length) {
|
|
|
1278
1356
|
mapGatePara = "\nBASELINE GATE (experiential task): " +
|
|
1279
1357
|
(mapBaselineNone
|
|
1280
1358
|
? "no baseline was capturable (baseline: none recorded) — write the spec without baseline comparison and note it."
|
|
1281
|
-
: "pre-change baseline
|
|
1359
|
+
: "pre-change baseline evidence: " + mapBaselineRefs + " — consult it when writing the spec.") +
|
|
1282
1360
|
" If the baseline evidence is missing with no baseline:none recorded, do not write the spec — report 'baseline evidence missing — Map gate bounce required' and stop.\n" +
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1361
|
+
(SURFACE_TERMINAL
|
|
1362
|
+
? "Declare the CLI surface to exercise: end your report with a line `terminal_targets: <comma-separated CLI commands/flags>` (machine-read; optional — falls back to the task description)."
|
|
1363
|
+
: "Declare capture targets for the post-change visual capture: end your report with a line `capture_targets: <comma-separated views/controls this change affects>` (optional; falls back to the task description)."); }
|
|
1364
|
+
instructions = "Update the task with a solution-oriented spec. Research options, pick the shortest path.\n" +
|
|
1365
|
+
(SURFACE_TERMINAL ? "TERMINAL SPEC: this project's surface is a CLI. Specify the exact commands, their expected stdout/stderr, exit codes, --help text, and error messages — Hazel judges the build against this spec and the shared bar at " + UX_DOCTRINE_PATH + ".\n" : "") +
|
|
1366
|
+
(SURFACE_ARTIFACT ? "ARTIFACT SPEC: this project's surface is a rendered artifact. Specify the exact screens, flows, and visual states the change affects — Hazel judges the build against this spec and the shared bar at " + UX_DOCTRINE_PATH + ".\n" : "") +
|
|
1367
|
+
"The builder will edit source files in a git worktree.\nProject: " + PROJECT_DESC + "\nWrite it clearly enough that the builder does not need to ask questions.\nReport back in plain prose — what you specified." + mapGatePara;
|
|
1286
1368
|
|
|
1287
1369
|
} else if (step.name === "Build") {
|
|
1288
1370
|
instructions = "STEP 1: Prepare your worktree.\n" +
|
|
@@ -1297,6 +1379,8 @@ while (i < STEPS.length) {
|
|
|
1297
1379
|
"This is the project source: " + PROJECT_DESC + "\n" +
|
|
1298
1380
|
"Edit the TypeScript source files directly. Do NOT use artifact_edit — that happens in the Publish phase.\n" +
|
|
1299
1381
|
"Do not add unrequested features. Build exactly what the spec calls for.\n" +
|
|
1382
|
+
(SURFACE_TERMINAL ? "TERMINAL UX: build to the shared bar at " + UX_DOCTRINE_PATH + " — --help text, error messages, and exit codes are user-facing and ship in this commit.\n" : "") +
|
|
1383
|
+
(SURFACE_ARTIFACT ? "ARTIFACT UX: build to the shared bar at " + UX_DOCTRINE_PATH + " — the rendered result is what the user sees; it ships in this commit.\n" : "") +
|
|
1300
1384
|
"PUBLIC DOCS: If your change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), update the public docs in the same commit — API.md for API changes. Documentation and implementation ship together.\n\n" +
|
|
1301
1385
|
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry. Versions are assigned at PUBLISH time — never in your branch. Do NOT touch the `version` field in package.json (or package-lock). Instead, end your report with exactly these two lines:\n" +
|
|
1302
1386
|
"release: yes|no — 'yes' if this change warrants a published release (anything a consumer can observe: workflow behavior, phase lists, identities, published docs, API); 'no' if internal-only.\n" +
|
|
@@ -1341,6 +1425,8 @@ while (i < STEPS.length) {
|
|
|
1341
1425
|
"You can also read specific files in the worktree at:\n" +
|
|
1342
1426
|
WORKTREE_HINT + "/\n\n" +
|
|
1343
1427
|
"Check quality, correctness, and spec compliance.\n" +
|
|
1428
|
+
(SURFACE_TERMINAL ? "TERMINAL UX REVIEW: judge the CLI surface against " + UX_DOCTRINE_PATH + " — help accuracy, error quality, exit codes, output clarity. Reject when the bar is not met.\n" : "") +
|
|
1429
|
+
(SURFACE_ARTIFACT ? "ARTIFACT UX REVIEW: judge the rendered surface against " + UX_DOCTRINE_PATH + " — alignment, spacing, hierarchy, composition, balance, finish, correctness. Reject when the bar is not met.\n" : "") +
|
|
1344
1430
|
"Check that public-affecting changes have matching public doc updates (API.md or the published API contract). If the docs are missing or inaccurate, report what is stale, then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1345
1431
|
"If the branch has no commits ahead of main (inspect shows an empty commit log), approve ONLY if the Build summary declares `repo_diff: none` with (a) a plausible runtime-state deliverable (e.g. a cron created via the cron tool), or (b) an already-merged declaration `repo_diff: none (already-merged: <sha>)` AND the mechanical fact below confirms the sha verified. MECHANICAL FACT (computed by the workflow, never by the builder): already_merged sha = " + (alreadyMergedSha ? alreadyMergedSha + " (verified ancestor of main: YES)" : "none declared") + ". Otherwise report 'no commits ahead of main and no valid repo_diff: none declaration — the builder likely forgot to commit', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1346
1432
|
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry, and versions are assigned at publish time — never in branches. Two checks:\n" +
|
|
@@ -2255,10 +2341,11 @@ while (i < STEPS.length) {
|
|
|
2255
2341
|
npmPublishCheck +
|
|
2256
2342
|
"Report your test results as plain prose.\n" +
|
|
2257
2343
|
"End your report with exactly one line: VERDICT: PASS if testing passes, VERDICT: FAIL if it fails.";
|
|
2258
|
-
//
|
|
2344
|
+
// qaArtifact: Hazel runs the experiential see-act loop herself (STEP 1
|
|
2259
2345
|
// below) and owns the visual verdict — no parent capture protocol.
|
|
2260
|
-
if (
|
|
2261
|
-
instructions = "
|
|
2346
|
+
if (qaArtifact) {
|
|
2347
|
+
instructions = "Read the shared UX bar FIRST: " + UX_DOCTRINE_PATH + " — it is the bar the whole crew builds to, and your verdict judges against it point by point.\n\n" +
|
|
2348
|
+
"You are code-blind QA. You NEVER read source files. Public docs are not source — read them as a user would.\n" +
|
|
2262
2349
|
"STEP 1: Experiential visual inspection — drive the fixed artifact as a user would, one browser step at a time, and verify the reported bug is actually fixed.\n" +
|
|
2263
2350
|
"You have a see-act driver: " + crewHome + "/current/lib/see-act.js (a node script; one browser action per invocation; it prints one JSON line to stdout). It launches its own Chromium through a self-contained loopback proxy — the ONLY url you may give it is the local artifact server you start below. Never point it at any other URL.\n" +
|
|
2264
2351
|
"Actions: aria | shot [--out <png>] [--full] | click [--out <png>] --selector <css> | scroll [--out <png>] --y <pixels|bottom> | type [--out <png>] --selector <css> --text <text>. Add --viewport mobile for a 390x844 frame. The JSON reports console_errors — treat any as a defect signal. Exit code 3 with not_possible set (a \"NOT POSSIBLE: <reason>\" string) means this environment cannot drive a browser: report NOT POSSIBLE: <reason> and continue with the mechanical checks — your VERDICT then covers mechanical checks only.\n" +
|
|
@@ -2282,6 +2369,39 @@ while (i < STEPS.length) {
|
|
|
2282
2369
|
"BASELINE SANITY: in the event history you fetched, the task's note events must contain a message starting with `baseline: captured` or `baseline: none`. If no message starts with either prefix, report 'baseline evidence missing at QA — the Map gate was bypassed', then end your report with exactly this line: VERDICT: FAIL.\n\n" +
|
|
2283
2370
|
"Report your test results as plain prose.\n" +
|
|
2284
2371
|
"End your report with exactly one line: VERDICT: PASS if testing passes, VERDICT: FAIL if it fails on the visual or the mechanical checks. Checks you could not run are evidence gaps, not silent drops: name every one in --missing — unknown is neither PASS nor FAIL. First ensure the OODA log exists even if you logged zero steps (touch " + crewHome + "/task-evidence/" + taskId + "/postchange/ooda-log.jsonl — an empty log is honest, an absent one is a broken report). Also write the same verdict machine-readably: node " + crewHome + "/current/lib/write-ooda-verdict.js --dir " + crewHome + "/task-evidence/" + taskId + "/postchange/ --attempt \"1\" --verdict <PASS|FAIL|NOT_POSSIBLE> --summary \"<one line>\" --expected \"<the reported bug, fixed>\" --actual \"<what you observed>\" --missing '[\"honest evidence gap, if any\"]' [--reason \"<why it failed — REQUIRED and non-empty when verdict is FAIL or NOT_POSSIBLE; the script rejects a reason-less negative verdict with exit 2>\"] — this writes verdict.json (the latest verdict) and appends to verdicts.jsonl (the append-only ledger: every attempt's verdict is preserved, never overwritten). A FAIL verdict must carry a machine-readable reason: the workflow closeout cross-checks verdict.json against your prose VERDICT line, and an unreasoned or contradictory verdict fails the phase (never routes to rework).";
|
|
2372
|
+
} else if (qaTerminal) {
|
|
2373
|
+
// Terminal-surface experiential QA: Hazel drives the CLI herself —
|
|
2374
|
+
// the terminal counterpart to the see-act loop above. Same OODA
|
|
2375
|
+
// discipline (append-ooda-step with --action terminal and a transcript
|
|
2376
|
+
// per step; verdict via write-ooda-verdict), judged against the
|
|
2377
|
+
// shared bar resolved via UX_DOCTRINE_PATH (the terminal doctrine page here). Transcripts are delivered;
|
|
2378
|
+
// screenshots are never invented for terminal work.
|
|
2379
|
+
var btermEvidence = crewHome + "/task-evidence/" + taskId + "/postchange";
|
|
2380
|
+
var btermTargetsLine = terminalTargets || "not declared — derive from --help and the task description";
|
|
2381
|
+
instructions = "You are code-blind QA. You NEVER read source files.\n" +
|
|
2382
|
+
"Public docs (API.md, README) are NOT source code — read them freely, exactly as a user would.\n" +
|
|
2383
|
+
"Read the shared UX bar FIRST: " + UX_DOCTRINE_PATH + " — it is the bar the whole crew builds to, and your verdict judges against it point by point.\n\n" +
|
|
2384
|
+
"STEP 1: Experiential terminal inspection — drive the fixed CLI as a user would, one command at a time, and verify the reported bug is actually fixed.\n" +
|
|
2385
|
+
"a. The CLI under test lives in " + REPO_PATH + " (the merged change is on main there). Start like a new user: run --help. You may RUN the CLI; you may never READ its source.\n" +
|
|
2386
|
+
"b. Terminal targets for this task: " + btermTargetsLine + ".\n" +
|
|
2387
|
+
"c. Bounded terminal loop — at most 8 commands. For each target: run it RIGHT (the happy path — the reported bug's scenario first), then run it WRONG on purpose (bad flags, missing args, nonexistent files, empty input, contradictory flags). Error quality is half the grade: every failure must exit non-zero, say what went wrong in plain language, and tell the user the fix. A raw stack trace shown to a user is a defect — file it as one.\n" +
|
|
2388
|
+
"d. Evidence: capture EVERY invocation as a transcript. Run: mkdir -p " + btermEvidence + "\n" +
|
|
2389
|
+
" For each command: <cmd> > " + btermEvidence + "/<nn>-<short-slug>.txt 2>&1; echo \"exit=$?\" >> " + btermEvidence + "/<nn>-<short-slug>.txt (number them 01, 02, ...). Then READ the transcript before judging it — an unread transcript is not evidence.\n" +
|
|
2390
|
+
"e. Log each step to the OODA log — run in shell, one command per step:\n" +
|
|
2391
|
+
" node " + crewHome + "/current/lib/append-ooda-step.js --log " + btermEvidence + "/ooda-log.jsonl --attempt \"1\" --step <N> --action terminal --exit <code> --transcript " + btermEvidence + "/<nn>-<short-slug>.txt --args '{\"cmd\":\"<the exact command>\"}' --observation \"<1-2 sentences: what the output said and what you concluded>\"\n" +
|
|
2392
|
+
" Steps are strictly monotonic within an attempt (1, 2, 3, ...). A rerun is a NEW attempt (\"2\", \"3\", ...) — never overwrite attempt 1. If the CLI will not run at all, log the step with --exit 3 and NOT POSSIBLE in the observation — never fabricate a transcript.\n" +
|
|
2393
|
+
"f. Compare against the pre-change baseline transcripts in " + crewHome + "/task-evidence/" + taskId + "/baseline/ — every finding cites its baseline and post-change transcripts by step number.\n" +
|
|
2394
|
+
"Then continue with the mechanical checks below. Your VERDICT covers both the experiential and the mechanical checks.\n\n" +
|
|
2395
|
+
"STEP 2: Verify data integrity via the crew API.\n" +
|
|
2396
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("get-state", { events_limit: 1 }) + "\n" +
|
|
2397
|
+
"Use the returned tasks, sessions, and events to check the task's data-level effects.\n" +
|
|
2398
|
+
"DOCS GATE: If the change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), verify the public docs describe it. If public docs are missing or stale for a public-affecting change, report 'public docs missing/stale for [the change]', then end your report with exactly this line: VERDICT: FAIL. QA always fails when public-affecting changes lack public docs. Guide/tutorial gaps are lower priority — file a follow-up task for those instead of failing.\n\n" +
|
|
2399
|
+
"STEP 3: File follow-up tasks for any related issues you discover.\n" +
|
|
2400
|
+
"For each issue, run in shell:\n" +
|
|
2401
|
+
"node " + CREW_API + " --crew-home " + crewHome + " create-task --json '{\"title\": \"<issue title>\", \"description\": \"<issue details>\", \"project\": \"" + LAUNCH_PROJECT_ID + "\", \"workflow\": \"bugfix\", \"filed_by\": \"hazel\"}'\n" +
|
|
2402
|
+
"(replace <issue title> and <issue details> with the real values).\n\n" +
|
|
2403
|
+
"BASELINE SANITY: in the event history you fetched, the task's note events must contain a message starting with `baseline: captured` or `baseline: none`. If no message starts with either prefix, report 'baseline evidence missing at QA — the Map gate was bypassed', then end your report with exactly this line: VERDICT: FAIL. When the baseline is terminal transcripts, confirm every terminal target you judged has a baseline transcript: a target with no pre-change transcript is an evidence gap — name it in --missing, never invent the baseline.\n\n" +
|
|
2404
|
+
"Report your test results as plain prose. Checks you could not run are evidence gaps, not silent drops: name every one in --missing — unknown is neither PASS nor FAIL. End your report with exactly one line: VERDICT: PASS if testing passes, VERDICT: FAIL if it fails on the terminal or the mechanical checks. First ensure the OODA log exists even if you logged zero steps (touch " + btermEvidence + "/ooda-log.jsonl — an empty log is honest, an absent one is a broken report). Also write the same verdict machine-readably: node " + crewHome + "/current/lib/write-ooda-verdict.js --dir " + btermEvidence + " --attempt \"1\" --verdict <PASS|FAIL|NOT_POSSIBLE> --summary \"<one line>\" --expected \"<the reported bug, fixed>\" --actual \"<what you observed>\" --missing '[\"honest evidence gap, if any\"]' [--reason \"<why it failed — REQUIRED and non-empty when verdict is FAIL or NOT_POSSIBLE; the script rejects a reason-less negative verdict with exit 2>\"] — this writes verdict.json (the latest verdict) and appends to verdicts.jsonl (the append-only ledger: every attempt's verdict is preserved, never overwritten). A FAIL verdict must carry a machine-readable reason: the workflow closeout cross-checks verdict.json against your prose VERDICT line, and an unreasoned or contradictory verdict fails the phase (never routes to rework).";
|
|
2285
2405
|
}
|
|
2286
2406
|
if (PUBLISH_TYPE === "artifact") {
|
|
2287
2407
|
instructions = "PROVENANCE CHECK (this project publishes to a dashboard artifact).\n" +
|
|
@@ -2490,8 +2610,8 @@ while (i < STEPS.length) {
|
|
|
2490
2610
|
// gate, a bare VERDICT: FAIL with an all-positive report (canary
|
|
2491
2611
|
// 2026-09-15, task 1d692d91) rebuilt nothing and parked at Publish on an
|
|
2492
2612
|
// unobserved artifact build. The gate applies only to the experiential QA
|
|
2493
|
-
// path (
|
|
2494
|
-
if (step.name === "QA" &&
|
|
2613
|
+
// path (qaArtifact / qaTerminal), the paths that write verdict.json.
|
|
2614
|
+
if (step.name === "QA" && (qaArtifact || qaTerminal) && verdictPassed !== null) {
|
|
2495
2615
|
var qaVerdictDir = crewHome + "/task-evidence/" + taskId + "/postchange";
|
|
2496
2616
|
var qaVerdictExpect = verdictPassed ? "PASS" : "FAIL";
|
|
2497
2617
|
var qaVerdictOut = "";
|
|
@@ -2541,23 +2661,28 @@ while (i < STEPS.length) {
|
|
|
2541
2661
|
}
|
|
2542
2662
|
log("QA verdict.json closeout gate passed: verdict.json agrees with prose VERDICT: " + qaVerdictExpect);
|
|
2543
2663
|
|
|
2544
|
-
// QA
|
|
2545
|
-
// verdict.json is honest about missing
|
|
2546
|
-
// treated a PASS as terminal done even when the
|
|
2547
|
-
//
|
|
2548
|
-
//
|
|
2549
|
-
//
|
|
2550
|
-
// terminal
|
|
2551
|
-
//
|
|
2552
|
-
//
|
|
2553
|
-
//
|
|
2554
|
-
//
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2664
|
+
// QA experiential-loop guard (clean-room defect 2026-09-16): Hazel's
|
|
2665
|
+
// verdict.json is honest about missing experiential evidence, but the
|
|
2666
|
+
// closeout treated a PASS as terminal done even when the experiential
|
|
2667
|
+
// loop never ran. A PASS verdict with missing experiential evidence must
|
|
2668
|
+
// never be terminal: the task parks fail-closed with
|
|
2669
|
+
// unattributable_reason=qa-visual-loop-unavailable (artifact surface)
|
|
2670
|
+
// or qa-terminal-loop-unavailable (terminal surface) instead of
|
|
2671
|
+
// transitioning to done. Code, not prompt text: the loop-availability
|
|
2672
|
+
// flags come from lib/read-ooda-verdict.js (the OODA log's NOT POSSIBLE
|
|
2673
|
+
// steps and the verdict's missing_evidence tool-unavailability notes),
|
|
2674
|
+
// already parsed into qaVerdictGate above.
|
|
2675
|
+
var qaLoopSurface = SURFACE_TERMINAL ? "terminal" : "visual";
|
|
2676
|
+
var qaLoopReason = SURFACE_TERMINAL ? "qa-terminal-loop-unavailable" : "qa-visual-loop-unavailable";
|
|
2677
|
+
var qaLoopUnavailable = SURFACE_TERMINAL ? qaVerdictGate.terminal_loop_unavailable : qaVerdictGate.visual_loop_unavailable;
|
|
2678
|
+
if (verdictPassed === true && qaLoopUnavailable === true) {
|
|
2679
|
+
log("QA " + qaLoopSurface + " loop unavailable — parking fail-closed (unattributable_reason=" + qaLoopReason + "), never done");
|
|
2680
|
+
return await parkTask("QA " + qaLoopSurface + " loop unavailable (unattributable_reason=" + qaLoopReason + "): " +
|
|
2681
|
+
(SURFACE_TERMINAL
|
|
2682
|
+
? "the terminal loop could not run — the CLI would not execute, and verdict.json records missing terminal evidence. A PASS without experiential evidence is never terminal. Human attention needed: check the project's runtime dependencies at " + REPO_PATH + ", then re-queue QA."
|
|
2683
|
+
: "the see-act browser loop could not run — verdict.json records missing visual evidence. A PASS without experiential evidence is never terminal. Human attention needed: repair the crew home's dependency symlink ($CREW_HOME/node_modules) or the npm install, then re-queue QA."));
|
|
2559
2684
|
}
|
|
2560
|
-
log("QA
|
|
2685
|
+
log("QA " + qaLoopSurface + "-loop guard passed: experiential evidence present");
|
|
2561
2686
|
}
|
|
2562
2687
|
|
|
2563
2688
|
// Reproduce wrong-layer guard (2026-09-16): for engine/docs tasks the
|
|
@@ -2920,6 +3045,8 @@ while (i < STEPS.length) {
|
|
|
2920
3045
|
// Capture Sage's experiential flag (machine-read marker line).
|
|
2921
3046
|
if (step.name === "Triage" && passed) {
|
|
2922
3047
|
isExperiential = extractExperiential(workerText);
|
|
3048
|
+
var ttm = /^terminal_targets:\s*(.+)/im.exec(workerText);
|
|
3049
|
+
terminalTargets = ttm ? ttm[1].trim().slice(0, 300) : "";
|
|
2923
3050
|
// Capture Sage's bug-layer flag (machine-read marker line): drives the
|
|
2924
3051
|
// Reproduce strategy dispatch. Unknown (null) degrades to "artifact" at
|
|
2925
3052
|
// resolveLayer — never parks on a garbled line.
|
package/workflows/chore.js
CHANGED
|
@@ -172,6 +172,31 @@ const WORKTREE_HINT = REPO_PATH + "/.worktrees/" + taskId;
|
|
|
172
172
|
const WORKTREE_PRESERVED_HINT = ".worktrees/" + taskId;
|
|
173
173
|
|
|
174
174
|
const PUBLISH_TYPE = projectConfig.deploy_type || "";
|
|
175
|
+
// User-facing surface for experiential routing: 'artifact' (a rendered
|
|
176
|
+
// web UI) | 'terminal' (a CLI) | null (unclassified). environment_type is
|
|
177
|
+
// the canonical UX-surface axis; deploy_type names the deployment target,
|
|
178
|
+
// but deploy_type === "artifact" remains a legacy artifact-surface signal so
|
|
179
|
+
// pre-field projects keep today's behavior (the migration does not backfill
|
|
180
|
+
// the column).
|
|
181
|
+
// Chore has no QA phase: Capture stays artifact-only (terminal baselines
|
|
182
|
+
// would be unused evidence), but the crew still specs, builds, and reviews
|
|
183
|
+
// terminal-surface changes against the shared bar.
|
|
184
|
+
const ENV_TYPE = projectConfig.environment_type || null;
|
|
185
|
+
const SURFACE_ARTIFACT = (PUBLISH_TYPE === "artifact" || ENV_TYPE === "artifact");
|
|
186
|
+
const SURFACE_TERMINAL = (!SURFACE_ARTIFACT && ENV_TYPE === "terminal");
|
|
187
|
+
const SURFACE_TRIAGE_DESC = SURFACE_ARTIFACT
|
|
188
|
+
? "This project's user-facing surface is artifact: a rendered web UI."
|
|
189
|
+
: SURFACE_TERMINAL
|
|
190
|
+
? "This project's user-facing surface is terminal: a command-line interface."
|
|
191
|
+
: "This project's user-facing surface is unclassified (environment_type not set): judge by what a user would directly observe.";
|
|
192
|
+
// UX doctrine page: the shared UX bar for this run's surface, resolved
|
|
193
|
+
// mechanically — every phase prompt reads UX_DOCTRINE_PATH, never a
|
|
194
|
+
// hardcoded filename. Canonical map: lib/ux-doctrine.js (mirrored here as a
|
|
195
|
+
// one-liner because the workflow runtime's relative-import support is
|
|
196
|
+
// unverified; tests pin the mirror). Null on unclassified surfaces: no
|
|
197
|
+
// shared page, and prompts say so instead of naming the wrong one.
|
|
198
|
+
const UX_DOCTRINE_PAGE = SURFACE_TERMINAL ? "terminal-ux.md" : (SURFACE_ARTIFACT ? "artifact-ux.md" : null);
|
|
199
|
+
const UX_DOCTRINE_PATH = UX_DOCTRINE_PAGE ? crewHome + "/current/docs/" + UX_DOCTRINE_PAGE : null;
|
|
175
200
|
const PUBLISH_SLUG = projectConfig.deploy_slug || "";
|
|
176
201
|
const PROJECT_DESC = projectConfig.description || "React + TypeScript web dashboard (client/src/, server/src/, drizzle/)";
|
|
177
202
|
const RELEASE_SCRIPT = crewHome + "/crew-release.sh";
|
|
@@ -585,7 +610,7 @@ function extractMarkerLines(workerText) {
|
|
|
585
610
|
var markers = [];
|
|
586
611
|
for (var i = 0; i < lines.length; i++) {
|
|
587
612
|
var line = lines[i].trim();
|
|
588
|
-
if (/^(repo_diff:|release:|version_bump:|VERDICT:|TARGET_VERSION=|published:|experiential:|layer:|capture_targets:|worktree:)/i.test(line)) {
|
|
613
|
+
if (/^(repo_diff:|release:|version_bump:|VERDICT:|TARGET_VERSION=|published:|experiential:|layer:|capture_targets:|terminal_targets:|worktree:)/i.test(line)) {
|
|
589
614
|
markers.push(line);
|
|
590
615
|
}
|
|
591
616
|
}
|
|
@@ -1090,8 +1115,11 @@ while (i < STEPS.length) {
|
|
|
1090
1115
|
if (step.name === "Capture") {
|
|
1091
1116
|
var capExp = await resolveExperiential();
|
|
1092
1117
|
var bounceSuffix = (mapGateBounceCount > 0 ? "-g" + mapGateBounceCount : "");
|
|
1093
|
-
|
|
1094
|
-
|
|
1118
|
+
// Chore has no QA phase, so Capture stays artifact-only: terminal
|
|
1119
|
+
// baselines would be unused evidence. (Chore crew members are still on
|
|
1120
|
+
// the terminal-UX page via the Triage/Map/Build/Review prompts.)
|
|
1121
|
+
if (capExp !== "yes" || !SURFACE_ARTIFACT) {
|
|
1122
|
+
log("Capture skipped for task " + taskId + " — " + (capExp !== "yes" ? "not experiential" : "no QA phase to consume terminal-surface baselines"));
|
|
1095
1123
|
await agent(
|
|
1096
1124
|
"Update the session and log the event.\n" +
|
|
1097
1125
|
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
@@ -1180,9 +1208,9 @@ while (i < STEPS.length) {
|
|
|
1180
1208
|
var mapBaselineRefs = "";
|
|
1181
1209
|
var mapBaselineNone = false;
|
|
1182
1210
|
if (step.name === "Map") {
|
|
1183
|
-
// Must match Capture's run condition (experiential + artifact
|
|
1211
|
+
// Must match Capture's run condition (experiential + artifact surface):
|
|
1184
1212
|
// when Capture skips, no baseline notes exist, so the gate must not apply.
|
|
1185
|
-
if ((await resolveExperiential()) === "yes" &&
|
|
1213
|
+
if ((await resolveExperiential()) === "yes" && SURFACE_ARTIFACT) {
|
|
1186
1214
|
var gateStatus = await baselineStatus();
|
|
1187
1215
|
if (!gateStatus.baseline_found) {
|
|
1188
1216
|
log("Map gate: no baseline evidence for experiential task " + taskId + " — bouncing to Capture");
|
|
@@ -1208,7 +1236,7 @@ while (i < STEPS.length) {
|
|
|
1208
1236
|
var instructions = "";
|
|
1209
1237
|
|
|
1210
1238
|
if (step.name === "Triage") {
|
|
1211
|
-
instructions = "Validate the task against the project's repo at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. If you run git commands, cd " + REPO_PATH + " first.\nCheck clarity, note dependencies, confirm the chore workflow assignment.\nReport back in plain prose — what you found.\nEXPERIENTIAL FLAG: does this task change anything
|
|
1239
|
+
instructions = "Validate the task against the project's repo at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. If you run git commands, cd " + REPO_PATH + " first.\nCheck clarity, note dependencies, confirm the chore workflow assignment.\nReport back in plain prose — what you found.\nEXPERIENTIAL FLAG: does this task change anything a user can directly observe in the project's user-facing surface? " + SURFACE_TRIAGE_DESC + " For an artifact surface that means rendered and visible — pages, components, styles, layout, copy, visual states. For a terminal surface it means the CLI experience — command output, help text, flags, error messages, defaults. The shared UX bar is " + (UX_DOCTRINE_PATH ? UX_DOCTRINE_PATH + " — flag experiential when the task touches anything it covers." : "not classified for this project — flag experiential when the task touches anything user-observable in the surface described above.") + " If yes it is experiential and gets baseline captures. End your report with exactly one line on its own, lowercase, unrephrased: experiential: yes — or experiential: no. This line is machine-read.";
|
|
1212
1240
|
|
|
1213
1241
|
} else if (step.name === "Map") {
|
|
1214
1242
|
var mapGatePara = "";
|
|
@@ -1216,11 +1244,14 @@ while (i < STEPS.length) {
|
|
|
1216
1244
|
mapGatePara = "\nBASELINE GATE (experiential task): " +
|
|
1217
1245
|
(mapBaselineNone
|
|
1218
1246
|
? "no baseline was capturable (baseline: none recorded) — write the spec without baseline comparison and note it."
|
|
1219
|
-
: "pre-change baseline captures: " + mapBaselineRefs + " — consult
|
|
1247
|
+
: "pre-change baseline captures: " + mapBaselineRefs + " — consult them when writing the spec.") +
|
|
1220
1248
|
" If the baseline evidence is missing with no baseline:none recorded, do not write the spec — report 'baseline evidence missing — Map gate bounce required' and stop.\n" +
|
|
1221
1249
|
"Declare capture targets for the post-change visual capture: end your report with a line `capture_targets: <comma-separated views/controls this change affects>` (optional; falls back to the task description).";
|
|
1222
1250
|
}
|
|
1223
|
-
instructions = "Research options, pick the path, write a clear spec for the builder.\
|
|
1251
|
+
instructions = "Research options, pick the path, write a clear spec for the builder.\n" +
|
|
1252
|
+
(SURFACE_TERMINAL ? "TERMINAL SPEC: this project's surface is a CLI. Specify the exact commands, their expected stdout/stderr, exit codes, --help text, and error messages — build and review against this spec and the shared bar at " + UX_DOCTRINE_PATH + ".\n" : "") +
|
|
1253
|
+
(SURFACE_ARTIFACT ? "ARTIFACT SPEC: this project's surface is a rendered artifact. Specify the exact screens, flows, and visual states the change affects — build and review against this spec and the shared bar at " + UX_DOCTRINE_PATH + ".\n" : "") +
|
|
1254
|
+
"The builder will edit source files in a git worktree of the project at " + REPO_PATH + ".\nProject: " + PROJECT_DESC + "\nTo understand the current code, read source files directly using the read tool. Do NOT use artifact_inspect — it was removed by the platform (2026-09-14) and does not exist; do not substitute artifact.inspect (malfunction diagnosis, not an inspection tool).\nIdentify the exact files and changes needed. Be specific: file paths, what to add or change.\nReport back in plain prose — what you specified." + mapGatePara;
|
|
1224
1255
|
|
|
1225
1256
|
} else if (step.name === "Build") {
|
|
1226
1257
|
instructions = "STEP 1: Prepare your worktree.\n" +
|
|
@@ -1235,6 +1266,8 @@ while (i < STEPS.length) {
|
|
|
1235
1266
|
"This is the project source: " + PROJECT_DESC + "\n" +
|
|
1236
1267
|
"Edit the TypeScript source files directly. Do NOT use artifact_edit — that happens in the Publish phase.\n" +
|
|
1237
1268
|
"Do not add unrequested features.\n" +
|
|
1269
|
+
(SURFACE_TERMINAL ? "TERMINAL UX: build to the shared bar at " + UX_DOCTRINE_PATH + " — --help text, error messages, and exit codes are user-facing and ship in this commit.\n" : "") +
|
|
1270
|
+
(SURFACE_ARTIFACT ? "ARTIFACT UX: build to the shared bar at " + UX_DOCTRINE_PATH + " — the rendered result is what the user sees; it ships in this commit.\n" : "") +
|
|
1238
1271
|
"PUBLIC DOCS: If your change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), update the public docs in the same commit — API.md for API changes. Documentation and implementation ship together.\n\n" +
|
|
1239
1272
|
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry. Versions are assigned at PUBLISH time — never in your branch. Do NOT touch the `version` field in package.json (or package-lock). Instead, end your report with exactly these two lines:\n" +
|
|
1240
1273
|
"release: yes|no — 'yes' if this change warrants a published release (anything a consumer can observe: workflow behavior, phase lists, identities, published docs, API); 'no' if internal-only.\n" +
|
|
@@ -1279,6 +1312,8 @@ while (i < STEPS.length) {
|
|
|
1279
1312
|
"You can also read specific files in the worktree at:\n" +
|
|
1280
1313
|
WORKTREE_HINT + "/\n\n" +
|
|
1281
1314
|
"Check quality, correctness, spec compliance.\n" +
|
|
1315
|
+
(SURFACE_TERMINAL ? "TERMINAL UX REVIEW: judge the CLI surface against " + UX_DOCTRINE_PATH + " — help accuracy, error quality, exit codes, output clarity. Reject when the bar is not met.\n" : "") +
|
|
1316
|
+
(SURFACE_ARTIFACT ? "ARTIFACT UX REVIEW: judge the rendered surface against " + UX_DOCTRINE_PATH + " — alignment, spacing, hierarchy, composition, balance, finish, correctness. Reject when the bar is not met.\n" : "") +
|
|
1282
1317
|
"Check that public-affecting changes have matching public doc updates (API.md or the published API contract). If the docs are missing or inaccurate, report what is stale, then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1283
1318
|
"If the branch has no commits ahead of main (inspect shows an empty commit log), approve ONLY if the Build summary declares `repo_diff: none` with (a) a plausible runtime-state deliverable (e.g. a cron created via the cron tool), or (b) an already-merged declaration `repo_diff: none (already-merged: <sha>)` AND the mechanical fact below confirms the sha verified. MECHANICAL FACT (computed by the workflow, never by the builder): already_merged sha = " + (alreadyMergedSha ? alreadyMergedSha + " (verified ancestor of main: YES)" : "none declared") + ". Otherwise report 'no commits ahead of main and no valid repo_diff: none declaration — the builder likely forgot to commit', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1284
1319
|
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry, and versions are assigned at publish time — never in branches. Two checks:\n" +
|
|
@@ -383,7 +383,11 @@ for (var pi = 0; pi < projects.length; pi++) {
|
|
|
383
383
|
repo_path: proj.repo_path,
|
|
384
384
|
deploy_type: proj.deploy_type,
|
|
385
385
|
deploy_slug: proj.deploy_slug,
|
|
386
|
-
description: proj.description
|
|
386
|
+
description: proj.description,
|
|
387
|
+
// The user-facing surface for experiential QA routing (artifact |
|
|
388
|
+
// terminal | null=unclassified). Carried alongside deploy_type — it is a
|
|
389
|
+
// separate axis, not a redeclaration of the deployment target.
|
|
390
|
+
environment_type: proj.environment_type || null
|
|
387
391
|
};
|
|
388
392
|
}
|
|
389
393
|
|
package/workflows/crew-init.js
CHANGED
|
@@ -362,13 +362,30 @@ try {
|
|
|
362
362
|
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " get-project --json '{\"id\": \"" + dashboardSlug + "\"}'\n" +
|
|
363
363
|
"2. If it returns a project (ok: true):\n" +
|
|
364
364
|
" a. Compare its repo_path to: " + gateFacts.dashboardRepoExpanded + "\n" +
|
|
365
|
-
" b. If they match,
|
|
365
|
+
" b. If they match, make sure the project declares its user-facing surface:\n" +
|
|
366
|
+
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " get-project --json '{\"id\": \"" + dashboardSlug + "\"}'\n" +
|
|
367
|
+
" If the returned project has environment_type null, run:\n" +
|
|
368
|
+
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " update-project --json '{\"id\": \"" + dashboardSlug + "\", \"environment_type\": \"artifact\"}'\n" +
|
|
369
|
+
" (the dashboard's user-facing surface is its rendered web UI).\n" +
|
|
370
|
+
" Return { action: \"exists\", project_id: \"" + dashboardSlug + "\" }.\n" +
|
|
366
371
|
" c. If they differ, repair it:\n" +
|
|
367
|
-
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " update-project --json '{\"id\": \"" + dashboardSlug + "\", \"repo_path\": \"" + safeDashboardRepo + "\"}'\n" +
|
|
372
|
+
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " update-project --json '{\"id\": \"" + dashboardSlug + "\", \"repo_path\": \"" + safeDashboardRepo + "\", \"environment_type\": \"artifact\"}'\n" +
|
|
368
373
|
" Return { action: \"repaired\", project_id: \"" + dashboardSlug + "\" }.\n" +
|
|
369
374
|
"3. If not found (error), create it:\n" +
|
|
370
|
-
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " create-project --json '{\"id\": \"" + dashboardSlug + "\", \"display_name\": \"" + safeDashboardName + "\", \"repo_path\": \"" + safeDashboardRepo + "\", \"deploy_type\": \"artifact\", \"deploy_slug\": \"" + dashboardSlug + "\", \"description\": \"The dashboard task service — the crew\\u0027s first project\"}'\n" +
|
|
375
|
+
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " create-project --json '{\"id\": \"" + dashboardSlug + "\", \"display_name\": \"" + safeDashboardName + "\", \"repo_path\": \"" + safeDashboardRepo + "\", \"deploy_type\": \"artifact\", \"deploy_slug\": \"" + dashboardSlug + "\", \"environment_type\": \"artifact\", \"description\": \"The dashboard task service — the crew\\u0027s first project\"}'\n" +
|
|
371
376
|
" Return { action: \"created\", project_id: \"" + dashboardSlug + "\" }.\n\n" +
|
|
377
|
+
"4. Seed the update watcher's trusted dashboard base (enrollment) — the\n" +
|
|
378
|
+
" commit this installation starts from. Only ever seeds when no base\n" +
|
|
379
|
+
" exists; an established base is never moved by a re-init:\n" +
|
|
380
|
+
" a. Run: git -C \"" + safeDashboardRepo + "\" rev-parse HEAD\n" +
|
|
381
|
+
" b. If it prints a 40-hex sha, run:\n" +
|
|
382
|
+
" node " + crewHome + "/current/lib/update-watch.js --crew-home " + crewHome + " --record-dashboard-sha <sha>\n" +
|
|
383
|
+
" Exit 0 with 'already' in the output means a base is already enrolled —\n" +
|
|
384
|
+
" that is the correct outcome, report it.\n" +
|
|
385
|
+
" c. If rev-parse fails (empty repo), skip seeding and report\n" +
|
|
386
|
+
" 'base not seeded: empty repo' — the watcher will file the first\n" +
|
|
387
|
+
" dashboard upgrade with oldSha unknown and park it with enrollment\n" +
|
|
388
|
+
" guidance, fail-closed.\n\n" +
|
|
372
389
|
"The repo_path must be the dashboard git repository — never the crew home.\n" +
|
|
373
390
|
"Return JSON with action (\"exists\", \"repaired\", or \"created\") and project_id (string).",
|
|
374
391
|
{
|
package/workflows/docs.js
CHANGED
|
@@ -62,6 +62,20 @@ const REPO_PATH = projectConfig.repo_path || "";
|
|
|
62
62
|
if (!REPO_PATH) {
|
|
63
63
|
throw new Error("Project '" + (inputs.project_id || "unknown") + "' has no repo_path configured — set it via updateproject before dispatching tasks.");
|
|
64
64
|
}
|
|
65
|
+
// UX surface resolution (same doctrine as standard.js/bugfix.js/chore.js):
|
|
66
|
+
// environment_type is the canonical axis; docs have no deploy target, so
|
|
67
|
+
// only environment_type resolves the surface here.
|
|
68
|
+
const ENV_TYPE = projectConfig.environment_type || null;
|
|
69
|
+
const SURFACE_TERMINAL = (ENV_TYPE === "terminal");
|
|
70
|
+
const SURFACE_ARTIFACT = (ENV_TYPE === "artifact");
|
|
71
|
+
// UX doctrine page: the shared UX bar for this run's surface, resolved
|
|
72
|
+
// mechanically — every phase prompt reads UX_DOCTRINE_PATH, never a
|
|
73
|
+
// hardcoded filename. Canonical map: lib/ux-doctrine.js (mirrored here as a
|
|
74
|
+
// one-liner because the workflow runtime's relative-import support is
|
|
75
|
+
// unverified; tests pin the mirror). Null on unclassified surfaces: no
|
|
76
|
+
// shared page.
|
|
77
|
+
const UX_DOCTRINE_PAGE = SURFACE_TERMINAL ? "terminal-ux.md" : (SURFACE_ARTIFACT ? "artifact-ux.md" : null);
|
|
78
|
+
const UX_DOCTRINE_PATH = UX_DOCTRINE_PAGE ? crewHome + "/current/docs/" + UX_DOCTRINE_PAGE : null;
|
|
65
79
|
|
|
66
80
|
if (!taskId) {
|
|
67
81
|
throw new Error("task_id is required in args");
|
|
@@ -409,10 +423,13 @@ function describeWorkAgentFailure(stepName, identity, attempts) {
|
|
|
409
423
|
instructions = "Validate the task against the project's repo at " + REPO_PATH + " — that exact checkout, not any other copy of the project on disk. If you run git commands, cd " + REPO_PATH + " first.\nCheck clarity, confirm the docs workflow assignment.\nReport back in plain prose — your assessment.";
|
|
410
424
|
} else if (step.name === "Write") {
|
|
411
425
|
instructions = "Write or revise the documentation the task asks for.\nFollow Tate's voice — clear, conversational, no jargon unless it earns its place.\nDo your work in the project repository at " + REPO_PATH + " — all doc files go there, not under the crew home." +
|
|
426
|
+
(UX_DOCTRINE_PATH ? "\nJudge the docs against the shared UX bar at " + UX_DOCTRINE_PATH + " — it is what correct behavior looks like for this project's surface. " + (SURFACE_TERMINAL ? "When the docs describe CLI behavior (commands, flags, help text, error messages, exit codes), document what the user actually experiences, verified against the real CLI, never from source-code inference." : "When the docs describe the UI (screens, flows, visual states, copy), document what the user actually sees, verified against the real artifact, never from source-code inference.") : "") +
|
|
412
427
|
(rejectionNotes ? "\n\nREWORK after review rejection. Address:\n" + rejectionNotes : "") +
|
|
413
428
|
"\nReport back in plain prose — what you wrote and where.";
|
|
414
429
|
} else if (step.name === "Review") {
|
|
415
|
-
instructions = "Review the docs independently and cold — clarity, accuracy, completeness.\nNo prior context from the writer
|
|
430
|
+
instructions = "Review the docs independently and cold — clarity, accuracy, completeness.\nNo prior context from the writer." +
|
|
431
|
+
(UX_DOCTRINE_PATH ? "\nCheck the docs against the shared UX bar at " + UX_DOCTRINE_PATH + ". Flag any " + (SURFACE_TERMINAL ? "CLI" : "UI") + " claim you cannot verify — undocumented behavior and documented-but-wrong behavior both fail." : "") +
|
|
432
|
+
"\nWrite your review as plain prose — findings, then decision. End your report with exactly one line: VERDICT: PASS if it passes, VERDICT: FAIL if it fails.";
|
|
416
433
|
}
|
|
417
434
|
|
|
418
435
|
// Work agent returns the runtime's native envelope {"status": "ok",
|