@davidorex/pi-context 0.26.0 → 0.28.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 (123) hide show
  1. package/CHANGELOG.md +69 -15
  2. package/README.md +71 -18
  3. package/dist/block-api.d.ts +165 -18
  4. package/dist/block-api.d.ts.map +1 -1
  5. package/dist/block-api.js +800 -57
  6. package/dist/block-api.js.map +1 -1
  7. package/dist/citation-rot-scanner.d.ts +80 -0
  8. package/dist/citation-rot-scanner.d.ts.map +1 -0
  9. package/dist/citation-rot-scanner.js +421 -0
  10. package/dist/citation-rot-scanner.js.map +1 -0
  11. package/dist/content-hash.d.ts +25 -0
  12. package/dist/content-hash.d.ts.map +1 -0
  13. package/dist/content-hash.js +62 -0
  14. package/dist/content-hash.js.map +1 -0
  15. package/dist/context-dir.d.ts +126 -1
  16. package/dist/context-dir.d.ts.map +1 -1
  17. package/dist/context-dir.js +208 -4
  18. package/dist/context-dir.js.map +1 -1
  19. package/dist/context-registry.d.ts +122 -0
  20. package/dist/context-registry.d.ts.map +1 -0
  21. package/dist/context-registry.js +189 -0
  22. package/dist/context-registry.js.map +1 -0
  23. package/dist/context-sdk.d.ts +179 -12
  24. package/dist/context-sdk.d.ts.map +1 -1
  25. package/dist/context-sdk.js +536 -101
  26. package/dist/context-sdk.js.map +1 -1
  27. package/dist/context.d.ts +222 -4
  28. package/dist/context.d.ts.map +1 -1
  29. package/dist/context.js +244 -56
  30. package/dist/context.js.map +1 -1
  31. package/dist/git-env.d.ts +2 -0
  32. package/dist/git-env.d.ts.map +1 -0
  33. package/dist/git-env.js +29 -0
  34. package/dist/git-env.js.map +1 -0
  35. package/dist/index.d.ts +124 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +440 -1125
  38. package/dist/index.js.map +1 -1
  39. package/dist/lens-view.d.ts.map +1 -1
  40. package/dist/lens-view.js +5 -3
  41. package/dist/lens-view.js.map +1 -1
  42. package/dist/migration-registry-loader.d.ts +121 -0
  43. package/dist/migration-registry-loader.d.ts.map +1 -0
  44. package/dist/migration-registry-loader.js +309 -0
  45. package/dist/migration-registry-loader.js.map +1 -0
  46. package/dist/migrations-store.d.ts +141 -0
  47. package/dist/migrations-store.d.ts.map +1 -0
  48. package/dist/migrations-store.js +206 -0
  49. package/dist/migrations-store.js.map +1 -0
  50. package/dist/object-store.d.ts +26 -0
  51. package/dist/object-store.d.ts.map +1 -0
  52. package/dist/object-store.js +104 -0
  53. package/dist/object-store.js.map +1 -0
  54. package/dist/ops-registry.d.ts +44 -0
  55. package/dist/ops-registry.d.ts.map +1 -0
  56. package/dist/ops-registry.js +1223 -0
  57. package/dist/ops-registry.js.map +1 -0
  58. package/dist/orientation.d.ts +16 -0
  59. package/dist/orientation.d.ts.map +1 -0
  60. package/dist/orientation.js +49 -0
  61. package/dist/orientation.js.map +1 -0
  62. package/dist/promote-item.d.ts +28 -0
  63. package/dist/promote-item.d.ts.map +1 -0
  64. package/dist/promote-item.js +257 -0
  65. package/dist/promote-item.js.map +1 -0
  66. package/dist/read-element.d.ts +129 -0
  67. package/dist/read-element.d.ts.map +1 -0
  68. package/dist/read-element.js +296 -0
  69. package/dist/read-element.js.map +1 -0
  70. package/dist/rename-canonical-id.d.ts.map +1 -1
  71. package/dist/rename-canonical-id.js +22 -13
  72. package/dist/rename-canonical-id.js.map +1 -1
  73. package/dist/roadmap-plan.d.ts.map +1 -1
  74. package/dist/roadmap-plan.js +23 -13
  75. package/dist/roadmap-plan.js.map +1 -1
  76. package/dist/schema-validator.d.ts +1 -0
  77. package/dist/schema-validator.d.ts.map +1 -1
  78. package/dist/schema-validator.js +7 -3
  79. package/dist/schema-validator.js.map +1 -1
  80. package/dist/schema-write.d.ts +78 -4
  81. package/dist/schema-write.d.ts.map +1 -1
  82. package/dist/schema-write.js +297 -34
  83. package/dist/schema-write.js.map +1 -1
  84. package/dist/status-vocab.d.ts +1 -0
  85. package/dist/status-vocab.d.ts.map +1 -1
  86. package/dist/status-vocab.js +4 -0
  87. package/dist/status-vocab.js.map +1 -1
  88. package/dist/truncate.d.ts +34 -0
  89. package/dist/truncate.d.ts.map +1 -0
  90. package/dist/truncate.js +85 -0
  91. package/dist/truncate.js.map +1 -0
  92. package/dist/write-schema-migration-tool.d.ts +46 -0
  93. package/dist/write-schema-migration-tool.d.ts.map +1 -0
  94. package/dist/write-schema-migration-tool.js +115 -0
  95. package/dist/write-schema-migration-tool.js.map +1 -0
  96. package/package.json +35 -2
  97. package/samples/blocks/work-orders.json +3 -0
  98. package/samples/conception.json +22 -3
  99. package/samples/migrations.json +133 -0
  100. package/samples/schemas/context-contracts.schema.json +19 -4
  101. package/samples/schemas/conventions.schema.json +68 -12
  102. package/samples/schemas/decisions.schema.json +125 -28
  103. package/samples/schemas/features.schema.json +140 -26
  104. package/samples/schemas/framework-gaps.schema.json +171 -26
  105. package/samples/schemas/issues.schema.json +86 -13
  106. package/samples/schemas/layer-plans.schema.json +195 -35
  107. package/samples/schemas/phase.schema.json +18 -3
  108. package/samples/schemas/rationale.schema.json +37 -7
  109. package/samples/schemas/requirements.schema.json +77 -10
  110. package/samples/schemas/research.schema.json +169 -36
  111. package/samples/schemas/spec-reviews.schema.json +88 -18
  112. package/samples/schemas/story.schema.json +18 -3
  113. package/samples/schemas/tasks.schema.json +66 -10
  114. package/samples/schemas/verification.schema.json +70 -12
  115. package/samples/schemas/work-orders.schema.json +195 -0
  116. package/schemas/bootstrap.schema.json +18 -6
  117. package/schemas/config.schema.json +58 -26
  118. package/schemas/context-registry.schema.json +46 -0
  119. package/schemas/migrations.schema.json +123 -0
  120. package/schemas/relations.schema.json +41 -10
  121. package/skill-narrative.md +42 -21
  122. package/skills/pi-context/SKILL.md +159 -65
  123. package/skills/pi-context/references/bundled-resources.md +7 -2
