@azure-id/orc 1.9.0 → 1.9.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 +202 -0
- package/README-id.md +57 -59
- package/README.md +53 -54
- package/bin/build-agents.js +109 -109
- package/bin/cli.js +262 -58
- package/bin/graph-audit.js +235 -0
- package/bin/graph-extract.js +308 -3
- package/bin/graph-gain.js +23 -3
- package/bin/graph-map.js +9 -1
- package/bin/graph-query.js +152 -10
- package/bin/graph-shard.js +6 -1
- package/bin/graph-signals.js +287 -4
- package/bin/graph.js +717 -658
- package/bin/onboarding-content.js +4 -4
- package/bin/pricing.json +207 -200
- package/bin/verify-contracts.js +117 -2
- package/bin/verify-package.js +11 -7
- package/bin/webui/css/04-motion.css +162 -152
- package/bin/webui/css/panels/knowledge.css +563 -116
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/hookui.js +5 -5
- package/bin/webui/fixtures/knowledge.js +21 -4
- package/bin/webui/fixtures/settings.js +305 -305
- package/bin/webui/i18n/en/knowledge.json +122 -4
- package/bin/webui/i18n/id/knowledge.json +122 -4
- package/bin/webui/js/panels/knowledge.js +573 -9
- package/bin/webui/js/panels/overview.js +13 -4
- package/mock-run/context-combiner.md +100 -100
- package/mock-run/orc-budget.md +534 -534
- package/mock-run/orc-challenge-council.md +262 -262
- package/mock-run/orc-ultra.md +103 -103
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +43 -43
- package/templates/agents/orc-advisor-opus-5-xhigh.md +56 -56
- package/templates/agents/orc-analyze-mini-opus-5-med.md +60 -60
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +58 -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-5-med.md +55 -55
- package/templates/agents/orc-context-combiner-opus-5-high.md +88 -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 +2 -1
- package/templates/agents/orc-executor-opus-4-7-high.md +2 -1
- package/templates/agents/orc-executor-opus-4-7-med.md +2 -1
- package/templates/agents/orc-executor-opus-4-8-high.md +2 -1
- package/templates/agents/orc-executor-opus-5-high.md +4 -3
- package/templates/agents/orc-executor-opus-5-low.md +4 -3
- package/templates/agents/orc-executor-opus-5-med.md +4 -3
- package/templates/agents/orc-executor-sonnet-4-6-high.md +2 -1
- package/templates/agents/orc-executor-sonnet-4-6-med.md +2 -1
- package/templates/agents/orc-executor-sonnet-5-high.md +2 -1
- package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -1
- package/templates/agents/orc-judge-opus-5-xhigh.md +85 -85
- package/templates/agents/orc-learn-writer-opus-5-low.md +73 -73
- package/templates/agents/orc-pattern-codifier-opus-5-med.md +65 -65
- package/templates/agents/orc-planner-mini-opus-5-med.md +4 -4
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +1 -1
- package/templates/agents/orc-planner-opus-5-med.md +160 -160
- package/templates/agents/orc-recon-opus-5-low.md +18 -8
- package/templates/agents/orc-recon-sonnet-4-6-med.md +15 -5
- package/templates/agents/orc-retro-opus-5-med.md +3 -3
- package/templates/agents/orc-reviewer-opus-5-med.md +60 -60
- package/templates/agents/orc-scout-opus-5-low.md +40 -40
- package/templates/agents/orc-system-analyst-opus-5-high.md +120 -120
- package/templates/agents/orc-test-author-opus-5-med.md +71 -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-verifier-opus-5-med.md +69 -69
- package/templates/agents/orc-wiki-scanner-opus-5-med.md +81 -81
- package/templates/commands/orc-ultra.md +17 -17
- package/templates/commands/orc-verify.md +11 -11
- package/templates/hooks/README.md +13 -3
- package/templates/hooks/orc-effort-guard.js +178 -178
- package/templates/hooks/orc-graph-hook.js +53 -7
- package/templates/hooks/orc-statusline.js +6 -6
- package/templates/skills/_shared/code-graph.md +57 -18
- package/templates/skills/_shared/config-precedence.md +198 -198
- package/templates/skills/_shared/extra-dispatch.md +1346 -1346
- package/templates/skills/_shared/opus5-only.md +8 -8
- package/templates/skills/_shared/phases/analyst-gates.md +136 -136
- package/templates/skills/_shared/phases/execution.md +4 -4
- package/templates/skills/_shared/phases/planning.md +3 -3
- package/templates/skills/_shared/phases/preflight.md +1 -1
- package/templates/skills/_shared/phases/review.md +4 -2
- package/templates/skills/_shared/phases/ship.md +1 -1
- package/templates/skills/_shared/phases/trace.md +3 -2
- package/templates/skills/_shared/read-ladder.md +14 -1
- package/templates/skills/_shared/return-validation.md +1 -1
- package/templates/skills/context-combiner/SKILL.md +1 -1
- 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 +8 -8
- 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/preflight-report.md +2 -2
- package/templates/skills/orc/references/ultra-mode.md +3 -3
- 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 +76 -76
- package/templates/skills/orc/subskills/orc-testgen/SKILL.md +45 -45
- package/templates/skills/orc-analyze/SKILL.md +2 -2
- package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -42
- package/templates/skills/orc-analyze/schemas/report-audit.md +83 -83
- package/templates/skills/orc-analyze/schemas/report-prose.md +63 -63
- package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -78
- 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 +143 -143
- 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 +4 -4
- 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 +16 -20
- package/templates/skills/orc-mini/references/complexity.md +25 -11
- package/templates/skills/orc-quick/SKILL.md +12 -13
- package/templates/skills/orc-quick/references/gh-mode.md +1 -1
- package/templates/skills/orc-quick/references/look.md +5 -5
- package/templates/skills/orc-verify/SKILL.md +15 -15
- package/templates/skills/orc-verify/examples/verify-mock.md +33 -33
- package/templates/skills/orc-wiki/references/extra.md +1 -1
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* ORC effort guard — a Claude Code PreToolUse hook.
|
|
6
|
-
*
|
|
7
|
-
* Blocks invocation of the full `orc` orchestrator skill unless the MAIN
|
|
8
|
-
* session is running at HIGH reasoning effort (Opus 5 / Fable 5 clear at
|
|
9
|
-
* medium+ — see the session-model bridge below). This is the one half of the
|
|
10
|
-
* "run ORC on Opus 4.8 high or better" rule hooks can enforce deterministically:
|
|
11
|
-
* Claude Code exposes `effort.level` (and $CLAUDE_EFFORT) to PreToolUse, but
|
|
12
|
-
* NOT the model id (only SessionStart may see the model, and cannot block).
|
|
13
|
-
* The model tier is surfaced separately by the statusline warning + the
|
|
14
|
-
* self-check inside skills/orc/SKILL.md.
|
|
15
|
-
*
|
|
16
|
-
* Wiring (installed by `orc init` into .claude/settings.json):
|
|
17
|
-
* hooks.PreToolUse[] { matcher: "Skill", hooks:[{ type:"command",
|
|
18
|
-
* command: 'node "<.claude>/hooks/orc-effort-guard.js"' }] }
|
|
19
|
-
*
|
|
20
|
-
* Contract: read PreToolUse JSON from stdin. Exit 0 = allow. Exit 2 = block
|
|
21
|
-
* the tool call and show stderr to Claude, which relays it to the user.
|
|
22
|
-
*
|
|
23
|
-
* Also surfaces a "newer orc version available" nudge here — this hook fires
|
|
24
|
-
* exactly when /orc is invoked, so it's the natural place to tell the user
|
|
25
|
-
* without them running `orc version`. On allow it emits a `systemMessage`
|
|
26
|
-
* (shown to the user, NOT added to model context → zero tokens); on block it
|
|
27
|
-
* appends the nudge to the block reason. Version check is cached 24h and
|
|
28
|
-
* fail-silent. (systemMessage handling may vary by Claude Code version — verify
|
|
29
|
-
* with /doctor if you don't see it; the statusline shows it regardless.)
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
// Shared update-check helper (sibling file). Degrade gracefully if absent.
|
|
33
|
-
let updater = null;
|
|
34
|
-
try {
|
|
35
|
-
updater = require("./orc-update-lib.js");
|
|
36
|
-
} catch (_) {
|
|
37
|
-
updater = null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Effort ladder: low < medium < high < xhigh < max. A tier that meets the bar
|
|
41
|
-
// meets it at that rung OR anything stronger — so "requires high" allows xhigh
|
|
42
|
-
// and max too (the bug this fixes: an xhigh/max session was hard-blocked).
|
|
43
|
-
const EFFORT_LADDER = ["low", "medium", "high", "xhigh", "max"];
|
|
44
|
-
const effortsAtOrAbove = (e) => {
|
|
45
|
-
const i = EFFORT_LADDER.indexOf(String(e).toLowerCase());
|
|
46
|
-
return i === -1 ? ["high", "xhigh", "max"] : EFFORT_LADDER.slice(i);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// Session-model bridge (written by orc-statusline.js). The guard can't see the
|
|
50
|
-
// model id itself, so it reads it here to grant the medium-effort allowance of
|
|
51
|
-
// the models that are strictly stronger than the Opus 4.8 baseline (Opus 5 and
|
|
52
|
-
// Fable 5). Fail-OPEN: missing / unreadable / stale → null, and the guard
|
|
53
|
-
// behaves exactly as it would without a bridge (never blocks on our own error).
|
|
54
|
-
// Stale = older than this window; a live session re-renders the statusline far
|
|
55
|
-
// more often, so a fresh file always exists during active use.
|
|
56
|
-
const BRIDGE_MAX_AGE_MS = 30 * 60 * 1000;
|
|
57
|
-
function readSessionModel(projectDir) {
|
|
58
|
-
try {
|
|
59
|
-
const fs = require("fs");
|
|
60
|
-
const path = require("path");
|
|
61
|
-
const p = path.join(projectDir, ".claude", "orc", "session-model.json");
|
|
62
|
-
const j = JSON.parse(fs.readFileSync(p, "utf8").replace(/^/, ""));
|
|
63
|
-
if (!j || typeof j.written_at !== "number") return null;
|
|
64
|
-
if (Date.now() - j.written_at > BRIDGE_MAX_AGE_MS) return null;
|
|
65
|
-
return String(j.model_id || "");
|
|
66
|
-
} catch (_) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// Models that clear /orc at medium effort — strictly stronger than the Opus 4.8
|
|
71
|
-
// baseline, so medium on them beats high on it. Opus 5 (v0.34.0) joins Fable 5.
|
|
72
|
-
// The `\b` after the 5 keeps 4.8 / 4.7 / 5-something-else correctly gated.
|
|
73
|
-
const isMediumOkModel = (id) =>
|
|
74
|
-
/(opus|fable)[\s._-]?5\b/.test(String(id || "").toLowerCase());
|
|
75
|
-
|
|
76
|
-
let raw = "";
|
|
77
|
-
process.stdin.on("data", (c) => (raw += c));
|
|
78
|
-
process.stdin.on("end", () => {
|
|
79
|
-
let data = {};
|
|
80
|
-
try {
|
|
81
|
-
data = JSON.parse(raw || "{}");
|
|
82
|
-
} catch (_) {
|
|
83
|
-
// Unparseable payload — never block on our own error.
|
|
84
|
-
process.exit(0);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const tool = data.tool_name || "";
|
|
88
|
-
const input = data.tool_input || {};
|
|
89
|
-
const skill = String(input.skill || input.name || "");
|
|
90
|
-
|
|
91
|
-
// Only gate the full orchestrator and the compiled DIY lane. orc-mini /
|
|
92
|
-
// orc-fast / orc-analyze / subskills pass — orc-fast is DESIGNED to run at
|
|
93
|
-
// Sonnet medium; never widen this match to include it.
|
|
94
|
-
const isOrc = tool === "Skill" && /^orc$/i.test(skill);
|
|
95
|
-
const isDiy = tool === "Skill" && /^orc-diy$/i.test(skill);
|
|
96
|
-
if (!isOrc && !isDiy) process.exit(0);
|
|
97
|
-
|
|
98
|
-
const effort = String(
|
|
99
|
-
(data.effort && data.effort.level) || process.env.CLAUDE_EFFORT || ""
|
|
100
|
-
).toLowerCase();
|
|
101
|
-
const installed = updater ? updater.installedVersion(__dirname) : null;
|
|
102
|
-
const projectDir = data.cwd || process.cwd();
|
|
103
|
-
|
|
104
|
-
// Baseline /orc: the session must be at high effort OR stronger (xhigh/max).
|
|
105
|
-
// Opus 5 and Fable 5 additionally clear at medium — strictly-capable models —
|
|
106
|
-
// detected through the session-model bridge (the guard can't see the model id
|
|
107
|
-
// itself; fail-open when the bridge is missing/stale, i.e. medium stays blocked).
|
|
108
|
-
let requiredEfforts = effortsAtOrAbove("high");
|
|
109
|
-
let requiredLabel =
|
|
110
|
-
"Opus 4.8 at high effort (Opus 5 / Fable 5 also clear at medium+)";
|
|
111
|
-
if (!isDiy && isMediumOkModel(readSessionModel(projectDir))) {
|
|
112
|
-
requiredEfforts = ["medium", ...requiredEfforts];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// /orc-diy: the required tier is whatever the flow was COMPILED for — read
|
|
116
|
-
// it from flow.lock.json (written only by the `orc diy` CLI). Fail closed:
|
|
117
|
-
// no lock = no compiled flow = deterministic onboarding block. `compile` /
|
|
118
|
-
// `status` invocations pass at any effort (they just shell out to the CLI).
|
|
119
|
-
if (isDiy) {
|
|
120
|
-
if (/^\s*(compile|status)\b/i.test(String(input.args || ""))) process.exit(0);
|
|
121
|
-
const fs = require("fs");
|
|
122
|
-
const path = require("path");
|
|
123
|
-
let lock = null;
|
|
124
|
-
try {
|
|
125
|
-
lock = JSON.parse(
|
|
126
|
-
fs
|
|
127
|
-
.readFileSync(
|
|
128
|
-
path.join(projectDir, ".claude", "orc", "diy", "flow.lock.json"),
|
|
129
|
-
"utf8"
|
|
130
|
-
)
|
|
131
|
-
.replace(/^\uFEFF/, "")
|
|
132
|
-
);
|
|
133
|
-
} catch (_) {}
|
|
134
|
-
if (!lock || !lock.session_tier) {
|
|
135
|
-
process.stderr.write(
|
|
136
|
-
"\n⛔ /orc-diy blocked — no compiled flow in this project (fail-closed gate)." +
|
|
137
|
-
"\n Compose and build your flow in the terminal first:" +
|
|
138
|
-
"\n orc diy init (then shape it: orc diy set <key> <value>)" +
|
|
139
|
-
"\n orc diy compile" +
|
|
140
|
-
"\n Guide: .claude/skills/orc-diy/README.md — or use plain /orc for this request.\n"
|
|
141
|
-
);
|
|
142
|
-
process.exit(2);
|
|
143
|
-
}
|
|
144
|
-
// Effort half is DETERMINISTIC from the compiled slug's suffix (allow that
|
|
145
|
-
// effort or anything stronger); the model half is warn-only on the
|
|
146
|
-
// statusline (hooks can't block on model). Covers the full tier grid —
|
|
147
|
-
// sonnet-4-6 / opus-4-7 / opus-4-8 / fable-5 at med|high|xhigh|max.
|
|
148
|
-
const m = String(lock.session_tier).match(/-(med|high|xhigh|max)$/);
|
|
149
|
-
const slugEffort = m ? (m[1] === "med" ? "medium" : m[1]) : "high";
|
|
150
|
-
requiredEfforts = effortsAtOrAbove(slugEffort);
|
|
151
|
-
requiredLabel = `${lock.session_tier} (compiled session_tier — needs effort ${slugEffort}+)`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const decide = (nudge) => {
|
|
155
|
-
if (requiredEfforts.includes(effort)) {
|
|
156
|
-
// Requirement met — allow. Surface a version nudge if one exists.
|
|
157
|
-
if (nudge) {
|
|
158
|
-
try {
|
|
159
|
-
process.stdout.write(JSON.stringify({ systemMessage: nudge }));
|
|
160
|
-
} catch (_) {}
|
|
161
|
-
}
|
|
162
|
-
process.exit(0);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
process.stderr.write(
|
|
166
|
-
`\n⛔ ${isDiy ? "/orc-diy" : "ORC"} blocked — required effort not met` +
|
|
167
|
-
(effort ? ` (current effort: ${effort}).` : " (effort not detected).") +
|
|
168
|
-
`\n Switch this session to ${requiredLabel}, then re-run.` +
|
|
169
|
-
"\n Run the MAIN session at (or above) the required tier — subagents cannot" +
|
|
170
|
-
"\n exceed the main tier, so pinned agents silently downgrade below it.\n" +
|
|
171
|
-
(nudge ? "\n" + nudge + "\n" : "")
|
|
172
|
-
);
|
|
173
|
-
process.exit(2);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (updater) updater.refreshAndNudge(installed, decide);
|
|
177
|
-
else decide(null);
|
|
178
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ORC effort guard — a Claude Code PreToolUse hook.
|
|
6
|
+
*
|
|
7
|
+
* Blocks invocation of the full `orc` orchestrator skill unless the MAIN
|
|
8
|
+
* session is running at HIGH reasoning effort (Opus 5.5 / Fable 5 clear at
|
|
9
|
+
* medium+ — see the session-model bridge below). This is the one half of the
|
|
10
|
+
* "run ORC on Opus 4.8 high or better" rule hooks can enforce deterministically:
|
|
11
|
+
* Claude Code exposes `effort.level` (and $CLAUDE_EFFORT) to PreToolUse, but
|
|
12
|
+
* NOT the model id (only SessionStart may see the model, and cannot block).
|
|
13
|
+
* The model tier is surfaced separately by the statusline warning + the
|
|
14
|
+
* self-check inside skills/orc/SKILL.md.
|
|
15
|
+
*
|
|
16
|
+
* Wiring (installed by `orc init` into .claude/settings.json):
|
|
17
|
+
* hooks.PreToolUse[] { matcher: "Skill", hooks:[{ type:"command",
|
|
18
|
+
* command: 'node "<.claude>/hooks/orc-effort-guard.js"' }] }
|
|
19
|
+
*
|
|
20
|
+
* Contract: read PreToolUse JSON from stdin. Exit 0 = allow. Exit 2 = block
|
|
21
|
+
* the tool call and show stderr to Claude, which relays it to the user.
|
|
22
|
+
*
|
|
23
|
+
* Also surfaces a "newer orc version available" nudge here — this hook fires
|
|
24
|
+
* exactly when /orc is invoked, so it's the natural place to tell the user
|
|
25
|
+
* without them running `orc version`. On allow it emits a `systemMessage`
|
|
26
|
+
* (shown to the user, NOT added to model context → zero tokens); on block it
|
|
27
|
+
* appends the nudge to the block reason. Version check is cached 24h and
|
|
28
|
+
* fail-silent. (systemMessage handling may vary by Claude Code version — verify
|
|
29
|
+
* with /doctor if you don't see it; the statusline shows it regardless.)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// Shared update-check helper (sibling file). Degrade gracefully if absent.
|
|
33
|
+
let updater = null;
|
|
34
|
+
try {
|
|
35
|
+
updater = require("./orc-update-lib.js");
|
|
36
|
+
} catch (_) {
|
|
37
|
+
updater = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Effort ladder: low < medium < high < xhigh < max. A tier that meets the bar
|
|
41
|
+
// meets it at that rung OR anything stronger — so "requires high" allows xhigh
|
|
42
|
+
// and max too (the bug this fixes: an xhigh/max session was hard-blocked).
|
|
43
|
+
const EFFORT_LADDER = ["low", "medium", "high", "xhigh", "max"];
|
|
44
|
+
const effortsAtOrAbove = (e) => {
|
|
45
|
+
const i = EFFORT_LADDER.indexOf(String(e).toLowerCase());
|
|
46
|
+
return i === -1 ? ["high", "xhigh", "max"] : EFFORT_LADDER.slice(i);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Session-model bridge (written by orc-statusline.js). The guard can't see the
|
|
50
|
+
// model id itself, so it reads it here to grant the medium-effort allowance of
|
|
51
|
+
// the models that are strictly stronger than the Opus 4.8 baseline (Opus 5.5 and
|
|
52
|
+
// Fable 5). Fail-OPEN: missing / unreadable / stale → null, and the guard
|
|
53
|
+
// behaves exactly as it would without a bridge (never blocks on our own error).
|
|
54
|
+
// Stale = older than this window; a live session re-renders the statusline far
|
|
55
|
+
// more often, so a fresh file always exists during active use.
|
|
56
|
+
const BRIDGE_MAX_AGE_MS = 30 * 60 * 1000;
|
|
57
|
+
function readSessionModel(projectDir) {
|
|
58
|
+
try {
|
|
59
|
+
const fs = require("fs");
|
|
60
|
+
const path = require("path");
|
|
61
|
+
const p = path.join(projectDir, ".claude", "orc", "session-model.json");
|
|
62
|
+
const j = JSON.parse(fs.readFileSync(p, "utf8").replace(/^/, ""));
|
|
63
|
+
if (!j || typeof j.written_at !== "number") return null;
|
|
64
|
+
if (Date.now() - j.written_at > BRIDGE_MAX_AGE_MS) return null;
|
|
65
|
+
return String(j.model_id || "");
|
|
66
|
+
} catch (_) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Models that clear /orc at medium effort — strictly stronger than the Opus 4.8
|
|
71
|
+
// baseline, so medium on them beats high on it. Opus 5.5 (v0.34.0) joins Fable 5.
|
|
72
|
+
// The `\b` after the 5 keeps 4.8 / 4.7 / 5-something-else correctly gated.
|
|
73
|
+
const isMediumOkModel = (id) =>
|
|
74
|
+
/(opus|fable)[\s._-]?5\b/.test(String(id || "").toLowerCase());
|
|
75
|
+
|
|
76
|
+
let raw = "";
|
|
77
|
+
process.stdin.on("data", (c) => (raw += c));
|
|
78
|
+
process.stdin.on("end", () => {
|
|
79
|
+
let data = {};
|
|
80
|
+
try {
|
|
81
|
+
data = JSON.parse(raw || "{}");
|
|
82
|
+
} catch (_) {
|
|
83
|
+
// Unparseable payload — never block on our own error.
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const tool = data.tool_name || "";
|
|
88
|
+
const input = data.tool_input || {};
|
|
89
|
+
const skill = String(input.skill || input.name || "");
|
|
90
|
+
|
|
91
|
+
// Only gate the full orchestrator and the compiled DIY lane. orc-mini /
|
|
92
|
+
// orc-fast / orc-analyze / subskills pass — orc-fast is DESIGNED to run at
|
|
93
|
+
// Sonnet medium; never widen this match to include it.
|
|
94
|
+
const isOrc = tool === "Skill" && /^orc$/i.test(skill);
|
|
95
|
+
const isDiy = tool === "Skill" && /^orc-diy$/i.test(skill);
|
|
96
|
+
if (!isOrc && !isDiy) process.exit(0);
|
|
97
|
+
|
|
98
|
+
const effort = String(
|
|
99
|
+
(data.effort && data.effort.level) || process.env.CLAUDE_EFFORT || ""
|
|
100
|
+
).toLowerCase();
|
|
101
|
+
const installed = updater ? updater.installedVersion(__dirname) : null;
|
|
102
|
+
const projectDir = data.cwd || process.cwd();
|
|
103
|
+
|
|
104
|
+
// Baseline /orc: the session must be at high effort OR stronger (xhigh/max).
|
|
105
|
+
// Opus 5.5 and Fable 5 additionally clear at medium — strictly-capable models —
|
|
106
|
+
// detected through the session-model bridge (the guard can't see the model id
|
|
107
|
+
// itself; fail-open when the bridge is missing/stale, i.e. medium stays blocked).
|
|
108
|
+
let requiredEfforts = effortsAtOrAbove("high");
|
|
109
|
+
let requiredLabel =
|
|
110
|
+
"Opus 4.8 at high effort (Opus 5.5 / Fable 5 also clear at medium+)";
|
|
111
|
+
if (!isDiy && isMediumOkModel(readSessionModel(projectDir))) {
|
|
112
|
+
requiredEfforts = ["medium", ...requiredEfforts];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// /orc-diy: the required tier is whatever the flow was COMPILED for — read
|
|
116
|
+
// it from flow.lock.json (written only by the `orc diy` CLI). Fail closed:
|
|
117
|
+
// no lock = no compiled flow = deterministic onboarding block. `compile` /
|
|
118
|
+
// `status` invocations pass at any effort (they just shell out to the CLI).
|
|
119
|
+
if (isDiy) {
|
|
120
|
+
if (/^\s*(compile|status)\b/i.test(String(input.args || ""))) process.exit(0);
|
|
121
|
+
const fs = require("fs");
|
|
122
|
+
const path = require("path");
|
|
123
|
+
let lock = null;
|
|
124
|
+
try {
|
|
125
|
+
lock = JSON.parse(
|
|
126
|
+
fs
|
|
127
|
+
.readFileSync(
|
|
128
|
+
path.join(projectDir, ".claude", "orc", "diy", "flow.lock.json"),
|
|
129
|
+
"utf8"
|
|
130
|
+
)
|
|
131
|
+
.replace(/^\uFEFF/, "")
|
|
132
|
+
);
|
|
133
|
+
} catch (_) {}
|
|
134
|
+
if (!lock || !lock.session_tier) {
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
"\n⛔ /orc-diy blocked — no compiled flow in this project (fail-closed gate)." +
|
|
137
|
+
"\n Compose and build your flow in the terminal first:" +
|
|
138
|
+
"\n orc diy init (then shape it: orc diy set <key> <value>)" +
|
|
139
|
+
"\n orc diy compile" +
|
|
140
|
+
"\n Guide: .claude/skills/orc-diy/README.md — or use plain /orc for this request.\n"
|
|
141
|
+
);
|
|
142
|
+
process.exit(2);
|
|
143
|
+
}
|
|
144
|
+
// Effort half is DETERMINISTIC from the compiled slug's suffix (allow that
|
|
145
|
+
// effort or anything stronger); the model half is warn-only on the
|
|
146
|
+
// statusline (hooks can't block on model). Covers the full tier grid —
|
|
147
|
+
// sonnet-4-6 / opus-4-7 / opus-4-8 / fable-5 at med|high|xhigh|max.
|
|
148
|
+
const m = String(lock.session_tier).match(/-(med|high|xhigh|max)$/);
|
|
149
|
+
const slugEffort = m ? (m[1] === "med" ? "medium" : m[1]) : "high";
|
|
150
|
+
requiredEfforts = effortsAtOrAbove(slugEffort);
|
|
151
|
+
requiredLabel = `${lock.session_tier} (compiled session_tier — needs effort ${slugEffort}+)`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const decide = (nudge) => {
|
|
155
|
+
if (requiredEfforts.includes(effort)) {
|
|
156
|
+
// Requirement met — allow. Surface a version nudge if one exists.
|
|
157
|
+
if (nudge) {
|
|
158
|
+
try {
|
|
159
|
+
process.stdout.write(JSON.stringify({ systemMessage: nudge }));
|
|
160
|
+
} catch (_) {}
|
|
161
|
+
}
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
process.stderr.write(
|
|
166
|
+
`\n⛔ ${isDiy ? "/orc-diy" : "ORC"} blocked — required effort not met` +
|
|
167
|
+
(effort ? ` (current effort: ${effort}).` : " (effort not detected).") +
|
|
168
|
+
`\n Switch this session to ${requiredLabel}, then re-run.` +
|
|
169
|
+
"\n Run the MAIN session at (or above) the required tier — subagents cannot" +
|
|
170
|
+
"\n exceed the main tier, so pinned agents silently downgrade below it.\n" +
|
|
171
|
+
(nudge ? "\n" + nudge + "\n" : "")
|
|
172
|
+
);
|
|
173
|
+
process.exit(2);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (updater) updater.refreshAndNudge(installed, decide);
|
|
177
|
+
else decide(null);
|
|
178
|
+
});
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
* the NEXT read can be a range read. The read still runs. OFF
|
|
25
25
|
* by default: it is armed with `code_graph_hooks: on,read`.
|
|
26
26
|
* PostToolUse (E4c) a Read of a file the extractor did NOT fully see →
|
|
27
|
-
* one line naming the lines it missed.
|
|
27
|
+
* one line naming the lines it missed. (R2, v1.9.1) Under `on`
|
|
28
|
+
* it also COUNTS a whole-file read of a wide file — a ledger
|
|
29
|
+
* row, no context — so the `on,read` default can be decided
|
|
30
|
+
* from data.
|
|
28
31
|
*
|
|
29
32
|
* NO DOUBLE PAYMENT. Every hint dedupes against the run's own counters file,
|
|
30
33
|
* and `orc graph ctx --for-slice` (D2) writes the names it delivered into that
|
|
@@ -213,7 +216,8 @@ function emit(event, text) {
|
|
|
213
216
|
// out, inside the fail-quiet wrapper, so a full disk can slow a Grep and can
|
|
214
217
|
// never block one. A coverage note records PAID ONLY: it adds, it saves
|
|
215
218
|
// nothing (K6).
|
|
216
|
-
function ledger(runName, cmd, body, avoided) {
|
|
219
|
+
function ledger(runName, cmd, body, avoided, row) {
|
|
220
|
+
const extra = row || {};
|
|
217
221
|
try {
|
|
218
222
|
fs.appendFileSync(
|
|
219
223
|
path.join(GRAPH_DIR, "gain.jsonl"),
|
|
@@ -225,7 +229,11 @@ function ledger(runName, cmd, body, avoided) {
|
|
|
225
229
|
targets: [],
|
|
226
230
|
gen: null,
|
|
227
231
|
ms: 0,
|
|
228
|
-
|
|
232
|
+
...extra,
|
|
233
|
+
// M1 (v1.9.1): a row that put NO tokens into anyone's context needs its
|
|
234
|
+
// own `paid`. The default counts the hint's own prefix, which is right
|
|
235
|
+
// for a hint and wrong for a background update that nobody read.
|
|
236
|
+
paid: extra.paid || { card: 0, source: 0, hints: Math.ceil((PREFIX.length + 1 + String(body || "").length) / 4), envelope: 0 },
|
|
229
237
|
avoided: avoided || { low: 0, high: 0, calls_low: 0, calls_high: 0 },
|
|
230
238
|
basis: { files: [], grep_hits: 0 },
|
|
231
239
|
}) + "\n"
|
|
@@ -295,7 +303,7 @@ function run() {
|
|
|
295
303
|
if (tool === "Read") return readHint ? onWideRead(data, runName, meta) : undefined;
|
|
296
304
|
return onSearch(data, runName, meta);
|
|
297
305
|
}
|
|
298
|
-
if (event === "PostToolUse") return onRead(data, runName, meta);
|
|
306
|
+
if (event === "PostToolUse") return onRead(data, runName, meta, readHint);
|
|
299
307
|
}
|
|
300
308
|
|
|
301
309
|
// ── E5b — an executor finished, so the map is behind ───────────────────────
|
|
@@ -326,6 +334,18 @@ function onExecutorStop(data, runName) {
|
|
|
326
334
|
if (r.error && r.error.code === "ETIMEDOUT") return traceLine(runName, "GRAPH-UPDATE deadline", `agent=${clean(agent, 60)}`);
|
|
327
335
|
if (!out || out.state === "off") return;
|
|
328
336
|
bump(runName, "updates");
|
|
337
|
+
// M1: the meter showed `0 hook updates` on a project whose hook had run 35 of
|
|
338
|
+
// them, because the counters file and the ledger are two different files and
|
|
339
|
+
// only one of them was written. This row is what `orc graph gain` counts. It
|
|
340
|
+
// is appended in the same fail-quiet path, AFTER the update is done, and it
|
|
341
|
+
// pays nothing: a background update puts no tokens into any context.
|
|
342
|
+
ledger(runName, "hook-update", "", { low: 0, high: 0, calls_low: 0, calls_high: 0 }, {
|
|
343
|
+
agent: clean(agent, 60),
|
|
344
|
+
targets: [clean(agent, 60)],
|
|
345
|
+
gen: out.generation || null,
|
|
346
|
+
ms: Number(out.ms) || 0,
|
|
347
|
+
paid: { card: 0, source: 0, hints: 0, envelope: 0 },
|
|
348
|
+
});
|
|
329
349
|
const moved = (out.added || 0) + (out.changed || 0) + (out.deleted || 0);
|
|
330
350
|
traceLine(
|
|
331
351
|
runName,
|
|
@@ -430,10 +450,17 @@ function onWideRead(data, runName, meta) {
|
|
|
430
450
|
const rows = Object.prototype.hasOwnProperty.call(wide.files, rel) ? wide.files[rel] : null;
|
|
431
451
|
if (!Array.isArray(rows) || !rows.length) return;
|
|
432
452
|
if (!bump(runName, "injected", "wide:" + rel)) return;
|
|
453
|
+
// R3 (v1.9.1): the store ranks by IMPORTANCE, which is the repository's view.
|
|
454
|
+
// A name this run was already told about — by a search hint or by the slice —
|
|
455
|
+
// is the TASK's view, so it goes first. A stable sort: every other row keeps
|
|
456
|
+
// its importance order, and the line text does not change.
|
|
457
|
+
const told = new Set(((readJson(seenFile(runName)) || {}).tokens || []).filter((t) => typeof t === "string" && t.startsWith("name:")).map((t) => t.slice(5)));
|
|
458
|
+
const mine = (r) => (told.has(String(r[0])) || told.has(String(r[0]).split(".").pop()) ? 0 : 1);
|
|
459
|
+
const ordered = told.size ? rows.map((r, i) => ({ r, i })).sort((a, b) => mine(a.r) - mine(b.r) || a.i - b.i).map((x) => x.r) : rows;
|
|
433
460
|
const body = emit(
|
|
434
461
|
"PreToolUse",
|
|
435
462
|
`${clean(rel, 160)} holds many symbols; the most reached are ` +
|
|
436
|
-
|
|
463
|
+
ordered.slice(0, WIDE_ROWS).map((r) => `${clean(r[0], 80)} ${Number(r[1]) || 0}-${Number(r[2]) || 0}`).join(" · ") +
|
|
437
464
|
`. The read below still runs; a later read of this file can name a range.`
|
|
438
465
|
);
|
|
439
466
|
ledger(runName, "hint", body, { low: 0, high: HINT_RANGE_TOKENS, calls_low: 0, calls_high: 1 });
|
|
@@ -442,9 +469,10 @@ function onWideRead(data, runName, meta) {
|
|
|
442
469
|
// ── E4c — a Read of a file the extractor did not fully see ─────────────────
|
|
443
470
|
// The ONLY case worth a line. A file the graph parsed whole needs no note, and
|
|
444
471
|
// a note on every read would be the noise that gets a hook switched off.
|
|
445
|
-
function onRead(data, runName, meta) {
|
|
472
|
+
function onRead(data, runName, meta, readHint) {
|
|
446
473
|
if (String(data.tool_name || "") !== "Read") return;
|
|
447
|
-
const
|
|
474
|
+
const input = data.tool_input || {};
|
|
475
|
+
const abs = String(input.file_path || "");
|
|
448
476
|
if (!abs) return;
|
|
449
477
|
let rel;
|
|
450
478
|
try {
|
|
@@ -454,6 +482,24 @@ function onRead(data, runName, meta) {
|
|
|
454
482
|
}
|
|
455
483
|
if (!rel || rel.startsWith("..")) return;
|
|
456
484
|
|
|
485
|
+
// R2 (v1.9.1) — COUNT, never say. A whole-file read of a wide file under
|
|
486
|
+
// `on` is a read the `on,read` hint would have named ranges for. The count is
|
|
487
|
+
// what the decision to arm that hint by default waits for (DE-8); nothing
|
|
488
|
+
// flips by itself. Under `on,read` the PreToolUse hint already fired, so the
|
|
489
|
+
// miss is zero and nothing is counted. Once per file per run, no context, no
|
|
490
|
+
// trace line.
|
|
491
|
+
if (!readHint && input.offset === undefined && input.limit === undefined) {
|
|
492
|
+
const wide = readJson(path.join(GRAPH_DIR, "wide.json"));
|
|
493
|
+
if (wide && wide.files && wide.generation === meta.generation && Object.prototype.hasOwnProperty.call(wide.files, rel)) {
|
|
494
|
+
if (bump(runName, "wide_unhinted", "wideu:" + rel))
|
|
495
|
+
ledger(runName, "wide-unhinted", "", { low: 0, high: 0, calls_low: 0, calls_high: 0 }, {
|
|
496
|
+
targets: [clean(rel, 160)],
|
|
497
|
+
gen: meta.generation,
|
|
498
|
+
paid: { card: 0, source: 0, hints: 0, envelope: 0 },
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
457
503
|
const files = readJson(path.join(GRAPH_DIR, "files.json"));
|
|
458
504
|
if (!files || !Object.prototype.hasOwnProperty.call(files, rel)) return;
|
|
459
505
|
const entry = files[rel];
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* ICON carries the verdict and the words carry the installed ORC version — but
|
|
25
25
|
* the degrade branch still names every reason, because a warning with no reason
|
|
26
26
|
* is an emoji:
|
|
27
|
-
* ✅ ORC v1.2.1 - Opus 5/high Opus 4.8 high (the baseline)
|
|
28
|
-
* 🚀 ORC v1.2.1 - Opus 5/high Opus 4.8 xhigh/max, or Opus 5 / Fable 5 medium…max
|
|
27
|
+
* ✅ ORC v1.2.1 - Opus 5.5/high Opus 4.8 high (the baseline)
|
|
28
|
+
* 🚀 ORC v1.2.1 - Opus 5.5/high Opus 4.8 xhigh/max, or Opus 5.5 / Fable 5 medium…max
|
|
29
29
|
* ⛔ ORC v1.2.1 - Sonnet 5/high (why) everything below (wrong model, sub-baseline effort, quota)
|
|
30
30
|
*
|
|
31
31
|
* The two lines, in full (v1.2.1):
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
* This is the ONLY place Claude Code exposes the live model id, so it also
|
|
53
53
|
* writes a fail-silent session-model bridge (.claude/orc/session-model.json)
|
|
54
54
|
* that the PreToolUse effort guard reads — the guard can't see the model id
|
|
55
|
-
* on its own, so the bridge is how the Opus 5 / Fable 5 medium-effort allowance
|
|
55
|
+
* on its own, so the bridge is how the Opus 5.5 / Fable 5 medium-effort allowance
|
|
56
56
|
* reaches it.
|
|
57
57
|
*
|
|
58
58
|
* Also appends a "newer orc version available" hint from the 24h update cache
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
* over that budget — ORC_STATUSLINE_SCAN_MS — and nothing in ORC ever sets it.
|
|
67
67
|
*/
|
|
68
68
|
|
|
69
|
-
// Opus 4.8 / Opus 5 / Fable 5 are matched by tolerant regexes below (accept
|
|
69
|
+
// Opus 4.8 / Opus 5.5 / Fable 5 are matched by tolerant regexes below (accept
|
|
70
70
|
// dated/suffixed ids and the display name), not strict strings. Effort ranks give
|
|
71
71
|
// the acceptance-matrix tiers (0 = unknown, never treated as a positive downgrade).
|
|
72
72
|
const EFFORT_RANK = { low: 1, medium: 2, high: 3, xhigh: 4, max: 5 };
|
|
@@ -526,7 +526,7 @@ process.stdin.on("end", () => {
|
|
|
526
526
|
// ── Session-model bridge (fail-silent) ─────────────────────────────────────
|
|
527
527
|
// The PreToolUse effort guard cannot see the model id; it can only read
|
|
528
528
|
// effort. Persist {model_id, effort, written_at} here so the guard can grant
|
|
529
|
-
// the Opus 5 / Fable 5 medium-effort allowance. The statusline re-renders constantly
|
|
529
|
+
// the Opus 5.5 / Fable 5 medium-effort allowance. The statusline re-renders constantly
|
|
530
530
|
// while a session is active, so written_at stays fresh; the guard treats a
|
|
531
531
|
// stale file (older than its freshness window) as absent and never blocks on
|
|
532
532
|
// it. Any error (no dir, read-only fs) is swallowed — this is a nicety, not a
|
|
@@ -674,7 +674,7 @@ process.stdin.on("end", () => {
|
|
|
674
674
|
const hay = `${model} ${display}`.toLowerCase();
|
|
675
675
|
const isOpus48 = /opus[\s._-]?4[\s._-]?8\b/.test(hay);
|
|
676
676
|
const isFable5 = /fable[\s._-]?5\b/.test(hay);
|
|
677
|
-
// Opus 5 (v0.34.0) — strictly above the baseline, so it boosts from medium up,
|
|
677
|
+
// Opus 5.5 (v0.34.0) — strictly above the baseline, so it boosts from medium up,
|
|
678
678
|
// exactly like Fable 5. The `\b` keeps opus 4.8 / 4.7 out of this branch.
|
|
679
679
|
const isOpus5 = /opus[\s._-]?5\b/.test(hay);
|
|
680
680
|
const modelKnown = model !== "" || (display !== "" && display !== "unknown");
|