ai-spec-dev 0.29.1 → 0.30.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.
@@ -5497,6 +5497,41 @@ async function loadDslForSpec(specFilePath) {
5497
5497
  // core/frontend-context-loader.ts
5498
5498
  import * as fs7 from "fs-extra";
5499
5499
  import * as path6 from "path";
5500
+ function parseImportStatements(content) {
5501
+ const stripped = content.replace(/\/\*[\s\S]*?\*\//g, (m) => "\n".repeat(m.split("\n").length - 1));
5502
+ const collapsed = stripped.replace(/import\s*\{[^}]*\}/gs, (m) => m.replace(/\n\s*/g, " "));
5503
+ const results = [];
5504
+ for (const rawLine of collapsed.split("\n")) {
5505
+ const line = rawLine.trim();
5506
+ if (!line) continue;
5507
+ if (/^import\s+type\b/.test(line)) continue;
5508
+ if (!line.startsWith("import")) continue;
5509
+ const match = line.match(/^(import\s+([\s\S]+?)\s+from\s+['"]([^'"]+)['"])/);
5510
+ if (!match) continue;
5511
+ results.push({
5512
+ line: match[1],
5513
+ modulePath: match[3],
5514
+ specifiers: match[2]
5515
+ });
5516
+ }
5517
+ return results;
5518
+ }
5519
+ var HTTP_MODULE_PATTERNS = [
5520
+ // Project path aliases (@/, @@/, ~/, #/) — catches '@/utils/request', '~/lib/http', etc.
5521
+ /^(?:@{1,2}|~|#)[/\\]/,
5522
+ // Well-known HTTP libraries (exact name match)
5523
+ /^(?:axios|ky(?:-universal)?|undici|node-fetch|cross-fetch|got|superagent|alova|openapi-fetch)$/,
5524
+ // Relative imports whose path contains an HTTP-utility keyword
5525
+ /\.{1,2}\/[^'"]*(?:http|request|fetch|client|api)[^'"]*/
5526
+ ];
5527
+ function findHttpClientImport(content) {
5528
+ for (const stmt of parseImportStatements(content)) {
5529
+ if (HTTP_MODULE_PATTERNS.some((p) => p.test(stmt.modulePath))) {
5530
+ return stmt.line;
5531
+ }
5532
+ }
5533
+ return void 0;
5534
+ }
5500
5535
  var STATE_MANAGEMENT_LIBS = [
5501
5536
  "zustand",
5502
5537
  "redux",
@@ -5685,13 +5720,12 @@ ${preview}`);
5685
5720
  } catch {
5686
5721
  }
5687
5722
  }
5688
- const httpImportRegex = /^import(?!\s+type)\s+(?:[\w*]+|\{[^}]+\})\s+from\s+['"]((?:@{1,2}|~|#)[/\\][^'"]+|\.{1,2}\/[^'"]*(?:http|request|fetch|client|api)[^'"]*|axios|ky(?:-universal)?|undici|node-fetch|cross-fetch|got|superagent|alova|openapi-fetch)['"]/im;
5689
5723
  for (const relPath of ctx.existingApiFiles.slice(0, 5)) {
5690
5724
  try {
5691
5725
  const content = await fs7.readFile(path6.join(projectRoot, relPath), "utf-8");
5692
- const match = content.match(httpImportRegex);
5693
- if (match) {
5694
- ctx.httpClientImport = match[0].trim();
5726
+ const found = findHttpClientImport(content);
5727
+ if (found) {
5728
+ ctx.httpClientImport = found;
5695
5729
  break;
5696
5730
  }
5697
5731
  } catch {
@@ -5821,13 +5855,28 @@ async function extractRouteModuleContext(projectRoot, ctx) {
5821
5855
  moduleFiles.push(...files);
5822
5856
  }
5823
5857
  if (moduleFiles.length === 0) return;
5824
- const layoutImportRegex = /^(?:const\s+Layout\s*=.*import\(['"][^'"]+['"]\)|import\s+Layout\s+from\s+['"][^'"]+['"])/m;
5858
+ const dynamicLayoutRegex = /const\s+Layout\s*=\s*(?:defineAsyncComponent\s*\(\s*)?(?:\(\s*\))?\s*(?:=>|function[^(]*\()\s*(?:[^)]*\))?\s*(?:=>)?\s*import\s*\(\s*['"]([^'"]+)['"]\s*\)/;
5825
5859
  for (const relPath of moduleFiles) {
5826
5860
  try {
5827
5861
  const content = await fs7.readFile(path6.join(projectRoot, relPath), "utf-8");
5828
- const match = content.match(layoutImportRegex);
5829
- if (match) {
5830
- ctx.layoutImport = match[0].trim();
5862
+ const stmts = parseImportStatements(content);
5863
+ const staticLayout = stmts.find(
5864
+ (s) => /\bLayout\b/.test(s.specifiers) && /layout/i.test(s.modulePath)
5865
+ );
5866
+ if (staticLayout) {
5867
+ ctx.layoutImport = staticLayout.line;
5868
+ const preview = content.split("\n").slice(0, 100).join("\n");
5869
+ ctx.routeModuleExample = { path: relPath, content: preview };
5870
+ break;
5871
+ }
5872
+ const singleLine = content.replace(
5873
+ /const\s+Layout\s*=[\s\S]*?import\s*\([^)]+\)/gm,
5874
+ (m) => m.replace(/\n\s*/g, " ")
5875
+ );
5876
+ const dynMatch = singleLine.match(dynamicLayoutRegex);
5877
+ if (dynMatch) {
5878
+ const constMatch = content.match(/^const\s+Layout\s*=.+/m);
5879
+ ctx.layoutImport = constMatch ? constMatch[0].trim() : dynMatch[0].trim();
5831
5880
  const preview = content.split("\n").slice(0, 100).join("\n");
5832
5881
  ctx.routeModuleExample = { path: relPath, content: preview };
5833
5882
  break;
@@ -7812,6 +7861,60 @@ function parseErrors(output, source) {
7812
7861
  }
7813
7862
  return errors;
7814
7863
  }
7864
+ function parseRelativeImports(content, fromFileRel) {
7865
+ const relDir = path15.dirname(fromFileRel);
7866
+ const results = [];
7867
+ const normalized = content.replace(/import\s*\{[^}]*\}/gs, (m) => m.replace(/\n\s*/g, " "));
7868
+ for (const line of normalized.split("\n")) {
7869
+ const trimmed = line.trim();
7870
+ if (/^import\s+type\b/.test(trimmed)) continue;
7871
+ const match = trimmed.match(/^import\b[^'"]*from\s+['"](\.\.?\/[^'"]+)['"]/);
7872
+ if (!match) continue;
7873
+ const resolved = path15.normalize(path15.join(relDir, match[1]));
7874
+ results.push(resolved);
7875
+ }
7876
+ return results;
7877
+ }
7878
+ async function buildRepairOrder(errorsByFile, workingDir) {
7879
+ const files = Array.from(errorsByFile.keys());
7880
+ if (files.length <= 1) return Array.from(errorsByFile.entries());
7881
+ const deps = new Map(files.map((f) => [f, []]));
7882
+ for (const file of files) {
7883
+ try {
7884
+ const content = await fs16.readFile(path15.join(workingDir, file), "utf-8");
7885
+ const importedPaths = parseRelativeImports(content, file);
7886
+ for (const importedPath of importedPaths) {
7887
+ const matched = files.find((f) => {
7888
+ if (f === file) return false;
7889
+ const fNoExt = f.replace(/\.[^.]+$/, "");
7890
+ return importedPath === fNoExt || importedPath === f || `${importedPath}.ts` === f || `${importedPath}.tsx` === f || `${importedPath}.js` === f || `${importedPath}.jsx` === f;
7891
+ });
7892
+ if (matched) deps.get(file).push(matched);
7893
+ }
7894
+ } catch {
7895
+ }
7896
+ }
7897
+ const dependents = new Map(files.map((f) => [f, []]));
7898
+ for (const [file, fileDeps] of deps) {
7899
+ for (const dep of fileDeps) dependents.get(dep).push(file);
7900
+ }
7901
+ const inDegree = new Map(files.map((f) => [f, deps.get(f).length]));
7902
+ const queue = files.filter((f) => inDegree.get(f) === 0);
7903
+ const sorted = [];
7904
+ while (queue.length > 0) {
7905
+ const file = queue.shift();
7906
+ sorted.push(file);
7907
+ for (const dependent of dependents.get(file) ?? []) {
7908
+ const degree = (inDegree.get(dependent) ?? 1) - 1;
7909
+ inDegree.set(dependent, degree);
7910
+ if (degree === 0) queue.push(dependent);
7911
+ }
7912
+ }
7913
+ for (const f of files) {
7914
+ if (!sorted.includes(f)) sorted.push(f);
7915
+ }
7916
+ return sorted.map((f) => [f, errorsByFile.get(f)]);
7917
+ }
7815
7918
  async function attemptFix(provider, errors, workingDir, dsl) {
7816
7919
  const results = [];
7817
7920
  const errorsByFile = /* @__PURE__ */ new Map();
@@ -7820,7 +7923,8 @@ async function attemptFix(provider, errors, workingDir, dsl) {
7820
7923
  if (!errorsByFile.has(file)) errorsByFile.set(file, []);
7821
7924
  errorsByFile.get(file).push(err);
7822
7925
  }
7823
- for (const [file, fileErrors] of errorsByFile) {
7926
+ const sortedEntries = await buildRepairOrder(errorsByFile, workingDir);
7927
+ for (const [file, fileErrors] of sortedEntries) {
7824
7928
  const fullPath = path15.join(workingDir, file);
7825
7929
  let existingContent = "";
7826
7930
  try {