@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.
- package/README.md +13 -69
- package/dist/cli.js +19 -7
- package/dist/codebase-to-spec/present.js +4 -5
- package/dist/codebase-to-spec/validate.js +3 -2
- package/dist/commands/acceptance-test.js +4 -2
- package/dist/commands/ai-setup.d.ts +8 -2
- package/dist/commands/ai-setup.js +154 -310
- package/dist/commands/get.js +6 -2
- package/dist/commands/init.js +8 -6
- package/dist/commands/link-resolution.d.ts +3 -1
- package/dist/commands/link-resolution.js +4 -2
- package/dist/commands/mcp.d.ts +8 -2
- package/dist/commands/mcp.js +17 -6
- package/dist/commands/pull.js +36 -3
- package/dist/commands/push.js +54 -16
- package/dist/commands/report.js +18 -3
- package/dist/commands/review-test.d.ts +5 -1
- package/dist/commands/review-test.js +117 -15
- package/dist/commands/style-check.d.ts +1 -0
- package/dist/commands/style-check.js +137 -13
- package/dist/commands/tests-for.js +13 -13
- package/dist/commands/validate.js +14 -14
- package/dist/convex.d.ts +1 -3
- package/dist/convex.js +3 -3
- package/dist/harness/cache.d.ts +19 -3
- package/dist/harness/cache.js +38 -12
- package/dist/harness/finalize.js +33 -1
- package/dist/harness/index.js +16 -9
- package/dist/harness/requirementsLoader.js +12 -0
- package/dist/harness/tracking.d.ts +17 -2
- package/dist/harness/tracking.js +83 -9
- package/dist/push/core.d.ts +50 -0
- package/dist/push/core.js +149 -11
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +1 -1
- package/dist/requirements/cloud-ai.d.ts +21 -8
- package/dist/requirements/cloud-ai.js +10 -8
- package/dist/requirements/cloud-coverage.d.ts +12 -2
- package/dist/requirements/cloud-coverage.js +30 -3
- package/dist/requirements/grep.d.ts +7 -2
- package/dist/requirements/grep.js +75 -47
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -0
- package/dist/schema/conversions.d.ts +7 -2
- package/dist/schema/conversions.js +13 -4
- package/dist/schema/parser-core.d.ts +41 -0
- package/dist/schema/parser-core.js +113 -18
- package/dist/schema/parser.d.ts +8 -26
- package/dist/schema/parser.js +23 -251
- package/dist/schema/resolver.js +18 -8
- package/dist/templates/context-file-section.md +25 -22
- package/dist/utils/context-file.d.ts +7 -3
- package/dist/utils/context-file.js +10 -7
- package/dist/utils/env.js +17 -1
- package/dist/utils/oauth-flow.js +8 -0
- package/dist/utils/project-settings.d.ts +5 -0
- package/dist/utils/project-settings.js +36 -1
- package/package.json +3 -5
- package/dist/mcp/convexClient.d.ts +0 -19
- package/dist/mcp/convexClient.js +0 -24
- package/dist/mcp/handlers/authoring.d.ts +0 -41
- package/dist/mcp/handlers/authoring.js +0 -104
- package/dist/mcp/handlers/debug.d.ts +0 -16
- package/dist/mcp/handlers/debug.js +0 -37
- package/dist/mcp/handlers/get.d.ts +0 -24
- package/dist/mcp/handlers/get.js +0 -65
- package/dist/mcp/handlers/index.d.ts +0 -28
- package/dist/mcp/handlers/index.js +0 -19
- package/dist/mcp/handlers/list.d.ts +0 -7
- package/dist/mcp/handlers/list.js +0 -43
- package/dist/mcp/handlers/push.d.ts +0 -26
- package/dist/mcp/handlers/push.js +0 -186
- package/dist/mcp/handlers/report.d.ts +0 -16
- package/dist/mcp/handlers/report.js +0 -134
- package/dist/mcp/handlers/review.d.ts +0 -51
- package/dist/mcp/handlers/review.js +0 -200
- package/dist/mcp/handlers/search.d.ts +0 -30
- package/dist/mcp/handlers/search.js +0 -58
- package/dist/mcp/handlers/test-mapping.d.ts +0 -39
- package/dist/mcp/handlers/test-mapping.js +0 -133
- package/dist/mcp/handlers/types.d.ts +0 -75
- package/dist/mcp/handlers/types.js +0 -25
- package/dist/mcp/index.d.ts +0 -45
- package/dist/mcp/index.js +0 -634
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync,
|
|
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),
|
|
18
|
-
*
|
|
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
|
|
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}
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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);
|
package/dist/commands/get.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
|
-
import { formatRequirementTree,
|
|
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
|
-
|
|
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`);
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
152
|
-
|
|
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.
|
|
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;
|
package/dist/commands/mcp.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP command
|
|
3
|
-
*
|
|
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
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP command
|
|
3
|
-
*
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|