@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.
- package/README.md +6 -0
- package/dist/assure-hydration.d.ts +14 -0
- package/dist/assure-hydration.d.ts.map +1 -0
- package/dist/assure-hydration.js +483 -0
- package/dist/assure-hydration.js.map +1 -0
- package/dist/assure.d.ts +35 -0
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +222 -1
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +13 -0
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +64 -1
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check.d.ts +125 -10
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +706 -145
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +23 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +194 -10
- package/dist/cli.js.map +1 -1
- package/dist/contract-proof.d.ts +49 -0
- package/dist/contract-proof.d.ts.map +1 -0
- package/dist/contract-proof.js +128 -0
- package/dist/contract-proof.js.map +1 -0
- package/dist/contracts.d.ts +22 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +134 -0
- package/dist/contracts.js.map +1 -0
- package/dist/diagnostics.d.ts +23 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +9 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/diff-tool.d.ts +2 -0
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +1 -1
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts +8 -3
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +36 -15
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +28 -2
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +173 -7
- package/dist/doctor.js.map +1 -1
- package/dist/examples.d.ts +1 -2
- package/dist/examples.d.ts.map +1 -1
- package/dist/examples.js +37 -16
- package/dist/examples.js.map +1 -1
- package/dist/fix-recipes.d.ts +12 -0
- package/dist/fix-recipes.d.ts.map +1 -0
- package/dist/fix-recipes.js +106 -0
- package/dist/fix-recipes.js.map +1 -0
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +60 -4
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/source-facts.d.ts +15 -0
- package/dist/internal/source-facts.d.ts.map +1 -0
- package/dist/internal/source-facts.js +82 -0
- package/dist/internal/source-facts.js.map +1 -0
- package/dist/internal/typescript-import.d.ts +9 -0
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +38 -0
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -2
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +53 -4
- package/dist/introspect.js.map +1 -1
- package/dist/load.d.ts +6 -0
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +14 -9
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +3 -0
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-root.d.ts +83 -0
- package/dist/mcp-root.d.ts.map +1 -0
- package/dist/mcp-root.js +221 -0
- package/dist/mcp-root.js.map +1 -0
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +18 -4
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp.d.ts +27 -4
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +358 -25
- package/dist/mcp.js.map +1 -1
- package/dist/project-path.d.ts +3 -0
- package/dist/project-path.d.ts.map +1 -0
- package/dist/project-path.js +36 -0
- package/dist/project-path.js.map +1 -0
- package/dist/replay.d.ts +12 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +36 -0
- package/dist/replay.js.map +1 -0
- package/dist/rules/codes.d.ts +42 -0
- package/dist/rules/codes.d.ts.map +1 -0
- package/dist/rules/codes.js +52 -0
- package/dist/rules/codes.js.map +1 -0
- package/dist/rules/comment-markers.d.ts +10 -0
- package/dist/rules/comment-markers.d.ts.map +1 -0
- package/dist/rules/comment-markers.js +24 -0
- package/dist/rules/comment-markers.js.map +1 -0
- package/dist/rules/index.d.ts +31 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +133 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/legacy.d.ts +7 -0
- package/dist/rules/legacy.d.ts.map +1 -0
- package/dist/rules/legacy.js +85 -0
- package/dist/rules/legacy.js.map +1 -0
- package/dist/rules/routes.d.ts +12 -0
- package/dist/rules/routes.d.ts.map +1 -0
- package/dist/rules/routes.js +142 -0
- package/dist/rules/routes.js.map +1 -0
- package/dist/rules/security.d.ts +10 -0
- package/dist/rules/security.d.ts.map +1 -0
- package/dist/rules/security.js +471 -0
- package/dist/rules/security.js.map +1 -0
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +5 -1
- package/dist/search-terms.js.map +1 -1
- package/dist/types-search.d.ts +1 -2
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +33 -16
- package/dist/types-search.js.map +1 -1
- package/dist/verification.d.ts +9 -0
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +17 -1
- package/dist/verification.js.map +1 -1
- package/dist/work-graph.d.ts +123 -0
- package/dist/work-graph.d.ts.map +1 -0
- package/dist/work-graph.js +465 -0
- package/dist/work-graph.js.map +1 -0
- package/docs/examples.json +32 -2
- package/docs/llms-full.txt +906 -15
- package/docs/types.json +1682 -130
- package/package.json +34 -9
- package/src/assure-hydration.ts +606 -0
- package/src/assure.ts +316 -2
- package/src/capabilities-tool.ts +80 -1
- package/src/check.ts +873 -168
- package/src/cli.ts +198 -11
- package/src/contract-proof.ts +194 -0
- package/src/contracts.ts +157 -0
- package/src/diagnostics.ts +32 -0
- package/src/diff-tool.ts +1 -1
- package/src/docs-search.ts +45 -15
- package/src/doctor.ts +259 -8
- package/src/examples.ts +43 -15
- package/src/fix-recipes.ts +126 -0
- package/src/init-agents.ts +61 -4
- package/src/internal/source-facts.ts +133 -0
- package/src/internal/typescript-import.ts +29 -0
- package/src/introspect.ts +75 -6
- package/src/load.ts +6 -0
- package/src/manifest-tool.ts +16 -10
- package/src/mcp-docs-tools.ts +3 -0
- package/src/mcp-http.ts +1 -1
- package/src/mcp-root.ts +271 -0
- package/src/mcp-run.ts +19 -4
- package/src/mcp.ts +438 -32
- package/src/project-path.ts +36 -0
- package/src/replay.ts +44 -0
- package/src/rules/codes.ts +57 -0
- package/src/rules/comment-markers.ts +26 -0
- package/src/rules/index.ts +175 -0
- package/src/rules/legacy.ts +93 -0
- package/src/rules/routes.ts +157 -0
- package/src/rules/security.ts +505 -0
- package/src/search-terms.ts +9 -4
- package/src/types-search.ts +39 -15
- package/src/verification.ts +18 -1
- package/src/work-graph.ts +613 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { resolveInsideProject } from "./project-path.js";
|
|
5
|
+
const recipes = new Map();
|
|
6
|
+
export function registerFixRecipe(recipe) {
|
|
7
|
+
if (recipes.has(recipe.id))
|
|
8
|
+
throw new Error(`duplicate fix recipe: ${recipe.id}`);
|
|
9
|
+
recipes.set(recipe.id, Object.freeze(recipe));
|
|
10
|
+
}
|
|
11
|
+
export function getFixRecipe(id) {
|
|
12
|
+
return recipes.get(id);
|
|
13
|
+
}
|
|
14
|
+
export function listFixRecipes() {
|
|
15
|
+
return Object.freeze([...recipes.values()]);
|
|
16
|
+
}
|
|
17
|
+
registerFixRecipe({
|
|
18
|
+
id: "security.timing-safe-equal",
|
|
19
|
+
description: "Replace a direct secret comparison with a length check and timing-safe comparison.",
|
|
20
|
+
verify: "nifra check --lints-only",
|
|
21
|
+
async apply(root, diagnostic) {
|
|
22
|
+
if (diagnostic.file === undefined || diagnostic.line === undefined)
|
|
23
|
+
return [];
|
|
24
|
+
const path = await resolveInsideProject(root, diagnostic.file);
|
|
25
|
+
if (path === undefined)
|
|
26
|
+
return [];
|
|
27
|
+
const lines = (await readFile(path, "utf8")).split("\n");
|
|
28
|
+
const index = diagnostic.line - 1;
|
|
29
|
+
const original = lines[index];
|
|
30
|
+
if (original === undefined || original.includes("@nifra-gate-reviewed"))
|
|
31
|
+
return [];
|
|
32
|
+
const comparison = /\b([A-Za-z_$][\w$]*)\s*(===|!==|==|!=)\s*([A-Za-z_$][\w$]*)/.exec(original);
|
|
33
|
+
if (comparison === null)
|
|
34
|
+
return [];
|
|
35
|
+
const left = comparison[1];
|
|
36
|
+
const operator = comparison[2];
|
|
37
|
+
const right = comparison[3];
|
|
38
|
+
const expression = `nifraTimingSafeEqual(${left}, ${right})`;
|
|
39
|
+
const replacement = operator === "!==" || operator === "!=" ? `!${expression}` : expression;
|
|
40
|
+
lines[index] = original.replace(comparison[0], replacement);
|
|
41
|
+
if (!lines.some((line) => line.includes("function nifraTimingSafeEqual"))) {
|
|
42
|
+
lines.unshift('import { timingSafeEqual } from "node:crypto"', "", "function nifraTimingSafeEqual(left: string, right: string): boolean {", " const leftBytes = Buffer.from(left)", " const rightBytes = Buffer.from(right)", " return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes)", "}", "");
|
|
43
|
+
}
|
|
44
|
+
await writeFile(path, lines.join("\n"), "utf8");
|
|
45
|
+
return [diagnostic.file];
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
registerFixRecipe({
|
|
49
|
+
id: "manifest.sync",
|
|
50
|
+
description: "Regenerate generated server manifests from the current route tree.",
|
|
51
|
+
verify: "nifra check --lints-only",
|
|
52
|
+
async apply(root) {
|
|
53
|
+
const { syncServerManifests } = await import("./sync-manifest.js");
|
|
54
|
+
const results = await syncServerManifests(root);
|
|
55
|
+
return results.filter((result) => result.changed).map((result) => result.file);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
registerFixRecipe({
|
|
59
|
+
id: "contracts.snapshot",
|
|
60
|
+
description: "Refresh the opt-in contract lock after reviewing an intentional route change.",
|
|
61
|
+
verify: "nifra contracts check",
|
|
62
|
+
async apply(root) {
|
|
63
|
+
const { snapshotContracts } = await import("./contracts.js");
|
|
64
|
+
await snapshotContracts(root);
|
|
65
|
+
return ["contracts.lock.json"];
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
registerFixRecipe({
|
|
69
|
+
id: "workspace-dist.rebuild",
|
|
70
|
+
description: "Rebuild the workspace-linked package whose artifact is older than its source.",
|
|
71
|
+
verify: "nifra doctor --json",
|
|
72
|
+
async apply(root, diagnostic) {
|
|
73
|
+
const packageName = diagnostic.evidence?.[0];
|
|
74
|
+
if (packageName === undefined || !/^@?[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)?$/.test(packageName))
|
|
75
|
+
return [];
|
|
76
|
+
// The segment regex above admits "." and "..", which would resolve outside node_modules.
|
|
77
|
+
if (packageName.split("/").some((segment) => segment === "." || segment === ".."))
|
|
78
|
+
return [];
|
|
79
|
+
const packageDir = await resolveInsideProject(root, join("node_modules", packageName));
|
|
80
|
+
if (packageDir === undefined)
|
|
81
|
+
return [];
|
|
82
|
+
const packageJson = await resolveInsideProject(root, join("node_modules", packageName, "package.json"));
|
|
83
|
+
if (packageJson === undefined || !existsSync(packageJson))
|
|
84
|
+
return [];
|
|
85
|
+
const proc = Bun.spawn(["bun", "run", "build"], {
|
|
86
|
+
cwd: packageDir,
|
|
87
|
+
stdout: "ignore",
|
|
88
|
+
stderr: "pipe",
|
|
89
|
+
});
|
|
90
|
+
const error = await new Response(proc.stderr).text();
|
|
91
|
+
const exitCode = await proc.exited;
|
|
92
|
+
if (exitCode !== 0)
|
|
93
|
+
throw new Error(error.trim() || `build failed for ${packageName}`);
|
|
94
|
+
return [diagnostic.evidence?.[1] ?? packageName];
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
export async function applyDiagnosticRecipe(root, diagnostic) {
|
|
98
|
+
const recipeId = diagnostic.fix?.recipe;
|
|
99
|
+
if (recipeId === undefined)
|
|
100
|
+
return [];
|
|
101
|
+
const recipe = getFixRecipe(recipeId);
|
|
102
|
+
if (recipe === undefined)
|
|
103
|
+
return [];
|
|
104
|
+
return recipe.apply(root, diagnostic);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=fix-recipes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix-recipes.js","sourceRoot":"","sources":["../src/fix-recipes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-agents.d.ts","sourceRoot":"","sources":["../src/init-agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"init-agents.d.ts","sourceRoot":"","sources":["../src/init-agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAeH,kGAAkG;AAClG,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;uGACmG;IACnG,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAA;IAC7D,2FAA2F;IAC3F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAA;CAChD;AAED,MAAM,WAAW,iBAAiB;IAChC,mGAAmG;IACnG,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB;AAED;;gFAEgF;AAChF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOzD;AAkHD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAS3B;AASD,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAUjE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/D,OAAO,CAAC,OAAO,CAAC,CAQlB"}
|
package/dist/init-agents.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* is not needed for that append since it's additive. Every write path is resolved + confined under the
|
|
18
18
|
* cwd (no `..` traversal escaping the project root).
|
|
19
19
|
*/
|
|
20
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
20
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
21
21
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
22
22
|
import { AGENTS_MD_PATH, agentsMcpSection, CLAUDE_MD_PATH, CURSOR_MCP_JSON_PATH, claudeMd, MCP_JSON_PATH, mcpJson, } from "create-nifra/agent-files";
|
|
23
23
|
/** Confine a project-relative path under `cwd` and return the absolute path. Rejects a spec that escapes
|
|
@@ -34,20 +34,74 @@ export function safeJoin(cwd, rel) {
|
|
|
34
34
|
const fileExists = (path) => readFile(path)
|
|
35
35
|
.then(() => true)
|
|
36
36
|
.catch(() => false);
|
|
37
|
+
/** `lstat`, with "it isn't there" reported as `undefined` instead of a throw. Every other failure
|
|
38
|
+
* (EACCES, ELOOP, ...) still propagates - only absence is an expected outcome here. */
|
|
39
|
+
async function lstatOrMissing(path) {
|
|
40
|
+
try {
|
|
41
|
+
return await lstat(path);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error.code === "ENOENT")
|
|
45
|
+
return undefined;
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Refuse to write through a symlinked ancestor. `safeJoin` only proves the *textual* path stays
|
|
51
|
+
* under the project root; a symlinked `.cursor` still lands the write wherever it points. The walk
|
|
52
|
+
* stops at the first missing segment - `mkdir` will create the rest, so there is nothing to check.
|
|
53
|
+
*/
|
|
54
|
+
async function assertSafeParents(cwd, abs) {
|
|
55
|
+
const rel = relative(cwd, resolve(abs, ".."));
|
|
56
|
+
let current = cwd;
|
|
57
|
+
for (const part of rel.split(/[\\/]/).filter(Boolean)) {
|
|
58
|
+
current = resolve(current, part);
|
|
59
|
+
const stat = await lstatOrMissing(current);
|
|
60
|
+
if (stat === undefined)
|
|
61
|
+
break;
|
|
62
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
63
|
+
throw new Error(`[nifra] refusing unsafe parent path: ${part}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Refuse to write anything that is not a plain absent-or-regular file: a symlink here would
|
|
68
|
+
* redirect the write, and a directory or device node would make it fail in a confusing way. */
|
|
69
|
+
async function assertRegularTarget(abs, rel) {
|
|
70
|
+
const stat = await lstatOrMissing(abs);
|
|
71
|
+
if (stat !== undefined && (stat.isSymbolicLink() || !stat.isFile())) {
|
|
72
|
+
throw new Error(`[nifra] refusing to write non-regular file: ${rel}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
37
75
|
/**
|
|
38
76
|
* Write a whole-file generator's output to `path` unless it already exists and `force` is false.
|
|
39
77
|
* Returns the per-file result for the report.
|
|
40
78
|
*/
|
|
41
79
|
async function writeOwned(cwd, rel, content, force) {
|
|
42
80
|
const abs = safeJoin(cwd, rel);
|
|
81
|
+
await assertSafeParents(cwd, abs);
|
|
82
|
+
await assertRegularTarget(abs, rel);
|
|
43
83
|
if (!force && (await fileExists(abs))) {
|
|
44
84
|
return { path: rel, action: "skipped", note: "exists - pass --force to overwrite" };
|
|
45
85
|
}
|
|
46
86
|
// `.cursor/mcp.json` needs its parent dir; `recursive` is a no-op for the root-level files.
|
|
47
87
|
await mkdir(resolve(abs, ".."), { recursive: true });
|
|
48
|
-
await
|
|
88
|
+
await atomicWrite(abs, content);
|
|
49
89
|
return { path: rel, action: "wrote" };
|
|
50
90
|
}
|
|
91
|
+
/** Write via a sibling temp file so a crash mid-write can't leave a half-written config in place.
|
|
92
|
+
* `wx` fails rather than following an existing name, and the temp is cleaned up on any failure so a
|
|
93
|
+
* botched run doesn't litter the user's project. */
|
|
94
|
+
async function atomicWrite(abs, content) {
|
|
95
|
+
const tmp = `${abs}.nifra-${process.pid}-${Date.now()}`;
|
|
96
|
+
await writeFile(tmp, content, { flag: "wx" });
|
|
97
|
+
try {
|
|
98
|
+
await rename(tmp, abs);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
await rm(tmp, { force: true });
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
51
105
|
/**
|
|
52
106
|
* AGENTS.md is additive, not owned: if it exists and already has the MCP section, leave it; if it exists
|
|
53
107
|
* without the section, append the section (preserving the user's conventions); if it's absent, write a
|
|
@@ -56,6 +110,8 @@ async function writeOwned(cwd, rel, content, force) {
|
|
|
56
110
|
*/
|
|
57
111
|
async function ensureAgentsMd(cwd) {
|
|
58
112
|
const abs = safeJoin(cwd, AGENTS_MD_PATH);
|
|
113
|
+
await assertSafeParents(cwd, abs);
|
|
114
|
+
await assertRegularTarget(abs, AGENTS_MD_PATH);
|
|
59
115
|
const section = agentsMcpSection();
|
|
60
116
|
let existing;
|
|
61
117
|
try {
|
|
@@ -65,7 +121,7 @@ async function ensureAgentsMd(cwd) {
|
|
|
65
121
|
existing = undefined; // no AGENTS.md yet
|
|
66
122
|
}
|
|
67
123
|
if (existing === undefined) {
|
|
68
|
-
await
|
|
124
|
+
await atomicWrite(abs, `# AGENTS.md\n\nGuidance for AI coding agents working in this repo.\n\n${section}\n`);
|
|
69
125
|
return { path: AGENTS_MD_PATH, action: "wrote" };
|
|
70
126
|
}
|
|
71
127
|
// Match on the section heading, not the full body, so a reformatted-but-present section still counts.
|
|
@@ -73,7 +129,7 @@ async function ensureAgentsMd(cwd) {
|
|
|
73
129
|
return { path: AGENTS_MD_PATH, action: "present", note: "already has an MCP section" };
|
|
74
130
|
}
|
|
75
131
|
const sep = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
76
|
-
await
|
|
132
|
+
await atomicWrite(abs, `${existing}${sep}${section}\n`);
|
|
77
133
|
return { path: AGENTS_MD_PATH, action: "appended" };
|
|
78
134
|
}
|
|
79
135
|
/**
|
package/dist/init-agents.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-agents.js","sourceRoot":"","sources":["../src/init-agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"init-agents.js","sourceRoot":"","sources":["../src/init-agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type * as TSApi from "typescript";
|
|
2
|
+
import type { TypeScriptApi } from "./typescript-import.js";
|
|
3
|
+
/**
|
|
4
|
+
* The deliberately small AST seam used by source lints. It is not a project-wide TypeScript
|
|
5
|
+
* Program: a rule asks for a file only after its lexical fast path found a candidate. That keeps
|
|
6
|
+
* cold checks and projects without a TypeScript install on the existing conservative path.
|
|
7
|
+
*/
|
|
8
|
+
export interface SourceFacts {
|
|
9
|
+
parse(file: string, content: string): TSApi.SourceFile | undefined;
|
|
10
|
+
isValueImportAt(source: TSApi.SourceFile, position: number, specifier: string): boolean | undefined;
|
|
11
|
+
isRouteRegistrationAt(source: TSApi.SourceFile, position: number, method: string, path: string): boolean | undefined;
|
|
12
|
+
isResponseSyntaxAt(source: TSApi.SourceFile, position: number): boolean | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare function createSourceFacts(ts: TypeScriptApi): SourceFacts;
|
|
15
|
+
//# sourceMappingURL=source-facts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-facts.d.ts","sourceRoot":"","sources":["../../src/internal/source-facts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAE3D;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,UAAU,GAAG,SAAS,CAAA;IAClE,eAAe,CACb,MAAM,EAAE,KAAK,CAAC,UAAU,EACxB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,GAAG,SAAS,CAAA;IACtB,qBAAqB,CACnB,MAAM,EAAE,KAAK,CAAC,UAAU,EACxB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,GAAG,SAAS,CAAA;IACtB,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CACpF;AAuCD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,aAAa,GAAG,WAAW,CAuEhE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function nodeAt(ts, source, position) {
|
|
2
|
+
let best = source;
|
|
3
|
+
const visit = (node) => {
|
|
4
|
+
if (position < node.pos || position >= node.end)
|
|
5
|
+
return;
|
|
6
|
+
best = node;
|
|
7
|
+
ts.forEachChild(node, visit);
|
|
8
|
+
};
|
|
9
|
+
visit(source);
|
|
10
|
+
return best;
|
|
11
|
+
}
|
|
12
|
+
function ancestorAt(ts, source, position, predicate) {
|
|
13
|
+
let node = nodeAt(ts, source, Math.max(0, Math.min(position, source.end - 1)));
|
|
14
|
+
for (;;) {
|
|
15
|
+
if (predicate(node))
|
|
16
|
+
return node;
|
|
17
|
+
if (node === source)
|
|
18
|
+
return undefined;
|
|
19
|
+
node = node.parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function rawResponseExpression(ts, node) {
|
|
23
|
+
if (ts.isNewExpression(node)) {
|
|
24
|
+
return ts.isIdentifier(node.expression) && node.expression.text === "Response";
|
|
25
|
+
}
|
|
26
|
+
if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression))
|
|
27
|
+
return false;
|
|
28
|
+
return (ts.isIdentifier(node.expression.expression) &&
|
|
29
|
+
node.expression.expression.text === "Response" &&
|
|
30
|
+
node.expression.name.text === "json");
|
|
31
|
+
}
|
|
32
|
+
export function createSourceFacts(ts) {
|
|
33
|
+
const cache = new Map();
|
|
34
|
+
const parse = (file, content) => {
|
|
35
|
+
const cached = cache.get(file);
|
|
36
|
+
if (cached?.content === content)
|
|
37
|
+
return cached.source;
|
|
38
|
+
const kind = /\.tsx?$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.JS;
|
|
39
|
+
const source = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true, kind);
|
|
40
|
+
const diagnostics = source.parseDiagnostics;
|
|
41
|
+
const parsed = diagnostics !== undefined && diagnostics.length > 0 ? undefined : source;
|
|
42
|
+
cache.set(file, { content, source: parsed });
|
|
43
|
+
return parsed;
|
|
44
|
+
};
|
|
45
|
+
const isValueImportAt = (source, position, specifier) => {
|
|
46
|
+
const declaration = ancestorAt(ts, source, position, ts.isImportDeclaration);
|
|
47
|
+
if (declaration === undefined || !ts.isStringLiteral(declaration.moduleSpecifier))
|
|
48
|
+
return undefined;
|
|
49
|
+
if (declaration.moduleSpecifier.text !== specifier)
|
|
50
|
+
return undefined;
|
|
51
|
+
const clause = declaration.importClause;
|
|
52
|
+
if (clause === undefined)
|
|
53
|
+
return true; // side-effect import
|
|
54
|
+
if (clause.isTypeOnly)
|
|
55
|
+
return false;
|
|
56
|
+
if (clause.namedBindings !== undefined && ts.isNamedImports(clause.namedBindings)) {
|
|
57
|
+
return clause.namedBindings.elements.some((element) => !element.isTypeOnly);
|
|
58
|
+
}
|
|
59
|
+
return true; // default and namespace bindings are runtime imports
|
|
60
|
+
};
|
|
61
|
+
const isRouteRegistrationAt = (source, position, method, path) => {
|
|
62
|
+
const call = ancestorAt(ts, source, position, ts.isCallExpression);
|
|
63
|
+
if (call === undefined || !ts.isPropertyAccessExpression(call.expression))
|
|
64
|
+
return false;
|
|
65
|
+
const name = call.expression.name.text.toUpperCase();
|
|
66
|
+
const first = call.arguments[0];
|
|
67
|
+
return (name === method && first !== undefined && ts.isStringLiteralLike(first) && first.text === path);
|
|
68
|
+
};
|
|
69
|
+
const isResponseSyntaxAt = (source, position) => {
|
|
70
|
+
const returnStatement = ancestorAt(ts, source, position, ts.isReturnStatement);
|
|
71
|
+
if (returnStatement !== undefined) {
|
|
72
|
+
return (returnStatement.expression !== undefined &&
|
|
73
|
+
rawResponseExpression(ts, returnStatement.expression));
|
|
74
|
+
}
|
|
75
|
+
const arrow = ancestorAt(ts, source, position, ts.isArrowFunction);
|
|
76
|
+
if (arrow !== undefined && !ts.isBlock(arrow.body))
|
|
77
|
+
return rawResponseExpression(ts, arrow.body);
|
|
78
|
+
return false;
|
|
79
|
+
};
|
|
80
|
+
return { parse, isValueImportAt, isRouteRegistrationAt, isResponseSyntaxAt };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=source-facts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-facts.js","sourceRoot":"","sources":["../../src/internal/source-facts.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,4 +21,13 @@ export type TypeScriptApi = typeof import("typescript");
|
|
|
21
21
|
* them the wrong way - so that error is rethrown for the caller to report.
|
|
22
22
|
*/
|
|
23
23
|
export declare function importTypeScript(): Promise<TypeScriptApi | undefined>;
|
|
24
|
+
/**
|
|
25
|
+
* Import TypeScript resolved from the PROJECT root first, falling back to the CLI's own dependency
|
|
26
|
+
* tree. A bare `import("typescript")` resolves relative to this file - the CLI's install - so a
|
|
27
|
+
* globally-installed or bunx-run CLI could miss the project's compiler (or load a different major)
|
|
28
|
+
* depending on where the CLI happened to live, while the project's own `typescript` sat unused in
|
|
29
|
+
* its node_modules. Resolving from `root` makes the verdicts a function of the project, not of how
|
|
30
|
+
* the CLI was installed - the same cwd-invariance the typecheck gate's tsc resolution has.
|
|
31
|
+
*/
|
|
32
|
+
export declare function importProjectTypeScript(root: string): Promise<TypeScriptApi | undefined>;
|
|
24
33
|
//# sourceMappingURL=typescript-import.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-import.d.ts","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,8FAA8F;AAC9F,MAAM,MAAM,aAAa,GAAG,cAAc,YAAY,CAAC,CAAA;AAEvD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAQ3E"}
|
|
1
|
+
{"version":3,"file":"typescript-import.d.ts","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,8FAA8F;AAC9F,MAAM,MAAM,aAAa,GAAG,cAAc,YAAY,CAAC,CAAA;AAEvD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAQ3E;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAmB9F"}
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
* - the templates all ship a `typecheck` script - so this resolves in practice; the point is that an
|
|
12
12
|
* install does not pay for it.
|
|
13
13
|
*/
|
|
14
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
15
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
16
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
17
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return path;
|
|
21
|
+
};
|
|
14
22
|
/**
|
|
15
23
|
* Import the project's TypeScript, or `undefined` when it is not installed.
|
|
16
24
|
*
|
|
@@ -29,4 +37,34 @@ export async function importTypeScript() {
|
|
|
29
37
|
throw cause;
|
|
30
38
|
}
|
|
31
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Import TypeScript resolved from the PROJECT root first, falling back to the CLI's own dependency
|
|
42
|
+
* tree. A bare `import("typescript")` resolves relative to this file - the CLI's install - so a
|
|
43
|
+
* globally-installed or bunx-run CLI could miss the project's compiler (or load a different major)
|
|
44
|
+
* depending on where the CLI happened to live, while the project's own `typescript` sat unused in
|
|
45
|
+
* its node_modules. Resolving from `root` makes the verdicts a function of the project, not of how
|
|
46
|
+
* the CLI was installed - the same cwd-invariance the typecheck gate's tsc resolution has.
|
|
47
|
+
*/
|
|
48
|
+
export async function importProjectTypeScript(root) {
|
|
49
|
+
let resolved;
|
|
50
|
+
try {
|
|
51
|
+
resolved = Bun.resolveSync("typescript", root);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Unresolvable from the project (not installed there, or a non-Bun host without resolveSync):
|
|
55
|
+
// fall back to the CLI's own tree, which keeps the previous behavior as the floor.
|
|
56
|
+
return importTypeScript();
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return (await import(__rewriteRelativeImportExtension(resolved)));
|
|
60
|
+
}
|
|
61
|
+
catch (cause) {
|
|
62
|
+
// Same contract as importTypeScript: a compiler that RESOLVES and then fails evaluating is a
|
|
63
|
+
// broken install, not a missing one - rethrow rather than silently checking with another copy.
|
|
64
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
65
|
+
if (/cannot find (?:package|module)|ERR_MODULE_NOT_FOUND/i.test(message))
|
|
66
|
+
return importTypeScript();
|
|
67
|
+
throw cause;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
32
70
|
//# sourceMappingURL=typescript-import.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-import.js","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG
|
|
1
|
+
{"version":3,"file":"typescript-import.js","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;;;;;;;;;AAKH;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAkB,CAAA;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACtE,IAAI,sDAAsD,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,SAAS,CAAA;QAC1F,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,IAAY;IACxD,IAAI,QAAgB,CAAA;IACpB,IAAI,CAAC;QACH,QAAQ,GAAG,GAAG,CAAC,WAAW,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,8FAA8F;QAC9F,mFAAmF;QACnF,OAAO,gBAAgB,EAAE,CAAA;IAC3B,CAAC;IACD,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,MAAM,kCAAC,QAAQ,EAAC,CAAkB,CAAA;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,6FAA6F;QAC7F,+FAA+F;QAC/F,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACtE,IAAI,sDAAsD,CAAC,IAAI,CAAC,OAAO,CAAC;YACtE,OAAO,gBAAgB,EAAE,CAAA;QAC3B,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC"}
|
package/dist/introspect.d.ts
CHANGED
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
* prompt. Used by `nifra context` (stdout) and the `nifra mcp` server (a tool result), so an agent sees
|
|
5
5
|
* *this* project's actual surface, not just generic docs.
|
|
6
6
|
*/
|
|
7
|
-
import { type
|
|
7
|
+
import { type ProjectEvidenceSnapshot } from "@nifrajs/core/evidence";
|
|
8
|
+
import type { ReflectedRoute } from "@nifrajs/core/reflection";
|
|
8
9
|
import type { Manifest } from "@nifrajs/web";
|
|
9
10
|
import type { LoadedApp } from "./load.js";
|
|
10
11
|
/** Read the backend's registered routes, if it's a `server()` with a `.routes()` method. */
|
|
11
12
|
export declare function backendRoutes(backend: unknown): ReflectedRoute[];
|
|
13
|
+
/** Adapt the canonical token-only snapshot to the legacy reflection view used by Markdown helpers. */
|
|
14
|
+
export declare function reflectedRoutesFromEvidence(evidence: ProjectEvidenceSnapshot): ReflectedRoute[];
|
|
12
15
|
/**
|
|
13
16
|
* Render a JSON Schema as a compact TypeScript-like type (`{ id: string, name?: string }`) - the
|
|
14
17
|
* shape agents already think in, at a fraction of the raw JSON Schema's tokens (~70-80% smaller).
|
|
@@ -66,7 +69,8 @@ export interface RouteJson {
|
|
|
66
69
|
* Markdown brief. Optionally filtered to routes whose path starts with `pathPrefix`. Reuses the exact
|
|
67
70
|
* same `call` form and TS-shaped contracts the Markdown brief shows.
|
|
68
71
|
*/
|
|
69
|
-
export declare function
|
|
72
|
+
export declare function routesToJsonFromEvidence(evidence: ProjectEvidenceSnapshot, pathPrefix?: string): RouteJson[];
|
|
73
|
+
export declare function routesToJson(app: LoadedApp, pathPrefix?: string, evidence?: ProjectEvidenceSnapshot): RouteJson[];
|
|
70
74
|
/** The kind of route: a file-routed `page` (SSR/SSG) or a backend `api` (HTTP handler). */
|
|
71
75
|
export type RouteKind = "page" | "api";
|
|
72
76
|
/** One row of the unified route table. */
|
package/dist/introspect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"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"}
|
package/dist/introspect.js
CHANGED
|
@@ -4,12 +4,58 @@
|
|
|
4
4
|
* prompt. Used by `nifra context` (stdout) and the `nifra mcp` server (a tool result), so an agent sees
|
|
5
5
|
* *this* project's actual surface, not just generic docs.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { snapshotProjectEvidence, } from "@nifrajs/core/evidence";
|
|
8
8
|
import { discoverRoutes } from "@nifrajs/web/fs";
|
|
9
9
|
import { chooseBuildPipeline, describePipeline } from "./pipeline-guard.js";
|
|
10
10
|
/** Read the backend's registered routes, if it's a `server()` with a `.routes()` method. */
|
|
11
11
|
export function backendRoutes(backend) {
|
|
12
|
-
return
|
|
12
|
+
return reflectedRoutesFromEvidence(snapshotProjectEvidence(backend));
|
|
13
|
+
}
|
|
14
|
+
function schemaPartToReflection(value) {
|
|
15
|
+
return value === undefined
|
|
16
|
+
? undefined
|
|
17
|
+
: { standard: undefined, jsonSchema: value.jsonSchema, fields: value.fields };
|
|
18
|
+
}
|
|
19
|
+
function schemaFromEvidence(schema) {
|
|
20
|
+
if (schema === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
const errors = Object.fromEntries(Object.entries(schema.errors ?? {}).map(([status, value]) => [
|
|
23
|
+
status,
|
|
24
|
+
schemaPartToReflection(value),
|
|
25
|
+
]));
|
|
26
|
+
const headers = schemaPartToReflection(schema.headers);
|
|
27
|
+
const body = schemaPartToReflection(schema.body);
|
|
28
|
+
const query = schemaPartToReflection(schema.query);
|
|
29
|
+
const params = schemaPartToReflection(schema.params);
|
|
30
|
+
const response = schemaPartToReflection(schema.response);
|
|
31
|
+
const sse = schemaPartToReflection(schema.sse);
|
|
32
|
+
return {
|
|
33
|
+
...(schema.bodyLimit !== undefined ? { bodyLimit: schema.bodyLimit } : {}),
|
|
34
|
+
...(schema.bodyLimitReason !== undefined ? { bodyLimitReason: schema.bodyLimitReason } : {}),
|
|
35
|
+
...(headers !== undefined ? { headers } : {}),
|
|
36
|
+
...(body !== undefined ? { body } : {}),
|
|
37
|
+
...(query !== undefined ? { query } : {}),
|
|
38
|
+
...(params !== undefined ? { params } : {}),
|
|
39
|
+
...(response !== undefined ? { response } : {}),
|
|
40
|
+
...(Object.keys(errors).length > 0 ? { errors } : {}),
|
|
41
|
+
...(sse !== undefined ? { sse } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Adapt the canonical token-only snapshot to the legacy reflection view used by Markdown helpers. */
|
|
45
|
+
export function reflectedRoutesFromEvidence(evidence) {
|
|
46
|
+
return evidence.routes.map((route) => {
|
|
47
|
+
const schema = schemaFromEvidence(route.schema);
|
|
48
|
+
return {
|
|
49
|
+
method: route.method,
|
|
50
|
+
path: route.path,
|
|
51
|
+
...(schema !== undefined ? { schema } : {}),
|
|
52
|
+
...(route.assurance !== undefined ? { assurance: route.assurance } : {}),
|
|
53
|
+
...(route.capabilities !== undefined ? { capabilities: route.capabilities } : {}),
|
|
54
|
+
...(route.family === true ? { family: true } : {}),
|
|
55
|
+
...(route.classification !== undefined ? { classification: route.classification } : {}),
|
|
56
|
+
...(route.tool !== undefined ? { tool: route.tool } : {}),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
13
59
|
}
|
|
14
60
|
/**
|
|
15
61
|
* Render a JSON Schema as a compact TypeScript-like type (`{ id: string, name?: string }`) - the
|
|
@@ -228,7 +274,7 @@ export function describeProject(app, filter) {
|
|
|
228
274
|
* Markdown brief. Optionally filtered to routes whose path starts with `pathPrefix`. Reuses the exact
|
|
229
275
|
* same `call` form and TS-shaped contracts the Markdown brief shows.
|
|
230
276
|
*/
|
|
231
|
-
export function
|
|
277
|
+
export function routesToJsonFromEvidence(evidence, pathPrefix) {
|
|
232
278
|
const shape = (v) => {
|
|
233
279
|
if (!v)
|
|
234
280
|
return undefined;
|
|
@@ -237,7 +283,7 @@ export function routesToJson(app, pathPrefix) {
|
|
|
237
283
|
? tsTypeOf(js)
|
|
238
284
|
: "(validated; shape not introspectable from this validator)";
|
|
239
285
|
};
|
|
240
|
-
let routes =
|
|
286
|
+
let routes = [...evidence.routes];
|
|
241
287
|
if (pathPrefix !== undefined && pathPrefix !== "") {
|
|
242
288
|
routes = routes.filter((r) => r.path.startsWith(pathPrefix));
|
|
243
289
|
}
|
|
@@ -259,6 +305,9 @@ export function routesToJson(app, pathPrefix) {
|
|
|
259
305
|
};
|
|
260
306
|
});
|
|
261
307
|
}
|
|
308
|
+
export function routesToJson(app, pathPrefix, evidence) {
|
|
309
|
+
return routesToJsonFromEvidence(evidence ?? snapshotProjectEvidence(app.backend), pathPrefix);
|
|
310
|
+
}
|
|
262
311
|
/** True when `path` is at or under `prefix` as a path segment boundary (`/api` matches `/api` and
|
|
263
312
|
* `/api/x`, but not `/apiary`). An empty prefix matches nothing (auto-mount disabled). */
|
|
264
313
|
function isUnderPrefix(path, prefix) {
|