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.
Files changed (70) hide show
  1. package/.claude/commands/add-lesson.md +34 -0
  2. package/.claude/commands/check-layers.md +65 -0
  3. package/.claude/commands/installed-deps.md +35 -0
  4. package/.claude/commands/recall-lessons.md +40 -0
  5. package/.claude/commands/scan-singletons.md +45 -0
  6. package/.claude/commands/verify-imports.md +48 -0
  7. package/.claude/settings.local.json +15 -1
  8. package/README.md +531 -213
  9. package/RELEASE_LOG.md +460 -0
  10. package/cli/commands/config.ts +93 -0
  11. package/cli/commands/create.ts +1233 -0
  12. package/cli/commands/dashboard.ts +62 -0
  13. package/cli/commands/export.ts +66 -0
  14. package/cli/commands/init.ts +190 -0
  15. package/cli/commands/learn.ts +30 -0
  16. package/cli/commands/logs.ts +106 -0
  17. package/cli/commands/mock.ts +175 -0
  18. package/cli/commands/model.ts +156 -0
  19. package/cli/commands/restore.ts +22 -0
  20. package/cli/commands/review.ts +63 -0
  21. package/cli/commands/scan.ts +99 -0
  22. package/cli/commands/trend.ts +36 -0
  23. package/cli/commands/types.ts +69 -0
  24. package/cli/commands/update.ts +178 -0
  25. package/cli/commands/vcr.ts +70 -0
  26. package/cli/commands/workspace.ts +219 -0
  27. package/cli/index.ts +34 -2240
  28. package/cli/utils.ts +83 -0
  29. package/core/combined-generator.ts +13 -3
  30. package/core/dashboard-generator.ts +340 -0
  31. package/core/design-dialogue.ts +124 -0
  32. package/core/dsl-feedback.ts +285 -0
  33. package/core/error-feedback.ts +46 -2
  34. package/core/project-index.ts +301 -0
  35. package/core/reviewer.ts +84 -6
  36. package/core/run-logger.ts +109 -3
  37. package/core/run-trend.ts +261 -0
  38. package/core/self-evaluator.ts +139 -7
  39. package/core/spec-generator.ts +14 -8
  40. package/core/task-generator.ts +17 -0
  41. package/core/types-generator.ts +219 -0
  42. package/core/vcr.ts +210 -0
  43. package/dist/cli/index.js +6692 -4512
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/cli/index.mjs +6692 -4512
  46. package/dist/cli/index.mjs.map +1 -1
  47. package/dist/index.d.mts +19 -5
  48. package/dist/index.d.ts +19 -5
  49. package/dist/index.js +420 -224
  50. package/dist/index.js.map +1 -1
  51. package/dist/index.mjs +418 -224
  52. package/dist/index.mjs.map +1 -1
  53. package/docs-assets/purpose/architecture-overview.svg +64 -0
  54. package/docs-assets/purpose/create-pipeline.svg +113 -0
  55. package/docs-assets/purpose/task-layering.svg +74 -0
  56. package/package.json +6 -3
  57. package/prompts/codegen.prompt.ts +97 -9
  58. package/prompts/design.prompt.ts +59 -0
  59. package/prompts/spec.prompt.ts +8 -1
  60. package/prompts/tasks.prompt.ts +27 -2
  61. package/purpose.md +600 -174
  62. package/tests/dsl-extractor.test.ts +264 -0
  63. package/tests/dsl-feedback.test.ts +266 -0
  64. package/tests/dsl-validator.test.ts +283 -0
  65. package/tests/error-feedback.test.ts +292 -0
  66. package/tests/provider-utils.test.ts +173 -0
  67. package/tests/run-trend.test.ts +186 -0
  68. package/tests/self-evaluator.test.ts +339 -0
  69. package/tests/spec-assessor.test.ts +142 -0
  70. package/tests/task-generator.test.ts +230 -0
