zefiro 0.3.7 → 0.4.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/agents/1.feature-analyzer-agent-v3.md +138 -0
- package/agents/2.scenario-planner-agent-v3.md +106 -0
- package/agents/workflow-agent-v3.md +184 -0
- package/dist/cli-fc97msmj.js +191627 -0
- package/dist/cli-pe555th6.js +191211 -0
- package/dist/cli.js +252 -6
- package/dist/mcp.js +163 -286
- package/package.json +5 -2
package/dist/mcp.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
runStage1
|
|
4
|
-
|
|
3
|
+
runStage1,
|
|
4
|
+
validateQAMap,
|
|
5
|
+
validateQAMapV3
|
|
6
|
+
} from "./cli-fc97msmj.js";
|
|
5
7
|
import {
|
|
6
8
|
$ZodObject,
|
|
7
9
|
$ZodType,
|
|
@@ -14871,12 +14873,15 @@ function summarizeAST(ast, savedPath) {
|
|
|
14871
14873
|
routes: ast.routes.map((r) => ({
|
|
14872
14874
|
path: r.path,
|
|
14873
14875
|
filePath: r.filePath,
|
|
14874
|
-
isDynamic: r.isDynamic
|
|
14876
|
+
isDynamic: r.isDynamic,
|
|
14877
|
+
layoutChain: r.layoutChain,
|
|
14878
|
+
routeGroup: r.routeGroup
|
|
14875
14879
|
})),
|
|
14876
14880
|
fileTree: ast.files.map((f) => f.path),
|
|
14877
14881
|
componentNames: ast.components.map((c) => c.name),
|
|
14878
14882
|
hookNames: ast.hooks.filter((h) => h.isCustom).map((h) => h.name),
|
|
14879
14883
|
directoryGroups: dirGroups,
|
|
14884
|
+
navigationPatternCount: ast.navigationPatterns?.length ?? 0,
|
|
14880
14885
|
astScanPath: savedPath
|
|
14881
14886
|
};
|
|
14882
14887
|
}
|
|
@@ -14904,6 +14909,9 @@ function filterASTByCategory(ast, category, filter, limit) {
|
|
|
14904
14909
|
case "files":
|
|
14905
14910
|
items = filter ? ast.files.filter((f) => matchesFilter(f.path)) : ast.files;
|
|
14906
14911
|
break;
|
|
14912
|
+
case "navigationPatterns":
|
|
14913
|
+
items = ast.navigationPatterns ? filter ? ast.navigationPatterns.filter((n) => matchesFilter(n.sourceFile) || matchesFilter(n.targetRoute ?? "")) : ast.navigationPatterns : [];
|
|
14914
|
+
break;
|
|
14907
14915
|
}
|
|
14908
14916
|
if (limit && limit > 0) {
|
|
14909
14917
|
items = items.slice(0, limit);
|
|
@@ -14916,221 +14924,6 @@ function filterASTByCategory(ast, category, filter, limit) {
|
|
|
14916
14924
|
};
|
|
14917
14925
|
}
|
|
14918
14926
|
|
|
14919
|
-
// src/scanner/validate.ts
|
|
14920
|
-
var WORKFLOW_TYPES = ["navigation", "crud", "multi-step", "configuration", "search-filter"];
|
|
14921
|
-
var COMPONENT_TYPES = ["form", "display", "navigation", "modal", "layout", "feedback"];
|
|
14922
|
-
var SCENARIO_CATEGORIES = ["happy-path", "permission", "validation", "error", "edge-case", "precondition"];
|
|
14923
|
-
var SCENARIO_PRIORITIES = ["critical", "high", "medium", "low"];
|
|
14924
|
-
var BRANCH_TYPES = ["validation", "permission", "error", "business-logic"];
|
|
14925
|
-
function validateQAMap(payload) {
|
|
14926
|
-
const errors = [];
|
|
14927
|
-
const warnings = [];
|
|
14928
|
-
if (!payload || typeof payload !== "object") {
|
|
14929
|
-
return { valid: false, errors: ["Payload must be a non-null object"], warnings };
|
|
14930
|
-
}
|
|
14931
|
-
const p = payload;
|
|
14932
|
-
const features = assertArray(p, "features", errors);
|
|
14933
|
-
const workflows = assertArray(p, "workflows", errors);
|
|
14934
|
-
const components = assertArray(p, "components", errors);
|
|
14935
|
-
const scenarios = assertArray(p, "scenarios", errors);
|
|
14936
|
-
if (errors.length > 0) {
|
|
14937
|
-
return { valid: false, errors, warnings };
|
|
14938
|
-
}
|
|
14939
|
-
const featureIds = new Set;
|
|
14940
|
-
const workflowIds = new Set;
|
|
14941
|
-
const componentIds = new Set;
|
|
14942
|
-
const scenarioIds = new Set;
|
|
14943
|
-
const workflowStepIds = new Set;
|
|
14944
|
-
for (const f of features) {
|
|
14945
|
-
requireString(f, "id", "feature", errors);
|
|
14946
|
-
requireString(f, "name", "feature", errors);
|
|
14947
|
-
requireString(f, "description", "feature", errors);
|
|
14948
|
-
requireArray(f, "routes", "feature", errors);
|
|
14949
|
-
requireArray(f, "workflowIds", "feature", errors);
|
|
14950
|
-
requireArray(f, "sourceFiles", "feature", errors);
|
|
14951
|
-
if (f.id) {
|
|
14952
|
-
if (featureIds.has(f.id))
|
|
14953
|
-
errors.push(`Duplicate feature id: ${f.id}`);
|
|
14954
|
-
featureIds.add(f.id);
|
|
14955
|
-
}
|
|
14956
|
-
}
|
|
14957
|
-
for (const w of workflows) {
|
|
14958
|
-
requireString(w, "id", "workflow", errors);
|
|
14959
|
-
requireString(w, "name", "workflow", errors);
|
|
14960
|
-
requireString(w, "featureId", "workflow", errors);
|
|
14961
|
-
requireEnum(w, "type", WORKFLOW_TYPES, "workflow", errors);
|
|
14962
|
-
requireArray(w, "preconditions", "workflow", errors);
|
|
14963
|
-
requireArray(w, "steps", "workflow", errors);
|
|
14964
|
-
requireArray(w, "componentIds", "workflow", errors);
|
|
14965
|
-
if (w.id) {
|
|
14966
|
-
if (workflowIds.has(w.id))
|
|
14967
|
-
errors.push(`Duplicate workflow id: ${w.id}`);
|
|
14968
|
-
workflowIds.add(w.id);
|
|
14969
|
-
}
|
|
14970
|
-
if (Array.isArray(w.steps)) {
|
|
14971
|
-
for (const s of w.steps) {
|
|
14972
|
-
requireString(s, "id", "workflowStep", errors);
|
|
14973
|
-
requireNumber(s, "order", "workflowStep", errors);
|
|
14974
|
-
requireString(s, "description", "workflowStep", errors);
|
|
14975
|
-
requireArray(s, "componentIds", "workflowStep", errors);
|
|
14976
|
-
requireArray(s, "apiCalls", "workflowStep", errors);
|
|
14977
|
-
requireArray(s, "conditionalBranches", "workflowStep", errors);
|
|
14978
|
-
if (s.id)
|
|
14979
|
-
workflowStepIds.add(s.id);
|
|
14980
|
-
if (Array.isArray(s.conditionalBranches)) {
|
|
14981
|
-
for (const b of s.conditionalBranches) {
|
|
14982
|
-
requireString(b, "condition", "conditionalBranch", errors);
|
|
14983
|
-
requireString(b, "outcome", "conditionalBranch", errors);
|
|
14984
|
-
requireEnum(b, "type", BRANCH_TYPES, "conditionalBranch", errors);
|
|
14985
|
-
}
|
|
14986
|
-
}
|
|
14987
|
-
}
|
|
14988
|
-
}
|
|
14989
|
-
}
|
|
14990
|
-
for (const c of components) {
|
|
14991
|
-
requireString(c, "id", "component", errors);
|
|
14992
|
-
requireString(c, "name", "component", errors);
|
|
14993
|
-
requireEnum(c, "type", COMPONENT_TYPES, "component", errors);
|
|
14994
|
-
requireArray(c, "sourceFiles", "component", errors);
|
|
14995
|
-
requireArray(c, "props", "component", errors);
|
|
14996
|
-
requireArray(c, "referencedByWorkflows", "component", errors);
|
|
14997
|
-
if (c.id) {
|
|
14998
|
-
if (componentIds.has(c.id))
|
|
14999
|
-
errors.push(`Duplicate component id: ${c.id}`);
|
|
15000
|
-
componentIds.add(c.id);
|
|
15001
|
-
}
|
|
15002
|
-
}
|
|
15003
|
-
for (const s of scenarios) {
|
|
15004
|
-
requireString(s, "id", "scenario", errors);
|
|
15005
|
-
requireString(s, "workflowId", "scenario", errors);
|
|
15006
|
-
requireString(s, "featureId", "scenario", errors);
|
|
15007
|
-
requireString(s, "name", "scenario", errors);
|
|
15008
|
-
requireString(s, "description", "scenario", errors);
|
|
15009
|
-
requireEnum(s, "category", SCENARIO_CATEGORIES, "scenario", errors);
|
|
15010
|
-
requireArray(s, "preconditions", "scenario", errors);
|
|
15011
|
-
requireArray(s, "steps", "scenario", errors);
|
|
15012
|
-
requireString(s, "expectedOutcome", "scenario", errors);
|
|
15013
|
-
requireArray(s, "componentIds", "scenario", errors);
|
|
15014
|
-
requireArray(s, "workflowStepIds", "scenario", errors);
|
|
15015
|
-
requireEnum(s, "priority", SCENARIO_PRIORITIES, "scenario", errors);
|
|
15016
|
-
if (s.id) {
|
|
15017
|
-
if (scenarioIds.has(s.id))
|
|
15018
|
-
errors.push(`Duplicate scenario id: ${s.id}`);
|
|
15019
|
-
scenarioIds.add(s.id);
|
|
15020
|
-
}
|
|
15021
|
-
if (Array.isArray(s.steps)) {
|
|
15022
|
-
for (const step of s.steps) {
|
|
15023
|
-
requireNumber(step, "order", "scenarioStep", errors);
|
|
15024
|
-
requireString(step, "action", "scenarioStep", errors);
|
|
15025
|
-
requireString(step, "expectedResult", "scenarioStep", errors);
|
|
15026
|
-
}
|
|
15027
|
-
}
|
|
15028
|
-
}
|
|
15029
|
-
for (const w of workflows) {
|
|
15030
|
-
if (w.featureId && !featureIds.has(w.featureId)) {
|
|
15031
|
-
errors.push(`Workflow "${w.id}" references unknown feature: ${w.featureId}`);
|
|
15032
|
-
}
|
|
15033
|
-
if (Array.isArray(w.componentIds)) {
|
|
15034
|
-
for (const cid of w.componentIds) {
|
|
15035
|
-
if (!componentIds.has(cid)) {
|
|
15036
|
-
errors.push(`Workflow "${w.id}" references unknown component: ${cid}`);
|
|
15037
|
-
}
|
|
15038
|
-
}
|
|
15039
|
-
}
|
|
15040
|
-
}
|
|
15041
|
-
for (const f of features) {
|
|
15042
|
-
if (Array.isArray(f.workflowIds)) {
|
|
15043
|
-
for (const wid of f.workflowIds) {
|
|
15044
|
-
if (!workflowIds.has(wid)) {
|
|
15045
|
-
errors.push(`Feature "${f.id}" references unknown workflow: ${wid}`);
|
|
15046
|
-
}
|
|
15047
|
-
}
|
|
15048
|
-
}
|
|
15049
|
-
}
|
|
15050
|
-
for (const s of scenarios) {
|
|
15051
|
-
if (s.workflowId && !workflowIds.has(s.workflowId)) {
|
|
15052
|
-
errors.push(`Scenario "${s.id}" references unknown workflow: ${s.workflowId}`);
|
|
15053
|
-
}
|
|
15054
|
-
if (s.featureId && !featureIds.has(s.featureId)) {
|
|
15055
|
-
errors.push(`Scenario "${s.id}" references unknown feature: ${s.featureId}`);
|
|
15056
|
-
}
|
|
15057
|
-
if (Array.isArray(s.componentIds)) {
|
|
15058
|
-
for (const cid of s.componentIds) {
|
|
15059
|
-
if (!componentIds.has(cid)) {
|
|
15060
|
-
errors.push(`Scenario "${s.id}" references unknown component: ${cid}`);
|
|
15061
|
-
}
|
|
15062
|
-
}
|
|
15063
|
-
}
|
|
15064
|
-
if (Array.isArray(s.workflowStepIds)) {
|
|
15065
|
-
for (const wsid of s.workflowStepIds) {
|
|
15066
|
-
if (!workflowStepIds.has(wsid)) {
|
|
15067
|
-
errors.push(`Scenario "${s.id}" references unknown workflow step: ${wsid}`);
|
|
15068
|
-
}
|
|
15069
|
-
}
|
|
15070
|
-
}
|
|
15071
|
-
}
|
|
15072
|
-
for (const c of components) {
|
|
15073
|
-
if (Array.isArray(c.referencedByWorkflows)) {
|
|
15074
|
-
for (const wid of c.referencedByWorkflows) {
|
|
15075
|
-
if (!workflowIds.has(wid)) {
|
|
15076
|
-
errors.push(`Component "${c.id}" references unknown workflow: ${wid}`);
|
|
15077
|
-
}
|
|
15078
|
-
}
|
|
15079
|
-
}
|
|
15080
|
-
}
|
|
15081
|
-
for (const f of features) {
|
|
15082
|
-
if (Array.isArray(f.workflowIds) && f.workflowIds.length === 0) {
|
|
15083
|
-
warnings.push(`Feature "${f.id}" has no workflows`);
|
|
15084
|
-
}
|
|
15085
|
-
}
|
|
15086
|
-
for (const w of workflows) {
|
|
15087
|
-
const hasScenarios = scenarios.some((s) => s.workflowId === w.id);
|
|
15088
|
-
if (!hasScenarios) {
|
|
15089
|
-
warnings.push(`Workflow "${w.id}" has no scenarios`);
|
|
15090
|
-
}
|
|
15091
|
-
}
|
|
15092
|
-
const referencedComponentIds = new Set;
|
|
15093
|
-
for (const w of workflows) {
|
|
15094
|
-
if (Array.isArray(w.componentIds)) {
|
|
15095
|
-
for (const cid of w.componentIds)
|
|
15096
|
-
referencedComponentIds.add(cid);
|
|
15097
|
-
}
|
|
15098
|
-
}
|
|
15099
|
-
for (const c of components) {
|
|
15100
|
-
if (c.id && !referencedComponentIds.has(c.id)) {
|
|
15101
|
-
warnings.push(`Component "${c.id}" is not referenced by any workflow`);
|
|
15102
|
-
}
|
|
15103
|
-
}
|
|
15104
|
-
return { valid: errors.length === 0, errors, warnings };
|
|
15105
|
-
}
|
|
15106
|
-
function assertArray(obj, field, errors) {
|
|
15107
|
-
if (!Array.isArray(obj[field])) {
|
|
15108
|
-
errors.push(`Missing or invalid top-level array: ${field}`);
|
|
15109
|
-
return [];
|
|
15110
|
-
}
|
|
15111
|
-
return obj[field];
|
|
15112
|
-
}
|
|
15113
|
-
function requireString(obj, field, context, errors) {
|
|
15114
|
-
if (typeof obj?.[field] !== "string" || obj[field].length === 0) {
|
|
15115
|
-
errors.push(`${context} missing required string field: ${field} (id: ${obj?.id ?? "unknown"})`);
|
|
15116
|
-
}
|
|
15117
|
-
}
|
|
15118
|
-
function requireNumber(obj, field, context, errors) {
|
|
15119
|
-
if (typeof obj?.[field] !== "number") {
|
|
15120
|
-
errors.push(`${context} missing required number field: ${field} (id: ${obj?.id ?? "unknown"})`);
|
|
15121
|
-
}
|
|
15122
|
-
}
|
|
15123
|
-
function requireArray(obj, field, context, errors) {
|
|
15124
|
-
if (!Array.isArray(obj?.[field])) {
|
|
15125
|
-
errors.push(`${context} missing required array field: ${field} (id: ${obj?.id ?? "unknown"})`);
|
|
15126
|
-
}
|
|
15127
|
-
}
|
|
15128
|
-
function requireEnum(obj, field, allowed, context, errors) {
|
|
15129
|
-
if (typeof obj?.[field] !== "string" || !allowed.includes(obj[field])) {
|
|
15130
|
-
errors.push(`${context} invalid ${field}: "${obj?.[field]}" — expected one of: ${allowed.join(", ")} (id: ${obj?.id ?? "unknown"})`);
|
|
15131
|
-
}
|
|
15132
|
-
}
|
|
15133
|
-
|
|
15134
14927
|
// src/utils/scan.ts
|
|
15135
14928
|
import { readdirSync, existsSync, readFileSync } from "node:fs";
|
|
15136
14929
|
import { join, relative } from "node:path";
|
|
@@ -15201,11 +14994,12 @@ var SERVER_INSTRUCTIONS = `
|
|
|
15201
14994
|
# Zefiro — Scanner & QA Map Guide
|
|
15202
14995
|
|
|
15203
14996
|
You have access to Zefiro, an AI-powered codebase scanner and QA map generator.
|
|
14997
|
+
Supports two schema versions: V2 (flat/linear) and V3 (hierarchical/DAG). **Default to V3.**
|
|
15204
14998
|
|
|
15205
14999
|
## Core Workflow
|
|
15206
15000
|
|
|
15207
15001
|
1. **Scan.** Call \`zefiro_scan_ast()\` to run the AST scanner and get a compact summary.
|
|
15208
|
-
2. **Explore.** Use \`zefiro_scan_ast_detail()\` to drill into routes, components, hooks, or
|
|
15002
|
+
2. **Explore.** Use \`zefiro_scan_ast_detail()\` to drill into routes, components, hooks, files, or navigationPatterns.
|
|
15209
15003
|
3. **Propose & discuss.** Present candidate features to the user, ask clarifying questions.
|
|
15210
15004
|
4. **Build.** Construct the QA map payload and validate with \`zefiro_build_qa_map({ dryRun: true })\`.
|
|
15211
15005
|
5. **Write.** Once validated and approved, call \`zefiro_build_qa_map({ dryRun: false })\` to save.
|
|
@@ -15214,126 +15008,207 @@ You have access to Zefiro, an AI-powered codebase scanner and QA map generator.
|
|
|
15214
15008
|
## Tools
|
|
15215
15009
|
|
|
15216
15010
|
- \`zefiro_scan_codebase\` — scan test files, configs, fixtures, path aliases
|
|
15217
|
-
- \`zefiro_scan_ast\` — deep AST scan: routes, components, hooks, imports
|
|
15218
|
-
- \`zefiro_scan_ast_detail\` — drill into a specific AST category
|
|
15219
|
-
- \`zefiro_build_qa_map\` — validate + write
|
|
15011
|
+
- \`zefiro_scan_ast\` — deep AST scan: routes, components, hooks, imports, navigation patterns
|
|
15012
|
+
- \`zefiro_scan_ast_detail\` — drill into a specific AST category (routes, components, hooks, dependencies, files, navigationPatterns)
|
|
15013
|
+
- \`zefiro_build_qa_map\` — validate + write QA map payload (V2 or V3, auto-detected)
|
|
15220
15014
|
- \`zefiro_read_qa_map\` — read existing QA map
|
|
15221
15015
|
|
|
15016
|
+
## Scanner V3 Enhancements
|
|
15017
|
+
|
|
15018
|
+
The AST scanner extracts additional data useful for V3 map generation:
|
|
15019
|
+
- **Navigation patterns**: \`<Link>\`, \`router.push()\`, \`redirect()\` — used for entry point detection
|
|
15020
|
+
- **Layout chains**: layout files in route hierarchy — used for section grouping
|
|
15021
|
+
- **Route groups**: Next.js route groups like \`(authenticated)\` — section boundaries
|
|
15022
|
+
- **Conditional logic**: if/ternary/switch counts and guard patterns per component — branching hints
|
|
15023
|
+
|
|
15024
|
+
Use \`zefiro_scan_ast_detail({ category: "navigationPatterns" })\` to explore navigation links.
|
|
15025
|
+
Routes returned by \`zefiro_scan_ast_detail({ category: "routes" })\` include \`layoutChain\` and \`routeGroup\`.
|
|
15026
|
+
Components include \`conditionalCount\` and \`guardPatterns\`.
|
|
15027
|
+
|
|
15222
15028
|
---
|
|
15223
15029
|
|
|
15224
|
-
##
|
|
15030
|
+
## QAMapV3Payload Schema Reference (preferred)
|
|
15225
15031
|
|
|
15226
|
-
|
|
15032
|
+
V3 payloads include \`schemaVersion: 3\` and support hierarchical features, DAG workflows, and structured conditions.
|
|
15227
15033
|
|
|
15228
15034
|
### Top-level structure
|
|
15229
15035
|
\`\`\`
|
|
15230
15036
|
{
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
|
|
15234
|
-
|
|
15235
|
-
|
|
15236
|
-
|
|
15037
|
+
schemaVersion: 3
|
|
15038
|
+
sections: Section[] // required, may be []
|
|
15039
|
+
features: FeatureV3[] // required, may be []
|
|
15040
|
+
workflows: WorkflowV3[] // required, may be []
|
|
15041
|
+
components: Component[] // required, may be [] (same as V2)
|
|
15042
|
+
scenarios: ScenarioV3[] // required, may be []
|
|
15043
|
+
commitSha?: string
|
|
15044
|
+
metadata?: object
|
|
15237
15045
|
}
|
|
15238
15046
|
\`\`\`
|
|
15239
15047
|
|
|
15240
|
-
###
|
|
15048
|
+
### ConditionV3 (used in preconditions, postconditions, branchCondition)
|
|
15241
15049
|
\`\`\`
|
|
15242
15050
|
{
|
|
15243
|
-
|
|
15051
|
+
entity: string // "user", "form", "list", "session"
|
|
15052
|
+
state: string // "role", "count", "isValid", "status"
|
|
15053
|
+
operator: "equals" | "notEquals" | "greaterThan" | "lessThan" | "contains" | "exists" | "isEmpty"
|
|
15054
|
+
value: string | number | boolean
|
|
15055
|
+
}
|
|
15056
|
+
\`\`\`
|
|
15057
|
+
|
|
15058
|
+
### Section (3-level hierarchy top level)
|
|
15059
|
+
\`\`\`
|
|
15060
|
+
{
|
|
15061
|
+
id: string // "sec:<slug>"
|
|
15244
15062
|
name: string
|
|
15245
15063
|
description: string
|
|
15246
|
-
|
|
15247
|
-
workflowIds: string[] // IDs of workflows that belong to this feature
|
|
15248
|
-
sourceFiles: string[] // relative file paths, e.g. ["src/app/login/page.tsx"]
|
|
15064
|
+
featureIds: string[] // top-level feature IDs in this section
|
|
15249
15065
|
}
|
|
15250
15066
|
\`\`\`
|
|
15251
15067
|
|
|
15252
|
-
###
|
|
15068
|
+
### FeatureV3
|
|
15253
15069
|
\`\`\`
|
|
15254
15070
|
{
|
|
15255
|
-
id:
|
|
15256
|
-
name:
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15071
|
+
id: string // "feat:<slug>-<hash>"
|
|
15072
|
+
name: string
|
|
15073
|
+
description: string
|
|
15074
|
+
sectionId: string // must match a section id
|
|
15075
|
+
parentFeatureId?: string // set if sub-feature
|
|
15076
|
+
subFeatureIds: string[] // child feature IDs
|
|
15077
|
+
routes: string[]
|
|
15078
|
+
workflowIds: string[]
|
|
15079
|
+
sourceFiles: string[]
|
|
15080
|
+
entryPoints: EntryPoint[] // how users reach this feature
|
|
15262
15081
|
}
|
|
15263
15082
|
\`\`\`
|
|
15264
15083
|
|
|
15265
|
-
###
|
|
15084
|
+
### EntryPoint
|
|
15266
15085
|
\`\`\`
|
|
15267
15086
|
{
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
description:
|
|
15271
|
-
componentIds: string[] // component ids involved in this step, may be []
|
|
15272
|
-
apiCalls: string[] // API endpoints called, e.g. ["POST /api/login"], may be []
|
|
15273
|
-
conditionalBranches: ConditionalBranch[] // may be []
|
|
15087
|
+
type: "sidebar-nav" | "breadcrumb" | "deep-link" | "redirect" | "button" | "notification" | "url-direct"
|
|
15088
|
+
sourceFeatureId?: string // feature the link originates from
|
|
15089
|
+
description: string
|
|
15274
15090
|
}
|
|
15275
15091
|
\`\`\`
|
|
15276
15092
|
|
|
15277
|
-
###
|
|
15093
|
+
### WorkflowV3 (DAG-based)
|
|
15278
15094
|
\`\`\`
|
|
15279
15095
|
{
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15096
|
+
id: string
|
|
15097
|
+
name: string
|
|
15098
|
+
featureId: string
|
|
15099
|
+
type: "navigation" | "crud" | "multi-step" | "configuration" | "search-filter" | "authentication" | "notification" | "integration"
|
|
15100
|
+
preconditions: ConditionV3[]
|
|
15101
|
+
postconditions: ConditionV3[]
|
|
15102
|
+
steps: WorkflowStepV3[]
|
|
15103
|
+
edges: WorkflowEdge[]
|
|
15104
|
+
entryStepIds: string[] // steps with no incoming edges
|
|
15105
|
+
componentIds: string[]
|
|
15283
15106
|
}
|
|
15284
15107
|
\`\`\`
|
|
15285
15108
|
|
|
15286
|
-
###
|
|
15109
|
+
### WorkflowStepV3
|
|
15287
15110
|
\`\`\`
|
|
15288
15111
|
{
|
|
15289
|
-
id:
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15112
|
+
id: string // "step:<workflowId>:<slug>"
|
|
15113
|
+
description: string
|
|
15114
|
+
action: "user-action" | "system-response" | "navigation" | "api-call" | "conditional"
|
|
15115
|
+
componentIds: string[]
|
|
15116
|
+
apiCalls: string[]
|
|
15117
|
+
nextStepIds: string[] // outgoing edges — empty = terminal step
|
|
15118
|
+
branchCondition?: ConditionV3 // set when action="conditional"
|
|
15119
|
+
metadata?: object
|
|
15120
|
+
}
|
|
15121
|
+
\`\`\`
|
|
15122
|
+
|
|
15123
|
+
### WorkflowEdge
|
|
15124
|
+
\`\`\`
|
|
15125
|
+
{
|
|
15126
|
+
fromStepId: string
|
|
15127
|
+
toStepId: string
|
|
15128
|
+
label?: string // "valid", "invalid", "admin", "error"
|
|
15129
|
+
edgeType: "default" | "branch" | "error" | "convergence"
|
|
15295
15130
|
}
|
|
15296
15131
|
\`\`\`
|
|
15297
15132
|
|
|
15298
|
-
###
|
|
15133
|
+
### ScenarioV3
|
|
15299
15134
|
\`\`\`
|
|
15300
15135
|
{
|
|
15301
15136
|
id: string
|
|
15302
|
-
workflowId: string
|
|
15303
|
-
featureId: string
|
|
15137
|
+
workflowId: string
|
|
15138
|
+
featureId: string
|
|
15304
15139
|
name: string
|
|
15305
15140
|
description: string
|
|
15306
15141
|
category: "happy-path" | "permission" | "validation" | "error" | "edge-case" | "precondition"
|
|
15307
|
-
preconditions:
|
|
15142
|
+
preconditions: ConditionV3[]
|
|
15143
|
+
path: string[] // ordered step IDs tracing one path through the DAG
|
|
15308
15144
|
steps: ScenarioStep[]
|
|
15309
15145
|
expectedOutcome: string
|
|
15310
|
-
componentIds: string[]
|
|
15311
|
-
workflowStepIds: string[] // workflow step ids covered, may be []
|
|
15146
|
+
componentIds: string[]
|
|
15312
15147
|
priority: "critical" | "high" | "medium" | "low"
|
|
15313
15148
|
}
|
|
15314
15149
|
\`\`\`
|
|
15315
15150
|
|
|
15316
|
-
###
|
|
15151
|
+
### Component (same as V2)
|
|
15152
|
+
\`\`\`
|
|
15153
|
+
{
|
|
15154
|
+
id: string
|
|
15155
|
+
name: string
|
|
15156
|
+
type: "form" | "display" | "navigation" | "modal" | "layout" | "feedback"
|
|
15157
|
+
sourceFiles: string[]
|
|
15158
|
+
props: string[]
|
|
15159
|
+
referencedByWorkflows: string[]
|
|
15160
|
+
}
|
|
15161
|
+
\`\`\`
|
|
15162
|
+
|
|
15163
|
+
### ScenarioStep (same as V2)
|
|
15317
15164
|
\`\`\`
|
|
15318
15165
|
{
|
|
15319
|
-
order: number
|
|
15320
|
-
action: string
|
|
15321
|
-
expectedResult: string
|
|
15166
|
+
order: number
|
|
15167
|
+
action: string
|
|
15168
|
+
expectedResult: string
|
|
15322
15169
|
}
|
|
15323
15170
|
\`\`\`
|
|
15324
15171
|
|
|
15325
|
-
###
|
|
15172
|
+
### V3 DAG rules
|
|
15173
|
+
- Every step must be reachable from at least one entryStepId
|
|
15174
|
+
- No cycles allowed (DAG = Directed Acyclic Graph)
|
|
15175
|
+
- Steps with \`action: "conditional"\` MUST have 2+ nextStepIds
|
|
15176
|
+
- Terminal steps have \`nextStepIds: []\`
|
|
15177
|
+
- Every edge in \`edges[]\` must reference valid step IDs in \`steps[]\`
|
|
15178
|
+
- Scenario \`path[]\` must trace a valid walk from an entry step to a terminal step
|
|
15179
|
+
|
|
15180
|
+
### V3 Referential integrity
|
|
15181
|
+
- Every \`feature.sectionId\` must exist in \`sections[].id\`
|
|
15182
|
+
- Every \`section.featureIds[]\` must exist in \`features[].id\`
|
|
15326
15183
|
- Every \`workflow.featureId\` must exist in \`features[].id\`
|
|
15327
|
-
- Every \`
|
|
15328
|
-
-
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15184
|
+
- Every \`scenario.workflowId\` and \`scenario.featureId\` must exist
|
|
15185
|
+
- All V2 referential integrity rules also apply
|
|
15186
|
+
|
|
15187
|
+
---
|
|
15188
|
+
|
|
15189
|
+
## QAMapV2Payload Schema Reference (legacy)
|
|
15190
|
+
|
|
15191
|
+
V2 payloads do NOT include a schemaVersion field. Flat features, linear workflow steps.
|
|
15192
|
+
|
|
15193
|
+
### Top-level
|
|
15194
|
+
\`\`\`
|
|
15195
|
+
{
|
|
15196
|
+
features: Feature[]
|
|
15197
|
+
workflows: Workflow[]
|
|
15198
|
+
components: Component[]
|
|
15199
|
+
scenarios: Scenario[]
|
|
15200
|
+
commitSha?: string
|
|
15201
|
+
metadata?: object
|
|
15202
|
+
}
|
|
15203
|
+
\`\`\`
|
|
15204
|
+
|
|
15205
|
+
### V2 Feature: \`{ id, name, description, routes: string[], workflowIds: string[], sourceFiles: string[] }\`
|
|
15206
|
+
### V2 Workflow: \`{ id, name, featureId, type: "navigation"|"crud"|"multi-step"|"configuration"|"search-filter", preconditions: string[], steps: WorkflowStep[], componentIds: string[] }\`
|
|
15207
|
+
### V2 WorkflowStep: \`{ id, order: number, description, componentIds: string[], apiCalls: string[], conditionalBranches: ConditionalBranch[] }\`
|
|
15208
|
+
### V2 ConditionalBranch: \`{ condition, outcome, type: "validation"|"permission"|"error"|"business-logic" }\`
|
|
15209
|
+
### V2 Scenario: \`{ id, workflowId, featureId, name, description, category, preconditions: string[], steps: ScenarioStep[], expectedOutcome, componentIds: string[], workflowStepIds: string[], priority }\`
|
|
15335
15210
|
`.trim();
|
|
15336
|
-
var server = new McpServer({ name: "zefiro", version: "0.
|
|
15211
|
+
var server = new McpServer({ name: "zefiro", version: "0.4.0" }, { instructions: SERVER_INSTRUCTIONS });
|
|
15337
15212
|
server.registerTool("zefiro_scan_codebase", {
|
|
15338
15213
|
title: "Scan Codebase",
|
|
15339
15214
|
description: "Scan a project directory for test files, configs, fixtures, path aliases, and sample test content.",
|
|
@@ -15378,7 +15253,7 @@ server.registerTool("zefiro_scan_ast_detail", {
|
|
|
15378
15253
|
title: "Scan AST Detail",
|
|
15379
15254
|
description: "Retrieve a filtered slice of the AST scan. Requires a prior zefiro_scan_ast call. " + "Use to drill into routes, components, hooks, dependencies, or files.",
|
|
15380
15255
|
inputSchema: exports_external.object({
|
|
15381
|
-
category: exports_external.enum(["routes", "components", "hooks", "dependencies", "files"]),
|
|
15256
|
+
category: exports_external.enum(["routes", "components", "hooks", "dependencies", "files", "navigationPatterns"]),
|
|
15382
15257
|
filter: exports_external.string().optional().describe("Glob pattern to filter results"),
|
|
15383
15258
|
limit: exports_external.number().optional().describe("Max number of items to return")
|
|
15384
15259
|
})
|
|
@@ -15397,15 +15272,16 @@ server.registerTool("zefiro_scan_ast_detail", {
|
|
|
15397
15272
|
});
|
|
15398
15273
|
server.registerTool("zefiro_build_qa_map", {
|
|
15399
15274
|
title: "Build QA Map",
|
|
15400
|
-
description: "Validate and optionally write a
|
|
15275
|
+
description: "Validate and optionally write a QA map payload (V2 or V3, auto-detected by schemaVersion field). Use dryRun: true to validate without writing.",
|
|
15401
15276
|
inputSchema: exports_external.object({
|
|
15402
|
-
payload: exports_external.any().describe("QAMapV2Payload JSON object"),
|
|
15277
|
+
payload: exports_external.any().describe("QAMapV2Payload or QAMapV3Payload JSON object. V3 must include schemaVersion: 3."),
|
|
15403
15278
|
output: exports_external.string().optional().describe(`Output file path (defaults to ${CONFIG_DIR}/qa-map.json)`),
|
|
15404
15279
|
dryRun: exports_external.boolean().optional().describe("Validate only, do not write (default: false)")
|
|
15405
15280
|
})
|
|
15406
15281
|
}, async ({ payload, output, dryRun }) => {
|
|
15407
15282
|
try {
|
|
15408
|
-
const
|
|
15283
|
+
const isV3 = payload && typeof payload === "object" && payload.schemaVersion === 3;
|
|
15284
|
+
const validation = isV3 ? validateQAMapV3(payload) : validateQAMap(payload);
|
|
15409
15285
|
if (validation.valid && payload && typeof payload === "object") {
|
|
15410
15286
|
try {
|
|
15411
15287
|
const sha = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
@@ -15423,6 +15299,7 @@ server.registerTool("zefiro_build_qa_map", {
|
|
|
15423
15299
|
}
|
|
15424
15300
|
const p = payload;
|
|
15425
15301
|
const stats = validation.valid ? {
|
|
15302
|
+
sections: isV3 && Array.isArray(p?.sections) ? p.sections.length : undefined,
|
|
15426
15303
|
features: Array.isArray(p?.features) ? p.features.length : 0,
|
|
15427
15304
|
workflows: Array.isArray(p?.workflows) ? p.workflows.length : 0,
|
|
15428
15305
|
components: Array.isArray(p?.components) ? p.components.length : 0,
|
|
@@ -15431,7 +15308,7 @@ server.registerTool("zefiro_build_qa_map", {
|
|
|
15431
15308
|
return {
|
|
15432
15309
|
content: [{
|
|
15433
15310
|
type: "text",
|
|
15434
|
-
text: JSON.stringify({ valid: validation.valid, errors: validation.errors, warnings: validation.warnings, written, outputPath: written ? outputPath : null, stats }, null, 2)
|
|
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)
|
|
15435
15312
|
}]
|
|
15436
15313
|
};
|
|
15437
15314
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zefiro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"zefiro": "./dist/cli.js",
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
"import": "./dist/index.js"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
"files": [
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/",
|
|
21
|
+
"agents/"
|
|
22
|
+
],
|
|
20
23
|
"scripts": {
|
|
21
24
|
"build": "bun build src/cli.ts src/index.ts src/mcp.ts --outdir dist --target node --format esm --splitting --packages=bundle --external react-devtools-core",
|
|
22
25
|
"dev": "bun run src/cli.ts"
|