ai-spec-dev 0.55.0 → 0.56.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-spec-dev",
3
- "version": "0.55.0",
3
+ "version": "0.56.0",
4
4
  "description": "AI-driven Development Orchestrator SDK & CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -78,6 +78,41 @@ describe("extractApiCallsFromSource", () => {
78
78
  const calls = extractApiCallsFromSource(src, "a.ts");
79
79
  expect(calls[0].method).toBe("UNKNOWN");
80
80
  });
81
+
82
+ it("extracts axios.get('/api/prefix/' + variable) as concat path", () => {
83
+ const src = `axios.get('/api/users/' + userId)`;
84
+ const calls = extractApiCallsFromSource(src, "a.ts");
85
+ expect(calls).toHaveLength(1);
86
+ expect(calls[0].method).toBe("GET");
87
+ expect(calls[0].isConcatPath).toBe(true);
88
+ // Path should end with /* wildcard
89
+ expect(calls[0].path).toBe("/api/users/*");
90
+ });
91
+
92
+ it("extracts axios.post('/api/prefix/' + variable) as concat path with correct method", () => {
93
+ const src = `axios.post('/api/orders/' + id, body)`;
94
+ const calls = extractApiCallsFromSource(src, "a.ts");
95
+ const concatCall = calls.find((c) => c.isConcatPath);
96
+ expect(concatCall).toBeDefined();
97
+ expect(concatCall!.method).toBe("POST");
98
+ expect(concatCall!.path).toBe("/api/orders/*");
99
+ });
100
+
101
+ it("does NOT double-count full-literal paths as concat", () => {
102
+ // '/api/users/' is the full path (no + follows), should not be marked concat
103
+ const src = `axios.get('/api/users/');`;
104
+ const calls = extractApiCallsFromSource(src, "a.ts");
105
+ expect(calls.every((c) => !c.isConcatPath)).toBe(true);
106
+ });
107
+
108
+ it("extracts fetch('/api/prefix/' + variable) as concat path", () => {
109
+ const src = `fetch('/api/items/' + id, { method: 'DELETE' })`;
110
+ const calls = extractApiCallsFromSource(src, "a.ts");
111
+ const concatCall = calls.find((c) => c.isConcatPath);
112
+ expect(concatCall).toBeDefined();
113
+ expect(concatCall!.method).toBe("DELETE");
114
+ expect(concatCall!.path).toBe("/api/items/*");
115
+ });
81
116
  });
82
117
 
83
118
  // ─── Path normalization & matching ────────────────────────────────────────────
@@ -298,4 +333,70 @@ describe("verifyCrossStackContract", () => {
298
333
  // Empty list is treated as "no scope" → walks whole tree
299
334
  expect(report.matched).toHaveLength(1);
300
335
  });
336
+
337
+ it("hasViolations is false when contract is clean", async () => {
338
+ await fs.writeFile(path.join(tmpDir, "a.ts"), `axios.get('/api/users');`);
339
+ const dsl = buildDsl([{ id: "EP-1", method: "GET", path: "/api/users" }]);
340
+
341
+ const report = await verifyCrossStackContract(dsl, tmpDir);
342
+ expect(report.hasViolations).toBe(false);
343
+ });
344
+
345
+ it("hasViolations is true when there are phantom calls", async () => {
346
+ await fs.writeFile(path.join(tmpDir, "a.ts"), `axios.get('/api/ghost');`);
347
+ const dsl = buildDsl([{ id: "EP-1", method: "GET", path: "/api/users" }]);
348
+
349
+ const report = await verifyCrossStackContract(dsl, tmpDir);
350
+ expect(report.hasViolations).toBe(true);
351
+ });
352
+
353
+ it("hasViolations is true when there are method mismatches", async () => {
354
+ await fs.writeFile(path.join(tmpDir, "a.ts"), `axios.get('/api/users');`);
355
+ const dsl = buildDsl([{ id: "EP-1", method: "POST", path: "/api/users" }]);
356
+
357
+ const report = await verifyCrossStackContract(dsl, tmpDir);
358
+ expect(report.hasViolations).toBe(true);
359
+ expect(report.methodMismatch).toHaveLength(1);
360
+ });
361
+
362
+ it("unknownMethodCalls is populated for UNKNOWN method calls", async () => {
363
+ await fs.writeFile(
364
+ path.join(tmpDir, "a.ts"),
365
+ `request('/api/users'); axios.get('/api/users');`
366
+ );
367
+ const dsl = buildDsl([{ id: "EP-1", method: "GET", path: "/api/users" }]);
368
+
369
+ const report = await verifyCrossStackContract(dsl, tmpDir);
370
+ expect(report.unknownMethodCalls).toHaveLength(1);
371
+ expect(report.unknownMethodCalls[0].method).toBe("UNKNOWN");
372
+ // UNKNOWN is matched permissively — not a violation
373
+ expect(report.hasViolations).toBe(false);
374
+ });
375
+
376
+ it("matches concat path axios.get('/api/users/' + id) against DSL /api/users/:id", async () => {
377
+ await fs.writeFile(
378
+ path.join(tmpDir, "a.ts"),
379
+ "axios.get('/api/users/' + userId);"
380
+ );
381
+ const dsl = buildDsl([{ id: "EP-1", method: "GET", path: "/api/users/:id" }]);
382
+
383
+ const report = await verifyCrossStackContract(dsl, tmpDir);
384
+ expect(report.phantom).toHaveLength(0);
385
+ expect(report.matched).toHaveLength(1);
386
+ expect(report.matched[0].call.isConcatPath).toBe(true);
387
+ expect(report.hasViolations).toBe(false);
388
+ });
389
+
390
+ it("flags concat path as phantom when no DSL endpoint matches the static prefix", async () => {
391
+ await fs.writeFile(
392
+ path.join(tmpDir, "a.ts"),
393
+ "axios.get('/api/ghost/' + id);"
394
+ );
395
+ const dsl = buildDsl([{ id: "EP-1", method: "GET", path: "/api/users/:id" }]);
396
+
397
+ const report = await verifyCrossStackContract(dsl, tmpDir);
398
+ expect(report.phantom).toHaveLength(1);
399
+ expect(report.phantom[0].isConcatPath).toBe(true);
400
+ expect(report.hasViolations).toBe(true);
401
+ });
301
402
  });
@@ -1,17 +0,0 @@
1
- {
2
- "name": "bookmark-demo",
3
- "repos": [
4
- {
5
- "name": "demo-backend",
6
- "path": "demo-backend",
7
- "type": "node-express",
8
- "role": "backend"
9
- },
10
- {
11
- "name": "demo-frontend",
12
- "path": "demo-frontend",
13
- "type": "react",
14
- "role": "frontend"
15
- }
16
- ]
17
- }
package/.ai-spec.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "provider": "glm",
3
- "model": "glm-4.5-air",
4
- "codegenMode": "api",
5
- "codegenProvider": "glm",
6
- "codegenModel": "glm-4.5-air"
7
- }