@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.
- package/CHANGELOG.md +69 -15
- package/README.md +71 -18
- package/dist/block-api.d.ts +165 -18
- package/dist/block-api.d.ts.map +1 -1
- package/dist/block-api.js +800 -57
- package/dist/block-api.js.map +1 -1
- package/dist/citation-rot-scanner.d.ts +80 -0
- package/dist/citation-rot-scanner.d.ts.map +1 -0
- package/dist/citation-rot-scanner.js +421 -0
- package/dist/citation-rot-scanner.js.map +1 -0
- package/dist/content-hash.d.ts +25 -0
- package/dist/content-hash.d.ts.map +1 -0
- package/dist/content-hash.js +62 -0
- package/dist/content-hash.js.map +1 -0
- package/dist/context-dir.d.ts +126 -1
- package/dist/context-dir.d.ts.map +1 -1
- package/dist/context-dir.js +208 -4
- package/dist/context-dir.js.map +1 -1
- package/dist/context-registry.d.ts +122 -0
- package/dist/context-registry.d.ts.map +1 -0
- package/dist/context-registry.js +189 -0
- package/dist/context-registry.js.map +1 -0
- package/dist/context-sdk.d.ts +179 -12
- package/dist/context-sdk.d.ts.map +1 -1
- package/dist/context-sdk.js +536 -101
- package/dist/context-sdk.js.map +1 -1
- package/dist/context.d.ts +222 -4
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +244 -56
- package/dist/context.js.map +1 -1
- package/dist/git-env.d.ts +2 -0
- package/dist/git-env.d.ts.map +1 -0
- package/dist/git-env.js +29 -0
- package/dist/git-env.js.map +1 -0
- package/dist/index.d.ts +124 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +440 -1125
- package/dist/index.js.map +1 -1
- package/dist/lens-view.d.ts.map +1 -1
- package/dist/lens-view.js +5 -3
- package/dist/lens-view.js.map +1 -1
- package/dist/migration-registry-loader.d.ts +121 -0
- package/dist/migration-registry-loader.d.ts.map +1 -0
- package/dist/migration-registry-loader.js +309 -0
- package/dist/migration-registry-loader.js.map +1 -0
- package/dist/migrations-store.d.ts +141 -0
- package/dist/migrations-store.d.ts.map +1 -0
- package/dist/migrations-store.js +206 -0
- package/dist/migrations-store.js.map +1 -0
- package/dist/object-store.d.ts +26 -0
- package/dist/object-store.d.ts.map +1 -0
- package/dist/object-store.js +104 -0
- package/dist/object-store.js.map +1 -0
- package/dist/ops-registry.d.ts +44 -0
- package/dist/ops-registry.d.ts.map +1 -0
- package/dist/ops-registry.js +1223 -0
- package/dist/ops-registry.js.map +1 -0
- package/dist/orientation.d.ts +16 -0
- package/dist/orientation.d.ts.map +1 -0
- package/dist/orientation.js +49 -0
- package/dist/orientation.js.map +1 -0
- package/dist/promote-item.d.ts +28 -0
- package/dist/promote-item.d.ts.map +1 -0
- package/dist/promote-item.js +257 -0
- package/dist/promote-item.js.map +1 -0
- package/dist/read-element.d.ts +129 -0
- package/dist/read-element.d.ts.map +1 -0
- package/dist/read-element.js +296 -0
- package/dist/read-element.js.map +1 -0
- package/dist/rename-canonical-id.d.ts.map +1 -1
- package/dist/rename-canonical-id.js +22 -13
- package/dist/rename-canonical-id.js.map +1 -1
- package/dist/roadmap-plan.d.ts.map +1 -1
- package/dist/roadmap-plan.js +23 -13
- package/dist/roadmap-plan.js.map +1 -1
- package/dist/schema-validator.d.ts +1 -0
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +7 -3
- package/dist/schema-validator.js.map +1 -1
- package/dist/schema-write.d.ts +78 -4
- package/dist/schema-write.d.ts.map +1 -1
- package/dist/schema-write.js +297 -34
- package/dist/schema-write.js.map +1 -1
- package/dist/status-vocab.d.ts +1 -0
- package/dist/status-vocab.d.ts.map +1 -1
- package/dist/status-vocab.js +4 -0
- package/dist/status-vocab.js.map +1 -1
- package/dist/truncate.d.ts +34 -0
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +85 -0
- package/dist/truncate.js.map +1 -0
- package/dist/write-schema-migration-tool.d.ts +46 -0
- package/dist/write-schema-migration-tool.d.ts.map +1 -0
- package/dist/write-schema-migration-tool.js +115 -0
- package/dist/write-schema-migration-tool.js.map +1 -0
- package/package.json +35 -2
- package/samples/blocks/work-orders.json +3 -0
- package/samples/conception.json +22 -3
- package/samples/migrations.json +133 -0
- package/samples/schemas/context-contracts.schema.json +19 -4
- package/samples/schemas/conventions.schema.json +68 -12
- package/samples/schemas/decisions.schema.json +125 -28
- package/samples/schemas/features.schema.json +140 -26
- package/samples/schemas/framework-gaps.schema.json +171 -26
- package/samples/schemas/issues.schema.json +86 -13
- package/samples/schemas/layer-plans.schema.json +195 -35
- package/samples/schemas/phase.schema.json +18 -3
- package/samples/schemas/rationale.schema.json +37 -7
- package/samples/schemas/requirements.schema.json +77 -10
- package/samples/schemas/research.schema.json +169 -36
- package/samples/schemas/spec-reviews.schema.json +88 -18
- package/samples/schemas/story.schema.json +18 -3
- package/samples/schemas/tasks.schema.json +66 -10
- package/samples/schemas/verification.schema.json +70 -12
- package/samples/schemas/work-orders.schema.json +195 -0
- package/schemas/bootstrap.schema.json +18 -6
- package/schemas/config.schema.json +58 -26
- package/schemas/context-registry.schema.json +46 -0
- package/schemas/migrations.schema.json +123 -0
- package/schemas/relations.schema.json +41 -10
- package/skill-narrative.md +42 -21
- package/skills/pi-context/SKILL.md +159 -65
- 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"}
|