@popoverai/dotrequirements 0.17.2 → 0.19.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 +18 -0
- package/dist/cli.js +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +126 -5
- package/dist/commands/mcp.js +3 -2
- package/dist/convex.d.ts +8 -0
- package/dist/convex.js +8 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +2 -2
- package/dist/templates/context-file-section.md +12 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,8 +96,26 @@ Initialize a new project. Creates `.requirements/` directory, example files, and
|
|
|
96
96
|
```bash
|
|
97
97
|
dotreq init
|
|
98
98
|
dotreq init --name my-project
|
|
99
|
+
dotreq init --invite abc123xyz
|
|
99
100
|
```
|
|
100
101
|
|
|
102
|
+
**Options:**
|
|
103
|
+
|
|
104
|
+
| Option | Description |
|
|
105
|
+
|--------|-------------|
|
|
106
|
+
| `--name <name>` | Set project name (skips prompt) |
|
|
107
|
+
| `--invite <token>` | Join a team using an invite token |
|
|
108
|
+
|
|
109
|
+
**Joining a team via invite:**
|
|
110
|
+
|
|
111
|
+
If a team admin shares an invite command with you, join the team and connect in one step:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx dotrequirements init --invite abc123xyz
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This shows the team name, prompts you to sign in, adds you to the team, and lets you select a project.
|
|
118
|
+
|
|
101
119
|
### `dotreq link`
|
|
102
120
|
|
|
103
121
|
Link your local environment to an existing project (when you already have a project in dot•requirements cloud).
|
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,7 @@ program
|
|
|
47
47
|
.command('init')
|
|
48
48
|
.description('Initialize a new dotrequirements project')
|
|
49
49
|
.option('-n, --name <name>', 'Project name (defaults to package.json name or directory name)')
|
|
50
|
+
.option('-i, --invite <token>', 'Join a team using an invite token')
|
|
50
51
|
.action(wrapCommand(initCommand));
|
|
51
52
|
program
|
|
52
53
|
.command('link')
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ensureGitignore } from '../utils/gitignore.js';
|
|
|
5
5
|
import { generateRequirementsReadme } from '../templates/requirements-readme.js';
|
|
6
6
|
import { generateExampleRequirements } from '../templates/example-requirements.js';
|
|
7
7
|
import { promptChoice, promptConfirm } from '../utils/prompts.js';
|
|
8
|
+
import { mcpSetupCommand } from './mcp-setup.js';
|
|
8
9
|
import { pullCommand } from './pull.js';
|
|
9
10
|
import { executeOAuthFlow } from '../utils/oauth-flow.js';
|
|
10
11
|
import { ConvexHttpClient } from 'convex/browser';
|
|
@@ -24,6 +25,19 @@ export async function initCommand(options = {}) {
|
|
|
24
25
|
console.log('Use "dotrequirements link" to connect or reconnect to cloud.\n');
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
28
|
+
// MEMBERSHIP-18: If invite token provided, use invite flow
|
|
29
|
+
if (options.invite) {
|
|
30
|
+
const initSucceeded = await inviteFlow(cwd, options.invite);
|
|
31
|
+
if (initSucceeded) {
|
|
32
|
+
console.log('\nDocumentation: https://docs.dotrequirements.io/getting-started');
|
|
33
|
+
console.log('By using this service, you agree to our Terms: https://app.dotrequirements.io/terms');
|
|
34
|
+
const setupMcp = await promptConfirm('\nConfigure AI assistant integration?', true);
|
|
35
|
+
if (setupMcp) {
|
|
36
|
+
await mcpSetupCommand();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
27
41
|
// INIT-1: Ask user whether to authenticate
|
|
28
42
|
const authChoice = await promptChoice('Do you want to authenticate before initializing a project? (Recommended)', [
|
|
29
43
|
{ title: 'Yes', value: 'yes' },
|
|
@@ -34,16 +48,24 @@ export async function initCommand(options = {}) {
|
|
|
34
48
|
console.log('Initialization cancelled.');
|
|
35
49
|
return;
|
|
36
50
|
}
|
|
51
|
+
let initSucceeded;
|
|
37
52
|
if (authChoice === 'no') {
|
|
38
53
|
// INIT-3: Local-only workflow
|
|
39
|
-
await localOnlyFlow(cwd);
|
|
54
|
+
initSucceeded = await localOnlyFlow(cwd);
|
|
40
55
|
}
|
|
41
56
|
else {
|
|
42
57
|
// INIT-17+: Authenticated workflow
|
|
43
|
-
await authenticatedFlow(cwd, options);
|
|
58
|
+
initSucceeded = await authenticatedFlow(cwd, options);
|
|
44
59
|
}
|
|
45
60
|
console.log('\nDocumentation: https://docs.dotrequirements.io/getting-started');
|
|
46
61
|
console.log('By using this service, you agree to our Terms: https://app.dotrequirements.io/terms');
|
|
62
|
+
// Offer to configure AI assistant integration after successful init
|
|
63
|
+
if (initSucceeded) {
|
|
64
|
+
const setupMcp = await promptConfirm('\nConfigure AI assistant integration?', true);
|
|
65
|
+
if (setupMcp) {
|
|
66
|
+
await mcpSetupCommand();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
47
69
|
}
|
|
48
70
|
catch (error) {
|
|
49
71
|
console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -57,6 +79,7 @@ export async function initCommand(options = {}) {
|
|
|
57
79
|
}
|
|
58
80
|
/**
|
|
59
81
|
* INIT-3: Local-only workflow - creates folder structure without credentials
|
|
82
|
+
* Returns true if project was created successfully
|
|
60
83
|
*/
|
|
61
84
|
async function localOnlyFlow(cwd) {
|
|
62
85
|
console.log('\nInitializing local project...');
|
|
@@ -84,9 +107,11 @@ async function localOnlyFlow(cwd) {
|
|
|
84
107
|
console.log(' 1. Create requirements in .requirements/ directory');
|
|
85
108
|
console.log(' 2. Add the test harness to your test setup (see README)');
|
|
86
109
|
console.log(' 3. Run "dotrequirements link" to connect to cloud features');
|
|
110
|
+
return true;
|
|
87
111
|
}
|
|
88
112
|
/**
|
|
89
113
|
* INIT-17, INIT-18, INIT-7, INIT-4, INIT-6: Authenticated workflow
|
|
114
|
+
* Returns true if project was created/connected successfully
|
|
90
115
|
*/
|
|
91
116
|
async function authenticatedFlow(cwd, options) {
|
|
92
117
|
// INIT-17: Inline OAuth (no persistent tokens)
|
|
@@ -100,7 +125,7 @@ async function authenticatedFlow(cwd, options) {
|
|
|
100
125
|
// INIT-17.3: OAuth failed
|
|
101
126
|
console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
|
|
102
127
|
console.log('Please try again later.\n');
|
|
103
|
-
return;
|
|
128
|
+
return false;
|
|
104
129
|
}
|
|
105
130
|
// Set up authenticated client using the session token
|
|
106
131
|
const client = new ConvexHttpClient(getConvexUrl());
|
|
@@ -110,13 +135,13 @@ async function authenticatedFlow(cwd, options) {
|
|
|
110
135
|
if (selectedTeam === undefined) {
|
|
111
136
|
// User cancelled
|
|
112
137
|
console.log('Initialization cancelled.');
|
|
113
|
-
return;
|
|
138
|
+
return false;
|
|
114
139
|
}
|
|
115
140
|
// INIT-7: Choose between create and connect
|
|
116
141
|
const projectChoice = await chooseProjectAction(client, selectedTeam.teamId);
|
|
117
142
|
if (!projectChoice) {
|
|
118
143
|
console.log('Initialization cancelled.');
|
|
119
|
-
return;
|
|
144
|
+
return false;
|
|
120
145
|
}
|
|
121
146
|
if (projectChoice === 'create') {
|
|
122
147
|
await createNewProjectFlow(cwd, client, selectedTeam, options);
|
|
@@ -124,6 +149,7 @@ async function authenticatedFlow(cwd, options) {
|
|
|
124
149
|
else {
|
|
125
150
|
await connectExistingProjectFlow(cwd, client, selectedTeam);
|
|
126
151
|
}
|
|
152
|
+
return true;
|
|
127
153
|
}
|
|
128
154
|
/**
|
|
129
155
|
* INIT-18: Prompt user to select a team after OAuth
|
|
@@ -277,6 +303,101 @@ async function connectExistingProjectFlow(cwd, client, team) {
|
|
|
277
303
|
console.log(' 1. Review the requirements in .requirements/');
|
|
278
304
|
console.log(' 2. Add the test harness to your test setup (see README)');
|
|
279
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* MEMBERSHIP-18: Invite flow - join a team using an invite token
|
|
308
|
+
* Returns true if successfully joined and connected
|
|
309
|
+
*/
|
|
310
|
+
async function inviteFlow(cwd, token) {
|
|
311
|
+
// MEMBERSHIP-18.0: Validate token before auth (no auth needed for this query)
|
|
312
|
+
const client = new ConvexHttpClient(getConvexUrl());
|
|
313
|
+
console.log('Validating invite token...');
|
|
314
|
+
const inviteInfo = await client.query(api.teamInvites.queries.getInviteInfo, { token });
|
|
315
|
+
// MEMBERSHIP-18.3: Handle invalid tokens
|
|
316
|
+
if (!inviteInfo.valid) {
|
|
317
|
+
const errorMessages = {
|
|
318
|
+
invalid: 'This invite token is not valid.',
|
|
319
|
+
expired: 'This invite token has expired.',
|
|
320
|
+
already_used: 'This invite token has already been used.',
|
|
321
|
+
};
|
|
322
|
+
const message = errorMessages[inviteInfo.error] || 'Invalid invite token.';
|
|
323
|
+
console.error(`\n✗ ${message}`);
|
|
324
|
+
console.log('Please ask the team admin for a new invite link.\n');
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
console.log(`\nYou've been invited to join team "${inviteInfo.teamName}"\n`);
|
|
328
|
+
// MEMBERSHIP-18.1: Auth flow
|
|
329
|
+
let oauthResult;
|
|
330
|
+
try {
|
|
331
|
+
oauthResult = await executeOAuthFlow();
|
|
332
|
+
console.log();
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
|
|
336
|
+
console.log('Please try again later.\n');
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
// Set up authenticated client
|
|
340
|
+
client.setAuth(oauthResult.accessToken);
|
|
341
|
+
// Ensure default team (this also claims any pending email invites via MEMBERSHIP-4)
|
|
342
|
+
await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
|
|
343
|
+
// MEMBERSHIP-18.1: Accept invite (joins team, marks token used)
|
|
344
|
+
// MEMBERSHIP-18.2: If already a member, continue gracefully
|
|
345
|
+
try {
|
|
346
|
+
await client.action(api.polar.acceptTeamInvite, { token });
|
|
347
|
+
console.log(`✓ Joined team "${inviteInfo.teamName}"`);
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
351
|
+
if (errorMessage.includes('already a member')) {
|
|
352
|
+
console.log(`Already a member of "${inviteInfo.teamName}"`);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// MEMBERSHIP-18.1.0: Select project from team
|
|
359
|
+
const teamId = inviteInfo.teamId;
|
|
360
|
+
// Get team tier for secret expiry prompt
|
|
361
|
+
const teams = await client.query(api.teams.queries.listForUserWithUsage);
|
|
362
|
+
const team = teams.find((t) => t.teamId === teamId);
|
|
363
|
+
const tier = team?.tier || 'free';
|
|
364
|
+
const selectedProject = await selectProject(client, teamId);
|
|
365
|
+
if (!selectedProject) {
|
|
366
|
+
console.log('Initialization cancelled.');
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
// AUTHZ-3: Prompt for secret expiry (paid tier only)
|
|
370
|
+
const expiryDays = await promptExpiryDays(tier);
|
|
371
|
+
// Get or create secret for this project
|
|
372
|
+
console.log('\nGetting project credentials...');
|
|
373
|
+
const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId, expiryDays);
|
|
374
|
+
const projectId = selectedProject.projectSlug || selectedProject.projectId;
|
|
375
|
+
const secret = secretResult.secret;
|
|
376
|
+
if (secretResult.created) {
|
|
377
|
+
console.log('✓ Created new project secret');
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
console.log('✓ Retrieved existing project secret');
|
|
381
|
+
}
|
|
382
|
+
// Write credentials to project-settings.json
|
|
383
|
+
writeProjectSettings(cwd, { projectId, projectSecret: secret });
|
|
384
|
+
console.log('✓ Wrote credentials to .requirements/project-settings.json');
|
|
385
|
+
// Create directory structure
|
|
386
|
+
await createRequirementsDirectory(cwd, projectId);
|
|
387
|
+
// Update .gitignore
|
|
388
|
+
ensureGitignore(cwd);
|
|
389
|
+
console.log('✓ Updated .gitignore\n');
|
|
390
|
+
// Pull existing requirements from cloud
|
|
391
|
+
console.log('Pulling requirements from cloud...');
|
|
392
|
+
await pullCommand({ project: projectId });
|
|
393
|
+
console.log(`\n✓ Successfully connected to project: ${selectedProject.projectName}`);
|
|
394
|
+
console.log(` Project ID: ${projectId}`);
|
|
395
|
+
// Show guidance
|
|
396
|
+
console.log('\nNext steps:');
|
|
397
|
+
console.log(' 1. Review the requirements in .requirements/');
|
|
398
|
+
console.log(' 2. Add the test harness to your test setup (see README)');
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
280
401
|
/**
|
|
281
402
|
* INIT-5: Create .requirements/ directory with README and example
|
|
282
403
|
*/
|
package/dist/commands/mcp.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* This allows AI assistants to query requirements data
|
|
4
4
|
*/
|
|
5
5
|
export async function mcpCommand() {
|
|
6
|
-
// Import the MCP server
|
|
6
|
+
// Import and start the MCP server
|
|
7
7
|
// The server handles stdio transport and runs until process termination
|
|
8
|
-
await import('../mcp/index.js');
|
|
8
|
+
const { main } = await import('../mcp/index.js');
|
|
9
|
+
await main();
|
|
9
10
|
}
|
|
10
11
|
//# sourceMappingURL=mcp.js.map
|
package/dist/convex.d.ts
CHANGED
package/dist/convex.js
CHANGED
|
@@ -41,6 +41,14 @@ export const api = {
|
|
|
41
41
|
ensureDefaultTeam: 'teams/mutations:ensureDefaultTeam',
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
|
+
teamInvites: {
|
|
45
|
+
queries: {
|
|
46
|
+
getInviteInfo: 'teamInvites/queries:getInviteInfo',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
polar: {
|
|
50
|
+
acceptTeamInvite: 'polar:acceptTeamInvite',
|
|
51
|
+
},
|
|
44
52
|
projectSecrets: {
|
|
45
53
|
queries: {
|
|
46
54
|
getOwnSecret: 'projectSecrets/queries:getOwnSecret',
|
package/dist/mcp/index.d.ts
CHANGED
package/dist/mcp/index.js
CHANGED
|
@@ -611,8 +611,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
611
611
|
};
|
|
612
612
|
}
|
|
613
613
|
});
|
|
614
|
-
// Start server
|
|
615
|
-
async function main() {
|
|
614
|
+
// Start server (exported for CLI command to call directly)
|
|
615
|
+
export async function main() {
|
|
616
616
|
const transport = new StdioServerTransport();
|
|
617
617
|
await server.connect(transport);
|
|
618
618
|
console.error('dot•requirements MCP server running');
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
## Requirements-
|
|
1
|
+
## Requirements-Driven Development
|
|
2
2
|
|
|
3
3
|
This project uses dotrequirements for requirements tracking. Requirements live in `.requirements/*.requirements.md`.
|
|
4
4
|
|
|
5
5
|
### Workflow
|
|
6
6
|
|
|
7
|
-
When
|
|
7
|
+
**Plan mode:** When planning behavioral changes, your plan should ALWAYS begin
|
|
8
|
+
with a step to write or update requirements. Implementation starts with
|
|
9
|
+
requirements, so your plan should too.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
**ALWAYS follow this workflow when changing system behavior** (new features, bug fixes, any behavioral change). Only pure refactoring (same behavior, different code) may skip requirements.
|
|
12
|
+
|
|
13
|
+
1. **Find or write requirements** - Check `.requirements/` for existing specs; write new ones if needed
|
|
10
14
|
2. **Style-check** - Run `mcp__dotrequirements__style_check` on the file
|
|
11
15
|
3. **Get approval** - Present requirements, wait for go-ahead
|
|
12
16
|
4. **Implement** - Build the feature
|
|
@@ -18,16 +22,15 @@ When adding new functionality (not bug fixes or refactoring):
|
|
|
18
22
|
|
|
19
23
|
```dotrequirements
|
|
20
24
|
REQ-ID: Short description of expected behavior
|
|
21
|
-
0.
|
|
22
|
-
1.
|
|
23
|
-
|
|
24
|
-
2.0. And -> Additional outcome detail
|
|
25
|
+
0. -> First criterion or condition
|
|
26
|
+
1. -> Second criterion
|
|
27
|
+
1.0. -> Nested detail under second criterion
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
- First line: `KEY: description`
|
|
28
|
-
- Criteria: `position.
|
|
31
|
+
- Criteria: `position. -> content` (optional label before the arrow)
|
|
29
32
|
- Nesting: Indent with 2 spaces, use `x.y` position paths
|
|
30
|
-
- Delimiter: `->` or
|
|
33
|
+
- Delimiter: `->` or `→`
|
|
31
34
|
|
|
32
35
|
### Test Usage
|
|
33
36
|
|