projectkeeper 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +283 -2
  4. package/README.zh-CN.md +285 -0
  5. package/THIRD_PARTY_NOTICES.md +53 -0
  6. package/bin/pk.mjs +6 -0
  7. package/bin/postinstall.mjs +5 -0
  8. package/bin/strip-types.mjs +28 -0
  9. package/package.json +68 -4
  10. package/patches/README.md +111 -0
  11. package/patches/pi-ai-0.85.0.ts +122 -0
  12. package/patches/pi-ai-0.87.1.ts +122 -0
  13. package/scripts/apply-pi-streaming-patch.ts +86 -0
  14. package/skills/clerk/coverage/SKILL.md +35 -0
  15. package/skills/clerk/cross-check/SKILL.md +118 -0
  16. package/skills/clerk/deepen-dispatch/SKILL.md +69 -0
  17. package/skills/clerk/lane-follow-up/SKILL.md +94 -0
  18. package/skills/clerk/lane-plan/SKILL.md +107 -0
  19. package/skills/clerk/lane-slot/SKILL.md +113 -0
  20. package/skills/clerk/lane-topic/SKILL.md +116 -0
  21. package/skills/clerk/orientation/SKILL.md +160 -0
  22. package/skills/clerk/reconcile/SKILL.md +77 -0
  23. package/skills/clerk/skeleton-dispatch/SKILL.md +43 -0
  24. package/skills/clerk/spot-check/SKILL.md +52 -0
  25. package/skills/clerk/synthesis/SKILL.md +65 -0
  26. package/skills/table-follow-up/SKILL.md +42 -0
  27. package/src/cli.ts +319 -0
  28. package/src/codemap/README.md +13 -0
  29. package/src/codemap/engines.ts +102 -0
  30. package/src/codemap/facts.ts +504 -0
  31. package/src/context/agent-entry.ts +232 -0
  32. package/src/context/ask.ts +97 -0
  33. package/src/context/assemble.ts +1058 -0
  34. package/src/context/code-source.ts +103 -0
  35. package/src/context/k-briefs.ts +114 -0
  36. package/src/context/lineage.ts +100 -0
  37. package/src/context/object-brief.ts +300 -0
  38. package/src/context/owner-words.ts +191 -0
  39. package/src/context/pack-fixture.ts +342 -0
  40. package/src/context/project-rules.ts +160 -0
  41. package/src/intake/intake.ts +392 -0
  42. package/src/intake/material-rules.ts +166 -0
  43. package/src/intake/removal.ts +316 -0
  44. package/src/intake/skipped.ts +72 -0
  45. package/src/keeper/adjustment.ts +565 -0
  46. package/src/keeper/bounds/boundary.ts +509 -0
  47. package/src/keeper/bounds/command.ts +1665 -0
  48. package/src/keeper/bounds/paths.ts +164 -0
  49. package/src/keeper/bounds/reads.ts +718 -0
  50. package/src/keeper/bounds/shell-write-guard.ts +521 -0
  51. package/src/keeper/capabilities.ts +136 -0
  52. package/src/keeper/clerk-steps.ts +151 -0
  53. package/src/keeper/clerk-tools.ts +1424 -0
  54. package/src/keeper/conversation.ts +253 -0
  55. package/src/keeper/evidence.ts +662 -0
  56. package/src/keeper/fake-provider.ts +132 -0
  57. package/src/keeper/held.ts +15 -0
  58. package/src/keeper/history-tools.ts +163 -0
  59. package/src/keeper/jobs/answering.ts +53 -0
  60. package/src/keeper/key-check.ts +105 -0
  61. package/src/keeper/ledger-tools.ts +295 -0
  62. package/src/keeper/link-check.ts +185 -0
  63. package/src/keeper/merge.ts +260 -0
  64. package/src/keeper/note-origin.ts +56 -0
  65. package/src/keeper/numbers-check.ts +102 -0
  66. package/src/keeper/open-in-pi.ts +57 -0
  67. package/src/keeper/organize/absence.ts +266 -0
  68. package/src/keeper/organize/carried-on.ts +214 -0
  69. package/src/keeper/organize/clerk-coverage.ts +293 -0
  70. package/src/keeper/organize/clerk-prompts.ts +132 -0
  71. package/src/keeper/organize/clerk.ts +1395 -0
  72. package/src/keeper/organize/coverage-gate.ts +19 -0
  73. package/src/keeper/organize/coverage-tools.ts +765 -0
  74. package/src/keeper/organize/entries.ts +153 -0
  75. package/src/keeper/organize/entry-gate.ts +289 -0
  76. package/src/keeper/organize/fill-tools.ts +718 -0
  77. package/src/keeper/organize/follow-up.ts +280 -0
  78. package/src/keeper/organize/generation-check.ts +504 -0
  79. package/src/keeper/organize/graph.ts +230 -0
  80. package/src/keeper/organize/lane-tools.ts +289 -0
  81. package/src/keeper/organize/looked-tools.ts +54 -0
  82. package/src/keeper/organize/materials.ts +243 -0
  83. package/src/keeper/organize/owner-lines.ts +493 -0
  84. package/src/keeper/organize/place-tools.ts +216 -0
  85. package/src/keeper/organize/placement-inference.ts +1045 -0
  86. package/src/keeper/organize/placement-time.ts +320 -0
  87. package/src/keeper/organize/placing.ts +821 -0
  88. package/src/keeper/organize/positioning.ts +33 -0
  89. package/src/keeper/organize/readiness.ts +47 -0
  90. package/src/keeper/organize/reading.ts +401 -0
  91. package/src/keeper/organize/round-blocks.ts +65 -0
  92. package/src/keeper/organize/round-news.ts +164 -0
  93. package/src/keeper/organize/round-open.ts +586 -0
  94. package/src/keeper/organize/schedule-state.ts +39 -0
  95. package/src/keeper/organize/schedule.ts +137 -0
  96. package/src/keeper/organize/service.ts +409 -0
  97. package/src/keeper/organize/skills.ts +116 -0
  98. package/src/keeper/organize/slots.ts +85 -0
  99. package/src/keeper/organize/stage-gate.ts +175 -0
  100. package/src/keeper/organize/stage-tools.ts +471 -0
  101. package/src/keeper/organize/standing-notes.ts +141 -0
  102. package/src/keeper/organize/table-follow-up.ts +330 -0
  103. package/src/keeper/organize/takeover-clerk.ts +832 -0
  104. package/src/keeper/organize/takeover-state.ts +103 -0
  105. package/src/keeper/organize/takeover.ts +216 -0
  106. package/src/keeper/organize/update-pending.ts +535 -0
  107. package/src/keeper/organize/workbench-placement.ts +92 -0
  108. package/src/keeper/own-keys.ts +114 -0
  109. package/src/keeper/owner-text.ts +165 -0
  110. package/src/keeper/project-folder.ts +334 -0
  111. package/src/keeper/prompts.ts +101 -0
  112. package/src/keeper/references.ts +177 -0
  113. package/src/keeper/relook/compose.ts +120 -0
  114. package/src/keeper/relook/prompts.ts +36 -0
  115. package/src/keeper/roles.ts +259 -0
  116. package/src/keeper/round-tools.ts +227 -0
  117. package/src/keeper/route.ts +148 -0
  118. package/src/keeper/rules.ts +97 -0
  119. package/src/keeper/runtime.ts +1858 -0
  120. package/src/keeper/scope-tools.ts +194 -0
  121. package/src/keeper/sendback-text.ts +83 -0
  122. package/src/keeper/step-timing.ts +207 -0
  123. package/src/keeper/testing/clerk-script.ts +212 -0
  124. package/src/keeper/testing/streaming-arguments.ts +57 -0
  125. package/src/keeper/tools.ts +2340 -0
  126. package/src/keeper/utterance-tools.ts +64 -0
  127. package/src/keeper/verbatim.ts +397 -0
  128. package/src/ledger/adapters.ts +186 -0
  129. package/src/ledger/arrangements.ts +257 -0
  130. package/src/ledger/blame.ts +58 -0
  131. package/src/ledger/code-engine-child.ts +69 -0
  132. package/src/ledger/code-engine.ts +548 -0
  133. package/src/ledger/code.ts +607 -0
  134. package/src/ledger/docs.ts +294 -0
  135. package/src/ledger/git-read.ts +321 -0
  136. package/src/ledger/index.ts +1850 -0
  137. package/src/ledger/item-range.ts +16 -0
  138. package/src/ledger/lines.ts +328 -0
  139. package/src/ledger/numbering.ts +269 -0
  140. package/src/ledger/ranges.ts +74 -0
  141. package/src/ledger/rebuild-worker.ts +15 -0
  142. package/src/ledger/rebuild.ts +387 -0
  143. package/src/ledger/repo-scan.ts +181 -0
  144. package/src/ledger/schema.ts +450 -0
  145. package/src/ledger/sessions.ts +134 -0
  146. package/src/ledger/text-scan.ts +466 -0
  147. package/src/ledger/time.ts +185 -0
  148. package/src/ledger/ts-symbols.ts +180 -0
  149. package/src/ledger/views.ts +377 -0
  150. package/src/mcp/server.ts +127 -0
  151. package/src/mcp/tools.ts +187 -0
  152. package/src/model/ids.ts +23 -0
  153. package/src/model/k-types.ts +912 -0
  154. package/src/model/time.ts +85 -0
  155. package/src/model/types.ts +1382 -0
  156. package/src/model/views-k.ts +707 -0
  157. package/src/model/vocab.ts +269 -0
  158. package/src/process/analysis.ts +79 -0
  159. package/src/process/breakpoint-candidates.ts +383 -0
  160. package/src/process/breakpoints.ts +746 -0
  161. package/src/process/claim-check.ts +295 -0
  162. package/src/process/delivery-links.ts +52 -0
  163. package/src/process/facts.ts +487 -0
  164. package/src/process/generations.ts +138 -0
  165. package/src/process/index.ts +243 -0
  166. package/src/process/integration.ts +128 -0
  167. package/src/process/placement.ts +107 -0
  168. package/src/process/plan.ts +276 -0
  169. package/src/process/sendbacks.ts +244 -0
  170. package/src/process/text.ts +185 -0
  171. package/src/process/units.ts +543 -0
  172. package/src/process/unnumbered.ts +431 -0
  173. package/src/process/work.ts +884 -0
  174. package/src/scope/candidates.ts +86 -0
  175. package/src/scope/decisions.ts +229 -0
  176. package/src/scope/discover.ts +741 -0
  177. package/src/scope/ignore.ts +126 -0
  178. package/src/scope/ignored-place.ts +93 -0
  179. package/src/scope/skip.ts +161 -0
  180. package/src/scope/toolchain.ts +180 -0
  181. package/src/scope/worktree.ts +187 -0
  182. package/src/server/api.ts +549 -0
  183. package/src/server/app.ts +595 -0
  184. package/src/server/drafts-api.ts +170 -0
  185. package/src/server/folders-api.ts +243 -0
  186. package/src/server/graph-view.ts +639 -0
  187. package/src/server/http.ts +224 -0
  188. package/src/server/k-agent-api.ts +56 -0
  189. package/src/server/k-rounds-d99.ts +91 -0
  190. package/src/server/k-views.ts +405 -0
  191. package/src/server/keeper-page.ts +446 -0
  192. package/src/server/keys-api.ts +239 -0
  193. package/src/server/project-folder-api.ts +104 -0
  194. package/src/server/vendor.ts +14 -0
  195. package/src/server/workbench-content.ts +561 -0
  196. package/src/sources/anchor.ts +129 -0
  197. package/src/sources/files.ts +267 -0
  198. package/src/sources/gitobs.ts +41 -0
  199. package/src/sources/history.ts +231 -0
  200. package/src/sources/scan-worker.ts +10 -0
  201. package/src/sources/sessions/locate.ts +292 -0
  202. package/src/sources/sessions/read.ts +346 -0
  203. package/src/sources/sessions/scope.ts +42 -0
  204. package/src/sources/sessions/utterances.ts +223 -0
  205. package/src/sources/watch.ts +215 -0
  206. package/src/store/json-file.ts +50 -0
  207. package/src/store/paths.ts +39 -0
  208. package/src/store/project-store.ts +295 -0
  209. package/src/store/versions.ts +172 -0
  210. package/src/store/workspace.ts +216 -0
  211. package/src/table/check.ts +333 -0
  212. package/src/table/follow-up-tools.ts +30 -0
  213. package/src/table/follow-up.ts +344 -0
  214. package/src/table/import.ts +610 -0
  215. package/src/table/project-home.ts +61 -0
  216. package/src/table/sessions.ts +168 -0
  217. package/src/table/takeover.ts +483 -0
  218. package/src/util/git-check.ts +29 -0
  219. package/src/util/git.ts +411 -0
  220. package/src/util/own-ts.ts +15 -0
  221. package/src/util/paths.ts +225 -0
  222. package/template/FILLING-IN.md +148 -0
  223. package/template/examples/papertrail.json +747 -0
  224. package/template/projectkeeper.schema.json +1001 -0
  225. package/ui/app.js +1135 -0
  226. package/ui/failures.js +82 -0
  227. package/ui/flyout.js +120 -0
  228. package/ui/folder-chooser.js +264 -0
  229. package/ui/glossary.js +156 -0
  230. package/ui/graph-fit.js +157 -0
  231. package/ui/graph-material.js +61 -0
  232. package/ui/graph-palette.js +109 -0
  233. package/ui/graph-tools.js +62 -0
  234. package/ui/graph.js +1712 -0
  235. package/ui/index.html +31 -0
  236. package/ui/k/code-view.js +230 -0
  237. package/ui/k/draft-view.js +93 -0
  238. package/ui/k/k-views.css +224 -0
  239. package/ui/k/round-tree.js +450 -0
  240. package/ui/k/scope-k.js +159 -0
  241. package/ui/k-data.js +118 -0
  242. package/ui/k-fold.js +106 -0
  243. package/ui/k-process.css +302 -0
  244. package/ui/k-process.js +934 -0
  245. package/ui/keeper-activity.js +175 -0
  246. package/ui/keeper-view.js +801 -0
  247. package/ui/keeper.css +101 -0
  248. package/ui/key-order.js +62 -0
  249. package/ui/list-fold.js +67 -0
  250. package/ui/markdown.d.ts +21 -0
  251. package/ui/markdown.js +472 -0
  252. package/ui/placement.d.ts +92 -0
  253. package/ui/placement.js +445 -0
  254. package/ui/popover-place.d.ts +16 -0
  255. package/ui/popover-place.js +89 -0
  256. package/ui/popover.js +198 -0
  257. package/ui/project-folder.js +37 -0
  258. package/ui/shell-state.js +40 -0
  259. package/ui/strip-rows.js +171 -0
  260. package/ui/styles.css +492 -0
  261. package/ui/theme-pick.js +36 -0
  262. package/ui/theme.js +74 -0
  263. package/ui/themes/LICENSE.md +13 -0
  264. package/ui/themes/README.md +44 -0
  265. package/ui/themes/TOKENS.md +291 -0
  266. package/ui/themes/a1/board.css +1557 -0
  267. package/ui/themes/a1/pk.css +392 -0
  268. package/ui/themes/b1/board.css +1163 -0
  269. package/ui/themes/b1/pk.css +218 -0
  270. package/ui/themes/d2/board.css +518 -0
  271. package/ui/themes/d2/pk.css +243 -0
  272. package/ui/themes/guard.css +73 -0
  273. package/ui/themes/i1/board.css +776 -0
  274. package/ui/themes/i1/pk.css +223 -0
  275. package/ui/themes/manifest.json +13 -0
  276. package/ui/views.js +2796 -0
