@azure-id/orc 1.9.2 → 2.0.2
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/CHANGELOG.md +321 -0
- package/README-id.md +21 -36
- package/README.md +25 -36
- package/bin/build-agents.js +117 -20
- package/bin/cli.js +725 -29
- package/bin/gotcha-import.js +1081 -0
- package/bin/gotcha.js +1286 -0
- package/bin/graph-query.js +1 -1
- package/bin/graph.js +717 -717
- package/bin/habit.js +1453 -0
- package/bin/mockrun-catalog.js +281 -276
- package/bin/run-undo.js +398 -0
- package/bin/trace-write.js +657 -0
- package/bin/verify-contracts.js +525 -79
- package/bin/verify-package.js +51 -4
- package/bin/webui/api.js +26 -0
- package/bin/webui/app.html +239 -232
- package/bin/webui/css/00-tokens.css +110 -92
- package/bin/webui/css/04-motion.css +87 -0
- package/bin/webui/css/06-responsive.css +203 -178
- package/bin/webui/css/panels/behaviour.css +205 -0
- package/bin/webui/fixtures/behaviour.js +532 -0
- package/bin/webui/fixtures/index.js +34 -0
- package/bin/webui/fixtures/knowledge.js +7 -1
- package/bin/webui/fixtures/stats.js +16 -0
- package/bin/webui/i18n/en/behaviour.json +143 -0
- package/bin/webui/i18n/en/nav.json +25 -24
- package/bin/webui/i18n/en/tour.json +37 -35
- package/bin/webui/i18n/id/behaviour.json +143 -0
- package/bin/webui/i18n/id/nav.json +25 -24
- package/bin/webui/i18n/id/tour.json +37 -35
- package/bin/webui/js/01-i18n.js +155 -154
- package/bin/webui/js/90-tour.js +498 -494
- package/bin/webui/js/91-shortcuts.js +126 -126
- package/bin/webui/js/99-boot.js +121 -118
- package/bin/webui/js/panels/behaviour.js +1022 -0
- package/mock-run/INDEX.md +109 -107
- package/mock-run/gotcha-import.md +118 -0
- package/mock-run/habits.md +129 -0
- package/mock-run/orc-quick.md +6 -1
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +7 -7
- package/templates/agents/orc-advisor-opus-5-xhigh.md +1 -7
- package/templates/agents/orc-analyze-mini-opus-5-med.md +1 -6
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +1 -4
- package/templates/agents/orc-claude-writer-opus-4-8-high.md +48 -53
- package/templates/agents/orc-claude-writer-opus-5-med.md +1 -8
- package/templates/agents/orc-context-combiner-opus-5-high.md +1 -11
- package/templates/agents/orc-executor-haiku-4-5.md +14 -6
- package/templates/agents/orc-executor-opus-4-7-high.md +14 -6
- package/templates/agents/orc-executor-opus-4-7-med.md +14 -6
- package/templates/agents/orc-executor-opus-4-8-high.md +14 -6
- package/templates/agents/orc-executor-opus-5-high.md +14 -6
- package/templates/agents/orc-executor-opus-5-low.md +14 -6
- package/templates/agents/orc-executor-opus-5-med.md +14 -6
- package/templates/agents/orc-executor-sonnet-4-6-high.md +14 -6
- package/templates/agents/orc-executor-sonnet-4-6-med.md +14 -6
- package/templates/agents/orc-executor-sonnet-5-high.md +14 -6
- package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -10
- package/templates/agents/orc-judge-opus-5-xhigh.md +5 -9
- package/templates/agents/orc-learn-writer-opus-5-low.md +1 -7
- package/templates/agents/orc-pattern-codifier-opus-5-med.md +1 -8
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +58 -63
- package/templates/agents/orc-planner-mini-opus-5-med.md +1 -4
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +1 -2
- package/templates/agents/orc-planner-opus-5-med.md +1 -4
- package/templates/agents/orc-recon-opus-5-low.md +1 -8
- package/templates/agents/orc-recon-sonnet-4-6-med.md +1 -8
- package/templates/agents/orc-retro-opus-5-med.md +6 -8
- package/templates/agents/orc-retro-sonnet-5-high.md +6 -7
- package/templates/agents/orc-reviewer-opus-5-med.md +52 -16
- package/templates/agents/orc-scout-opus-5-low.md +1 -6
- package/templates/agents/orc-scout-sonnet-4-6-high.md +35 -39
- package/templates/agents/orc-system-analyst-opus-5-high.md +1 -6
- package/templates/agents/orc-test-author-opus-5-med.md +4 -5
- package/templates/agents/orc-trace-writer-haiku-4-5.md +3 -7
- package/templates/agents/orc-verifier-opus-5-med.md +16 -8
- package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +74 -79
- package/templates/agents/orc-wiki-scanner-opus-5-med.md +1 -8
- package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +97 -106
- package/templates/commands/orc-analyze.md +13 -21
- package/templates/commands/orc-fast.md +10 -15
- package/templates/commands/orc-poly.md +12 -21
- package/templates/commands/orc-pr-driver.md +11 -30
- package/templates/commands/orc-pr-setup.md +10 -31
- package/templates/commands/orc-route.md +11 -41
- package/templates/commands/orc-test.md +5 -60
- package/templates/hooks/README.md +34 -0
- package/templates/hooks/orc-session-hook.js +264 -0
- package/templates/hooks/orc-statusline.js +3 -1
- package/templates/skills/_shared/README.md +9 -0
- package/templates/skills/_shared/code-graph.md +47 -55
- package/templates/skills/_shared/config-precedence.md +3 -1
- package/templates/skills/_shared/extra-dispatch.md +73 -88
- package/templates/skills/_shared/gotchas.md +228 -177
- package/templates/skills/_shared/habits.md +101 -0
- package/templates/skills/_shared/lane-contract.md +84 -0
- package/templates/skills/_shared/phases/README.md +142 -83
- package/templates/skills/_shared/phases/analyst-gates.md +10 -21
- package/templates/skills/_shared/phases/execution.md +8 -14
- package/templates/skills/_shared/phases/house-rules.md +27 -32
- package/templates/skills/_shared/phases/intake.md +127 -133
- package/templates/skills/_shared/phases/mock-example.md +46 -56
- package/templates/skills/_shared/phases/plan-handoff.md +91 -97
- package/templates/skills/_shared/phases/planning.md +7 -17
- package/templates/skills/_shared/phases/preflight.md +19 -42
- package/templates/skills/_shared/phases/review.md +23 -27
- package/templates/skills/_shared/phases/rules.md +18 -42
- package/templates/skills/_shared/phases/scoring.md +55 -65
- package/templates/skills/_shared/phases/security-checklist.md +46 -50
- package/templates/skills/_shared/phases/security.md +45 -55
- package/templates/skills/_shared/phases/ship.md +6 -15
- package/templates/skills/_shared/phases/stop-resume.md +2 -5
- package/templates/skills/_shared/phases/summary.md +73 -48
- package/templates/skills/_shared/phases/testgen.md +41 -51
- package/templates/skills/_shared/phases/trace-verbs.md +433 -0
- package/templates/skills/_shared/phases/trace.md +136 -367
- package/templates/skills/_shared/phases/verify.md +62 -70
- package/templates/skills/_shared/phases/wave-grouping.md +128 -133
- package/templates/skills/_shared/phases/wiki-consult.md +10 -6
- package/templates/skills/_shared/read-ladder.md +2 -55
- package/templates/skills/_shared/return-validation.md +17 -70
- package/templates/skills/_shared/review-slice.md +79 -0
- package/templates/skills/_shared/smoke-gate.md +46 -28
- package/templates/skills/context-combiner/SKILL.md +15 -44
- package/templates/skills/orc/SKILL.md +34 -62
- package/templates/skills/orc/references/pattern-gate.md +89 -89
- package/templates/skills/orc/references/phases/intake.md +41 -47
- package/templates/skills/orc/references/phases/integration.md +13 -19
- package/templates/skills/orc/references/preflight-report.md +8 -9
- package/templates/skills/orc/references/ultra-mode.md +8 -6
- package/templates/skills/orc/subskills/orc-execution/SKILL.md +27 -73
- package/templates/skills/orc/subskills/orc-execution/core.md +12 -99
- package/templates/skills/orc/subskills/orc-execution/subagent.md +14 -13
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +11 -52
- package/templates/skills/orc/subskills/orc-review-verify/core.md +52 -135
- package/templates/skills/orc/subskills/orc-review-verify/subagent.md +7 -7
- package/templates/skills/orc/subskills/orc-testgen/SKILL.md +11 -22
- package/templates/skills/orc/subskills/orc-testgen/core.md +20 -59
- package/templates/skills/orc/subskills/orc-testgen/subagent.md +7 -7
- package/templates/skills/orc-advisor/SKILL.md +56 -60
- package/templates/skills/orc-analyze/SKILL.md +30 -66
- package/templates/skills/orc-analyze/schemas/report-audit.md +2 -1
- package/templates/skills/orc-analyze/schemas/report-prose.md +2 -1
- package/templates/skills/orc-analyze-mini/SKILL.md +35 -70
- package/templates/skills/orc-diy/README.md +31 -0
- package/templates/skills/orc-diy/SKILL.md +23 -74
- package/templates/skills/orc-diy/references/blocks/pattern.md +18 -18
- package/templates/skills/orc-fast/SKILL.md +44 -72
- package/templates/skills/orc-judge/SKILL.md +77 -82
- package/templates/skills/orc-mini/SKILL.md +61 -109
- package/templates/skills/orc-pattern/SKILL.md +27 -48
- package/templates/skills/orc-poly/SKILL.md +32 -61
- package/templates/skills/orc-pr-driver/SKILL.md +25 -51
- package/templates/skills/orc-pr-driver/references/green-gate.md +113 -105
- package/templates/skills/orc-pr-setup/SKILL.md +20 -45
- package/templates/skills/orc-quick/README.md +43 -2
- package/templates/skills/orc-quick/SKILL.md +76 -107
- package/templates/skills/orc-quick/references/dispatch-gate.md +16 -5
- package/templates/skills/orc-quick/references/gh-mode.md +48 -1
- package/templates/skills/orc-quick/references/look.md +3 -1
- package/templates/skills/orc-retro/SKILL.md +19 -18
- package/templates/skills/orc-retro/examples/retro-mock.md +1 -1
- package/templates/skills/orc-route/SKILL.md +25 -45
- package/templates/skills/orc-test/SKILL.md +16 -37
- package/templates/skills/orc-verify/SKILL.md +14 -32
- package/templates/skills/orc-wait/SKILL.md +156 -163
- package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -6
- package/templates/skills/orc-wiki/references/phases/phase-1.md +1 -6
- package/templates/skills/orc-wiki/references/phases/phase-2.md +1 -6
- package/templates/skills/orc-wiki/references/phases/phase-3.md +1 -6
- package/templates/skills/orc-wiki/references/phases/phase-3c.md +1 -6
package/bin/cli.js
CHANGED
|
@@ -179,10 +179,19 @@ function writeStdoutSync(str) {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
let PROBE_CAPTURE = null;
|
|
183
|
+
|
|
182
184
|
// Print the object and (optionally) exit with the human path's code.
|
|
183
185
|
// `compact` (v1.9.1 B1) writes the answer with no indentation: a `--brief`
|
|
184
186
|
// answer is read by a model, and the card inside it carries its own newlines.
|
|
185
187
|
function emitJson(obj, exitCode, compact) {
|
|
188
|
+
// v2.0.0 T20 — an in-process probe (`orc lane config` `probes{}`) takes the
|
|
189
|
+
// object instead of stdout. Outside a probe this line does nothing.
|
|
190
|
+
if (PROBE_CAPTURE) {
|
|
191
|
+
PROBE_CAPTURE.obj = obj;
|
|
192
|
+
if (exitCode !== undefined) process.exit(exitCode);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
186
195
|
writeStdoutSync((compact ? JSON.stringify(obj) : JSON.stringify(obj, null, 2)) + "\n");
|
|
187
196
|
if (exitCode !== undefined) process.exit(exitCode);
|
|
188
197
|
}
|
|
@@ -427,6 +436,35 @@ function installGuards(claudeDir) {
|
|
|
427
436
|
wireGraph("PreToolUse", "Bash|Read");
|
|
428
437
|
wireGraph("PostToolUse", "Read");
|
|
429
438
|
|
|
439
|
+
// 7) The session hook (v2.0.0 Q8/Q9) — TWO entries, one file. `Stop` rings
|
|
440
|
+
// the terminal bell when a turn of an ORC run ends, and only under
|
|
441
|
+
// `notify: bell` (default off: the hook reads the key and returns, so an
|
|
442
|
+
// unarmed bell is byte-identical to not having it — the read-gate rule).
|
|
443
|
+
// `SessionStart` on `compact` prints ONE line naming the run's
|
|
444
|
+
// state-of-play.md while a run is in flight. Same (event, matcher) match as
|
|
445
|
+
// the graph hook, so re-running `orc update` changes nothing.
|
|
446
|
+
const sessionHookCmd = nodeCmd(path.join(hooksDest, "orc-session-hook.js"));
|
|
447
|
+
const wireSession = (arrName, matcher) => {
|
|
448
|
+
settings.hooks[arrName] = settings.hooks[arrName] || [];
|
|
449
|
+
for (const entry of settings.hooks[arrName]) {
|
|
450
|
+
if ((entry.matcher || null) !== (matcher || null)) continue;
|
|
451
|
+
for (const h of entry.hooks || []) {
|
|
452
|
+
if (typeof h.command === "string" && h.command.includes("orc-session-hook")) {
|
|
453
|
+
h.command = sessionHookCmd;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const entry = { hooks: [{ type: "command", command: sessionHookCmd }] };
|
|
459
|
+
if (matcher) entry.matcher = matcher;
|
|
460
|
+
settings.hooks[arrName].push(entry);
|
|
461
|
+
console.log(` add settings.json → ${arrName} session hook${matcher ? ` (${matcher})` : ""}`);
|
|
462
|
+
};
|
|
463
|
+
wireSession("Stop", null);
|
|
464
|
+
wireSession("SessionStart", "compact");
|
|
465
|
+
// v2.0.2 (eval D13): the reviewer / verifier / judge get the gotcha card here.
|
|
466
|
+
wireSession("SubagentStart", null);
|
|
467
|
+
|
|
430
468
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
431
469
|
}
|
|
432
470
|
|
|
@@ -1130,6 +1168,15 @@ const vFallbackAgent = tag((raw) => {
|
|
|
1130
1168
|
"pinned to one agent — this overrides the score table AND a slot's own pinned agent for every fallback, whatever the task. `band` is what keeps a fallback a change of WHO and not a change of tier.",
|
|
1131
1169
|
};
|
|
1132
1170
|
}, { kind: "text", choices: ["band", "ask", "orc-executor-opus-5-med", "orc-executor-opus-5-low", "orc-executor-sonnet-4-6-high"] });
|
|
1171
|
+
// v2.0.0 W2 (DE-16) — `auto` is refused BY NAME. It is the one value a reader
|
|
1172
|
+
// of the research expects, and ORC does not have it: no habit is ever applied
|
|
1173
|
+
// without a yes, so a generic "must be one of" would read as a missing feature.
|
|
1174
|
+
const vHabits = tag((raw) => {
|
|
1175
|
+
const v = String(raw || "").trim();
|
|
1176
|
+
if (v === "auto")
|
|
1177
|
+
return { err: "`auto` is not a habits level. ORC never applies a habit without your yes. Use off, observe or propose." };
|
|
1178
|
+
return ["off", "observe", "propose"].includes(v) ? { value: v } : { err: "must be one of: off, observe, propose" };
|
|
1179
|
+
}, { kind: "enum", choices: ["off", "observe", "propose"] });
|
|
1133
1180
|
const vPath = tag(
|
|
1134
1181
|
(raw) => (raw && raw.trim() ? { value: raw } : { err: "must be a non-empty path" }),
|
|
1135
1182
|
{ kind: "path" }
|
|
@@ -1161,6 +1208,26 @@ const vRepo = tag(
|
|
|
1161
1208
|
: { err: "must be a GitHub owner/repo (e.g. azure-id/orc)" },
|
|
1162
1209
|
{ kind: "repo" }
|
|
1163
1210
|
);
|
|
1211
|
+
// v2.0.0 W4 — the reviewer card budget. Refused BY NAME below the floor: the
|
|
1212
|
+
// budget is a size, and review learning has no off switch (DE-10).
|
|
1213
|
+
const vCardBudget = tag((raw) => {
|
|
1214
|
+
const n = Number(raw);
|
|
1215
|
+
if (!Number.isInteger(n)) return { err: "gotcha_card_budget must be an integer >= 200" };
|
|
1216
|
+
if (n < 200)
|
|
1217
|
+
return { err: `gotcha_card_budget must be 200 or more (got ${n}). It is the SIZE of the reviewer card, not an off switch — review learning is always on` };
|
|
1218
|
+
return { value: n };
|
|
1219
|
+
}, { kind: "int", min: 200 });
|
|
1220
|
+
// An optional one-word value: empty clears it.
|
|
1221
|
+
const vOptText = tag((raw) => {
|
|
1222
|
+
const s = String(raw == null ? "" : raw).trim();
|
|
1223
|
+
if (/\s/.test(s)) return { err: "must be one word (no spaces)" };
|
|
1224
|
+
return { value: s };
|
|
1225
|
+
}, { kind: "text" });
|
|
1226
|
+
const vSonarUrl = tag((raw) => {
|
|
1227
|
+
const s = String(raw == null ? "" : raw).trim().replace(/\/+$/, "");
|
|
1228
|
+
if (s && !/^https?:\/\/[^\s/]+(\/\S*)?$/.test(s)) return { err: "must be an http(s) URL (e.g. https://sonarcloud.io)" };
|
|
1229
|
+
return { value: s };
|
|
1230
|
+
}, { kind: "text" });
|
|
1164
1231
|
|
|
1165
1232
|
// Roles `orc extra` may hand to a non-Claude worker. Declared HERE, above
|
|
1166
1233
|
// CONFIG_META, because CONFIG_META is evaluated at module load and a `const`
|
|
@@ -1319,6 +1386,14 @@ const CONFIG_FAMILIES = {
|
|
|
1319
1386
|
// structure costs no model tokens, so nothing about a dispatched model can
|
|
1320
1387
|
// shadow whether it is built.
|
|
1321
1388
|
graph: { contested: false, question: "whether code lanes build and read a local map of how the repo is connected" },
|
|
1389
|
+
// v2.0.0 W2 — habits. UNCONTESTED: an accepted habit is not a key, it is the
|
|
1390
|
+
// `learned` RANK under your config file (bin/habit.js), so nothing here
|
|
1391
|
+
// competes with anything. `offers` holds the end-of-run offers a habit can
|
|
1392
|
+
// answer; before 2.0.0 no key existed, so nothing could remember them.
|
|
1393
|
+
habits: { contested: false, question: "whether ORC learns your usual answers and proposes them — never applied without your yes" },
|
|
1394
|
+
offers: { contested: false, question: "which offers a lane still asks after the work, and which your setting answers" },
|
|
1395
|
+
notify: { contested: false, question: "whether a turn of an ORC run ends with the terminal bell" },
|
|
1396
|
+
rules: { contested: false, question: "how large the rules card is in an executor slice" },
|
|
1322
1397
|
};
|
|
1323
1398
|
|
|
1324
1399
|
// Ordered, tiered metadata. Common first, then advanced.
|
|
@@ -1348,6 +1423,15 @@ const CONFIG_META = [
|
|
|
1348
1423
|
{ key: "pattern_findings", def: "ask", tier: "common", answers: [{ family: "patterns", prio: "P2", mode: "replace" }], lanes: ["orc", "orc-pattern", "orc-wiki"], validate: vEnum("ask", "on", "off"), options: ["ask", "on", "off"], desc: "Code-pattern gate on an FE/BE cache miss: ask = prompt, on = auto-codify, off = always agnostic." },
|
|
1349
1424
|
{ key: "gotchas", def: "on", tier: "common", answers: [{ family: "gotchas", prio: "P2", mode: "replace" }], lanes: ["orc", "orc-brainstorm", "orc-claude", "orc-diy", "orc-doc", "orc-fast", "orc-learn", "orc-mini", "orc-retro", "orc-wiki"], validate: vEnum("on", "off"), options: ["on", "off"], desc: "Repair memory: record a gotcha when a repair loop goes red → green, and inject the scope-matching ones into executor slices. Never injected unfiltered; see .claude/orc/gotchas.md." },
|
|
1350
1425
|
{ key: "gotchas_max", def: 40, tier: "common", answers: [{ family: "gotchas", prio: "P2", mode: "replace" }], lanes: ["orc"], validate: vInt(5), options: [20, 40, 60, 100], desc: "Live gotcha entries kept before the lowest-value tail is archived to gotchas-archive.md (never deleted)." },
|
|
1426
|
+
// --- v2.0.0 W4 — the gotchas engine (bin/gotcha.js) ---------------------------
|
|
1427
|
+
// Read by the CLI (`orc gotcha card`, `import`, `sync`), never by a spine,
|
|
1428
|
+
// so each `lanes[]` is empty and each key is on the seed-empty allowlist.
|
|
1429
|
+
// Review learning is ALWAYS ON (DE-10): the budget is a SIZE, never a switch.
|
|
1430
|
+
{ key: "gotcha_card_budget", def: 600, tier: "common", answers: [{ family: "gotchas", prio: "P2", mode: "replace" }], lanes: [], validate: vCardBudget, options: [200, 400, 600, 900], desc: "The size of the reviewer card from `orc gotcha card`, in tokens. It is a SIZE, never an off switch: review learning is always on. The minimum is 200; a lower value is refused. Entries that do not fit are COUNTED in the card header, never dropped in silence." },
|
|
1431
|
+
{ key: "gotcha_sync_hours", def: 6, tier: "advanced", answers: [{ family: "gotchas", prio: "P2", mode: "replace" }], lanes: [], validate: vInt(1), options: [1, 6, 24], desc: "How old the last `orc gotcha sync` may be before the next review step runs it again." },
|
|
1432
|
+
{ key: "sonar_url", def: "", tier: "advanced", answers: [{ family: "gotchas", prio: "P2", mode: "replace" }], lanes: [], validate: vSonarUrl, desc: "The SonarQube / SonarCloud base URL for `orc gotcha import sonar` (e.g. https://sonarcloud.io). The token is NEVER a key: set SONAR_TOKEN in the environment." },
|
|
1433
|
+
{ key: "sonar_project", def: "", tier: "advanced", answers: [{ family: "gotchas", prio: "P2", mode: "replace" }], lanes: [], validate: vOptText, desc: "The Sonar project key for `orc gotcha import sonar`, so the import needs no flags." },
|
|
1434
|
+
{ key: "sonar_org", def: "", tier: "advanced", answers: [{ family: "gotchas", prio: "P2", mode: "replace" }], lanes: [], validate: vOptText, desc: "The SonarCloud organization for `orc gotcha import sonar` (empty for a self-hosted SonarQube)." },
|
|
1351
1435
|
{ key: "security_review", def: "off", tier: "common", answers: [{ family: "security", prio: "P2", mode: "replace" }], lanes: ["orc"], validate: vEnum("off", "ask", "on"), options: ["off", "ask", "on"], desc: "Opt-in Phase 5.5 security pass on runs with a task scored >= 70 (risk floor). OFF by default." },
|
|
1352
1436
|
{ key: "run_budget_dispatches", def: 0, tier: "common", answers: [{ family: "waves", prio: "P2", mode: "replace" }], lanes: ["orc", "orc-budget"], validate: vInt(0), options: [0, 8, 12, 20, 30], desc: "Subagent budget for one run. The Phase-1 forecast estimates how many subagents the run will dispatch; if that exceeds this number the run STOPS before wave 1 (a hard gate like the batch pause, not a hint) and offers proceed / a cheaper lane / re-plan smaller. 0 = off, nothing changes." },
|
|
1353
1437
|
{ key: "mock_example", def: "ask", tier: "common", answers: [{ family: "mock", prio: "P2", mode: "replace" }], lanes: ["orc", "orc-brainstorm", "orc-diy", "orc-fast", "orc-grill", "orc-mini"], validate: vEnum("ask", "on", "off"), options: ["ask", "on", "off"], desc: "Post-verify mocked runnable example (mock-examples/<slug>/, never committed): ask = offer after a green verify, on = always, off = never." },
|
|
@@ -1477,7 +1561,23 @@ const CONFIG_META = [
|
|
|
1477
1561
|
{ key: "test_max_rps", def: 4, tier: "common", answers: [{ family: "test", prio: "P2", mode: "replace" }], lanes: [], validate: vInt(1), options: [1, 2, 4, 8], desc: "Requests per second the runner is allowed to send, with a fixed small concurrency beside it. The CLI PACES — this is not a suggestion, because ORC's own run must never be the incident. A 429 is a RESULT and not an error: it means rate limiting works, so the runner records it and backs off rather than pushing through." },
|
|
1478
1562
|
{ key: "test_case_budget", def: 200, tier: "common", answers: [{ family: "test", prio: "P2", mode: "replace" }], lanes: ["orc-test"], validate: vInt(1), options: [50, 100, 200, 500], desc: "Cases one run may expand to before it STOPS expanding. A PLANNED stop, not an interrupt (the wiki_refresh_budget shape): the matrix is combinatorial, and a run that silently grows to 4,000 cases against a staging box is a denial of service you wrote yourself. On reaching it the CLI names the targets it did not finish and offers `--budget`." },
|
|
1479
1563
|
{ key: "test_ui_driver", def: "playwright", tier: "common", answers: [{ family: "test", prio: "P2", mode: "replace" }], lanes: ["orc-test"], validate: vEnum("playwright", "none"), options: ["playwright", "none"], desc: "What drives the front-end half. `playwright` authors a journey SCRIPT and runs it — a step an LLM took is not a step you can re-run, and the artifacts (trace.zip, video, HAR) are the evidence. `none` skips the FE half entirely. ORC ships zero dependencies and NEVER installs Playwright into your project: an absent driver is a STATE with a named install command, not a failure." },
|
|
1564
|
+
// --- v2.0.0 W2 — habits (bin/habit.js) -------------------------------------
|
|
1565
|
+
// `habits` is read by the CLI, never by a lane: `orc lane config` resolves it
|
|
1566
|
+
// and hands the lane a `habits{}` block (the `code_graph` answer). The other
|
|
1567
|
+
// three are the offers a habit can answer; W3 wires the lanes that read them,
|
|
1568
|
+
// so their `lanes[]` is empty until then. OFF by default, and off is
|
|
1569
|
+
// specified as ZERO BYTES in `orc lane config` — that is a test.
|
|
1570
|
+
{ key: "habits", def: "off", tier: "common", answers: [{ family: "habits", prio: "P2", mode: "replace" }], lanes: [], validate: vHabits, options: ["off", "observe", "propose"], desc: "Whether ORC learns the answers you give to its questions, from the ASK lines in your run traces. off = nothing is read and nothing changes (the default). observe = ORC computes your usual answers and shows them in `orc habit show`, and never proposes one. propose = at the END of a run, at most once, ORC asks whether an answer you keep giving should become your usual. NOTHING is applied without your yes, and there is no automatic level. An accepted habit is the `learned` rank, below your own config file: `orc config set` always wins, and `orc habit forget <id>` undoes it." },
|
|
1571
|
+
{ key: "review_before_push", def: "ask", tier: "common", answers: [{ family: "offers", prio: "P2", mode: "replace" }], lanes: [], validate: vEnum("ask", "on", "off"), options: ["ask", "on", "off"], desc: "Whether a lane offers a code review before it pushes. ask = offer it (the default), on = always review first, off = never offer it. A habit can learn only `on`." },
|
|
1572
|
+
{ key: "mini_tdd", def: "ask", tier: "common", answers: [{ family: "testing", prio: "P2", mode: "replace" }], lanes: [], validate: vEnum("ask", "on", "off"), options: ["ask", "on", "off"], desc: "Whether /orc-mini asks at intake for a test-driven loop. ask = ask (the default), on = always use it, off = never. A habit can learn only `on`." },
|
|
1573
|
+
{ key: "quick_update_tests", def: "ask", tier: "common", answers: [{ family: "offers", prio: "P2", mode: "replace" }], lanes: [], validate: vEnum("ask", "on"), options: ["ask", "on"], desc: "Whether /orc-quick offers to update the tests after a code change. ask = offer it (the default), on = always update them. There is no `off`: tests are the careful side. It affects only the offer after the work, never the dispatch gate." },
|
|
1574
|
+
// v2.0.0 W6c — Q8. The terminal bell when a turn of an ORC run ends. The hook is
|
|
1575
|
+
// wired always and reads this key first, so off is byte-identical to no hook.
|
|
1576
|
+
{ key: "notify", def: "off", tier: "common", answers: [{ family: "notify", prio: "P2", mode: "replace" }], lanes: [], validate: vEnum("off", "bell"), options: ["off", "bell"], desc: "Whether the installed session hook rings the terminal bell when a turn of an ORC run ends. off = silent (the default). bell = one bell through the Stop hook, only while an ORC run is open in this session and only when the run moved since the last bell, so a normal chat never rings. No OS notification and no network." },
|
|
1480
1577
|
{ key: "doc_dir", def: DOC_DIR_DEFAULT, tier: "advanced", answers: [{ family: "paths", prio: "P2", mode: "replace" }], lanes: ["orc-doc"], validate: vPath, desc: "Where /orc-doc folders live. Project root, not .claude/ — a document is a deliverable a human opens, and the same call /orc-quick, /orc-brainstorm and poly-repo-implementation/ already made." },
|
|
1578
|
+
// v2.0.0 W6c — DE-17. The compact rules card for orc-mini / orc-fast executors.
|
|
1579
|
+
// OFF until eval E4 passes (eval/results/2.0.0/E4.md); off = today's card, byte for byte.
|
|
1580
|
+
{ key: "rules_card_compact", def: "on", tier: "advanced", answers: [{ family: "rules", prio: "P2", mode: "replace" }], lanes: [], validate: vEnum("off", "on"), options: ["off", "on"], desc: "Whether `orc rules slice` builds the COMPACT rules card for the orc-mini and orc-fast lanes too (orc-quick always gets it). The compact card keeps every rule id and the first line of each HARD rule, and drops the worked examples. on (the default since v2.0.2 — eval E4 passed: no new unmet[] items, no new rules_conflicts[], the smoke gate green on the first try at least as often). off = the full card, byte-identical to 2.0.0." },
|
|
1481
1581
|
{ key: "wiki_scan_tier", def: "ladder", tier: "advanced", answers: [{ family: "wiki", prio: "P2", mode: "replace" }], lanes: ["orc-wiki"], validate: vEnum("ladder", "always_deep"), desc: "Wiki scan tier: ladder picks light/deep per delta (first scan, STRUCTURAL, wide delta or a new exported symbol → deep; otherwise light), always_deep restores pre-v0.46.0 behaviour. The resolved tier is always printed — a cheaper model is never a quiet substitution." },
|
|
1482
1582
|
{ key: "wiki_tier_deep_files", def: 3, tier: "advanced", answers: [{ family: "wiki", prio: "P2", mode: "replace" }], lanes: ["orc-wiki"], validate: vInt(1), desc: "Covered files touched at or above this count send the refresh to the DEEP scanner." },
|
|
1483
1583
|
{ key: "wiki_refresh_budget", def: 0, tier: "advanced", answers: [{ family: "wiki", prio: "P2", mode: "replace" }], lanes: ["orc-wiki"], validate: vInt(0), desc: "Max scan-tasks per refresh run; 0 = no cap. A capped refresh is a PLANNED stop, not an interrupt: sync has already run, so the wiki is registered and consistent, and the remaining docs are AGING, not broken. Separate from the fixed pause-every-5 rule — do not merge them." },
|
|
@@ -1827,7 +1927,7 @@ function groupRule(title) {
|
|
|
1827
1927
|
// `not-read`: a higher rank in this key's family resolved, so nobody looked;
|
|
1828
1928
|
// `inert`: this key's master gate is off, which removes it from the
|
|
1829
1929
|
// conversation rather than losing it a precedence contest (`design-03` §5).
|
|
1830
|
-
function configKeyState(m, map, claudeDir) {
|
|
1930
|
+
function configKeyState(m, map, claudeDir, learned) {
|
|
1831
1931
|
const has = Object.prototype.hasOwnProperty.call(map, m.key);
|
|
1832
1932
|
if (m.gated_by) {
|
|
1833
1933
|
const g = metaFor(m.gated_by);
|
|
@@ -1841,14 +1941,35 @@ function configKeyState(m, map, claudeDir) {
|
|
|
1841
1941
|
}
|
|
1842
1942
|
const why = shadowReason(m.key, map, claudeDir);
|
|
1843
1943
|
if (why) return { state: "not-read", reason: why, source: has ? "overridden" : "default" };
|
|
1944
|
+
// v2.0.0 W2 — the `learned` rank: below the file, above the shipped default.
|
|
1945
|
+
const l = !has && learned && learned[m.key];
|
|
1946
|
+
if (l)
|
|
1947
|
+
return {
|
|
1948
|
+
state: "learned",
|
|
1949
|
+
reason: `learned from habit ${l.id} (${l.count} of ${l.total}) — undo: orc habit forget ${l.id}`,
|
|
1950
|
+
source: `learned:${l.id}`,
|
|
1951
|
+
value: l.value,
|
|
1952
|
+
};
|
|
1844
1953
|
return { state: has ? "overridden" : "default", reason: null, source: has ? "overridden" : "default" };
|
|
1845
1954
|
}
|
|
1846
1955
|
|
|
1956
|
+
// The learned values in force project-wide, or {} under `habits: off`.
|
|
1957
|
+
function configLearned(map, claudeDir) {
|
|
1958
|
+
const H = require("./habit.js");
|
|
1959
|
+
if (H.habitsMode(map) === "off") return {};
|
|
1960
|
+
try {
|
|
1961
|
+
return H.learnedFor(claudeDir, habitDeps(), map, null);
|
|
1962
|
+
} catch (_) {
|
|
1963
|
+
return {};
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1847
1967
|
const STATE_PAINT = {
|
|
1848
1968
|
overridden: (s) => ui.color.green(s),
|
|
1849
1969
|
default: (s) => ui.color.gray(s),
|
|
1850
1970
|
"not-read": (s) => ui.color.yellow(s),
|
|
1851
1971
|
inert: (s) => ui.color.yellow(s),
|
|
1972
|
+
learned: (s) => ui.color.cyan(s),
|
|
1852
1973
|
};
|
|
1853
1974
|
|
|
1854
1975
|
function configList(claudeDir) {
|
|
@@ -1863,6 +1984,7 @@ function configList(claudeDir) {
|
|
|
1863
1984
|
// UI axis and it still drives the interactive menu, so it rides along as a row
|
|
1864
1985
|
// marker instead of being the table's spine.
|
|
1865
1986
|
const families = laneFamilies(null, map, claudeDir);
|
|
1987
|
+
const learned = configLearned(map, claudeDir);
|
|
1866
1988
|
const byFamily = new Map();
|
|
1867
1989
|
for (const m of CONFIG_META) {
|
|
1868
1990
|
const f = m.answers[0].family;
|
|
@@ -1891,9 +2013,9 @@ function configList(claudeDir) {
|
|
|
1891
2013
|
return (ra === -1 ? 99 : ra) - (rb === -1 ? 99 : rb);
|
|
1892
2014
|
});
|
|
1893
2015
|
for (const m of metas) {
|
|
1894
|
-
const st = configKeyState(m, map, claudeDir);
|
|
2016
|
+
const st = configKeyState(m, map, claudeDir, learned);
|
|
1895
2017
|
const has = Object.prototype.hasOwnProperty.call(map, m.key);
|
|
1896
|
-
const val = has ? map[m.key] : m.def;
|
|
2018
|
+
const val = has ? map[m.key] : st.value !== undefined ? st.value : m.def;
|
|
1897
2019
|
const src = (STATE_PAINT[st.state] || ((s) => s))(st.state.padEnd(10));
|
|
1898
2020
|
const opts = m.options ? ` ${ui.color.gray("[options: " + m.options.join(" | ") + "]")}` : "";
|
|
1899
2021
|
// v0.55.0 — a DEPRECATED member of a live key is marked on the row that
|
|
@@ -2195,10 +2317,11 @@ function scoreTableJson(map, claudeDir) {
|
|
|
2195
2317
|
function configListJson(claudeDir) {
|
|
2196
2318
|
const { path: p, map } = readOverride(claudeDir);
|
|
2197
2319
|
const has = (k) => Object.prototype.hasOwnProperty.call(map, k);
|
|
2320
|
+
const learned = configLearned(map, claudeDir);
|
|
2198
2321
|
const keys = CONFIG_META.map((m) => {
|
|
2199
2322
|
const v = m.validate || {};
|
|
2200
2323
|
const why = shadowReason(m.key, map, claudeDir);
|
|
2201
|
-
const st = configKeyState(m, map, claudeDir);
|
|
2324
|
+
const st = configKeyState(m, map, claudeDir, learned);
|
|
2202
2325
|
return {
|
|
2203
2326
|
key: m.key,
|
|
2204
2327
|
tier: m.tier,
|
|
@@ -2217,7 +2340,7 @@ function configListJson(claudeDir) {
|
|
|
2217
2340
|
// The master gate that makes this key inert. A gate does not win a
|
|
2218
2341
|
// precedence contest; it removes its dependants from the conversation.
|
|
2219
2342
|
gated_by: m.gated_by || null,
|
|
2220
|
-
value: has(m.key) ? map[m.key] : m.def,
|
|
2343
|
+
value: has(m.key) ? map[m.key] : st.value !== undefined ? st.value : m.def,
|
|
2221
2344
|
default: m.def,
|
|
2222
2345
|
is_overridden: has(m.key),
|
|
2223
2346
|
is_shadowed: !!why,
|
|
@@ -2228,7 +2351,7 @@ function configListJson(claudeDir) {
|
|
|
2228
2351
|
// answers "does anything read it", from `LANE_RANK_STATES`' own words.
|
|
2229
2352
|
state: st.state,
|
|
2230
2353
|
state_reason: st.reason,
|
|
2231
|
-
source:
|
|
2354
|
+
source: st.source,
|
|
2232
2355
|
desc: m.desc,
|
|
2233
2356
|
options: m.options || null,
|
|
2234
2357
|
control: {
|
|
@@ -2292,7 +2415,7 @@ function configListJson(claudeDir) {
|
|
|
2292
2415
|
// lane-scoped answer asks `orc lane config <lane> --json`, which is the
|
|
2293
2416
|
// command that owns it.
|
|
2294
2417
|
families_resolved: laneFamilies(null, map, claudeDir),
|
|
2295
|
-
rank_states: LANE_RANK_STATES,
|
|
2418
|
+
rank_states: require("./habit.js").habitsMode(map) !== "off" ? LANE_RANK_STATES.concat("learned") : LANE_RANK_STATES,
|
|
2296
2419
|
// Permanently on and deliberately not a key — say so, or a reader hunts for
|
|
2297
2420
|
// the switch (only the folder is configurable).
|
|
2298
2421
|
behavior_trace: { always_on: true, configurable_key: "log_dir" },
|
|
@@ -2358,6 +2481,14 @@ function configSet(claudeDir, key, rawValue) {
|
|
|
2358
2481
|
// precedent: a renamed mechanism must never be a silent revert.
|
|
2359
2482
|
if (key === "extra_roles") extraLegacyRoleWarn(res.value);
|
|
2360
2483
|
if (key === "opus5_only") opus5Notice(String(res.value) === "true", claudeDir);
|
|
2484
|
+
// DE-27 — the v0.40.0 key stays so an existing file is never silently
|
|
2485
|
+
// overridden, but it is not a feature switch any more.
|
|
2486
|
+
if (key === "gotchas" && String(res.value) === "off")
|
|
2487
|
+
console.error(
|
|
2488
|
+
" ⚠ review learning is designed to stay on. `gotchas: off` is kept for compatibility: it stops the\n" +
|
|
2489
|
+
" gotcha block in EXECUTOR slices only. The reviewer card (`orc gotcha card`) is always built.\n" +
|
|
2490
|
+
" To make the card smaller, set gotcha_card_budget (min 200)."
|
|
2491
|
+
);
|
|
2361
2492
|
if (key === "opus5_only" || key === "extra_enabled") extraShadowNotice(claudeDir);
|
|
2362
2493
|
}
|
|
2363
2494
|
|
|
@@ -3234,9 +3365,26 @@ function laneConfig(lane, claudeDir) {
|
|
|
3234
3365
|
inert_reason: hit ? hit.reason : gate ? `${m.gated_by} is off, so nothing in its block is consulted` : null,
|
|
3235
3366
|
};
|
|
3236
3367
|
});
|
|
3368
|
+
// v2.0.0 W2 — the `learned` rank (bin/habit.js, DE-6). `habits: off` (the
|
|
3369
|
+
// default) adds ZERO BYTES: the habit keys leave `not_read`, no key gains a
|
|
3370
|
+
// field, and there is no `habits` block — the answer is the one 1.9.2 gave.
|
|
3371
|
+
const H = require("./habit.js");
|
|
3372
|
+
const hmode = H.habitsMode(map);
|
|
3373
|
+
let hab = null;
|
|
3374
|
+
if (hmode !== "off") {
|
|
3375
|
+
hab = H.laneBlock(claudeDir, habitDeps(), map, lane);
|
|
3376
|
+
for (const k of keys) {
|
|
3377
|
+
const l = !has(k.key) && hab.learned[k.key];
|
|
3378
|
+
if (l) k.value = l.value;
|
|
3379
|
+
k.source = has(k.key) ? "overridden" : l ? `learned:${l.id}` : "default";
|
|
3380
|
+
k.state = k.is_inert ? "inert" : k.is_shadowed ? "not-read" : l ? "learned" : has(k.key) ? "overridden" : "default";
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3237
3383
|
const effective = {};
|
|
3238
3384
|
for (const k of keys) effective[k.key] = k.value;
|
|
3239
|
-
|
|
3385
|
+
const announce = laneAnnounce(lane, map, claudeDir, families);
|
|
3386
|
+
if (hab) for (const k of keys) if (k.state === "learned") announce.push(H.announceLine(k.key, hab.learned[k.key]));
|
|
3387
|
+
const out = {
|
|
3240
3388
|
lane,
|
|
3241
3389
|
command: (LANES.find((l) => l.lane === lane) || {}).command || null,
|
|
3242
3390
|
command_note: (LANES.find((l) => l.lane === lane) || {}).note || null,
|
|
@@ -3247,16 +3395,31 @@ function laneConfig(lane, claudeDir) {
|
|
|
3247
3395
|
effective,
|
|
3248
3396
|
families,
|
|
3249
3397
|
roles: laneRoles(lane, map, claudeDir),
|
|
3250
|
-
announce
|
|
3398
|
+
announce,
|
|
3251
3399
|
stops: LANE_STOPS.filter((s) => rows.some((m) => m.key === s.key))
|
|
3252
3400
|
.filter((s) => s.arms(has(s.key) ? map[s.key] : metaFor(s.key).def))
|
|
3253
3401
|
.map((s) => ({ key: s.key, value: has(s.key) ? map[s.key] : metaFor(s.key).def, when: s.when, action: s.action })),
|
|
3254
3402
|
// The keys this lane deliberately ignores. An empty answer is an ANSWER
|
|
3255
3403
|
// (v0.43.0): "this lane does not read doc_language" is information, and it
|
|
3256
3404
|
// is what makes the two-way lint possible at all.
|
|
3257
|
-
not_read: CONFIG_META.filter((m) => !(m.lanes || []).includes(lane))
|
|
3258
|
-
|
|
3405
|
+
not_read: CONFIG_META.filter((m) => !(m.lanes || []).includes(lane))
|
|
3406
|
+
.filter((m) => hab || !H.HABIT_KEYS.includes(m.key))
|
|
3407
|
+
.map((m) => m.key),
|
|
3408
|
+
rank_states: hab ? LANE_RANK_STATES.concat("learned") : LANE_RANK_STATES,
|
|
3259
3409
|
};
|
|
3410
|
+
if (hab) out.habits = hab.block;
|
|
3411
|
+
else for (const f of H.HABIT_FAMILIES) delete out.families[f];
|
|
3412
|
+
return out;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
// The helpers `bin/habit.js` borrows, so it REUSES the trace listing and the
|
|
3416
|
+
// timestamp parser `orc stats` / `orc run list` already use.
|
|
3417
|
+
function runUndoDeps() {
|
|
3418
|
+
return { args, wantsJson, emitJson, resolveClaudeDir, resolveRunDir, ensureRunDir, repoRootOf };
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
function habitDeps() {
|
|
3422
|
+
return { flag, positionals, emitJson, wantsJson, resolveClaudeDir, readOverride, listTraces, traceTs };
|
|
3260
3423
|
}
|
|
3261
3424
|
|
|
3262
3425
|
function laneList(claudeDir) {
|
|
@@ -3330,6 +3493,19 @@ const LANE_CALLS = {
|
|
|
3330
3493
|
never: "never read `clear` as proof that an AD-HOC dispatch finished — the hook writes no SPAWN for one, so no record exists",
|
|
3331
3494
|
lanes: ["orc", "orc-doc", "orc-fast", "orc-mini", "orc-quick", "orc-test", "orc-wiki"],
|
|
3332
3495
|
},
|
|
3496
|
+
// v2.0.0 T21 — the CLI holds the trace pen: one packet → the .txt AND the .jsonl.
|
|
3497
|
+
"trace-write": {
|
|
3498
|
+
cmd: "orc trace write --packet - [--json]",
|
|
3499
|
+
what: "write ONE phase packet (stdin, plain YAML or JSON) into the run's trace pair",
|
|
3500
|
+
exits: { 0: "written", 1: "usage or unreadable input", 2: "invalid packet — unknown verb, bad ts, parse error; nothing written", 3: "trace-file state — no `.current` on a later packet, or a rename target in the way; nothing written" },
|
|
3501
|
+
states: null,
|
|
3502
|
+
cost: "free",
|
|
3503
|
+
when: "at every phase close, in the same tool block as the next phase's first dispatch",
|
|
3504
|
+
on_absent: "exit ≠ 0 → dispatch `orc-trace-writer-haiku-4-5` with the SAME packet (the fallback)",
|
|
3505
|
+
canonical: "_shared/phases/trace.md",
|
|
3506
|
+
never: "never append trace lines by hand, and never retry a refused packet with a \"now\" timestamp",
|
|
3507
|
+
lanes: ["orc", "orc-fast", "orc-mini", "orc-quick", "orc-test"],
|
|
3508
|
+
},
|
|
3333
3509
|
"lane-phases": {
|
|
3334
3510
|
cmd: "orc lane phases <lane> [--json]",
|
|
3335
3511
|
what: "which SHARED phases this lane runs, in order — the file, the layers to read, and when",
|
|
@@ -3938,7 +4114,7 @@ function laneCallsCmd(lane, claudeDir) {
|
|
|
3938
4114
|
//
|
|
3939
4115
|
// Per phase: \`file\` + \`layers\` (never a line number — /orc-doc rule 2, a
|
|
3940
4116
|
// stored line number is a wrong line number one edit later), \`when\` and
|
|
3941
|
-
// \`read\` (the partial-read declarations registered in \`_shared/
|
|
4117
|
+
// \`read\` (the partial-read declarations registered in \`_shared/phases/README.md\`
|
|
3942
4118
|
// at W10), \`optional_when\` (a config KEY, resolved through the priority ladder
|
|
3943
4119
|
// by \`orc lane config\`, never read raw), and \`calls\` — catalogue ids from
|
|
3944
4120
|
// \`LANE_CALLS\`, DERIVED rather than restated, so a lane can ask for one
|
|
@@ -3966,9 +4142,25 @@ const PHASE_FILES = {
|
|
|
3966
4142
|
why_single_layer: null,
|
|
3967
4143
|
why_two_layers:
|
|
3968
4144
|
"every other lane READS the protocol; orc-diy STITCHES a flow-shaped restatement of it into FLOW-COMPILED.md, and a composed flow owes one packet per ENABLED phase group rather than per phase",
|
|
4145
|
+
// v2.0.0 T1 — the verbs are NOT here: every lane owes TRACE_ALWAYS_VERBS,
|
|
4146
|
+
// and the rest arrive with the phase that emits them.
|
|
4147
|
+
trace_verbs: [],
|
|
4148
|
+
},
|
|
4149
|
+
// v2.0.0 T1 — the full human verb table, split out of trace.md so a run
|
|
4150
|
+
// stops reading ~30 KB of grammar it never emits. `when: on-demand`: a lane
|
|
4151
|
+
// opens it only for a verb its `trace_grammar` does not carry. `via` names
|
|
4152
|
+
// the phase file that points at it — the lint accepts that pointer instead
|
|
4153
|
+
// of one per lane spine.
|
|
4154
|
+
"trace-verbs": {
|
|
4155
|
+
file: "_shared/phases/trace-verbs.md",
|
|
4156
|
+
layers: ["core"],
|
|
4157
|
+
via: "trace",
|
|
4158
|
+
why_single_layer: "it is a reference table, and TRACE_VERBS in bin/cli.js is the data behind it; a lane reads its own rows from `trace_grammar`",
|
|
4159
|
+
trace_verbs: [],
|
|
3969
4160
|
},
|
|
3970
4161
|
preflight: {
|
|
3971
4162
|
file: "_shared/phases/preflight.md",
|
|
4163
|
+
trace_verbs: ["GATE", "GRAPH-CONSULT"],
|
|
3972
4164
|
layers: ["core", "full"],
|
|
3973
4165
|
// W13 moved this off a hardcoded `lane !== "orc"` branch and onto the same
|
|
3974
4166
|
// lane->layer map the build phases use — one mechanism, not two.
|
|
@@ -3983,6 +4175,7 @@ const PHASE_FILES = {
|
|
|
3983
4175
|
},
|
|
3984
4176
|
"plan-handoff": {
|
|
3985
4177
|
file: "_shared/phases/plan-handoff.md",
|
|
4178
|
+
trace_verbs: ["PHASE", "GATE"],
|
|
3986
4179
|
layers: ["core"],
|
|
3987
4180
|
why_single_layer:
|
|
3988
4181
|
"executing a plan another session wrote is one procedure, and /orc-route READS it to define what a plan is — a second definition is drift the lint cannot see (v0.42.0)",
|
|
@@ -3994,12 +4187,14 @@ const PHASE_FILES = {
|
|
|
3994
4187
|
},
|
|
3995
4188
|
"analyst-gates": {
|
|
3996
4189
|
file: "_shared/phases/analyst-gates.md",
|
|
4190
|
+
trace_verbs: ["GATE"],
|
|
3997
4191
|
layers: ["core"],
|
|
3998
4192
|
why_single_layer:
|
|
3999
4193
|
"these are the orchestrator-side gates on a returned analysis or plan, and a trimmed lane runs fewer of them rather than different ones",
|
|
4000
4194
|
},
|
|
4001
4195
|
"wiki-consult": {
|
|
4002
4196
|
file: "_shared/phases/wiki-consult.md",
|
|
4197
|
+
trace_verbs: ["WIKI-CONSULT", "CROSSLINK"],
|
|
4003
4198
|
layers: ["core"],
|
|
4004
4199
|
why_single_layer: "the precedence ladder (code > fresh wiki > stale wiki > model priors) does not bend for a faster lane",
|
|
4005
4200
|
},
|
|
@@ -4024,6 +4219,7 @@ const PHASE_FILES = {
|
|
|
4024
4219
|
},
|
|
4025
4220
|
"stop-resume": {
|
|
4026
4221
|
file: "_shared/phases/stop-resume.md",
|
|
4222
|
+
trace_verbs: ["GATE"],
|
|
4027
4223
|
layers: ["core"],
|
|
4028
4224
|
why_single_layer:
|
|
4029
4225
|
"the stop sequence is identical in every lane that stops; which moments are MANDATORY stops is already a per-lane rule in the spine",
|
|
@@ -4031,6 +4227,7 @@ const PHASE_FILES = {
|
|
|
4031
4227
|
|
|
4032
4228
|
"planning": {
|
|
4033
4229
|
file: "_shared/phases/planning.md",
|
|
4230
|
+
trace_verbs: ["PHASE", "CONFIG", "WIKI-CONSULT", "CROSSLINK", "GRAPH-MAP", "GRAPH-COCHANGE", "GATE", "PACT"],
|
|
4034
4231
|
layers: ["full", "composed"],
|
|
4035
4232
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4036
4233
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4043,6 +4240,7 @@ const PHASE_FILES = {
|
|
|
4043
4240
|
},
|
|
4044
4241
|
"scoring": {
|
|
4045
4242
|
file: "_shared/phases/scoring.md",
|
|
4243
|
+
trace_verbs: ["PHASE", "SCORE"],
|
|
4046
4244
|
layers: ["full", "composed"],
|
|
4047
4245
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4048
4246
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4055,6 +4253,7 @@ const PHASE_FILES = {
|
|
|
4055
4253
|
},
|
|
4056
4254
|
"execution": {
|
|
4057
4255
|
file: "_shared/phases/execution.md",
|
|
4256
|
+
trace_verbs: ["PHASE", "GATE", "OUTCOME", "TDD-RED", "TDD-GREEN", "REPRO", "REPLAN", "QUESTION", "CONTEXT-GAP", "BOUNDARY", "EXTRA", "GRAPH-UPDATE", "GRAPH-NOTES", "GRAPH-HINT"],
|
|
4058
4257
|
layers: ["full", "composed"],
|
|
4059
4258
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4060
4259
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4067,6 +4266,7 @@ const PHASE_FILES = {
|
|
|
4067
4266
|
},
|
|
4068
4267
|
"review": {
|
|
4069
4268
|
file: "_shared/phases/review.md",
|
|
4269
|
+
trace_verbs: ["PHASE", "FINDING", "GRAPH-CHANGES"],
|
|
4070
4270
|
layers: ["full", "composed"],
|
|
4071
4271
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4072
4272
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4091,6 +4291,7 @@ const PHASE_FILES = {
|
|
|
4091
4291
|
},
|
|
4092
4292
|
"verify": {
|
|
4093
4293
|
file: "_shared/phases/verify.md",
|
|
4294
|
+
trace_verbs: ["PHASE", "VERDICT", "PACT"],
|
|
4094
4295
|
layers: ["full", "composed"],
|
|
4095
4296
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4096
4297
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4115,6 +4316,7 @@ const PHASE_FILES = {
|
|
|
4115
4316
|
},
|
|
4116
4317
|
"mock-example": {
|
|
4117
4318
|
file: "_shared/phases/mock-example.md",
|
|
4319
|
+
trace_verbs: ["PHASE", "DRIFT"],
|
|
4118
4320
|
layers: ["full", "composed"],
|
|
4119
4321
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4120
4322
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4127,6 +4329,7 @@ const PHASE_FILES = {
|
|
|
4127
4329
|
},
|
|
4128
4330
|
"ship": {
|
|
4129
4331
|
file: "_shared/phases/ship.md",
|
|
4332
|
+
trace_verbs: ["PHASE", "GATE", "GRAPH-UPDATE", "GRAPH-GAIN"],
|
|
4130
4333
|
layers: ["full", "composed"],
|
|
4131
4334
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4132
4335
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4139,6 +4342,7 @@ const PHASE_FILES = {
|
|
|
4139
4342
|
},
|
|
4140
4343
|
"summary": {
|
|
4141
4344
|
file: "_shared/phases/summary.md",
|
|
4345
|
+
trace_verbs: ["PHASE", "FINDING-OUTCOME"],
|
|
4142
4346
|
layers: ["full", "composed"],
|
|
4143
4347
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4144
4348
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4169,12 +4373,205 @@ const PREFLIGHT_CALL_IDS = new Set([
|
|
|
4169
4373
|
"run-list",
|
|
4170
4374
|
]);
|
|
4171
4375
|
|
|
4376
|
+
// ── v2.0.0 T20 — `probes{}`: one preflight call instead of 4–6 ──────────────
|
|
4377
|
+
//
|
|
4378
|
+
// W0 counted 2–6 CLI calls at preflight before the first dispatch, each one a
|
|
4379
|
+
// round trip that re-sends the whole cached session. `orc lane config` is
|
|
4380
|
+
// already the one call every lane makes first, so it now carries the ANSWERS of
|
|
4381
|
+
// the probes that lane makes at start.
|
|
4382
|
+
//
|
|
4383
|
+
// Each probe runs the stand-alone command's OWN code, in this process, with its
|
|
4384
|
+
// own argv: the same function, the same exit code, the same fields — so the
|
|
4385
|
+
// entry cannot drift from the command. The entry is BRIEF (T17): `exit`, `line`
|
|
4386
|
+
// and the fields a lane branches on. `--probes-full` adds the whole stand-alone
|
|
4387
|
+
// answer as `full`; `--no-probes` leaves the block out.
|
|
4388
|
+
//
|
|
4389
|
+
// READ-ONLY, always. `graph status` runs WITHOUT `--heal` (a heal builds or
|
|
4390
|
+
// writes the index) — `heal_needed: true` says the lane still makes that one
|
|
4391
|
+
// call. `extra resolve` runs without the `orc extra` credential sweep (a
|
|
4392
|
+
// delete). A probe that throws or gives no answer is `{exit, error}` — never a
|
|
4393
|
+
// thrown error, and never a change to `lane config`'s own exit code.
|
|
4394
|
+
//
|
|
4395
|
+
// The set per lane is DERIVED: PREFLIGHT_CALL_IDS ∩ the lane's LANE_CALLS rows,
|
|
4396
|
+
// plus `graph-status` and `rules-slice` where the lane makes them, plus
|
|
4397
|
+
// /orc-quick's slot probe. `challenge-status` needs a slug, so it is not here.
|
|
4398
|
+
const PROBE_DEFS = {
|
|
4399
|
+
"wiki-status": {
|
|
4400
|
+
argv: () => ["wiki", "status"],
|
|
4401
|
+
run: () => wiki(),
|
|
4402
|
+
pick: ["state", "tier", "docs", "distance", "last_scan"],
|
|
4403
|
+
line: (j) =>
|
|
4404
|
+
j.state === "none"
|
|
4405
|
+
? "wiki: none — no wiki yet"
|
|
4406
|
+
: j.state === "registered"
|
|
4407
|
+
? `wiki: ${j.tier || "tier unknown"}${j.distance === null ? "" : ` (${j.distance}c)`} — ${plural(j.docs, "doc")} · last scan ${j.last_scan || "?"}`
|
|
4408
|
+
: `wiki: ${String(j.state).toUpperCase()} — ${plural(j.docs, "doc")} (run: orc wiki sync)`,
|
|
4409
|
+
},
|
|
4410
|
+
"pattern-status": {
|
|
4411
|
+
argv: () => ["pattern", "status"],
|
|
4412
|
+
run: () => pattern(),
|
|
4413
|
+
pick: ["cached"],
|
|
4414
|
+
derive: (j) => ({ langs: (j.patterns || []).map((p) => p.lang) }),
|
|
4415
|
+
line: (j) => (j.cached ? `pattern: cached — ${(j.patterns || []).map((p) => p.lang).join(", ")}` : "pattern: none cached"),
|
|
4416
|
+
},
|
|
4417
|
+
"gotcha-status": {
|
|
4418
|
+
argv: () => ["gotcha", "status"],
|
|
4419
|
+
run: () => gotcha(),
|
|
4420
|
+
pick: ["count", "gotchas_max"],
|
|
4421
|
+
line: (j) => (j.count ? `gotchas: ${j.count} live (cap ${j.gotchas_max})` : "gotchas: none recorded — nothing to inject"),
|
|
4422
|
+
},
|
|
4423
|
+
"pact-status": {
|
|
4424
|
+
argv: () => ["pact", "status"],
|
|
4425
|
+
run: () => pact(),
|
|
4426
|
+
pick: ["ok", "reason", "entries", "counts"],
|
|
4427
|
+
line: (j) => j.line || "pact: no ledger yet — a first run",
|
|
4428
|
+
},
|
|
4429
|
+
"boundary-status": {
|
|
4430
|
+
argv: () => ["boundary", "status"],
|
|
4431
|
+
run: () => boundary(),
|
|
4432
|
+
pick: ["ok", "reason", "counts", "stale"],
|
|
4433
|
+
line: (j) => j.line || "boundary: no cards yet — every area is UNKNOWN, never assumed safe",
|
|
4434
|
+
},
|
|
4435
|
+
"aftermath-status": {
|
|
4436
|
+
argv: () => ["aftermath", "status"],
|
|
4437
|
+
run: () => aftermath(),
|
|
4438
|
+
pick: ["ok", "reason", "window_days", "counts"],
|
|
4439
|
+
line: (j) =>
|
|
4440
|
+
j.ok
|
|
4441
|
+
? `aftermath: ${plural((j.runs || []).length, "run")} in the last ${j.window_days} days · ${Object.entries(j.counts || {}).map(([k, v]) => `${v} ${k}`).join(" · ") || "no grade"}`
|
|
4442
|
+
: j.reason === "shallow"
|
|
4443
|
+
? `aftermath: no runs in the last ${j.window_days} days — too shallow to grade`
|
|
4444
|
+
: `aftermath: ${j.reason}`,
|
|
4445
|
+
},
|
|
4446
|
+
"diy-status": {
|
|
4447
|
+
argv: () => ["diy", "status"],
|
|
4448
|
+
run: () => diy(),
|
|
4449
|
+
pick: ["state", "reason"],
|
|
4450
|
+
line: (j) => `diy: ${j.state}${j.reason ? " — " + j.reason : ""}`,
|
|
4451
|
+
},
|
|
4452
|
+
"run-list": {
|
|
4453
|
+
argv: () => ["run", "list"],
|
|
4454
|
+
run: () => runCmd(),
|
|
4455
|
+
pick: ["total"],
|
|
4456
|
+
line: (j) => `runs: ${j.total}`,
|
|
4457
|
+
},
|
|
4458
|
+
"graph-status": {
|
|
4459
|
+
argv: () => ["graph", "status", "--if-enabled", "--brief"],
|
|
4460
|
+
run: () => graphCmd(),
|
|
4461
|
+
pick: ["state", "enabled", "exists", "files", "symbols", "generation", "auto_update", "notes", "trace"],
|
|
4462
|
+
derive: (j) => ({ heal_needed: !!(j.enabled && j.auto_update && (j.state === "none" || j.state === "drifted")) }),
|
|
4463
|
+
line: (j) => j.line,
|
|
4464
|
+
},
|
|
4465
|
+
"rules-slice": {
|
|
4466
|
+
argv: (lane) => ["rules", "slice", "--lane", lane],
|
|
4467
|
+
run: () => rulesCmd(),
|
|
4468
|
+
pick: ["packs", "overrides"],
|
|
4469
|
+
line: (j) => j.line,
|
|
4470
|
+
},
|
|
4471
|
+
"extra-slot": {
|
|
4472
|
+
argv: () => ["extra", "resolve", "--slot", "quick-executor"],
|
|
4473
|
+
run: () => extraResolveCmd(resolveClaudeDir(), undefined),
|
|
4474
|
+
pick: ["resolved", "via", "slot", "needs_reping", "held_back"],
|
|
4475
|
+
line: (j) => (j.resolved === "extra" ? `extra: ${j.announce}` : `extra: ${j.slot} → claude ${(j.claude && j.claude.agent) || ""}`.trim()),
|
|
4476
|
+
},
|
|
4477
|
+
};
|
|
4478
|
+
|
|
4479
|
+
function laneProbeIds(lane) {
|
|
4480
|
+
const ids = Object.entries(LANE_CALLS)
|
|
4481
|
+
.filter(([id, c]) => id !== "lane-config" && PREFLIGHT_CALL_IDS.has(id) && c.lanes.includes(lane) && PROBE_DEFS[id])
|
|
4482
|
+
.map(([id]) => id);
|
|
4483
|
+
if (LANE_CALLS["graph-status"].lanes.includes(lane)) ids.push("graph-status");
|
|
4484
|
+
if (LANE_CALLS["rules-slice"].lanes.includes(lane)) ids.push("rules-slice");
|
|
4485
|
+
if (lane === "orc-quick") ids.push("extra-slot");
|
|
4486
|
+
return ids;
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
// Run ONE stand-alone command in this process and take its answer. The argv is
|
|
4490
|
+
// swapped in for the call and put back after it; stdout, stderr and exit are
|
|
4491
|
+
// caught, so nothing reaches the terminal and nothing ends this process.
|
|
4492
|
+
function runProbe(argv, fn) {
|
|
4493
|
+
const saved = args.slice();
|
|
4494
|
+
const pass = [];
|
|
4495
|
+
const di = saved.indexOf("--dir");
|
|
4496
|
+
if (di !== -1 && saved[di + 1] !== undefined) pass.push("--dir", saved[di + 1]);
|
|
4497
|
+
if (saved.includes("--global")) pass.push("--global");
|
|
4498
|
+
args.splice(0, args.length, ...argv, "--json", ...pass);
|
|
4499
|
+
const cap = { obj: null, text: [], err: [] };
|
|
4500
|
+
const SENT = { probe: true };
|
|
4501
|
+
let code = null;
|
|
4502
|
+
let error = null;
|
|
4503
|
+
const keep = { exit: process.exit, log: console.log, error: console.error, warn: console.warn };
|
|
4504
|
+
process.exit = (c) => {
|
|
4505
|
+
code = c === undefined ? 0 : c;
|
|
4506
|
+
throw SENT;
|
|
4507
|
+
};
|
|
4508
|
+
console.log = (...a) => cap.text.push(a.map(String).join(" "));
|
|
4509
|
+
console.error = console.warn = (...a) => cap.err.push(a.map(String).join(" "));
|
|
4510
|
+
PROBE_CAPTURE = cap;
|
|
4511
|
+
try {
|
|
4512
|
+
fn();
|
|
4513
|
+
if (code === null) code = 0;
|
|
4514
|
+
} catch (e) {
|
|
4515
|
+
if (e !== SENT) {
|
|
4516
|
+
error = String((e && e.message) || e).split("\n")[0];
|
|
4517
|
+
if (code === null) code = 1;
|
|
4518
|
+
}
|
|
4519
|
+
} finally {
|
|
4520
|
+
PROBE_CAPTURE = null;
|
|
4521
|
+
process.exit = keep.exit;
|
|
4522
|
+
console.log = keep.log;
|
|
4523
|
+
console.error = keep.error;
|
|
4524
|
+
console.warn = keep.warn;
|
|
4525
|
+
args.splice(0, args.length, ...saved);
|
|
4526
|
+
}
|
|
4527
|
+
let obj = cap.obj;
|
|
4528
|
+
if (!obj && cap.text.length) {
|
|
4529
|
+
try {
|
|
4530
|
+
obj = JSON.parse(cap.text.join("\n"));
|
|
4531
|
+
} catch (_) {}
|
|
4532
|
+
}
|
|
4533
|
+
if (!obj && !error) error = String(cap.err.find((l) => l.trim()) || "no answer").trim().split("\n")[0];
|
|
4534
|
+
return { exit: code, obj, error };
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
function laneProbes(lane, full) {
|
|
4538
|
+
const out = {};
|
|
4539
|
+
for (const id of laneProbeIds(lane)) {
|
|
4540
|
+
const def = PROBE_DEFS[id];
|
|
4541
|
+
const r = runProbe(def.argv(lane), def.run);
|
|
4542
|
+
if (r.error || !r.obj) {
|
|
4543
|
+
out[id] = { exit: r.exit, error: r.error };
|
|
4544
|
+
continue;
|
|
4545
|
+
}
|
|
4546
|
+
const j = r.obj;
|
|
4547
|
+
let line;
|
|
4548
|
+
try {
|
|
4549
|
+
line = def.line(j);
|
|
4550
|
+
} catch (e) {
|
|
4551
|
+
line = null;
|
|
4552
|
+
}
|
|
4553
|
+
const e = { exit: r.exit, line };
|
|
4554
|
+
for (const k of def.pick) if (Object.prototype.hasOwnProperty.call(j, k)) e[k] = j[k];
|
|
4555
|
+
if (def.derive) Object.assign(e, def.derive(j));
|
|
4556
|
+
if (full) e.full = j;
|
|
4557
|
+
out[id] = e;
|
|
4558
|
+
}
|
|
4559
|
+
return out;
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4172
4562
|
// tier + packet cadence per trace-owning lane. This is the same data the tier
|
|
4173
4563
|
// table in \`_shared/phases/trace.md\` renders for a human; the lint asserts the
|
|
4174
4564
|
// two agree, so a lane cannot be in one and absent from the other.
|
|
4175
4565
|
const LANE_TRACE = {
|
|
4176
|
-
|
|
4177
|
-
|
|
4566
|
+
// `spine_verbs` (v2.0.0 T1): the verbs a lane's own SPINE emits outside
|
|
4567
|
+
// any declared phase row — orc's ultra gates, and orc-mini, whose pipeline is
|
|
4568
|
+
// still in-spine. They join `trace_grammar` like a phase's `trace_verbs`.
|
|
4569
|
+
orc: { tier: "Build lanes", token: "orc", spine_verbs: ["ADVISE", "JUDGE"] },
|
|
4570
|
+
"orc-mini": {
|
|
4571
|
+
tier: "Build lanes",
|
|
4572
|
+
token: "mini",
|
|
4573
|
+
spine_verbs: ["PHASE", "GATE", "OUTCOME", "DRIFT", "TDD-RED", "TDD-GREEN", "FINDING", "FINDING-OUTCOME", "GRAPH-CONSULT", "GRAPH-UPDATE", "GRAPH-COMPLEXITY", "GRAPH-NOTES"],
|
|
4574
|
+
},
|
|
4178
4575
|
"orc-fast": { tier: "Build lanes", token: "fast" },
|
|
4179
4576
|
"orc-wiki": { tier: "Multi-dispatch", token: "wiki" },
|
|
4180
4577
|
"orc-pr-driver": { tier: "Multi-dispatch", token: "prdriver" },
|
|
@@ -4202,12 +4599,96 @@ const LANE_TRACE = {
|
|
|
4202
4599
|
"orc-export": { tier: "Single-dispatch", token: "export" },
|
|
4203
4600
|
};
|
|
4204
4601
|
|
|
4602
|
+
// v2.0.0 T1 — the CLOSED trace verb set, as DATA (the `LANE_CALLS` shape).
|
|
4603
|
+
// `_shared/phases/trace-verbs.md` renders this table for a human, and
|
|
4604
|
+
// `bin/verify-contracts.js` holds the two together BOTH ways, plus every
|
|
4605
|
+
// `trace_verbs` / `spine_verbs` entry in the phase manifest: a verb a phase
|
|
4606
|
+
// lists that this registry lacks FAILS. `orc lane phases <lane> --json` hands a
|
|
4607
|
+
// lane `trace_grammar` = the grammar lines of exactly the verbs its phases emit
|
|
4608
|
+
// plus TRACE_ALWAYS_VERBS, so a run never reads the whole table.
|
|
4609
|
+
//
|
|
4610
|
+
// grammar — the line shape; ` · ` separates the sub-forms of one verb.
|
|
4611
|
+
// emitter — who produces it: `orc → writer` is a packet event, `hook` needs
|
|
4612
|
+
// no cooperation, `X→orc → writer` is a subagent-returned marker.
|
|
4613
|
+
// meaning — ONE line. The detail lives at `owner`.
|
|
4614
|
+
const TRACE_VERBS = {
|
|
4615
|
+
PHASE: { grammar: "PHASE <name> start|end", emitter: "orc → writer", meaning: "phase transition", owner: "_shared/phases/trace.md" },
|
|
4616
|
+
"PHASE-EDGE": { grammar: "PHASE-EDGE <role-family> :: first=<agent>", emitter: "hook", meaning: "deterministic phase inference: a SPAWN whose role family differs from the previous one opens an edge", owner: "hooks/orc-trace.js" },
|
|
4617
|
+
CONFIG: { grammar: "CONFIG <key=value …>", emitter: "orc → writer", meaning: "Phase 1 — the resolved config values this run will consume, ALWAYS with opus5_only", owner: "_shared/phases/trace-verbs.md" },
|
|
4618
|
+
"WIKI-CONSULT": { grammar: "WIKI-CONSULT <fresh|aging|stale|absent|empty> :: docs=<list|none>", emitter: "orc → writer", meaning: "project wiki consulted for grounding, with its staleness tier", owner: "_shared/phases/wiki-consult.md" },
|
|
4619
|
+
CROSSLINK: { grammar: "CROSSLINK <cached|configured-no-cache|none> :: boundaries=<n> peers=<names> · CROSSLINK inject task=<id> :: <boundary>", emitter: "orc → writer", meaning: "cross-repo peer-knowledge state at the consult point; `inject` when a slice receives a linked contract", owner: "_shared/phases/wiki-consult.md" },
|
|
4620
|
+
"GRAPH-CONSULT": { grammar: "GRAPH-CONSULT <fresh|updated|built|drifted|none|off> :: files=<n> symbols=<n>[ density=<n>[ thin=1]] · GRAPH-CONSULT card task=<id> :: targets=<…>", emitter: "orc → writer", meaning: "the code graph's preflight state, copied VERBATIM from the CLI `trace` field", owner: "_shared/code-graph.md" },
|
|
4621
|
+
"GRAPH-UPDATE": { grammar: "GRAPH-UPDATE <state> :: parsed=<n> reused=<n> deleted=<n> gen=<n> route=<n> ms=<n>", emitter: "orc → writer", meaning: "one graph update, the `trace` field of `orc graph update --json`, verbatim", owner: "_shared/phases/trace-verbs.md" },
|
|
4622
|
+
"GRAPH-MAP": { grammar: "GRAPH-MAP <repo|focused> :: files=<shown>/<total> [focus=<a,b>] gen=<n>", emitter: "orc → writer", meaning: "ONE line at the start of planning, the `trace` field of `orc graph map --json`, verbatim", owner: "_shared/phases/trace-verbs.md" },
|
|
4623
|
+
"GRAPH-CHANGES": { grammar: "GRAPH-CHANGES <found|none> :: symbols=<n> high=<n> medium=<n> low=<n> gen=<n>", emitter: "orc → writer", meaning: "one line at review, the `trace` field of `orc graph changes --json`, verbatim", owner: "_shared/phases/trace-verbs.md" },
|
|
4624
|
+
"GRAPH-COMPLEXITY": { grammar: "GRAPH-COMPLEXITY <mini-ok|recommend-orc> :: files=<n> callers=<n> caller_files=<n> maybe=<n> tests=<n> risk=<n> cochange=<n> gen=<n>", emitter: "orc → writer", meaning: "one line per /orc-mini run, the `trace` field of the `--complexity` impact call, verbatim", owner: "_shared/phases/trace-verbs.md" },
|
|
4625
|
+
"GRAPH-COCHANGE": { grammar: "GRAPH-COCHANGE <found|none> :: rows=<n> commits=<n>", emitter: "orc → writer", meaning: "one line per planning batch, for the file that produced the widest answer", owner: "_shared/phases/trace-verbs.md" },
|
|
4626
|
+
"GRAPH-HINT": { grammar: "GRAPH-HINT injected=<n> subagent_start=<n> read_notes=<n> updates=<n>", emitter: "orc → writer", meaning: "ONE line per phase close, the graph hook's counters; omitted when the counter file does not exist", owner: "_shared/phases/trace-verbs.md" },
|
|
4627
|
+
"GRAPH-GAIN": { grammar: "GRAPH-GAIN paid=<n> low=<n> high=<n> calls=<n>", emitter: "orc → writer", meaning: "ONE line per run at ship; `paid` is exact, `low`/`high` are an estimate never collapsed into one number", owner: "_shared/phases/trace-verbs.md" },
|
|
4628
|
+
"GRAPH-NOTES": { grammar: "GRAPH-NOTES <applied|below-min|none|deferred|off|skipped> :: <detail>", emitter: "orc → writer", meaning: "one notes batch; `applied` copies the noter's one-line return verbatim", owner: "_shared/phases/trace-verbs.md" },
|
|
4629
|
+
SPAWN: { grammar: "SPAWN <agent>", emitter: "hook", meaning: "an agent dispatch was observed (skeleton)", owner: "hooks/orc-trace.js" },
|
|
4630
|
+
RETURN: { grammar: "RETURN <agent> :: <desc> dur=<m>m<s>s [model=<id>]", emitter: "hook", meaning: "a subagent finished (skeleton) — not an orchestrator obligation; the model check is VERIFY", owner: "hooks/orc-trace.js" },
|
|
4631
|
+
DISPATCH: { grammar: "DISPATCH <agent> :: <task> expect=<model>/<effort>[ via=extra:<profile>]", emitter: "orc → writer", meaning: "orchestrator dispatched a named agent (the claim)", owner: "_shared/phases/trace-verbs.md" },
|
|
4632
|
+
SCORE: { grammar: "SCORE task=<id> score=<n> band=<range> model=<m> facets=<compact-vector>[ via=extra:<profile>] :: <reason>", emitter: "orc → writer", meaning: "scoring decision (tunes the rubric); a fix cycle emits `task=fix-<n>`", owner: "_shared/phases/scoring.md" },
|
|
4633
|
+
VERIFY: { grammar: "VERIFY <task> actual=<model>/<effort> ✅ MATCH · VERIFY <task> actual=<model>/<effort> ⛔ DOWNGRADE expected=<m>/<e>", emitter: "orc → writer", meaning: "claimed-vs-actual model check; surface a downgrade to the user, not just the trace", owner: "_shared/return-validation.md" },
|
|
4634
|
+
ASK: { grammar: "ASK <qid> :: offered=<o1|o2|…> rec=<o|none> chose=<o|other> by=<user|ledger|learned|config|default> [pre=<o>] [ctx=<k=v,…>]", emitter: "orc → writer", meaning: "one answered decision point — what `orc habit` learns from; the free text of an `other` answer is never stored", owner: "_shared/phases/trace-verbs.md" },
|
|
4635
|
+
QUESTION: { grammar: "QUESTION count=<n> :: <topic>", emitter: "subagent→orc → writer", meaning: "stopped to ask the user", owner: "_shared/phases/trace-verbs.md" },
|
|
4636
|
+
"CONTEXT-GAP": { grammar: "CONTEXT-GAP :: <what was already known>", emitter: "subagent→orc → writer", meaning: "asked/re-derived something already in context", owner: "_shared/phases/trace-verbs.md" },
|
|
4637
|
+
REPLAN: { grammar: "REPLAN wave=<n> :: <reason>", emitter: "orc → writer", meaning: "re-planned after a conflict/failure", owner: "_shared/phases/trace-verbs.md" },
|
|
4638
|
+
GATE: { grammar: "GATE <grounding|coverage|graph|evidence|derivation|facet|schema|judgment|wave-boundary|budget|stack-gate|stack-certainty|layer-green> pass|bounce|escalate :: <detail>", emitter: "orc → writer", meaning: "exit-gate result; `escalate` is judgment-only; bounce detail lists the misses", owner: "_shared/phases/trace-verbs.md" },
|
|
4639
|
+
ADVISE: { grammar: "ADVISE :: brief=<path> questions=<n>", emitter: "orc → writer", meaning: "ultra Phase U0 — advisor brief received, clarification round relayed", owner: "orc/references/ultra-mode.md" },
|
|
4640
|
+
JUDGE: { grammar: "JUDGE <analysis|plan|implementation> <verdict> round=<n> blocking=<n> advisory=<n> downgraded=<n>", emitter: "orc → writer", meaning: "ultra judgment verdict", owner: "orc/references/ultra-mode.md" },
|
|
4641
|
+
OUTCOME: { grammar: "OUTCOME task=<id> score=<n> band=<range> model=<m> retries=<n> requeues=<n> needs_context=<n> unmet=<n>", emitter: "orc → writer", meaning: "task closed — links the scoring band to what it actually took", owner: "_shared/phases/execution.md" },
|
|
4642
|
+
FINDING: { grammar: "FINDING p0=<n> p1=<n> p2=<n> p3=<n>[ pre=<n> suppressed=<n> folded=<n>]", emitter: "reviewer→orc → writer", meaning: "review outcome (P0–P3 severity ladder); the tail counts the after-filter's buckets", owner: "_shared/phases/review.md" },
|
|
4643
|
+
"FINDING-OUTCOME": { grammar: "FINDING-OUTCOME addressed=<n> disputed=<n> wontfix=<n> open=<n> pre=<n> suppressed=<n> :: <cat>:<addressed>/<total>,…", emitter: "orc → writer", meaning: "ONE line at review close — what became of each finding; `orc gotcha quality` reads it", owner: "_shared/phases/trace-verbs.md" },
|
|
4644
|
+
VERDICT: { grammar: "VERDICT pass|fail :: <detail>", emitter: "verifier→orc → writer", meaning: "verification outcome", owner: "_shared/phases/verify.md" },
|
|
4645
|
+
DRIFT: { grammar: "DRIFT loop=<n> :: <user description, compressed>", emitter: "orc → writer", meaning: "mock-example drift-recovery loop opened (hard cap 2 loops)", owner: "_shared/drift-recovery.md" },
|
|
4646
|
+
"TDD-RED": { grammar: "TDD-RED task=<id> iter=<n> :: <failing tests>", emitter: "executor→orc → writer", meaning: "TDD repair-loop iteration — the plan's acceptance tests still red", owner: "_shared/phases/trace-verbs.md" },
|
|
4647
|
+
"TDD-GREEN": { grammar: "TDD-GREEN task=<id> iter=<n>", emitter: "executor→orc → writer", meaning: "the task's TDD acceptance tests pass", owner: "_shared/phases/trace-verbs.md" },
|
|
4648
|
+
REPRO: { grammar: "REPRO red|green :: <cmd> exit=<n> · REPRO none :: <reason>", emitter: "executor→orc → writer", meaning: "a DEFECT task's reproduction: RED before the fix, GREEN after", owner: "_shared/return-validation.md" },
|
|
4649
|
+
NOTE: { grammar: "NOTE :: <decisions>", emitter: "writer", meaning: "the packet's `decisions` field — the WHY layer; one line, only when non-empty", owner: "agents/orc-trace-writer-haiku-4-5.md" },
|
|
4650
|
+
STATS: { grammar: "STATS lane=<l> slug=<s> dispatches=<n> waves=<n> tasks=<n> bands=<h:n,m:n,l:n> downgrades=<n> duration_ms=<n>", emitter: "orc → writer", meaning: "ONE summary line per run, in the FINISH packet, immediately BEFORE the FINISH line", owner: "_shared/phases/trace-verbs.md" },
|
|
4651
|
+
PACT: { grammar: "PACT <state> :: <ids> · PACT inject task=<id> :: <PACT-id> · PACT recheck pass|fail :: <ids>", emitter: "orc → writer", meaning: "invariant-ledger state at the Phase-1 probe, a promise injected into a task, the Phase-6 recheck", owner: "orc-pact/references/gate.md" },
|
|
4652
|
+
BOUNDARY: { grammar: "BOUNDARY <EXECUTE|ESCALATE|REFUSE|unknown> task=<id> :: <area> · BOUNDARY lift task=<id> :: <area>", emitter: "orc → writer", meaning: "per-task boundary verdict; an uncarded area is `unknown`, never REFUSE", owner: "orc-boundary/references/gate.md" },
|
|
4653
|
+
CHALLENGE: { grammar: "CHALLENGE iter=<n> findings=P0:<n>/P1:<n>/P2:<n> coverage=<n>% verdict=PASS|FAIL · CHALLENGE accept|rebut :: <id> · CHALLENGE regoal|retemplate :: v<n>", emitter: "orc → writer", meaning: "one line per completed /orc-challenge iteration — `orc challenge record`'s `trace_line`, verbatim", owner: "orc-challenge/SKILL.md" },
|
|
4654
|
+
EXTRA: { grammar: "EXTRA <profile>/<model> engine=<api|claude-shim|cli> task=<id> band=[lo,hi) tok=in/cw/cr/out outcome=<done|partial|failed|fallback> dur=<m>m<s>s · EXTRA fallback task=<id> :: <reason> → <agent> · EXTRA substitution task=<id> :: requested=<m> reported=<m> · EXTRA reroute task=<id> :: <providers> · EXTRA resume task=<id> attempt=<n> :: from=<reason> attribution=<verdict> target=<extra:profile|agent> files_preexisting=<n> · EXTRA orphan task=<id> :: attempt=<n> lease-expired files_changed=<n> state=<state> · EXTRA demote run=<slug> :: profile=<p> reason=<consecutive-stall|stale-live-attempt|manual> n=<k> → <ladder>", emitter: "orc → writer", meaning: "one line per FOREIGN dispatch — `orc extra dispatch`'s `trace_line`, verbatim", owner: "_shared/extra-dispatch.md" },
|
|
4655
|
+
FINISH: { grammar: "FINISH :: <detail>", emitter: "orc → writer", meaning: "run ended — mandatory, even on an abort", owner: "_shared/phases/trace.md" },
|
|
4656
|
+
};
|
|
4657
|
+
|
|
4658
|
+
// The verbs EVERY trace-owning lane owes, whatever its phases list. Every lane
|
|
4659
|
+
// sends at least the ONE end-of-run packet, and trace.md makes each of these
|
|
4660
|
+
// part of it: DISPATCH + VERIFY (the main return is dispatched and checked),
|
|
4661
|
+
// NOTE (the packet's `decisions`), STATS (every trace-owning lane emits it, not
|
|
4662
|
+
// just `orc`) and FINISH (mandatory, even on an abort). PHASE and CONFIG are
|
|
4663
|
+
// NOT here: a single-dispatch lane closes no named phase, and CONFIG is the
|
|
4664
|
+
// build lanes' Phase-1 line — both come in through the phase rows that emit them.
|
|
4665
|
+
const TRACE_ALWAYS_VERBS = ["DISPATCH", "VERIFY", "NOTE", "STATS", "FINISH"];
|
|
4666
|
+
|
|
4667
|
+
// `trace_grammar` for one lane: TRACE_VERBS order, hook verbs left out (a lane
|
|
4668
|
+
// never emits them), `null` for a lane that owns no trace.
|
|
4669
|
+
function laneTraceGrammar(lane) {
|
|
4670
|
+
const t = LANE_TRACE[lane];
|
|
4671
|
+
if (!t) return null;
|
|
4672
|
+
const want = new Set(TRACE_ALWAYS_VERBS);
|
|
4673
|
+
for (const id of LANE_PHASE_ORDER[lane] || PHASE_ORDER)
|
|
4674
|
+
if ((LANE_PHASES[id] || []).includes(lane)) for (const v of PHASE_FILES[id].trace_verbs || []) want.add(v);
|
|
4675
|
+
for (const p of LANE_OWN_PHASES[lane] || []) for (const v of p.trace_verbs || []) want.add(v);
|
|
4676
|
+
for (const v of t.spine_verbs || []) want.add(v);
|
|
4677
|
+
const out = {};
|
|
4678
|
+
for (const [v, d] of Object.entries(TRACE_VERBS)) if (want.has(v) && d.emitter !== "hook") out[v] = d.grammar;
|
|
4679
|
+
return out;
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4205
4682
|
// Which lanes run which SHARED phase. A lane absent from a row does not run
|
|
4206
4683
|
// that phase — \`/orc-retro\` is in no trace row because it mines traces and
|
|
4207
4684
|
// writes none (its hard rule 4), and \`context-combiner\` is in none because it
|
|
4208
4685
|
// is a PHASE of the analyze run, not a lane (v0.42.0).
|
|
4209
4686
|
const LANE_PHASES = {
|
|
4210
4687
|
trace: Object.keys(LANE_TRACE),
|
|
4688
|
+
// v2.0.0 T1 — the verb table, ON DEMAND. Every lane that traces may need a
|
|
4689
|
+
// verb its `trace_grammar` does not carry, and it reaches the table through
|
|
4690
|
+
// trace.md (`via` in PHASE_FILES), never through its own spine.
|
|
4691
|
+
"trace-verbs": Object.keys(LANE_TRACE),
|
|
4211
4692
|
preflight: [
|
|
4212
4693
|
"orc",
|
|
4213
4694
|
"orc-boundary",
|
|
@@ -4262,6 +4743,7 @@ const LANE_PHASES = {
|
|
|
4262
4743
|
const PHASE_ORDER = [
|
|
4263
4744
|
"preflight",
|
|
4264
4745
|
"trace",
|
|
4746
|
+
"trace-verbs",
|
|
4265
4747
|
"intake",
|
|
4266
4748
|
"plan-handoff",
|
|
4267
4749
|
"wiki-consult",
|
|
@@ -4296,6 +4778,7 @@ const PHASE_ORDER = [
|
|
|
4296
4778
|
const LANE_PHASE_ORDER = {
|
|
4297
4779
|
"orc-diy": [
|
|
4298
4780
|
"trace",
|
|
4781
|
+
"trace-verbs",
|
|
4299
4782
|
"intake",
|
|
4300
4783
|
"planning",
|
|
4301
4784
|
"wave-grouping",
|
|
@@ -4320,7 +4803,7 @@ const LANE_PHASE_ORDER = {
|
|
|
4320
4803
|
// A row names a FILE and, when the phase still lives in that lane's spine, the
|
|
4321
4804
|
// HEADING inside it (plan rule: a manifest names a file, a layer and at most a
|
|
4322
4805
|
// heading — never a line number, /orc-doc rule 2). `read: "section"` is the
|
|
4323
|
-
// partial-read declaration registered in `_shared/
|
|
4806
|
+
// partial-read declaration registered in `_shared/phases/README.md`.
|
|
4324
4807
|
//
|
|
4325
4808
|
// WHY MOST ROWS NAME A HEADING RATHER THAN A FILE (v1.0.0 W14, measured):
|
|
4326
4809
|
// moving a phase into its own file pays only when a run does NOT reach it —
|
|
@@ -4463,7 +4946,7 @@ const LANE_OWN_PHASES = {
|
|
|
4463
4946
|
{ ord: 0, id: "q0", file: "orc-quick/SKILL.md", heading: "## Q0 — Preflight (ONE time per session, silent, nothing can stop the run)", read: "section", trace_verbs: ["GATE", "GRAPH-CONSULT"] },
|
|
4464
4947
|
{ ord: 1, id: "q1", file: "orc-quick/SKILL.md", heading: "## Q1 — LOOK (silent — no questions here)", read: "section", trace_verbs: ["GATE", "WIKI-CONSULT", "GRAPH-CONSULT", "GRAPH-MAP"] },
|
|
4465
4948
|
{ ord: 2, id: "q2", file: "orc-quick/SKILL.md", heading: "## Q2 — ASK (ONE user turn: questions + the gate together)", read: "section", trace_verbs: [] },
|
|
4466
|
-
{ ord: 3, id: "q3", file: "orc-quick/SKILL.md", heading: "## Q3 — DO (dispatch → build/test → write the doc → offer)", read: "section", trace_verbs: ["DISPATCH", "VERIFY", "REPRO", "GRAPH-CHANGES", "GRAPH-UPDATE", "GRAPH-NOTES", "GRAPH-GAIN", "OUTCOME", "FINISH"] },
|
|
4949
|
+
{ ord: 3, id: "q3", file: "orc-quick/SKILL.md", heading: "## Q3 — DO (dispatch → build/test → write the doc → offer)", read: "section", trace_verbs: ["DISPATCH", "VERIFY", "REPRO", "GRAPH-CHANGES", "GRAPH-UPDATE", "GRAPH-NOTES", "GRAPH-GAIN", "OUTCOME", "FINDING", "FINDING-OUTCOME", "FINISH"] },
|
|
4467
4950
|
],
|
|
4468
4951
|
"orc-wiki": [
|
|
4469
4952
|
{ ord: 0, id: "phase-0", file: "orc-wiki/references/phases/phase-0.md", layers: ["full"], trace_verbs: [] },
|
|
@@ -4493,20 +4976,22 @@ function lanePhaseRows(lane) {
|
|
|
4493
4976
|
file: def.file,
|
|
4494
4977
|
layers,
|
|
4495
4978
|
read: "whole",
|
|
4496
|
-
// `always` must be justified (
|
|
4979
|
+
// `always` must be justified (phases/README.md, W10): a lane reads the
|
|
4497
4980
|
// config resolver and opens its trace pointer before it can do anything
|
|
4498
4981
|
// at all. Every other phase is `on-phase` — most runs skip most phases.
|
|
4499
4982
|
// `compile-time` is orc-diy and only orc-diy: it opens none of these
|
|
4500
4983
|
// during a run. `orc diy compile` reads the `composed` layer once and
|
|
4501
4984
|
// stitches it into FLOW-COMPILED.md, which is the only spine that run
|
|
4502
4985
|
// follows. Calling that `on-phase` would describe a read that never
|
|
4503
|
-
// happens (
|
|
4986
|
+
// happens (phases/README.md rule 5).
|
|
4504
4987
|
when:
|
|
4505
4988
|
lane === "orc-diy" && (PHASE_FILES[id].lane_layers || {})["orc-diy"]
|
|
4506
4989
|
? "compile-time"
|
|
4507
4990
|
: id === "preflight" || id === "trace"
|
|
4508
4991
|
? "always"
|
|
4509
|
-
:
|
|
4992
|
+
: PHASE_FILES[id].via
|
|
4993
|
+
? "on-demand"
|
|
4994
|
+
: "on-phase",
|
|
4510
4995
|
optional_when: null,
|
|
4511
4996
|
// v1.7.0 — `rules` names its own call for the same reason `preflight`
|
|
4512
4997
|
// does: the phase IS that command, and a phase row reading "none
|
|
@@ -4654,8 +5139,10 @@ function laneRailsManifest() {
|
|
|
4654
5139
|
// is an answer, not a gap.
|
|
4655
5140
|
if (!t || !t.token) continue;
|
|
4656
5141
|
const rows = [];
|
|
5142
|
+
// An `on-demand` row is a reference a lane MAY open, never a phase a run
|
|
5143
|
+
// is IN — a rail step for it would be a phase the statusline can never show.
|
|
4657
5144
|
for (const p of lanePhaseRows(lane))
|
|
4658
|
-
rows.push({ id: p.id, label: railLabel(p), kind: railKind(p), verbs: [] });
|
|
5145
|
+
if (p.when !== "on-demand") rows.push({ id: p.id, label: railLabel(p), kind: railKind(p), verbs: [] });
|
|
4659
5146
|
for (const p of LANE_OWN_PHASES[lane] || [])
|
|
4660
5147
|
rows.push({
|
|
4661
5148
|
id: p.id,
|
|
@@ -4700,6 +5187,9 @@ function lanePhasesCmd(lane, claudeDir) {
|
|
|
4700
5187
|
lane: l,
|
|
4701
5188
|
trace_tier: t ? t.tier : null,
|
|
4702
5189
|
trace_token: t ? t.token : null,
|
|
5190
|
+
// v2.0.0 T1 — the grammar line of every verb this lane's phases emit,
|
|
5191
|
+
// plus TRACE_ALWAYS_VERBS. A verb not here → `_shared/phases/trace-verbs.md`.
|
|
5192
|
+
trace_grammar: laneTraceGrammar(l),
|
|
4703
5193
|
phases,
|
|
4704
5194
|
shared_phase_count: phases.length,
|
|
4705
5195
|
// `null` is NOT `[]`: a lane whose own pipeline is still declared in its
|
|
@@ -4730,6 +5220,11 @@ function lanePhasesCmd(lane, claudeDir) {
|
|
|
4730
5220
|
? ui.color.gray(`tier ${r.trace_tier} · lane token \`${r.trace_token}\``)
|
|
4731
5221
|
: ui.color.gray("this lane owns no trace (it is not a run entry point)"))
|
|
4732
5222
|
);
|
|
5223
|
+
if (r.trace_grammar)
|
|
5224
|
+
console.log(
|
|
5225
|
+
" trace grammar " +
|
|
5226
|
+
ui.color.gray(`${Object.keys(r.trace_grammar).length} verbs: ${Object.keys(r.trace_grammar).join(" · ")} (--json → trace_grammar)`)
|
|
5227
|
+
);
|
|
4733
5228
|
if (!r.phases.length) {
|
|
4734
5229
|
// An empty answer is an ANSWER: this lane runs no SHARED phase yet.
|
|
4735
5230
|
console.log(
|
|
@@ -4800,6 +5295,9 @@ function laneConfigCmd(lane, claudeDir) {
|
|
|
4800
5295
|
return 2;
|
|
4801
5296
|
}
|
|
4802
5297
|
const d = laneConfig(lane, claudeDir);
|
|
5298
|
+
// v2.0.0 T20 — the preflight probes' answers ride on this one call. LAST, so
|
|
5299
|
+
// every 1.9.2 field keeps its position.
|
|
5300
|
+
if (!args.includes("--no-probes")) d.probes = laneProbes(lane, args.includes("--probes-full"));
|
|
4803
5301
|
if (wantsJson()) {
|
|
4804
5302
|
emitJson({ ok: true, ...d });
|
|
4805
5303
|
return 0;
|
|
@@ -4816,6 +5314,31 @@ function laneConfigCmd(lane, claudeDir) {
|
|
|
4816
5314
|
console.log("");
|
|
4817
5315
|
}
|
|
4818
5316
|
|
|
5317
|
+
// v2.0.0 T20 — the probes this lane makes at start, one line each.
|
|
5318
|
+
if (d.probes && Object.keys(d.probes).length) {
|
|
5319
|
+
console.log(ui.header("Probes (the preflight answers — exit code is data)"));
|
|
5320
|
+
const pw = Math.max(...Object.keys(d.probes).map((k) => k.length));
|
|
5321
|
+
for (const [id, p] of Object.entries(d.probes))
|
|
5322
|
+
console.log(` ${ui.color.cyan(id.padEnd(pw))} exit ${p.exit} ${p.error ? ui.color.yellow("error: " + p.error) : p.line}`);
|
|
5323
|
+
console.log("");
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5326
|
+
// v2.0.0 W2 — only when `habits` is not off; off prints nothing new.
|
|
5327
|
+
if (d.habits) {
|
|
5328
|
+
const h = d.habits;
|
|
5329
|
+
console.log(ui.header("Habits"));
|
|
5330
|
+
console.log(" " + h.line);
|
|
5331
|
+
for (const l of h.learned) console.log(` learned ${l.key}: ${l.value} (${l.id}, ${l.count} of ${l.total}) undo: ${l.undo}`);
|
|
5332
|
+
for (const x of h.suggestions) console.log(` usual ${x.qid}: ${x.option} ${x.mark} (${x.id})`);
|
|
5333
|
+
if (h.proposal) {
|
|
5334
|
+
console.log(` proposal ${h.proposal.id} (${h.proposal.kind}) — show at the END of the run only:`);
|
|
5335
|
+
console.log(" " + h.proposal.line);
|
|
5336
|
+
console.log(ui.color.gray(` yes: ${h.proposal.yes}${h.proposal.keep ? " keep: " + h.proposal.keep : ""}${h.proposal.later ? " not now: " + h.proposal.later : ""} never: ${h.proposal.never}`));
|
|
5337
|
+
console.log(ui.color.gray(` trace: ${h.proposal.trace}`));
|
|
5338
|
+
}
|
|
5339
|
+
console.log("");
|
|
5340
|
+
}
|
|
5341
|
+
|
|
4819
5342
|
console.log(ui.header(`Keys this lane reads (${d.keys.length})`));
|
|
4820
5343
|
if (!d.keys.length) console.log(ui.color.gray(" none — this lane reads no config key."));
|
|
4821
5344
|
const pad = d.keys.length ? Math.max(...d.keys.map((k) => k.key.length)) : 0;
|
|
@@ -4826,7 +5349,9 @@ function laneConfigCmd(lane, claudeDir) {
|
|
|
4826
5349
|
? ui.color.yellow(" shadowed")
|
|
4827
5350
|
: k.is_overridden
|
|
4828
5351
|
? ui.color.green(" overridden")
|
|
4829
|
-
:
|
|
5352
|
+
: k.state === "learned"
|
|
5353
|
+
? ui.color.cyan(" learned") + ui.color.gray(` (${k.source})`)
|
|
5354
|
+
: ui.color.gray(" default");
|
|
4830
5355
|
console.log(` ${ui.color.cyan(k.key.padEnd(pad))} ${String(k.value).padEnd(24)} ${k.prio} ${k.family}${mark}`);
|
|
4831
5356
|
if (k.inert_reason) console.log(ui.color.gray(` ↳ ${k.inert_reason}`));
|
|
4832
5357
|
else if (k.shadow_reason) console.log(ui.color.gray(` ↳ ${k.shadow_reason}`));
|
|
@@ -8404,6 +8929,11 @@ function gotchaStatus(claudeDir, verbose) {
|
|
|
8404
8929
|
trigger: e.fields.trigger || null,
|
|
8405
8930
|
fields: e.fields,
|
|
8406
8931
|
})),
|
|
8932
|
+
// v2.0.0 W7 — the Behaviour panel's Gotchas tab: source, computed
|
|
8933
|
+
// status and a 7-week hits line per entry, plus the last sync state.
|
|
8934
|
+
// It READS the sync file; it never runs a sync.
|
|
8935
|
+
// `status` (a lane preflight) skips it: only `list` pays for the git call.
|
|
8936
|
+
...(verbose ? { panel: require("./gotcha.js").panelView(claudeDir, gotchaDeps(), entries) } : {}),
|
|
8407
8937
|
},
|
|
8408
8938
|
entries.length ? 0 : 1
|
|
8409
8939
|
);
|
|
@@ -9752,6 +10282,18 @@ ${line || ""}`;
|
|
|
9752
10282
|
process.exit(1);
|
|
9753
10283
|
}
|
|
9754
10284
|
|
|
10285
|
+
// The helpers `bin/gotcha.js` borrows: the v1 parser and paths stay HERE, so
|
|
10286
|
+
// `GOTCHA_HEAD` has one home and a 1.9.2 CLI still reads a 2.0 file.
|
|
10287
|
+
function gotchaDeps() {
|
|
10288
|
+
return { args, flag, positionals, emitJson, wantsJson, resolveClaudeDir, readOverride, repoRootOf, parseGotchas, gotchasPath, gotchasArchivePath, GOTCHA_HEAD };
|
|
10289
|
+
}
|
|
10290
|
+
|
|
10291
|
+
// An async importer that throws: one line, exit 1, never a stack in a lane.
|
|
10292
|
+
function asyncFail(e) {
|
|
10293
|
+
console.error("❌ " + ((e && e.message) || String(e)));
|
|
10294
|
+
process.exit(1);
|
|
10295
|
+
}
|
|
10296
|
+
|
|
9755
10297
|
function gotcha() {
|
|
9756
10298
|
if (flag("--global")) {
|
|
9757
10299
|
console.error("❌ orc gotcha is project-scoped — the memory is this repo's. Run it from the project (or with --dir <path>).");
|
|
@@ -9765,7 +10307,8 @@ function gotcha() {
|
|
|
9765
10307
|
gotchaStatus(claudeDir, false);
|
|
9766
10308
|
break;
|
|
9767
10309
|
case "list":
|
|
9768
|
-
if (args.includes("--
|
|
10310
|
+
if (args.includes("--candidates")) require("./gotcha.js").gotchaCmd(gotchaDeps(), "list");
|
|
10311
|
+
else if (args.includes("--archived")) gotchaArchived(claudeDir);
|
|
9769
10312
|
else gotchaStatus(claudeDir, true);
|
|
9770
10313
|
break;
|
|
9771
10314
|
case "show":
|
|
@@ -9777,13 +10320,35 @@ function gotcha() {
|
|
|
9777
10320
|
if (args.includes("--dry-run")) gotchaPruneDryRun(claudeDir);
|
|
9778
10321
|
else gotchaPrune(claudeDir);
|
|
9779
10322
|
break;
|
|
10323
|
+
// v2.0.0 W4 — the gotchas engine (bin/gotcha.js): the CLI assigns ids,
|
|
10324
|
+
// dedupes, matches, builds the reviewer card and computes promotion.
|
|
10325
|
+
case "add":
|
|
10326
|
+
case "match":
|
|
10327
|
+
case "card":
|
|
10328
|
+
case "filter":
|
|
10329
|
+
case "observe":
|
|
10330
|
+
case "accept":
|
|
10331
|
+
case "quality":
|
|
10332
|
+
case "why":
|
|
10333
|
+
case "export":
|
|
10334
|
+
require("./gotcha.js").gotchaCmd(gotchaDeps(), pos[1]);
|
|
10335
|
+
break;
|
|
10336
|
+
// v2.0.0 W6a — the importers and the review-step sync (bin/gotcha-import.js).
|
|
10337
|
+
// They FEED `observe`; they never write a gotchas.md entry themselves.
|
|
10338
|
+
case "import":
|
|
10339
|
+
require("./gotcha-import.js").importCmd(gotchaDeps()).catch(asyncFail);
|
|
10340
|
+
break;
|
|
10341
|
+
case "sync":
|
|
10342
|
+
require("./gotcha-import.js").syncCmd(gotchaDeps()).catch(asyncFail);
|
|
10343
|
+
break;
|
|
9780
10344
|
default:
|
|
9781
10345
|
console.error(
|
|
9782
10346
|
`Unknown: orc gotcha ${pos[1]}\n` +
|
|
9783
10347
|
"Usage: orc gotcha status | list [--archived] repair memory (exit 0 = entries, 1 = none)\n" +
|
|
9784
10348
|
" orc gotcha show <id> ONE entry, every field (exit 0 / 3 unknown id)\n" +
|
|
9785
10349
|
" orc gotcha prune [--dry-run] archive the low-value tail down to gotchas_max.\n" +
|
|
9786
|
-
" --dry-run NAMES every entry and writes nothing"
|
|
10350
|
+
" --dry-run NAMES every entry and writes nothing\n" +
|
|
10351
|
+
require("./gotcha.js").USAGE.replace(/^Usage: /, " ")
|
|
9787
10352
|
);
|
|
9788
10353
|
process.exit(1);
|
|
9789
10354
|
}
|
|
@@ -10227,9 +10792,12 @@ function pr(alias) {
|
|
|
10227
10792
|
// alias `orc pr-stack-template [<slug>]`.
|
|
10228
10793
|
if (alias === "pr-stack-template") return stackTemplate(claudeDir, pos[1]);
|
|
10229
10794
|
if (alias === "pr-stack-status") return stackStatus(claudeDir, pos[1]);
|
|
10795
|
+
// v2.0.0 W6a (Q1) — the unresolved review threads, read-only, via `gh`.
|
|
10796
|
+
if (pos[1] === "threads") return require("./gotcha-import.js").prThreadsCmd(gotchaDeps());
|
|
10230
10797
|
const usage =
|
|
10231
10798
|
"Usage: orc pr stack template [<slug>] write a fill-in stack-plan skeleton\n" +
|
|
10232
10799
|
" orc pr stack status [<slug>] is a plan READY? (exit 0 ready / 1 absent-or-unfilled)\n" +
|
|
10800
|
+
" orc pr threads <n> [--all] the unresolved review threads (read-only; exit 0 · 1 none open · 5 gh missing/not authed)\n" +
|
|
10233
10801
|
" (aliases: orc pr-stack-template, orc pr-stack-status)";
|
|
10234
10802
|
if (pos[1] !== "stack") {
|
|
10235
10803
|
console.error(`Unknown: orc ${pos.slice(1).join(" ") || ""}\n${usage}`);
|
|
@@ -11386,6 +11954,9 @@ function runCmd() {
|
|
|
11386
11954
|
// Read-only, and the ONE reader of the pending sidecar. 0 clear / 1 in-flight / 2 unknown.
|
|
11387
11955
|
if (sub === "inflight") return runInflightCmd(claudeDir);
|
|
11388
11956
|
|
|
11957
|
+
// v2.0.0 W6b (Q2) the pre-run tree, so `orc undo` can never eat a user edit.
|
|
11958
|
+
if (sub === "snapshot") return require("./run-undo.js").snapshotCmd(runUndoDeps());
|
|
11959
|
+
|
|
11389
11960
|
if (sub === "show") {
|
|
11390
11961
|
const arg = pos[2];
|
|
11391
11962
|
const pick = /^\d+$/.test(String(arg)) ? runs[Number(arg) - 1] : runs.find((r) => r.slug === arg);
|
|
@@ -11473,7 +12044,7 @@ function runCmd() {
|
|
|
11473
12044
|
if (sub !== "list") {
|
|
11474
12045
|
console.error(
|
|
11475
12046
|
`Unknown subcommand: orc run ${sub}\n` +
|
|
11476
|
-
`Try: orc run list | orc run show <slug|n> | orc run close <slug> --reason "<why>" | orc run reopen <slug>`
|
|
12047
|
+
`Try: orc run list | orc run show <slug|n> | orc run close <slug> --reason "<why>" | orc run reopen <slug> | orc run snapshot --run <slug>`
|
|
11477
12048
|
);
|
|
11478
12049
|
process.exit(1);
|
|
11479
12050
|
}
|
|
@@ -11653,7 +12224,7 @@ function stats() {
|
|
|
11653
12224
|
const empty = (msg) => {
|
|
11654
12225
|
if (asJson)
|
|
11655
12226
|
emitJson(
|
|
11656
|
-
{ log_dir: dir, runs: 0, from: null, to: null, lanes: {}, agents: {}, dispatches: 0, downgrades: 0, unfinished: 0, unknown_lane: 0, graph: statsGraph(claudeDir) },
|
|
12227
|
+
{ log_dir: dir, runs: 0, from: null, to: null, lanes: {}, agents: {}, dispatches: 0, downgrades: 0, unfinished: 0, unknown_lane: 0, graph: statsGraph(claudeDir), questions: null },
|
|
11657
12228
|
1
|
|
11658
12229
|
);
|
|
11659
12230
|
console.log(msg);
|
|
@@ -11715,6 +12286,13 @@ function stats() {
|
|
|
11715
12286
|
|
|
11716
12287
|
const laneRows = [...lanes.entries()].sort((a, b) => b[1] - a[1]);
|
|
11717
12288
|
const agentRows = [...agents.entries()].sort((a, b) => b[1] - a[1]);
|
|
12289
|
+
// v2.0.0 W6b (Q7) — questions per run, from the habit engine's ONE pass over
|
|
12290
|
+
// the ASK / QUESTION lines. null = it could not be computed, never a guess.
|
|
12291
|
+
let questions = null;
|
|
12292
|
+
try {
|
|
12293
|
+
const H = require("./habit.js");
|
|
12294
|
+
questions = H.questionStats(H.collect(claudeDir, habitDeps()), { since: typeof since === "string" && since ? since : null });
|
|
12295
|
+
} catch (_) {}
|
|
11718
12296
|
|
|
11719
12297
|
if (asJson) {
|
|
11720
12298
|
console.log(
|
|
@@ -11731,6 +12309,7 @@ function stats() {
|
|
|
11731
12309
|
unfinished,
|
|
11732
12310
|
unknown_lane: unknownLane,
|
|
11733
12311
|
graph: statsGraph(claudeDir),
|
|
12312
|
+
questions,
|
|
11734
12313
|
},
|
|
11735
12314
|
null,
|
|
11736
12315
|
2
|
|
@@ -11768,6 +12347,20 @@ function stats() {
|
|
|
11768
12347
|
["retrieval probably kept out", `~${kTok(gr.avoided_low)} – ${kTok(gr.avoided_high)} (an ESTIMATE — see \`orc graph gain\`)`],
|
|
11769
12348
|
]));
|
|
11770
12349
|
}
|
|
12350
|
+
// Q7 — "fewer questions", measured. Every field --json carries is printed.
|
|
12351
|
+
if (questions && questions.runs) {
|
|
12352
|
+
console.log("\n" + ui.color.bold("Questions") + ` ${questions.asked} asked · ${questions.answered_for_you} answered for you`);
|
|
12353
|
+
console.log(ui.kv([
|
|
12354
|
+
["per run (median)", String(questions.per_run_p50)],
|
|
12355
|
+
["asked by a subagent", String(questions.subagent_questions)],
|
|
12356
|
+
["how each was answered", Object.entries(questions.by).map(([k, v]) => `${k} ${v}`).join(" · ")],
|
|
12357
|
+
["last runs, oldest first", questions.per_run_trend.map((r) => r.asked).join(" ")],
|
|
12358
|
+
]));
|
|
12359
|
+
for (const [l, r] of Object.entries(questions.by_lane).sort((a, b) => b[1].asked - a[1].asked))
|
|
12360
|
+
console.log(` /${l.padEnd(18)} ${String(r.asked).padStart(3)} asked in ${plural(r.runs, "run")} · median ${r.per_run_p50} · ${r.answered_for_you} answered for you`);
|
|
12361
|
+
const pts = Object.entries(questions.by_point).sort((a, b) => b[1].asked - a[1].asked);
|
|
12362
|
+
for (const [q, r] of pts) console.log(ui.color.gray(` ${q.padEnd(34)} ${r.asked} asked · ${r.answered_for_you} answered for you`));
|
|
12363
|
+
}
|
|
11771
12364
|
console.log("\n" + ui.color.bold("Health"));
|
|
11772
12365
|
console.log(ui.kv([
|
|
11773
12366
|
["model downgrades", String(downgrades)],
|
|
@@ -34471,6 +35064,37 @@ function doctor() {
|
|
|
34471
35064
|
}
|
|
34472
35065
|
} catch (_) {}
|
|
34473
35066
|
|
|
35067
|
+
// 5a-bis2) the session hook (v2.0.0 Q8/Q9). TWO entries: `Stop` (the bell,
|
|
35068
|
+
// armed only by `notify: bell`) and `SessionStart` on `compact` (the run
|
|
35069
|
+
// pointer after a compaction, always armed). One finding when either is missing.
|
|
35070
|
+
try {
|
|
35071
|
+
const cfg = resolvedConfig(claudeDir);
|
|
35072
|
+
let hk = {};
|
|
35073
|
+
try {
|
|
35074
|
+
hk = (JSON.parse(fs.readFileSync(path.join(claudeDir, "settings.json"), "utf8")) || {}).hooks || {};
|
|
35075
|
+
} catch (_) {}
|
|
35076
|
+
const wiredOn = (event, matcher) =>
|
|
35077
|
+
Array.isArray(hk[event]) &&
|
|
35078
|
+
hk[event].some((e) => (e.matcher || null) === (matcher || null) && (e.hooks || []).some((h) => String(h.command || "").includes("orc-session-hook")));
|
|
35079
|
+
const missing = [["Stop", null], ["SessionStart", "compact"], ["SubagentStart", null]].filter(([e, m]) => !wiredOn(e, m)).map(([e, m]) => (m ? `${e} ${m}` : e));
|
|
35080
|
+
const bell = String(cfg.notify || "off") === "bell";
|
|
35081
|
+
if (missing.length)
|
|
35082
|
+
warn(
|
|
35083
|
+
"session-hook-unwired",
|
|
35084
|
+
`session hook not wired on ${missing.join(", ")} — ` +
|
|
35085
|
+
[
|
|
35086
|
+
missing.includes("SessionStart compact") ? "a compacted session is not told which run is in flight" : null,
|
|
35087
|
+
missing.includes("Stop") ? (bell ? "notify is bell, but the bell never rings" : "the bell cannot ring if notify is set to bell") : null,
|
|
35088
|
+
missing.includes("SubagentStart") ? "a reviewer gets no gotcha card" : null,
|
|
35089
|
+
]
|
|
35090
|
+
.filter(Boolean)
|
|
35091
|
+
.join("; ") +
|
|
35092
|
+
"; run `orc update`",
|
|
35093
|
+
{ fixable: true, fix: "orc update", fix_command: "orc update" }
|
|
35094
|
+
);
|
|
35095
|
+
else ok(`session hook wired (Stop + SessionStart compact + SubagentStart) · notify ${bell ? "bell" : "off"}`);
|
|
35096
|
+
} catch (_) {}
|
|
35097
|
+
|
|
34474
35098
|
// 5a-ter) the code graph (v1.8.0). ONE finding, and only while `code_graph` is
|
|
34475
35099
|
// on — the read-gate rule. DRIFTED is the only state worth a line: NONE is
|
|
34476
35100
|
// built by the next code lane's preflight, and a doctor that warns about a
|
|
@@ -40906,8 +41530,23 @@ Usage:
|
|
|
40906
41530
|
--reason "<why>" moves RESUME.md aside (never deletes it) and records why,
|
|
40907
41531
|
so it stops counting as waiting [--json]
|
|
40908
41532
|
orc run reopen <slug|n> put it back — it is waiting again [--json]
|
|
41533
|
+
orc run snapshot --run <slug> at run start: what the tree looked like BEFORE the run
|
|
41534
|
+
(a git ref + the untracked files), so undo can tell your
|
|
41535
|
+
edits from the run's own [--json]
|
|
41536
|
+
orc undo --run <slug> [--files a,b] PRINTS the commands that revert ONLY the files this run
|
|
41537
|
+
[--apply] changed, back to the snapshot; --apply runs them. Never
|
|
41538
|
+
touches a file outside the run, keeps a run file you
|
|
41539
|
+
edited later (exit 4) [--json]
|
|
40909
41540
|
orc stats [--since YYYY-MM-DD] [--json] how much you actually use each lane and agent, counted
|
|
40910
41541
|
from the trace filenames — no model, instant, free
|
|
41542
|
+
orc habit show|log|points|why|accept|decline|forget|reset|doctor|export|purge your usual answers,
|
|
41543
|
+
learned from the ASK lines in your traces (config key
|
|
41544
|
+
\`habits\`, off by default). Never applied without your yes [--json]
|
|
41545
|
+
orc habit repo [accept|decline|forget <id>] soft preferences read from git history (commit,
|
|
41546
|
+
branch, test-file naming). An accepted one rides in
|
|
41547
|
+
\`orc rules slice\` as the LEARNED tier [--json]
|
|
41548
|
+
orc trace write --packet -|<file> write ONE phase packet into the run's trace pair (.txt +
|
|
41549
|
+
.jsonl). Exit 2 = a bad packet, nothing written [--json]
|
|
40911
41550
|
orc wait the deterministic half of /orc-wait — a wait costs zero
|
|
40912
41551
|
tokens, because a detached command does it, not a model
|
|
40913
41552
|
orc wait lanes [--json] which lanes support a wait, what each one checkpoints,
|
|
@@ -44454,6 +45093,22 @@ const RULES_PRECEDENCE = [
|
|
|
44454
45093
|
{ rank: 3, layer: "ORC rules", scope: "everything", where: "<claude>/skills/_shared/rules/", beats: "nothing — it is the floor, and yours replaces it" },
|
|
44455
45094
|
];
|
|
44456
45095
|
|
|
45096
|
+
// v2.0.0 DE-8 — the LEARNED tier sits between your rules and the ORC rules.
|
|
45097
|
+
// It is named ONLY while `habits` is on: off = the ladder of the release before.
|
|
45098
|
+
function rulesPrecedence(claudeDir) {
|
|
45099
|
+
let on = false;
|
|
45100
|
+
try {
|
|
45101
|
+
on = require("./habit.js").habitsMode(readOverride(claudeDir).map) !== "off";
|
|
45102
|
+
} catch (_) {}
|
|
45103
|
+
if (!on) return RULES_PRECEDENCE;
|
|
45104
|
+
return [
|
|
45105
|
+
RULES_PRECEDENCE[0],
|
|
45106
|
+
RULES_PRECEDENCE[1],
|
|
45107
|
+
{ rank: 3, layer: "learned", scope: "commit, branch and test-file naming", where: "<claude>/orc/habits-state.json (`orc habit repo`)", beats: "the ORC rules — and your rules beat it" },
|
|
45108
|
+
Object.assign({}, RULES_PRECEDENCE[2], { rank: 4 }),
|
|
45109
|
+
];
|
|
45110
|
+
}
|
|
45111
|
+
|
|
44457
45112
|
// Which packs ride in which lane. `ui` is absent from every row ON PURPOSE: it
|
|
44458
45113
|
// is added PER TASK by the orchestrator when a task's declared files are
|
|
44459
45114
|
// front-end. A UI rule in a backend slice is tokens paid on every spawn for a
|
|
@@ -44722,6 +45377,10 @@ function rulesLine(packs, blocks, overrides) {
|
|
|
44722
45377
|
// which are what a rule's body is mostly made of. Nothing is hidden: the pack
|
|
44723
45378
|
// file is named beside the rules, exactly as the light tiers already are.
|
|
44724
45379
|
const RULES_COMPACT_LANES = new Set(["orc-quick"]);
|
|
45380
|
+
// v2.0.0 DE-17 — the SAME compact card for orc-mini / orc-fast, behind
|
|
45381
|
+
// `rules_card_compact` (default off). It flips only when eval E4 passes
|
|
45382
|
+
// (eval/results/2.0.0/E4.md). Off = the full card, byte for byte.
|
|
45383
|
+
const RULES_COMPACT_OPT_IN = new Set(["orc-mini", "orc-fast"]);
|
|
44725
45384
|
|
|
44726
45385
|
// One line out of a rule body: the first paragraph, whitespace collapsed, cut
|
|
44727
45386
|
// at a word boundary. A rule whose instruction does not survive that is a rule
|
|
@@ -44742,7 +45401,16 @@ function rulesSlice(claudeDir, lane, extraPacks) {
|
|
|
44742
45401
|
const overridden = new Set(overrides.filter((o) => o.known).map((o) => o.id));
|
|
44743
45402
|
const want = new Set([...(RULE_LANE_PACKS[lane] || []), ...(extraPacks || [])]);
|
|
44744
45403
|
const chosen = packs.packs.filter((p) => want.has(p.id));
|
|
44745
|
-
|
|
45404
|
+
let cfgMap = {};
|
|
45405
|
+
try {
|
|
45406
|
+
cfgMap = readOverride(claudeDir).map || {};
|
|
45407
|
+
} catch (_) {}
|
|
45408
|
+
const compact =
|
|
45409
|
+
RULES_COMPACT_LANES.has(lane) || (RULES_COMPACT_OPT_IN.has(lane) && /^(on|true)$/.test(String(cfgMap.rules_card_compact === undefined || cfgMap.rules_card_compact === null ? "on" : cfgMap.rules_card_compact)));
|
|
45410
|
+
// DE-8 — the LEARNED tier. `null` under `habits: off`: then the slice is
|
|
45411
|
+
// byte-identical to the release before, and the field is not even present.
|
|
45412
|
+
const learned = require("./habit.js").learnedRules(claudeDir, repoRootOf(claudeDir), cfgMap);
|
|
45413
|
+
const inForce = learned ? learned.filter((r) => r.state === "applied") : [];
|
|
44746
45414
|
|
|
44747
45415
|
const L = [];
|
|
44748
45416
|
if (!led.empty) {
|
|
@@ -44752,6 +45420,13 @@ function rulesSlice(claudeDir, lane, extraPacks) {
|
|
|
44752
45420
|
if (body) L.push(pr, body, "");
|
|
44753
45421
|
}
|
|
44754
45422
|
}
|
|
45423
|
+
if (inForce.length) {
|
|
45424
|
+
L.push(
|
|
45425
|
+
"LEARNED PREFERENCES — computed from this repo's own history, and accepted by you. Your rules above win over these. These win over the ORC rules below.",
|
|
45426
|
+
...inForce.map((r) => `${r.id} · ${r.rule}`),
|
|
45427
|
+
""
|
|
45428
|
+
);
|
|
45429
|
+
}
|
|
44755
45430
|
if (chosen.length) {
|
|
44756
45431
|
L.push(
|
|
44757
45432
|
`ORC RULES — the anti-slop baseline. Shipped with ORC, read-only. ${packs.count} rules, ${plural(chosen.length, "pack")} in this slice.`,
|
|
@@ -44795,6 +45470,9 @@ function rulesSlice(claudeDir, lane, extraPacks) {
|
|
|
44795
45470
|
// `compact` is REPORTED, never asked for. A reader that cannot tell a short
|
|
44796
45471
|
// card from a stripped one cannot trust either.
|
|
44797
45472
|
compact,
|
|
45473
|
+
// Present ONLY when habits is on (DE-16: off = zero bytes). Stale rows are
|
|
45474
|
+
// listed with their reason and are NOT in `text`.
|
|
45475
|
+
...(learned ? { learned } : {}),
|
|
44798
45476
|
text: L.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\s+$/, ""),
|
|
44799
45477
|
overrides,
|
|
44800
45478
|
line: rulesLine(packs, led.blocks, overrides),
|
|
@@ -45071,7 +45749,7 @@ function rulesCmd() {
|
|
|
45071
45749
|
},
|
|
45072
45750
|
priorities: RULE_PRIORITIES,
|
|
45073
45751
|
tiers: RULE_TIERS,
|
|
45074
|
-
precedence:
|
|
45752
|
+
precedence: rulesPrecedence(claudeDir),
|
|
45075
45753
|
overrides,
|
|
45076
45754
|
overrides_note:
|
|
45077
45755
|
"counted from ORC rule ids you NAMED in your own rules. A conflict you did not name is found by the agent at dispatch and returned as rules_conflicts[] — the CLI cannot parse intent, so it does not pretend to.",
|
|
@@ -45084,7 +45762,7 @@ function rulesCmd() {
|
|
|
45084
45762
|
|
|
45085
45763
|
console.log(`\norc rules — ${line}\n`);
|
|
45086
45764
|
console.log(ui.color.bold(" Precedence") + ui.color.gray(" the order never changes"));
|
|
45087
|
-
for (const r of
|
|
45765
|
+
for (const r of rulesPrecedence(claudeDir))
|
|
45088
45766
|
console.log(
|
|
45089
45767
|
` ${r.rank}. ${ui.color.cyan(r.layer.padEnd(12))} ${ui.color.gray(r.scope)}\n` +
|
|
45090
45768
|
` ${ui.color.gray(r.where)}\n` +
|
|
@@ -45247,7 +45925,7 @@ function lintScanFile(abs, rel, opts) {
|
|
|
45247
45925
|
} catch (_) {
|
|
45248
45926
|
return out;
|
|
45249
45927
|
}
|
|
45250
|
-
if (text.includes("
|
|
45928
|
+
if (text.includes("\u0000")) return out; // binary
|
|
45251
45929
|
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
45252
45930
|
|
|
45253
45931
|
// THE CARVE-OUT, and it is not a convenience. A rule that bans a word has to
|
|
@@ -45590,6 +46268,16 @@ function jsonCrash(err) {
|
|
|
45590
46268
|
case "graph":
|
|
45591
46269
|
graphCmd();
|
|
45592
46270
|
break;
|
|
46271
|
+
// v2.0.0 W2 — the habit engine. Every subcommand is a READ except accept,
|
|
46272
|
+
// decline, forget, reset and purge, which are habits-state.json's only writers.
|
|
46273
|
+
case "habit":
|
|
46274
|
+
require("./habit.js").habitCmd(habitDeps());
|
|
46275
|
+
break;
|
|
46276
|
+
// v2.0.0 T21 — the CLI holds the trace pen: one packet → the .txt + .jsonl
|
|
46277
|
+
// pair. The Haiku writer is the fallback when this exits ≠ 0.
|
|
46278
|
+
case "trace":
|
|
46279
|
+
require("./trace-write.js").traceCmd({ flag, positionals, emitJson, wantsJson, resolveClaudeDir, resolveLogDir, TRACE_VERBS, readOverride });
|
|
46280
|
+
break;
|
|
45593
46281
|
case "mock":
|
|
45594
46282
|
mock();
|
|
45595
46283
|
break;
|
|
@@ -45679,12 +46367,20 @@ function jsonCrash(err) {
|
|
|
45679
46367
|
case "pr-stack-status":
|
|
45680
46368
|
pr(cmd);
|
|
45681
46369
|
break;
|
|
46370
|
+
// v2.0.0 W6a (Q3) — the failing CI steps, read-only, flaky re-attempts named.
|
|
46371
|
+
case "ci":
|
|
46372
|
+
require("./gotcha-import.js").ciFailedCmd(gotchaDeps());
|
|
46373
|
+
break;
|
|
45682
46374
|
case "resume":
|
|
45683
46375
|
resume();
|
|
45684
46376
|
break;
|
|
45685
46377
|
case "run":
|
|
45686
46378
|
runCmd();
|
|
45687
46379
|
break;
|
|
46380
|
+
// v2.0.0 W6b (Q2) revert ONLY what a run changed, back to its snapshot.
|
|
46381
|
+
case "undo":
|
|
46382
|
+
require("./run-undo.js").undoCmd(runUndoDeps());
|
|
46383
|
+
break;
|
|
45688
46384
|
case "stats":
|
|
45689
46385
|
stats();
|
|
45690
46386
|
break;
|