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/dist/mcp.js CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- runStage1
4
- } from "./cli-ek34kdc1.js";
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 files.
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 QAMapV2Payload
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
- ## QAMapV2Payload Schema Reference
15030
+ ## QAMapV3Payload Schema Reference (preferred)
15225
15031
 
15226
- The payload passed to \`zefiro_build_qa_map\` MUST conform exactly to this schema. All fields are required unless marked optional.
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
- features: Feature[] // required, may be []
15232
- workflows: Workflow[] // required, may be []
15233
- components: Component[] // required, may be []
15234
- scenarios: Scenario[] // required, may be []
15235
- commitSha?: string // optional, auto-injected by the tool
15236
- metadata?: object // optional free-form
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
- ### Feature
15048
+ ### ConditionV3 (used in preconditions, postconditions, branchCondition)
15241
15049
  \`\`\`
15242
15050
  {
15243
- id: string // unique, e.g. "feat-auth"
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
- routes: string[] // URL paths this feature covers, e.g. ["/login", "/register"]
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
- ### Workflow
15068
+ ### FeatureV3
15253
15069
  \`\`\`
15254
15070
  {
15255
- id: string
15256
- name: string
15257
- featureId: string // must match an existing feature id
15258
- type: "navigation" | "crud" | "multi-step" | "configuration" | "search-filter"
15259
- preconditions: string[] // may be []
15260
- steps: WorkflowStep[]
15261
- componentIds: string[] // component ids used by this workflow, may be []
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
- ### WorkflowStep
15084
+ ### EntryPoint
15266
15085
  \`\`\`
15267
15086
  {
15268
- id: string
15269
- order: number // 1-based integer
15270
- description: string
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
- ### ConditionalBranch
15093
+ ### WorkflowV3 (DAG-based)
15278
15094
  \`\`\`
15279
15095
  {
15280
- condition: string // e.g. "invalid credentials"
15281
- outcome: string // e.g. "show error message"
15282
- type: "validation" | "permission" | "error" | "business-logic"
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
- ### Component
15109
+ ### WorkflowStepV3
15287
15110
  \`\`\`
15288
15111
  {
15289
- id: string
15290
- name: string
15291
- type: "form" | "display" | "navigation" | "modal" | "layout" | "feedback"
15292
- sourceFiles: string[] // relative file paths
15293
- props: string[] // prop names, may be []
15294
- referencedByWorkflows: string[] // workflow ids that use this component, may be []
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
- ### Scenario
15133
+ ### ScenarioV3
15299
15134
  \`\`\`
15300
15135
  {
15301
15136
  id: string
15302
- workflowId: string // must match an existing workflow id
15303
- featureId: string // must match an existing feature id
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: string[] // may be []
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[] // component ids exercised, may be []
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
- ### ScenarioStep
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 // 1-based integer
15320
- action: string // what the user/system does
15321
- expectedResult: string // what should happen
15166
+ order: number
15167
+ action: string
15168
+ expectedResult: string
15322
15169
  }
15323
15170
  \`\`\`
15324
15171
 
15325
- ### Referential integrity rules
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 \`feature.workflowIds[]\` entry must exist in \`workflows[].id\`
15328
- - Every \`workflow.componentIds[]\` entry must exist in \`components[].id\`
15329
- - Every \`scenario.workflowId\` must exist in \`workflows[].id\`
15330
- - Every \`scenario.featureId\` must exist in \`features[].id\`
15331
- - Every \`scenario.componentIds[]\` entry must exist in \`components[].id\`
15332
- - Every \`scenario.workflowStepIds[]\` entry must exist in a \`workflow.steps[].id\`
15333
- - Every \`component.referencedByWorkflows[]\` entry must exist in \`workflows[].id\`
15334
- - All ids must be unique within their entity type
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.3.2" }, { instructions: SERVER_INSTRUCTIONS });
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 QAMapV2Payload. Use dryRun: true to validate without writing.",
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 validation = validateQAMap(payload);
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.7",
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": ["dist/", "agents/"],
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"