@popoverai/dotrequirements 0.11.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 (127) hide show
  1. package/README.md +478 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.js +82 -0
  4. package/dist/commands/init.d.ts +6 -0
  5. package/dist/commands/init.js +355 -0
  6. package/dist/commands/link.d.ts +15 -0
  7. package/dist/commands/link.js +156 -0
  8. package/dist/commands/login.d.ts +12 -0
  9. package/dist/commands/login.js +117 -0
  10. package/dist/commands/logout.d.ts +5 -0
  11. package/dist/commands/logout.js +17 -0
  12. package/dist/commands/mcp-setup.d.ts +5 -0
  13. package/dist/commands/mcp-setup.js +367 -0
  14. package/dist/commands/mcp.d.ts +6 -0
  15. package/dist/commands/mcp.js +10 -0
  16. package/dist/commands/pull.d.ts +7 -0
  17. package/dist/commands/pull.js +171 -0
  18. package/dist/commands/push.d.ts +11 -0
  19. package/dist/commands/push.js +310 -0
  20. package/dist/commands/test.d.ts +6 -0
  21. package/dist/commands/test.js +78 -0
  22. package/dist/config.d.ts +5 -0
  23. package/dist/config.js +16 -0
  24. package/dist/convex.d.ts +56 -0
  25. package/dist/convex.js +58 -0
  26. package/dist/harness/cache.d.ts +135 -0
  27. package/dist/harness/cache.js +342 -0
  28. package/dist/harness/convexReporting.d.ts +15 -0
  29. package/dist/harness/convexReporting.js +136 -0
  30. package/dist/harness/coverageCache.d.ts +30 -0
  31. package/dist/harness/coverageCache.js +70 -0
  32. package/dist/harness/finalize.d.ts +48 -0
  33. package/dist/harness/finalize.js +299 -0
  34. package/dist/harness/index.d.ts +70 -0
  35. package/dist/harness/index.js +103 -0
  36. package/dist/harness/localReporting.d.ts +6 -0
  37. package/dist/harness/localReporting.js +49 -0
  38. package/dist/harness/prepare.d.ts +41 -0
  39. package/dist/harness/prepare.js +83 -0
  40. package/dist/harness/requirementsLoader.d.ts +45 -0
  41. package/dist/harness/requirementsLoader.js +201 -0
  42. package/dist/harness/tracking.d.ts +49 -0
  43. package/dist/harness/tracking.js +179 -0
  44. package/dist/harness/types.d.ts +12 -0
  45. package/dist/harness/types.js +6 -0
  46. package/dist/mcp/convexClient.d.ts +43 -0
  47. package/dist/mcp/convexClient.js +101 -0
  48. package/dist/mcp/grep.d.ts +24 -0
  49. package/dist/mcp/grep.js +261 -0
  50. package/dist/mcp/index.d.ts +3 -0
  51. package/dist/mcp/index.js +1758 -0
  52. package/dist/mcp/requirements.d.ts +47 -0
  53. package/dist/mcp/requirements.js +141 -0
  54. package/dist/mcp/testCodeExtractor.d.ts +22 -0
  55. package/dist/mcp/testCodeExtractor.js +152 -0
  56. package/dist/mcp/types.d.ts +27 -0
  57. package/dist/mcp/types.js +2 -0
  58. package/dist/schema/browser.d.ts +12 -0
  59. package/dist/schema/browser.js +24 -0
  60. package/dist/schema/builder.d.ts +25 -0
  61. package/dist/schema/builder.js +125 -0
  62. package/dist/schema/conversions.d.ts +69 -0
  63. package/dist/schema/conversions.js +201 -0
  64. package/dist/schema/index.d.ts +14 -0
  65. package/dist/schema/index.js +24 -0
  66. package/dist/schema/parser-core.d.ts +61 -0
  67. package/dist/schema/parser-core.js +247 -0
  68. package/dist/schema/parser.d.ts +44 -0
  69. package/dist/schema/parser.js +295 -0
  70. package/dist/schema/resolver.d.ts +66 -0
  71. package/dist/schema/resolver.js +185 -0
  72. package/dist/schema/schemas.d.ts +312 -0
  73. package/dist/schema/schemas.js +258 -0
  74. package/dist/schema/test-schema.d.ts +5 -0
  75. package/dist/schema/test-schema.js +81 -0
  76. package/dist/templates/antigravity-gemini.md +3 -0
  77. package/dist/templates/antigravity-overview-rule.md +3 -0
  78. package/dist/templates/antigravity-test-rule.md +3 -0
  79. package/dist/templates/behavioral-core.md +25 -0
  80. package/dist/templates/claude-code-overview-skill.md +6 -0
  81. package/dist/templates/claude-code-skill.md +6 -0
  82. package/dist/templates/claude-code-test-skill.md +6 -0
  83. package/dist/templates/codex-agents.md +3 -0
  84. package/dist/templates/codex-overview-agents.md +3 -0
  85. package/dist/templates/codex-test-agents.md +3 -0
  86. package/dist/templates/cursor-overview-rule.mdc +5 -0
  87. package/dist/templates/cursor-rule.mdc +5 -0
  88. package/dist/templates/cursor-test-rule.mdc +5 -0
  89. package/dist/templates/example-requirements.d.ts +8 -0
  90. package/dist/templates/example-requirements.js +88 -0
  91. package/dist/templates/example-requirements.ts +88 -0
  92. package/dist/templates/overview-core.md +27 -0
  93. package/dist/templates/requirements-readme.d.ts +5 -0
  94. package/dist/templates/requirements-readme.js +31 -0
  95. package/dist/templates/requirements-readme.ts +30 -0
  96. package/dist/templates/test-writing-core.md +72 -0
  97. package/dist/utils/brand.d.ts +5 -0
  98. package/dist/utils/brand.js +8 -0
  99. package/dist/utils/browser-launch.d.ts +19 -0
  100. package/dist/utils/browser-launch.js +36 -0
  101. package/dist/utils/detect-existing-project.d.ts +5 -0
  102. package/dist/utils/detect-existing-project.js +34 -0
  103. package/dist/utils/env.d.ts +19 -0
  104. package/dist/utils/env.js +56 -0
  105. package/dist/utils/gitignore.d.ts +7 -0
  106. package/dist/utils/gitignore.js +29 -0
  107. package/dist/utils/local-project.d.ts +31 -0
  108. package/dist/utils/local-project.js +33 -0
  109. package/dist/utils/oauth-callback-server.d.ts +28 -0
  110. package/dist/utils/oauth-callback-server.js +156 -0
  111. package/dist/utils/oauth-flow.d.ts +22 -0
  112. package/dist/utils/oauth-flow.js +120 -0
  113. package/dist/utils/project-discovery.d.ts +57 -0
  114. package/dist/utils/project-discovery.js +146 -0
  115. package/dist/utils/project-name.d.ts +8 -0
  116. package/dist/utils/project-name.js +48 -0
  117. package/dist/utils/project-selector.d.ts +25 -0
  118. package/dist/utils/project-selector.js +69 -0
  119. package/dist/utils/prompts.d.ts +33 -0
  120. package/dist/utils/prompts.js +60 -0
  121. package/dist/utils/templates.d.ts +29 -0
  122. package/dist/utils/templates.js +67 -0
  123. package/dist/utils/token-refresh.d.ts +24 -0
  124. package/dist/utils/token-refresh.js +69 -0
  125. package/dist/utils/token-storage.d.ts +31 -0
  126. package/dist/utils/token-storage.js +57 -0
  127. package/package.json +82 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Logout command - clear stored authentication tokens.
