@nifrajs/cli 2.11.0 → 2.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.
Files changed (177) hide show
  1. package/README.md +6 -0
  2. package/dist/assure-hydration.d.ts +14 -0
  3. package/dist/assure-hydration.d.ts.map +1 -0
  4. package/dist/assure-hydration.js +483 -0
  5. package/dist/assure-hydration.js.map +1 -0
  6. package/dist/assure.d.ts +35 -0
  7. package/dist/assure.d.ts.map +1 -1
  8. package/dist/assure.js +222 -1
  9. package/dist/assure.js.map +1 -1
  10. package/dist/capabilities-tool.d.ts +13 -0
  11. package/dist/capabilities-tool.d.ts.map +1 -1
  12. package/dist/capabilities-tool.js +64 -1
  13. package/dist/capabilities-tool.js.map +1 -1
  14. package/dist/check.d.ts +125 -10
  15. package/dist/check.d.ts.map +1 -1
  16. package/dist/check.js +706 -145
  17. package/dist/check.js.map +1 -1
  18. package/dist/cli.d.ts +23 -0
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +194 -10
  21. package/dist/cli.js.map +1 -1
  22. package/dist/contract-proof.d.ts +49 -0
  23. package/dist/contract-proof.d.ts.map +1 -0
  24. package/dist/contract-proof.js +128 -0
  25. package/dist/contract-proof.js.map +1 -0
  26. package/dist/contracts.d.ts +22 -0
  27. package/dist/contracts.d.ts.map +1 -0
  28. package/dist/contracts.js +134 -0
  29. package/dist/contracts.js.map +1 -0
  30. package/dist/diagnostics.d.ts +23 -0
  31. package/dist/diagnostics.d.ts.map +1 -0
  32. package/dist/diagnostics.js +9 -0
  33. package/dist/diagnostics.js.map +1 -0
  34. package/dist/diff-tool.d.ts +2 -0
  35. package/dist/diff-tool.d.ts.map +1 -1
  36. package/dist/diff-tool.js +1 -1
  37. package/dist/diff-tool.js.map +1 -1
  38. package/dist/docs-search.d.ts +8 -3
  39. package/dist/docs-search.d.ts.map +1 -1
  40. package/dist/docs-search.js +36 -15
  41. package/dist/docs-search.js.map +1 -1
  42. package/dist/doctor.d.ts +28 -2
  43. package/dist/doctor.d.ts.map +1 -1
  44. package/dist/doctor.js +173 -7
  45. package/dist/doctor.js.map +1 -1
  46. package/dist/examples.d.ts +1 -2
  47. package/dist/examples.d.ts.map +1 -1
  48. package/dist/examples.js +37 -16
  49. package/dist/examples.js.map +1 -1
  50. package/dist/fix-recipes.d.ts +12 -0
  51. package/dist/fix-recipes.d.ts.map +1 -0
  52. package/dist/fix-recipes.js +106 -0
  53. package/dist/fix-recipes.js.map +1 -0
  54. package/dist/init-agents.d.ts.map +1 -1
  55. package/dist/init-agents.js +60 -4
  56. package/dist/init-agents.js.map +1 -1
  57. package/dist/internal/source-facts.d.ts +15 -0
  58. package/dist/internal/source-facts.d.ts.map +1 -0
  59. package/dist/internal/source-facts.js +82 -0
  60. package/dist/internal/source-facts.js.map +1 -0
  61. package/dist/internal/typescript-import.d.ts +9 -0
  62. package/dist/internal/typescript-import.d.ts.map +1 -1
  63. package/dist/internal/typescript-import.js +38 -0
  64. package/dist/internal/typescript-import.js.map +1 -1
  65. package/dist/introspect.d.ts +6 -2
  66. package/dist/introspect.d.ts.map +1 -1
  67. package/dist/introspect.js +53 -4
  68. package/dist/introspect.js.map +1 -1
  69. package/dist/load.d.ts +6 -0
  70. package/dist/load.d.ts.map +1 -1
  71. package/dist/load.js.map +1 -1
  72. package/dist/manifest-tool.d.ts.map +1 -1
  73. package/dist/manifest-tool.js +14 -9
  74. package/dist/manifest-tool.js.map +1 -1
  75. package/dist/mcp-docs-tools.d.ts.map +1 -1
  76. package/dist/mcp-docs-tools.js +3 -0
  77. package/dist/mcp-docs-tools.js.map +1 -1
  78. package/dist/mcp-http.js +1 -1
  79. package/dist/mcp-root.d.ts +83 -0
  80. package/dist/mcp-root.d.ts.map +1 -0
  81. package/dist/mcp-root.js +221 -0
  82. package/dist/mcp-root.js.map +1 -0
  83. package/dist/mcp-run.d.ts.map +1 -1
  84. package/dist/mcp-run.js +18 -4
  85. package/dist/mcp-run.js.map +1 -1
  86. package/dist/mcp.d.ts +27 -4
  87. package/dist/mcp.d.ts.map +1 -1
  88. package/dist/mcp.js +358 -25
  89. package/dist/mcp.js.map +1 -1
  90. package/dist/project-path.d.ts +3 -0
  91. package/dist/project-path.d.ts.map +1 -0
  92. package/dist/project-path.js +36 -0
  93. package/dist/project-path.js.map +1 -0
  94. package/dist/replay.d.ts +12 -0
  95. package/dist/replay.d.ts.map +1 -0
  96. package/dist/replay.js +36 -0
  97. package/dist/replay.js.map +1 -0
  98. package/dist/rules/codes.d.ts +42 -0
  99. package/dist/rules/codes.d.ts.map +1 -0
  100. package/dist/rules/codes.js +52 -0
  101. package/dist/rules/codes.js.map +1 -0
  102. package/dist/rules/comment-markers.d.ts +10 -0
  103. package/dist/rules/comment-markers.d.ts.map +1 -0
  104. package/dist/rules/comment-markers.js +24 -0
  105. package/dist/rules/comment-markers.js.map +1 -0
  106. package/dist/rules/index.d.ts +31 -0
  107. package/dist/rules/index.d.ts.map +1 -0
  108. package/dist/rules/index.js +133 -0
  109. package/dist/rules/index.js.map +1 -0
  110. package/dist/rules/legacy.d.ts +7 -0
  111. package/dist/rules/legacy.d.ts.map +1 -0
  112. package/dist/rules/legacy.js +85 -0
  113. package/dist/rules/legacy.js.map +1 -0
  114. package/dist/rules/routes.d.ts +12 -0
  115. package/dist/rules/routes.d.ts.map +1 -0
  116. package/dist/rules/routes.js +142 -0
  117. package/dist/rules/routes.js.map +1 -0
  118. package/dist/rules/security.d.ts +10 -0
  119. package/dist/rules/security.d.ts.map +1 -0
  120. package/dist/rules/security.js +471 -0
  121. package/dist/rules/security.js.map +1 -0
  122. package/dist/search-terms.d.ts +2 -0
  123. package/dist/search-terms.d.ts.map +1 -1
  124. package/dist/search-terms.js +5 -1
  125. package/dist/search-terms.js.map +1 -1
  126. package/dist/types-search.d.ts +1 -2
  127. package/dist/types-search.d.ts.map +1 -1
  128. package/dist/types-search.js +33 -16
  129. package/dist/types-search.js.map +1 -1
  130. package/dist/verification.d.ts +9 -0
  131. package/dist/verification.d.ts.map +1 -1
  132. package/dist/verification.js +17 -1
  133. package/dist/verification.js.map +1 -1
  134. package/dist/work-graph.d.ts +123 -0
  135. package/dist/work-graph.d.ts.map +1 -0
  136. package/dist/work-graph.js +465 -0
  137. package/dist/work-graph.js.map +1 -0
  138. package/docs/examples.json +32 -2
  139. package/docs/llms-full.txt +906 -15
  140. package/docs/types.json +1682 -130
  141. package/package.json +34 -9
  142. package/src/assure-hydration.ts +606 -0
  143. package/src/assure.ts +316 -2
  144. package/src/capabilities-tool.ts +80 -1
  145. package/src/check.ts +873 -168
  146. package/src/cli.ts +198 -11
  147. package/src/contract-proof.ts +194 -0
  148. package/src/contracts.ts +157 -0
  149. package/src/diagnostics.ts +32 -0
  150. package/src/diff-tool.ts +1 -1
  151. package/src/docs-search.ts +45 -15
  152. package/src/doctor.ts +259 -8
  153. package/src/examples.ts +43 -15
  154. package/src/fix-recipes.ts +126 -0
  155. package/src/init-agents.ts +61 -4
  156. package/src/internal/source-facts.ts +133 -0
  157. package/src/internal/typescript-import.ts +29 -0
  158. package/src/introspect.ts +75 -6
  159. package/src/load.ts +6 -0
  160. package/src/manifest-tool.ts +16 -10
  161. package/src/mcp-docs-tools.ts +3 -0
  162. package/src/mcp-http.ts +1 -1
  163. package/src/mcp-root.ts +271 -0
  164. package/src/mcp-run.ts +19 -4
  165. package/src/mcp.ts +438 -32
  166. package/src/project-path.ts +36 -0
  167. package/src/replay.ts +44 -0
  168. package/src/rules/codes.ts +57 -0
  169. package/src/rules/comment-markers.ts +26 -0
  170. package/src/rules/index.ts +175 -0
  171. package/src/rules/legacy.ts +93 -0
  172. package/src/rules/routes.ts +157 -0
  173. package/src/rules/security.ts +505 -0
  174. package/src/search-terms.ts +9 -4
  175. package/src/types-search.ts +39 -15
  176. package/src/verification.ts +18 -1
  177. package/src/work-graph.ts +613 -0
