@nifrajs/cli 2.11.0 → 2.12.1
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,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static verification work graph for agents.
|
|
3
|
+
*
|
|
4
|
+
* The graph is intentionally a compact, token-only description of project structure. It connects
|
|
5
|
+
* reflected routes to schemas, capabilities, assurance evidence, source/test files, and static
|
|
6
|
+
* manifests. It never probes a running application and never persists payloads.
|
|
7
|
+
*/
|
|
8
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
9
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
10
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
11
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return path;
|
|
15
|
+
};
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { readFile, stat } from "node:fs/promises";
|
|
18
|
+
import { basename, resolve } from "node:path";
|
|
19
|
+
import { reflectRoutes } from "@nifrajs/core/reflection";
|
|
20
|
+
import { Glob } from "bun";
|
|
21
|
+
import { digestRoute } from "./contracts.js";
|
|
22
|
+
export class StaleBuildError extends Error {
|
|
23
|
+
freshness;
|
|
24
|
+
constructor(freshness) {
|
|
25
|
+
super(freshness.reason ?? "nifra work graph requires a fresh build");
|
|
26
|
+
this.freshness = freshness;
|
|
27
|
+
this.name = "StaleBuildError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const SOURCE_GLOB = new Glob("**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,json}");
|
|
31
|
+
const IGNORED = /(^|\/)(node_modules|dist|build|\.nifra|\.git|\.wrangler|coverage)\//;
|
|
32
|
+
const MANIFEST_NAMES = new Set([
|
|
33
|
+
"server-manifest.ts",
|
|
34
|
+
"nifra-manifest.json",
|
|
35
|
+
"nifra-manifest.ts",
|
|
36
|
+
"manifest.json",
|
|
37
|
+
]);
|
|
38
|
+
export function evaluateBuildFreshness(input) {
|
|
39
|
+
if (!input.hasArtifact) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
...(input.buildDir === undefined ? {} : { buildDir: input.buildDir }),
|
|
43
|
+
newestSourceMs: input.newestSourceMs,
|
|
44
|
+
newestBuildMs: input.newestBuildMs,
|
|
45
|
+
reason: "nifra work graph refused: no built artifact found; run `nifra build` first",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (input.newestSourceMs >= input.newestBuildMs) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
...(input.buildDir === undefined ? {} : { buildDir: input.buildDir }),
|
|
52
|
+
newestSourceMs: input.newestSourceMs,
|
|
53
|
+
newestBuildMs: input.newestBuildMs,
|
|
54
|
+
reason: "nifra work graph refused: the build is stale; run `nifra build` before graphing",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
...(input.buildDir === undefined ? {} : { buildDir: input.buildDir }),
|
|
60
|
+
newestSourceMs: input.newestSourceMs,
|
|
61
|
+
newestBuildMs: input.newestBuildMs,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function inspectBuildFreshness(cwd) {
|
|
65
|
+
const sourcePaths = [];
|
|
66
|
+
for (const name of ["backend.ts", "framework.ts", "nifra.config.ts"]) {
|
|
67
|
+
const path = resolve(cwd, name);
|
|
68
|
+
if (existsSync(path))
|
|
69
|
+
sourcePaths.push(path);
|
|
70
|
+
}
|
|
71
|
+
for await (const path of SOURCE_GLOB.scan({ cwd, dot: false })) {
|
|
72
|
+
if (!IGNORED.test(path) && path.startsWith("routes/"))
|
|
73
|
+
sourcePaths.push(resolve(cwd, path));
|
|
74
|
+
}
|
|
75
|
+
const sourceTimes = await fileTimes(sourcePaths);
|
|
76
|
+
for (const buildDirName of ["dist", "build"]) {
|
|
77
|
+
const buildDir = resolve(cwd, buildDirName);
|
|
78
|
+
if (!existsSync(buildDir))
|
|
79
|
+
continue;
|
|
80
|
+
const artifacts = [];
|
|
81
|
+
for await (const path of new Glob("**/*.{js,mjs,cjs}").scan({ cwd: buildDir, dot: false })) {
|
|
82
|
+
artifacts.push(resolve(buildDir, path));
|
|
83
|
+
}
|
|
84
|
+
if (artifacts.length === 0)
|
|
85
|
+
continue;
|
|
86
|
+
const buildTimes = await fileTimes(artifacts);
|
|
87
|
+
return evaluateBuildFreshness({
|
|
88
|
+
hasArtifact: true,
|
|
89
|
+
newestSourceMs: sourceTimes,
|
|
90
|
+
newestBuildMs: buildTimes,
|
|
91
|
+
buildDir: buildDirName,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return evaluateBuildFreshness({
|
|
95
|
+
hasArtifact: false,
|
|
96
|
+
newestSourceMs: sourceTimes,
|
|
97
|
+
newestBuildMs: 0,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export async function collectProjectWorkGraph(cwd, options = {}) {
|
|
101
|
+
const freshness = await inspectBuildFreshness(cwd);
|
|
102
|
+
if (!freshness.ok)
|
|
103
|
+
throw new StaleBuildError(freshness);
|
|
104
|
+
const backendPath = resolve(cwd, "backend.ts");
|
|
105
|
+
if (!existsSync(backendPath))
|
|
106
|
+
throw new Error(`[nifra] no backend.ts in ${cwd}`);
|
|
107
|
+
const loadedValue = await import(__rewriteRelativeImportExtension(`${backendPath}?nifra-work-graph=${Date.now()}`));
|
|
108
|
+
const loaded = recordOf(loadedValue);
|
|
109
|
+
if (loaded?.backend === undefined)
|
|
110
|
+
throw new Error(`[nifra] ${backendPath} does not export backend`);
|
|
111
|
+
const files = await collectProjectFiles(cwd);
|
|
112
|
+
return buildProjectWorkGraph({ source: loaded.backend, files, freshness }, options);
|
|
113
|
+
}
|
|
114
|
+
export async function buildProjectWorkGraph(input, options = {}) {
|
|
115
|
+
const freshness = input.freshness ?? { ok: true };
|
|
116
|
+
if (!freshness.ok)
|
|
117
|
+
throw new StaleBuildError(freshness);
|
|
118
|
+
const graph = await buildWorkGraph(input);
|
|
119
|
+
const impact = queryImpact(graph, options.changedFiles ?? []);
|
|
120
|
+
const plan = planProofs(graph, impact, options.minLevel ?? 1);
|
|
121
|
+
const evidence = createEvidenceBundle(graph, impact, plan);
|
|
122
|
+
return Object.freeze({ graph, impact, plan, evidence });
|
|
123
|
+
}
|
|
124
|
+
export async function buildWorkGraph(input) {
|
|
125
|
+
const routes = reflectRoutes(input.source);
|
|
126
|
+
const files = input.files ?? [];
|
|
127
|
+
const nodes = new Map();
|
|
128
|
+
const edges = [];
|
|
129
|
+
const addNode = (node) => {
|
|
130
|
+
if (!nodes.has(node.id))
|
|
131
|
+
nodes.set(node.id, node);
|
|
132
|
+
};
|
|
133
|
+
const addEdge = (edge) => {
|
|
134
|
+
if (!edges.some((item) => item.from === edge.from && item.to === edge.to && item.relation === edge.relation))
|
|
135
|
+
edges.push(edge);
|
|
136
|
+
};
|
|
137
|
+
for (const file of files) {
|
|
138
|
+
const kind = file.kind ??
|
|
139
|
+
(isTestFile(file.path)
|
|
140
|
+
? "test"
|
|
141
|
+
: MANIFEST_NAMES.has(basename(file.path))
|
|
142
|
+
? "manifest"
|
|
143
|
+
: "source");
|
|
144
|
+
if (kind === "source")
|
|
145
|
+
addNode(await fileNode(file, "file"));
|
|
146
|
+
if (kind === "test")
|
|
147
|
+
addNode(await fileNode(file, "test"));
|
|
148
|
+
if (kind === "manifest")
|
|
149
|
+
addNode(await fileNode(file, "manifest"));
|
|
150
|
+
}
|
|
151
|
+
const sourceFiles = files.filter((file) => (file.kind ?? "source") === "source");
|
|
152
|
+
const testFiles = files.filter((file) => (file.kind ?? "source") === "test" || isTestFile(file.path));
|
|
153
|
+
const manifestFiles = files.filter((file) => (file.kind ?? "source") === "manifest" || MANIFEST_NAMES.has(basename(file.path)));
|
|
154
|
+
for (const route of routes) {
|
|
155
|
+
const routeKey = `${route.method} ${route.path}`;
|
|
156
|
+
const routeId = `route:${routeKey}`;
|
|
157
|
+
const routeDigest = await digestRoute(route);
|
|
158
|
+
const routeFiles = filesForRoute(route, sourceFiles);
|
|
159
|
+
addNode({
|
|
160
|
+
id: routeId,
|
|
161
|
+
kind: "route",
|
|
162
|
+
label: routeKey,
|
|
163
|
+
digest: await digestJson({ route: routeKey, routeDigest }),
|
|
164
|
+
files: routeFiles,
|
|
165
|
+
});
|
|
166
|
+
for (const file of routeFiles)
|
|
167
|
+
addEdge({ from: `file:${file}`, to: routeId, relation: "implements" });
|
|
168
|
+
if (routeFiles.length === 0 && sourceFiles.some((file) => file.path === "backend.ts"))
|
|
169
|
+
addEdge({ from: "file:backend.ts", to: routeId, relation: "implements" });
|
|
170
|
+
for (const [name, schema] of Object.entries(route.schema ?? {})) {
|
|
171
|
+
if (schema === undefined || name === "errors")
|
|
172
|
+
continue;
|
|
173
|
+
const schemaId = `schema:${routeKey}:${name}`;
|
|
174
|
+
addNode({
|
|
175
|
+
id: schemaId,
|
|
176
|
+
kind: "schema",
|
|
177
|
+
label: `${routeKey} ${name}`,
|
|
178
|
+
digest: await digestJson(schema.jsonSchema ?? schema.fields ?? null),
|
|
179
|
+
files: routeFiles,
|
|
180
|
+
});
|
|
181
|
+
addEdge({ from: routeId, to: schemaId, relation: "contains" });
|
|
182
|
+
}
|
|
183
|
+
for (const capability of route.capabilities ?? []) {
|
|
184
|
+
const capabilityId = `capability:${capability}`;
|
|
185
|
+
addNode({
|
|
186
|
+
id: capabilityId,
|
|
187
|
+
kind: "capability",
|
|
188
|
+
label: capability,
|
|
189
|
+
digest: await digestJson(capability),
|
|
190
|
+
files: routeFiles,
|
|
191
|
+
});
|
|
192
|
+
addEdge({ from: routeId, to: capabilityId, relation: "declares" });
|
|
193
|
+
}
|
|
194
|
+
for (const assurance of route.assurance ?? []) {
|
|
195
|
+
const assuranceId = `assurance:${routeKey}:${assurance.id}`;
|
|
196
|
+
addNode({
|
|
197
|
+
id: assuranceId,
|
|
198
|
+
kind: "assurance",
|
|
199
|
+
label: `${routeKey} ${assurance.id}`,
|
|
200
|
+
digest: await digestJson(assurance),
|
|
201
|
+
files: routeFiles,
|
|
202
|
+
});
|
|
203
|
+
addEdge({ from: routeId, to: assuranceId, relation: "enforces" });
|
|
204
|
+
}
|
|
205
|
+
for (const file of testFiles) {
|
|
206
|
+
if (!mentionsRoute(file.content, route))
|
|
207
|
+
continue;
|
|
208
|
+
const testId = `test:${file.path}`;
|
|
209
|
+
addEdge({ from: testId, to: routeId, relation: "tests" });
|
|
210
|
+
}
|
|
211
|
+
for (const file of manifestFiles)
|
|
212
|
+
addEdge({ from: `manifest:${file.path}`, to: routeId, relation: "describes" });
|
|
213
|
+
}
|
|
214
|
+
const sortedNodes = [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
215
|
+
const sortedEdges = edges.sort((a, b) => `${a.from}\n${a.to}\n${a.relation}`.localeCompare(`${b.from}\n${b.to}\n${b.relation}`));
|
|
216
|
+
const digest = await digestJson({ nodes: sortedNodes, edges: sortedEdges });
|
|
217
|
+
return Object.freeze({
|
|
218
|
+
version: 1,
|
|
219
|
+
digest,
|
|
220
|
+
nodes: Object.freeze(sortedNodes),
|
|
221
|
+
edges: Object.freeze(sortedEdges),
|
|
222
|
+
freshness: input.freshness ?? { ok: true },
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
export function queryImpact(graph, changedFiles) {
|
|
226
|
+
const normalized = [
|
|
227
|
+
...new Set(changedFiles.map((file) => file.replace(/^\.\//, "").replaceAll("\\", "/"))),
|
|
228
|
+
];
|
|
229
|
+
const seeds = new Set();
|
|
230
|
+
for (const file of normalized) {
|
|
231
|
+
for (const node of graph.nodes) {
|
|
232
|
+
if ((node.kind === "file" || node.kind === "test" || node.kind === "manifest") &&
|
|
233
|
+
(node.id === `file:${file}` ||
|
|
234
|
+
node.id === `test:${file}` ||
|
|
235
|
+
node.id === `manifest:${file}` ||
|
|
236
|
+
node.files.includes(file)))
|
|
237
|
+
seeds.add(node.id);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (normalized.includes("backend.ts"))
|
|
241
|
+
for (const node of graph.nodes)
|
|
242
|
+
if (node.kind === "route")
|
|
243
|
+
seeds.add(node.id);
|
|
244
|
+
const impacted = new Set(seeds);
|
|
245
|
+
const queue = [...seeds];
|
|
246
|
+
while (queue.length > 0) {
|
|
247
|
+
const current = queue.shift();
|
|
248
|
+
for (const edge of graph.edges) {
|
|
249
|
+
if (edge.from !== current || impacted.has(edge.to))
|
|
250
|
+
continue;
|
|
251
|
+
impacted.add(edge.to);
|
|
252
|
+
queue.push(edge.to);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const impactedRoutes = graph.nodes
|
|
256
|
+
.filter((node) => node.kind === "route" && impacted.has(node.id))
|
|
257
|
+
.map((node) => node.label)
|
|
258
|
+
.sort();
|
|
259
|
+
return Object.freeze({
|
|
260
|
+
changedFiles: Object.freeze(normalized.sort()),
|
|
261
|
+
impactedNodes: Object.freeze([...impacted].sort()),
|
|
262
|
+
impactedRoutes: Object.freeze(impactedRoutes),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
export function planProofs(graph, impact, targetLevel = 1) {
|
|
266
|
+
if (!Number.isInteger(targetLevel) || targetLevel < 0 || targetLevel > 4)
|
|
267
|
+
throw new RangeError("work graph: targetLevel must be an integer from 0 to 4");
|
|
268
|
+
const nodeIds = impact.impactedNodes;
|
|
269
|
+
const routeIds = graph.nodes
|
|
270
|
+
.filter((node) => node.kind === "route" && nodeIds.includes(node.id))
|
|
271
|
+
.map((node) => node.id);
|
|
272
|
+
const steps = [];
|
|
273
|
+
if (nodeIds.length > 0) {
|
|
274
|
+
steps.push({
|
|
275
|
+
id: "proof:typecheck",
|
|
276
|
+
kind: "typecheck",
|
|
277
|
+
level: 0,
|
|
278
|
+
cost: 1,
|
|
279
|
+
command: "nifra check --lints-only",
|
|
280
|
+
nodeIds,
|
|
281
|
+
});
|
|
282
|
+
steps.push({
|
|
283
|
+
id: "proof:contract",
|
|
284
|
+
kind: "contract",
|
|
285
|
+
level: 1,
|
|
286
|
+
cost: 2,
|
|
287
|
+
command: "nifra contracts check --json",
|
|
288
|
+
nodeIds: routeIds,
|
|
289
|
+
});
|
|
290
|
+
steps.push({
|
|
291
|
+
id: "proof:assurance",
|
|
292
|
+
kind: "assurance",
|
|
293
|
+
level: 2,
|
|
294
|
+
cost: 3,
|
|
295
|
+
command: "nifra assure --json --strict",
|
|
296
|
+
nodeIds: routeIds,
|
|
297
|
+
});
|
|
298
|
+
steps.push({
|
|
299
|
+
id: "proof:suite",
|
|
300
|
+
kind: "suite",
|
|
301
|
+
level: 3,
|
|
302
|
+
cost: 4,
|
|
303
|
+
command: "nifra test",
|
|
304
|
+
nodeIds,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return Object.freeze({
|
|
308
|
+
steps: Object.freeze(steps.filter((step) => step.level <= Math.max(targetLevel, 0) || targetLevel > 3)),
|
|
309
|
+
targetLevel,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
export function createEvidenceBundle(graph, impact, plan, proofs = []) {
|
|
313
|
+
const evidence = [...proofs].sort((a, b) => a.id.localeCompare(b.id));
|
|
314
|
+
// Levels are not cumulative (assure does not typecheck), so every planned step needs its own
|
|
315
|
+
// passing proof; a higher-level pass never subsumes a lower one.
|
|
316
|
+
const done = impact.impactedNodes.length === 0 ||
|
|
317
|
+
plan.steps.every((step) => evidence.some((item) => item.id === step.id && item.status === "pass"));
|
|
318
|
+
const missing = done
|
|
319
|
+
? []
|
|
320
|
+
: plan.steps
|
|
321
|
+
.filter((step) => !evidence.some((item) => item.id === step.id && item.status === "pass"))
|
|
322
|
+
.map((step) => step.command);
|
|
323
|
+
const next = done
|
|
324
|
+
? undefined
|
|
325
|
+
: plan.steps.find((step) => !evidence.some((item) => item.id === step.id && item.status === "pass"));
|
|
326
|
+
return Object.freeze({
|
|
327
|
+
version: 1,
|
|
328
|
+
graphDigest: graph.digest,
|
|
329
|
+
changedFiles: impact.changedFiles,
|
|
330
|
+
targetLevel: plan.targetLevel,
|
|
331
|
+
plan,
|
|
332
|
+
proofs: Object.freeze(evidence),
|
|
333
|
+
stop: Object.freeze({
|
|
334
|
+
done,
|
|
335
|
+
missing: Object.freeze(missing),
|
|
336
|
+
...(next === undefined ? {} : { next }),
|
|
337
|
+
}),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
export function recordProof(bundle, proof) {
|
|
341
|
+
const graph = {
|
|
342
|
+
version: 1,
|
|
343
|
+
digest: bundle.graphDigest,
|
|
344
|
+
nodes: [],
|
|
345
|
+
edges: [],
|
|
346
|
+
freshness: { ok: true },
|
|
347
|
+
};
|
|
348
|
+
const impact = {
|
|
349
|
+
changedFiles: bundle.changedFiles,
|
|
350
|
+
impactedNodes: bundle.stop.done ? [] : ["pending"],
|
|
351
|
+
impactedRoutes: [],
|
|
352
|
+
};
|
|
353
|
+
return createEvidenceBundle(graph, impact, bundle.plan, [
|
|
354
|
+
...bundle.proofs.filter((item) => item.id !== proof.id),
|
|
355
|
+
proof,
|
|
356
|
+
]);
|
|
357
|
+
}
|
|
358
|
+
export function renderWorkGraphJson(result) {
|
|
359
|
+
return JSON.stringify(result, null, 2);
|
|
360
|
+
}
|
|
361
|
+
export async function runWorkGraph(cwd, options = {}) {
|
|
362
|
+
const result = await collectProjectWorkGraph(cwd, options);
|
|
363
|
+
console.log(options.json === false ? renderWorkGraphText(result) : renderWorkGraphJson(result));
|
|
364
|
+
return result.evidence.stop.done;
|
|
365
|
+
}
|
|
366
|
+
export function renderWorkGraphText(result) {
|
|
367
|
+
const lines = [
|
|
368
|
+
`work graph ${result.graph.digest.slice(0, 12)} (${result.graph.nodes.length} nodes, ${result.graph.edges.length} edges)`,
|
|
369
|
+
`changed: ${result.impact.changedFiles.length === 0 ? "none" : result.impact.changedFiles.join(", ")}`,
|
|
370
|
+
`impacted routes: ${result.impact.impactedRoutes.length === 0 ? "none" : result.impact.impactedRoutes.join(", ")}`,
|
|
371
|
+
`stop: ${result.evidence.stop.done ? "done" : "pending"}`,
|
|
372
|
+
];
|
|
373
|
+
if (!result.evidence.stop.done)
|
|
374
|
+
lines.push(`next: ${result.evidence.stop.next?.command ?? "no proof available"}`);
|
|
375
|
+
return lines.join("\n");
|
|
376
|
+
}
|
|
377
|
+
async function collectProjectFiles(cwd) {
|
|
378
|
+
const files = [];
|
|
379
|
+
for await (const path of SOURCE_GLOB.scan({ cwd, dot: false })) {
|
|
380
|
+
if (IGNORED.test(path))
|
|
381
|
+
continue;
|
|
382
|
+
const full = resolve(cwd, path);
|
|
383
|
+
try {
|
|
384
|
+
const content = await readFile(full, "utf8");
|
|
385
|
+
files.push({
|
|
386
|
+
path,
|
|
387
|
+
content,
|
|
388
|
+
...(isTestFile(path)
|
|
389
|
+
? { kind: "test" }
|
|
390
|
+
: MANIFEST_NAMES.has(basename(path))
|
|
391
|
+
? { kind: "manifest" }
|
|
392
|
+
: {}),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
// A file can disappear while an agent is editing. The next graph run sees the settled tree.
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return files;
|
|
400
|
+
}
|
|
401
|
+
async function fileTimes(paths) {
|
|
402
|
+
let newest = 0;
|
|
403
|
+
for (const path of paths) {
|
|
404
|
+
try {
|
|
405
|
+
newest = Math.max(newest, (await stat(path)).mtimeMs);
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
// Missing source files are simply absent from the timestamp scan.
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return newest;
|
|
412
|
+
}
|
|
413
|
+
async function fileNode(file, kind) {
|
|
414
|
+
return {
|
|
415
|
+
id: `${kind}:${file.path}`,
|
|
416
|
+
kind,
|
|
417
|
+
label: file.path,
|
|
418
|
+
digest: await digestJson(file.content),
|
|
419
|
+
files: [file.path],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function filesForRoute(route, files) {
|
|
423
|
+
const matches = files
|
|
424
|
+
.filter((file) => file.path === "backend.ts" ||
|
|
425
|
+
(file.path.startsWith("routes/") && file.content.includes(route.path)))
|
|
426
|
+
.map((file) => file.path);
|
|
427
|
+
return [...new Set(matches)].sort();
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* A missing edge silently drops a test from the proof plan, so matching is recall-first: a literal
|
|
431
|
+
* path mention, or the static prefix of a parameterised path (tests call "/orders/123", not
|
|
432
|
+
* "/orders/:id"). Bare method words are never a signal on their own - "get" appears everywhere.
|
|
433
|
+
*/
|
|
434
|
+
function mentionsRoute(content, route) {
|
|
435
|
+
if (route.path === "/")
|
|
436
|
+
return /["'`]\/["'`]/.test(content);
|
|
437
|
+
if (content.includes(route.path))
|
|
438
|
+
return true;
|
|
439
|
+
const prefix = staticPathPrefix(route.path);
|
|
440
|
+
return prefix !== undefined && content.includes(prefix);
|
|
441
|
+
}
|
|
442
|
+
function staticPathPrefix(path) {
|
|
443
|
+
const staticSegments = [];
|
|
444
|
+
for (const segment of path.split("/")) {
|
|
445
|
+
if (segment.startsWith(":") || segment.startsWith("*"))
|
|
446
|
+
break;
|
|
447
|
+
if (segment !== "")
|
|
448
|
+
staticSegments.push(segment);
|
|
449
|
+
}
|
|
450
|
+
return staticSegments.length === 0 ? undefined : `/${staticSegments.join("/")}/`;
|
|
451
|
+
}
|
|
452
|
+
function isTestFile(path) {
|
|
453
|
+
return /(^|\/)(test|tests|__tests__)(\/|$)|\.(test|spec)\.[^.]+$/.test(path);
|
|
454
|
+
}
|
|
455
|
+
function recordOf(value) {
|
|
456
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
457
|
+
? Object.fromEntries(Object.entries(value))
|
|
458
|
+
: undefined;
|
|
459
|
+
}
|
|
460
|
+
async function digestJson(value) {
|
|
461
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
462
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
463
|
+
return [...digest].map((item) => item.toString(16).padStart(2, "0")).join("");
|
|
464
|
+
}
|
|
465
|
+
//# sourceMappingURL=work-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-graph.js","sourceRoot":"","sources":["../src/work-graph.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;;;;;;;;;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAuB,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAqD5C,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACnB;IAArB,YAAqB,SAAyB;QAC5C,KAAK,CAAC,SAAS,CAAC,MAAM,IAAI,yCAAyC,CAAC,CAAA;QADjD,cAAS,GAAT,SAAS,CAAgB;QAE5C,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAyDD,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,2CAA2C,CAAC,CAAA;AACzE,MAAM,OAAO,GAAG,qEAAqE,CAAA;AACrF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,oBAAoB;IACpB,qBAAqB;IACrB,mBAAmB;IACnB,eAAe;CAChB,CAAC,CAAA;AAEF,MAAM,UAAU,sBAAsB,CAAC,KAKtC;IACC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;YACrE,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,MAAM,EAAE,4EAA4E;SACrF,CAAA;IACH,CAAC;IACD,IAAI,KAAK,CAAC,cAAc,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QAChD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;YACrE,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,MAAM,EAAE,iFAAiF;SAC1F,CAAA;IACH,CAAC;IACD,OAAO;QACL,EAAE,EAAE,IAAI;QACR,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrE,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,aAAa,EAAE,KAAK,CAAC,aAAa;KACnC,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAW;IACrD,MAAM,WAAW,GAAa,EAAE,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,CAAC,YAAY,EAAE,cAAc,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAC/B,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC9C,CAAC;IACD,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;IAC7F,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,CAAA;IAChD,KAAK,MAAM,YAAY,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QAC3C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACnC,MAAM,SAAS,GAAa,EAAE,CAAA;QAC9B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;YAC3F,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAA;QACzC,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QACpC,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,CAAA;QAC7C,OAAO,sBAAsB,CAAC;YAC5B,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE,WAAW;YAC3B,aAAa,EAAE,UAAU;YACzB,QAAQ,EAAE,YAAY;SACvB,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,sBAAsB,CAAC;QAC5B,WAAW,EAAE,KAAK;QAClB,cAAc,EAAE,WAAW;QAC3B,aAAa,EAAE,CAAC;KACjB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,GAAW,EACX,UAAqF,EAAE;IAEvF,MAAM,SAAS,GAAG,MAAM,qBAAqB,CAAC,GAAG,CAAC,CAAA;IAClD,IAAI,CAAC,SAAS,CAAC,EAAE;QAAE,MAAM,IAAI,eAAe,CAAC,SAAS,CAAC,CAAA;IACvD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;IAC9C,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAA;IAChF,MAAM,WAAW,GAAY,MAAM,MAAM,kCAAC,GAAG,WAAW,qBAAqB,IAAI,CAAC,GAAG,EAAE,EAAE,EAAC,CAAA;IAC1F,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAA;IACpC,IAAI,MAAM,EAAE,OAAO,KAAK,SAAS;QAC/B,MAAM,IAAI,KAAK,CAAC,WAAW,WAAW,0BAA0B,CAAC,CAAA;IACnE,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAA;IAC5C,OAAO,qBAAqB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,CAAA;AACrF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAA0B,EAC1B,UAAqF,EAAE;IAEvF,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;IACjD,IAAI,CAAC,SAAS,CAAC,EAAE;QAAE,MAAM,IAAI,eAAe,CAAC,SAAS,CAAC,CAAA;IACvD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,CAAA;IACzC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAA;IAC7D,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAA;IAC7D,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;IAC1D,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAA0B;IAC7D,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAA;IAC/B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAA;IAC9C,MAAM,KAAK,GAAoB,EAAE,CAAA;IACjC,MAAM,OAAO,GAAG,CAAC,IAAmB,EAAQ,EAAE;QAC5C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IACnD,CAAC,CAAA;IACD,MAAM,OAAO,GAAG,CAAC,IAAmB,EAAQ,EAAE;QAC5C,IACE,CAAC,KAAK,CAAC,IAAI,CACT,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAC5F;YAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC,CAAA;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GACR,IAAI,CAAC,IAAI;YACT,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;gBACpB,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACvC,CAAC,CAAC,UAAU;oBACZ,CAAC,CAAC,QAAQ,CAAC,CAAA;QACjB,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QAC5D,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QAC1D,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;IACpE,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK,QAAQ,CAAC,CAAA;IAChF,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAC5B,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK,MAAM,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CACtE,CAAA;IACD,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAChC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK,UAAU,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAC5F,CAAA;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAA;QAChD,MAAM,OAAO,GAAG,SAAS,QAAQ,EAAE,CAAA;QACnC,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,CAAA;QAC5C,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;QACpD,OAAO,CAAC;YACN,EAAE,EAAE,OAAO;YACX,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,MAAM,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;YAC1D,KAAK,EAAE,UAAU;SAClB,CAAC,CAAA;QACF,KAAK,MAAM,IAAI,IAAI,UAAU;YAC3B,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAA;QACxE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC;YACnF,OAAO,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAA;QAC3E,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;YAChE,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ;gBAAE,SAAQ;YACvD,MAAM,QAAQ,GAAG,UAAU,QAAQ,IAAI,IAAI,EAAE,CAAA;YAC7C,OAAO,CAAC;gBACN,EAAE,EAAE,QAAQ;gBACZ,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,GAAG,QAAQ,IAAI,IAAI,EAAE;gBAC5B,MAAM,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC;gBACpE,KAAK,EAAE,UAAU;aAClB,CAAC,CAAA;YACF,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;QAChE,CAAC;QACD,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;YAClD,MAAM,YAAY,GAAG,cAAc,UAAU,EAAE,CAAA;YAC/C,OAAO,CAAC;gBACN,EAAE,EAAE,YAAY;gBAChB,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,UAAU;gBACjB,MAAM,EAAE,MAAM,UAAU,CAAC,UAAU,CAAC;gBACpC,KAAK,EAAE,UAAU;aAClB,CAAC,CAAA;YACF,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;QACpE,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;YAC9C,MAAM,WAAW,GAAG,aAAa,QAAQ,IAAI,SAAS,CAAC,EAAE,EAAE,CAAA;YAC3D,OAAO,CAAC;gBACN,EAAE,EAAE,WAAW;gBACf,IAAI,EAAE,WAAW;gBACjB,KAAK,EAAE,GAAG,QAAQ,IAAI,SAAS,CAAC,EAAE,EAAE;gBACpC,MAAM,EAAE,MAAM,UAAU,CAAC,SAAS,CAAC;gBACnC,KAAK,EAAE,UAAU;aAClB,CAAC,CAAA;YACF,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;QACnE,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC;gBAAE,SAAQ;YACjD,MAAM,MAAM,GAAG,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAA;YAClC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC3D,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,aAAa;YAC9B,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAA;IAClF,CAAC;IACD,MAAM,WAAW,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAChF,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACtC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CACvF,CAAA;IACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;IAC3E,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,CAAC;QACV,MAAM;QACN,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;QACjC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;QACjC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE;KAC3C,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAgB,EAAE,YAA+B;IAC3E,MAAM,UAAU,GAAG;QACjB,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;KACxF,CAAA;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAA;IAC/B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/B,IACE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC;gBAC1E,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,EAAE;oBACzB,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,EAAE;oBAC1B,IAAI,CAAC,EAAE,KAAK,YAAY,IAAI,EAAE;oBAC9B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAE5B,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACtB,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK;YAAE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC/E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;IAC/B,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;IACxB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAG,CAAA;QAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,SAAQ;YAC5D,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACrB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IACD,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK;SAC/B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SAChE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;SACzB,IAAI,EAAE,CAAA;IACT,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAC9C,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC;KAC9C,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAgB,EAAE,MAAoB,EAAE,WAAW,GAAG,CAAC;IAChF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,IAAI,WAAW,GAAG,CAAC;QACtE,MAAM,IAAI,UAAU,CAAC,wDAAwD,CAAC,CAAA;IAChF,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAA;IACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK;SACzB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SACpE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACzB,MAAM,KAAK,GAAgB,EAAE,CAAA;IAC7B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,iBAAiB;YACrB,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,CAAC;YACR,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,0BAA0B;YACnC,OAAO;SACR,CAAC,CAAA;QACF,KAAK,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,gBAAgB;YACpB,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,CAAC;YACR,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,8BAA8B;YACvC,OAAO,EAAE,QAAQ;SAClB,CAAC,CAAA;QACF,KAAK,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,iBAAiB;YACrB,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,CAAC;YACR,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,8BAA8B;YACvC,OAAO,EAAE,QAAQ;SAClB,CAAC,CAAA;QACF,KAAK,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,aAAa;YACjB,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,CAAC;YACR,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,YAAY;YACrB,OAAO;SACR,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC,MAAM,CAClB,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC,CAClF;QACD,WAAW;KACZ,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,KAAgB,EAChB,MAAoB,EACpB,IAAe,EACf,SAAmC,EAAE;IAErC,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACrE,6FAA6F;IAC7F,iEAAiE;IACjE,MAAM,IAAI,GACR,MAAM,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CACxB,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CACvE,CAAA;IACH,MAAM,OAAO,GAAG,IAAI;QAClB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,IAAI,CAAC,KAAK;aACP,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;aACzF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,IAAI;QACf,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CACb,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAClF,CAAA;IACL,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,IAAI;QACJ,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC;YAClB,IAAI;YACJ,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;YAC/B,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;SACxC,CAAC;KACH,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAsB,EAAE,KAAoB;IACtE,MAAM,KAAK,GAAc;QACvB,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,MAAM,CAAC,WAAW;QAC1B,KAAK,EAAE,EAAE;QACT,KAAK,EAAE,EAAE;QACT,SAAS,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE;KACxB,CAAA;IACD,MAAM,MAAM,GAAiB;QAC3B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAClD,cAAc,EAAE,EAAE;KACnB,CAAA;IACD,OAAO,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;QACtD,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC;QACvD,KAAK;KACN,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAA8B;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,UAII,EAAE;IAEN,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC1D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;IAC/F,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAA8B;IAChE,MAAM,KAAK,GAAG;QACZ,cAAc,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,WAAW,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,SAAS;QACzH,YAAY,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACtG,oBAAoB,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAClH,SAAS,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE;KAC1D,CAAA;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI;QAC5B,KAAK,CAAC,IAAI,CAAC,SAAS,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,oBAAoB,EAAE,CAAC,CAAA;IACnF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,GAAW;IAC5C,MAAM,KAAK,GAA0B,EAAE,CAAA;IACvC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QAChC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAC/B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YAC5C,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI;gBACJ,OAAO;gBACP,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;oBAClB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE;oBAC3B,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;wBAClC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAmB,EAAE;wBAC/B,CAAC,CAAC,EAAE,CAAC;aACV,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,4FAA4F;QAC9F,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,KAAwB;IAC/C,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,kEAAkE;QACpE,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,IAAyB,EACzB,IAAkC;IAElC,OAAO;QACL,EAAE,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE;QAC1B,IAAI;QACJ,KAAK,EAAE,IAAI,CAAC,IAAI;QAChB,MAAM,EAAE,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;QACtC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;KACnB,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CACpB,KAAqB,EACrB,KAAqC;IAErC,MAAM,OAAO,GAAG,KAAK;SAClB,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,IAAI,KAAK,YAAY;QAC1B,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CACzE;SACA,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3B,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AACrC,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,OAAe,EAAE,KAAqB;IAC3D,IAAI,KAAK,CAAC,IAAI,KAAK,GAAG;QAAE,OAAO,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3D,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7C,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC3C,OAAO,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACzD,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,MAAM,cAAc,GAAa,EAAE,CAAA;IACnC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,MAAK;QAC7D,IAAI,OAAO,KAAK,EAAE;YAAE,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,cAAc,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;AAClF,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,0DAA0D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC9E,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzE,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC,CAAC,SAAS,CAAA;AACf,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,KAAc;IACtC,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7D,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC/E,CAAC"}
|
package/docs/examples.json
CHANGED
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"name": "REUSABLE_ROUTER",
|
|
130
130
|
"topic": "Nifra - Framework contract",
|
|
131
131
|
"slug": "contract",
|
|
132
|
-
"code": "// A PUBLISHABLE router - contract-first, so its types survive `.d.ts` emit for consumers.\nimport { client } from \"@nifrajs/client\"\nimport { defineContract, implement } from \"@nifrajs/core/contract\"\nimport { server } from \"@nifrajs/core/server\"\nimport { t } from \"@nifrajs/schema\"\n\n// --- in the package (say @your-org/billing) ---\n// 1. The CONTRACT is ONE plain declared object - methods, paths, schemas, no handlers.\n// Export it: it is the single type the package publishes, and a consumer's typed client is\n// built from THIS value (not from `typeof app`), so no internal server type crosses the seam.\nexport const billingContract = defineContract({\n getPlan: { method: \"GET\", path: \"/billing/plan\" },\n subscribe: { method: \"POST\", path: \"/billing/subscribe\", body: t.object({ plan: t.string() }) },\n})\n\n// 2. A FACTORY closes over the host's dependencies and returns a real, mountable router.\n// `implement` binds handlers to the contract; each handler's context is typed from the contract\n// alone (`c.body` is { plan: string } below) - no per-route `AddRoute` inference accreting.\nexport interface BillingDeps {\n charge(plan: string): Promise<{ id: string }>\n}\nexport function createBillingRouter(deps: BillingDeps) {\n return implement(billingContract, {\n getPlan: () => ({ plan: \"pro\" }),\n subscribe: (c) => deps.charge(c.body.plan),\n })\n}\n\n// --- in the consuming app ---\n// 3. Mount the router with ONE `.merge()` - no per-route `.get()/.post()` chain to re-infer.\nconst app = server().merge(createBillingRouter({ charge: async (plan) => ({ id: plan }) }))\n\n// 4. Build the client from the EXPORTED contract value - decoupled, no `typeof app` import needed.\nconst api = client(billingContract, \"https://api.example.com\")\nconst { data } = await api.billing.plan.get() // GET /billing/plan\nawait api.billing
|
|
132
|
+
"code": "// A PUBLISHABLE router - contract-first, so its types survive `.d.ts` emit for consumers.\nimport { client } from \"@nifrajs/client\"\nimport { defineContract, implement } from \"@nifrajs/core/contract\"\nimport { server } from \"@nifrajs/core/server\"\nimport { t } from \"@nifrajs/schema\"\n\n// --- in the package (say @your-org/billing) ---\n// 1. The CONTRACT is ONE plain declared object - methods, paths, schemas, no handlers.\n// Export it: it is the single type the package publishes, and a consumer's typed client is\n// built from THIS value (not from `typeof app`), so no internal server type crosses the seam.\nexport const billingContract = defineContract({\n getPlan: { method: \"GET\", path: \"/billing/plan\" },\n subscribe: { method: \"POST\", path: \"/billing/subscribe\", body: t.object({ plan: t.string() }) },\n})\n\n// 2. A FACTORY closes over the host's dependencies and returns a real, mountable router.\n// `implement` binds handlers to the contract; each handler's context is typed from the contract\n// alone (`c.body` is { plan: string } below) - no per-route `AddRoute` inference accreting.\nexport interface BillingDeps {\n charge(plan: string): Promise<{ id: string }>\n}\nexport function createBillingRouter(deps: BillingDeps) {\n return implement(billingContract, {\n getPlan: () => ({ plan: \"pro\" }),\n subscribe: (c) => deps.charge(c.body.plan),\n })\n}\n\n// --- in the consuming app ---\n// 3. Mount the router with ONE `.merge()` - no per-route `.get()/.post()` chain to re-infer.\nconst app = server().merge(createBillingRouter({ charge: async (plan) => ({ id: plan }) }))\n\n// 4. Build the client from the EXPORTED contract value - decoupled, no `typeof app` import needed.\nconst api = client(billingContract, \"https://api.example.com\")\nconst { data } = await api.billing.plan.get() // GET /billing/plan\n// \"subscribe\" is a reserved client word (the SSE subscriber), so this segment is reached by\n// CALLING the parent node with the segment name - same request, same typed body.\nawait api.billing(\"subscribe\").post({ plan: \"pro\" }) // POST /billing/subscribe, body typed"
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"name": "SEO_EXAMPLE",
|
|
@@ -167,6 +167,12 @@
|
|
|
167
167
|
"slug": "deployment",
|
|
168
168
|
"code": "// _worker.ts - the edge SSR entry\nimport { toFetchHandler } from \"@nifrajs/core/server\"\nimport { createWebApp } from \"@nifrajs/web\"\nimport { reactAdapter } from \"@nifrajs/web-react\"\nimport { clientEntry, manifest } from \"./server-manifest\" // generated by buildServer\n\nconst app = createWebApp({ adapter: reactAdapter, manifest, clientEntry })\nexport default toFetchHandler(app) // a Workers/Pages fetch handler\n\n// build.ts - buildClient (→ /assets) + buildServer (edge conditions → _worker.js)\nimport { buildClient, buildServer } from \"@nifrajs/web/build\""
|
|
169
169
|
},
|
|
170
|
+
{
|
|
171
|
+
"name": "LAMBDA",
|
|
172
|
+
"topic": "Nifra - Deployment",
|
|
173
|
+
"slug": "deployment",
|
|
174
|
+
"code": "// handler.ts - one app per container, built at module scope\nimport { handle, streamHandle, type LambdaEnv } from \"@nifrajs/aws-lambda\"\nimport { server } from \"@nifrajs/core\"\n\nconst app = server<LambdaEnv>()\n .get(\"/\", (c) => c.json({ ip: c.clientIp, requestId: c.env.context?.awsRequestId }))\n\nexport const handler = handle(app) // API Gateway HTTP API (v2) + Function URLs\n// or, on a Function URL with InvokeMode: RESPONSE_STREAM:\n// export const handler = streamHandle(app)"
|
|
175
|
+
},
|
|
170
176
|
{
|
|
171
177
|
"name": "SW",
|
|
172
178
|
"topic": "Nifra - Deployment",
|
|
@@ -215,11 +221,17 @@
|
|
|
215
221
|
"slug": "frameworks",
|
|
216
222
|
"code": "// Server - pick an adapter. Everything else is identical across all five frameworks:\n// the same routes, loaders, actions, streaming, <Await>, fetchers, query cache.\nimport { createWebApp } from \"@nifrajs/web\"\nimport { reactAdapter } from \"@nifrajs/web-react\" // ← or web-solid · web-vue · web-preact · web-svelte\nimport { clientEntry, manifest } from \"./server-manifest\" // generated by buildServer\n\nconst app = createWebApp({\n adapter: reactAdapter, // the one line that changes per framework\n manifest,\n clientEntry, // the built client bundle (from buildClient's manifest)\n})"
|
|
217
223
|
},
|
|
224
|
+
{
|
|
225
|
+
"name": "DETECTOR",
|
|
226
|
+
"topic": "Nifra - i18n",
|
|
227
|
+
"slug": "i18n",
|
|
228
|
+
"code": "// Or as a server plugin: c.locale on every handler, Content-Language on every response.\nimport { server } from \"@nifrajs/core\"\nimport { localeDetector } from \"@nifrajs/i18n/detector\"\n\nconst app = server().use(localeDetector({\n locales: [\"en\", \"fr\", \"de\"],\n defaultLocale: \"en\",\n queryParam: \"lang\",\n cookie: \"locale\",\n persist: true, // pin an explicit ?lang= choice into the cookie\n})).get(\"/\", (c) => c.json({ locale: c.locale, via: c.localeSource }))"
|
|
229
|
+
},
|
|
218
230
|
{
|
|
219
231
|
"name": "NEGOTIATE",
|
|
220
232
|
"topic": "Nifra - i18n",
|
|
221
233
|
"slug": "i18n",
|
|
222
|
-
"code": "// In a loader: resolve the locale + return only that catalog's messages.\nimport { negotiateLocale } from \"@nifrajs/i18n\"\nimport { catalogs, locales } from \"../catalogs\"\n\nexport async function loader({ request }: { request: Request }) {\n const
|
|
234
|
+
"code": "// In a loader: resolve the locale + return only that catalog's messages.\nimport { negotiateLocale } from \"@nifrajs/i18n\"\nimport { catalogs, locales } from \"../catalogs\"\n\nexport async function loader({ request }: { request: Request }) {\n const locale = negotiateLocale(request, { locales, defaultLocale: \"en\", queryParam: \"lang\", cookie: \"lang\" })\n return { locale, messages: catalogs[locale] } // ?lang= → cookie → Accept-Language → default\n}"
|
|
223
235
|
},
|
|
224
236
|
{
|
|
225
237
|
"name": "DIMENSIONS",
|
|
@@ -251,6 +263,12 @@
|
|
|
251
263
|
"slug": "index",
|
|
252
264
|
"code": "import { server } from \"@nifrajs/core/server\"\n\nserver()\n .get(\"/\", () => ({ hello: \"world\" }))\n .get(\"/users/:id\", (c) => ({ id: c.params.id }))\n .listen(3000)"
|
|
253
265
|
},
|
|
266
|
+
{
|
|
267
|
+
"name": "HONO",
|
|
268
|
+
"topic": "Nifra - Migrate route by route",
|
|
269
|
+
"slug": "migrate-route-by-route",
|
|
270
|
+
"code": "import { Hono } from \"hono\"\nimport { server } from \"@nifrajs/core/server\"\nimport { t } from \"@nifrajs/schema\"\n\nconst legacy = new Hono()\nlegacy.get(\"/users\", (c) => c.json({ source: \"hono\", users: [] }))\nlegacy.post(\"/users\", async (c) => c.json(await c.req.json()))\n\nexport const app = server()\n // The wildcard is a literal mount prefix, not a typed Nifra route.\n .mountFetch(\"/legacy/*\", legacy.fetch, { stripPrefix: true })\n // Typed routes take precedence, so move one route out of Hono at a time.\n .get(\"/legacy/users/:id\", (c) => ({ id: c.params.id, source: \"nifra\" }))\n .post(\n \"/users\",\n { body: t.object({ name: t.string() }) },\n (c) => ({ id: crypto.randomUUID(), name: c.body.name }),\n )"
|
|
271
|
+
},
|
|
254
272
|
{
|
|
255
273
|
"name": "CACHING",
|
|
256
274
|
"topic": "Nifra - Plugins & middleware",
|
|
@@ -341,12 +359,24 @@
|
|
|
341
359
|
"slug": "security",
|
|
342
360
|
"code": "import { server, jsonLogger, commonSecretPatterns } from \"@nifrajs/core/server\"\n\n// Key-name redaction is always on; valuePatterns adds opt-in value + message scanning.\nconst app = server({\n logger: jsonLogger(undefined, { valuePatterns: commonSecretPatterns }),\n})\n\n// logger.error(\"auth failed for user@example.com with Bearer abc.def\")\n// → { ...,\"message\":\"auth failed for [REDACTED] with [REDACTED]\" }\n// Add your own: { valuePatterns: [...commonSecretPatterns, /\\bord_[a-z0-9]+/g] }"
|
|
343
361
|
},
|
|
362
|
+
{
|
|
363
|
+
"name": "PROTO",
|
|
364
|
+
"topic": "Nifra - security & hardening",
|
|
365
|
+
"slug": "security",
|
|
366
|
+
"code": "import { server } from \"@nifrajs/core/server\"\n\n// Default is \"reject\". A JSON body with an own `__proto__` key - or a `constructor`\n// carrying a `prototype` - is the exact shape that turns a later innocent { ...body }\n// merge or Object.assign into prototype pollution. It answers the SAME flat 400 as\n// malformed JSON, so an attacker learns nothing from the response.\nconst app = server({ protoPoisoning: \"reject\" }) // \"strip\" | \"ignore\" also available\n\napp.post(\"/profile\", async (c) => {\n const body = await c.boundedJson<{ name: string }>() // and the schema-route path\n // Reached only for a clean payload. Under \"strip\" the offending keys are deleted and\n // the handler sees the cleaned object; under \"ignore\" the body passes through as-is.\n return { name: body.name }\n})"
|
|
367
|
+
},
|
|
344
368
|
{
|
|
345
369
|
"name": "RESPONSE_CONTRACT",
|
|
346
370
|
"topic": "Nifra - security & hardening",
|
|
347
371
|
"slug": "security",
|
|
348
372
|
"code": "import { server } from \"@nifrajs/core/server\"\nimport { responseContract } from \"@nifrajs/core/response-contract\"\nimport { t } from \"@nifrajs/schema\"\n\nconst PublicUser = t.object({ id: t.string(), name: t.string() })\n\n// \"warn\" logs and changes nothing; \"enforce\" makes the contract the upper bound too.\n// Not installing the plugin is \"off\" - and keeps the lane out of your bundle entirely.\nexport const app = server().use(responseContract(\"enforce\")).get(\n \"/me\",\n { response: PublicUser },\n async () => {\n // Every column, including the ones the contract never declared.\n const user = { id: \"u1\", name: \"Ada\", email: \"a@b.c\", passwordHash: \"...\" }\n return user\n },\n)\n\n// off -> {\"id\":\"u1\",\"name\":\"Ada\",\"email\":\"a@b.c\",\"passwordHash\":\"...\"}\n// enforce -> {\"id\":\"u1\",\"name\":\"Ada\"}"
|
|
349
373
|
},
|
|
374
|
+
{
|
|
375
|
+
"name": "SECURITY_HEADERS",
|
|
376
|
+
"topic": "Nifra - security & hardening",
|
|
377
|
+
"slug": "security",
|
|
378
|
+
"code": "import { server } from \"@nifrajs/core/server\"\nimport { securityHeaders } from \"@nifrajs/middleware\"\n\n// Always on (covering errors and 404s too): X-Content-Type-Options: nosniff,\n// X-Frame-Options: DENY, Referrer-Policy: no-referrer. The rest are opt-in - each is a\n// deliberate cross-origin decision, so nifra never turns them on behind your back:\nconst app = server().use(securityHeaders({\n crossOriginOpenerPolicy: \"same-origin\", // isolate the browsing-context group\n crossOriginResourcePolicy: \"same-origin\", // block cross-origin embedding of your responses\n crossOriginEmbedderPolicy: \"require-corp\", // enable crossOriginIsolated (SharedArrayBuffer, …)\n permissionsPolicy: \"camera=(), geolocation=()\",\n contentSecurityPolicy: \"default-src 'self'\",\n hsts: { maxAge: 63072000, includeSubDomains: true, preload: true }, // opt in once HTTPS-only\n}))"
|
|
379
|
+
},
|
|
350
380
|
{
|
|
351
381
|
"name": "DECLARE",
|
|
352
382
|
"topic": "Nifra - Server functions",
|