@popoverai/dotrequirements 0.12.1 → 0.14.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 +66 -61
- package/dist/cli.js +0 -10
- package/dist/commands/init.js +167 -224
- package/dist/commands/link.d.ts +9 -10
- package/dist/commands/link.js +81 -106
- package/dist/commands/mcp-setup.js +77 -94
- package/dist/commands/pull.js +17 -45
- package/dist/commands/push.js +9 -24
- package/dist/convex.d.ts +3 -0
- package/dist/convex.js +3 -0
- package/dist/harness/cache.d.ts +0 -5
- package/dist/harness/cache.js +0 -48
- package/dist/harness/convexReporting.js +7 -14
- package/dist/harness/finalize.d.ts +8 -2
- package/dist/harness/finalize.js +48 -31
- package/dist/harness/prepare.js +7 -9
- package/dist/mcp/convexClient.d.ts +5 -1
- package/dist/mcp/convexClient.js +14 -34
- package/dist/mcp/index.js +25 -78
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +2 -3
- package/dist/schema/schemas.d.ts +14 -37
- package/dist/schema/schemas.js +7 -10
- package/dist/schema/test-schema.js +1 -1
- package/dist/templates/context-file-section.md +59 -0
- package/dist/utils/context-file.d.ts +38 -0
- package/dist/utils/context-file.js +94 -0
- package/dist/utils/env.d.ts +0 -13
- package/dist/utils/env.js +0 -19
- package/dist/utils/gitignore.d.ts +2 -2
- package/dist/utils/gitignore.js +4 -4
- package/dist/utils/oauth-flow.d.ts +0 -1
- package/dist/utils/oauth-flow.js +0 -9
- package/dist/utils/project-discovery.d.ts +3 -5
- package/dist/utils/project-discovery.js +18 -42
- package/dist/utils/project-selector.d.ts +17 -3
- package/dist/utils/project-selector.js +37 -3
- package/dist/utils/project-settings.d.ts +47 -0
- package/dist/utils/project-settings.js +110 -0
- package/dist/utils/templates.d.ts +0 -24
- package/dist/utils/templates.js +0 -39
- package/package.json +1 -1
- package/dist/harness/localReporting.d.ts +0 -6
- package/dist/harness/localReporting.js +0 -49
- package/dist/templates/antigravity-gemini.md +0 -3
- package/dist/templates/antigravity-overview-rule.md +0 -3
- package/dist/templates/antigravity-test-rule.md +0 -3
- package/dist/templates/behavioral-core.md +0 -25
- package/dist/templates/claude-code-overview-skill.md +0 -6
- package/dist/templates/claude-code-skill.md +0 -6
- package/dist/templates/claude-code-test-skill.md +0 -6
- package/dist/templates/codex-agents.md +0 -3
- package/dist/templates/codex-overview-agents.md +0 -3
- package/dist/templates/codex-test-agents.md +0 -3
- package/dist/templates/cursor-overview-rule.mdc +0 -5
- package/dist/templates/cursor-rule.mdc +0 -5
- package/dist/templates/cursor-test-rule.mdc +0 -5
- package/dist/templates/overview-core.md +0 -27
- package/dist/templates/test-writing-core.md +0 -72
- package/dist/utils/detect-existing-project.d.ts +0 -5
- package/dist/utils/detect-existing-project.js +0 -34
package/dist/commands/link.js
CHANGED
|
@@ -1,74 +1,81 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { promptConfirm } from '../utils/prompts.js';
|
|
1
|
+
import { promptConfirm, promptChoice } from '../utils/prompts.js';
|
|
4
2
|
import { pullCommand } from './pull.js';
|
|
5
|
-
import { hasValidTokens, getTokens } from '../utils/token-storage.js';
|
|
6
3
|
import { executeOAuthFlow } from '../utils/oauth-flow.js';
|
|
7
4
|
import { ConvexHttpClient } from 'convex/browser';
|
|
8
5
|
import { getConvexUrl } from '../config.js';
|
|
9
6
|
import { brand } from '../utils/brand.js';
|
|
10
|
-
import { selectTeam, selectProject, getOrCreateProjectSecret } from '../utils/project-selector.js';
|
|
7
|
+
import { selectTeam, selectProject, getOrCreateProjectSecret, promptExpiryDays } from '../utils/project-selector.js';
|
|
8
|
+
import { findProjectRoot, readProjectSettings, writeProjectSettings, } from '../utils/project-settings.js';
|
|
11
9
|
/**
|
|
12
|
-
* Link command - connect local
|
|
10
|
+
* Link command - connect or reconnect local project to cloud.
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
* 1
|
|
16
|
-
* 2
|
|
17
|
-
* 3
|
|
18
|
-
* 4
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* 8. Optionally pull requirements
|
|
12
|
+
* Requirements:
|
|
13
|
+
* - LINK-1: Requires existing .requirements/ folder
|
|
14
|
+
* - LINK-2: Offers refresh when credentials exist
|
|
15
|
+
* - LINK-3: Uses inline OAuth (no persistent tokens)
|
|
16
|
+
* - LINK-4, LINK-5: Team and project selection
|
|
17
|
+
* - LINK-6: Saves credentials to project-settings.json
|
|
18
|
+
* - LINK-7: Optionally pulls requirements
|
|
19
|
+
* - LINK-8: Handles network errors gracefully
|
|
23
20
|
*/
|
|
24
21
|
export async function linkCommand() {
|
|
25
22
|
try {
|
|
26
|
-
console.log(`Linking to
|
|
23
|
+
console.log(`Linking to ${brand} cloud...\n`);
|
|
27
24
|
const cwd = process.cwd();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
// LINK-1: Check if .requirements/ folder exists
|
|
26
|
+
const projectRoot = findProjectRoot(cwd);
|
|
27
|
+
if (!projectRoot || projectRoot !== cwd) {
|
|
28
|
+
console.log('No .requirements/ folder found.');
|
|
29
|
+
console.log('Run "dotrequirements init" to initialize a project first.\n');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// LINK-2: Check for existing credentials and offer refresh
|
|
33
|
+
let existingCredentials;
|
|
34
|
+
try {
|
|
35
|
+
existingCredentials = readProjectSettings(projectRoot);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Invalid settings - treat as no credentials
|
|
39
|
+
existingCredentials = undefined;
|
|
40
|
+
}
|
|
41
|
+
if (existingCredentials) {
|
|
42
|
+
const shouldContinue = await handleExistingCredentials(existingCredentials);
|
|
43
|
+
if (!shouldContinue) {
|
|
46
44
|
return;
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
// LINK-3: Inline OAuth (no persistent tokens)
|
|
48
|
+
let oauthResult;
|
|
49
|
+
try {
|
|
50
|
+
oauthResult = await executeOAuthFlow();
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
|
|
55
|
+
console.log('Please try again later.\n');
|
|
56
|
+
return;
|
|
53
57
|
}
|
|
58
|
+
// Set up authenticated client using the session token
|
|
54
59
|
const client = new ConvexHttpClient(getConvexUrl());
|
|
55
|
-
client.setAuth(
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
-
console.log('
|
|
60
|
+
client.setAuth(oauthResult.accessToken);
|
|
61
|
+
// LINK-4: Select team
|
|
62
|
+
const selectedTeam = await selectTeam(client);
|
|
63
|
+
if (!selectedTeam) {
|
|
64
|
+
console.log('Link cancelled.');
|
|
60
65
|
return;
|
|
61
66
|
}
|
|
62
|
-
//
|
|
63
|
-
const selectedProject = await selectProject(client,
|
|
67
|
+
// LINK-5: Select project
|
|
68
|
+
const selectedProject = await selectProject(client, selectedTeam.teamId);
|
|
64
69
|
if (!selectedProject) {
|
|
65
|
-
|
|
70
|
+
// selectProject already shows message about no projects
|
|
66
71
|
return;
|
|
67
72
|
}
|
|
68
|
-
//
|
|
73
|
+
// AUTHZ-3: Prompt for secret expiry (paid tier only)
|
|
74
|
+
const expiryDays = await promptExpiryDays(selectedTeam.tier);
|
|
75
|
+
// LINK-5.3: Get or create secret for this project
|
|
69
76
|
console.log('\nGetting project credentials...');
|
|
70
|
-
const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId);
|
|
71
|
-
const
|
|
77
|
+
const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId, expiryDays);
|
|
78
|
+
const projectId = selectedProject.projectSlug || selectedProject.projectId;
|
|
72
79
|
const secret = secretResult.secret;
|
|
73
80
|
if (secretResult.created) {
|
|
74
81
|
console.log('✓ Created new project secret');
|
|
@@ -76,23 +83,23 @@ export async function linkCommand() {
|
|
|
76
83
|
else {
|
|
77
84
|
console.log('✓ Retrieved existing project secret');
|
|
78
85
|
}
|
|
79
|
-
// 6
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
// 7. Optionally pull requirements
|
|
86
|
+
// LINK-6: Write credentials to project-settings.json
|
|
87
|
+
writeProjectSettings(projectRoot, { projectId, projectSecret: secret });
|
|
88
|
+
console.log('✓ Wrote credentials to .requirements/project-settings.json');
|
|
89
|
+
// LINK-7: Optionally pull requirements
|
|
85
90
|
const shouldPull = await promptConfirm('Pull requirements from cloud now?', true);
|
|
86
91
|
if (shouldPull) {
|
|
87
92
|
console.log('\nPulling requirements...');
|
|
88
|
-
await pullCommand({ project:
|
|
93
|
+
await pullCommand({ project: projectId });
|
|
89
94
|
}
|
|
95
|
+
// LINK-7.2: Success message
|
|
90
96
|
console.log(`\n✓ Successfully linked to project: ${selectedProject.projectName}`);
|
|
91
|
-
console.log(` Project ID: ${
|
|
97
|
+
console.log(` Project ID: ${projectId}`);
|
|
92
98
|
console.log('\nDocumentation: https://docs.dotrequirements.io/getting-started');
|
|
93
99
|
}
|
|
94
100
|
catch (error) {
|
|
95
101
|
console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
102
|
+
// LINK-8: Network error guidance
|
|
96
103
|
if (error instanceof Error && error.message.includes('fetch')) {
|
|
97
104
|
console.error('\nCheck your internet connection and try again.');
|
|
98
105
|
console.error('If the problem persists, visit https://status.convex.dev');
|
|
@@ -101,56 +108,24 @@ export async function linkCommand() {
|
|
|
101
108
|
}
|
|
102
109
|
}
|
|
103
110
|
/**
|
|
104
|
-
*
|
|
105
|
-
* Returns true if
|
|
106
|
-
* Returns false if user
|
|
111
|
+
* LINK-2: Handle existing credentials - offer refresh or connect to different project
|
|
112
|
+
* Returns true if user wants to continue (either refresh or different project)
|
|
113
|
+
* Returns false if user cancels
|
|
107
114
|
*/
|
|
108
|
-
async function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
];
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const hasProjectSecret = existingContent.includes('DOTREQUIREMENTS_PROJECT_SECRET');
|
|
118
|
-
if (hasProjectId || hasProjectSecret) {
|
|
119
|
-
const shouldUpdate = await promptConfirm(`.env.local already contains ${brand} configuration. Update it?`, true);
|
|
120
|
-
if (!shouldUpdate) {
|
|
121
|
-
console.log('Aborted.');
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
// Remove old dotrequirements lines
|
|
125
|
-
const lines = existingContent.split('\n');
|
|
126
|
-
const filteredLines = lines.filter(line => !line.includes('DOTREQUIREMENTS_PROJECT_ID') &&
|
|
127
|
-
!line.includes('DOTREQUIREMENTS_PROJECT_SECRET') &&
|
|
128
|
-
!(line.trim() === '# dotrequirements configuration') &&
|
|
129
|
-
!(line.trim() === '# dot•requirements configuration'));
|
|
130
|
-
const updatedContent = filteredLines.join('\n').trimEnd() + '\n\n' + newEnvLines.join('\n') + '\n';
|
|
131
|
-
fs.writeFileSync(envPath, updatedContent);
|
|
132
|
-
console.log(`✓ Updated ${brand} configuration in .env.local`);
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
const shouldAppend = await promptConfirm(`.env.local already exists. Add ${brand} configuration to it?`, true);
|
|
137
|
-
if (!shouldAppend) {
|
|
138
|
-
console.log('\nSkipped writing to .env.local. Please add these values manually:');
|
|
139
|
-
console.log(` DOTREQUIREMENTS_PROJECT_ID=${projectId}`);
|
|
140
|
-
console.log(` DOTREQUIREMENTS_PROJECT_SECRET=${secret}\n`);
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
const appendContent = '\n' + newEnvLines.join('\n') + '\n';
|
|
144
|
-
fs.appendFileSync(envPath, appendContent);
|
|
145
|
-
console.log(`✓ Added ${brand} configuration to .env.local`);
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
const envContent = newEnvLines.join('\n') + '\n';
|
|
151
|
-
fs.writeFileSync(envPath, envContent);
|
|
152
|
-
console.log('✓ Wrote configuration to .env.local');
|
|
153
|
-
return true;
|
|
115
|
+
async function handleExistingCredentials(credentials) {
|
|
116
|
+
console.log(`Currently linked to: ${credentials.projectId}\n`);
|
|
117
|
+
const choice = await promptChoice('What would you like to do?', [
|
|
118
|
+
{ title: 'Refresh credentials for this project', value: 'refresh' },
|
|
119
|
+
{ title: 'Connect to a different project', value: 'different' },
|
|
120
|
+
]);
|
|
121
|
+
if (!choice) {
|
|
122
|
+
console.log('Link cancelled.');
|
|
123
|
+
return false;
|
|
154
124
|
}
|
|
125
|
+
// Both options continue with OAuth flow
|
|
126
|
+
// For 'refresh', we'll match on the same project after team/project selection
|
|
127
|
+
// For 'different', user selects any project
|
|
128
|
+
// The flow is the same - user authenticates and selects team/project
|
|
129
|
+
return true;
|
|
155
130
|
}
|
|
156
131
|
//# sourceMappingURL=link.js.map
|
|
@@ -1,98 +1,71 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { join
|
|
4
|
+
import { join } from 'path';
|
|
5
5
|
import prompts from 'prompts';
|
|
6
|
-
import {
|
|
7
|
-
import { loadPlatformTemplate, loadTestWritingTemplate, loadOverviewTemplate } from '../utils/templates.js';
|
|
6
|
+
import { loadTemplate } from '../utils/templates.js';
|
|
8
7
|
import { brand } from '../utils/brand.js';
|
|
8
|
+
import { getContextFileName, getContextFilePath, findDotrequirementsSection, appendOrUpdateSection, } from '../utils/context-file.js';
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Load the context file section template
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
'
|
|
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
|
-
}
|
|
12
|
+
function loadContextFileSection() {
|
|
13
|
+
return loadTemplate('context-file-section.md');
|
|
31
14
|
}
|
|
32
15
|
/**
|
|
33
|
-
* Install
|
|
16
|
+
* Install workflow guidance to the appropriate context file for a platform
|
|
34
17
|
*/
|
|
35
|
-
async function
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
mkdirSync(skillDir, { recursive: true });
|
|
18
|
+
async function installContextFileSection(platform) {
|
|
19
|
+
const contextFilePath = await getContextFilePath(platform);
|
|
20
|
+
if (!contextFilePath) {
|
|
21
|
+
console.error(`\n❌ Could not determine context file path for ${platform}`);
|
|
22
|
+
return;
|
|
41
23
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
24
|
+
const fileName = getContextFileName(platform);
|
|
25
|
+
const sectionContent = loadContextFileSection();
|
|
26
|
+
// Check if file exists and has existing section
|
|
27
|
+
let existingSection = null;
|
|
28
|
+
if (existsSync(contextFilePath)) {
|
|
29
|
+
const content = readFileSync(contextFilePath, 'utf-8');
|
|
30
|
+
const section = findDotrequirementsSection(content);
|
|
31
|
+
if (section) {
|
|
32
|
+
existingSection = section.content;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Prompt user
|
|
36
|
+
const action = existingSection ? 'Update' : existsSync(contextFilePath) ? 'Add to' : 'Create';
|
|
49
37
|
const response = await prompts({
|
|
50
38
|
type: 'confirm',
|
|
51
39
|
name: 'install',
|
|
52
|
-
message:
|
|
40
|
+
message: `${action} ${fileName} with dotrequirements workflow guidance?`,
|
|
53
41
|
initial: true,
|
|
54
42
|
});
|
|
55
|
-
|
|
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.');
|
|
43
|
+
if (!response.install) {
|
|
44
|
+
console.log('');
|
|
65
45
|
return;
|
|
66
46
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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('');
|
|
47
|
+
// If updating, show what will change
|
|
48
|
+
if (existingSection) {
|
|
49
|
+
const confirmUpdate = await prompts({
|
|
50
|
+
type: 'confirm',
|
|
51
|
+
name: 'confirm',
|
|
52
|
+
message: 'Existing dotrequirements section found. Replace it?',
|
|
53
|
+
initial: true,
|
|
54
|
+
});
|
|
55
|
+
if (!confirmUpdate.confirm) {
|
|
56
|
+
console.log(' Skipped.\n');
|
|
57
|
+
return;
|
|
94
58
|
}
|
|
95
59
|
}
|
|
60
|
+
try {
|
|
61
|
+
const result = appendOrUpdateSection(contextFilePath, sectionContent);
|
|
62
|
+
const actionVerb = result.action === 'created' ? 'Created' : result.action === 'appended' ? 'Added to' : 'Updated';
|
|
63
|
+
console.log(`✅ ${actionVerb} ${contextFilePath}\n`);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(`❌ Failed to update ${fileName}`);
|
|
67
|
+
console.log(` Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
68
|
+
}
|
|
96
69
|
}
|
|
97
70
|
/**
|
|
98
71
|
* MCP setup command - configures the MCP server for AI assistants
|
|
@@ -175,7 +148,7 @@ async function setupClaudeCode() {
|
|
|
175
148
|
if (mcpConfigured) {
|
|
176
149
|
console.log('\n⚠️ Important: Restart Claude Code for changes to take effect.');
|
|
177
150
|
console.log('\nYou can now ask your AI assistant to help write requirements!\n');
|
|
178
|
-
await
|
|
151
|
+
await installContextFileSection('claude-code');
|
|
179
152
|
}
|
|
180
153
|
}
|
|
181
154
|
async function setupClaudeDesktop() {
|
|
@@ -285,7 +258,7 @@ async function setupCursor() {
|
|
|
285
258
|
console.log(` Config file: ${configPath}\n`);
|
|
286
259
|
console.log('⚠️ Important: Reload or restart Cursor for changes to take effect.');
|
|
287
260
|
console.log('\nYou can now ask your AI assistant to help write requirements!\n');
|
|
288
|
-
await
|
|
261
|
+
await installContextFileSection('cursor');
|
|
289
262
|
}
|
|
290
263
|
catch (error) {
|
|
291
264
|
console.error('❌ Failed to configure MCP automatically.');
|
|
@@ -379,7 +352,7 @@ async function setupAntigravity() {
|
|
|
379
352
|
else {
|
|
380
353
|
console.log('');
|
|
381
354
|
}
|
|
382
|
-
await
|
|
355
|
+
await installContextFileSection('antigravity');
|
|
383
356
|
}
|
|
384
357
|
catch (error) {
|
|
385
358
|
console.error('❌ Failed to configure MCP automatically.');
|
|
@@ -396,7 +369,7 @@ async function setupCodex() {
|
|
|
396
369
|
console.log('\n✅ MCP configured successfully for Codex!');
|
|
397
370
|
console.log('\n⚠️ Important: Restart Codex for changes to take effect.');
|
|
398
371
|
console.log('\nYou can now ask your AI assistant to help write requirements!\n');
|
|
399
|
-
await
|
|
372
|
+
await installContextFileSection('codex');
|
|
400
373
|
}
|
|
401
374
|
catch (error) {
|
|
402
375
|
console.error('\n❌ Failed to configure MCP automatically.');
|
|
@@ -413,24 +386,27 @@ async function setupGitHubCopilot() {
|
|
|
413
386
|
console.log('ℹ️ GitHub Copilot discovers MCP servers from your IDE configuration.');
|
|
414
387
|
console.log(' Please configure the MCP server in VS Code settings or GitHub settings.\n');
|
|
415
388
|
console.log(' MCP server command: npx -y @popoverai/dotrequirements mcp\n');
|
|
416
|
-
await
|
|
389
|
+
await installContextFileSection('github-copilot');
|
|
417
390
|
}
|
|
418
391
|
async function setupOther() {
|
|
419
|
-
console.log('\n📦 Custom
|
|
420
|
-
console.log('ℹ️
|
|
421
|
-
console.log(' Common
|
|
422
|
-
console.log(' - .
|
|
423
|
-
console.log(' - .
|
|
424
|
-
console.log(' - .
|
|
425
|
-
console.log(' - .codex/skills/ (Codex)');
|
|
426
|
-
console.log(' - .github/skills/ (GitHub Copilot)\n');
|
|
392
|
+
console.log('\n📦 Custom Context File Setup\n');
|
|
393
|
+
console.log('ℹ️ Workflow guidance is added to a context file at your project root.');
|
|
394
|
+
console.log(' Common file names by platform:');
|
|
395
|
+
console.log(' - CLAUDE.md (Claude Code)');
|
|
396
|
+
console.log(' - AGENTS.md (Cursor, Codex, GitHub Copilot)');
|
|
397
|
+
console.log(' - GEMINI.md (Antigravity)\n');
|
|
427
398
|
const response = await prompts({
|
|
428
|
-
type: '
|
|
429
|
-
name: '
|
|
430
|
-
message: '
|
|
431
|
-
|
|
399
|
+
type: 'select',
|
|
400
|
+
name: 'file',
|
|
401
|
+
message: 'Which context file should we update?',
|
|
402
|
+
choices: [
|
|
403
|
+
{ title: 'AGENTS.md (most compatible)', value: 'AGENTS.md' },
|
|
404
|
+
{ title: 'CLAUDE.md', value: 'CLAUDE.md' },
|
|
405
|
+
{ title: 'GEMINI.md', value: 'GEMINI.md' },
|
|
406
|
+
],
|
|
407
|
+
initial: 0,
|
|
432
408
|
});
|
|
433
|
-
if (!response.
|
|
409
|
+
if (!response.file) {
|
|
434
410
|
console.log('\nSetup cancelled.');
|
|
435
411
|
return;
|
|
436
412
|
}
|
|
@@ -438,6 +414,13 @@ async function setupOther() {
|
|
|
438
414
|
console.log('Configure your AI assistant to run the MCP server:');
|
|
439
415
|
console.log(' Command: npx');
|
|
440
416
|
console.log(' Args: -y @popoverai/dotrequirements mcp\n');
|
|
441
|
-
|
|
417
|
+
// Determine platform based on file choice for the install function
|
|
418
|
+
const platformMap = {
|
|
419
|
+
'CLAUDE.md': 'claude-code',
|
|
420
|
+
'AGENTS.md': 'cursor', // Any AGENTS.md platform works
|
|
421
|
+
'GEMINI.md': 'antigravity',
|
|
422
|
+
};
|
|
423
|
+
const platform = platformMap[response.file];
|
|
424
|
+
await installContextFileSection(platform);
|
|
442
425
|
}
|
|
443
426
|
//# sourceMappingURL=mcp-setup.js.map
|
package/dist/commands/pull.js
CHANGED
|
@@ -4,36 +4,21 @@ import { ConvexHttpClient } from 'convex/browser';
|
|
|
4
4
|
import { getConvexUrl } from '../config.js';
|
|
5
5
|
import { buildRequirementsFile } from '../schema/index.js';
|
|
6
6
|
import { api } from '../convex.js';
|
|
7
|
-
import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
|
|
8
7
|
import { brand } from '../utils/brand.js';
|
|
8
|
+
import { findRequirementsFiles } from '../mcp/requirements.js';
|
|
9
|
+
import { getProjectCredentials } from '../utils/project-settings.js';
|
|
9
10
|
export async function pullCommand(options) {
|
|
10
11
|
try {
|
|
11
12
|
console.log(`Syncing requirements from ${brand} cloud...`);
|
|
12
13
|
// Connect to Convex
|
|
13
14
|
const convexUrl = getConvexUrl();
|
|
14
15
|
const client = new ConvexHttpClient(convexUrl);
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
}
|
|
16
|
+
// Get project credentials from .requirements/project-settings.json
|
|
17
|
+
// This throws helpful errors if project not found or not connected to cloud
|
|
18
|
+
const credentials = getProjectCredentials();
|
|
19
|
+
// Allow --project flag to override, but still need secret from settings
|
|
20
|
+
const projectId = options.project ?? credentials.projectId;
|
|
21
|
+
const projectSecret = credentials.projectSecret;
|
|
37
22
|
// Fetch documents
|
|
38
23
|
let documents;
|
|
39
24
|
if (options.document) {
|
|
@@ -66,13 +51,13 @@ export async function pullCommand(options) {
|
|
|
66
51
|
console.log('No documents found.');
|
|
67
52
|
return;
|
|
68
53
|
}
|
|
69
|
-
// Ensure .requirements/ directory exists
|
|
54
|
+
// Ensure .requirements/ directory exists (for new documents)
|
|
70
55
|
const requirementsDir = path.join(process.cwd(), '.requirements');
|
|
71
56
|
if (!fs.existsSync(requirementsDir)) {
|
|
72
57
|
fs.mkdirSync(requirementsDir, { recursive: true });
|
|
73
58
|
}
|
|
74
|
-
// Build index of existing files by document ID
|
|
75
|
-
const existingFilesByDocId = buildDocumentIdIndex(
|
|
59
|
+
// Build index of existing files by document ID (search entire workspace)
|
|
60
|
+
const existingFilesByDocId = await buildDocumentIdIndex(process.cwd());
|
|
76
61
|
// Write each document as a Markdown file
|
|
77
62
|
for (const doc of documents) {
|
|
78
63
|
// Check if an existing file has this document ID
|
|
@@ -81,9 +66,7 @@ export async function pullCommand(options) {
|
|
|
81
66
|
const fileName = path.basename(filePath);
|
|
82
67
|
// Build metadata with pulledAt for conflict detection
|
|
83
68
|
// 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
69
|
const metadata = {
|
|
86
|
-
projectId: projectId,
|
|
87
70
|
pulledAt: new Date().toISOString(),
|
|
88
71
|
version: doc.version,
|
|
89
72
|
document: {
|
|
@@ -114,27 +97,16 @@ function sanitizeFileName(title) {
|
|
|
114
97
|
.replace(/[^a-z0-9]+/g, '-')
|
|
115
98
|
.replace(/^-+|-+$/g, '');
|
|
116
99
|
}
|
|
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
100
|
/**
|
|
124
101
|
* Build an index mapping document IDs to their file paths.
|
|
125
|
-
* Scans existing .requirements.md files and extracts document.id from frontmatter.
|
|
102
|
+
* Scans existing .requirements.md files across the entire workspace and extracts document.id from frontmatter.
|
|
103
|
+
* SYNC-DISCOVERY-2.0: Uses the same discovery logic as push and MCP.
|
|
126
104
|
*/
|
|
127
|
-
function buildDocumentIdIndex(
|
|
105
|
+
async function buildDocumentIdIndex(workspaceRoot) {
|
|
128
106
|
const index = new Map();
|
|
129
|
-
|
|
130
|
-
|
|
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);
|
|
107
|
+
// Use the same file discovery as MCP and push
|
|
108
|
+
const allFiles = await findRequirementsFiles(workspaceRoot);
|
|
109
|
+
for (const filePath of allFiles) {
|
|
138
110
|
const docId = extractDocumentIdFromFile(filePath);
|
|
139
111
|
if (docId) {
|
|
140
112
|
index.set(docId, filePath);
|
package/dist/commands/push.js
CHANGED
|
@@ -5,29 +5,16 @@ import { getConvexUrl } from '../config.js';
|
|
|
5
5
|
import { parseRequirementsFromFile, getAllRequirements, parseRequirementKey, buildRequirementsFile } from '../schema/index.js';
|
|
6
6
|
import { api } from '../convex.js';
|
|
7
7
|
import * as readline from 'readline';
|
|
8
|
-
import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
|
|
9
8
|
import { brand } from '../utils/brand.js';
|
|
9
|
+
import { findRequirementsFiles } from '../mcp/requirements.js';
|
|
10
|
+
import { getProjectCredentials } from '../utils/project-settings.js';
|
|
10
11
|
export async function pushCommand(file, options) {
|
|
11
12
|
try {
|
|
12
13
|
console.log(`Preparing to push requirements to ${brand} cloud...\n`);
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
const projectSecret =
|
|
16
|
-
if (!projectId || !projectSecret) {
|
|
17
|
-
throw new Error('Project credentials not found. Either:\n' +
|
|
18
|
-
' 1. Run "dotreq init" to initialize the project, or\n' +
|
|
19
|
-
' 2. Set DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET in .env.local');
|
|
20
|
-
}
|
|
21
|
-
// AUTHZ-2.0: Check if project is local-only
|
|
22
|
-
if (isLocalOnlyProject(projectId)) {
|
|
23
|
-
throw new Error(CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE);
|
|
24
|
-
}
|
|
14
|
+
// Get project credentials from .requirements/project-settings.json
|
|
15
|
+
// This throws helpful errors if project not found or not connected to cloud
|
|
16
|
+
const { projectId, projectSecret } = getProjectCredentials();
|
|
25
17
|
// Determine which files to push
|
|
26
|
-
const requirementsDir = path.join(process.cwd(), '.requirements');
|
|
27
|
-
if (!fs.existsSync(requirementsDir)) {
|
|
28
|
-
console.log('No .requirements/ directory found. Nothing to push.');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
18
|
let filesToPush;
|
|
32
19
|
if (file) {
|
|
33
20
|
// Push specific file
|
|
@@ -38,13 +25,11 @@ export async function pushCommand(file, options) {
|
|
|
38
25
|
filesToPush = [filePath];
|
|
39
26
|
}
|
|
40
27
|
else {
|
|
41
|
-
// Push all *.requirements.md files in .requirements/
|
|
42
|
-
const
|
|
43
|
-
filesToPush =
|
|
44
|
-
.filter(f => f.endsWith('.requirements.md'))
|
|
45
|
-
.map(f => path.join(requirementsDir, f));
|
|
28
|
+
// SYNC-DISCOVERY-1.0: Push all *.requirements.md files in workspace (not just .requirements/)
|
|
29
|
+
const workspaceRoot = process.cwd();
|
|
30
|
+
filesToPush = await findRequirementsFiles(workspaceRoot);
|
|
46
31
|
if (filesToPush.length === 0) {
|
|
47
|
-
console.log('No *.requirements.md files found
|
|
32
|
+
console.log('No *.requirements.md files found. Nothing to push.');
|
|
48
33
|
return;
|
|
49
34
|
}
|
|
50
35
|
}
|