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,216 @@
1
+ /**
2
+ * The main agent's bulk tools for what the program can carry (CM, E151; CK fixes 2, 12):
3
+ *
4
+ * - `pk_place_range` places a run of numbered items in one call: `D3–D18 → M2`. On the gated run placement was 154 + 111
5
+ * single-item writes, and an ADR lane that could not find the Areas handed all of it to the main agent. The numbers are
6
+ * the project's own; a range names every number between its ends, and what no item carries comes back as missing. A
7
+ * decision, boundary, requirement or design refines the target; a work item serves it.
8
+ * - `pk_generation_candidate` accepts or rejects a candidate generation the program listed (generation-check.ts):
9
+ * accepting writes the generation — its plan documents, what ended it, its work items (generation-check.ts: the rows of
10
+ * its plan documents, by the document each item was written from) — in one call.
11
+ *
12
+ * Which stage or slot may call them is the stage gate's (clerk-steps.ts `STAGE_WRITERS`, `SLOT_WRITERS`); a lane places
13
+ * only what its slots give it.
14
+ */
15
+ import { Type } from 'typebox';
16
+ import { defineTool, type ToolDefinition } from '@earendil-works/pi-coding-agent';
17
+ import { Ledger } from '../../ledger/index.ts';
18
+ import { expandRanges } from '../../ledger/ranges.ts';
19
+ import type { EvidenceRef, Generation, Occurred } from '../../model/k-types.ts';
20
+ import type { ReferenceItem, WorkThread } from '../../model/types.ts';
21
+ import { stableId } from '../../model/ids.ts';
22
+ import { resolveEvidence, resolveEvidenceList, type EvidenceContext, type LedgerHook } from '../evidence.ts';
23
+ import type { ToolContext } from '../tools.ts';
24
+ import { primaryIdentifier } from '../tools.ts';
25
+ import { areaByWrittenName } from './placing.ts';
26
+ import { generationCandidates, generationItems, withVerdict } from './generation-check.ts';
27
+
28
+ const text = (v: unknown): string => (typeof v === 'string' ? v.trim() : '');
29
+ const now = () => new Date().toISOString();
30
+ const ok = (value: unknown) => ({ content: [{ type: 'text' as const, text: typeof value === 'string' ? value : JSON.stringify(value, null, 1) }], details: {} });
31
+ const fail = (message: string) => ({ content: [{ type: 'text' as const, text: `ERROR: ${message}` }], details: {}, isError: true });
32
+ const squash = (s: string): string => s.replace(/\s+/g, ' ').trim().toLowerCase();
33
+
34
+ /** The numbers a written list names: ranges written out, then every number in it (`D3–D18, D12、R-03`). */
35
+ export function numbersIn(written: string): string[] {
36
+ const out: string[] = [];
37
+ for (const m of expandRanges(written).toUpperCase().matchAll(/(?<![A-Z0-9_])([A-Z]{1,6}-[A-Z]?\d{1,4}|[A-Z]\d{1,4}|[A-Z]{2})(?![A-Z0-9_])/g)) if (!out.includes(m[1]!)) out.push(m[1]!);
38
+ return out;
39
+ }
40
+
41
+ const PLACEABLE: ReadonlySet<string> = new Set(['Decision', 'Boundary', 'Requirement', 'Design']);
42
+ const TARGETS: ReadonlySet<string> = new Set(['Area', 'Plan', 'Goal', 'Product']);
43
+
44
+ export function placeTools(ctx: ToolContext & { readonly ledger?: LedgerHook | null }): ToolDefinition[] {
45
+ const { store, project } = ctx;
46
+ const trace = (summary: string, basisSourceIds: readonly string[] = []) => ({ jobId: ctx.jobId, basisSourceIds, summary });
47
+ const laneSlots = (): readonly string[] | null => (ctx.step?.kind === 'lane' ? ctx.step.lane?.slots ?? [] : null);
48
+
49
+ /** The Area, Plan, Goal or Product a call names: by id, by the project's own id, by its name or its short form. */
50
+ const target = (value: string): ReferenceItem | string => {
51
+ const live = store.reference.filter((r) => TARGETS.has(r.category) && r.validity !== 'Replaced' && r.validity !== 'Removed' && r.validity !== 'Abandoned');
52
+ const direct = store.reference.get(value);
53
+ if (direct) return TARGETS.has(direct.category) ? direct : `${value} is a ${direct.category}: items are placed on an Area or a Plan (or, with its reason, on the Product).`;
54
+ const up = value.toUpperCase();
55
+ const byNumber = live.filter((r) => r.ids.some((i) => i.toUpperCase() === up) || primaryIdentifier(r.ids, r.name) === up);
56
+ if (byNumber.length === 1) return byNumber[0]!;
57
+ const byName = live.filter((r) => squash(r.name) === squash(value));
58
+ if (byName.length === 1) return byName[0]!;
59
+ const area = areaByWrittenName(live.filter((r) => r.category === 'Area'), value);
60
+ if (area) return area;
61
+ const starts = live.filter((r) => squash(r.name).startsWith(`${squash(value)} `));
62
+ if (starts.length === 1) return starts[0]!;
63
+ return byNumber.length + byName.length + starts.length > 1 ? `several items answer to “${value}”: give the id of one` : `nothing among the Areas, Plans, Goals and the Product is “${value}”`;
64
+ };
65
+
66
+ const tools: ToolDefinition[] = [];
67
+
68
+ tools.push(defineTool({
69
+ name: 'pk_place_range', label: 'Place a run of numbered items',
70
+ description: 'Place many numbered items in one call: numbers is the project’s own numbers as a list or a range — "D3–D18", "D12, D28、D87", "AB~CD" is not a range (two letters do not count up) — and to is the Area or Plan they fall on (its id, its project id such as M2 or P1, its name or its short form). A decision, boundary, requirement or design that carries one of the numbers refines it; a work item serves it. What an item already refines or serves stays. Give category to place only reference items of that category (Decision …), or only: "threads" for work items alone. basis says who placed them: Explicit (the default; the document names the run and where it falls) or Inferred (you read it off the records). Numbers no item carries come back in missing; items already there are counted as already. A lane places only what its slots give it. Placing on the Product alone is not a placement: an item that concerns the whole product is written one by one with its reason (pk_write_reference wholeProductWhy).',
71
+ parameters: Type.Object({
72
+ numbers: Type.String({ description: 'the numbers, as a list and ranges: "D3–D18", "D12, D28、D87"' }),
73
+ to: Type.String({ description: 'the Area or Plan: its id, its project id (M2, P1), its name or short form' }),
74
+ category: Type.Optional(Type.String({ description: 'only reference items of this category: Decision | Boundary | Requirement | Design' })),
75
+ only: Type.Optional(Type.String({ description: 'threads: only work items; reference: only reference items' })),
76
+ claim: Type.Optional(Type.String({ description: 'for work items: why they serve it, in a few words (the document that says so)' })),
77
+ basis: Type.Optional(Type.String({ description: 'Explicit (default) | Inferred — who says the items fall there: the document, or your reading of the records' })),
78
+ }),
79
+ execute: async (_id, raw) => {
80
+ const p = raw as Record<string, unknown>;
81
+ if (!ctx.step) return fail('pk_place_range places items in a round of the clerk method, and this job is no step of a round. Nothing was written.');
82
+ const numbers = numbersIn(text(p.numbers));
83
+ if (!numbers.length) return fail('numbers: the project’s own numbers, as a list or ranges ("D3–D18", "D12, D28"). Nothing was written.');
84
+ const to = target(text(p.to));
85
+ if (typeof to === 'string') return fail(`to: ${to}. Nothing was written.`);
86
+ if (to.category === 'Product' || to.category === 'Goal') return fail(`to: ${to.name} is the ${to.category}; a run of items is placed on an Area or a Plan. An item that concerns the whole product is written with its reason (pk_write_reference wholeProductWhy). Nothing was written.`);
87
+ const category = text(p.category);
88
+ if (category && !PLACEABLE.has(category)) return fail(`category must be one of ${[...PLACEABLE].join(', ')}. Nothing was written.`);
89
+ const only = text(p.only);
90
+ if (only && only !== 'threads' && only !== 'reference') return fail('only is threads or reference. Nothing was written.');
91
+ // CQ (D104): the caller says who places — the document (Explicit, the default) or its own reading of the records (Inferred).
92
+ const basis = (text(p.basis) || 'Explicit') as 'Explicit' | 'Inferred';
93
+ if (basis !== 'Explicit' && basis !== 'Inferred') return fail('basis is Explicit or Inferred. Nothing was written.');
94
+ const slots = laneSlots();
95
+ const mayRef = (c: string) => slots === null || slots.includes('relations') || slots.some((s) => s.toLowerCase() === `reference:${c}`.toLowerCase());
96
+ const mayThreads = slots === null || slots.includes('relations') || slots.includes('threads');
97
+ const want = new Set(numbers);
98
+ const carried = new Set<string>();
99
+ const placed: string[] = [];
100
+ let already = 0;
101
+ const refusedSlots = new Set<string>();
102
+ const at = now();
103
+ if (only !== 'threads') {
104
+ for (const r of store.reference.filter((x) => PLACEABLE.has(x.category) && (!category || x.category === category) && x.validity !== 'Removed')) {
105
+ const n = [...r.ids.map((i) => i.toUpperCase()), primaryIdentifier(r.ids, r.name) ?? ''].find((i) => want.has(i));
106
+ if (!n) continue;
107
+ carried.add(n);
108
+ if (!mayRef(r.category)) { refusedSlots.add(`reference:${r.category}`); continue; }
109
+ if (r.refines.includes(to.id)) { already += 1; continue; }
110
+ store.reference.put({ ...r, refines: [...r.refines, to.id], updatedAt: at }, trace(`${n} placed on ${to.name} (pk_place_range)`, r.sourceIds));
111
+ ctx.onSaved?.('reference', r.id, `Placed on ${to.name}`);
112
+ placed.push(n);
113
+ }
114
+ }
115
+ if (only !== 'reference' && !category) {
116
+ for (const t of store.threads.filter((x) => x.validity !== 'Removed')) {
117
+ const n = t.ids.map((i) => i.toUpperCase()).find((i) => want.has(i));
118
+ if (!n) continue;
119
+ carried.add(n);
120
+ if (!mayThreads) { refusedSlots.add('threads'); continue; }
121
+ if (t.serves.some((s) => s.referenceId === to.id)) { already += 1; continue; }
122
+ const mine = { referenceId: to.id, claim: text(p.claim) || `placed with ${numbers.length > 1 ? `${numbers[0]}…${numbers[numbers.length - 1]}` : n} (pk_place_range)`, basis };
123
+ // An Area goes after the Areas it already serves (the first stays its main one), a Plan at the end.
124
+ const lastArea = t.serves.reduce((k, s, i) => (store.reference.get(s.referenceId)?.category === 'Area' ? i : k), -1);
125
+ const serves = to.category === 'Area' ? [...t.serves.slice(0, lastArea + 1), mine, ...t.serves.slice(lastArea + 1)] : [...t.serves, mine];
126
+ store.threads.put({ ...t, serves, updatedAt: at } as WorkThread, trace(`${n} placed on ${to.name} (pk_place_range)`));
127
+ ctx.onSaved?.('threads', t.id, `Placed on ${to.name}`);
128
+ if (!placed.includes(n)) placed.push(n);
129
+ }
130
+ }
131
+ const missing = numbers.filter((n) => !carried.has(n));
132
+ if (refusedSlots.size && !placed.length && !already) return fail(`This lane writes ${slots!.join(', ') || 'no slot'}, and the items these numbers name are ${[...refusedSlots].join(', ')}: a lane places only what its slots give it. Put the placement in your Report for the main agent. Nothing was written.`);
133
+ return ok({
134
+ to: { id: to.id, name: to.name, category: to.category }, numbers: numbers.length, placed: placed.length, already,
135
+ ...(missing.length ? { missing: missing.length > 40 ? [...missing.slice(0, 40), `… ${missing.length - 40} more`] : missing, note: 'missing: no item of the workbench carries these numbers (fill them from their document first, or account for them).' } : {}),
136
+ ...(refusedSlots.size ? { notPlaced: `items of ${[...refusedSlots].join(', ')} were left: this lane does not hold those slots` } : {}),
137
+ });
138
+ },
139
+ }));
140
+
141
+ tools.push(defineTool({
142
+ name: 'pk_generation_candidate', label: 'Accept or reject a candidate generation',
143
+ description: 'Orientation (and reconcile, the cross-check): the program lists candidate generations — archived or superseded plan, contract and module sets — with the owner’s lines that name generations beside them. Whether a candidate is an earlier generation of the project’s plans is yours to judge from the documents. verdict accept writes the generation in this one call: its plan documents, what ended it (the candidate’s own commit unless you give ended), and its work items — the rows of its plan documents: the items written from them, and the current items of the rows that were carried on under the same number (what its other documents define is not its work, and the reply says so apart); name is the material’s own name for it; generationId adds the candidate to a generation already recorded instead. verdict reject records why, and the candidate is not listed again. Each item of an accepted generation then needs its destination (pk_write_thread replacedBy, validity, a current item that depends on or carries it): pk_round_state { list: "withoutDestination" } lists those without one.',
144
+ parameters: Type.Object({
145
+ key: Type.String({ description: 'the candidate’s key as listed, e.g. set:docs/archive/roadmap-v1' }),
146
+ verdict: Type.String({ description: 'accept | reject' }),
147
+ why: Type.Optional(Type.String({ description: 'reject: why it is not a generation (required). accept: what makes it one, in a sentence' })),
148
+ name: Type.Optional(Type.String({ description: 'accept: the material’s own name for the generation, e.g. "Roadmap v1 and the 2024 plan"' })),
149
+ generationId: Type.Optional(Type.String({ description: 'accept: a recorded generation this set belongs to — its documents and items are added to it' })),
150
+ ended: Type.Optional(Type.Object({ kind: Type.String(), id: Type.String(), line: Type.Optional(Type.String()), repo: Type.Optional(Type.String()) }, { description: 'accept: what ended it, where the material says so — { kind, id, line? } (a decision’s line, the cleanup commit); the program reads its date' })),
151
+ }),
152
+ execute: async (_id, raw) => {
153
+ const p = raw as Record<string, unknown>;
154
+ const step = ctx.step;
155
+ if (!step) return fail('pk_generation_candidate is a round’s (the main agent in orientation, reconcile or the cross-check). Nothing was written.');
156
+ const round = store.clerkRounds.get(step.roundId);
157
+ if (!round) return fail(`The round ${step.roundId} is not in the assets.`);
158
+ const verdict = text(p.verdict).toLowerCase();
159
+ if (verdict !== 'accept' && verdict !== 'reject') return fail('verdict is accept or reject. Nothing was written.');
160
+ let ledger: Ledger | null = null;
161
+ try { ledger = Ledger.openDir(store.dir); } catch { ledger = null; }
162
+ try {
163
+ const key = text(p.key);
164
+ const candidates = generationCandidates(store, ledger, { all: true });
165
+ const candidate = candidates.find((c) => c.key === key);
166
+ if (!candidate) return fail(`${key || '(empty)'} is not a candidate the program lists (pk_round_state { list: "generationCandidates" }). To record a generation the program did not list, use pk_write_generation. Nothing was written.`);
167
+ if (candidate.kind !== 'set') return fail('An owner’s line is read, not judged: it tells you how the owner counts the generations. Accept or reject the document sets. Nothing was written.');
168
+ const why = text(p.why);
169
+ const at = now();
170
+ if (verdict === 'reject') {
171
+ if (!why) return fail('why: why this set is not an earlier generation of the plans (a version history, a batch of reports, a recovery copy …). Nothing was written.');
172
+ store.clerkRounds.put(withVerdict(store.clerkRounds.get(round.id) ?? round, { key, accepted: false, why, generationId: null, at }), trace(`Candidate generation ${candidate.name} rejected: ${why}`));
173
+ return ok({ key, verdict: 'reject', why });
174
+ }
175
+ const ev: EvidenceContext = { store, project, ledger: ctx.ledger ?? null };
176
+ const existing = text(p.generationId) ? store.generations.get(text(p.generationId)) : undefined;
177
+ if (text(p.generationId) && !existing) return fail(`${text(p.generationId)} is not a generation of this project. Nothing was written.`);
178
+ const name = text(p.name) || existing?.name || candidate.name;
179
+ const planRefs = resolveEvidenceList(ev, candidate.planRefs.map((id) => ({ kind: 'file', id })), 'planRefs');
180
+ if (typeof planRefs === 'string') return fail(`${planRefs} Nothing was written.`);
181
+ let endedBy: EvidenceRef | string | null = null;
182
+ if (p.ended !== undefined && p.ended !== null) endedBy = resolveEvidence(ev, p.ended, 'ended');
183
+ else if (existing) endedBy = existing.endedBy;
184
+ else if (candidate.endedCommit) endedBy = resolveEvidence(ev, { kind: 'commit', id: candidate.endedCommit }, 'ended');
185
+ if (typeof endedBy === 'string') return fail(`${endedBy} Nothing was written.`);
186
+ if (!endedBy) return fail('ended: what ended this generation, where the material says so — { kind, id, line? } (the program found no commit that set these documents aside). Nothing was written.');
187
+ const ended: Occurred = (p.ended === undefined || p.ended === null) && existing ? existing.ended : endedBy.occurred ?? { at, basis: 'First observed', anchor: endedBy.id, undated: true };
188
+ // CZ: its items are the ones written from its documents, or carried on under the same number — never by number alone.
189
+ const workIds = [...new Set([...(existing?.workIds ?? []), ...generationItems(store, ledger, candidate, candidates)])];
190
+ const id = existing?.id ?? stableId('gen', project.id, squash(name));
191
+ const before = store.generations.get(id);
192
+ const merged = [...(before?.planRefs ?? existing?.planRefs ?? [])];
193
+ for (const r of planRefs) if (!merged.some((x) => x.kind === r.kind && x.id === r.id)) merged.push(r);
194
+ const generation: Generation = { id, projectId: project.id, name, started: before?.started ?? null, ended, endedBy, planRefs: merged, workIds, roundId: step.roundId, updatedAt: at };
195
+ store.generations.put(generation, trace(`Generation “${name}” from the candidate ${candidate.name}: ${merged.length} plan documents, ${workIds.length} work items, ended ${ended.at} (${endedBy.label})`));
196
+ ctx.onSaved?.('generations', id, `Generation: ${name}`);
197
+ store.clerkRounds.put(withVerdict(store.clerkRounds.get(round.id) ?? round, { key, accepted: true, why: why || `recorded as ${name}`, generationId: id, at }), trace(`Candidate generation ${candidate.name} accepted as “${name}”`));
198
+ // CZ: only the rows of its plan documents are work to fill; what current documents carry on, and what its other
199
+ // documents define, are said apart — on the flash run this reply told a lane to fill 60 rows of an archived README.
200
+ const carriedBy = (n: string) => store.threads.find((t) => t.validity !== 'Removed' && t.ids.some((i) => i.toUpperCase() === n.toUpperCase()));
201
+ const unfilled = candidate.numbers.filter((n) => !carriedBy(n));
202
+ const listed = new Set(workIds);
203
+ const notJoined = [...candidate.numbers, ...candidate.carriedOn].filter((n) => { const t = carriedBy(n); return t !== undefined && !listed.has(t.id); });
204
+ return ok({
205
+ key, verdict: 'accept', generation: { id, name, ended, endedBy: endedBy.label, planRefs: merged.length, workIds: workIds.length },
206
+ ...(unfilled.length ? { numbersWithNoWorkItem: unfilled.slice(0, 40), note: 'Its plan documents list these rows and no work item carries them yet: a lane with the generations and threads slots fills them from the plan table or contract index that lists them (pk_fill_from_table); they join the generation as they are filled from its documents.' } : {}),
207
+ ...(candidate.carriedOn.length ? { carriedOn: { count: candidate.carriedOn.length, numbers: candidate.carriedOn.slice(0, 40), note: 'Current documents define these rows too: carried on under the same number. The current items of those numbers are this generation’s items, with that destination; they join it as they are filled from the current documents, and need no item of their own here.' } } : {}),
208
+ ...(candidate.alsoDefined.length ? { alsoDefined: candidate.alsoDefined.map((a) => ({ family: a.family, count: a.count, in: a.documents.slice(0, 3), continuedIn: a.continuedIn })), alsoDefinedNote: 'Defined in this set’s other documents, not in a plan document: not work items of this generation, and nothing here asks a lane to fill them as work. What they are is read from their document.' } : {}),
209
+ ...(notJoined.length ? { notJoined: notJoined.slice(0, 40), notJoinedNote: 'Work items carry these numbers, and nothing recorded says they were written from this set’s documents (another set defines the number too, or the item names no source): the number alone does not make them its items. Add the ones that are (pk_write_generation workIds).' } : {}),
210
+ });
211
+ } finally { ledger?.close(); }
212
+ },
213
+ }));
214
+
215
+ return tools;
216
+ }