ai-spec-dev 0.42.0 → 0.55.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/README.md +86 -40
  2. package/cli/commands/config.ts +129 -1
  3. package/cli/commands/create.ts +246 -11
  4. package/cli/commands/fix-history.ts +176 -0
  5. package/cli/commands/init.ts +344 -106
  6. package/cli/index.ts +3 -7
  7. package/cli/pipeline/helpers.ts +6 -0
  8. package/cli/pipeline/multi-repo.ts +291 -26
  9. package/cli/pipeline/single-repo.ts +103 -2
  10. package/cli/utils.ts +95 -4
  11. package/core/code-generator.ts +63 -14
  12. package/core/config-defaults.ts +44 -0
  13. package/core/constitution-generator.ts +2 -1
  14. package/core/cross-stack-verifier.ts +395 -0
  15. package/core/dsl-extractor.ts +2 -1
  16. package/core/error-feedback.ts +3 -2
  17. package/core/fix-history.ts +333 -0
  18. package/core/import-fixer.ts +827 -0
  19. package/core/import-verifier.ts +569 -0
  20. package/core/knowledge-memory.ts +55 -6
  21. package/core/openapi-exporter.ts +3 -2
  22. package/core/repo-store.ts +95 -0
  23. package/core/reviewer.ts +14 -13
  24. package/core/run-logger.ts +3 -4
  25. package/core/run-snapshot.ts +2 -3
  26. package/core/run-trend.ts +3 -4
  27. package/core/self-evaluator.ts +44 -7
  28. package/core/spec-generator.ts +30 -45
  29. package/core/token-budget.ts +3 -8
  30. package/core/types-generator.ts +2 -2
  31. package/core/vcr.ts +3 -1
  32. package/dist/cli/index.js +3889 -1937
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/index.mjs +3888 -1936
  35. package/dist/cli/index.mjs.map +1 -1
  36. package/dist/index.d.mts +17 -2
  37. package/dist/index.d.ts +17 -2
  38. package/dist/index.js +292 -181
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +292 -181
  41. package/dist/index.mjs.map +1 -1
  42. package/package.json +2 -2
  43. package/tests/cross-stack-verifier.test.ts +301 -0
  44. package/tests/fix-history.test.ts +335 -0
  45. package/tests/import-fixer.test.ts +944 -0
  46. package/tests/import-verifier.test.ts +420 -0
  47. package/tests/knowledge-memory.test.ts +40 -0
  48. package/tests/self-evaluator.test.ts +97 -0
  49. package/cli/commands/model.ts +0 -156
  50. package/cli/commands/scan.ts +0 -99
  51. package/cli/commands/workspace.ts +0 -219
  52. package/demo-backend/.ai-spec-constitution.md +0 -65
  53. package/demo-backend/package.json +0 -21
  54. package/demo-backend/prisma/schema.prisma +0 -22
  55. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +0 -186
  56. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +0 -211
  57. package/demo-backend/src/controllers/bookmark.controller.test.ts +0 -255
  58. package/demo-backend/src/controllers/bookmark.controller.ts +0 -187
  59. package/demo-backend/src/index.ts +0 -17
  60. package/demo-backend/src/routes/bookmark.routes.test.ts +0 -264
  61. package/demo-backend/src/routes/bookmark.routes.ts +0 -11
  62. package/demo-backend/src/routes/index.ts +0 -8
  63. package/demo-backend/src/services/bookmark.service.test.ts +0 -433
  64. package/demo-backend/src/services/bookmark.service.ts +0 -261
  65. package/demo-backend/tsconfig.json +0 -12
  66. package/demo-frontend/.ai-spec-constitution.md +0 -95
  67. package/demo-frontend/package.json +0 -23
  68. package/demo-frontend/src/App.tsx +0 -12
  69. package/demo-frontend/src/main.tsx +0 -9
  70. package/demo-frontend/tsconfig.json +0 -13