3
+ */
4
+ import { deleteTokens, hasValidTokens } from '../utils/token-storage.js';
5
+ export async function logoutCommand() {
6
+ const wasLoggedIn = await hasValidTokens();
7
+ await deleteTokens();
8
+ if (wasLoggedIn) {
9
+ console.log('Logged out successfully.');
10
+ }
11
+ else {
12
+ console.log('No active session found.');
13
+ }
14
+ console.log('\nYour project credentials in .env.local are unchanged.');
15
+ console.log('Run "dotrequirements login" to authenticate again.');
16
+ }
17
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * MCP setup command - configures the MCP server for AI assistants
3
+ */
4
+ export declare function mcpSetupCommand(): Promise<void>;
5
+ //# sourceMappingURL=mcp-setup.d.ts.map
@@ -0,0 +1,367 @@
1
+ import { execSync } from 'child_process';
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join, dirname } from 'path';
5
+ import prompts from 'prompts';
6
+ import { findUp } from 'find-up';
7
+ import { loadPlatformTemplate, loadTestWritingTemplate, loadOverviewTemplate } from '../utils/templates.js';
8
+ import { brand } from '../utils/brand.js';
9
+ /**
10
+ * Platform-specific skills directory paths (relative to git root)
11
+ */
12
+ const PLATFORM_SKILLS_DIRS = {
13
+ 'claude-code': '.claude/skills',
14
+ 'cursor': '.cursor/skills',
15
+ 'antigravity': '.agent/skills',
16
+ 'codex': '.codex/skills',
17
+ 'github-copilot': '.github/skills',
18
+ };
19
+ /**
20
+ * Load skill content by name
21
+ */
22
+ function loadSkillContent(skillName) {
23
+ switch (skillName) {
24
+ case 'dotreq-requirements':
25
+ return loadPlatformTemplate('claude-code-skill.md');
26
+ case 'dotreq-tests':
27
+ return loadTestWritingTemplate('claude-code-test-skill.md');
28
+ case 'dotreq-overview':
29
+ return loadOverviewTemplate('claude-code-overview-skill.md');
30
+ }
31
+ }
32
+ /**
33
+ * Install a single skill to the specified base directory
34
+ */
35
+ async function installSkill(skillName, baseDir, gitRoot) {
36
+ const content = loadSkillContent(skillName);
37
+ const skillDir = join(gitRoot, baseDir, skillName);
38
+ const skillPath = join(skillDir, 'SKILL.md');
39
+ if (!existsSync(skillDir)) {
40
+ mkdirSync(skillDir, { recursive: true });
41
+ }
42
+ writeFileSync(skillPath, content, 'utf-8');
43
+ console.log(` Created: ${skillPath}`);
44
+ }
45
+ /**
46
+ * Prompt user to install workflow guidance
47
+ */
48
+ async function promptForGuidanceInstall(installPath) {
49
+ const response = await prompts({
50
+ type: 'confirm',
51
+ name: 'install',
52
+ message: `Install workflow guidance? (recommended)\n → ${installPath}`,
53
+ initial: true,
54
+ });
55
+ return response.install ?? false;
56
+ }
57
+ /**
58
+ * Install all three skills to a platform's skills directory
59
+ */
60
+ async function installAllSkills(platform, customPath) {
61
+ const gitDir = await findUp('.git', { type: 'directory' });
62
+ if (!gitDir) {
63
+ console.error('\n❌ Could not find git root directory.');
64
+ console.log(' Make sure you are inside a git repository.');
65
+ return;
66
+ }
67
+ const gitRoot = dirname(gitDir);
68
+ const baseDir = customPath || PLATFORM_SKILLS_DIRS[platform];
69
+ if (!baseDir) {
70
+ console.error(`\n❌ Unknown platform: ${platform}`);
71
+ return;
72
+ }
73
+ // Prompt for each skill
74
+ const skills = [
75
+ { name: 'dotreq-overview', label: 'Overview' },
76
+ { name: 'dotreq-requirements', label: 'Requirements capture' },
77
+ { name: 'dotreq-tests', label: 'Test writing' },
78
+ ];
79
+ for (const skill of skills) {
80
+ const skillPath = join(gitRoot, baseDir, skill.name, 'SKILL.md');
81
+ const shouldInstall = await promptForGuidanceInstall(skillPath);
82
+ if (shouldInstall) {
83
+ try {
84
+ await installSkill(skill.name, baseDir, gitRoot);
85
+ console.log(`✅ ${skill.label} guidance installed\n`);
86
+ }
87
+ catch (error) {
88
+ console.error(`❌ Failed to install ${skill.label} guidance`);
89
+ console.log(` Error: ${error instanceof Error ? error.message : String(error)}\n`);
90
+ }
91
+ }
92
+ else {
93
+ console.log('');
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * MCP setup command - configures the MCP server for AI assistants
99
+ */
100
+ export async function mcpSetupCommand() {
101
+ console.log(`🔧 Setting up ${brand} MCP server\n`);
102
+ // Interactive client selector
103
+ const response = await prompts({
104
+ type: 'select',
105
+ name: 'client',
106
+ message: 'Which AI assistant are you using?',
107
+ choices: [
108
+ { title: 'Claude Code', value: 'claude-code' },
109
+ { title: 'Cursor', value: 'cursor' },
110
+ { title: 'Google Antigravity', value: 'antigravity' },
111
+ { title: 'OpenAI Codex', value: 'codex' },
112
+ { title: 'GitHub Copilot', value: 'github-copilot' },
113
+ { title: 'Other (custom path)', value: 'other' },
114
+ ],
115
+ initial: 0,
116
+ });
117
+ if (!response.client) {
118
+ console.log('\nSetup cancelled.');
119
+ return;
120
+ }
121
+ switch (response.client) {
122
+ case 'claude-code':
123
+ await setupClaudeCode();
124
+ break;
125
+ case 'cursor':
126
+ await setupCursor();
127
+ break;
128
+ case 'antigravity':
129
+ await setupAntigravity();
130
+ break;
131
+ case 'codex':
132
+ await setupCodex();
133
+ break;
134
+ case 'github-copilot':
135
+ await setupGitHubCopilot();
136
+ break;
137
+ case 'other':
138
+ await setupOther();
139
+ break;
140
+ default:
141
+ console.log(`\n❌ ${response.client} is not yet supported.`);
142
+ console.log('Please refer to the documentation for manual setup.');
143
+ }
144
+ }
145
+ async function setupClaudeCode() {
146
+ console.log('\n📦 Configuring MCP for Claude Code...\n');
147
+ const command = `claude mcp add-json dotrequirements '{"type":"stdio","command":"npx","args":["-y","@popoverai/dotrequirements","mcp"]}'`;
148
+ let mcpConfigured = false;
149
+ try {
150
+ execSync(command, { stdio: 'pipe' });
151
+ console.log('✅ MCP configured successfully for Claude Code!');
152
+ mcpConfigured = true;
153
+ }
154
+ catch (error) {
155
+ const stderr = error.stderr?.toString() || '';
156
+ const errorMessage = error.message || '';
157
+ const combinedError = stderr + errorMessage;
158
+ if (combinedError.includes('already exists')) {
159
+ console.log('✅ MCP server already configured for Claude Code');
160
+ mcpConfigured = true;
161
+ }
162
+ else {
163
+ console.error('❌ Failed to configure MCP automatically.');
164
+ console.log('\nYou can manually run:');
165
+ console.log(` ${command}`);
166
+ if (stderr) {
167
+ console.log(`\nError: ${stderr}`);
168
+ }
169
+ }
170
+ }
171
+ if (mcpConfigured) {
172
+ console.log('\n⚠️ Important: Restart Claude Code for changes to take effect.');
173
+ console.log('\nYou can now ask your AI assistant to help write requirements!\n');
174
+ await installAllSkills('claude-code');
175
+ }
176
+ }
177
+ async function setupCursor() {
178
+ console.log('\n📦 Configuring MCP for Cursor (project-specific)...\n');
179
+ const configPath = join(process.cwd(), '.cursor', 'mcp.json');
180
+ const configDir = join(process.cwd(), '.cursor');
181
+ const mcpServerConfig = {
182
+ command: 'npx',
183
+ args: ['-y', '@popoverai/dotrequirements', 'mcp'],
184
+ };
185
+ try {
186
+ if (!existsSync(configDir)) {
187
+ console.log('Creating Cursor config directory...');
188
+ mkdirSync(configDir, { recursive: true });
189
+ }
190
+ let config = {};
191
+ if (existsSync(configPath)) {
192
+ try {
193
+ const existingContent = readFileSync(configPath, 'utf-8');
194
+ if (existingContent.trim()) {
195
+ config = JSON.parse(existingContent);
196
+ }
197
+ }
198
+ catch (parseError) {
199
+ console.log('⚠️ Existing config file is invalid, creating fresh config...');
200
+ config = {};
201
+ }
202
+ }
203
+ if (!config.mcpServers) {
204
+ config.mcpServers = {};
205
+ }
206
+ config.mcpServers['dotrequirements'] = mcpServerConfig;
207
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
208
+ console.log(`✅ MCP configured successfully!`);
209
+ console.log(` Config file: ${configPath}\n`);
210
+ console.log('⚠️ Important: Reload or restart Cursor for changes to take effect.');
211
+ console.log('\nYou can now ask your AI assistant to help write requirements!\n');
212
+ await installAllSkills('cursor');
213
+ }
214
+ catch (error) {
215
+ console.error('❌ Failed to configure MCP automatically.');
216
+ console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
217
+ console.log('\nYou can manually edit the config file at:');
218
+ console.log(` ${configPath}\n`);
219
+ console.log('Add the following configuration:');
220
+ const manualConfig = {
221
+ mcpServers: {
222
+ dotrequirements: mcpServerConfig,
223
+ },
224
+ };
225
+ console.log(JSON.stringify(manualConfig, null, 2));
226
+ }
227
+ }
228
+ async function setupAntigravity() {
229
+ console.log('\n📦 Configuring MCP for Google Antigravity...\n');
230
+ const configPath = join(homedir(), '.gemini', 'antigravity', 'mcp_config.json');
231
+ const configDir = join(homedir(), '.gemini', 'antigravity');
232
+ // Check if we're in a dotrequirements project directory
233
+ const envLocalPath = join(process.cwd(), '.env.local');
234
+ let projectId = null;
235
+ let projectPath = null;
236
+ if (existsSync(envLocalPath)) {
237
+ try {
238
+ const envContent = readFileSync(envLocalPath, 'utf-8');
239
+ const lines = envContent.split('\n');
240
+ for (const line of lines) {
241
+ const trimmed = line.trim();
242
+ if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_ID=')) {
243
+ projectId = trimmed.split('=')[1]?.trim().replace(/['"]/g, '');
244
+ projectPath = process.cwd();
245
+ break;
246
+ }
247
+ }
248
+ }
249
+ catch (error) {
250
+ // .env.local exists but couldn't read it - continue with global setup
251
+ }
252
+ }
253
+ try {
254
+ if (!existsSync(configDir)) {
255
+ console.log('Creating Antigravity config directory...');
256
+ mkdirSync(configDir, { recursive: true });
257
+ }
258
+ let config = {};
259
+ if (existsSync(configPath)) {
260
+ try {
261
+ const existingContent = readFileSync(configPath, 'utf-8');
262
+ if (existingContent.trim()) {
263
+ config = JSON.parse(existingContent);
264
+ }
265
+ }
266
+ catch (parseError) {
267
+ console.log('⚠️ Existing config file is invalid, creating fresh config...');
268
+ config = {};
269
+ }
270
+ }
271
+ if (!config.mcpServers) {
272
+ config.mcpServers = {};
273
+ }
274
+ if (!config.mcpServers['dotrequirements']) {
275
+ config.mcpServers['dotrequirements'] = {
276
+ command: 'npx',
277
+ args: ['-y', '@popoverai/dotrequirements', 'mcp'],
278
+ env: {},
279
+ };
280
+ }
281
+ const dotreqServer = config.mcpServers['dotrequirements'];
282
+ if (!dotreqServer.env) {
283
+ dotreqServer.env = {};
284
+ }
285
+ if (projectId && projectPath) {
286
+ const envKey = `PROJ_${projectId}`;
287
+ dotreqServer.env[envKey] = projectPath;
288
+ console.log(`✅ Added project to MCP config: ${envKey}`);
289
+ console.log(` Path: ${projectPath}\n`);
290
+ }
291
+ else {
292
+ console.log(`ℹ️ Not in a ${brand} project directory (no .env.local with DOTREQUIREMENTS_PROJECT_ID found)`);
293
+ console.log(' Setting up global MCP server only.\n');
294
+ }
295
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
296
+ console.log(`✅ MCP configured successfully!`);
297
+ console.log(` Config file: ${configPath}\n`);
298
+ console.log('⚠️ Important: Reload or restart Google Antigravity for changes to take effect.');
299
+ console.log('\nYou can now ask your AI assistant to help write requirements!');
300
+ if (projectId) {
301
+ console.log(`\n💡 To add more projects, run \`dotreq mcp-setup\` from each project directory.\n`);
302
+ }
303
+ else {
304
+ console.log('');
305
+ }
306
+ await installAllSkills('antigravity');
307
+ }
308
+ catch (error) {
309
+ console.error('❌ Failed to configure MCP automatically.');
310
+ console.log(`\nError: ${error instanceof Error ? error.message : String(error)}`);
311
+ console.log('\nYou can manually edit the config file at:');
312
+ console.log(` ${configPath}`);
313
+ }
314
+ }
315
+ async function setupCodex() {
316
+ console.log('\n📦 Configuring MCP for OpenAI Codex...\n');
317
+ const command = `codex mcp add dotrequirements -- npx -y @popoverai/dotrequirements mcp`;
318
+ try {
319
+ execSync(command, { stdio: 'inherit' });
320
+ console.log('\n✅ MCP configured successfully for Codex!');
321
+ console.log('\n⚠️ Important: Restart Codex for changes to take effect.');
322
+ console.log('\nYou can now ask your AI assistant to help write requirements!\n');
323
+ await installAllSkills('codex');
324
+ }
325
+ catch (error) {
326
+ console.error('\n❌ Failed to configure MCP automatically.');
327
+ console.log('\nYou can manually run:');
328
+ console.log(` ${command}`);
329
+ console.log('\nOr manually edit ~/.codex/config.toml and add:');
330
+ console.log('[mcp_servers.dotrequirements]');
331
+ console.log('command = "npx"');
332
+ console.log('args = ["-y", "@popoverai/dotrequirements", "mcp"]');
333
+ }
334
+ }
335
+ async function setupGitHubCopilot() {
336
+ console.log('\n📦 Configuring skills for GitHub Copilot...\n');
337
+ console.log('ℹ️ GitHub Copilot discovers MCP servers from your IDE configuration.');
338
+ console.log(' Please configure the MCP server in VS Code settings or GitHub settings.\n');
339
+ console.log(' MCP server command: npx -y @popoverai/dotrequirements mcp\n');
340
+ await installAllSkills('github-copilot');
341
+ }
342
+ async function setupOther() {
343
+ console.log('\n📦 Custom Agent Skills Setup\n');
344
+ console.log('ℹ️ Agent Skills are installed as SKILL.md files in a skills directory.');
345
+ console.log(' Common locations:');
346
+ console.log(' - .claude/skills/ (Claude Code)');
347
+ console.log(' - .cursor/skills/ (Cursor)');
348
+ console.log(' - .agent/skills/ (Antigravity)');
349
+ console.log(' - .codex/skills/ (Codex)');
350
+ console.log(' - .github/skills/ (GitHub Copilot)\n');
351
+ const response = await prompts({
352
+ type: 'text',
353
+ name: 'path',
354
+ message: 'Enter the skills directory path (relative to git root):',
355
+ initial: '.skills',
356
+ });
357
+ if (!response.path) {
358
+ console.log('\nSetup cancelled.');
359
+ return;
360
+ }
361
+ console.log('\n📝 MCP Server Setup\n');
362
+ console.log('Configure your AI assistant to run the MCP server:');
363
+ console.log(' Command: npx');
364
+ console.log(' Args: -y @popoverai/dotrequirements mcp\n');
365
+ await installAllSkills('other', response.path);
366
+ }
367
+ //# sourceMappingURL=mcp-setup.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MCP command - starts the Model Context Protocol server
3
+ * This allows AI assistants to query requirements data
4
+ */
5
+ export declare function mcpCommand(): Promise<void>;
6
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MCP command - starts the Model Context Protocol server
3
+ * This allows AI assistants to query requirements data
4
+ */
5
+ export async function mcpCommand() {
6
+ // Import the MCP server - this starts it automatically
7
+ // The server handles stdio transport and runs until process termination
8
+ await import('../mcp/index.js');
9
+ }
10
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,7 @@
1
+ interface PullOptions {
2
+ project?: string;
3
+ document?: string;
4
+ }
5
+ export declare function pullCommand(options: PullOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=pull.d.ts.map
@@ -0,0 +1,171 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { ConvexHttpClient } from 'convex/browser';
4
+ import { getConvexUrl } from '../config.js';
5
+ import { buildRequirementsFile } from '../schema/index.js';
6
+ import { api } from '../convex.js';
7
+ import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
8
+ import { brand } from '../utils/brand.js';
9
+ export async function pullCommand(options) {
10
+ try {
11
+ console.log(`Syncing requirements from ${brand} cloud...`);
12
+ // Connect to Convex
13
+ const convexUrl = getConvexUrl();
14
+ const client = new ConvexHttpClient(convexUrl);
15
+ // Determine project ID
16
+ let projectId = options.project;
17
+ // If no project flag provided, try to read from .env.local
18
+ if (!projectId && !options.document) {
19
+ projectId = loadProjectIdFromEnv();
20
+ if (!projectId) {
21
+ throw new Error('Project ID not found. Either:\n' +
22
+ ' 1. Run "dotreq init" to initialize the project, or\n' +
23
+ ' 2. Use --project flag to specify the project ID');
24
+ }
25
+ // AUTHZ-2.0: Check if project is local-only
26
+ if (isLocalOnlyProject(projectId)) {
27
+ throw new Error(CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE);
28
+ }
29
+ }
30
+ // Load project secret for authentication
31
+ const projectSecret = process.env.DOTREQUIREMENTS_PROJECT_SECRET;
32
+ if (!projectSecret) {
33
+ throw new Error('DOTREQUIREMENTS_PROJECT_SECRET not found. Either:\n' +
34
+ ' 1. Run "dotrequirements init" or "dotrequirements login" to set up authentication, or\n' +
35
+ ' 2. Set DOTREQUIREMENTS_PROJECT_SECRET in .env.local');
36
+ }
37
+ // Fetch documents
38
+ let documents;
39
+ if (options.document) {
40
+ // Fetch specific document
41
+ const doc = await client.query(api.documents.queries.exportForCli, {
42
+ projectAuth: {
43
+ projectSlug: projectId,
44
+ projectSecret,
45
+ },
46
+ target: { type: 'document', id: options.document },
47
+ documentId: options.document,
48
+ });
49
+ documents = doc ? [doc] : [];
50
+ }
51
+ else if (projectId) {
52
+ // Fetch all documents for project
53
+ // SYNC-ARCH-3.1: Use projectSlug (not projectId) since env stores the slug
54
+ documents = await client.query(api.documents.queries.exportProjectForCli, {
55
+ projectAuth: {
56
+ projectSlug: projectId,
57
+ projectSecret,
58
+ },
59
+ target: { type: 'project', slug: projectId },
60
+ });
61
+ }
62
+ else {
63
+ throw new Error('Either --project or --document must be specified');
64
+ }
65
+ if (documents.length === 0) {
66
+ console.log('No documents found.');
67
+ return;
68
+ }
69
+ // Ensure .requirements/ directory exists
70
+ const requirementsDir = path.join(process.cwd(), '.requirements');
71
+ if (!fs.existsSync(requirementsDir)) {
72
+ fs.mkdirSync(requirementsDir, { recursive: true });
73
+ }
74
+ // Build index of existing files by document ID
75
+ const existingFilesByDocId = buildDocumentIdIndex(requirementsDir);
76
+ // Write each document as a Markdown file
77
+ for (const doc of documents) {
78
+ // Check if an existing file has this document ID
79
+ const existingFilePath = existingFilesByDocId.get(doc.documentId);
80
+ const filePath = existingFilePath ?? path.join(requirementsDir, sanitizeFileName(doc.title) + '.requirements.md');
81
+ const fileName = path.basename(filePath);
82
+ // Build metadata with pulledAt for conflict detection
83
+ // SYNC-CLI-EDIT-2: pulledAt is compared with cloud updatedAt during push
84
+ // Use the slug we authenticated with (not the Convex ID from the response)
85
+ const metadata = {
86
+ projectId: projectId,
87
+ pulledAt: new Date().toISOString(),
88
+ version: doc.version,
89
+ document: {
90
+ id: doc.documentId,
91
+ title: doc.title,
92
+ defaultPrefix: doc.defaultPrefix,
93
+ },
94
+ };
95
+ // SYNC-ARCH-1: Use markdownContent directly from cloud (source of truth)
96
+ // No need to rebuild from requirements - just combine with frontmatter
97
+ const fileContent = buildRequirementsFile(metadata, doc.markdownContent);
98
+ fs.writeFileSync(filePath, fileContent, 'utf-8');
99
+ console.log(`✓ Synced: ${fileName} (version ${doc.version})`);
100
+ }
101
+ console.log(`\nSuccessfully synced ${documents.length} document(s) to .requirements/`);
102
+ }
103
+ catch (error) {
104
+ // Re-throw to let the CLI error handler display it cleanly
105
+ throw error;
106
+ }
107
+ }
108
+ /**
109
+ * Sanitize document title for use as filename
110
+ */
111
+ function sanitizeFileName(title) {
112
+ return title
113
+ .toLowerCase()
114
+ .replace(/[^a-z0-9]+/g, '-')
115
+ .replace(/^-+|-+$/g, '');
116
+ }
117
+ /**
118
+ * Load project ID from process.env (already loaded from .env.local by CLI)
119
+ */
120
+ function loadProjectIdFromEnv() {
121
+ return process.env.DOTREQUIREMENTS_PROJECT_ID;
122
+ }
123
+ /**
124
+ * Build an index mapping document IDs to their file paths.
125
+ * Scans existing .requirements.md files and extracts document.id from frontmatter.
126
+ */
127
+ function buildDocumentIdIndex(requirementsDir) {
128
+ const index = new Map();
129
+ if (!fs.existsSync(requirementsDir)) {
130
+ return index;
131
+ }
132
+ const files = fs.readdirSync(requirementsDir);
133
+ for (const file of files) {
134
+ if (!file.endsWith('.requirements.md')) {
135
+ continue;
136
+ }
137
+ const filePath = path.join(requirementsDir, file);
138
+ const docId = extractDocumentIdFromFile(filePath);
139
+ if (docId) {
140
+ index.set(docId, filePath);
141
+ }
142
+ }
143
+ return index;
144
+ }
145
+ /**
146
+ * Extract document.id from a requirements file's frontmatter.
147
+ * Returns undefined if the file can't be read or doesn't have a document ID.
148
+ */
149
+ function extractDocumentIdFromFile(filePath) {
150
+ try {
151
+ const content = fs.readFileSync(filePath, 'utf-8');
152
+ // Match document.id in YAML frontmatter - handles both inline and nested formats
153
+ // Inline: document: { id: "abc123", ... }
154
+ // Nested:
155
+ // document:
156
+ // id: abc123
157
+ const inlineMatch = content.match(/document:\s*\{[^}]*id:\s*["']?([^"',}\s]+)/);
158
+ if (inlineMatch) {
159
+ return inlineMatch[1];
160
+ }
161
+ const nestedMatch = content.match(/document:\s*\n\s+id:\s*["']?([^"'\s\n]+)/);
162
+ if (nestedMatch) {
163
+ return nestedMatch[1];
164
+ }
165
+ return undefined;
166
+ }
167
+ catch {
168
+ return undefined;
169
+ }
170
+ }
171
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1,11 @@
1
+ interface PushOptions {
2
+ yes?: boolean;
3
+ }
4
+ export declare function pushCommand(file: string | undefined, options: PushOptions): Promise<void>;
5
+ /**
6
+ * Extract markdown content from a file, stripping YAML frontmatter.
7
+ * Exported for testing.
8
+ */
9
+ export declare function extractMarkdownContent(rawContent: string): string;
10
+ export {};
11
+ //# sourceMappingURL=push.d.ts.map