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/standard.js
CHANGED
|
@@ -123,6 +123,36 @@ const WORKTREE_HINT = REPO_PATH + "/.worktrees/" + taskId;
|
|
|
123
123
|
const WORKTREE_PRESERVED_HINT = ".worktrees/" + taskId;
|
|
124
124
|
|
|
125
125
|
const PUBLISH_TYPE = projectConfig.deploy_type || "";
|
|
126
|
+
// User-facing surface for experiential QA routing: 'artifact' (a rendered
|
|
127
|
+
// web UI Hazel drives with the see-act browser loop) | 'terminal' (a CLI
|
|
128
|
+
// Hazel drives herself, keeping attempt-scoped transcripts) | null
|
|
129
|
+
// (unclassified — no experiential QA). environment_type is the canonical
|
|
130
|
+
// UX-surface axis; deploy_type names the deployment target, but
|
|
131
|
+
// deploy_type === "artifact" remains a legacy artifact-surface signal so
|
|
132
|
+
// pre-field projects keep today's experiential QA (the migration does not
|
|
133
|
+
// backfill the column).
|
|
134
|
+
const ENV_TYPE = projectConfig.environment_type || null;
|
|
135
|
+
// Surface resolution: artifact wins on contradictory config (the deployed
|
|
136
|
+
// artifact is what users see). Unclassified surface => Capture skips, QA
|
|
137
|
+
// runs the plain code-blind prompt — today's behavior, unchanged.
|
|
138
|
+
const SURFACE_ARTIFACT = (PUBLISH_TYPE === "artifact" || ENV_TYPE === "artifact");
|
|
139
|
+
const SURFACE_TERMINAL = (!SURFACE_ARTIFACT && ENV_TYPE === "terminal");
|
|
140
|
+
const SURFACE_CLASSIFIED = SURFACE_ARTIFACT || SURFACE_TERMINAL;
|
|
141
|
+
// One-line surface description for the Triage prompt, so Sage judges the
|
|
142
|
+
// experiential flag against the project's actual user-facing surface.
|
|
143
|
+
const SURFACE_TRIAGE_DESC = SURFACE_ARTIFACT
|
|
144
|
+
? "This project's user-facing surface is artifact: a rendered web UI."
|
|
145
|
+
: SURFACE_TERMINAL
|
|
146
|
+
? "This project's user-facing surface is terminal: a command-line interface."
|
|
147
|
+
: "This project's user-facing surface is unclassified (environment_type not set): judge by what a user would directly observe.";
|
|
148
|
+
// UX doctrine page: the shared UX bar for this run's surface, resolved
|
|
149
|
+
// mechanically — every phase prompt reads UX_DOCTRINE_PATH, never a
|
|
150
|
+
// hardcoded filename. Canonical map: lib/ux-doctrine.js (mirrored here as a
|
|
151
|
+
// one-liner because the workflow runtime's relative-import support is
|
|
152
|
+
// unverified; tests pin the mirror). Null on unclassified surfaces: no
|
|
153
|
+
// shared page, and prompts say so instead of naming the wrong one.
|
|
154
|
+
const UX_DOCTRINE_PAGE = SURFACE_TERMINAL ? "terminal-ux.md" : (SURFACE_ARTIFACT ? "artifact-ux.md" : null);
|
|
155
|
+
const UX_DOCTRINE_PATH = UX_DOCTRINE_PAGE ? crewHome + "/current/docs/" + UX_DOCTRINE_PAGE : null;
|
|
126
156
|
const PUBLISH_SLUG = projectConfig.deploy_slug || "";
|
|
127
157
|
const PROJECT_DESC = projectConfig.description || "React + TypeScript web dashboard (client/src/, server/src/, drizzle/)";
|
|
128
158
|
const RELEASE_SCRIPT = crewHome + "/crew-release.sh";
|
|
@@ -540,7 +570,7 @@ function extractMarkerLines(workerText) {
|
|
|
540
570
|
var markers = [];
|
|
541
571
|
for (var i = 0; i < lines.length; i++) {
|
|
542
572
|
var line = lines[i].trim();
|
|
543
|
-
if (/^(repo_diff:|release:|version_bump:|VERDICT:|TARGET_VERSION=|published:|experiential:|layer:|capture_targets:|worktree:)/i.test(line)) {
|
|
573
|
+
if (/^(repo_diff:|release:|version_bump:|VERDICT:|TARGET_VERSION=|published:|experiential:|layer:|capture_targets:|terminal_targets:|worktree:)/i.test(line)) {
|
|
544
574
|
markers.push(line);
|
|
545
575
|
}
|
|
546
576
|
}
|
|
@@ -730,6 +760,10 @@ let isExperiential = null;
|
|
|
730
760
|
// notes are immutable within a run, so the lookup runs at most once.
|
|
731
761
|
let experientialResolved = null;
|
|
732
762
|
let captureTargets = "";
|
|
763
|
+
// Sage's terminal_targets marker: the CLI commands/flags exercising the
|
|
764
|
+
// changed surface (terminal-surface tasks). Falls back to the task
|
|
765
|
+
// description when Sage omits it.
|
|
766
|
+
let terminalTargets = "";
|
|
733
767
|
let mapGateBounceCount = 0;
|
|
734
768
|
// Merge-time versioning: the release decision is extracted deterministically
|
|
735
769
|
// from the accepted Build worker report (extractReleaseDecision) so the Publish
|
|
@@ -1034,13 +1068,13 @@ while (i < STEPS.length) {
|
|
|
1034
1068
|
if (step.name === "Capture") {
|
|
1035
1069
|
var capExp = await resolveExperiential();
|
|
1036
1070
|
var bounceSuffix = (mapGateBounceCount > 0 ? "-g" + mapGateBounceCount : "");
|
|
1037
|
-
if (capExp !== "yes" ||
|
|
1038
|
-
log("Capture skipped for task " + taskId + " — " + (capExp !== "yes" ? "not experiential" : "
|
|
1071
|
+
if (capExp !== "yes" || !SURFACE_CLASSIFIED) {
|
|
1072
|
+
log("Capture skipped for task " + taskId + " — " + (capExp !== "yes" ? "not experiential" : "surface unclassified (environment_type=" + (ENV_TYPE || "null") + ")"));
|
|
1039
1073
|
await agent(
|
|
1040
1074
|
"Update the session and log the event.\n" +
|
|
1041
1075
|
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
1042
1076
|
task_id: taskId,
|
|
1043
|
-
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "completed", notes: "Capture skipped — not an experiential
|
|
1077
|
+
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" },
|
|
1044
1078
|
event: { task_id: taskId, type: "completed", identity: step.identity, message: "Capture completed by " + step.identity }
|
|
1045
1079
|
}),
|
|
1046
1080
|
{ key: "record-Capture" + bounceSuffix, label: "Recording Capture result" }
|
|
@@ -1048,6 +1082,34 @@ while (i < STEPS.length) {
|
|
|
1048
1082
|
i++;
|
|
1049
1083
|
continue;
|
|
1050
1084
|
}
|
|
1085
|
+
// Terminal-surface Capture: Hazel runs the CLI herself against the
|
|
1086
|
+
// pre-change tree and archives attempt-scoped transcripts. No parent
|
|
1087
|
+
// protocol exists for terminal projects (there is no see-act loop to
|
|
1088
|
+
// drive), so the baseline is agent-driven in one shot — the Map gate
|
|
1089
|
+
// reads the same "baseline: captured/none" note prefixes either way.
|
|
1090
|
+
if (SURFACE_TERMINAL) {
|
|
1091
|
+
var termBaseDir = crewHome + "/task-evidence/" + taskId + "/baseline";
|
|
1092
|
+
log("Capture: terminal-surface baseline for task " + taskId + " — Hazel drives the CLI herself");
|
|
1093
|
+
await agent(
|
|
1094
|
+
"Run the pre-change terminal baseline yourself — there is no parent capture protocol for terminal-surface projects.\n" +
|
|
1095
|
+
"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" +
|
|
1096
|
+
"2. Terminal targets for this task: " + (terminalTargets || "not declared — derive them from --help and the task description") + ".\n" +
|
|
1097
|
+
"3. Run in shell: mkdir -p " + termBaseDir + "\n" +
|
|
1098
|
+
"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" +
|
|
1099
|
+
"5. READ every transcript file you wrote — an unread transcript is not evidence.\n" +
|
|
1100
|
+
"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" +
|
|
1101
|
+
" (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" +
|
|
1102
|
+
"7. Record the phase — run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
1103
|
+
task_id: taskId,
|
|
1104
|
+
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "completed", notes: "Terminal baseline captured by the QA agent (transcripts archived)" },
|
|
1105
|
+
event: { task_id: taskId, type: "completed", identity: step.identity, message: "Capture completed by " + step.identity }
|
|
1106
|
+
}) + "\n" +
|
|
1107
|
+
"Report back in plain prose: which commands you ran and what the pre-change baseline looks like.",
|
|
1108
|
+
{ key: "terminal-baseline-" + taskId + bounceSuffix, label: "Capturing terminal baseline" }
|
|
1109
|
+
);
|
|
1110
|
+
i++;
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1051
1113
|
var capStatus = await baselineStatus();
|
|
1052
1114
|
// Stale-decision guard: a "baseline: none (visual protocol unavailable)"
|
|
1053
1115
|
// note is only durable while the protocol is unavailable. When
|
|
@@ -1125,9 +1187,9 @@ while (i < STEPS.length) {
|
|
|
1125
1187
|
var mapBaselineRefs = "";
|
|
1126
1188
|
var mapBaselineNone = false;
|
|
1127
1189
|
if (step.name === "Map") {
|
|
1128
|
-
// Must match Capture's run condition (experiential +
|
|
1190
|
+
// Must match Capture's run condition (experiential + classified surface):
|
|
1129
1191
|
// when Capture skips, no baseline notes exist, so the gate must not apply.
|
|
1130
|
-
if ((await resolveExperiential()) === "yes" &&
|
|
1192
|
+
if ((await resolveExperiential()) === "yes" && SURFACE_CLASSIFIED) {
|
|
1131
1193
|
var gateStatus = await baselineStatus();
|
|
1132
1194
|
if (!gateStatus.baseline_found) {
|
|
1133
1195
|
log("Map gate: no baseline evidence for experiential task " + taskId + " — bouncing to Capture");
|
|
@@ -1149,13 +1211,16 @@ while (i < STEPS.length) {
|
|
|
1149
1211
|
}
|
|
1150
1212
|
}
|
|
1151
1213
|
|
|
1152
|
-
// Experiential routing: experiential
|
|
1153
|
-
// experiential QA prompt (built in the
|
|
1154
|
-
// of the QA step) — Hazel drives the
|
|
1155
|
-
// verdict through her OODA report and
|
|
1156
|
-
|
|
1214
|
+
// Experiential routing: experiential tasks on a classified surface run the
|
|
1215
|
+
// Hazel experiential QA prompt (built in the SURFACE_ARTIFACT /
|
|
1216
|
+
// SURFACE_TERMINAL branches of the QA step) — Hazel drives the surface
|
|
1217
|
+
// herself and owns the verdict through her OODA report and the
|
|
1218
|
+
// write-ooda-verdict ledger. qaExperiential gates the closeout
|
|
1219
|
+
// experiential-loop guard (a PASS with missing experiential evidence is
|
|
1220
|
+
// never terminal).
|
|
1221
|
+
var qaExperiential = false;
|
|
1157
1222
|
if (step.name === "QA") {
|
|
1158
|
-
|
|
1223
|
+
qaExperiential = (await resolveExperiential()) === "yes" && SURFACE_CLASSIFIED;
|
|
1159
1224
|
}
|
|
1160
1225
|
|
|
1161
1226
|
// Step-specific instructions
|
|
@@ -1163,7 +1228,7 @@ while (i < STEPS.length) {
|
|
|
1163
1228
|
var instructions = "";
|
|
1164
1229
|
|
|
1165
1230
|
if (step.name === "Triage") {
|
|
1166
|
-
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 standard workflow assignment.\nWrite a brief triage assessment as notes for the next step.\nReport back in plain prose — what you found.\nEXPERIENTIAL FLAG: does this task change anything
|
|
1231
|
+
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 standard workflow assignment.\nWrite a brief triage assessment as notes for the next step.\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.";
|
|
1167
1232
|
|
|
1168
1233
|
} else if (step.name === "Map") {
|
|
1169
1234
|
var mapGatePara = "";
|
|
@@ -1171,11 +1236,16 @@ while (i < STEPS.length) {
|
|
|
1171
1236
|
mapGatePara = "\nBASELINE GATE (experiential task): " +
|
|
1172
1237
|
(mapBaselineNone
|
|
1173
1238
|
? "no baseline was capturable (baseline: none recorded) — write the spec without baseline comparison and note it."
|
|
1174
|
-
: "pre-change baseline
|
|
1239
|
+
: "pre-change baseline evidence: " + mapBaselineRefs + " — consult it when writing the spec.") +
|
|
1175
1240
|
" 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" +
|
|
1176
|
-
|
|
1241
|
+
(SURFACE_TERMINAL
|
|
1242
|
+
? "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)."
|
|
1243
|
+
: "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).");
|
|
1177
1244
|
}
|
|
1178
|
-
instructions = "Research the problem space, evaluate options, pick the shortest path.\nWrite a clear spec that a builder can execute without asking questions.\
|
|
1245
|
+
instructions = "Research the problem space, evaluate options, pick the shortest path.\nWrite a clear spec that a builder can execute without asking questions.\n" +
|
|
1246
|
+
(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" : "") +
|
|
1247
|
+
(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" : "") +
|
|
1248
|
+
"The builder will edit source files in a git worktree.\nProject: " + PROJECT_DESC + "\nSave the spec to exactly this file: " + SPEC_PATH + " — run mkdir -p \"" + SPEC_DIR + "\" first. Do not save the spec anywhere else; this exact path is fixed and will be checked mechanically after your step.\nReport back in plain prose — what you specified." + mapGatePara;
|
|
1179
1249
|
|
|
1180
1250
|
} else if (step.name === "Build") {
|
|
1181
1251
|
instructions = "STEP 1: Prepare your worktree.\n" +
|
|
@@ -1190,6 +1260,8 @@ while (i < STEPS.length) {
|
|
|
1190
1260
|
"This is the project source: " + PROJECT_DESC + "\n" +
|
|
1191
1261
|
"Edit source files directly. Do NOT use artifact_edit — that happens in the Publish phase.\n" +
|
|
1192
1262
|
"Do not add unrequested features. Build exactly what the spec calls for.\n" +
|
|
1263
|
+
(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" : "") +
|
|
1264
|
+
(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" : "") +
|
|
1193
1265
|
"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" +
|
|
1194
1266
|
(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" +
|
|
1195
1267
|
"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" +
|
|
@@ -1234,6 +1306,8 @@ while (i < STEPS.length) {
|
|
|
1234
1306
|
"You can also read specific files in the worktree at:\n" +
|
|
1235
1307
|
WORKTREE_HINT + "/\n\n" +
|
|
1236
1308
|
"Check quality, correctness, and spec compliance.\n" +
|
|
1309
|
+
(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" : "") +
|
|
1310
|
+
(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" : "") +
|
|
1237
1311
|
"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" +
|
|
1238
1312
|
"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" +
|
|
1239
1313
|
(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" +
|
|
@@ -2137,9 +2211,10 @@ while (i < STEPS.length) {
|
|
|
2137
2211
|
"Verify the bump SCOPE: the <scope> in that line MUST equal the accepted version_bump scope \"" + releaseDecision.version_bump + "\" — if it differs, report the mismatch, then end your report with exactly this line: VERDICT: FAIL. Verify the ARITHMETIC: <base> + <scope> must equal <new-version> (patch increments the last segment only, e.g. 0.3.0 + patch → 0.3.1; minor increments the middle and resets the last to 0; major increments the first and resets the rest to 0) — if the math is wrong, report it, then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
2138
2212
|
"Extract the published version from the notes line matching published: muse-crew@<version>. It MUST equal <new-version> from the TARGET_VERSION line. Then run: npm view muse-crew version. The registry version MUST equal <new-version>. If any of these checks fails, report 'npm publish verification failed: [details]', then end your report with exactly this line: VERDICT: FAIL.\n"
|
|
2139
2213
|
: "";
|
|
2140
|
-
if (
|
|
2214
|
+
if (SURFACE_ARTIFACT) {
|
|
2141
2215
|
var safeDesc = taskDescription.replace(/"/g, "'").replace(/\\/g, "\\\\").slice(0, 500);
|
|
2142
|
-
instructions = "
|
|
2216
|
+
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" +
|
|
2217
|
+
"You are code-blind QA. You NEVER read source files.\n" +
|
|
2143
2218
|
"Public docs (API.md, README, published action schemas) are NOT source code — read them freely, exactly as a user would.\n\n" +
|
|
2144
2219
|
"STEP 1: Experiential visual inspection — drive the artifact as a user would, one browser step at a time.\n" +
|
|
2145
2220
|
"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" +
|
|
@@ -2173,6 +2248,40 @@ while (i < STEPS.length) {
|
|
|
2173
2248
|
"(replace <issue title> and <issue details> with the real values).\n\n" +
|
|
2174
2249
|
"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" +
|
|
2175
2250
|
"Report back in plain prose — what checks you ran and their results. 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 or VERDICT: 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 \"<what the task required>\" --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).";
|
|
2251
|
+
} else if (SURFACE_TERMINAL) {
|
|
2252
|
+
// Terminal-surface experiential QA: Hazel drives the CLI herself —
|
|
2253
|
+
// the terminal counterpart to the see-act loop above. Same OODA
|
|
2254
|
+
// discipline (append-ooda-step with --action terminal and a transcript
|
|
2255
|
+
// per step; verdict via write-ooda-verdict), judged against the
|
|
2256
|
+
// shared bar resolved via UX_DOCTRINE_PATH (the terminal doctrine page here). Transcripts are delivered;
|
|
2257
|
+
// screenshots are never invented for terminal work.
|
|
2258
|
+
var termEvidence = crewHome + "/task-evidence/" + taskId + "/postchange";
|
|
2259
|
+
var termTargetsLine = terminalTargets || "not declared — derive from --help and the task description";
|
|
2260
|
+
instructions = "You are code-blind QA. You NEVER read source files.\n" +
|
|
2261
|
+
"Public docs (API.md, README) are NOT source code — read them freely, exactly as a user would.\n" +
|
|
2262
|
+
"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" +
|
|
2263
|
+
"STEP 1: Experiential terminal inspection — drive the CLI as a user would, one command at a time.\n" +
|
|
2264
|
+
"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" +
|
|
2265
|
+
"b. Terminal targets for this task: " + termTargetsLine + ".\n" +
|
|
2266
|
+
"c. Bounded terminal loop — at most 8 commands. For each target: run it RIGHT (the happy path), 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" +
|
|
2267
|
+
"d. Evidence: capture EVERY invocation as a transcript. Run: mkdir -p " + termEvidence + "\n" +
|
|
2268
|
+
" For each command: <cmd> > " + termEvidence + "/<nn>-<short-slug>.txt 2>&1; echo \"exit=$?\" >> " + termEvidence + "/<nn>-<short-slug>.txt (number them 01, 02, ...). Then READ the transcript before judging it — an unread transcript is not evidence.\n" +
|
|
2269
|
+
"e. Log each step to the OODA log — run in shell, one command per step:\n" +
|
|
2270
|
+
" node " + crewHome + "/current/lib/append-ooda-step.js --log " + termEvidence + "/ooda-log.jsonl --attempt \"1\" --step <N> --action terminal --exit <code> --transcript " + termEvidence + "/<nn>-<short-slug>.txt --args '{\"cmd\":\"<the exact command>\"}' --observation \"<1-2 sentences: what the output said and what you concluded>\"\n" +
|
|
2271
|
+
" 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" +
|
|
2272
|
+
"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" +
|
|
2273
|
+
"Then continue with the mechanical checks below. Your VERDICT covers both the experiential and the mechanical checks.\n\n" +
|
|
2274
|
+
"STEP 2: Verify data integrity via the crew API.\n" +
|
|
2275
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("get-state", { events_limit: 1 }) + "\n" +
|
|
2276
|
+
"Use the returned tasks, sessions, and events to check the task's data-level effects.\n" +
|
|
2277
|
+
"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" +
|
|
2278
|
+
"STEP 3: File follow-up tasks for any related issues you discover.\n" +
|
|
2279
|
+
"For each issue, run in shell:\n" +
|
|
2280
|
+
"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" +
|
|
2281
|
+
"(replace <issue title> and <issue details> with the real values).\n\n" +
|
|
2282
|
+
npmPublishCheck +
|
|
2283
|
+
"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" +
|
|
2284
|
+
"Report back in plain prose — what checks you ran and their results. 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 or VERDICT: FAIL. First ensure the OODA log exists even if you logged zero steps (touch " + termEvidence + "/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 " + termEvidence + " --attempt \"1\" --verdict <PASS|FAIL|NOT_POSSIBLE> --summary \"<one line>\" --expected \"<what the task required>\" --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).";
|
|
2176
2285
|
} else {
|
|
2177
2286
|
instructions = "Test from a user's perspective. You are CODE-BLIND — do NOT read source code.\n" +
|
|
2178
2287
|
"Public docs (API.md, README) are NOT source code — read them freely, exactly as a user would.\n" +
|
|
@@ -2184,10 +2293,10 @@ while (i < STEPS.length) {
|
|
|
2184
2293
|
npmPublishCheck +
|
|
2185
2294
|
"Report back in plain prose — what you tested and found. End your report with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
|
|
2186
2295
|
}
|
|
2187
|
-
//
|
|
2188
|
-
//
|
|
2189
|
-
//
|
|
2190
|
-
//
|
|
2296
|
+
// qaExperiential: Hazel owns the experiential verdict through the QA
|
|
2297
|
+
// prompt built in the SURFACE_ARTIFACT / SURFACE_TERMINAL branches above
|
|
2298
|
+
// (see-act loop or terminal loop + OODA report). There is no parent
|
|
2299
|
+
// visual-verdict protocol anymore — no override here.
|
|
2191
2300
|
}
|
|
2192
2301
|
|
|
2193
2302
|
// Task event history — Review and Publish are excluded. Review is cold by
|
|
@@ -2416,26 +2525,30 @@ while (i < STEPS.length) {
|
|
|
2416
2525
|
}
|
|
2417
2526
|
}
|
|
2418
2527
|
|
|
2419
|
-
// QA
|
|
2420
|
-
// is honest about missing
|
|
2421
|
-
// as terminal done even when the
|
|
2528
|
+
// QA experiential-loop guard (clean-room defect 2026-09-16): Hazel's verdict.json
|
|
2529
|
+
// is honest about missing experiential evidence, but the closeout treated a PASS
|
|
2530
|
+
// as terminal done even when the experiential loop never ran (playwright-core
|
|
2422
2531
|
// was unresolvable from the release layout — the dependency lived in the
|
|
2423
2532
|
// npm install dir, severed from the crew home). A PASS verdict with missing
|
|
2424
2533
|
// experiential evidence must never be terminal: the task parks fail-closed
|
|
2425
|
-
// with unattributable_reason=qa-visual-loop-unavailable
|
|
2534
|
+
// with unattributable_reason=qa-visual-loop-unavailable (artifact surface)
|
|
2535
|
+
// or qa-terminal-loop-unavailable (terminal surface) instead of
|
|
2426
2536
|
// transitioning to done. Code, not prompt text: the check reads the
|
|
2427
2537
|
// machine-readable verdict via lib/read-ooda-verdict.js, which reports
|
|
2428
2538
|
// visual_loop_unavailable from the OODA log's NOT POSSIBLE browser steps
|
|
2429
|
-
// and
|
|
2430
|
-
|
|
2539
|
+
// and terminal_loop_unavailable from NOT POSSIBLE terminal steps, plus the
|
|
2540
|
+
// verdict's missing_evidence tool-unavailability notes.
|
|
2541
|
+
if (step.name === "QA" && qaExperiential && verdictPassed === true) {
|
|
2431
2542
|
var qaLoopDir = crewHome + "/task-evidence/" + taskId + "/postchange";
|
|
2543
|
+
var qaLoopSurface = SURFACE_TERMINAL ? "terminal" : "visual";
|
|
2544
|
+
var qaLoopReason = SURFACE_TERMINAL ? "qa-terminal-loop-unavailable" : "qa-visual-loop-unavailable";
|
|
2432
2545
|
var qaLoopOut = "";
|
|
2433
2546
|
try {
|
|
2434
2547
|
var qaLoopCheck = await agent(
|
|
2435
|
-
"Check the QA
|
|
2548
|
+
"Check the QA " + qaLoopSurface + " loop's availability.\n" +
|
|
2436
2549
|
"Run: node " + crewHome + "/current/lib/read-ooda-verdict.js --dir " + qaLoopDir + " --expect PASS\n" +
|
|
2437
2550
|
"Return JSON { \"output\": \"<the command's full stdout, trimmed>\" } and nothing else.",
|
|
2438
|
-
{ key: attemptKey("qa-
|
|
2551
|
+
{ key: attemptKey("qa-" + qaLoopSurface + "-loop-check-" + taskId, totalReworkCount), label: "Checking QA " + qaLoopSurface + "-loop availability",
|
|
2439
2552
|
schema: { type: "object", properties: { output: { type: "string" } }, required: ["output"] } }
|
|
2440
2553
|
);
|
|
2441
2554
|
qaLoopOut = (qaLoopCheck && qaLoopCheck.output ? qaLoopCheck.output : "").trim();
|
|
@@ -2452,14 +2565,18 @@ while (i < STEPS.length) {
|
|
|
2452
2565
|
qaLoopGate = null;
|
|
2453
2566
|
}
|
|
2454
2567
|
if (!qaLoopGate || qaLoopGate.ok !== true) {
|
|
2455
|
-
log("QA
|
|
2456
|
-
return await parkTask("QA
|
|
2568
|
+
log("QA " + qaLoopSurface + "-loop check unreadable — cannot confirm experiential evidence; parking fail-closed");
|
|
2569
|
+
return await parkTask("QA " + qaLoopSurface + " loop unverifiable (unattributable_reason=" + qaLoopReason + "): lib/read-ooda-verdict.js could not confirm the QA verdict record — a PASS without a machine-readable experiential record is never terminal. Human attention needed.");
|
|
2457
2570
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2571
|
+
var qaLoopUnavailable = SURFACE_TERMINAL ? qaLoopGate.terminal_loop_unavailable : qaLoopGate.visual_loop_unavailable;
|
|
2572
|
+
if (qaLoopUnavailable === true) {
|
|
2573
|
+
log("QA " + qaLoopSurface + " loop unavailable — parking fail-closed (unattributable_reason=" + qaLoopReason + "), never done");
|
|
2574
|
+
return await parkTask("QA " + qaLoopSurface + " loop unavailable (unattributable_reason=" + qaLoopReason + "): " +
|
|
2575
|
+
(SURFACE_TERMINAL
|
|
2576
|
+
? "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."
|
|
2577
|
+
: "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."));
|
|
2461
2578
|
}
|
|
2462
|
-
log("QA
|
|
2579
|
+
log("QA " + qaLoopSurface + "-loop guard passed: experiential evidence present");
|
|
2463
2580
|
}
|
|
2464
2581
|
|
|
2465
2582
|
// Deterministic closeout: no formatter agent. The verdict is mechanical
|
|
@@ -2683,9 +2800,12 @@ while (i < STEPS.length) {
|
|
|
2683
2800
|
captureTargets = ctm ? ctm[1].trim().slice(0, 300) : "";
|
|
2684
2801
|
}
|
|
2685
2802
|
|
|
2686
|
-
// Capture Sage's experiential flag (machine-read marker line)
|
|
2803
|
+
// Capture Sage's experiential flag (machine-read marker line) and the
|
|
2804
|
+
// terminal targets for terminal-surface tasks.
|
|
2687
2805
|
if (step.name === "Triage" && passed) {
|
|
2688
2806
|
isExperiential = extractExperiential(workerText);
|
|
2807
|
+
var ttm = /^terminal_targets:\s*(.+)/im.exec(workerText);
|
|
2808
|
+
terminalTargets = ttm ? ttm[1].trim().slice(0, 300) : "";
|
|
2689
2809
|
}
|
|
2690
2810
|
|
|
2691
2811
|
// Capture the accepted Build report's machine-readable release decision.
|