@@ -0,0 +1,176 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { confirm } from "@inquirer/prompts";
4
+ import {
5
+ loadFixHistory,
6
+ pruneFixHistory,
7
+ aggregateFixPatterns,
8
+ detectPromotionCandidates,
9
+ computeFixHistoryStats,
10
+ FIX_HISTORY_FILE,
11
+ } from "../../core/fix-history";
12
+ import { appendDirectLesson } from "../../core/knowledge-memory";
13
+ import { loadConfig } from "../utils";
14
+
15
+ export function registerFixHistory(program: Command): void {
16
+ program
17
+ .command("fix-history")
18
+ .description("Inspect and manage the import auto-fix history ledger")
19
+ .option("--list", "List raw entries instead of the aggregated summary")
20
+ .option("--prune <days>", "Remove entries older than N days")
21
+ .option("--promote", "Review patterns above threshold and promote to constitution §9")
22
+ .option("--threshold <n>", "Override promotion threshold (default from config, usually 5)")
23
+ .action(async (opts) => {
24
+ const currentDir = process.cwd();
25
+ const config = await loadConfig(currentDir);
26
+ const history = await loadFixHistory(currentDir);
27
+
28
+ // ── --prune ────────────────────────────────────────────────────────────
29
+ if (opts.prune !== undefined) {
30
+ const days = parseInt(opts.prune, 10);
31
+ if (isNaN(days) || days < 0) {
32
+ console.error(chalk.red(` --prune must be a non-negative integer (days)`));
33
+ process.exit(1);
34
+ }
35
+ const removed = await pruneFixHistory(currentDir, days);
36
+ if (removed === 0) {
37
+ console.log(chalk.gray(` No entries older than ${days} day(s) to remove.`));
38
+ } else {
39
+ console.log(chalk.green(` ✔ Removed ${removed} entry/entries older than ${days} day(s).`));
40
+ }
41
+ return;
42
+ }
43
+
44
+ // ── Empty ledger ──────────────────────────────────────────────────────
45
+ if (history.entries.length === 0) {
46
+ console.log(chalk.gray(`\nNo fix history found. Ledger: ${FIX_HISTORY_FILE}`));
47
+ console.log(chalk.gray(` The ledger is populated automatically when import-fixer repairs broken imports.`));
48
+ return;
49
+ }
50
+
51
+ // ── --promote ─────────────────────────────────────────────────────────
52
+ if (opts.promote) {
53
+ const threshold = opts.threshold
54
+ ? parseInt(opts.threshold, 10)
55
+ : config.fixHistoryPromotionThreshold ?? 5;
56
+
57
+ const candidates = detectPromotionCandidates(history, threshold);
58
+ if (candidates.length === 0) {
59
+ console.log(chalk.gray(`\n No patterns have crossed the promotion threshold (${threshold}x).`));
60
+ console.log(chalk.gray(` Run the pipeline more to accumulate patterns, or lower the threshold with --threshold <n>.`));
61
+ return;
62
+ }
63
+
64
+ console.log(
65
+ chalk.bold(`\n─── Promotion Candidates (threshold: ${threshold}x) ────────────────`)
66
+ );
67
+ console.log(
68
+ chalk.gray(` ${candidates.length} pattern(s) seen at least ${threshold} time(s).`)
69
+ );
70
+ console.log(
71
+ chalk.gray(` Accepted lessons are written to constitution §9 (accumulated lessons).\n`)
72
+ );
73
+
74
+ let accepted = 0;
75
+ for (const c of candidates) {
76
+ console.log(
77
+ chalk.cyan(`\n Pattern: ${c.aggregate.source}`) +
78
+ chalk.gray(` (${c.aggregate.count}x, ${c.aggregate.uniqueRunIds} run(s))`)
79
+ );
80
+ console.log(chalk.gray(` Names: { ${c.aggregate.names.join(", ")} }`));
81
+ console.log(chalk.gray(` Reason: ${c.aggregate.reason}`));
82
+ console.log(chalk.gray(` Lesson text:`));
83
+ console.log(chalk.white(` ${c.lessonText}`));
84
+
85
+ const ok = await confirm({
86
+ message: `Promote this pattern to constitution §9?`,
87
+ default: true,
88
+ });
89
+ if (!ok) {
90
+ console.log(chalk.gray(` skipped.`));
91
+ continue;
92
+ }
93
+ const result = await appendDirectLesson(currentDir, c.lessonText);
94
+ if (result.appended) {
95
+ console.log(chalk.green(` ✔ Appended to constitution §9.`));
96
+ accepted++;
97
+ } else {
98
+ console.log(chalk.yellow(` ⚠ Not appended: ${result.reason}`));
99
+ }
100
+ }
101
+
102
+ console.log(
103
+ chalk.green(`\n ✔ Promotion complete: ${accepted}/${candidates.length} pattern(s) added to §9.`)
104
+ );
105
+ return;
106
+ }
107
+
108
+ // ── --list: raw entries ───────────────────────────────────────────────
109
+ if (opts.list) {
110
+ console.log(chalk.bold(`\n─── Fix History Entries (${history.entries.length}) ────────────────`));
111
+ console.log(chalk.gray(` File: ${FIX_HISTORY_FILE}\n`));
112
+ // Show newest first
113
+ const sorted = [...history.entries].sort((a, b) => b.ts.localeCompare(a.ts));
114
+ for (const e of sorted.slice(0, 50)) {
115
+ const tsShort = e.ts.slice(0, 19).replace("T", " ");
116
+ const stageTag = e.fix.stage === "deterministic" ? chalk.green("[DSL]") : chalk.cyan("[AI ]");
117
+ console.log(
118
+ ` ${stageTag} ${chalk.gray(tsShort)} ${chalk.white(e.brokenImport.source)} ${chalk.gray(`{ ${e.brokenImport.names.join(", ")} }`)}`
119
+ );
120
+ console.log(
121
+ chalk.gray(` ${e.fix.kind} → ${e.fix.target} (run: ${e.runId}, ${e.brokenImport.file}:${e.brokenImport.line})`)
122
+ );
123
+ }
124
+ if (history.entries.length > 50) {
125
+ console.log(chalk.gray(`\n ... ${history.entries.length - 50} older entry(ies) not shown`));
126
+ }
127
+ return;
128
+ }
129
+
130
+ // ── Default: aggregated summary ───────────────────────────────────────
131
+ const stats = computeFixHistoryStats(history);
132
+ const patterns = aggregateFixPatterns(history);
133
+
134
+ console.log(chalk.bold(`\n─── Fix History Summary ────────────────────────────`));
135
+ console.log(chalk.gray(` File: ${FIX_HISTORY_FILE}\n`));
136
+ console.log(` Total fixes applied : ${chalk.white(String(stats.totalEntries))}`);
137
+ console.log(` Unique patterns : ${chalk.white(String(stats.uniquePatterns))}`);
138
+ console.log(` Runs that triggered : ${chalk.white(String(stats.uniqueRunIds))}`);
139
+ console.log(
140
+ ` Stage A (deterministic): ${chalk.green(String(stats.byStage.deterministic))} · Stage B (AI): ${chalk.cyan(String(stats.byStage.ai))}`
141
+ );
142
+ console.log(
143
+ ` Reasons : file_not_found ${stats.byReason.file_not_found} · missing_export ${stats.byReason.missing_export}`
144
+ );
145
+ if (stats.lastEntryTs) {
146
+ console.log(` Last fix : ${chalk.gray(stats.lastEntryTs.slice(0, 19).replace("T", " "))}`);
147
+ }
148
+
149
+ console.log(chalk.bold(`\n Top patterns (by frequency):`));
150
+ const top = patterns.slice(0, 10);
151
+ for (const p of top) {
152
+ const stageTag = p.fix.stage === "deterministic" ? chalk.green("[DSL]") : chalk.cyan("[AI ]");
153
+ const countColor = p.count >= 5 ? chalk.red : p.count >= 3 ? chalk.yellow : chalk.gray;
154
+ console.log(
155
+ ` ${stageTag} ${countColor(`${p.count}x`.padStart(4))} ${chalk.white(p.source)} ${chalk.gray(`{ ${p.names.join(", ")} }`)}`
156
+ );
157
+ console.log(chalk.gray(` last seen: ${p.lastSeen.slice(0, 10)} · ${p.uniqueRunIds} run(s)`));
158
+ }
159
+
160
+ const promotionThreshold = config.fixHistoryPromotionThreshold ?? 5;
161
+ const promotable = patterns.filter((p) => p.count >= promotionThreshold).length;
162
+ if (promotable > 0) {
163
+ console.log(
164
+ chalk.yellow(
165
+ `\n ⚠ ${promotable} pattern(s) have crossed the promotion threshold (${promotionThreshold}x). ` +
166
+ `Run \`ai-spec fix-history --promote\` to review.`
167
+ )
168
+ );
169
+ }
170
+
171
+ console.log(chalk.gray(`\n Hints:`));
172
+ console.log(chalk.gray(` ai-spec fix-history --list Show raw entries (newest first)`));
173
+ console.log(chalk.gray(` ai-spec fix-history --promote Promote repeated patterns to constitution §9`));
174
+ console.log(chalk.gray(` ai-spec fix-history --prune 30 Remove entries older than 30 days`));
175
+ });
176
+ }