@azure-id/orc 1.9.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 +722 -0
- package/README-id.md +56 -42
- package/README.md +58 -41
- package/bin/build-agents.js +125 -29
- package/bin/clear-logs.js +620 -0
- package/bin/cli.js +1873 -180
- package/bin/fix.js +459 -0
- package/bin/gotcha-import.js +1081 -0
- package/bin/gotcha.js +1566 -0
- package/bin/graph-query.js +1 -1
- package/bin/graph.js +717 -717
- package/bin/habit.js +1538 -0
- package/bin/mockrun-catalog.js +284 -276
- package/bin/onboarding-content.js +2 -1
- package/bin/pricing.json +28 -21
- package/bin/run-undo.js +398 -0
- package/bin/trace-write.js +869 -0
- package/bin/verify-contracts.js +627 -100
- package/bin/verify-package.js +68 -10
- package/bin/webui/api.js +151 -6
- package/bin/webui/app.html +239 -232
- package/bin/webui/css/00-tokens.css +110 -92
- package/bin/webui/css/04-motion.css +87 -0
- package/bin/webui/css/06-responsive.css +203 -178
- package/bin/webui/css/panels/behaviour.css +205 -0
- package/bin/webui/css/panels/hookui.css +79 -0
- package/bin/webui/fixtures/behaviour.js +562 -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 +66 -3
- package/bin/webui/fixtures/knowledge.js +9 -3
- 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 +25 -6
- package/bin/webui/fixtures/wait.js +98 -97
- package/bin/webui/i18n/en/behaviour.json +156 -0
- package/bin/webui/i18n/en/hookui.json +290 -194
- package/bin/webui/i18n/en/maintenance.json +71 -52
- package/bin/webui/i18n/en/nav.json +25 -24
- package/bin/webui/i18n/en/settings.json +60 -60
- package/bin/webui/i18n/en/tour.json +37 -35
- package/bin/webui/i18n/id/behaviour.json +156 -0
- package/bin/webui/i18n/id/hookui.json +290 -194
- package/bin/webui/i18n/id/maintenance.json +71 -52
- package/bin/webui/i18n/id/nav.json +25 -24
- package/bin/webui/i18n/id/settings.json +60 -60
- package/bin/webui/i18n/id/tour.json +37 -35
- package/bin/webui/js/01-i18n.js +155 -154
- package/bin/webui/js/05-banners.js +181 -172
- package/bin/webui/js/06-edit.js +193 -183
- 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 +1087 -0
- 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 +110 -107
- package/mock-run/extra-slots.md +1 -1
- package/mock-run/gotcha-import.md +118 -0
- package/mock-run/habits.md +129 -0
- 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-quick.md +6 -1
- package/mock-run/orc.md +157 -157
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +37 -34
- package/templates/agents/orc-advisor-opus-5-xhigh.md +1 -7
- package/templates/agents/orc-analyze-mini-opus-5-med.md +1 -6
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -5
- package/templates/agents/{orc-claude-writer-opus-4-8-high.md → orc-claude-writer-opus-5-low.md} +48 -53
- package/templates/agents/orc-context-combiner-opus-5-high.md +1 -11
- package/templates/agents/orc-executor-haiku-4-5.md +14 -6
- package/templates/agents/orc-executor-opus-4-7-high.md +14 -6
- package/templates/agents/orc-executor-opus-4-7-med.md +14 -6
- package/templates/agents/orc-executor-opus-4-8-high.md +14 -6
- package/templates/agents/orc-executor-opus-5-high.md +14 -6
- package/templates/agents/orc-executor-opus-5-low.md +14 -6
- package/templates/agents/orc-executor-opus-5-med.md +14 -6
- package/templates/agents/orc-executor-sonnet-4-6-high.md +14 -6
- package/templates/agents/orc-executor-sonnet-4-6-med.md +14 -6
- package/templates/agents/orc-executor-sonnet-5-high.md +15 -7
- 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-graph-noter-sonnet-4-6-med.md +1 -10
- package/templates/agents/orc-judge-opus-5-xhigh.md +5 -9
- package/templates/agents/orc-learn-writer-opus-5-low.md +1 -7
- package/templates/agents/orc-pattern-codifier-opus-5-med.md +1 -8
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +58 -63
- package/templates/agents/orc-planner-mini-opus-5-med.md +1 -4
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -3
- package/templates/agents/orc-planner-opus-5-med.md +1 -4
- package/templates/agents/orc-recon-opus-5-low.md +1 -8
- package/templates/agents/orc-recon-sonnet-4-6-med.md +1 -8
- package/templates/agents/orc-retro-opus-5-med.md +6 -8
- package/templates/agents/orc-retro-sonnet-5-high.md +7 -8
- package/templates/agents/orc-reviewer-opus-5-low.md +96 -0
- package/templates/agents/orc-scout-opus-5-low.md +1 -6
- package/templates/agents/orc-scout-sonnet-4-6-high.md +35 -39
- package/templates/agents/orc-system-analyst-opus-5-high.md +1 -6
- package/templates/agents/orc-test-author-opus-5-med.md +4 -5
- package/templates/agents/orc-trace-writer-haiku-4-5.md +3 -7
- package/templates/agents/orc-verifier-opus-5-med.md +16 -8
- package/templates/agents/{orc-wiki-scanner-opus-4-8-high.md → orc-wiki-scanner-opus-5-low.md} +74 -79
- 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-fix.md +18 -0
- 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-wiki.md +64 -64
- package/templates/hooks/README.md +109 -7
- package/templates/hooks/orc-session-hook.js +339 -0
- package/templates/hooks/orc-statusline-render.js +317 -23
- package/templates/hooks/orc-statusline.js +165 -50
- package/templates/hooks/orc-subagent-line.js +23 -5
- package/templates/hooks/orc-weather-fetch.js +123 -0
- package/templates/skills/_shared/README.md +9 -0
- package/templates/skills/_shared/code-graph.md +47 -55
- package/templates/skills/_shared/config-precedence.md +3 -1
- package/templates/skills/_shared/extra-dispatch.md +78 -93
- package/templates/skills/_shared/gotchas.md +248 -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 +14 -14
- package/templates/skills/_shared/phases/README.md +142 -83
- package/templates/skills/_shared/phases/analyst-gates.md +10 -21
- package/templates/skills/_shared/phases/execution.md +8 -14
- package/templates/skills/_shared/phases/house-rules.md +27 -32
- package/templates/skills/_shared/phases/intake.md +127 -133
- package/templates/skills/_shared/phases/mock-example.md +46 -56
- package/templates/skills/_shared/phases/plan-handoff.md +91 -97
- package/templates/skills/_shared/phases/planning.md +7 -17
- package/templates/skills/_shared/phases/preflight.md +19 -42
- package/templates/skills/_shared/phases/review.md +24 -28
- 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 +447 -0
- package/templates/skills/_shared/phases/trace.md +66 -297
- 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 +106 -0
- package/templates/skills/_shared/smoke-gate.md +46 -28
- package/templates/skills/_shared/wait.md +1 -0
- package/templates/skills/context-combiner/SKILL.md +15 -44
- package/templates/skills/orc/README.md +2 -2
- package/templates/skills/orc/SKILL.md +35 -63
- 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/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 +11 -12
- package/templates/skills/orc/references/ultra-mode.md +9 -7
- package/templates/skills/orc/schemas/checkpoint.md +122 -122
- package/templates/skills/orc/subskills/orc-execution/SKILL.md +27 -73
- package/templates/skills/orc/subskills/orc-execution/core.md +12 -99
- package/templates/skills/orc/subskills/orc-execution/subagent.md +14 -13
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +12 -53
- package/templates/skills/orc/subskills/orc-review-verify/core.md +52 -135
- package/templates/skills/orc/subskills/orc-review-verify/subagent.md +7 -7
- package/templates/skills/orc/subskills/orc-testgen/SKILL.md +11 -22
- package/templates/skills/orc/subskills/orc-testgen/core.md +20 -59
- package/templates/skills/orc/subskills/orc-testgen/subagent.md +7 -7
- package/templates/skills/orc-advisor/SKILL.md +56 -60
- package/templates/skills/orc-analyze/SKILL.md +33 -67
- package/templates/skills/orc-analyze/schemas/report-audit.md +2 -1
- package/templates/skills/orc-analyze/schemas/report-prose.md +2 -1
- package/templates/skills/orc-analyze-mini/SKILL.md +35 -70
- package/templates/skills/orc-claude/SKILL.md +14 -14
- package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
- package/templates/skills/orc-diy/README.md +31 -0
- package/templates/skills/orc-diy/SKILL.md +27 -78
- package/templates/skills/orc-diy/references/blocks/pattern.md +18 -18
- package/templates/skills/orc-fast/SKILL.md +49 -75
- package/templates/skills/orc-fix/SKILL.md +110 -0
- package/templates/skills/orc-judge/SKILL.md +77 -82
- package/templates/skills/orc-mini/SKILL.md +64 -112
- package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
- 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 +44 -3
- package/templates/skills/orc-quick/SKILL.md +77 -108
- package/templates/skills/orc-quick/references/dispatch-gate.md +18 -6
- package/templates/skills/orc-quick/references/gh-mode.md +50 -1
- package/templates/skills/orc-quick/references/look.md +3 -1
- package/templates/skills/orc-retro/SKILL.md +20 -19
- package/templates/skills/orc-retro/examples/retro-mock.md +17 -17
- package/templates/skills/orc-route/SKILL.md +25 -45
- package/templates/skills/orc-test/SKILL.md +16 -37
- package/templates/skills/orc-verify/SKILL.md +14 -32
- package/templates/skills/orc-wait/SKILL.md +156 -163
- package/templates/skills/orc-wiki/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 +2 -7
- package/templates/skills/orc-wiki/references/phases/phase-1.md +1 -6
- package/templates/skills/orc-wiki/references/phases/phase-2.md +4 -9
- package/templates/skills/orc-wiki/references/phases/phase-3.md +1 -6
- package/templates/skills/orc-wiki/references/phases/phase-3c.md +4 -8
- package/templates/agents/orc-claude-writer-opus-5-med.md +0 -55
- package/templates/agents/orc-reviewer-opus-5-med.md +0 -60
- package/templates/agents/orc-wiki-scanner-opus-5-med.md +0 -81
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ORC session hook (v2.0.0) — two main-session events, one file.
|
|
6
|
+
*
|
|
7
|
+
* Stop (Q8) the terminal bell when a turn of an ORC run
|
|
8
|
+
* ends. Config `notify`: `off` (the default) · `bell`.
|
|
9
|
+
* SessionStart `compact` (Q9) ONE line after a compaction, while a run is in
|
|
10
|
+
* flight, so the session is told where its state is.
|
|
11
|
+
*
|
|
12
|
+
* Both halves say NOTHING outside an open ORC run. A normal chat never rings
|
|
13
|
+
* and never gets a line. No OS notification, no network, no model text.
|
|
14
|
+
*
|
|
15
|
+
* An open run = the `.current` pointer in `log_dir` exists and is fresh (the
|
|
16
|
+
* same 6-hour window as orc-trace.js and orc-read-gate.js — one idea of "a run
|
|
17
|
+
* is open", not three). The lanes delete `.current` at FINISH.
|
|
18
|
+
*
|
|
19
|
+
* Wiring (installed by `orc init` into .claude/settings.json):
|
|
20
|
+
* hooks.Stop[] { hooks:[{command:"node <..>/orc-session-hook.js"}] }
|
|
21
|
+
* hooks.SessionStart[] { matcher:"compact", hooks:[{command:"node <..>/orc-session-hook.js"}] }
|
|
22
|
+
*
|
|
23
|
+
* Contract: read hook JSON from stdin. ALWAYS exit 0, silent on any error. It
|
|
24
|
+
* blocks a stop in ONE case only (v2.0.2, the narration guard below): the run in
|
|
25
|
+
* this session dispatched agents and wrote no narration line — once per run,
|
|
26
|
+
* never when `stop_hook_active` is set, never for a subagent.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const fs = require("fs");
|
|
30
|
+
const path = require("path");
|
|
31
|
+
|
|
32
|
+
// .claude/hooks/orc-session-hook.js → CLAUDE_DIR = .. → PROJECT_ROOT = ../..
|
|
33
|
+
const CLAUDE_DIR = path.join(__dirname, "..");
|
|
34
|
+
const PROJECT_ROOT = path.join(CLAUDE_DIR, "..");
|
|
35
|
+
const STALE_MS = 6 * 60 * 60 * 1000;
|
|
36
|
+
// The canonical trace name — the same regex the CLI's in-flight read uses.
|
|
37
|
+
const TRACE_NAME = /^run-([a-z0-9]+)-(.+)-(\d{6})-(\d{6})\.txt$/;
|
|
38
|
+
const RUN_DIR_DEFAULT = ".claude/orc/run";
|
|
39
|
+
|
|
40
|
+
// Tolerant top-level YAML scalar read, matching orc-read-gate.js exactly.
|
|
41
|
+
function readConfigScalar(key) {
|
|
42
|
+
try {
|
|
43
|
+
const text = fs.readFileSync(path.join(CLAUDE_DIR, "orc.config.yaml"), "utf8");
|
|
44
|
+
const re = new RegExp("^" + key + "\\s*:\\s*(.+?)\\s*(?:#.*)?$", "m");
|
|
45
|
+
const m = text.match(re);
|
|
46
|
+
return m ? m[1].replace(/^['"]|['"]$/g, "").trim() : null;
|
|
47
|
+
} catch (_) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function abs(rel) {
|
|
53
|
+
return path.isAbsolute(rel) ? rel : path.join(PROJECT_ROOT, rel);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logDir() {
|
|
57
|
+
return abs(readConfigScalar("log_dir") || ".claude/orc/logs");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function mtime(p) {
|
|
61
|
+
try {
|
|
62
|
+
return fs.statSync(p).mtimeMs;
|
|
63
|
+
} catch (_) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The open run, or null. `last` = the newest sign of life (the trace file or
|
|
69
|
+
// the pointer), which the bell compares with the session start.
|
|
70
|
+
function openRun() {
|
|
71
|
+
const dir = logDir();
|
|
72
|
+
let name;
|
|
73
|
+
try {
|
|
74
|
+
name = fs.readFileSync(path.join(dir, ".current"), "utf8").trim();
|
|
75
|
+
} catch (_) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (!name) return null;
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const last = Math.max(mtime(path.join(dir, name)) || 0, mtime(path.join(dir, ".current")) || 0);
|
|
81
|
+
if (!last || now - last >= STALE_MS) return null;
|
|
82
|
+
return { dir, name, last };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── The narration guard (v2.0.2, eval D9) ────────────────────────────────────
|
|
86
|
+
// A live lane can dispatch agents and end its turn with ZERO narration: the trace
|
|
87
|
+
// holds only the hook's SPAWN/RETURN lines, and the reply still says "FINISH".
|
|
88
|
+
// Then the stats, the retro and the habit engine see nothing. So ONCE per run,
|
|
89
|
+
// in the main session, a stop is blocked with the one command that fixes it.
|
|
90
|
+
// `stop_hook_active` (Claude Code's own re-entry flag) means the lane already
|
|
91
|
+
// had its chance — never block twice.
|
|
92
|
+
function narrationGuard(data) {
|
|
93
|
+
if (data.agent_id || data.stop_hook_active) return false;
|
|
94
|
+
const dir = logDir();
|
|
95
|
+
let born = 0;
|
|
96
|
+
try {
|
|
97
|
+
if (data.transcript_path) born = fs.statSync(data.transcript_path).birthtimeMs || 0;
|
|
98
|
+
} catch (_) {}
|
|
99
|
+
let best = null;
|
|
100
|
+
try {
|
|
101
|
+
for (const f of fs.readdirSync(dir)) {
|
|
102
|
+
if (!TRACE_NAME.test(f)) continue;
|
|
103
|
+
const t = mtime(path.join(dir, f));
|
|
104
|
+
if (t && (!best || t > best.t)) best = { f, t };
|
|
105
|
+
}
|
|
106
|
+
} catch (_) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (!best || Date.now() - best.t >= STALE_MS || (born && best.t < born)) return false;
|
|
110
|
+
let text = "";
|
|
111
|
+
try {
|
|
112
|
+
text = fs.readFileSync(path.join(dir, best.f), "utf8");
|
|
113
|
+
} catch (_) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const all = text.split(/\r?\n/).filter((l) => l.trim());
|
|
117
|
+
const lines = all.filter((l) => /^\[/.test(l));
|
|
118
|
+
if (!lines.some((l) => /\]\s+hook\s+SPAWN /.test(l))) return false; // nothing was dispatched yet
|
|
119
|
+
const statePath = path.join(CLAUDE_DIR, "orc", "narration-guard.json");
|
|
120
|
+
// v2.1.0 (E21 D7) — narration written BY HAND. A live /orc-quick appended its
|
|
121
|
+
// lines itself (no `.mmm` stamp, or no stamp at all), so `orc trace write` never
|
|
122
|
+
// saw them: no grammar check, and none of the FINISH reminders (observe, which
|
|
123
|
+
// review, config, phases) ran — the lane then claimed the findings were
|
|
124
|
+
// "logged". The CLI writes every line to the .txt AND its .jsonl twin, so more
|
|
125
|
+
// narration lines than jsonl rows, or a line without the CLI's stamp, is proof.
|
|
126
|
+
const narr = all.filter((l) => !/^\[[^\]]*\]\s+hook\s+/.test(l));
|
|
127
|
+
if (narr.length) {
|
|
128
|
+
let rows = 0;
|
|
129
|
+
try {
|
|
130
|
+
rows = fs.readFileSync(path.join(dir, best.f + ".jsonl"), "utf8").split(/\r?\n/).filter((l) => l.trim()).length;
|
|
131
|
+
} catch (_) {}
|
|
132
|
+
// NOT the `.mmm` stamp: `orc trace write` also accepts HH:MM:SS (a live run
|
|
133
|
+
// proved it, and an early guard blocked a correct run on it).
|
|
134
|
+
const hand = narr.some((l) => !/^\[\d{6} \d{2}:\d{2}:\d{2}(?:\.\d{1,3})?\] /.test(l)) || narr.length > rows;
|
|
135
|
+
if (!hand) return reviewGuard(best, text, statePath); // a CLI packet already landed
|
|
136
|
+
try {
|
|
137
|
+
const st = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
138
|
+
if (st.run === best.f && st.kind === "hand") return false;
|
|
139
|
+
} catch (_) {}
|
|
140
|
+
try {
|
|
141
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
142
|
+
fs.writeFileSync(statePath, JSON.stringify({ run: best.f, kind: "hand", at: Date.now() }) + "\n");
|
|
143
|
+
} catch (_) {}
|
|
144
|
+
process.stdout.write(
|
|
145
|
+
JSON.stringify({
|
|
146
|
+
decision: "block",
|
|
147
|
+
reason:
|
|
148
|
+
`ORC: the trace ${best.f} has lines written by hand — \`orc trace write\` never saw them, so no check and no FINISH reminder ran. ` +
|
|
149
|
+
`Never append to a trace yourself. Send the FINISH packet now through \`orc trace write --packet -\` (\`run: ${best.f.replace(/\.txt$/, "")}\` if .current is gone) ` +
|
|
150
|
+
`and do what its reply says (for a review: one \`orc gotcha observe\` per finding), then finish.`,
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
if (JSON.parse(fs.readFileSync(statePath, "utf8")).run === best.f) return false;
|
|
157
|
+
} catch (_) {}
|
|
158
|
+
try {
|
|
159
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
160
|
+
fs.writeFileSync(statePath, JSON.stringify({ run: best.f, at: Date.now() }) + "\n");
|
|
161
|
+
} catch (_) {}
|
|
162
|
+
process.stdout.write(
|
|
163
|
+
JSON.stringify({
|
|
164
|
+
decision: "block",
|
|
165
|
+
reason:
|
|
166
|
+
`ORC: the trace ${best.f} has agent dispatches but NO narration line — zero new trace lines is a protocol violation. ` +
|
|
167
|
+
`Pipe this run's phase packets (with their REAL event times, and the ASK events if habits is on) to ` +
|
|
168
|
+
`\`orc trace write --packet -\` now (\`run: ${best.f.replace(/\.txt$/, "")}\` if .current is gone), then finish.`,
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// v2.1.0 (E21 D8) — a FINISHED run that reviewed and recorded nothing. A live
|
|
175
|
+
// /orc ignored the FINISH observe reminder after a review with findings; Review
|
|
176
|
+
// Quality then counts the review but learns no acceptance from it. ONCE per run,
|
|
177
|
+
// in the main session: the trace has a reviewer RETURN and a FINISH line, the
|
|
178
|
+
// review was not clean, and no observation names this run → block one stop.
|
|
179
|
+
function reviewGuard(best, text, statePath) {
|
|
180
|
+
if (!/\]\s+hook\s+RETURN\s+orc-reviewer-/.test(text) || !/\]\s+\S+\s+FINISH\b/.test(text)) return false;
|
|
181
|
+
const fo = /FINDING-OUTCOME addressed=(\d+) disputed=(\d+) wontfix=(\d+) open=(\d+)/.exec(text);
|
|
182
|
+
const f = /\]\s+\S+\s+FINDING p0=(\d+) p1=(\d+) p2=(\d+) p3=(\d+)/.exec(text);
|
|
183
|
+
if ((fo && fo.slice(1, 5).every((n) => n === "0")) || (!fo && f && f.slice(1, 5).every((n) => n === "0"))) return false; // clean
|
|
184
|
+
const run = best.f.replace(/\.txt$/, "");
|
|
185
|
+
try {
|
|
186
|
+
for (const line of fs.readFileSync(path.join(CLAUDE_DIR, "orc", "observations.jsonl"), "utf8").split(/\r?\n/)) {
|
|
187
|
+
if (!line.trim()) continue;
|
|
188
|
+
try {
|
|
189
|
+
const o = JSON.parse(line);
|
|
190
|
+
if (o && o.author === "orc" && o.run === run) return false;
|
|
191
|
+
} catch (_) {}
|
|
192
|
+
}
|
|
193
|
+
} catch (_) {}
|
|
194
|
+
try {
|
|
195
|
+
const st = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
196
|
+
if (st.run === best.f && st.kind === "review") return false;
|
|
197
|
+
} catch (_) {}
|
|
198
|
+
try {
|
|
199
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
200
|
+
fs.writeFileSync(statePath, JSON.stringify({ run: best.f, kind: "review", at: Date.now() }) + "\n");
|
|
201
|
+
} catch (_) {}
|
|
202
|
+
process.stdout.write(
|
|
203
|
+
JSON.stringify({
|
|
204
|
+
decision: "block",
|
|
205
|
+
reason:
|
|
206
|
+
`ORC: the run ${run} ran a review with findings and recorded no outcome. Pipe ONE object per finding to \`orc gotcha observe - --json\` ` +
|
|
207
|
+
`(source review · author orc · run ${run} · ref "${run} · F<n>" · outcome addressed|disputed|wontfix|open · category · rule or sig), ` +
|
|
208
|
+
`and write the FINDING-OUTCOME line if it is missing (\`orc trace write --packet -\`, run: ${run}). Then finish.`,
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── The review card at SubagentStart (v2.0.2, eval D13) ─────────────────────
|
|
215
|
+
// A live /orc-quick review dispatched `orc-reviewer-opus-5-low` with NO gotcha
|
|
216
|
+
// card in its prompt in 5 of 5 runs, although review learning is always on. The
|
|
217
|
+
// card is a CLI answer, so the hook hands it over itself: for a reviewer,
|
|
218
|
+
// verifier or judge, the files git sees as changed → `orc gotcha card` → one
|
|
219
|
+
// `additionalContext` block. No match, no CLI, any error → silent.
|
|
220
|
+
const REVIEW_AGENT = /^orc-(reviewer|verifier|judge)-/;
|
|
221
|
+
function onReviewStart(data) {
|
|
222
|
+
const agent = String(data.agent_type || data.agentType || "");
|
|
223
|
+
if (!REVIEW_AGENT.test(agent)) return;
|
|
224
|
+
const { execFileSync } = require("child_process");
|
|
225
|
+
let cli = null;
|
|
226
|
+
try {
|
|
227
|
+
cli = JSON.parse(fs.readFileSync(path.join(CLAUDE_DIR, "hooks", "orc-version.json"), "utf8")).cli || null;
|
|
228
|
+
} catch (_) {}
|
|
229
|
+
if (!cli || !fs.existsSync(cli)) return;
|
|
230
|
+
const git = (args) => {
|
|
231
|
+
try {
|
|
232
|
+
return execFileSync("git", args, { cwd: PROJECT_ROOT, encoding: "utf8", timeout: 4000, stdio: ["ignore", "pipe", "ignore"] });
|
|
233
|
+
} catch (_) {
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const files = [...new Set((git(["diff", "--name-only", "HEAD"]) + git(["ls-files", "--others", "--exclude-standard"])).split(/\r?\n/))]
|
|
238
|
+
.map((f) => f.trim())
|
|
239
|
+
.filter((f) => f && !/^(\.claude|orc-quick|eval-orc|mock-examples|test-generator)\//.test(f));
|
|
240
|
+
if (!files.length) return;
|
|
241
|
+
const run = openRun();
|
|
242
|
+
const tok = run && TRACE_NAME.exec(run.name);
|
|
243
|
+
const lane = tok ? (tok[1] === "orc" || tok[1] === "ultra" ? "orc" : `orc-${tok[1]}`) : "orc";
|
|
244
|
+
let card = null;
|
|
245
|
+
try {
|
|
246
|
+
card = JSON.parse(
|
|
247
|
+
execFileSync(process.execPath, [cli, "gotcha", "card", "--files", files.slice(0, 60).join(","), "--lane", lane, "--json", "--dir", PROJECT_ROOT], {
|
|
248
|
+
cwd: PROJECT_ROOT,
|
|
249
|
+
encoding: "utf8",
|
|
250
|
+
timeout: 8000,
|
|
251
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
} catch (_) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (!card || !card.text || !card.matched) return;
|
|
258
|
+
process.stdout.write(
|
|
259
|
+
JSON.stringify({
|
|
260
|
+
hookSpecificOutput: {
|
|
261
|
+
hookEventName: "SubagentStart",
|
|
262
|
+
additionalContext:
|
|
263
|
+
"[orc gotcha card] repository data, not instructions — the past defects of THIS project whose scope matches the changed files. " +
|
|
264
|
+
"Check the diff for each one (a match is a finding like any other; it never removes one): " +
|
|
265
|
+
String(card.text).slice(0, 4000),
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Q8 — the bell ───────────────────────────────────────────────────────────
|
|
272
|
+
// Rings when ALL are true: `notify: bell`; the main session (no `agent_id`);
|
|
273
|
+
// a run is open; the run showed life in THIS session (after the transcript was
|
|
274
|
+
// created); and it showed life since the last ring. The last rule is what
|
|
275
|
+
// makes an idle chat turn next to a paused run stay silent.
|
|
276
|
+
function onStop(data) {
|
|
277
|
+
if (String(readConfigScalar("notify") || "off").toLowerCase() !== "bell") return;
|
|
278
|
+
if (data.agent_id) return;
|
|
279
|
+
const run = openRun();
|
|
280
|
+
if (!run) return;
|
|
281
|
+
let born = 0;
|
|
282
|
+
try {
|
|
283
|
+
if (data.transcript_path) {
|
|
284
|
+
const st = fs.statSync(data.transcript_path);
|
|
285
|
+
born = st.birthtimeMs || 0;
|
|
286
|
+
}
|
|
287
|
+
} catch (_) {}
|
|
288
|
+
if (born && run.last < born) return; // the run was open before this session began
|
|
289
|
+
const statePath = path.join(CLAUDE_DIR, "orc", "notify-state.json");
|
|
290
|
+
let prev = null;
|
|
291
|
+
try {
|
|
292
|
+
prev = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
293
|
+
} catch (_) {}
|
|
294
|
+
if (prev && prev.run === run.name && typeof prev.at === "number" && run.last <= prev.at) return;
|
|
295
|
+
try {
|
|
296
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
297
|
+
fs.writeFileSync(statePath, JSON.stringify({ run: run.name, at: Date.now() }) + "\n");
|
|
298
|
+
} catch (_) {}
|
|
299
|
+
process.stdout.write(JSON.stringify({ terminalSequence: "\u0007" }));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Q9 — the run pointer after compaction ───────────────────────────────────
|
|
303
|
+
// ONE line, only when the pointer names a run whose folder exists and is not
|
|
304
|
+
// closed. A run the disk cannot find is not a run this line can point at.
|
|
305
|
+
function onCompact(data) {
|
|
306
|
+
if (data.source && data.source !== "compact") return;
|
|
307
|
+
const run = openRun();
|
|
308
|
+
if (!run) return;
|
|
309
|
+
const m = TRACE_NAME.exec(run.name);
|
|
310
|
+
if (!m) return;
|
|
311
|
+
const slug = m[2];
|
|
312
|
+
const rel = readConfigScalar("run_dir") || RUN_DIR_DEFAULT;
|
|
313
|
+
const folder = path.join(abs(rel), slug);
|
|
314
|
+
try {
|
|
315
|
+
if (!fs.statSync(folder).isDirectory()) return;
|
|
316
|
+
} catch (_) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (fs.existsSync(path.join(folder, "RESUME.closed.md"))) return;
|
|
320
|
+
const shown = path.isAbsolute(rel) ? rel : rel.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
321
|
+
process.stdout.write(
|
|
322
|
+
`orc: run ${slug} is in flight — read ${shown}/${slug}/state-of-play.md, then the checkpoint (hard rule 2)\n`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let raw = "";
|
|
327
|
+
process.stdin.on("data", (c) => (raw += c));
|
|
328
|
+
process.stdin.on("end", () => {
|
|
329
|
+
try {
|
|
330
|
+
const data = JSON.parse(raw || "{}") || {};
|
|
331
|
+
const ev = String(data.hook_event_name || "");
|
|
332
|
+
if (ev === "Stop") {
|
|
333
|
+
if (!narrationGuard(data)) onStop(data);
|
|
334
|
+
}
|
|
335
|
+
else if (ev === "SessionStart") onCompact(data);
|
|
336
|
+
else if (ev === "SubagentStart") onReviewStart(data);
|
|
337
|
+
} catch (_) {}
|
|
338
|
+
process.exit(0);
|
|
339
|
+
});
|