@popoverai/dotrequirements 0.23.0 → 0.24.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 (229) hide show
  1. package/README.md +167 -20
  2. package/dist/cli.js +121 -60
  3. package/dist/codebase-to-spec/budget.d.ts +53 -0
  4. package/dist/codebase-to-spec/budget.js +80 -0
  5. package/dist/codebase-to-spec/cache.d.ts +49 -0
  6. package/dist/codebase-to-spec/cache.js +54 -0
  7. package/dist/codebase-to-spec/claude.d.ts +69 -0
  8. package/dist/codebase-to-spec/claude.js +126 -0
  9. package/dist/codebase-to-spec/compose.d.ts +49 -0
  10. package/dist/codebase-to-spec/compose.js +124 -0
  11. package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
  12. package/dist/codebase-to-spec/edit-loop.js +195 -0
  13. package/dist/codebase-to-spec/editor.d.ts +54 -0
  14. package/dist/codebase-to-spec/editor.js +74 -0
  15. package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
  16. package/dist/codebase-to-spec/exit-codes.js +58 -0
  17. package/dist/codebase-to-spec/fan-out.d.ts +63 -0
  18. package/dist/codebase-to-spec/fan-out.js +215 -0
  19. package/dist/codebase-to-spec/interactive.d.ts +30 -0
  20. package/dist/codebase-to-spec/interactive.js +48 -0
  21. package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
  22. package/dist/codebase-to-spec/outline-review-loop.js +187 -0
  23. package/dist/codebase-to-spec/pack.d.ts +51 -0
  24. package/dist/codebase-to-spec/pack.js +127 -0
  25. package/dist/codebase-to-spec/planner.d.ts +41 -0
  26. package/dist/codebase-to-spec/planner.js +76 -0
  27. package/dist/codebase-to-spec/present.d.ts +94 -0
  28. package/dist/codebase-to-spec/present.js +288 -0
  29. package/dist/codebase-to-spec/progress.d.ts +33 -0
  30. package/dist/codebase-to-spec/progress.js +28 -0
  31. package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
  32. package/dist/codebase-to-spec/prompts/editor.js +57 -0
  33. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
  34. package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
  35. package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
  36. package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
  37. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
  38. package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
  39. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
  40. package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
  41. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
  42. package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
  43. package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
  44. package/dist/codebase-to-spec/prompts/specifier.js +100 -0
  45. package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
  46. package/dist/codebase-to-spec/prompts/style-check.js +78 -0
  47. package/dist/codebase-to-spec/schemas.d.ts +257 -0
  48. package/dist/codebase-to-spec/schemas.js +183 -0
  49. package/dist/codebase-to-spec/skill-install.d.ts +57 -0
  50. package/dist/codebase-to-spec/skill-install.js +79 -0
  51. package/dist/codebase-to-spec/slice.d.ts +49 -0
  52. package/dist/codebase-to-spec/slice.js +111 -0
  53. package/dist/codebase-to-spec/specifier.d.ts +60 -0
  54. package/dist/codebase-to-spec/specifier.js +79 -0
  55. package/dist/codebase-to-spec/style-check.d.ts +29 -0
  56. package/dist/codebase-to-spec/style-check.js +33 -0
  57. package/dist/codebase-to-spec/summary.d.ts +51 -0
  58. package/dist/codebase-to-spec/summary.js +183 -0
  59. package/dist/codebase-to-spec/validate.d.ts +46 -0
  60. package/dist/codebase-to-spec/validate.js +130 -0
  61. package/dist/commands/acceptance-test.d.ts +6 -0
  62. package/dist/commands/acceptance-test.js +212 -0
  63. package/dist/commands/ai-setup.d.ts +5 -0
  64. package/dist/commands/ai-setup.js +441 -0
  65. package/dist/commands/browsertest.js +34 -27
  66. package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
  67. package/dist/commands/codebase-to-spec/compose.js +57 -0
  68. package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
  69. package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
  70. package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
  71. package/dist/commands/codebase-to-spec/fan-out.js +77 -0
  72. package/dist/commands/codebase-to-spec/index.d.ts +9 -0
  73. package/dist/commands/codebase-to-spec/index.js +135 -0
  74. package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
  75. package/dist/commands/codebase-to-spec/pack.js +76 -0
  76. package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
  77. package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
  78. package/dist/commands/codebase-to-spec/present.d.ts +21 -0
  79. package/dist/commands/codebase-to-spec/present.js +92 -0
  80. package/dist/commands/codebase-to-spec/run.d.ts +20 -0
  81. package/dist/commands/codebase-to-spec/run.js +85 -0
  82. package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
  83. package/dist/commands/codebase-to-spec/skill-install.js +51 -0
  84. package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
  85. package/dist/commands/codebase-to-spec/specify-area.js +82 -0
  86. package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
  87. package/dist/commands/codebase-to-spec/style-check.js +42 -0
  88. package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
  89. package/dist/commands/codebase-to-spec/validate.js +38 -0
  90. package/dist/commands/create-requirement-document.d.ts +2 -0
  91. package/dist/commands/create-requirement-document.js +41 -0
  92. package/dist/commands/finalize.js +7 -7
  93. package/dist/commands/get.d.ts +2 -0
  94. package/dist/commands/get.js +55 -0
  95. package/dist/commands/init.js +132 -117
  96. package/dist/commands/link.js +27 -27
  97. package/dist/commands/list.d.ts +6 -0
  98. package/dist/commands/list.js +43 -0
  99. package/dist/commands/mcp-setup.js +159 -149
  100. package/dist/commands/mcp.js +1 -1
  101. package/dist/commands/prepare.js +4 -4
  102. package/dist/commands/pull.js +116 -121
  103. package/dist/commands/push.js +106 -112
  104. package/dist/commands/report.d.ts +6 -2
  105. package/dist/commands/report.js +177 -122
  106. package/dist/commands/requirements-for.d.ts +2 -0
  107. package/dist/commands/requirements-for.js +29 -0
  108. package/dist/commands/review-test.d.ts +2 -0
  109. package/dist/commands/review-test.js +75 -0
  110. package/dist/commands/search.d.ts +6 -0
  111. package/dist/commands/search.js +39 -0
  112. package/dist/commands/style-check.d.ts +7 -0
  113. package/dist/commands/style-check.js +75 -0
  114. package/dist/commands/test.js +53 -59
  115. package/dist/commands/tests-for.d.ts +2 -0
  116. package/dist/commands/tests-for.js +80 -0
  117. package/dist/commands/validate.d.ts +6 -0
  118. package/dist/commands/validate.js +72 -0
  119. package/dist/config.js +1 -1
  120. package/dist/convex.d.ts +34 -22
  121. package/dist/convex.js +38 -22
  122. package/dist/harness/cache.d.ts +1 -5
  123. package/dist/harness/cache.js +49 -59
  124. package/dist/harness/convexReporting.d.ts +1 -1
  125. package/dist/harness/convexReporting.js +9 -7
  126. package/dist/harness/coverageCache.js +3 -3
  127. package/dist/harness/finalize.js +59 -46
  128. package/dist/harness/index.d.ts +6 -7
  129. package/dist/harness/index.js +9 -10
  130. package/dist/harness/prepare.js +6 -5
  131. package/dist/harness/requirementsLoader.d.ts +2 -2
  132. package/dist/harness/requirementsLoader.js +13 -35
  133. package/dist/harness/tracking.js +18 -18
  134. package/dist/harness/types.d.ts +1 -1
  135. package/dist/mcp/convexClient.d.ts +0 -39
  136. package/dist/mcp/convexClient.js +2 -107
  137. package/dist/mcp/grep.d.ts +1 -1
  138. package/dist/mcp/grep.js +87 -42
  139. package/dist/mcp/handlers/authoring.d.ts +1 -1
  140. package/dist/mcp/handlers/authoring.js +30 -234
  141. package/dist/mcp/handlers/coverage.d.ts +1 -1
  142. package/dist/mcp/handlers/coverage.js +13 -15
  143. package/dist/mcp/handlers/debug.d.ts +2 -3
  144. package/dist/mcp/handlers/debug.js +10 -10
  145. package/dist/mcp/handlers/get.d.ts +1 -1
  146. package/dist/mcp/handlers/get.js +11 -10
  147. package/dist/mcp/handlers/index.d.ts +20 -20
  148. package/dist/mcp/handlers/index.js +10 -10
  149. package/dist/mcp/handlers/list.d.ts +4 -33
  150. package/dist/mcp/handlers/list.js +16 -38
  151. package/dist/mcp/handlers/push.d.ts +1 -1
  152. package/dist/mcp/handlers/push.js +28 -18
  153. package/dist/mcp/handlers/report.d.ts +16 -0
  154. package/dist/mcp/handlers/report.js +134 -0
  155. package/dist/mcp/handlers/review.d.ts +1 -1
  156. package/dist/mcp/handlers/review.js +40 -59
  157. package/dist/mcp/handlers/search.d.ts +1 -1
  158. package/dist/mcp/handlers/search.js +7 -9
  159. package/dist/mcp/handlers/test-mapping.d.ts +1 -1
  160. package/dist/mcp/handlers/test-mapping.js +14 -14
  161. package/dist/mcp/handlers/types.d.ts +3 -3
  162. package/dist/mcp/handlers/types.js +2 -2
  163. package/dist/mcp/index.d.ts +1 -1
  164. package/dist/mcp/index.js +147 -167
  165. package/dist/mcp/requirements.d.ts +2 -2
  166. package/dist/mcp/requirements.js +30 -30
  167. package/dist/mcp/testCodeExtractor.js +24 -26
  168. package/dist/mcp/types.d.ts +1 -1
  169. package/dist/push/core.d.ts +2 -2
  170. package/dist/push/core.js +20 -20
  171. package/dist/push/index.d.ts +1 -1
  172. package/dist/push/index.js +2 -2
  173. package/dist/requirements/cloud-ai.d.ts +57 -0
  174. package/dist/requirements/cloud-ai.js +104 -0
  175. package/dist/requirements/cloud-coverage.d.ts +41 -0
  176. package/dist/requirements/cloud-coverage.js +60 -0
  177. package/dist/requirements/coverage.d.ts +45 -0
  178. package/dist/requirements/coverage.js +114 -0
  179. package/dist/requirements/grep.d.ts +33 -0
  180. package/dist/requirements/grep.js +306 -0
  181. package/dist/requirements/index.d.ts +73 -0
  182. package/dist/requirements/index.js +174 -0
  183. package/dist/requirements/style-guide.d.ts +67 -0
  184. package/dist/requirements/style-guide.js +299 -0
  185. package/dist/requirements/testCodeExtractor.d.ts +22 -0
  186. package/dist/requirements/testCodeExtractor.js +150 -0
  187. package/dist/schema/browser.d.ts +8 -8
  188. package/dist/schema/browser.js +13 -15
  189. package/dist/schema/builder.d.ts +1 -1
  190. package/dist/schema/builder.js +13 -44
  191. package/dist/schema/conversions.d.ts +2 -2
  192. package/dist/schema/conversions.js +11 -11
  193. package/dist/schema/index.d.ts +9 -9
  194. package/dist/schema/index.js +15 -15
  195. package/dist/schema/parser-core.d.ts +1 -1
  196. package/dist/schema/parser-core.js +23 -22
  197. package/dist/schema/parser.d.ts +3 -3
  198. package/dist/schema/parser.js +27 -31
  199. package/dist/schema/resolver.d.ts +1 -1
  200. package/dist/schema/resolver.js +9 -9
  201. package/dist/schema/scenario.d.ts +1 -1
  202. package/dist/schema/scenario.js +1 -1
  203. package/dist/schema/schemas.d.ts +3 -3
  204. package/dist/schema/schemas.js +41 -28
  205. package/dist/schema/test-schema.js +27 -27
  206. package/dist/templates/context-file-section.md +3 -2
  207. package/dist/templates/example-requirements.js +1 -1
  208. package/dist/templates/example-requirements.ts +3 -1
  209. package/dist/templates/requirements-readme.js +1 -1
  210. package/dist/templates/requirements-readme.ts +1 -1
  211. package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
  212. package/dist/utils/brand.js +3 -3
  213. package/dist/utils/browser-launch.js +4 -4
  214. package/dist/utils/context-file.d.ts +1 -1
  215. package/dist/utils/context-file.js +26 -26
  216. package/dist/utils/env.js +7 -7
  217. package/dist/utils/gitignore.js +7 -7
  218. package/dist/utils/oauth-callback-server.d.ts +1 -1
  219. package/dist/utils/oauth-callback-server.js +27 -25
  220. package/dist/utils/oauth-flow.js +32 -29
  221. package/dist/utils/project-discovery.d.ts +3 -3
  222. package/dist/utils/project-discovery.js +18 -17
  223. package/dist/utils/project-name.js +8 -8
  224. package/dist/utils/project-selector.d.ts +1 -1
  225. package/dist/utils/project-selector.js +24 -21
  226. package/dist/utils/project-settings.d.ts +1 -1
  227. package/dist/utils/project-settings.js +24 -22
  228. package/dist/utils/templates.js +6 -6
  229. package/package.json +2 -1
