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
@@ -0,0 +1,243 @@
1
+ /**
2
+ * The process engine (Spec v3.0 §2.12 工作的过程与断点, §1.18 送回, §2.13 rows 1–5 — what the program computes): each
3
+ * piece of work's process as the ledger shows it, its four things, a plan's execution shape, the breakpoints lit and
4
+ * put out, and the send-backs moved on by themselves. The program half of the owner's process view (CKC-24; CKC-27
5
+ * AC-6, AC-11); the round's model steps confirm or refute what rests on judgement and suggest the other send-backs.
6
+ *
7
+ * What the rest of the app wires in:
8
+ * - `processRunner(ledgerService)` — the round's program step `process` (keeper/organize/clerk.ts `ProcessRunner`);
9
+ * - `processEngines(ledgerService)` — `KEngines.process` for the process view (server/k-views.ts): a work item's steps
10
+ * and four things, a plan's shape, and the work item a fix or check is a step of (`stepOf`);
11
+ * - `processBlock(store)` — the synthesis' block: breakpoints lit by kind and object, the candidates as leads, open send-backs;
12
+ * - `computeBreakpoints` — the candidates recomputed when the main agent enters a stage that reads them (D99, `pk_stage`);
13
+ * - `spotCheckTargets(store, round)` — what the spot-check checks: every looked candidate and the weighted sample (D99);
14
+ * and (CS) every standing reason for leaving an item unplaced no spot-check has checked, and the program's unreviewed placements;
15
+ * - `unlightUnchecked(store)` — at start: breakpoints lit without the independent check become candidates again (D99).
16
+ */
17
+ import type { Ledger } from '../ledger/index.ts';
18
+ import type { LedgerService } from '../ledger/adapters.ts';
19
+ import type { ProcessRunner } from '../keeper/organize/clerk.ts';
20
+ import type { KEngines } from '../server/k-views.ts';
21
+ import type { Project } from '../model/types.ts';
22
+ import type { FourThingsView, PlanShapeView, ProcessStepView } from '../model/views-k.ts';
23
+ import type { ProjectStore } from '../store/project-store.ts';
24
+ import { analyze, type Analysis } from './analysis.ts';
25
+ import type { Breakpoint } from '../model/k-types.ts';
26
+ import { BREAKPOINT_KINDS, computeFindings, isCandidate, reconcile, type ReconcileResult } from './breakpoints.ts';
27
+ import { generationPlanText } from './generations.ts';
28
+ import { planShapeOf } from './plan.ts';
29
+ import { advance, type SendBackResult } from './sendbacks.ts';
30
+ import { clip } from './text.ts';
31
+ import { stepOfThread } from './units.ts';
32
+ import { fourThingsOf } from './work.ts';
33
+
34
+ export { analyze } from './analysis.ts';
35
+ export type { Analysis } from './analysis.ts';
36
+ export { isCandidate, spotCheckTargets, spotCheckBlock, unlightUnchecked, type SpotCheckTargets } from './breakpoint-candidates.ts';
37
+
38
+ // ───────────────────────── one work item, one plan ─────────────────────────
39
+
40
+ /** A work item's steps and four things (§2.12). Null when the store has no such work item. */
41
+ export function work(store: ProjectStore, project: Project, ledger: Ledger, workId: string): { readonly steps: readonly ProcessStepView[]; readonly four: FourThingsView } | null {
42
+ return workOf(analyze(store, project, ledger), workId);
43
+ }
44
+
45
+ function workOf(a: Analysis, workId: string): { readonly steps: readonly ProcessStepView[]; readonly four: FourThingsView } | null {
46
+ const p = a.process(workId);
47
+ if (!p) return null;
48
+ return { steps: p.steps, four: fourThingsOf(a.store, p, a.expecting(p.thread, 'Independent check') !== null) };
49
+ }
50
+
51
+ /** A plan's execution shape (§6.3, CKC-24 AC-2). Null when the store has no such `Plan` item. */
52
+ export function plan(store: ProjectStore, project: Project, ledger: Ledger, planId: string): PlanShapeView | null {
53
+ return planShapeOf(analyze(store, project, ledger), planId);
54
+ }
55
+
56
+ /**
57
+ * The work item a work item is a step of — a fix or a check of it, however many fixes and checks lie between (AD,
58
+ * AF, AK and AO → AB) — so List and Graph fold it under that work; null for a piece of work of its own.
59
+ */
60
+ export function stepOf(store: ProjectStore, project: Project, ledger: Ledger, workId: string): string | null {
61
+ return stepOfThread(analyze(store, project, ledger).index, workId);
62
+ }
63
+
64
+ // ───────────────────────── the round's program step ─────────────────────────
65
+
66
+ /** The job of this round's `process` step, so what it writes is counted as the round's (clerk.ts `closeRound`). */
67
+ function processJobOf(store: ProjectStore, roundId: string | null): string | null {
68
+ if (!roundId) return null;
69
+ return store.jobs.filter((j) => j.step?.roundId === roundId && j.step.kind === 'process').sort((a, b) => b.queuedAt.localeCompare(a.queuedAt))[0]?.id ?? null;
70
+ }
71
+
72
+ /**
73
+ * Recompute the candidates, keep what the spot-check lit and still holds, put out what no longer holds (§2.12, D99). The
74
+ * writes count under `jobId` when given (the main agent entering a stage), else under the round's process step.
75
+ */
76
+ export function computeBreakpoints(store: ProjectStore, project: Project, ledger: Ledger, roundId: string | null, jobId?: string | null): ReconcileResult {
77
+ const a = analyze(store, project, ledger);
78
+ return reconcile(store, project.id, computeFindings(a), roundId, jobId === undefined ? processJobOf(store, roundId) : jobId);
79
+ }
80
+
81
+ /** Record the send-backs the project's failed checks and reviews started, and move every send-back on (§1.18). */
82
+ export function advanceSendBacks(store: ProjectStore, project: Project, ledger: Ledger, roundId: string | null): SendBackResult {
83
+ return advance(analyze(store, project, ledger), roundId, processJobOf(store, roundId));
84
+ }
85
+
86
+ export interface ProcessRun {
87
+ readonly works: number;
88
+ readonly steps: number;
89
+ readonly sendBacks: SendBackResult;
90
+ readonly breakpoints: ReconcileResult;
91
+ readonly ms: number;
92
+ readonly note: string;
93
+ }
94
+
95
+ /**
96
+ * The whole step once, on one analysis: the breakpoints first, then the send-backs — a send-back that came from a
97
+ * breakpoint closes on the evidence that put the breakpoint out in the same round.
98
+ */
99
+ export function runProcess(store: ProjectStore, project: Project, ledger: Ledger, roundId: string | null): ProcessRun {
100
+ const started = Date.now();
101
+ const jobId = processJobOf(store, roundId);
102
+ const a = analyze(store, project, ledger);
103
+ const breakpoints = reconcile(store, project.id, computeFindings(a), roundId, jobId);
104
+ const sendBacks = advance(a, roundId, jobId);
105
+ const threads = store.threads.all();
106
+ const steps = threads.reduce((n, t) => n + (a.process(t.id)?.steps.length ?? 0), 0);
107
+ const ms = Date.now() - started;
108
+ const kinds = BREAKPOINT_KINDS.filter((k) => breakpoints.litByKind[k]).map((k) => `${k} ${breakpoints.litByKind[k]}`).join(', ');
109
+ const leads = BREAKPOINT_KINDS.filter((k) => breakpoints.candidatesByKind[k]).map((k) => `${k} ${breakpoints.candidatesByKind[k]}`).join(', ');
110
+ const note = [
111
+ `${threads.length} work item${threads.length === 1 ? '' : 's'}, ${steps} step${steps === 1 ? '' : 's'} from the ledger`,
112
+ `breakpoints lit ${breakpoints.lit}${kinds ? ` (${kinds})` : ''}, candidates ${breakpoints.candidates}${leads ? ` (${leads})` : ''}, ${breakpoints.putOut} put out, ${breakpoints.keptOut} kept out`,
113
+ `send-backs: ${sendBacks.created} recorded from failed checks and reviews, ${sendBacks.returned} returned, ${sendBacks.closed} closed, ${sendBacks.open} open`,
114
+ `${(ms / 1000).toFixed(1)} s`,
115
+ ].join(' · ');
116
+ return { works: threads.length, steps, sendBacks, breakpoints, ms, note };
117
+ }
118
+
119
+ /** The round's program step (clerk.ts `ProcessRunner`): recompute each round, after the trunk and before the synthesis. */
120
+ export function processRunner(ledgerService: LedgerService): ProcessRunner {
121
+ return {
122
+ run: async (store, project, roundId) => {
123
+ const ledger = ledgerService.ledger(project.id);
124
+ if (!ledger) return { note: 'The project has no ledger yet: no process, breakpoint or send-back was computed.' };
125
+ return { note: runProcess(store, project, ledger, roundId).note };
126
+ },
127
+ };
128
+ }
129
+
130
+ // ───────────────────────── the process view's engines ─────────────────────────
131
+
132
+ /**
133
+ * What the store looks like to the engine: when any of what the processes read changed, the analysis is recomputed. The
134
+ * view asks for every work item in one request; one analysis serves them all.
135
+ */
136
+ function storeMark(store: ProjectStore): string {
137
+ const part = (xs: readonly { readonly id: string; readonly updatedAt?: string }[]) => `${xs.length}:${xs.reduce((m, x) => ((x.updatedAt ?? '') > m ? x.updatedAt ?? '' : m), '')}`;
138
+ return [
139
+ part(store.threads.all()), part(store.reference.all()), part(store.relations.all()), part(store.rules.all()), part(store.generations.all()),
140
+ part(store.layers.all()), // CZ: which documents list work decides what was carried on (carried-on.ts)
141
+ part(store.breakpoints.all()), part(store.sendbacks.all()), part(store.numbers.all().map((n) => ({ id: n.id, updatedAt: n.at }))),
142
+ part(store.links.all().map((l) => ({ id: `${l.id}${l.confirmed ? '+' : l.check?.passed ? '~' : ''}`, updatedAt: l.at }))), String(store.sources.size), String(store.patches.size),
143
+ ].join('|');
144
+ }
145
+
146
+ function ledgerMark(ledger: Ledger): string {
147
+ const r = ledger.db.prepare('SELECT max(id) id, max(ended_at) at FROM rebuilds').get() as { id: number | null; at: string | null } | undefined;
148
+ return `${r?.id ?? 0}:${r?.at ?? ''}`;
149
+ }
150
+
151
+ /** `KEngines.process` (server/k-views.ts): a work item's steps and four things, a plan's execution shape, what a work item
152
+ * is a step of, and an earlier generation's plan document as it stood (§2.12, D82). */
153
+ export function processEngines(ledgerService: LedgerService): NonNullable<KEngines['process']> {
154
+ const cache = new Map<string, { ledger: Ledger; store: ProjectStore; mark: string; analysis: Analysis }>();
155
+ const analysisOf = (store: ProjectStore, project: Project): Analysis | null => {
156
+ const ledger = ledgerService.ledger(project.id);
157
+ if (!ledger) return null;
158
+ const mark = `${ledgerMark(ledger)}#${storeMark(store)}`;
159
+ const hit = cache.get(project.id);
160
+ if (hit && hit.ledger === ledger && hit.store === store && hit.mark === mark) return hit.analysis;
161
+ const analysis = analyze(store, project, ledger);
162
+ cache.set(project.id, { ledger, store, mark, analysis });
163
+ return analysis;
164
+ };
165
+ return {
166
+ work: (store, project, workId) => {
167
+ try { const a = analysisOf(store, project); return a ? workOf(a, workId) : null; } catch { return null; }
168
+ },
169
+ plan: (store, project, planId) => {
170
+ try { const a = analysisOf(store, project); return a ? planShapeOf(a, planId) : null; } catch { return null; }
171
+ },
172
+ stepOf: (store, project, workId) => {
173
+ try { const a = analysisOf(store, project); return a ? stepOfThread(a.index, workId) : null; } catch { return null; }
174
+ },
175
+ generationPlan: (store, project, generationId, index, fromLine) => {
176
+ const ledger = ledgerService.ledger(project.id);
177
+ const g = store.generations.get(generationId);
178
+ if (!ledger || !g) return null;
179
+ try { return generationPlanText(ledger, store, g, index, fromLine ?? 1); } catch { return null; }
180
+ },
181
+ };
182
+ }
183
+
184
+ // ───────────────────────── the synthesis' block ─────────────────────────
185
+
186
+ /**
187
+ * What the main agent and the synthesis are given of the program's process (§3.3, §2.12): the breakpoints lit — found
188
+ * missing after a lane looked and the spot-check confirmed (D99) — by kind and object, with their ids and evidence, the
189
+ * lines themselves; then, apart from them, the candidates, leads for reading and not findings; and the send-backs still
190
+ * open. The synthesis tags the six things and writes the notes and send-backs from the lit ones only.
191
+ */
192
+ export function processBlock(store: ProjectStore, opts: { readonly candidates?: 'list' | 'count'; readonly answered?: number } = {}): string {
193
+ const name = (id: string) => {
194
+ const t = store.threads.get(id);
195
+ const r = t ? undefined : store.reference.get(id);
196
+ return t ? `${t.ids[0] ? `${t.ids[0]} ` : ''}${t.title}` : r ? `${r.category} ${r.name}` : id;
197
+ };
198
+ const evidence = (b: Breakpoint, lines: string[]) => {
199
+ for (const e of b.evidence.slice(0, 5)) lines.push(` · ${e.kind} ${e.kind === 'file' ? e.label : e.id}${e.line ? `: “${clip(e.line, 160)}”` : ` (${clip(e.label, 100)})`}`);
200
+ if (b.evidence.length > 5) lines.push(` · … ${b.evidence.length - 5} more`);
201
+ };
202
+ const lit = store.breakpoints.filter((b) => b.lit && !b.ownerResponse);
203
+ const leads = store.breakpoints.filter(isCandidate);
204
+ const lines: string[] = [`=== Process and breakpoints (computed by the program this round; ids to cite, lines as the material wrote them)`];
205
+ lines.push(`Breakpoints lit (${lit.length}) — a lane looked and the spot-check confirmed:`);
206
+ for (const kind of BREAKPOINT_KINDS) {
207
+ const of = lit.filter((b) => b.kind === kind).sort((x, y) => x.since.at.localeCompare(y.since.at));
208
+ if (of.length === 0) continue;
209
+ lines.push(`${kind} (${of.length})${of[0]!.sixThing ? ` — six thing ${of[0]!.sixThing}` : ''}:`);
210
+ for (const b of of) {
211
+ lines.push(`- ${b.id} on ${name(b.targetId)} (${b.targetId}) · ${b.basis}${b.basis === 'Inferred' ? ' (refute with pk_confirm if it does not hold)' : ''}${b.checked ? ` · checked ${b.checked.at.slice(0, 10)}` : b.confirmedInRoundId ? ' · confirmed' : ''}${b.sendBackId ? ` · send-back ${b.sendBackId}` : ''} · since ${b.since.at.slice(0, 10)}`);
212
+ lines.push(` ${b.why}`);
213
+ evidence(b, lines);
214
+ }
215
+ }
216
+ // D103: the synthesis job gets the candidates' state as counts — a candidate is a clue it writes about as an open
217
+ // question, not a finding it judges one by one; the list comes on request (pk_round_state { list: "candidates" }).
218
+ if (opts.candidates === 'count') {
219
+ const looked = leads.filter((b) => b.looked).length;
220
+ const byKind = BREAKPOINT_KINDS.flatMap((kind) => { const n = leads.filter((b) => b.kind === kind).length; return n ? [`${kind} ${n}`] : []; });
221
+ lines.push(`Breakpoint candidates (${leads.length}) — leads, not findings; only the spot-check, which runs after you, lights one, and never in a First usable round: ${leads.length ? byKind.join(', ') : 'none'}. ${looked} of them a lane looked for and did not find (the spot-check checks those); ${leads.length - looked} nobody looked for yet${opts.answered !== undefined ? `; ${opts.answered} more were answered by this round's writes` : ''}. pk_round_state({ list: "candidates" }) lists those with no result; pk_read_assets kind breakpoint reads any.`);
222
+ }
223
+ if (opts.candidates !== 'count') lines.push(`Breakpoint candidates (${leads.length}) — leads, not findings: the program computed them from the ledger's ids and the links made so far, and what is written elsewhere it may have missed. Before anything is written as missing, a lane reads where the step would be written (execution arrangements, summaries, receipts, QC reports, the ledger, the code): found, it links it (pk_link_process); not found, it records where it looked (pk_record_looked). Only the spot-check lights one, and never in a First usable round:`);
224
+ for (const kind of opts.candidates === 'count' ? [] : BREAKPOINT_KINDS) {
225
+ const of = leads.filter((b) => b.kind === kind).sort((x, y) => x.since.at.localeCompare(y.since.at));
226
+ if (of.length === 0) continue;
227
+ lines.push(`${kind} (${of.length}):`);
228
+ for (const b of of) {
229
+ lines.push(`- ${b.id} on ${name(b.targetId)} (${b.targetId}) · ${b.basis}${b.looked ? ` · looked in ${clip(b.looked.where.join('; '), 200)}` : ' · not looked for yet'}`);
230
+ lines.push(` ${b.why}`);
231
+ evidence(b, lines);
232
+ }
233
+ }
234
+ const open = store.sendbacks.filter((s) => s.stage !== 'Closed' && !s.ownerResponse).sort((x, y) => x.occurred.at.localeCompare(y.occurred.at));
235
+ lines.push(`Send-backs open (${open.length}):`);
236
+ for (const s of open) {
237
+ lines.push(`- ${s.id} · ${s.stage} · to ${s.to} · on ${name(s.targetId)} (${s.targetId}) · from ${s.from.kind} ${s.from.id}`);
238
+ lines.push(` ${clip(s.what, 200)}${s.returned ? ` — returned by ${clip(s.returned.by.label, 100)}` : ''}`);
239
+ }
240
+ if (!lit.length && !leads.length && !open.length) lines.push('Nothing is lit, no candidate waits and no send-back is open.');
241
+ return lines.join('\n');
242
+ }
243
+
@@ -0,0 +1,128 @@
1
+ /**
2
+ * How each commit reached the trunk (Spec v3.0 §1.16 row 1; §2.12 `Merged`): the merges that brought it in, innermost
3
+ * first, and the branch each one names.
4
+ *
5
+ * A branch is often merged into an integration branch that is then merged into the trunk (ContextKeeper's k-clerk-tools
6
+ * → k-clerk → main). The ledger's `introducedBy` gives only the last of these, the trunk's merge, for everything under
7
+ * it; the merge a piece of work was integrated by is the innermost one — of the merges that brought a commit in, the one
8
+ * that brought in the fewest commits. A merge of the trunk into a branch (`Merge main into b-scope`, `Merge main (AQ)
9
+ * into k-clerk`) only keeps that branch up to date and integrates nothing: it is left out.
10
+ *
11
+ * The walk stays inside what each trunk merge brought in: a merge nested under it can only bring in commits of that set,
12
+ * so the cost follows the size of the branches, not of the whole history.
13
+ *
14
+ * Everything here is a fact of the ledger's commit graph; nothing is judged.
15
+ */
16
+ import type { CommitFact, Facts } from './facts.ts';
17
+
18
+ export interface Integration {
19
+ /** The merge commit. */
20
+ readonly merge: CommitFact;
21
+ /** The branch it merged, as its subject names it (`Merge branch 'k-longout'`, `Merge k-ayfix-a (…)`), else the branch whose tip is its second parent. */
22
+ readonly source: string | null;
23
+ /** The branch it merged into, when its subject says (`… into k-clerk`). */
24
+ readonly target: string | null;
25
+ /** Every commit it brought into its first parent's line (merges among them), by full hash. */
26
+ readonly brought: ReadonlySet<string>;
27
+ }
28
+
29
+ export interface Integrations {
30
+ /** The integrating merges, oldest first; the merges that only bring the trunk into a branch are not among them. */
31
+ readonly merges: readonly Integration[];
32
+ /** The innermost integrating merge of each commit one brought in. */
33
+ readonly innermost: ReadonlyMap<string, Integration>;
34
+ /** The integrating merges that brought a commit in, innermost first (the trunk's own merge last). */
35
+ chainOf(hash: string): readonly Integration[];
36
+ /** The trunk's own merge (on its first-parent line) that brought a commit in, or the commit itself when it is on that line. */
37
+ trunkMergeOf(hash: string): CommitFact | null;
38
+ }
39
+
40
+ /** The branch names a merge's subject gives: `Merge branch 'x' into y`, `Merge pull request #n from u/x`, `Merge x into y: …`, `Merge x (…)`. */
41
+ export function mergeSubjectBranches(subject: string): { source: string | null; target: string | null } {
42
+ const s = subject.trim();
43
+ const quoted = /^Merge (?:remote-tracking )?branch(?:es)? '([^']+)'(?:\s+(?:of\s+\S+\s+)?into\s+'?([^'\s:(]+)'?)?/i.exec(s);
44
+ if (quoted) return { source: quoted[1]!, target: quoted[2] ?? null };
45
+ const pull = /^Merge pull request #\d+ from [^/\s]+\/(\S+)/i.exec(s);
46
+ if (pull) return { source: pull[1]!, target: null };
47
+ // `Merge k-clerk into k-ledger: …`, `Merge k-ayfix-a (…)`, `Merge k-clerk: …`, `Merge main (AQ) into k-clerk`.
48
+ const plain = /^Merge\s+([A-Za-z0-9][\w./-]*)(?=\s*(?:[::(]|\binto\b|$))(?:\s*\([^)]*\))?(?:\s+into\s+([\w./-]+))?/i.exec(s);
49
+ if (plain && !/^(?:branch|branches|pull|remote-tracking)$/i.test(plain[1]!)) return { source: plain[1]!, target: plain[2] ?? null };
50
+ return { source: null, target: null };
51
+ }
52
+
53
+ const plainBranch = (name: string): string => name.replace(/^(?:refs\/)?(?:heads\/|remotes\/[^/]+\/|origin\/|upstream\/)/, '');
54
+
55
+ /** Whether a branch name is the trunk of its repository (a merge of it into a branch only keeps that branch up to date). */
56
+ function isTrunkName(name: string, trunk: string | null): boolean {
57
+ const n = plainBranch(name).toLowerCase();
58
+ return n === (trunk ?? '').toLowerCase() || n === 'main' || n === 'master' || n === 'trunk' || n === 'head';
59
+ }
60
+
61
+ export function integrationsOf(facts: Facts): Integrations {
62
+ const trunkName = new Map(facts.ledger.repos().map((r) => [r.id, r.trunk]));
63
+ const tipNames = new Map<string, string[]>();
64
+ for (const b of facts.branches) {
65
+ const name = b.namespace === 'remotes' ? b.name.replace(/^[^/]+\//, '') : b.name;
66
+ if (name === 'HEAD') continue;
67
+ tipNames.set(b.tip, [...new Set([...(tipNames.get(b.tip) ?? []), name])]);
68
+ }
69
+ // What each merge of the trunk's first-parent line brought in.
70
+ const members = new Map<string, Set<string>>();
71
+ for (const [h, by] of facts.introducedBy) {
72
+ if (h === by) continue;
73
+ const s = members.get(by) ?? new Set<string>();
74
+ s.add(h);
75
+ members.set(by, s);
76
+ }
77
+ /** Every commit reachable from `from` without leaving `inside`. */
78
+ const walk = (from: readonly string[], inside: ReadonlySet<string>, stop: ReadonlySet<string> | null): Set<string> => {
79
+ const seen = new Set<string>();
80
+ const stack = from.filter((h) => inside.has(h));
81
+ while (stack.length) {
82
+ const h = stack.pop()!;
83
+ if (seen.has(h) || stop?.has(h)) continue;
84
+ seen.add(h);
85
+ for (const p of facts.commits.get(h)?.parents ?? []) if (inside.has(p)) stack.push(p);
86
+ }
87
+ return seen;
88
+ };
89
+ const merges: Integration[] = [];
90
+ const describe = (c: CommitFact, brought: Set<string>): void => {
91
+ if (brought.size === 0) return;
92
+ const named = mergeSubjectBranches(c.subject);
93
+ const byTip = c.parents.slice(1).flatMap((p) => tipNames.get(p) ?? []).filter((n) => !isTrunkName(n, trunkName.get(c.repo) ?? null));
94
+ const source = named.source ?? byTip[0] ?? null;
95
+ if (source && isTrunkName(source, trunkName.get(c.repo) ?? null)) return;
96
+ merges.push({ merge: c, source: source ? plainBranch(source) : null, target: named.target ? plainBranch(named.target) : null, brought });
97
+ };
98
+ for (const [by, set] of members) {
99
+ const top = facts.commits.get(by);
100
+ if (!top || !top.merge) continue;
101
+ describe(top, set);
102
+ for (const h of set) {
103
+ const c = facts.commits.get(h)!;
104
+ if (!c.merge || c.parents.length < 2) continue;
105
+ const base = walk([c.parents[0]!], set, null);
106
+ describe(c, walk(c.parents.slice(1), set, base));
107
+ }
108
+ }
109
+ merges.sort((a, b) => a.merge.ms - b.merge.ms || a.merge.hash.localeCompare(b.merge.hash));
110
+ const chains = new Map<string, Integration[]>();
111
+ for (const m of merges) for (const h of m.brought) chains.set(h, [...(chains.get(h) ?? []), m]);
112
+ const innermost = new Map<string, Integration>();
113
+ for (const [h, list] of chains) {
114
+ list.sort((a, b) => a.brought.size - b.brought.size || a.merge.ms - b.merge.ms);
115
+ innermost.set(h, list[0]!);
116
+ }
117
+ return {
118
+ merges,
119
+ innermost,
120
+ chainOf: (hash) => chains.get(hash) ?? [],
121
+ trunkMergeOf(hash: string): CommitFact | null {
122
+ const c = facts.commits.get(hash);
123
+ if (!c || !c.onTrunk) return null;
124
+ const by = facts.introducedBy.get(hash);
125
+ return by ? facts.commits.get(by) ?? null : null;
126
+ },
127
+ };
128
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Where a piece of work stands in a plan (Spec v3.0 §2.12 `Planned`, §6.3 execution shape): which `Plan` reference item
3
+ * it belongs to — by the relations the round recorded (`serves`, `implements`, `carries out`) — which batch of that
4
+ * plan it is, by its own arrangement's words (`批次 2`, `batch 6 of 7`, `批次 5b`), and the version of the plan document
5
+ * the plan's section first appears in.
6
+ */
7
+ import type { ReferenceItem, WorkThread } from '../model/types.ts';
8
+ import type { ProjectStore } from '../store/project-store.ts';
9
+ import type { Occurred } from '../model/k-types.ts';
10
+ import { pathKey } from '../util/paths.ts';
11
+ import type { DocVersionFact, Facts } from './facts.ts';
12
+ import type { Unit } from './units.ts';
13
+
14
+ export interface BatchLabel {
15
+ /** The plan's batch number as the arrangement writes it (`5`). */
16
+ readonly batch: string;
17
+ /** A half or part of the batch (`a`, `b`, `前半`), when the work is one of several. */
18
+ readonly part: string | null;
19
+ /** The line it was read from. */
20
+ readonly line: string;
21
+ }
22
+
23
+ /** `批次 2`, `批次 5a`, `第 3 批`, `batch 6 of 7`, `Batch 5b`, `批次 5 前半`. */
24
+ const BATCH = /批次\s*(\d{1,3})\s*([a-z]|[前后上下]半)?|第\s*(\d{1,3})\s*批\s*([a-z])?|\bbatch(?:es)?\s*(\d{1,3})\s*([a-z])?\b(?:\s*of\s*\d{1,3})?/i;
25
+
26
+ export function batchOf(unit: Unit): BatchLabel | null {
27
+ const latest = unit.prompts[unit.prompts.length - 1];
28
+ const candidates = [unit.title ?? '', unit.fields.milestone ?? '', unit.fields.batch ?? '', ...(latest?.data.batches ?? []).slice(0, 3)];
29
+ for (const text of candidates) {
30
+ const m = BATCH.exec(text);
31
+ if (!m) continue;
32
+ const batch = m[1] ?? m[3] ?? m[5];
33
+ if (!batch) continue;
34
+ const part = m[2] ?? m[4] ?? m[6] ?? null;
35
+ return { batch, part: part ? part.toLowerCase() : null, line: text };
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /** The `Plan` reference items a work item belongs to, by the round's relations. */
41
+ export function plansOf(store: ProjectStore, thread: WorkThread): ReferenceItem[] {
42
+ const ids = new Set<string>();
43
+ for (const s of thread.serves) ids.add(s.referenceId);
44
+ for (const r of store.relations.all()) if (r.from === thread.id && (r.type === 'serves' || r.type === 'implements' || r.type === 'carries out' || r.type === 'refines')) ids.add(r.to);
45
+ return [...ids].flatMap((id) => { const r = store.reference.get(id); return r && r.category === 'Plan' ? [r] : []; });
46
+ }
47
+
48
+ /** The work items a plan names, by the round's relations (the thread side and the graph side). */
49
+ export function workOfPlan(store: ProjectStore, planId: string): WorkThread[] {
50
+ const out = new Map<string, WorkThread>();
51
+ for (const t of store.threads.all()) if (t.serves.some((s) => s.referenceId === planId)) out.set(t.id, t);
52
+ for (const r of store.relations.all()) {
53
+ if (r.to !== planId || !(r.type === 'serves' || r.type === 'implements' || r.type === 'carries out' || r.type === 'refines')) continue;
54
+ const t = store.threads.get(r.from);
55
+ if (t) out.set(t.id, t);
56
+ }
57
+ return [...out.values()];
58
+ }
59
+
60
+ export interface PlanDocument {
61
+ readonly repo: string;
62
+ readonly path: string;
63
+ /** The heading path of the plan's section in the document ("Title › 11. 实施顺序"), or null for the whole document. */
64
+ readonly section: string | null;
65
+ readonly headingPath: readonly string[];
66
+ /** The version the section first appears in. */
67
+ readonly first: DocVersionFact | null;
68
+ /** The version the checkout has. */
69
+ readonly current: DocVersionFact | null;
70
+ }
71
+
72
+ /** The document (and section) a plan item was read from, located in the ledger's document history. */
73
+ export function planDocument(store: ProjectStore, facts: Facts, plan: ReferenceItem): PlanDocument | null {
74
+ const repos = facts.ledger.repos();
75
+ for (const sid of plan.sourceIds) {
76
+ const s = store.sources.get(sid);
77
+ if (!s) continue;
78
+ const a = s.anchor;
79
+ let repo: string | null = null;
80
+ let rel: string | null = null;
81
+ let headingPath: readonly string[] = [];
82
+ if (a.kind === 'file') {
83
+ const at = facts.relPathOf(a.path);
84
+ if (!at) continue;
85
+ repo = at.repo;
86
+ rel = at.path;
87
+ headingPath = a.headingPath;
88
+ } else if (a.kind === 'revision') {
89
+ const r = repos.find((x) => pathKey(x.path) === pathKey(a.repo)) ?? repos.find((x) => facts.relPathOf(a.repo)?.repo === x.id);
90
+ if (!r) continue;
91
+ repo = r.id;
92
+ rel = a.path;
93
+ } else continue;
94
+ const versions = (facts.docsByPath.get(rel) ?? []).filter((d) => d.repo === repo);
95
+ if (versions.length === 0) continue;
96
+ const section = headingPath.length ? headingPath.join(' › ') : null;
97
+ const first = section ? versions.find((d) => d.added.includes(section) || d.changed.includes(section)) ?? versions[0]! : versions[0]!;
98
+ const current = [...versions].reverse().find((d) => d.current) ?? null;
99
+ return { repo, path: rel, section, headingPath, first, current };
100
+ }
101
+ return null;
102
+ }
103
+
104
+ /** When a plan document version happened: the ledger's own time for it. */
105
+ export function versionOccurred(facts: Facts, v: DocVersionFact): Occurred {
106
+ return facts.entry(v.id)?.occurred ?? { at: new Date(v.ms).toISOString(), basis: 'Commit', anchor: v.id };
107
+ }