zefiro 0.3.7 → 0.5.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/cli.js CHANGED
@@ -4,14 +4,17 @@ import {
4
4
  error,
5
5
  header,
6
6
  info,
7
+ isDAGValid,
8
+ linearToDAG,
7
9
  require_picocolors,
8
10
  require_src,
9
11
  runStage1,
10
12
  setVerbose,
11
13
  success,
14
+ validateQAMapV3,
12
15
  verbose,
13
16
  warn
14
- } from "./cli-ek34kdc1.js";
17
+ } from "./cli-fc97msmj.js";
15
18
  import {
16
19
  CONFIG_DIR,
17
20
  getPackageRoot,
@@ -7476,6 +7479,9 @@ function generateComponentId(filePath, componentName) {
7476
7479
  const hash = stableHash([filePath, componentName]);
7477
7480
  return `comp:${slugify(componentName)}-${hash}`;
7478
7481
  }
7482
+ function generateSectionId(name) {
7483
+ return `sec:${slugify(name)}`;
7484
+ }
7479
7485
  function normalizeIds(qaMap) {
7480
7486
  const featureIdMap = new Map;
7481
7487
  const workflowIdMap = new Map;
@@ -7528,6 +7534,76 @@ function normalizeIds(qaMap) {
7528
7534
  scenarios: newScenarios
7529
7535
  };
7530
7536
  }
7537
+ function normalizeIdsV3(qaMap) {
7538
+ const sectionIdMap = new Map;
7539
+ const featureIdMap = new Map;
7540
+ const workflowIdMap = new Map;
7541
+ const componentIdMap = new Map;
7542
+ for (const section of qaMap.sections) {
7543
+ const newId = generateSectionId(section.name);
7544
+ sectionIdMap.set(section.id, newId);
7545
+ }
7546
+ for (const feature of qaMap.features) {
7547
+ const newId = generateFeatureId(feature.name, feature.routes);
7548
+ featureIdMap.set(feature.id, newId);
7549
+ }
7550
+ for (const component of qaMap.components) {
7551
+ const primaryFile = component.sourceFiles[0] ?? "";
7552
+ const newId = generateComponentId(primaryFile, component.name);
7553
+ componentIdMap.set(component.id, newId);
7554
+ }
7555
+ for (const workflow of qaMap.workflows) {
7556
+ const newFeatureId = featureIdMap.get(workflow.featureId) ?? workflow.featureId;
7557
+ const newId = generateWorkflowId(newFeatureId, workflow.name);
7558
+ workflowIdMap.set(workflow.id, newId);
7559
+ }
7560
+ const newSections = qaMap.sections.map((s) => ({
7561
+ ...s,
7562
+ id: sectionIdMap.get(s.id) ?? s.id,
7563
+ featureIds: s.featureIds.map((fId) => featureIdMap.get(fId) ?? fId)
7564
+ }));
7565
+ const newFeatures = qaMap.features.map((f) => ({
7566
+ ...f,
7567
+ id: featureIdMap.get(f.id) ?? f.id,
7568
+ sectionId: sectionIdMap.get(f.sectionId) ?? f.sectionId,
7569
+ parentFeatureId: f.parentFeatureId ? featureIdMap.get(f.parentFeatureId) ?? f.parentFeatureId : undefined,
7570
+ subFeatureIds: f.subFeatureIds.map((id) => featureIdMap.get(id) ?? id),
7571
+ workflowIds: f.workflowIds.map((wId) => workflowIdMap.get(wId) ?? wId),
7572
+ entryPoints: f.entryPoints.map((ep) => ({
7573
+ ...ep,
7574
+ sourceFeatureId: ep.sourceFeatureId ? featureIdMap.get(ep.sourceFeatureId) ?? ep.sourceFeatureId : undefined
7575
+ }))
7576
+ }));
7577
+ const newWorkflows = qaMap.workflows.map((w) => ({
7578
+ ...w,
7579
+ id: workflowIdMap.get(w.id) ?? w.id,
7580
+ featureId: featureIdMap.get(w.featureId) ?? w.featureId,
7581
+ componentIds: w.componentIds.map((cId) => componentIdMap.get(cId) ?? cId),
7582
+ steps: w.steps.map((step) => ({
7583
+ ...step,
7584
+ componentIds: step.componentIds.map((cId) => componentIdMap.get(cId) ?? cId)
7585
+ }))
7586
+ }));
7587
+ const newComponents = qaMap.components.map((c) => ({
7588
+ ...c,
7589
+ id: componentIdMap.get(c.id) ?? c.id,
7590
+ referencedByWorkflows: c.referencedByWorkflows.map((wId) => workflowIdMap.get(wId) ?? wId)
7591
+ }));
7592
+ const newScenarios = qaMap.scenarios.map((s) => ({
7593
+ ...s,
7594
+ featureId: featureIdMap.get(s.featureId) ?? s.featureId,
7595
+ workflowId: workflowIdMap.get(s.workflowId) ?? s.workflowId,
7596
+ componentIds: s.componentIds.map((cId) => componentIdMap.get(cId) ?? cId)
7597
+ }));
7598
+ return {
7599
+ ...qaMap,
7600
+ sections: newSections,
7601
+ features: newFeatures,
7602
+ workflows: newWorkflows,
7603
+ components: newComponents,
7604
+ scenarios: newScenarios
7605
+ };
7606
+ }
7531
7607
 
7532
7608
  // src/commands/analyze.ts
7533
7609
  var ENV_KEY = {
@@ -7561,7 +7637,8 @@ async function ensureApiKey(provider) {
7561
7637
  }
7562
7638
  }
7563
7639
  function registerAnalyze(program2) {
7564
- 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)").action(async (inputArg, opts) => {
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 3)", "3").action(async (inputArg, opts) => {
7641
+ const schemaVersion = parseInt(opts?.schemaVersion ?? "3", 10);
7565
7642
  const ctx = await resolveCommandContext(program2);
