ai-spec-dev 0.31.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +15 -1
- package/README.md +531 -213
- package/RELEASE_LOG.md +460 -0
- package/cli/commands/config.ts +93 -0
- package/cli/commands/create.ts +1233 -0
- package/cli/commands/dashboard.ts +62 -0
- package/cli/commands/export.ts +66 -0
- package/cli/commands/init.ts +190 -0
- package/cli/commands/learn.ts +30 -0
- package/cli/commands/logs.ts +106 -0
- package/cli/commands/mock.ts +175 -0
- package/cli/commands/model.ts +156 -0
- package/cli/commands/restore.ts +22 -0
- package/cli/commands/review.ts +63 -0
- package/cli/commands/scan.ts +99 -0
- package/cli/commands/trend.ts +36 -0
- package/cli/commands/types.ts +69 -0
- package/cli/commands/update.ts +178 -0
- package/cli/commands/vcr.ts +70 -0
- package/cli/commands/workspace.ts +219 -0
- package/cli/index.ts +34 -2240
- package/cli/utils.ts +83 -0
- package/core/combined-generator.ts +13 -3
- package/core/dashboard-generator.ts +340 -0
- package/core/design-dialogue.ts +124 -0
- package/core/dsl-feedback.ts +285 -0
- package/core/error-feedback.ts +46 -2
- package/core/project-index.ts +301 -0
- package/core/reviewer.ts +84 -6
- package/core/run-logger.ts +109 -3
- package/core/run-trend.ts +261 -0
- package/core/self-evaluator.ts +139 -7
- 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 +6692 -4512
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +6692 -4512
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +420 -224
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +418 -224
- 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 +6 -3
- 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/dsl-extractor.test.ts +264 -0
- package/tests/dsl-feedback.test.ts +266 -0
- package/tests/dsl-validator.test.ts +283 -0
- package/tests/error-feedback.test.ts +292 -0
- package/tests/provider-utils.test.ts +173 -0
- package/tests/run-trend.test.ts +186 -0
- package/tests/self-evaluator.test.ts +339 -0
- package/tests/spec-assessor.test.ts +142 -0
- package/tests/task-generator.test.ts +230 -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";
|
|
@@ -258,17 +265,23 @@ var PROVIDER_CATALOG = {
|
|
|
258
265
|
},
|
|
259
266
|
glm: {
|
|
260
267
|
displayName: "\u667A\u8C31 GLM (Zhipu AI)",
|
|
261
|
-
description: "\u667A\u8C31 AI \u2014 GLM-5 / GLM-4 series
|
|
268
|
+
description: "\u667A\u8C31 AI \u2014 GLM-5.1 / GLM-5 / GLM-4 series",
|
|
262
269
|
models: [
|
|
270
|
+
"glm-5.1",
|
|
271
|
+
// GLM-5.1 — latest flagship (2026)
|
|
263
272
|
"glm-5",
|
|
264
|
-
// GLM-5
|
|
265
|
-
"glm-5-
|
|
273
|
+
// GLM-5 — premium (Max/Pro plans)
|
|
274
|
+
"glm-5-turbo",
|
|
275
|
+
// GLM-5-Turbo — fast & cost-efficient
|
|
276
|
+
"glm-4.7",
|
|
277
|
+
// GLM-4.7
|
|
278
|
+
"glm-4.6",
|
|
279
|
+
// GLM-4.6
|
|
280
|
+
"glm-4.5-air",
|
|
281
|
+
// GLM-4.5-Air — lightweight
|
|
266
282
|
"glm-z1",
|
|
267
|
-
// GLM-Z1 reasoning model
|
|
268
|
-
"glm-z1-flash"
|
|
269
|
-
"glm-4-plus",
|
|
270
|
-
"glm-4-flash",
|
|
271
|
-
"glm-4-long"
|
|
283
|
+
// GLM-Z1 — reasoning model
|
|
284
|
+
"glm-z1-flash"
|
|
272
285
|
],
|
|
273
286
|
envKey: "ZHIPU_API_KEY",
|
|
274
287
|
baseURL: "https://open.bigmodel.cn/api/paas/v4/"
|
|
@@ -464,8 +477,15 @@ var SpecGenerator = class {
|
|
|
464
477
|
constructor(provider) {
|
|
465
478
|
this.provider = provider;
|
|
466
479
|
}
|
|
467
|
-
async generateSpec(idea, context) {
|
|
480
|
+
async generateSpec(idea, context, architectureDecision) {
|
|
468
481
|
const parts = [idea];
|
|
482
|
+
if (architectureDecision) {
|
|
483
|
+
parts.push(
|
|
484
|
+
`
|
|
485
|
+
=== Architecture Decision (MUST follow this approach in the spec) ===
|
|
486
|
+
${architectureDecision}`
|
|
487
|
+
);
|
|
488
|
+
}
|
|
469
489
|
if (context) {
|
|
470
490
|
if (context.constitution) {
|
|
471
491
|
parts.push(
|
|
@@ -4220,34 +4240,110 @@ function getCodeGenSystemPrompt(repoType) {
|
|
|
4220
4240
|
return codeGenSystemPrompt;
|
|
4221
4241
|
}
|
|
4222
4242
|
}
|
|
4223
|
-
var
|
|
4243
|
+
var specComplianceSystemPrompt = `You are a QA Engineer performing a SPEC COMPLIANCE CHECK.
|
|
4244
|
+
|
|
4245
|
+
Your sole job is to verify that the implementation covers every requirement stated in the feature spec.
|
|
4246
|
+
This is a completeness audit \u2014 NOT a code quality review. Do not comment on architecture, style, or implementation details.
|
|
4247
|
+
|
|
4248
|
+
## How to audit:
|
|
4249
|
+
|
|
4250
|
+
1. Parse the spec and extract EVERY stated requirement into these categories:
|
|
4251
|
+
- **Endpoints**: each HTTP method + path listed or implied
|
|
4252
|
+
- **Data Models**: each entity, field, constraint mentioned
|
|
4253
|
+
- **Business Rules**: validations, conditions, calculations stated
|
|
4254
|
+
- **Auth Requirements**: which endpoints need auth, which roles are allowed
|
|
4255
|
+
- **Error Cases**: explicit error codes or failure scenarios mentioned
|
|
4256
|
+
- **Side Effects**: emails sent, events fired, caches invalidated, etc.
|
|
4257
|
+
|
|
4258
|
+
2. For each extracted requirement, check the provided code:
|
|
4259
|
+
- \u2705 **Covered** \u2014 clearly implemented
|
|
4260
|
+
- \u26A0\uFE0F **Partial** \u2014 exists but incomplete (e.g. endpoint exists but missing a field or error case)
|
|
4261
|
+
- \u274C **Missing** \u2014 requirement stated in spec but not found in code
|
|
4262
|
+
|
|
4263
|
+
3. Output a compliance checklist. Be exhaustive \u2014 list every single requirement.
|
|
4264
|
+
|
|
4265
|
+
## Output format:
|
|
4266
|
+
|
|
4267
|
+
## \u{1F4CB} Spec Compliance Report
|
|
4268
|
+
|
|
4269
|
+
### Endpoints
|
|
4270
|
+
\u2705 / \u26A0\uFE0F / \u274C METHOD /path \u2014 one-line status
|
|
4271
|
+
|
|
4272
|
+
### Data Models
|
|
4273
|
+
\u2705 / \u26A0\uFE0F / \u274C ModelName \u2014 one-line status
|
|
4274
|
+
|
|
4275
|
+
### Business Rules
|
|
4276
|
+
\u2705 / \u26A0\uFE0F / \u274C Rule description \u2014 one-line status
|
|
4277
|
+
|
|
4278
|
+
### Auth & Permissions
|
|
4279
|
+
\u2705 / \u26A0\uFE0F / \u274C Requirement \u2014 one-line status
|
|
4280
|
+
|
|
4281
|
+
### Error Cases
|
|
4282
|
+
\u2705 / \u26A0\uFE0F / \u274C Error scenario \u2014 one-line status
|
|
4283
|
+
|
|
4284
|
+
### Side Effects
|
|
4285
|
+
\u2705 / \u26A0\uFE0F / \u274C Side effect \u2014 one-line status (omit section if none in spec)
|
|
4286
|
+
|
|
4287
|
+
---
|
|
4288
|
+
|
|
4289
|
+
## \u{1F4CA} Compliance Summary
|
|
4290
|
+
Covered: N | Partial: N | Missing: N | Total: N
|
|
4291
|
+
|
|
4292
|
+
## \u{1F522} Compliance Score
|
|
4293
|
+
ComplianceScore: X/10
|
|
4294
|
+
|
|
4295
|
+
(10 = all requirements implemented, 0 = nothing implemented.
|
|
4296
|
+
Deduct 1 point per missing requirement, 0.5 per partial.
|
|
4297
|
+
Round to nearest integer.)
|
|
4298
|
+
|
|
4299
|
+
## \u{1F6A8} Blockers (Missing requirements that MUST be implemented before ship)
|
|
4300
|
+
List only \u274C Missing items here, ordered by severity. If none, write "None".
|
|
4301
|
+
|
|
4302
|
+
---
|
|
4303
|
+
|
|
4304
|
+
IMPORTANT: Be exhaustive. A requirement not listed here is assumed to be covered.
|
|
4305
|
+
If the spec is vague, note the ambiguity as \u26A0\uFE0F Partial rather than assuming coverage.`;
|
|
4306
|
+
var reviewArchitectureSystemPrompt = `You are a Senior Software Architect reviewing the HIGH-LEVEL DESIGN of a code change.
|
|
4307
|
+
|
|
4308
|
+
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
4309
|
|
|
4225
4310
|
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?
|
|
4311
|
+
1. **Layer separation** \u2014 Does each layer have the right responsibilities? (e.g., no business logic in controllers, no HTTP in stores)
|
|
4312
|
+
2. **API contract quality** \u2014 Are request/response shapes well-designed? Are error codes consistent with project conventions?
|
|
4313
|
+
3. **Data model integrity** \u2014 Are constraints, unique fields, and relationships modelled correctly?
|
|
4314
|
+
4. **Security posture** \u2014 Are auth checks applied correctly? Any privilege escalation risks?
|
|
4231
4315
|
|
|
4232
4316
|
DO NOT comment on:
|
|
4317
|
+
- Whether specific endpoints or features are missing (covered by Pass 0)
|
|
4233
4318
|
- Code style, naming conventions, formatting
|
|
4234
4319
|
- Minor implementation details (variable names, inline comments)
|
|
4235
4320
|
- Performance micro-optimizations
|
|
4236
4321
|
|
|
4237
4322
|
Format:
|
|
4238
4323
|
|
|
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
4324
|
## \u{1F500} \u5C42\u804C\u8D23\u5206\u79BB (Layer Separation)
|
|
4243
4325
|
Any layer boundary violations?
|
|
4244
4326
|
|
|
4245
4327
|
## \u{1F512} \u5B89\u5168\u4E0E\u6743\u9650 (Security & Auth)
|
|
4246
4328
|
Any missing auth checks, exposed data, or privilege issues?
|
|
4247
4329
|
|
|
4330
|
+
## \u{1F4D0} \u5951\u7EA6\u4E0E\u6A21\u578B\u8BBE\u8BA1 (Contract & Model Design)
|
|
4331
|
+
Response shape issues, missing constraints, relationship problems.
|
|
4332
|
+
|
|
4248
4333
|
## \u{1F4CB} \u67B6\u6784\u8BC4\u5206 (Architecture Score)
|
|
4249
4334
|
Score: X/10 \u2014 One short paragraph.
|
|
4250
4335
|
|
|
4336
|
+
## \u{1F50D} \u7ED3\u6784\u6027\u53D1\u73B0 JSON (Structural Findings \u2014 for pipeline processing)
|
|
4337
|
+
Output a JSON block with any design-level issues found above.
|
|
4338
|
+
Categories: "auth_design" | "api_contract" | "model_design" | "layer_violation" | "other_design"
|
|
4339
|
+
If no findings, output an empty array.
|
|
4340
|
+
|
|
4341
|
+
\`\`\`json
|
|
4342
|
+
{"structuralFindings": [{"category": "...", "description": "one sentence referencing the specific endpoint/model/file"}]}
|
|
4343
|
+
\`\`\`
|
|
4344
|
+
|
|
4345
|
+
IMPORTANT: Always include this JSON block, even when structuralFindings is []. This block is parsed by the pipeline.
|
|
4346
|
+
|
|
4251
4347
|
Be specific. Reference file names or endpoint paths.`;
|
|
4252
4348
|
var reviewImplementationSystemPrompt = `You are a Senior Engineer reviewing the IMPLEMENTATION DETAILS of a code change.
|
|
4253
4349
|
|
|
@@ -4340,7 +4436,8 @@ Each task object must have these exact fields:
|
|
|
4340
4436
|
"description": "...", // 1-2 sentences, specific and actionable
|
|
4341
4437
|
"layer": "data|service|api|view|route|test|infra", // implementation layer
|
|
4342
4438
|
"filesToTouch": ["..."], // VERIFIED paths only \u2014 see rules below
|
|
4343
|
-
"acceptanceCriteria": ["..."], //
|
|
4439
|
+
"acceptanceCriteria": ["..."], // behavioral completion conditions (the "what")
|
|
4440
|
+
"verificationSteps": ["..."], // concrete runnable checks with expected output (the "how to verify") \u2014 see rules below
|
|
4344
4441
|
"dependencies": ["TASK-001"], // task ids that must complete first (empty array if none)
|
|
4345
4442
|
"priority": "high|medium|low"
|
|
4346
4443
|
}
|
|
@@ -4382,8 +4479,32 @@ CRITICAL \u2014 filesToTouch Rules (hallucination prevention):
|
|
|
4382
4479
|
- If you are unsure of the exact path for a new file, leave it as "TBD:<description>" rather than guessing.
|
|
4383
4480
|
- 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
4481
|
|
|
4482
|
+
CRITICAL \u2014 verificationSteps Rules:
|
|
4483
|
+
Each step must be a concrete, self-contained check with an observable expected outcome.
|
|
4484
|
+
|
|
4485
|
+
Good examples (specific command + expected result):
|
|
4486
|
+
"POST /api/tasks with body {"title":"test"} \u2192 HTTP 201, response body contains {id, status:"pending"}"
|
|
4487
|
+
"GET /api/tasks/:id with unknown id \u2192 HTTP 404 with {code: 4040X, message: "..."}"
|
|
4488
|
+
"npm run build exits 0 with no TypeScript errors"
|
|
4489
|
+
"Prisma schema has Task model with fields: id, title, status, createdAt"
|
|
4490
|
+
"Store action createTask sets loading:true during request, loading:false on completion"
|
|
4491
|
+
"Route /tasks renders TaskList component, visible in router DevTools"
|
|
4492
|
+
|
|
4493
|
+
Bad examples (too vague \u2014 do NOT use these):
|
|
4494
|
+
"The endpoint works correctly" \u2717
|
|
4495
|
+
"Data is saved to the database" \u2717
|
|
4496
|
+
"UI displays the correct data" \u2717
|
|
4497
|
+
"Error handling works" \u2717
|
|
4498
|
+
|
|
4499
|
+
Rules:
|
|
4500
|
+
- At least 2 verification steps per task, max 5
|
|
4501
|
+
- Each step must be independently runnable/checkable
|
|
4502
|
+
- Backend tasks: include at least one HTTP request/response check and one data-layer check
|
|
4503
|
+
- Frontend tasks: include at least one UI render check and one state check
|
|
4504
|
+
- Build/compile tasks: always include "npm run build exits 0" or equivalent
|
|
4505
|
+
|
|
4385
4506
|
Other rules:
|
|
4386
|
-
- acceptanceCriteria
|
|
4507
|
+
- acceptanceCriteria: behavioral statements ("order is created with pending status") \u2014 complementary to verificationSteps, not duplicates
|
|
4387
4508
|
- dependencies must reflect real implementation order
|
|
4388
4509
|
- Aim for 4-10 tasks total \u2014 not too granular, not too coarse
|
|
4389
4510
|
- Each task should be completable in one focused coding session`;
|
|
@@ -4493,6 +4614,14 @@ function printTasks(tasks) {
|
|
|
4493
4614
|
const badge = color(`[${task.layer}]`);
|
|
4494
4615
|
const prio = task.priority === "high" ? chalk4.red("\u25CF") : task.priority === "medium" ? chalk4.yellow("\u25CF") : chalk4.gray("\u25CF");
|
|
4495
4616
|
console.log(` ${prio} ${chalk4.bold(task.id)} ${badge} ${task.title}`);
|
|
4617
|
+
if (task.verificationSteps?.length) {
|
|
4618
|
+
for (const step of task.verificationSteps.slice(0, 2)) {
|
|
4619
|
+
console.log(chalk4.gray(` \u2713 ${step}`));
|
|
4620
|
+
}
|
|
4621
|
+
if (task.verificationSteps.length > 2) {
|
|
4622
|
+
console.log(chalk4.gray(` + ${task.verificationSteps.length - 2} more verification step(s)`));
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4496
4625
|
}
|
|
4497
4626
|
}
|
|
4498
4627
|
async function loadTasksForSpec(specFilePath) {
|
|
@@ -6116,27 +6245,202 @@ function printTaskProgress(completed, total, task, mode) {
|
|
|
6116
6245
|
}
|
|
6117
6246
|
|
|
6118
6247
|
// core/reviewer.ts
|
|
6119
|
-
import
|
|
6248
|
+
import chalk10 from "chalk";
|
|
6120
6249
|
import { execSync as execSync2 } from "child_process";
|
|
6121
|
-
import * as
|
|
6250
|
+
import * as path8 from "path";
|
|
6251
|
+
import * as fs12 from "fs-extra";
|
|
6252
|
+
|
|
6253
|
+
// core/constitution-generator.ts
|
|
6254
|
+
import chalk9 from "chalk";
|
|
6122
6255
|
import * as fs11 from "fs-extra";
|
|
6256
|
+
import * as path7 from "path";
|
|
6257
|
+
|
|
6258
|
+
// prompts/constitution.prompt.ts
|
|
6259
|
+
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.
|
|
6260
|
+
|
|
6261
|
+
Output a Markdown document with EXACTLY these sections. Be specific and derive rules directly from the observed codebase \u2014 no generic advice.
|
|
6262
|
+
|
|
6263
|
+
---
|
|
6264
|
+
|
|
6265
|
+
# Project Constitution
|
|
6266
|
+
|
|
6267
|
+
## 1. \u67B6\u6784\u89C4\u5219 (Architecture Rules)
|
|
6268
|
+
\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
|
|
6269
|
+
- \u5206\u5C42\u67B6\u6784\u89C4\u5219\uFF08\u5982\uFF1Aroutes \u2192 controllers \u2192 services \u2192 DB\uFF09
|
|
6270
|
+
- \u7981\u6B62\u8DE8\u5C42\u76F4\u63A5\u8C03\u7528\u7684\u89C4\u5219
|
|
6271
|
+
- \u6A21\u5757\u7EC4\u7EC7\u89C4\u8303
|
|
6272
|
+
|
|
6273
|
+
## 2. \u547D\u540D\u89C4\u8303 (Naming Conventions)
|
|
6274
|
+
- \u6587\u4EF6\u547D\u540D\u89C4\u5219\uFF08\u9A7C\u5CF0/\u4E0B\u5212\u7EBF/kebab\uFF09
|
|
6275
|
+
- \u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u7684\u547D\u540D\u6A21\u5F0F
|
|
6276
|
+
- \u8DEF\u7531\u8DEF\u5F84\u7684\u547D\u540D\u89C4\u8303
|
|
6277
|
+
|
|
6278
|
+
## 3. API \u89C4\u8303 (API Patterns)
|
|
6279
|
+
- \u8DEF\u7531\u524D\u7F00\u89C4\u5219\uFF08\u5982 /api/v1/client/... vs /api/v1/admin/...\uFF09
|
|
6280
|
+
- \u7EDF\u4E00\u54CD\u5E94\u7ED3\u6784\u6A21\u677F\uFF08code/message/data \u683C\u5F0F\uFF09
|
|
6281
|
+
- \u9519\u8BEF\u7801\u89C4\u8303\uFF08\u5DF2\u6709\u7684\u9519\u8BEF\u7801\u8303\u56F4\u548C\u542B\u4E49\uFF09
|
|
6282
|
+
- \u8BA4\u8BC1/\u9274\u6743\u6A21\u5F0F\uFF08middleware \u540D\u79F0\u548C\u4F7F\u7528\u4F4D\u7F6E\uFF09
|
|
6283
|
+
|
|
6284
|
+
## 4. \u6570\u636E\u5C42\u89C4\u8303 (Data Layer Rules)
|
|
6285
|
+
- ORM/\u6570\u636E\u5E93\u8BBF\u95EE\u89C4\u5219\uFF08\u4EC5\u901A\u8FC7 service \u5C42\u8BBF\u95EE\uFF1F\u76F4\u63A5\u7528 Prisma/Mongoose\uFF1F\uFF09
|
|
6286
|
+
- \u5DF2\u6709\u7684\u6570\u636E\u6A21\u578B\u547D\u540D\u89C4\u8303
|
|
6287
|
+
- \u4E8B\u52A1\u5904\u7406\u6A21\u5F0F
|
|
6288
|
+
|
|
6289
|
+
## 5. \u9519\u8BEF\u5904\u7406\u89C4\u8303 (Error Handling Patterns)
|
|
6290
|
+
- \u7EDF\u4E00\u9519\u8BEF\u5904\u7406 middleware \u7684\u4F7F\u7528\u89C4\u5219
|
|
6291
|
+
- \u9519\u8BEF\u629B\u51FA\u548C\u6355\u83B7\u7684\u6A21\u5F0F
|
|
6292
|
+
- \u5DF2\u77E5\u9519\u8BEF\u7801\u5217\u8868\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF09
|
|
6293
|
+
|
|
6294
|
+
## 6. \u7981\u533A (Red Lines \u2014 Never Violate)
|
|
6295
|
+
\u660E\u786E\u5217\u51FA\u7EDD\u5BF9\u4E0D\u80FD\u505A\u7684\u4E8B\u60C5\uFF08\u4ECE\u73B0\u6709\u4EE3\u7801/\u67B6\u6784\u63A8\u65AD\uFF09\uFF1A
|
|
6296
|
+
- [ ] \u7981\u6B62 ...
|
|
6297
|
+
- [ ] \u7981\u6B62 ...
|
|
6298
|
+
|
|
6299
|
+
## 7. \u6D4B\u8BD5\u89C4\u8303 (Testing Rules)
|
|
6300
|
+
- \u6D4B\u8BD5\u6587\u4EF6\u5B58\u653E\u4F4D\u7F6E
|
|
6301
|
+
- \u5FC5\u987B\u8986\u76D6\u7684\u6D4B\u8BD5\u573A\u666F\u7C7B\u578B
|
|
6302
|
+
- \u6D4B\u8BD5\u6846\u67B6\u548C\u5DE5\u5177
|
|
6303
|
+
|
|
6304
|
+
## 8. \u5171\u4EAB\u914D\u7F6E\u6587\u4EF6\u6E05\u5355 (Shared Config Files \u2014 Append-Only)
|
|
6305
|
+
|
|
6306
|
+
CRITICAL: The following files are **singleton config files** that already exist in the project.
|
|
6307
|
+
When any feature needs to add entries (translations, constants, routes, enums, etc.), they MUST be
|
|
6308
|
+
appended/merged into these existing files. **NEVER create a new parallel file.**
|
|
6309
|
+
|
|
6310
|
+
For each discovered file, list it as:
|
|
6311
|
+
- \`<relative-path>\` \u2014 <category> \u2014 **MODIFY ONLY, never recreate**
|
|
6312
|
+
|
|
6313
|
+
If the project context includes i18n/locale files: list ALL of them with their paths.
|
|
6314
|
+
If the project context includes constants/enums files: list ALL of them.
|
|
6315
|
+
If the project context includes route index files: list ALL of them.
|
|
6316
|
+
If none are provided in the context, write: "(No shared config files detected \u2014 will be populated on first run)"
|
|
6317
|
+
|
|
6318
|
+
---
|
|
6319
|
+
|
|
6320
|
+
Be concise. Each rule must be specific enough to enforce, not a vague principle.
|
|
6321
|
+
**Section 8 is the most important section for preventing file duplication bugs.**`;
|
|
6322
|
+
|
|
6323
|
+
// core/constitution-generator.ts
|
|
6324
|
+
var CONSTITUTION_FILE = ".ai-spec-constitution.md";
|
|
6325
|
+
var ConstitutionGenerator = class {
|
|
6326
|
+
constructor(provider) {
|
|
6327
|
+
this.provider = provider;
|
|
6328
|
+
}
|
|
6329
|
+
async generate(projectRoot) {
|
|
6330
|
+
const loader = new ContextLoader(projectRoot);
|
|
6331
|
+
const context = await loader.loadProjectContext();
|
|
6332
|
+
const prompt = buildConstitutionPrompt(context, projectRoot);
|
|
6333
|
+
return this.provider.generate(prompt, constitutionSystemPrompt);
|
|
6334
|
+
}
|
|
6335
|
+
async saveConstitution(projectRoot, content) {
|
|
6336
|
+
const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
|
|
6337
|
+
await fs11.writeFile(filePath, content, "utf-8");
|
|
6338
|
+
return filePath;
|
|
6339
|
+
}
|
|
6340
|
+
};
|
|
6341
|
+
function buildConstitutionPrompt(context, projectRoot) {
|
|
6342
|
+
const parts = [
|
|
6343
|
+
"Analyze this project and generate its Project Constitution.\n",
|
|
6344
|
+
`=== Tech Stack ===
|
|
6345
|
+
${context.techStack.join(", ") || "unknown"}
|
|
6346
|
+
`,
|
|
6347
|
+
`=== Dependencies (top 30) ===
|
|
6348
|
+
${context.dependencies.slice(0, 30).join(", ")}
|
|
6349
|
+
`
|
|
6350
|
+
];
|
|
6351
|
+
if (context.apiStructure.length > 0) {
|
|
6352
|
+
parts.push(`=== API/Route Files ===
|
|
6353
|
+
${context.apiStructure.join("\n")}
|
|
6354
|
+
`);
|
|
6355
|
+
}
|
|
6356
|
+
if (context.routeSummary) {
|
|
6357
|
+
parts.push(`=== Route Code Samples ===
|
|
6358
|
+
${context.routeSummary}
|
|
6359
|
+
`);
|
|
6360
|
+
}
|
|
6361
|
+
if (context.schema) {
|
|
6362
|
+
parts.push(`=== Prisma Schema ===
|
|
6363
|
+
${context.schema.slice(0, 4e3)}
|
|
6364
|
+
`);
|
|
6365
|
+
}
|
|
6366
|
+
if (context.errorPatterns) {
|
|
6367
|
+
parts.push(`=== Error Handling Patterns ===
|
|
6368
|
+
${context.errorPatterns}
|
|
6369
|
+
`);
|
|
6370
|
+
}
|
|
6371
|
+
if (context.sharedConfigFiles && context.sharedConfigFiles.length > 0) {
|
|
6372
|
+
const grouped = context.sharedConfigFiles.reduce(
|
|
6373
|
+
(acc, f) => {
|
|
6374
|
+
(acc[f.category] ??= []).push(f);
|
|
6375
|
+
return acc;
|
|
6376
|
+
},
|
|
6377
|
+
{}
|
|
6378
|
+
);
|
|
6379
|
+
const sections = [];
|
|
6380
|
+
for (const [category, files] of Object.entries(grouped)) {
|
|
6381
|
+
sections.push(`--- ${category} ---`);
|
|
6382
|
+
for (const f of files) {
|
|
6383
|
+
sections.push(`File: ${f.path}
|
|
6384
|
+
${f.preview.slice(0, 600)}
|
|
6385
|
+
`);
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
parts.push(`=== Existing Shared Config Files (Append-Only \u2014 NEVER Recreate) ===
|
|
6389
|
+
${sections.join("\n")}
|
|
6390
|
+
`);
|
|
6391
|
+
}
|
|
6392
|
+
return parts.join("\n");
|
|
6393
|
+
}
|
|
6394
|
+
async function loadConstitution(projectRoot) {
|
|
6395
|
+
const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
|
|
6396
|
+
if (await fs11.pathExists(filePath)) {
|
|
6397
|
+
return fs11.readFile(filePath, "utf-8");
|
|
6398
|
+
}
|
|
6399
|
+
return void 0;
|
|
6400
|
+
}
|
|
6401
|
+
function printConstitutionHint(exists) {
|
|
6402
|
+
if (!exists) {
|
|
6403
|
+
console.log(
|
|
6404
|
+
chalk9.yellow(
|
|
6405
|
+
" \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
|
|
6406
|
+
)
|
|
6407
|
+
);
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
|
|
6411
|
+
// core/reviewer.ts
|
|
6412
|
+
async function loadAccumulatedLessons(projectRoot) {
|
|
6413
|
+
const constitutionPath = path8.join(projectRoot, CONSTITUTION_FILE);
|
|
6414
|
+
let content;
|
|
6415
|
+
try {
|
|
6416
|
+
content = await fs12.readFile(constitutionPath, "utf-8");
|
|
6417
|
+
} catch {
|
|
6418
|
+
return null;
|
|
6419
|
+
}
|
|
6420
|
+
const marker = "## 9. \u79EF\u7D2F\u6559\u8BAD";
|
|
6421
|
+
const idx = content.indexOf(marker);
|
|
6422
|
+
if (idx === -1) return null;
|
|
6423
|
+
const section = content.slice(idx);
|
|
6424
|
+
const nextSection = section.slice(marker.length).match(/\n## \d/);
|
|
6425
|
+
return nextSection ? section.slice(0, marker.length + nextSection.index) : section;
|
|
6426
|
+
}
|
|
6123
6427
|
var REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
|
|
6124
6428
|
async function loadReviewHistory(projectRoot) {
|
|
6125
|
-
const historyPath =
|
|
6429
|
+
const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6126
6430
|
try {
|
|
6127
|
-
if (await
|
|
6128
|
-
return await
|
|
6431
|
+
if (await fs12.pathExists(historyPath)) {
|
|
6432
|
+
return await fs12.readJson(historyPath);
|
|
6129
6433
|
}
|
|
6130
6434
|
} catch {
|
|
6131
6435
|
}
|
|
6132
6436
|
return [];
|
|
6133
6437
|
}
|
|
6134
6438
|
async function appendReviewHistory(projectRoot, entry) {
|
|
6135
|
-
const historyPath =
|
|
6439
|
+
const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6136
6440
|
const existing = await loadReviewHistory(projectRoot);
|
|
6137
6441
|
const updated = [...existing, entry].slice(-20);
|
|
6138
6442
|
try {
|
|
6139
|
-
await
|
|
6443
|
+
await fs12.writeJson(historyPath, updated, { spaces: 2 });
|
|
6140
6444
|
} catch {
|
|
6141
6445
|
}
|
|
6142
6446
|
}
|
|
@@ -6144,6 +6448,14 @@ function extractScore(reviewText) {
|
|
|
6144
6448
|
const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
|
|
6145
6449
|
return match ? parseFloat(match[1]) : 0;
|
|
6146
6450
|
}
|
|
6451
|
+
function extractComplianceScore(complianceText) {
|
|
6452
|
+
const match = complianceText.match(/ComplianceScore:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
|
|
6453
|
+
return match ? parseFloat(match[1]) : 0;
|
|
6454
|
+
}
|
|
6455
|
+
function extractMissingCount(complianceText) {
|
|
6456
|
+
const summaryMatch = complianceText.match(/Missing:\s*(\d+)/i);
|
|
6457
|
+
return summaryMatch ? parseInt(summaryMatch[1], 10) : 0;
|
|
6458
|
+
}
|
|
6147
6459
|
function extractImpactLevel(reviewText) {
|
|
6148
6460
|
const match = reviewText.match(/影响等级[::]\s*(低|中|高)/);
|
|
6149
6461
|
return match ? match[1] : void 0;
|
|
@@ -6162,7 +6474,7 @@ function buildHistoryContext(history) {
|
|
|
6162
6474
|
const lines = ["\n=== \u5386\u53F2\u5BA1\u67E5\u95EE\u9898 (Past Review Issues \u2014 check if any recur) ==="];
|
|
6163
6475
|
for (const entry of recent) {
|
|
6164
6476
|
lines.push(`
|
|
6165
|
-
[${entry.date}] ${
|
|
6477
|
+
[${entry.date}] ${path8.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
|
|
6166
6478
|
entry.topIssues.forEach((issue) => lines.push(` \xB7 ${issue}`));
|
|
6167
6479
|
}
|
|
6168
6480
|
return lines.join("\n") + "\n";
|
|
@@ -6197,23 +6509,52 @@ var CodeReviewer = class {
|
|
|
6197
6509
|
};
|
|
6198
6510
|
}
|
|
6199
6511
|
/**
|
|
6200
|
-
*
|
|
6201
|
-
* Pass
|
|
6512
|
+
* Four-pass review:
|
|
6513
|
+
* Pass 0 — spec compliance (exhaustive requirement coverage audit)
|
|
6514
|
+
* Pass 1 — architecture (layer separation, contract design, auth posture)
|
|
6202
6515
|
* Pass 2 — implementation details (validation, error handling, edge cases)
|
|
6203
6516
|
* + historical issue recurrence check
|
|
6204
6517
|
* Pass 3 — impact assessment + code complexity
|
|
6205
6518
|
*/
|
|
6206
6519
|
async runThreePassReview(specContent, codeContext, specFile) {
|
|
6207
|
-
|
|
6208
|
-
|
|
6520
|
+
let complianceReview = "";
|
|
6521
|
+
if (specContent && specContent.trim() && specContent !== "(No spec \u2014 review for general code quality)") {
|
|
6522
|
+
console.log(chalk10.gray(" Pass 0/3: Spec compliance check..."));
|
|
6523
|
+
const compliancePrompt = `Check whether the implementation covers every requirement in the spec.
|
|
6524
|
+
|
|
6525
|
+
=== Feature Spec ===
|
|
6526
|
+
${specContent}
|
|
6209
6527
|
|
|
6528
|
+
=== Code ===
|
|
6529
|
+
${codeContext}`;
|
|
6530
|
+
complianceReview = await this.provider.generate(compliancePrompt, specComplianceSystemPrompt);
|
|
6531
|
+
const complianceScore2 = extractComplianceScore(complianceReview);
|
|
6532
|
+
const missingCount = extractMissingCount(complianceReview);
|
|
6533
|
+
if (complianceScore2 > 0) {
|
|
6534
|
+
const scoreColor = complianceScore2 >= 8 ? chalk10.green : complianceScore2 >= 6 ? chalk10.yellow : chalk10.red;
|
|
6535
|
+
console.log(
|
|
6536
|
+
chalk10.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? chalk10.red(` \xB7 ${missingCount} missing requirement(s)`) : chalk10.green(" \xB7 all requirements covered"))
|
|
6537
|
+
);
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
console.log(chalk10.gray(` Pass 1/3: Architecture review...`));
|
|
6541
|
+
const accumulatedLessons = await loadAccumulatedLessons(this.projectRoot);
|
|
6542
|
+
const archPrompt = `Review the architecture of this change.
|
|
6543
|
+
${complianceReview ? `
|
|
6544
|
+
=== Spec Compliance Report (Pass 0 \u2014 already audited, do NOT re-audit missing requirements) ===
|
|
6545
|
+
${complianceReview}
|
|
6546
|
+
` : ""}
|
|
6547
|
+
${accumulatedLessons ? `
|
|
6548
|
+
=== \xA79 \u5386\u53F2\u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons \u2014 check if any are repeated in this code) ===
|
|
6549
|
+
${accumulatedLessons}
|
|
6550
|
+
` : ""}
|
|
6210
6551
|
=== Feature Spec ===
|
|
6211
6552
|
${specContent || "(No spec \u2014 review for general code quality)"}
|
|
6212
6553
|
|
|
6213
6554
|
=== Code ===
|
|
6214
6555
|
${codeContext}`;
|
|
6215
6556
|
const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
|
|
6216
|
-
console.log(
|
|
6557
|
+
console.log(chalk10.gray(" Pass 2/3: Implementation review..."));
|
|
6217
6558
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6218
6559
|
const historyContext = buildHistoryContext(history);
|
|
6219
6560
|
const implPrompt = `Review the implementation details of this change.
|
|
@@ -6228,7 +6569,7 @@ ${codeContext}
|
|
|
6228
6569
|
${archReview}
|
|
6229
6570
|
${historyContext}`;
|
|
6230
6571
|
const implReview = await this.provider.generate(implPrompt, reviewImplementationSystemPrompt);
|
|
6231
|
-
console.log(
|
|
6572
|
+
console.log(chalk10.gray(" Pass 3/3: Impact & complexity assessment..."));
|
|
6232
6573
|
const impactPrompt = `Assess the impact and complexity of this change.
|
|
6233
6574
|
|
|
6234
6575
|
=== Feature Spec ===
|
|
@@ -6244,24 +6585,23 @@ ${archReview}
|
|
|
6244
6585
|
${implReview}`;
|
|
6245
6586
|
const impactReview = await this.provider.generate(impactPrompt, reviewImpactComplexitySystemPrompt);
|
|
6246
6587
|
const sep = "\u2500".repeat(52);
|
|
6247
|
-
const
|
|
6248
|
-
|
|
6249
|
-
${sep}
|
|
6250
|
-
|
|
6251
|
-
${implReview}
|
|
6588
|
+
const parts = complianceReview ? [complianceReview, archReview, implReview, impactReview] : [archReview, implReview, impactReview];
|
|
6589
|
+
const combined = parts.join(`
|
|
6252
6590
|
|
|
6253
6591
|
${sep}
|
|
6254
6592
|
|
|
6255
|
-
|
|
6593
|
+
`);
|
|
6256
6594
|
const score = extractScore(implReview) || extractScore(archReview);
|
|
6595
|
+
const complianceScore = extractComplianceScore(complianceReview);
|
|
6257
6596
|
const topIssues = extractTopIssues(implReview);
|
|
6258
6597
|
const impactLevel = extractImpactLevel(impactReview);
|
|
6259
6598
|
const complexityLevel = extractComplexityLevel(impactReview);
|
|
6260
6599
|
if (score > 0 && specFile) {
|
|
6261
6600
|
await appendReviewHistory(this.projectRoot, {
|
|
6262
6601
|
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
6263
|
-
specFile:
|
|
6602
|
+
specFile: path8.relative(this.projectRoot, specFile),
|
|
6264
6603
|
score,
|
|
6604
|
+
...complianceScore > 0 ? { complianceScore } : {},
|
|
6265
6605
|
topIssues,
|
|
6266
6606
|
...impactLevel ? { impactLevel } : {},
|
|
6267
6607
|
...complexityLevel ? { complexityLevel } : {}
|
|
@@ -6270,43 +6610,43 @@ ${impactReview}`;
|
|
|
6270
6610
|
return combined;
|
|
6271
6611
|
}
|
|
6272
6612
|
async reviewCode(specContent, specFile) {
|
|
6273
|
-
console.log(
|
|
6613
|
+
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
6614
|
const diff = this.getGitDiff();
|
|
6275
6615
|
if (!diff.trim()) {
|
|
6276
6616
|
console.log(
|
|
6277
|
-
|
|
6617
|
+
chalk10.yellow(" No git diff found. Stage or commit changes first, then run review.")
|
|
6278
6618
|
);
|
|
6279
|
-
console.log(
|
|
6619
|
+
console.log(chalk10.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
|
|
6280
6620
|
return "No changes";
|
|
6281
6621
|
}
|
|
6282
6622
|
const { files, added, removed } = this.getDiffStats(diff);
|
|
6283
6623
|
console.log(
|
|
6284
|
-
|
|
6624
|
+
chalk10.gray(` Diff: ${files} file(s), ${chalk10.green("+" + added)} ${chalk10.red("-" + removed)}`)
|
|
6285
6625
|
);
|
|
6286
6626
|
console.log(
|
|
6287
|
-
|
|
6627
|
+
chalk10.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6288
6628
|
);
|
|
6289
6629
|
const codeContext = diff.slice(0, 1e4);
|
|
6290
6630
|
const reviewResult = await this.runThreePassReview(specContent, codeContext, specFile);
|
|
6291
|
-
console.log(
|
|
6631
|
+
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
6632
|
console.log(reviewResult);
|
|
6293
|
-
console.log(
|
|
6633
|
+
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
6634
|
return reviewResult;
|
|
6295
6635
|
}
|
|
6296
6636
|
/**
|
|
6297
6637
|
* Review directly from generated file contents (for api mode where git diff is empty).
|
|
6298
6638
|
*/
|
|
6299
6639
|
async reviewFiles(specContent, filePaths, workingDir, specFile) {
|
|
6300
|
-
console.log(
|
|
6301
|
-
console.log(
|
|
6640
|
+
console.log(chalk10.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6641
|
+
console.log(chalk10.gray(` Reviewing ${filePaths.length} generated file(s)...`));
|
|
6302
6642
|
console.log(
|
|
6303
|
-
|
|
6643
|
+
chalk10.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6304
6644
|
);
|
|
6305
6645
|
let filesSection = "";
|
|
6306
6646
|
for (const filePath of filePaths) {
|
|
6307
|
-
const fullPath =
|
|
6647
|
+
const fullPath = path8.join(workingDir, filePath);
|
|
6308
6648
|
try {
|
|
6309
|
-
const content = await
|
|
6649
|
+
const content = await fs12.readFile(fullPath, "utf-8");
|
|
6310
6650
|
filesSection += `
|
|
6311
6651
|
|
|
6312
6652
|
=== ${filePath} ===
|
|
@@ -6321,189 +6661,31 @@ ${content.slice(0, 3e3)}`;
|
|
|
6321
6661
|
}
|
|
6322
6662
|
}
|
|
6323
6663
|
const reviewResult = await this.runThreePassReview(specContent, filesSection, specFile);
|
|
6324
|
-
console.log(
|
|
6664
|
+
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
6665
|
console.log(reviewResult);
|
|
6326
|
-
console.log(
|
|
6666
|
+
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
6667
|
return reviewResult;
|
|
6328
6668
|
}
|
|
6329
6669
|
/** Print score trend from history (last N reviews) */
|
|
6330
6670
|
async printScoreTrend(limit = 5) {
|
|
6331
6671
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6332
6672
|
if (history.length === 0) {
|
|
6333
|
-
console.log(
|
|
6673
|
+
console.log(chalk10.gray(" No review history yet."));
|
|
6334
6674
|
return;
|
|
6335
6675
|
}
|
|
6336
6676
|
const recent = history.slice(-limit);
|
|
6337
|
-
console.log(
|
|
6677
|
+
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
6678
|
for (const entry of recent) {
|
|
6339
6679
|
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} ${
|
|
6680
|
+
const color = entry.score >= 8 ? chalk10.green : entry.score >= 6 ? chalk10.yellow : chalk10.red;
|
|
6681
|
+
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)}`) : "";
|
|
6682
|
+
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)}`) : "";
|
|
6683
|
+
console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
|
|
6344
6684
|
}
|
|
6345
|
-
console.log(
|
|
6685
|
+
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
6686
|
}
|
|
6347
6687
|
};
|
|
6348
6688
|
|
|
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
6689
|
// core/combined-generator.ts
|
|
6508
6690
|
var TASKS_SEPARATOR = "---TASKS_JSON---";
|
|
6509
6691
|
var tasksInstruction = `
|
|
@@ -6512,11 +6694,21 @@ var tasksInstruction = `
|
|
|
6512
6694
|
After outputting the complete spec above, append EXACTLY this line on its own (no extra text before or after it):
|
|
6513
6695
|
${TASKS_SEPARATOR}
|
|
6514
6696
|
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":["
|
|
6697
|
+
{"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"}
|
|
6698
|
+
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
6699
|
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) {
|
|
6700
|
+
async function generateSpecWithTasks(provider, idea, context, architectureDecision) {
|
|
6518
6701
|
const contextBlock = buildTaskPrompt("", context).trim();
|
|
6519
|
-
const
|
|
6702
|
+
const parts = [idea];
|
|
6703
|
+
if (architectureDecision) {
|
|
6704
|
+
parts.push(
|
|
6705
|
+
`
|
|
6706
|
+
=== Architecture Decision (MUST follow this approach in the spec) ===
|
|
6707
|
+
${architectureDecision}`
|
|
6708
|
+
);
|
|
6709
|
+
}
|
|
6710
|
+
if (contextBlock) parts.push(contextBlock);
|
|
6711
|
+
const fullPrompt = parts.join("\n\n");
|
|
6520
6712
|
const combinedSystemPrompt = specPrompt + tasksInstruction;
|
|
6521
6713
|
const raw = await provider.generate(fullPrompt, combinedSystemPrompt);
|
|
6522
6714
|
return parseSpecAndTasks(raw);
|
|
@@ -6651,6 +6843,8 @@ export {
|
|
|
6651
6843
|
TaskGenerator,
|
|
6652
6844
|
buildTaskPrompt,
|
|
6653
6845
|
createProvider,
|
|
6846
|
+
extractComplianceScore,
|
|
6847
|
+
extractMissingCount,
|
|
6654
6848
|
generateSpecWithTasks,
|
|
6655
6849
|
isFrontendDeps,
|
|
6656
6850
|
loadConstitution,
|