@narrativetrace/cli 0.1.3 → 0.2.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/README.md +77 -6
- package/dist/cli-bin.cjs +234 -513
- package/dist/cli-bin.cjs.map +1 -1
- package/dist/cli-bin.d.cts +1 -0
- package/dist/cli-bin.d.ts +1 -0
- package/dist/cli-bin.js +249 -13
- package/dist/cli-bin.js.map +1 -1
- package/dist/index.cjs +9 -520
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -91
- package/dist/index.d.ts +1 -91
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/agents/add-narrative-tracing/SKILL.md +124 -0
- package/skills/agents/narrativetrace-doctor/SKILL.md +49 -0
- package/skills/catalogue.json +17 -0
- package/skills/claude/add-narrative-tracing/SKILL.md +126 -0
- package/skills/claude/narrativetrace-doctor/SKILL.md +51 -0
- package/dist/chunk-WUK4GWFW.js +0 -523
- package/dist/chunk-WUK4GWFW.js.map +0 -1
package/dist/chunk-WUK4GWFW.js
DELETED
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
// src/semver-lite.ts
|
|
2
|
-
function parseVersion(raw) {
|
|
3
|
-
const cleaned = raw.trim().replace(/^v/, "");
|
|
4
|
-
const match = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(cleaned);
|
|
5
|
-
if (!match) return void 0;
|
|
6
|
-
return {
|
|
7
|
-
major: Number(match[1]),
|
|
8
|
-
minor: match[2] === void 0 ? 0 : Number(match[2]),
|
|
9
|
-
patch: match[3] === void 0 ? 0 : Number(match[3])
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
function compareVersions(left, right) {
|
|
13
|
-
if (left.major !== right.major) return left.major - right.major;
|
|
14
|
-
if (left.minor !== right.minor) return left.minor - right.minor;
|
|
15
|
-
return left.patch - right.patch;
|
|
16
|
-
}
|
|
17
|
-
function satisfiesGte(version, floor) {
|
|
18
|
-
return compareVersions(version, floor) >= 0;
|
|
19
|
-
}
|
|
20
|
-
function satisfiesCaret(version, base) {
|
|
21
|
-
if (compareVersions(version, base) < 0) return false;
|
|
22
|
-
if (base.major > 0) return version.major === base.major;
|
|
23
|
-
if (base.minor > 0) return version.major === 0 && version.minor === base.minor;
|
|
24
|
-
return version.major === 0 && version.minor === 0 && version.patch === base.patch;
|
|
25
|
-
}
|
|
26
|
-
function satisfiesClause(version, trimmed) {
|
|
27
|
-
if (trimmed.startsWith("^")) {
|
|
28
|
-
const base = parseVersion(trimmed.slice(1));
|
|
29
|
-
return base !== void 0 && satisfiesCaret(version, base);
|
|
30
|
-
}
|
|
31
|
-
if (trimmed.startsWith(">=")) {
|
|
32
|
-
const floor = parseVersion(trimmed.slice(2));
|
|
33
|
-
return floor !== void 0 && satisfiesGte(version, floor);
|
|
34
|
-
}
|
|
35
|
-
if (/\.x\b/.test(trimmed)) {
|
|
36
|
-
const base = parseVersion(trimmed.replace(/\.x/g, ".0"));
|
|
37
|
-
return base !== void 0 && satisfiesCaret(version, base);
|
|
38
|
-
}
|
|
39
|
-
const exact = parseVersion(trimmed);
|
|
40
|
-
return exact !== void 0 && compareVersions(version, exact) === 0;
|
|
41
|
-
}
|
|
42
|
-
function satisfiesRange(versionRaw, range) {
|
|
43
|
-
const version = parseVersion(versionRaw);
|
|
44
|
-
if (!version) return false;
|
|
45
|
-
return range.split("||").map((clause) => clause.trim()).filter(Boolean).some((clause) => satisfiesClause(version, clause));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/doctor/doc-urls.ts
|
|
49
|
-
var BASE = "https://github.com/narrativetrace/narrativetrace-typescript/blob/main/documentation/";
|
|
50
|
-
var DOC = {
|
|
51
|
-
installationPrerequisites: `${BASE}installation-guide.md#prerequisites`,
|
|
52
|
-
installationDependencies: `${BASE}installation-guide.md#1-add-dependencies`,
|
|
53
|
-
vitestConfiguration: `${BASE}configuration-guide.md#2-vitest-configuration`,
|
|
54
|
-
whereSettingsComeFrom: `${BASE}configuration-guide.md#2b-where-settings-come-from`,
|
|
55
|
-
proxyOptions: `${BASE}configuration-guide.md#6-proxy-options`,
|
|
56
|
-
eventPipelineBuffering: `${BASE}configuration-guide.md#8-event-pipeline-buffering-bufferedeventconsumer`,
|
|
57
|
-
noTraceFilesWritten: `${BASE}troubleshooting.md#no-trace-files-are-written`,
|
|
58
|
-
manualParameterNames: `${BASE}troubleshooting.md#parameters-show-as-arg0-arg1`,
|
|
59
|
-
redactionSurfaceBySurface: `${BASE}privacy-and-redaction.md#redaction-surface-by-surface`,
|
|
60
|
-
approvalTracesEndToEnd: `${BASE}structural-trace-format.md#approval-traces-end-to-end`,
|
|
61
|
-
sixtySecondsNewProject: `${BASE}sixty-seconds.md#1-new-project-add-the-packages`
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// src/doctor/finding.ts
|
|
65
|
-
function pass(id, message, docUrl) {
|
|
66
|
-
return { id, status: "pass", message, fix: "", docUrl };
|
|
67
|
-
}
|
|
68
|
-
function fail(id, message, fix, docUrl) {
|
|
69
|
-
return { id, status: "fail", message, fix, docUrl };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// src/doctor/checks/approval-traces.ts
|
|
73
|
-
var ID = "trap.approval-traces";
|
|
74
|
-
var FIX = "Review each .received.nt diff against its .approved.nt baseline, then run pnpm run approve-narratives (or narrativetrace-approve) to promote it, or delete it if the change was wrong \u2014 never commit a .received.nt file.";
|
|
75
|
-
var checkApprovalTraces = (snapshot) => {
|
|
76
|
-
const paths = [...snapshot.approvedDirFiles.keys()];
|
|
77
|
-
if (paths.length === 0) {
|
|
78
|
-
return pass(
|
|
79
|
-
ID,
|
|
80
|
-
"no approval traces configured yet \u2014 nothing to check",
|
|
81
|
-
DOC.approvalTracesEndToEnd
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
const received = paths.filter((p) => p.endsWith(".received.nt"));
|
|
85
|
-
if (received.length > 0) {
|
|
86
|
-
const message2 = `${received.length} stale received trace(s) found: ${received.join(", ")}`;
|
|
87
|
-
return fail(ID, message2, FIX, DOC.approvalTracesEndToEnd);
|
|
88
|
-
}
|
|
89
|
-
const approved = paths.filter((p) => p.endsWith(".approved.nt"));
|
|
90
|
-
const message = `${approved.length} approved trace(s) found, no pending received diffs`;
|
|
91
|
-
return pass(ID, message, DOC.approvalTracesEndToEnd);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// src/doctor/checks/llms-before-you-start.ts
|
|
95
|
-
var ID2 = "trap.llms-before-you-start";
|
|
96
|
-
var PLAIN_JS = /\.js$/;
|
|
97
|
-
var ESM_IMPORT = /^\s*import\s/m;
|
|
98
|
-
var FIX2 = "Run `npm pkg set type=module` before `npm add` \u2014 without it, Node throws SyntaxError: Cannot use import statement outside a module (pnpm/yarn default to it and never hit this).";
|
|
99
|
-
var checkLlmsBeforeYouStart = (snapshot) => {
|
|
100
|
-
if (snapshot.rootPackageJson?.type === "module") {
|
|
101
|
-
return pass(ID2, 'package.json declares "type": "module"', DOC.sixtySecondsNewProject);
|
|
102
|
-
}
|
|
103
|
-
const offender = [...snapshot.sourceFiles].find(
|
|
104
|
-
([path2, content]) => PLAIN_JS.test(path2) && ESM_IMPORT.test(content)
|
|
105
|
-
);
|
|
106
|
-
if (!offender) {
|
|
107
|
-
const message2 = 'no plain .js file uses ESM import syntax without "type": "module"';
|
|
108
|
-
return pass(ID2, message2, DOC.sixtySecondsNewProject);
|
|
109
|
-
}
|
|
110
|
-
const [path] = offender;
|
|
111
|
-
const message = `${path} uses ESM import syntax but package.json has no "type": "module"`;
|
|
112
|
-
return fail(ID2, message, FIX2, DOC.sixtySecondsNewProject);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// src/doctor/checks/node-engine.ts
|
|
116
|
-
var ID3 = "toolchain.node-engine";
|
|
117
|
-
var DEFAULT_ENGINE_RANGE = ">=20";
|
|
118
|
-
function requiredRange(snapshot) {
|
|
119
|
-
const core = snapshot.installedPackages.get("@narrativetrace/core");
|
|
120
|
-
const coreNode = snapshot.installedPackages.get("@narrativetrace/core-node");
|
|
121
|
-
return core?.engines?.node ?? coreNode?.engines?.node ?? DEFAULT_ENGINE_RANGE;
|
|
122
|
-
}
|
|
123
|
-
var checkNodeEngine = (snapshot) => {
|
|
124
|
-
const required = requiredRange(snapshot);
|
|
125
|
-
if (satisfiesRange(snapshot.nodeVersion, required)) {
|
|
126
|
-
return pass(
|
|
127
|
-
ID3,
|
|
128
|
-
`Node ${snapshot.nodeVersion} satisfies the required ${required}`,
|
|
129
|
-
DOC.installationPrerequisites
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
return fail(
|
|
133
|
-
ID3,
|
|
134
|
-
`Node ${snapshot.nodeVersion} does not satisfy the required ${required}`,
|
|
135
|
-
`Upgrade Node to a version satisfying ${required} (nvm, volta, asdf, or your CI image).`,
|
|
136
|
-
DOC.installationPrerequisites
|
|
137
|
-
);
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// src/doctor/checks/output-env.ts
|
|
141
|
-
var ID4 = "config.output-env";
|
|
142
|
-
var VALID_VALUES = /* @__PURE__ */ new Set(["true", "false"]);
|
|
143
|
-
var checkOutputEnv = (snapshot) => {
|
|
144
|
-
const raw = snapshot.env.NARRATIVETRACE_OUTPUT;
|
|
145
|
-
if (raw === void 0) {
|
|
146
|
-
return pass(
|
|
147
|
-
ID4,
|
|
148
|
-
"NARRATIVETRACE_OUTPUT is not set \u2014 output stays on its default (on)",
|
|
149
|
-
DOC.whereSettingsComeFrom
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
if (VALID_VALUES.has(raw.toLowerCase())) {
|
|
153
|
-
return pass(ID4, `NARRATIVETRACE_OUTPUT=${raw}`, DOC.whereSettingsComeFrom);
|
|
154
|
-
}
|
|
155
|
-
return fail(
|
|
156
|
-
ID4,
|
|
157
|
-
`NARRATIVETRACE_OUTPUT is set to '${raw}', which is neither "true" nor "false"`,
|
|
158
|
-
"Set NARRATIVETRACE_OUTPUT=true or NARRATIVETRACE_OUTPUT=false (or unset it) \u2014 any other value is read as a truthy string, most likely turning output on when you meant to turn it off.",
|
|
159
|
-
DOC.whereSettingsComeFrom
|
|
160
|
-
);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// src/doctor/checks/parameter-arg0.ts
|
|
164
|
-
var ID5 = "trap.parameter-arg0";
|
|
165
|
-
var ARG_PLACEHOLDER = /\barg0\b/;
|
|
166
|
-
var FIX3 = 'Pass parameter names explicitly: traceObject(target, context, { methodName: ["paramA", "paramB"] }) \u2014 required for classes you do not own, or when a build tool strips them.';
|
|
167
|
-
var checkParameterArg0 = (snapshot) => {
|
|
168
|
-
if (snapshot.outputFiles.size === 0) {
|
|
169
|
-
const message2 = "no rendered output found yet \u2014 run your tests or app once to check this";
|
|
170
|
-
return pass(ID5, message2, DOC.manualParameterNames);
|
|
171
|
-
}
|
|
172
|
-
const offender = [...snapshot.outputFiles].find(([, content]) => ARG_PLACEHOLDER.test(content));
|
|
173
|
-
if (!offender) {
|
|
174
|
-
const message2 = "rendered output carries real parameter names \u2014 no arg0 placeholders found";
|
|
175
|
-
return pass(ID5, message2, DOC.manualParameterNames);
|
|
176
|
-
}
|
|
177
|
-
const [path] = offender;
|
|
178
|
-
const message = `rendered output shows arg0-style placeholders (first seen in ${path}) \u2014 parameter names were not captured`;
|
|
179
|
-
return fail(ID5, message, FIX3, DOC.manualParameterNames);
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// src/doctor/checks/redaction-proof.ts
|
|
183
|
-
var ID6 = "trap.redaction-proof";
|
|
184
|
-
var TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
185
|
-
var REDACTED_ASSERTION = /\[REDACTED\]/;
|
|
186
|
-
var checkRedactionProof = (snapshot) => {
|
|
187
|
-
const testFiles = [...snapshot.sourceFiles].filter(([path]) => TEST_FILE.test(path));
|
|
188
|
-
const proven = testFiles.some(([, content]) => REDACTED_ASSERTION.test(content));
|
|
189
|
-
if (proven) {
|
|
190
|
-
return pass(
|
|
191
|
-
ID6,
|
|
192
|
-
"a test asserts [REDACTED] for a deny-listed parameter name",
|
|
193
|
-
DOC.redactionSurfaceBySurface
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
return fail(
|
|
197
|
-
ID6,
|
|
198
|
-
"no test asserts [REDACTED] \u2014 redaction is unproven",
|
|
199
|
-
'Render a call with a deny-listed parameter name (e.g. "password", "token") in a test and assert the output contains "[REDACTED]" \u2014 and that a neighboring, non-sensitive value is still present, so an over-broad redaction also fails.',
|
|
200
|
-
DOC.redactionSurfaceBySurface
|
|
201
|
-
);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
// src/doctor/checks/reporter-subpath.ts
|
|
205
|
-
var ID7 = "config.reporter-subpath";
|
|
206
|
-
var VITEST_CONFIG_NAME = /(^|\/)vitest\.config\.[cm]?[jt]s$/;
|
|
207
|
-
var REPORTER_NAMES = /\b(ClaritySuiteReporter|GlossarySuiteReporter|StructuralSuiteReporter)\b/;
|
|
208
|
-
var ROOT_IMPORT = /from\s+["']@narrativetrace\/vitest["']/;
|
|
209
|
-
var SUBPATH_IMPORT = /from\s+["']@narrativetrace\/vitest\/reporters["']/;
|
|
210
|
-
function importsFromRoot(content) {
|
|
211
|
-
return REPORTER_NAMES.test(content) && ROOT_IMPORT.test(content) && !SUBPATH_IMPORT.test(content);
|
|
212
|
-
}
|
|
213
|
-
var checkReporterSubpath = (snapshot) => {
|
|
214
|
-
const configs = [...snapshot.sourceFiles].filter(([path]) => VITEST_CONFIG_NAME.test(path));
|
|
215
|
-
const offender = configs.find(([, content]) => importsFromRoot(content));
|
|
216
|
-
if (offender) {
|
|
217
|
-
const [path] = offender;
|
|
218
|
-
return fail(
|
|
219
|
-
ID7,
|
|
220
|
-
`${path} imports a NarrativeTrace vitest reporter from the package root, not the /reporters subpath`,
|
|
221
|
-
'Import reporters from "@narrativetrace/vitest/reporters" \u2014 importing from the package root also loads vitest itself and crashes config loading.',
|
|
222
|
-
DOC.vitestConfiguration
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
const message = configs.length === 0 ? "no vitest.config found \u2014 nothing to check" : "every registered reporter is imported from the /reporters subpath";
|
|
226
|
-
return pass(ID7, message, DOC.vitestConfiguration);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// src/doctor/checks/sibling-packages.ts
|
|
230
|
-
var ID8 = "toolchain.sibling-packages";
|
|
231
|
-
var checkSiblingPackages = (snapshot) => {
|
|
232
|
-
const ntVitest = snapshot.installedPackages.get("@narrativetrace/vitest");
|
|
233
|
-
if (!ntVitest) {
|
|
234
|
-
const message2 = "@narrativetrace/vitest is not installed \u2014 nothing to check";
|
|
235
|
-
return pass(ID8, message2, DOC.installationDependencies);
|
|
236
|
-
}
|
|
237
|
-
const siblings = Object.keys(ntVitest.dependencies ?? {}).filter(
|
|
238
|
-
(n) => n.startsWith("@narrativetrace/")
|
|
239
|
-
);
|
|
240
|
-
const unresolved = siblings.filter((name) => !snapshot.installedPackages.has(name));
|
|
241
|
-
if (unresolved.length === 0) {
|
|
242
|
-
const message2 = `all ${siblings.length} sibling package(s) of @narrativetrace/vitest resolve from the consumer`;
|
|
243
|
-
return pass(ID8, message2, DOC.installationDependencies);
|
|
244
|
-
}
|
|
245
|
-
const message = `${unresolved.length} sibling package(s) of @narrativetrace/vitest do not resolve from the consumer: ${unresolved.join(", ")}`;
|
|
246
|
-
const fix = `Add ${unresolved.join(", ")} as explicit direct dependencies \u2014 pnpm's strict layout does not hoist a dependency's own transitive dependencies to your project root.`;
|
|
247
|
-
return fail(ID8, message, fix, DOC.installationDependencies);
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
// src/doctor/checks/silent-sink.ts
|
|
251
|
-
var ID9 = "trap.silent-sink";
|
|
252
|
-
var TRACE_OBJECT_CALL = /\btraceObject\s*\(/;
|
|
253
|
-
var SINK_SIGNAL = /\b(BufferedEventConsumer|captureTrace|registerConsumer|createNarrativeTest|narrativeTest)\b|@narrativetrace\/(vitest|pino|winston|opentelemetry|observability)\b/;
|
|
254
|
-
var checkSilentSink = (snapshot) => {
|
|
255
|
-
const contents = [...snapshot.sourceFiles.values()];
|
|
256
|
-
if (!contents.some((content) => TRACE_OBJECT_CALL.test(content))) {
|
|
257
|
-
return pass(ID9, "traceObject() is not used \u2014 nothing to check", DOC.noTraceFilesWritten);
|
|
258
|
-
}
|
|
259
|
-
if (contents.some((content) => SINK_SIGNAL.test(content))) {
|
|
260
|
-
return pass(
|
|
261
|
-
ID9,
|
|
262
|
-
"traceObject() is used and a consumer/sink is attached",
|
|
263
|
-
DOC.noTraceFilesWritten
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
return fail(
|
|
267
|
-
ID9,
|
|
268
|
-
"traceObject() is used but no consumer or sink (BufferedEventConsumer, captureTrace(), a log/OTel bridge, or @narrativetrace/vitest) was found",
|
|
269
|
-
"Attach a sink: pass a BufferedEventConsumer to your pipeline, call captureTrace() and do something with the tree, or wire a log/OTel bridge \u2014 otherwise every traced call narrates to nowhere.",
|
|
270
|
-
DOC.noTraceFilesWritten
|
|
271
|
-
);
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
// src/doctor/checks/trace-object-keys.ts
|
|
275
|
-
var ID10 = "config.trace-object-keys";
|
|
276
|
-
var OLD_SHAPE = /\bmethods\s*:\s*\{[^{}]*:\s*\[/;
|
|
277
|
-
var checkTraceObjectKeys = (snapshot) => {
|
|
278
|
-
const offender = [...snapshot.sourceFiles].find(([, content]) => OLD_SHAPE.test(content));
|
|
279
|
-
if (offender) {
|
|
280
|
-
const [path] = offender;
|
|
281
|
-
return fail(
|
|
282
|
-
ID10,
|
|
283
|
-
`${path} calls traceObject(...) with a method entry that skips the per-method config object`,
|
|
284
|
-
"Nest the parameter names under params: traceObject(target, context, { methods: { methodName: { params: [...] } } }) \u2014 a bare array under the method name throws.",
|
|
285
|
-
DOC.proxyOptions
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
return pass(
|
|
289
|
-
ID10,
|
|
290
|
-
"no traceObject(...) call uses the old { methods: { ... } } option shape",
|
|
291
|
-
DOC.proxyOptions
|
|
292
|
-
);
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
// src/doctor/checks/vitest-peer.ts
|
|
296
|
-
var ID11 = "toolchain.vitest-peer";
|
|
297
|
-
function noVitestInstalled(range) {
|
|
298
|
-
const message = `@narrativetrace/vitest requires a peer vitest@${range}, but no vitest install was found`;
|
|
299
|
-
const fix = `Install a vitest version satisfying ${range} (npm add -D vitest, then npm ci for a clean lockfile install).`;
|
|
300
|
-
return fail(ID11, message, fix, DOC.vitestConfiguration);
|
|
301
|
-
}
|
|
302
|
-
function mismatchedVitest(range, installed) {
|
|
303
|
-
const message = `vitest@${installed} does not satisfy @narrativetrace/vitest's declared peer range ${range}`;
|
|
304
|
-
const fix = `Install a vitest version satisfying ${range}, then reinstall clean (rm -rf node_modules && npm ci) \u2014 a mismatched peer here is the one failure mode that breaks the library's own build.`;
|
|
305
|
-
return fail(ID11, message, fix, DOC.vitestConfiguration);
|
|
306
|
-
}
|
|
307
|
-
function evaluateInstalledVitest(range, vitest) {
|
|
308
|
-
if (!vitest?.version) return noVitestInstalled(range);
|
|
309
|
-
if (satisfiesRange(vitest.version, range)) {
|
|
310
|
-
const message = `vitest@${vitest.version} satisfies the declared peer range ${range}`;
|
|
311
|
-
return pass(ID11, message, DOC.vitestConfiguration);
|
|
312
|
-
}
|
|
313
|
-
return mismatchedVitest(range, vitest.version);
|
|
314
|
-
}
|
|
315
|
-
var checkVitestPeer = (snapshot) => {
|
|
316
|
-
const ntVitest = snapshot.installedPackages.get("@narrativetrace/vitest");
|
|
317
|
-
const range = ntVitest?.peerDependencies?.vitest;
|
|
318
|
-
if (!ntVitest || !range) {
|
|
319
|
-
return pass(
|
|
320
|
-
ID11,
|
|
321
|
-
"@narrativetrace/vitest is not installed \u2014 nothing to check",
|
|
322
|
-
DOC.vitestConfiguration
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
return evaluateInstalledVitest(range, snapshot.installedPackages.get("vitest"));
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// src/doctor/doctor.ts
|
|
329
|
-
var DOCTOR_CHECKS = [
|
|
330
|
-
checkNodeEngine,
|
|
331
|
-
checkVitestPeer,
|
|
332
|
-
checkSiblingPackages,
|
|
333
|
-
checkOutputEnv,
|
|
334
|
-
checkReporterSubpath,
|
|
335
|
-
checkTraceObjectKeys,
|
|
336
|
-
checkSilentSink,
|
|
337
|
-
checkParameterArg0,
|
|
338
|
-
checkRedactionProof,
|
|
339
|
-
checkApprovalTraces,
|
|
340
|
-
checkLlmsBeforeYouStart
|
|
341
|
-
];
|
|
342
|
-
function runDoctor(snapshot) {
|
|
343
|
-
const findings = DOCTOR_CHECKS.map((check) => check(snapshot));
|
|
344
|
-
const exitCode = findings.some((f) => f.status === "fail") ? 1 : 0;
|
|
345
|
-
return { findings, exitCode };
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// src/doctor/render.ts
|
|
349
|
-
function renderFinding(finding) {
|
|
350
|
-
const tag = finding.status === "fail" ? "FAIL" : "PASS";
|
|
351
|
-
const lines = [`[${tag}] ${finding.id} \u2014 ${finding.message}`];
|
|
352
|
-
if (finding.status === "fail") {
|
|
353
|
-
lines.push(` fix: ${finding.fix}`, ` docs: ${finding.docUrl}`);
|
|
354
|
-
}
|
|
355
|
-
lines.push("");
|
|
356
|
-
return lines;
|
|
357
|
-
}
|
|
358
|
-
function renderHuman(report) {
|
|
359
|
-
const failing = report.findings.filter((f) => f.status === "fail");
|
|
360
|
-
const passing = report.findings.filter((f) => f.status === "pass");
|
|
361
|
-
const header = `narrativetrace doctor \u2014 ${report.findings.length} check(s), ${failing.length} finding(s)`;
|
|
362
|
-
const summary = failing.length === 0 ? "All checks passed." : `${failing.length} finding(s). Exit code ${report.exitCode}.`;
|
|
363
|
-
const body = [...failing, ...passing].flatMap(renderFinding);
|
|
364
|
-
return [header, "", ...body, summary].join("\n");
|
|
365
|
-
}
|
|
366
|
-
function renderJson(report) {
|
|
367
|
-
return JSON.stringify(report, null, 2);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// src/doctor/environment.ts
|
|
371
|
-
import { readdirSync, readFileSync, statSync } from "fs";
|
|
372
|
-
import { createRequire } from "module";
|
|
373
|
-
import { join, relative } from "path";
|
|
374
|
-
var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
375
|
-
// Stryker disable next-line StringLiteral: equivalent — every dot-prefixed entry name is
|
|
376
|
-
// already excluded earlier, in visitEntry's leading-dot check (the only exception there is
|
|
377
|
-
// ".env", which isn't a directory this set would ever mention), so ".git" never actually
|
|
378
|
-
// reaches this set's `.has()` check.
|
|
379
|
-
".git",
|
|
380
|
-
// Stryker disable next-line StringLiteral: same equivalence as ".git" above.
|
|
381
|
-
".turbo",
|
|
382
|
-
// Stryker disable next-line StringLiteral: same equivalence as ".git" above.
|
|
383
|
-
".stryker-tmp",
|
|
384
|
-
// Not dot-prefixed, so NOT equivalent — reaching this set's `.has()` check is the only thing
|
|
385
|
-
// that excludes each of the four names below; each is covered by a real test.
|
|
386
|
-
"node_modules",
|
|
387
|
-
"dist",
|
|
388
|
-
"build",
|
|
389
|
-
"coverage"
|
|
390
|
-
]);
|
|
391
|
-
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
392
|
-
var BASE_PACKAGES = [
|
|
393
|
-
"vitest",
|
|
394
|
-
"@narrativetrace/core",
|
|
395
|
-
"@narrativetrace/core-node",
|
|
396
|
-
"@narrativetrace/vitest"
|
|
397
|
-
];
|
|
398
|
-
var MAX_FILES = 2e4;
|
|
399
|
-
function readJson(path) {
|
|
400
|
-
try {
|
|
401
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
402
|
-
} catch {
|
|
403
|
-
return void 0;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
function resolvePackageJson(name, cwd) {
|
|
407
|
-
try {
|
|
408
|
-
const require2 = createRequire(join(cwd, "package.json"));
|
|
409
|
-
return readJson(require2.resolve(`${name}/package.json`));
|
|
410
|
-
} catch {
|
|
411
|
-
return void 0;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
function isDirectorySafe(path) {
|
|
415
|
-
try {
|
|
416
|
-
return statSync(path).isDirectory();
|
|
417
|
-
} catch {
|
|
418
|
-
return void 0;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
function safeRead(path) {
|
|
422
|
-
try {
|
|
423
|
-
return readFileSync(path, "utf8");
|
|
424
|
-
} catch {
|
|
425
|
-
return "";
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
function listDirectory(dir) {
|
|
429
|
-
try {
|
|
430
|
-
return readdirSync(dir);
|
|
431
|
-
} catch {
|
|
432
|
-
return [];
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
function classify(rel, outputDirName, approvedDirName) {
|
|
436
|
-
const topSegment = rel.split("/")[0];
|
|
437
|
-
if (topSegment === outputDirName) return "output";
|
|
438
|
-
if (topSegment === approvedDirName) return "approved";
|
|
439
|
-
return SOURCE_EXTENSIONS.some((ext) => rel.endsWith(ext)) ? "source" : "skip";
|
|
440
|
-
}
|
|
441
|
-
function bucketFor(state, kind) {
|
|
442
|
-
return kind === "output" ? state.outputFiles : kind === "approved" ? state.approvedDirFiles : state.sourceFiles;
|
|
443
|
-
}
|
|
444
|
-
function visitEntry(ctx, dir, entry) {
|
|
445
|
-
if (entry.startsWith(".") && entry !== ".env") return;
|
|
446
|
-
const full = join(dir, entry);
|
|
447
|
-
const isDir = isDirectorySafe(full);
|
|
448
|
-
if (isDir === void 0) return;
|
|
449
|
-
if (isDir) {
|
|
450
|
-
if (!EXCLUDED_DIRS.has(entry)) ctx.queue.push(full);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
ctx.state.visited++;
|
|
454
|
-
const rel = relative(ctx.root, full);
|
|
455
|
-
const kind = classify(rel, ctx.output, ctx.approved);
|
|
456
|
-
if (kind === "skip") return;
|
|
457
|
-
bucketFor(ctx.state, kind).set(rel, safeRead(full));
|
|
458
|
-
}
|
|
459
|
-
function walk(root, output, approved) {
|
|
460
|
-
const state = {
|
|
461
|
-
sourceFiles: /* @__PURE__ */ new Map(),
|
|
462
|
-
outputFiles: /* @__PURE__ */ new Map(),
|
|
463
|
-
approvedDirFiles: /* @__PURE__ */ new Map(),
|
|
464
|
-
visited: 0
|
|
465
|
-
};
|
|
466
|
-
const ctx = { state, root, output, approved, queue: [root] };
|
|
467
|
-
while (ctx.queue.length > 0 && state.visited < MAX_FILES) {
|
|
468
|
-
const dir = ctx.queue.shift();
|
|
469
|
-
for (const entry of listDirectory(dir)) {
|
|
470
|
-
if (state.visited >= MAX_FILES) break;
|
|
471
|
-
visitEntry(ctx, dir, entry);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
return state;
|
|
475
|
-
}
|
|
476
|
-
function resolveBasePackages(cwd) {
|
|
477
|
-
const installed = /* @__PURE__ */ new Map();
|
|
478
|
-
for (const name of BASE_PACKAGES) {
|
|
479
|
-
const pkg = resolvePackageJson(name, cwd);
|
|
480
|
-
if (pkg) installed.set(name, pkg);
|
|
481
|
-
}
|
|
482
|
-
return installed;
|
|
483
|
-
}
|
|
484
|
-
function resolveVitestSiblings(cwd, installed) {
|
|
485
|
-
const ntVitest = installed.get("@narrativetrace/vitest");
|
|
486
|
-
for (const name of Object.keys(ntVitest?.dependencies ?? {})) {
|
|
487
|
-
if (!name.startsWith("@narrativetrace/") || installed.has(name)) continue;
|
|
488
|
-
const pkg = resolvePackageJson(name, cwd);
|
|
489
|
-
if (pkg) installed.set(name, pkg);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
function resolveInstalledPackages(cwd) {
|
|
493
|
-
const installed = resolveBasePackages(cwd);
|
|
494
|
-
resolveVitestSiblings(cwd, installed);
|
|
495
|
-
return installed;
|
|
496
|
-
}
|
|
497
|
-
function buildSnapshot(cwd, env) {
|
|
498
|
-
const output = env.NARRATIVETRACE_OUTPUT_DIR ?? "narrativetrace-output";
|
|
499
|
-
const approved = env.NARRATIVETRACE_APPROVED_DIR ?? "narratives";
|
|
500
|
-
const { sourceFiles, outputFiles, approvedDirFiles } = walk(cwd, output, approved);
|
|
501
|
-
return {
|
|
502
|
-
cwd,
|
|
503
|
-
nodeVersion: process.version.replace(/^v/, ""),
|
|
504
|
-
env,
|
|
505
|
-
rootPackageJson: readJson(join(cwd, "package.json")),
|
|
506
|
-
sourceFiles,
|
|
507
|
-
outputFiles,
|
|
508
|
-
approvedDirFiles,
|
|
509
|
-
installedPackages: resolveInstalledPackages(cwd)
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
export {
|
|
514
|
-
parseVersion,
|
|
515
|
-
compareVersions,
|
|
516
|
-
satisfiesRange,
|
|
517
|
-
DOCTOR_CHECKS,
|
|
518
|
-
runDoctor,
|
|
519
|
-
renderHuman,
|
|
520
|
-
renderJson,
|
|
521
|
-
buildSnapshot
|
|
522
|
-
};
|
|
523
|
-
//# sourceMappingURL=chunk-WUK4GWFW.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/semver-lite.ts","../src/doctor/doc-urls.ts","../src/doctor/finding.ts","../src/doctor/checks/approval-traces.ts","../src/doctor/checks/llms-before-you-start.ts","../src/doctor/checks/node-engine.ts","../src/doctor/checks/output-env.ts","../src/doctor/checks/parameter-arg0.ts","../src/doctor/checks/redaction-proof.ts","../src/doctor/checks/reporter-subpath.ts","../src/doctor/checks/sibling-packages.ts","../src/doctor/checks/silent-sink.ts","../src/doctor/checks/trace-object-keys.ts","../src/doctor/checks/vitest-peer.ts","../src/doctor/doctor.ts","../src/doctor/render.ts","../src/doctor/environment.ts"],"sourcesContent":["// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\n/**\n * Minimal semver range matcher covering exactly the range shapes this repository's own\n * `package.json` files use (`>=20`, `^1.0.0 || ^2.0.0 || ^3.0.0`, `1.x`): the doctor never needs to\n * parse an arbitrary npm range, only the ones NarrativeTrace itself declares in `engines` and\n * `peerDependencies`. A real `semver` dependency would cover forms this codebase never emits.\n */\n\nexport interface Version {\n readonly major: number;\n readonly minor: number;\n readonly patch: number;\n}\n\n/** Parses `\"20.11.0\"`, `\"v20.11.0\"`, `\"20\"`, or `\"20.11\"` — missing parts default to zero. */\nexport function parseVersion(raw: string): Version | undefined {\n const cleaned = raw.trim().replace(/^v/, \"\");\n const match = /^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/.exec(cleaned);\n if (!match) return undefined;\n return {\n major: Number(match[1]),\n minor: match[2] === undefined ? 0 : Number(match[2]),\n patch: match[3] === undefined ? 0 : Number(match[3]),\n };\n}\n\n/** Three-way compare, standard sign convention: negative if `left` < `right`, positive if greater. */\nexport function compareVersions(left: Version, right: Version): number {\n if (left.major !== right.major) return left.major - right.major;\n if (left.minor !== right.minor) return left.minor - right.minor;\n return left.patch - right.patch;\n}\n\nfunction satisfiesGte(version: Version, floor: Version): boolean {\n return compareVersions(version, floor) >= 0;\n}\n\n/** Standard caret semantics: `^1.2.3` := `>=1.2.3 <2.0.0`; `^0.2.3` := `>=0.2.3 <0.3.0`; `^0.0.3` := `>=0.0.3 <0.0.4`. */\nfunction satisfiesCaret(version: Version, base: Version): boolean {\n if (compareVersions(version, base) < 0) return false;\n if (base.major > 0) return version.major === base.major;\n if (base.minor > 0) return version.major === 0 && version.minor === base.minor;\n return version.major === 0 && version.minor === 0 && version.patch === base.patch;\n}\n\n/**\n * One `||`-separated clause: `^x.y.z`, `>=x.y.z`, `x.x`/`x.y.x` (caret-equivalent), or an exact\n * version. `trimmed` must already be whitespace-trimmed — {@link satisfiesRange} does that once\n * for every clause it splits out, so the prefix checks below (`startsWith(\"^\")`) see it without a\n * leading space.\n */\nfunction satisfiesClause(version: Version, trimmed: string): boolean {\n if (trimmed.startsWith(\"^\")) {\n const base = parseVersion(trimmed.slice(1));\n return base !== undefined && satisfiesCaret(version, base);\n }\n if (trimmed.startsWith(\">=\")) {\n const floor = parseVersion(trimmed.slice(2));\n return floor !== undefined && satisfiesGte(version, floor);\n }\n if (/\\.x\\b/.test(trimmed)) {\n const base = parseVersion(trimmed.replace(/\\.x/g, \".0\"));\n return base !== undefined && satisfiesCaret(version, base);\n }\n const exact = parseVersion(trimmed);\n return exact !== undefined && compareVersions(version, exact) === 0;\n}\n\n/**\n * Whether `versionRaw` satisfies `range` (`||`-separated clauses; a version satisfies the range if\n * it satisfies any one clause). Unparseable input on either side is a mismatch, never a throw — a\n * doctor check reports a finding, it does not crash the run.\n */\nexport function satisfiesRange(versionRaw: string, range: string): boolean {\n const version = parseVersion(versionRaw);\n if (!version) return false;\n // Stryker disable next-line MethodExpression: dropping .filter(Boolean) is equivalent here —\n // an empty clause (from \"||\" or a trailing/leading \"||\") only ever adds a `satisfiesClause(v,\n // \"\")` call to the .some() chain, and that always returns false (parseVersion(\"\") is\n // undefined, and \"\" matches none of the prefix checks either), so it can never flip the\n // overall result. The filter is documentation of intent, not a behavior the type checker or a\n // mutation test can observe.\n return range\n .split(\"||\")\n .map((clause) => clause.trim())\n .filter(Boolean)\n .some((clause) => satisfiesClause(version, clause));\n}\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\n/**\n * Public doc anchors every finding points at. The TypeScript runtime's guides are not yet\n * published on narrativetrace.ai (only the Java docs are, as of 2026-09 — see the doctor P1 run's\n * report for the amendment note); this repository is public on GitHub\n * (`publishConfig.access: \"public\"` on every package), so a stable blob link into `documentation/`\n * on `main` is the honest \"public docs URL\" today. Swap the base once the TS guides land on the\n * site — the anchors (GitHub's own heading slugs) do not change.\n */\nconst BASE = \"https://github.com/narrativetrace/narrativetrace-typescript/blob/main/documentation/\";\n\nexport const DOC = {\n installationPrerequisites: `${BASE}installation-guide.md#prerequisites`,\n installationDependencies: `${BASE}installation-guide.md#1-add-dependencies`,\n vitestConfiguration: `${BASE}configuration-guide.md#2-vitest-configuration`,\n whereSettingsComeFrom: `${BASE}configuration-guide.md#2b-where-settings-come-from`,\n proxyOptions: `${BASE}configuration-guide.md#6-proxy-options`,\n eventPipelineBuffering: `${BASE}configuration-guide.md#8-event-pipeline-buffering-bufferedeventconsumer`,\n noTraceFilesWritten: `${BASE}troubleshooting.md#no-trace-files-are-written`,\n manualParameterNames: `${BASE}troubleshooting.md#parameters-show-as-arg0-arg1`,\n redactionSurfaceBySurface: `${BASE}privacy-and-redaction.md#redaction-surface-by-surface`,\n approvalTracesEndToEnd: `${BASE}structural-trace-format.md#approval-traces-end-to-end`,\n sixtySecondsNewProject: `${BASE}sixty-seconds.md#1-new-project-add-the-packages`,\n} as const;\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport type { Finding, FindingStatus } from \"./types.js\";\n\n/** A passing finding: no fix needed, by construction (see {@link Finding.fix}). */\nexport function pass(id: string, message: string, docUrl: string): Finding {\n return { id, status: \"pass\" as FindingStatus, message, fix: \"\", docUrl };\n}\n\n/** A failing finding: `fix` is mandatory — a fail with nothing to do about it is a wording bug. */\nexport function fail(id: string, message: string, fix: string, docUrl: string): Finding {\n return { id, status: \"fail\" as FindingStatus, message, fix, docUrl };\n}\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"trap.approval-traces\";\nconst FIX =\n \"Review each .received.nt diff against its .approved.nt baseline, then run pnpm run approve-narratives (or narrativetrace-approve) to promote it, or delete it if the change was wrong — never commit a .received.nt file.\";\n\n/**\n * Approval traces (`.approved.nt` reviewed baselines, `.received.nt` written on a mismatch) landed\n * in the runtime's structural-trace layer. A `.received.nt` sitting in the approved directory is a\n * reviewed-but-not-yet-resolved diff — stale ones are exactly what `what-to-commit.md` warns never\n * to commit, and exactly what a doctor run should surface before someone else does.\n */\nexport const checkApprovalTraces: DoctorCheck = (snapshot) => {\n const paths = [...snapshot.approvedDirFiles.keys()];\n if (paths.length === 0) {\n return pass(\n ID,\n \"no approval traces configured yet — nothing to check\",\n DOC.approvalTracesEndToEnd,\n );\n }\n const received = paths.filter((p) => p.endsWith(\".received.nt\"));\n if (received.length > 0) {\n const message = `${received.length} stale received trace(s) found: ${received.join(\", \")}`;\n return fail(ID, message, FIX, DOC.approvalTracesEndToEnd);\n }\n const approved = paths.filter((p) => p.endsWith(\".approved.nt\"));\n const message = `${approved.length} approved trace(s) found, no pending received diffs`;\n return pass(ID, message, DOC.approvalTracesEndToEnd);\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"trap.llms-before-you-start\";\nconst PLAIN_JS = /\\.js$/;\nconst ESM_IMPORT = /^\\s*import\\s/m;\nconst FIX =\n \"Run `npm pkg set type=module` before `npm add` — without it, Node throws SyntaxError: Cannot use import statement outside a module (pnpm/yarn default to it and never hit this).\";\n\n/**\n * `llms.txt`'s first \"before you start\" trap: a plain `.js` file using `import` syntax with no\n * `\"type\": \"module\"` in `package.json` throws `SyntaxError: Cannot use import statement outside a\n * module` the moment Node loads it — npm's default (unlike pnpm/yarn) does not set this for you.\n */\nexport const checkLlmsBeforeYouStart: DoctorCheck = (snapshot) => {\n if (snapshot.rootPackageJson?.type === \"module\") {\n return pass(ID, 'package.json declares \"type\": \"module\"', DOC.sixtySecondsNewProject);\n }\n const offender = [...snapshot.sourceFiles].find(\n ([path, content]) => PLAIN_JS.test(path) && ESM_IMPORT.test(content),\n );\n if (!offender) {\n const message = 'no plain .js file uses ESM import syntax without \"type\": \"module\"';\n return pass(ID, message, DOC.sixtySecondsNewProject);\n }\n const [path] = offender;\n const message = `${path} uses ESM import syntax but package.json has no \"type\": \"module\"`;\n return fail(ID, message, FIX, DOC.sixtySecondsNewProject);\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { satisfiesRange } from \"../../semver-lite.js\";\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"toolchain.node-engine\";\nconst DEFAULT_ENGINE_RANGE = \">=20\";\n\n/** Node version required by whichever core NarrativeTrace package is installed, else the repo default. */\nfunction requiredRange(snapshot: Parameters<DoctorCheck>[0]): string {\n const core = snapshot.installedPackages.get(\"@narrativetrace/core\");\n const coreNode = snapshot.installedPackages.get(\"@narrativetrace/core-node\");\n return core?.engines?.node ?? coreNode?.engines?.node ?? DEFAULT_ENGINE_RANGE;\n}\n\n/** Node engines (Installation Guide §Prerequisites): the running Node must satisfy the installed package's `engines.node`. */\nexport const checkNodeEngine: DoctorCheck = (snapshot) => {\n const required = requiredRange(snapshot);\n if (satisfiesRange(snapshot.nodeVersion, required)) {\n return pass(\n ID,\n `Node ${snapshot.nodeVersion} satisfies the required ${required}`,\n DOC.installationPrerequisites,\n );\n }\n return fail(\n ID,\n `Node ${snapshot.nodeVersion} does not satisfy the required ${required}`,\n `Upgrade Node to a version satisfying ${required} (nvm, volta, asdf, or your CI image).`,\n DOC.installationPrerequisites,\n );\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"config.output-env\";\nconst VALID_VALUES = new Set([\"true\", \"false\"]);\n\n/** `NARRATIVETRACE_OUTPUT`, if set, must be exactly `\"true\"` or `\"false\"` (case-insensitive). */\nexport const checkOutputEnv: DoctorCheck = (snapshot) => {\n const raw = snapshot.env.NARRATIVETRACE_OUTPUT;\n if (raw === undefined) {\n return pass(\n ID,\n \"NARRATIVETRACE_OUTPUT is not set — output stays on its default (on)\",\n DOC.whereSettingsComeFrom,\n );\n }\n if (VALID_VALUES.has(raw.toLowerCase())) {\n return pass(ID, `NARRATIVETRACE_OUTPUT=${raw}`, DOC.whereSettingsComeFrom);\n }\n return fail(\n ID,\n `NARRATIVETRACE_OUTPUT is set to '${raw}', which is neither \"true\" nor \"false\"`,\n \"Set NARRATIVETRACE_OUTPUT=true or NARRATIVETRACE_OUTPUT=false (or unset it) — any other value is read as a truthy string, most likely turning output on when you meant to turn it off.\",\n DOC.whereSettingsComeFrom,\n );\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"trap.parameter-arg0\";\nconst ARG_PLACEHOLDER = /\\barg0\\b/;\nconst FIX =\n 'Pass parameter names explicitly: traceObject(target, context, { methodName: [\"paramA\", \"paramB\"] }) — required for classes you do not own, or when a build tool strips them.';\n\n/**\n * Parameter names of classes you don't own (or a build that strips them) are lost at compile\n * time and render as `arg0`, `arg1`, ... Doctor reads already-rendered output, if any exists, and\n * flags the tell-tale placeholder rather than guessing from source.\n */\nexport const checkParameterArg0: DoctorCheck = (snapshot) => {\n if (snapshot.outputFiles.size === 0) {\n const message = \"no rendered output found yet — run your tests or app once to check this\";\n return pass(ID, message, DOC.manualParameterNames);\n }\n const offender = [...snapshot.outputFiles].find(([, content]) => ARG_PLACEHOLDER.test(content));\n if (!offender) {\n const message = \"rendered output carries real parameter names — no arg0 placeholders found\";\n return pass(ID, message, DOC.manualParameterNames);\n }\n const [path] = offender;\n const message = `rendered output shows arg0-style placeholders (first seen in ${path}) — parameter names were not captured`;\n return fail(ID, message, FIX, DOC.manualParameterNames);\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"trap.redaction-proof\";\nconst TEST_FILE = /\\.(test|spec)\\.[cm]?[jt]sx?$/;\nconst REDACTED_ASSERTION = /\\[REDACTED\\]/;\n\n/**\n * Redaction is a security property; the trap named across every study is trusting it by\n * inspection rather than proving it — importing the redaction primitives and never asserting on\n * their output. This looks for a test that actually asserts the literal `[REDACTED]` marker.\n */\nexport const checkRedactionProof: DoctorCheck = (snapshot) => {\n const testFiles = [...snapshot.sourceFiles].filter(([path]) => TEST_FILE.test(path));\n const proven = testFiles.some(([, content]) => REDACTED_ASSERTION.test(content));\n if (proven) {\n return pass(\n ID,\n \"a test asserts [REDACTED] for a deny-listed parameter name\",\n DOC.redactionSurfaceBySurface,\n );\n }\n return fail(\n ID,\n \"no test asserts [REDACTED] — redaction is unproven\",\n 'Render a call with a deny-listed parameter name (e.g. \"password\", \"token\") in a test and assert the output contains \"[REDACTED]\" — and that a neighboring, non-sensitive value is still present, so an over-broad redaction also fails.',\n DOC.redactionSurfaceBySurface,\n );\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"config.reporter-subpath\";\nconst VITEST_CONFIG_NAME = /(^|\\/)vitest\\.config\\.[cm]?[jt]s$/;\nconst REPORTER_NAMES = /\\b(ClaritySuiteReporter|GlossarySuiteReporter|StructuralSuiteReporter)\\b/;\nconst ROOT_IMPORT = /from\\s+[\"']@narrativetrace\\/vitest[\"']/;\nconst SUBPATH_IMPORT = /from\\s+[\"']@narrativetrace\\/vitest\\/reporters[\"']/;\n\nfunction importsFromRoot(content: string): boolean {\n return REPORTER_NAMES.test(content) && ROOT_IMPORT.test(content) && !SUBPATH_IMPORT.test(content);\n}\n\n/**\n * `ClaritySuiteReporter`/`GlossarySuiteReporter`/`StructuralSuiteReporter` must be imported from\n * the `/reporters` subpath — importing them from the package root also loads `vitest` itself,\n * which crashes config loading on every version (llms.txt \"Before you start\").\n */\nexport const checkReporterSubpath: DoctorCheck = (snapshot) => {\n const configs = [...snapshot.sourceFiles].filter(([path]) => VITEST_CONFIG_NAME.test(path));\n const offender = configs.find(([, content]) => importsFromRoot(content));\n if (offender) {\n const [path] = offender;\n return fail(\n ID,\n `${path} imports a NarrativeTrace vitest reporter from the package root, not the /reporters subpath`,\n 'Import reporters from \"@narrativetrace/vitest/reporters\" — importing from the package root also loads vitest itself and crashes config loading.',\n DOC.vitestConfiguration,\n );\n }\n const message =\n configs.length === 0\n ? \"no vitest.config found — nothing to check\"\n : \"every registered reporter is imported from the /reporters subpath\";\n return pass(ID, message, DOC.vitestConfiguration);\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"toolchain.sibling-packages\";\n\n/**\n * `@narrativetrace/vitest` depends on five sibling `@narrativetrace/*` packages (clarity,\n * core-node, diagrams, glossary, proxy — not peers, regular dependencies). Under pnpm's default\n * strict layout those resolve fine from inside `@narrativetrace/vitest`'s own `node_modules`, but a\n * consumer that also imports one of them directly needs it resolvable from ITS OWN root too, or\n * pnpm's non-hoisting layout hands the app a second, unrelated copy. This check reads the sibling\n * list from the installed package itself (never a hardcoded list — it tracks the dependency as it\n * grows) and confirms each one resolves from the consumer.\n */\nexport const checkSiblingPackages: DoctorCheck = (snapshot) => {\n const ntVitest = snapshot.installedPackages.get(\"@narrativetrace/vitest\");\n if (!ntVitest) {\n const message = \"@narrativetrace/vitest is not installed — nothing to check\";\n return pass(ID, message, DOC.installationDependencies);\n }\n const siblings = Object.keys(ntVitest.dependencies ?? {}).filter((n) =>\n n.startsWith(\"@narrativetrace/\"),\n );\n const unresolved = siblings.filter((name) => !snapshot.installedPackages.has(name));\n if (unresolved.length === 0) {\n const message = `all ${siblings.length} sibling package(s) of @narrativetrace/vitest resolve from the consumer`;\n return pass(ID, message, DOC.installationDependencies);\n }\n const message = `${unresolved.length} sibling package(s) of @narrativetrace/vitest do not resolve from the consumer: ${unresolved.join(\", \")}`;\n const fix = `Add ${unresolved.join(\", \")} as explicit direct dependencies — pnpm's strict layout does not hoist a dependency's own transitive dependencies to your project root.`;\n return fail(ID, message, fix, DOC.installationDependencies);\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"trap.silent-sink\";\nconst TRACE_OBJECT_CALL = /\\btraceObject\\s*\\(/;\nconst SINK_SIGNAL =\n /\\b(BufferedEventConsumer|captureTrace|registerConsumer|createNarrativeTest|narrativeTest)\\b|@narrativetrace\\/(vitest|pino|winston|opentelemetry|observability)\\b/;\n\n/**\n * The silent-sink trap: `traceObject()` proxies calls into events, but nothing narrates unless a\n * consumer/sink is attached (`BufferedEventConsumer`, `captureTrace()`, a log/OTel bridge, or the\n * vitest integration, which is its own sink). Wrapping without one of these is a project that looks\n * instrumented and narrates to nowhere.\n */\nexport const checkSilentSink: DoctorCheck = (snapshot) => {\n const contents = [...snapshot.sourceFiles.values()];\n if (!contents.some((content) => TRACE_OBJECT_CALL.test(content))) {\n return pass(ID, \"traceObject() is not used — nothing to check\", DOC.noTraceFilesWritten);\n }\n if (contents.some((content) => SINK_SIGNAL.test(content))) {\n return pass(\n ID,\n \"traceObject() is used and a consumer/sink is attached\",\n DOC.noTraceFilesWritten,\n );\n }\n return fail(\n ID,\n \"traceObject() is used but no consumer or sink (BufferedEventConsumer, captureTrace(), a log/OTel bridge, or @narrativetrace/vitest) was found\",\n \"Attach a sink: pass a BufferedEventConsumer to your pipeline, call captureTrace() and do something with the tree, or wire a log/OTel bridge — otherwise every traced call narrates to nowhere.\",\n DOC.noTraceFilesWritten,\n );\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck } from \"../types.js\";\n\nconst ID = \"config.trace-object-keys\";\n\n// The valid nested shape is `methods: { methodName: { params: [...], ... } }` — a per-method\n// config OBJECT. The trap is skipping the method-config level and putting the param array\n// straight under a method name (`methods: { methodName: [...] }`), which now throws naming the\n// accepted per-method keys rather than the silent no-op it used to be. `[^{}]*` stops the match at\n// the first nested `{`, so the legitimate form (an object, not an array, under the method name)\n// never matches here.\nconst OLD_SHAPE = /\\bmethods\\s*:\\s*\\{[^{}]*:\\s*\\[/;\n\n/**\n * `traceObject`'s options used to silently no-op when a per-method entry skipped straight to an\n * array instead of the `{ params: [...] }` object (now throws, naming the accepted keys) — a\n * source file still shaped that way needs the method-config object added back, not just an\n * upgrade.\n */\nexport const checkTraceObjectKeys: DoctorCheck = (snapshot) => {\n const offender = [...snapshot.sourceFiles].find(([, content]) => OLD_SHAPE.test(content));\n if (offender) {\n const [path] = offender;\n return fail(\n ID,\n `${path} calls traceObject(...) with a method entry that skips the per-method config object`,\n \"Nest the parameter names under params: traceObject(target, context, { methods: { methodName: { params: [...] } } }) — a bare array under the method name throws.\",\n DOC.proxyOptions,\n );\n }\n return pass(\n ID,\n \"no traceObject(...) call uses the old { methods: { ... } } option shape\",\n DOC.proxyOptions,\n );\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { satisfiesRange } from \"../../semver-lite.js\";\nimport { DOC } from \"../doc-urls.js\";\nimport { fail, pass } from \"../finding.js\";\nimport type { DoctorCheck, Finding, PackageJsonLike } from \"../types.js\";\n\nconst ID = \"toolchain.vitest-peer\";\n\nfunction noVitestInstalled(range: string): Finding {\n const message = `@narrativetrace/vitest requires a peer vitest@${range}, but no vitest install was found`;\n const fix = `Install a vitest version satisfying ${range} (npm add -D vitest, then npm ci for a clean lockfile install).`;\n return fail(ID, message, fix, DOC.vitestConfiguration);\n}\n\nfunction mismatchedVitest(range: string, installed: string): Finding {\n const message = `vitest@${installed} does not satisfy @narrativetrace/vitest's declared peer range ${range}`;\n const fix = `Install a vitest version satisfying ${range}, then reinstall clean (rm -rf node_modules && npm ci) — a mismatched peer here is the one failure mode that breaks the library's own build.`;\n return fail(ID, message, fix, DOC.vitestConfiguration);\n}\n\nfunction evaluateInstalledVitest(range: string, vitest: PackageJsonLike | undefined): Finding {\n if (!vitest?.version) return noVitestInstalled(range);\n if (satisfiesRange(vitest.version, range)) {\n const message = `vitest@${vitest.version} satisfies the declared peer range ${range}`;\n return pass(ID, message, DOC.vitestConfiguration);\n }\n return mismatchedVitest(range, vitest.version);\n}\n\n/** The installed `vitest` version must satisfy `@narrativetrace/vitest`'s declared peer range. */\nexport const checkVitestPeer: DoctorCheck = (snapshot) => {\n const ntVitest = snapshot.installedPackages.get(\"@narrativetrace/vitest\");\n const range = ntVitest?.peerDependencies?.vitest;\n if (!ntVitest || !range) {\n return pass(\n ID,\n \"@narrativetrace/vitest is not installed — nothing to check\",\n DOC.vitestConfiguration,\n );\n }\n return evaluateInstalledVitest(range, snapshot.installedPackages.get(\"vitest\"));\n};\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { checkApprovalTraces } from \"./checks/approval-traces.js\";\nimport { checkLlmsBeforeYouStart } from \"./checks/llms-before-you-start.js\";\nimport { checkNodeEngine } from \"./checks/node-engine.js\";\nimport { checkOutputEnv } from \"./checks/output-env.js\";\nimport { checkParameterArg0 } from \"./checks/parameter-arg0.js\";\nimport { checkRedactionProof } from \"./checks/redaction-proof.js\";\nimport { checkReporterSubpath } from \"./checks/reporter-subpath.js\";\nimport { checkSiblingPackages } from \"./checks/sibling-packages.js\";\nimport { checkSilentSink } from \"./checks/silent-sink.js\";\nimport { checkTraceObjectKeys } from \"./checks/trace-object-keys.js\";\nimport { checkVitestPeer } from \"./checks/vitest-peer.js\";\nimport type { DoctorCheck, DoctorReport, DoctorSnapshot } from \"./types.js\";\n\n/**\n * Every check `narrativetrace doctor` runs, in stable, documented order. Adding a check means\n * appending here — the id is what stays stable across releases, not the position.\n */\nexport const DOCTOR_CHECKS: readonly DoctorCheck[] = [\n checkNodeEngine,\n checkVitestPeer,\n checkSiblingPackages,\n checkOutputEnv,\n checkReporterSubpath,\n checkTraceObjectKeys,\n checkSilentSink,\n checkParameterArg0,\n checkRedactionProof,\n checkApprovalTraces,\n checkLlmsBeforeYouStart,\n];\n\n/** Runs every check over `snapshot` and derives the process exit code. Read-only: mutates nothing. */\nexport function runDoctor(snapshot: DoctorSnapshot): DoctorReport {\n const findings = DOCTOR_CHECKS.map((check) => check(snapshot));\n const exitCode = findings.some((f) => f.status === \"fail\") ? 1 : 0;\n return { findings, exitCode };\n}\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport type { DoctorReport, Finding } from \"./types.js\";\n\nfunction renderFinding(finding: Finding): string[] {\n const tag = finding.status === \"fail\" ? \"FAIL\" : \"PASS\";\n const lines = [`[${tag}] ${finding.id} — ${finding.message}`];\n if (finding.status === \"fail\") {\n lines.push(` fix: ${finding.fix}`, ` docs: ${finding.docUrl}`);\n }\n lines.push(\"\");\n return lines;\n}\n\n/** Human-readable default output: one block per finding, worst-first (failures before passes). */\nexport function renderHuman(report: DoctorReport): string {\n const failing = report.findings.filter((f) => f.status === \"fail\");\n const passing = report.findings.filter((f) => f.status === \"pass\");\n const header = `narrativetrace doctor — ${report.findings.length} check(s), ${failing.length} finding(s)`;\n const summary =\n failing.length === 0\n ? \"All checks passed.\"\n : `${failing.length} finding(s). Exit code ${report.exitCode}.`;\n const body = [...failing, ...passing].flatMap(renderFinding);\n return [header, \"\", ...body, summary].join(\"\\n\");\n}\n\n/** Machine-readable `--json` output: the report verbatim, stable field names. */\nexport function renderJson(report: DoctorReport): string {\n return JSON.stringify(report, null, 2);\n}\n","// SPDX-License-Identifier: BUSL-1.1\n// Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four years from publication; Change License: Apache-2.0\n// Copyright (c) 2026 Empower Agile\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join, relative } from \"node:path\";\nimport type { DoctorSnapshot, Env, PackageJsonLike } from \"./types.js\";\n\nconst EXCLUDED_DIRS = new Set([\n // Stryker disable next-line StringLiteral: equivalent — every dot-prefixed entry name is\n // already excluded earlier, in visitEntry's leading-dot check (the only exception there is\n // \".env\", which isn't a directory this set would ever mention), so \".git\" never actually\n // reaches this set's `.has()` check.\n \".git\",\n // Stryker disable next-line StringLiteral: same equivalence as \".git\" above.\n \".turbo\",\n // Stryker disable next-line StringLiteral: same equivalence as \".git\" above.\n \".stryker-tmp\",\n // Not dot-prefixed, so NOT equivalent — reaching this set's `.has()` check is the only thing\n // that excludes each of the four names below; each is covered by a real test.\n \"node_modules\",\n \"dist\",\n \"build\",\n \"coverage\",\n]);\n\nconst SOURCE_EXTENSIONS = [\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\", \".mts\", \".cts\"];\n\n/** Well-known package names the checks resolve directly; siblings of @narrativetrace/vitest are added dynamically. */\nconst BASE_PACKAGES = [\n \"vitest\",\n \"@narrativetrace/core\",\n \"@narrativetrace/core-node\",\n \"@narrativetrace/vitest\",\n];\n\nconst MAX_FILES = 20_000;\n\ntype Bucket = \"output\" | \"approved\" | \"source\" | \"skip\";\n\ninterface WalkState {\n readonly sourceFiles: Map<string, string>;\n readonly outputFiles: Map<string, string>;\n readonly approvedDirFiles: Map<string, string>;\n visited: number;\n}\n\n// Stryker disable BlockStatement: the three functions below whose catch block is just `return\n// undefined;` are equivalent under mutation — an emptied `catch {}` falls off the end of the\n// function and implicitly returns `undefined` too. No test can observe a difference between the\n// explicit and implicit forms. Restored below, before safeRead/listDirectory, whose catch blocks\n// return \"\" / [] respectively and are NOT equivalent (a real behavior change is observable there).\n\nfunction readJson(path: string): PackageJsonLike | undefined {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as PackageJsonLike;\n } catch {\n return undefined;\n }\n}\n\nfunction resolvePackageJson(name: string, cwd: string): PackageJsonLike | undefined {\n try {\n const require = createRequire(join(cwd, \"package.json\"));\n return readJson(require.resolve(`${name}/package.json`));\n } catch {\n return undefined;\n }\n}\n\n/** `undefined` when `path` cannot be stat'd at all (a dangling symlink, a permission error, a race). */\nfunction isDirectorySafe(path: string): boolean | undefined {\n try {\n return statSync(path).isDirectory();\n } catch {\n return undefined;\n }\n}\n\n// Stryker restore BlockStatement\n\nfunction safeRead(path: string): string {\n try {\n return readFileSync(path, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nfunction listDirectory(dir: string): string[] {\n try {\n return readdirSync(dir);\n } catch {\n return [];\n }\n}\n\nfunction classify(rel: string, outputDirName: string, approvedDirName: string): Bucket {\n const topSegment = rel.split(\"/\")[0];\n if (topSegment === outputDirName) return \"output\";\n if (topSegment === approvedDirName) return \"approved\";\n // Stryker disable next-line StringLiteral: the \"source\" branch string is equivalent — bucketFor\n // below routes anything that isn't \"output\" or \"approved\" into sourceFiles by default, so\n // whether this literal reads \"source\" or something else never changes which map a file lands\n // in. The \"skip\" branch is NOT equivalent (a real behavior change is observable there) and is\n // exercised by a real test.\n return SOURCE_EXTENSIONS.some((ext) => rel.endsWith(ext)) ? \"source\" : \"skip\";\n}\n\nfunction bucketFor(state: WalkState, kind: Bucket): Map<string, string> {\n return kind === \"output\"\n ? state.outputFiles\n : kind === \"approved\"\n ? state.approvedDirFiles\n : state.sourceFiles;\n}\n\ninterface WalkContext {\n readonly state: WalkState;\n readonly root: string;\n readonly output: string;\n readonly approved: string;\n readonly queue: string[];\n}\n\n/** Visits one directory entry: enqueues a subdirectory, or files it into the right bucket. */\nfunction visitEntry(ctx: WalkContext, dir: string, entry: string): void {\n if (entry.startsWith(\".\") && entry !== \".env\") return;\n const full = join(dir, entry);\n const isDir = isDirectorySafe(full);\n if (isDir === undefined) return;\n if (isDir) {\n if (!EXCLUDED_DIRS.has(entry)) ctx.queue.push(full);\n return;\n }\n ctx.state.visited++;\n const rel = relative(ctx.root, full);\n const kind = classify(rel, ctx.output, ctx.approved);\n if (kind === \"skip\") return;\n bucketFor(ctx.state, kind).set(rel, safeRead(full));\n}\n\n/**\n * Walks `root` breadth-first, bucketing files into source (extension-filtered), the output\n * directory, and the approved-trace directory — excluding `node_modules`/build/coverage noise.\n * Bounded by {@link MAX_FILES} so a doctor run in a huge repo degrades to a partial scan rather\n * than hanging. The outer loop's own `state.visited < MAX_FILES` half of that bound is equivalent\n * under mutation — the inner loop's `if (state.visited >= MAX_FILES) break;` enforces the exact\n * same cap on its own, before any further entry is ever visited, so weakening the outer guard only\n * costs a few extra, immediately aborted `listDirectory` calls on already-queued directories; no\n * test can observe a different `WalkState`.\n */\nfunction walk(root: string, output: string, approved: string): WalkState {\n const state: WalkState = {\n sourceFiles: new Map(),\n outputFiles: new Map(),\n approvedDirFiles: new Map(),\n visited: 0,\n };\n const ctx: WalkContext = { state, root, output, approved, queue: [root] };\n // Stryker disable next-line ConditionalExpression,EqualityOperator: see the doc comment above.\n while (ctx.queue.length > 0 && state.visited < MAX_FILES) {\n const dir = ctx.queue.shift() as string;\n for (const entry of listDirectory(dir)) {\n if (state.visited >= MAX_FILES) break;\n visitEntry(ctx, dir, entry);\n }\n }\n return state;\n}\n\nfunction resolveBasePackages(cwd: string): Map<string, PackageJsonLike> {\n const installed = new Map<string, PackageJsonLike>();\n for (const name of BASE_PACKAGES) {\n const pkg = resolvePackageJson(name, cwd);\n if (pkg) installed.set(name, pkg);\n }\n return installed;\n}\n\n/** Resolves `@narrativetrace/vitest`'s own `@narrativetrace/*` dependencies — the sibling check's data. */\nfunction resolveVitestSiblings(cwd: string, installed: Map<string, PackageJsonLike>): void {\n const ntVitest = installed.get(\"@narrativetrace/vitest\");\n for (const name of Object.keys(ntVitest?.dependencies ?? {})) {\n if (!name.startsWith(\"@narrativetrace/\") || installed.has(name)) continue;\n const pkg = resolvePackageJson(name, cwd);\n if (pkg) installed.set(name, pkg);\n }\n}\n\nfunction resolveInstalledPackages(cwd: string): Map<string, PackageJsonLike> {\n const installed = resolveBasePackages(cwd);\n resolveVitestSiblings(cwd, installed);\n return installed;\n}\n\n/** Builds a {@link DoctorSnapshot} from the real filesystem rooted at `cwd`. The one impure module. */\nexport function buildSnapshot(cwd: string, env: Env): DoctorSnapshot {\n const output = env.NARRATIVETRACE_OUTPUT_DIR ?? \"narrativetrace-output\";\n const approved = env.NARRATIVETRACE_APPROVED_DIR ?? \"narratives\";\n const { sourceFiles, outputFiles, approvedDirFiles } = walk(cwd, output, approved);\n return {\n cwd,\n nodeVersion: process.version.replace(/^v/, \"\"),\n env,\n rootPackageJson: readJson(join(cwd, \"package.json\")),\n sourceFiles,\n outputFiles,\n approvedDirFiles,\n installedPackages: resolveInstalledPackages(cwd),\n };\n}\n"],"mappings":"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","names":["message","ID","FIX","path","message","ID","ID","ID","FIX","message","ID","ID","ID","message","ID","ID","ID","require"]}
|