7566
7643
  const root = ctx.paths.projectRoot;
7567
7644
  let inputPath;
@@ -7597,6 +7674,10 @@ function registerAnalyze(program2) {
7597
7674
  if (!hasKey)
7598
7675
  return;
7599
7676
  const spinner = createSpinner();
7677
+ if (schemaVersion === 3) {
7678
+ await runV3Analysis(ctx, ast, outputPath, prevQAMap, affected, spinner, opts);
7679
+ return;
7680
+ }
7600
7681
  spinner.start("Stage 2a: Identifying features...");
7601
7682
  const featureAgent = loadAgent("feature-analyzer-agent", ctx.config);
7602
7683
  const featureResponse = await callLLM({
@@ -7745,6 +7826,166 @@ function registerAnalyze(program2) {
7745
7826
  success(`QA map written to ${outputPath}`);
7746
7827
  });
7747
7828
  }
7829
+ async function runV3Analysis(ctx, ast, outputPath, _prevQAMap, _affected, spinner, opts) {
7830
+ spinner.start("Stage 2a: Identifying sections and features (V3)...");
7831
+ const featureAgent = loadAgent("feature-analyzer-agent-v3", ctx.config);
7832
+ const featureResponse = await callLLM({
7833
+ provider: ctx.provider,
7834
+ model: ctx.model ?? featureAgent.config.model,
7835
+ systemPrompt: featureAgent.systemPrompt,
7836
+ userMessage: JSON.stringify({ ast }),
7837
+ maxTokens: featureAgent.config.maxTokens,
7838
+ temperature: featureAgent.config.temperature,
7839
+ jsonMode: true
7840
+ });
7841
+ spinner.stop();
7842
+ const featureMap = JSON.parse(extractJSON(featureResponse.content));
7843
+ if (!featureMap?.features || !featureMap?.sections) {
7844
+ error("Feature analysis failed: invalid response (missing features or sections)");
7845
+ process.exit(1);
7846
+ }
7847
+ const sections = featureMap.sections;
7848
+ const features = featureMap.features;
7849
+ const baseComponents = featureMap.components ?? [];
7850
+ success(`Identified ${sections.length} sections, ${features.length} features, ${baseComponents.length} components`);
7851
+ spinner.start(`Stage 2b: Mapping DAG workflows for ${features.length} feature(s)...`);
7852
+ const workflowTasks = features.map((feature) => {
7853
+ const featureComponents = baseComponents.filter((c) => c.sourceFiles.some((sf) => feature.sourceFiles.includes(sf)));
7854
+ const featureRoutes = ast.routes.filter((r) => feature.routes.some((fr) => r.path.startsWith(fr.split("/").slice(0, 3).join("/"))));
7855
+ const featureForms = (ast.forms ?? []).filter((f) => feature.sourceFiles.includes(f.filePath));
7856
+ return {
7857
+ agentName: "workflow-agent-v3",
7858
+ userMessage: JSON.stringify({ feature, components: featureComponents, routes: featureRoutes, forms: featureForms }),
7859
+ config: ctx.config,
7860
+ provider: ctx.provider,
7861
+ model: ctx.model
7862
+ };
7863
+ });
7864
+ const workflowResults = await runAgentsInParallel(workflowTasks, 4);
7865
+ spinner.stop();
7866
+ let allWorkflows = workflowResults.flatMap((r) => r.workflows ?? []);
7867
+ spinner.start("Validating workflow DAGs...");
7868
+ const validatedWorkflows = [];
7869
+ for (const wf of allWorkflows) {
7870
+ const dagResult = isDAGValid(wf.steps, wf.edges, wf.entryStepIds);
7871
+ if (dagResult.valid) {
7872
+ validatedWorkflows.push(wf);
7873
+ } else {
7874
+ warn(`Workflow "${wf.name}" has invalid DAG: ${dagResult.errors.join("; ")}. Falling back to linear chain.`);
7875
+ const linear = linearToDAG(wf.steps);
7876
+ validatedWorkflows.push({
7877
+ ...wf,
7878
+ steps: linear.steps,
7879
+ edges: linear.edges,
7880
+ entryStepIds: linear.entryStepIds
7881
+ });
7882
+ }
7883
+ }
7884
+ spinner.stop();
7885
+ allWorkflows = validatedWorkflows;
7886
+ success(`Mapped ${allWorkflows.length} workflows with DAG structure`);
7887
+ const workflowsByFeature = new Map;
7888
+ for (const wf of allWorkflows) {
7889
+ const ids = workflowsByFeature.get(wf.featureId) ?? [];
7890
+ ids.push(wf.id);
7891
+ workflowsByFeature.set(wf.featureId, ids);
7892
+ }
7893
+ for (const feature of features) {
7894
+ const wfIds = workflowsByFeature.get(feature.id);
7895
+ if (wfIds)
7896
+ feature.workflowIds = wfIds;
7897
+ }
7898
+ const formComponents = baseComponents.filter((c) => c.type === "form");
7899
+ if (formComponents.length > 0 && (ast.forms ?? []).length > 0) {
7900
+ spinner.start(`Stage 2c: Enriching ${formComponents.length} form component(s)...`);
7901
+ const inputTasks = features.map((feature) => {
7902
+ const featureFormComponents = formComponents.filter((c) => c.sourceFiles.some((sf) => feature.sourceFiles.includes(sf)));
7903
+ const featureForms = (ast.forms ?? []).filter((f) => feature.sourceFiles.includes(f.filePath));
7904
+ if (featureFormComponents.length === 0)
7905
+ return null;
7906
+ return {
7907
+ agentName: "input-agent",
7908
+ userMessage: JSON.stringify({ featureId: feature.id, components: featureFormComponents, forms: featureForms }),
7909
+ config: ctx.config,
7910
+ provider: ctx.provider,
7911
+ model: ctx.model
7912
+ };
7913
+ }).filter((t) => t !== null);
7914
+ if (inputTasks.length > 0) {
7915
+ try {
7916
+ const inputResults = await runAgentsInParallel(inputTasks, 4);
7917
+ for (const result of inputResults) {
7918
+ for (const enriched of result.components ?? []) {
7919
+ const idx = baseComponents.findIndex((c) => c.id === enriched.id);
7920
+ if (idx !== -1) {
7921
+ baseComponents[idx] = { ...baseComponents[idx], ...enriched };
7922
+ }
7923
+ }
7924
+ }
7925
+ } catch {
7926
+ warn("Input enrichment failed (non-critical), continuing with base components");
7927
+ }
7928
+ }
7929
+ spinner.stop();
7930
+ }
7931
+ if (opts?.skipScenarios) {
7932
+ const finalPayload2 = {
7933
+ schemaVersion: 3,
7934
+ sections,
7935
+ features,
7936
+ workflows: allWorkflows,
7937
+ components: baseComponents,
7938
+ scenarios: [],
7939
+ metadata: ast.git ? { git: ast.git } : undefined
7940
+ };
7941
+ const normalizedPayload2 = normalizeIdsV3(finalPayload2);
7942
+ writeFile(outputPath, JSON.stringify(normalizedPayload2, null, 2));
7943
+ success(`V3 QA map written to ${outputPath}`);
7944
+ return;
7945
+ }
7946
+ spinner.start(`Stage 3: Generating scenarios for ${features.length} feature(s)...`);
7947
+ const scenarioTasks = features.map((feature) => {
7948
+ const featureWorkflows = allWorkflows.filter((w) => w.featureId === feature.id);
7949
+ const featureComponents = baseComponents.filter((c) => c.referencedByWorkflows.some((wfId) => featureWorkflows.map((w) => w.id).includes(wfId)));
7950
+ return {
7951
+ agentName: "scenario-planner-agent-v3",
7952
+ userMessage: JSON.stringify({
7953
+ features: [feature],
7954
+ workflows: featureWorkflows,
7955
+ components: featureComponents
7956
+ }),
7957
+ config: ctx.config,
7958
+ provider: ctx.provider,
7959
+ model: ctx.model
7960
+ };
7961
+ });
7962
+ const scenarioResults = await runAgentsInParallel(scenarioTasks, 4);
7963
+ spinner.stop();
7964
+ const allScenarios = scenarioResults.flatMap((r) => r.scenarios ?? []);
7965
+ success(`Generated ${allScenarios.length} scenarios`);
7966
+ const finalPayload = {
7967
+ schemaVersion: 3,
7968
+ sections,
7969
+ features,
7970
+ workflows: allWorkflows,
7971
+ components: baseComponents,
7972
+ scenarios: allScenarios,
7973
+ metadata: ast.git ? { git: ast.git } : undefined
7974
+ };
7975
+ const normalizedPayload = normalizeIdsV3(finalPayload);
7976
+ const validation = validateQAMapV3(normalizedPayload);
7977
+ if (!validation.valid) {
7978
+ warn("V3 QA map has validation issues:");
7979
+ for (const err of validation.errors)
7980
+ warn(` - ${err}`);
7981
+ }
7982
+ if (validation.warnings.length > 0) {
7983
+ for (const w of validation.warnings)
7984
+ info(` ⚠ ${w}`);
7985
+ }
7986
+ writeFile(outputPath, JSON.stringify(normalizedPayload, null, 2));
7987
+ success(`V3 QA map written to ${outputPath}`);
7988
+ }
7748
7989
 
7749
7990
  // src/commands/auth.ts
7750
7991
  function registerAuth(program2) {
@@ -8060,7 +8301,9 @@ import { join as join7 } from "node:path";
8060
8301
 
8061
8302
  // src/scanner/push.ts
8062
8303
  async function pushToApi(payload, apiUrl, apiKey) {
8063
- const response = await fetch(apiUrl, {
8304
+ const isV3 = payload.schemaVersion === 3;
8305
+ const url = isV3 ? apiUrl.replace("/v2/push", "/v3/push") : apiUrl.replace("/v3/push", "/v2/push");
8306
+ const response = await fetch(url, {
8064
8307
  method: "POST",
8065
8308
  headers: {
8066
8309
  "Content-Type": "application/json",
@@ -8092,7 +8335,9 @@ function registerPush(program2) {
8092
8335
  error(`QA map not found: ${inputPath}`);
8093
8336
  process.exit(1);
8094
8337
  }
8095
- const payload = JSON.parse(readFile(inputPath));
8338
+ const raw = JSON.parse(readFile(inputPath));
8339
+ const isV3 = raw.schemaVersion === 3;
8340
+ const payload = raw;
8096
8341
  if (opts?.commitSha) {
8097
8342
  payload.commitSha = opts.commitSha;
8098
8343
  }
@@ -8109,14 +8354,16 @@ function registerPush(program2) {
8109
8354
  payload.commitSha = gitMeta.commitSha;
8110
8355
  }
8111
8356
  }
8112
- info(`Pushing: ${payload.features.length} features, ${payload.workflows.length} workflows, ${payload.scenarios.length} scenarios`);
8357
+ const sectionCount = isV3 ? payload.sections.length : 0;
8358
+ info(`Pushing (V${isV3 ? 3 : 2}): ${sectionCount ? sectionCount + " sections, " : ""}${payload.features.length} features, ${payload.workflows.length} workflows, ${payload.scenarios.length} scenarios`);
8359
+ const pushEndpoint = isV3 ? "/api/cli/push-v3" : "/api/cli/push";
8113
8360
  try {
8114
8361
  const { AuthClient } = await import("./index-fbvbnntc.js");
8115
8362
  const client = AuthClient.fromSession();
8116
8363
  if (client) {
8117
8364
  const spinner2 = createSpinner();
8118
8365
  spinner2.start("Pushing via authenticated session...");
8119
- const result2 = await client.post("/api/cli/push", payload);
8366
+ const result2 = await client.post(pushEndpoint, payload);
8120
8367
  spinner2.stop();
8121
8368
  success("Push successful!");
8122
8369
  info(` Version: ${result2.version} (schema v${result2.schemaVersion})`);