zefiro 0.6.0 → 0.6.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/agents/1.feature-analyzer-agent-v4.md +121 -0
- package/dist/cli-ychk5szm.js +192636 -0
- package/dist/cli.js +152 -10
- package/dist/mcp.js +155 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
banner,
|
|
4
|
+
buildV4Graph,
|
|
4
5
|
error,
|
|
6
|
+
extractStateVariables,
|
|
5
7
|
header,
|
|
6
8
|
info,
|
|
7
9
|
isDAGValid,
|
|
@@ -14,7 +16,7 @@ import {
|
|
|
14
16
|
validateQAMapV3,
|
|
15
17
|
verbose,
|
|
16
18
|
warn
|
|
17
|
-
} from "./cli-
|
|
19
|
+
} from "./cli-ychk5szm.js";
|
|
18
20
|
import {
|
|
19
21
|
CONFIG_DIR,
|
|
20
22
|
getPackageRoot,
|
|
@@ -7637,7 +7639,7 @@ async function ensureApiKey(provider) {
|
|
|
7637
7639
|
}
|
|
7638
7640
|
}
|
|
7639
7641
|
function registerAnalyze(program2) {
|
|
7640
|
-
program2.command("analyze [input]").description("Analyze AST scan with AI to generate QA map (features, workflows, scenarios)").option("--output <file>", "Write QA map to specific file").option("--skip-scenarios", "Only run feature analysis (skip scenario generation)").option("--schema-version <version>", "Output schema version (2 or
|
|
7642
|
+
program2.command("analyze [input]").description("Analyze AST scan with AI to generate QA map (features, workflows, scenarios)").option("--output <file>", "Write QA map to specific file").option("--skip-scenarios", "Only run feature analysis (skip scenario generation)").option("--schema-version <version>", "Output schema version (2, 3, or 4)", "3").action(async (inputArg, opts) => {
|
|
7641
7643
|
const schemaVersion = parseInt(opts?.schemaVersion ?? "3", 10);
|
|
7642
7644
|
const ctx = await resolveCommandContext(program2);
|
|
7643
7645
|
const root = ctx.paths.projectRoot;
|
|
@@ -7674,6 +7676,10 @@ function registerAnalyze(program2) {
|
|
|
7674
7676
|
if (!hasKey)
|
|
7675
7677
|
return;
|
|
7676
7678
|
const spinner = createSpinner();
|
|
7679
|
+
if (schemaVersion === 4) {
|
|
7680
|
+
await runV4Analysis(ctx, ast, outputPath, spinner, opts);
|
|
7681
|
+
return;
|
|
7682
|
+
}
|
|
7677
7683
|
if (schemaVersion === 3) {
|
|
7678
7684
|
await runV3Analysis(ctx, ast, outputPath, prevQAMap, affected, spinner, opts);
|
|
7679
7685
|
return;
|
|
@@ -7986,6 +7992,107 @@ async function runV3Analysis(ctx, ast, outputPath, _prevQAMap, _affected, spinne
|
|
|
7986
7992
|
writeFile(outputPath, JSON.stringify(normalizedPayload, null, 2));
|
|
7987
7993
|
success(`V3 QA map written to ${outputPath}`);
|
|
7988
7994
|
}
|
|
7995
|
+
async function runV4Analysis(ctx, ast, outputPath, spinner, opts) {
|
|
7996
|
+
const { join: join5 } = await import("node:path");
|
|
7997
|
+
spinner.start("V4 Stage 1: Extracting state variables...");
|
|
7998
|
+
const scanDir = ast.files?.[0]?.path ? join5(process.cwd(), ast.files[0].path).replace(ast.files[0].path, "") : process.cwd();
|
|
7999
|
+
const filePaths = ast.files.map((f) => join5(scanDir, f.path));
|
|
8000
|
+
const stateVariables = extractStateVariables(filePaths, ast.routes, scanDir);
|
|
8001
|
+
spinner.stop();
|
|
8002
|
+
const highConf = stateVariables.filter((v) => v.extractionConfidence === "high").length;
|
|
8003
|
+
success(`Extracted ${stateVariables.length} state variables (${highConf} high confidence)`);
|
|
8004
|
+
spinner.start("V4 Stage 2: Building formal model (structural graph + state space + transitions)...");
|
|
8005
|
+
const graphResult = buildV4Graph(ast, stateVariables);
|
|
8006
|
+
spinner.stop();
|
|
8007
|
+
success(`Structural graph: ${graphResult.structuralGraph.nodes.length} nodes, ${graphResult.structuralGraph.edges.length} edges
|
|
8008
|
+
State space: ${graphResult.stateSpace.stats.totalVariables} vars, ${graphResult.stateSpace.stats.totalValidConfigurations} valid configs
|
|
8009
|
+
Transition system: ${graphResult.transitionSystem.stats.reachableNodes} reachable nodes, ${graphResult.transitionSystem.stats.totalTransitions} transitions`);
|
|
8010
|
+
spinner.start("V4 Stage 3: Generating semantic projection (feature labels)...");
|
|
8011
|
+
const featureAgent = loadAgent("feature-analyzer-agent-v4", ctx.config);
|
|
8012
|
+
const projectionInput = {
|
|
8013
|
+
structuralGraph: {
|
|
8014
|
+
nodes: graphResult.structuralGraph.nodes.map((n) => ({
|
|
8015
|
+
id: n.id,
|
|
8016
|
+
routePath: n.routePath,
|
|
8017
|
+
routeGroup: n.routeGroup,
|
|
8018
|
+
components: n.components,
|
|
8019
|
+
guards: n.guards,
|
|
8020
|
+
forms: n.forms
|
|
8021
|
+
})),
|
|
8022
|
+
edges: graphResult.structuralGraph.edges.map((e) => ({
|
|
8023
|
+
id: e.id,
|
|
8024
|
+
fromNodeId: e.fromNodeId,
|
|
8025
|
+
toNodeId: e.toNodeId,
|
|
8026
|
+
type: e.type
|
|
8027
|
+
})),
|
|
8028
|
+
entryPoints: graphResult.structuralGraph.entryPoints
|
|
8029
|
+
},
|
|
8030
|
+
stateVariables: stateVariables.map((v) => ({
|
|
8031
|
+
id: v.id,
|
|
8032
|
+
name: v.name,
|
|
8033
|
+
category: v.category,
|
|
8034
|
+
domain: v.domain,
|
|
8035
|
+
affectsRoutes: v.affectsRoutes,
|
|
8036
|
+
extractionConfidence: v.extractionConfidence
|
|
8037
|
+
})),
|
|
8038
|
+
transitionSystemStats: graphResult.transitionSystem.stats,
|
|
8039
|
+
ast: { routes: ast.routes, stats: ast.stats }
|
|
8040
|
+
};
|
|
8041
|
+
const featureResponse = await callLLM({
|
|
8042
|
+
provider: ctx.provider,
|
|
8043
|
+
model: ctx.model ?? featureAgent.config.model,
|
|
8044
|
+
systemPrompt: featureAgent.systemPrompt,
|
|
8045
|
+
userMessage: JSON.stringify(projectionInput),
|
|
8046
|
+
maxTokens: featureAgent.config.maxTokens,
|
|
8047
|
+
temperature: featureAgent.config.temperature,
|
|
8048
|
+
jsonMode: true
|
|
8049
|
+
});
|
|
8050
|
+
spinner.stop();
|
|
8051
|
+
const projection = JSON.parse(extractJSON(featureResponse.content));
|
|
8052
|
+
const features = (projection.features ?? []).map((f) => ({
|
|
8053
|
+
id: f.id,
|
|
8054
|
+
name: f.name,
|
|
8055
|
+
description: f.description ?? "",
|
|
8056
|
+
subgraph: {
|
|
8057
|
+
routeIds: f.subgraph?.routeIds ?? [],
|
|
8058
|
+
transitionIds: f.subgraph?.transitionIds ?? [],
|
|
8059
|
+
stateVariableIds: f.subgraph?.stateVariableIds ?? []
|
|
8060
|
+
},
|
|
8061
|
+
complexity: (f.subgraph?.routeIds?.length ?? 0) + (f.subgraph?.transitionIds?.length ?? 0),
|
|
8062
|
+
criticalPaths: [],
|
|
8063
|
+
routes: f.routes ?? [],
|
|
8064
|
+
sourceFiles: f.sourceFiles ?? []
|
|
8065
|
+
}));
|
|
8066
|
+
const baseComponents = projection.components ?? [];
|
|
8067
|
+
success(`Semantic projection: ${features.length} features`);
|
|
8068
|
+
const v4Payload = {
|
|
8069
|
+
schemaVersion: 4,
|
|
8070
|
+
structuralGraph: graphResult.structuralGraph,
|
|
8071
|
+
stateSpace: graphResult.stateSpace,
|
|
8072
|
+
transitionSystem: graphResult.transitionSystem,
|
|
8073
|
+
semanticProjection: {
|
|
8074
|
+
labels: [],
|
|
8075
|
+
features
|
|
8076
|
+
},
|
|
8077
|
+
components: baseComponents,
|
|
8078
|
+
git: ast.git ? {
|
|
8079
|
+
commitSha: ast.git.commitSha,
|
|
8080
|
+
shortSha: ast.git.shortSha,
|
|
8081
|
+
branch: ast.git.branch,
|
|
8082
|
+
tags: ast.git.tags,
|
|
8083
|
+
isDirty: ast.git.isDirty
|
|
8084
|
+
} : undefined,
|
|
8085
|
+
metadata: ast.git ? { git: ast.git } : undefined,
|
|
8086
|
+
compat: projection.sections ? {
|
|
8087
|
+
sections: projection.sections,
|
|
8088
|
+
features: projection.features,
|
|
8089
|
+
workflows: [],
|
|
8090
|
+
scenarios: []
|
|
8091
|
+
} : undefined
|
|
8092
|
+
};
|
|
8093
|
+
writeFile(outputPath, JSON.stringify(v4Payload, null, 2));
|
|
8094
|
+
success(`V4 QA map written to ${outputPath}`);
|
|
8095
|
+
}
|
|
7989
8096
|
|
|
7990
8097
|
// src/commands/auth.ts
|
|
7991
8098
|
function registerAuth(program2) {
|
|
@@ -8301,8 +8408,15 @@ import { join as join7 } from "node:path";
|
|
|
8301
8408
|
|
|
8302
8409
|
// src/scanner/push.ts
|
|
8303
8410
|
async function pushToApi(payload, apiUrl, apiKey) {
|
|
8304
|
-
const
|
|
8305
|
-
|
|
8411
|
+
const schemaVersion = payload.schemaVersion;
|
|
8412
|
+
let url;
|
|
8413
|
+
if (schemaVersion === 4) {
|
|
8414
|
+
url = apiUrl.replace(/\/v[23]\/push/, "/v4/push");
|
|
8415
|
+
} else if (schemaVersion === 3) {
|
|
8416
|
+
url = apiUrl.replace("/v2/push", "/v3/push");
|
|
8417
|
+
} else {
|
|
8418
|
+
url = apiUrl.replace("/v3/push", "/v2/push");
|
|
8419
|
+
}
|
|
8306
8420
|
const response = await fetch(url, {
|
|
8307
8421
|
method: "POST",
|
|
8308
8422
|
headers: {
|
|
@@ -8336,7 +8450,9 @@ function registerPush(program2) {
|
|
|
8336
8450
|
process.exit(1);
|
|
8337
8451
|
}
|
|
8338
8452
|
const raw = JSON.parse(readFile(inputPath));
|
|
8339
|
-
const
|
|
8453
|
+
const schemaVersion = raw.schemaVersion ?? 2;
|
|
8454
|
+
const isV4 = schemaVersion === 4;
|
|
8455
|
+
const isV3 = schemaVersion === 3;
|
|
8340
8456
|
const payload = raw;
|
|
8341
8457
|
if (opts?.commitSha) {
|
|
8342
8458
|
payload.commitSha = opts.commitSha;
|
|
@@ -8354,9 +8470,18 @@ function registerPush(program2) {
|
|
|
8354
8470
|
payload.commitSha = gitMeta.commitSha;
|
|
8355
8471
|
}
|
|
8356
8472
|
}
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8473
|
+
if (isV4) {
|
|
8474
|
+
const v4 = raw;
|
|
8475
|
+
const nodeCount = v4.structuralGraph?.nodes?.length ?? 0;
|
|
8476
|
+
const edgeCount = v4.structuralGraph?.edges?.length ?? 0;
|
|
8477
|
+
const stateVarCount = v4.stateSpace?.variables?.length ?? 0;
|
|
8478
|
+
const featureCount = v4.semanticProjection?.features?.length ?? 0;
|
|
8479
|
+
info(`Pushing (V4): ${nodeCount} nodes, ${edgeCount} edges, ${stateVarCount} state vars, ${featureCount} features`);
|
|
8480
|
+
} else {
|
|
8481
|
+
const sectionCount = isV3 ? payload.sections.length : 0;
|
|
8482
|
+
info(`Pushing (V${isV3 ? 3 : 2}): ${sectionCount ? sectionCount + " sections, " : ""}${payload.features.length} features, ${payload.workflows.length} workflows, ${payload.scenarios.length} scenarios`);
|
|
8483
|
+
}
|
|
8484
|
+
const pushEndpoint = isV4 ? "/api/cli/push-v4" : isV3 ? "/api/cli/push-v3" : "/api/cli/push";
|
|
8360
8485
|
try {
|
|
8361
8486
|
const { AuthClient } = await import("./index-fbvbnntc.js");
|
|
8362
8487
|
const client = AuthClient.fromSession();
|
|
@@ -8367,7 +8492,11 @@ function registerPush(program2) {
|
|
|
8367
8492
|
spinner2.stop();
|
|
8368
8493
|
success("Push successful!");
|
|
8369
8494
|
info(` Version: ${result2.version} (schema v${result2.schemaVersion})`);
|
|
8370
|
-
|
|
8495
|
+
if (result2.schemaVersion === 4) {
|
|
8496
|
+
info(` Nodes: ${result2.stats.structuralNodes ?? 0}, Transitions: ${result2.stats.transitions ?? 0}, Features: ${result2.stats.features ?? 0}`);
|
|
8497
|
+
} else {
|
|
8498
|
+
info(` Auto-linked scenarios: ${result2.stats.autoLinkedScenarios}`);
|
|
8499
|
+
}
|
|
8371
8500
|
return;
|
|
8372
8501
|
}
|
|
8373
8502
|
} catch {}
|
|
@@ -8387,7 +8516,11 @@ function registerPush(program2) {
|
|
|
8387
8516
|
spinner.stop();
|
|
8388
8517
|
success("Push successful!");
|
|
8389
8518
|
info(` Version: ${result.version} (schema v${result.schemaVersion})`);
|
|
8390
|
-
|
|
8519
|
+
if (result.schemaVersion === 4) {
|
|
8520
|
+
info(` Nodes: ${result.stats.structuralNodes ?? 0}, Transitions: ${result.stats.transitions ?? 0}, Features: ${result.stats.features ?? 0}`);
|
|
8521
|
+
} else {
|
|
8522
|
+
info(` Auto-linked scenarios: ${result.stats.autoLinkedScenarios}`);
|
|
8523
|
+
}
|
|
8391
8524
|
});
|
|
8392
8525
|
}
|
|
8393
8526
|
|
|
@@ -12509,11 +12642,20 @@ function registerScan(program2) {
|
|
|
12509
12642
|
info(` Diff: first scan`);
|
|
12510
12643
|
}
|
|
12511
12644
|
writeFile(outputPath, JSON.stringify(ast, null, 2));
|
|
12645
|
+
const filePaths = ast.files.map((f) => join8(scanConfig.scanDir, f.path));
|
|
12646
|
+
const stateVars = extractStateVariables(filePaths, ast.routes, scanConfig.scanDir);
|
|
12647
|
+
if (stateVars.length > 0) {
|
|
12648
|
+
const stateVarsPath = outputPath.replace("ast-scan.json", "state-variables.json");
|
|
12649
|
+
writeFile(stateVarsPath, JSON.stringify(stateVars, null, 2));
|
|
12650
|
+
}
|
|
12512
12651
|
success(`AST written to ${outputPath}`);
|
|
12513
12652
|
info(` Files: ${ast.stats.totalFiles} (${ast.stats.totalLines} lines)`);
|
|
12514
12653
|
info(` Routes: ${ast.routes.length}`);
|
|
12515
12654
|
info(` Components: ${ast.components.length}`);
|
|
12516
12655
|
info(` Hooks: ${ast.hooks.length}`);
|
|
12656
|
+
if (stateVars.length > 0) {
|
|
12657
|
+
info(` State variables: ${stateVars.length} (${stateVars.filter((v) => v.extractionConfidence === "high").length} high confidence)`);
|
|
12658
|
+
}
|
|
12517
12659
|
if (ast.git) {
|
|
12518
12660
|
info(` Git: ${ast.git.branch}@${ast.git.shortSha}${ast.git.isDirty ? " (dirty)" : ""}`);
|
|
12519
12661
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
buildV4Graph,
|
|
4
|
+
extractStateVariables,
|
|
3
5
|
runStage1,
|
|
4
6
|
validateQAMap,
|
|
5
7
|
validateQAMapV3
|
|
6
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-ychk5szm.js";
|
|
7
9
|
import {
|
|
8
10
|
$ZodObject,
|
|
9
11
|
$ZodType,
|
|
@@ -14990,11 +14992,61 @@ async function scanCodebase(root) {
|
|
|
14990
14992
|
|
|
14991
14993
|
// src/mcp.ts
|
|
14992
14994
|
import { execSync } from "node:child_process";
|
|
14995
|
+
function validateQAMapV4(payload) {
|
|
14996
|
+
const errors = [];
|
|
14997
|
+
const warnings = [];
|
|
14998
|
+
if (!payload || typeof payload !== "object") {
|
|
14999
|
+
return { valid: false, errors: ["Payload must be a non-null object"], warnings };
|
|
15000
|
+
}
|
|
15001
|
+
const p = payload;
|
|
15002
|
+
if (p.schemaVersion !== 4) {
|
|
15003
|
+
errors.push("schemaVersion must be 4");
|
|
15004
|
+
}
|
|
15005
|
+
const sg = p.structuralGraph;
|
|
15006
|
+
if (!sg || typeof sg !== "object") {
|
|
15007
|
+
errors.push("structuralGraph is required");
|
|
15008
|
+
} else {
|
|
15009
|
+
if (!Array.isArray(sg.nodes))
|
|
15010
|
+
errors.push("structuralGraph.nodes must be an array");
|
|
15011
|
+
if (!Array.isArray(sg.edges))
|
|
15012
|
+
errors.push("structuralGraph.edges must be an array");
|
|
15013
|
+
}
|
|
15014
|
+
const ss = p.stateSpace;
|
|
15015
|
+
if (!ss || typeof ss !== "object") {
|
|
15016
|
+
errors.push("stateSpace is required");
|
|
15017
|
+
} else {
|
|
15018
|
+
if (!Array.isArray(ss.variables))
|
|
15019
|
+
errors.push("stateSpace.variables must be an array");
|
|
15020
|
+
if (!Array.isArray(ss.constraints))
|
|
15021
|
+
errors.push("stateSpace.constraints must be an array");
|
|
15022
|
+
if (!Array.isArray(ss.configurations))
|
|
15023
|
+
errors.push("stateSpace.configurations must be an array");
|
|
15024
|
+
}
|
|
15025
|
+
const ts = p.transitionSystem;
|
|
15026
|
+
if (!ts || typeof ts !== "object") {
|
|
15027
|
+
errors.push("transitionSystem is required");
|
|
15028
|
+
} else {
|
|
15029
|
+
if (!Array.isArray(ts.nodes))
|
|
15030
|
+
errors.push("transitionSystem.nodes must be an array");
|
|
15031
|
+
if (!Array.isArray(ts.transitions))
|
|
15032
|
+
errors.push("transitionSystem.transitions must be an array");
|
|
15033
|
+
}
|
|
15034
|
+
const sp = p.semanticProjection;
|
|
15035
|
+
if (sp && typeof sp === "object") {
|
|
15036
|
+
if (!Array.isArray(sp.features))
|
|
15037
|
+
warnings.push("semanticProjection.features should be an array");
|
|
15038
|
+
if (!Array.isArray(sp.labels))
|
|
15039
|
+
warnings.push("semanticProjection.labels should be an array");
|
|
15040
|
+
} else {
|
|
15041
|
+
warnings.push("semanticProjection is missing — AI labeling not yet applied");
|
|
15042
|
+
}
|
|
15043
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
15044
|
+
}
|
|
14993
15045
|
var SERVER_INSTRUCTIONS = `
|
|
14994
15046
|
# Zefiro — Scanner & QA Map Guide
|
|
14995
15047
|
|
|
14996
15048
|
You have access to Zefiro, an AI-powered codebase scanner and QA map generator.
|
|
14997
|
-
Supports
|
|
15049
|
+
Supports three schema versions: V2 (flat/linear), V3 (hierarchical/DAG), and V4 (formal model). **Default to V3 for AI analysis, V4 for formal graph-based analysis.**
|
|
14998
15050
|
|
|
14999
15051
|
## Core Workflow
|
|
15000
15052
|
|
|
@@ -15010,7 +15062,9 @@ Supports two schema versions: V2 (flat/linear) and V3 (hierarchical/DAG). **Defa
|
|
|
15010
15062
|
- \`zefiro_scan_codebase\` — scan test files, configs, fixtures, path aliases
|
|
15011
15063
|
- \`zefiro_scan_ast\` — deep AST scan: routes, components, hooks, imports, navigation patterns
|
|
15012
15064
|
- \`zefiro_scan_ast_detail\` — drill into a specific AST category (routes, components, hooks, dependencies, files, navigationPatterns)
|
|
15013
|
-
- \`
|
|
15065
|
+
- \`zefiro_extract_state_variables\` — V4: extract state variables (auth, roles, flags, permissions) from scanned AST
|
|
15066
|
+
- \`zefiro_build_v4_graph\` — V4: build formal model (structural graph + state space + transition system) from AST + state variables
|
|
15067
|
+
- \`zefiro_build_qa_map\` — validate + write QA map payload (V2, V3, or V4 auto-detected)
|
|
15014
15068
|
- \`zefiro_read_qa_map\` — read existing QA map
|
|
15015
15069
|
|
|
15016
15070
|
## Scanner V3 Enhancements
|
|
@@ -15208,7 +15262,7 @@ V2 payloads do NOT include a schemaVersion field. Flat features, linear workflow
|
|
|
15208
15262
|
### V2 ConditionalBranch: \`{ condition, outcome, type: "validation"|"permission"|"error"|"business-logic" }\`
|
|
15209
15263
|
### V2 Scenario: \`{ id, workflowId, featureId, name, description, category, preconditions: string[], steps: ScenarioStep[], expectedOutcome, componentIds: string[], workflowStepIds: string[], priority }\`
|
|
15210
15264
|
`.trim();
|
|
15211
|
-
var server = new McpServer({ name: "zefiro", version: "0.
|
|
15265
|
+
var server = new McpServer({ name: "zefiro", version: "0.6.1" }, { instructions: SERVER_INSTRUCTIONS });
|
|
15212
15266
|
server.registerTool("zefiro_scan_codebase", {
|
|
15213
15267
|
title: "Scan Codebase",
|
|
15214
15268
|
description: "Scan a project directory for test files, configs, fixtures, path aliases, and sample test content.",
|
|
@@ -15272,16 +15326,17 @@ server.registerTool("zefiro_scan_ast_detail", {
|
|
|
15272
15326
|
});
|
|
15273
15327
|
server.registerTool("zefiro_build_qa_map", {
|
|
15274
15328
|
title: "Build QA Map",
|
|
15275
|
-
description: "Validate and optionally write a QA map payload (V2 or
|
|
15329
|
+
description: "Validate and optionally write a QA map payload (V2, V3, or V4, auto-detected by schemaVersion field). Use dryRun: true to validate without writing.",
|
|
15276
15330
|
inputSchema: exports_external.object({
|
|
15277
|
-
payload: exports_external.any().describe("QAMapV2Payload or
|
|
15331
|
+
payload: exports_external.any().describe("QAMapV2Payload, QAMapV3Payload, or QAMapV4Payload JSON object. V3 must include schemaVersion: 3, V4 must include schemaVersion: 4."),
|
|
15278
15332
|
output: exports_external.string().optional().describe(`Output file path (defaults to ${CONFIG_DIR}/qa-map.json)`),
|
|
15279
15333
|
dryRun: exports_external.boolean().optional().describe("Validate only, do not write (default: false)")
|
|
15280
15334
|
})
|
|
15281
15335
|
}, async ({ payload, output, dryRun }) => {
|
|
15282
15336
|
try {
|
|
15337
|
+
const isV4 = payload && typeof payload === "object" && payload.schemaVersion === 4;
|
|
15283
15338
|
const isV3 = payload && typeof payload === "object" && payload.schemaVersion === 3;
|
|
15284
|
-
const validation = isV3 ? validateQAMapV3(payload) : validateQAMap(payload);
|
|
15339
|
+
const validation = isV4 ? validateQAMapV4(payload) : isV3 ? validateQAMapV3(payload) : validateQAMap(payload);
|
|
15285
15340
|
if (validation.valid && payload && typeof payload === "object") {
|
|
15286
15341
|
try {
|
|
15287
15342
|
const sha = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
@@ -15298,7 +15353,15 @@ server.registerTool("zefiro_build_qa_map", {
|
|
|
15298
15353
|
written = true;
|
|
15299
15354
|
}
|
|
15300
15355
|
const p = payload;
|
|
15301
|
-
const stats = validation.valid ? {
|
|
15356
|
+
const stats = validation.valid ? isV4 ? {
|
|
15357
|
+
structuralNodes: Array.isArray(p?.structuralGraph?.nodes) ? p.structuralGraph.nodes.length : 0,
|
|
15358
|
+
structuralEdges: Array.isArray(p?.structuralGraph?.edges) ? p.structuralGraph.edges.length : 0,
|
|
15359
|
+
stateVariables: Array.isArray(p?.stateSpace?.variables) ? p.stateSpace.variables.length : 0,
|
|
15360
|
+
stateConfigurations: Array.isArray(p?.stateSpace?.configurations) ? p.stateSpace.configurations.length : 0,
|
|
15361
|
+
transitionNodes: Array.isArray(p?.transitionSystem?.nodes) ? p.transitionSystem.nodes.length : 0,
|
|
15362
|
+
transitions: Array.isArray(p?.transitionSystem?.transitions) ? p.transitionSystem.transitions.length : 0,
|
|
15363
|
+
features: Array.isArray(p?.semanticProjection?.features) ? p.semanticProjection.features.length : 0
|
|
15364
|
+
} : {
|
|
15302
15365
|
sections: isV3 && Array.isArray(p?.sections) ? p.sections.length : undefined,
|
|
15303
15366
|
features: Array.isArray(p?.features) ? p.features.length : 0,
|
|
15304
15367
|
workflows: Array.isArray(p?.workflows) ? p.workflows.length : 0,
|
|
@@ -15308,13 +15371,96 @@ server.registerTool("zefiro_build_qa_map", {
|
|
|
15308
15371
|
return {
|
|
15309
15372
|
content: [{
|
|
15310
15373
|
type: "text",
|
|
15311
|
-
text: JSON.stringify({ valid: validation.valid, schemaVersion: isV3 ? 3 : 2, errors: validation.errors, warnings: validation.warnings, written, outputPath: written ? outputPath : null, stats }, null, 2)
|
|
15374
|
+
text: JSON.stringify({ valid: validation.valid, schemaVersion: isV4 ? 4 : isV3 ? 3 : 2, errors: validation.errors, warnings: validation.warnings, written, outputPath: written ? outputPath : null, stats }, null, 2)
|
|
15312
15375
|
}]
|
|
15313
15376
|
};
|
|
15314
15377
|
} catch (err) {
|
|
15315
15378
|
return { content: [{ type: "text", text: `Error building QA map: ${err.message}` }], isError: true };
|
|
15316
15379
|
}
|
|
15317
15380
|
});
|
|
15381
|
+
server.registerTool("zefiro_extract_state_variables", {
|
|
15382
|
+
title: "Extract State Variables (V4)",
|
|
15383
|
+
description: "Extract state variables from the scanned codebase. Requires a prior zefiro_scan_ast call. " + "Returns state variables (auth guards, roles, feature flags, permissions, UI state, URL params) with confidence levels.",
|
|
15384
|
+
inputSchema: exports_external.object({})
|
|
15385
|
+
}, async () => {
|
|
15386
|
+
try {
|
|
15387
|
+
const cwd = process.cwd();
|
|
15388
|
+
const astPath = join2(cwd, CONFIG_DIR, "ast-scan.json");
|
|
15389
|
+
if (!existsSync2(astPath)) {
|
|
15390
|
+
return { content: [{ type: "text", text: "Error: No AST scan found. Run zefiro_scan_ast first." }], isError: true };
|
|
15391
|
+
}
|
|
15392
|
+
const ast = JSON.parse(readFileSync2(astPath, "utf-8"));
|
|
15393
|
+
const scanDir = ast.files.length > 0 ? join2(cwd, ast.scanDir ?? "src") : join2(cwd, "src");
|
|
15394
|
+
const filePaths = ast.files.map((f) => join2(scanDir, f.path));
|
|
15395
|
+
const stateVars = extractStateVariables(filePaths, ast.routes, scanDir);
|
|
15396
|
+
const stateVarsPath = join2(cwd, CONFIG_DIR, "state-variables.json");
|
|
15397
|
+
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
15398
|
+
mkdirSync(join2(cwd, CONFIG_DIR), { recursive: true });
|
|
15399
|
+
writeFileSync(stateVarsPath, JSON.stringify(stateVars, null, 2));
|
|
15400
|
+
const summary = {
|
|
15401
|
+
total: stateVars.length,
|
|
15402
|
+
byConfidence: {
|
|
15403
|
+
high: stateVars.filter((v) => v.extractionConfidence === "high").length,
|
|
15404
|
+
medium: stateVars.filter((v) => v.extractionConfidence === "medium").length,
|
|
15405
|
+
low: stateVars.filter((v) => v.extractionConfidence === "low").length
|
|
15406
|
+
},
|
|
15407
|
+
byCategory: stateVars.reduce((acc, v) => {
|
|
15408
|
+
const cat = v.id.split(":")[1]?.split("-")[0] ?? "unknown";
|
|
15409
|
+
acc[cat] = (acc[cat] ?? 0) + 1;
|
|
15410
|
+
return acc;
|
|
15411
|
+
}, {}),
|
|
15412
|
+
variables: stateVars,
|
|
15413
|
+
savedTo: stateVarsPath
|
|
15414
|
+
};
|
|
15415
|
+
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
15416
|
+
} catch (err) {
|
|
15417
|
+
return { content: [{ type: "text", text: `Error extracting state variables: ${err.message}` }], isError: true };
|
|
15418
|
+
}
|
|
15419
|
+
});
|
|
15420
|
+
server.registerTool("zefiro_build_v4_graph", {
|
|
15421
|
+
title: "Build V4 Graph",
|
|
15422
|
+
description: "Build the formal V4 model (structural graph + state space + transition system) from the AST scan and state variables. " + "Requires prior zefiro_scan_ast and zefiro_extract_state_variables calls. Returns graph statistics and saves the result.",
|
|
15423
|
+
inputSchema: exports_external.object({})
|
|
15424
|
+
}, async () => {
|
|
15425
|
+
try {
|
|
15426
|
+
const cwd = process.cwd();
|
|
15427
|
+
const astPath = join2(cwd, CONFIG_DIR, "ast-scan.json");
|
|
15428
|
+
const stateVarsPath = join2(cwd, CONFIG_DIR, "state-variables.json");
|
|
15429
|
+
if (!existsSync2(astPath)) {
|
|
15430
|
+
return { content: [{ type: "text", text: "Error: No AST scan found. Run zefiro_scan_ast first." }], isError: true };
|
|
15431
|
+
}
|
|
15432
|
+
if (!existsSync2(stateVarsPath)) {
|
|
15433
|
+
return { content: [{ type: "text", text: "Error: No state variables found. Run zefiro_extract_state_variables first." }], isError: true };
|
|
15434
|
+
}
|
|
15435
|
+
const ast = JSON.parse(readFileSync2(astPath, "utf-8"));
|
|
15436
|
+
const stateVars = JSON.parse(readFileSync2(stateVarsPath, "utf-8"));
|
|
15437
|
+
const result = buildV4Graph(ast, stateVars);
|
|
15438
|
+
const graphPath = join2(cwd, CONFIG_DIR, "v4-graph.json");
|
|
15439
|
+
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
15440
|
+
mkdirSync(join2(cwd, CONFIG_DIR), { recursive: true });
|
|
15441
|
+
writeFileSync(graphPath, JSON.stringify(result, null, 2));
|
|
15442
|
+
const summary = {
|
|
15443
|
+
structuralGraph: {
|
|
15444
|
+
nodes: result.structuralGraph.nodes.length,
|
|
15445
|
+
edges: result.structuralGraph.edges.length
|
|
15446
|
+
},
|
|
15447
|
+
stateSpace: {
|
|
15448
|
+
variables: result.stateSpace.variables.length,
|
|
15449
|
+
constraints: result.stateSpace.constraints.length,
|
|
15450
|
+
configurations: result.stateSpace.configurations.length
|
|
15451
|
+
},
|
|
15452
|
+
transitionSystem: {
|
|
15453
|
+
nodes: result.transitionSystem.nodes.length,
|
|
15454
|
+
transitions: result.transitionSystem.transitions.length,
|
|
15455
|
+
entryPoints: result.transitionSystem.nodes.filter((n) => n.isEntryPoint).length
|
|
15456
|
+
},
|
|
15457
|
+
savedTo: graphPath
|
|
15458
|
+
};
|
|
15459
|
+
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
15460
|
+
} catch (err) {
|
|
15461
|
+
return { content: [{ type: "text", text: `Error building V4 graph: ${err.message}` }], isError: true };
|
|
15462
|
+
}
|
|
15463
|
+
});
|
|
15318
15464
|
server.registerTool("zefiro_read_qa_map", {
|
|
15319
15465
|
title: "Read QA Map",
|
|
15320
15466
|
description: "Read an existing QA map file.",
|