ai-spec-dev 0.55.0 → 0.57.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 +5 -1
- package/cli/pipeline/multi-repo.ts +9 -0
- package/core/cross-stack-verifier.ts +90 -3
- package/demo/demo.gif +0 -0
- package/demo/demo.sh +433 -0
- package/demo/demo.tape +52 -0
- package/dist/cli/index.js +68 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +68 -5
- package/dist/cli/index.mjs.map +1 -1
- package/docs-assets/demo.gif +0 -0
- package/package.json +1 -1
- package/specs/2026-04-08-vision-frontend-workflow-design.md +548 -0
- package/tests/cross-stack-verifier.test.ts +101 -0
- package/.ai-spec-workspace.json +0 -17
- package/.ai-spec.json +0 -7
package/demo/demo.tape
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ai-spec demo
|
|
2
|
+
# Generate: vhs demo.tape
|
|
3
|
+
|
|
4
|
+
Output demo.gif
|
|
5
|
+
Set Theme "Dracula"
|
|
6
|
+
Set FontSize 13
|
|
7
|
+
Set Width 1100
|
|
8
|
+
Set Height 720
|
|
9
|
+
Set Padding 24
|
|
10
|
+
Set FontFamily "JetBrains Mono"
|
|
11
|
+
Set PlaybackSpeed 1.0
|
|
12
|
+
|
|
13
|
+
# ── Opening ────────────────────────────────────────────────────────────────────
|
|
14
|
+
Sleep 500ms
|
|
15
|
+
Type "ai-spec --help"
|
|
16
|
+
Sleep 300ms
|
|
17
|
+
Enter
|
|
18
|
+
Sleep 3s
|
|
19
|
+
|
|
20
|
+
# ── Scene 2: single-repo create pipeline ──────────────────────────────────────
|
|
21
|
+
Sleep 800ms
|
|
22
|
+
Type "ai-spec create 'Add task management feature to Vue admin'"
|
|
23
|
+
Sleep 300ms
|
|
24
|
+
Enter
|
|
25
|
+
Sleep 300ms
|
|
26
|
+
Type "bash demo.sh create"
|
|
27
|
+
Enter
|
|
28
|
+
Sleep 40s
|
|
29
|
+
|
|
30
|
+
# ── Scene 3: multi-repo workspace ─────────────────────────────────────────────
|
|
31
|
+
Sleep 800ms
|
|
32
|
+
Type "ai-spec create 'Add user profile sync' --workspace"
|
|
33
|
+
Sleep 300ms
|
|
34
|
+
Enter
|
|
35
|
+
Sleep 300ms
|
|
36
|
+
Type "bash demo.sh multirepo"
|
|
37
|
+
Enter
|
|
38
|
+
Sleep 14s
|
|
39
|
+
|
|
40
|
+
# ── Scene 4: DSL artifacts ─────────────────────────────────────────────────────
|
|
41
|
+
Sleep 800ms
|
|
42
|
+
Type "bash demo.sh artifacts"
|
|
43
|
+
Enter
|
|
44
|
+
Sleep 10s
|
|
45
|
+
|
|
46
|
+
# ── Scene 5: observability ─────────────────────────────────────────────────────
|
|
47
|
+
Sleep 800ms
|
|
48
|
+
Type "bash demo.sh observability"
|
|
49
|
+
Enter
|
|
50
|
+
Sleep 8s
|
|
51
|
+
|
|
52
|
+
Sleep 2s
|
package/dist/cli/index.js
CHANGED
|
@@ -715,7 +715,7 @@ var require_package = __commonJS({
|
|
|
715
715
|
"package.json"(exports2, module2) {
|
|
716
716
|
module2.exports = {
|
|
717
717
|
name: "ai-spec-dev",
|
|
718
|
-
version: "0.
|
|
718
|
+
version: "0.56.0",
|
|
719
719
|
description: "AI-driven Development Orchestrator SDK & CLI",
|
|
720
720
|
main: "dist/index.js",
|
|
721
721
|
types: "dist/index.d.ts",
|
|
@@ -10321,10 +10321,12 @@ async function walkSource(root) {
|
|
|
10321
10321
|
function extractApiCallsFromSource(source, relFile) {
|
|
10322
10322
|
const calls = [];
|
|
10323
10323
|
const lines = source.split("\n");
|
|
10324
|
-
const methodCallRegex = /\.(get|post|put|patch|delete)\s*\(\s*(['"`])([^'"`]+)\2/gi;
|
|
10324
|
+
const methodCallRegex = /\.(get|post|put|patch|delete)\s*\(\s*(['"`])([^'"`]+)\2(?!\s*\+)/gi;
|
|
10325
10325
|
const fetchRegex = /\bfetch\s*\(\s*(['"`])([^'"`]+)\1([^)]*)\)/g;
|
|
10326
10326
|
const useRequestRegex = /\buseRequest\s*\(\s*(['"`])([^'"`]+)\1([^)]*)\)/g;
|
|
10327
10327
|
const genericRequestRegex = /\brequest\s*\(\s*(['"`])([^'"`]+)\1\s*(?:,\s*(['"`])(GET|POST|PUT|PATCH|DELETE)\3)?/gi;
|
|
10328
|
+
const concatMethodRegex = /\.(get|post|put|patch|delete)\s*\(\s*(['"`])([^'"`]+)\2\s*\+/gi;
|
|
10329
|
+
const concatFetchRegex = /\bfetch\s*\(\s*(['"`])([^'"`]+)\1\s*\+([^)]*)\)/g;
|
|
10328
10330
|
function getLineNumber(offset) {
|
|
10329
10331
|
let ln = 1;
|
|
10330
10332
|
for (let i = 0; i < offset && i < source.length; i++) {
|
|
@@ -10341,6 +10343,10 @@ function extractApiCallsFromSource(source, relFile) {
|
|
|
10341
10343
|
if (/\.(css|svg|png|jpe?g|gif|ico|woff2?|ttf|eot)$/i.test(p)) return false;
|
|
10342
10344
|
return true;
|
|
10343
10345
|
}
|
|
10346
|
+
function concatPath(prefix) {
|
|
10347
|
+
const stripped = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
10348
|
+
return stripped + "/*";
|
|
10349
|
+
}
|
|
10344
10350
|
let match;
|
|
10345
10351
|
while ((match = methodCallRegex.exec(source)) !== null) {
|
|
10346
10352
|
const rawPath = match[3];
|
|
@@ -10394,12 +10400,41 @@ function extractApiCallsFromSource(source, relFile) {
|
|
|
10394
10400
|
snippet: getSnippet(line)
|
|
10395
10401
|
});
|
|
10396
10402
|
}
|
|
10403
|
+
while ((match = concatMethodRegex.exec(source)) !== null) {
|
|
10404
|
+
const rawPrefix = match[3];
|
|
10405
|
+
if (!isApiLike(rawPrefix)) continue;
|
|
10406
|
+
const line = getLineNumber(match.index);
|
|
10407
|
+
calls.push({
|
|
10408
|
+
method: match[1].toUpperCase(),
|
|
10409
|
+
path: concatPath(rawPrefix),
|
|
10410
|
+
file: relFile,
|
|
10411
|
+
line,
|
|
10412
|
+
snippet: getSnippet(line),
|
|
10413
|
+
isConcatPath: true
|
|
10414
|
+
});
|
|
10415
|
+
}
|
|
10416
|
+
while ((match = concatFetchRegex.exec(source)) !== null) {
|
|
10417
|
+
const rawPrefix = match[2];
|
|
10418
|
+
if (!isApiLike(rawPrefix)) continue;
|
|
10419
|
+
const tail = match[3] ?? "";
|
|
10420
|
+
const methodMatch = tail.match(/method\s*:\s*['"`](GET|POST|PUT|PATCH|DELETE)['"`]/i);
|
|
10421
|
+
const line = getLineNumber(match.index);
|
|
10422
|
+
calls.push({
|
|
10423
|
+
method: methodMatch ? methodMatch[1].toUpperCase() : "GET",
|
|
10424
|
+
path: concatPath(rawPrefix),
|
|
10425
|
+
file: relFile,
|
|
10426
|
+
line,
|
|
10427
|
+
snippet: getSnippet(line),
|
|
10428
|
+
isConcatPath: true
|
|
10429
|
+
});
|
|
10430
|
+
}
|
|
10397
10431
|
return calls;
|
|
10398
10432
|
}
|
|
10399
10433
|
function normalizePathSegments(p) {
|
|
10400
10434
|
const withoutQs = p.split("?")[0];
|
|
10401
10435
|
const segments = withoutQs.split("/").filter(Boolean);
|
|
10402
10436
|
return segments.map((seg) => {
|
|
10437
|
+
if (seg === "*") return "*";
|
|
10403
10438
|
if (seg.startsWith(":")) return "*";
|
|
10404
10439
|
if (seg.includes("${") || seg.includes("{{")) return "*";
|
|
10405
10440
|
if (/^\d+$/.test(seg)) return "*";
|
|
@@ -10451,8 +10486,10 @@ async function verifyCrossStackContract(backendDsl, frontendRoot, opts = {}) {
|
|
|
10451
10486
|
const phantom = [];
|
|
10452
10487
|
const methodMismatch = [];
|
|
10453
10488
|
const matched = [];
|
|
10489
|
+
const unknownMethodCalls = [];
|
|
10454
10490
|
const usedEndpointIds = /* @__PURE__ */ new Set();
|
|
10455
10491
|
for (const call of allCalls) {
|
|
10492
|
+
if (call.method === "UNKNOWN") unknownMethodCalls.push(call);
|
|
10456
10493
|
const pathMatches = backendEndpoints.filter((ep) => pathsMatch(ep.path, call.path));
|
|
10457
10494
|
if (pathMatches.length === 0) {
|
|
10458
10495
|
phantom.push(call);
|
|
@@ -10477,7 +10514,9 @@ async function verifyCrossStackContract(backendDsl, frontendRoot, opts = {}) {
|
|
|
10477
10514
|
unused,
|
|
10478
10515
|
methodMismatch,
|
|
10479
10516
|
matched,
|
|
10480
|
-
|
|
10517
|
+
unknownMethodCalls,
|
|
10518
|
+
totalScannedFiles: files.length,
|
|
10519
|
+
hasViolations: phantom.length > 0 || methodMismatch.length > 0
|
|
10481
10520
|
};
|
|
10482
10521
|
}
|
|
10483
10522
|
function printCrossStackReport(repoName, report) {
|
|
@@ -10486,11 +10525,13 @@ function printCrossStackReport(repoName, report) {
|
|
|
10486
10525
|
const phantomCount = report.phantom.length;
|
|
10487
10526
|
const mismatchCount = report.methodMismatch.length;
|
|
10488
10527
|
const unusedCount = report.unused.length;
|
|
10528
|
+
const concatCount = report.frontendCalls.filter((c) => c.isConcatPath).length;
|
|
10529
|
+
const concatNote = concatCount > 0 ? ` (${concatCount} via string concat \u2014 approximate)` : "";
|
|
10489
10530
|
console.log(import_chalk19.default.cyan(`
|
|
10490
10531
|
\u2500\u2500\u2500 Cross-Stack Contract Verification [${repoName}] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
|
|
10491
10532
|
console.log(
|
|
10492
10533
|
import_chalk19.default.gray(
|
|
10493
|
-
` Scanned ${report.totalScannedFiles} file(s), found ${report.frontendCalls.length} HTTP call(s)`
|
|
10534
|
+
` Scanned ${report.totalScannedFiles} file(s), found ${report.frontendCalls.length} HTTP call(s)${concatNote}`
|
|
10494
10535
|
)
|
|
10495
10536
|
);
|
|
10496
10537
|
console.log(import_chalk19.default.gray(` Backend DSL endpoints: ${totalEp}`));
|
|
@@ -10532,7 +10573,22 @@ function printCrossStackReport(repoName, report) {
|
|
|
10532
10573
|
console.log(import_chalk19.default.gray(` ... and ${unusedCount - 8} more`));
|
|
10533
10574
|
}
|
|
10534
10575
|
}
|
|
10535
|
-
if (
|
|
10576
|
+
if (report.unknownMethodCalls.length > 0) {
|
|
10577
|
+
console.log(
|
|
10578
|
+
import_chalk19.default.gray(
|
|
10579
|
+
`
|
|
10580
|
+
\xB7 Unknown method (${report.unknownMethodCalls.length}): HTTP method could not be determined \u2014 matched permissively`
|
|
10581
|
+
)
|
|
10582
|
+
);
|
|
10583
|
+
for (const call of report.unknownMethodCalls.slice(0, 5)) {
|
|
10584
|
+
console.log(import_chalk19.default.gray(` UNKNWN ${call.path}`));
|
|
10585
|
+
console.log(import_chalk19.default.gray(` ${call.file}:${call.line}`));
|
|
10586
|
+
}
|
|
10587
|
+
if (report.unknownMethodCalls.length > 5) {
|
|
10588
|
+
console.log(import_chalk19.default.gray(` ... and ${report.unknownMethodCalls.length - 5} more`));
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
if (!report.hasViolations && unusedCount === 0 && matchedCount === totalEp && totalEp > 0) {
|
|
10536
10592
|
console.log(import_chalk19.default.green(`
|
|
10537
10593
|
\u2714 Contract fully aligned \u2014 all ${totalEp} endpoints consumed correctly.`));
|
|
10538
10594
|
}
|
|
@@ -12012,6 +12068,13 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
12012
12068
|
{ scopedFiles: fe2.generatedFiles }
|
|
12013
12069
|
);
|
|
12014
12070
|
printCrossStackReport(fe2.repoName, report);
|
|
12071
|
+
if (report.hasViolations) {
|
|
12072
|
+
console.log(
|
|
12073
|
+
import_chalk22.default.yellow(
|
|
12074
|
+
` \u26A0 [W5] ${fe2.repoName} has cross-stack violations (${report.phantom.length} phantom, ${report.methodMismatch.length} method mismatch). Review the report above and fix generated frontend code.`
|
|
12075
|
+
)
|
|
12076
|
+
);
|
|
12077
|
+
}
|
|
12015
12078
|
} catch (err) {
|
|
12016
12079
|
console.log(import_chalk22.default.yellow(` \u26A0 Verification failed for ${fe2.repoName}: ${err.message}`));
|
|
12017
12080
|
}
|