package/dist/index.js CHANGED
@@ -50,6 +50,8 @@ __export(index_exports, {
50
50
  TaskGenerator: () => TaskGenerator,
51
51
  buildTaskPrompt: () => buildTaskPrompt,
52
52
  createProvider: () => createProvider,
53
+ extractComplianceScore: () => extractComplianceScore,
54
+ extractMissingCount: () => extractMissingCount,
53
55
  generateSpecWithTasks: () => generateSpecWithTasks,
54
56
  isFrontendDeps: () => isFrontendDeps,
55
57
  loadConstitution: () => loadConstitution,
@@ -170,7 +172,14 @@ model ExampleModel {
170
172
 
171
173
  ---
172
174
 
173
- \u6839\u636E\u7528\u6237\u7684\u60F3\u6CD5\u548C\u9879\u76EE\u4E0A\u4E0B\u6587\u751F\u6210\u4E0A\u8FF0\u5B8C\u6574 Spec\u3002\u786E\u4FDD API \u8BBE\u8BA1\u4E0E\u73B0\u6709\u9879\u76EE\u7684\u8DEF\u7531\u98CE\u683C\u3001\u9519\u8BEF\u7801\u89C4\u8303\u4FDD\u6301\u4E00\u81F4\uFF0C\u6570\u636E\u6A21\u578B\u4E0E\u73B0\u6709 Prisma Schema \u534F\u8C03\u3002`;
175
+ \u6839\u636E\u7528\u6237\u7684\u60F3\u6CD5\u548C\u9879\u76EE\u4E0A\u4E0B\u6587\u751F\u6210\u4E0A\u8FF0\u5B8C\u6574 Spec\u3002\u786E\u4FDD API \u8BBE\u8BA1\u4E0E\u73B0\u6709\u9879\u76EE\u7684\u8DEF\u7531\u98CE\u683C\u3001\u9519\u8BEF\u7801\u89C4\u8303\u4FDD\u6301\u4E00\u81F4\uFF0C\u6570\u636E\u6A21\u578B\u4E0E\u73B0\u6709 Prisma Schema \u534F\u8C03\u3002
176
+
177
+ CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uFF09\uFF1A
178
+ \u5982\u679C\u9879\u76EE\u5BAA\u6CD5\u4E2D\u5305\u542B"\xA79 \u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons)"\u7AE0\u8282\uFF0C\u4F60\u5FC5\u987B\uFF1A
179
+ 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
180
+ 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
181
+ 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
182
+ 4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
174
183
 
175
184
  // core/provider-utils.ts
176
185
  var import_chalk = __toESM(require("chalk"));
@@ -321,17 +330,23 @@ var PROVIDER_CATALOG = {
321
330
  },
322
331
  glm: {
323
332
  displayName: "\u667A\u8C31 GLM (Zhipu AI)",
324
- description: "\u667A\u8C31 AI \u2014 GLM-5 / GLM-4 series + Z1 reasoning",
333
+ description: "\u667A\u8C31 AI \u2014 GLM-5.1 / GLM-5 / GLM-4 series",
325
334
  models: [
335
+ "glm-5.1",
336
+ // GLM-5.1 — latest flagship (2026)
326
337
  "glm-5",
327
- // GLM-5 flagship (如不可用请确认最新 model ID)
328
- "glm-5-flash",
338
+ // GLM-5 premium (Max/Pro plans)
339
+ "glm-5-turbo",
340
+ // GLM-5-Turbo — fast & cost-efficient
341
+ "glm-4.7",
342
+ // GLM-4.7
343
+ "glm-4.6",
344
+ // GLM-4.6
345
+ "glm-4.5-air",
346
+ // GLM-4.5-Air — lightweight
329
347
  "glm-z1",
330
- // GLM-Z1 reasoning model
331
- "glm-z1-flash",
332
- "glm-4-plus",
333
- "glm-4-flash",
334
- "glm-4-long"
348
+ // GLM-Z1 reasoning model
349
+ "glm-z1-flash"
335
350
  ],
336
351
  envKey: "ZHIPU_API_KEY",
337
352
  baseURL: "https://open.bigmodel.cn/api/paas/v4/"
@@ -527,8 +542,15 @@ var SpecGenerator = class {
527
542
  constructor(provider) {
528
543
  this.provider = provider;
529
544
  }
530
- async generateSpec(idea, context) {
545
+ async generateSpec(idea, context, architectureDecision) {
531
546
  const parts = [idea];
547
+ if (architectureDecision) {
548
+ parts.push(
549
+ `
550
+ === Architecture Decision (MUST follow this approach in the spec) ===
551
+ ${architectureDecision}`
552
+ );
553
+ }
532
554
  if (context) {
533
555
  if (context.constitution) {
534
556
  parts.push(
@@ -4283,34 +4305,110 @@ function getCodeGenSystemPrompt(repoType) {
4283
4305
  return codeGenSystemPrompt;
4284
4306
  }
4285
4307
  }
4286
- var reviewArchitectureSystemPrompt = `You are a Senior Software Architect reviewing the HIGH-LEVEL design of a code change.
4308
+ var specComplianceSystemPrompt = `You are a QA Engineer performing a SPEC COMPLIANCE CHECK.
4309
+
4310
+ Your sole job is to verify that the implementation covers every requirement stated in the feature spec.
4311
+ This is a completeness audit \u2014 NOT a code quality review. Do not comment on architecture, style, or implementation details.
4312
+
4313
+ ## How to audit:
4314
+
4315
+ 1. Parse the spec and extract EVERY stated requirement into these categories:
4316
+ - **Endpoints**: each HTTP method + path listed or implied
4317
+ - **Data Models**: each entity, field, constraint mentioned
4318
+ - **Business Rules**: validations, conditions, calculations stated
4319
+ - **Auth Requirements**: which endpoints need auth, which roles are allowed
4320
+ - **Error Cases**: explicit error codes or failure scenarios mentioned
4321
+ - **Side Effects**: emails sent, events fired, caches invalidated, etc.
4322
+
4323
+ 2. For each extracted requirement, check the provided code:
4324
+ - \u2705 **Covered** \u2014 clearly implemented
4325
+ - \u26A0\uFE0F **Partial** \u2014 exists but incomplete (e.g. endpoint exists but missing a field or error case)
4326
+ - \u274C **Missing** \u2014 requirement stated in spec but not found in code
4327
+
4328
+ 3. Output a compliance checklist. Be exhaustive \u2014 list every single requirement.
4329
+
4330
+ ## Output format:
4331
+
4332
+ ## \u{1F4CB} Spec Compliance Report
4333
+
4334
+ ### Endpoints
4335
+ \u2705 / \u26A0\uFE0F / \u274C METHOD /path \u2014 one-line status
4336
+
4337
+ ### Data Models
4338
+ \u2705 / \u26A0\uFE0F / \u274C ModelName \u2014 one-line status
4339
+
4340
+ ### Business Rules
4341
+ \u2705 / \u26A0\uFE0F / \u274C Rule description \u2014 one-line status
4342
+
4343
+ ### Auth & Permissions
4344
+ \u2705 / \u26A0\uFE0F / \u274C Requirement \u2014 one-line status
4345
+
4346
+ ### Error Cases
4347
+ \u2705 / \u26A0\uFE0F / \u274C Error scenario \u2014 one-line status
4348
+
4349
+ ### Side Effects
4350
+ \u2705 / \u26A0\uFE0F / \u274C Side effect \u2014 one-line status (omit section if none in spec)
4351
+
4352
+ ---
4353
+
4354
+ ## \u{1F4CA} Compliance Summary
4355
+ Covered: N | Partial: N | Missing: N | Total: N
4356
+
4357
+ ## \u{1F522} Compliance Score
4358
+ ComplianceScore: X/10
4359
+
4360
+ (10 = all requirements implemented, 0 = nothing implemented.
4361
+ Deduct 1 point per missing requirement, 0.5 per partial.
4362
+ Round to nearest integer.)
4363
+
4364
+ ## \u{1F6A8} Blockers (Missing requirements that MUST be implemented before ship)
4365
+ List only \u274C Missing items here, ordered by severity. If none, write "None".
4366
+
4367
+ ---
4368
+
4369
+ IMPORTANT: Be exhaustive. A requirement not listed here is assumed to be covered.
4370
+ If the spec is vague, note the ambiguity as \u26A0\uFE0F Partial rather than assuming coverage.`;
4371
+ var reviewArchitectureSystemPrompt = `You are a Senior Software Architect reviewing the HIGH-LEVEL DESIGN of a code change.
4372
+
4373
+ A spec compliance check (Pass 0) has already verified feature completeness. Do NOT re-audit whether requirements are missing \u2014 focus purely on HOW the present implementation is architected.
4287
4374
 
4288
4375
  Focus ONLY on:
4289
- 1. **Spec compliance** \u2014 Does the implementation match the spec? Are there missing or extra endpoints/components?
4290
- 2. **Layer separation** \u2014 Does each layer have the right responsibilities? (e.g., no business logic in controllers, no HTTP in stores)
4291
- 3. **API contract** \u2014 Are request/response shapes correct? Are all error codes from the spec implemented?
4292
- 4. **Data model integrity** \u2014 Are constraints, unique fields, and relationships correct?
4293
- 5. **Security posture** \u2014 Are auth checks applied to the right endpoints? Any obvious missing auth?
4376
+ 1. **Layer separation** \u2014 Does each layer have the right responsibilities? (e.g., no business logic in controllers, no HTTP in stores)
4377
+ 2. **API contract quality** \u2014 Are request/response shapes well-designed? Are error codes consistent with project conventions?
4378
+ 3. **Data model integrity** \u2014 Are constraints, unique fields, and relationships modelled correctly?
4379
+ 4. **Security posture** \u2014 Are auth checks applied correctly? Any privilege escalation risks?
4294
4380
 
4295
4381
  DO NOT comment on:
4382
+ - Whether specific endpoints or features are missing (covered by Pass 0)
4296
4383
  - Code style, naming conventions, formatting
4297
4384
  - Minor implementation details (variable names, inline comments)
4298
4385
  - Performance micro-optimizations
4299
4386
 
4300
4387
  Format:
4301
4388
 
4302
- ## \u{1F3D7} \u67B6\u6784\u5408\u89C4\u6027 (Spec Compliance)
4303
- Does the implementation match the spec? List any missing or wrong endpoints/components.
4304
-
4305
4389
  ## \u{1F500} \u5C42\u804C\u8D23\u5206\u79BB (Layer Separation)
4306
4390
  Any layer boundary violations?
4307
4391
 
4308
4392
  ## \u{1F512} \u5B89\u5168\u4E0E\u6743\u9650 (Security & Auth)
4309
4393
  Any missing auth checks, exposed data, or privilege issues?
4310
4394
 
4395
+ ## \u{1F4D0} \u5951\u7EA6\u4E0E\u6A21\u578B\u8BBE\u8BA1 (Contract & Model Design)
4396
+ Response shape issues, missing constraints, relationship problems.
4397
+
4311
4398
  ## \u{1F4CB} \u67B6\u6784\u8BC4\u5206 (Architecture Score)
4312
4399
  Score: X/10 \u2014 One short paragraph.
4313
4400
 
4401
+ ## \u{1F50D} \u7ED3\u6784\u6027\u53D1\u73B0 JSON (Structural Findings \u2014 for pipeline processing)
4402
+ Output a JSON block with any design-level issues found above.
4403
+ Categories: "auth_design" | "api_contract" | "model_design" | "layer_violation" | "other_design"
4404
+ If no findings, output an empty array.
4405
+
4406
+ \`\`\`json
4407
+ {"structuralFindings": [{"category": "...", "description": "one sentence referencing the specific endpoint/model/file"}]}
4408
+ \`\`\`
4409
+
4410
+ IMPORTANT: Always include this JSON block, even when structuralFindings is []. This block is parsed by the pipeline.
4411
+
4314
4412
  Be specific. Reference file names or endpoint paths.`;
4315
4413
  var reviewImplementationSystemPrompt = `You are a Senior Engineer reviewing the IMPLEMENTATION DETAILS of a code change.
4316
4414
 
@@ -4403,7 +4501,8 @@ Each task object must have these exact fields:
4403
4501
  "description": "...", // 1-2 sentences, specific and actionable
4404
4502
  "layer": "data|service|api|view|route|test|infra", // implementation layer
4405
4503
  "filesToTouch": ["..."], // VERIFIED paths only \u2014 see rules below
4406
- "acceptanceCriteria": ["..."], // verifiable completion conditions
4504
+ "acceptanceCriteria": ["..."], // behavioral completion conditions (the "what")
4505
+ "verificationSteps": ["..."], // concrete runnable checks with expected output (the "how to verify") \u2014 see rules below
4407
4506
  "dependencies": ["TASK-001"], // task ids that must complete first (empty array if none)
4408
4507
  "priority": "high|medium|low"
4409
4508
  }
@@ -4445,8 +4544,32 @@ CRITICAL \u2014 filesToTouch Rules (hallucination prevention):
4445
4544
  - If you are unsure of the exact path for a new file, leave it as "TBD:<description>" rather than guessing.
4446
4545
  - Cross-check: after writing all tasks, verify every path in filesToTouch exists in the inventory or is a logical new sibling. If it doesn't pass this check, fix it.
4447
4546
 
4547
+ CRITICAL \u2014 verificationSteps Rules:
4548
+ Each step must be a concrete, self-contained check with an observable expected outcome.
4549
+
4550
+ Good examples (specific command + expected result):
4551
+ "POST /api/tasks with body {"title":"test"} \u2192 HTTP 201, response body contains {id, status:"pending"}"
4552
+ "GET /api/tasks/:id with unknown id \u2192 HTTP 404 with {code: 4040X, message: "..."}"
4553
+ "npm run build exits 0 with no TypeScript errors"
4554
+ "Prisma schema has Task model with fields: id, title, status, createdAt"
4555
+ "Store action createTask sets loading:true during request, loading:false on completion"
4556
+ "Route /tasks renders TaskList component, visible in router DevTools"
4557
+
4558
+ Bad examples (too vague \u2014 do NOT use these):
4559
+ "The endpoint works correctly" \u2717
4560
+ "Data is saved to the database" \u2717
4561
+ "UI displays the correct data" \u2717
4562
+ "Error handling works" \u2717
4563
+
4564
+ Rules:
4565
+ - At least 2 verification steps per task, max 5
4566
+ - Each step must be independently runnable/checkable
4567
+ - Backend tasks: include at least one HTTP request/response check and one data-layer check
4568
+ - Frontend tasks: include at least one UI render check and one state check
4569
+ - Build/compile tasks: always include "npm run build exits 0" or equivalent
4570
+
4448
4571
  Other rules:
4449
- - acceptanceCriteria must be verifiable (not vague like "works correctly")
4572
+ - acceptanceCriteria: behavioral statements ("order is created with pending status") \u2014 complementary to verificationSteps, not duplicates
4450
4573
  - dependencies must reflect real implementation order
4451
4574
  - Aim for 4-10 tasks total \u2014 not too granular, not too coarse
4452
4575
  - Each task should be completable in one focused coding session`;
@@ -4556,6 +4679,14 @@ function printTasks(tasks) {
4556
4679
  const badge = color(`[${task.layer}]`);
4557
4680
  const prio = task.priority === "high" ? import_chalk4.default.red("\u25CF") : task.priority === "medium" ? import_chalk4.default.yellow("\u25CF") : import_chalk4.default.gray("\u25CF");
4558
4681
  console.log(` ${prio} ${import_chalk4.default.bold(task.id)} ${badge} ${task.title}`);
4682
+ if (task.verificationSteps?.length) {
4683
+ for (const step of task.verificationSteps.slice(0, 2)) {
4684
+ console.log(import_chalk4.default.gray(` \u2713 ${step}`));
4685
+ }
4686
+ if (task.verificationSteps.length > 2) {
4687
+ console.log(import_chalk4.default.gray(` + ${task.verificationSteps.length - 2} more verification step(s)`));
4688
+ }
4689
+ }
4559
4690
  }
4560
4691
  }
4561
4692
  async function loadTasksForSpec(specFilePath) {
@@ -6179,27 +6310,202 @@ function printTaskProgress(completed, total, task, mode) {
6179
6310
  }
6180
6311
 
6181
6312
  // core/reviewer.ts
6182
- var import_chalk9 = __toESM(require("chalk"));
6313
+ var import_chalk10 = __toESM(require("chalk"));
6183
6314
  var import_child_process2 = require("child_process");
6184
- var path7 = __toESM(require("path"));
6315
+ var path8 = __toESM(require("path"));
6316
+ var fs12 = __toESM(require("fs-extra"));
6317
+
6318
+ // core/constitution-generator.ts
6319
+ var import_chalk9 = __toESM(require("chalk"));
6185
6320
  var fs11 = __toESM(require("fs-extra"));
6321
+ var path7 = __toESM(require("path"));
6322
+
6323
+ // prompts/constitution.prompt.ts
6324
+ 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.
6325
+
6326
+ Output a Markdown document with EXACTLY these sections. Be specific and derive rules directly from the observed codebase \u2014 no generic advice.
6327
+
6328
+ ---
6329
+
6330
+ # Project Constitution
6331
+
6332
+ ## 1. \u67B6\u6784\u89C4\u5219 (Architecture Rules)
6333
+ \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
6334
+ - \u5206\u5C42\u67B6\u6784\u89C4\u5219\uFF08\u5982\uFF1Aroutes \u2192 controllers \u2192 services \u2192 DB\uFF09
6335
+ - \u7981\u6B62\u8DE8\u5C42\u76F4\u63A5\u8C03\u7528\u7684\u89C4\u5219
6336
+ - \u6A21\u5757\u7EC4\u7EC7\u89C4\u8303
6337
+
6338
+ ## 2. \u547D\u540D\u89C4\u8303 (Naming Conventions)
6339
+ - \u6587\u4EF6\u547D\u540D\u89C4\u5219\uFF08\u9A7C\u5CF0/\u4E0B\u5212\u7EBF/kebab\uFF09
6340
+ - \u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u7684\u547D\u540D\u6A21\u5F0F
6341
+ - \u8DEF\u7531\u8DEF\u5F84\u7684\u547D\u540D\u89C4\u8303
6342
+
6343
+ ## 3. API \u89C4\u8303 (API Patterns)
6344
+ - \u8DEF\u7531\u524D\u7F00\u89C4\u5219\uFF08\u5982 /api/v1/client/... vs /api/v1/admin/...\uFF09
6345
+ - \u7EDF\u4E00\u54CD\u5E94\u7ED3\u6784\u6A21\u677F\uFF08code/message/data \u683C\u5F0F\uFF09
6346
+ - \u9519\u8BEF\u7801\u89C4\u8303\uFF08\u5DF2\u6709\u7684\u9519\u8BEF\u7801\u8303\u56F4\u548C\u542B\u4E49\uFF09
6347
+ - \u8BA4\u8BC1/\u9274\u6743\u6A21\u5F0F\uFF08middleware \u540D\u79F0\u548C\u4F7F\u7528\u4F4D\u7F6E\uFF09
6348
+
6349
+ ## 4. \u6570\u636E\u5C42\u89C4\u8303 (Data Layer Rules)
6350
+ - ORM/\u6570\u636E\u5E93\u8BBF\u95EE\u89C4\u5219\uFF08\u4EC5\u901A\u8FC7 service \u5C42\u8BBF\u95EE\uFF1F\u76F4\u63A5\u7528 Prisma/Mongoose\uFF1F\uFF09
6351
+ - \u5DF2\u6709\u7684\u6570\u636E\u6A21\u578B\u547D\u540D\u89C4\u8303
6352
+ - \u4E8B\u52A1\u5904\u7406\u6A21\u5F0F
6353
+
6354
+ ## 5. \u9519\u8BEF\u5904\u7406\u89C4\u8303 (Error Handling Patterns)
6355
+ - \u7EDF\u4E00\u9519\u8BEF\u5904\u7406 middleware \u7684\u4F7F\u7528\u89C4\u5219
6356
+ - \u9519\u8BEF\u629B\u51FA\u548C\u6355\u83B7\u7684\u6A21\u5F0F
6357
+ - \u5DF2\u77E5\u9519\u8BEF\u7801\u5217\u8868\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF09
6358
+
6359
+ ## 6. \u7981\u533A (Red Lines \u2014 Never Violate)
6360
+ \u660E\u786E\u5217\u51FA\u7EDD\u5BF9\u4E0D\u80FD\u505A\u7684\u4E8B\u60C5\uFF08\u4ECE\u73B0\u6709\u4EE3\u7801/\u67B6\u6784\u63A8\u65AD\uFF09\uFF1A
6361
+ - [ ] \u7981\u6B62 ...
6362
+ - [ ] \u7981\u6B62 ...
6363
+
6364
+ ## 7. \u6D4B\u8BD5\u89C4\u8303 (Testing Rules)
6365
+ - \u6D4B\u8BD5\u6587\u4EF6\u5B58\u653E\u4F4D\u7F6E
6366
+ - \u5FC5\u987B\u8986\u76D6\u7684\u6D4B\u8BD5\u573A\u666F\u7C7B\u578B
6367
+ - \u6D4B\u8BD5\u6846\u67B6\u548C\u5DE5\u5177
6368
+
6369
+ ## 8. \u5171\u4EAB\u914D\u7F6E\u6587\u4EF6\u6E05\u5355 (Shared Config Files \u2014 Append-Only)
6370
+
6371
+ CRITICAL: The following files are **singleton config files** that already exist in the project.
6372
+ When any feature needs to add entries (translations, constants, routes, enums, etc.), they MUST be
6373
+ appended/merged into these existing files. **NEVER create a new parallel file.**
6374
+
6375
+ For each discovered file, list it as:
6376
+ - \`<relative-path>\` \u2014 <category> \u2014 **MODIFY ONLY, never recreate**
6377
+
6378
+ If the project context includes i18n/locale files: list ALL of them with their paths.
6379
+ If the project context includes constants/enums files: list ALL of them.
6380
+ If the project context includes route index files: list ALL of them.
6381
+ If none are provided in the context, write: "(No shared config files detected \u2014 will be populated on first run)"
6382
+
6383
+ ---
6384
+
6385
+ Be concise. Each rule must be specific enough to enforce, not a vague principle.
6386
+ **Section 8 is the most important section for preventing file duplication bugs.**`;
6387
+
6388
+ // core/constitution-generator.ts
6389
+ var CONSTITUTION_FILE = ".ai-spec-constitution.md";
6390
+ var ConstitutionGenerator = class {
6391
+ constructor(provider) {
6392
+ this.provider = provider;
6393
+ }
6394
+ async generate(projectRoot) {
6395
+ const loader = new ContextLoader(projectRoot);
6396
+ const context = await loader.loadProjectContext();
6397
+ const prompt = buildConstitutionPrompt(context, projectRoot);
6398
+ return this.provider.generate(prompt, constitutionSystemPrompt);
6399
+ }
6400
+ async saveConstitution(projectRoot, content) {
6401
+ const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
6402
+ await fs11.writeFile(filePath, content, "utf-8");
6403
+ return filePath;
6404
+ }
6405
+ };
6406
+ function buildConstitutionPrompt(context, projectRoot) {
6407
+ const parts = [
6408
+ "Analyze this project and generate its Project Constitution.\n",
6409
+ `=== Tech Stack ===
6410
+ ${context.techStack.join(", ") || "unknown"}
6411
+ `,
6412
+ `=== Dependencies (top 30) ===
6413
+ ${context.dependencies.slice(0, 30).join(", ")}
6414
+ `
6415
+ ];
6416
+ if (context.apiStructure.length > 0) {
6417
+ parts.push(`=== API/Route Files ===
6418
+ ${context.apiStructure.join("\n")}
6419
+ `);
6420
+ }
6421
+ if (context.routeSummary) {
6422
+ parts.push(`=== Route Code Samples ===
6423
+ ${context.routeSummary}
6424
+ `);
6425
+ }
6426
+ if (context.schema) {
6427
+ parts.push(`=== Prisma Schema ===
6428
+ ${context.schema.slice(0, 4e3)}
6429
+ `);
6430
+ }
6431
+ if (context.errorPatterns) {
6432
+ parts.push(`=== Error Handling Patterns ===
6433
+ ${context.errorPatterns}
6434
+ `);
6435
+ }
6436
+ if (context.sharedConfigFiles && context.sharedConfigFiles.length > 0) {
6437
+ const grouped = context.sharedConfigFiles.reduce(
6438
+ (acc, f) => {
6439
+ (acc[f.category] ??= []).push(f);
6440
+ return acc;
6441
+ },
6442
+ {}
6443
+ );
6444
+ const sections = [];
6445
+ for (const [category, files] of Object.entries(grouped)) {
6446
+ sections.push(`--- ${category} ---`);
6447
+ for (const f of files) {
6448
+ sections.push(`File: ${f.path}
6449
+ ${f.preview.slice(0, 600)}
6450
+ `);
6451
+ }
6452
+ }
6453
+ parts.push(`=== Existing Shared Config Files (Append-Only \u2014 NEVER Recreate) ===
6454
+ ${sections.join("\n")}
6455
+ `);
6456
+ }
6457
+ return parts.join("\n");
6458
+ }
6459
+ async function loadConstitution(projectRoot) {
6460
+ const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
6461
+ if (await fs11.pathExists(filePath)) {
6462
+ return fs11.readFile(filePath, "utf-8");
6463
+ }
6464
+ return void 0;
6465
+ }
6466
+ function printConstitutionHint(exists) {
6467
+ if (!exists) {
6468
+ console.log(
6469
+ import_chalk9.default.yellow(
6470
+ " \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
6471
+ )
6472
+ );
6473
+ }
6474
+ }
6475
+
6476
+ // core/reviewer.ts
6477
+ async function loadAccumulatedLessons(projectRoot) {
6478
+ const constitutionPath = path8.join(projectRoot, CONSTITUTION_FILE);
6479
+ let content;
6480
+ try {
6481
+ content = await fs12.readFile(constitutionPath, "utf-8");
6482
+ } catch {
6483
+ return null;
6484
+ }
6485
+ const marker = "## 9. \u79EF\u7D2F\u6559\u8BAD";
6486
+ const idx = content.indexOf(marker);
6487
+ if (idx === -1) return null;
6488
+ const section = content.slice(idx);
6489
+ const nextSection = section.slice(marker.length).match(/\n## \d/);
6490
+ return nextSection ? section.slice(0, marker.length + nextSection.index) : section;
6491
+ }
6186
6492
  var REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
6187
6493
  async function loadReviewHistory(projectRoot) {
6188
- const historyPath = path7.join(projectRoot, REVIEW_HISTORY_FILE);
6494
+ const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6189
6495
  try {
6190
- if (await fs11.pathExists(historyPath)) {
6191
- return await fs11.readJson(historyPath);
6496
+ if (await fs12.pathExists(historyPath)) {
6497
+ return await fs12.readJson(historyPath);
6192
6498
  }
6193
6499
  } catch {
6194
6500
  }
6195
6501
  return [];
6196
6502
  }
6197
6503
  async function appendReviewHistory(projectRoot, entry) {
6198
- const historyPath = path7.join(projectRoot, REVIEW_HISTORY_FILE);
6504
+ const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6199
6505
  const existing = await loadReviewHistory(projectRoot);
6200
6506
  const updated = [...existing, entry].slice(-20);
6201
6507
  try {
6202
- await fs11.writeJson(historyPath, updated, { spaces: 2 });
6508
+ await fs12.writeJson(historyPath, updated, { spaces: 2 });
6203
6509
  } catch {
6204
6510
  }
6205
6511
  }
@@ -6207,6 +6513,14 @@ function extractScore(reviewText) {
6207
6513
  const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
6208
6514
  return match ? parseFloat(match[1]) : 0;
6209
6515
  }
6516
+ function extractComplianceScore(complianceText) {
6517
+ const match = complianceText.match(/ComplianceScore:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
6518
+ return match ? parseFloat(match[1]) : 0;
6519
+ }
6520
+ function extractMissingCount(complianceText) {
6521
+ const summaryMatch = complianceText.match(/Missing:\s*(\d+)/i);
6522
+ return summaryMatch ? parseInt(summaryMatch[1], 10) : 0;
6523
+ }
6210
6524
  function extractImpactLevel(reviewText) {
6211
6525
  const match = reviewText.match(/影响等级[::]\s*(低|中|高)/);
6212
6526
  return match ? match[1] : void 0;
@@ -6225,7 +6539,7 @@ function buildHistoryContext(history) {
6225
6539
  const lines = ["\n=== \u5386\u53F2\u5BA1\u67E5\u95EE\u9898 (Past Review Issues \u2014 check if any recur) ==="];
6226
6540
  for (const entry of recent) {
6227
6541
  lines.push(`
6228
- [${entry.date}] ${path7.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
6542
+ [${entry.date}] ${path8.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
6229
6543
  entry.topIssues.forEach((issue) => lines.push(` \xB7 ${issue}`));
6230
6544
  }
6231
6545
  return lines.join("\n") + "\n";
@@ -6260,23 +6574,52 @@ var CodeReviewer = class {
6260
6574
  };
6261
6575
  }
6262
6576
  /**
6263
- * Three-pass review:
6264
- * Pass 1architecture (spec compliance, layer separation, auth)
6577
+ * Four-pass review:
6578
+ * Pass 0 — spec compliance (exhaustive requirement coverage audit)
6579
+ * Pass 1 — architecture (layer separation, contract design, auth posture)
6265
6580
  * Pass 2 — implementation details (validation, error handling, edge cases)
6266
6581
  * + historical issue recurrence check
6267
6582
  * Pass 3 — impact assessment + code complexity
6268
6583
  */
6269
6584
  async runThreePassReview(specContent, codeContext, specFile) {
6270
- console.log(import_chalk9.default.gray(" Pass 1/3: Architecture review..."));
6271
- const archPrompt = `Review the architecture of this change.
6585
+ let complianceReview = "";
6586
+ if (specContent && specContent.trim() && specContent !== "(No spec \u2014 review for general code quality)") {
6587
+ console.log(import_chalk10.default.gray(" Pass 0/3: Spec compliance check..."));
6588
+ const compliancePrompt = `Check whether the implementation covers every requirement in the spec.
6589
+
6590
+ === Feature Spec ===
6591
+ ${specContent}
6272
6592
 
6593
+ === Code ===
6594
+ ${codeContext}`;
6595
+ complianceReview = await this.provider.generate(compliancePrompt, specComplianceSystemPrompt);
6596
+ const complianceScore2 = extractComplianceScore(complianceReview);
6597
+ const missingCount = extractMissingCount(complianceReview);
6598
+ if (complianceScore2 > 0) {
6599
+ const scoreColor = complianceScore2 >= 8 ? import_chalk10.default.green : complianceScore2 >= 6 ? import_chalk10.default.yellow : import_chalk10.default.red;
6600
+ console.log(
6601
+ import_chalk10.default.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? import_chalk10.default.red(` \xB7 ${missingCount} missing requirement(s)`) : import_chalk10.default.green(" \xB7 all requirements covered"))
6602
+ );
6603
+ }
6604
+ }
6605
+ console.log(import_chalk10.default.gray(` Pass 1/3: Architecture review...`));
6606
+ const accumulatedLessons = await loadAccumulatedLessons(this.projectRoot);
6607
+ const archPrompt = `Review the architecture of this change.
6608
+ ${complianceReview ? `
6609
+ === Spec Compliance Report (Pass 0 \u2014 already audited, do NOT re-audit missing requirements) ===
6610
+ ${complianceReview}
6611
+ ` : ""}
6612
+ ${accumulatedLessons ? `
6613
+ === \xA79 \u5386\u53F2\u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons \u2014 check if any are repeated in this code) ===
6614
+ ${accumulatedLessons}
6615
+ ` : ""}
6273
6616
  === Feature Spec ===
6274
6617
  ${specContent || "(No spec \u2014 review for general code quality)"}
6275
6618
 
6276
6619
  === Code ===
6277
6620
  ${codeContext}`;
6278
6621
  const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
6279
- console.log(import_chalk9.default.gray(" Pass 2/3: Implementation review..."));
6622
+ console.log(import_chalk10.default.gray(" Pass 2/3: Implementation review..."));
6280
6623
  const history = await loadReviewHistory(this.projectRoot);
6281
6624
  const historyContext = buildHistoryContext(history);
6282
6625
  const implPrompt = `Review the implementation details of this change.
@@ -6291,7 +6634,7 @@ ${codeContext}
6291
6634
  ${archReview}
6292
6635
  ${historyContext}`;
6293
6636
  const implReview = await this.provider.generate(implPrompt, reviewImplementationSystemPrompt);
6294
- console.log(import_chalk9.default.gray(" Pass 3/3: Impact & complexity assessment..."));
6637
+ console.log(import_chalk10.default.gray(" Pass 3/3: Impact & complexity assessment..."));
6295
6638
  const impactPrompt = `Assess the impact and complexity of this change.
6296
6639
 
6297
6640
  === Feature Spec ===
@@ -6307,24 +6650,23 @@ ${archReview}
6307
6650
  ${implReview}`;
6308
6651
  const impactReview = await this.provider.generate(impactPrompt, reviewImpactComplexitySystemPrompt);
6309
6652
  const sep = "\u2500".repeat(52);
6310
- const combined = `${archReview}
6311
-
6312
- ${sep}
6313
-
6314
- ${implReview}
6653
+ const parts = complianceReview ? [complianceReview, archReview, implReview, impactReview] : [archReview, implReview, impactReview];
6654
+ const combined = parts.join(`
6315
6655
 
6316
6656
  ${sep}
6317
6657
 
6318
- ${impactReview}`;
6658
+ `);
6319
6659
  const score = extractScore(implReview) || extractScore(archReview);
6660
+ const complianceScore = extractComplianceScore(complianceReview);
6320
6661
  const topIssues = extractTopIssues(implReview);
6321
6662
  const impactLevel = extractImpactLevel(impactReview);
6322
6663
  const complexityLevel = extractComplexityLevel(impactReview);
6323
6664
  if (score > 0 && specFile) {
6324
6665
  await appendReviewHistory(this.projectRoot, {
6325
6666
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
6326
- specFile: path7.relative(this.projectRoot, specFile),
6667
+ specFile: path8.relative(this.projectRoot, specFile),
6327
6668
  score,
6669
+ ...complianceScore > 0 ? { complianceScore } : {},
6328
6670
  topIssues,
6329
6671
  ...impactLevel ? { impactLevel } : {},
6330
6672
  ...complexityLevel ? { complexityLevel } : {}
@@ -6333,43 +6675,43 @@ ${impactReview}`;
6333
6675
  return combined;
6334
6676
  }
6335
6677
  async reviewCode(specContent, specFile) {
6336
- console.log(import_chalk9.default.cyan("\n\u2500\u2500\u2500 Automated Code Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6678
+ console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Automated Code Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6337
6679
  const diff = this.getGitDiff();
6338
6680
  if (!diff.trim()) {
6339
6681
  console.log(
6340
- import_chalk9.default.yellow(" No git diff found. Stage or commit changes first, then run review.")
6682
+ import_chalk10.default.yellow(" No git diff found. Stage or commit changes first, then run review.")
6341
6683
  );
6342
- console.log(import_chalk9.default.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
6684
+ console.log(import_chalk10.default.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
6343
6685
  return "No changes";
6344
6686
  }
6345
6687
  const { files, added, removed } = this.getDiffStats(diff);
6346
6688
  console.log(
6347
- import_chalk9.default.gray(` Diff: ${files} file(s), ${import_chalk9.default.green("+" + added)} ${import_chalk9.default.red("-" + removed)}`)
6689
+ import_chalk10.default.gray(` Diff: ${files} file(s), ${import_chalk10.default.green("+" + added)} ${import_chalk10.default.red("-" + removed)}`)
6348
6690
  );
6349
6691
  console.log(
6350
- import_chalk9.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6692
+ import_chalk10.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6351
6693
  );
6352
6694
  const codeContext = diff.slice(0, 1e4);
6353
6695
  const reviewResult = await this.runThreePassReview(specContent, codeContext, specFile);
6354
- console.log(import_chalk9.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6696
+ console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6355
6697
  console.log(reviewResult);
6356
- console.log(import_chalk9.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6698
+ console.log(import_chalk10.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6357
6699
  return reviewResult;
6358
6700
  }
6359
6701
  /**
6360
6702
  * Review directly from generated file contents (for api mode where git diff is empty).
6361
6703
  */
6362
6704
  async reviewFiles(specContent, filePaths, workingDir, specFile) {
6363
- console.log(import_chalk9.default.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6364
- console.log(import_chalk9.default.gray(` Reviewing ${filePaths.length} generated file(s)...`));
6705
+ console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6706
+ console.log(import_chalk10.default.gray(` Reviewing ${filePaths.length} generated file(s)...`));
6365
6707
  console.log(
6366
- import_chalk9.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6708
+ import_chalk10.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6367
6709
  );
6368
6710
  let filesSection = "";
6369
6711
  for (const filePath of filePaths) {
6370
- const fullPath = path7.join(workingDir, filePath);
6712
+ const fullPath = path8.join(workingDir, filePath);
6371
6713
  try {
6372
- const content = await fs11.readFile(fullPath, "utf-8");
6714
+ const content = await fs12.readFile(fullPath, "utf-8");
6373
6715
  filesSection += `
6374
6716
 
6375
6717
  === ${filePath} ===
@@ -6384,189 +6726,31 @@ ${content.slice(0, 3e3)}`;
6384
6726
  }
6385
6727
  }
6386
6728
  const reviewResult = await this.runThreePassReview(specContent, filesSection, specFile);
6387
- console.log(import_chalk9.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6729
+ console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6388
6730
  console.log(reviewResult);
6389
- console.log(import_chalk9.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6731
+ console.log(import_chalk10.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6390
6732
  return reviewResult;
6391
6733
  }
6392
6734
  /** Print score trend from history (last N reviews) */
6393
6735
  async printScoreTrend(limit = 5) {
6394
6736
  const history = await loadReviewHistory(this.projectRoot);
6395
6737
  if (history.length === 0) {
6396
- console.log(import_chalk9.default.gray(" No review history yet."));
6738
+ console.log(import_chalk10.default.gray(" No review history yet."));
6397
6739
  return;
6398
6740
  }
6399
6741
  const recent = history.slice(-limit);
6400
- console.log(import_chalk9.default.cyan("\n\u2500\u2500\u2500 Review Score Trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6742
+ console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Review Score Trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6401
6743
  for (const entry of recent) {
6402
6744
  const bar = "\u2588".repeat(entry.score) + "\u2591".repeat(10 - entry.score);
6403
- const color = entry.score >= 8 ? import_chalk9.default.green : entry.score >= 6 ? import_chalk9.default.yellow : import_chalk9.default.red;
6404
- const impactTag = entry.impactLevel ? import_chalk9.default.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? import_chalk9.default.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? import_chalk9.default.yellow(entry.impactLevel) : import_chalk9.default.green(entry.impactLevel)}`) : "";
6405
- const complexityTag = entry.complexityLevel ? import_chalk9.default.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? import_chalk9.default.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? import_chalk9.default.yellow(entry.complexityLevel) : import_chalk9.default.green(entry.complexityLevel)}`) : "";
6406
- console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path7.basename(entry.specFile)}`);
6745
+ const color = entry.score >= 8 ? import_chalk10.default.green : entry.score >= 6 ? import_chalk10.default.yellow : import_chalk10.default.red;
6746
+ const impactTag = entry.impactLevel ? import_chalk10.default.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? import_chalk10.default.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? import_chalk10.default.yellow(entry.impactLevel) : import_chalk10.default.green(entry.impactLevel)}`) : "";
6747
+ const complexityTag = entry.complexityLevel ? import_chalk10.default.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? import_chalk10.default.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? import_chalk10.default.yellow(entry.complexityLevel) : import_chalk10.default.green(entry.complexityLevel)}`) : "";
6748
+ console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
6407
6749
  }
6408
- console.log(import_chalk9.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6750
+ console.log(import_chalk10.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6409
6751
  }
6410
6752
  };
6411
6753
 
6412
- // core/constitution-generator.ts
6413
- var import_chalk10 = __toESM(require("chalk"));
6414
- var fs12 = __toESM(require("fs-extra"));
6415
- var path8 = __toESM(require("path"));
6416
-
6417
- // prompts/constitution.prompt.ts
6418
- var constitutionSystemPrompt = `You are a Senior Software Architect. Analyze the provided project codebase context and generate a concise "Project Constitution" \u2014 a living document that captures the architectural rules, conventions, and red lines that ALL future feature specs and code generation MUST follow.
6419
-
6420
- Output a Markdown document with EXACTLY these sections. Be specific and derive rules directly from the observed codebase \u2014 no generic advice.
6421
-
6422
- ---
6423
-
6424
- # Project Constitution
6425
-
6426
- ## 1. \u67B6\u6784\u89C4\u5219 (Architecture Rules)
6427
- \u5217\u51FA\u9879\u76EE\u7684\u6838\u5FC3\u67B6\u6784\u6A21\u5F0F\u548C\u5F3A\u5236\u7EA6\u675F\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF0C\u800C\u975E\u901A\u7528\u5EFA\u8BAE\uFF09\u3002
6428
- - \u5206\u5C42\u67B6\u6784\u89C4\u5219\uFF08\u5982\uFF1Aroutes \u2192 controllers \u2192 services \u2192 DB\uFF09
6429
- - \u7981\u6B62\u8DE8\u5C42\u76F4\u63A5\u8C03\u7528\u7684\u89C4\u5219
6430
- - \u6A21\u5757\u7EC4\u7EC7\u89C4\u8303
6431
-
6432
- ## 2. \u547D\u540D\u89C4\u8303 (Naming Conventions)
6433
- - \u6587\u4EF6\u547D\u540D\u89C4\u5219\uFF08\u9A7C\u5CF0/\u4E0B\u5212\u7EBF/kebab\uFF09
6434
- - \u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u7684\u547D\u540D\u6A21\u5F0F
6435
- - \u8DEF\u7531\u8DEF\u5F84\u7684\u547D\u540D\u89C4\u8303
6436
-
6437
- ## 3. API \u89C4\u8303 (API Patterns)
6438
- - \u8DEF\u7531\u524D\u7F00\u89C4\u5219\uFF08\u5982 /api/v1/client/... vs /api/v1/admin/...\uFF09
6439
- - \u7EDF\u4E00\u54CD\u5E94\u7ED3\u6784\u6A21\u677F\uFF08code/message/data \u683C\u5F0F\uFF09
6440
- - \u9519\u8BEF\u7801\u89C4\u8303\uFF08\u5DF2\u6709\u7684\u9519\u8BEF\u7801\u8303\u56F4\u548C\u542B\u4E49\uFF09
6441
- - \u8BA4\u8BC1/\u9274\u6743\u6A21\u5F0F\uFF08middleware \u540D\u79F0\u548C\u4F7F\u7528\u4F4D\u7F6E\uFF09
6442
-
6443
- ## 4. \u6570\u636E\u5C42\u89C4\u8303 (Data Layer Rules)
6444
- - ORM/\u6570\u636E\u5E93\u8BBF\u95EE\u89C4\u5219\uFF08\u4EC5\u901A\u8FC7 service \u5C42\u8BBF\u95EE\uFF1F\u76F4\u63A5\u7528 Prisma/Mongoose\uFF1F\uFF09
6445
- - \u5DF2\u6709\u7684\u6570\u636E\u6A21\u578B\u547D\u540D\u89C4\u8303
6446
- - \u4E8B\u52A1\u5904\u7406\u6A21\u5F0F
6447
-
6448
- ## 5. \u9519\u8BEF\u5904\u7406\u89C4\u8303 (Error Handling Patterns)
6449
- - \u7EDF\u4E00\u9519\u8BEF\u5904\u7406 middleware \u7684\u4F7F\u7528\u89C4\u5219
6450
- - \u9519\u8BEF\u629B\u51FA\u548C\u6355\u83B7\u7684\u6A21\u5F0F
6451
- - \u5DF2\u77E5\u9519\u8BEF\u7801\u5217\u8868\uFF08\u4ECE\u4EE3\u7801\u4E2D\u63D0\u53D6\uFF09
6452
-
6453
- ## 6. \u7981\u533A (Red Lines \u2014 Never Violate)
6454
- \u660E\u786E\u5217\u51FA\u7EDD\u5BF9\u4E0D\u80FD\u505A\u7684\u4E8B\u60C5\uFF08\u4ECE\u73B0\u6709\u4EE3\u7801/\u67B6\u6784\u63A8\u65AD\uFF09\uFF1A
6455
- - [ ] \u7981\u6B62 ...
6456
- - [ ] \u7981\u6B62 ...
6457
-
6458
- ## 7. \u6D4B\u8BD5\u89C4\u8303 (Testing Rules)
6459
- - \u6D4B\u8BD5\u6587\u4EF6\u5B58\u653E\u4F4D\u7F6E
6460
- - \u5FC5\u987B\u8986\u76D6\u7684\u6D4B\u8BD5\u573A\u666F\u7C7B\u578B
6461
- - \u6D4B\u8BD5\u6846\u67B6\u548C\u5DE5\u5177
6462
-
6463
- ## 8. \u5171\u4EAB\u914D\u7F6E\u6587\u4EF6\u6E05\u5355 (Shared Config Files \u2014 Append-Only)
6464
-
6465
- CRITICAL: The following files are **singleton config files** that already exist in the project.
6466
- When any feature needs to add entries (translations, constants, routes, enums, etc.), they MUST be
6467
- appended/merged into these existing files. **NEVER create a new parallel file.**
6468
-
6469
- For each discovered file, list it as:
6470
- - \`<relative-path>\` \u2014 <category> \u2014 **MODIFY ONLY, never recreate**
6471
-
6472
- If the project context includes i18n/locale files: list ALL of them with their paths.
6473
- If the project context includes constants/enums files: list ALL of them.
6474
- If the project context includes route index files: list ALL of them.
6475
- If none are provided in the context, write: "(No shared config files detected \u2014 will be populated on first run)"
6476
-
6477
- ---
6478
-
6479
- Be concise. Each rule must be specific enough to enforce, not a vague principle.
6480
- **Section 8 is the most important section for preventing file duplication bugs.**`;
6481
-
6482
- // core/constitution-generator.ts
6483
- var CONSTITUTION_FILE = ".ai-spec-constitution.md";
6484
- var ConstitutionGenerator = class {
6485
- constructor(provider) {
6486
- this.provider = provider;
6487
- }
6488
- async generate(projectRoot) {
6489
- const loader = new ContextLoader(projectRoot);
6490
- const context = await loader.loadProjectContext();
6491
- const prompt = buildConstitutionPrompt(context, projectRoot);
6492
- return this.provider.generate(prompt, constitutionSystemPrompt);
6493
- }
6494
- async saveConstitution(projectRoot, content) {
6495
- const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
6496
- await fs12.writeFile(filePath, content, "utf-8");
6497
- return filePath;
6498
- }
6499
- };
6500
- function buildConstitutionPrompt(context, projectRoot) {
6501
- const parts = [
6502
- "Analyze this project and generate its Project Constitution.\n",
6503
- `=== Tech Stack ===
6504
- ${context.techStack.join(", ") || "unknown"}
6505
- `,
6506
- `=== Dependencies (top 30) ===
6507
- ${context.dependencies.slice(0, 30).join(", ")}
6508
- `
6509
- ];
6510
- if (context.apiStructure.length > 0) {
6511
- parts.push(`=== API/Route Files ===
6512
- ${context.apiStructure.join("\n")}
6513
- `);
6514
- }
6515
- if (context.routeSummary) {
6516
- parts.push(`=== Route Code Samples ===
6517
- ${context.routeSummary}
6518
- `);
6519
- }
6520
- if (context.schema) {
6521
- parts.push(`=== Prisma Schema ===
6522
- ${context.schema.slice(0, 4e3)}
6523
- `);
6524
- }
6525
- if (context.errorPatterns) {
6526
- parts.push(`=== Error Handling Patterns ===
6527
- ${context.errorPatterns}
6528
- `);
6529
- }
6530
- if (context.sharedConfigFiles && context.sharedConfigFiles.length > 0) {
6531
- const grouped = context.sharedConfigFiles.reduce(
6532
- (acc, f) => {
6533
- (acc[f.category] ??= []).push(f);
6534
- return acc;
6535
- },
6536
- {}
6537
- );
6538
- const sections = [];
6539
- for (const [category, files] of Object.entries(grouped)) {
6540
- sections.push(`--- ${category} ---`);
6541
- for (const f of files) {
6542
- sections.push(`File: ${f.path}
6543
- ${f.preview.slice(0, 600)}
6544
- `);
6545
- }
6546
- }
6547
- parts.push(`=== Existing Shared Config Files (Append-Only \u2014 NEVER Recreate) ===
6548
- ${sections.join("\n")}
6549
- `);
6550
- }
6551
- return parts.join("\n");
6552
- }
6553
- async function loadConstitution(projectRoot) {
6554
- const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
6555
- if (await fs12.pathExists(filePath)) {
6556
- return fs12.readFile(filePath, "utf-8");
6557
- }
6558
- return void 0;
6559
- }
6560
- function printConstitutionHint(exists) {
6561
- if (!exists) {
6562
- console.log(
6563
- import_chalk10.default.yellow(
6564
- " \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
6565
- )
6566
- );
6567
- }
6568
- }
6569
-
6570
6754
  // core/combined-generator.ts
6571
6755
  var TASKS_SEPARATOR = "---TASKS_JSON---";
6572
6756
  var tasksInstruction = `
@@ -6575,11 +6759,21 @@ var tasksInstruction = `
6575
6759
  After outputting the complete spec above, append EXACTLY this line on its own (no extra text before or after it):
6576
6760
  ${TASKS_SEPARATOR}
6577
6761
  Then output a valid JSON array of implementation tasks. Each element must have these exact fields:
6578
- {"id":"TASK-001","title":"...","description":"1-2 sentences, specific","layer":"data|infra|service|api|test","filesToTouch":["src/..."],"acceptanceCriteria":["verifiable condition"],"dependencies":[],"priority":"high|medium|low"}
6762
+ {"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"}
6763
+ verificationSteps rules: each step is a specific command or action with observable expected output (e.g. "POST /api/orders \u2192 201 {id, status:'pending'}"). At least 2 per task, max 5. Never vague.
6579
6764
  Layer order: data \u2192 infra \u2192 service \u2192 api \u2192 test. 4-10 tasks total. filesToTouch must use real paths from the project context.`;
6580
- async function generateSpecWithTasks(provider, idea, context) {
6765
+ async function generateSpecWithTasks(provider, idea, context, architectureDecision) {
6581
6766
  const contextBlock = buildTaskPrompt("", context).trim();
6582
- const fullPrompt = [idea, contextBlock].filter(Boolean).join("\n\n");
6767
+ const parts = [idea];
6768
+ if (architectureDecision) {
6769
+ parts.push(
6770
+ `
6771
+ === Architecture Decision (MUST follow this approach in the spec) ===
6772
+ ${architectureDecision}`
6773
+ );
6774
+ }
6775
+ if (contextBlock) parts.push(contextBlock);
6776
+ const fullPrompt = parts.join("\n\n");
6583
6777
  const combinedSystemPrompt = specPrompt + tasksInstruction;
6584
6778
  const raw = await provider.generate(fullPrompt, combinedSystemPrompt);
6585
6779
  return parseSpecAndTasks(raw);
@@ -6715,6 +6909,8 @@ var GitWorktreeManager = class {
6715
6909
  TaskGenerator,
6716
6910
  buildTaskPrompt,
6717
6911
  createProvider,
6912
+ extractComplianceScore,
6913
+ extractMissingCount,
6718
6914
  generateSpecWithTasks,
6719
6915
  isFrontendDeps,
6720
6916
  loadConstitution,