@@ -0,0 +1,421 @@
1
+ /**
2
+ * citation-rot-scanner — rigorous AST + JSON + markdown/YAML body scanner that
3
+ * surfaces pi-project-workflows-repo canonical_id citations (FGAP-NNN /
4
+ * DEC-NNNN / FEAT-NNN / TASK-NNN / VER-NNN / REVIEW-N / RAT-N / CTX-N / WO-N /
5
+ * STORY-N / PLAN-N / REQ-N / R-NNNN / ISSUE-N / issue-N / PHASE-NNN+ / JI-NNN)
6
+ * inside shipped artifacts of all monorepo packages.
7
+ *
8
+ * Aim: replace the naive line-by-line CITATION_RE scan that landed at FGAP-130
9
+ * (commit 4fd28a6) with an AST-aware enumerator that discriminates by node
10
+ * type and JSON path. The naive scan carried six fragility classes:
11
+ * (a) multi-line strings not captured by line iteration,
12
+ * (b) JSDoc / comment exclusion based on line-prefix regex producing both
13
+ * false-negative and false-positive depending on indentation,
14
+ * (c) "e.g." + "NNN" heuristic ambiguous in both directions,
15
+ * (d) enum-value false-positive when a schema's enum legitimately encodes a
16
+ * canonical_id-shaped constraint,
17
+ * (e) path-suffix-only structural carve-out imprecise (whole-file exclusion
18
+ * loses per-item discrimination),
19
+ * (f) JSDoc-vs-tool-description string-literal ambiguity (both live in .ts
20
+ * text but only the latter ships to the operator surface).
21
+ *
22
+ * This module addresses each: TypeScript compiler API SyntaxKind discriminates
23
+ * comment trivia from CallExpression string-literal arguments; the JSON walker
24
+ * tracks JSONPath + carves out item-level `id` properties only when the path
25
+ * matches the structural-seed-data shape; markdown + YAML body text is matched
26
+ * via full-text regex (catches multi-line straddle).
27
+ *
28
+ * Failure-mode contract: scanner is purely enumerative. The calling test
29
+ * renders results as hard refusal with full per-hit detail; the scanner itself
30
+ * never warns, never suggests rewrites, and exposes no exclusion list at the
31
+ * call site (carve-outs are coded internally per the aim above).
32
+ */
33
+ import fs from "node:fs";
34
+ import path from "node:path";
35
+ import ts from "typescript";
36
+ /**
37
+ * Canonical-id regex shared across all surfaces. Mirrors the regex from
38
+ * samples-catalog.test.ts (commit 4fd28a6) extended with JI- (JIT-agents
39
+ * intentions tracker prefix surfaced in pi-agent-dispatch substrate).
40
+ *
41
+ * The regex deliberately does NOT carve out "e.g." prose context — at the
42
+ * surface boundary the scanner is enumerative; carve-outs happen one level up
43
+ * (JSONPath-aware skip for structural seed-data id, AST-aware skip for JSDoc
44
+ * + line-comment nodes).
45
+ */
46
+ const CITATION_RE = /\b(FGAP-\d{3}|DEC-\d{4}|FEAT-\d{3}|TASK-\d{3}|VER-\d{3}|REVIEW-\d+|RAT-\d+|CTX-\d+|WO-\d+|STORY-\d+|PLAN-\d+|REQ-\d+|R-\d{4}|ISSUE-\d+|issue-\d+|PHASE-\d{3,}|JI-\d{3})\b/;
47
+ /** Global variant used to enumerate every occurrence in a multi-line body. */
48
+ const CITATION_RE_G = new RegExp(CITATION_RE.source, "g");
49
+ // ─── AST surface (.ts files) ───────────────────────────────────────────────
50
+ /**
51
+ * AST predicate: identifies CallExpression nodes whose callee is either
52
+ * `pi.registerTool(...)` (the canonical Pi tool registration site) or a bare
53
+ * `registerTool(...)` identifier call. Only string-literal arguments inside
54
+ * these call trees (and their nested Type.X({description: ...}) parameter
55
+ * descriptors) are considered operator-facing.
56
+ */
57
+ function isToolRegistrationCall(node) {
58
+ const callee = node.expression;
59
+ if (ts.isPropertyAccessExpression(callee)) {
60
+ // pi.registerTool / context.registerTool / this.registerTool — any
61
+ // PropertyAccessExpression whose right-hand identifier is registerTool.
62
+ return ts.isIdentifier(callee.name) && callee.name.text === "registerTool";
63
+ }
64
+ if (ts.isIdentifier(callee)) {
65
+ return callee.text === "registerTool";
66
+ }
67
+ return false;
68
+ }
69
+ /**
70
+ * AST predicate: identifies CallExpression nodes whose callee is a member of
71
+ * the `Type` namespace (typebox: Type.String, Type.Object, Type.Array, etc.).
72
+ * These calls carry `{ description: <literal> }` argument objects that ship to
73
+ * the tool-parameter operator surface.
74
+ */
75
+ function isTypeBoxCall(node) {
76
+ const callee = node.expression;
77
+ if (!ts.isPropertyAccessExpression(callee))
78
+ return false;
79
+ if (!ts.isIdentifier(callee.expression))
80
+ return false;
81
+ return callee.expression.text === "Type";
82
+ }
83
+ /**
84
+ * AST predicate (FGAP-133): identifies NewExpression nodes whose constructor
85
+ * identifier ends in `Error`. Matches both plain `new Error(...)` and custom
86
+ * subclasses (e.g. `new CommitAttestedRefusedError(...)`). Constructor first
87
+ * argument string-literal values flow to the operator surface via exception
88
+ * propagation when the error is thrown.
89
+ *
90
+ * Deliberately narrow: only constructors whose name suffix-matches `Error`.
91
+ * Non-Error constructors (data classes, builders) emit value objects that do
92
+ * not necessarily reach an operator-visible surface; including them would
93
+ * inflate noise without governance benefit.
94
+ */
95
+ function isErrorConstructorCall(node) {
96
+ const callee = node.expression;
97
+ if (ts.isIdentifier(callee)) {
98
+ return /Error$/.test(callee.text);
99
+ }
100
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
101
+ return /Error$/.test(callee.name.text);
102
+ }
103
+ return false;
104
+ }
105
+ /**
106
+ * Walk a .ts source file and enumerate every CallExpression whose callee is
107
+ * a tool-registration entry-point or a typebox descriptor; pull description
108
+ * string-literal values out of those subtrees. Additionally (FGAP-133)
109
+ * enumerate NewExpression argument string-literals where the constructor
110
+ * identifier ends in `Error` — error messages are an operator-visible
111
+ * surface via exception propagation.
112
+ */
113
+ function scanTsFile(file, packageDir) {
114
+ const text = fs.readFileSync(file, "utf-8");
115
+ const sourceFile = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, /* setParentNodes */ true);
116
+ const descriptionStrings = [];
117
+ const errorMessageStrings = [];
118
+ // De-dup string-literal node positions: when an inner Type.X() call sits
119
+ // inside an outer registerTool({ parameters: Type.Object(...) }) tree,
120
+ // both the outer registerTool walk + the standalone module-level Type.X
121
+ // walk would otherwise visit the same description literal. The seen-set
122
+ // keys by literal-node start position (unique within a SourceFile).
123
+ const seenLiteralStart = new Set();
124
+ const collect = (arg) => {
125
+ const buf = [];
126
+ const inner = (n) => {
127
+ if (ts.isObjectLiteralExpression(n)) {
128
+ for (const prop of n.properties) {
129
+ if (!ts.isPropertyAssignment(prop))
130
+ continue;
131
+ const name = ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : undefined;
132
+ if (name === "description") {
133
+ if (ts.isStringLiteral(prop.initializer) || ts.isNoSubstitutionTemplateLiteral(prop.initializer)) {
134
+ buf.push({
135
+ line: sourceFile.getLineAndCharacterOfPosition(prop.initializer.getStart()).line + 1,
136
+ value: prop.initializer.text,
137
+ start: prop.initializer.getStart(),
138
+ });
139
+ }
140
+ continue;
141
+ }
142
+ ts.forEachChild(prop.initializer, inner);
143
+ }
144
+ return;
145
+ }
146
+ if (ts.isCallExpression(n)) {
147
+ for (const a of n.arguments)
148
+ inner(a);
149
+ return;
150
+ }
151
+ ts.forEachChild(n, inner);
152
+ };
153
+ inner(arg);
154
+ for (const entry of buf) {
155
+ if (seenLiteralStart.has(entry.start))
156
+ continue;
157
+ seenLiteralStart.add(entry.start);
158
+ descriptionStrings.push({ line: entry.line, value: entry.value });
159
+ }
160
+ };
161
+ const visit = (node) => {
162
+ if (ts.isCallExpression(node)) {
163
+ if (isToolRegistrationCall(node)) {
164
+ for (const arg of node.arguments)
165
+ collect(arg);
166
+ }
167
+ else if (isTypeBoxCall(node)) {
168
+ // Typebox calls outside a registerTool tree still ship to the
169
+ // operator surface when they appear in module-level exported
170
+ // parameter schemas — scan their description args too.
171
+ for (const arg of node.arguments)
172
+ collect(arg);
173
+ }
174
+ }
175
+ else if (ts.isNewExpression(node) && isErrorConstructorCall(node)) {
176
+ // FGAP-133: extract first string-literal argument from Error
177
+ // constructors. Only the first arg is examined — Error subclasses
178
+ // conventionally place the human-readable reason there. Template
179
+ // literals with substitutions are not extracted (their static
180
+ // text fragments would need separate handling; the empirical
181
+ // audit found zero such hits, so the simpler form suffices).
182
+ const args = node.arguments;
183
+ if (args && args.length > 0) {
184
+ const first = args[0];
185
+ if (ts.isStringLiteral(first) || ts.isNoSubstitutionTemplateLiteral(first)) {
186
+ errorMessageStrings.push({
187
+ line: sourceFile.getLineAndCharacterOfPosition(first.getStart()).line + 1,
188
+ value: first.text,
189
+ });
190
+ }
191
+ }
192
+ }
193
+ ts.forEachChild(node, visit);
194
+ };
195
+ visit(sourceFile);
196
+ const hits = [];
197
+ for (const entry of descriptionStrings) {
198
+ const matches = entry.value.matchAll(CITATION_RE_G);
199
+ for (const m of matches) {
200
+ hits.push({
201
+ packageDir,
202
+ file,
203
+ line: entry.line,
204
+ surface: "ast-string-literal",
205
+ value: entry.value,
206
+ matched: m[0],
207
+ });
208
+ }
209
+ }
210
+ for (const entry of errorMessageStrings) {
211
+ const matches = entry.value.matchAll(CITATION_RE_G);
212
+ for (const m of matches) {
213
+ hits.push({
214
+ packageDir,
215
+ file,
216
+ line: entry.line,
217
+ surface: "ast-error-message",
218
+ value: entry.value,
219
+ matched: m[0],
220
+ });
221
+ }
222
+ }
223
+ return hits;
224
+ }
225
+ // ─── JSON surface ────────────────────────────────────────────────────────
226
+ /**
227
+ * Heuristic: is this JSON file a JSON-schema file (carves out `pattern` and
228
+ * `enum` string values that legitimately encode canonical-id constraints)?
229
+ */
230
+ function isSchemaFile(file) {
231
+ return file.endsWith(".schema.json");
232
+ }
233
+ /**
234
+ * Heuristic: is this JSON file an array-of-items seed-data file (carves out
235
+ * top-level `id` string values that legitimately encode each item's
236
+ * canonical_id)? Covers:
237
+ * - samples/blocks/ — packaged conception seed data
238
+ * - .project/ — live substrate
239
+ * - registry/blocks/ + defaults/blocks/ — legacy on-disk fixtures
240
+ * (structurally identical to samples/blocks/; the scanner gates them
241
+ * defensively per FGAP-131 plan step 3 scope)
242
+ * - test-fixtures/blocks/ — per-package test-fixture block-data, the same
243
+ * item-data category as samples/blocks/ (array-of-items files whose
244
+ * top-level `id` values are canonical_ids, not citations)
245
+ */
246
+ function isItemsFile(file) {
247
+ const norm = file.replace(/\\/g, "/");
248
+ return (norm.includes("/samples/blocks/") ||
249
+ norm.includes("/.project/") ||
250
+ norm.includes("/registry/blocks/") ||
251
+ norm.includes("/defaults/blocks/") ||
252
+ norm.includes("/test-fixtures/blocks/"));
253
+ }
254
+ /**
255
+ * Recursive JSON walker; tracks JSONPath; carves out per-file allowlist of
256
+ * structural-value paths (id values in items files; pattern / enum string
257
+ * values in schema files).
258
+ */
259
+ function visitJsonNode(node, jsonPath, parentKey, file, packageDir, hits) {
260
+ if (typeof node === "string") {
261
+ // Item-level id at array-element top-level under samples/blocks/ or
262
+ // .project/ — structural canonical_id, not a citation.
263
+ if (parentKey === "id" && isItemsFile(file))
264
+ return;
265
+ // Schema pattern / enum string values in *.schema.json — allowed to
266
+ // encode canonical-id form as a constraint, not a citation.
267
+ if (isSchemaFile(file) && (parentKey === "pattern" || parentKey === "enum"))
268
+ return;
269
+ const matches = node.matchAll(CITATION_RE_G);
270
+ for (const m of matches) {
271
+ hits.push({
272
+ packageDir,
273
+ file,
274
+ line: 0,
275
+ surface: "json-string-value",
276
+ path: jsonPath || "$",
277
+ value: node,
278
+ matched: m[0],
279
+ });
280
+ }
281
+ return;
282
+ }
283
+ if (Array.isArray(node)) {
284
+ node.forEach((v, i) => {
285
+ visitJsonNode(v, `${jsonPath}[${i}]`, null, file, packageDir, hits);
286
+ });
287
+ return;
288
+ }
289
+ if (node && typeof node === "object") {
290
+ for (const [k, v] of Object.entries(node)) {
291
+ // Schema enum is an Array; the enum-string carve-out fires when
292
+ // the parent property name is `enum` (the Array's parentKey was set
293
+ // above for individual elements as `null` — re-attribute via
294
+ // jsonPath suffix-check inside the string branch).
295
+ visitJsonNode(v, jsonPath ? `${jsonPath}.${k}` : k, k, file, packageDir, hits);
296
+ }
297
+ }
298
+ }
299
+ function scanJsonFile(file, packageDir) {
300
+ const text = fs.readFileSync(file, "utf-8");
301
+ let parsed;
302
+ try {
303
+ parsed = JSON.parse(text);
304
+ }
305
+ catch {
306
+ // Unparseable JSON — defer to the calling test to surface separately.
307
+ return [];
308
+ }
309
+ const hits = [];
310
+ visitJsonNode(parsed, "", null, file, packageDir, hits);
311
+ // Augment line numbers via a coarse scan (the JSON walker doesn't track
312
+ // source positions; line is derived by string-index search over the file
313
+ // text on a best-effort basis — citation values are short + distinctive).
314
+ if (hits.length > 0) {
315
+ const lines = text.split("\n");
316
+ for (const hit of hits) {
317
+ for (let i = 0; i < lines.length; i++) {
318
+ if (lines[i].includes(hit.matched)) {
319
+ hit.line = i + 1;
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ }
325
+ // JSON enum element strings — when the parent ARRAY's property name was
326
+ // `enum` inside a schema file, the per-element walker lost the parent key.
327
+ // Filter those out here via JSONPath suffix-check.
328
+ return hits.filter((h) => {
329
+ if (!isSchemaFile(file))
330
+ return true;
331
+ // jsonPath like "...enum[0]" — strip trailing "[N]" and check.
332
+ const stripped = (h.path ?? "").replace(/\[\d+\]$/, "");
333
+ if (stripped.endsWith(".enum") || stripped === "enum")
334
+ return false;
335
+ return true;
336
+ });
337
+ }
338
+ // ─── Markdown / YAML body surface ─────────────────────────────────────────
339
+ /**
340
+ * Full-text body scan; catches multi-line straddle. Each match's line is
341
+ * derived by counting newlines up to the match index — robust to multi-line
342
+ * citation references that the FGAP-130 naive line-by-line scan misses.
343
+ */
344
+ function scanTextBody(file, packageDir, surface) {
345
+ const text = fs.readFileSync(file, "utf-8");
346
+ const hits = [];
347
+ const matches = text.matchAll(CITATION_RE_G);
348
+ for (const m of matches) {
349
+ const idx = m.index ?? 0;
350
+ const line = text.slice(0, idx).split("\n").length;
351
+ // Carry only the surrounding ~120 chars for readability in the test
352
+ // failure message.
353
+ const start = Math.max(0, idx - 60);
354
+ const end = Math.min(text.length, idx + 60);
355
+ hits.push({
356
+ packageDir,
357
+ file,
358
+ line,
359
+ surface,
360
+ value: text.slice(start, end),
361
+ matched: m[0],
362
+ });
363
+ }
364
+ return hits;
365
+ }
366
+ // ─── File walker ─────────────────────────────────────────────────────────
367
+ function walkPackage(dir, root, exclusions, files) {
368
+ if (!fs.existsSync(dir))
369
+ return;
370
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
371
+ const full = path.join(dir, entry.name);
372
+ // Always-exclude directories.
373
+ if (entry.isDirectory()) {
374
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git")
375
+ continue;
376
+ walkPackage(full, root, exclusions, files);
377
+ continue;
378
+ }
379
+ if (!entry.isFile())
380
+ continue;
381
+ // Test files never ship to the operator surface; structural-data
382
+ // fixtures live there + would generate noise.
383
+ if (full.endsWith(".test.ts") || full.endsWith(".test.js"))
384
+ continue;
385
+ // Caller-pinned exclusions.
386
+ if (exclusions.some((sub) => full.includes(sub)))
387
+ continue;
388
+ files.push({ abs: full, rel: path.relative(root, full) });
389
+ }
390
+ }
391
+ /**
392
+ * Entry point. Walks every packageDir; per-file dispatches to the
393
+ * AST / JSON / markdown / YAML scanner; returns a flat ScanResult array.
394
+ */
395
+ export function scanForCitationRot(opts) {
396
+ const exclusions = opts.sourceFileExclusions ?? [];
397
+ const results = [];
398
+ for (const pkgDir of opts.packageDirs) {
399
+ const abs = path.isAbsolute(pkgDir) ? pkgDir : path.join(opts.projectRoot, pkgDir);
400
+ const packageDir = path.relative(opts.projectRoot, abs) || abs;
401
+ const files = [];
402
+ walkPackage(abs, opts.projectRoot, exclusions, files);
403
+ for (const f of files) {
404
+ const lower = f.abs.toLowerCase();
405
+ if (lower.endsWith(".ts") || lower.endsWith(".tsx")) {
406
+ results.push(...scanTsFile(f.abs, packageDir));
407
+ }
408
+ else if (lower.endsWith(".json")) {
409
+ results.push(...scanJsonFile(f.abs, packageDir));
410
+ }
411
+ else if (lower.endsWith(".md")) {
412
+ results.push(...scanTextBody(f.abs, packageDir, "markdown-body"));
413
+ }
414
+ else if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
415
+ results.push(...scanTextBody(f.abs, packageDir, "yaml-value"));
416
+ }
417
+ }
418
+ }
419
+ return results;
420
+ }
421
+ //# sourceMappingURL=citation-rot-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citation-rot-scanner.js","sourceRoot":"","sources":["../src/citation-rot-scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * RFC 8785 (JCS) canonical JSON serialization of `value`.
3
+ *
4
+ * `canonicalize` returns `undefined` for inputs JSON itself cannot represent
5
+ * (e.g. a bare `undefined`); content hashing operates on concrete JSON
6
+ * objects, so an `undefined` result is a programming error at the call site
7
+ * rather than a representable content state — surface it loudly instead of
8
+ * hashing the string "undefined".
9
+ */
10
+ export declare function canonicalJson(value: unknown): string;
11
+ /**
12
+ * SHA-256 hex digest of an already-canonicalized string. Lowercase hex,
13
+ * 64 characters. Kept separate from `canonicalJson` so callers that already
14
+ * hold a canonical string (e.g. when composing a Merkle node) can hash without
15
+ * re-canonicalizing.
16
+ */
17
+ export declare function sha256Hex(canonical: string): string;
18
+ /**
19
+ * Content hash of a JSON content object: `sha256Hex(canonicalJson(content))`.
20
+ * Key-order- and number-format-insensitive by construction (JCS), so any two
21
+ * objects with equal content produce the same hash regardless of how they were
22
+ * built.
23
+ */
24
+ export declare function computeContentHash(content: Record<string, unknown>): string;
25
+ //# sourceMappingURL=content-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.d.ts","sourceRoot":"","sources":["../src/content-hash.ts"],"names":[],"mappings":"AA6BA;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQpD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE3E"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Content-hash primitives for content-addressed substrate identity (Cycle 2,
3
+ * Phase A). Pure functions with no schema or filesystem knowledge: they map an
4
+ * arbitrary JSON value to a stable, canonical string and then to a SHA-256 hex
5
+ * digest.
6
+ *
7
+ * Canonicalization is RFC 8785 (JSON Canonicalization Scheme) via the
8
+ * `canonicalize` package, NOT `JSON.stringify`. JCS sorts object keys
9
+ * lexicographically by UTF-16 code unit and normalizes number serialization,
10
+ * so two structurally-equal values that differ only in key insertion order (or
11
+ * in numeric formatting) canonicalize to the same string and therefore hash
12
+ * identically. That property is what makes the content hash a faithful
13
+ * identity for a value's content rather than its byte layout.
14
+ *
15
+ * Dormant this cycle: nothing in any write path calls these yet (Cycle 3 wires
16
+ * `computeContentHash(contentProjection(...))`). This module is infrastructure
17
+ * (like block-api) and is intentionally not re-exported from `index.ts`.
18
+ */
19
+ import { createHash } from "node:crypto";
20
+ import { createRequire } from "node:module";
21
+ // `canonicalize` ships CJS (`module.exports = fn`) but types it as an ESM
22
+ // `export default function`, which the default import cannot bind as callable
23
+ // under module:Node16. Consume it via createRequire so the binding matches the
24
+ // CJS runtime (require returns the function directly), typed to its real call
25
+ // signature (RFC 8785 JCS; returns undefined for non-representable inputs).
26
+ const require = createRequire(import.meta.url);
27
+ const canonicalize = require("canonicalize");
28
+ /**
29
+ * RFC 8785 (JCS) canonical JSON serialization of `value`.
30
+ *
31
+ * `canonicalize` returns `undefined` for inputs JSON itself cannot represent
32
+ * (e.g. a bare `undefined`); content hashing operates on concrete JSON
33
+ * objects, so an `undefined` result is a programming error at the call site
34
+ * rather than a representable content state — surface it loudly instead of
35
+ * hashing the string "undefined".
36
+ */
37
+ export function canonicalJson(value) {
38
+ const out = canonicalize(value);
39
+ if (typeof out !== "string") {
40
+ throw new Error(`canonicalJson: canonicalize() did not return a string (got ${typeof out}); value is not representable as canonical JSON`);
41
+ }
42
+ return out;
43
+ }
44
+ /**
45
+ * SHA-256 hex digest of an already-canonicalized string. Lowercase hex,
46
+ * 64 characters. Kept separate from `canonicalJson` so callers that already
47
+ * hold a canonical string (e.g. when composing a Merkle node) can hash without
48
+ * re-canonicalizing.
49
+ */
50
+ export function sha256Hex(canonical) {
51
+ return createHash("sha256").update(canonical).digest("hex");
52
+ }
53
+ /**
54
+ * Content hash of a JSON content object: `sha256Hex(canonicalJson(content))`.
55
+ * Key-order- and number-format-insensitive by construction (JCS), so any two
56
+ * objects with equal content produce the same hash regardless of how they were
57
+ * built.
58
+ */
59
+ export function computeContentHash(content) {
60
+ return sha256Hex(canonicalJson(content));
61
+ }
62
+ //# sourceMappingURL=content-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.js","sourceRoot":"","sources":["../src/content-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,4EAA4E;AAC5E,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CAA2C,CAAC;AAEvF;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC3C,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACd,8DAA8D,OAAO,GAAG,iDAAiD,CACzH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB;IAC1C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgC;IAClE,OAAO,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;AAC1C,CAAC"}