@nifrajs/cli 2.1.0 → 2.3.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 +25 -25
- package/dist/assure.d.ts +2 -2
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +17 -10
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +7 -0
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +68 -16
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check.d.ts +64 -22
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +400 -83
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +37 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +291 -53
- package/dist/cli.js.map +1 -1
- package/dist/diff-tool.d.ts +3 -3
- package/dist/diff-tool.js +7 -7
- package/dist/docs-search.d.ts +3 -3
- package/dist/docs-search.js +6 -6
- package/dist/doctor.d.ts +8 -4
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +74 -14
- package/dist/doctor.js.map +1 -1
- package/dist/examples.d.ts +5 -5
- package/dist/examples.js +5 -5
- package/dist/init-agents.d.ts +14 -14
- package/dist/init-agents.js +14 -14
- package/dist/internal/typescript-import.d.ts +24 -0
- package/dist/internal/typescript-import.d.ts.map +1 -0
- package/dist/internal/typescript-import.js +32 -0
- package/dist/internal/typescript-import.js.map +1 -0
- package/dist/introspect.d.ts +12 -12
- package/dist/introspect.js +24 -24
- package/dist/levels-tool.d.ts +8 -8
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +33 -30
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +17 -6
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +45 -7
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +4 -2
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-docs-tools.d.ts +1 -1
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +14 -10
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-http.d.ts +5 -5
- package/dist/mcp-http.js +7 -7
- package/dist/mcp-protocol.d.ts +2 -2
- package/dist/mcp-protocol.js +2 -2
- package/dist/mcp-render.d.ts +3 -3
- package/dist/mcp-render.js +15 -7
- package/dist/mcp-render.js.map +1 -1
- package/dist/mcp-run.d.ts +2 -2
- package/dist/mcp-run.js +2 -2
- package/dist/mcp.d.ts +14 -14
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +119 -47
- package/dist/mcp.js.map +1 -1
- package/dist/openapi-tool.d.ts +1 -1
- package/dist/openapi-tool.js +2 -2
- package/dist/pipeline-guard.d.ts +83 -0
- package/dist/pipeline-guard.d.ts.map +1 -0
- package/dist/pipeline-guard.js +136 -0
- package/dist/pipeline-guard.js.map +1 -0
- package/dist/port.d.ts +9 -9
- package/dist/port.js +27 -27
- package/dist/recipes/1.8.0.d.ts +2 -2
- package/dist/recipes/1.8.0.js +5 -5
- package/dist/recipes/index.d.ts +2 -2
- package/dist/recipes/index.js +1 -1
- package/dist/scaffold.d.ts +3 -3
- package/dist/scaffold.js +8 -8
- package/dist/sync-routes.d.ts +28 -0
- package/dist/sync-routes.d.ts.map +1 -0
- package/dist/sync-routes.js +55 -0
- package/dist/sync-routes.js.map +1 -0
- package/dist/types-search.d.ts +6 -6
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +53 -8
- package/dist/types-search.js.map +1 -1
- package/dist/upgrade.d.ts +7 -7
- package/dist/upgrade.js +12 -12
- package/dist/verification.d.ts +51 -0
- package/dist/verification.d.ts.map +1 -0
- package/dist/verification.js +80 -0
- package/dist/verification.js.map +1 -0
- package/docs/examples.json +178 -88
- package/docs/llms-full.txt +1766 -766
- package/docs/types.json +2171 -2807
- package/package.json +22 -11
- package/src/assure.ts +17 -18
- package/src/capabilities-tool.ts +82 -16
- package/src/check.ts +493 -100
- package/src/cli.ts +306 -54
- package/src/diff-tool.ts +8 -8
- package/src/docs-search.ts +6 -6
- package/src/doctor.ts +81 -16
- package/src/examples.ts +7 -7
- package/src/init-agents.ts +17 -17
- package/src/internal/typescript-import.ts +33 -0
- package/src/introspect.ts +28 -28
- package/src/levels-tool.ts +37 -31
- package/src/load.ts +65 -12
- package/src/manifest-tool.ts +4 -2
- package/src/mcp-docs-tools.ts +20 -10
- package/src/mcp-http.ts +7 -7
- package/src/mcp-protocol.ts +2 -2
- package/src/mcp-render.ts +15 -7
- package/src/mcp-run.ts +2 -2
- package/src/mcp.ts +127 -47
- package/src/openapi-tool.ts +2 -2
- package/src/pipeline-guard.ts +186 -0
- package/src/port.ts +31 -31
- package/src/recipes/1.8.0.ts +5 -5
- package/src/recipes/index.ts +2 -2
- package/src/scaffold.ts +8 -8
- package/src/sync-routes.ts +69 -0
- package/src/types-search.ts +57 -8
- package/src/upgrade.ts +12 -12
- package/src/verification.ts +123 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/cli",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "The nifra CLI
|
|
3
|
+
"version": "2.3.0",
|
|
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",
|
|
7
7
|
"typescript",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@nifrajs/client": "2.
|
|
45
|
-
"@nifrajs/core": "2.
|
|
46
|
-
"@nifrajs/mcp": "2.
|
|
47
|
-
"@nifrajs/runner": "2.
|
|
48
|
-
"@nifrajs/schema": "2.
|
|
49
|
-
"@nifrajs/testing": "2.
|
|
50
|
-
"@nifrajs/web": "2.
|
|
51
|
-
"create-nifra": "2.
|
|
44
|
+
"@nifrajs/client": "2.3.0",
|
|
45
|
+
"@nifrajs/core": "2.3.0",
|
|
46
|
+
"@nifrajs/mcp": "2.3.0",
|
|
47
|
+
"@nifrajs/runner": "2.3.0",
|
|
48
|
+
"@nifrajs/schema": "2.3.0",
|
|
49
|
+
"@nifrajs/testing": "2.3.0",
|
|
50
|
+
"@nifrajs/web": "2.3.0",
|
|
51
|
+
"create-nifra": "2.3.0"
|
|
52
52
|
},
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
|
@@ -58,5 +58,16 @@
|
|
|
58
58
|
"bugs": {
|
|
59
59
|
"url": "https://github.com/nifrajs/nifra/issues"
|
|
60
60
|
},
|
|
61
|
-
"homepage": "https://github.com/nifrajs/nifra#readme"
|
|
61
|
+
"homepage": "https://github.com/nifrajs/nifra#readme",
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"typescript": "^5.0.0 || ^6.0.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"typescript": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"typescript": "^6.0.3"
|
|
72
|
+
}
|
|
62
73
|
}
|
package/src/assure.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
/** `nifra assure`
|
|
1
|
+
/** `nifra assure` - evaluate a project's reflected routes against `nifra.assurance.ts`. */
|
|
2
2
|
|
|
3
3
|
import { existsSync } from "node:fs"
|
|
4
4
|
import { resolve } from "node:path"
|
|
5
5
|
import { pathToFileURL } from "node:url"
|
|
6
|
-
import {
|
|
7
|
-
type AssuranceConfig,
|
|
8
|
-
type AssuranceReport,
|
|
9
|
-
evaluateRouteAssurance,
|
|
10
|
-
} from "@nifrajs/core/assurance"
|
|
6
|
+
import type { AssuranceConfig, AssuranceReport } from "@nifrajs/core/assurance"
|
|
11
7
|
|
|
12
8
|
export const DEFAULT_ASSURANCE_CONFIG = "nifra.assurance.ts"
|
|
13
9
|
|
|
@@ -28,7 +24,7 @@ export async function loadAssuranceConfig(
|
|
|
28
24
|
const path = resolve(cwd, configPath)
|
|
29
25
|
if (!existsSync(path)) {
|
|
30
26
|
throw new Error(
|
|
31
|
-
`[nifra] route assurance config not found: ${path}
|
|
27
|
+
`[nifra] route assurance config not found: ${path} - create ${DEFAULT_ASSURANCE_CONFIG} with a default defineAssuranceConfig({ source, policy }) export.`,
|
|
32
28
|
)
|
|
33
29
|
}
|
|
34
30
|
const specifier = pathToFileURL(path)
|
|
@@ -60,19 +56,22 @@ export async function collectAssuranceReport(
|
|
|
60
56
|
cwd: string,
|
|
61
57
|
configPath?: string,
|
|
62
58
|
): Promise<AssuranceReport> {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
// The route-assurance view over the one project verification. The config load, route reflection, and
|
|
60
|
+
// capability walk it needs are exactly what `collectProjectVerification` already ran.
|
|
61
|
+
const { collectProjectVerification } = await import("./verification.ts")
|
|
62
|
+
const verification = await collectProjectVerification(cwd, {
|
|
63
|
+
...(configPath !== undefined ? { config: configPath } : {}),
|
|
64
|
+
})
|
|
65
|
+
// A missing/broken config threw here before; re-throw the same error to keep that contract.
|
|
66
|
+
if (verification.configError !== undefined) throw verification.configError
|
|
67
|
+
// A present config means routeAssurance is computed; capability is set exactly when the config
|
|
68
|
+
// declares a capabilities policy.
|
|
69
|
+
const routeReport = verification.routeAssurance as AssuranceReport
|
|
70
|
+
if (verification.capability === undefined) return routeReport
|
|
72
71
|
return Object.freeze({
|
|
73
72
|
...routeReport,
|
|
74
|
-
ok: routeReport.ok &&
|
|
75
|
-
capabilities:
|
|
73
|
+
ok: routeReport.ok && verification.capability.report.ok,
|
|
74
|
+
capabilities: verification.capability.report,
|
|
76
75
|
})
|
|
77
76
|
}
|
|
78
77
|
|
package/src/capabilities-tool.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
validCapabilityId,
|
|
15
15
|
} from "@nifrajs/core/capabilities"
|
|
16
16
|
import { reflectRoutes } from "@nifrajs/core/reflection"
|
|
17
|
-
import { loadAssuranceConfig } from "./assure.ts"
|
|
18
17
|
import { scanStaticRouteText, stripComments, walkSource } from "./check.ts"
|
|
19
18
|
|
|
20
19
|
const EFFECT_IMPORT =
|
|
@@ -61,6 +60,14 @@ export interface CapabilityImportViolation {
|
|
|
61
60
|
export interface CapabilityProjectReport {
|
|
62
61
|
readonly report: CapabilityAssuranceReport
|
|
63
62
|
readonly violations: readonly CapabilityImportViolation[]
|
|
63
|
+
readonly truncations: readonly CapabilityProvenanceTruncation[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CapabilityProvenanceTruncation {
|
|
67
|
+
readonly method: string
|
|
68
|
+
readonly path: string
|
|
69
|
+
readonly reason: "depth-limit" | "module-limit"
|
|
70
|
+
readonly chain: readonly string[]
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
function resolveLocalModule(
|
|
@@ -113,8 +120,12 @@ interface WalkResult {
|
|
|
113
120
|
readonly covered: boolean
|
|
114
121
|
readonly evidence: Array<{ id: string; kind: "static"; source: string }>
|
|
115
122
|
readonly violations: CapabilityImportViolation[]
|
|
123
|
+
readonly truncations: CapabilityProvenanceTruncation[]
|
|
116
124
|
}
|
|
117
125
|
|
|
126
|
+
const MAX_PROVENANCE_MODULES = 500
|
|
127
|
+
const MAX_PROVENANCE_CHAIN = 16
|
|
128
|
+
|
|
118
129
|
async function walkRouteModules(
|
|
119
130
|
cwd: string,
|
|
120
131
|
modules: readonly string[],
|
|
@@ -127,11 +138,34 @@ async function walkRouteModules(
|
|
|
127
138
|
const visited = new Set<string>()
|
|
128
139
|
const evidence: Array<{ id: string; kind: "static"; source: string }> = []
|
|
129
140
|
const violations: CapabilityImportViolation[] = []
|
|
141
|
+
const truncations: CapabilityProvenanceTruncation[] = []
|
|
130
142
|
let covered = false
|
|
131
143
|
|
|
132
|
-
while (queue.length > 0
|
|
144
|
+
while (queue.length > 0) {
|
|
145
|
+
if (visited.size >= MAX_PROVENANCE_MODULES) {
|
|
146
|
+
const pending = queue[0]
|
|
147
|
+
truncations.push({
|
|
148
|
+
method,
|
|
149
|
+
path,
|
|
150
|
+
reason: "module-limit",
|
|
151
|
+
chain: Object.freeze([
|
|
152
|
+
...(pending?.chain ?? []),
|
|
153
|
+
`<${MAX_PROVENANCE_MODULES}-module limit>`,
|
|
154
|
+
]),
|
|
155
|
+
})
|
|
156
|
+
break
|
|
157
|
+
}
|
|
133
158
|
const current = queue.shift()
|
|
134
|
-
if (current === undefined || visited.has(current.module)
|
|
159
|
+
if (current === undefined || visited.has(current.module)) continue
|
|
160
|
+
if (current.chain.length > MAX_PROVENANCE_CHAIN) {
|
|
161
|
+
truncations.push({
|
|
162
|
+
method,
|
|
163
|
+
path,
|
|
164
|
+
reason: "depth-limit",
|
|
165
|
+
chain: Object.freeze([...current.chain, `<${MAX_PROVENANCE_CHAIN}-hop limit>`]),
|
|
166
|
+
})
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
135
169
|
visited.add(current.module)
|
|
136
170
|
let content = sources.get(current.module)
|
|
137
171
|
if (content === undefined) {
|
|
@@ -174,7 +208,7 @@ async function walkRouteModules(
|
|
|
174
208
|
}
|
|
175
209
|
}
|
|
176
210
|
}
|
|
177
|
-
return { covered, evidence, violations }
|
|
211
|
+
return { covered, evidence, violations, truncations }
|
|
178
212
|
}
|
|
179
213
|
|
|
180
214
|
/** Build coverage-qualified static evidence for every reflected route. */
|
|
@@ -196,6 +230,7 @@ export async function collectCapabilityProjectReport(
|
|
|
196
230
|
|
|
197
231
|
const evidenceRoutes = []
|
|
198
232
|
const violations: CapabilityImportViolation[] = []
|
|
233
|
+
const truncations: CapabilityProvenanceTruncation[] = []
|
|
199
234
|
for (const route of reflectRoutes(source)) {
|
|
200
235
|
const modules = new Set(automatic.get(routeKey(route.method, route.path)) ?? [])
|
|
201
236
|
for (const association of policy.provenance.routeModules ?? []) {
|
|
@@ -225,10 +260,11 @@ export async function collectCapabilityProjectReport(
|
|
|
225
260
|
evidence: walked.evidence,
|
|
226
261
|
})
|
|
227
262
|
violations.push(...walked.violations)
|
|
263
|
+
truncations.push(...walked.truncations)
|
|
228
264
|
}
|
|
229
265
|
const evaluated = evaluateCapabilityAssurance(source, policy, { routes: evidenceRoutes })
|
|
230
266
|
const report: CapabilityAssuranceReport =
|
|
231
|
-
violations.length === 0
|
|
267
|
+
violations.length === 0 && truncations.length === 0
|
|
232
268
|
? evaluated
|
|
233
269
|
: Object.freeze({
|
|
234
270
|
...evaluated,
|
|
@@ -241,9 +277,19 @@ export async function collectCapabilityProjectReport(
|
|
|
241
277
|
path: violation.path,
|
|
242
278
|
message: `${violation.method} ${violation.path} reaches forbidden ${violation.specifier} via ${violation.chain.join(" → ")}: ${violation.reason}`,
|
|
243
279
|
})),
|
|
280
|
+
...truncations.map((truncation) => ({
|
|
281
|
+
code: "provenance-truncated" as const,
|
|
282
|
+
method: truncation.method,
|
|
283
|
+
path: truncation.path,
|
|
284
|
+
message: `${truncation.method} ${truncation.path} capability provenance hit the ${truncation.reason === "depth-limit" ? "import-depth" : "module-count"} safety limit via ${truncation.chain.join(" → ")}; assurance cannot prove the remaining graph`,
|
|
285
|
+
})),
|
|
244
286
|
]),
|
|
245
287
|
})
|
|
246
|
-
return Object.freeze({
|
|
288
|
+
return Object.freeze({
|
|
289
|
+
report,
|
|
290
|
+
violations: Object.freeze(violations),
|
|
291
|
+
truncations: Object.freeze(truncations),
|
|
292
|
+
})
|
|
247
293
|
}
|
|
248
294
|
|
|
249
295
|
export function parseCapabilityLockfile(content: string, sourcePath: string): CapabilitySnapshot {
|
|
@@ -261,7 +307,7 @@ export function parseCapabilityLockfile(content: string, sourcePath: string): Ca
|
|
|
261
307
|
!Array.isArray(candidate.routes)
|
|
262
308
|
) {
|
|
263
309
|
throw new Error(
|
|
264
|
-
`[nifra] ${sourcePath} is not a nifra capability lockfile
|
|
310
|
+
`[nifra] ${sourcePath} is not a nifra capability lockfile - expected { "nifraCapabilities": 1, "routes": [...] }`,
|
|
265
311
|
)
|
|
266
312
|
}
|
|
267
313
|
for (const route of candidate.routes) {
|
|
@@ -341,18 +387,28 @@ async function currentProject(
|
|
|
341
387
|
readonly policy: CapabilityPolicy
|
|
342
388
|
readonly project: CapabilityProjectReport
|
|
343
389
|
}> {
|
|
344
|
-
|
|
345
|
-
|
|
390
|
+
// Source the capability report from the one project verification that `check`/`assure`/`levels` also
|
|
391
|
+
// read, rather than loading the config and walking the module graph a second time here. `snapshot` and
|
|
392
|
+
// `check` are then thin faces over the shared core; the lockfile write/diff is the only work that stays
|
|
393
|
+
// capability-specific.
|
|
394
|
+
const { collectProjectVerification } = await import("./verification.ts")
|
|
395
|
+
const verification = await collectProjectVerification(
|
|
396
|
+
cwd,
|
|
397
|
+
configPath !== undefined ? { config: configPath } : {},
|
|
398
|
+
)
|
|
399
|
+
// A missing/broken config threw here before; re-throw the same error to keep that contract.
|
|
400
|
+
if (verification.configError !== undefined) throw verification.configError
|
|
401
|
+
const policy = verification.config?.capabilities
|
|
402
|
+
if (policy === undefined) {
|
|
346
403
|
throw new Error("[nifra] assurance config does not define capabilities")
|
|
347
404
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
405
|
+
// The core computes `capability` exactly when the config declares a capabilities policy, so it is
|
|
406
|
+
// present whenever `policy` is.
|
|
407
|
+
return { policy, project: verification.capability as CapabilityProjectReport }
|
|
352
408
|
}
|
|
353
409
|
|
|
354
410
|
function unsafeProject(project: CapabilityProjectReport): boolean {
|
|
355
|
-
return !project.report.ok || project.violations.length > 0
|
|
411
|
+
return !project.report.ok || project.violations.length > 0 || project.truncations.length > 0
|
|
356
412
|
}
|
|
357
413
|
|
|
358
414
|
/** Write a lockfile only from a clean, fully-covered project report. */
|
|
@@ -387,7 +443,13 @@ export async function runCapabilityCheck(
|
|
|
387
443
|
if (options.json === true) {
|
|
388
444
|
console.log(
|
|
389
445
|
JSON.stringify(
|
|
390
|
-
{
|
|
446
|
+
{
|
|
447
|
+
ok,
|
|
448
|
+
report: project.report,
|
|
449
|
+
violations: project.violations,
|
|
450
|
+
truncations: project.truncations,
|
|
451
|
+
changes,
|
|
452
|
+
},
|
|
391
453
|
null,
|
|
392
454
|
2,
|
|
393
455
|
),
|
|
@@ -398,7 +460,11 @@ export async function runCapabilityCheck(
|
|
|
398
460
|
for (const finding of project.report.findings) console.log(`✖ ${finding.message}`)
|
|
399
461
|
for (const violation of project.violations)
|
|
400
462
|
console.log(
|
|
401
|
-
`✖ ${violation.method} ${violation.path}: ${violation.chain.join(" → ")}
|
|
463
|
+
`✖ ${violation.method} ${violation.path}: ${violation.chain.join(" → ")} - ${violation.reason}`,
|
|
464
|
+
)
|
|
465
|
+
for (const truncation of project.truncations)
|
|
466
|
+
console.log(
|
|
467
|
+
`✖ ${truncation.method} ${truncation.path}: ${truncation.chain.join(" → ")} - ${truncation.reason}`,
|
|
402
468
|
)
|
|
403
469
|
for (const change of changes) console.log(`✖ capability lock drift: ${change}`)
|
|
404
470
|
}
|