@popoverai/dotrequirements 0.26.1 → 0.27.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 (84) hide show
  1. package/README.md +13 -69
  2. package/dist/cli.js +19 -7
  3. package/dist/codebase-to-spec/present.js +4 -5
  4. package/dist/codebase-to-spec/validate.js +3 -2
  5. package/dist/commands/acceptance-test.js +4 -2
  6. package/dist/commands/ai-setup.d.ts +8 -2
  7. package/dist/commands/ai-setup.js +154 -310
  8. package/dist/commands/get.js +6 -2
  9. package/dist/commands/init.js +8 -6
  10. package/dist/commands/link-resolution.d.ts +3 -1
  11. package/dist/commands/link-resolution.js +4 -2
  12. package/dist/commands/mcp.d.ts +8 -2
  13. package/dist/commands/mcp.js +17 -6
  14. package/dist/commands/pull.js +36 -3
  15. package/dist/commands/push.js +54 -16
  16. package/dist/commands/report.js +18 -3
  17. package/dist/commands/review-test.d.ts +5 -1
  18. package/dist/commands/review-test.js +117 -15
  19. package/dist/commands/style-check.d.ts +1 -0
  20. package/dist/commands/style-check.js +137 -13
  21. package/dist/commands/tests-for.js +13 -13
  22. package/dist/commands/validate.js +14 -14
  23. package/dist/convex.d.ts +1 -3
  24. package/dist/convex.js +3 -3
  25. package/dist/harness/cache.d.ts +19 -3
  26. package/dist/harness/cache.js +38 -12
  27. package/dist/harness/finalize.js +33 -1
  28. package/dist/harness/index.js +16 -9
  29. package/dist/harness/requirementsLoader.js +12 -0
  30. package/dist/harness/tracking.d.ts +17 -2
  31. package/dist/harness/tracking.js +83 -9
  32. package/dist/push/core.d.ts +50 -0
  33. package/dist/push/core.js +149 -11
  34. package/dist/push/index.d.ts +1 -1
  35. package/dist/push/index.js +1 -1
  36. package/dist/requirements/cloud-ai.d.ts +21 -8
  37. package/dist/requirements/cloud-ai.js +10 -8
  38. package/dist/requirements/cloud-coverage.d.ts +12 -2
  39. package/dist/requirements/cloud-coverage.js +30 -3
  40. package/dist/requirements/grep.d.ts +7 -2
  41. package/dist/requirements/grep.js +75 -47
  42. package/dist/schema/builder.d.ts +1 -1
  43. package/dist/schema/builder.js +13 -0
  44. package/dist/schema/conversions.d.ts +7 -2
  45. package/dist/schema/conversions.js +13 -4
  46. package/dist/schema/parser-core.d.ts +41 -0
  47. package/dist/schema/parser-core.js +113 -18
  48. package/dist/schema/parser.d.ts +8 -26
  49. package/dist/schema/parser.js +23 -251
  50. package/dist/schema/resolver.js +18 -8
  51. package/dist/templates/context-file-section.md +25 -22
  52. package/dist/utils/context-file.d.ts +7 -3
  53. package/dist/utils/context-file.js +10 -7
  54. package/dist/utils/env.js +17 -1
  55. package/dist/utils/oauth-flow.js +8 -0
  56. package/dist/utils/project-settings.d.ts +5 -0
  57. package/dist/utils/project-settings.js +36 -1
  58. package/package.json +3 -5
  59. package/dist/mcp/convexClient.d.ts +0 -19
  60. package/dist/mcp/convexClient.js +0 -24
  61. package/dist/mcp/handlers/authoring.d.ts +0 -41
  62. package/dist/mcp/handlers/authoring.js +0 -104
  63. package/dist/mcp/handlers/debug.d.ts +0 -16
  64. package/dist/mcp/handlers/debug.js +0 -37
  65. package/dist/mcp/handlers/get.d.ts +0 -24
  66. package/dist/mcp/handlers/get.js +0 -65
  67. package/dist/mcp/handlers/index.d.ts +0 -28
  68. package/dist/mcp/handlers/index.js +0 -19
  69. package/dist/mcp/handlers/list.d.ts +0 -7
  70. package/dist/mcp/handlers/list.js +0 -43
  71. package/dist/mcp/handlers/push.d.ts +0 -26
  72. package/dist/mcp/handlers/push.js +0 -186
  73. package/dist/mcp/handlers/report.d.ts +0 -16
  74. package/dist/mcp/handlers/report.js +0 -134
  75. package/dist/mcp/handlers/review.d.ts +0 -51
  76. package/dist/mcp/handlers/review.js +0 -200
  77. package/dist/mcp/handlers/search.d.ts +0 -30
  78. package/dist/mcp/handlers/search.js +0 -58
  79. package/dist/mcp/handlers/test-mapping.d.ts +0 -39
  80. package/dist/mcp/handlers/test-mapping.js +0 -133
  81. package/dist/mcp/handlers/types.d.ts +0 -75
  82. package/dist/mcp/handlers/types.js +0 -25
  83. package/dist/mcp/index.d.ts +0 -45
  84. package/dist/mcp/index.js +0 -634
