ai-spec-dev 0.29.0 → 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.
- package/README.md +6 -3
- package/RELEASE_LOG.md +24 -0
- package/core/error-feedback.ts +102 -2
- package/core/frontend-context-loader.ts +117 -20
- package/dist/cli/index.js +113 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +113 -9
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +57 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +57 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/purpose.md +29 -4
package/dist/cli/index.mjs
CHANGED
|
@@ -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
|
|
5693
|
-
if (
|
|
5694
|
-
ctx.httpClientImport =
|
|
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
|
|
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
|
|
5829
|
-
|
|
5830
|
-
|
|
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
|
-
|
|
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 {
|