@@ -0,0 +1,133 @@
1
+ import { isBuiltInCode } from "./codes.js";
2
+ export function validateRulePacks(packs) {
3
+ if (packs === undefined)
4
+ return [];
5
+ const names = new Set();
6
+ const codes = new Set();
7
+ const normalized = [];
8
+ for (const pack of packs) {
9
+ if (typeof pack?.name !== "string" || pack.name.trim() === "")
10
+ throw new Error("rule pack name must be a non-empty string");
11
+ if (names.has(pack.name))
12
+ throw new Error(`duplicate rule pack: ${pack.name}`);
13
+ names.add(pack.name);
14
+ if (!Array.isArray(pack.rules))
15
+ throw new Error(`rule pack ${pack.name} must contain rules`);
16
+ const rules = [];
17
+ for (const rule of pack.rules) {
18
+ if (typeof rule?.code !== "string" ||
19
+ typeof rule.title !== "string" ||
20
+ typeof rule.scan !== "function") {
21
+ throw new Error(`rule pack ${pack.name} contains an invalid rule`);
22
+ }
23
+ if (rule.code.startsWith("NF-"))
24
+ throw new Error(`application rule pack code ${rule.code} uses the reserved NF- prefix`);
25
+ if (codes.has(rule.code))
26
+ throw new Error(`duplicate application rule code: ${rule.code}`);
27
+ codes.add(rule.code);
28
+ rules.push(rule);
29
+ }
30
+ normalized.push(Object.freeze({ name: pack.name, rules: Object.freeze(rules) }));
31
+ }
32
+ return Object.freeze(normalized);
33
+ }
34
+ export function parseRulePacks(value) {
35
+ if (value === undefined)
36
+ return [];
37
+ if (!Array.isArray(value))
38
+ throw new TypeError("rulePacks must be an array");
39
+ const packs = [];
40
+ for (const rawPack of value) {
41
+ if (typeof rawPack !== "object" || rawPack === null || Array.isArray(rawPack))
42
+ throw new TypeError("rule pack must be an object");
43
+ const packRecord = Object.fromEntries(Object.entries(rawPack));
44
+ const name = packRecord.name;
45
+ const rawRules = packRecord.rules;
46
+ if (typeof name !== "string" || !Array.isArray(rawRules))
47
+ throw new TypeError("rule pack requires a name and rules array");
48
+ const rules = [];
49
+ for (const rawRule of rawRules) {
50
+ if (typeof rawRule !== "object" || rawRule === null || Array.isArray(rawRule))
51
+ throw new TypeError(`rule pack ${name} contains an invalid rule`);
52
+ const ruleRecord = Object.fromEntries(Object.entries(rawRule));
53
+ if (typeof ruleRecord.code !== "string" ||
54
+ typeof ruleRecord.title !== "string" ||
55
+ typeof ruleRecord.scan !== "function") {
56
+ throw new TypeError(`rule pack ${name} contains an invalid rule`);
57
+ }
58
+ const scan = ruleRecord.scan;
59
+ rules.push({
60
+ code: ruleRecord.code,
61
+ title: ruleRecord.title,
62
+ scan: async (ctx) => {
63
+ const result = await Reflect.apply(scan, undefined, [ctx]);
64
+ if (!Array.isArray(result))
65
+ throw new TypeError(`rule ${ruleRecord.code} did not return diagnostics`);
66
+ const findings = [];
67
+ for (const item of result) {
68
+ if (typeof item !== "object" || item === null || Array.isArray(item))
69
+ throw new TypeError(`rule ${ruleRecord.code} returned an invalid diagnostic`);
70
+ const finding = Object.fromEntries(Object.entries(item));
71
+ if (typeof finding.code !== "string" ||
72
+ (finding.severity !== "error" &&
73
+ finding.severity !== "warn" &&
74
+ finding.severity !== "info") ||
75
+ typeof finding.message !== "string") {
76
+ throw new TypeError(`rule ${ruleRecord.code} returned an invalid diagnostic`);
77
+ }
78
+ const code = finding.code;
79
+ const severity = finding.severity;
80
+ const message = finding.message;
81
+ const rawFix = typeof finding.fix === "object" && finding.fix !== null && !Array.isArray(finding.fix)
82
+ ? Object.fromEntries(Object.entries(finding.fix))
83
+ : undefined;
84
+ findings.push({
85
+ code,
86
+ severity,
87
+ message,
88
+ ...(typeof finding.file === "string" ? { file: finding.file } : {}),
89
+ ...(typeof finding.line === "number" ? { line: finding.line } : {}),
90
+ ...(Array.isArray(finding.evidence)
91
+ ? {
92
+ evidence: finding.evidence.filter((item) => typeof item === "string"),
93
+ }
94
+ : {}),
95
+ ...(typeof rawFix?.recipe === "string"
96
+ ? {
97
+ fix: {
98
+ recipe: rawFix.recipe,
99
+ ...(typeof rawFix.command === "string" ? { command: rawFix.command } : {}),
100
+ },
101
+ }
102
+ : {}),
103
+ ...(typeof finding.verify === "string" ? { verify: finding.verify } : {}),
104
+ });
105
+ }
106
+ return findings;
107
+ },
108
+ });
109
+ }
110
+ packs.push({ name, rules });
111
+ }
112
+ return validateRulePacks(packs);
113
+ }
114
+ export async function runRuleRegistry(ctx, builtIns, packs = []) {
115
+ const rules = [...builtIns, ...validateRulePacks(packs).flatMap((pack) => pack.rules)];
116
+ const out = [];
117
+ for (const rule of rules) {
118
+ if (isBuiltInCode(rule.code) === false && rule.code.startsWith("NF-")) {
119
+ throw new Error(`application rule pack code ${rule.code} uses the reserved NF- prefix`);
120
+ }
121
+ const findings = await rule.scan(ctx);
122
+ out.push(...findings);
123
+ }
124
+ return out;
125
+ }
126
+ export function sourceIndex(files) {
127
+ const map = new Map(files.map((file) => [file.file, file.content]));
128
+ return Object.freeze({
129
+ files: Object.freeze(files.map((file) => file.file)),
130
+ read: (file) => map.get(file),
131
+ });
132
+ }
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { CheckRule } from "./index.js";
2
+ /** Legacy rule name → stable NF- code. Exported so `nifra.check.json` rule overrides can be keyed
3
+ * by either name and still hit both diagnostic views. */
4
+ export declare const LEGACY_RULE_CODES: Readonly<Record<string, string>>;
5
+ /** Stable registry adapters for the existing source scanners. */
6
+ export declare const legacyRules: readonly CheckRule[];
7
+ //# sourceMappingURL=legacy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy.d.ts","sourceRoot":"","sources":["../../src/rules/legacy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAA;AAExD;yDACyD;AACzD,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkB7D,CAAA;AAiEF,iEAAiE;AACjE,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAE3C,CAAA"}
@@ -0,0 +1,85 @@
1
+ /** Legacy rule name → stable NF- code. Exported so `nifra.check.json` rule overrides can be keyed
2
+ * by either name and still hit both diagnostic views. */
3
+ export const LEGACY_RULE_CODES = Object.freeze({
4
+ typecheck: "NF-C001",
5
+ "typed-client": "NF-C002",
6
+ "untyped-client": "NF-C003",
7
+ "server-only-import": "NF-C004",
8
+ "removed-import": "NF-C005",
9
+ "interpolated-sql": "NF-C006",
10
+ "response-route": "NF-C007",
11
+ "undeclared-dependency": "NF-C008",
12
+ "duplicate-install": "NF-C009",
13
+ "stale-workspace-dist": "NF-C010",
14
+ pipeline: "NF-C011",
15
+ "server-manifest-drift": "NF-C012",
16
+ "manifest-drift": "NF-C013",
17
+ "capability-assurance": "NF-C014",
18
+ "capability-config": "NF-C015",
19
+ "check-config": "NF-C016",
20
+ "contract-drift": "NF-K001",
21
+ });
22
+ const TITLES = Object.freeze({
23
+ typecheck: "TypeScript contract check",
24
+ "typed-client": "Typed client check",
25
+ "untyped-client": "Untyped client check",
26
+ "server-only-import": "Server-only import check",
27
+ "removed-import": "Removed import check",
28
+ "interpolated-sql": "Interpolated SQL check",
29
+ "response-route": "Raw response route advisory",
30
+ "undeclared-dependency": "Undeclared dependency check",
31
+ "duplicate-install": "Duplicate install check",
32
+ "stale-workspace-dist": "Stale workspace build check",
33
+ pipeline: "Build pipeline check",
34
+ "server-manifest-drift": "Server manifest drift check",
35
+ "manifest-drift": "Trust manifest drift check",
36
+ "capability-assurance": "Capability assurance check",
37
+ "capability-config": "Assurance configuration check",
38
+ "check-config": "Check configuration check",
39
+ "contract-drift": "Contract snapshot drift",
40
+ });
41
+ function recipeFor(finding) {
42
+ if (finding.rule === "stale-workspace-dist")
43
+ return { recipe: "workspace-dist.rebuild", command: "nifra fix --code NF-C010" };
44
+ if (finding.rule === "server-manifest-drift")
45
+ return { recipe: "manifest.sync", command: "nifra sync-manifest" };
46
+ if (finding.rule === "contract-drift")
47
+ return { recipe: "contracts.snapshot", command: "nifra contracts snapshot" };
48
+ return undefined;
49
+ }
50
+ function toDiagnostic(finding, code) {
51
+ const severity = finding.severity === "warning" ? "warn" : finding.severity;
52
+ const evidence = finding.evidence ?? finding.chain;
53
+ const fix = recipeFor(finding);
54
+ return Object.freeze({
55
+ code,
56
+ severity,
57
+ message: finding.message,
58
+ ...(finding.file === undefined ? {} : { file: finding.file }),
59
+ ...(finding.line === undefined ? {} : { line: finding.line }),
60
+ ...(evidence === undefined ? {} : { evidence }),
61
+ ...(fix === undefined ? {} : { fix }),
62
+ ...(finding.verify === undefined ? {} : { verify: finding.verify }),
63
+ });
64
+ }
65
+ function findingsFor(ctx, rule) {
66
+ const findings = ctx.project.legacyDiagnostics;
67
+ if (!Array.isArray(findings))
68
+ return [];
69
+ const code = LEGACY_RULE_CODES[rule];
70
+ if (code === undefined)
71
+ return [];
72
+ return findings
73
+ .filter((finding) => finding?.rule === rule)
74
+ .map((finding) => toDiagnostic(finding, code));
75
+ }
76
+ function makeRule(rule) {
77
+ const code = LEGACY_RULE_CODES[rule];
78
+ const title = TITLES[rule];
79
+ if (code === undefined || title === undefined)
80
+ throw new Error(`unknown legacy rule ${rule}`);
81
+ return Object.freeze({ code, title, scan: async (ctx) => findingsFor(ctx, rule) });
82
+ }
83
+ /** Stable registry adapters for the existing source scanners. */
84
+ export const legacyRules = Object.freeze(Object.keys(LEGACY_RULE_CODES).map((name) => makeRule(name)));
85
+ //# sourceMappingURL=legacy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy.js","sourceRoot":"","sources":["../../src/rules/legacy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { CheckRule } from "./index.js";
2
+ export declare const reservedSegmentRule: CheckRule;
3
+ /**
4
+ * NF-C019: the same method+path registered twice in one file - the later registration is dead or
5
+ * shadowing, and which one serves is a router implementation detail nobody should depend on.
6
+ * Scoped to a single file on purpose: two different apps in one repo (a monorepo with several
7
+ * backends) legitimately both register `GET /health`, and this scanner cannot tell app instances
8
+ * apart across files - so cross-file duplicates are NOT flagged rather than guessed at.
9
+ */
10
+ export declare const duplicateRouteRule: CheckRule;
11
+ export declare const routeRules: readonly CheckRule[];
12
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/rules/routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAA;AA8ExD,eAAO,MAAM,mBAAmB,EAAE,SAiCjC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAgChC,CAAA;AAED,eAAO,MAAM,UAAU,sBAA2D,CAAA"}
@@ -0,0 +1,142 @@
1
+ import { diagnostic } from "../diagnostics.js";
2
+ import { commentBlockHasMarker } from "./comment-markers.js";
3
+ /**
4
+ * Route-table lints over the statically collected route registrations (`scanStaticRouteText` in
5
+ * ../check.ts fills `ctx.project.staticRoutes` during the source walk - no second scan here).
6
+ *
7
+ * NF-C018 exists because the typed client's proxy intercepts a fixed set of property names before
8
+ * path resolution (`resolveSegment` + the thenable guard in @nifrajs/client): the seven HTTP verbs
9
+ * (case-insensitively), `subscribe`, `ws`, `index`, and `then`. A route whose path contains a
10
+ * static segment spelling one of these cannot be reached by DOT ACCESS - `api.delete.post` resolves
11
+ * the DELETE verb, not the path. The typed spelling is a call on the parent node
12
+ * (`api.api("delete").post()`, treaty.ts `CollisionEscape`), and the client type rejects the dot
13
+ * access with the same guidance - so the collision is a warning that teaches the escape spelling,
14
+ * not a blocking error: the route IS reachable, just not by the spelling its name suggests.
15
+ */
16
+ const RESERVED_VERB_SEGMENTS = new Set([
17
+ "get",
18
+ "post",
19
+ "put",
20
+ "patch",
21
+ "delete",
22
+ "head",
23
+ "options",
24
+ ]);
25
+ // Intercepted by exact match only (`key === "subscribe"` etc.), unlike the lowercased verbs.
26
+ const RESERVED_EXACT_SEGMENTS = new Set(["subscribe", "ws", "index", "then"]);
27
+ const RESERVED_READOUT = "get/post/put/patch/delete/head/options (any casing), subscribe, ws, index, then";
28
+ /** Opt-out pragma for a route deliberately served only to NON-typed-client consumers. */
29
+ const RESERVED_SEGMENT_PRAGMA = "nifra-expect reserved-segment";
30
+ /** Parse-don't-cast over the project fact: a malformed entry is dropped, never trusted. */
31
+ function routeFacts(ctx) {
32
+ const raw = ctx.project.staticRoutes;
33
+ if (!Array.isArray(raw))
34
+ return [];
35
+ const out = [];
36
+ for (const item of raw) {
37
+ if (typeof item !== "object" || item === null)
38
+ continue;
39
+ const record = item;
40
+ if (typeof record.file === "string" &&
41
+ typeof record.line === "number" &&
42
+ typeof record.method === "string" &&
43
+ typeof record.path === "string") {
44
+ out.push({ file: record.file, line: record.line, method: record.method, path: record.path });
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ /**
50
+ * The typed escape spelling for a colliding segment, e.g. `/api/delete` + `delete` →
51
+ * `api("delete").post()` shown as the chain up to the collision. Best-effort readability: earlier
52
+ * segments render as dot access, the colliding one as the parent-node call.
53
+ */
54
+ function routeEscapeHint(path, colliding) {
55
+ const segments = path.split("/").filter((segment) => segment !== "");
56
+ const before = segments.slice(0, Math.max(segments.indexOf(colliding), 0));
57
+ return `${["api", ...before].join(".")}("${colliding}")`;
58
+ }
59
+ /** The reserved key a static path segment collides with, or undefined. Params/wildcards never collide. */
60
+ function reservedCollision(segment) {
61
+ if (segment.startsWith(":") || segment.startsWith("*"))
62
+ return undefined;
63
+ if (RESERVED_EXACT_SEGMENTS.has(segment))
64
+ return segment;
65
+ const lower = segment.toLowerCase();
66
+ return RESERVED_VERB_SEGMENTS.has(lower) ? lower : undefined;
67
+ }
68
+ export const reservedSegmentRule = {
69
+ code: "NF-C018",
70
+ title: "Route segment collides with a reserved client proxy key",
71
+ async scan(ctx) {
72
+ const findings = [];
73
+ const linesByFile = new Map();
74
+ for (const route of routeFacts(ctx)) {
75
+ for (const segment of route.path.split("/")) {
76
+ if (segment === "")
77
+ continue;
78
+ const collision = reservedCollision(segment);
79
+ if (collision === undefined)
80
+ continue;
81
+ let lines = linesByFile.get(route.file);
82
+ if (lines === undefined) {
83
+ lines = (ctx.sources.read(route.file) ?? "").split("\n");
84
+ linesByFile.set(route.file, lines);
85
+ }
86
+ if (commentBlockHasMarker(lines, route.line, RESERVED_SEGMENT_PRAGMA))
87
+ continue;
88
+ findings.push(diagnostic({
89
+ code: "NF-C018",
90
+ severity: "warn",
91
+ file: route.file,
92
+ line: route.line,
93
+ message: `${route.method} ${route.path} - segment '${segment}' collides with the reserved client proxy key '${collision}' (reserved: ${RESERVED_READOUT}); dot access cannot reach it - call the parent node with the segment instead (\`${routeEscapeHint(route.path, segment)}\`), rename the segment, or mark an intentionally non-typed-client route with \`// ${RESERVED_SEGMENT_PRAGMA}\` above the registration`,
94
+ evidence: [`${route.method} ${route.path}`, `segment: ${segment}`],
95
+ verify: "nifra check --lints-only",
96
+ }));
97
+ break; // one finding per route, even if several segments collide
98
+ }
99
+ }
100
+ return findings;
101
+ },
102
+ };
103
+ /**
104
+ * NF-C019: the same method+path registered twice in one file - the later registration is dead or
105
+ * shadowing, and which one serves is a router implementation detail nobody should depend on.
106
+ * Scoped to a single file on purpose: two different apps in one repo (a monorepo with several
107
+ * backends) legitimately both register `GET /health`, and this scanner cannot tell app instances
108
+ * apart across files - so cross-file duplicates are NOT flagged rather than guessed at.
109
+ */
110
+ export const duplicateRouteRule = {
111
+ code: "NF-C019",
112
+ title: "Duplicate route registration",
113
+ async scan(ctx) {
114
+ const findings = [];
115
+ const byFile = new Map();
116
+ for (const route of routeFacts(ctx)) {
117
+ let seen = byFile.get(route.file);
118
+ if (seen === undefined) {
119
+ seen = new Map();
120
+ byFile.set(route.file, seen);
121
+ }
122
+ const key = `${route.method} ${route.path}`;
123
+ const first = seen.get(key);
124
+ if (first === undefined) {
125
+ seen.set(key, route);
126
+ continue;
127
+ }
128
+ findings.push(diagnostic({
129
+ code: "NF-C019",
130
+ severity: "error",
131
+ file: route.file,
132
+ line: route.line,
133
+ message: `${key} is registered twice in this file (first at line ${first.line}) - remove or rename one; which registration serves is undefined`,
134
+ evidence: [key, `first registration: line ${first.line}`],
135
+ verify: "nifra check --lints-only",
136
+ }));
137
+ }
138
+ return findings;
139
+ },
140
+ };
141
+ export const routeRules = Object.freeze([reservedSegmentRule, duplicateRouteRule]);
142
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/rules/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { CheckRule } from "./index.js";
2
+ export declare const secretComparisonRule: CheckRule;
3
+ export declare const piiLogRule: CheckRule;
4
+ export declare const failOpenGateRule: CheckRule;
5
+ export declare const corsOriginPredicateRule: CheckRule;
6
+ export declare const externalRedirectRule: CheckRule;
7
+ export declare const assuranceEscapeHatchRule: CheckRule;
8
+ export declare const unprefixedSecureCookieRule: CheckRule;
9
+ export declare const securityRules: readonly CheckRule[];
10
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/rules/security.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAA;AAmGxD,eAAO,MAAM,oBAAoB,EAAE,SA2DlC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,SA8CxB,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,SAkD9B,CAAA;AAiBD,eAAO,MAAM,uBAAuB,EAAE,SA2DrC,CAAA;AAED,eAAO,MAAM,oBAAoB,EAAE,SA8ClC,CAAA;AAWD,eAAO,MAAM,wBAAwB,EAAE,SAuCtC,CAAA;AAID,eAAO,MAAM,0BAA0B,EAAE,SAsDxC,CAAA;AAED,eAAO,MAAM,aAAa,sBAQxB,CAAA"}