@azure-id/orc 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +401 -0
- package/README-id.md +52 -23
- package/README.md +50 -22
- package/bin/build-agents.js +24 -25
- package/bin/clear-logs.js +620 -0
- package/bin/cli.js +1155 -158
- package/bin/fix.js +459 -0
- package/bin/gotcha.js +288 -8
- package/bin/habit.js +1538 -1453
- package/bin/mockrun-catalog.js +3 -0
- package/bin/onboarding-content.js +2 -1
- package/bin/pricing.json +28 -21
- package/bin/trace-write.js +869 -657
- package/bin/verify-contracts.js +108 -27
- package/bin/verify-package.js +17 -6
- package/bin/webui/api.js +125 -6
- package/bin/webui/css/panels/hookui.css +79 -0
- package/bin/webui/fixtures/behaviour.js +30 -0
- package/bin/webui/fixtures/extra.js +9 -9
- package/bin/webui/fixtures/flow.js +81 -81
- package/bin/webui/fixtures/hookui.js +506 -431
- package/bin/webui/fixtures/index.js +32 -3
- package/bin/webui/fixtures/knowledge.js +2 -2
- package/bin/webui/fixtures/maintenance.js +123 -42
- package/bin/webui/fixtures/rules.js +1007 -1002
- package/bin/webui/fixtures/settings.js +6 -7
- package/bin/webui/fixtures/stats.js +9 -6
- package/bin/webui/fixtures/wait.js +98 -97
- package/bin/webui/i18n/en/behaviour.json +13 -0
- package/bin/webui/i18n/en/hookui.json +290 -194
- package/bin/webui/i18n/en/maintenance.json +71 -52
- package/bin/webui/i18n/en/settings.json +60 -60
- package/bin/webui/i18n/id/behaviour.json +13 -0
- package/bin/webui/i18n/id/hookui.json +290 -194
- package/bin/webui/i18n/id/maintenance.json +71 -52
- package/bin/webui/i18n/id/settings.json +60 -60
- package/bin/webui/js/05-banners.js +181 -172
- package/bin/webui/js/06-edit.js +193 -183
- package/bin/webui/js/panels/behaviour.js +77 -12
- package/bin/webui/js/panels/hookui.js +2196 -1691
- package/bin/webui/js/panels/maintenance.js +336 -235
- package/bin/webui/js/panels/settings.js +726 -719
- package/mock-run/INDEX.md +1 -0
- package/mock-run/extra-slots.md +1 -1
- package/mock-run/habits.md +1 -1
- package/mock-run/orc-aftermath.md +392 -392
- package/mock-run/orc-budget.md +29 -33
- package/mock-run/orc-cli.md +200 -200
- package/mock-run/orc-explain.md +86 -86
- package/mock-run/orc-extra.md +389 -392
- package/mock-run/orc-fix.md +87 -0
- package/mock-run/orc-pattern.md +112 -112
- package/mock-run/orc.md +157 -157
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +32 -29
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-claude-writer-opus-5-med.md → orc-claude-writer-opus-5-low.md} +4 -4
- package/templates/agents/orc-executor-haiku-4-5.md +1 -1
- package/templates/agents/orc-executor-opus-5-low.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-high.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-med.md +1 -1
- package/templates/agents/orc-executor-sonnet-5-high.md +2 -2
- package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
- package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +2 -2
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -2
- package/templates/agents/orc-retro-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-reviewer-opus-5-med.md → orc-reviewer-opus-5-low.md} +4 -4
- package/templates/agents/{orc-wiki-scanner-opus-5-med.md → orc-wiki-scanner-opus-5-low.md} +3 -3
- package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +2 -2
- package/templates/commands/orc-fix.md +18 -0
- package/templates/commands/orc-wiki.md +64 -64
- package/templates/hooks/README.md +75 -7
- package/templates/hooks/orc-session-hook.js +78 -3
- package/templates/hooks/orc-statusline-render.js +317 -23
- package/templates/hooks/orc-statusline.js +162 -49
- package/templates/hooks/orc-subagent-line.js +23 -5
- package/templates/hooks/orc-weather-fetch.js +123 -0
- package/templates/skills/_shared/extra-dispatch.md +5 -5
- package/templates/skills/_shared/gotchas.md +27 -7
- package/templates/skills/_shared/habits.md +2 -2
- package/templates/skills/_shared/opus5-only.md +14 -14
- package/templates/skills/_shared/phases/review.md +2 -2
- package/templates/skills/_shared/phases/trace-verbs.md +17 -3
- package/templates/skills/_shared/phases/trace.md +136 -136
- package/templates/skills/_shared/review-slice.md +28 -1
- package/templates/skills/_shared/wait.md +1 -0
- package/templates/skills/orc/README.md +2 -2
- package/templates/skills/orc/SKILL.md +1 -1
- package/templates/skills/orc/config.md +25 -27
- package/templates/skills/orc/examples/full-run-mock.md +2 -1
- package/templates/skills/orc/references/effort-and-mode.md +17 -17
- package/templates/skills/orc/references/preflight-report.md +3 -3
- package/templates/skills/orc/references/ultra-mode.md +1 -1
- package/templates/skills/orc/schemas/checkpoint.md +122 -122
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +3 -3
- package/templates/skills/orc/subskills/orc-review-verify/core.md +1 -1
- package/templates/skills/orc-analyze/SKILL.md +4 -2
- package/templates/skills/orc-claude/SKILL.md +14 -14
- package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
- package/templates/skills/orc-diy/SKILL.md +5 -5
- package/templates/skills/orc-fast/SKILL.md +8 -6
- package/templates/skills/orc-fix/SKILL.md +110 -0
- package/templates/skills/orc-mini/SKILL.md +4 -4
- package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
- package/templates/skills/orc-quick/README.md +1 -1
- package/templates/skills/orc-quick/SKILL.md +2 -2
- package/templates/skills/orc-quick/references/dispatch-gate.md +3 -2
- package/templates/skills/orc-quick/references/gh-mode.md +4 -2
- package/templates/skills/orc-retro/SKILL.md +1 -1
- package/templates/skills/orc-retro/examples/retro-mock.md +16 -16
- package/templates/skills/orc-wiki/README.md +325 -325
- package/templates/skills/orc-wiki/SKILL.md +195 -195
- package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
- package/templates/skills/orc-wiki/references/extra.md +2 -2
- package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
- package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -1
- package/templates/skills/orc-wiki/references/phases/phase-2.md +3 -3
- package/templates/skills/orc-wiki/references/phases/phase-3c.md +3 -2
- package/templates/agents/orc-claude-writer-opus-4-8-high.md +0 -48
- package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +0 -74
|
@@ -401,6 +401,23 @@ const SCAN = {
|
|
|
401
401
|
trace_age_min: null, trace_state: null, last_agent: null, retries: null,
|
|
402
402
|
};
|
|
403
403
|
|
|
404
|
+
// v2.1.0 (DE-23) — is a `statusLine.refreshInterval` set? The local file wins
|
|
405
|
+
// over the project file, as in Claude Code. One small read per run, fail-silent.
|
|
406
|
+
function refreshIntervalSet(projectDir) {
|
|
407
|
+
const fs = require("fs");
|
|
408
|
+
const path = require("path");
|
|
409
|
+
for (const f of ["settings.local.json", "settings.json"]) {
|
|
410
|
+
try {
|
|
411
|
+
const j = JSON.parse(fs.readFileSync(path.join(projectDir, ".claude", f), "utf8"));
|
|
412
|
+
if (j && j.statusLine && Object.prototype.hasOwnProperty.call(j.statusLine, "refreshInterval")) {
|
|
413
|
+
const n = Number(j.statusLine.refreshInterval);
|
|
414
|
+
return Number.isFinite(n) && n >= 1;
|
|
415
|
+
}
|
|
416
|
+
} catch (_) {}
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
404
421
|
function ledger(projectDir, sid) {
|
|
405
422
|
if (LED) return LED;
|
|
406
423
|
const fs = require("fs");
|
|
@@ -473,7 +490,7 @@ function readPlan(d) {
|
|
|
473
490
|
const projectDir =
|
|
474
491
|
(d.workspace && d.workspace.project_dir) || d.cwd || process.cwd();
|
|
475
492
|
const raw = fs.readFileSync(path.join(projectDir, ".claude", "orc.config.yaml"), "utf8");
|
|
476
|
-
if (!/^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]
|
|
493
|
+
if (!/^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]*(?:#.*)?\r?$/m.test(raw)) return null;
|
|
477
494
|
const lock = JSON.parse(
|
|
478
495
|
fs.readFileSync(path.join(projectDir, ".claude", "orc", "statusline.lock.json"), "utf8")
|
|
479
496
|
);
|
|
@@ -572,16 +589,27 @@ process.stdin.on("end", () => {
|
|
|
572
589
|
? { used_percentage: o.used_percentage, resets_at: o.resets_at == null ? null : o.resets_at }
|
|
573
590
|
: null;
|
|
574
591
|
fs.mkdirSync(orcDir, { recursive: true });
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
592
|
+
// v2.1.0 (DE-24) — rewrite ONLY when the numbers change. With a refresh
|
|
593
|
+
// timer every tick re-ran this, and a fresh `written_at` on old numbers
|
|
594
|
+
// made them look new to `orc usage check`. `written_at` is now the time
|
|
595
|
+
// the reading last MOVED.
|
|
596
|
+
const usageFile = path.join(orcDir, "usage.json");
|
|
597
|
+
const reading = {
|
|
598
|
+
five_hour: win(rl0 && rl0.five_hour),
|
|
599
|
+
seven_day: win(rl0 && rl0.seven_day),
|
|
600
|
+
context_used_percentage:
|
|
601
|
+
cw0 && typeof cw0.used_percentage === "number" ? cw0.used_percentage : null,
|
|
602
|
+
};
|
|
603
|
+
let prev = null;
|
|
604
|
+
try {
|
|
605
|
+
prev = JSON.parse(fs.readFileSync(usageFile, "utf8"));
|
|
606
|
+
} catch (_) {}
|
|
607
|
+
const same =
|
|
608
|
+
prev &&
|
|
609
|
+
prev.written_at &&
|
|
610
|
+
JSON.stringify([prev.five_hour, prev.seven_day, prev.context_used_percentage]) ===
|
|
611
|
+
JSON.stringify([reading.five_hour, reading.seven_day, reading.context_used_percentage]);
|
|
612
|
+
if (!same) fs.writeFileSync(usageFile, JSON.stringify(Object.assign(reading, { written_at: Date.now() })) + "\n");
|
|
585
613
|
// -- Session consumption (v1.2.0) -------------------------------------
|
|
586
614
|
// `usage.json` is a SNAPSHOT of the window. It cannot answer "how much
|
|
587
615
|
// has THIS session eaten", which is the question a user actually asks
|
|
@@ -1085,6 +1113,18 @@ process.stdin.on("end", () => {
|
|
|
1085
1113
|
line2 = "";
|
|
1086
1114
|
}
|
|
1087
1115
|
|
|
1116
|
+
// v2.1.0 (DE-23) — when a refresh timer is set, a motion part advances ONE
|
|
1117
|
+
// step per run (the frame after the last frame shown), not the frame for this
|
|
1118
|
+
// millisecond: at 2-10 s the wall clock skipped most frames. No timer → the
|
|
1119
|
+
// wall-clock rule stays, so `step` is null.
|
|
1120
|
+
let step = null;
|
|
1121
|
+
try {
|
|
1122
|
+
if (LED && refreshIntervalSet((d.workspace && d.workspace.project_dir) || d.cwd || process.cwd())) {
|
|
1123
|
+
LED.frame = (Number(LED.frame) || 0) + 1;
|
|
1124
|
+
step = LED.frame;
|
|
1125
|
+
}
|
|
1126
|
+
} catch (_) {}
|
|
1127
|
+
|
|
1088
1128
|
// ONE write, after every block that touches the ledger has had its say. It is
|
|
1089
1129
|
// last on purpose: a render that throws half way through still prints, and a
|
|
1090
1130
|
// ledger that could not be written never takes the status line down with it.
|
|
@@ -1102,6 +1142,7 @@ process.stdin.on("end", () => {
|
|
|
1102
1142
|
scan: SCAN,
|
|
1103
1143
|
derived: { verdict, reasons, version: ver },
|
|
1104
1144
|
now: Date.now(),
|
|
1145
|
+
step,
|
|
1105
1146
|
});
|
|
1106
1147
|
if (composed != null) {
|
|
1107
1148
|
process.stdout.write(composed);
|
|
@@ -1117,7 +1158,8 @@ process.stdin.on("end", () => {
|
|
|
1117
1158
|
// 1 statusline_custom is not `on` the shipped lines. BYTE-IDENTICAL.
|
|
1118
1159
|
// 2 statusline-compiled.json missing / default + statusline-layout-unreadable
|
|
1119
1160
|
// unparseable / schema mismatch
|
|
1120
|
-
// 3 orc_version
|
|
1161
|
+
// 3 catalog_hash moved (orc_version when default + statusline-layout-stale
|
|
1162
|
+
// the install has no catalog_hash)
|
|
1121
1163
|
// 4 an unknown op or an unknown binding default + statusline-layout-skew
|
|
1122
1164
|
// 5 the cheap shape guard fails default + statusline-layout-invalid
|
|
1123
1165
|
// 6 otherwise run the program
|
|
@@ -1145,7 +1187,7 @@ function custom(d, ctx) {
|
|
|
1145
1187
|
// The trailing \r is tolerated on purpose: a config file written on
|
|
1146
1188
|
// Windows carries CRLF, and a $-anchored match silently never fires
|
|
1147
1189
|
// there — which is a feature that is ON in the file and OFF on the bar.
|
|
1148
|
-
on = /^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]
|
|
1190
|
+
on = /^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]*(?:#.*)?\r?$/m.test(raw);
|
|
1149
1191
|
} catch (_) {}
|
|
1150
1192
|
if (!on) return null;
|
|
1151
1193
|
|
|
@@ -1164,11 +1206,13 @@ function custom(d, ctx) {
|
|
|
1164
1206
|
try {
|
|
1165
1207
|
lock = JSON.parse(fs.readFileSync(path.join(orcDir, "statusline.lock.json"), "utf8"));
|
|
1166
1208
|
} catch (_) {}
|
|
1167
|
-
|
|
1209
|
+
// The catalogue hash decides when the install carries one: a CLI version
|
|
1210
|
+
// bump that changed no component must not drop the user's design.
|
|
1211
|
+
let inst = {};
|
|
1168
1212
|
try {
|
|
1169
|
-
|
|
1213
|
+
inst = JSON.parse(fs.readFileSync(path.join(__dirname, "orc-version.json"), "utf8")) || {};
|
|
1170
1214
|
} catch (_) {}
|
|
1171
|
-
if (!lock || (
|
|
1215
|
+
if (!lock || (inst.catalog_hash ? lock.catalog_hash !== inst.catalog_hash : (inst.version && lock.orc_version !== inst.version)))
|
|
1172
1216
|
return slFallback(orcDir, "statusline-layout-stale");
|
|
1173
1217
|
|
|
1174
1218
|
const engine = require("./orc-statusline-render.js");
|
|
@@ -1178,6 +1222,7 @@ function custom(d, ctx) {
|
|
|
1178
1222
|
for (const b of lock.bindings || []) {
|
|
1179
1223
|
if (!engine.BINDINGS[b]) return slFallback(orcDir, "statusline-layout-skew");
|
|
1180
1224
|
}
|
|
1225
|
+
if (engine.OPS && !opsKnown(prog.lines, engine.OPS)) return slFallback(orcDir, "statusline-layout-skew");
|
|
1181
1226
|
|
|
1182
1227
|
// Rung 5. The cheap shape guard: at most three lines, at most `max_per_line`
|
|
1183
1228
|
// components on any of them, and the dense-prefix rule —
|
|
@@ -1206,16 +1251,25 @@ function custom(d, ctx) {
|
|
|
1206
1251
|
// Rung 6. Run the program. Failure isolation lives inside the engine: one
|
|
1207
1252
|
// throwing item emits its unknown form and the rest of the line survives.
|
|
1208
1253
|
SCAN.preset = prog.preset || null;
|
|
1254
|
+
if ((lock.providers || []).indexOf("scan.weather") >= 0) weather(orcDir, prog.lines);
|
|
1209
1255
|
const out = engine.render(prog, {
|
|
1210
1256
|
payload: ctx.payload,
|
|
1211
1257
|
ledger: ctx.ledger,
|
|
1212
1258
|
scan: ctx.scan,
|
|
1213
1259
|
derived: ctx.derived,
|
|
1214
1260
|
now: ctx.now,
|
|
1215
|
-
|
|
1261
|
+
step: ctx.step,
|
|
1262
|
+
cols: Number(process.env.COLUMNS) || Number(d.columns) || 0,
|
|
1216
1263
|
env: process.env,
|
|
1217
1264
|
});
|
|
1218
1265
|
if (out.errors && out.errors.length) slNote(orcDir, "statusline-item-failed", out.errors[0]);
|
|
1266
|
+
else {
|
|
1267
|
+
// A working bar clears the old fallback, so `orc doctor` stops naming it.
|
|
1268
|
+
try {
|
|
1269
|
+
const st = path.join(orcDir, "statusline-state.json");
|
|
1270
|
+
if (fs.existsSync(st)) fs.unlinkSync(st);
|
|
1271
|
+
} catch (_) {}
|
|
1272
|
+
}
|
|
1219
1273
|
return out.text;
|
|
1220
1274
|
} catch (_) {
|
|
1221
1275
|
// Even the ladder must not throw. A status line that crashes is a status
|
|
@@ -1224,6 +1278,67 @@ function custom(d, ctx) {
|
|
|
1224
1278
|
}
|
|
1225
1279
|
}
|
|
1226
1280
|
|
|
1281
|
+
// Every compiled op must be one the engine walks. A plain stack, no copies.
|
|
1282
|
+
function opsKnown(lines, OPS) {
|
|
1283
|
+
const st = [];
|
|
1284
|
+
for (const l of lines || []) st.push(l.ops || []);
|
|
1285
|
+
while (st.length) {
|
|
1286
|
+
const list = st.pop();
|
|
1287
|
+
for (let i = 0; i < list.length; i++) {
|
|
1288
|
+
const o = list[i];
|
|
1289
|
+
if (!o || !OPS[o.op]) return false;
|
|
1290
|
+
if (o.children) st.push(o.children);
|
|
1291
|
+
if (o.cap && o.cap.ops) st.push(o.cap.ops);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
return true;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// ── WEATHER (v2.0.4) ───────────────────────────────────────────────────────
|
|
1298
|
+
// ONE small file read. The network belongs to a DETACHED fetcher that this
|
|
1299
|
+
// hook starts and never waits for; the bar shows the last cached answer, or an
|
|
1300
|
+
// em dash. A lock file keeps a slow network from starting a fetch per render.
|
|
1301
|
+
function weather(orcDir, lines) {
|
|
1302
|
+
try {
|
|
1303
|
+
const fs = require("fs");
|
|
1304
|
+
const path = require("path");
|
|
1305
|
+
const now = Date.now();
|
|
1306
|
+
let j = null;
|
|
1307
|
+
try {
|
|
1308
|
+
j = JSON.parse(fs.readFileSync(path.join(orcDir, "weather.json"), "utf8"));
|
|
1309
|
+
} catch (_) {}
|
|
1310
|
+
SCAN.weather = j && typeof j === "object" ? j : undefined;
|
|
1311
|
+
if (j && typeof j.at === "number" && now - j.at < 30 * 60 * 1000) return;
|
|
1312
|
+
if (process.env.ORC_STATUSLINE_NO_NET === "1") return;
|
|
1313
|
+
const lockFile = path.join(orcDir, "weather.lock");
|
|
1314
|
+
try {
|
|
1315
|
+
if (now - fs.statSync(lockFile).mtimeMs < 2 * 60 * 1000) return;
|
|
1316
|
+
} catch (_) {}
|
|
1317
|
+
// Location and units come from the first op that binds `weather.*`.
|
|
1318
|
+
let p = null;
|
|
1319
|
+
const st = (lines || []).map((l) => l.ops || []);
|
|
1320
|
+
while (st.length && !p) {
|
|
1321
|
+
for (const o of st.pop()) {
|
|
1322
|
+
if (!o) continue;
|
|
1323
|
+
if (typeof o.b === "string" && o.b.indexOf("weather.") === 0 && o.p) {
|
|
1324
|
+
p = o.p;
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
if (o.children) st.push(o.children);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
p = p || {};
|
|
1331
|
+
fs.writeFileSync(lockFile, String(now));
|
|
1332
|
+
require("child_process")
|
|
1333
|
+
.spawn(process.execPath, [path.join(__dirname, "orc-weather-fetch.js"), orcDir, String(p.location || ""), p.units === "us" ? "us" : "metric"], {
|
|
1334
|
+
detached: true,
|
|
1335
|
+
stdio: "ignore",
|
|
1336
|
+
windowsHide: true,
|
|
1337
|
+
})
|
|
1338
|
+
.unref();
|
|
1339
|
+
} catch (_) {}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1227
1342
|
// A fallback RECORDS ITSELF. `orc doctor` reads this file and names the finding
|
|
1228
1343
|
// with the exact command that clears it.
|
|
1229
1344
|
function slFallback(orcDir, finding) {
|
|
@@ -1548,7 +1663,15 @@ function extendedScan(d, wants, wantsProvider) {
|
|
|
1548
1663
|
// planner again. A series nothing binds is not kept, so the ledger does not
|
|
1549
1664
|
// grow for a user whose layout has no sparkline on it.
|
|
1550
1665
|
const SERIES_MAX = 16;
|
|
1551
|
-
const SERIES_MIN_GAP_MS =
|
|
1666
|
+
const SERIES_MIN_GAP_MS = 10000;
|
|
1667
|
+
// Series name → the binding whose number it stores. The stored sample IS the
|
|
1668
|
+
// binding's value, so a spark and the number beside it can never disagree.
|
|
1669
|
+
const SERIES_BINDING = {
|
|
1670
|
+
agents: "run.agents", burn: "burn.rate", cost: "cost.usd", cachehit: "cache.hit_ratio",
|
|
1671
|
+
cachewrite: "cache.write_tokens", lines: "cost.lines_net", extraspend: "extra.spend",
|
|
1672
|
+
speed: "mtok.speed", mtok: "mtok.total", mtokkind: "mtok.cache_read",
|
|
1673
|
+
quota5h: "quota.5h.pct", quotawk: "quota.week.pct", ucs: "ucs.pct",
|
|
1674
|
+
};
|
|
1552
1675
|
|
|
1553
1676
|
function sampleSeries(d, plan) {
|
|
1554
1677
|
try {
|
|
@@ -1556,42 +1679,32 @@ function sampleSeries(d, plan) {
|
|
|
1556
1679
|
const led = LED;
|
|
1557
1680
|
if (!led) return;
|
|
1558
1681
|
const now = Date.now();
|
|
1559
|
-
// A sample every 20 seconds, not every render. Sixteen samples at the
|
|
1560
|
-
// render rate would be five seconds of history, which is not history — it
|
|
1561
|
-
// is the same number sixteen times.
|
|
1562
|
-
if (led.series_at && now - led.series_at < SERIES_MIN_GAP_MS) return;
|
|
1563
|
-
led.series_at = now;
|
|
1564
1682
|
led.series = led.series || {};
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1683
|
+
// A sample every 10 seconds, not every render. Sixteen samples at the
|
|
1684
|
+
// render rate would be a few seconds of history, which is not history — it
|
|
1685
|
+
// is the same number sixteen times. While no series has a point yet, it is
|
|
1686
|
+
// sampled at once, so a new spark has a point on its first render.
|
|
1687
|
+
const empty = [...plan.series].every((k) => !(led.series[k] && led.series[k].length));
|
|
1688
|
+
if (!empty && led.series_at && now - led.series_at < SERIES_MIN_GAP_MS) return;
|
|
1689
|
+
led.series_at = now;
|
|
1690
|
+
// The engine is required by custom() on this same render, so this costs no
|
|
1691
|
+
// extra file read.
|
|
1692
|
+
const B = require("./orc-statusline-render.js").BINDINGS;
|
|
1693
|
+
const c = { payload: d, ledger: led, scan: SCAN, now, derived: {}, task: null };
|
|
1694
|
+
for (const key of plan.series) {
|
|
1695
|
+
const b = SERIES_BINDING[key];
|
|
1696
|
+
if (!b || !B[b]) continue;
|
|
1697
|
+
let v = null;
|
|
1698
|
+
try {
|
|
1699
|
+
v = B[b](c);
|
|
1700
|
+
} catch (_) {}
|
|
1701
|
+
if (typeof v !== "number" || !Number.isFinite(v)) continue;
|
|
1568
1702
|
const arr = led.series[key] || [];
|
|
1569
|
-
|
|
1703
|
+
// Two decimals: Math.round turned every dollar amount below 1 into 0.
|
|
1704
|
+
arr.push(Number(v.toFixed(2)));
|
|
1570
1705
|
while (arr.length > SERIES_MAX) arr.shift();
|
|
1571
1706
|
led.series[key] = arr;
|
|
1572
|
-
};
|
|
1573
|
-
const rl = d.rate_limits || {};
|
|
1574
|
-
const w = led.five_hour;
|
|
1575
|
-
push("quota5h", rl.five_hour && rl.five_hour.used_percentage);
|
|
1576
|
-
push("quotawk", rl.seven_day && rl.seven_day.used_percentage);
|
|
1577
|
-
push("ucs", w && typeof w.last === "number" && typeof w.baseline === "number"
|
|
1578
|
-
? Math.max(0, (w.accumulated || 0) + Math.max(0, w.last - w.baseline))
|
|
1579
|
-
: null);
|
|
1580
|
-
const tok = led.tok;
|
|
1581
|
-
if (tok) {
|
|
1582
|
-
push("mtok", (tok.input || 0) + (tok.cache_write || 0) + (tok.cache_read || 0) + (tok.output || 0));
|
|
1583
|
-
push("mtokkind", tok.cache_read || 0);
|
|
1584
1707
|
}
|
|
1585
|
-
push("agents", SCAN.spawns);
|
|
1586
|
-
push("cost", d.cost && d.cost.total_cost_usd != null ? d.cost.total_cost_usd * 100 : null);
|
|
1587
|
-
push("cachehit", d.prompt_cache && d.prompt_cache.hit_ratio != null
|
|
1588
|
-
? (d.prompt_cache.hit_ratio <= 1 ? d.prompt_cache.hit_ratio * 100 : d.prompt_cache.hit_ratio)
|
|
1589
|
-
: null);
|
|
1590
|
-
push("cachewrite", d.prompt_cache && d.prompt_cache.cache_write_tokens);
|
|
1591
|
-
push("lines", d.cost && d.cost.total_lines_added != null
|
|
1592
|
-
? (d.cost.total_lines_added || 0) - (d.cost.total_lines_removed || 0)
|
|
1593
|
-
: null);
|
|
1594
|
-
push("extraspend", SCAN.extra_spend && SCAN.extra_spend.usd != null ? SCAN.extra_spend.usd * 100 : null);
|
|
1595
1708
|
} catch (_) {
|
|
1596
1709
|
// A series is a nicety. It never takes the status line down with it.
|
|
1597
1710
|
}
|
|
@@ -151,7 +151,7 @@ function render(d, tasks) {
|
|
|
151
151
|
let on = false;
|
|
152
152
|
try {
|
|
153
153
|
const cfg = fs.readFileSync(path.join(projectDir, ".claude", "orc.config.yaml"), "utf8");
|
|
154
|
-
on = /^[ \t]*subagent_line_custom:[ \t]*["']?on["']?[ \t]
|
|
154
|
+
on = /^[ \t]*subagent_line_custom:[ \t]*["']?on["']?[ \t]*(?:#.*)?\r?$/m.test(cfg);
|
|
155
155
|
} catch (_) {}
|
|
156
156
|
if (!on) return null;
|
|
157
157
|
|
|
@@ -167,11 +167,12 @@ function render(d, tasks) {
|
|
|
167
167
|
try {
|
|
168
168
|
lock = JSON.parse(fs.readFileSync(path.join(orcDir, "subagent.lock.json"), "utf8"));
|
|
169
169
|
} catch (_) {}
|
|
170
|
-
|
|
170
|
+
// The catalogue hash decides when the install carries one.
|
|
171
|
+
let inst = {};
|
|
171
172
|
try {
|
|
172
|
-
|
|
173
|
+
inst = JSON.parse(fs.readFileSync(path.join(__dirname, "orc-version.json"), "utf8")) || {};
|
|
173
174
|
} catch (_) {}
|
|
174
|
-
if (!lock || (
|
|
175
|
+
if (!lock || (inst.catalog_hash ? lock.catalog_hash !== inst.catalog_hash : (inst.version && lock.orc_version !== inst.version))) return null;
|
|
175
176
|
|
|
176
177
|
const engine = require("./orc-statusline-render.js");
|
|
177
178
|
|
|
@@ -179,6 +180,7 @@ function render(d, tasks) {
|
|
|
179
180
|
for (const b of lock.bindings || []) {
|
|
180
181
|
if (!engine.BINDINGS[b]) return null;
|
|
181
182
|
}
|
|
183
|
+
if (engine.OPS && !opsKnown(prog.lines, engine.OPS)) return null;
|
|
182
184
|
|
|
183
185
|
// Rung 5. A subagent row is ONE line by construction — Claude Code renders
|
|
184
186
|
// one row per task — so the board's three-line shape does not apply and the
|
|
@@ -202,7 +204,7 @@ function render(d, tasks) {
|
|
|
202
204
|
payload: d,
|
|
203
205
|
ledger: {},
|
|
204
206
|
scan: {},
|
|
205
|
-
derived: { verdict: null, reasons: [], version:
|
|
207
|
+
derived: { verdict: null, reasons: [], version: inst.version || null },
|
|
206
208
|
task,
|
|
207
209
|
now,
|
|
208
210
|
cols: Number(process.env.COLUMNS) || 0,
|
|
@@ -220,3 +222,19 @@ function render(d, tasks) {
|
|
|
220
222
|
return null;
|
|
221
223
|
}
|
|
222
224
|
}
|
|
225
|
+
|
|
226
|
+
// Every compiled op must be one the engine walks. A plain stack, no copies.
|
|
227
|
+
function opsKnown(lines, OPS) {
|
|
228
|
+
const st = [];
|
|
229
|
+
for (const l of lines || []) st.push(l.ops || []);
|
|
230
|
+
while (st.length) {
|
|
231
|
+
const list = st.pop();
|
|
232
|
+
for (let i = 0; i < list.length; i++) {
|
|
233
|
+
const o = list[i];
|
|
234
|
+
if (!o || !OPS[o.op]) return false;
|
|
235
|
+
if (o.children) st.push(o.children);
|
|
236
|
+
if (o.cap && o.cap.ops) st.push(o.cap.ops);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ORC statusline — THE WEATHER FETCHER. (v2.0.4)
|
|
6
|
+
*
|
|
7
|
+
* The status-line hook starts this DETACHED and never waits for it:
|
|
8
|
+
* node orc-weather-fetch.js <orcDir> <location> <metric|us>
|
|
9
|
+
* It asks wttr.in once, writes `<orcDir>/weather.json` atomically (a temp file,
|
|
10
|
+
* then a rename) and deletes `<orcDir>/weather.lock`. Any error deletes the
|
|
11
|
+
* lock and exits quietly: the old cache stays as it was. It never prints.
|
|
12
|
+
* Zero dependencies.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SUN = [113];
|
|
16
|
+
const PARTLY = [116];
|
|
17
|
+
const CLOUD = [119, 122];
|
|
18
|
+
const FOG = [143, 248, 260];
|
|
19
|
+
const STORM = [200, 386, 389, 392, 395];
|
|
20
|
+
const RAIN = [176, 263, 266, 281, 284, 293, 296, 299, 302, 305, 308, 311, 314, 353, 356, 359];
|
|
21
|
+
const SNOW = [179, 182, 185, 227, 230, 317, 320, 323, 326, 329, 332, 335, 338, 350, 362, 365, 368, 371, 374, 377];
|
|
22
|
+
|
|
23
|
+
// wttr.in weatherCode → one of the `weather-icon` states. An unknown code is
|
|
24
|
+
// fog when the description names haze/smoke/mist/fog/dust/sand/ash, else cloud.
|
|
25
|
+
function stateOf(code, desc) {
|
|
26
|
+
const n = Number(code);
|
|
27
|
+
if (!Number.isFinite(n)) return null;
|
|
28
|
+
if (SUN.includes(n)) return "sun";
|
|
29
|
+
if (PARTLY.includes(n)) return "partly";
|
|
30
|
+
if (CLOUD.includes(n)) return "cloud";
|
|
31
|
+
if (FOG.includes(n)) return "fog";
|
|
32
|
+
if (STORM.includes(n)) return "storm";
|
|
33
|
+
if (SNOW.includes(n)) return "snow";
|
|
34
|
+
if (RAIN.includes(n)) return "rain";
|
|
35
|
+
if (/haze|smoke|mist|fog|dust|sand|ash/i.test(String(desc || ""))) return "fog";
|
|
36
|
+
return "cloud";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A provider string is printed to a terminal: no control characters (an ESC
|
|
40
|
+
// is a terminal-escape injection), at most 24 characters.
|
|
41
|
+
function clean(s) {
|
|
42
|
+
if (s == null) return null;
|
|
43
|
+
const t = [...String(s).replace(/[\u0000-\u001f\u007f-\u009f]/g, "")].slice(0, 24).join("").trim();
|
|
44
|
+
return t || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function numOr(v) {
|
|
48
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
49
|
+
return Number.isFinite(n) ? n : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The pure half: a parsed wttr.in `j1` body → the cache record, or null.
|
|
53
|
+
function record(body, now) {
|
|
54
|
+
const cur = body && Array.isArray(body["current_condition"]) ? body["current_condition"][0] : null;
|
|
55
|
+
if (!cur) return null;
|
|
56
|
+
const desc = Array.isArray(cur.weatherDesc) && cur.weatherDesc[0] ? cur.weatherDesc[0].value : null;
|
|
57
|
+
return {
|
|
58
|
+
at: now,
|
|
59
|
+
temp_c: numOr(cur.temp_C),
|
|
60
|
+
temp_f: numOr(cur.temp_F),
|
|
61
|
+
state: stateOf(cur.weatherCode, desc),
|
|
62
|
+
desc: clean(desc),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function main(argv) {
|
|
67
|
+
const fs = require("fs");
|
|
68
|
+
const path = require("path");
|
|
69
|
+
const orcDir = argv[0];
|
|
70
|
+
if (!orcDir) return;
|
|
71
|
+
const lockFile = path.join(orcDir, "weather.lock");
|
|
72
|
+
const drop = () => {
|
|
73
|
+
try {
|
|
74
|
+
fs.unlinkSync(lockFile);
|
|
75
|
+
} catch (_) {}
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
const loc = String(argv[1] || "").replace(/[\u0000-\u001f\u007f-\u009f]/g, "").slice(0, 40);
|
|
79
|
+
const url = "https://wttr.in/" + encodeURIComponent(loc) + "?format=j1";
|
|
80
|
+
const req = require("https").get(url, { headers: { "User-Agent": "orc-statusline" }, timeout: 6000 }, (res) => {
|
|
81
|
+
if (res.statusCode !== 200) {
|
|
82
|
+
res.resume();
|
|
83
|
+
drop();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let raw = "";
|
|
87
|
+
res.setEncoding("utf8");
|
|
88
|
+
res.on("data", (c) => {
|
|
89
|
+
raw += c;
|
|
90
|
+
if (raw.length > 1e6) req.destroy();
|
|
91
|
+
});
|
|
92
|
+
res.on("end", () => {
|
|
93
|
+
try {
|
|
94
|
+
const rec = record(JSON.parse(raw), Date.now());
|
|
95
|
+
if (rec) {
|
|
96
|
+
const out = path.join(orcDir, "weather.json");
|
|
97
|
+
const tmp = out + "." + process.pid + ".tmp";
|
|
98
|
+
fs.writeFileSync(tmp, JSON.stringify(rec) + "\n");
|
|
99
|
+
fs.renameSync(tmp, out);
|
|
100
|
+
}
|
|
101
|
+
} catch (_) {}
|
|
102
|
+
drop();
|
|
103
|
+
});
|
|
104
|
+
res.on("error", drop);
|
|
105
|
+
});
|
|
106
|
+
req.on("timeout", () => req.destroy());
|
|
107
|
+
req.on("error", drop);
|
|
108
|
+
} catch (_) {
|
|
109
|
+
drop();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (require.main === module) {
|
|
114
|
+
process.on("uncaughtException", () => {
|
|
115
|
+
try {
|
|
116
|
+
require("fs").unlinkSync(require("path").join(process.argv[2] || ".", "weather.lock"));
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
process.exit(0);
|
|
119
|
+
});
|
|
120
|
+
main(process.argv.slice(2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { stateOf, clean, record };
|
|
@@ -131,9 +131,9 @@ tasks and then pins one executor over them, so both edges of that agent's band i
|
|
|
131
131
|
a question about numbers the run actually produced. `/orc-fast` produces none.
|
|
132
132
|
Mini is the LAST caller of the both-edges rule, and that rule keeps its reason.
|
|
133
133
|
**Resolve BOTH EDGES of the pinned agent's band and require them to agree** —
|
|
134
|
-
`[
|
|
134
|
+
`[31,41)` is `orc extra resolve 31` and `orc extra resolve 40`, same profile and
|
|
135
135
|
model on both or the lane stays on Claude. The alternative was a midpoint, and a
|
|
136
|
-
row covering `[
|
|
136
|
+
row covering `[31,34)` would then capture an entire mini run on the strength of
|
|
137
137
|
three scores out of ten. **A number ORC invented to satisfy an
|
|
138
138
|
interface is not a routing decision the user made.**
|
|
139
139
|
|
|
@@ -153,7 +153,7 @@ too.
|
|
|
153
153
|
| `doc-writer` | `/orc-doc` | `orc-doc-writer-opus-5-med` | announced, before the wave, naming the sections | a writer owns ONE part file and invents no fact; its output is read by a checker and by you before it ships |
|
|
154
154
|
| `doc-checker` | `/orc-doc` | `orc-doc-checker-opus-5-low` | announced, before the wave | the checker reads one bounded part and reports; it rewrites nothing |
|
|
155
155
|
| `test-designer` | `/orc-test` | `orc-test-designer-opus-5-high` | announced, the T1 `extra:` line, before any case runs | the designer writes a FILE the CLI reads back through a validating command, and a row that fails validation is refused BY NAME — so its output is checked before anything is sent |
|
|
156
|
-
| `wiki-scanner-deep` | `/orc-wiki` | `orc-wiki-scanner-opus-
|
|
156
|
+
| `wiki-scanner-deep` | `/orc-wiki` | `orc-wiki-scanner-opus-5-low` | announced, per scan-batch, beside the resolved tier | a scanner returns an evidence-anchored doc body; every claim in it is anchored to a file you can open |
|
|
157
157
|
| `wiki-scanner-light` | `/orc-wiki` | `orc-wiki-scanner-sonnet-5-high` | announced, per scan-batch, beside the resolved tier | the LIGHT tier is already a small no-new-surface delta on an existing doc |
|
|
158
158
|
|
|
159
159
|
**`/orc-test`'s INTERPRETER deliberately has NO SLOT, and that refusal is the
|
|
@@ -163,7 +163,7 @@ fence fences FILES, not a request body. There is no version of that route this
|
|
|
163
163
|
repo could describe honestly, so there is none. It is the `/orc-challenge never`
|
|
164
164
|
shape, for a stronger reason than cost.
|
|
165
165
|
|
|
166
|
-
Both wiki slots collapse onto `orc-wiki-scanner-opus-5-
|
|
166
|
+
Both wiki slots collapse onto `orc-wiki-scanner-opus-5-low` while `opus5_only` is
|
|
167
167
|
on, which is why this release **adds no agent and no pair**. Two slots and one
|
|
168
168
|
Opus 5.5 agent is not a contradiction: **a slot names the POSITION, not the model.**
|
|
169
169
|
|
|
@@ -235,7 +235,7 @@ One sentence, and it is the same sentence for both shapes:
|
|
|
235
235
|
an extra route row covering this score (only for the scores it covers)
|
|
236
236
|
> opus5_only
|
|
237
237
|
> rubric_bands_override
|
|
238
|
-
> the default
|
|
238
|
+
> the default 5-band table
|
|
239
239
|
```
|
|
240
240
|
|
|
241
241
|
**A slot**, highest wins:
|
|
@@ -185,7 +185,19 @@ cited one), then ONE trace line:
|
|
|
185
185
|
`FINDING-OUTCOME addressed=<n> disputed=<n> wontfix=<n> open=<n> pre=<n> suppressed=<n> :: <cat>:<addressed>/<total>,…`.
|
|
186
186
|
Where: `/orc` and `/orc-ultra` at Phase 7 (`phases/summary.md`), `/orc-mini`
|
|
187
187
|
after the risk option's review, `/orc-quick` after the review offer. A review
|
|
188
|
-
with zero findings records
|
|
188
|
+
with zero findings records no observation and is a HEALTHY answer: its
|
|
189
|
+
`FINDING-OUTCOME` line (or the hook `RETURN orc-reviewer-*` line) already counts
|
|
190
|
+
it as a review in `orc gotcha quality` (v2.1.0). An `author: orc` observation
|
|
191
|
+
without `run` is refused (exit 2) — a `ref` that starts with the run id fills it.
|
|
192
|
+
|
|
193
|
+
**The reviewed ranges (v2.1.0, DE-13).** In the same step, store what the review
|
|
194
|
+
READ: `orc gotcha observe -` with
|
|
195
|
+
`{"kind": "review-scope", "run": "<trace name>", "commit": "<HEAD>", "files": {"<path>": [[start, end], …]}}`
|
|
196
|
+
— the slice's `diff_ranges`, a clean review too. The CLI keeps the last 500
|
|
197
|
+
reviews in `.claude/orc/review-scope.jsonl` (user data, never pruned by age) and
|
|
198
|
+
counts what it drops. A later `orc fix record` on lines inside a stored range
|
|
199
|
+
of an EARLIER run is a computed `miss`: the fix is promoted at 1 case and goes
|
|
200
|
+
first in the next card.
|
|
189
201
|
|
|
190
202
|
### Per-lane mechanics (the spines keep the trigger; this is the detail)
|
|
191
203
|
|
|
@@ -205,16 +217,24 @@ file is never a reason to fall back, and gotchas here are purely additive: probe
|
|
|
205
217
|
inject the scope-matching entries into the F2 slice, and stop there. It never
|
|
206
218
|
writes one, because one executor plus one repair round is too little signal to
|
|
207
219
|
attribute a cause — and a lane with no analyst writing repair memory is how that
|
|
208
|
-
memory fills with guesses.
|
|
220
|
+
memory fills with guesses. **A review is different (v2.1.0):** when `/orc-fast` runs
|
|
221
|
+
a review (the F4 offer, or the user asks for one), it records that review's outcomes
|
|
222
|
+
as "Review close" says — an observation is REVIEW LEARNING, not a repair-memory
|
|
223
|
+
entry, and every lane that ran a review records it.
|
|
209
224
|
|
|
210
225
|
**`/orc-quick` — reads and records, since 2.0.0 (a deliberate reversal).**
|
|
211
|
-
Record
|
|
212
|
-
programmatic proof: a defect entry with `REPRO red` then `REPRO green`
|
|
226
|
+
Record after the step 3.5 offers (v2.1.0 — it was 3.3, before the review offer
|
|
227
|
+
existed to record), and only for a request with programmatic proof: a defect entry with `REPRO red` then `REPRO green`
|
|
213
228
|
(`source: defect`, `reproduced: true`) · a PR thread the request took and fixed
|
|
214
229
|
(`source: pr`) · a Sonar or SARIF item fixed (`source: sonar` / `sarif`) · a CI
|
|
215
|
-
failure fixed (`source: ci`) · a review
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
failure fixed (`source: ci`) · every finding of a review offer, ONE observation per finding
|
|
231
|
+
with its outcome, as "Review close" says (`source: review`; "not a problem here"
|
|
232
|
+
= `disputed`). A plain fix with none of these records nothing.
|
|
233
|
+
A FIX of a named finding (a Sonar issue, a failed check, a defect ORC or an AI
|
|
234
|
+
tool caused) goes through `orc fix record -` (`via: "orc-quick"`, v2.1.0): the
|
|
235
|
+
author is the finder, never `orc`; a Sonar issue key gives the importer's own id,
|
|
236
|
+
so a later gotcha sync completes the same row; and the CLI computes `miss`
|
|
237
|
+
from `review-scope.jsonl`.
|
|
218
238
|
Why the reversal: quick is where most of the fixes happen, so a memory that
|
|
219
239
|
skips it never learns; a PR thread is a human's judgement and a red → green
|
|
220
240
|
repro is proof, so the v1 "too little signal" objection does not apply; and the
|
|
@@ -74,14 +74,14 @@ compiled flow's last phase. `/orc-wait` makes no proposal.
|
|
|
74
74
|
question; its `announce[]` line names the habit and its undo. A habit
|
|
75
75
|
toward `no` is a pre-mark only, never applied. A learned
|
|
76
76
|
`review_before_push: on` → the end-of-run batch ALWAYS carries option *a*
|
|
77
|
-
(dispatch `orc-reviewer-opus-5-
|
|
77
|
+
(dispatch `orc-reviewer-opus-5-low` on the diff), marked `→ usual`, even with
|
|
78
78
|
no `risk: high` row. Record `any.ship.review-before-push` whenever option *a*
|
|
79
79
|
is offered: `chose=review` when the user takes it, else `push`.
|
|
80
80
|
- **`/orc-fast`.** `fast.f0.stale-wiki`: a habit NEVER suggests `continue`
|
|
81
81
|
(a stale wiki is a risk the user takes each time). The CLI never proposes
|
|
82
82
|
it (`never_option`); a suggestion row that names it anyway is ignored. Show
|
|
83
83
|
a `refresh` or `mini` suggestion only, and never apply one. A learned `review_before_push: on` → F4
|
|
84
|
-
offers a dispatch of `orc-reviewer-opus-5-
|
|
84
|
+
offers a dispatch of `orc-reviewer-opus-5-low` on the diff FIRST (P0/P1 block
|
|
85
85
|
the commit), marked `→ usual`; the reviewer is not in the fast pipeline, so
|
|
86
86
|
it runs only on that yes. Record `any.ship.review-before-push` whenever it is
|
|
87
87
|
offered.
|