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.mjs
CHANGED
|
@@ -107,7 +107,14 @@ model ExampleModel {
|
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
|
110
|
-
\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
|
|
110
|
+
\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
|
|
111
|
+
|
|
112
|
+
CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uFF09\uFF1A
|
|
113
|
+
\u5982\u679C\u9879\u76EE\u5BAA\u6CD5\u4E2D\u5305\u542B"\xA79 \u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons)"\u7AE0\u8282\uFF0C\u4F60\u5FC5\u987B\uFF1A
|
|
114
|
+
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
|
|
115
|
+
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
|
|
116
|
+
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
|
|
117
|
+
4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
|
|
111
118
|
|
|
112
119
|
// core/provider-utils.ts
|
|
113
120
|
import chalk from "chalk";
|
|
@@ -124,14 +131,49 @@ function classifyError(err, label) {
|
|
|
124
131
|
const e = err;
|
|
125
132
|
const status = e.status ?? e.response?.status;
|
|
126
133
|
if (status === 401 || status === 403)
|
|
127
|
-
return new ProviderError(
|
|
134
|
+
return new ProviderError(
|
|
135
|
+
`Auth error (${label}): API key is invalid or expired.
|
|
136
|
+
\u2192 Check that the correct API key is set in your environment or ~/.ai-spec-keys.json
|
|
137
|
+
\u2192 Run "ai-spec model" to reconfigure your provider and key`,
|
|
138
|
+
"auth",
|
|
139
|
+
err
|
|
140
|
+
);
|
|
128
141
|
if (status === 429)
|
|
129
|
-
return new ProviderError(
|
|
142
|
+
return new ProviderError(
|
|
143
|
+
`Rate limit hit (${label}): too many requests.
|
|
144
|
+
\u2192 Wait a few minutes and retry, or switch to a different provider/model
|
|
145
|
+
\u2192 Check your provider's billing dashboard for quota status`,
|
|
146
|
+
"rate_limit",
|
|
147
|
+
err
|
|
148
|
+
);
|
|
130
149
|
if (e._timeout || e.message?.toLowerCase().includes("timed out"))
|
|
131
150
|
return new ProviderError(`Request timed out (${label})`, "timeout", err);
|
|
132
151
|
if (e.code === "ECONNRESET" || e.code === "ENOTFOUND" || e.code === "ECONNREFUSED")
|
|
133
|
-
return new ProviderError(
|
|
134
|
-
|
|
152
|
+
return new ProviderError(
|
|
153
|
+
`Network error (${label}): ${e.message}
|
|
154
|
+
\u2192 Check your internet connection and proxy settings (HTTPS_PROXY)
|
|
155
|
+
\u2192 If behind a firewall, ensure the provider's API endpoint is reachable`,
|
|
156
|
+
"network",
|
|
157
|
+
err
|
|
158
|
+
);
|
|
159
|
+
const msg = e.message ?? "";
|
|
160
|
+
if (status === 404 || msg.includes("model") && (msg.includes("not found") || msg.includes("does not exist")))
|
|
161
|
+
return new ProviderError(
|
|
162
|
+
`Model not found (${label}): ${msg}
|
|
163
|
+
\u2192 Run "ai-spec model" to see available models for your provider
|
|
164
|
+
\u2192 The model name may have changed \u2014 check your provider's documentation`,
|
|
165
|
+
"provider",
|
|
166
|
+
err
|
|
167
|
+
);
|
|
168
|
+
if (msg.includes("insufficient") || msg.includes("quota") || msg.includes("balance"))
|
|
169
|
+
return new ProviderError(
|
|
170
|
+
`Quota/balance error (${label}): ${msg}
|
|
171
|
+
\u2192 Check your provider's billing dashboard
|
|
172
|
+
\u2192 Consider switching to a different provider with "ai-spec model"`,
|
|
173
|
+
"provider",
|
|
174
|
+
err
|
|
175
|
+
);
|
|
176
|
+
return new ProviderError(`Provider error (${label}): ${msg}`, "provider", err);
|
|
135
177
|
}
|
|
136
178
|
function isRetryable(err) {
|
|
137
179
|
const e = err;
|
|
@@ -258,17 +300,23 @@ var PROVIDER_CATALOG = {
|
|
|
258
300
|
},
|
|
259
301
|
glm: {
|
|
260
302
|
displayName: "\u667A\u8C31 GLM (Zhipu AI)",
|
|
261
|
-
description: "\u667A\u8C31 AI \u2014 GLM-5 / GLM-4 series
|
|
303
|
+
description: "\u667A\u8C31 AI \u2014 GLM-5.1 / GLM-5 / GLM-4 series",
|
|
262
304
|
models: [
|
|
305
|
+
"glm-5.1",
|
|
306
|
+
// GLM-5.1 — latest flagship (2026)
|
|
263
307
|
"glm-5",
|
|
264
|
-
// GLM-5
|
|
265
|
-
"glm-5-
|
|
308
|
+
// GLM-5 — premium (Max/Pro plans)
|
|
309
|
+
"glm-5-turbo",
|
|
310
|
+
// GLM-5-Turbo — fast & cost-efficient
|
|
311
|
+
"glm-4.7",
|
|
312
|
+
// GLM-4.7
|
|
313
|
+
"glm-4.6",
|
|
314
|
+
// GLM-4.6
|
|
315
|
+
"glm-4.5-air",
|
|
316
|
+
// GLM-4.5-Air — lightweight
|
|
266
317
|
"glm-z1",
|
|
267
|
-
// GLM-Z1 reasoning model
|
|
268
|
-
"glm-z1-flash"
|
|
269
|
-
"glm-4-plus",
|
|
270
|
-
"glm-4-flash",
|
|
271
|
-
"glm-4-long"
|
|
318
|
+
// GLM-Z1 — reasoning model
|
|
319
|
+
"glm-z1-flash"
|
|
272
320
|
],
|
|
273
321
|
envKey: "ZHIPU_API_KEY",
|
|
274
322
|
baseURL: "https://open.bigmodel.cn/api/paas/v4/"
|
|
@@ -464,8 +512,15 @@ var SpecGenerator = class {
|
|
|
464
512
|
constructor(provider) {
|
|
465
513
|
this.provider = provider;
|
|
466
514
|
}
|
|
467
|
-
async generateSpec(idea, context) {
|
|
515
|
+
async generateSpec(idea, context, architectureDecision) {
|
|
468
516
|
const parts = [idea];
|
|
517
|
+
if (architectureDecision) {
|
|
518
|
+
parts.push(
|
|
519
|
+
`
|
|
520
|
+
=== Architecture Decision (MUST follow this approach in the spec) ===
|
|
521
|
+
${architectureDecision}`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
469
524
|
if (context) {
|
|
470
525
|
if (context.constitution) {
|
|
471
526
|
parts.push(
|
|
@@ -4053,7 +4108,7 @@ ${currentSpec}`,
|
|
|
4053
4108
|
|
|
4054
4109
|
// core/code-generator.ts
|
|
4055
4110
|
import chalk8 from "chalk";
|
|
4056
|
-
import { execSync } from "child_process";
|
|
4111
|
+
import { execSync, spawnSync } from "child_process";
|
|
4057
4112
|
import * as path6 from "path";
|
|
4058
4113
|
import * as fs10 from "fs-extra";
|
|
4059
4114
|
|
|
@@ -4220,34 +4275,110 @@ function getCodeGenSystemPrompt(repoType) {
|
|
|
4220
4275
|
return codeGenSystemPrompt;
|
|
4221
4276
|
}
|
|
4222
4277
|
}
|
|
4223
|
-
var
|
|
4278
|
+
var specComplianceSystemPrompt = `You are a QA Engineer performing a SPEC COMPLIANCE CHECK.
|
|
4279
|
+
|
|
4280
|
+
Your sole job is to verify that the implementation covers every requirement stated in the feature spec.
|
|
4281
|
+
This is a completeness audit \u2014 NOT a code quality review. Do not comment on architecture, style, or implementation details.
|
|
4282
|
+
|
|
4283
|
+
## How to audit:
|
|
4284
|
+
|
|
4285
|
+
1. Parse the spec and extract EVERY stated requirement into these categories:
|
|
4286
|
+
- **Endpoints**: each HTTP method + path listed or implied
|
|
4287
|
+
- **Data Models**: each entity, field, constraint mentioned
|
|
4288
|
+
- **Business Rules**: validations, conditions, calculations stated
|
|
4289
|
+
- **Auth Requirements**: which endpoints need auth, which roles are allowed
|
|
4290
|
+
- **Error Cases**: explicit error codes or failure scenarios mentioned
|
|
4291
|
+
- **Side Effects**: emails sent, events fired, caches invalidated, etc.
|
|
4292
|
+
|
|
4293
|
+
2. For each extracted requirement, check the provided code:
|
|
4294
|
+
- \u2705 **Covered** \u2014 clearly implemented
|
|
4295
|
+
- \u26A0\uFE0F **Partial** \u2014 exists but incomplete (e.g. endpoint exists but missing a field or error case)
|
|
4296
|
+
- \u274C **Missing** \u2014 requirement stated in spec but not found in code
|
|
4297
|
+
|
|
4298
|
+
3. Output a compliance checklist. Be exhaustive \u2014 list every single requirement.
|
|
4299
|
+
|
|
4300
|
+
## Output format:
|
|
4301
|
+
|
|
4302
|
+
## \u{1F4CB} Spec Compliance Report
|
|
4303
|
+
|
|
4304
|
+
### Endpoints
|
|
4305
|
+
\u2705 / \u26A0\uFE0F / \u274C METHOD /path \u2014 one-line status
|
|
4306
|
+
|
|
4307
|
+
### Data Models
|
|
4308
|
+
\u2705 / \u26A0\uFE0F / \u274C ModelName \u2014 one-line status
|
|
4309
|
+
|
|
4310
|
+
### Business Rules
|
|
4311
|
+
\u2705 / \u26A0\uFE0F / \u274C Rule description \u2014 one-line status
|
|
4312
|
+
|
|
4313
|
+
### Auth & Permissions
|
|
4314
|
+
\u2705 / \u26A0\uFE0F / \u274C Requirement \u2014 one-line status
|
|
4315
|
+
|
|
4316
|
+
### Error Cases
|
|
4317
|
+
\u2705 / \u26A0\uFE0F / \u274C Error scenario \u2014 one-line status
|
|
4318
|
+
|
|
4319
|
+
### Side Effects
|
|
4320
|
+
\u2705 / \u26A0\uFE0F / \u274C Side effect \u2014 one-line status (omit section if none in spec)
|
|
4321
|
+
|
|
4322
|
+
---
|
|
4323
|
+
|
|
4324
|
+
## \u{1F4CA} Compliance Summary
|
|
4325
|
+
Covered: N | Partial: N | Missing: N | Total: N
|
|
4326
|
+
|
|
4327
|
+
## \u{1F522} Compliance Score
|
|
4328
|
+
ComplianceScore: X/10
|
|
4329
|
+
|
|
4330
|
+
(10 = all requirements implemented, 0 = nothing implemented.
|
|
4331
|
+
Deduct 1 point per missing requirement, 0.5 per partial.
|
|
4332
|
+
Round to nearest integer.)
|
|
4333
|
+
|
|
4334
|
+
## \u{1F6A8} Blockers (Missing requirements that MUST be implemented before ship)
|
|
4335
|
+
List only \u274C Missing items here, ordered by severity. If none, write "None".
|
|
4336
|
+
|
|
4337
|
+
---
|
|
4338
|
+
|
|
4339
|
+
IMPORTANT: Be exhaustive. A requirement not listed here is assumed to be covered.
|
|
4340
|
+
If the spec is vague, note the ambiguity as \u26A0\uFE0F Partial rather than assuming coverage.`;
|
|
4341
|
+
var reviewArchitectureSystemPrompt = `You are a Senior Software Architect reviewing the HIGH-LEVEL DESIGN of a code change.
|
|
4342
|
+
|
|
4343
|
+
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.
|
|
4224
4344
|
|
|
4225
4345
|
Focus ONLY on:
|
|
4226
|
-
1. **
|
|
4227
|
-
2. **
|
|
4228
|
-
3. **
|
|
4229
|
-
4. **
|
|
4230
|
-
5. **Security posture** \u2014 Are auth checks applied to the right endpoints? Any obvious missing auth?
|
|
4346
|
+
1. **Layer separation** \u2014 Does each layer have the right responsibilities? (e.g., no business logic in controllers, no HTTP in stores)
|
|
4347
|
+
2. **API contract quality** \u2014 Are request/response shapes well-designed? Are error codes consistent with project conventions?
|
|
4348
|
+
3. **Data model integrity** \u2014 Are constraints, unique fields, and relationships modelled correctly?
|
|
4349
|
+
4. **Security posture** \u2014 Are auth checks applied correctly? Any privilege escalation risks?
|
|
4231
4350
|
|
|
4232
4351
|
DO NOT comment on:
|
|
4352
|
+
- Whether specific endpoints or features are missing (covered by Pass 0)
|
|
4233
4353
|
- Code style, naming conventions, formatting
|
|
4234
4354
|
- Minor implementation details (variable names, inline comments)
|
|
4235
4355
|
- Performance micro-optimizations
|
|
4236
4356
|
|
|
4237
4357
|
Format:
|
|
4238
4358
|
|
|
4239
|
-
## \u{1F3D7} \u67B6\u6784\u5408\u89C4\u6027 (Spec Compliance)
|
|
4240
|
-
Does the implementation match the spec? List any missing or wrong endpoints/components.
|
|
4241
|
-
|
|
4242
4359
|
## \u{1F500} \u5C42\u804C\u8D23\u5206\u79BB (Layer Separation)
|
|
4243
4360
|
Any layer boundary violations?
|
|
4244
4361
|
|
|
4245
4362
|
## \u{1F512} \u5B89\u5168\u4E0E\u6743\u9650 (Security & Auth)
|
|
4246
4363
|
Any missing auth checks, exposed data, or privilege issues?
|
|
4247
4364
|
|
|
4365
|
+
## \u{1F4D0} \u5951\u7EA6\u4E0E\u6A21\u578B\u8BBE\u8BA1 (Contract & Model Design)
|
|
4366
|
+
Response shape issues, missing constraints, relationship problems.
|
|
4367
|
+
|
|
4248
4368
|
## \u{1F4CB} \u67B6\u6784\u8BC4\u5206 (Architecture Score)
|
|
4249
4369
|
Score: X/10 \u2014 One short paragraph.
|
|
4250
4370
|
|
|
4371
|
+
## \u{1F50D} \u7ED3\u6784\u6027\u53D1\u73B0 JSON (Structural Findings \u2014 for pipeline processing)
|
|
4372
|
+
Output a JSON block with any design-level issues found above.
|
|
4373
|
+
Categories: "auth_design" | "api_contract" | "model_design" | "layer_violation" | "other_design"
|
|
4374
|
+
If no findings, output an empty array.
|
|
4375
|
+
|
|
4376
|
+
\`\`\`json
|
|
4377
|
+
{"structuralFindings": [{"category": "...", "description": "one sentence referencing the specific endpoint/model/file"}]}
|
|
4378
|
+
\`\`\`
|
|
4379
|
+
|
|
4380
|
+
IMPORTANT: Always include this JSON block, even when structuralFindings is []. This block is parsed by the pipeline.
|
|
4381
|
+
|
|
4251
4382
|
Be specific. Reference file names or endpoint paths.`;
|
|
4252
4383
|
var reviewImplementationSystemPrompt = `You are a Senior Engineer reviewing the IMPLEMENTATION DETAILS of a code change.
|
|
4253
4384
|
|
|
@@ -4340,7 +4471,8 @@ Each task object must have these exact fields:
|
|
|
4340
4471
|
"description": "...", // 1-2 sentences, specific and actionable
|
|
4341
4472
|
"layer": "data|service|api|view|route|test|infra", // implementation layer
|
|
4342
4473
|
"filesToTouch": ["..."], // VERIFIED paths only \u2014 see rules below
|
|
4343
|
-
"acceptanceCriteria": ["..."], //
|
|
4474
|
+
"acceptanceCriteria": ["..."], // behavioral completion conditions (the "what")
|
|
4475
|
+
"verificationSteps": ["..."], // concrete runnable checks with expected output (the "how to verify") \u2014 see rules below
|
|
4344
4476
|
"dependencies": ["TASK-001"], // task ids that must complete first (empty array if none)
|
|
4345
4477
|
"priority": "high|medium|low"
|
|
4346
4478
|
}
|
|
@@ -4382,8 +4514,32 @@ CRITICAL \u2014 filesToTouch Rules (hallucination prevention):
|
|
|
4382
4514
|
- If you are unsure of the exact path for a new file, leave it as "TBD:<description>" rather than guessing.
|
|
4383
4515
|
- 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.
|
|
4384
4516
|
|
|
4517
|
+
CRITICAL \u2014 verificationSteps Rules:
|
|
4518
|
+
Each step must be a concrete, self-contained check with an observable expected outcome.
|
|
4519
|
+
|
|
4520
|
+
Good examples (specific command + expected result):
|
|
4521
|
+
"POST /api/tasks with body {"title":"test"} \u2192 HTTP 201, response body contains {id, status:"pending"}"
|
|
4522
|
+
"GET /api/tasks/:id with unknown id \u2192 HTTP 404 with {code: 4040X, message: "..."}"
|
|
4523
|
+
"npm run build exits 0 with no TypeScript errors"
|
|
4524
|
+
"Prisma schema has Task model with fields: id, title, status, createdAt"
|
|
4525
|
+
"Store action createTask sets loading:true during request, loading:false on completion"
|
|
4526
|
+
"Route /tasks renders TaskList component, visible in router DevTools"
|
|
4527
|
+
|
|
4528
|
+
Bad examples (too vague \u2014 do NOT use these):
|
|
4529
|
+
"The endpoint works correctly" \u2717
|
|
4530
|
+
"Data is saved to the database" \u2717
|
|
4531
|
+
"UI displays the correct data" \u2717
|
|
4532
|
+
"Error handling works" \u2717
|
|
4533
|
+
|
|
4534
|
+
Rules:
|
|
4535
|
+
- At least 2 verification steps per task, max 5
|
|
4536
|
+
- Each step must be independently runnable/checkable
|
|
4537
|
+
- Backend tasks: include at least one HTTP request/response check and one data-layer check
|
|
4538
|
+
- Frontend tasks: include at least one UI render check and one state check
|
|
4539
|
+
- Build/compile tasks: always include "npm run build exits 0" or equivalent
|
|
4540
|
+
|
|
4385
4541
|
Other rules:
|
|
4386
|
-
- acceptanceCriteria
|
|
4542
|
+
- acceptanceCriteria: behavioral statements ("order is created with pending status") \u2014 complementary to verificationSteps, not duplicates
|
|
4387
4543
|
- dependencies must reflect real implementation order
|
|
4388
4544
|
- Aim for 4-10 tasks total \u2014 not too granular, not too coarse
|
|
4389
4545
|
- Each task should be completable in one focused coding session`;
|
|
@@ -4493,6 +4649,14 @@ function printTasks(tasks) {
|
|
|
4493
4649
|
const badge = color(`[${task.layer}]`);
|
|
4494
4650
|
const prio = task.priority === "high" ? chalk4.red("\u25CF") : task.priority === "medium" ? chalk4.yellow("\u25CF") : chalk4.gray("\u25CF");
|
|
4495
4651
|
console.log(` ${prio} ${chalk4.bold(task.id)} ${badge} ${task.title}`);
|
|
4652
|
+
if (task.verificationSteps?.length) {
|
|
4653
|
+
for (const step of task.verificationSteps.slice(0, 2)) {
|
|
4654
|
+
console.log(chalk4.gray(` \u2713 ${step}`));
|
|
4655
|
+
}
|
|
4656
|
+
if (task.verificationSteps.length > 2) {
|
|
4657
|
+
console.log(chalk4.gray(` + ${task.verificationSteps.length - 2} more verification step(s)`));
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4496
4660
|
}
|
|
4497
4661
|
}
|
|
4498
4662
|
async function loadTasksForSpec(specFilePath) {
|
|
@@ -4571,6 +4735,21 @@ function validateDsl(raw) {
|
|
|
4571
4735
|
for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
|
|
4572
4736
|
validateEndpoint(eps[i], `endpoints[${i}]`, errors);
|
|
4573
4737
|
}
|
|
4738
|
+
const seenEpIds = /* @__PURE__ */ new Set();
|
|
4739
|
+
for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
|
|
4740
|
+
const ep = eps[i];
|
|
4741
|
+
if (ep && typeof ep === "object" && typeof ep["id"] === "string") {
|
|
4742
|
+
const id = ep["id"];
|
|
4743
|
+
if (seenEpIds.has(id)) {
|
|
4744
|
+
errors.push({
|
|
4745
|
+
path: `endpoints[${i}].id`,
|
|
4746
|
+
message: `Duplicate endpoint id "${id}" \u2014 each endpoint must have a unique id`
|
|
4747
|
+
});
|
|
4748
|
+
} else {
|
|
4749
|
+
seenEpIds.add(id);
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4574
4753
|
}
|
|
4575
4754
|
if (obj["behaviors"] !== void 0) {
|
|
4576
4755
|
if (!Array.isArray(obj["behaviors"])) {
|
|
@@ -4627,6 +4806,21 @@ function validateModel(raw, path10, errors) {
|
|
|
4627
4806
|
for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
|
|
4628
4807
|
validateModelField(fields[j2], `${path10}.fields[${j2}]`, errors);
|
|
4629
4808
|
}
|
|
4809
|
+
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
4810
|
+
for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
|
|
4811
|
+
const f = fields[j2];
|
|
4812
|
+
if (f && typeof f === "object" && typeof f["name"] === "string") {
|
|
4813
|
+
const name = f["name"];
|
|
4814
|
+
if (seenFieldNames.has(name)) {
|
|
4815
|
+
errors.push({
|
|
4816
|
+
path: `${path10}.fields[${j2}].name`,
|
|
4817
|
+
message: `Duplicate field name "${name}" \u2014 each field within a model must have a unique name`
|
|
4818
|
+
});
|
|
4819
|
+
} else {
|
|
4820
|
+
seenFieldNames.add(name);
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4630
4824
|
}
|
|
4631
4825
|
if (m["relations"] !== void 0) {
|
|
4632
4826
|
if (!Array.isArray(m["relations"])) {
|
|
@@ -5657,9 +5851,10 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
|
|
|
5657
5851
|
console.log(chalk8.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
|
|
5658
5852
|
console.log(chalk8.gray(` Spec: ${specFilePath}`));
|
|
5659
5853
|
try {
|
|
5660
|
-
|
|
5854
|
+
spawnSync(claudeCmd, ["-p", promptContent], {
|
|
5661
5855
|
cwd: workingDir,
|
|
5662
|
-
stdio: "inherit"
|
|
5856
|
+
stdio: "inherit",
|
|
5857
|
+
shell: false
|
|
5663
5858
|
});
|
|
5664
5859
|
console.log(chalk8.green("\n \u2714 Claude Code completed."));
|
|
5665
5860
|
} catch {
|
|
@@ -5711,9 +5906,10 @@ Full spec is at: ${specFilePath}
|
|
|
5711
5906
|
Implement ONLY this task. Do not implement other tasks.`;
|
|
5712
5907
|
let taskStatus = "done";
|
|
5713
5908
|
try {
|
|
5714
|
-
|
|
5909
|
+
spawnSync(claudeCmd, ["-p", taskPrompt], {
|
|
5715
5910
|
cwd: workingDir,
|
|
5716
|
-
stdio: "inherit"
|
|
5911
|
+
stdio: "inherit",
|
|
5912
|
+
shell: false
|
|
5717
5913
|
});
|
|
5718
5914
|
completed++;
|
|
5719
5915
|
} catch {
|
|
@@ -6116,27 +6312,202 @@ function printTaskProgress(completed, total, task, mode) {
|
|
|
6116
6312
|
}
|
|
6117
6313
|
|
|
6118
6314
|
// core/reviewer.ts
|
|
6119
|
-
import
|
|
6315
|
+
import chalk10 from "chalk";
|
|
6120
6316
|
import { execSync as execSync2 } from "child_process";
|
|
6121
|
-
import * as
|
|
6317
|
+
import * as path8 from "path";
|
|
6318
|
+
import * as fs12 from "fs-extra";
|
|
6319
|
+
|
|
6320
|
+
// core/constitution-generator.ts
|
|
6321
|
+
import chalk9 from "chalk";
|
|
6122
6322
|
import * as fs11 from "fs-extra";
|
|
6323
|
+
import * as path7 from "path";
|
|
6324
|
+
|
|
6325
|
+
// prompts/constitution.prompt.ts
|
|
6326
|
+
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.
|
|
6327
|
+
|
|
6328
|
+
Output a Markdown document with EXACTLY these sections. Be specific and derive rules directly from the observed codebase \u2014 no generic advice.
|
|
6329
|
+
|
|
6330
|
+
---
|
|
6331
|
+
|
|
6332
|
+
# Project Constitution
|
|
6333
|
+
|
|
6334
|
+
## 1. \u67B6\u6784\u89C4\u5219 (Architecture Rules)
|
|
6335
|
+
\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
|
|
6336
|
+
- \u5206\u5C42\u67B6\u6784\u89C4\u5219\uFF08\u5982\uFF1Aroutes \u2192 controllers \u2192 services \u2192 DB\uFF09
|
|
6337
|
+
- \u7981\u6B62\u8DE8\u5C42\u76F4\u63A5\u8C03\u7528\u7684\u89C4\u5219
|
|
6338
|
+
- \u6A21\u5757\u7EC4\u7EC7\u89C4\u8303
|
|
6339
|
+
|
|
6340
|
+
## 2. \u547D\u540D\u89C4\u8303 (Naming Conventions)
|
|
6341
|
+
- \u6587\u4EF6\u547D\u540D\u89C4\u5219\uFF08\u9A7C\u5CF0/\u4E0B\u5212\u7EBF/kebab\uFF09
|
|
6342
|
+
- \u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u7684\u547D\u540D\u6A21\u5F0F
|
|
6343
|
+
- \u8DEF\u7531\u8DEF\u5F84\u7684\u547D\u540D\u89C4\u8303
|
|
6344
|
+
|
|
6345
|
+
## 3. API \u89C4\u8303 (API Patterns)
|
|
6346
|
+
- \u8DEF\u7531\u524D\u7F00\u89C4\u5219\uFF08\u5982 /api/v1/client/... vs /api/v1/admin/...\uFF09
|
|
6347
|
+
- \u7EDF\u4E00\u54CD\u5E94\u7ED3\u6784\u6A21\u677F\uFF08code/message/data \u683C\u5F0F\uFF09
|
|
6348
|
+
- \u9519\u8BEF\u7801\u89C4\u8303\uFF08\u5DF2\u6709\u7684\u9519\u8BEF\u7801\u8303\u56F4\u548C\u542B\u4E49\uFF09
|
|
6349
|
+
- \u8BA4\u8BC1/\u9274\u6743\u6A21\u5F0F\uFF08middleware \u540D\u79F0\u548C\u4F7F\u7528\u4F4D\u7F6E\uFF09
|
|
6350
|
+
|
|
6351
|
+
## 4. \u6570\u636E\u5C42\u89C4\u8303 (Data Layer Rules)
|
|
6352
|
+
- ORM/\u6570\u636E\u5E93\u8BBF\u95EE\u89C4\u5219\uFF08\u4EC5\u901A\u8FC7 service \u5C42\u8BBF\u95EE\uFF1F\u76F4\u63A5\u7528 Prisma/Mongoose\uFF1F\uFF09
|
|
6353
|
+
- \u5DF2\u6709\u7684\u6570\u636E\u6A21\u578B\u547D\u540D\u89C4\u8303
|
|
6354
|
+
- \u4E8B\u52A1\u5904\u7406\u6A21\u5F0F
|
|
6355
|
+
|
|
6356
|
+
## 5. \u9519\u8BEF\u5904\u7406\u89C4\u8303 (Error Handling Patterns)
|
|
6357
|
+
- \u7EDF\u4E00\u9519\u8BEF\u5904\u7406 middleware \u7684\u4F7F\u7528\u89C4\u5219
|
|
6358
|
+
- \u9519\u8BEF\u629B\u51FA\u548C\u6355\u83B7\u7684\u6A21\u5F0F
|
|
6359
|
+
- \u5DF2\u77E5\u9519\u8BEF\u7801\u5217\u8868\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF09
|
|
6360
|
+
|
|
6361
|
+
## 6. \u7981\u533A (Red Lines \u2014 Never Violate)
|
|
6362
|
+
\u660E\u786E\u5217\u51FA\u7EDD\u5BF9\u4E0D\u80FD\u505A\u7684\u4E8B\u60C5\uFF08\u4ECE\u73B0\u6709\u4EE3\u7801/\u67B6\u6784\u63A8\u65AD\uFF09\uFF1A
|
|
6363
|
+
- [ ] \u7981\u6B62 ...
|
|
6364
|
+
- [ ] \u7981\u6B62 ...
|
|
6365
|
+
|
|
6366
|
+
## 7. \u6D4B\u8BD5\u89C4\u8303 (Testing Rules)
|
|
6367
|
+
- \u6D4B\u8BD5\u6587\u4EF6\u5B58\u653E\u4F4D\u7F6E
|
|
6368
|
+
- \u5FC5\u987B\u8986\u76D6\u7684\u6D4B\u8BD5\u573A\u666F\u7C7B\u578B
|
|
6369
|
+
- \u6D4B\u8BD5\u6846\u67B6\u548C\u5DE5\u5177
|
|
6370
|
+
|
|
6371
|
+
## 8. \u5171\u4EAB\u914D\u7F6E\u6587\u4EF6\u6E05\u5355 (Shared Config Files \u2014 Append-Only)
|
|
6372
|
+
|
|
6373
|
+
CRITICAL: The following files are **singleton config files** that already exist in the project.
|
|
6374
|
+
When any feature needs to add entries (translations, constants, routes, enums, etc.), they MUST be
|
|
6375
|
+
appended/merged into these existing files. **NEVER create a new parallel file.**
|
|
6376
|
+
|
|
6377
|
+
For each discovered file, list it as:
|
|
6378
|
+
- \`<relative-path>\` \u2014 <category> \u2014 **MODIFY ONLY, never recreate**
|
|
6379
|
+
|
|
6380
|
+
If the project context includes i18n/locale files: list ALL of them with their paths.
|
|
6381
|
+
If the project context includes constants/enums files: list ALL of them.
|
|
6382
|
+
If the project context includes route index files: list ALL of them.
|
|
6383
|
+
If none are provided in the context, write: "(No shared config files detected \u2014 will be populated on first run)"
|
|
6384
|
+
|
|
6385
|
+
---
|
|
6386
|
+
|
|
6387
|
+
Be concise. Each rule must be specific enough to enforce, not a vague principle.
|
|
6388
|
+
**Section 8 is the most important section for preventing file duplication bugs.**`;
|
|
6389
|
+
|
|
6390
|
+
// core/constitution-generator.ts
|
|
6391
|
+
var CONSTITUTION_FILE = ".ai-spec-constitution.md";
|
|
6392
|
+
var ConstitutionGenerator = class {
|
|
6393
|
+
constructor(provider) {
|
|
6394
|
+
this.provider = provider;
|
|
6395
|
+
}
|
|
6396
|
+
async generate(projectRoot) {
|
|
6397
|
+
const loader = new ContextLoader(projectRoot);
|
|
6398
|
+
const context = await loader.loadProjectContext();
|
|
6399
|
+
const prompt = buildConstitutionPrompt(context, projectRoot);
|
|
6400
|
+
return this.provider.generate(prompt, constitutionSystemPrompt);
|
|
6401
|
+
}
|
|
6402
|
+
async saveConstitution(projectRoot, content) {
|
|
6403
|
+
const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
|
|
6404
|
+
await fs11.writeFile(filePath, content, "utf-8");
|
|
6405
|
+
return filePath;
|
|
6406
|
+
}
|
|
6407
|
+
};
|
|
6408
|
+
function buildConstitutionPrompt(context, projectRoot) {
|
|
6409
|
+
const parts = [
|
|
6410
|
+
"Analyze this project and generate its Project Constitution.\n",
|
|
6411
|
+
`=== Tech Stack ===
|
|
6412
|
+
${context.techStack.join(", ") || "unknown"}
|
|
6413
|
+
`,
|
|
6414
|
+
`=== Dependencies (top 30) ===
|
|
6415
|
+
${context.dependencies.slice(0, 30).join(", ")}
|
|
6416
|
+
`
|
|
6417
|
+
];
|
|
6418
|
+
if (context.apiStructure.length > 0) {
|
|
6419
|
+
parts.push(`=== API/Route Files ===
|
|
6420
|
+
${context.apiStructure.join("\n")}
|
|
6421
|
+
`);
|
|
6422
|
+
}
|
|
6423
|
+
if (context.routeSummary) {
|
|
6424
|
+
parts.push(`=== Route Code Samples ===
|
|
6425
|
+
${context.routeSummary}
|
|
6426
|
+
`);
|
|
6427
|
+
}
|
|
6428
|
+
if (context.schema) {
|
|
6429
|
+
parts.push(`=== Prisma Schema ===
|
|
6430
|
+
${context.schema.slice(0, 4e3)}
|
|
6431
|
+
`);
|
|
6432
|
+
}
|
|
6433
|
+
if (context.errorPatterns) {
|
|
6434
|
+
parts.push(`=== Error Handling Patterns ===
|
|
6435
|
+
${context.errorPatterns}
|
|
6436
|
+
`);
|
|
6437
|
+
}
|
|
6438
|
+
if (context.sharedConfigFiles && context.sharedConfigFiles.length > 0) {
|
|
6439
|
+
const grouped = context.sharedConfigFiles.reduce(
|
|
6440
|
+
(acc, f) => {
|
|
6441
|
+
(acc[f.category] ??= []).push(f);
|
|
6442
|
+
return acc;
|
|
6443
|
+
},
|
|
6444
|
+
{}
|
|
6445
|
+
);
|
|
6446
|
+
const sections = [];
|
|
6447
|
+
for (const [category, files] of Object.entries(grouped)) {
|
|
6448
|
+
sections.push(`--- ${category} ---`);
|
|
6449
|
+
for (const f of files) {
|
|
6450
|
+
sections.push(`File: ${f.path}
|
|
6451
|
+
${f.preview.slice(0, 600)}
|
|
6452
|
+
`);
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
parts.push(`=== Existing Shared Config Files (Append-Only \u2014 NEVER Recreate) ===
|
|
6456
|
+
${sections.join("\n")}
|
|
6457
|
+
`);
|
|
6458
|
+
}
|
|
6459
|
+
return parts.join("\n");
|
|
6460
|
+
}
|
|
6461
|
+
async function loadConstitution(projectRoot) {
|
|
6462
|
+
const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
|
|
6463
|
+
if (await fs11.pathExists(filePath)) {
|
|
6464
|
+
return fs11.readFile(filePath, "utf-8");
|
|
6465
|
+
}
|
|
6466
|
+
return void 0;
|
|
6467
|
+
}
|
|
6468
|
+
function printConstitutionHint(exists) {
|
|
6469
|
+
if (!exists) {
|
|
6470
|
+
console.log(
|
|
6471
|
+
chalk9.yellow(
|
|
6472
|
+
" \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
|
|
6473
|
+
)
|
|
6474
|
+
);
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6478
|
+
// core/reviewer.ts
|
|
6479
|
+
async function loadAccumulatedLessons(projectRoot) {
|
|
6480
|
+
const constitutionPath = path8.join(projectRoot, CONSTITUTION_FILE);
|
|
6481
|
+
let content;
|
|
6482
|
+
try {
|
|
6483
|
+
content = await fs12.readFile(constitutionPath, "utf-8");
|
|
6484
|
+
} catch {
|
|
6485
|
+
return null;
|
|
6486
|
+
}
|
|
6487
|
+
const marker = "## 9. \u79EF\u7D2F\u6559\u8BAD";
|
|
6488
|
+
const idx = content.indexOf(marker);
|
|
6489
|
+
if (idx === -1) return null;
|
|
6490
|
+
const section = content.slice(idx);
|
|
6491
|
+
const nextSection = section.slice(marker.length).match(/\n## \d/);
|
|
6492
|
+
return nextSection ? section.slice(0, marker.length + nextSection.index) : section;
|
|
6493
|
+
}
|
|
6123
6494
|
var REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
|
|
6124
6495
|
async function loadReviewHistory(projectRoot) {
|
|
6125
|
-
const historyPath =
|
|
6496
|
+
const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6126
6497
|
try {
|
|
6127
|
-
if (await
|
|
6128
|
-
return await
|
|
6498
|
+
if (await fs12.pathExists(historyPath)) {
|
|
6499
|
+
return await fs12.readJson(historyPath);
|
|
6129
6500
|
}
|
|
6130
6501
|
} catch {
|
|
6131
6502
|
}
|
|
6132
6503
|
return [];
|
|
6133
6504
|
}
|
|
6134
6505
|
async function appendReviewHistory(projectRoot, entry) {
|
|
6135
|
-
const historyPath =
|
|
6506
|
+
const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6136
6507
|
const existing = await loadReviewHistory(projectRoot);
|
|
6137
6508
|
const updated = [...existing, entry].slice(-20);
|
|
6138
6509
|
try {
|
|
6139
|
-
await
|
|
6510
|
+
await fs12.writeJson(historyPath, updated, { spaces: 2 });
|
|
6140
6511
|
} catch {
|
|
6141
6512
|
}
|
|
6142
6513
|
}
|
|
@@ -6144,6 +6515,14 @@ function extractScore(reviewText) {
|
|
|
6144
6515
|
const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
|
|
6145
6516
|
return match ? parseFloat(match[1]) : 0;
|
|
6146
6517
|
}
|
|
6518
|
+
function extractComplianceScore(complianceText) {
|
|
6519
|
+
const match = complianceText.match(/ComplianceScore:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
|
|
6520
|
+
return match ? parseFloat(match[1]) : 0;
|
|
6521
|
+
}
|
|
6522
|
+
function extractMissingCount(complianceText) {
|
|
6523
|
+
const summaryMatch = complianceText.match(/Missing:\s*(\d+)/i);
|
|
6524
|
+
return summaryMatch ? parseInt(summaryMatch[1], 10) : 0;
|
|
6525
|
+
}
|
|
6147
6526
|
function extractImpactLevel(reviewText) {
|
|
6148
6527
|
const match = reviewText.match(/影响等级[::]\s*(低|中|高)/);
|
|
6149
6528
|
return match ? match[1] : void 0;
|
|
@@ -6162,7 +6541,7 @@ function buildHistoryContext(history) {
|
|
|
6162
6541
|
const lines = ["\n=== \u5386\u53F2\u5BA1\u67E5\u95EE\u9898 (Past Review Issues \u2014 check if any recur) ==="];
|
|
6163
6542
|
for (const entry of recent) {
|
|
6164
6543
|
lines.push(`
|
|
6165
|
-
[${entry.date}] ${
|
|
6544
|
+
[${entry.date}] ${path8.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
|
|
6166
6545
|
entry.topIssues.forEach((issue) => lines.push(` \xB7 ${issue}`));
|
|
6167
6546
|
}
|
|
6168
6547
|
return lines.join("\n") + "\n";
|
|
@@ -6197,23 +6576,52 @@ var CodeReviewer = class {
|
|
|
6197
6576
|
};
|
|
6198
6577
|
}
|
|
6199
6578
|
/**
|
|
6200
|
-
*
|
|
6201
|
-
* Pass
|
|
6579
|
+
* Four-pass review:
|
|
6580
|
+
* Pass 0 — spec compliance (exhaustive requirement coverage audit)
|
|
6581
|
+
* Pass 1 — architecture (layer separation, contract design, auth posture)
|
|
6202
6582
|
* Pass 2 — implementation details (validation, error handling, edge cases)
|
|
6203
6583
|
* + historical issue recurrence check
|
|
6204
6584
|
* Pass 3 — impact assessment + code complexity
|
|
6205
6585
|
*/
|
|
6206
6586
|
async runThreePassReview(specContent, codeContext, specFile) {
|
|
6207
|
-
|
|
6208
|
-
|
|
6587
|
+
let complianceReview = "";
|
|
6588
|
+
if (specContent && specContent.trim() && specContent !== "(No spec \u2014 review for general code quality)") {
|
|
6589
|
+
console.log(chalk10.gray(" Pass 0/3: Spec compliance check..."));
|
|
6590
|
+
const compliancePrompt = `Check whether the implementation covers every requirement in the spec.
|
|
6209
6591
|
|
|
6592
|
+
=== Feature Spec ===
|
|
6593
|
+
${specContent}
|
|
6594
|
+
|
|
6595
|
+
=== Code ===
|
|
6596
|
+
${codeContext}`;
|
|
6597
|
+
complianceReview = await this.provider.generate(compliancePrompt, specComplianceSystemPrompt);
|
|
6598
|
+
const complianceScore2 = extractComplianceScore(complianceReview);
|
|
6599
|
+
const missingCount = extractMissingCount(complianceReview);
|
|
6600
|
+
if (complianceScore2 > 0) {
|
|
6601
|
+
const scoreColor = complianceScore2 >= 8 ? chalk10.green : complianceScore2 >= 6 ? chalk10.yellow : chalk10.red;
|
|
6602
|
+
console.log(
|
|
6603
|
+
chalk10.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? chalk10.red(` \xB7 ${missingCount} missing requirement(s)`) : chalk10.green(" \xB7 all requirements covered"))
|
|
6604
|
+
);
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6607
|
+
console.log(chalk10.gray(` Pass 1/3: Architecture review...`));
|
|
6608
|
+
const accumulatedLessons = await loadAccumulatedLessons(this.projectRoot);
|
|
6609
|
+
const archPrompt = `Review the architecture of this change.
|
|
6610
|
+
${complianceReview ? `
|
|
6611
|
+
=== Spec Compliance Report (Pass 0 \u2014 already audited, do NOT re-audit missing requirements) ===
|
|
6612
|
+
${complianceReview}
|
|
6613
|
+
` : ""}
|
|
6614
|
+
${accumulatedLessons ? `
|
|
6615
|
+
=== \xA79 \u5386\u53F2\u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons \u2014 check if any are repeated in this code) ===
|
|
6616
|
+
${accumulatedLessons}
|
|
6617
|
+
` : ""}
|
|
6210
6618
|
=== Feature Spec ===
|
|
6211
6619
|
${specContent || "(No spec \u2014 review for general code quality)"}
|
|
6212
6620
|
|
|
6213
6621
|
=== Code ===
|
|
6214
6622
|
${codeContext}`;
|
|
6215
6623
|
const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
|
|
6216
|
-
console.log(
|
|
6624
|
+
console.log(chalk10.gray(" Pass 2/3: Implementation review..."));
|
|
6217
6625
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6218
6626
|
const historyContext = buildHistoryContext(history);
|
|
6219
6627
|
const implPrompt = `Review the implementation details of this change.
|
|
@@ -6228,7 +6636,7 @@ ${codeContext}
|
|
|
6228
6636
|
${archReview}
|
|
6229
6637
|
${historyContext}`;
|
|
6230
6638
|
const implReview = await this.provider.generate(implPrompt, reviewImplementationSystemPrompt);
|
|
6231
|
-
console.log(
|
|
6639
|
+
console.log(chalk10.gray(" Pass 3/3: Impact & complexity assessment..."));
|
|
6232
6640
|
const impactPrompt = `Assess the impact and complexity of this change.
|
|
6233
6641
|
|
|
6234
6642
|
=== Feature Spec ===
|
|
@@ -6244,24 +6652,23 @@ ${archReview}
|
|
|
6244
6652
|
${implReview}`;
|
|
6245
6653
|
const impactReview = await this.provider.generate(impactPrompt, reviewImpactComplexitySystemPrompt);
|
|
6246
6654
|
const sep = "\u2500".repeat(52);
|
|
6247
|
-
const
|
|
6655
|
+
const parts = complianceReview ? [complianceReview, archReview, implReview, impactReview] : [archReview, implReview, impactReview];
|
|
6656
|
+
const combined = parts.join(`
|
|
6248
6657
|
|
|
6249
6658
|
${sep}
|
|
6250
6659
|
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
${sep}
|
|
6254
|
-
|
|
6255
|
-
${impactReview}`;
|
|
6660
|
+
`);
|
|
6256
6661
|
const score = extractScore(implReview) || extractScore(archReview);
|
|
6662
|
+
const complianceScore = extractComplianceScore(complianceReview);
|
|
6257
6663
|
const topIssues = extractTopIssues(implReview);
|
|
6258
6664
|
const impactLevel = extractImpactLevel(impactReview);
|
|
6259
6665
|
const complexityLevel = extractComplexityLevel(impactReview);
|
|
6260
6666
|
if (score > 0 && specFile) {
|
|
6261
6667
|
await appendReviewHistory(this.projectRoot, {
|
|
6262
6668
|
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
6263
|
-
specFile:
|
|
6669
|
+
specFile: path8.relative(this.projectRoot, specFile),
|
|
6264
6670
|
score,
|
|
6671
|
+
...complianceScore > 0 ? { complianceScore } : {},
|
|
6265
6672
|
topIssues,
|
|
6266
6673
|
...impactLevel ? { impactLevel } : {},
|
|
6267
6674
|
...complexityLevel ? { complexityLevel } : {}
|
|
@@ -6270,43 +6677,43 @@ ${impactReview}`;
|
|
|
6270
6677
|
return combined;
|
|
6271
6678
|
}
|
|
6272
6679
|
async reviewCode(specContent, specFile) {
|
|
6273
|
-
console.log(
|
|
6680
|
+
console.log(chalk10.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"));
|
|
6274
6681
|
const diff = this.getGitDiff();
|
|
6275
6682
|
if (!diff.trim()) {
|
|
6276
6683
|
console.log(
|
|
6277
|
-
|
|
6684
|
+
chalk10.yellow(" No git diff found. Stage or commit changes first, then run review.")
|
|
6278
6685
|
);
|
|
6279
|
-
console.log(
|
|
6686
|
+
console.log(chalk10.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
|
|
6280
6687
|
return "No changes";
|
|
6281
6688
|
}
|
|
6282
6689
|
const { files, added, removed } = this.getDiffStats(diff);
|
|
6283
6690
|
console.log(
|
|
6284
|
-
|
|
6691
|
+
chalk10.gray(` Diff: ${files} file(s), ${chalk10.green("+" + added)} ${chalk10.red("-" + removed)}`)
|
|
6285
6692
|
);
|
|
6286
6693
|
console.log(
|
|
6287
|
-
|
|
6694
|
+
chalk10.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6288
6695
|
);
|
|
6289
6696
|
const codeContext = diff.slice(0, 1e4);
|
|
6290
6697
|
const reviewResult = await this.runThreePassReview(specContent, codeContext, specFile);
|
|
6291
|
-
console.log(
|
|
6698
|
+
console.log(chalk10.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"));
|
|
6292
6699
|
console.log(reviewResult);
|
|
6293
|
-
console.log(
|
|
6700
|
+
console.log(chalk10.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"));
|
|
6294
6701
|
return reviewResult;
|
|
6295
6702
|
}
|
|
6296
6703
|
/**
|
|
6297
6704
|
* Review directly from generated file contents (for api mode where git diff is empty).
|
|
6298
6705
|
*/
|
|
6299
6706
|
async reviewFiles(specContent, filePaths, workingDir, specFile) {
|
|
6300
|
-
console.log(
|
|
6301
|
-
console.log(
|
|
6707
|
+
console.log(chalk10.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6708
|
+
console.log(chalk10.gray(` Reviewing ${filePaths.length} generated file(s)...`));
|
|
6302
6709
|
console.log(
|
|
6303
|
-
|
|
6710
|
+
chalk10.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6304
6711
|
);
|
|
6305
6712
|
let filesSection = "";
|
|
6306
6713
|
for (const filePath of filePaths) {
|
|
6307
|
-
const fullPath =
|
|
6714
|
+
const fullPath = path8.join(workingDir, filePath);
|
|
6308
6715
|
try {
|
|
6309
|
-
const content = await
|
|
6716
|
+
const content = await fs12.readFile(fullPath, "utf-8");
|
|
6310
6717
|
filesSection += `
|
|
6311
6718
|
|
|
6312
6719
|
=== ${filePath} ===
|
|
@@ -6321,189 +6728,31 @@ ${content.slice(0, 3e3)}`;
|
|
|
6321
6728
|
}
|
|
6322
6729
|
}
|
|
6323
6730
|
const reviewResult = await this.runThreePassReview(specContent, filesSection, specFile);
|
|
6324
|
-
console.log(
|
|
6731
|
+
console.log(chalk10.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"));
|
|
6325
6732
|
console.log(reviewResult);
|
|
6326
|
-
console.log(
|
|
6733
|
+
console.log(chalk10.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"));
|
|
6327
6734
|
return reviewResult;
|
|
6328
6735
|
}
|
|
6329
6736
|
/** Print score trend from history (last N reviews) */
|
|
6330
6737
|
async printScoreTrend(limit = 5) {
|
|
6331
6738
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6332
6739
|
if (history.length === 0) {
|
|
6333
|
-
console.log(
|
|
6740
|
+
console.log(chalk10.gray(" No review history yet."));
|
|
6334
6741
|
return;
|
|
6335
6742
|
}
|
|
6336
6743
|
const recent = history.slice(-limit);
|
|
6337
|
-
console.log(
|
|
6744
|
+
console.log(chalk10.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"));
|
|
6338
6745
|
for (const entry of recent) {
|
|
6339
6746
|
const bar = "\u2588".repeat(entry.score) + "\u2591".repeat(10 - entry.score);
|
|
6340
|
-
const color = entry.score >= 8 ?
|
|
6341
|
-
const impactTag = entry.impactLevel ?
|
|
6342
|
-
const complexityTag = entry.complexityLevel ?
|
|
6343
|
-
console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${
|
|
6747
|
+
const color = entry.score >= 8 ? chalk10.green : entry.score >= 6 ? chalk10.yellow : chalk10.red;
|
|
6748
|
+
const impactTag = entry.impactLevel ? chalk10.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? chalk10.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? chalk10.yellow(entry.impactLevel) : chalk10.green(entry.impactLevel)}`) : "";
|
|
6749
|
+
const complexityTag = entry.complexityLevel ? chalk10.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? chalk10.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? chalk10.yellow(entry.complexityLevel) : chalk10.green(entry.complexityLevel)}`) : "";
|
|
6750
|
+
console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
|
|
6344
6751
|
}
|
|
6345
|
-
console.log(
|
|
6752
|
+
console.log(chalk10.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"));
|
|
6346
6753
|
}
|
|
6347
6754
|
};
|
|
6348
6755
|
|
|
6349
|
-
// core/constitution-generator.ts
|
|
6350
|
-
import chalk10 from "chalk";
|
|
6351
|
-
import * as fs12 from "fs-extra";
|
|
6352
|
-
import * as path8 from "path";
|
|
6353
|
-
|
|
6354
|
-
// prompts/constitution.prompt.ts
|
|
6355
|
-
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.
|
|
6356
|
-
|
|
6357
|
-
Output a Markdown document with EXACTLY these sections. Be specific and derive rules directly from the observed codebase \u2014 no generic advice.
|
|
6358
|
-
|
|
6359
|
-
---
|
|
6360
|
-
|
|
6361
|
-
# Project Constitution
|
|
6362
|
-
|
|
6363
|
-
## 1. \u67B6\u6784\u89C4\u5219 (Architecture Rules)
|
|
6364
|
-
\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
|
|
6365
|
-
- \u5206\u5C42\u67B6\u6784\u89C4\u5219\uFF08\u5982\uFF1Aroutes \u2192 controllers \u2192 services \u2192 DB\uFF09
|
|
6366
|
-
- \u7981\u6B62\u8DE8\u5C42\u76F4\u63A5\u8C03\u7528\u7684\u89C4\u5219
|
|
6367
|
-
- \u6A21\u5757\u7EC4\u7EC7\u89C4\u8303
|
|
6368
|
-
|
|
6369
|
-
## 2. \u547D\u540D\u89C4\u8303 (Naming Conventions)
|
|
6370
|
-
- \u6587\u4EF6\u547D\u540D\u89C4\u5219\uFF08\u9A7C\u5CF0/\u4E0B\u5212\u7EBF/kebab\uFF09
|
|
6371
|
-
- \u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u7684\u547D\u540D\u6A21\u5F0F
|
|
6372
|
-
- \u8DEF\u7531\u8DEF\u5F84\u7684\u547D\u540D\u89C4\u8303
|
|
6373
|
-
|
|
6374
|
-
## 3. API \u89C4\u8303 (API Patterns)
|
|
6375
|
-
- \u8DEF\u7531\u524D\u7F00\u89C4\u5219\uFF08\u5982 /api/v1/client/... vs /api/v1/admin/...\uFF09
|
|
6376
|
-
- \u7EDF\u4E00\u54CD\u5E94\u7ED3\u6784\u6A21\u677F\uFF08code/message/data \u683C\u5F0F\uFF09
|
|
6377
|
-
- \u9519\u8BEF\u7801\u89C4\u8303\uFF08\u5DF2\u6709\u7684\u9519\u8BEF\u7801\u8303\u56F4\u548C\u542B\u4E49\uFF09
|
|
6378
|
-
- \u8BA4\u8BC1/\u9274\u6743\u6A21\u5F0F\uFF08middleware \u540D\u79F0\u548C\u4F7F\u7528\u4F4D\u7F6E\uFF09
|
|
6379
|
-
|
|
6380
|
-
## 4. \u6570\u636E\u5C42\u89C4\u8303 (Data Layer Rules)
|
|
6381
|
-
- ORM/\u6570\u636E\u5E93\u8BBF\u95EE\u89C4\u5219\uFF08\u4EC5\u901A\u8FC7 service \u5C42\u8BBF\u95EE\uFF1F\u76F4\u63A5\u7528 Prisma/Mongoose\uFF1F\uFF09
|
|
6382
|
-
- \u5DF2\u6709\u7684\u6570\u636E\u6A21\u578B\u547D\u540D\u89C4\u8303
|
|
6383
|
-
- \u4E8B\u52A1\u5904\u7406\u6A21\u5F0F
|
|
6384
|
-
|
|
6385
|
-
## 5. \u9519\u8BEF\u5904\u7406\u89C4\u8303 (Error Handling Patterns)
|
|
6386
|
-
- \u7EDF\u4E00\u9519\u8BEF\u5904\u7406 middleware \u7684\u4F7F\u7528\u89C4\u5219
|
|
6387
|
-
- \u9519\u8BEF\u629B\u51FA\u548C\u6355\u83B7\u7684\u6A21\u5F0F
|
|
6388
|
-
- \u5DF2\u77E5\u9519\u8BEF\u7801\u5217\u8868\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF09
|
|
6389
|
-
|
|
6390
|
-
## 6. \u7981\u533A (Red Lines \u2014 Never Violate)
|
|
6391
|
-
\u660E\u786E\u5217\u51FA\u7EDD\u5BF9\u4E0D\u80FD\u505A\u7684\u4E8B\u60C5\uFF08\u4ECE\u73B0\u6709\u4EE3\u7801/\u67B6\u6784\u63A8\u65AD\uFF09\uFF1A
|
|
6392
|
-
- [ ] \u7981\u6B62 ...
|
|
6393
|
-
- [ ] \u7981\u6B62 ...
|
|
6394
|
-
|
|
6395
|
-
## 7. \u6D4B\u8BD5\u89C4\u8303 (Testing Rules)
|
|
6396
|
-
- \u6D4B\u8BD5\u6587\u4EF6\u5B58\u653E\u4F4D\u7F6E
|
|
6397
|
-
- \u5FC5\u987B\u8986\u76D6\u7684\u6D4B\u8BD5\u573A\u666F\u7C7B\u578B
|
|
6398
|
-
- \u6D4B\u8BD5\u6846\u67B6\u548C\u5DE5\u5177
|
|
6399
|
-
|
|
6400
|
-
## 8. \u5171\u4EAB\u914D\u7F6E\u6587\u4EF6\u6E05\u5355 (Shared Config Files \u2014 Append-Only)
|
|
6401
|
-
|
|
6402
|
-
CRITICAL: The following files are **singleton config files** that already exist in the project.
|
|
6403
|
-
When any feature needs to add entries (translations, constants, routes, enums, etc.), they MUST be
|
|
6404
|
-
appended/merged into these existing files. **NEVER create a new parallel file.**
|
|
6405
|
-
|
|
6406
|
-
For each discovered file, list it as:
|
|
6407
|
-
- \`<relative-path>\` \u2014 <category> \u2014 **MODIFY ONLY, never recreate**
|
|
6408
|
-
|
|
6409
|
-
If the project context includes i18n/locale files: list ALL of them with their paths.
|
|
6410
|
-
If the project context includes constants/enums files: list ALL of them.
|
|
6411
|
-
If the project context includes route index files: list ALL of them.
|
|
6412
|
-
If none are provided in the context, write: "(No shared config files detected \u2014 will be populated on first run)"
|
|
6413
|
-
|
|
6414
|
-
---
|
|
6415
|
-
|
|
6416
|
-
Be concise. Each rule must be specific enough to enforce, not a vague principle.
|
|
6417
|
-
**Section 8 is the most important section for preventing file duplication bugs.**`;
|
|
6418
|
-
|
|
6419
|
-
// core/constitution-generator.ts
|
|
6420
|
-
var CONSTITUTION_FILE = ".ai-spec-constitution.md";
|
|
6421
|
-
var ConstitutionGenerator = class {
|
|
6422
|
-
constructor(provider) {
|
|
6423
|
-
this.provider = provider;
|
|
6424
|
-
}
|
|
6425
|
-
async generate(projectRoot) {
|
|
6426
|
-
const loader = new ContextLoader(projectRoot);
|
|
6427
|
-
const context = await loader.loadProjectContext();
|
|
6428
|
-
const prompt = buildConstitutionPrompt(context, projectRoot);
|
|
6429
|
-
return this.provider.generate(prompt, constitutionSystemPrompt);
|
|
6430
|
-
}
|
|
6431
|
-
async saveConstitution(projectRoot, content) {
|
|
6432
|
-
const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
|
|
6433
|
-
await fs12.writeFile(filePath, content, "utf-8");
|
|
6434
|
-
return filePath;
|
|
6435
|
-
}
|
|
6436
|
-
};
|
|
6437
|
-
function buildConstitutionPrompt(context, projectRoot) {
|
|
6438
|
-
const parts = [
|
|
6439
|
-
"Analyze this project and generate its Project Constitution.\n",
|
|
6440
|
-
`=== Tech Stack ===
|
|
6441
|
-
${context.techStack.join(", ") || "unknown"}
|
|
6442
|
-
`,
|
|
6443
|
-
`=== Dependencies (top 30) ===
|
|
6444
|
-
${context.dependencies.slice(0, 30).join(", ")}
|
|
6445
|
-
`
|
|
6446
|
-
];
|
|
6447
|
-
if (context.apiStructure.length > 0) {
|
|
6448
|
-
parts.push(`=== API/Route Files ===
|
|
6449
|
-
${context.apiStructure.join("\n")}
|
|
6450
|
-
`);
|
|
6451
|
-
}
|
|
6452
|
-
if (context.routeSummary) {
|
|
6453
|
-
parts.push(`=== Route Code Samples ===
|
|
6454
|
-
${context.routeSummary}
|
|
6455
|
-
`);
|
|
6456
|
-
}
|
|
6457
|
-
if (context.schema) {
|
|
6458
|
-
parts.push(`=== Prisma Schema ===
|
|
6459
|
-
${context.schema.slice(0, 4e3)}
|
|
6460
|
-
`);
|
|
6461
|
-
}
|
|
6462
|
-
if (context.errorPatterns) {
|
|
6463
|
-
parts.push(`=== Error Handling Patterns ===
|
|
6464
|
-
${context.errorPatterns}
|
|
6465
|
-
`);
|
|
6466
|
-
}
|
|
6467
|
-
if (context.sharedConfigFiles && context.sharedConfigFiles.length > 0) {
|
|
6468
|
-
const grouped = context.sharedConfigFiles.reduce(
|
|
6469
|
-
(acc, f) => {
|
|
6470
|
-
(acc[f.category] ??= []).push(f);
|
|
6471
|
-
return acc;
|
|
6472
|
-
},
|
|
6473
|
-
{}
|
|
6474
|
-
);
|
|
6475
|
-
const sections = [];
|
|
6476
|
-
for (const [category, files] of Object.entries(grouped)) {
|
|
6477
|
-
sections.push(`--- ${category} ---`);
|
|
6478
|
-
for (const f of files) {
|
|
6479
|
-
sections.push(`File: ${f.path}
|
|
6480
|
-
${f.preview.slice(0, 600)}
|
|
6481
|
-
`);
|
|
6482
|
-
}
|
|
6483
|
-
}
|
|
6484
|
-
parts.push(`=== Existing Shared Config Files (Append-Only \u2014 NEVER Recreate) ===
|
|
6485
|
-
${sections.join("\n")}
|
|
6486
|
-
`);
|
|
6487
|
-
}
|
|
6488
|
-
return parts.join("\n");
|
|
6489
|
-
}
|
|
6490
|
-
async function loadConstitution(projectRoot) {
|
|
6491
|
-
const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
|
|
6492
|
-
if (await fs12.pathExists(filePath)) {
|
|
6493
|
-
return fs12.readFile(filePath, "utf-8");
|
|
6494
|
-
}
|
|
6495
|
-
return void 0;
|
|
6496
|
-
}
|
|
6497
|
-
function printConstitutionHint(exists) {
|
|
6498
|
-
if (!exists) {
|
|
6499
|
-
console.log(
|
|
6500
|
-
chalk10.yellow(
|
|
6501
|
-
" \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
|
|
6502
|
-
)
|
|
6503
|
-
);
|
|
6504
|
-
}
|
|
6505
|
-
}
|
|
6506
|
-
|
|
6507
6756
|
// core/combined-generator.ts
|
|
6508
6757
|
var TASKS_SEPARATOR = "---TASKS_JSON---";
|
|
6509
6758
|
var tasksInstruction = `
|
|
@@ -6512,11 +6761,21 @@ var tasksInstruction = `
|
|
|
6512
6761
|
After outputting the complete spec above, append EXACTLY this line on its own (no extra text before or after it):
|
|
6513
6762
|
${TASKS_SEPARATOR}
|
|
6514
6763
|
Then output a valid JSON array of implementation tasks. Each element must have these exact fields:
|
|
6515
|
-
{"id":"TASK-001","title":"...","description":"1-2 sentences, specific","layer":"data|infra|service|api|test","filesToTouch":["src/..."],"acceptanceCriteria":["
|
|
6764
|
+
{"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"}
|
|
6765
|
+
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.
|
|
6516
6766
|
Layer order: data \u2192 infra \u2192 service \u2192 api \u2192 test. 4-10 tasks total. filesToTouch must use real paths from the project context.`;
|
|
6517
|
-
async function generateSpecWithTasks(provider, idea, context) {
|
|
6767
|
+
async function generateSpecWithTasks(provider, idea, context, architectureDecision) {
|
|
6518
6768
|
const contextBlock = buildTaskPrompt("", context).trim();
|
|
6519
|
-
const
|
|
6769
|
+
const parts = [idea];
|
|
6770
|
+
if (architectureDecision) {
|
|
6771
|
+
parts.push(
|
|
6772
|
+
`
|
|
6773
|
+
=== Architecture Decision (MUST follow this approach in the spec) ===
|
|
6774
|
+
${architectureDecision}`
|
|
6775
|
+
);
|
|
6776
|
+
}
|
|
6777
|
+
if (contextBlock) parts.push(contextBlock);
|
|
6778
|
+
const fullPrompt = parts.join("\n\n");
|
|
6520
6779
|
const combinedSystemPrompt = specPrompt + tasksInstruction;
|
|
6521
6780
|
const raw = await provider.generate(fullPrompt, combinedSystemPrompt);
|
|
6522
6781
|
return parseSpecAndTasks(raw);
|
|
@@ -6651,6 +6910,9 @@ export {
|
|
|
6651
6910
|
TaskGenerator,
|
|
6652
6911
|
buildTaskPrompt,
|
|
6653
6912
|
createProvider,
|
|
6913
|
+
extractBehavioralContract,
|
|
6914
|
+
extractComplianceScore,
|
|
6915
|
+
extractMissingCount,
|
|
6654
6916
|
generateSpecWithTasks,
|
|
6655
6917
|
isFrontendDeps,
|
|
6656
6918
|
loadConstitution,
|