ai-spec-dev 0.1.0 → 0.17.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 (60) hide show
  1. package/.claude/settings.local.json +18 -0
  2. package/README.md +1215 -146
  3. package/RELEASE_LOG.md +1489 -0
  4. package/cli/index.ts +1981 -0
  5. package/cli/welcome.ts +151 -0
  6. package/core/code-generator.ts +757 -0
  7. package/core/combined-generator.ts +63 -0
  8. package/core/constitution-consolidator.ts +141 -0
  9. package/core/constitution-generator.ts +89 -0
  10. package/core/context-loader.ts +453 -0
  11. package/core/contract-bridge.ts +217 -0
  12. package/core/dsl-extractor.ts +337 -0
  13. package/core/dsl-types.ts +166 -0
  14. package/core/dsl-validator.ts +450 -0
  15. package/core/error-feedback.ts +354 -0
  16. package/core/frontend-context-loader.ts +602 -0
  17. package/core/global-constitution.ts +88 -0
  18. package/core/key-store.ts +49 -0
  19. package/core/knowledge-memory.ts +171 -0
  20. package/core/mock-server-generator.ts +571 -0
  21. package/core/openapi-exporter.ts +361 -0
  22. package/core/requirement-decomposer.ts +198 -0
  23. package/core/reviewer.ts +259 -0
  24. package/core/spec-assessor.ts +99 -0
  25. package/core/spec-generator.ts +428 -0
  26. package/core/spec-refiner.ts +89 -0
  27. package/core/spec-updater.ts +227 -0
  28. package/core/spec-versioning.ts +213 -0
  29. package/core/task-generator.ts +174 -0
  30. package/core/test-generator.ts +273 -0
  31. package/core/workspace-loader.ts +256 -0
  32. package/dist/cli/index.js +6717 -672
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/index.mjs +6717 -670
  35. package/dist/cli/index.mjs.map +1 -1
  36. package/dist/index.d.mts +147 -27
  37. package/dist/index.d.ts +147 -27
  38. package/dist/index.js +2337 -286
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +2329 -285
  41. package/dist/index.mjs.map +1 -1
  42. package/git/worktree.ts +109 -0
  43. package/index.ts +9 -0
  44. package/package.json +4 -28
  45. package/prompts/codegen.prompt.ts +259 -0
  46. package/prompts/consolidate.prompt.ts +73 -0
  47. package/prompts/constitution.prompt.ts +63 -0
  48. package/prompts/decompose.prompt.ts +168 -0
  49. package/prompts/dsl.prompt.ts +203 -0
  50. package/prompts/frontend-spec.prompt.ts +191 -0
  51. package/prompts/global-constitution.prompt.ts +61 -0
  52. package/prompts/spec-assess.prompt.ts +53 -0
  53. package/prompts/spec.prompt.ts +102 -0
  54. package/prompts/tasks.prompt.ts +35 -0
  55. package/prompts/testgen.prompt.ts +84 -0
  56. package/prompts/update.prompt.ts +131 -0
  57. package/purpose.docx +0 -0
  58. package/purpose.md +444 -0
  59. package/tsconfig.json +14 -0
  60. package/tsup.config.ts +10 -0
