@azure-id/orc 1.9.1 → 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.
Files changed (107) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README-id.md +38 -39
  3. package/README.md +36 -35
  4. package/bin/build-agents.js +109 -109
  5. package/bin/cli.js +35 -33
  6. package/bin/onboarding-content.js +4 -4
  7. package/bin/pricing.json +207 -200
  8. package/bin/verify-contracts.js +2 -2
  9. package/bin/verify-package.js +7 -7
  10. package/bin/webui/css/04-motion.css +162 -152
  11. package/bin/webui/css/panels/knowledge.css +563 -116
  12. package/bin/webui/fixtures/extra.js +2036 -2036
  13. package/bin/webui/fixtures/hookui.js +5 -5
  14. package/bin/webui/fixtures/settings.js +305 -305
  15. package/bin/webui/i18n/en/knowledge.json +117 -2
  16. package/bin/webui/i18n/id/knowledge.json +117 -2
  17. package/bin/webui/js/panels/knowledge.js +552 -7
  18. package/bin/webui/js/panels/overview.js +13 -4
  19. package/mock-run/context-combiner.md +100 -100
  20. package/mock-run/orc-budget.md +534 -534
  21. package/mock-run/orc-challenge-council.md +262 -262
  22. package/mock-run/orc-ultra.md +103 -103
  23. package/package.json +1 -1
  24. package/templates/agents/MODEL-MAPPING.md +43 -43
  25. package/templates/agents/orc-advisor-opus-5-xhigh.md +56 -56
  26. package/templates/agents/orc-analyze-mini-opus-5-med.md +60 -60
  27. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +58 -58
  28. package/templates/agents/orc-challenge-advisor-opus-5-med.md +75 -75
  29. package/templates/agents/orc-challenge-contrarian-opus-5-high.md +110 -110
  30. package/templates/agents/orc-challenge-executor-opus-5-med.md +114 -114
  31. package/templates/agents/orc-challenge-expansionist-opus-5-med.md +112 -112
  32. package/templates/agents/orc-challenge-judge-opus-5-high.md +132 -132
  33. package/templates/agents/orc-challenge-outsider-opus-5-low.md +109 -109
  34. package/templates/agents/orc-challenge-principles-opus-5-high.md +109 -109
  35. package/templates/agents/orc-challenge-reader-opus-5-low.md +90 -90
  36. package/templates/agents/orc-claude-writer-opus-5-med.md +55 -55
  37. package/templates/agents/orc-context-combiner-opus-5-high.md +88 -88
  38. package/templates/agents/orc-doc-checker-opus-5-low.md +108 -108
  39. package/templates/agents/orc-doc-writer-opus-5-med.md +134 -134
  40. package/templates/agents/orc-executor-opus-5-high.md +2 -2
  41. package/templates/agents/orc-executor-opus-5-low.md +2 -2
  42. package/templates/agents/orc-executor-opus-5-med.md +2 -2
  43. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -1
  44. package/templates/agents/orc-judge-opus-5-xhigh.md +85 -85
  45. package/templates/agents/orc-learn-writer-opus-5-low.md +73 -73
  46. package/templates/agents/orc-pattern-codifier-opus-5-med.md +65 -65
  47. package/templates/agents/orc-planner-mini-opus-5-med.md +4 -4
  48. package/templates/agents/orc-planner-mini-sonnet-5-high.md +1 -1
  49. package/templates/agents/orc-planner-opus-5-med.md +160 -160
  50. package/templates/agents/orc-recon-opus-5-low.md +3 -3
  51. package/templates/agents/orc-retro-opus-5-med.md +3 -3
  52. package/templates/agents/orc-reviewer-opus-5-med.md +60 -60
  53. package/templates/agents/orc-scout-opus-5-low.md +40 -40
  54. package/templates/agents/orc-system-analyst-opus-5-high.md +120 -120
  55. package/templates/agents/orc-test-author-opus-5-med.md +71 -71
  56. package/templates/agents/orc-test-designer-opus-5-high.md +158 -158
  57. package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -130
  58. package/templates/agents/orc-verifier-opus-5-med.md +69 -69
  59. package/templates/agents/orc-wiki-scanner-opus-5-med.md +81 -81
  60. package/templates/commands/orc-ultra.md +17 -17
  61. package/templates/commands/orc-verify.md +11 -11
  62. package/templates/hooks/README.md +2 -2
  63. package/templates/hooks/orc-effort-guard.js +178 -178
  64. package/templates/hooks/orc-statusline.js +6 -6
  65. package/templates/skills/_shared/code-graph.md +1 -1
  66. package/templates/skills/_shared/config-precedence.md +198 -198
  67. package/templates/skills/_shared/extra-dispatch.md +1346 -1346
  68. package/templates/skills/_shared/opus5-only.md +8 -8
  69. package/templates/skills/_shared/phases/analyst-gates.md +136 -136
  70. package/templates/skills/_shared/phases/review.md +1 -1
  71. package/templates/skills/_shared/phases/trace.md +1 -1
  72. package/templates/skills/_shared/return-validation.md +1 -1
  73. package/templates/skills/context-combiner/SKILL.md +1 -1
  74. package/templates/skills/context-combiner/schemas/combined-report.md +78 -78
  75. package/templates/skills/orc/README.md +2 -2
  76. package/templates/skills/orc/SKILL.md +7 -7
  77. package/templates/skills/orc/config.md +9 -9
  78. package/templates/skills/orc/examples/full-run-mock.md +73 -73
  79. package/templates/skills/orc/references/effort-and-mode.md +222 -222
  80. package/templates/skills/orc/references/preflight-report.md +2 -2
  81. package/templates/skills/orc/references/ultra-mode.md +3 -3
  82. package/templates/skills/orc/subskills/orc-planner/SKILL.md +2 -2
  83. package/templates/skills/orc/subskills/orc-planner-mini/SKILL.md +121 -121
  84. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +76 -76
  85. package/templates/skills/orc/subskills/orc-testgen/SKILL.md +45 -45
  86. package/templates/skills/orc-analyze/SKILL.md +2 -2
  87. package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -42
  88. package/templates/skills/orc-analyze/schemas/report-audit.md +83 -83
  89. package/templates/skills/orc-analyze/schemas/report-prose.md +63 -63
  90. package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -78
  91. package/templates/skills/orc-challenge/README.md +142 -142
  92. package/templates/skills/orc-challenge/examples/council-full-roster.md +273 -273
  93. package/templates/skills/orc-challenge/references/council.md +315 -315
  94. package/templates/skills/orc-challenge/references/intake.md +171 -171
  95. package/templates/skills/orc-claude/SKILL.md +14 -14
  96. package/templates/skills/orc-diy/README.md +143 -143
  97. package/templates/skills/orc-diy/references/flow-schema.md +1 -1
  98. package/templates/skills/orc-doc/README.md +229 -229
  99. package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +325 -325
  100. package/templates/skills/orc-doc/references/chunking.md +527 -527
  101. package/templates/skills/orc-fast/SKILL.md +1 -1
  102. package/templates/skills/orc-learn/SKILL.md +14 -14
  103. package/templates/skills/orc-learn/examples/learn-run-mock.md +61 -61
  104. package/templates/skills/orc-mini/SKILL.md +3 -3
  105. package/templates/skills/orc-verify/SKILL.md +15 -15
  106. package/templates/skills/orc-verify/examples/verify-mock.md +33 -33
  107. package/templates/skills/orc-wiki/references/extra.md +1 -1