package/ui/index.html ADDED
@@ -0,0 +1,31 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <meta name="color-scheme" content="dark">
7
+ <title>ProjectKeeper</title>
8
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%2323211d'/%3E%3Cpath d='M23 8H12l-5 8 5 8h11M14 8v16M7 16h16' fill='none' stroke='%23e3b76f' stroke-width='2'/%3E%3C/svg%3E">
9
+ <link rel="stylesheet" href="/styles.css">
10
+ <!-- Increment K: the process view, the popover's lineage and versions, the generation bands (CKC-24, §6.3, §6.4). -->
11
+ <link rel="stylesheet" href="/k-process.css">
12
+ <!-- Increment K: AS's renderers (Code, the round tree, the scope additions; ui/k/*.js). They also attach it
13
+ themselves when mounted; linked here so the views never flash unstyled on first paint. -->
14
+ <link rel="stylesheet" href="/k/k-views.css">
15
+ <!-- Themes (Spec §6.16): the frozen WorkflowKeeper board, then ProjectKeeper's own layer for it. Both hrefs are set by
16
+ theme.js from the choice kept in this browser; empty, they are the factory look. The guard is always last: it
17
+ pins the shell's layout and says no colour (themes/guard.css). -->
18
+ <link rel="stylesheet" id="theme-board" href="">
19
+ <link rel="stylesheet" id="theme-pk" href="">
20
+ <link rel="stylesheet" href="/themes/guard.css">
21
+ </head>
22
+ <body>
23
+ <div id="app" class="shell"></div>
24
+ <dialog id="dialog" aria-labelledby="dialog-title"></dialog>
25
+ <div id="toast" role="status" aria-live="polite"></div>
26
+ <script src="/vendor/cytoscape/dist/cytoscape.min.js"></script>
27
+ <script src="/vendor/dagre/dist/dagre.min.js"></script>
28
+ <script src="/vendor/cytoscape-dagre/dist/cytoscape-dagre.js"></script>
29
+ <script type="module" src="/app.js"></script>
30
+ </body>
31
+ </html>
@@ -0,0 +1,230 @@
1
+ // §6.17 the Code view: the current version's code territories by product area (CKC-25, §1.19).
2
+ // Pure rendering: the CodeView comes in as a parameter; anything more goes through ctx.
3
+ import { h, fmtTime } from '../app.js';
4
+
5
+ const ensureStyle = () => {
6
+ if (document.getElementById('k-views-css')) return;
7
+ const link = document.createElement('link');
8
+ link.id = 'k-views-css';
9
+ link.rel = 'stylesheet';
10
+ link.href = '/k/k-views.css';
11
+ document.head.append(link);
12
+ };
13
+
14
+ const num = (n) => Number(n).toLocaleString('en-US');
15
+ const short = (commit) => (commit ? commit.slice(0, 7) : '—');
16
+ const day = (occurred) => (occurred && occurred.at ? occurred.at.slice(0, 10) : '—');
17
+
18
+ // D74/D54: "also serves N" stays one chip; the list opens on hover, and a click pins it open.
19
+ const alsoServesChip = (list) => {
20
+ const el = h('span', { class: 'kv-also' },
21
+ h('button', {
22
+ class: 'kv-tag kv-blue', type: 'button',
23
+ onClick: (e) => { e.stopPropagation(); el.classList.toggle('kv-open'); },
24
+ }, `also serves ${list.length}`),
25
+ h('ul', { class: 'kv-also-list' }, list.map((a) => h('li', {}, a.areaName))),
26
+ );
27
+ return el;
28
+ };
29
+
30
+ const terrNames = (ids, byId, counts) => ids.length
31
+ ? ids.map((id) => `${byId.get(id)?.name ?? id}${counts?.[id] != null ? ` (${num(counts[id])})` : ''}`).join(', ')
32
+ : null;
33
+
34
+ // The work items behind one generation's share of the lines (TerritoryView.generations[].works): one chip, the list
35
+ // opens on hover and a click pins it — the same pattern as "also serves N" (D74).
36
+ const genWorksChip = (g) => {
37
+ const el = h('span', { class: 'kv-also kv-genworks' },
38
+ h('button', {
39
+ class: 'kv-tag', type: 'button', title: 'Which work items made these lines',
40
+ onClick: (e) => { e.stopPropagation(); el.classList.toggle('kv-open'); },
41
+ }, `${g.works.length} work${g.works.length === 1 ? '' : 's'}`),
42
+ h('ul', { class: 'kv-also-list' }, g.works.map((w) => h('li', {}, `${w.label} · ${num(w.lines)} lines`))),
43
+ );
44
+ return el;
45
+ };
46
+
47
+ /** The anomalies that judge code by its references (§1.19): their `Inferred` is the ledger's reach, not a guess. */
48
+ const BY_REFERENCES = new Set(['Unreferenced', 'Looks residual, is live']);
49
+
50
+ const plural = (n, one, many = `${one}s`) => `${num(n)} ${n === 1 ? one : many}`;
51
+
52
+ // How deep the ledger reads one language (CodeView.levels; §1.16, §6.17; D98 补), in what was measured: the TypeScript
53
+ // compiler's languages to symbols; the code engine's to symbols, with the imports it resolved to a file of the repository
54
+ // and those it left unresolved naming one; a language nothing reads, its files and sizes only. Where a file is not read or
55
+ // a reference was left unresolved, residual calls there are Inferred; the ledger's own words for that are on hover.
56
+ function levelText(l) {
57
+ if (!l.readBy) return 'not read: files and sizes only (residual calls here are Inferred)';
58
+ const measured = [
59
+ l.read < l.files ? `${num(l.read)} of ${num(l.files)} files read` : null,
60
+ l.imports ? `${plural(l.imports.resolved, 'import')} resolved` : null,
61
+ l.imports ? `${num(l.imports.unresolvedNamingRepoFiles)} left unresolved naming a repository file` : null,
62
+ l.namedButUnreferenced ? `${plural(l.namedButUnreferenced, 'file')} named by other files but reached by no counted reference` : null,
63
+ l.parseErrors ? `${plural(l.parseErrors, 'file')} not parsed in full` : null,
64
+ ].filter(Boolean);
65
+ return `read by the ${l.readBy === 'compiler' ? 'TypeScript compiler' : 'code engine'}: symbols${measured.length ? `, ${measured.join(', ')}` : ''}${l.complete ? '' : ' (residual calls here are Inferred)'}`;
66
+ }
67
+
68
+ function anomalyView(a, ctx) {
69
+ const okTone = a.kind === 'Looks residual, is live';
70
+ const sb = a.sendBackId ? ctx.getSendBack?.(a.sendBackId) : null;
71
+ // The notes behind noteIds, by title when the view names them (TerritoryAnomalyView.notes); a bare id otherwise.
72
+ const notes = a.notes?.length ? a.notes : a.noteIds.map((id) => ({ id, title: null, ask: null, status: null }));
73
+ let sbBox = null;
74
+ return h('div', { class: `kv-anomaly ${okTone ? 'kv-ok' : 'kv-lit'}`, dataset: { kind: a.kind } },
75
+ h('div', {},
76
+ h('span', { class: 'kv-kind' }, `${okTone ? '✓' : '⚑'} ${a.kind}`),
77
+ a.basis === 'Inferred' ? h('span', { class: 'kv-tag kv-purple', title: BY_REFERENCES.has(a.kind) ? 'The ledger left references here unresolved, or reads none of them: this call is judged, not computed' : 'Judged from the material, not shown by the ledger alone' }, 'Inferred') : null),
78
+ h('p', { class: 'kv-anomaly-text' }, a.text),
79
+ h('details', { class: 'kv-evid-wrap' },
80
+ h('summary', { class: 'kv-text-btn' }, `basis · ${a.evidence.length}`),
81
+ h('ul', { class: 'kv-evid' }, a.evidence.map((e) => h('li', {},
82
+ h('span', {}, `${e.kind} · ${e.label}`),
83
+ e.line ? h('span', { class: 'kv-line' }, `“${e.line}”`) : null)))),
84
+ h('div', { class: 'kv-anomaly-acts' },
85
+ // From the anomaly to its notes (§6.17 "从异常跳到相关的 note 与送回").
86
+ notes.map((n) => h('button', { class: 'btn small kv-btn kv-note', type: 'button', dataset: { note: n.id }, title: n.title ? `${n.ask ?? 'Note'}${n.status && n.status !== 'Current' ? ` · ${n.status}` : ''} — open the note` : 'Open the note', onClick: () => ctx.openNote(n.id) },
87
+ n.title ? `✎ ${n.title.length > 48 ? `${n.title.slice(0, 47)}…` : n.title}` : 'Note')),
88
+ sb ? (sbBox = h('div', { class: 'kv-sendback', dataset: { sendback: sb.id } },
89
+ h('div', {}, h('span', { class: 'kv-stage' }, `↩ ${sb.stage} · back to ${sb.to}`), h('span', { class: 'kv-muted' }, ` ${sb.suggestion}`)),
90
+ h('div', { class: 'kv-sendback-acts' },
91
+ sb.stage === 'Suggested'
92
+ ? h('button', { class: 'btn small kv-btn', type: 'button', onClick: () => ctx.copy(sb.copyForAgent) }, 'Copy for agent')
93
+ : null,
94
+ // …and to its send-back: the whole of it beside the anomaly — where, the evidence and its lines, where to.
95
+ ctx.openSendBack ? h('button', { class: 'btn small kv-btn kv-open-sb', type: 'button', title: 'Open the send-back: the problem, its evidence and the original lines, where it goes back to', onClick: () => ctx.openSendBack(sb, sbBox) }, 'Open send-back') : null))) : a.sendBackId ? h('span', { class: 'kv-none', title: a.sendBackId }, 'Send-back not found') : null));
96
+ }
97
+
98
+ function drillRow(t, repo, ctx, workLabels) {
99
+ const td = h('td', { colspan: '5' }, h('div', { class: 'kv-doc-loading' }, 'Loading files…'));
100
+ const tr = h('tr', { class: 'kv-drill', dataset: { tid: t.id } }, td);
101
+ const draw = (detail) => {
102
+ td.replaceChildren();
103
+ if (!detail || !detail.files) {
104
+ td.append(h('div', { class: 'kv-none' }, 'No file detail for this territory.'));
105
+ return;
106
+ }
107
+ const refList = (refs) => refs.length === 0
108
+ ? h('span', { class: 'kv-none' }, 'none')
109
+ : h('span', { class: 'kv-reflist', title: refs.join('\n') }, refs.length <= 2 ? refs.map((r) => r.split('/').pop()).join(', ') : `${refs.slice(0, 2).map((r) => r.split('/').pop()).join(', ')} +${refs.length - 2}`);
110
+ td.append(h('table', { class: 'kv-files' },
111
+ h('thead', {}, h('tr', {},
112
+ h('th', {}, 'File'), h('th', {}, 'Lines'), h('th', {}, 'Referenced by'), h('th', {}, 'References'), h('th', {}, 'Last commit'), h('th', {}, 'Work'))),
113
+ h('tbody', {}, detail.files.map((f) => h('tr', { dataset: { path: f.path } },
114
+ h('td', {}, h('button', { class: 'kv-text-btn kv-mono', type: 'button', onClick: () => ctx.openFile(t.repo ?? repo, f.path) }, f.path),
115
+ f.generated ? h('span', { class: 'kv-tag kv-purple', title: 'Generated code' }, 'generated') : null),
116
+ h('td', { class: 'kv-muted' }, num(f.lines)),
117
+ h('td', {}, refList(f.importedBy)),
118
+ h('td', {}, refList(f.imports)),
119
+ h('td', { class: 'kv-muted' }, f.lastCommit ? h('span', { title: f.lastCommit.subject }, `${day(f.lastCommit.occurred)} · ${short(f.lastCommit.commit)}`) : '—'),
120
+ h('td', {}, f.workId
121
+ ? h('button', { class: 'kv-tag', type: 'button', onClick: () => ctx.openWork(f.workId) }, f.workLabel ?? workLabels.get(f.workId) ?? f.workId)
122
+ : h('span', { class: 'kv-none' }, '—')))))));
123
+ };
124
+ ctx.fetchTerritory(t.id).then(draw, (e) => { td.replaceChildren(h('div', { class: 'kv-none' }, `Could not load: ${e.message}`)); });
125
+ return tr;
126
+ }
127
+
128
+ export function renderCodeView(container, codeView, ctx) {
129
+ ensureStyle();
130
+ container.replaceChildren();
131
+ const byId = new Map(codeView.territories.map((t) => [t.id, t]));
132
+ const workLabels = new Map();
133
+ for (const t of codeView.territories) for (const b of t.builtBy) if (b.workId) workLabels.set(b.workId, b.label);
134
+ const drillOpen = new Set();
135
+
136
+ const groups = [];
137
+ for (const t of codeView.territories) {
138
+ const key = t.row.kind === 'area' ? t.row.areaId : t.row.kind;
139
+ const last = groups[groups.length - 1];
140
+ if (last && last.key === key) last.territories.push(t);
141
+ else groups.push({ key, row: t.row, territories: [t] });
142
+ }
143
+
144
+ const terrCells = (t) => {
145
+ const deps = terrNames(t.dependsOn, byId, t.dependsOnCounts);
146
+ const depsBy = terrNames(t.dependedBy, byId, t.dependedByCounts);
147
+ const built = t.builtBy.map((b) => b.workId
148
+ ? h('button', { class: 'kv-tag', type: 'button', title: `${b.label} · merge ${b.commits.map(short).join(', ')}`, onClick: () => ctx.openWork(b.workId) }, `${b.label.split(' ')[0]} ×${b.files}`)
149
+ : h('span', { class: 'kv-tag', title: `${b.label} · ${b.commits.map(short).join(', ')}` }, `— ×${b.files}`));
150
+ const drill = () => {
151
+ const name = `${t.name} · ${t.paths.join(' ')}`;
152
+ const row = container.querySelector(`tr.kv-drill[data-tid="${t.id}"]`);
153
+ if (row) { row.remove(); drillOpen.delete(t.id); return; }
154
+ drillOpen.add(t.id);
155
+ const anchor = container.querySelector(`tr[data-terr="${t.id}"]`);
156
+ anchor?.after(drillRow(t, codeView.version.repo, ctx, workLabels));
157
+ };
158
+ return [
159
+ h('td', { class: 'kv-col-terr' },
160
+ h('div', { class: 'kv-tblock' },
161
+ h('div', {},
162
+ h('button', { class: 'kv-text-btn kv-tname', type: 'button', title: 'Drill down to files', onClick: drill }, t.name),
163
+ t.paths.map((p) => h('span', { class: 'kv-path kv-mono' }, ` ${p} `)),
164
+ h('span', { class: 'kv-size' }, `· ${num(t.size.files)} files · ${num(t.size.lines)} lines`)),
165
+ h('div', { class: 'kv-muted' }, t.summary),
166
+ // A path the current version no longer has (CKC-25 AC-10): the numbers count only what is left.
167
+ t.gonePaths?.length ? h('div', { class: 'kv-gone', title: 'Deleted or moved since the territory was drawn; the next round’s cross-check draws it again on the current paths' },
168
+ `⚠ ${t.gonePaths.length === t.paths.length ? 'Its code is gone from the current version' : 'Not in the current version'}: `, t.gonePaths.map((p) => h('span', { class: 'kv-mono' }, `${p} `))) : null,
169
+ t.size.generatedLines
170
+ ? h('div', { class: 'kv-generated' }, `generated: ${num(t.size.generatedFiles)} file · ${num(t.size.generatedLines)} lines; handwritten ≈ ${num(t.size.lines - t.size.generatedLines)} lines`)
171
+ : null,
172
+ deps || depsBy ? h('div', { class: 'kv-deps kv-muted' },
173
+ deps ? h('div', {}, `depends on: ${deps}`) : null,
174
+ depsBy ? h('div', {}, `depended by: ${depsBy}`) : null) : null,
175
+ t.alsoServes.length ? h('div', { class: 'kv-deps' }, alsoServesChip(t.alsoServes)) : null)),
176
+ h('td', { class: 'kv-col-built' },
177
+ h('div', {}, built),
178
+ t.generations.length ? h('div', { class: 'kv-gens kv-muted' }, 'made in: ', t.generations.slice(0, 2).map((g, i) => h('span', {}, i ? ' · ' : null, `${g.name} ${Math.round(g.share * 100)}%`, g.works?.length ? [' ', genWorksChip(g)] : null))) : null,
179
+ t.tests ? h('div', { style: 'margin-top:3px' }, h('span', { class: 'kv-tag kv-green' }, `tests ${t.tests}`)) : null,
180
+ t.lastChange ? h('div', { class: 'kv-faint', style: 'margin-top:3px' }, `last change ${day(t.lastChange.occurred)} · `, h('span', { class: 'kv-mono', title: t.lastChange.subject }, short(t.lastChange.commit))) : null),
181
+ h('td', { class: 'kv-col-use' },
182
+ // `Not known` (§1.16): no code, or code nothing reaches whose references the ledger reads none of — stated as it
183
+ // stands, neither in use nor residual, so no ⚠ and no lit tone. `In use, not in current plan`: current code
184
+ // references it and nothing current in the plan points at it — in use, so not lit, and said apart from `In current plan`.
185
+ t.currentUse === 'Not known'
186
+ ? h('span', { class: 'kv-use kv-unknown', title: 'The ledger reads the references of none of this territory’s code, or it holds none: nothing is claimed about its use either way' }, t.currentUse)
187
+ : t.currentUse === 'In use, not in current plan'
188
+ ? h('span', { class: 'kv-use kv-unplanned', title: 'Current code references it, and no current work item changed it and no current requirement, design, decision, plan or work points at its area' }, t.currentUse)
189
+ : h('span', {
190
+ class: `kv-use ${t.currentUse === 'Unreferenced' ? 'kv-lit-red' : t.currentUse === 'Previous generation only' ? 'kv-lit' : ''}`,
191
+ title: t.currentUse === 'In current plan' ? 'Current code references it, and current work or requirements point at it' : '',
192
+ }, t.currentUse === 'In current plan' ? t.currentUse : `⚠ ${t.currentUse}`)),
193
+ h('td', { class: 'kv-col-state' },
194
+ t.anomalies.length ? t.anomalies.map((a) => anomalyView(a, ctx)) : h('span', { class: 'kv-none' }, '—')),
195
+ ];
196
+ };
197
+
198
+ const body = [];
199
+ for (const g of groups) {
200
+ const label = g.row.kind === 'area' ? g.row.areaName : g.row.kind === 'shared' ? 'Shared base' : 'Not product code';
201
+ g.territories.forEach((t, i) => {
202
+ body.push(h('tr', { dataset: { terr: t.id, rowKind: g.row.kind } },
203
+ i === 0 ? h('td', { class: 'kv-area-cell', rowspan: String(g.territories.length) },
204
+ h('div', { class: g.row.kind === 'area' ? 'kv-area-name' : 'kv-muted' }, label),
205
+ // The area's one line as Product intent writes it (TerritoryView.row.areaLine), when the view carries it.
206
+ g.row.kind === 'area' && g.row.areaLine ? h('div', { class: 'kv-muted kv-arealine' }, g.row.areaLine) : null) : null,
207
+ terrCells(t)));
208
+ });
209
+ }
210
+
211
+ container.append(h('div', { class: 'kv-code', dataset: { kv: 'code' } },
212
+ h('div', { class: 'kv-head' },
213
+ h('h2', {}, 'Code'),
214
+ h('div', { class: 'kv-chips' },
215
+ h('span', { class: 'kv-chip' }, 'Current version: ', h('b', {}, codeView.version.repo), codeView.version.branch ? ` · ${codeView.version.branch}` : '', ' · ', h('span', { class: 'kv-mono' }, short(codeView.version.commit)), ` · ${day(codeView.version.occurred)}`),
216
+ h('span', { class: 'kv-chip kv-levels' }, 'Ledger code structure: ', codeView.levels.length
217
+ ? codeView.levels.map((l, i) => [i ? ' · ' : null, h('span', { class: 'kv-level', dataset: { lang: l.language, readBy: l.readBy ?? 'none' }, title: l.gaps.length ? l.gaps.join('\n') : null },
218
+ h('b', {}, l.language), ` — ${levelText(l)}`)])
219
+ : 'no code in the current version'),
220
+ h('span', { class: 'kv-chip' }, 'Generated: ', h('b', {}, fmtTime(codeView.generatedAt))))),
221
+ h('table', { class: 'kv-table' },
222
+ h('thead', {}, h('tr', {},
223
+ h('th', {}, 'Product area'),
224
+ h('th', {}, 'Code territories (current version)'),
225
+ h('th', {}, 'Built by · verified'),
226
+ h('th', {}, 'Used by current plan'),
227
+ h('th', {}, 'State & anomalies'))),
228
+ h('tbody', {}, body))));
229
+ return container;
230
+ }
@@ -0,0 +1,93 @@
1
+ // §3.11 a session's draft where the owner drills into a session (CKC-23 AC-18; D88): the owner's lines verbatim, each
2
+ // with its kind — chat, decision or confirmation — what a confirmation answers and what it confirms, and the agents'
3
+ // intents and reports as claims. Opened from an object's sessions (a work item's session sources) and from the
4
+ // session-drafts step of the round that wrote it. Pure rendering over SessionDraftView / SessionDraftRef
5
+ // (src/model/views-k.ts); fetching and opening an original go through ctx.
6
+ import { h, fmtTime, openDialog } from '../app.js';
7
+
8
+ const KIND_TONE = { Decision: 'amber', Confirmation: 'green', Chat: '' };
9
+ const shortId = (id) => (id && id.length > 12 ? id.slice(0, 8) : id);
10
+ const span = (from, to) => (from ? `${fmtTime(from)}${to && to !== from ? ` – ${fmtTime(to)}` : ''}` : 'time not recorded');
11
+ const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
12
+
13
+ /** The counts of one draft, in words: the owner's lines and how they were read. */
14
+ export function draftCounts(d) {
15
+ const lines = d.ownerLines ?? d.counts?.lines ?? 0;
16
+ const n = typeof lines === 'number' ? lines : lines.length;
17
+ const parts = [plural(n, 'owner line')];
18
+ const c = d.counts ?? d;
19
+ if (c.decisions) parts.push(plural(c.decisions, 'decision'));
20
+ if (c.confirmations) parts.push(plural(c.confirmations, 'confirmation'));
21
+ if (c.chat) parts.push(`${c.chat} chat`);
22
+ if (c.unjudged) parts.push(`${c.unjudged} not yet classified`);
23
+ return parts.join(' · ');
24
+ }
25
+
26
+ /** One session as a list names it: host, session, when; with its draft, or saying it has none yet. */
27
+ export function draftRefLine(ref, ctx) {
28
+ const head = `${ref.host} session ${shortId(ref.sessionId)}`;
29
+ return h('div', { class: 'kd-ref', dataset: { draft: ref.id ?? '', session: ref.sessionId } },
30
+ ref.id
31
+ ? h('button', { class: 'text-btn kd-open', title: 'Open the draft of this session: the owner’s words verbatim, and what the agents said, as claims', onClick: (e) => { e.stopPropagation(); void ctx.openDraft(ref.id); } }, `Session draft · ${head}`)
32
+ : h('span', { class: 'kd-none', title: 'This session is among the sources, and the round has not drafted it yet' }, `${head} · not drafted yet`),
33
+ ' ', h('span', { class: 'faint' }, `${span(ref.startedAt, ref.endedAt)}${ref.id ? ` · ${draftCounts(ref)}` : ''}`));
34
+ }
35
+
36
+ /** The popover's and the full details' block: the sessions an object stands on, each openable (a work item's sessions). */
37
+ export function sessionsBlock(refs, ctx) {
38
+ if (!refs || !refs.length) return null;
39
+ return h('div', { class: 'kp-block kd-sessions' },
40
+ h('h4', {}, `Sessions (${refs.length})`),
41
+ ...refs.map((r) => draftRefLine(r, ctx)));
42
+ }
43
+
44
+ /** One owner line: when, where, its kind, the words verbatim; a confirmation with what it answers and confirms. */
45
+ function ownerLine(l, ctx) {
46
+ const kind = l.kind
47
+ ? h('span', { class: `tag ${KIND_TONE[l.kind] ?? ''} kd-kind`, title: l.kind === 'Confirmation' ? 'A decision: the owner said yes to what the agent proposed' : l.kind === 'Decision' ? 'The owner decided something' : 'The owner talking, not deciding' }, l.kind)
48
+ : h('span', { class: 'tag kd-kind kd-unjudged', title: 'The round has not classified this line yet' }, 'Not yet classified');
49
+ return h('div', { class: `kd-line${l.kind ? ` kd-${l.kind.toLowerCase()}` : ''}`, dataset: { ref: l.ref } },
50
+ h('div', { class: 'kd-line-head' },
51
+ h('span', { class: 'kd-when' }, l.at ? fmtTime(l.at) : 'time not recorded'), ' ',
52
+ h('span', { class: 'faint mono', title: 'Its place in the session' }, /^\d+$/.test(l.ref) ? `[${l.ref}]` : l.ref), ' ', kind,
53
+ l.sourceId && ctx.openSource ? h('button', { class: 'text-btn kd-orig', title: 'Read this part of the session as it was read', onClick: () => ctx.openSource(l.sourceId) }, 'Original') : null),
54
+ h('div', { class: 'kd-text', title: 'The owner’s words, verbatim' }, l.text),
55
+ l.kind === 'Confirmation' ? h('div', { class: 'kd-confirm' },
56
+ l.answers ? h('div', {}, h('small', { class: 'faint' }, 'Answers — the agent message just before, verbatim'),
57
+ l.answers.length > 700
58
+ ? h('details', { class: 'fold' }, h('summary', {}, `${l.answers.slice(0, 200)}…`), h('blockquote', { class: 'quote kd-answers' }, l.answers))
59
+ : h('blockquote', { class: 'quote kd-answers' }, l.answers)) : null,
60
+ l.confirms ? h('div', { class: 'kd-confirms' }, h('small', { class: 'faint' }, 'Confirms — in the Keeper’s words'), ' ', l.confirms) : null) : null);
61
+ }
62
+
63
+ /** The draft, read in the dialog (§6.4's reading surface). */
64
+ export function draftBody(d, ctx) {
65
+ const c = d.counts;
66
+ return [
67
+ h('dl', { class: 'kv kd-meta' },
68
+ h('dt', {}, 'Session'), h('dd', { class: 'mono' }, `${d.session.host} · ${d.session.sessionId}`),
69
+ h('dt', {}, 'When'), h('dd', {}, span(d.session.startedAt, d.session.endedAt)),
70
+ d.session.file ? h('dt', {}, 'Log') : null, d.session.file ? h('dd', { class: 'mono faint' }, d.session.file) : null,
71
+ h('dt', {}, 'Owner’s lines'), h('dd', { class: 'kd-counts' }, draftCounts(d)),
72
+ h('dt', {}, 'Drafted'), h('dd', {}, (d.writtenBy ?? []).map((w, i) => [i ? ' · ' : null, `${w.roundLabel ?? 'a round'} ${fmtTime(w.at)}`]))),
73
+ h('h4', { class: 'kd-h' }, `The owner’s words (${c.lines})`),
74
+ c.lines ? h('div', { class: 'kd-lines' }, ...d.ownerLines.map((l) => ownerLine(l, ctx))) : h('div', { class: 'faint' }, 'The owner said nothing in this session.'),
75
+ h('h4', { class: 'kd-h' }, `What the agents said they did or meant to do (${d.agentSummary.length})`),
76
+ d.agentSummary.length
77
+ ? h('div', { class: 'kd-claims' }, ...d.agentSummary.map((s) => h('div', { class: 'kd-claim' },
78
+ h('span', { class: 'tag purple', title: 'An agent’s account of itself: a claim, until the ledger or the code shows it' }, 'Claimed'), ' ',
79
+ h('b', {}, s.who), ' ', h('span', { class: 'faint' }, s.at ? fmtTime(s.at) : ''), h('div', { class: 'kd-claim-text' }, s.summary))))
80
+ : h('div', { class: 'faint' }, 'No summary of the agents was written.'),
81
+ h('p', { class: 'faint kd-foot' }, 'The owner’s words and their times are taken from the session by the program, verbatim; which lines are decisions, and the agents’ summary, are the Keeper’s reading.'),
82
+ ];
83
+ }
84
+
85
+ /** Opens one draft in the dialog: `ctx.fetchDraft(key)` gives the SessionDraftView, `ctx.openSource(id)` an original. */
86
+ export async function openDraft(key, ctx) {
87
+ let d;
88
+ try { d = await ctx.fetchDraft(key); } catch (e) { openDialog('Session draft', [h('p', { class: 'muted' }, `Could not load: ${e.message}`)]); return null; }
89
+ if (!d) { openDialog('Session draft', [h('p', { class: 'muted' }, 'No draft of this session yet.')]); return null; }
90
+ const dialog = openDialog(`Session draft · ${d.session.host} ${shortId(d.session.sessionId)}`, draftBody(d, ctx));
91
+ dialog.classList.add('draft-dialog');
92
+ return dialog;
93
+ }
@@ -0,0 +1,224 @@
1
+ /* Increment K views: Code (§6.17), a round's tree (§6.9, §3.10), Project-scope additions (§6.7).
2
+ Own layer only (§6.16): reads the theme's --pk-* tokens with factory fallbacks, adds no colors of its own.
3
+ Cards use the one card material (--pk-card-*). Text on a card is the card's ink: --kv-ink is --pk-card-text
4
+ (a1's card is light parchment with dark ink, the others are dark cards with light text); muted/faint are the
5
+ same ink at 76%/58%, no new colors. Color-coded markers always pair a shape/word with the color. */
6
+ .kv-card, .kv-table {
7
+ --kv-ink: var(--pk-card-text, #e7e7df);
8
+ --kv-muted: color-mix(in srgb, var(--kv-ink) 76%, transparent);
9
+ --kv-faint: color-mix(in srgb, var(--kv-ink) 58%, transparent);
10
+ /* family hues pulled halfway to the card's ink: readable as text on both light and dark cards */
11
+ --kv-accent: color-mix(in srgb, var(--pk-accent, #d8a24a) 55%, var(--kv-ink));
12
+ --kv-green: color-mix(in srgb, var(--pk-green, #7ec88a) 55%, var(--kv-ink));
13
+ --kv-blue: color-mix(in srgb, var(--pk-blue, #8aa8e8) 55%, var(--kv-ink));
14
+ --kv-red: color-mix(in srgb, var(--pk-red, #e08a8a) 55%, var(--kv-ink));
15
+ --kv-purple: color-mix(in srgb, var(--pk-purple, #b99ae0) 55%, var(--kv-ink));
16
+ }
17
+ .kv-card {
18
+ background: var(--pk-card-bg, var(--pk-surface, #1e1e1a));
19
+ background-image: var(--pk-card-texture, none);
20
+ border: 1px solid var(--pk-card-border, var(--pk-line, #3a3a33));
21
+ border-radius: var(--pk-card-radius, var(--pk-radius, 8px));
22
+ box-shadow: var(--pk-card-shadow, none);
23
+ color: var(--kv-ink);
24
+ }
25
+ .kv-muted { color: var(--kv-muted, var(--pk-text-muted, #a5a59b)); }
26
+ .kv-faint { color: var(--kv-faint, var(--pk-text-faint, #7f7f76)); }
27
+ .kv-mono { font-family: var(--pk-font-mono, ui-monospace, monospace); font-size: 0.92em; }
28
+ .kv-strong { color: var(--kv-ink, var(--pk-text-strong, #fff)); }
29
+
30
+ /* Buttons are the app's own keys (.btn.small): each theme paints them, so on a1's parchment card they are its
31
+ brass-lettered keys like every other button there (the card's ink on the dark key read about 1:1). .kv-btn only
32
+ names them for the views' checks. */
33
+ .kv-btn { white-space: nowrap; }
34
+ .kv-text-btn { font: inherit; padding: 0; border: 0; background: none; cursor: pointer; color: var(--pk-accent, #7aa2f7); text-align: left; }
35
+ .kv-text-btn:hover { text-decoration: underline; }
36
+ .kv-card .kv-text-btn, .kv-table .kv-text-btn { color: var(--kv-accent); }
37
+
38
+ .kv-tag {
39
+ display: inline-block; padding: 0 6px; border-radius: var(--pk-radius-sm, 4px);
40
+ background: var(--pk-raised, #242420); color: var(--pk-text-muted, #a5a59b);
41
+ font-size: 0.86em; line-height: 1.6; white-space: nowrap; border: 0;
42
+ }
43
+ button.kv-tag { cursor: pointer; font: inherit; font-size: 0.86em; }
44
+ button.kv-tag:hover { color: var(--pk-text-strong, #fff); outline: 1px solid var(--pk-line-strong, #4a4a42); }
45
+ .kv-tag.kv-green { background: var(--pk-green-bg, #1d2b1e); color: var(--pk-green, #7ec88a); }
46
+ .kv-tag.kv-blue { background: var(--pk-blue-bg, #1c2433); color: var(--pk-blue, #8aa8e8); }
47
+ .kv-tag.kv-purple { background: var(--pk-purple-bg, #2a2333); color: var(--pk-purple, #b99ae0); }
48
+ .kv-tag.kv-amber { background: var(--pk-accent-soft, #33291c); color: var(--pk-accent, #d8a24a); }
49
+ .kv-tag.kv-red { background: var(--pk-red-bg, #331f1f); color: var(--pk-red, #e08a8a); }
50
+ .kv-tag.kv-strike { text-decoration: line-through; opacity: 0.75; }
51
+
52
+ /* ─────────── Code view ─────────── */
53
+ .kv-code { padding: 10px 14px 18px; overflow: auto; }
54
+ .kv-head { margin: 2px 0 12px; }
55
+ .kv-head h2 { margin: 0 0 6px; }
56
+ .kv-chips { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: baseline; }
57
+ .kv-chip { color: var(--pk-text-muted, #a5a59b); font-size: 0.92em; }
58
+ .kv-chip b { color: var(--pk-text, #e8e8e2); font-weight: 600; }
59
+
60
+ .kv-table { width: 100%; border-collapse: collapse; background: var(--pk-card-bg, var(--pk-surface, #1e1e1a)); background-image: var(--pk-card-texture, none); border: 1px solid var(--pk-card-border, var(--pk-line, #3a3a33)); border-radius: var(--pk-card-radius, var(--pk-radius, 8px)); box-shadow: var(--pk-card-shadow, none); color: var(--kv-ink); }
61
+ .kv-table th {
62
+ text-align: left; font-weight: 600; font-size: 0.86em; letter-spacing: 0.02em;
63
+ color: var(--kv-muted); padding: 8px 10px;
64
+ border-bottom: 1px solid var(--pk-card-line, var(--pk-line, #3a3a33));
65
+ background: var(--pk-card-head-bg, transparent);
66
+ }
67
+ .kv-table td { vertical-align: top; padding: 9px 10px; border-bottom: 1px solid var(--pk-card-line, var(--pk-line-soft, #2e2e28)); }
68
+ .kv-table tr:last-child td { border-bottom: 0; }
69
+ .kv-area-cell { width: 15%; border-right: 1px solid var(--pk-card-line, var(--pk-line, #3a3a33)); }
70
+ .kv-area-name { font-weight: 600; color: var(--kv-ink); }
71
+ .kv-col-terr { width: 37%; }
72
+ .kv-col-built { width: 20%; }
73
+ .kv-col-use { width: 10%; }
74
+ .kv-col-state { width: 18%; }
75
+
76
+ .kv-tname { font-weight: 600; font-size: 1em; }
77
+ .kv-tblock + .kv-tblock { margin-top: 6px; }
78
+ .kv-path { color: var(--kv-faint); }
79
+ .kv-size { color: var(--kv-muted); }
80
+ .kv-generated { color: var(--kv-purple); }
81
+ .kv-deps { margin-top: 3px; font-size: 0.92em; }
82
+ .kv-gens { margin-top: 3px; font-size: 0.92em; }
83
+
84
+ .kv-also { position: relative; display: inline-block; }
85
+ .kv-also-list {
86
+ display: none; position: absolute; z-index: 30; left: 0; top: calc(100% + 4px); min-width: 180px;
87
+ padding: 6px 8px; margin: 0; list-style: none;
88
+ background: var(--pk-card-bg, var(--pk-raised, #242420));
89
+ background-image: var(--pk-card-texture, none);
90
+ border: 1px solid var(--pk-card-border, var(--pk-line-strong, #4a4a42));
91
+ border-radius: var(--pk-card-radius, var(--pk-radius, 8px));
92
+ box-shadow: var(--pk-card-shadow, var(--pk-shadow-float, 0 8px 24px rgba(0,0,0,0.35)));
93
+ }
94
+ .kv-also:hover .kv-also-list, .kv-also.kv-open .kv-also-list { display: block; }
95
+ .kv-also-list li { padding: 2px 0; color: var(--pk-card-text, #e8e8e2); font-size: 0.92em; white-space: nowrap; }
96
+
97
+ .kv-use { display: inline-flex; align-items: center; gap: 5px; font-size: 0.92em; color: var(--kv-muted); }
98
+ .kv-use.kv-lit { color: var(--kv-accent); font-weight: 600; }
99
+ .kv-use.kv-lit-red { color: var(--kv-red); font-weight: 600; }
100
+
101
+ .kv-anomaly { padding: 6px 8px; border-radius: var(--pk-radius-sm, 4px); font-size: 0.92em; }
102
+ .kv-anomaly + .kv-anomaly { margin-top: 6px; }
103
+ .kv-anomaly.kv-lit { background: color-mix(in srgb, var(--pk-accent, #d8a24a) 12%, transparent); border-left: 3px solid var(--pk-accent, #d8a24a); }
104
+ .kv-anomaly.kv-ok { background: color-mix(in srgb, var(--pk-green, #7ec88a) 12%, transparent); border-left: 3px solid var(--pk-green, #7ec88a); }
105
+ .kv-anomaly .kv-kind { font-weight: 600; }
106
+ .kv-anomaly.kv-lit .kv-kind { color: var(--kv-accent); }
107
+ .kv-anomaly.kv-ok .kv-kind { color: var(--kv-green); }
108
+ .kv-anomaly-text { margin: 2px 0 0; color: var(--kv-ink); }
109
+ .kv-anomaly-acts { margin-top: 5px; display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
110
+ .kv-evid { margin-top: 4px; font-size: 0.9em; color: var(--kv-muted); }
111
+ .kv-evid li { margin: 1px 0; }
112
+ .kv-evid .kv-line { display: block; padding-left: 12px; color: var(--kv-faint); }
113
+ .kv-sendback { margin-top: 6px; padding: 5px 7px; border: 1px dashed var(--pk-blue, #8aa8e8); border-radius: var(--pk-radius-sm, 4px); }
114
+ .kv-sendback .kv-stage { color: var(--kv-blue); font-weight: 600; }
115
+
116
+ .kv-drill td { background: color-mix(in srgb, var(--kv-ink) 5%, var(--pk-card-bg, var(--pk-ground, #191916))); padding: 8px 12px; }
117
+ .kv-files { width: 100%; border-collapse: collapse; font-size: 0.92em; }
118
+ .kv-files th { text-align: left; font-weight: 600; color: var(--kv-faint); padding: 4px 8px; border-bottom: 1px solid var(--pk-card-line, var(--pk-line, #3a3a33)); }
119
+ .kv-files td { padding: 4px 8px; border-bottom: 1px solid var(--pk-card-line, var(--pk-line-soft, #2e2e28)); vertical-align: top; }
120
+ .kv-files tr:last-child td { border-bottom: 0; }
121
+ .kv-reflist { color: var(--kv-muted); }
122
+ .kv-none { color: var(--kv-faint, var(--pk-text-faint, #7f7f76)); }
123
+
124
+ /* ─────────── round tree ─────────── */
125
+ .kv-rounds { padding: 10px 14px 18px; display: flex; flex-direction: column; gap: 14px; overflow: auto; }
126
+ .kv-round { padding: 10px 12px 12px; }
127
+ .kv-round-head { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: baseline; }
128
+ .kv-round-title { font-weight: 700; font-size: 1.05em; color: var(--kv-ink); }
129
+ .kv-round-meta { color: var(--kv-muted); font-size: 0.92em; }
130
+ .kv-round-note { margin-top: 4px; color: var(--kv-faint); font-size: 0.9em; }
131
+
132
+ .kv-steps { list-style: none; margin: 10px 0 0; padding: 0; }
133
+ .kv-step { border-top: 1px solid var(--pk-card-line, var(--pk-line-soft, #2e2e28)); padding: 8px 0 8px 14px; position: relative; }
134
+ .kv-step::before { content: ''; position: absolute; left: 2px; top: 14px; width: 6px; height: 6px; border-radius: 3px; background: var(--pk-line-strong, #4a4a42); }
135
+ .kv-step.kv-failed::before { background: var(--pk-red, #e08a8a); }
136
+ .kv-digs { border: 1px dashed var(--pk-card-line, var(--pk-line, #3a3a33)); border-radius: var(--pk-radius-sm, 4px); margin: 4px 0; }
137
+ .kv-digs-head { padding: 6px 10px 0; color: var(--kv-faint); font-size: 0.88em; }
138
+ .kv-digs .kv-step { padding-left: 22px; }
139
+ .kv-step-head { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; }
140
+ .kv-step-label { font-weight: 600; color: var(--kv-ink); }
141
+ .kv-step-path { color: var(--kv-accent); }
142
+ .kv-model { color: var(--kv-faint); font-size: 0.88em; }
143
+ .kv-status { font-size: 0.88em; color: var(--kv-green); }
144
+ .kv-status.kv-bad { color: var(--kv-red); }
145
+
146
+ .kv-timing { margin-top: 5px; display: flex; flex-wrap: wrap; gap: 3px 12px; align-items: center; font-size: 0.88em; color: var(--kv-muted); }
147
+ .kv-bar { display: inline-flex; width: 180px; height: 8px; border-radius: 4px; overflow: hidden; background: var(--pk-raised, #242420); vertical-align: middle; }
148
+ .kv-bar span { display: block; height: 100%; }
149
+ .kv-seg-gen { background: var(--pk-accent, #d8a24a); }
150
+ .kv-seg-tool { background: var(--pk-blue, #8aa8e8); }
151
+ .kv-seg-queue { background: var(--pk-purple, #b99ae0); }
152
+ .kv-seg-parse { background: var(--pk-red, #e08a8a); }
153
+ .kv-seg-other { background: var(--pk-line-strong, #4a4a42); }
154
+ .kv-legend { display: inline-flex; gap: 4px; align-items: center; }
155
+ .kv-legend i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }
156
+ .kv-usage { margin-top: 3px; font-size: 0.88em; color: var(--kv-muted); }
157
+ .kv-docs { margin-top: 5px; display: flex; flex-wrap: wrap; gap: 5px; }
158
+ .kv-doc-panel { margin: 6px 0 2px; padding: 8px 12px; border: 1px solid var(--pk-card-line, var(--pk-line, #3a3a33)); border-radius: var(--pk-radius-sm, 4px); background: color-mix(in srgb, var(--kv-ink) 5%, var(--pk-card-bg, var(--pk-ground, #191916))); color: var(--kv-ink); max-width: 960px; }
159
+ .kv-doc-panel h1, .kv-doc-panel h2, .kv-doc-panel h3 { margin: 8px 0 4px; }
160
+ .kv-doc-panel table { border-collapse: collapse; }
161
+ .kv-doc-panel th, .kv-doc-panel td { border: 1px solid var(--pk-card-line, var(--pk-line-soft, #2e2e28)); padding: 3px 8px; }
162
+ .kv-doc-loading { color: var(--kv-faint); }
163
+
164
+ .kv-round-sum { margin-top: 10px; display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 8px; }
165
+ .kv-sum-cell { padding: 7px 9px; border: 1px solid var(--pk-card-line, var(--pk-line-soft, #2e2e28)); border-radius: var(--pk-radius-sm, 4px); font-size: 0.9em; }
166
+ .kv-sum-title { font-weight: 600; color: var(--kv-muted); font-size: 0.88em; margin-bottom: 3px; }
167
+ .kv-sum-cell ul { margin: 0; padding-left: 16px; }
168
+ .kv-sum-cell li { margin: 1px 0; }
169
+ .kv-sum-cell.kv-lit { border-color: var(--pk-accent, #d8a24a); }
170
+ .kv-sum-cell.kv-lit .kv-sum-title { color: var(--kv-accent); }
171
+
172
+ /* ─────────── Project scope additions ─────────── */
173
+ .kv-scope { padding: 10px 14px 18px; display: flex; flex-direction: column; gap: 12px; overflow: auto; }
174
+ .kv-section { padding: 9px 12px 11px; }
175
+ .kv-section > h3 { margin: 0 0 7px; font-size: 1em; color: var(--kv-ink); }
176
+ .kv-kv { display: grid; grid-template-columns: max-content 1fr; gap: 3px 14px; font-size: 0.92em; }
177
+ .kv-kv dt { color: var(--kv-muted); margin: 0; }
178
+ .kv-kv dd { margin: 0; color: var(--kv-ink); }
179
+ .kv-mini { width: 100%; border-collapse: collapse; font-size: 0.92em; }
180
+ .kv-mini th { text-align: left; font-weight: 600; color: var(--kv-faint); padding: 3px 8px 3px 0; border-bottom: 1px solid var(--pk-card-line, var(--pk-line, #3a3a33)); }
181
+ .kv-mini td { padding: 4px 8px 4px 0; border-bottom: 1px solid var(--pk-card-line, var(--pk-line-soft, #2e2e28)); vertical-align: top; }
182
+ .kv-mini tr:last-child td { border-bottom: 0; }
183
+ .kv-missing { color: var(--kv-accent); }
184
+ .kv-history { color: var(--kv-faint); text-decoration: line-through; }
185
+ .kv-note-block { padding: 8px 10px; border: 1px dashed var(--pk-card-border, var(--pk-line, #3a3a33)); border-radius: var(--pk-radius-sm, 4px); color: var(--kv-muted); font-size: 0.92em; }
186
+ .kv-note-block b { color: var(--kv-ink); }
187
+
188
+ /* ─────────── QC AY: the jumps and the honest states of Code; the drafts of a round ─────────── */
189
+ .kv-gone { margin-top: 3px; color: var(--kv-accent); font-size: 0.92em; }
190
+ .kv-use.kv-unplanned { color: var(--kv-ink); }
191
+ .kv-sendback-acts { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 5px; }
192
+ .kv-table tr.kv-target > td { box-shadow: inset 3px 0 0 var(--pk-accent, #d8a24a); }
193
+ .kv-drafts { margin-top: 5px; font-size: 0.9em; }
194
+ .kv-drafts > summary { cursor: pointer; }
195
+ .kv-draft-list { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
196
+ .kv-draft-list li { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: baseline; }
197
+
198
+ /* ─────────── QC AY package C: what package A added, in sight ─────────── */
199
+ /* What the ledger keeps without reading it (Project scope, ScopeKView ledger.notRead). */
200
+ .kv-notread { display: flex; flex-direction: column; gap: 4px; }
201
+ .kv-notread-paths { list-style: none; margin: 2px 0 0; padding: 0 0 0 12px; display: flex; flex-direction: column; gap: 1px; }
202
+ /* The sweeps the program added to a deepening (RoundView.sweepsAdded): the round's note and each step's tag. */
203
+ .kv-sweeps-added ul { margin: 2px 0 0; padding-left: 18px; }
204
+ .kv-sweeps-added li { margin: 1px 0; }
205
+ /* What of a round did not finish (the coverage's failure list): each with why, and Retry or Continue. */
206
+ .kv-failures { color: var(--kv-red); }
207
+ .kv-failures ul { margin: 2px 0 0; padding-left: 18px; }
208
+ .kv-failures li { margin: 3px 0; display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: baseline; }
209
+ .kv-fail-reason { color: var(--kv-ink); overflow-wrap: anywhere; min-width: 0; flex: 1 1 320px; }
210
+ .kv-added-tag { cursor: help; }
211
+ /* A deepening read by reading assignments: each sweep with its reading line, its assignments under it. */
212
+ .kv-dig-path { padding: 6px 10px 2px; border-top: 1px dashed var(--pk-card-line, var(--pk-line, #3a3a33)); }
213
+ .kv-dig-path:first-of-type { border-top: 0; }
214
+ .kv-dig-path-head { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; font-size: 0.94em; }
215
+ .kv-reading { margin-top: 3px; font-size: 0.88em; color: var(--kv-muted); overflow-wrap: anywhere; }
216
+ .kv-accounted { margin-top: 3px; }
217
+ .kv-accounted > summary { cursor: pointer; }
218
+ .kv-accounted ul { margin: 4px 0 2px; padding-left: 18px; }
219
+ .kv-accounted li { margin: 2px 0; }
220
+ .kv-dig-path .kv-step { padding-left: 12px; }
221
+ /* D99: the main agent's stages, its lanes and the coverage check sit under its step, in the same frames as the sweeps. */
222
+ .kv-main-body { margin-top: 6px; }
223
+ .kv-stages { margin: 2px 0 6px; }
224
+ .kv-coverage-body { padding: 6px 10px 6px; }