package/cli/welcome.ts ADDED
@@ -0,0 +1,151 @@
1
+ import chalk from "chalk";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import * as fs from "fs-extra";
5
+
6
+ // ─── Layout constants ─────────────────────────────────────────────────────────
7
+
8
+ const VERSION = "0.14.1";
9
+ /** Total visible width of the title / bottom bar */
10
+ const TOTAL_W = 76;
11
+ /** Visible width of the left content column */
12
+ const L_WIDTH = 44;
13
+ /** Visible width of the right content column (TOTAL_W - L_WIDTH - 4 for " │ " + leading space) */
14
+ const R_WIDTH = TOTAL_W - L_WIDTH - 4;
15
+
16
+ // ─── ASCII robot ──────────────────────────────────────────────────────────────
17
+
18
+ const ROBOT_COLOR = chalk.hex("#E8885A");
19
+
20
+ const ROBOT: string[] = [
21
+ ROBOT_COLOR(" ┌───────────┐"),
22
+ ROBOT_COLOR(" │") + chalk.bold.white(" ◉ ") + ROBOT_COLOR(" ") + chalk.bold.white("◉ ") + ROBOT_COLOR("│"),
23
+ ROBOT_COLOR(" │") + chalk.dim(" ╰─╯ ") + ROBOT_COLOR("│"),
24
+ ROBOT_COLOR(" └─────┬─────┘"),
25
+ ROBOT_COLOR(" │"),
26
+ ROBOT_COLOR(" ────┴────"),
27
+ ];
28
+
29
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
30
+
31
+ /** Visible (non-ANSI) length of a string */
32
+ function visLen(s: string): number {
33
+ // eslint-disable-next-line no-control-regex
34
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
35
+ }
36
+
37
+ /** Pad a string on the right to reach `width` visible chars */
38
+ function padR(s: string, width: number): string {
39
+ const vl = visLen(s);
40
+ return vl >= width ? s : s + " ".repeat(width - vl);
41
+ }
42
+
43
+ /** Render one full-width row: left column + separator + right column */
44
+ function row(left: string, right: string): string {
45
+ return padR(left, L_WIDTH) + " " + chalk.gray("│") + " " + padR(right, R_WIDTH);
46
+ }
47
+
48
+ /** Center a string (with chalk codes) within `width` visible chars */
49
+ function center(s: string, width: number): string {
50
+ const vl = visLen(s);
51
+ const pad = Math.max(0, Math.floor((width - vl) / 2));
52
+ return " ".repeat(pad) + s;
53
+ }
54
+
55
+ // ─── Recent specs ─────────────────────────────────────────────────────────────
56
+
57
+ async function getRecentSpecs(dir: string): Promise<string[]> {
58
+ const specsDir = path.join(dir, "specs");
59
+ if (!(await fs.pathExists(specsDir))) return [];
60
+ try {
61
+ const files = await fs.readdir(specsDir);
62
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
63
+ const withStats = await Promise.all(
64
+ mdFiles.map(async (f) => {
65
+ const stat = await fs.stat(path.join(specsDir, f));
66
+ return { name: f, mtime: stat.mtime.getTime() };
67
+ })
68
+ );
69
+ withStats.sort((a, b) => b.mtime - a.mtime);
70
+ return withStats.slice(0, 3).map(({ name, mtime }) => {
71
+ const ms = Date.now() - mtime;
72
+ const hours = Math.floor(ms / 3_600_000);
73
+ const days = Math.floor(hours / 24);
74
+ const age = days > 0 ? `${days}d ago` : hours > 0 ? `${hours}h ago` : "just now";
75
+ // trim to fit right column
76
+ const slug = name.replace(/\.md$/, "").slice(0, R_WIDTH - age.length - 2);
77
+ return chalk.white(slug) + chalk.dim(" " + age);
78
+ });
79
+ } catch {
80
+ return [];
81
+ }
82
+ }
83
+
84
+ // ─── Public API ───────────────────────────────────────────────────────────────
85
+
86
+ export async function printWelcome(
87
+ currentDir: string,
88
+ config?: { provider?: string; model?: string }
89
+ ): Promise<void> {
90
+ const username = os.userInfo().username;
91
+ const homeDir = os.homedir();
92
+ const shortDir = currentDir.startsWith(homeDir)
93
+ ? "~" + currentDir.slice(homeDir.length)
94
+ : currentDir;
95
+
96
+ const recentSpecs = await getRecentSpecs(currentDir);
97
+
98
+ const providerBit = config?.provider
99
+ ? config.provider + (config.model ? " · " + config.model : "")
100
+ : "";
101
+ // Build bottom info and truncate to fit left column (leave 2-char indent)
102
+ const bottomRaw = [providerBit, shortDir].filter(Boolean).join(" · ");
103
+ const maxInfoLen = L_WIDTH - 2;
104
+ const bottomTruncated = bottomRaw.length > maxInfoLen
105
+ ? bottomRaw.slice(0, maxInfoLen - 1) + "…"
106
+ : bottomRaw;
107
+ const bottomLine = " " + chalk.dim(bottomTruncated);
108
+
109
+ // ── Title bar ───────────────────────────────────────────────────────────────
110
+ const titleInner = `ai-spec v${VERSION} `;
111
+ const titleDashes = "─".repeat(Math.max(0, TOTAL_W - titleInner.length - 4));
112
+ console.log(
113
+ "\n" +
114
+ chalk.hex("#FF6B35")("─── " + titleInner + titleDashes)
115
+ );
116
+
117
+ // ── Build left-column lines ──────────────────────────────────────────────────
118
+ const welcomeText = "Welcome back, " + chalk.bold.white(username) + "!";
119
+ const leftLines: string[] = [
120
+ "",
121
+ center(welcomeText, L_WIDTH),
122
+ "",
123
+ ...ROBOT,
124
+ "",
125
+ bottomLine,
126
+ "",
127
+ ];
128
+
129
+ // ── Build right-column lines ─────────────────────────────────────────────────
130
+ const rightLines: string[] = [
131
+ chalk.hex("#FF8C00").bold("Tips for getting started"),
132
+ chalk.gray("─".repeat(R_WIDTH)),
133
+ chalk.white('ai-spec create "feature"'),
134
+ chalk.gray("ai-spec workspace run"),
135
+ chalk.gray("ai-spec update \"change\""),
136
+ "",
137
+ chalk.hex("#FF8C00").bold("Recent activity"),
138
+ chalk.gray("─".repeat(R_WIDTH)),
139
+ ...(recentSpecs.length > 0 ? recentSpecs : [chalk.dim("No recent activity")]),
140
+ ];
141
+
142
+ // ── Render rows ─────────────────────────────────────────────────────────────
143
+ const maxLines = Math.max(leftLines.length, rightLines.length);
144
+ for (let i = 0; i < maxLines; i++) {
145
+ console.log(row(leftLines[i] ?? "", rightLines[i] ?? ""));
146
+ }
147
+
148
+ // ── Bottom bar ──────────────────────────────────────────────────────────────
149
+ console.log(chalk.gray("─".repeat(TOTAL_W)));
150
+ console.log();
151
+ }