@@ -0,0 +1,441 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import prompts from "prompts";
6
+ import { brand } from "../utils/brand.js";
7
+ import { appendOrUpdateSection, buildNoGitRepoMessage, findDotrequirementsSection, findGitRoot, getContextFileName, } from "../utils/context-file.js";
8
+ import { loadTemplate } from "../utils/templates.js";
9
+ /**
10
+ * Load the context file section template
11
+ */
12
+ function loadContextFileSection() {
13
+ return loadTemplate("context-file-section.md");
14
+ }
15
+ /**
16
+ * Install workflow guidance to the appropriate context file for a platform
17
+ */
18
+ async function installContextFileSection(platform) {
19
+ const fileName = getContextFileName(platform);
20
+ if (!fileName) {
21
+ console.error(`\n❌ Unknown platform: ${platform}`);
22
+ return;
23
+ }
24
+ const gitRoot = await findGitRoot();
25
+ if (!gitRoot) {
26
+ console.log(`\n${buildNoGitRepoMessage(fileName)}\n`);
27
+ return;
28
+ }
29
+ const contextFilePath = join(gitRoot, fileName);
30
+ const sectionContent = loadContextFileSection();
31
+ // Check if file exists and has existing section
32
+ let existingSection = null;
33
+ if (existsSync(contextFilePath)) {
34
+ const content = readFileSync(contextFilePath, "utf-8");
35
+ const section = findDotrequirementsSection(content);
36
+ if (section) {
37
+ existingSection = section.content;
38
+ }
39
+ }
40
+ // Prompt user
41
+ const action = existingSection
42
+ ? "Update"
43
+ : existsSync(contextFilePath)
44
+ ? "Add to"
45
+ : "Create";
46
+ const response = await prompts({
47
+ type: "confirm",
48
+ name: "install",
49
+ message: `${action} ${fileName} with dotrequirements workflow guidance?`,
50
+ initial: true,
51
+ });
52
+ if (!response.install) {
53
+ console.log("");
54
+ return;
55
+ }
56
+ // If updating, show what will change
57
+ if (existingSection) {
58
+ const confirmUpdate = await prompts({
59
+ type: "confirm",
60
+ name: "confirm",
61
+ message: "Existing dotrequirements section found. Replace it?",
62
+ initial: true,
63
+ });
64
+ if (!confirmUpdate.confirm) {
65
+ console.log(" Skipped.\n");
66
+ return;
67
+ }
68
+ }
69
+ try {
70
+ const result = appendOrUpdateSection(contextFilePath, sectionContent);
71
+ const actionVerb = result.action === "created"
72
+ ? "Created"
73
+ : result.action === "appended"
74
+ ? "Added to"
75
+ : "Updated";
76
+ console.log(`✅ ${actionVerb} ${contextFilePath}\n`);
77
+ }
78
+ catch (error) {
79
+ console.error(`❌ Failed to update ${fileName}`);
80
+ console.log(` Error: ${error instanceof Error ? error.message : String(error)}\n`);
81
+ }
82
+ }
83
+ /**
84
+ * AI setup command - configures the MCP server for AI assistants
85
+ */
86
+ export async function aiSetupCommand() {
87
+ console.log(`🔧 Setting up ${brand} MCP server\n`);
88
+ // Interactive client selector
89
+ const response = await prompts({
90
+ type: "select",
91
+ name: "client",
92
+ message: "Which AI assistant are you using?",
93
+ choices: [
94
+ { title: "Claude Code", value: "claude-code" },
95
+ { title: "Claude Desktop", value: "claude-desktop" },
96
+ { title: "Cursor", value: "cursor" },
97
+ { title: "Google Antigravity", value: "antigravity" },
98
+ { title: "OpenAI Codex", value: "codex" },
99
+ { title: "GitHub Copilot", value: "github-copilot" },
100
+ { title: "Other (custom path)", value: "other" },
101
+ ],
102
+ initial: 0,
103
+ });
104
+ if (!response.client) {
105
+ console.log("\nSetup cancelled.");
106
+ return;
107
+ }
108
+ switch (response.client) {
109
+ case "claude-code":
110
+ await setupClaudeCode();
111
+ break;
112
+ case "claude-desktop":
113
+ await setupClaudeDesktop();
114
+ break;
115
+ case "cursor":
116
+ await setupCursor();
117
+ break;
118
+ case "antigravity":
119
+ await setupAntigravity();
120
+ break;
121
+ case "codex":
122
+ await setupCodex();
123
+ break;
124
+ case "github-copilot":
125
+ await setupGitHubCopilot();
126
+ break;
127
+ case "other":
128
+ await setupOther();
129
+ break;
130
+ default:
131
+ console.log(`\n❌ ${response.client} is not yet supported.`);
132
+ console.log("Please refer to the documentation for manual setup.");
133
+ }
134
+ }
135
+ async function setupClaudeCode() {
136
+ console.log("\n📦 Configuring MCP for Claude Code...\n");
137
+ const command = `claude mcp add-json dotrequirements '{"type":"stdio","command":"npx","args":["-y","@popoverai/dotrequirements","mcp"]}'`;
138
+ let mcpConfigured = false;
139
+ try {
140
+ execSync(command, { stdio: "pipe" });
141
+ console.log("✅ MCP configured successfully for Claude Code!");
142
+ mcpConfigured = true;
143
+ }
144
+ catch (error) {
145
+ const execError = error;
146
+ const stderr = execError.stderr?.toString() || "";
147
+ const errorMessage = execError.message || "";
148
+ const combinedError = stderr + errorMessage;
149
+ if (combinedError.includes("already exists")) {
150
+ console.log("✅ MCP server already configured for Claude Code");
151
+ mcpConfigured = true;
152
+ }
153
+ else {
154
+ console.error("❌ Failed to configure MCP automatically.");
155
+ console.log("\nYou can manually run:");
156
+ console.log(` ${command}`);
157
+ if (stderr) {
158
+ console.log(`\nError: ${stderr}`);
159
+ }
160
+ }
161
+ }
162
+ if (mcpConfigured) {
163
+ console.log("\n⚠️ Important: Restart Claude Code for changes to take effect.");
164
+ console.log("\nYou can now ask your AI assistant to help write requirements!\n");
165
+ await installContextFileSection("claude-code");
166
+ }
167
+ }
168
+ async function setupClaudeDesktop() {
169
+ console.log("\n📦 Configuring MCP for Claude Desktop...\n");
170
+ // Determine config path based on OS
171
+ const platform = process.platform;
172
+ let configPath;
173
+ let configDir;
174
+ if (platform === "darwin") {
175
+ configDir = join(homedir(), "Library", "Application Support", "Claude");
176
+ configPath = join(configDir, "claude_desktop_config.json");
177
+ }
178
+ else if (platform === "win32") {
179
+ const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
180
+ configDir = join(appData, "Claude");
181
+ configPath = join(configDir, "claude_desktop_config.json");
182
+ }
183
+ else {
184
+ console.error("❌ Claude Desktop is only supported on macOS and Windows.");
185
+ console.log("\nFor Linux, please manually configure the MCP server.");
186
+ return;
187
+ }
188
+ const mcpServerConfig = {
189
+ command: "npx",
190
+ args: ["-y", "@popoverai/dotrequirements", "mcp"],
191
+ env: {
192
+ REQUIREMENTS_DIR: process.cwd(),
193
+ },
194
+ };
195
+ try {
196
+ if (!existsSync(configDir)) {
197
+ console.log("Creating Claude Desktop config directory...");
198
+ mkdirSync(configDir, { recursive: true });
199
+ }
200
+ let config = {};
201
+ if (existsSync(configPath)) {
202
+ try {
203
+ const existingContent = readFileSync(configPath, "utf-8");
204
+ if (existingContent.trim()) {
205
+ config = JSON.parse(existingContent);
206
+ }
207
+ }
208
+ catch {
209
+ console.log("⚠️ Existing config file is invalid, creating fresh config...");
210
+ config = {};
211
+ }
212
+ }
213
+ if (!config.mcpServers) {
214
+ config.mcpServers = {};
215
+ }
216
+ config.mcpServers.dotrequirements = mcpServerConfig;
217
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
218
+ console.log(`✅ MCP configured successfully!`);
219
+ console.log(` Config file: ${configPath}`);
220
+ console.log(` REQUIREMENTS_DIR: ${process.cwd()}\n`);
221
+ console.log("⚠️ Important: Quit and restart Claude Desktop for changes to take effect.");
222
+ console.log(" (Cmd+Q on macOS, not just close the window)\n");
223
+ console.log("You can now ask Claude Desktop to help write requirements!\n");
224
+ // Claude Desktop doesn't use skills like Claude Code, so we skip that step
225
+ }
226
+ catch (error) {
227
+ console.error("❌ Failed to configure MCP automatically.");
228
+ console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
229
+ console.log("\nYou can manually edit the config file at:");
230
+ console.log(` ${configPath}\n`);
231
+ console.log("Add the following configuration:");
232
+ const manualConfig = {
233
+ mcpServers: {
234
+ dotrequirements: mcpServerConfig,
235
+ },
236
+ };
237
+ console.log(JSON.stringify(manualConfig, null, 2));
238
+ }
239
+ }
240
+ async function setupCursor() {
241
+ console.log("\n📦 Configuring MCP for Cursor (project-specific)...\n");
242
+ const configPath = join(process.cwd(), ".cursor", "mcp.json");
243
+ const configDir = join(process.cwd(), ".cursor");
244
+ const mcpServerConfig = {
245
+ command: "npx",
246
+ args: ["-y", "@popoverai/dotrequirements", "mcp"],
247
+ };
248
+ try {
249
+ if (!existsSync(configDir)) {
250
+ console.log("Creating Cursor config directory...");
251
+ mkdirSync(configDir, { recursive: true });
252
+ }
253
+ let config = {};
254
+ if (existsSync(configPath)) {
255
+ try {
256
+ const existingContent = readFileSync(configPath, "utf-8");
257
+ if (existingContent.trim()) {
258
+ config = JSON.parse(existingContent);
259
+ }
260
+ }
261
+ catch {
262
+ console.log("⚠️ Existing config file is invalid, creating fresh config...");
263
+ config = {};
264
+ }
265
+ }
266
+ if (!config.mcpServers) {
267
+ config.mcpServers = {};
268
+ }
269
+ config.mcpServers.dotrequirements = mcpServerConfig;
270
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
271
+ console.log(`✅ MCP configured successfully!`);
272
+ console.log(` Config file: ${configPath}\n`);
273
+ console.log("⚠️ Important: Reload or restart Cursor for changes to take effect.");
274
+ console.log("\nYou can now ask your AI assistant to help write requirements!\n");
275
+ await installContextFileSection("cursor");
276
+ }
277
+ catch (error) {
278
+ console.error("❌ Failed to configure MCP automatically.");
279
+ console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
280
+ console.log("\nYou can manually edit the config file at:");
281
+ console.log(` ${configPath}\n`);
282
+ console.log("Add the following configuration:");
283
+ const manualConfig = {
284
+ mcpServers: {
285
+ dotrequirements: mcpServerConfig,
286
+ },
287
+ };
288
+ console.log(JSON.stringify(manualConfig, null, 2));
289
+ }
290
+ }
291
+ async function setupAntigravity() {
292
+ console.log("\n📦 Configuring MCP for Google Antigravity...\n");
293
+ const configPath = join(homedir(), ".gemini", "antigravity", "mcp_config.json");
294
+ const configDir = join(homedir(), ".gemini", "antigravity");
295
+ // Check if we're in a dotrequirements project directory
296
+ const envLocalPath = join(process.cwd(), ".env.local");
297
+ let projectId = null;
298
+ let projectPath = null;
299
+ if (existsSync(envLocalPath)) {
300
+ try {
301
+ const envContent = readFileSync(envLocalPath, "utf-8");
302
+ const lines = envContent.split("\n");
303
+ for (const line of lines) {
304
+ const trimmed = line.trim();
305
+ if (trimmed.startsWith("DOTREQUIREMENTS_PROJECT_ID=")) {
306
+ projectId = trimmed.split("=")[1]?.trim().replace(/['"]/g, "");
307
+ projectPath = process.cwd();
308
+ break;
309
+ }
310
+ }
311
+ }
312
+ catch {
313
+ // .env.local exists but couldn't read it - continue with global setup
314
+ }
315
+ }
316
+ try {
317
+ if (!existsSync(configDir)) {
318
+ console.log("Creating Antigravity config directory...");
319
+ mkdirSync(configDir, { recursive: true });
320
+ }
321
+ let config = {};
322
+ if (existsSync(configPath)) {
323
+ try {
324
+ const existingContent = readFileSync(configPath, "utf-8");
325
+ if (existingContent.trim()) {
326
+ config = JSON.parse(existingContent);
327
+ }
328
+ }
329
+ catch {
330
+ console.log("⚠️ Existing config file is invalid, creating fresh config...");
331
+ config = {};
332
+ }
333
+ }
334
+ if (!config.mcpServers) {
335
+ config.mcpServers = {};
336
+ }
337
+ if (!config.mcpServers.dotrequirements) {
338
+ config.mcpServers.dotrequirements = {
339
+ command: "npx",
340
+ args: ["-y", "@popoverai/dotrequirements", "mcp"],
341
+ env: {},
342
+ };
343
+ }
344
+ // biome-ignore lint/suspicious/noExplicitAny: `config` is parsed JSON from a user-owned MCP config file; its shape isn't ours to type fully. We only need the `env` bag, which we narrow ad-hoc here.
345
+ const dotreqServer = config.mcpServers.dotrequirements;
346
+ if (!dotreqServer.env) {
347
+ dotreqServer.env = {};
348
+ }
349
+ if (projectId && projectPath) {
350
+ const envKey = `PROJ_${projectId}`;
351
+ dotreqServer.env[envKey] = projectPath;
352
+ console.log(`✅ Added project to MCP config: ${envKey}`);
353
+ console.log(` Path: ${projectPath}\n`);
354
+ }
355
+ else {
356
+ console.log(`ℹ️ Not in a ${brand} project directory (no .env.local with DOTREQUIREMENTS_PROJECT_ID found)`);
357
+ console.log(" Setting up global MCP server only.\n");
358
+ }
359
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
360
+ console.log(`✅ MCP configured successfully!`);
361
+ console.log(` Config file: ${configPath}\n`);
362
+ console.log("⚠️ Important: Reload or restart Google Antigravity for changes to take effect.");
363
+ console.log("\nYou can now ask your AI assistant to help write requirements!");
364
+ if (projectId) {
365
+ console.log(`\n💡 To add more projects, run \`dotreq ai-setup\` from each project directory.\n`);
366
+ }
367
+ else {
368
+ console.log("");
369
+ }
370
+ await installContextFileSection("antigravity");
371
+ }
372
+ catch (error) {
373
+ console.error("❌ Failed to configure MCP automatically.");
374
+ console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
375
+ console.log("\nYou can manually edit the config file at:");
376
+ console.log(` ${configPath}`);
377
+ }
378
+ }
379
+ async function setupCodex() {
380
+ console.log("\n📦 Configuring MCP for OpenAI Codex...\n");
381
+ const command = `codex mcp add dotrequirements -- npx -y @popoverai/dotrequirements mcp`;
382
+ try {
383
+ execSync(command, { stdio: "inherit" });
384
+ console.log("\n✅ MCP configured successfully for Codex!");
385
+ console.log("\n⚠️ Important: Restart Codex for changes to take effect.");
386
+ console.log("\nYou can now ask your AI assistant to help write requirements!\n");
387
+ await installContextFileSection("codex");
388
+ }
389
+ catch {
390
+ console.error("\n❌ Failed to configure MCP automatically.");
391
+ console.log("\nYou can manually run:");
392
+ console.log(` ${command}`);
393
+ console.log("\nOr manually edit ~/.codex/config.toml and add:");
394
+ console.log("[mcp_servers.dotrequirements]");
395
+ console.log('command = "npx"');
396
+ console.log('args = ["-y", "@popoverai/dotrequirements", "mcp"]');
397
+ }
398
+ }
399
+ async function setupGitHubCopilot() {
400
+ console.log("\n📦 Configuring skills for GitHub Copilot...\n");
401
+ console.log("ℹ️ GitHub Copilot discovers MCP servers from your IDE configuration.");
402
+ console.log(" Please configure the MCP server in VS Code settings or GitHub settings.\n");
403
+ console.log(" MCP server command: npx -y @popoverai/dotrequirements mcp\n");
404
+ await installContextFileSection("github-copilot");
405
+ }
406
+ async function setupOther() {
407
+ console.log("\n📦 Custom Context File Setup\n");
408
+ console.log("ℹ️ Workflow guidance is added to a context file at your project root.");
409
+ console.log(" Common file names by platform:");
410
+ console.log(" - CLAUDE.md (Claude Code)");
411
+ console.log(" - AGENTS.md (Cursor, Codex, GitHub Copilot)");
412
+ console.log(" - GEMINI.md (Antigravity)\n");
413
+ const response = await prompts({
414
+ type: "select",
415
+ name: "file",
416
+ message: "Which context file should we update?",
417
+ choices: [
418
+ { title: "AGENTS.md (most compatible)", value: "AGENTS.md" },
419
+ { title: "CLAUDE.md", value: "CLAUDE.md" },
420
+ { title: "GEMINI.md", value: "GEMINI.md" },
421
+ ],
422
+ initial: 0,
423
+ });
424
+ if (!response.file) {
425
+ console.log("\nSetup cancelled.");
426
+ return;
427
+ }
428
+ console.log("\n📝 MCP Server Setup\n");
429
+ console.log("Configure your AI assistant to run the MCP server:");
430
+ console.log(" Command: npx");
431
+ console.log(" Args: -y @popoverai/dotrequirements mcp\n");
432
+ // Determine platform based on file choice for the install function
433
+ const platformMap = {
434
+ "CLAUDE.md": "claude-code",
435
+ "AGENTS.md": "cursor", // Any AGENTS.md platform works
436
+ "GEMINI.md": "antigravity",
437
+ };
438
+ const platform = platformMap[response.file];
439
+ await installContextFileSection(platform);
440
+ }
441
+ //# sourceMappingURL=ai-setup.js.map
@@ -1,30 +1,30 @@
1
- import { execFile } from 'child_process';
2
- import { promisify } from 'util';
3
- import { getProjectInfo, readProjectSettings, } from '../utils/project-settings.js';
4
- import { loadAllRequirements, getRequirementTree, formatRequirement, } from '../mcp/requirements.js';
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { formatRequirement, getRequirementTree, loadAllRequirements, } from "../mcp/requirements.js";
4
+ import { getProjectInfo, readProjectSettings, } from "../utils/project-settings.js";
5
5
  const execFileAsync = promisify(execFile);
6
6
  function extractResults(parsed) {
7
7
  if (Array.isArray(parsed))
8
8
  return parsed;
9
- if (parsed && typeof parsed === 'object') {
9
+ if (parsed && typeof parsed === "object") {
10
10
  const obj = parsed;
11
11
  // { results: [...] } format
12
12
  if (Array.isArray(obj.results))
13
13
  return obj.results;
14
14
  // { structured_output: { results: [...] } } format
15
- if (obj.structured_output && typeof obj.structured_output === 'object') {
15
+ if (obj.structured_output && typeof obj.structured_output === "object") {
16
16
  const so = obj.structured_output;
17
17
  if (Array.isArray(so.results))
18
18
  return so.results;
19
19
  }
20
20
  }
21
- throw new Error('Unexpected browser-automation output format');
21
+ throw new Error("Unexpected browser-automation output format");
22
22
  }
23
23
  export async function browserTestCommand(requirementKey, url, options) {
24
24
  // 1. Find project and load settings
25
25
  const projectInfo = getProjectInfo();
26
26
  if (!projectInfo) {
27
- console.error('Error: No dotrequirements project found.');
27
+ console.error("Error: No dotrequirements project found.");
28
28
  console.log('Run "dotrequirements init" to create one.');
29
29
  process.exit(1);
30
30
  }
@@ -40,7 +40,7 @@ export async function browserTestCommand(requirementKey, url, options) {
40
40
  // 2. Determine URL
41
41
  const targetURL = url || settings?.defaultURL;
42
42
  if (!targetURL) {
43
- console.error('Error: No URL provided and no defaultURL in project-settings.json.');
43
+ console.error("Error: No URL provided and no defaultURL in project-settings.json.");
44
44
  console.log('Either provide a URL argument or add "defaultURL" to .requirements/project-settings.json');
45
45
  process.exit(1);
46
46
  }
@@ -49,23 +49,23 @@ export async function browserTestCommand(requirementKey, url, options) {
49
49
  const modelApiKey = settings?.browserTest?.modelApiKey;
50
50
  const geminiApiKey = settings?.browserTest?.geminiApiKey;
51
51
  if (!modelName && !modelApiKey && !geminiApiKey) {
52
- console.error('Error: no browser-test model configured in project-settings.json.');
53
- console.log('Add a block like:\n' +
52
+ console.error("Error: no browser-test model configured in project-settings.json.");
53
+ console.log("Add a block like:\n" +
54
54
  ' "browserTest": {\n' +
55
55
  ' "modelName": "gateway/anthropic/claude-haiku-4-5",\n' +
56
56
  ' "modelApiKey": "..."\n' +
57
- ' }\n' +
58
- 'to .requirements/project-settings.json. See the docs for supported models.');
57
+ " }\n" +
58
+ "to .requirements/project-settings.json. See the docs for supported models.");
59
59
  process.exit(1);
60
60
  }
61
61
  if ((modelName && !modelApiKey) || (modelApiKey && !modelName)) {
62
- console.error('Error: browserTest.modelName and browserTest.modelApiKey must be set together.');
62
+ console.error("Error: browserTest.modelName and browserTest.modelApiKey must be set together.");
63
63
  process.exit(1);
64
64
  }
65
65
  // 4. Load requirements
66
66
  const { flattened } = await loadAllRequirements(projectInfo.rootPath);
67
67
  if (flattened.length === 0) {
68
- console.error('Error: No requirements found in .requirements/');
68
+ console.error("Error: No requirements found in .requirements/");
69
69
  process.exit(1);
70
70
  }
71
71
  // 5. Find the requirement tree
@@ -86,7 +86,8 @@ export async function browserTestCommand(requirementKey, url, options) {
86
86
  env.GEMINI_API_KEY = geminiApiKey;
87
87
  }
88
88
  if (settings?.browserTest?.vercelBypassSecret) {
89
- env.VERCEL_AUTOMATION_BYPASS_SECRET = settings.browserTest.vercelBypassSecret;
89
+ env.VERCEL_AUTOMATION_BYPASS_SECRET =
90
+ settings.browserTest.vercelBypassSecret;
90
91
  }
91
92
  if (settings?.browserTest?.browserbaseApiKey) {
92
93
  env.BROWSERBASE_API_KEY = settings.browserTest.browserbaseApiKey;
@@ -99,8 +100,14 @@ export async function browserTestCommand(requirementKey, url, options) {
99
100
  console.log(`Testing ${requirementKey} against ${targetURL}...\n`);
100
101
  }
101
102
  try {
102
- const modelFlags = modelName ? ['--modelName', modelName] : [];
103
- const { stdout } = await execFileAsync('npx', ['@popoverai/browser-automation', 'test', ...modelFlags, targetURL, ...assertions], { env, maxBuffer: 10 * 1024 * 1024 });
103
+ const modelFlags = modelName ? ["--modelName", modelName] : [];
104
+ const { stdout } = await execFileAsync("npx", [
105
+ "@popoverai/browser-automation",
106
+ "test",
107
+ ...modelFlags,
108
+ targetURL,
109
+ ...assertions,
110
+ ], { env, maxBuffer: 10 * 1024 * 1024 });
104
111
  // 9. Parse results
105
112
  let results;
106
113
  try {
@@ -108,7 +115,7 @@ export async function browserTestCommand(requirementKey, url, options) {
108
115
  results = extractResults(parsed);
109
116
  }
110
117
  catch (parseErr) {
111
- console.error('Error: Could not parse browser-automation output');
118
+ console.error("Error: Could not parse browser-automation output");
112
119
  console.error(parseErr.message);
113
120
  console.error(stdout);
114
121
  process.exit(1);
@@ -121,7 +128,7 @@ export async function browserTestCommand(requirementKey, url, options) {
121
128
  displayResults(requirementTree, results);
122
129
  }
123
130
  // 11. Exit with appropriate code
124
- const failedCount = results.filter((r) => r.status !== 'passed').length;
131
+ const failedCount = results.filter((r) => r.status !== "passed").length;
125
132
  if (failedCount > 0) {
126
133
  process.exit(1);
127
134
  }
@@ -143,7 +150,7 @@ export async function browserTestCommand(requirementKey, url, options) {
143
150
  }
144
151
  catch {
145
152
  // Not JSON, show raw output
146
- console.error('browser-automation error:');
153
+ console.error("browser-automation error:");
147
154
  if (error.stdout)
148
155
  console.error(error.stdout);
149
156
  if (error.stderr)
@@ -170,14 +177,14 @@ function displayResults(requirements, results) {
170
177
  failedCount++;
171
178
  continue;
172
179
  }
173
- const indent = ' '.repeat(req.path.length);
174
- const label = req.label ? ` (${req.label})` : '';
175
- const icon = result.status === 'passed' ? '\u2713' : '\u2717';
180
+ const indent = " ".repeat(req.path.length);
181
+ const label = req.label ? ` (${req.label})` : "";
182
+ const icon = result.status === "passed" ? "\u2713" : "\u2717";
176
183
  console.log(`${indent}${icon} ${req.id}${label}: ${req.content}`);
177
- if (result.status !== 'passed' && result.notes) {
184
+ if (result.status !== "passed" && result.notes) {
178
185
  console.log(`${indent} \u2192 ${result.notes}`);
179
186
  }
180
- if (result.status === 'passed') {
187
+ if (result.status === "passed") {
181
188
  passedCount++;
182
189
  }
183
190
  else {
@@ -197,7 +204,7 @@ function outputJson(requirements, results) {
197
204
  id: req.id,
198
205
  label: req.label || null,
199
206
  content: req.content,
200
- status: results[i]?.status || 'blocked',
207
+ status: results[i]?.status || "blocked",
201
208
  notes: results[i]?.notes || null,
202
209
  }));
203
210
  console.log(JSON.stringify(combined, null, 2));
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `dotrequirements codebase-to-spec compose` subcommand.
3
+ *
4
+ * Assembles per-area partials into a single composed Markdown document.
5
+ *
6
+ * Requirements covered:
7
+ * - CTS-CLI-2: granular `compose` subcommand
8
+ * - CTS-COMPOSE-1, CTS-COMPOSE-2
9
+ */
10
+ export interface ComposeCmdOptions {
11
+ projectRoot?: string;
12
+ }
13
+ export declare function composeCommand(options?: ComposeCmdOptions): Promise<void>;
14
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `dotrequirements codebase-to-spec compose` subcommand.
3
+ *
4
+ * Assembles per-area partials into a single composed Markdown document.
5
+ *
6
+ * Requirements covered:
7
+ * - CTS-CLI-2: granular `compose` subcommand
8
+ * - CTS-COMPOSE-1, CTS-COMPOSE-2
9
+ */
10
+ import { existsSync } from "node:fs";
11
+ import { cachePaths } from "../../codebase-to-spec/cache.js";
12
+ import { runCompose } from "../../codebase-to-spec/compose.js";
13
+ import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
14
+ import { StdoutProgress } from "../../codebase-to-spec/progress.js";
15
+ import { findProjectRoot } from "../../utils/project-settings.js";
16
+ import { readFinalOutline } from "./plan-loop.js";
17
+ export async function composeCommand(options = {}) {
18
+ const progress = new StdoutProgress();
19
+ const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
20
+ const paths = cachePaths(projectRoot);
21
+ if (!existsSync(paths.outlineFinal)) {
22
+ process.stderr.write(`Missing outline at ${paths.outlineFinal}. Run \`dotrequirements cts plan-loop\` first.\n`);
23
+ process.exitCode = ExitCode.MissingInput;
24
+ return;
25
+ }
26
+ const outline = readFinalOutline(projectRoot);
27
+ progress.emit({
28
+ stage: "compose",
29
+ step: "start",
30
+ message: `Composing spec from ${outline.areas.length} area partials`,
31
+ });
32
+ const result = runCompose({
33
+ outline,
34
+ partialPathFor: (sanitized) => paths.partial(sanitized),
35
+ composedPath: paths.composedSpec,
36
+ });
37
+ progress.emit({
38
+ stage: "compose",
39
+ step: "done",
40
+ message: `Composed spec → ${result.composedPath} (${result.partialsIncluded} partials included, ${result.partialsMissing} missing)`,
41
+ data: {
42
+ partialsIncluded: result.partialsIncluded,
43
+ partialsMissing: result.partialsMissing,
44
+ },
45
+ });
46
+ if (result.validationError) {
47
+ process.stderr.write(`Composed spec failed validation:\n${result.validationError}\n`);
48
+ process.exitCode = ExitCode.StageFailed;
49
+ return;
50
+ }
51
+ progress.emit({
52
+ stage: "compose",
53
+ step: "validation-ok",
54
+ message: "Composed spec passed schema validation",
55
+ });
56
+ }
57
+ //# sourceMappingURL=compose.js.map