@@ -1,5 +1,5 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import prompts from "prompts";
@@ -14,8 +14,8 @@ function loadContextFileSection() {
14
14
  }
15
15
  /**
16
16
  * Install workflow guidance to the appropriate context file for a platform.
17
- * With `yes` (the non-interactive --assistant path, AISETUP-1.0), both
18
- * confirmations are auto-accepted — matching their interactive defaults.
17
+ * With `yes` (the non-interactive --assistant path, AISETUP-1.0), the
18
+ * confirmation is auto-accepted — matching its interactive default.
19
19
  */
20
20
  async function installContextFileSection(platform, yes = false) {
21
21
  const fileName = getContextFileName(platform);
@@ -82,28 +82,164 @@ async function installContextFileSection(platform, yes = false) {
82
82
  catch (error) {
83
83
  console.error(`❌ Failed to update ${fileName}`);
84
84
  console.log(` Error: ${error instanceof Error ? error.message : String(error)}\n`);
85
+ // AISETUP-3: writing the guidance is the whole of setup — a CI/agent
86
+ // caller must not see a failed write as success.
87
+ process.exitCode = 1;
88
+ }
89
+ }
90
+ /**
91
+ * Remove the dotrequirements entry from a JSON MCP config file an earlier
92
+ * ai-setup version wrote (Cursor's `.cursor/mcp.json`, Antigravity's
93
+ * `mcp_config.json`). Only our entry is deleted; every other configured
94
+ * server is preserved (AISETUP-2.2). Unusable files are skipped with their
95
+ * path shown (AISETUP-2.4); an absent file or a config with no dotrequirements
96
+ * entry is a silent no-op (AISETUP-2.3).
97
+ */
98
+ async function cleanupJsonMcpConfig(configPath, yes) {
99
+ if (!existsSync(configPath)) {
100
+ return; // AISETUP-2.3: nothing an earlier version left here
101
+ }
102
+ let raw;
103
+ try {
104
+ raw = readFileSync(configPath, "utf-8");
105
+ }
106
+ catch {
107
+ // AISETUP-2.4: unreadable (e.g. a directory) — skip, but keep going
108
+ console.log(`ℹ️ Skipped MCP cleanup: could not read ${configPath}`);
109
+ return;
110
+ }
111
+ if (!raw.trim()) {
112
+ return;
113
+ }
114
+ let parsed;
115
+ try {
116
+ parsed = JSON.parse(raw);
117
+ }
118
+ catch {
119
+ // AISETUP-2.4: invalid JSON — we won't guess at its structure
120
+ console.log(`ℹ️ Skipped MCP cleanup: invalid JSON in ${configPath}`);
121
+ return;
122
+ }
123
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
124
+ // AISETUP-2.4: a non-object root
125
+ console.log(`ℹ️ Skipped MCP cleanup: ${configPath} is not a JSON object`);
126
+ return;
127
+ }
128
+ const mcpServers = parsed.mcpServers;
129
+ if (mcpServers == null ||
130
+ typeof mcpServers !== "object" ||
131
+ Array.isArray(mcpServers)) {
132
+ return; // no usable server map → nothing of ours to remove
133
+ }
134
+ const servers = mcpServers;
135
+ if (!("dotrequirements" in servers)) {
136
+ return; // AISETUP-2.3: our entry isn't here
137
+ }
138
+ if (!yes) {
139
+ const { remove } = await prompts({
140
+ type: "confirm",
141
+ name: "remove",
142
+ message: `Remove the retired dotrequirements MCP server from ${configPath}?`,
143
+ initial: true,
144
+ });
145
+ if (!remove) {
146
+ return;
147
+ }
148
+ }
149
+ delete servers.dotrequirements;
150
+ writeFileSync(configPath, JSON.stringify(parsed, null, 2), "utf-8");
151
+ console.log(`✅ Removed the retired dotrequirements MCP server from ${configPath}`);
152
+ }
153
+ /**
154
+ * Remove the dotrequirements MCP server from a host that manages its own MCP
155
+ * config through a CLI (Claude Code, Codex). Detection via `<bin> mcp get`;
156
+ * removal via `<bin> mcp remove`. A missing entry — or a host CLI that isn't
157
+ * installed — is a silent no-op; the host CLI guarantees only our entry is
158
+ * touched (AISETUP-2.2).
159
+ */
160
+ async function cleanupHostCliMcp(bin, yes) {
161
+ let present = false;
162
+ try {
163
+ execFileSync(bin, ["mcp", "get", "dotrequirements"], { stdio: "pipe" });
164
+ present = true;
165
+ }
166
+ catch {
167
+ // Exit non-zero → not configured, or the host CLI isn't on PATH. Either
168
+ // way there is nothing for us to clean up (AISETUP-2.3).
169
+ return;
170
+ }
171
+ if (!present) {
172
+ return;
173
+ }
174
+ if (!yes) {
175
+ const { remove } = await prompts({
176
+ type: "confirm",
177
+ name: "remove",
178
+ message: `Remove the retired dotrequirements MCP server from ${bin}?`,
179
+ initial: true,
180
+ });
181
+ if (!remove) {
182
+ return;
183
+ }
184
+ }
185
+ try {
186
+ execFileSync(bin, ["mcp", "remove", "dotrequirements"], { stdio: "pipe" });
187
+ console.log(`✅ Removed the retired dotrequirements MCP server from ${bin}`);
188
+ }
189
+ catch {
190
+ console.log(`⚠️ Could not remove the dotrequirements MCP server automatically.\n` +
191
+ ` Remove it manually: ${bin} mcp remove dotrequirements`);
192
+ }
193
+ }
194
+ /**
195
+ * Clean up any MCP configuration an earlier ai-setup version wrote for this
196
+ * assistant. The local MCP server is retired (AISETUP-2.0: setup writes no MCP
197
+ * config); this leaves the entries prior versions created ready to remove.
198
+ */
199
+ async function cleanupStaleMcpConfig(platform, yes) {
200
+ switch (platform) {
201
+ case "claude-code":
202
+ await cleanupHostCliMcp("claude", yes);
203
+ return;
204
+ case "codex":
205
+ await cleanupHostCliMcp("codex", yes);
206
+ return;
207
+ case "cursor": {
208
+ const root = (await findGitRoot()) ?? process.cwd();
209
+ await cleanupJsonMcpConfig(join(root, ".cursor", "mcp.json"), yes);
210
+ return;
211
+ }
212
+ case "antigravity":
213
+ await cleanupJsonMcpConfig(join(homedir(), ".gemini", "antigravity", "mcp_config.json"), yes);
214
+ return;
215
+ // github-copilot never had ai-setup write MCP config — nothing to clean.
85
216
  }
86
217
  }
