ai-spec-dev 0.33.0 → 0.36.1
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/.claude/commands/add-lesson.md +34 -0
- package/.claude/commands/check-layers.md +65 -0
- package/.claude/commands/installed-deps.md +35 -0
- package/.claude/commands/recall-lessons.md +40 -0
- package/.claude/commands/scan-singletons.md +45 -0
- package/.claude/commands/verify-imports.md +48 -0
- package/.claude/settings.local.json +11 -1
- package/README.md +531 -213
- package/RELEASE_LOG.md +424 -0
- package/cli/commands/config.ts +18 -0
- package/cli/commands/create.ts +1248 -0
- package/cli/commands/dashboard.ts +62 -0
- package/cli/commands/init.ts +45 -8
- package/cli/commands/mock.ts +175 -0
- package/cli/commands/scan.ts +99 -0
- package/cli/commands/types.ts +69 -0
- package/cli/commands/vcr.ts +70 -0
- package/cli/index.ts +34 -2517
- package/cli/utils.ts +4 -0
- package/core/code-generator.ts +6 -4
- package/core/combined-generator.ts +13 -3
- package/core/dashboard-generator.ts +340 -0
- package/core/design-dialogue.ts +124 -0
- package/core/dsl-extractor.ts +9 -1
- package/core/dsl-feedback.ts +41 -5
- package/core/dsl-validator.ts +32 -0
- package/core/error-feedback.ts +46 -2
- package/core/key-store.ts +5 -4
- package/core/project-index.ts +301 -0
- package/core/provider-utils.ts +39 -4
- package/core/reviewer.ts +84 -6
- package/core/run-logger.ts +109 -3
- package/core/run-trend.ts +24 -4
- package/core/self-evaluator.ts +39 -11
- package/core/spec-generator.ts +14 -8
- package/core/task-generator.ts +17 -0
- package/core/types-generator.ts +219 -0
- package/core/vcr.ts +210 -0
- package/dist/cli/index.js +7407 -5643
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +7401 -5637
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +34 -5
- package/dist/index.d.ts +34 -5
- package/dist/index.js +497 -232
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +495 -233
- package/dist/index.mjs.map +1 -1
- package/docs-assets/purpose/architecture-overview.svg +64 -0
- package/docs-assets/purpose/create-pipeline.svg +113 -0
- package/docs-assets/purpose/task-layering.svg +74 -0
- package/package.json +1 -1
- package/prompts/codegen.prompt.ts +97 -9
- package/prompts/design.prompt.ts +59 -0
- package/prompts/spec.prompt.ts +8 -1
- package/prompts/tasks.prompt.ts +27 -2
- package/purpose.md +600 -174
- package/tests/code-generator.test.ts +253 -0
- package/tests/context-loader.test.ts +207 -0
- package/tests/dsl-validator.test.ts +105 -0
- package/tests/openapi-exporter.test.ts +310 -0
- package/tests/reviewer.test.ts +214 -0
- package/tests/spec-generator.test.ts +228 -0
- package/tests/spec-versioning.test.ts +205 -0
package/dist/index.js
CHANGED
|
@@ -50,6 +50,9 @@ __export(index_exports, {
|
|
|
50
50
|
TaskGenerator: () => TaskGenerator,
|
|
51
51
|
buildTaskPrompt: () => buildTaskPrompt,
|
|
52
52
|
createProvider: () => createProvider,
|
|
53
|
+
extractBehavioralContract: () => extractBehavioralContract,
|
|
54
|
+
extractComplianceScore: () => extractComplianceScore,
|
|
55
|
+
extractMissingCount: () => extractMissingCount,
|
|
53
56
|
generateSpecWithTasks: () => generateSpecWithTasks,
|
|
54
57
|
isFrontendDeps: () => isFrontendDeps,
|
|
55
58
|
loadConstitution: () => loadConstitution,
|
|
@@ -170,7 +173,14 @@ model ExampleModel {
|
|
|
170
173
|
|
|
171
174
|
---
|
|
172
175
|
|
|
173
|
-
\u6839\u636E\u7528\u6237\u7684\u60F3\u6CD5\u548C\u9879\u76EE\u4E0A\u4E0B\u6587\u751F\u6210\u4E0A\u8FF0\u5B8C\u6574 Spec\u3002\u786E\u4FDD API \u8BBE\u8BA1\u4E0E\u73B0\u6709\u9879\u76EE\u7684\u8DEF\u7531\u98CE\u683C\u3001\u9519\u8BEF\u7801\u89C4\u8303\u4FDD\u6301\u4E00\u81F4\uFF0C\u6570\u636E\u6A21\u578B\u4E0E\u73B0\u6709 Prisma Schema \u534F\u8C03\u3002
|
|
176
|
+
\u6839\u636E\u7528\u6237\u7684\u60F3\u6CD5\u548C\u9879\u76EE\u4E0A\u4E0B\u6587\u751F\u6210\u4E0A\u8FF0\u5B8C\u6574 Spec\u3002\u786E\u4FDD API \u8BBE\u8BA1\u4E0E\u73B0\u6709\u9879\u76EE\u7684\u8DEF\u7531\u98CE\u683C\u3001\u9519\u8BEF\u7801\u89C4\u8303\u4FDD\u6301\u4E00\u81F4\uFF0C\u6570\u636E\u6A21\u578B\u4E0E\u73B0\u6709 Prisma Schema \u534F\u8C03\u3002
|
|
177
|
+
|
|
178
|
+
CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uFF09\uFF1A
|
|
179
|
+
\u5982\u679C\u9879\u76EE\u5BAA\u6CD5\u4E2D\u5305\u542B"\xA79 \u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons)"\u7AE0\u8282\uFF0C\u4F60\u5FC5\u987B\uFF1A
|
|
180
|
+
1. \u5728\u751F\u6210 \xA75 API \u8BBE\u8BA1\u548C \xA76 \u6570\u636E\u6A21\u578B\u4E4B\u524D\uFF0C\u9010\u6761\u5BA1\u9605\u6240\u6709\u6559\u8BAD\u6761\u76EE
|
|
181
|
+
2. \u786E\u4FDD\u672C\u6B21 Spec \u7684\u8BBE\u8BA1\u4E0D\u91CD\u8E48\u5DF2\u77E5\u95EE\u9898\uFF08\u4F8B\u5982\uFF1A\u67D0\u6559\u8BAD\u8BF4"\u907F\u514D N+1 \u67E5\u8BE2"\uFF0C\u5219\u5728 \xA78 \u5B9E\u65BD\u8981\u70B9\u4E2D\u660E\u786E\u8BF4\u660E\u6279\u91CF\u52A0\u8F7D\u7B56\u7565\uFF09
|
|
182
|
+
3. \u5BF9\u4E8E\u6BCF\u6761\u76F4\u63A5\u76F8\u5173\u7684\u6559\u8BAD\uFF0C\u5728 \xA78 \u5B9E\u65BD\u8981\u70B9\u672B\u5C3E\u8FFD\u52A0\u4E00\u884C\uFF1A\u300C\u26A0 \u57FA\u4E8E\u5386\u53F2\u6559\u8BAD\uFF1A[\u7B80\u8FF0\u672C\u6B21 spec \u5982\u4F55\u89C4\u907F\u8BE5\u95EE\u9898]\u300D
|
|
183
|
+
4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
|
|
174
184
|
|
|
175
185
|
// core/provider-utils.ts
|
|
176
186
|
var import_chalk = __toESM(require("chalk"));
|
|
@@ -187,14 +197,49 @@ function classifyError(err, label) {
|
|
|
187
197
|
const e = err;
|
|
188
198
|
const status = e.status ?? e.response?.status;
|
|
189
199
|
if (status === 401 || status === 403)
|
|
190
|
-
return new ProviderError(
|
|
200
|
+
return new ProviderError(
|
|
201
|
+
`Auth error (${label}): API key is invalid or expired.
|
|
202
|
+
\u2192 Check that the correct API key is set in your environment or ~/.ai-spec-keys.json
|
|
203
|
+
\u2192 Run "ai-spec model" to reconfigure your provider and key`,
|
|
204
|
+
"auth",
|
|
205
|
+
err
|
|
206
|
+
);
|
|
191
207
|
if (status === 429)
|
|
192
|
-
return new ProviderError(
|
|
208
|
+
return new ProviderError(
|
|
209
|
+
`Rate limit hit (${label}): too many requests.
|
|
210
|
+
\u2192 Wait a few minutes and retry, or switch to a different provider/model
|
|
211
|
+
\u2192 Check your provider's billing dashboard for quota status`,
|
|
212
|
+
"rate_limit",
|
|
213
|
+
err
|
|
214
|
+
);
|
|
193
215
|
if (e._timeout || e.message?.toLowerCase().includes("timed out"))
|
|
194
216
|
return new ProviderError(`Request timed out (${label})`, "timeout", err);
|
|
195
217
|
if (e.code === "ECONNRESET" || e.code === "ENOTFOUND" || e.code === "ECONNREFUSED")
|
|
196
|
-
return new ProviderError(
|
|
197
|
-
|
|
218
|
+
return new ProviderError(
|
|
219
|
+
`Network error (${label}): ${e.message}
|
|
220
|
+
\u2192 Check your internet connection and proxy settings (HTTPS_PROXY)
|
|
221
|
+
\u2192 If behind a firewall, ensure the provider's API endpoint is reachable`,
|
|
222
|
+
"network",
|
|
223
|
+
err
|
|
224
|
+
);
|
|
225
|
+
const msg = e.message ?? "";
|
|
226
|
+
if (status === 404 || msg.includes("model") && (msg.includes("not found") || msg.includes("does not exist")))
|
|
227
|
+
return new ProviderError(
|
|
228
|
+
`Model not found (${label}): ${msg}
|
|
229
|
+
\u2192 Run "ai-spec model" to see available models for your provider
|
|
230
|
+
\u2192 The model name may have changed \u2014 check your provider's documentation`,
|
|
231
|
+
"provider",
|
|
232
|
+
err
|
|
233
|
+
);
|
|
234
|
+
if (msg.includes("insufficient") || msg.includes("quota") || msg.includes("balance"))
|
|
235
|
+
return new ProviderError(
|
|
236
|
+
`Quota/balance error (${label}): ${msg}
|
|
237
|
+
\u2192 Check your provider's billing dashboard
|
|
238
|
+
\u2192 Consider switching to a different provider with "ai-spec model"`,
|
|
239
|
+
"provider",
|
|
240
|
+
err
|
|
241
|
+
);
|
|
242
|
+
return new ProviderError(`Provider error (${label}): ${msg}`, "provider", err);
|
|
198
243
|
}
|
|
199
244
|
function isRetryable(err) {
|
|
200
245
|
const e = err;
|
|
@@ -321,17 +366,23 @@ var PROVIDER_CATALOG = {
|
|
|
321
366
|
},
|
|
322
367
|
glm: {
|
|
323
368
|
displayName: "\u667A\u8C31 GLM (Zhipu AI)",
|
|
324
|
-
description: "\u667A\u8C31 AI \u2014 GLM-5 / GLM-4 series
|
|
369
|
+
description: "\u667A\u8C31 AI \u2014 GLM-5.1 / GLM-5 / GLM-4 series",
|
|
325
370
|
models: [
|
|
371
|
+
"glm-5.1",
|
|
372
|
+
// GLM-5.1 — latest flagship (2026)
|
|
326
373
|
"glm-5",
|
|
327
|
-
// GLM-5
|
|
328
|
-
"glm-5-
|
|
374
|
+
// GLM-5 — premium (Max/Pro plans)
|
|
375
|
+
"glm-5-turbo",
|
|
376
|
+
// GLM-5-Turbo — fast & cost-efficient
|
|
377
|
+
"glm-4.7",
|
|
378
|
+
// GLM-4.7
|
|
379
|
+
"glm-4.6",
|
|
380
|
+
// GLM-4.6
|
|
381
|
+
"glm-4.5-air",
|
|
382
|
+
// GLM-4.5-Air — lightweight
|
|
329
383
|
"glm-z1",
|
|
330
|
-
// GLM-Z1 reasoning model
|
|
331
|
-
"glm-z1-flash"
|
|
332
|
-
"glm-4-plus",
|
|
333
|
-
"glm-4-flash",
|
|
334
|
-
"glm-4-long"
|
|
384
|
+
// GLM-Z1 — reasoning model
|
|
385
|
+
"glm-z1-flash"
|
|
335
386
|
],
|
|
336
387
|
envKey: "ZHIPU_API_KEY",
|
|
337
388
|
baseURL: "https://open.bigmodel.cn/api/paas/v4/"
|
|
@@ -527,8 +578,15 @@ var SpecGenerator = class {
|
|
|
527
578
|
constructor(provider) {
|
|
528
579
|
this.provider = provider;
|
|
529
580
|
}
|
|
530
|
-
async generateSpec(idea, context) {
|
|
581
|
+
async generateSpec(idea, context, architectureDecision) {
|
|
531
582
|
const parts = [idea];
|
|
583
|
+
if (architectureDecision) {
|
|
584
|
+
parts.push(
|
|
585
|
+
`
|
|
586
|
+
=== Architecture Decision (MUST follow this approach in the spec) ===
|
|
587
|
+
${architectureDecision}`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
532
590
|
if (context) {
|
|
533
591
|
if (context.constitution) {
|
|
534
592
|
parts.push(
|
|
@@ -4283,34 +4341,110 @@ function getCodeGenSystemPrompt(repoType) {
|
|
|
4283
4341
|
return codeGenSystemPrompt;
|
|
4284
4342
|
}
|
|
4285
4343
|
}
|
|
4286
|
-
var
|
|
4344
|
+
var specComplianceSystemPrompt = `You are a QA Engineer performing a SPEC COMPLIANCE CHECK.
|
|
4345
|
+
|
|
4346
|
+
Your sole job is to verify that the implementation covers every requirement stated in the feature spec.
|
|
4347
|
+
This is a completeness audit \u2014 NOT a code quality review. Do not comment on architecture, style, or implementation details.
|
|
4348
|
+
|
|
4349
|
+
## How to audit:
|
|
4350
|
+
|
|
4351
|
+
1. Parse the spec and extract EVERY stated requirement into these categories:
|
|
4352
|
+
- **Endpoints**: each HTTP method + path listed or implied
|
|
4353
|
+
- **Data Models**: each entity, field, constraint mentioned
|
|
4354
|
+
- **Business Rules**: validations, conditions, calculations stated
|
|
4355
|
+
- **Auth Requirements**: which endpoints need auth, which roles are allowed
|
|
4356
|
+
- **Error Cases**: explicit error codes or failure scenarios mentioned
|
|
4357
|
+
- **Side Effects**: emails sent, events fired, caches invalidated, etc.
|
|
4358
|
+
|
|
4359
|
+
2. For each extracted requirement, check the provided code:
|
|
4360
|
+
- \u2705 **Covered** \u2014 clearly implemented
|
|
4361
|
+
- \u26A0\uFE0F **Partial** \u2014 exists but incomplete (e.g. endpoint exists but missing a field or error case)
|
|
4362
|
+
- \u274C **Missing** \u2014 requirement stated in spec but not found in code
|
|
4363
|
+
|
|
4364
|
+
3. Output a compliance checklist. Be exhaustive \u2014 list every single requirement.
|
|
4365
|
+
|
|
4366
|
+
## Output format:
|
|
4367
|
+
|
|
4368
|
+
## \u{1F4CB} Spec Compliance Report
|
|
4369
|
+
|
|
4370
|
+
### Endpoints
|
|
4371
|
+
\u2705 / \u26A0\uFE0F / \u274C METHOD /path \u2014 one-line status
|
|
4372
|
+
|
|
4373
|
+
### Data Models
|
|
4374
|
+
\u2705 / \u26A0\uFE0F / \u274C ModelName \u2014 one-line status
|
|
4375
|
+
|
|
4376
|
+
### Business Rules
|
|
4377
|
+
\u2705 / \u26A0\uFE0F / \u274C Rule description \u2014 one-line status
|
|
4378
|
+
|
|
4379
|
+
### Auth & Permissions
|
|
4380
|
+
\u2705 / \u26A0\uFE0F / \u274C Requirement \u2014 one-line status
|
|
4381
|
+
|
|
4382
|
+
### Error Cases
|
|
4383
|
+
\u2705 / \u26A0\uFE0F / \u274C Error scenario \u2014 one-line status
|
|
4384
|
+
|
|
4385
|
+
### Side Effects
|
|
4386
|
+
\u2705 / \u26A0\uFE0F / \u274C Side effect \u2014 one-line status (omit section if none in spec)
|
|
4387
|
+
|
|
4388
|
+
---
|
|
4389
|
+
|
|
4390
|
+
## \u{1F4CA} Compliance Summary
|
|
4391
|
+
Covered: N | Partial: N | Missing: N | Total: N
|
|
4392
|
+
|
|
4393
|
+
## \u{1F522} Compliance Score
|
|
4394
|
+
ComplianceScore: X/10
|
|
4395
|
+
|
|
4396
|
+
(10 = all requirements implemented, 0 = nothing implemented.
|
|
4397
|
+
Deduct 1 point per missing requirement, 0.5 per partial.
|
|
4398
|
+
Round to nearest integer.)
|
|
4399
|
+
|
|
4400
|
+
## \u{1F6A8} Blockers (Missing requirements that MUST be implemented before ship)
|
|
4401
|
+
List only \u274C Missing items here, ordered by severity. If none, write "None".
|
|
4402
|
+
|
|
4403
|
+
---
|
|
4404
|
+
|
|
4405
|
+
IMPORTANT: Be exhaustive. A requirement not listed here is assumed to be covered.
|
|
4406
|
+
If the spec is vague, note the ambiguity as \u26A0\uFE0F Partial rather than assuming coverage.`;
|
|
4407
|
+
var reviewArchitectureSystemPrompt = `You are a Senior Software Architect reviewing the HIGH-LEVEL DESIGN of a code change.
|
|
4408
|
+
|
|
4409
|
+
A spec compliance check (Pass 0) has already verified feature completeness. Do NOT re-audit whether requirements are missing \u2014 focus purely on HOW the present implementation is architected.
|
|
4287
4410
|
|
|
4288
4411
|
Focus ONLY on:
|
|
4289
|
-
1. **
|
|
4290
|
-
2. **
|
|
4291
|
-
3. **
|
|
4292
|
-
4. **
|
|
4293
|
-
5. **Security posture** \u2014 Are auth checks applied to the right endpoints? Any obvious missing auth?
|
|
4412
|
+
1. **Layer separation** \u2014 Does each layer have the right responsibilities? (e.g., no business logic in controllers, no HTTP in stores)
|
|
4413
|
+
2. **API contract quality** \u2014 Are request/response shapes well-designed? Are error codes consistent with project conventions?
|
|
4414
|
+
3. **Data model integrity** \u2014 Are constraints, unique fields, and relationships modelled correctly?
|
|
4415
|
+
4. **Security posture** \u2014 Are auth checks applied correctly? Any privilege escalation risks?
|
|
4294
4416
|
|
|
4295
4417
|
DO NOT comment on:
|
|
4418
|
+
- Whether specific endpoints or features are missing (covered by Pass 0)
|
|
4296
4419
|
- Code style, naming conventions, formatting
|
|
4297
4420
|
- Minor implementation details (variable names, inline comments)
|
|
4298
4421
|
- Performance micro-optimizations
|
|
4299
4422
|
|
|
4300
4423
|
Format:
|
|
4301
4424
|
|
|
4302
|
-
## \u{1F3D7} \u67B6\u6784\u5408\u89C4\u6027 (Spec Compliance)
|
|
4303
|
-
Does the implementation match the spec? List any missing or wrong endpoints/components.
|
|
4304
|
-
|
|
4305
4425
|
## \u{1F500} \u5C42\u804C\u8D23\u5206\u79BB (Layer Separation)
|
|
4306
4426
|
Any layer boundary violations?
|
|
4307
4427
|
|
|
4308
4428
|
## \u{1F512} \u5B89\u5168\u4E0E\u6743\u9650 (Security & Auth)
|
|
4309
4429
|
Any missing auth checks, exposed data, or privilege issues?
|
|
4310
4430
|
|
|
4431
|
+
## \u{1F4D0} \u5951\u7EA6\u4E0E\u6A21\u578B\u8BBE\u8BA1 (Contract & Model Design)
|
|
4432
|
+
Response shape issues, missing constraints, relationship problems.
|
|
4433
|
+
|
|
4311
4434
|
## \u{1F4CB} \u67B6\u6784\u8BC4\u5206 (Architecture Score)
|
|
4312
4435
|
Score: X/10 \u2014 One short paragraph.
|
|
4313
4436
|
|
|
4437
|
+
## \u{1F50D} \u7ED3\u6784\u6027\u53D1\u73B0 JSON (Structural Findings \u2014 for pipeline processing)
|
|
4438
|
+
Output a JSON block with any design-level issues found above.
|
|
4439
|
+
Categories: "auth_design" | "api_contract" | "model_design" | "layer_violation" | "other_design"
|
|
4440
|
+
If no findings, output an empty array.
|
|
4441
|
+
|
|
4442
|
+
\`\`\`json
|
|
4443
|
+
{"structuralFindings": [{"category": "...", "description": "one sentence referencing the specific endpoint/model/file"}]}
|
|
4444
|
+
\`\`\`
|
|
4445
|
+
|
|
4446
|
+
IMPORTANT: Always include this JSON block, even when structuralFindings is []. This block is parsed by the pipeline.
|
|
4447
|
+
|
|
4314
4448
|
Be specific. Reference file names or endpoint paths.`;
|
|
4315
4449
|
var reviewImplementationSystemPrompt = `You are a Senior Engineer reviewing the IMPLEMENTATION DETAILS of a code change.
|
|
4316
4450
|
|
|
@@ -4403,7 +4537,8 @@ Each task object must have these exact fields:
|
|
|
4403
4537
|
"description": "...", // 1-2 sentences, specific and actionable
|
|
4404
4538
|
"layer": "data|service|api|view|route|test|infra", // implementation layer
|
|
4405
4539
|
"filesToTouch": ["..."], // VERIFIED paths only \u2014 see rules below
|
|
4406
|
-
"acceptanceCriteria": ["..."], //
|
|
4540
|
+
"acceptanceCriteria": ["..."], // behavioral completion conditions (the "what")
|
|
4541
|
+
"verificationSteps": ["..."], // concrete runnable checks with expected output (the "how to verify") \u2014 see rules below
|
|
4407
4542
|
"dependencies": ["TASK-001"], // task ids that must complete first (empty array if none)
|
|
4408
4543
|
"priority": "high|medium|low"
|
|
4409
4544
|
}
|
|
@@ -4445,8 +4580,32 @@ CRITICAL \u2014 filesToTouch Rules (hallucination prevention):
|
|
|
4445
4580
|
- If you are unsure of the exact path for a new file, leave it as "TBD:<description>" rather than guessing.
|
|
4446
4581
|
- Cross-check: after writing all tasks, verify every path in filesToTouch exists in the inventory or is a logical new sibling. If it doesn't pass this check, fix it.
|
|
4447
4582
|
|
|
4583
|
+
CRITICAL \u2014 verificationSteps Rules:
|
|
4584
|
+
Each step must be a concrete, self-contained check with an observable expected outcome.
|
|
4585
|
+
|
|
4586
|
+
Good examples (specific command + expected result):
|
|
4587
|
+
"POST /api/tasks with body {"title":"test"} \u2192 HTTP 201, response body contains {id, status:"pending"}"
|
|
4588
|
+
"GET /api/tasks/:id with unknown id \u2192 HTTP 404 with {code: 4040X, message: "..."}"
|
|
4589
|
+
"npm run build exits 0 with no TypeScript errors"
|
|
4590
|
+
"Prisma schema has Task model with fields: id, title, status, createdAt"
|
|
4591
|
+
"Store action createTask sets loading:true during request, loading:false on completion"
|
|
4592
|
+
"Route /tasks renders TaskList component, visible in router DevTools"
|
|
4593
|
+
|
|
4594
|
+
Bad examples (too vague \u2014 do NOT use these):
|
|
4595
|
+
"The endpoint works correctly" \u2717
|
|
4596
|
+
"Data is saved to the database" \u2717
|
|
4597
|
+
"UI displays the correct data" \u2717
|
|
4598
|
+
"Error handling works" \u2717
|
|
4599
|
+
|
|
4600
|
+
Rules:
|
|
4601
|
+
- At least 2 verification steps per task, max 5
|
|
4602
|
+
- Each step must be independently runnable/checkable
|
|
4603
|
+
- Backend tasks: include at least one HTTP request/response check and one data-layer check
|
|
4604
|
+
- Frontend tasks: include at least one UI render check and one state check
|
|
4605
|
+
- Build/compile tasks: always include "npm run build exits 0" or equivalent
|
|
4606
|
+
|
|
4448
4607
|
Other rules:
|
|
4449
|
-
- acceptanceCriteria
|
|
4608
|
+
- acceptanceCriteria: behavioral statements ("order is created with pending status") \u2014 complementary to verificationSteps, not duplicates
|
|
4450
4609
|
- dependencies must reflect real implementation order
|
|
4451
4610
|
- Aim for 4-10 tasks total \u2014 not too granular, not too coarse
|
|
4452
4611
|
- Each task should be completable in one focused coding session`;
|
|
@@ -4556,6 +4715,14 @@ function printTasks(tasks) {
|
|
|
4556
4715
|
const badge = color(`[${task.layer}]`);
|
|
4557
4716
|
const prio = task.priority === "high" ? import_chalk4.default.red("\u25CF") : task.priority === "medium" ? import_chalk4.default.yellow("\u25CF") : import_chalk4.default.gray("\u25CF");
|
|
4558
4717
|
console.log(` ${prio} ${import_chalk4.default.bold(task.id)} ${badge} ${task.title}`);
|
|
4718
|
+
if (task.verificationSteps?.length) {
|
|
4719
|
+
for (const step of task.verificationSteps.slice(0, 2)) {
|
|
4720
|
+
console.log(import_chalk4.default.gray(` \u2713 ${step}`));
|
|
4721
|
+
}
|
|
4722
|
+
if (task.verificationSteps.length > 2) {
|
|
4723
|
+
console.log(import_chalk4.default.gray(` + ${task.verificationSteps.length - 2} more verification step(s)`));
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4559
4726
|
}
|
|
4560
4727
|
}
|
|
4561
4728
|
async function loadTasksForSpec(specFilePath) {
|
|
@@ -4634,6 +4801,21 @@ function validateDsl(raw) {
|
|
|
4634
4801
|
for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
|
|
4635
4802
|
validateEndpoint(eps[i], `endpoints[${i}]`, errors);
|
|
4636
4803
|
}
|
|
4804
|
+
const seenEpIds = /* @__PURE__ */ new Set();
|
|
4805
|
+
for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
|
|
4806
|
+
const ep = eps[i];
|
|
4807
|
+
if (ep && typeof ep === "object" && typeof ep["id"] === "string") {
|
|
4808
|
+
const id = ep["id"];
|
|
4809
|
+
if (seenEpIds.has(id)) {
|
|
4810
|
+
errors.push({
|
|
4811
|
+
path: `endpoints[${i}].id`,
|
|
4812
|
+
message: `Duplicate endpoint id "${id}" \u2014 each endpoint must have a unique id`
|
|
4813
|
+
});
|
|
4814
|
+
} else {
|
|
4815
|
+
seenEpIds.add(id);
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4637
4819
|
}
|
|
4638
4820
|
if (obj["behaviors"] !== void 0) {
|
|
4639
4821
|
if (!Array.isArray(obj["behaviors"])) {
|
|
@@ -4690,6 +4872,21 @@ function validateModel(raw, path10, errors) {
|
|
|
4690
4872
|
for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
|
|
4691
4873
|
validateModelField(fields[j2], `${path10}.fields[${j2}]`, errors);
|
|
4692
4874
|
}
|
|
4875
|
+
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
4876
|
+
for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
|
|
4877
|
+
const f = fields[j2];
|
|
4878
|
+
if (f && typeof f === "object" && typeof f["name"] === "string") {
|
|
4879
|
+
const name = f["name"];
|
|
4880
|
+
if (seenFieldNames.has(name)) {
|
|
4881
|
+
errors.push({
|
|
4882
|
+
path: `${path10}.fields[${j2}].name`,
|
|
4883
|
+
message: `Duplicate field name "${name}" \u2014 each field within a model must have a unique name`
|
|
4884
|
+
});
|
|
4885
|
+
} else {
|
|
4886
|
+
seenFieldNames.add(name);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4693
4890
|
}
|
|
4694
4891
|
if (m["relations"] !== void 0) {
|
|
4695
4892
|
if (!Array.isArray(m["relations"])) {
|
|
@@ -5720,9 +5917,10 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
|
|
|
5720
5917
|
console.log(import_chalk8.default.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
|
|
5721
5918
|
console.log(import_chalk8.default.gray(` Spec: ${specFilePath}`));
|
|
5722
5919
|
try {
|
|
5723
|
-
(0, import_child_process.
|
|
5920
|
+
(0, import_child_process.spawnSync)(claudeCmd, ["-p", promptContent], {
|
|
5724
5921
|
cwd: workingDir,
|
|
5725
|
-
stdio: "inherit"
|
|
5922
|
+
stdio: "inherit",
|
|
5923
|
+
shell: false
|
|
5726
5924
|
});
|
|
5727
5925
|
console.log(import_chalk8.default.green("\n \u2714 Claude Code completed."));
|
|
5728
5926
|
} catch {
|
|
@@ -5774,9 +5972,10 @@ Full spec is at: ${specFilePath}
|
|
|
5774
5972
|
Implement ONLY this task. Do not implement other tasks.`;
|
|
5775
5973
|
let taskStatus = "done";
|
|
5776
5974
|
try {
|
|
5777
|
-
(0, import_child_process.
|
|
5975
|
+
(0, import_child_process.spawnSync)(claudeCmd, ["-p", taskPrompt], {
|
|
5778
5976
|
cwd: workingDir,
|
|
5779
|
-
stdio: "inherit"
|
|
5977
|
+
stdio: "inherit",
|
|
5978
|
+
shell: false
|
|
5780
5979
|
});
|
|
5781
5980
|
completed++;
|
|
5782
5981
|
} catch {
|
|
@@ -6179,27 +6378,202 @@ function printTaskProgress(completed, total, task, mode) {
|
|
|
6179
6378
|
}
|
|
6180
6379
|
|
|
6181
6380
|
// core/reviewer.ts
|
|
6182
|
-
var
|
|
6381
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
6183
6382
|
var import_child_process2 = require("child_process");
|
|
6184
|
-
var
|
|
6383
|
+
var path8 = __toESM(require("path"));
|
|
6384
|
+
var fs12 = __toESM(require("fs-extra"));
|
|
6385
|
+
|
|
6386
|
+
// core/constitution-generator.ts
|
|
6387
|
+
var import_chalk9 = __toESM(require("chalk"));
|
|
6185
6388
|
var fs11 = __toESM(require("fs-extra"));
|
|
6389
|
+
var path7 = __toESM(require("path"));
|
|
6390
|
+
|
|
6391
|
+
// prompts/constitution.prompt.ts
|
|
6392
|
+
var constitutionSystemPrompt = `You are a Senior Software Architect. Analyze the provided project codebase context and generate a concise "Project Constitution" \u2014 a living document that captures the architectural rules, conventions, and red lines that ALL future feature specs and code generation MUST follow.
|
|
6393
|
+
|
|
6394
|
+
Output a Markdown document with EXACTLY these sections. Be specific and derive rules directly from the observed codebase \u2014 no generic advice.
|
|
6395
|
+
|
|
6396
|
+
---
|
|
6397
|
+
|
|
6398
|
+
# Project Constitution
|
|
6399
|
+
|
|
6400
|
+
## 1. \u67B6\u6784\u89C4\u5219 (Architecture Rules)
|
|
6401
|
+
\u5217\u51FA\u9879\u76EE\u7684\u6838\u5FC3\u67B6\u6784\u6A21\u5F0F\u548C\u5F3A\u5236\u7EA6\u675F\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF0C\u800C\u975E\u901A\u7528\u5EFA\u8BAE\uFF09\u3002
|
|
6402
|
+
- \u5206\u5C42\u67B6\u6784\u89C4\u5219\uFF08\u5982\uFF1Aroutes \u2192 controllers \u2192 services \u2192 DB\uFF09
|
|
6403
|
+
- \u7981\u6B62\u8DE8\u5C42\u76F4\u63A5\u8C03\u7528\u7684\u89C4\u5219
|
|
6404
|
+
- \u6A21\u5757\u7EC4\u7EC7\u89C4\u8303
|
|
6405
|
+
|
|
6406
|
+
## 2. \u547D\u540D\u89C4\u8303 (Naming Conventions)
|
|
6407
|
+
- \u6587\u4EF6\u547D\u540D\u89C4\u5219\uFF08\u9A7C\u5CF0/\u4E0B\u5212\u7EBF/kebab\uFF09
|
|
6408
|
+
- \u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u7684\u547D\u540D\u6A21\u5F0F
|
|
6409
|
+
- \u8DEF\u7531\u8DEF\u5F84\u7684\u547D\u540D\u89C4\u8303
|
|
6410
|
+
|
|
6411
|
+
## 3. API \u89C4\u8303 (API Patterns)
|
|
6412
|
+
- \u8DEF\u7531\u524D\u7F00\u89C4\u5219\uFF08\u5982 /api/v1/client/... vs /api/v1/admin/...\uFF09
|
|
6413
|
+
- \u7EDF\u4E00\u54CD\u5E94\u7ED3\u6784\u6A21\u677F\uFF08code/message/data \u683C\u5F0F\uFF09
|
|
6414
|
+
- \u9519\u8BEF\u7801\u89C4\u8303\uFF08\u5DF2\u6709\u7684\u9519\u8BEF\u7801\u8303\u56F4\u548C\u542B\u4E49\uFF09
|
|
6415
|
+
- \u8BA4\u8BC1/\u9274\u6743\u6A21\u5F0F\uFF08middleware \u540D\u79F0\u548C\u4F7F\u7528\u4F4D\u7F6E\uFF09
|
|
6416
|
+
|
|
6417
|
+
## 4. \u6570\u636E\u5C42\u89C4\u8303 (Data Layer Rules)
|
|
6418
|
+
- ORM/\u6570\u636E\u5E93\u8BBF\u95EE\u89C4\u5219\uFF08\u4EC5\u901A\u8FC7 service \u5C42\u8BBF\u95EE\uFF1F\u76F4\u63A5\u7528 Prisma/Mongoose\uFF1F\uFF09
|
|
6419
|
+
- \u5DF2\u6709\u7684\u6570\u636E\u6A21\u578B\u547D\u540D\u89C4\u8303
|
|
6420
|
+
- \u4E8B\u52A1\u5904\u7406\u6A21\u5F0F
|
|
6421
|
+
|
|
6422
|
+
## 5. \u9519\u8BEF\u5904\u7406\u89C4\u8303 (Error Handling Patterns)
|
|
6423
|
+
- \u7EDF\u4E00\u9519\u8BEF\u5904\u7406 middleware \u7684\u4F7F\u7528\u89C4\u5219
|
|
6424
|
+
- \u9519\u8BEF\u629B\u51FA\u548C\u6355\u83B7\u7684\u6A21\u5F0F
|
|
6425
|
+
- \u5DF2\u77E5\u9519\u8BEF\u7801\u5217\u8868\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF09
|
|
6426
|
+
|
|
6427
|
+
## 6. \u7981\u533A (Red Lines \u2014 Never Violate)
|
|
6428
|
+
\u660E\u786E\u5217\u51FA\u7EDD\u5BF9\u4E0D\u80FD\u505A\u7684\u4E8B\u60C5\uFF08\u4ECE\u73B0\u6709\u4EE3\u7801/\u67B6\u6784\u63A8\u65AD\uFF09\uFF1A
|
|
6429
|
+
- [ ] \u7981\u6B62 ...
|
|
6430
|
+
- [ ] \u7981\u6B62 ...
|
|
6431
|
+
|
|
6432
|
+
## 7. \u6D4B\u8BD5\u89C4\u8303 (Testing Rules)
|
|
6433
|
+
- \u6D4B\u8BD5\u6587\u4EF6\u5B58\u653E\u4F4D\u7F6E
|
|
6434
|
+
- \u5FC5\u987B\u8986\u76D6\u7684\u6D4B\u8BD5\u573A\u666F\u7C7B\u578B
|
|
6435
|
+
- \u6D4B\u8BD5\u6846\u67B6\u548C\u5DE5\u5177
|
|
6436
|
+
|
|
6437
|
+
## 8. \u5171\u4EAB\u914D\u7F6E\u6587\u4EF6\u6E05\u5355 (Shared Config Files \u2014 Append-Only)
|
|
6438
|
+
|
|
6439
|
+
CRITICAL: The following files are **singleton config files** that already exist in the project.
|
|
6440
|
+
When any feature needs to add entries (translations, constants, routes, enums, etc.), they MUST be
|
|
6441
|
+
appended/merged into these existing files. **NEVER create a new parallel file.**
|
|
6442
|
+
|
|
6443
|
+
For each discovered file, list it as:
|
|
6444
|
+
- \`<relative-path>\` \u2014 <category> \u2014 **MODIFY ONLY, never recreate**
|
|
6445
|
+
|
|
6446
|
+
If the project context includes i18n/locale files: list ALL of them with their paths.
|
|
6447
|
+
If the project context includes constants/enums files: list ALL of them.
|
|
6448
|
+
If the project context includes route index files: list ALL of them.
|
|
6449
|
+
If none are provided in the context, write: "(No shared config files detected \u2014 will be populated on first run)"
|
|
6450
|
+
|
|
6451
|
+
---
|
|
6452
|
+
|
|
6453
|
+
Be concise. Each rule must be specific enough to enforce, not a vague principle.
|
|
6454
|
+
**Section 8 is the most important section for preventing file duplication bugs.**`;
|
|
6455
|
+
|
|
6456
|
+
// core/constitution-generator.ts
|
|
6457
|
+
var CONSTITUTION_FILE = ".ai-spec-constitution.md";
|
|
6458
|
+
var ConstitutionGenerator = class {
|
|
6459
|
+
constructor(provider) {
|
|
6460
|
+
this.provider = provider;
|
|
6461
|
+
}
|
|
6462
|
+
async generate(projectRoot) {
|
|
6463
|
+
const loader = new ContextLoader(projectRoot);
|
|
6464
|
+
const context = await loader.loadProjectContext();
|
|
6465
|
+
const prompt = buildConstitutionPrompt(context, projectRoot);
|
|
6466
|
+
return this.provider.generate(prompt, constitutionSystemPrompt);
|
|
6467
|
+
}
|
|
6468
|
+
async saveConstitution(projectRoot, content) {
|
|
6469
|
+
const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
|
|
6470
|
+
await fs11.writeFile(filePath, content, "utf-8");
|
|
6471
|
+
return filePath;
|
|
6472
|
+
}
|
|
6473
|
+
};
|
|
6474
|
+
function buildConstitutionPrompt(context, projectRoot) {
|
|
6475
|
+
const parts = [
|
|
6476
|
+
"Analyze this project and generate its Project Constitution.\n",
|
|
6477
|
+
`=== Tech Stack ===
|
|
6478
|
+
${context.techStack.join(", ") || "unknown"}
|
|
6479
|
+
`,
|
|
6480
|
+
`=== Dependencies (top 30) ===
|
|
6481
|
+
${context.dependencies.slice(0, 30).join(", ")}
|
|
6482
|
+
`
|
|
6483
|
+
];
|
|
6484
|
+
if (context.apiStructure.length > 0) {
|
|
6485
|
+
parts.push(`=== API/Route Files ===
|
|
6486
|
+
${context.apiStructure.join("\n")}
|
|
6487
|
+
`);
|
|
6488
|
+
}
|
|
6489
|
+
if (context.routeSummary) {
|
|
6490
|
+
parts.push(`=== Route Code Samples ===
|
|
6491
|
+
${context.routeSummary}
|
|
6492
|
+
`);
|
|
6493
|
+
}
|
|
6494
|
+
if (context.schema) {
|
|
6495
|
+
parts.push(`=== Prisma Schema ===
|
|
6496
|
+
${context.schema.slice(0, 4e3)}
|
|
6497
|
+
`);
|
|
6498
|
+
}
|
|
6499
|
+
if (context.errorPatterns) {
|
|
6500
|
+
parts.push(`=== Error Handling Patterns ===
|
|
6501
|
+
${context.errorPatterns}
|
|
6502
|
+
`);
|
|
6503
|
+
}
|
|
6504
|
+
if (context.sharedConfigFiles && context.sharedConfigFiles.length > 0) {
|
|
6505
|
+
const grouped = context.sharedConfigFiles.reduce(
|
|
6506
|
+
(acc, f) => {
|
|
6507
|
+
(acc[f.category] ??= []).push(f);
|
|
6508
|
+
return acc;
|
|
6509
|
+
},
|
|
6510
|
+
{}
|
|
6511
|
+
);
|
|
6512
|
+
const sections = [];
|
|
6513
|
+
for (const [category, files] of Object.entries(grouped)) {
|
|
6514
|
+
sections.push(`--- ${category} ---`);
|
|
6515
|
+
for (const f of files) {
|
|
6516
|
+
sections.push(`File: ${f.path}
|
|
6517
|
+
${f.preview.slice(0, 600)}
|
|
6518
|
+
`);
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
parts.push(`=== Existing Shared Config Files (Append-Only \u2014 NEVER Recreate) ===
|
|
6522
|
+
${sections.join("\n")}
|
|
6523
|
+
`);
|
|
6524
|
+
}
|
|
6525
|
+
return parts.join("\n");
|
|
6526
|
+
}
|
|
6527
|
+
async function loadConstitution(projectRoot) {
|
|
6528
|
+
const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
|
|
6529
|
+
if (await fs11.pathExists(filePath)) {
|
|
6530
|
+
return fs11.readFile(filePath, "utf-8");
|
|
6531
|
+
}
|
|
6532
|
+
return void 0;
|
|
6533
|
+
}
|
|
6534
|
+
function printConstitutionHint(exists) {
|
|
6535
|
+
if (!exists) {
|
|
6536
|
+
console.log(
|
|
6537
|
+
import_chalk9.default.yellow(
|
|
6538
|
+
" \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
|
|
6539
|
+
)
|
|
6540
|
+
);
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
|
|
6544
|
+
// core/reviewer.ts
|
|
6545
|
+
async function loadAccumulatedLessons(projectRoot) {
|
|
6546
|
+
const constitutionPath = path8.join(projectRoot, CONSTITUTION_FILE);
|
|
6547
|
+
let content;
|
|
6548
|
+
try {
|
|
6549
|
+
content = await fs12.readFile(constitutionPath, "utf-8");
|
|
6550
|
+
} catch {
|
|
6551
|
+
return null;
|
|
6552
|
+
}
|
|
6553
|
+
const marker = "## 9. \u79EF\u7D2F\u6559\u8BAD";
|
|
6554
|
+
const idx = content.indexOf(marker);
|
|
6555
|
+
if (idx === -1) return null;
|
|
6556
|
+
const section = content.slice(idx);
|
|
6557
|
+
const nextSection = section.slice(marker.length).match(/\n## \d/);
|
|
6558
|
+
return nextSection ? section.slice(0, marker.length + nextSection.index) : section;
|
|
6559
|
+
}
|
|
6186
6560
|
var REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
|
|
6187
6561
|
async function loadReviewHistory(projectRoot) {
|
|
6188
|
-
const historyPath =
|
|
6562
|
+
const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6189
6563
|
try {
|
|
6190
|
-
if (await
|
|
6191
|
-
return await
|
|
6564
|
+
if (await fs12.pathExists(historyPath)) {
|
|
6565
|
+
return await fs12.readJson(historyPath);
|
|
6192
6566
|
}
|
|
6193
6567
|
} catch {
|
|
6194
6568
|
}
|
|
6195
6569
|
return [];
|
|
6196
6570
|
}
|
|
6197
6571
|
async function appendReviewHistory(projectRoot, entry) {
|
|
6198
|
-
const historyPath =
|
|
6572
|
+
const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6199
6573
|
const existing = await loadReviewHistory(projectRoot);
|
|
6200
6574
|
const updated = [...existing, entry].slice(-20);
|
|
6201
6575
|
try {
|
|
6202
|
-
await
|
|
6576
|
+
await fs12.writeJson(historyPath, updated, { spaces: 2 });
|
|
6203
6577
|
} catch {
|
|
6204
6578
|
}
|
|
6205
6579
|
}
|
|
@@ -6207,6 +6581,14 @@ function extractScore(reviewText) {
|
|
|
6207
6581
|
const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
|
|
6208
6582
|
return match ? parseFloat(match[1]) : 0;
|
|
6209
6583
|
}
|
|
6584
|
+
function extractComplianceScore(complianceText) {
|
|
6585
|
+
const match = complianceText.match(/ComplianceScore:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
|
|
6586
|
+
return match ? parseFloat(match[1]) : 0;
|
|
6587
|
+
}
|
|
6588
|
+
function extractMissingCount(complianceText) {
|
|
6589
|
+
const summaryMatch = complianceText.match(/Missing:\s*(\d+)/i);
|
|
6590
|
+
return summaryMatch ? parseInt(summaryMatch[1], 10) : 0;
|
|
6591
|
+
}
|
|
6210
6592
|
function extractImpactLevel(reviewText) {
|
|
6211
6593
|
const match = reviewText.match(/影响等级[::]\s*(低|中|高)/);
|
|
6212
6594
|
return match ? match[1] : void 0;
|
|
@@ -6225,7 +6607,7 @@ function buildHistoryContext(history) {
|
|
|
6225
6607
|
const lines = ["\n=== \u5386\u53F2\u5BA1\u67E5\u95EE\u9898 (Past Review Issues \u2014 check if any recur) ==="];
|
|
6226
6608
|
for (const entry of recent) {
|
|
6227
6609
|
lines.push(`
|
|
6228
|
-
[${entry.date}] ${
|
|
6610
|
+
[${entry.date}] ${path8.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
|
|
6229
6611
|
entry.topIssues.forEach((issue) => lines.push(` \xB7 ${issue}`));
|
|
6230
6612
|
}
|
|
6231
6613
|
return lines.join("\n") + "\n";
|
|
@@ -6260,23 +6642,52 @@ var CodeReviewer = class {
|
|
|
6260
6642
|
};
|
|
6261
6643
|
}
|
|
6262
6644
|
/**
|
|
6263
|
-
*
|
|
6264
|
-
* Pass
|
|
6645
|
+
* Four-pass review:
|
|
6646
|
+
* Pass 0 — spec compliance (exhaustive requirement coverage audit)
|
|
6647
|
+
* Pass 1 — architecture (layer separation, contract design, auth posture)
|
|
6265
6648
|
* Pass 2 — implementation details (validation, error handling, edge cases)
|
|
6266
6649
|
* + historical issue recurrence check
|
|
6267
6650
|
* Pass 3 — impact assessment + code complexity
|
|
6268
6651
|
*/
|
|
6269
6652
|
async runThreePassReview(specContent, codeContext, specFile) {
|
|
6270
|
-
|
|
6271
|
-
|
|
6653
|
+
let complianceReview = "";
|
|
6654
|
+
if (specContent && specContent.trim() && specContent !== "(No spec \u2014 review for general code quality)") {
|
|
6655
|
+
console.log(import_chalk10.default.gray(" Pass 0/3: Spec compliance check..."));
|
|
6656
|
+
const compliancePrompt = `Check whether the implementation covers every requirement in the spec.
|
|
6272
6657
|
|
|
6658
|
+
=== Feature Spec ===
|
|
6659
|
+
${specContent}
|
|
6660
|
+
|
|
6661
|
+
=== Code ===
|
|
6662
|
+
${codeContext}`;
|
|
6663
|
+
complianceReview = await this.provider.generate(compliancePrompt, specComplianceSystemPrompt);
|
|
6664
|
+
const complianceScore2 = extractComplianceScore(complianceReview);
|
|
6665
|
+
const missingCount = extractMissingCount(complianceReview);
|
|
6666
|
+
if (complianceScore2 > 0) {
|
|
6667
|
+
const scoreColor = complianceScore2 >= 8 ? import_chalk10.default.green : complianceScore2 >= 6 ? import_chalk10.default.yellow : import_chalk10.default.red;
|
|
6668
|
+
console.log(
|
|
6669
|
+
import_chalk10.default.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? import_chalk10.default.red(` \xB7 ${missingCount} missing requirement(s)`) : import_chalk10.default.green(" \xB7 all requirements covered"))
|
|
6670
|
+
);
|
|
6671
|
+
}
|
|
6672
|
+
}
|
|
6673
|
+
console.log(import_chalk10.default.gray(` Pass 1/3: Architecture review...`));
|
|
6674
|
+
const accumulatedLessons = await loadAccumulatedLessons(this.projectRoot);
|
|
6675
|
+
const archPrompt = `Review the architecture of this change.
|
|
6676
|
+
${complianceReview ? `
|
|
6677
|
+
=== Spec Compliance Report (Pass 0 \u2014 already audited, do NOT re-audit missing requirements) ===
|
|
6678
|
+
${complianceReview}
|
|
6679
|
+
` : ""}
|
|
6680
|
+
${accumulatedLessons ? `
|
|
6681
|
+
=== \xA79 \u5386\u53F2\u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons \u2014 check if any are repeated in this code) ===
|
|
6682
|
+
${accumulatedLessons}
|
|
6683
|
+
` : ""}
|
|
6273
6684
|
=== Feature Spec ===
|
|
6274
6685
|
${specContent || "(No spec \u2014 review for general code quality)"}
|
|
6275
6686
|
|
|
6276
6687
|
=== Code ===
|
|
6277
6688
|
${codeContext}`;
|
|
6278
6689
|
const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
|
|
6279
|
-
console.log(
|
|
6690
|
+
console.log(import_chalk10.default.gray(" Pass 2/3: Implementation review..."));
|
|
6280
6691
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6281
6692
|
const historyContext = buildHistoryContext(history);
|
|
6282
6693
|
const implPrompt = `Review the implementation details of this change.
|
|
@@ -6291,7 +6702,7 @@ ${codeContext}
|
|
|
6291
6702
|
${archReview}
|
|
6292
6703
|
${historyContext}`;
|
|
6293
6704
|
const implReview = await this.provider.generate(implPrompt, reviewImplementationSystemPrompt);
|
|
6294
|
-
console.log(
|
|
6705
|
+
console.log(import_chalk10.default.gray(" Pass 3/3: Impact & complexity assessment..."));
|
|
6295
6706
|
const impactPrompt = `Assess the impact and complexity of this change.
|
|
6296
6707
|
|
|
6297
6708
|
=== Feature Spec ===
|
|
@@ -6307,24 +6718,23 @@ ${archReview}
|
|
|
6307
6718
|
${implReview}`;
|
|
6308
6719
|
const impactReview = await this.provider.generate(impactPrompt, reviewImpactComplexitySystemPrompt);
|
|
6309
6720
|
const sep = "\u2500".repeat(52);
|
|
6310
|
-
const
|
|
6721
|
+
const parts = complianceReview ? [complianceReview, archReview, implReview, impactReview] : [archReview, implReview, impactReview];
|
|
6722
|
+
const combined = parts.join(`
|
|
6311
6723
|
|
|
6312
6724
|
${sep}
|
|
6313
6725
|
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
${sep}
|
|
6317
|
-
|
|
6318
|
-
${impactReview}`;
|
|
6726
|
+
`);
|
|
6319
6727
|
const score = extractScore(implReview) || extractScore(archReview);
|
|
6728
|
+
const complianceScore = extractComplianceScore(complianceReview);
|
|
6320
6729
|
const topIssues = extractTopIssues(implReview);
|
|
6321
6730
|
const impactLevel = extractImpactLevel(impactReview);
|
|
6322
6731
|
const complexityLevel = extractComplexityLevel(impactReview);
|
|
6323
6732
|
if (score > 0 && specFile) {
|
|
6324
6733
|
await appendReviewHistory(this.projectRoot, {
|
|
6325
6734
|
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
6326
|
-
specFile:
|
|
6735
|
+
specFile: path8.relative(this.projectRoot, specFile),
|
|
6327
6736
|
score,
|
|
6737
|
+
...complianceScore > 0 ? { complianceScore } : {},
|
|
6328
6738
|
topIssues,
|
|
6329
6739
|
...impactLevel ? { impactLevel } : {},
|
|
6330
6740
|
...complexityLevel ? { complexityLevel } : {}
|
|
@@ -6333,43 +6743,43 @@ ${impactReview}`;
|
|
|
6333
6743
|
return combined;
|
|
6334
6744
|
}
|
|
6335
6745
|
async reviewCode(specContent, specFile) {
|
|
6336
|
-
console.log(
|
|
6746
|
+
console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Automated Code Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6337
6747
|
const diff = this.getGitDiff();
|
|
6338
6748
|
if (!diff.trim()) {
|
|
6339
6749
|
console.log(
|
|
6340
|
-
|
|
6750
|
+
import_chalk10.default.yellow(" No git diff found. Stage or commit changes first, then run review.")
|
|
6341
6751
|
);
|
|
6342
|
-
console.log(
|
|
6752
|
+
console.log(import_chalk10.default.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
|
|
6343
6753
|
return "No changes";
|
|
6344
6754
|
}
|
|
6345
6755
|
const { files, added, removed } = this.getDiffStats(diff);
|
|
6346
6756
|
console.log(
|
|
6347
|
-
|
|
6757
|
+
import_chalk10.default.gray(` Diff: ${files} file(s), ${import_chalk10.default.green("+" + added)} ${import_chalk10.default.red("-" + removed)}`)
|
|
6348
6758
|
);
|
|
6349
6759
|
console.log(
|
|
6350
|
-
|
|
6760
|
+
import_chalk10.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6351
6761
|
);
|
|
6352
6762
|
const codeContext = diff.slice(0, 1e4);
|
|
6353
6763
|
const reviewResult = await this.runThreePassReview(specContent, codeContext, specFile);
|
|
6354
|
-
console.log(
|
|
6764
|
+
console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6355
6765
|
console.log(reviewResult);
|
|
6356
|
-
console.log(
|
|
6766
|
+
console.log(import_chalk10.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
6357
6767
|
return reviewResult;
|
|
6358
6768
|
}
|
|
6359
6769
|
/**
|
|
6360
6770
|
* Review directly from generated file contents (for api mode where git diff is empty).
|
|
6361
6771
|
*/
|
|
6362
6772
|
async reviewFiles(specContent, filePaths, workingDir, specFile) {
|
|
6363
|
-
console.log(
|
|
6364
|
-
console.log(
|
|
6773
|
+
console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6774
|
+
console.log(import_chalk10.default.gray(` Reviewing ${filePaths.length} generated file(s)...`));
|
|
6365
6775
|
console.log(
|
|
6366
|
-
|
|
6776
|
+
import_chalk10.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6367
6777
|
);
|
|
6368
6778
|
let filesSection = "";
|
|
6369
6779
|
for (const filePath of filePaths) {
|
|
6370
|
-
const fullPath =
|
|
6780
|
+
const fullPath = path8.join(workingDir, filePath);
|
|
6371
6781
|
try {
|
|
6372
|
-
const content = await
|
|
6782
|
+
const content = await fs12.readFile(fullPath, "utf-8");
|
|
6373
6783
|
filesSection += `
|
|
6374
6784
|
|
|
6375
6785
|
=== ${filePath} ===
|
|
@@ -6384,189 +6794,31 @@ ${content.slice(0, 3e3)}`;
|
|
|
6384
6794
|
}
|
|
6385
6795
|
}
|
|
6386
6796
|
const reviewResult = await this.runThreePassReview(specContent, filesSection, specFile);
|
|
6387
|
-
console.log(
|
|
6797
|
+
console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6388
6798
|
console.log(reviewResult);
|
|
6389
|
-
console.log(
|
|
6799
|
+
console.log(import_chalk10.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
6390
6800
|
return reviewResult;
|
|
6391
6801
|
}
|
|
6392
6802
|
/** Print score trend from history (last N reviews) */
|
|
6393
6803
|
async printScoreTrend(limit = 5) {
|
|
6394
6804
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6395
6805
|
if (history.length === 0) {
|
|
6396
|
-
console.log(
|
|
6806
|
+
console.log(import_chalk10.default.gray(" No review history yet."));
|
|
6397
6807
|
return;
|
|
6398
6808
|
}
|
|
6399
6809
|
const recent = history.slice(-limit);
|
|
6400
|
-
console.log(
|
|
6810
|
+
console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Review Score Trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6401
6811
|
for (const entry of recent) {
|
|
6402
6812
|
const bar = "\u2588".repeat(entry.score) + "\u2591".repeat(10 - entry.score);
|
|
6403
|
-
const color = entry.score >= 8 ?
|
|
6404
|
-
const impactTag = entry.impactLevel ?
|
|
6405
|
-
const complexityTag = entry.complexityLevel ?
|
|
6406
|
-
console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${
|
|
6813
|
+
const color = entry.score >= 8 ? import_chalk10.default.green : entry.score >= 6 ? import_chalk10.default.yellow : import_chalk10.default.red;
|
|
6814
|
+
const impactTag = entry.impactLevel ? import_chalk10.default.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? import_chalk10.default.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? import_chalk10.default.yellow(entry.impactLevel) : import_chalk10.default.green(entry.impactLevel)}`) : "";
|
|
6815
|
+
const complexityTag = entry.complexityLevel ? import_chalk10.default.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? import_chalk10.default.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? import_chalk10.default.yellow(entry.complexityLevel) : import_chalk10.default.green(entry.complexityLevel)}`) : "";
|
|
6816
|
+
console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
|
|
6407
6817
|
}
|
|
6408
|
-
console.log(
|
|
6818
|
+
console.log(import_chalk10.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6409
6819
|
}
|
|
6410
6820
|
};
|
|
6411
6821
|
|
|
6412
|
-
// core/constitution-generator.ts
|
|
6413
|
-
var import_chalk10 = __toESM(require("chalk"));
|
|
6414
|
-
var fs12 = __toESM(require("fs-extra"));
|
|
6415
|
-
var path8 = __toESM(require("path"));
|
|
6416
|
-
|
|
6417
|
-
// prompts/constitution.prompt.ts
|
|
6418
|
-
var constitutionSystemPrompt = `You are a Senior Software Architect. Analyze the provided project codebase context and generate a concise "Project Constitution" \u2014 a living document that captures the architectural rules, conventions, and red lines that ALL future feature specs and code generation MUST follow.
|
|
6419
|
-
|
|
6420
|
-
Output a Markdown document with EXACTLY these sections. Be specific and derive rules directly from the observed codebase \u2014 no generic advice.
|
|
6421
|
-
|
|
6422
|
-
---
|
|
6423
|
-
|
|
6424
|
-
# Project Constitution
|
|
6425
|
-
|
|
6426
|
-
## 1. \u67B6\u6784\u89C4\u5219 (Architecture Rules)
|
|
6427
|
-
\u5217\u51FA\u9879\u76EE\u7684\u6838\u5FC3\u67B6\u6784\u6A21\u5F0F\u548C\u5F3A\u5236\u7EA6\u675F\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF0C\u800C\u975E\u901A\u7528\u5EFA\u8BAE\uFF09\u3002
|
|
6428
|
-
- \u5206\u5C42\u67B6\u6784\u89C4\u5219\uFF08\u5982\uFF1Aroutes \u2192 controllers \u2192 services \u2192 DB\uFF09
|
|
6429
|
-
- \u7981\u6B62\u8DE8\u5C42\u76F4\u63A5\u8C03\u7528\u7684\u89C4\u5219
|
|
6430
|
-
- \u6A21\u5757\u7EC4\u7EC7\u89C4\u8303
|
|
6431
|
-
|
|
6432
|
-
## 2. \u547D\u540D\u89C4\u8303 (Naming Conventions)
|
|
6433
|
-
- \u6587\u4EF6\u547D\u540D\u89C4\u5219\uFF08\u9A7C\u5CF0/\u4E0B\u5212\u7EBF/kebab\uFF09
|
|
6434
|
-
- \u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u7684\u547D\u540D\u6A21\u5F0F
|
|
6435
|
-
- \u8DEF\u7531\u8DEF\u5F84\u7684\u547D\u540D\u89C4\u8303
|
|
6436
|
-
|
|
6437
|
-
## 3. API \u89C4\u8303 (API Patterns)
|
|
6438
|
-
- \u8DEF\u7531\u524D\u7F00\u89C4\u5219\uFF08\u5982 /api/v1/client/... vs /api/v1/admin/...\uFF09
|
|
6439
|
-
- \u7EDF\u4E00\u54CD\u5E94\u7ED3\u6784\u6A21\u677F\uFF08code/message/data \u683C\u5F0F\uFF09
|
|
6440
|
-
- \u9519\u8BEF\u7801\u89C4\u8303\uFF08\u5DF2\u6709\u7684\u9519\u8BEF\u7801\u8303\u56F4\u548C\u542B\u4E49\uFF09
|
|
6441
|
-
- \u8BA4\u8BC1/\u9274\u6743\u6A21\u5F0F\uFF08middleware \u540D\u79F0\u548C\u4F7F\u7528\u4F4D\u7F6E\uFF09
|
|
6442
|
-
|
|
6443
|
-
## 4. \u6570\u636E\u5C42\u89C4\u8303 (Data Layer Rules)
|
|
6444
|
-
- ORM/\u6570\u636E\u5E93\u8BBF\u95EE\u89C4\u5219\uFF08\u4EC5\u901A\u8FC7 service \u5C42\u8BBF\u95EE\uFF1F\u76F4\u63A5\u7528 Prisma/Mongoose\uFF1F\uFF09
|
|
6445
|
-
- \u5DF2\u6709\u7684\u6570\u636E\u6A21\u578B\u547D\u540D\u89C4\u8303
|
|
6446
|
-
- \u4E8B\u52A1\u5904\u7406\u6A21\u5F0F
|
|
6447
|
-
|
|
6448
|
-
## 5. \u9519\u8BEF\u5904\u7406\u89C4\u8303 (Error Handling Patterns)
|
|
6449
|
-
- \u7EDF\u4E00\u9519\u8BEF\u5904\u7406 middleware \u7684\u4F7F\u7528\u89C4\u5219
|
|
6450
|
-
- \u9519\u8BEF\u629B\u51FA\u548C\u6355\u83B7\u7684\u6A21\u5F0F
|
|
6451
|
-
- \u5DF2\u77E5\u9519\u8BEF\u7801\u5217\u8868\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF09
|
|
6452
|
-
|
|
6453
|
-
## 6. \u7981\u533A (Red Lines \u2014 Never Violate)
|
|
6454
|
-
\u660E\u786E\u5217\u51FA\u7EDD\u5BF9\u4E0D\u80FD\u505A\u7684\u4E8B\u60C5\uFF08\u4ECE\u73B0\u6709\u4EE3\u7801/\u67B6\u6784\u63A8\u65AD\uFF09\uFF1A
|
|
6455
|
-
- [ ] \u7981\u6B62 ...
|
|
6456
|
-
- [ ] \u7981\u6B62 ...
|
|
6457
|
-
|
|
6458
|
-
## 7. \u6D4B\u8BD5\u89C4\u8303 (Testing Rules)
|
|
6459
|
-
- \u6D4B\u8BD5\u6587\u4EF6\u5B58\u653E\u4F4D\u7F6E
|
|
6460
|
-
- \u5FC5\u987B\u8986\u76D6\u7684\u6D4B\u8BD5\u573A\u666F\u7C7B\u578B
|
|
6461
|
-
- \u6D4B\u8BD5\u6846\u67B6\u548C\u5DE5\u5177
|
|
6462
|
-
|
|
6463
|
-
## 8. \u5171\u4EAB\u914D\u7F6E\u6587\u4EF6\u6E05\u5355 (Shared Config Files \u2014 Append-Only)
|
|
6464
|
-
|
|
6465
|
-
CRITICAL: The following files are **singleton config files** that already exist in the project.
|
|
6466
|
-
When any feature needs to add entries (translations, constants, routes, enums, etc.), they MUST be
|
|
6467
|
-
appended/merged into these existing files. **NEVER create a new parallel file.**
|
|
6468
|
-
|
|
6469
|
-
For each discovered file, list it as:
|
|
6470
|
-
- \`<relative-path>\` \u2014 <category> \u2014 **MODIFY ONLY, never recreate**
|
|
6471
|
-
|
|
6472
|
-
If the project context includes i18n/locale files: list ALL of them with their paths.
|
|
6473
|
-
If the project context includes constants/enums files: list ALL of them.
|
|
6474
|
-
If the project context includes route index files: list ALL of them.
|
|
6475
|
-
If none are provided in the context, write: "(No shared config files detected \u2014 will be populated on first run)"
|
|
6476
|
-
|
|
6477
|
-
---
|
|
6478
|
-
|
|
6479
|
-
Be concise. Each rule must be specific enough to enforce, not a vague principle.
|
|
6480
|
-
**Section 8 is the most important section for preventing file duplication bugs.**`;
|
|
6481
|
-
|
|
6482
|
-
// core/constitution-generator.ts
|
|
6483
|
-
var CONSTITUTION_FILE = ".ai-spec-constitution.md";
|
|
6484
|
-
var ConstitutionGenerator = class {
|
|
6485
|
-
constructor(provider) {
|
|
6486
|
-
this.provider = provider;
|
|
6487
|
-
}
|
|
6488
|
-
async generate(projectRoot) {
|
|
6489
|
-
const loader = new ContextLoader(projectRoot);
|
|
6490
|
-
const context = await loader.loadProjectContext();
|
|
6491
|
-
const prompt = buildConstitutionPrompt(context, projectRoot);
|
|
6492
|
-
return this.provider.generate(prompt, constitutionSystemPrompt);
|
|
6493
|
-
}
|
|
6494
|
-
async saveConstitution(projectRoot, content) {
|
|
6495
|
-
const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
|
|
6496
|
-
await fs12.writeFile(filePath, content, "utf-8");
|
|
6497
|
-
return filePath;
|
|
6498
|
-
}
|
|
6499
|
-
};
|
|
6500
|
-
function buildConstitutionPrompt(context, projectRoot) {
|
|
6501
|
-
const parts = [
|
|
6502
|
-
"Analyze this project and generate its Project Constitution.\n",
|
|
6503
|
-
`=== Tech Stack ===
|
|
6504
|
-
${context.techStack.join(", ") || "unknown"}
|
|
6505
|
-
`,
|
|
6506
|
-
`=== Dependencies (top 30) ===
|
|
6507
|
-
${context.dependencies.slice(0, 30).join(", ")}
|
|
6508
|
-
`
|
|
6509
|
-
];
|
|
6510
|
-
if (context.apiStructure.length > 0) {
|
|
6511
|
-
parts.push(`=== API/Route Files ===
|
|
6512
|
-
${context.apiStructure.join("\n")}
|
|
6513
|
-
`);
|
|
6514
|
-
}
|
|
6515
|
-
if (context.routeSummary) {
|
|
6516
|
-
parts.push(`=== Route Code Samples ===
|
|
6517
|
-
${context.routeSummary}
|
|
6518
|
-
`);
|
|
6519
|
-
}
|
|
6520
|
-
if (context.schema) {
|
|
6521
|
-
parts.push(`=== Prisma Schema ===
|
|
6522
|
-
${context.schema.slice(0, 4e3)}
|
|
6523
|
-
`);
|
|
6524
|
-
}
|
|
6525
|
-
if (context.errorPatterns) {
|
|
6526
|
-
parts.push(`=== Error Handling Patterns ===
|
|
6527
|
-
${context.errorPatterns}
|
|
6528
|
-
`);
|
|
6529
|
-
}
|
|
6530
|
-
if (context.sharedConfigFiles && context.sharedConfigFiles.length > 0) {
|
|
6531
|
-
const grouped = context.sharedConfigFiles.reduce(
|
|
6532
|
-
(acc, f) => {
|
|
6533
|
-
(acc[f.category] ??= []).push(f);
|
|
6534
|
-
return acc;
|
|
6535
|
-
},
|
|
6536
|
-
{}
|
|
6537
|
-
);
|
|
6538
|
-
const sections = [];
|
|
6539
|
-
for (const [category, files] of Object.entries(grouped)) {
|
|
6540
|
-
sections.push(`--- ${category} ---`);
|
|
6541
|
-
for (const f of files) {
|
|
6542
|
-
sections.push(`File: ${f.path}
|
|
6543
|
-
${f.preview.slice(0, 600)}
|
|
6544
|
-
`);
|
|
6545
|
-
}
|
|
6546
|
-
}
|
|
6547
|
-
parts.push(`=== Existing Shared Config Files (Append-Only \u2014 NEVER Recreate) ===
|
|
6548
|
-
${sections.join("\n")}
|
|
6549
|
-
`);
|
|
6550
|
-
}
|
|
6551
|
-
return parts.join("\n");
|
|
6552
|
-
}
|
|
6553
|
-
async function loadConstitution(projectRoot) {
|
|
6554
|
-
const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
|
|
6555
|
-
if (await fs12.pathExists(filePath)) {
|
|
6556
|
-
return fs12.readFile(filePath, "utf-8");
|
|
6557
|
-
}
|
|
6558
|
-
return void 0;
|
|
6559
|
-
}
|
|
6560
|
-
function printConstitutionHint(exists) {
|
|
6561
|
-
if (!exists) {
|
|
6562
|
-
console.log(
|
|
6563
|
-
import_chalk10.default.yellow(
|
|
6564
|
-
" \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
|
|
6565
|
-
)
|
|
6566
|
-
);
|
|
6567
|
-
}
|
|
6568
|
-
}
|
|
6569
|
-
|
|
6570
6822
|
// core/combined-generator.ts
|
|
6571
6823
|
var TASKS_SEPARATOR = "---TASKS_JSON---";
|
|
6572
6824
|
var tasksInstruction = `
|
|
@@ -6575,11 +6827,21 @@ var tasksInstruction = `
|
|
|
6575
6827
|
After outputting the complete spec above, append EXACTLY this line on its own (no extra text before or after it):
|
|
6576
6828
|
${TASKS_SEPARATOR}
|
|
6577
6829
|
Then output a valid JSON array of implementation tasks. Each element must have these exact fields:
|
|
6578
|
-
{"id":"TASK-001","title":"...","description":"1-2 sentences, specific","layer":"data|infra|service|api|test","filesToTouch":["src/..."],"acceptanceCriteria":["
|
|
6830
|
+
{"id":"TASK-001","title":"...","description":"1-2 sentences, specific","layer":"data|infra|service|api|test","filesToTouch":["src/..."],"acceptanceCriteria":["behavioral condition"],"verificationSteps":["concrete runnable check \u2192 expected result"],"dependencies":[],"priority":"high|medium|low"}
|
|
6831
|
+
verificationSteps rules: each step is a specific command or action with observable expected output (e.g. "POST /api/orders \u2192 201 {id, status:'pending'}"). At least 2 per task, max 5. Never vague.
|
|
6579
6832
|
Layer order: data \u2192 infra \u2192 service \u2192 api \u2192 test. 4-10 tasks total. filesToTouch must use real paths from the project context.`;
|
|
6580
|
-
async function generateSpecWithTasks(provider, idea, context) {
|
|
6833
|
+
async function generateSpecWithTasks(provider, idea, context, architectureDecision) {
|
|
6581
6834
|
const contextBlock = buildTaskPrompt("", context).trim();
|
|
6582
|
-
const
|
|
6835
|
+
const parts = [idea];
|
|
6836
|
+
if (architectureDecision) {
|
|
6837
|
+
parts.push(
|
|
6838
|
+
`
|
|
6839
|
+
=== Architecture Decision (MUST follow this approach in the spec) ===
|
|
6840
|
+
${architectureDecision}`
|
|
6841
|
+
);
|
|
6842
|
+
}
|
|
6843
|
+
if (contextBlock) parts.push(contextBlock);
|
|
6844
|
+
const fullPrompt = parts.join("\n\n");
|
|
6583
6845
|
const combinedSystemPrompt = specPrompt + tasksInstruction;
|
|
6584
6846
|
const raw = await provider.generate(fullPrompt, combinedSystemPrompt);
|
|
6585
6847
|
return parseSpecAndTasks(raw);
|
|
@@ -6715,6 +6977,9 @@ var GitWorktreeManager = class {
|
|
|
6715
6977
|
TaskGenerator,
|
|
6716
6978
|
buildTaskPrompt,
|
|
6717
6979
|
createProvider,
|
|
6980
|
+
extractBehavioralContract,
|
|
6981
|
+
extractComplianceScore,
|
|
6982
|
+
extractMissingCount,
|
|
6718
6983
|
generateSpecWithTasks,
|
|
6719
6984
|
isFrontendDeps,
|
|
6720
6985
|
loadConstitution,
|