@nifrajs/cli 1.11.0 → 1.12.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 +8 -1
- package/dist/check.d.ts +1 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +19 -0
- package/dist/check.js.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +146 -10
- package/dist/doctor.js.map +1 -1
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +11 -8
- package/dist/levels-tool.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/docs/llms-full.txt +106 -3
- package/docs/types.json +479 -11
- package/package.json +9 -8
- package/src/check.ts +20 -0
- package/src/cli.ts +4 -4
- package/src/doctor.ts +179 -11
- package/src/levels-tool.ts +17 -7
- package/src/mcp-http.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "The nifra CLI — zero-config dev/build/start for a nifra app (`nifra dev`, `nifra build`, `nifra start`).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -41,13 +41,14 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@nifrajs/client": "1.
|
|
45
|
-
"@nifrajs/core": "1.
|
|
46
|
-
"@nifrajs/mcp": "1.
|
|
47
|
-
"@nifrajs/runner": "1.
|
|
48
|
-
"@nifrajs/schema": "1.
|
|
49
|
-
"@nifrajs/
|
|
50
|
-
"
|
|
44
|
+
"@nifrajs/client": "1.12.0",
|
|
45
|
+
"@nifrajs/core": "1.12.0",
|
|
46
|
+
"@nifrajs/mcp": "1.12.0",
|
|
47
|
+
"@nifrajs/runner": "1.12.0",
|
|
48
|
+
"@nifrajs/schema": "1.12.0",
|
|
49
|
+
"@nifrajs/testing": "1.12.0",
|
|
50
|
+
"@nifrajs/web": "1.12.0",
|
|
51
|
+
"create-nifra": "1.12.0"
|
|
51
52
|
},
|
|
52
53
|
"repository": {
|
|
53
54
|
"type": "git",
|
package/src/check.ts
CHANGED
|
@@ -770,6 +770,7 @@ export interface CheckDiagnostic {
|
|
|
770
770
|
| "server-only-import"
|
|
771
771
|
| "response-route"
|
|
772
772
|
| "undeclared-dependency"
|
|
773
|
+
| "duplicate-install"
|
|
773
774
|
| "server-manifest-drift"
|
|
774
775
|
| "manifest-drift"
|
|
775
776
|
| "capability-assurance"
|
|
@@ -1139,6 +1140,24 @@ export async function collectCheckResult(
|
|
|
1139
1140
|
},
|
|
1140
1141
|
})
|
|
1141
1142
|
}
|
|
1143
|
+
for (const finding of dr.duplicateInstalls) {
|
|
1144
|
+
const copies = finding.copies.map((copy) => `${copy.version} at ${copy.path}`).join("; ")
|
|
1145
|
+
diagnostics.push({
|
|
1146
|
+
rule: "duplicate-install",
|
|
1147
|
+
severity: "error",
|
|
1148
|
+
message: `${finding.package} resolves to multiple physical copies (${copies}) - module identity is unsafe`,
|
|
1149
|
+
fix: "align dependency ranges and reinstall from the workspace root",
|
|
1150
|
+
suggestion: {
|
|
1151
|
+
kind: "manual",
|
|
1152
|
+
title: `Deduplicate ${finding.package}`,
|
|
1153
|
+
steps: [
|
|
1154
|
+
"Align workspace dependency and peer ranges on one compatible version.",
|
|
1155
|
+
"Remove stale nested installs and run the package manager from the workspace root.",
|
|
1156
|
+
"Run `nifra doctor` again; do not suppress same-version duplicate paths.",
|
|
1157
|
+
],
|
|
1158
|
+
},
|
|
1159
|
+
})
|
|
1160
|
+
}
|
|
1142
1161
|
}
|
|
1143
1162
|
// #7: a committed server-manifest.ts that drifted from routes/ — name the exact missing/extra routes.
|
|
1144
1163
|
for (const f of manifestDrift) {
|
|
@@ -1317,6 +1336,7 @@ export async function runCheck(
|
|
|
1317
1336
|
["server-only-import", "server-only import in a route module"],
|
|
1318
1337
|
["response-route", "route returns a raw Response (typed client → data: never)"],
|
|
1319
1338
|
["undeclared-dependency", "undeclared dependency in package.json"],
|
|
1339
|
+
["duplicate-install", "duplicate identity-sensitive dependency install"],
|
|
1320
1340
|
["server-manifest-drift", "server-manifest.ts drifted from routes/"],
|
|
1321
1341
|
["manifest-drift", "versioned trust manifest drift"],
|
|
1322
1342
|
["capability-assurance", "effect/capability assurance"],
|
package/src/cli.ts
CHANGED
|
@@ -59,7 +59,7 @@ Usage:
|
|
|
59
59
|
agent: nifra_context, nifra_routes (routes+schemas as JSON),
|
|
60
60
|
nifra_run (backend), nifra_render (SSR a page), nifra_docs,
|
|
61
61
|
nifra_example (verified snippets), nifra_scaffold (route→file),
|
|
62
|
-
nifra_check (drift gate + fixes), nifra_doctor (
|
|
62
|
+
nifra_check (drift gate + fixes), nifra_doctor (deps + duplicate installs).
|
|
63
63
|
nifra docs-mcp [--port <n>] Serve the PUBLIC docs MCP over HTTP (nifra_docs + nifra_example) —
|
|
64
64
|
self-host on a VPS so any remote agent can learn nifra. Default :8787.
|
|
65
65
|
nifra check [--json] [--lints-only] Gate: typecheck + lints (hand-rolled fetch(), untyped client("…"),
|
|
@@ -91,8 +91,8 @@ Usage:
|
|
|
91
91
|
Compute the verification ladder (L0 typed contract → L1 route
|
|
92
92
|
assurance → L2 capability lockfile → L3 route manifest → L4
|
|
93
93
|
invariant-tested); --min <n> fails the exit code below level n.
|
|
94
|
-
nifra doctor [--json] [--auto-fix] Flag
|
|
95
|
-
|
|
94
|
+
nifra doctor [--json] [--auto-fix] Flag undeclared imports and multiple physical copies of
|
|
95
|
+
@nifrajs/core/React identity-sensitive dependencies;
|
|
96
96
|
--auto-fix writes safe local-version dependency fixes.
|
|
97
97
|
nifra upgrade <version> Run the per-release upgrade recipe for <version>: sweep every
|
|
98
98
|
[--write] [--no-verify] matching dependency pin (preserving ^/~/exact) + apply exact
|
|
@@ -114,7 +114,7 @@ Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with
|
|
|
114
114
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).`
|
|
115
115
|
|
|
116
116
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
117
|
-
const CLI_VERSION = "1.
|
|
117
|
+
const CLI_VERSION = "1.12.0"
|
|
118
118
|
|
|
119
119
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
120
120
|
const asAdapter = (v: unknown): RenderAdapter => v as RenderAdapter
|
package/src/doctor.ts
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* when a monorepo would hoist it. Relative paths, runtime builtins (node core, `node:`/`bun:`, `bun`),
|
|
11
11
|
* the package's own name, and tsconfig `paths` aliases are excluded — none of them are npm deps.
|
|
12
12
|
*/
|
|
13
|
+
import { realpath } from "node:fs/promises"
|
|
13
14
|
import { builtinModules } from "node:module"
|
|
14
|
-
import { dirname, join } from "node:path"
|
|
15
|
+
import { dirname, join, relative } from "node:path"
|
|
15
16
|
import { type SourceFinding, stripComments, walkSource } from "./check.ts"
|
|
16
17
|
|
|
17
18
|
// Runtime-provided modules that are never an npm dependency: Node core (bare + `node:` form) and Bun's
|
|
@@ -117,12 +118,28 @@ export interface DoctorResult {
|
|
|
117
118
|
/** `false` when no `package.json` was found at cwd — doctor can't run (reported, not a crash). */
|
|
118
119
|
readonly ran: boolean
|
|
119
120
|
readonly findings: readonly DoctorFinding[]
|
|
121
|
+
/** Packages that resolve to more than one physical install across this workspace. */
|
|
122
|
+
readonly duplicateInstalls: readonly DuplicateInstallFinding[]
|
|
120
123
|
/** Dependencies written by `--auto-fix` / MCP `autoFix:true`. */
|
|
121
124
|
readonly fixed?: readonly DoctorAppliedFix[]
|
|
122
125
|
/** Findings that were safe to report but not safe to write automatically. */
|
|
123
126
|
readonly skippedFixes?: readonly DoctorSkippedFix[]
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
export interface DuplicateInstallCopy {
|
|
130
|
+
/** Installed version, or `unknown` when package metadata is incomplete. */
|
|
131
|
+
readonly version: string
|
|
132
|
+
/** Physical package directory, relative to the doctor root when possible. */
|
|
133
|
+
readonly path: string
|
|
134
|
+
/** Workspace package roots whose normal Node/Bun resolution selects this copy. */
|
|
135
|
+
readonly importers: readonly string[]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface DuplicateInstallFinding {
|
|
139
|
+
readonly package: string
|
|
140
|
+
readonly copies: readonly DuplicateInstallCopy[]
|
|
141
|
+
}
|
|
142
|
+
|
|
126
143
|
export interface DoctorAppliedFix {
|
|
127
144
|
readonly package: string
|
|
128
145
|
readonly field: "dependencies"
|
|
@@ -159,6 +176,132 @@ const depNames = (pkg: Record<string, unknown>, field: string): string[] => {
|
|
|
159
176
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
160
177
|
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
161
178
|
|
|
179
|
+
const MAX_WORKSPACE_IMPORTERS = 2_048
|
|
180
|
+
const IDENTITY_SENSITIVE_PACKAGES = new Set(["@nifrajs/core", "react", "react-dom"])
|
|
181
|
+
|
|
182
|
+
function workspacePatterns(pkg: Record<string, unknown>): string[] {
|
|
183
|
+
const raw = pkg.workspaces
|
|
184
|
+
const entries = Array.isArray(raw)
|
|
185
|
+
? raw
|
|
186
|
+
: isRecord(raw) && Array.isArray(raw.packages)
|
|
187
|
+
? raw.packages
|
|
188
|
+
: []
|
|
189
|
+
return entries
|
|
190
|
+
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
|
|
191
|
+
.sort()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Discover package roots declared by npm/Bun's two workspace manifest shapes. Bounded so a hostile
|
|
195
|
+
* `**` pattern cannot turn a diagnostic into an unbounded filesystem crawl. */
|
|
196
|
+
async function workspaceImporters(
|
|
197
|
+
cwd: string,
|
|
198
|
+
rootPackage: Record<string, unknown>,
|
|
199
|
+
): Promise<Array<{ root: string; package: Record<string, unknown> }>> {
|
|
200
|
+
const manifests = new Set<string>([join(cwd, "package.json")])
|
|
201
|
+
for (const pattern of workspacePatterns(rootPackage)) {
|
|
202
|
+
const packagePattern = `${pattern.replace(/\/$/, "")}/package.json`
|
|
203
|
+
for await (const rel of new Bun.Glob(packagePattern).scan({ cwd, dot: false })) {
|
|
204
|
+
if (rel.split(/[\\/]/).includes("node_modules")) continue
|
|
205
|
+
manifests.add(join(cwd, rel))
|
|
206
|
+
// A pathological workspace pattern should not make doctor unbounded. Duplicate detection is
|
|
207
|
+
// skipped, while the existing source/declaration diagnostic still runs normally.
|
|
208
|
+
if (manifests.size > MAX_WORKSPACE_IMPORTERS) return []
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const out: Array<{ root: string; package: Record<string, unknown> }> = []
|
|
213
|
+
for (const manifest of [...manifests].sort()) {
|
|
214
|
+
const pkg = await readJson(manifest)
|
|
215
|
+
if (pkg !== undefined) out.push({ root: dirname(manifest), package: pkg })
|
|
216
|
+
}
|
|
217
|
+
return out
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function duplicateTargets(pkg: Record<string, unknown>): string[] {
|
|
221
|
+
const targets = new Set<string>()
|
|
222
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
223
|
+
for (const name of depNames(pkg, field)) {
|
|
224
|
+
if (name.startsWith("@nifrajs/") || IDENTITY_SENSITIVE_PACKAGES.has(name)) targets.add(name)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return [...targets].sort()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function resolvedInstalledCopy(
|
|
231
|
+
importer: string,
|
|
232
|
+
boundary: string,
|
|
233
|
+
name: string,
|
|
234
|
+
): Promise<{ path: string; version: string } | undefined> {
|
|
235
|
+
const parts = name.split("/")
|
|
236
|
+
for (let dir = importer; ; dir = dirname(dir)) {
|
|
237
|
+
const packageDir = join(dir, "node_modules", ...parts)
|
|
238
|
+
const meta = await readJson(join(packageDir, "package.json"))
|
|
239
|
+
if (meta !== undefined) {
|
|
240
|
+
try {
|
|
241
|
+
return {
|
|
242
|
+
path: await realpath(packageDir),
|
|
243
|
+
version:
|
|
244
|
+
typeof meta.version === "string" && meta.version.length > 0 ? meta.version : "unknown",
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
return undefined
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (dir === boundary) return undefined
|
|
251
|
+
const parent = dirname(dir)
|
|
252
|
+
if (parent === dir) return undefined
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const displayPath = (cwd: string, path: string): string => {
|
|
257
|
+
const rel = relative(cwd, path)
|
|
258
|
+
return rel === "" ? "." : rel
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Find identity-sensitive dependencies that resolve to multiple physical directories. Two copies at
|
|
262
|
+
* the same version still fail: module identity (React hooks, Nifra symbols/registries) is path-based. */
|
|
263
|
+
export async function collectDuplicateInstalls(
|
|
264
|
+
cwd: string,
|
|
265
|
+
rootPackage: Record<string, unknown>,
|
|
266
|
+
): Promise<DuplicateInstallFinding[]> {
|
|
267
|
+
const importers = await workspaceImporters(cwd, rootPackage)
|
|
268
|
+
const byPackage = new Map<string, Map<string, { version: string; importers: Set<string> }>>()
|
|
269
|
+
for (const importer of importers) {
|
|
270
|
+
for (const name of duplicateTargets(importer.package)) {
|
|
271
|
+
if (importer.package.name === name) continue
|
|
272
|
+
const copy = await resolvedInstalledCopy(importer.root, cwd, name)
|
|
273
|
+
if (copy === undefined) continue
|
|
274
|
+
let copies = byPackage.get(name)
|
|
275
|
+
if (copies === undefined) {
|
|
276
|
+
copies = new Map()
|
|
277
|
+
byPackage.set(name, copies)
|
|
278
|
+
}
|
|
279
|
+
const record = copies.get(copy.path) ?? {
|
|
280
|
+
version: copy.version,
|
|
281
|
+
importers: new Set<string>(),
|
|
282
|
+
}
|
|
283
|
+
record.importers.add(displayPath(cwd, importer.root))
|
|
284
|
+
copies.set(copy.path, record)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const findings: DuplicateInstallFinding[] = []
|
|
289
|
+
for (const [name, copies] of [...byPackage.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
290
|
+
if (copies.size < 2) continue
|
|
291
|
+
findings.push({
|
|
292
|
+
package: name,
|
|
293
|
+
copies: [...copies.entries()]
|
|
294
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
295
|
+
.map(([path, copy]) => ({
|
|
296
|
+
version: copy.version,
|
|
297
|
+
path: displayPath(cwd, path),
|
|
298
|
+
importers: [...copy.importers].sort(),
|
|
299
|
+
})),
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
return findings
|
|
303
|
+
}
|
|
304
|
+
|
|
162
305
|
function depRecord(
|
|
163
306
|
pkg: Record<string, unknown>,
|
|
164
307
|
field: string,
|
|
@@ -217,7 +360,7 @@ async function inferDependencyFix(
|
|
|
217
360
|
/** Run doctor against the project at `cwd`: diff source imports vs declared deps. */
|
|
218
361
|
export async function collectDoctorResult(cwd: string): Promise<DoctorResult> {
|
|
219
362
|
const pkg = await readJson(join(cwd, "package.json"))
|
|
220
|
-
if (pkg === undefined) return { ok: true, ran: false, findings: [] }
|
|
363
|
+
if (pkg === undefined) return { ok: true, ran: false, findings: [], duplicateInstalls: [] }
|
|
221
364
|
|
|
222
365
|
const declared = new Set<string>()
|
|
223
366
|
if (typeof pkg.name === "string") declared.add(pkg.name) // a package may import its own name (exports map)
|
|
@@ -238,7 +381,13 @@ export async function collectDoctorResult(cwd: string): Promise<DoctorResult> {
|
|
|
238
381
|
}
|
|
239
382
|
})
|
|
240
383
|
findings.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line)
|
|
241
|
-
|
|
384
|
+
const duplicateInstalls = await collectDuplicateInstalls(cwd, pkg)
|
|
385
|
+
return {
|
|
386
|
+
ok: findings.length === 0 && duplicateInstalls.length === 0,
|
|
387
|
+
ran: true,
|
|
388
|
+
findings,
|
|
389
|
+
duplicateInstalls,
|
|
390
|
+
}
|
|
242
391
|
}
|
|
243
392
|
|
|
244
393
|
/** Safely add undeclared imports to package.json when the version can be inferred without network I/O. */
|
|
@@ -327,7 +476,9 @@ export async function runDoctor(
|
|
|
327
476
|
console.log("")
|
|
328
477
|
}
|
|
329
478
|
if (result.ok) {
|
|
330
|
-
console.log(
|
|
479
|
+
console.log(
|
|
480
|
+
"✓ every imported package is declared and identity-sensitive installs are deduplicated",
|
|
481
|
+
)
|
|
331
482
|
return true
|
|
332
483
|
}
|
|
333
484
|
// Group by package so the fix ("add X to dependencies") is stated once with its import sites.
|
|
@@ -337,13 +488,30 @@ export async function runDoctor(
|
|
|
337
488
|
list.push(f)
|
|
338
489
|
byPkg.set(f.package, list)
|
|
339
490
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
491
|
+
if (byPkg.size > 0) {
|
|
492
|
+
console.log(`✗ ${byPkg.size} package(s) imported but not declared in package.json:\n`)
|
|
493
|
+
for (const [pkg, sites] of [...byPkg.entries()].sort()) {
|
|
494
|
+
console.log(` ${pkg} - add to dependencies (\`bun add ${pkg}\`)`)
|
|
495
|
+
for (const s of sites) console.log(` ${s.file}:${s.line}`)
|
|
496
|
+
}
|
|
344
497
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
498
|
+
if (result.duplicateInstalls.length > 0) {
|
|
499
|
+
console.log(
|
|
500
|
+
`${byPkg.size > 0 ? "\n" : ""}✗ identity-sensitive packages resolve to multiple physical copies:\n`,
|
|
501
|
+
)
|
|
502
|
+
for (const finding of result.duplicateInstalls) {
|
|
503
|
+
console.log(` ${finding.package}`)
|
|
504
|
+
for (const copy of finding.copies) {
|
|
505
|
+
console.log(` ${copy.version} at ${copy.path} ← ${copy.importers.join(", ")}`)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
console.log(
|
|
509
|
+
"\n Align dependency ranges and reinstall from the workspace root; doctor never deletes installs.",
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
if (byPkg.size > 0)
|
|
513
|
+
console.log(
|
|
514
|
+
"\nThese resolve at Bun runtime via hoisting/workspace but break `tsc` and a standalone install.",
|
|
515
|
+
)
|
|
348
516
|
return false
|
|
349
517
|
}
|
package/src/levels-tool.ts
CHANGED
|
@@ -15,8 +15,8 @@ import { existsSync } from "node:fs"
|
|
|
15
15
|
import { resolve } from "node:path"
|
|
16
16
|
import { evaluateRouteAssurance } from "@nifrajs/core/assurance"
|
|
17
17
|
import { type evaluateCapabilityAssurance, snapshotCapabilities } from "@nifrajs/core/capabilities"
|
|
18
|
-
import { runContractInvariants } from "@nifrajs/core/invariants"
|
|
19
18
|
import { buildNifraManifest, parseNifraManifest } from "@nifrajs/core/manifest"
|
|
19
|
+
import { type ContractTestApp, runAdversarialContract } from "@nifrajs/testing"
|
|
20
20
|
import { loadAssuranceConfig } from "./assure.ts"
|
|
21
21
|
import {
|
|
22
22
|
collectCapabilityProjectReport,
|
|
@@ -153,17 +153,27 @@ export async function collectVerificationLevels(
|
|
|
153
153
|
"no isolated invariant executor configured — add `invariants.executor` in nifra.assurance.ts",
|
|
154
154
|
)
|
|
155
155
|
} else {
|
|
156
|
-
const invariants = await
|
|
156
|
+
const invariants = await runAdversarialContract(config.source as ContractTestApp, {
|
|
157
157
|
seed: options.seed ?? 1,
|
|
158
|
-
|
|
158
|
+
runtimes: [{ name: "isolated", fetch: config.invariants.executor }],
|
|
159
|
+
expectedValidationStatuses: [400, 422],
|
|
160
|
+
requireCoverage: true,
|
|
159
161
|
})
|
|
160
|
-
if (!invariants.ok)
|
|
161
|
-
if (invariants.skipped.length > 0) {
|
|
162
|
+
if (!invariants.ok) {
|
|
162
163
|
l4Reasons.push(
|
|
163
|
-
...invariants.
|
|
164
|
+
...invariants.failures
|
|
164
165
|
.slice(0, 5)
|
|
165
166
|
.map(
|
|
166
|
-
(
|
|
167
|
+
(failure) =>
|
|
168
|
+
`${failure.runtime}: ${failure.id} - ${failure.message} (replay seed ${failure.replay.seed})`,
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
l4Reasons.push(
|
|
172
|
+
...invariants.gaps
|
|
173
|
+
.slice(0, Math.max(0, 5 - l4Reasons.length))
|
|
174
|
+
.map(
|
|
175
|
+
(gap) =>
|
|
176
|
+
`${gap.route ?? "contract"}${gap.target ? ` ${gap.target}` : ""}: ${gap.message}`,
|
|
167
177
|
),
|
|
168
178
|
)
|
|
169
179
|
}
|
package/src/mcp-http.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { docsTools } from "./mcp-docs-tools.ts"
|
|
|
26
26
|
export type { TypeEntry } from "./types-search.ts"
|
|
27
27
|
|
|
28
28
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
29
|
-
const VERSION = "1.
|
|
29
|
+
const VERSION = "1.12.0"
|
|
30
30
|
const SERVER_INFO = { name: "nifra-docs", version: VERSION }
|
|
31
31
|
const DOCS_HEALTH =
|
|
32
32
|
"nifra docs MCP — POST JSON-RPC 2.0 here (methods: initialize, tools/list, tools/call). Tools: nifra_docs, nifra_example."
|