87
218
  /**
88
219
  * Assistant identifiers accepted by the non-interactive --assistant flag
89
220
  * (AISETUP-1). "other" is interactive-only: it prompts for a custom path.
221
+ *
222
+ * Claude Desktop was dropped: it consumed only the retired local MCP server
223
+ * (no terminal, no context file), so setup has nothing to install for it. Its
224
+ * path forward is the remote MCP connector.
90
225
  */
91
226
  export const SUPPORTED_ASSISTANTS = [
92
227
  "claude-code",
93
- "claude-desktop",
94
228
  "cursor",
95
229
  "antigravity",
96
230
  "codex",
97
231
  "github-copilot",
98
232
  ];
99
233
  /**
100
- * AI setup command - configures the MCP server for AI assistants
234
+ * AI setup command — installs the requirements-driven workflow guidance into
235
+ * the assistant's context file, and removes any MCP configuration an earlier
236
+ * version wrote (the local MCP server is retired).
101
237
  *
102
238
  * AISETUP-1: with --assistant <id>, setup runs without any interactive prompt
103
239
  * (an AI assistant configuring itself knows which assistant it is).
104
240
  */
105
241
  export async function aiSetupCommand(options = {}) {
106
- console.log(`🔧 Setting up ${brand} MCP server\n`);
242
+ console.log(`🔧 Setting up ${brand} workflow guidance\n`);
107
243
  if (options.assistant) {
108
244
  const id = options.assistant.toLowerCase();
109
245
  if (!SUPPORTED_ASSISTANTS.includes(id)) {
@@ -121,7 +257,6 @@ export async function aiSetupCommand(options = {}) {
121
257
  message: "Which AI assistant are you using?",
122
258
  choices: [
123
259
  { title: "Claude Code", value: "claude-code" },
124
- { title: "Claude Desktop", value: "claude-desktop" },
125
260
  { title: "Cursor", value: "cursor" },
126
261
  { title: "Google Antigravity", value: "antigravity" },
127
262
  { title: "OpenAI Codex", value: "codex" },
@@ -137,311 +272,26 @@ export async function aiSetupCommand(options = {}) {
137
272
  await dispatchSetup(response.client);
138
273
  }
139
274
  async function dispatchSetup(client, yes = false) {
140
- switch (client) {
141
- case "claude-code":
142
- await setupClaudeCode(yes);
143
- break;
144
- case "claude-desktop":
145
- await setupClaudeDesktop();
146
- break;
147
- case "cursor":
148
- await setupCursor(yes);
149
- break;
150
- case "antigravity":
151
- await setupAntigravity(yes);
152
- break;
153
- case "codex":
154
- await setupCodex(yes);
155
- break;
156
- case "github-copilot":
157
- await setupGitHubCopilot(yes);
158
- break;
159
- case "other":
160
- await setupOther();
161
- break;
162
- default:
163
- console.log(`\n❌ ${client} is not yet supported.`);
164
- console.log("Please refer to the documentation for manual setup.");
165
- }
166
- }
167
- async function setupClaudeCode(yes = false) {
168
- console.log("\n📦 Configuring MCP for Claude Code...\n");
169
- const command = `claude mcp add-json dotrequirements '{"type":"stdio","command":"npx","args":["-y","@popoverai/dotrequirements","mcp"]}'`;
170
- let mcpConfigured = false;
171
- try {
172
- execSync(command, { stdio: "pipe" });
173
- console.log("✅ MCP configured successfully for Claude Code!");
174
- mcpConfigured = true;
175
- }
176
- catch (error) {
177
- const execError = error;
178
- const stderr = execError.stderr?.toString() || "";
179
- const errorMessage = execError.message || "";
180
- const combinedError = stderr + errorMessage;
181
- if (combinedError.includes("already exists")) {
182
- console.log("✅ MCP server already configured for Claude Code");
183
- mcpConfigured = true;
184
- }
185
- else {
186
- console.error("❌ Failed to configure MCP automatically.");
187
- console.log("\nYou can manually run:");
188
- console.log(` ${command}`);
189
- if (stderr) {
190
- console.log(`\nError: ${stderr}`);
191
- }
192
- }
193
- }
194
- if (mcpConfigured) {
195
- console.log("\n⚠️ Important: Restart Claude Code for changes to take effect.");
196
- console.log("\nYou can now ask your AI assistant to help write requirements!\n");
197
- await installContextFileSection("claude-code", yes);
198
- }
199
- }
200
- async function setupClaudeDesktop() {
201
- console.log("\n📦 Configuring MCP for Claude Desktop...\n");
202
- // Determine config path based on OS
203
- const platform = process.platform;
204
- let configPath;
205
- let configDir;
206
- if (platform === "darwin") {
207
- configDir = join(homedir(), "Library", "Application Support", "Claude");
208
- configPath = join(configDir, "claude_desktop_config.json");
209
- }
210
- else if (platform === "win32") {
211
- const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
212
- configDir = join(appData, "Claude");
213
- configPath = join(configDir, "claude_desktop_config.json");
214
- }
215
- else {
216
- console.error("❌ Claude Desktop is only supported on macOS and Windows.");
217
- console.log("\nFor Linux, please manually configure the MCP server.");
275
+ if (client === "other") {
276
+ await setupOther();
218
277
  return;
219
278
  }
220
- const mcpServerConfig = {
221
- command: "npx",
222
- args: ["-y", "@popoverai/dotrequirements", "mcp"],
223
- env: {
224
- REQUIREMENTS_DIR: process.cwd(),
225
- },
226
- };
227
- try {
228
- if (!existsSync(configDir)) {
229
- console.log("Creating Claude Desktop config directory...");
230
- mkdirSync(configDir, { recursive: true });
231
- }
232
- let config = {};
233
- if (existsSync(configPath)) {
234
- try {
235
- const existingContent = readFileSync(configPath, "utf-8");
236
- if (existingContent.trim()) {
237
- config = JSON.parse(existingContent);
238
- }
239
- }
240
- catch {
241
- console.log("⚠️ Existing config file is invalid, creating fresh config...");
242
- config = {};
243
- }
244
- }
245
- if (!config.mcpServers) {
246
- config.mcpServers = {};
247
- }
248
- config.mcpServers.dotrequirements = mcpServerConfig;
249
- writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
250
- console.log(`✅ MCP configured successfully!`);
251
- console.log(` Config file: ${configPath}`);
252
- console.log(` REQUIREMENTS_DIR: ${process.cwd()}\n`);
253
- console.log("⚠️ Important: Quit and restart Claude Desktop for changes to take effect.");
254
- console.log(" (Cmd+Q on macOS, not just close the window)\n");
255
- console.log("You can now ask Claude Desktop to help write requirements!\n");
256
- // Claude Desktop doesn't use skills like Claude Code, so we skip that step
257
- }
258
- catch (error) {
259
- console.error("❌ Failed to configure MCP automatically.");
260
- console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
261
- console.log("\nYou can manually edit the config file at:");
262
- console.log(` ${configPath}\n`);
263
- console.log("Add the following configuration:");
264
- const manualConfig = {
265
- mcpServers: {
266
- dotrequirements: mcpServerConfig,
267
- },
268
- };
269
- console.log(JSON.stringify(manualConfig, null, 2));
270
- }
271
- }
272
- async function setupCursor(yes = false) {
273
- console.log("\n📦 Configuring MCP for Cursor (project-specific)...\n");
274
- const configPath = join(process.cwd(), ".cursor", "mcp.json");
275
- const configDir = join(process.cwd(), ".cursor");
276
- const mcpServerConfig = {
277
- command: "npx",
278
- args: ["-y", "@popoverai/dotrequirements", "mcp"],
279
- };
280
- try {
281
- if (!existsSync(configDir)) {
282
- console.log("Creating Cursor config directory...");
283
- mkdirSync(configDir, { recursive: true });
284
- }
285
- let config = {};
286
- if (existsSync(configPath)) {
287
- try {
288
- const existingContent = readFileSync(configPath, "utf-8");
289
- if (existingContent.trim()) {
290
- config = JSON.parse(existingContent);
291
- }
292
- }
293
- catch {
294
- console.log("⚠️ Existing config file is invalid, creating fresh config...");
295
- config = {};
296
- }
297
- }
298
- if (!config.mcpServers) {
299
- config.mcpServers = {};
300
- }
301
- config.mcpServers.dotrequirements = mcpServerConfig;
302
- writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
303
- console.log(`✅ MCP configured successfully!`);
304
- console.log(` Config file: ${configPath}\n`);
305
- console.log("⚠️ Important: Reload or restart Cursor for changes to take effect.");
306
- console.log("\nYou can now ask your AI assistant to help write requirements!\n");
307
- await installContextFileSection("cursor", yes);
308
- }
309
- catch (error) {
310
- console.error("❌ Failed to configure MCP automatically.");
311
- console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
312
- console.log("\nYou can manually edit the config file at:");
313
- console.log(` ${configPath}\n`);
314
- console.log("Add the following configuration:");
315
- const manualConfig = {
316
- mcpServers: {
317
- dotrequirements: mcpServerConfig,
318
- },
319
- };
320
- console.log(JSON.stringify(manualConfig, null, 2));
321
- }
322
- }
323
- async function setupAntigravity(yes = false) {
324
- console.log("\n📦 Configuring MCP for Google Antigravity...\n");
325
- const configPath = join(homedir(), ".gemini", "antigravity", "mcp_config.json");
326
- const configDir = join(homedir(), ".gemini", "antigravity");
327
- // Check if we're in a dotrequirements project directory
328
- const envLocalPath = join(process.cwd(), ".env.local");
329
- let projectId = null;
330
- let projectPath = null;
331
- if (existsSync(envLocalPath)) {
332
- try {
333
- const envContent = readFileSync(envLocalPath, "utf-8");
334
- const lines = envContent.split("\n");
335
- for (const line of lines) {
336
- const trimmed = line.trim();
337
- if (trimmed.startsWith("DOTREQUIREMENTS_PROJECT_ID=")) {
338
- projectId = trimmed.split("=")[1]?.trim().replace(/['"]/g, "");
339
- projectPath = process.cwd();
340
- break;
341
- }
342
- }
343
- }
344
- catch {
345
- // .env.local exists but couldn't read it - continue with global setup
346
- }
347
- }
348
- try {
349
- if (!existsSync(configDir)) {
350
- console.log("Creating Antigravity config directory...");
351
- mkdirSync(configDir, { recursive: true });
352
- }
353
- let config = {};
354
- if (existsSync(configPath)) {
355
- try {
356
- const existingContent = readFileSync(configPath, "utf-8");
357
- if (existingContent.trim()) {
358
- config = JSON.parse(existingContent);
359
- }
360
- }
361
- catch {
362
- console.log("⚠️ Existing config file is invalid, creating fresh config...");
363
- config = {};
364
- }
365
- }
366
- if (!config.mcpServers) {
367
- config.mcpServers = {};
368
- }
369
- if (!config.mcpServers.dotrequirements) {
370
- config.mcpServers.dotrequirements = {
371
- command: "npx",
372
- args: ["-y", "@popoverai/dotrequirements", "mcp"],
373
- env: {},
374
- };
375
- }
376
- // 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.
377
- const dotreqServer = config.mcpServers.dotrequirements;
378
- if (!dotreqServer.env) {
379
- dotreqServer.env = {};
380
- }
381
- if (projectId && projectPath) {
382
- const envKey = `PROJ_${projectId}`;
383
- dotreqServer.env[envKey] = projectPath;
384
- console.log(`✅ Added project to MCP config: ${envKey}`);
385
- console.log(` Path: ${projectPath}\n`);
386
- }
387
- else {
388
- console.log(`ℹ️ Not in a ${brand} project directory (no .env.local with DOTREQUIREMENTS_PROJECT_ID found)`);
389
- console.log(" Setting up global MCP server only.\n");
390
- }
391
- writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
392
- console.log(`✅ MCP configured successfully!`);
393
- console.log(` Config file: ${configPath}\n`);
394
- console.log("⚠️ Important: Reload or restart Google Antigravity for changes to take effect.");
395
- console.log("\nYou can now ask your AI assistant to help write requirements!");
396
- if (projectId) {
397
- console.log(`\n💡 To add more projects, run \`dotreq ai-setup\` from each project directory.\n`);
398
- }
399
- else {
400
- console.log("");
401
- }
402
- await installContextFileSection("antigravity", yes);
403
- }
404
- catch (error) {
405
- console.error("❌ Failed to configure MCP automatically.");
406
- console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
407
- console.log("\nYou can manually edit the config file at:");
408
- console.log(` ${configPath}`);
409
- }
410
- }
411
- async function setupCodex(yes = false) {
412
- console.log("\n📦 Configuring MCP for OpenAI Codex...\n");
413
- const command = `codex mcp add dotrequirements -- npx -y @popoverai/dotrequirements mcp`;
414
- try {
415
- execSync(command, { stdio: "inherit" });
416
- console.log("\n✅ MCP configured successfully for Codex!");
417
- console.log("\n⚠️ Important: Restart Codex for changes to take effect.");
418
- console.log("\nYou can now ask your AI assistant to help write requirements!\n");
419
- await installContextFileSection("codex", yes);
420
- }
421
- catch {
422
- console.error("\n❌ Failed to configure MCP automatically.");
423
- console.log("\nYou can manually run:");
424
- console.log(` ${command}`);
425
- console.log("\nOr manually edit ~/.codex/config.toml and add:");
426
- console.log("[mcp_servers.dotrequirements]");
427
- console.log('command = "npx"');
428
- console.log('args = ["-y", "@popoverai/dotrequirements", "mcp"]');
279
+ if (!SUPPORTED_ASSISTANTS.includes(client)) {
280
+ console.log(`\n❌ ${client} is not yet supported.`);
281
+ console.log("Please refer to the documentation for manual setup.");
282
+ return;
429
283
  }
430
- }
431
- async function setupGitHubCopilot(yes = false) {
432
- console.log("\n📦 Configuring skills for GitHub Copilot...\n");
433
- console.log("ℹ️ GitHub Copilot discovers MCP servers from your IDE configuration.");
434
- console.log(" Please configure the MCP server in VS Code settings or GitHub settings.\n");
435
- console.log(" MCP server command: npx -y @popoverai/dotrequirements mcp\n");
436
- await installContextFileSection("github-copilot", yes);
284
+ // AISETUP-2: remove any MCP config an earlier version wrote, then install
285
+ // the workflow guidance (the whole of setup now).
286
+ await cleanupStaleMcpConfig(client, yes);
287
+ await installContextFileSection(client, yes);
437
288
  }
438
289
  async function setupOther() {
439
290
  console.log("\n📦 Custom Context File Setup\n");
440
291
  console.log("ℹ️ Workflow guidance is added to a context file at your project root.");
441
292
  console.log(" Common file names by platform:");
442
293
  console.log(" - CLAUDE.md (Claude Code)");
443
- console.log(" - AGENTS.md (Cursor, Codex, GitHub Copilot)");
444
- console.log(" - GEMINI.md (Antigravity)\n");
294
+ console.log(" - AGENTS.md (Cursor, Codex, Antigravity, GitHub Copilot)\n");
445
295
  const response = await prompts({
446
296
  type: "select",
447
297
  name: "file",
@@ -449,7 +299,6 @@ async function setupOther() {
449
299
  choices: [
450
300
  { title: "AGENTS.md (most compatible)", value: "AGENTS.md" },
451
301
  { title: "CLAUDE.md", value: "CLAUDE.md" },
452
- { title: "GEMINI.md", value: "GEMINI.md" },
453
302
  ],
454
303
  initial: 0,
455
304
  });
@@ -457,15 +306,10 @@ async function setupOther() {
457
306
  console.log("\nSetup cancelled.");
458
307
  return;
459
308
  }
460
- console.log("\n📝 MCP Server Setup\n");
461
- console.log("Configure your AI assistant to run the MCP server:");
462
- console.log(" Command: npx");
463
- console.log(" Args: -y @popoverai/dotrequirements mcp\n");
464
309
  // Determine platform based on file choice for the install function
465
310
  const platformMap = {
466
311
  "CLAUDE.md": "claude-code",
467
312
  "AGENTS.md": "cursor", // Any AGENTS.md platform works
468
- "GEMINI.md": "antigravity",
469
313
  };
470
314
  const platform = platformMap[response.file];
471
315
  await installContextFileSection(platform);
@@ -1,10 +1,14 @@
1
1
  import { glob } from "glob";
2
- import { formatRequirementTree, getRequirementTree, loadAllRequirements, } from "../requirements/index.js";
2
+ import { formatRequirementTree, loadAllRequirements, } from "../requirements/index.js";
3
3
  import { findFilesWithRequirement, findTestCodeForRequirement, } from "../requirements/testCodeExtractor.js";
4
4
  export async function getCommand(id) {
5
5
  const workspaceRoot = process.cwd();
6
6
  const { flattened } = await loadAllRequirements(workspaceRoot);
7
- const tree = getRequirementTree(flattened, id);
7
+ // Match the node itself plus all of its descendants by id prefix (mirrors
8
+ // the #46 MCP get fix). The shared getRequirementTree only matches on
9
+ // rootId/id equality, which drops grandchildren (e.g. REQ-123.0.0) when a
10
+ // child id (REQ-123.0) is requested.
11
+ const tree = flattened.filter((r) => r.id === id || r.id.startsWith(`${id}.`));
8
12
  // CLI-FIND-GET-1.2: missing ID → error + non-zero exit
9
13
  if (tree.length === 0) {
10
14
  throw new Error(`Requirement "${id}" not found`);
@@ -147,11 +147,10 @@ async function authenticatedFlow(cwd, options) {
147
147
  }
148
148
  if (projectChoice === "create") {
149
149
  await createNewProjectFlow(cwd, client, selectedTeam, options);
150
+ return true;
150
151
  }
151
- else {
152
- await connectExistingProjectFlow(cwd, client, selectedTeam);
153
- }
154
- return true;
152
+ // INIT-12: a cancelled project selection means init did not complete
153
+ return await connectExistingProjectFlow(cwd, client, selectedTeam);
155
154
  }
156
155
  /**
157
156
  * INIT-18: Prompt user to select a team after OAuth
@@ -268,13 +267,15 @@ async function createNewProjectFlow(cwd, client, team, options) {
268
267
  }
269
268
  /**
270
269
  * INIT-6, INIT-16: Connect to an existing project
270
+ * Returns true if connected successfully, false if the user cancelled
271
+ * (INIT-12: cancellation must not read as a successful init)
271
272
  */
272
273
  async function connectExistingProjectFlow(cwd, client, team) {
273
274
  // INIT-6.0, INIT-16.0: Select project from team
274
275
  const selectedProject = await selectProject(client, team.teamId);
275
276
  if (!selectedProject) {
276
277
  console.log("Initialization cancelled.");
277
- return;
278
+ return false;
278
279
  }
279
280
  // AUTHZ-3: Prompt for secret expiry (paid tier only, if creating new secret)
280
281
  const expiryDays = await promptExpiryDays(team.tier);
@@ -306,6 +307,7 @@ async function connectExistingProjectFlow(cwd, client, team) {
306
307
  console.log("\nNext steps:");
307
308
  console.log(" 1. Review the requirements in .requirements/");
308
309
  console.log(" 2. Add the test harness to your test setup (see README)");
310
+ return true;
309
311
  }
310
312
  /**
311
313
  * MEMBERSHIP-18: Invite flow - join a team using an invite token
@@ -349,7 +351,7 @@ async function inviteFlow(cwd, token) {
349
351
  // MEMBERSHIP-18.1: Accept invite (joins team, marks token used)
350
352
  // MEMBERSHIP-18.2: If already a member, continue gracefully
351
353
  try {
352
- await client.action(api.polar.acceptTeamInvite, { token });
354
+ await client.mutation(api.teamInvites.mutations.acceptInvite, { token });
353
355
  console.log(`✓ Joined team "${inviteInfo.teamName}"`);
354
356
  }
355
357
  catch (err) {
@@ -29,7 +29,9 @@ export interface LinkFlags {
29
29
  json?: boolean;
30
30
  }
31
31
  /**
32
- * LINK-12.3: any deviation flag implies non-interactive mode.
32
+ * LINK-12.3: any deviation flag implies non-interactive mode. The JSON
33
+ * output flag does too — machine-readable output only exists on the
34
+ * non-interactive path, so `link --json` alone must not run interactively.
33
35
  */
34
36
  export declare function isNonInteractive(flags: LinkFlags): boolean;
35
37
  export declare function hasCapacity(team: TeamOption): boolean;
@@ -6,10 +6,12 @@
6
6
  * assistant needs to ask the user and retry with a deviation flag.
7
7
  */
8
8
  /**
9
- * LINK-12.3: any deviation flag implies non-interactive mode.
9
+ * LINK-12.3: any deviation flag implies non-interactive mode. The JSON
10
+ * output flag does too — machine-readable output only exists on the
11
+ * non-interactive path, so `link --json` alone must not run interactively.
10
12
  */
11
13
  export function isNonInteractive(flags) {
12
- return Boolean(flags.yes || flags.team || flags.connect || flags.create);
14
+ return Boolean(flags.yes || flags.team || flags.connect || flags.create || flags.json);
13
15
  }
14
16
  export function hasCapacity(team) {
15
17
  return team.projectLimit === -1 || team.projectCount < team.projectLimit;
@@ -1,6 +1,12 @@
1
1
  /**
2
- * MCP command - starts the Model Context Protocol server
3
- * This allows AI assistants to query requirements data
2
+ * MCP command — retired stub.
3
+ *
4
+ * The local stdio MCP server was retired with the MCP surface migration:
5
+ * coding agents use the CLI verbs directly (discovered via the workflow
6
+ * guidance ai-setup installs), and conversational agents use the remote
7
+ * dotrequirements connector. Configured assistants still invoking
8
+ * `npx @popoverai/dotrequirements mcp` land here, so this failure explains
9
+ * itself at the moment someone investigates.
4
10
  */
5
11
  export declare function mcpCommand(): Promise<void>;
6
12
  //# sourceMappingURL=mcp.d.ts.map
@@ -1,11 +1,22 @@
1
1
  /**
2
- * MCP command - starts the Model Context Protocol server
3
- * This allows AI assistants to query requirements data
2
+ * MCP command — retired stub.
3
+ *
4
+ * The local stdio MCP server was retired with the MCP surface migration:
5
+ * coding agents use the CLI verbs directly (discovered via the workflow
6
+ * guidance ai-setup installs), and conversational agents use the remote
7
+ * dotrequirements connector. Configured assistants still invoking
8
+ * `npx @popoverai/dotrequirements mcp` land here, so this failure explains
9
+ * itself at the moment someone investigates.
4
10
  */
5
11
  export async function mcpCommand() {
6
- // Import and start the MCP server
7
- // The server handles stdio transport and runs until process termination
8
- const { main } = await import("../mcp/index.js");
9
- await main();
12
+ throw new Error([
13
+ "The local MCP server was retired — coding agents now use the dotrequirements CLI directly.",
14
+ "",
15
+ "To update this machine's configuration:",
16
+ " dotreq ai-setup",
17
+ "",
18
+ "It removes stale MCP registrations and installs the current workflow guidance.",
19
+ "For chat apps (Claude, ChatGPT), use the dot•requirements remote connector instead.",
20
+ ].join("\n"));
10
21
  }
11
22
  //# sourceMappingURL=mcp.js.map