@azure-id/orc 1.9.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/README-id.md +20 -36
  3. package/README.md +24 -34
  4. package/bin/build-agents.js +206 -109
  5. package/bin/cli.js +760 -62
  6. package/bin/gotcha-import.js +1081 -0
  7. package/bin/gotcha.js +1286 -0
  8. package/bin/graph-query.js +1 -1
  9. package/bin/graph.js +717 -717
  10. package/bin/habit.js +1453 -0
  11. package/bin/mockrun-catalog.js +281 -276
  12. package/bin/onboarding-content.js +4 -4
  13. package/bin/pricing.json +207 -200
  14. package/bin/run-undo.js +398 -0
  15. package/bin/trace-write.js +657 -0
  16. package/bin/verify-contracts.js +527 -81
  17. package/bin/verify-package.js +58 -11
  18. package/bin/webui/api.js +26 -0
  19. package/bin/webui/app.html +239 -232
  20. package/bin/webui/css/00-tokens.css +110 -92
  21. package/bin/webui/css/04-motion.css +249 -152
  22. package/bin/webui/css/06-responsive.css +203 -178
  23. package/bin/webui/css/panels/behaviour.css +205 -0
  24. package/bin/webui/css/panels/knowledge.css +563 -116
  25. package/bin/webui/fixtures/behaviour.js +532 -0
  26. package/bin/webui/fixtures/extra.js +2036 -2036
  27. package/bin/webui/fixtures/hookui.js +5 -5
  28. package/bin/webui/fixtures/index.js +34 -0
  29. package/bin/webui/fixtures/knowledge.js +7 -1
  30. package/bin/webui/fixtures/settings.js +305 -305
  31. package/bin/webui/fixtures/stats.js +16 -0
  32. package/bin/webui/i18n/en/behaviour.json +143 -0
  33. package/bin/webui/i18n/en/knowledge.json +117 -2
  34. package/bin/webui/i18n/en/nav.json +25 -24
  35. package/bin/webui/i18n/en/tour.json +37 -35
  36. package/bin/webui/i18n/id/behaviour.json +143 -0
  37. package/bin/webui/i18n/id/knowledge.json +117 -2
  38. package/bin/webui/i18n/id/nav.json +25 -24
  39. package/bin/webui/i18n/id/tour.json +37 -35
  40. package/bin/webui/js/01-i18n.js +155 -154
  41. package/bin/webui/js/90-tour.js +498 -494
  42. package/bin/webui/js/91-shortcuts.js +126 -126
  43. package/bin/webui/js/99-boot.js +121 -118
  44. package/bin/webui/js/panels/behaviour.js +1022 -0
  45. package/bin/webui/js/panels/knowledge.js +552 -7
  46. package/bin/webui/js/panels/overview.js +13 -4
  47. package/mock-run/INDEX.md +109 -107
  48. package/mock-run/context-combiner.md +100 -100
  49. package/mock-run/gotcha-import.md +118 -0
  50. package/mock-run/habits.md +129 -0
  51. package/mock-run/orc-budget.md +534 -534
  52. package/mock-run/orc-challenge-council.md +262 -262
  53. package/mock-run/orc-quick.md +6 -1
  54. package/mock-run/orc-ultra.md +103 -103
  55. package/package.json +1 -1
  56. package/templates/agents/MODEL-MAPPING.md +49 -49
  57. package/templates/agents/orc-advisor-opus-5-xhigh.md +50 -56
  58. package/templates/agents/orc-analyze-mini-opus-5-med.md +55 -60
  59. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +55 -58
  60. package/templates/agents/orc-challenge-advisor-opus-5-med.md +75 -75
  61. package/templates/agents/orc-challenge-contrarian-opus-5-high.md +110 -110
  62. package/templates/agents/orc-challenge-executor-opus-5-med.md +114 -114
  63. package/templates/agents/orc-challenge-expansionist-opus-5-med.md +112 -112
  64. package/templates/agents/orc-challenge-judge-opus-5-high.md +132 -132
  65. package/templates/agents/orc-challenge-outsider-opus-5-low.md +109 -109
  66. package/templates/agents/orc-challenge-principles-opus-5-high.md +109 -109
  67. package/templates/agents/orc-challenge-reader-opus-5-low.md +90 -90
  68. package/templates/agents/orc-claude-writer-opus-4-8-high.md +48 -53
  69. package/templates/agents/orc-claude-writer-opus-5-med.md +48 -55
  70. package/templates/agents/orc-context-combiner-opus-5-high.md +78 -88
  71. package/templates/agents/orc-doc-checker-opus-5-low.md +108 -108
  72. package/templates/agents/orc-doc-writer-opus-5-med.md +134 -134
  73. package/templates/agents/orc-executor-haiku-4-5.md +14 -6
  74. package/templates/agents/orc-executor-opus-4-7-high.md +14 -6
  75. package/templates/agents/orc-executor-opus-4-7-med.md +14 -6
  76. package/templates/agents/orc-executor-opus-4-8-high.md +14 -6
  77. package/templates/agents/orc-executor-opus-5-high.md +15 -7
  78. package/templates/agents/orc-executor-opus-5-low.md +15 -7
  79. package/templates/agents/orc-executor-opus-5-med.md +15 -7
  80. package/templates/agents/orc-executor-sonnet-4-6-high.md +14 -6
  81. package/templates/agents/orc-executor-sonnet-4-6-med.md +14 -6
  82. package/templates/agents/orc-executor-sonnet-5-high.md +14 -6
  83. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -10
  84. package/templates/agents/orc-judge-opus-5-xhigh.md +81 -85
  85. package/templates/agents/orc-learn-writer-opus-5-low.md +67 -73
  86. package/templates/agents/orc-pattern-codifier-opus-5-med.md +58 -65
  87. package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +58 -63
  88. package/templates/agents/orc-planner-mini-opus-5-med.md +4 -7
  89. package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -3
  90. package/templates/agents/orc-planner-opus-5-med.md +157 -160
  91. package/templates/agents/orc-recon-opus-5-low.md +3 -10
  92. package/templates/agents/orc-recon-sonnet-4-6-med.md +1 -8
  93. package/templates/agents/orc-retro-opus-5-med.md +8 -10
  94. package/templates/agents/orc-retro-sonnet-5-high.md +6 -7
  95. package/templates/agents/orc-reviewer-opus-5-med.md +96 -60
  96. package/templates/agents/orc-scout-opus-5-low.md +35 -40
  97. package/templates/agents/orc-scout-sonnet-4-6-high.md +35 -39
  98. package/templates/agents/orc-system-analyst-opus-5-high.md +115 -120
  99. package/templates/agents/orc-test-author-opus-5-med.md +70 -71
  100. package/templates/agents/orc-test-designer-opus-5-high.md +158 -158
  101. package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -130
  102. package/templates/agents/orc-trace-writer-haiku-4-5.md +3 -7
  103. package/templates/agents/orc-verifier-opus-5-med.md +77 -69
  104. package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +74 -79
  105. package/templates/agents/orc-wiki-scanner-opus-5-med.md +74 -81
  106. package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +97 -106
  107. package/templates/commands/orc-analyze.md +13 -21
  108. package/templates/commands/orc-fast.md +10 -15
  109. package/templates/commands/orc-poly.md +12 -21
  110. package/templates/commands/orc-pr-driver.md +11 -30
  111. package/templates/commands/orc-pr-setup.md +10 -31
  112. package/templates/commands/orc-route.md +11 -41
  113. package/templates/commands/orc-test.md +5 -60
  114. package/templates/commands/orc-ultra.md +17 -17
  115. package/templates/commands/orc-verify.md +11 -11
  116. package/templates/hooks/README.md +36 -2
  117. package/templates/hooks/orc-effort-guard.js +178 -178
  118. package/templates/hooks/orc-session-hook.js +264 -0
  119. package/templates/hooks/orc-statusline.js +9 -7
  120. package/templates/skills/_shared/README.md +9 -0
  121. package/templates/skills/_shared/code-graph.md +48 -56
  122. package/templates/skills/_shared/config-precedence.md +200 -198
  123. package/templates/skills/_shared/extra-dispatch.md +1331 -1346
  124. package/templates/skills/_shared/gotchas.md +228 -177
  125. package/templates/skills/_shared/habits.md +101 -0
  126. package/templates/skills/_shared/lane-contract.md +84 -0
  127. package/templates/skills/_shared/opus5-only.md +8 -8
  128. package/templates/skills/_shared/phases/README.md +142 -83
  129. package/templates/skills/_shared/phases/analyst-gates.md +125 -136
  130. package/templates/skills/_shared/phases/execution.md +8 -14
  131. package/templates/skills/_shared/phases/house-rules.md +27 -32
  132. package/templates/skills/_shared/phases/intake.md +127 -133
  133. package/templates/skills/_shared/phases/mock-example.md +46 -56
  134. package/templates/skills/_shared/phases/plan-handoff.md +91 -97
  135. package/templates/skills/_shared/phases/planning.md +7 -17
  136. package/templates/skills/_shared/phases/preflight.md +19 -42
  137. package/templates/skills/_shared/phases/review.md +23 -27
  138. package/templates/skills/_shared/phases/rules.md +18 -42
  139. package/templates/skills/_shared/phases/scoring.md +55 -65
  140. package/templates/skills/_shared/phases/security-checklist.md +46 -50
  141. package/templates/skills/_shared/phases/security.md +45 -55
  142. package/templates/skills/_shared/phases/ship.md +6 -15
  143. package/templates/skills/_shared/phases/stop-resume.md +2 -5
  144. package/templates/skills/_shared/phases/summary.md +73 -48
  145. package/templates/skills/_shared/phases/testgen.md +41 -51
  146. package/templates/skills/_shared/phases/trace-verbs.md +433 -0
  147. package/templates/skills/_shared/phases/trace.md +136 -367
  148. package/templates/skills/_shared/phases/verify.md +62 -70
  149. package/templates/skills/_shared/phases/wave-grouping.md +128 -133
  150. package/templates/skills/_shared/phases/wiki-consult.md +10 -6
  151. package/templates/skills/_shared/read-ladder.md +2 -55
  152. package/templates/skills/_shared/return-validation.md +17 -70
  153. package/templates/skills/_shared/review-slice.md +79 -0
  154. package/templates/skills/_shared/smoke-gate.md +46 -28
  155. package/templates/skills/context-combiner/SKILL.md +16 -45
  156. package/templates/skills/context-combiner/schemas/combined-report.md +78 -78
  157. package/templates/skills/orc/README.md +2 -2
  158. package/templates/skills/orc/SKILL.md +41 -69
  159. package/templates/skills/orc/config.md +9 -9
  160. package/templates/skills/orc/examples/full-run-mock.md +73 -73
  161. package/templates/skills/orc/references/effort-and-mode.md +222 -222
  162. package/templates/skills/orc/references/pattern-gate.md +89 -89
  163. package/templates/skills/orc/references/phases/intake.md +41 -47
  164. package/templates/skills/orc/references/phases/integration.md +13 -19
  165. package/templates/skills/orc/references/preflight-report.md +10 -11
  166. package/templates/skills/orc/references/ultra-mode.md +11 -9
  167. package/templates/skills/orc/subskills/orc-execution/SKILL.md +27 -73
  168. package/templates/skills/orc/subskills/orc-execution/core.md +12 -99
  169. package/templates/skills/orc/subskills/orc-execution/subagent.md +14 -13
  170. package/templates/skills/orc/subskills/orc-planner/SKILL.md +2 -2
  171. package/templates/skills/orc/subskills/orc-planner-mini/SKILL.md +121 -121
  172. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +35 -76
  173. package/templates/skills/orc/subskills/orc-review-verify/core.md +52 -135
  174. package/templates/skills/orc/subskills/orc-review-verify/subagent.md +7 -7
  175. package/templates/skills/orc/subskills/orc-testgen/SKILL.md +34 -45
  176. package/templates/skills/orc/subskills/orc-testgen/core.md +20 -59
  177. package/templates/skills/orc/subskills/orc-testgen/subagent.md +7 -7
  178. package/templates/skills/orc-advisor/SKILL.md +56 -60
  179. package/templates/skills/orc-analyze/SKILL.md +32 -68
  180. package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -42
  181. package/templates/skills/orc-analyze/schemas/report-audit.md +84 -83
  182. package/templates/skills/orc-analyze/schemas/report-prose.md +64 -63
  183. package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -78
  184. package/templates/skills/orc-analyze-mini/SKILL.md +35 -70
  185. package/templates/skills/orc-challenge/README.md +142 -142
  186. package/templates/skills/orc-challenge/examples/council-full-roster.md +273 -273
  187. package/templates/skills/orc-challenge/references/council.md +315 -315
  188. package/templates/skills/orc-challenge/references/intake.md +171 -171
  189. package/templates/skills/orc-claude/SKILL.md +14 -14
  190. package/templates/skills/orc-diy/README.md +174 -143
  191. package/templates/skills/orc-diy/SKILL.md +23 -74
  192. package/templates/skills/orc-diy/references/blocks/pattern.md +18 -18
  193. package/templates/skills/orc-diy/references/flow-schema.md +1 -1
  194. package/templates/skills/orc-doc/README.md +229 -229
  195. package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +325 -325
  196. package/templates/skills/orc-doc/references/chunking.md +527 -527
  197. package/templates/skills/orc-fast/SKILL.md +45 -73
  198. package/templates/skills/orc-judge/SKILL.md +77 -82
  199. package/templates/skills/orc-learn/SKILL.md +14 -14
  200. package/templates/skills/orc-learn/examples/learn-run-mock.md +61 -61
  201. package/templates/skills/orc-mini/SKILL.md +65 -113
  202. package/templates/skills/orc-pattern/SKILL.md +27 -48
  203. package/templates/skills/orc-poly/SKILL.md +32 -61
  204. package/templates/skills/orc-pr-driver/SKILL.md +25 -51
  205. package/templates/skills/orc-pr-driver/references/green-gate.md +113 -105
  206. package/templates/skills/orc-pr-setup/SKILL.md +20 -45
  207. package/templates/skills/orc-quick/README.md +43 -2
  208. package/templates/skills/orc-quick/SKILL.md +76 -107
  209. package/templates/skills/orc-quick/references/dispatch-gate.md +16 -5
  210. package/templates/skills/orc-quick/references/gh-mode.md +48 -1
  211. package/templates/skills/orc-quick/references/look.md +3 -1
  212. package/templates/skills/orc-retro/SKILL.md +19 -18
  213. package/templates/skills/orc-retro/examples/retro-mock.md +1 -1
  214. package/templates/skills/orc-route/SKILL.md +25 -45
  215. package/templates/skills/orc-test/SKILL.md +16 -37
  216. package/templates/skills/orc-verify/SKILL.md +21 -39
  217. package/templates/skills/orc-verify/examples/verify-mock.md +33 -33
  218. package/templates/skills/orc-wait/SKILL.md +156 -163
  219. package/templates/skills/orc-wiki/references/extra.md +1 -1
  220. package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -6
  221. package/templates/skills/orc-wiki/references/phases/phase-1.md +1 -6
  222. package/templates/skills/orc-wiki/references/phases/phase-2.md +1 -6
  223. package/templates/skills/orc-wiki/references/phases/phase-3.md +1 -6
  224. package/templates/skills/orc-wiki/references/phases/phase-3c.md +1 -6
@@ -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);