@@ -3,7 +3,8 @@
3
3
  What ORC knows about this repo: the wiki, its coverage, the code patterns,
4
4
  the repair memory, and what it knows from next door.
5
5
 
6
- FIVE TABS since v0.49.1, the Crosslink two-tab precedent. This was one
6
+ SIX TABS since v1.9.2 (the code graph got its own), FIVE since v0.49.1 —
7
+ the Crosslink two-tab precedent. This was one
7
8
  scrolling column of six cards, and the release that made the CLI stop
8
9
  discarding what it computes roughly tripled the content — which is not
9
10
  survivable as one scroll.
@@ -89,6 +90,7 @@ async function renderKnowledge(body) {
89
90
  patterns: () => knPatternsTab(d),
90
91
  memory: () => knMemoryTab(d, body),
91
92
  peers: () => knPeersTab(d),
93
+ graph: () => knGraphTab(d, body),
92
94
  };
93
95
  const select = (which) => {
94
96
  KN_TAB = which;
@@ -103,6 +105,8 @@ async function renderKnowledge(body) {
103
105
  ["patterns", t("knowledge.tab.patterns")],
104
106
  ["memory", t("knowledge.tab.memory")],
105
107
  ["peers", t("knowledge.tab.peers")],
108
+ // v1.9.2 — the code graph moved off the Wiki tab onto its own.
109
+ ["graph", t("knowledge.tab.graph")],
106
110
  ]) {
107
111
  const b = el("button", null, label);
108
112
  b.type = "button";
@@ -149,8 +153,10 @@ function knowledgeHeaderStrip(d) {
149
153
  the state word is never replaced by a friendlier synonym. An update is FREE
150
154
  (parser only, no model), so it is a button; an OFF graph shows the one config
151
155
  command that turns it on, because turning a feature on is the user's call. */
152
- function graphCard(g, body, gain) {
153
- const c = card(t("knowledge.graph.title"));
156
+ // `exact` (v1.9.2): on the Code graph tab the state ladder above already carries
157
+ // the chip and the update button, so this card is the exact numbers only.
158
+ function graphCard(g, body, gain, exact) {
159
+ const c = card(exact ? t("knowledge.cg.exact.title") : t("knowledge.graph.title"));
154
160
  if (!g || !g.state) {
155
161
  c.append(empty(t("knowledge.graph.unknown"), t("knowledge.graph.unknownHint")));
156
162
  return c;
@@ -160,6 +166,7 @@ function graphCard(g, body, gain) {
160
166
  c.append(el("pre", "cmd", "orc config set code_graph on"));
161
167
  return c;
162
168
  }
169
+ if (exact) c.append(el("div", "note", t("knowledge.cg.exact.lead")));
163
170
  const head = el("div", "row-actions");
164
171
  head.append(chip(String(g.state).toUpperCase(), g.state === "fresh" ? "ok" : g.state === "drifted" ? "warn" : "idle", g.state === "drifted"));
165
172
  const upd = el("button", "btn btn-sm", "orc graph update");
@@ -170,7 +177,7 @@ function graphCard(g, body, gain) {
170
177
  renderKnowledge(body);
171
178
  });
172
179
  head.append(upd);
173
- c.append(head);
180
+ if (!exact) c.append(head);
174
181
  if (g.state === "none") {
175
182
  c.append(el("div", "note", t("knowledge.graph.none")));
176
183
  return c;
@@ -205,8 +212,10 @@ function graphCard(g, body, gain) {
205
212
  // EW3/EW4 — two things keep the map fresh without a lane step. A panel that
206
213
  // shows a DRIFTED map without saying that is a panel that invites a needless
207
214
  // click.
208
- c.append(el("div", "note", t("knowledge.graph.selfheal")));
209
- c.append(el("div", "note", t("knowledge.graph.locator")));
215
+ if (!exact) {
216
+ c.append(el("div", "note", t("knowledge.graph.selfheal")));
217
+ c.append(el("div", "note", t("knowledge.graph.locator")));
218
+ }
210
219
  // K5 (v1.8.2 W4b) — the gain strip. It renders exactly the CLI's JSON and
211
220
  // invents nothing: the word "estimate" is part of the string, never a
212
221
  // tooltip, and `avoided` is a RANGE because one number for a counterfactual
@@ -283,7 +292,7 @@ function knWikiTab(d, body) {
283
292
  const out = frag();
284
293
  out.append(wikiPlanCard(d.plan, d.debt, body));
285
294
  out.append(wikiOneDocCard(d.refs));
286
- out.append(graphCard(d.graph, body, d.graphGain));
295
+ // v1.9.2 — the code graph card moved to its own tab (knGraphTab).
287
296
  if (d.usage && d.usage.rows) out.append(wikiUsageCard(d.usage, body));
288
297
 
289
298
  const w = d.wiki;
@@ -1147,3 +1156,539 @@ function wikiActions(body, w) {
1147
1156
  wrap.append(sync);
1148
1157
  return wrap;
1149
1158
  }
1159
+
1160
+ /* ── TAB 6 — CODE GRAPH (v1.9.2) ──────────────────────────────────────────────
1161
+ The graph used to be one card on the Wiki tab: a list of exact numbers and no
1162
+ picture of what any of them meant. It has its own tab now, in reading order:
1163
+ WHERE THIS GRAPH STANDS (the state ladder and the one action it needs), WHAT
1164
+ IT IS (the pipeline), WHAT A GRAPH LOOKS LIKE (an example, labelled as one),
1165
+ THE NUMBERS (drawn to scale), WHAT IT COST AND SAVED (two bars on one scale),
1166
+ every exact number, and what each word means.
1167
+
1168
+ THE PICTURES DERIVE NOTHING. Every number is `orc graph status --json` or
1169
+ `orc graph gain --json`, drawn to scale and never re-computed into a new
1170
+ claim. The example graph is static prose, and it says so on its face. */
1171
+ function knGraphTab(d, body) {
1172
+ const out = frag();
1173
+ const g = d.graph;
1174
+ const live = !!(g && (g.state === "fresh" || g.state === "drifted"));
1175
+ out.append(cgStateCard(g, body));
1176
+ out.append(cgFlowCard(g, live));
1177
+ out.append(cgAnatomyCard());
1178
+ if (live) out.append(cgNumbersCard(g));
1179
+ if (g && g.state && g.state !== "off") out.append(cgGainCard(d.graphGain));
1180
+ if (live) out.append(graphCard(g, body, d.graphGain, true));
1181
+ out.append(cgGlossaryCard());
1182
+ return out;
1183
+ }
1184
+
1185
+ // Reduced motion means the final number at once — never a count that moves.
1186
+ const cgStill = () => !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
1187
+
1188
+ // Counts a tile up to the CLI's number, then writes the CLI's own string, so the
1189
+ // resting value is exactly what `--json` sent.
1190
+ function cgCount(node, value, delay) {
1191
+ const final = value === undefined || value === null ? "—" : String(value);
1192
+ const n = Number(value);
1193
+ if (value === undefined || value === null || !Number.isFinite(n) || cgStill()) {
1194
+ node.textContent = final;
1195
+ return;
1196
+ }
1197
+ const dec = Number.isInteger(n) ? 0 : 1;
1198
+ node.textContent = (0).toFixed(dec);
1199
+ const dur = 1100;
1200
+ let start = null;
1201
+ const step = (ts) => {
1202
+ if (start === null) start = ts;
1203
+ const p = Math.min(1, (ts - start) / dur);
1204
+ const eased = 1 - Math.pow(1 - p, 3);
1205
+ node.textContent = p < 1 ? (n * eased).toFixed(dec) : final;
1206
+ if (p < 1) requestAnimationFrame(step);
1207
+ };
1208
+ setTimeout(() => requestAnimationFrame(step), delay || 0);
1209
+ }
1210
+
1211
+ // A bar segment that grows to its share. The width is set through CSSOM, never a
1212
+ // `style` attribute (the panel serves under `style-src 'self'`).
1213
+ function cgSeg(cls, share, label) {
1214
+ const s = el("div", "cg-seg " + cls);
1215
+ s.style.setProperty("--w", Math.max(0, Math.min(100, share * 100)).toFixed(2) + "%");
1216
+ if (label) s.title = label;
1217
+ return s;
1218
+ }
1219
+
1220
+ /* ── 1. the state ladder ─────────────────────────────────────────────────────
1221
+ Four states, in the order a graph lives through them. The word is the CLI's;
1222
+ the sentence under it says what it means, and the connector says what moves
1223
+ the graph to the next one. */
1224
+ function cgStateCard(g, body) {
1225
+ const c = card(t("knowledge.cg.state.title"));
1226
+ if (!g || !g.state) {
1227
+ c.append(empty(t("knowledge.graph.unknown"), t("knowledge.graph.unknownHint")));
1228
+ return c;
1229
+ }
1230
+ c.append(el("div", "note", t("knowledge.cg.state.lead")));
1231
+ const ladder = el("div", "cg-ladder");
1232
+ const steps = [
1233
+ ["OFF", t("knowledge.cg.state.off"), t("knowledge.cg.state.toNone")],
1234
+ ["NONE", t("knowledge.cg.state.none"), t("knowledge.cg.state.toFresh")],
1235
+ ["FRESH", t("knowledge.cg.state.fresh"), t("knowledge.cg.state.toDrifted")],
1236
+ ["DRIFTED", t("knowledge.cg.state.drifted"), null],
1237
+ ];
1238
+ // The CLI's state word, upper-cased the way the header strip shows it.
1239
+ const nowWord = String(g.state).toUpperCase();
1240
+ steps.forEach(([word, what, next], i) => {
1241
+ const here = nowWord === word;
1242
+ const s = el("div", "cg-step cg-step-" + word.toLowerCase() + (here ? " cg-step-now" : ""));
1243
+ s.style.setProperty("--i", String(i));
1244
+ const dot = el("span", "cg-step-dot");
1245
+ if (here) dot.append(el("span", "cg-beacon"));
1246
+ s.append(dot);
1247
+ s.append(el("span", "cg-step-word", word));
1248
+ s.append(el("span", "cg-step-what", what));
1249
+ if (here) s.append(el("span", "cg-step-here", t("knowledge.cg.state.here")));
1250
+ ladder.append(s);
1251
+ if (next) {
1252
+ const link = el("div", "cg-step-link");
1253
+ link.style.setProperty("--i", String(i));
1254
+ link.append(el("span", "cg-step-line"));
1255
+ link.append(el("span", "cg-step-next", next));
1256
+ ladder.append(link);
1257
+ }
1258
+ });
1259
+ // The loop back: a DRIFTED graph becomes FRESH again with one free update.
1260
+ c.append(ladder, el("div", "cg-loop", t("knowledge.cg.state.loop")));
1261
+
1262
+ // What to do now — the ONE action this state needs, or none.
1263
+ const now = el("div", "cg-now cg-now-" + g.state);
1264
+ if (g.state === "off") {
1265
+ now.append(el("div", null, t("knowledge.cg.now.off")));
1266
+ now.append(el("pre", "cmd", "orc config set code_graph on"));
1267
+ } else if (g.state === "none" || g.state === "drifted") {
1268
+ now.append(el("div", null, g.state === "none" ? t("knowledge.cg.now.none") : t("knowledge.cg.now.drifted")));
1269
+ const upd = el("button", "btn btn-sm", "orc graph update");
1270
+ upd.type = "button";
1271
+ upd.addEventListener("click", async () => {
1272
+ upd.disabled = true;
1273
+ const r = await post("/api/graph/update", {});
1274
+ toast(r.command, r.ok ? "ok" : "bad", r.output);
1275
+ renderKnowledge(body);
1276
+ });
1277
+ now.append(upd);
1278
+ } else {
1279
+ now.append(el("div", null, t("knowledge.cg.now.fresh")));
1280
+ }
1281
+ c.append(now);
1282
+ // The line a lane prints into the chat, verbatim. It is the CLI's sentence.
1283
+ if (g.line) {
1284
+ c.append(el("div", "note", t("knowledge.cg.state.lineNote")));
1285
+ c.append(el("pre", "cmd cg-line", g.line));
1286
+ }
1287
+ return c;
1288
+ }
1289
+
1290
+ /* ── 2. the pipeline ─────────────────────────────────────────────────────────
1291
+ Five stations, left to right, with dots running between them. The first
1292
+ three are free (a parser, no model); the card at the end is what a lane
1293
+ pays for in context tokens. */
1294
+ function cgFlowCard(g, live) {
1295
+ const c = card(t("knowledge.cg.flow.title"));
1296
+ c.append(el("div", "note", t("knowledge.cg.flow.lead")));
1297
+ const rail = el("div", "cg-flow");
1298
+ const stations = [
1299
+ ["</>", t("knowledge.cg.flow.code"), t("knowledge.cg.flow.codeWhat"), live ? t("knowledge.cg.flow.files", { n: g.files }) : null, "free"],
1300
+ ["f(x)", t("knowledge.cg.flow.parse"), t("knowledge.cg.flow.parseWhat"), t("knowledge.cg.flow.parseCost"), "free"],
1301
+ ["◉─◉", t("knowledge.cg.flow.store"), t("knowledge.cg.flow.storeWhat"), live ? t("knowledge.cg.flow.symbols", { n: g.symbols }) : null, "free"],
1302
+ ["?", t("knowledge.cg.flow.ask"), t("knowledge.cg.flow.askWhat"), "ctx · impact · map", null],
1303
+ ["▤", t("knowledge.cg.flow.card"), t("knowledge.cg.flow.cardWhat"), t("knowledge.cg.flow.cardCost"), "paid"],
1304
+ ];
1305
+ stations.forEach(([glyph, name, what, value, cost], i) => {
1306
+ if (i) {
1307
+ const link = el("div", "cg-link");
1308
+ link.style.setProperty("--i", String(i));
1309
+ for (let k = 0; k < 3; k++) {
1310
+ const dot = el("span", "cg-link-dot");
1311
+ dot.style.setProperty("--k", String(k));
1312
+ link.append(dot);
1313
+ }
1314
+ rail.append(link);
1315
+ }
1316
+ const s = el("div", "cg-station" + (cost ? " cg-cost-" + cost : ""));
1317
+ s.style.setProperty("--i", String(i));
1318
+ s.append(el("span", "cg-station-num", String(i + 1)));
1319
+ s.append(el("span", "cg-station-glyph", glyph));
1320
+ s.append(el("span", "cg-station-name", name));
1321
+ s.append(el("span", "cg-station-what", what));
1322
+ if (value) s.append(el("span", "cg-station-value", value));
1323
+ rail.append(s);
1324
+ });
1325
+ c.append(rail);
1326
+ // The second layer — the only one that can cost a model.
1327
+ const notes = el("div", "cg-notes-layer");
1328
+ notes.append(el("span", "cg-notes-tag", t("knowledge.cg.flow.notesTag")));
1329
+ notes.append(el("span", null, t("knowledge.cg.flow.notes", { mode: g && g.notes ? g.notes : "—" })));
1330
+ c.append(notes);
1331
+ c.append(el("div", "note", t("knowledge.graph.locator")));
1332
+ return c;
1333
+ }
1334
+
1335
+ /* ── 3. the anatomy — an EXAMPLE graph ───────────────────────────────────────
1336
+ Four small files from a made-up shop, drawn the way the graph sees them.
1337
+ It is never this repo's code, and the card says so in its title. Point at a
1338
+ dot and the caption says what `orc graph ctx` would answer for it. */
1339
+ const CG_SAMPLE = {
1340
+ files: [
1341
+ { name: "routes/orders.js", x: 16, y: 34, w: 212, h: 236 },
1342
+ { name: "middleware/auth.js", x: 262, y: 20, w: 184, h: 110 },
1343
+ { name: "services/orders.js", x: 262, y: 162, w: 184, h: 122 },
1344
+ { name: "db/index.js", x: 480, y: 96, w: 184, h: 116 },
1345
+ ],
1346
+ syms: [
1347
+ { id: "route", label: "GET /orders/:id", x: 122, y: 110 },
1348
+ { id: "list", label: "listOrders", x: 122, y: 206 },
1349
+ { id: "auth", label: "requireAuth", x: 354, y: 78 },
1350
+ { id: "find", label: "findOrder", x: 354, y: 226 },
1351
+ { id: "query", label: "query", x: 572, y: 158 },
1352
+ ],
1353
+ // kind: call (solid, a dot runs along it) · ref (dashed) · import (dotted, file to file)
1354
+ edges: [
1355
+ { from: "route", to: "auth", kind: "ref" },
1356
+ { from: "route", to: "find", kind: "call" },
1357
+ { from: "list", to: "find", kind: "call" },
1358
+ { from: "find", to: "query", kind: "call" },
1359
+ { from: "f1", to: "f2", kind: "import", d: "M228 60 L262 60" },
1360
+ { from: "f1", to: "f3", kind: "import", d: "M228 256 L262 256" },
1361
+ { from: "f3", to: "f4", kind: "import", d: "M446 196 L480 184" },
1362
+ ],
1363
+ };
1364
+
1365
+ // The captions, one per example dot. Keys are written out in full.
1366
+ const CG_CAPTION = {
1367
+ route: () => t("knowledge.cg.anatomy.route"),
1368
+ list: () => t("knowledge.cg.anatomy.list"),
1369
+ auth: () => t("knowledge.cg.anatomy.auth"),
1370
+ find: () => t("knowledge.cg.anatomy.find"),
1371
+ query: () => t("knowledge.cg.anatomy.query"),
1372
+ };
1373
+
1374
+ function cgSvg(tag, attrs) {
1375
+ const node = document.createElementNS("http://www.w3.org/2000/svg", tag);
1376
+ for (const [k, v] of Object.entries(attrs || {})) node.setAttribute(k, String(v));
1377
+ return node;
1378
+ }
1379
+
1380
+ function cgAnatomyCard() {
1381
+ const c = card(t("knowledge.cg.anatomy.title"));
1382
+ c.append(el("div", "note", t("knowledge.cg.anatomy.lead")));
1383
+ const wrap = el("div", "cg-anatomy");
1384
+ const scroll = el("div", "scroll-x cg-anatomy-scroll");
1385
+ const svg = cgSvg("svg", { class: "cg-svg", viewBox: "0 0 680 300", role: "img", "aria-label": t("knowledge.cg.anatomy.title") });
1386
+ const defs = cgSvg("defs");
1387
+ for (const [id, cls] of [["cg-arrow-call", "cg-head-call"], ["cg-arrow-ref", "cg-head-ref"]]) {
1388
+ const m = cgSvg("marker", { id, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "7", markerHeight: "7", orient: "auto-start-reverse" });
1389
+ m.append(cgSvg("path", { d: "M0 0 L10 5 L0 10 z", class: cls }));
1390
+ defs.append(m);
1391
+ }
1392
+ svg.append(defs);
1393
+
1394
+ CG_SAMPLE.files.forEach((f, i) => {
1395
+ const gF = cgSvg("g", { class: "cg-file" });
1396
+ gF.style.setProperty("--d", i * 90 + "ms");
1397
+ gF.append(cgSvg("rect", { x: f.x, y: f.y, width: f.w, height: f.h, rx: 10 }));
1398
+ const label = cgSvg("text", { x: f.x + 12, y: f.y + 20, class: "cg-file-name" });
1399
+ label.textContent = f.name;
1400
+ gF.append(label);
1401
+ svg.append(gF);
1402
+ });
1403
+ const pos = {};
1404
+ for (const s of CG_SAMPLE.syms) pos[s.id] = s;
1405
+
1406
+ // Edges first, so the dots sit on top of them.
1407
+ const edgeEls = [];
1408
+ CG_SAMPLE.edges.forEach((e, i) => {
1409
+ let d = e.d;
1410
+ if (!d) {
1411
+ const a = pos[e.from];
1412
+ const b = pos[e.to];
1413
+ const mx = (a.x + b.x) / 2;
1414
+ d = `M${a.x + 10} ${a.y} C${mx} ${a.y} ${mx} ${b.y} ${b.x - 11} ${b.y}`;
1415
+ }
1416
+ const gE = cgSvg("g", { class: "cg-edge cg-edge-" + e.kind });
1417
+ gE.dataset.from = e.from;
1418
+ gE.dataset.to = e.to;
1419
+ gE.style.setProperty("--d", 380 + i * 110 + "ms");
1420
+ const attrs = { d, pathLength: 1, class: "cg-edge-line" };
1421
+ if (e.kind === "call") attrs["marker-end"] = "url(#cg-arrow-call)";
1422
+ if (e.kind === "ref") attrs["marker-end"] = "url(#cg-arrow-ref)";
1423
+ gE.append(cgSvg("path", attrs));
1424
+ if (e.kind === "call") {
1425
+ const p = cgSvg("path", { d, pathLength: 1, class: "cg-pulse" });
1426
+ p.style.setProperty("--d", 1200 + i * 450 + "ms");
1427
+ gE.append(p);
1428
+ }
1429
+ svg.append(gE);
1430
+ edgeEls.push(gE);
1431
+ });
1432
+
1433
+ const caption = el("div", "cg-caption");
1434
+ const setCaption = (id) => {
1435
+ caption.replaceChildren();
1436
+ if (!id) {
1437
+ caption.append(el("span", "cg-caption-hint", t("knowledge.cg.anatomy.hint")));
1438
+ return;
1439
+ }
1440
+ const s = pos[id];
1441
+ caption.append(el("span", "cg-caption-sym mono", s.label));
1442
+ caption.append(el("span", "cg-caption-text", CG_CAPTION[id]()));
1443
+ caption.append(el("span", "cg-caption-cmd mono", "orc graph ctx " + (s.label.includes(" ") ? `"${s.label}"` : s.label)));
1444
+ };
1445
+ const focus = (id) => {
1446
+ wrap.classList.toggle("cg-focus", !!id);
1447
+ for (const gE of edgeEls) gE.classList.toggle("cg-edge-hi", !!id && (gE.dataset.from === id || gE.dataset.to === id));
1448
+ for (const n of svg.querySelectorAll(".cg-sym")) n.classList.toggle("cg-sym-hi", n.dataset.id === id);
1449
+ setCaption(id);
1450
+ };
1451
+
1452
+ CG_SAMPLE.syms.forEach((s, i) => {
1453
+ const gS = cgSvg("g", { class: "cg-sym", tabindex: 0, role: "button", "aria-label": s.label });
1454
+ gS.dataset.id = s.id;
1455
+ gS.style.setProperty("--d", 200 + i * 80 + "ms");
1456
+ gS.append(cgSvg("circle", { cx: s.x, cy: s.y, r: 16, class: "cg-sym-halo" }));
1457
+ gS.append(cgSvg("circle", { cx: s.x, cy: s.y, r: 8, class: "cg-sym-dot" }));
1458
+ const tx = cgSvg("text", { x: s.x, y: s.y - 20, class: "cg-sym-name", "text-anchor": "middle" });
1459
+ tx.textContent = s.label;
1460
+ gS.append(tx);
1461
+ gS.addEventListener("mouseenter", () => focus(s.id));
1462
+ gS.addEventListener("focus", () => focus(s.id));
1463
+ gS.addEventListener("mouseleave", () => focus(null));
1464
+ gS.addEventListener("blur", () => focus(null));
1465
+ svg.append(gS);
1466
+ });
1467
+
1468
+ scroll.append(svg);
1469
+ wrap.append(scroll);
1470
+ setCaption(null);
1471
+ wrap.append(caption);
1472
+
1473
+ // The legend: every mark on the picture, and what it means.
1474
+ const legend = el("div", "cg-legend");
1475
+ for (const [cls, name, what] of [
1476
+ ["cg-lg-file", t("knowledge.cg.legend.file"), t("knowledge.cg.legend.fileWhat")],
1477
+ ["cg-lg-sym", t("knowledge.cg.legend.symbol"), t("knowledge.cg.legend.symbolWhat")],
1478
+ ["cg-lg-call", t("knowledge.cg.legend.call"), t("knowledge.cg.legend.callWhat")],
1479
+ ["cg-lg-ref", t("knowledge.cg.legend.ref"), t("knowledge.cg.legend.refWhat")],
1480
+ ["cg-lg-import", t("knowledge.cg.legend.import"), t("knowledge.cg.legend.importWhat")],
1481
+ ]) {
1482
+ const row = el("div", "cg-legend-row");
1483
+ row.append(el("span", "cg-lg " + cls));
1484
+ const txt = el("div", "cg-legend-text");
1485
+ txt.append(el("strong", null, name));
1486
+ txt.append(el("span", "note", what));
1487
+ row.append(txt);
1488
+ legend.append(row);
1489
+ }
1490
+ wrap.append(legend);
1491
+ c.append(wrap);
1492
+ return c;
1493
+ }
1494
+
1495
+ /* ── 4. the numbers, drawn ───────────────────────────────────────────────────
1496
+ Tiles that count up to the CLI's own number, the three change counts, and
1497
+ the density as a bar — per language when the CLI sent the split. */
1498
+ function cgNumbersCard(g) {
1499
+ const c = card(t("knowledge.cg.num.title"));
1500
+ const tiles = el("div", "cg-tiles");
1501
+ const tile = (label, value, what, i) => {
1502
+ const box = el("div", "cg-tile");
1503
+ box.style.setProperty("--i", String(i));
1504
+ const v = el("span", "cg-tile-value");
1505
+ cgCount(v, value, i * 120);
1506
+ box.append(v, el("span", "cg-tile-label", label), el("span", "cg-tile-what", what));
1507
+ tiles.append(box);
1508
+ };
1509
+ tile(t("knowledge.graph.files"), g.files, t("knowledge.cg.num.filesWhat"), 0);
1510
+ tile(t("knowledge.graph.symbols"), g.symbols, t("knowledge.cg.num.symbolsWhat"), 1);
1511
+ tile(t("knowledge.cg.num.perFile"), g.density ? g.density.symbols_per_file : undefined, t("knowledge.cg.num.perFileWhat"), 2);
1512
+ tile(t("knowledge.graph.generation"), g.generation, t("knowledge.cg.num.generationWhat"), 3);
1513
+ c.append(tiles);
1514
+
1515
+ // What changed since the map was last built. Three counts, the CLI's.
1516
+ if (g.behind) {
1517
+ const b = g.behind;
1518
+ const row = el("div", "cg-behind");
1519
+ row.append(el("span", "cg-behind-label", t("knowledge.cg.num.behind")));
1520
+ for (const [cls, sign, n, label] of [
1521
+ ["cg-b-add", "+", b.added || 0, t("knowledge.cg.num.added")],
1522
+ ["cg-b-chg", "~", b.changed || 0, t("knowledge.cg.num.changed")],
1523
+ ["cg-b-del", "-", b.deleted || 0, t("knowledge.cg.num.deleted")],
1524
+ ]) {
1525
+ const chipEl = el("span", "cg-bchip " + cls + (n ? "" : " cg-b-zero"));
1526
+ chipEl.append(el("strong", null, sign + n), el("span", null, label));
1527
+ row.append(chipEl);
1528
+ }
1529
+ c.append(row);
1530
+ }
1531
+
1532
+ // The density. `zero_share` is the CLI's; the bar only draws it.
1533
+ if (g.density) {
1534
+ const den = g.density;
1535
+ const pctEmpty = Math.round(den.zero_share * 100);
1536
+ c.append(el("div", "cg-sub", t("knowledge.cg.num.densityTitle")));
1537
+ const bar = el("div", "cg-bar");
1538
+ bar.append(cgSeg("cg-seg-ok", 1 - den.zero_share), cgSeg("cg-seg-empty", den.zero_share));
1539
+ c.append(bar);
1540
+ const key = el("div", "cg-bar-key");
1541
+ key.append(el("span", "cg-key cg-key-ok", t("knowledge.cg.num.hasSymbols", { pct: 100 - pctEmpty })));
1542
+ key.append(el("span", "cg-key cg-key-empty", t("knowledge.cg.num.empty", { pct: pctEmpty, n: den.zero_files })));
1543
+ c.append(key);
1544
+ if (den.by_lang && Object.keys(den.by_lang).length) {
1545
+ const langs = el("div", "cg-langs");
1546
+ Object.entries(den.by_lang).forEach(([lang, v], i) => {
1547
+ const r = el("div", "cg-lang");
1548
+ r.style.setProperty("--i", String(i));
1549
+ r.append(el("span", "cg-lang-name mono", lang));
1550
+ const lb = el("div", "cg-bar cg-bar-sm");
1551
+ const share = v.files ? v.zero / v.files : 0;
1552
+ lb.append(cgSeg("cg-seg-ok", 1 - share), cgSeg("cg-seg-empty", share));
1553
+ r.append(lb);
1554
+ r.append(el("span", "cg-lang-nums", t("knowledge.cg.num.lang", { files: v.files, symbols: v.symbols, zero: v.zero })));
1555
+ langs.append(r);
1556
+ });
1557
+ c.append(langs);
1558
+ }
1559
+ c.append(el("div", "note", t("knowledge.cg.num.densityNote")));
1560
+ if (g.thin) c.append(el("div", "note warn", t("knowledge.graph.thin")));
1561
+ }
1562
+ return c;
1563
+ }
1564
+
1565
+ /* ── 5. what it cost, and what it probably saved ─────────────────────────────
1566
+ Two bars on ONE scale. `paid` is exact and solid; `avoided` is an ESTIMATE
1567
+ and a RANGE, so it is drawn solid to its low end and striped to its high
1568
+ end — a single-number bar for a counterfactual is the claim this meter
1569
+ refuses to make. */
1570
+ const CG_READS = {
1571
+ ctx: () => t("knowledge.cg.read.ctx"),
1572
+ "for-slice": () => t("knowledge.cg.read.forSlice"),
1573
+ impact: () => t("knowledge.cg.read.impact"),
1574
+ map: () => t("knowledge.cg.read.map"),
1575
+ changes: () => t("knowledge.cg.read.changes"),
1576
+ cochange: () => t("knowledge.cg.read.cochange"),
1577
+ coverage: () => t("knowledge.cg.read.coverage"),
1578
+ path: () => t("knowledge.cg.read.path"),
1579
+ };
1580
+
1581
+ function cgGainCard(gain) {
1582
+ const c = card(t("knowledge.cg.gain.title"));
1583
+ if (!gain || gain.state !== "rows") {
1584
+ c.append(empty(t("knowledge.gain.none"), t("knowledge.cg.gain.noneHint")));
1585
+ return c;
1586
+ }
1587
+ c.append(el("div", "note", t("knowledge.cg.gain.lead")));
1588
+ const p = gain.paid || {};
1589
+ const a = gain.avoided || {};
1590
+ const scale = Math.max(1, p.total || 0, a.high || 0);
1591
+
1592
+ const rows = el("div", "cg-gain");
1593
+ const paidRow = el("div", "cg-gain-row");
1594
+ paidRow.append(el("span", "cg-gain-name", t("knowledge.cg.gain.paid")));
1595
+ const paidBar = el("div", "cg-bar cg-bar-lg");
1596
+ for (const [cls, n, label] of [
1597
+ ["cg-seg-card", p.card || 0, t("knowledge.cg.gain.card")],
1598
+ ["cg-seg-source", p.source || 0, t("knowledge.cg.gain.source")],
1599
+ ["cg-seg-hints", p.hints || 0, t("knowledge.cg.gain.hintsPart")],
1600
+ ["cg-seg-env", p.envelope || 0, t("knowledge.cg.gain.envelope")],
1601
+ ])
1602
+ paidBar.append(cgSeg(cls, n / scale, `${label} ${kTokUi(n)}`));
1603
+ paidRow.append(paidBar, el("span", "cg-gain-val", kTokUi(p.total || 0)));
1604
+ rows.append(paidRow);
1605
+ const avRow = el("div", "cg-gain-row");
1606
+ avRow.append(el("span", "cg-gain-name", t("knowledge.cg.gain.avoided")));
1607
+ const avBar = el("div", "cg-bar cg-bar-lg");
1608
+ avBar.append(cgSeg("cg-seg-low", (a.low || 0) / scale), cgSeg("cg-seg-range", ((a.high || 0) - (a.low || 0)) / scale));
1609
+ avRow.append(avBar, el("span", "cg-gain-val", `~${kTokUi(a.low || 0)} – ${kTokUi(a.high || 0)}`));
1610
+ rows.append(avRow);
1611
+ c.append(rows);
1612
+
1613
+ const key = el("div", "cg-bar-key");
1614
+ for (const [cls, label, n] of [
1615
+ ["cg-key-card", t("knowledge.cg.gain.card"), p.card],
1616
+ ["cg-key-source", t("knowledge.cg.gain.source"), p.source],
1617
+ ["cg-key-hints", t("knowledge.cg.gain.hintsPart"), p.hints],
1618
+ ["cg-key-env", t("knowledge.cg.gain.envelope"), p.envelope],
1619
+ ["cg-key-range", t("knowledge.cg.gain.range"), null],
1620
+ ])
1621
+ key.append(el("span", "cg-key " + cls, n === null ? label : `${label} ${kTokUi(n || 0)}`));
1622
+ c.append(key);
1623
+ c.append(el("div", "note", t("knowledge.gain.estimate")));
1624
+
1625
+ // Which reads were asked, in the CLI's own order. A read never asked keeps its
1626
+ // slot, muted — "never asked" and "not a read" are different facts.
1627
+ const names = gain.read_set && gain.read_set.length ? gain.read_set : Object.keys(gain.by_command || {});
1628
+ if (names.length) {
1629
+ c.append(el("div", "cg-sub", t("knowledge.cg.gain.readsTitle", { n: gain.calls_recorded, runs: gain.runs === undefined ? "—" : gain.runs })));
1630
+ const counts = names.map((k) => (gain.by_command && gain.by_command[k]) || 0);
1631
+ const most = Math.max(1, ...counts);
1632
+ const list = el("div", "cg-reads");
1633
+ names.forEach((k, i) => {
1634
+ const n = counts[i];
1635
+ const r = el("div", "cg-read" + (n ? "" : " cg-read-never"));
1636
+ r.style.setProperty("--i", String(i));
1637
+ r.append(el("span", "cg-read-cmd mono", k));
1638
+ const lb = el("div", "cg-bar cg-bar-sm");
1639
+ lb.append(cgSeg("cg-seg-read", n / most));
1640
+ r.append(lb);
1641
+ r.append(el("span", "cg-read-n", n ? String(n) : t("knowledge.cg.gain.never")));
1642
+ r.append(el("span", "cg-read-what", CG_READS[k] ? CG_READS[k]() : ""));
1643
+ list.append(r);
1644
+ });
1645
+ c.append(list);
1646
+ }
1647
+ return c;
1648
+ }
1649
+
1650
+ /* ── 6. what each word means ─────────────────────────────────────────────────
1651
+ Every word this tab and the exact card use, in four groups. */
1652
+ function cgGlossaryCard() {
1653
+ const c = card(t("knowledge.cg.gloss.title"));
1654
+ c.append(el("div", "note", t("knowledge.cg.gloss.lead")));
1655
+ const groups = [
1656
+ [t("knowledge.cg.gloss.gStructure"), [
1657
+ [t("knowledge.cg.legend.file"), t("knowledge.cg.gloss.file")],
1658
+ [t("knowledge.cg.legend.symbol"), t("knowledge.cg.gloss.symbol")],
1659
+ [t("knowledge.cg.legend.call"), t("knowledge.cg.gloss.call")],
1660
+ [t("knowledge.graph.generation"), t("knowledge.cg.gloss.generation")],
1661
+ ]],
1662
+ [t("knowledge.cg.gloss.gFresh"), [
1663
+ ["FRESH · DRIFTED", t("knowledge.cg.gloss.freshDrifted")],
1664
+ ["NONE · OFF", t("knowledge.cg.gloss.noneOff")],
1665
+ [t("knowledge.cg.num.behind"), t("knowledge.cg.gloss.behind")],
1666
+ [t("knowledge.cg.gloss.selfHealTerm"), t("knowledge.graph.selfheal")],
1667
+ ]],
1668
+ [t("knowledge.cg.gloss.gQuality"), [
1669
+ [t("knowledge.cg.num.perFile"), t("knowledge.cg.gloss.density")],
1670
+ ["THIN", t("knowledge.cg.gloss.thin")],
1671
+ [t("knowledge.graph.notes"), t("knowledge.cg.gloss.notes")],
1672
+ ]],
1673
+ [t("knowledge.cg.gloss.gCost"), [
1674
+ [t("knowledge.cg.gain.paid"), t("knowledge.cg.gloss.paid")],
1675
+ [t("knowledge.cg.gain.envelope"), t("knowledge.cg.gloss.envelope")],
1676
+ [t("knowledge.cg.gain.avoided"), t("knowledge.cg.gloss.avoided")],
1677
+ [t("knowledge.cg.gloss.netTerm"), t("knowledge.cg.gloss.net")],
1678
+ [t("knowledge.cg.gloss.hintsTerm"), t("knowledge.cg.gloss.hints")],
1679
+ [t("knowledge.gain.measure"), t("knowledge.cg.gloss.measured")],
1680
+ ]],
1681
+ ];
1682
+ const grid = el("div", "cg-gloss");
1683
+ groups.forEach(([title, terms], gi) => {
1684
+ const box = el("div", "cg-gloss-group");
1685
+ box.style.setProperty("--i", String(gi));
1686
+ box.append(el("div", "cg-gloss-head", title));
1687
+ const dl = el("dl", "cg-gloss-list");
1688
+ for (const [term, meaning] of terms) dl.append(el("dt", null, term), el("dd", null, meaning));
1689
+ box.append(dl);
1690
+ grid.append(box);
1691
+ });
1692
+ c.append(grid);
1693
+ return c;
1694
+ }
@@ -66,8 +66,9 @@ const FINDING_ROUTE = {
66
66
  "lane-keys-drifted": { panel: "maintenance", cta: "overview.item.laneKeysDrifted.cta" },
67
67
  // v1.8.0. A DRIFTED code graph is cleared by the free `orc graph update`, which
68
68
  // is a button on the Knowledge panel's code graph card — the panel that can
69
- // CLEAR it, not the default.
70
- "graph-drifted": { panel: "knowledge", cta: "overview.item.graphDrifted.cta" },
69
+ // CLEAR it, not the default. Since v1.9.2 it opens the Code graph TAB, where
70
+ // the state ladder carries that button.
71
+ "graph-drifted": { panel: "knowledge", tab: "graph", cta: "overview.item.graphDrifted.cta" },
71
72
  // v1.3.0. The rule again, and cleanly: every one of these is cleared on the
72
73
  // CLI Hook Interface panel — the board is where an orphaned component is
73
74
  // removed, the caution strip is where an invalid layout is named, and the
@@ -97,6 +98,13 @@ const FINDING_ROUTE = {
97
98
  const DEFAULT_FINDING_ROUTE = { panel: "maintenance", cta: "overview.item.doctor.cta" };
98
99
  const findingRoute = (id) => FINDING_ROUTE[id] || DEFAULT_FINDING_ROUTE;
99
100
 
101
+ // Go to a panel, and — when the route names one — to its tab. Only Knowledge
102
+ // has a tab a finding can name (KN_TAB, which survives a re-render).
103
+ function goRoute(panel, tab) {
104
+ if (panel === "knowledge" && tab && typeof KN_TAB !== "undefined") KN_TAB = tab;
105
+ location.hash = "#/" + panel;
106
+ }
107
+
100
108
  PANELS.overview = function (host) {
101
109
  head(host, t("overview.title"), t("overview.sub"));
102
110
  section(
@@ -317,7 +325,7 @@ PANELS.overview = function (host) {
317
325
  if (r.panel) {
318
326
  const go = el("button", "btn btn-ghost btn-sm", t(r.cta));
319
327
  go.type = "button";
320
- go.addEventListener("click", () => (location.hash = "#/" + r.panel));
328
+ go.addEventListener("click", () => goRoute(r.panel, r.tab));
321
329
  detail.append(go);
322
330
  } else {
323
331
  detail.append(el("div", "note", t("overview.attention.nothingToDo")));
@@ -387,6 +395,7 @@ function attentionCard(d, findings) {
387
395
  // as the title beats paraphrasing it into something less exact.
388
396
  title: f.message,
389
397
  panel: r.panel,
398
+ tab: r.tab,
390
399
  cta: t(r.cta),
391
400
  });
392
401
  }
@@ -458,7 +467,7 @@ function attentionCard(d, findings) {
458
467
  // The CLI's own words for WHY, kept verbatim under our explanation.
459
468
  if (it.evidence) mid.append(el("div", "todo-evidence", it.evidence));
460
469
  row.append(mid, el("span", "todo-cta", it.cta));
461
- row.addEventListener("click", () => (location.hash = "#/" + it.panel));
470
+ row.addEventListener("click", () => goRoute(it.panel, it.tab));
462
471
  list.append(row);
463
472
  }
464
473
  if (items.length) c.append(list);