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/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.55.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
- totalScannedFiles: files.length
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 (phantomCount === 0 && mismatchCount === 0 && unusedCount === 0 && matchedCount === totalEp && totalEp > 0) {
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
  }