@azure-id/orc 1.9.1 → 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 +390 -0
- package/README-id.md +20 -36
- package/README.md +24 -34
- package/bin/build-agents.js +206 -109
- package/bin/cli.js +760 -62
- 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/onboarding-content.js +4 -4
- package/bin/pricing.json +207 -200
- package/bin/run-undo.js +398 -0
- package/bin/trace-write.js +657 -0
- package/bin/verify-contracts.js +527 -81
- package/bin/verify-package.js +58 -11
- 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 +249 -152
- package/bin/webui/css/06-responsive.css +203 -178
- package/bin/webui/css/panels/behaviour.css +205 -0
- package/bin/webui/css/panels/knowledge.css +563 -116
- package/bin/webui/fixtures/behaviour.js +532 -0
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/hookui.js +5 -5
- package/bin/webui/fixtures/index.js +34 -0
- package/bin/webui/fixtures/knowledge.js +7 -1
- package/bin/webui/fixtures/settings.js +305 -305
- package/bin/webui/fixtures/stats.js +16 -0
- package/bin/webui/i18n/en/behaviour.json +143 -0
- package/bin/webui/i18n/en/knowledge.json +117 -2
- 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/knowledge.json +117 -2
- 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/bin/webui/js/panels/knowledge.js +552 -7
- package/bin/webui/js/panels/overview.js +13 -4
- package/mock-run/INDEX.md +109 -107
- package/mock-run/context-combiner.md +100 -100
- package/mock-run/gotcha-import.md +118 -0
- package/mock-run/habits.md +129 -0
- package/mock-run/orc-budget.md +534 -534
- package/mock-run/orc-challenge-council.md +262 -262
- package/mock-run/orc-quick.md +6 -1
- package/mock-run/orc-ultra.md +103 -103
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +49 -49
- package/templates/agents/orc-advisor-opus-5-xhigh.md +50 -56
- package/templates/agents/orc-analyze-mini-opus-5-med.md +55 -60
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +55 -58
- package/templates/agents/orc-challenge-advisor-opus-5-med.md +75 -75
- package/templates/agents/orc-challenge-contrarian-opus-5-high.md +110 -110
- package/templates/agents/orc-challenge-executor-opus-5-med.md +114 -114
- package/templates/agents/orc-challenge-expansionist-opus-5-med.md +112 -112
- package/templates/agents/orc-challenge-judge-opus-5-high.md +132 -132
- package/templates/agents/orc-challenge-outsider-opus-5-low.md +109 -109
- package/templates/agents/orc-challenge-principles-opus-5-high.md +109 -109
- package/templates/agents/orc-challenge-reader-opus-5-low.md +90 -90
- package/templates/agents/orc-claude-writer-opus-4-8-high.md +48 -53
- package/templates/agents/orc-claude-writer-opus-5-med.md +48 -55
- package/templates/agents/orc-context-combiner-opus-5-high.md +78 -88
- package/templates/agents/orc-doc-checker-opus-5-low.md +108 -108
- package/templates/agents/orc-doc-writer-opus-5-med.md +134 -134
- 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 +15 -7
- package/templates/agents/orc-executor-opus-5-low.md +15 -7
- package/templates/agents/orc-executor-opus-5-med.md +15 -7
- 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 +81 -85
- package/templates/agents/orc-learn-writer-opus-5-low.md +67 -73
- package/templates/agents/orc-pattern-codifier-opus-5-med.md +58 -65
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +58 -63
- package/templates/agents/orc-planner-mini-opus-5-med.md +4 -7
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -3
- package/templates/agents/orc-planner-opus-5-med.md +157 -160
- package/templates/agents/orc-recon-opus-5-low.md +3 -10
- package/templates/agents/orc-recon-sonnet-4-6-med.md +1 -8
- package/templates/agents/orc-retro-opus-5-med.md +8 -10
- package/templates/agents/orc-retro-sonnet-5-high.md +6 -7
- package/templates/agents/orc-reviewer-opus-5-med.md +96 -60
- package/templates/agents/orc-scout-opus-5-low.md +35 -40
- package/templates/agents/orc-scout-sonnet-4-6-high.md +35 -39
- package/templates/agents/orc-system-analyst-opus-5-high.md +115 -120
- package/templates/agents/orc-test-author-opus-5-med.md +70 -71
- package/templates/agents/orc-test-designer-opus-5-high.md +158 -158
- package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -130
- package/templates/agents/orc-trace-writer-haiku-4-5.md +3 -7
- package/templates/agents/orc-verifier-opus-5-med.md +77 -69
- package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +74 -79
- package/templates/agents/orc-wiki-scanner-opus-5-med.md +74 -81
- 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/commands/orc-ultra.md +17 -17
- package/templates/commands/orc-verify.md +11 -11
- package/templates/hooks/README.md +36 -2
- package/templates/hooks/orc-effort-guard.js +178 -178
- package/templates/hooks/orc-session-hook.js +264 -0
- package/templates/hooks/orc-statusline.js +9 -7
- package/templates/skills/_shared/README.md +9 -0
- package/templates/skills/_shared/code-graph.md +48 -56
- package/templates/skills/_shared/config-precedence.md +200 -198
- package/templates/skills/_shared/extra-dispatch.md +1331 -1346
- 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/opus5-only.md +8 -8
- package/templates/skills/_shared/phases/README.md +142 -83
- package/templates/skills/_shared/phases/analyst-gates.md +125 -136
- 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 +16 -45
- package/templates/skills/context-combiner/schemas/combined-report.md +78 -78
- package/templates/skills/orc/README.md +2 -2
- package/templates/skills/orc/SKILL.md +41 -69
- package/templates/skills/orc/config.md +9 -9
- package/templates/skills/orc/examples/full-run-mock.md +73 -73
- package/templates/skills/orc/references/effort-and-mode.md +222 -222
- 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 +10 -11
- package/templates/skills/orc/references/ultra-mode.md +11 -9
- 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-planner/SKILL.md +2 -2
- package/templates/skills/orc/subskills/orc-planner-mini/SKILL.md +121 -121
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +35 -76
- 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 +34 -45
- 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 +32 -68
- package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -42
- package/templates/skills/orc-analyze/schemas/report-audit.md +84 -83
- package/templates/skills/orc-analyze/schemas/report-prose.md +64 -63
- package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -78
- package/templates/skills/orc-analyze-mini/SKILL.md +35 -70
- package/templates/skills/orc-challenge/README.md +142 -142
- package/templates/skills/orc-challenge/examples/council-full-roster.md +273 -273
- package/templates/skills/orc-challenge/references/council.md +315 -315
- package/templates/skills/orc-challenge/references/intake.md +171 -171
- package/templates/skills/orc-claude/SKILL.md +14 -14
- package/templates/skills/orc-diy/README.md +174 -143
- package/templates/skills/orc-diy/SKILL.md +23 -74
- package/templates/skills/orc-diy/references/blocks/pattern.md +18 -18
- package/templates/skills/orc-diy/references/flow-schema.md +1 -1
- package/templates/skills/orc-doc/README.md +229 -229
- package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +325 -325
- package/templates/skills/orc-doc/references/chunking.md +527 -527
- package/templates/skills/orc-fast/SKILL.md +45 -73
- package/templates/skills/orc-judge/SKILL.md +77 -82
- package/templates/skills/orc-learn/SKILL.md +14 -14
- package/templates/skills/orc-learn/examples/learn-run-mock.md +61 -61
- package/templates/skills/orc-mini/SKILL.md +65 -113
- 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 +21 -39
- package/templates/skills/orc-verify/examples/verify-mock.md +33 -33
- package/templates/skills/orc-wait/SKILL.md +156 -163
- package/templates/skills/orc-wiki/references/extra.md +1 -1
- 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
|
|
|
@@ -1067,6 +1105,8 @@ function upgrade() {
|
|
|
1067
1105
|
// ---------------------------------------------------------------------------
|
|
1068
1106
|
|
|
1069
1107
|
const KNOWN_MODELS = [
|
|
1108
|
+
"claude-opus-5-5",
|
|
1109
|
+
// Kept so a config written before v1.9.2 still validates.
|
|
1070
1110
|
"claude-opus-5",
|
|
1071
1111
|
"claude-opus-4-8",
|
|
1072
1112
|
"claude-opus-4-7",
|
|
@@ -1128,6 +1168,15 @@ const vFallbackAgent = tag((raw) => {
|
|
|
1128
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.",
|
|
1129
1169
|
};
|
|
1130
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"] });
|
|
1131
1180
|
const vPath = tag(
|
|
1132
1181
|
(raw) => (raw && raw.trim() ? { value: raw } : { err: "must be a non-empty path" }),
|
|
1133
1182
|
{ kind: "path" }
|
|
@@ -1159,6 +1208,26 @@ const vRepo = tag(
|
|
|
1159
1208
|
: { err: "must be a GitHub owner/repo (e.g. azure-id/orc)" },
|
|
1160
1209
|
{ kind: "repo" }
|
|
1161
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" });
|
|
1162
1231
|
|
|
1163
1232
|
// Roles `orc extra` may hand to a non-Claude worker. Declared HERE, above
|
|
1164
1233
|
// CONFIG_META, because CONFIG_META is evaluated at module load and a `const`
|
|
@@ -1253,7 +1322,7 @@ const CONFIG_FAMILIES = {
|
|
|
1253
1322
|
// without being a CONFIG_META key, because where it sits in the
|
|
1254
1323
|
// precedence is a fact even though `orc config set` refuses to write it.
|
|
1255
1324
|
registry_less: true,
|
|
1256
|
-
shadow_note: "shadowed by {by} — executors use the fixed 2-band Opus 5 ladder",
|
|
1325
|
+
shadow_note: "shadowed by {by} — executors use the fixed 2-band Opus 5.5 ladder",
|
|
1257
1326
|
},
|
|
1258
1327
|
{ prio: "P3", key: null, terminal: "the shipped score→model table" },
|
|
1259
1328
|
],
|
|
@@ -1317,6 +1386,14 @@ const CONFIG_FAMILIES = {
|
|
|
1317
1386
|
// structure costs no model tokens, so nothing about a dispatched model can
|
|
1318
1387
|
// shadow whether it is built.
|
|
1319
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" },
|
|
1320
1397
|
};
|
|
1321
1398
|
|
|
1322
1399
|
// Ordered, tiered metadata. Common first, then advanced.
|
|
@@ -1346,6 +1423,15 @@ const CONFIG_META = [
|
|
|
1346
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." },
|
|
1347
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." },
|
|
1348
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)." },
|
|
1349
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." },
|
|
1350
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." },
|
|
1351
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." },
|
|
@@ -1440,7 +1526,7 @@ const CONFIG_META = [
|
|
|
1440
1526
|
// decision with a recorded reason (the /orc-pact retirement rule).
|
|
1441
1527
|
{ key: "extra_demote_after", def: 2, tier: "common", answers: [{ family: "extra", prio: "P2", mode: "replace" }], gated_by: "extra_enabled", lanes: [], validate: vInt(0), options: [0, 2, 3, 5], desc: "Consecutive `stalled` dispatches on ONE profile, inside one run, before that profile is DEMOTED to the bottom of the ladder for the rest of the run — so `opus5_only` (or the shipped score table) becomes the effective P0 and the work stays on Claude. Only `stalled` counts: a 401 or a rate limit has its own answer (`extra_on_failure`, the vault, `extra_resume`), and demoting on one would hide a credential problem behind a routing change. A resume of the same stalled attempt is the SAME stall, never a second one. A demotion is RUN state — it never writes your config — it is ANNOUNCED before the next dispatch, and it is never auto-promoted back: `orc extra promote <run> --reason \"<why>\"`. 0 turns this clock off and leaves only extra_demote_stale_min." },
|
|
1442
1528
|
{ key: "extra_demote_stale_min", def: 20, tier: "common", answers: [{ family: "extra", prio: "P2", mode: "replace" }], gated_by: "extra_enabled", lanes: [], validate: vInt(0), options: [0, 10, 20, 45], desc: "Minutes a LIVE foreign attempt may show no observable progress before its profile is demoted. This is a different question from `extra_stall_s`, which stops ONE dispatch after 180s of silence: this clock is about the RUN — two workers in flight, both quiet, and a wave that is going nowhere — so the two have their own budgets and their own off values and neither is a simplification of the other. It reads the journal's own progress file on disk, never a remembered fact. 0 turns this clock off and leaves only extra_demote_after." },
|
|
1443
|
-
{ key: "opus5_only", def: false, tier: "common", answers: [{ family: "executor-band", prio: "P1", mode: "replace" }, { family: "fixed-role-model", prio: "P1", mode: "replace" }], lanes: ["orc", "orc-analyze", "orc-challenge", "orc-claude", "orc-diy", "orc-doc", "orc-fast", "orc-mini", "orc-pattern", "orc-quick", "orc-retro", "orc-wiki"], validate: vEnum("true", "false"), options: ["true", "false"], desc: "EVERY dispatched role uses ONE model — Opus 5 — with EFFORT as the cost dial (executors: [0,40) low · [40,80) medium · [80,100] high; each fixed role its own pinned effort). Deep SWE-benchmark work on cost vs efficiency across Claude models finds a single Opus 5 agent with the effort ladder the most efficient setup. It FORCES: while on it outranks a hand-written rubric_bands_override. Needs an Opus 5 main session or EVERY dispatch silently downgrades. Excludes the Haiku trace writer and orc-diy (compile-owned)." },
|
|
1529
|
+
{ key: "opus5_only", def: false, tier: "common", answers: [{ family: "executor-band", prio: "P1", mode: "replace" }, { family: "fixed-role-model", prio: "P1", mode: "replace" }], lanes: ["orc", "orc-analyze", "orc-challenge", "orc-claude", "orc-diy", "orc-doc", "orc-fast", "orc-mini", "orc-pattern", "orc-quick", "orc-retro", "orc-wiki"], validate: vEnum("true", "false"), options: ["true", "false"], desc: "EVERY dispatched role uses ONE model — Opus 5.5 — with EFFORT as the cost dial (executors: [0,40) low · [40,80) medium · [80,100] high; each fixed role its own pinned effort). Deep SWE-benchmark work on cost vs efficiency across Claude models finds a single Opus 5.5 agent with the effort ladder the most efficient setup. It FORCES: while on it outranks a hand-written rubric_bands_override. Needs an Opus 5.5 main session or EVERY dispatch silently downgrades. Excludes the Haiku trace writer and orc-diy (compile-owned)." },
|
|
1444
1530
|
// --- v0.46.0 — the six new lanes ------------------------------------------
|
|
1445
1531
|
{ key: "pact_gate", def: "warn", tier: "common", answers: [{ family: "pact", prio: "P2", mode: "replace" }], lanes: ["orc", "orc-pact"], validate: vEnum("off", "warn"), options: ["off", "warn"], desc: "Invariant ledger at Phase 1 + planning: warn = print the one pact line and inject a DRIFTED/BROKEN promise whose anchors intersect the plan's declared files as a planner constraint; off = nothing. NEVER blocks — a promise is advice with a receipt, not a gate. See /orc-pact." },
|
|
1446
1532
|
{ key: "pact_recheck_on_verify", def: "true", tier: "common", answers: [{ family: "pact", prio: "P2", mode: "replace" }], lanes: ["orc", "orc-pact"], validate: vEnum("true", "false"), options: ["true", "false"], desc: "Phase 6: re-run the cheap checks for ONLY the invariants the change touched (`orc pact check`), so a promise that just leaked is caught in the run that broke it." },
|
|
@@ -1475,7 +1561,23 @@ const CONFIG_META = [
|
|
|
1475
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." },
|
|
1476
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`." },
|
|
1477
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." },
|
|
1478
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." },
|
|
1479
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." },
|
|
1480
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." },
|
|
1481
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." },
|
|
@@ -1523,7 +1625,7 @@ const LEGACY_KEYS = {
|
|
|
1523
1625
|
// So `config list` prints them, `config list --json` carries them, and
|
|
1524
1626
|
// `config set` refuses them BY NAME instead of with a generic unknown-key list.
|
|
1525
1627
|
const RETIRED_KEYS = {
|
|
1526
|
-
fable5_enabled: { removed_in: "1.0.0", why: "the Fable 5 role override was removed — every role dispatches its shipped Claude agent, or the Opus 5 variant under opus5_only" },
|
|
1628
|
+
fable5_enabled: { removed_in: "1.0.0", why: "the Fable 5 role override was removed — every role dispatches its shipped Claude agent, or the Opus 5.5 variant under opus5_only" },
|
|
1527
1629
|
fable5_roles: { removed_in: "1.0.0", why: "the Fable 5 role override was removed" },
|
|
1528
1630
|
fable5_effort: { removed_in: "1.0.0", why: "the Fable 5 role override was removed" },
|
|
1529
1631
|
};
|
|
@@ -1825,7 +1927,7 @@ function groupRule(title) {
|
|
|
1825
1927
|
// `not-read`: a higher rank in this key's family resolved, so nobody looked;
|
|
1826
1928
|
// `inert`: this key's master gate is off, which removes it from the
|
|
1827
1929
|
// conversation rather than losing it a precedence contest (`design-03` §5).
|
|
1828
|
-
function configKeyState(m, map, claudeDir) {
|
|
1930
|
+
function configKeyState(m, map, claudeDir, learned) {
|
|
1829
1931
|
const has = Object.prototype.hasOwnProperty.call(map, m.key);
|
|
1830
1932
|
if (m.gated_by) {
|
|
1831
1933
|
const g = metaFor(m.gated_by);
|
|
@@ -1839,14 +1941,35 @@ function configKeyState(m, map, claudeDir) {
|
|
|
1839
1941
|
}
|
|
1840
1942
|
const why = shadowReason(m.key, map, claudeDir);
|
|
1841
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
|
+
};
|
|
1842
1953
|
return { state: has ? "overridden" : "default", reason: null, source: has ? "overridden" : "default" };
|
|
1843
1954
|
}
|
|
1844
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
|
+
|
|
1845
1967
|
const STATE_PAINT = {
|
|
1846
1968
|
overridden: (s) => ui.color.green(s),
|
|
1847
1969
|
default: (s) => ui.color.gray(s),
|
|
1848
1970
|
"not-read": (s) => ui.color.yellow(s),
|
|
1849
1971
|
inert: (s) => ui.color.yellow(s),
|
|
1972
|
+
learned: (s) => ui.color.cyan(s),
|
|
1850
1973
|
};
|
|
1851
1974
|
|
|
1852
1975
|
function configList(claudeDir) {
|
|
@@ -1861,6 +1984,7 @@ function configList(claudeDir) {
|
|
|
1861
1984
|
// UI axis and it still drives the interactive menu, so it rides along as a row
|
|
1862
1985
|
// marker instead of being the table's spine.
|
|
1863
1986
|
const families = laneFamilies(null, map, claudeDir);
|
|
1987
|
+
const learned = configLearned(map, claudeDir);
|
|
1864
1988
|
const byFamily = new Map();
|
|
1865
1989
|
for (const m of CONFIG_META) {
|
|
1866
1990
|
const f = m.answers[0].family;
|
|
@@ -1889,9 +2013,9 @@ function configList(claudeDir) {
|
|
|
1889
2013
|
return (ra === -1 ? 99 : ra) - (rb === -1 ? 99 : rb);
|
|
1890
2014
|
});
|
|
1891
2015
|
for (const m of metas) {
|
|
1892
|
-
const st = configKeyState(m, map, claudeDir);
|
|
2016
|
+
const st = configKeyState(m, map, claudeDir, learned);
|
|
1893
2017
|
const has = Object.prototype.hasOwnProperty.call(map, m.key);
|
|
1894
|
-
const val = has ? map[m.key] : m.def;
|
|
2018
|
+
const val = has ? map[m.key] : st.value !== undefined ? st.value : m.def;
|
|
1895
2019
|
const src = (STATE_PAINT[st.state] || ((s) => s))(st.state.padEnd(10));
|
|
1896
2020
|
const opts = m.options ? ` ${ui.color.gray("[options: " + m.options.join(" | ") + "]")}` : "";
|
|
1897
2021
|
// v0.55.0 — a DEPRECATED member of a live key is marked on the row that
|
|
@@ -2082,7 +2206,7 @@ function shadowReason(key, map, claudeDir) {
|
|
|
2082
2206
|
//
|
|
2083
2207
|
// v1.0.0 W4 — TWO BANDS, not three, and the same 90 edge as the default table's
|
|
2084
2208
|
// top two rows (D13). That symmetry is the point: once the default table's high
|
|
2085
|
-
// end is already Opus 5 with effort as the dial, the forcing mode differs from
|
|
2209
|
+
// end is already Opus 5.5 with effort as the dial, the forcing mode differs from
|
|
2086
2210
|
// it only BELOW 65, so a third band here would be a distinction the default
|
|
2087
2211
|
// table stopped making.
|
|
2088
2212
|
//
|
|
@@ -2128,7 +2252,7 @@ function scoreTableJson(map, claudeDir) {
|
|
|
2128
2252
|
// > the default 6-band table
|
|
2129
2253
|
//
|
|
2130
2254
|
// Extra is an OVERLAY, not a replacement, which is what makes "cheap grunt
|
|
2131
|
-
// work goes to DeepSeek, hard work stays on Opus 5" a two-command setup
|
|
2255
|
+
// work goes to DeepSeek, hard work stays on Opus 5.5" a two-command setup
|
|
2132
2256
|
// rather than a full table rewrite. `active` can therefore read as a
|
|
2133
2257
|
// COMPOSITE — because the truth is a composite, and a single word would be
|
|
2134
2258
|
// a lie about what the next dispatch will do.
|
|
@@ -2193,10 +2317,11 @@ function scoreTableJson(map, claudeDir) {
|
|
|
2193
2317
|
function configListJson(claudeDir) {
|
|
2194
2318
|
const { path: p, map } = readOverride(claudeDir);
|
|
2195
2319
|
const has = (k) => Object.prototype.hasOwnProperty.call(map, k);
|
|
2320
|
+
const learned = configLearned(map, claudeDir);
|
|
2196
2321
|
const keys = CONFIG_META.map((m) => {
|
|
2197
2322
|
const v = m.validate || {};
|
|
2198
2323
|
const why = shadowReason(m.key, map, claudeDir);
|
|
2199
|
-
const st = configKeyState(m, map, claudeDir);
|
|
2324
|
+
const st = configKeyState(m, map, claudeDir, learned);
|
|
2200
2325
|
return {
|
|
2201
2326
|
key: m.key,
|
|
2202
2327
|
tier: m.tier,
|
|
@@ -2215,7 +2340,7 @@ function configListJson(claudeDir) {
|
|
|
2215
2340
|
// The master gate that makes this key inert. A gate does not win a
|
|
2216
2341
|
// precedence contest; it removes its dependants from the conversation.
|
|
2217
2342
|
gated_by: m.gated_by || null,
|
|
2218
|
-
value: has(m.key) ? map[m.key] : m.def,
|
|
2343
|
+
value: has(m.key) ? map[m.key] : st.value !== undefined ? st.value : m.def,
|
|
2219
2344
|
default: m.def,
|
|
2220
2345
|
is_overridden: has(m.key),
|
|
2221
2346
|
is_shadowed: !!why,
|
|
@@ -2226,7 +2351,7 @@ function configListJson(claudeDir) {
|
|
|
2226
2351
|
// answers "does anything read it", from `LANE_RANK_STATES`' own words.
|
|
2227
2352
|
state: st.state,
|
|
2228
2353
|
state_reason: st.reason,
|
|
2229
|
-
source:
|
|
2354
|
+
source: st.source,
|
|
2230
2355
|
desc: m.desc,
|
|
2231
2356
|
options: m.options || null,
|
|
2232
2357
|
control: {
|
|
@@ -2290,7 +2415,7 @@ function configListJson(claudeDir) {
|
|
|
2290
2415
|
// lane-scoped answer asks `orc lane config <lane> --json`, which is the
|
|
2291
2416
|
// command that owns it.
|
|
2292
2417
|
families_resolved: laneFamilies(null, map, claudeDir),
|
|
2293
|
-
rank_states: LANE_RANK_STATES,
|
|
2418
|
+
rank_states: require("./habit.js").habitsMode(map) !== "off" ? LANE_RANK_STATES.concat("learned") : LANE_RANK_STATES,
|
|
2294
2419
|
// Permanently on and deliberately not a key — say so, or a reader hunts for
|
|
2295
2420
|
// the switch (only the folder is configurable).
|
|
2296
2421
|
behavior_trace: { always_on: true, configurable_key: "log_dir" },
|
|
@@ -2356,6 +2481,14 @@ function configSet(claudeDir, key, rawValue) {
|
|
|
2356
2481
|
// precedent: a renamed mechanism must never be a silent revert.
|
|
2357
2482
|
if (key === "extra_roles") extraLegacyRoleWarn(res.value);
|
|
2358
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
|
+
);
|
|
2359
2492
|
if (key === "opus5_only" || key === "extra_enabled") extraShadowNotice(claudeDir);
|
|
2360
2493
|
}
|
|
2361
2494
|
|
|
@@ -2373,7 +2506,7 @@ function extraLegacyRoleWarn(value) {
|
|
|
2373
2506
|
|
|
2374
2507
|
// A shadowed setting must never be silent — the v0.36.0 rule, applied to a
|
|
2375
2508
|
// shadow that runs BOTH WAYS. Turning on opus5_only while route rows exist
|
|
2376
|
-
// does not give you the Opus 5 ladder everywhere, and turning on Extra does
|
|
2509
|
+
// does not give you the Opus 5.5 ladder everywhere, and turning on Extra does
|
|
2377
2510
|
// not take the Claude table away; saying either would be wrong. So name the
|
|
2378
2511
|
// ranges, which is the only statement that is true from both sides.
|
|
2379
2512
|
function extraShadowNotice(claudeDir) {
|
|
@@ -2434,17 +2567,17 @@ function opus5Notice(on, claudeDir) {
|
|
|
2434
2567
|
console.log(
|
|
2435
2568
|
"\n " + ui.color.bold("Opus-5-only dispatch is now ACTIVE — for EVERY role, not just executors.") + "\n" +
|
|
2436
2569
|
"\n Why: deep SWE-benchmark work on cost vs efficiency across Claude models finds a\n" +
|
|
2437
|
-
" single Opus 5 agent, with the EFFORT ladder as the cost dial, the most efficient\n" +
|
|
2570
|
+
" single Opus 5.5 agent, with the EFFORT ladder as the cost dial, the most efficient\n" +
|
|
2438
2571
|
" configuration. You trade model-class variety for effort variety.\n" +
|
|
2439
2572
|
"\n Scored executors (/orc + /orc-ultra):\n\n" +
|
|
2440
2573
|
ladderTable(OPUS5_SCORE_TABLE) +
|
|
2441
2574
|
"\n Fixed roles now dispatched instead of their defaults:\n\n" + roleRows + "\n" +
|
|
2442
|
-
"\n Already Opus 5, unchanged: analyst · planner · reviewer · verifier · test-author\n" +
|
|
2575
|
+
"\n Already Opus 5.5, unchanged: analyst · planner · reviewer · verifier · test-author\n" +
|
|
2443
2576
|
" · combiner · learn-writer · advisor · judge.\n" +
|
|
2444
2577
|
" NEVER forced: orc-trace-writer-haiku-4-5 (it transcribes a packet, no reasoning)\n" +
|
|
2445
2578
|
" and orc-diy (its table is compile-owned — re-run `orc diy compile` to change it).\n" +
|
|
2446
2579
|
"\n " + ui.mark.warn("Tier requirement — read this one:") + "\n" +
|
|
2447
|
-
" Today only the [90,100] executor band needs an Opus 5 main session. With this ON,\n" +
|
|
2580
|
+
" Today only the [90,100] executor band needs an Opus 5.5 main session. With this ON,\n" +
|
|
2448
2581
|
" EVERY dispatch does. A subagent can never outrank the main session, so on a lower\n" +
|
|
2449
2582
|
" session every role silently falls back to the session model and the tier-honesty\n" +
|
|
2450
2583
|
" rule reports a downgrade on EVERY return instead of occasionally. Hooks cannot\n" +
|
|
@@ -2860,17 +2993,17 @@ const LANE_INERT = {
|
|
|
2860
2993
|
{ key: "extra_fallback_agent", reason: "re-opening the gate IS the ask, so a second menu composed from a config key would be the same question twice in different words" },
|
|
2861
2994
|
],
|
|
2862
2995
|
"orc-challenge": [
|
|
2863
|
-
{ key: "opus5_only", reason: "every agent in this lane is already claude-opus-5, so this is a no-op — the lane is unaffected, not exempt" },
|
|
2996
|
+
{ key: "opus5_only", reason: "every agent in this lane is already claude-opus-5-5, so this is a no-op — the lane is unaffected, not exempt" },
|
|
2864
2997
|
],
|
|
2865
2998
|
"orc-doc": [
|
|
2866
|
-
{ key: "opus5_only", reason: "both agents in this lane are already claude-opus-5, so this is a no-op — the lane is unaffected, not exempt" },
|
|
2999
|
+
{ key: "opus5_only", reason: "both agents in this lane are already claude-opus-5-5, so this is a no-op — the lane is unaffected, not exempt" },
|
|
2867
3000
|
],
|
|
2868
|
-
// v1.5.0 — both agents in this lane ship as claude-opus-5, and the
|
|
3001
|
+
// v1.5.0 — both agents in this lane ship as claude-opus-5-5, and the
|
|
2869
3002
|
// interpreter's `low` is a MEASUREMENT choice rather than a cost one: a
|
|
2870
3003
|
// harder-thinking interpreter reasons its way to why a leaked stack trace is
|
|
2871
3004
|
// probably fine in staging, which is exactly the gap it exists to find.
|
|
2872
3005
|
"orc-test": [
|
|
2873
|
-
{ key: "opus5_only", reason: "both agents in this lane are already claude-opus-5, so this is a no-op — the lane is unaffected, not exempt" },
|
|
3006
|
+
{ key: "opus5_only", reason: "both agents in this lane are already claude-opus-5-5, so this is a no-op — the lane is unaffected, not exempt" },
|
|
2874
3007
|
],
|
|
2875
3008
|
// v1.1.0 W2 — this lane DISPATCHES NOTHING. A detached command does the
|
|
2876
3009
|
// waiting, so every family that answers "which model runs this" has no work
|
|
@@ -3145,7 +3278,7 @@ function laneAnnounce(lane, map, claudeDir, families) {
|
|
|
3145
3278
|
out.push(
|
|
3146
3279
|
off
|
|
3147
3280
|
? `opus5_only: ON, and INERT in this lane — ${off.reason}`
|
|
3148
|
-
: "opus5_only: ON — every dispatched role uses Opus 5, with effort as the only cost dial"
|
|
3281
|
+
: "opus5_only: ON — every dispatched role uses Opus 5.5, with effort as the only cost dial"
|
|
3149
3282
|
);
|
|
3150
3283
|
}
|
|
3151
3284
|
if (isTrue(map.extra_enabled)) {
|
|
@@ -3232,9 +3365,26 @@ function laneConfig(lane, claudeDir) {
|
|
|
3232
3365
|
inert_reason: hit ? hit.reason : gate ? `${m.gated_by} is off, so nothing in its block is consulted` : null,
|
|
3233
3366
|
};
|
|
3234
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
|
+
}
|
|
3235
3383
|
const effective = {};
|
|
3236
3384
|
for (const k of keys) effective[k.key] = k.value;
|
|
3237
|
-
|
|
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 = {
|
|
3238
3388
|
lane,
|
|
3239
3389
|
command: (LANES.find((l) => l.lane === lane) || {}).command || null,
|
|
3240
3390
|
command_note: (LANES.find((l) => l.lane === lane) || {}).note || null,
|
|
@@ -3245,16 +3395,31 @@ function laneConfig(lane, claudeDir) {
|
|
|
3245
3395
|
effective,
|
|
3246
3396
|
families,
|
|
3247
3397
|
roles: laneRoles(lane, map, claudeDir),
|
|
3248
|
-
announce
|
|
3398
|
+
announce,
|
|
3249
3399
|
stops: LANE_STOPS.filter((s) => rows.some((m) => m.key === s.key))
|
|
3250
3400
|
.filter((s) => s.arms(has(s.key) ? map[s.key] : metaFor(s.key).def))
|
|
3251
3401
|
.map((s) => ({ key: s.key, value: has(s.key) ? map[s.key] : metaFor(s.key).def, when: s.when, action: s.action })),
|
|
3252
3402
|
// The keys this lane deliberately ignores. An empty answer is an ANSWER
|
|
3253
3403
|
// (v0.43.0): "this lane does not read doc_language" is information, and it
|
|
3254
3404
|
// is what makes the two-way lint possible at all.
|
|
3255
|
-
not_read: CONFIG_META.filter((m) => !(m.lanes || []).includes(lane))
|
|
3256
|
-
|
|
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,
|
|
3257
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 };
|
|
3258
3423
|
}
|
|
3259
3424
|
|
|
3260
3425
|
function laneList(claudeDir) {
|
|
@@ -3328,6 +3493,19 @@ const LANE_CALLS = {
|
|
|
3328
3493
|
never: "never read `clear` as proof that an AD-HOC dispatch finished — the hook writes no SPAWN for one, so no record exists",
|
|
3329
3494
|
lanes: ["orc", "orc-doc", "orc-fast", "orc-mini", "orc-quick", "orc-test", "orc-wiki"],
|
|
3330
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
|
+
},
|
|
3331
3509
|
"lane-phases": {
|
|
3332
3510
|
cmd: "orc lane phases <lane> [--json]",
|
|
3333
3511
|
what: "which SHARED phases this lane runs, in order — the file, the layers to read, and when",
|
|
@@ -3936,7 +4114,7 @@ function laneCallsCmd(lane, claudeDir) {
|
|
|
3936
4114
|
//
|
|
3937
4115
|
// Per phase: \`file\` + \`layers\` (never a line number — /orc-doc rule 2, a
|
|
3938
4116
|
// stored line number is a wrong line number one edit later), \`when\` and
|
|
3939
|
-
// \`read\` (the partial-read declarations registered in \`_shared/
|
|
4117
|
+
// \`read\` (the partial-read declarations registered in \`_shared/phases/README.md\`
|
|
3940
4118
|
// at W10), \`optional_when\` (a config KEY, resolved through the priority ladder
|
|
3941
4119
|
// by \`orc lane config\`, never read raw), and \`calls\` — catalogue ids from
|
|
3942
4120
|
// \`LANE_CALLS\`, DERIVED rather than restated, so a lane can ask for one
|
|
@@ -3964,9 +4142,25 @@ const PHASE_FILES = {
|
|
|
3964
4142
|
why_single_layer: null,
|
|
3965
4143
|
why_two_layers:
|
|
3966
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: [],
|
|
3967
4160
|
},
|
|
3968
4161
|
preflight: {
|
|
3969
4162
|
file: "_shared/phases/preflight.md",
|
|
4163
|
+
trace_verbs: ["GATE", "GRAPH-CONSULT"],
|
|
3970
4164
|
layers: ["core", "full"],
|
|
3971
4165
|
// W13 moved this off a hardcoded `lane !== "orc"` branch and onto the same
|
|
3972
4166
|
// lane->layer map the build phases use — one mechanism, not two.
|
|
@@ -3981,6 +4175,7 @@ const PHASE_FILES = {
|
|
|
3981
4175
|
},
|
|
3982
4176
|
"plan-handoff": {
|
|
3983
4177
|
file: "_shared/phases/plan-handoff.md",
|
|
4178
|
+
trace_verbs: ["PHASE", "GATE"],
|
|
3984
4179
|
layers: ["core"],
|
|
3985
4180
|
why_single_layer:
|
|
3986
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)",
|
|
@@ -3992,12 +4187,14 @@ const PHASE_FILES = {
|
|
|
3992
4187
|
},
|
|
3993
4188
|
"analyst-gates": {
|
|
3994
4189
|
file: "_shared/phases/analyst-gates.md",
|
|
4190
|
+
trace_verbs: ["GATE"],
|
|
3995
4191
|
layers: ["core"],
|
|
3996
4192
|
why_single_layer:
|
|
3997
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",
|
|
3998
4194
|
},
|
|
3999
4195
|
"wiki-consult": {
|
|
4000
4196
|
file: "_shared/phases/wiki-consult.md",
|
|
4197
|
+
trace_verbs: ["WIKI-CONSULT", "CROSSLINK"],
|
|
4001
4198
|
layers: ["core"],
|
|
4002
4199
|
why_single_layer: "the precedence ladder (code > fresh wiki > stale wiki > model priors) does not bend for a faster lane",
|
|
4003
4200
|
},
|
|
@@ -4022,6 +4219,7 @@ const PHASE_FILES = {
|
|
|
4022
4219
|
},
|
|
4023
4220
|
"stop-resume": {
|
|
4024
4221
|
file: "_shared/phases/stop-resume.md",
|
|
4222
|
+
trace_verbs: ["GATE"],
|
|
4025
4223
|
layers: ["core"],
|
|
4026
4224
|
why_single_layer:
|
|
4027
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",
|
|
@@ -4029,6 +4227,7 @@ const PHASE_FILES = {
|
|
|
4029
4227
|
|
|
4030
4228
|
"planning": {
|
|
4031
4229
|
file: "_shared/phases/planning.md",
|
|
4230
|
+
trace_verbs: ["PHASE", "CONFIG", "WIKI-CONSULT", "CROSSLINK", "GRAPH-MAP", "GRAPH-COCHANGE", "GATE", "PACT"],
|
|
4032
4231
|
layers: ["full", "composed"],
|
|
4033
4232
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4034
4233
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4041,6 +4240,7 @@ const PHASE_FILES = {
|
|
|
4041
4240
|
},
|
|
4042
4241
|
"scoring": {
|
|
4043
4242
|
file: "_shared/phases/scoring.md",
|
|
4243
|
+
trace_verbs: ["PHASE", "SCORE"],
|
|
4044
4244
|
layers: ["full", "composed"],
|
|
4045
4245
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4046
4246
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4053,6 +4253,7 @@ const PHASE_FILES = {
|
|
|
4053
4253
|
},
|
|
4054
4254
|
"execution": {
|
|
4055
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"],
|
|
4056
4257
|
layers: ["full", "composed"],
|
|
4057
4258
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4058
4259
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4065,6 +4266,7 @@ const PHASE_FILES = {
|
|
|
4065
4266
|
},
|
|
4066
4267
|
"review": {
|
|
4067
4268
|
file: "_shared/phases/review.md",
|
|
4269
|
+
trace_verbs: ["PHASE", "FINDING", "GRAPH-CHANGES"],
|
|
4068
4270
|
layers: ["full", "composed"],
|
|
4069
4271
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4070
4272
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4089,6 +4291,7 @@ const PHASE_FILES = {
|
|
|
4089
4291
|
},
|
|
4090
4292
|
"verify": {
|
|
4091
4293
|
file: "_shared/phases/verify.md",
|
|
4294
|
+
trace_verbs: ["PHASE", "VERDICT", "PACT"],
|
|
4092
4295
|
layers: ["full", "composed"],
|
|
4093
4296
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4094
4297
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4113,6 +4316,7 @@ const PHASE_FILES = {
|
|
|
4113
4316
|
},
|
|
4114
4317
|
"mock-example": {
|
|
4115
4318
|
file: "_shared/phases/mock-example.md",
|
|
4319
|
+
trace_verbs: ["PHASE", "DRIFT"],
|
|
4116
4320
|
layers: ["full", "composed"],
|
|
4117
4321
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4118
4322
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4125,6 +4329,7 @@ const PHASE_FILES = {
|
|
|
4125
4329
|
},
|
|
4126
4330
|
"ship": {
|
|
4127
4331
|
file: "_shared/phases/ship.md",
|
|
4332
|
+
trace_verbs: ["PHASE", "GATE", "GRAPH-UPDATE", "GRAPH-GAIN"],
|
|
4128
4333
|
layers: ["full", "composed"],
|
|
4129
4334
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4130
4335
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4137,6 +4342,7 @@ const PHASE_FILES = {
|
|
|
4137
4342
|
},
|
|
4138
4343
|
"summary": {
|
|
4139
4344
|
file: "_shared/phases/summary.md",
|
|
4345
|
+
trace_verbs: ["PHASE", "FINDING-OUTCOME"],
|
|
4140
4346
|
layers: ["full", "composed"],
|
|
4141
4347
|
// v1.0.0 W13 — orc-diy became the second reader, so the phase left
|
|
4142
4348
|
// orc/references/phases/ for the library. `full` is /orc's procedure;
|
|
@@ -4167,12 +4373,205 @@ const PREFLIGHT_CALL_IDS = new Set([
|
|
|
4167
4373
|
"run-list",
|
|
4168
4374
|
]);
|
|
4169
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
|
+
|
|
4170
4562
|
// tier + packet cadence per trace-owning lane. This is the same data the tier
|
|
4171
4563
|
// table in \`_shared/phases/trace.md\` renders for a human; the lint asserts the
|
|
4172
4564
|
// two agree, so a lane cannot be in one and absent from the other.
|
|
4173
4565
|
const LANE_TRACE = {
|
|
4174
|
-
|
|
4175
|
-
|
|
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
|
+
},
|
|
4176
4575
|
"orc-fast": { tier: "Build lanes", token: "fast" },
|
|
4177
4576
|
"orc-wiki": { tier: "Multi-dispatch", token: "wiki" },
|
|
4178
4577
|
"orc-pr-driver": { tier: "Multi-dispatch", token: "prdriver" },
|
|
@@ -4200,12 +4599,96 @@ const LANE_TRACE = {
|
|
|
4200
4599
|
"orc-export": { tier: "Single-dispatch", token: "export" },
|
|
4201
4600
|
};
|
|
4202
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
|
+
|
|
4203
4682
|
// Which lanes run which SHARED phase. A lane absent from a row does not run
|
|
4204
4683
|
// that phase — \`/orc-retro\` is in no trace row because it mines traces and
|
|
4205
4684
|
// writes none (its hard rule 4), and \`context-combiner\` is in none because it
|
|
4206
4685
|
// is a PHASE of the analyze run, not a lane (v0.42.0).
|
|
4207
4686
|
const LANE_PHASES = {
|
|
4208
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),
|
|
4209
4692
|
preflight: [
|
|
4210
4693
|
"orc",
|
|
4211
4694
|
"orc-boundary",
|
|
@@ -4260,6 +4743,7 @@ const LANE_PHASES = {
|
|
|
4260
4743
|
const PHASE_ORDER = [
|
|
4261
4744
|
"preflight",
|
|
4262
4745
|
"trace",
|
|
4746
|
+
"trace-verbs",
|
|
4263
4747
|
"intake",
|
|
4264
4748
|
"plan-handoff",
|
|
4265
4749
|
"wiki-consult",
|
|
@@ -4294,6 +4778,7 @@ const PHASE_ORDER = [
|
|
|
4294
4778
|
const LANE_PHASE_ORDER = {
|
|
4295
4779
|
"orc-diy": [
|
|
4296
4780
|
"trace",
|
|
4781
|
+
"trace-verbs",
|
|
4297
4782
|
"intake",
|
|
4298
4783
|
"planning",
|
|
4299
4784
|
"wave-grouping",
|
|
@@ -4318,7 +4803,7 @@ const LANE_PHASE_ORDER = {
|
|
|
4318
4803
|
// A row names a FILE and, when the phase still lives in that lane's spine, the
|
|
4319
4804
|
// HEADING inside it (plan rule: a manifest names a file, a layer and at most a
|
|
4320
4805
|
// heading — never a line number, /orc-doc rule 2). `read: "section"` is the
|
|
4321
|
-
// partial-read declaration registered in `_shared/
|
|
4806
|
+
// partial-read declaration registered in `_shared/phases/README.md`.
|
|
4322
4807
|
//
|
|
4323
4808
|
// WHY MOST ROWS NAME A HEADING RATHER THAN A FILE (v1.0.0 W14, measured):
|
|
4324
4809
|
// moving a phase into its own file pays only when a run does NOT reach it —
|
|
@@ -4461,7 +4946,7 @@ const LANE_OWN_PHASES = {
|
|
|
4461
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"] },
|
|
4462
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"] },
|
|
4463
4948
|
{ ord: 2, id: "q2", file: "orc-quick/SKILL.md", heading: "## Q2 — ASK (ONE user turn: questions + the gate together)", read: "section", trace_verbs: [] },
|
|
4464
|
-
{ 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"] },
|
|
4465
4950
|
],
|
|
4466
4951
|
"orc-wiki": [
|
|
4467
4952
|
{ ord: 0, id: "phase-0", file: "orc-wiki/references/phases/phase-0.md", layers: ["full"], trace_verbs: [] },
|
|
@@ -4491,20 +4976,22 @@ function lanePhaseRows(lane) {
|
|
|
4491
4976
|
file: def.file,
|
|
4492
4977
|
layers,
|
|
4493
4978
|
read: "whole",
|
|
4494
|
-
// `always` must be justified (
|
|
4979
|
+
// `always` must be justified (phases/README.md, W10): a lane reads the
|
|
4495
4980
|
// config resolver and opens its trace pointer before it can do anything
|
|
4496
4981
|
// at all. Every other phase is `on-phase` — most runs skip most phases.
|
|
4497
4982
|
// `compile-time` is orc-diy and only orc-diy: it opens none of these
|
|
4498
4983
|
// during a run. `orc diy compile` reads the `composed` layer once and
|
|
4499
4984
|
// stitches it into FLOW-COMPILED.md, which is the only spine that run
|
|
4500
4985
|
// follows. Calling that `on-phase` would describe a read that never
|
|
4501
|
-
// happens (
|
|
4986
|
+
// happens (phases/README.md rule 5).
|
|
4502
4987
|
when:
|
|
4503
4988
|
lane === "orc-diy" && (PHASE_FILES[id].lane_layers || {})["orc-diy"]
|
|
4504
4989
|
? "compile-time"
|
|
4505
4990
|
: id === "preflight" || id === "trace"
|
|
4506
4991
|
? "always"
|
|
4507
|
-
:
|
|
4992
|
+
: PHASE_FILES[id].via
|
|
4993
|
+
? "on-demand"
|
|
4994
|
+
: "on-phase",
|
|
4508
4995
|
optional_when: null,
|
|
4509
4996
|
// v1.7.0 — `rules` names its own call for the same reason `preflight`
|
|
4510
4997
|
// does: the phase IS that command, and a phase row reading "none
|
|
@@ -4652,8 +5139,10 @@ function laneRailsManifest() {
|
|
|
4652
5139
|
// is an answer, not a gap.
|
|
4653
5140
|
if (!t || !t.token) continue;
|
|
4654
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.
|
|
4655
5144
|
for (const p of lanePhaseRows(lane))
|
|
4656
|
-
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: [] });
|
|
4657
5146
|
for (const p of LANE_OWN_PHASES[lane] || [])
|
|
4658
5147
|
rows.push({
|
|
4659
5148
|
id: p.id,
|
|
@@ -4698,6 +5187,9 @@ function lanePhasesCmd(lane, claudeDir) {
|
|
|
4698
5187
|
lane: l,
|
|
4699
5188
|
trace_tier: t ? t.tier : null,
|
|
4700
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),
|
|
4701
5193
|
phases,
|
|
4702
5194
|
shared_phase_count: phases.length,
|
|
4703
5195
|
// `null` is NOT `[]`: a lane whose own pipeline is still declared in its
|
|
@@ -4728,6 +5220,11 @@ function lanePhasesCmd(lane, claudeDir) {
|
|
|
4728
5220
|
? ui.color.gray(`tier ${r.trace_tier} · lane token \`${r.trace_token}\``)
|
|
4729
5221
|
: ui.color.gray("this lane owns no trace (it is not a run entry point)"))
|
|
4730
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
|
+
);
|
|
4731
5228
|
if (!r.phases.length) {
|
|
4732
5229
|
// An empty answer is an ANSWER: this lane runs no SHARED phase yet.
|
|
4733
5230
|
console.log(
|
|
@@ -4798,6 +5295,9 @@ function laneConfigCmd(lane, claudeDir) {
|
|
|
4798
5295
|
return 2;
|
|
4799
5296
|
}
|
|
4800
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"));
|
|
4801
5301
|
if (wantsJson()) {
|
|
4802
5302
|
emitJson({ ok: true, ...d });
|
|
4803
5303
|
return 0;
|
|
@@ -4814,6 +5314,31 @@ function laneConfigCmd(lane, claudeDir) {
|
|
|
4814
5314
|
console.log("");
|
|
4815
5315
|
}
|
|
4816
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
|
+
|
|
4817
5342
|
console.log(ui.header(`Keys this lane reads (${d.keys.length})`));
|
|
4818
5343
|
if (!d.keys.length) console.log(ui.color.gray(" none — this lane reads no config key."));
|
|
4819
5344
|
const pad = d.keys.length ? Math.max(...d.keys.map((k) => k.key.length)) : 0;
|
|
@@ -4824,7 +5349,9 @@ function laneConfigCmd(lane, claudeDir) {
|
|
|
4824
5349
|
? ui.color.yellow(" shadowed")
|
|
4825
5350
|
: k.is_overridden
|
|
4826
5351
|
? ui.color.green(" overridden")
|
|
4827
|
-
:
|
|
5352
|
+
: k.state === "learned"
|
|
5353
|
+
? ui.color.cyan(" learned") + ui.color.gray(` (${k.source})`)
|
|
5354
|
+
: ui.color.gray(" default");
|
|
4828
5355
|
console.log(` ${ui.color.cyan(k.key.padEnd(pad))} ${String(k.value).padEnd(24)} ${k.prio} ${k.family}${mark}`);
|
|
4829
5356
|
if (k.inert_reason) console.log(ui.color.gray(` ↳ ${k.inert_reason}`));
|
|
4830
5357
|
else if (k.shadow_reason) console.log(ui.color.gray(` ↳ ${k.shadow_reason}`));
|
|
@@ -4982,7 +5509,7 @@ const sha256 = (s) => crypto.createHash("sha256").update(s).digest("hex");
|
|
|
4982
5509
|
// The tier-clip roster. It must contain EVERY agent the default table names —
|
|
4983
5510
|
// `diyScoreTable()` looks each row's agent up here, so a table row naming an
|
|
4984
5511
|
// agent this map does not know is a crash, not a fallback. v1.0.0 W4 added the
|
|
4985
|
-
// two Opus 5 rows for exactly that reason.
|
|
5512
|
+
// two Opus 5.5 rows for exactly that reason.
|
|
4986
5513
|
//
|
|
4987
5514
|
// It also feeds `fixed_executor`'s option list, so every entry here is offerable
|
|
4988
5515
|
// — which is what keeps the four agents no band names reachable (D14).
|
|
@@ -5014,10 +5541,10 @@ const DIY_TIERS = {
|
|
|
5014
5541
|
"opus-4-8-high": { model: 5, effort: 2, modelId: "claude-opus-4-8", effortName: "high" },
|
|
5015
5542
|
"opus-4-8-xhigh": { model: 5, effort: 3, modelId: "claude-opus-4-8", effortName: "xhigh" },
|
|
5016
5543
|
"opus-4-8-max": { model: 5, effort: 4, modelId: "claude-opus-4-8", effortName: "max" },
|
|
5017
|
-
"opus-5-med": { model: 6, effort: 1, modelId: "claude-opus-5", effortName: "medium" },
|
|
5018
|
-
"opus-5-high": { model: 6, effort: 2, modelId: "claude-opus-5", effortName: "high" },
|
|
5019
|
-
"opus-5-xhigh": { model: 6, effort: 3, modelId: "claude-opus-5", effortName: "xhigh" },
|
|
5020
|
-
"opus-5-max": { model: 6, effort: 4, modelId: "claude-opus-5", effortName: "max" },
|
|
5544
|
+
"opus-5-med": { model: 6, effort: 1, modelId: "claude-opus-5-5", effortName: "medium" },
|
|
5545
|
+
"opus-5-high": { model: 6, effort: 2, modelId: "claude-opus-5-5", effortName: "high" },
|
|
5546
|
+
"opus-5-xhigh": { model: 6, effort: 3, modelId: "claude-opus-5-5", effortName: "xhigh" },
|
|
5547
|
+
"opus-5-max": { model: 6, effort: 4, modelId: "claude-opus-5-5", effortName: "max" },
|
|
5021
5548
|
"fable-5-med": { model: 7, effort: 1, modelId: "claude-fable-5", effortName: "medium" },
|
|
5022
5549
|
"fable-5-high": { model: 7, effort: 2, modelId: "claude-fable-5", effortName: "high" },
|
|
5023
5550
|
"fable-5-xhigh": { model: 7, effort: 3, modelId: "claude-fable-5", effortName: "xhigh" },
|
|
@@ -5254,15 +5781,15 @@ function diyValidate(cfg) {
|
|
|
5254
5781
|
if (cfg.autonomy === "hands-off" && (cfg.ship_mode === "commit" || cfg.ship_mode === "pr")) {
|
|
5255
5782
|
warnings.push(`hands-off + ship_mode ${cfg.ship_mode}: git actions will run fully unattended`);
|
|
5256
5783
|
}
|
|
5257
|
-
// The pinned reviewer/verifier moved to claude-opus-5 (model rank 6) in
|
|
5784
|
+
// The pinned reviewer/verifier moved to claude-opus-5-5 (model rank 6) in
|
|
5258
5785
|
// v0.34.0 — anything below that tier silently runs them at the session model.
|
|
5259
5786
|
if (tier && tier.model < 6 && (cfg.review !== "off" || cfg.verify !== "off")) {
|
|
5260
5787
|
// Careful wording (v0.34.7): `session_tier` is a DECLARATION, not the real
|
|
5261
5788
|
// session model — a hook cannot read the model, only the effort. On a
|
|
5262
|
-
// session that actually outranks the declared tier the pinned Opus 5 roles
|
|
5789
|
+
// session that actually outranks the declared tier the pinned Opus 5.5 roles
|
|
5263
5790
|
// run at FULL pin (observed), so asserting they "will" downgrade is a false
|
|
5264
5791
|
// alarm; what is certain is only that a LOWER real session caps them.
|
|
5265
|
-
warnings.push(`session_tier ${cfg.session_tier} is below the pinned Opus 5 reviewer/verifier: if the REAL session is also below Opus 5 they run capped at it (the tier-honesty rule reports the actual model). A session above this tier runs them at full pin — but your executor table stays clipped to ${cfg.session_tier}; recompile to use the full ladder`);
|
|
5792
|
+
warnings.push(`session_tier ${cfg.session_tier} is below the pinned Opus 5.5 reviewer/verifier: if the REAL session is also below Opus 5.5 they run capped at it (the tier-honesty rule reports the actual model). A session above this tier runs them at full pin — but your executor table stays clipped to ${cfg.session_tier}; recompile to use the full ladder`);
|
|
5266
5793
|
}
|
|
5267
5794
|
return { errors, warnings };
|
|
5268
5795
|
}
|
|
@@ -7646,7 +8173,7 @@ const WIKI_TIER_LADDER = [
|
|
|
7646
8173
|
{ id: "small-delta", tier: "light", why: "small delta, no new surface" },
|
|
7647
8174
|
];
|
|
7648
8175
|
|
|
7649
|
-
// The POSITION each tier resolves to (v0.55.0). Two slots and ONE Opus 5 agent
|
|
8176
|
+
// The POSITION each tier resolves to (v0.55.0). Two slots and ONE Opus 5.5 agent
|
|
7650
8177
|
// is not a contradiction: a slot names the POSITION, not the model, and the
|
|
7651
8178
|
// ladder already collapses both tiers onto that agent while `opus5_only` is on.
|
|
7652
8179
|
const WIKI_TIER_SLOT = { deep: "wiki-scanner-deep", light: "wiki-scanner-light" };
|
|
@@ -8402,6 +8929,11 @@ function gotchaStatus(claudeDir, verbose) {
|
|
|
8402
8929
|
trigger: e.fields.trigger || null,
|
|
8403
8930
|
fields: e.fields,
|
|
8404
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) } : {}),
|
|
8405
8937
|
},
|
|
8406
8938
|
entries.length ? 0 : 1
|
|
8407
8939
|
);
|
|
@@ -9750,6 +10282,18 @@ ${line || ""}`;
|
|
|
9750
10282
|
process.exit(1);
|
|
9751
10283
|
}
|
|
9752
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
|
+
|
|
9753
10297
|
function gotcha() {
|
|
9754
10298
|
if (flag("--global")) {
|
|
9755
10299
|
console.error("❌ orc gotcha is project-scoped — the memory is this repo's. Run it from the project (or with --dir <path>).");
|
|
@@ -9763,7 +10307,8 @@ function gotcha() {
|
|
|
9763
10307
|
gotchaStatus(claudeDir, false);
|
|
9764
10308
|
break;
|
|
9765
10309
|
case "list":
|
|
9766
|
-
if (args.includes("--
|
|
10310
|
+
if (args.includes("--candidates")) require("./gotcha.js").gotchaCmd(gotchaDeps(), "list");
|
|
10311
|
+
else if (args.includes("--archived")) gotchaArchived(claudeDir);
|
|
9767
10312
|
else gotchaStatus(claudeDir, true);
|
|
9768
10313
|
break;
|
|
9769
10314
|
case "show":
|
|
@@ -9775,13 +10320,35 @@ function gotcha() {
|
|
|
9775
10320
|
if (args.includes("--dry-run")) gotchaPruneDryRun(claudeDir);
|
|
9776
10321
|
else gotchaPrune(claudeDir);
|
|
9777
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;
|
|
9778
10344
|
default:
|
|
9779
10345
|
console.error(
|
|
9780
10346
|
`Unknown: orc gotcha ${pos[1]}\n` +
|
|
9781
10347
|
"Usage: orc gotcha status | list [--archived] repair memory (exit 0 = entries, 1 = none)\n" +
|
|
9782
10348
|
" orc gotcha show <id> ONE entry, every field (exit 0 / 3 unknown id)\n" +
|
|
9783
10349
|
" orc gotcha prune [--dry-run] archive the low-value tail down to gotchas_max.\n" +
|
|
9784
|
-
" --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: /, " ")
|
|
9785
10352
|
);
|
|
9786
10353
|
process.exit(1);
|
|
9787
10354
|
}
|
|
@@ -10225,9 +10792,12 @@ function pr(alias) {
|
|
|
10225
10792
|
// alias `orc pr-stack-template [<slug>]`.
|
|
10226
10793
|
if (alias === "pr-stack-template") return stackTemplate(claudeDir, pos[1]);
|
|
10227
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());
|
|
10228
10797
|
const usage =
|
|
10229
10798
|
"Usage: orc pr stack template [<slug>] write a fill-in stack-plan skeleton\n" +
|
|
10230
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" +
|
|
10231
10801
|
" (aliases: orc pr-stack-template, orc pr-stack-status)";
|
|
10232
10802
|
if (pos[1] !== "stack") {
|
|
10233
10803
|
console.error(`Unknown: orc ${pos.slice(1).join(" ") || ""}\n${usage}`);
|
|
@@ -11236,7 +11806,7 @@ function resume() {
|
|
|
11236
11806
|
// assumed the opposite, so an interrupted turn produced a SECOND agent on the
|
|
11237
11807
|
// same task while the first was still working. A graded run put THREE
|
|
11238
11808
|
// `orc-executor-opus-5-low` agents on one task for 50m19s + 115m22s + 100m53s
|
|
11239
|
-
// — 266 minutes of Opus 5 for one authorised dispatch, all editing the same
|
|
11809
|
+
// — 266 minutes of Opus 5.5 for one authorised dispatch, all editing the same
|
|
11240
11810
|
// files. The hook already recorded every one of them; nothing ever READ it.
|
|
11241
11811
|
//
|
|
11242
11812
|
// The pending sidecar (`<trace>.pending.json`, written by orc-trace.js on every
|
|
@@ -11384,6 +11954,9 @@ function runCmd() {
|
|
|
11384
11954
|
// Read-only, and the ONE reader of the pending sidecar. 0 clear / 1 in-flight / 2 unknown.
|
|
11385
11955
|
if (sub === "inflight") return runInflightCmd(claudeDir);
|
|
11386
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
|
+
|
|
11387
11960
|
if (sub === "show") {
|
|
11388
11961
|
const arg = pos[2];
|
|
11389
11962
|
const pick = /^\d+$/.test(String(arg)) ? runs[Number(arg) - 1] : runs.find((r) => r.slug === arg);
|
|
@@ -11471,7 +12044,7 @@ function runCmd() {
|
|
|
11471
12044
|
if (sub !== "list") {
|
|
11472
12045
|
console.error(
|
|
11473
12046
|
`Unknown subcommand: orc run ${sub}\n` +
|
|
11474
|
-
`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>`
|
|
11475
12048
|
);
|
|
11476
12049
|
process.exit(1);
|
|
11477
12050
|
}
|
|
@@ -11651,7 +12224,7 @@ function stats() {
|
|
|
11651
12224
|
const empty = (msg) => {
|
|
11652
12225
|
if (asJson)
|
|
11653
12226
|
emitJson(
|
|
11654
|
-
{ 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 },
|
|
11655
12228
|
1
|
|
11656
12229
|
);
|
|
11657
12230
|
console.log(msg);
|
|
@@ -11713,6 +12286,13 @@ function stats() {
|
|
|
11713
12286
|
|
|
11714
12287
|
const laneRows = [...lanes.entries()].sort((a, b) => b[1] - a[1]);
|
|
11715
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 (_) {}
|
|
11716
12296
|
|
|
11717
12297
|
if (asJson) {
|
|
11718
12298
|
console.log(
|
|
@@ -11729,6 +12309,7 @@ function stats() {
|
|
|
11729
12309
|
unfinished,
|
|
11730
12310
|
unknown_lane: unknownLane,
|
|
11731
12311
|
graph: statsGraph(claudeDir),
|
|
12312
|
+
questions,
|
|
11732
12313
|
},
|
|
11733
12314
|
null,
|
|
11734
12315
|
2
|
|
@@ -11766,6 +12347,20 @@ function stats() {
|
|
|
11766
12347
|
["retrieval probably kept out", `~${kTok(gr.avoided_low)} – ${kTok(gr.avoided_high)} (an ESTIMATE — see \`orc graph gain\`)`],
|
|
11767
12348
|
]));
|
|
11768
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
|
+
}
|
|
11769
12364
|
console.log("\n" + ui.color.bold("Health"));
|
|
11770
12365
|
console.log(ui.kv([
|
|
11771
12366
|
["model downgrades", String(downgrades)],
|
|
@@ -21240,7 +21835,7 @@ function docForecastCmd(claudeDir, slugArg) {
|
|
|
21240
21835
|
for (const part of roleParts) {
|
|
21241
21836
|
const vec = vecFor(part.agent, part.n, pct);
|
|
21242
21837
|
if (!part.target) {
|
|
21243
|
-
const m = priceVector(claudeDir, vec, "claude-opus-5");
|
|
21838
|
+
const m = priceVector(claudeDir, vec, "claude-opus-5-5");
|
|
21244
21839
|
if (m.usd === null) priceable = false;
|
|
21245
21840
|
else total += m.usd;
|
|
21246
21841
|
continue;
|
|
@@ -21264,8 +21859,8 @@ function docForecastCmd(claudeDir, slugArg) {
|
|
|
21264
21859
|
};
|
|
21265
21860
|
const usd50 = usdAt("p50");
|
|
21266
21861
|
const usd90 = usdAt("p90");
|
|
21267
|
-
const money50 = priceVector(claudeDir, p50, "claude-opus-5");
|
|
21268
|
-
const money90 = priceVector(claudeDir, p90, "claude-opus-5");
|
|
21862
|
+
const money50 = priceVector(claudeDir, p50, "claude-opus-5-5");
|
|
21863
|
+
const money90 = priceVector(claudeDir, p90, "claude-opus-5-5");
|
|
21269
21864
|
const extraView = {
|
|
21270
21865
|
resolved: docExtra.resolved,
|
|
21271
21866
|
why: docExtra.why,
|
|
@@ -21484,7 +22079,7 @@ function docCostCmd(claudeDir, slugArg) {
|
|
|
21484
22079
|
runRows.push({ trace: r.name, lane: r.lane, date: r.date, tokens: runVec, weighted: weightedTokens(runVec) });
|
|
21485
22080
|
}
|
|
21486
22081
|
|
|
21487
|
-
const money = priceVector(claudeDir, total, "claude-opus-5");
|
|
22082
|
+
const money = priceVector(claudeDir, total, "claude-opus-5-5");
|
|
21488
22083
|
const sections = Object.values(bySection).map((s) => ({
|
|
21489
22084
|
id: s.id,
|
|
21490
22085
|
heading: s.heading,
|
|
@@ -21493,7 +22088,7 @@ function docCostCmd(claudeDir, slugArg) {
|
|
|
21493
22088
|
joined: s.joined > 0,
|
|
21494
22089
|
tokens: s.joined ? s.vec : null,
|
|
21495
22090
|
weighted: s.joined ? weightedTokens(s.vec) : null,
|
|
21496
|
-
usd: s.joined ? priceVector(claudeDir, s.vec, "claude-opus-5").usd : null,
|
|
22091
|
+
usd: s.joined ? priceVector(claudeDir, s.vec, "claude-opus-5-5").usd : null,
|
|
21497
22092
|
}));
|
|
21498
22093
|
const unjoined = sections.filter((s) => !s.joined).length;
|
|
21499
22094
|
const code = joined === 0 ? 3 : unjoined ? 1 : 0;
|
|
@@ -25827,8 +26422,8 @@ function extraResolveFor(claudeDir, score, opts) {
|
|
|
25827
26422
|
//
|
|
25828
26423
|
// `claude` is an ARRAY because `quick-executor` has two: a menu is what that
|
|
25829
26424
|
// lane is. `claude_opus5` is the variant `opus5_only` would have used, derived
|
|
25830
|
-
// from the shipped ladders — NULL where the agent is already Opus 5, so two
|
|
25831
|
-
// wiki slots collapsing onto one Opus 5 scanner is not a contradiction: the
|
|
26425
|
+
// from the shipped ladders — NULL where the agent is already Opus 5.5, so two
|
|
26426
|
+
// wiki slots collapsing onto one Opus 5.5 scanner is not a contradiction: the
|
|
25832
26427
|
// slot names the POSITION, not the model. NO AGENT IS ADDED by this release
|
|
25833
26428
|
// and no pair is needed (the floor stays 51).
|
|
25834
26429
|
const EXTRA_SLOTS = [
|
|
@@ -34469,6 +35064,37 @@ function doctor() {
|
|
|
34469
35064
|
}
|
|
34470
35065
|
} catch (_) {}
|
|
34471
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
|
+
|
|
34472
35098
|
// 5a-ter) the code graph (v1.8.0). ONE finding, and only while `code_graph` is
|
|
34473
35099
|
// on — the read-gate rule. DRIFTED is the only state worth a line: NONE is
|
|
34474
35100
|
// built by the next code lane's preflight, and a doctor that warns about a
|
|
@@ -40904,8 +41530,23 @@ Usage:
|
|
|
40904
41530
|
--reason "<why>" moves RESUME.md aside (never deletes it) and records why,
|
|
40905
41531
|
so it stops counting as waiting [--json]
|
|
40906
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]
|
|
40907
41540
|
orc stats [--since YYYY-MM-DD] [--json] how much you actually use each lane and agent, counted
|
|
40908
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]
|
|
40909
41550
|
orc wait the deterministic half of /orc-wait — a wait costs zero
|
|
40910
41551
|
tokens, because a detached command does it, not a model
|
|
40911
41552
|
orc wait lanes [--json] which lanes support a wait, what each one checkpoints,
|
|
@@ -43603,7 +44244,7 @@ function slEngine() {
|
|
|
43603
44244
|
// panel really hands over — including `tokenCount`, which is the number v1.2.0
|
|
43604
44245
|
// concluded could not be measured.
|
|
43605
44246
|
const SL_TASK_FIXTURES = {
|
|
43606
|
-
healthy: { id: "t1", name: "orc-executor-opus-5-low", type: "orc-executor-opus-5-low", description: "wire the retry ladder", status: "running", model: "claude-opus-5", effort: "low", tokenCount: 84000, contextWindowSize: 200000, startTime: 1767225600000 - 17 * 60000 },
|
|
44247
|
+
healthy: { id: "t1", name: "orc-executor-opus-5-low", type: "orc-executor-opus-5-low", description: "wire the retry ladder", status: "running", model: "claude-opus-5-5", effort: "low", tokenCount: 84000, contextWindowSize: 200000, startTime: 1767225600000 - 17 * 60000 },
|
|
43607
44248
|
degraded: { id: "t2", name: "orc-executor-sonnet-4-6-med", type: "orc-executor-sonnet-4-6-med", description: "rename two files", status: "failed", model: "claude-sonnet-4-6", effort: "medium", tokenCount: 191000, contextWindowSize: 200000, startTime: 1767225600000 - 96 * 60000 },
|
|
43608
44249
|
empty: { id: "t3" },
|
|
43609
44250
|
};
|
|
@@ -43612,7 +44253,7 @@ const SL_FIXTURES = {
|
|
|
43612
44253
|
healthy: {
|
|
43613
44254
|
label: "a healthy session mid-run",
|
|
43614
44255
|
payload: {
|
|
43615
|
-
model: { id: "claude-opus-5", display_name: "Opus 5" },
|
|
44256
|
+
model: { id: "claude-opus-5-5", display_name: "Opus 5.5" },
|
|
43616
44257
|
effort: { level: "high" },
|
|
43617
44258
|
version: "2.1.80",
|
|
43618
44259
|
context_window: { used_percentage: 38, remaining_percentage: 62, context_window_size: 200000 },
|
|
@@ -44452,6 +45093,22 @@ const RULES_PRECEDENCE = [
|
|
|
44452
45093
|
{ rank: 3, layer: "ORC rules", scope: "everything", where: "<claude>/skills/_shared/rules/", beats: "nothing — it is the floor, and yours replaces it" },
|
|
44453
45094
|
];
|
|
44454
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
|
+
|
|
44455
45112
|
// Which packs ride in which lane. `ui` is absent from every row ON PURPOSE: it
|
|
44456
45113
|
// is added PER TASK by the orchestrator when a task's declared files are
|
|
44457
45114
|
// front-end. A UI rule in a backend slice is tokens paid on every spawn for a
|
|
@@ -44720,6 +45377,10 @@ function rulesLine(packs, blocks, overrides) {
|
|
|
44720
45377
|
// which are what a rule's body is mostly made of. Nothing is hidden: the pack
|
|
44721
45378
|
// file is named beside the rules, exactly as the light tiers already are.
|
|
44722
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"]);
|
|
44723
45384
|
|
|
44724
45385
|
// One line out of a rule body: the first paragraph, whitespace collapsed, cut
|
|
44725
45386
|
// at a word boundary. A rule whose instruction does not survive that is a rule
|
|
@@ -44740,7 +45401,16 @@ function rulesSlice(claudeDir, lane, extraPacks) {
|
|
|
44740
45401
|
const overridden = new Set(overrides.filter((o) => o.known).map((o) => o.id));
|
|
44741
45402
|
const want = new Set([...(RULE_LANE_PACKS[lane] || []), ...(extraPacks || [])]);
|
|
44742
45403
|
const chosen = packs.packs.filter((p) => want.has(p.id));
|
|
44743
|
-
|
|
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") : [];
|
|
44744
45414
|
|
|
44745
45415
|
const L = [];
|
|
44746
45416
|
if (!led.empty) {
|
|
@@ -44750,6 +45420,13 @@ function rulesSlice(claudeDir, lane, extraPacks) {
|
|
|
44750
45420
|
if (body) L.push(pr, body, "");
|
|
44751
45421
|
}
|
|
44752
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
|
+
}
|
|
44753
45430
|
if (chosen.length) {
|
|
44754
45431
|
L.push(
|
|
44755
45432
|
`ORC RULES — the anti-slop baseline. Shipped with ORC, read-only. ${packs.count} rules, ${plural(chosen.length, "pack")} in this slice.`,
|
|
@@ -44793,6 +45470,9 @@ function rulesSlice(claudeDir, lane, extraPacks) {
|
|
|
44793
45470
|
// `compact` is REPORTED, never asked for. A reader that cannot tell a short
|
|
44794
45471
|
// card from a stripped one cannot trust either.
|
|
44795
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 } : {}),
|
|
44796
45476
|
text: L.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\s+$/, ""),
|
|
44797
45477
|
overrides,
|
|
44798
45478
|
line: rulesLine(packs, led.blocks, overrides),
|
|
@@ -45069,7 +45749,7 @@ function rulesCmd() {
|
|
|
45069
45749
|
},
|
|
45070
45750
|
priorities: RULE_PRIORITIES,
|
|
45071
45751
|
tiers: RULE_TIERS,
|
|
45072
|
-
precedence:
|
|
45752
|
+
precedence: rulesPrecedence(claudeDir),
|
|
45073
45753
|
overrides,
|
|
45074
45754
|
overrides_note:
|
|
45075
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.",
|
|
@@ -45082,7 +45762,7 @@ function rulesCmd() {
|
|
|
45082
45762
|
|
|
45083
45763
|
console.log(`\norc rules — ${line}\n`);
|
|
45084
45764
|
console.log(ui.color.bold(" Precedence") + ui.color.gray(" the order never changes"));
|
|
45085
|
-
for (const r of
|
|
45765
|
+
for (const r of rulesPrecedence(claudeDir))
|
|
45086
45766
|
console.log(
|
|
45087
45767
|
` ${r.rank}. ${ui.color.cyan(r.layer.padEnd(12))} ${ui.color.gray(r.scope)}\n` +
|
|
45088
45768
|
` ${ui.color.gray(r.where)}\n` +
|
|
@@ -45245,7 +45925,7 @@ function lintScanFile(abs, rel, opts) {
|
|
|
45245
45925
|
} catch (_) {
|
|
45246
45926
|
return out;
|
|
45247
45927
|
}
|
|
45248
|
-
if (text.includes("
|
|
45928
|
+
if (text.includes("\u0000")) return out; // binary
|
|
45249
45929
|
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
45250
45930
|
|
|
45251
45931
|
// THE CARVE-OUT, and it is not a convenience. A rule that bans a word has to
|
|
@@ -45588,6 +46268,16 @@ function jsonCrash(err) {
|
|
|
45588
46268
|
case "graph":
|
|
45589
46269
|
graphCmd();
|
|
45590
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;
|
|
45591
46281
|
case "mock":
|
|
45592
46282
|
mock();
|
|
45593
46283
|
break;
|
|
@@ -45677,12 +46367,20 @@ function jsonCrash(err) {
|
|
|
45677
46367
|
case "pr-stack-status":
|
|
45678
46368
|
pr(cmd);
|
|
45679
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;
|
|
45680
46374
|
case "resume":
|
|
45681
46375
|
resume();
|
|
45682
46376
|
break;
|
|
45683
46377
|
case "run":
|
|
45684
46378
|
runCmd();
|
|
45685
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;
|
|
45686
46384
|
case "stats":
|
|
45687
46385
|
stats();
|
|
45688
46386
|
break;
|