@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 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')
@@ -1,5 +1,6 @@
1
1
  interface InitOptions {
2
2
  name?: string;
3
+ invite?: string;
3
4
  }
4
5
  export declare function initCommand(options?: InitOptions): Promise<void>;
5
6
  export {};
@@ -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
  */
@@ -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 - this starts it automatically
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
@@ -39,6 +39,14 @@ export declare const api: {
39
39
  ensureDefaultTeam: any;
40
40
  };
41
41
  };
42
+ teamInvites: {
43
+ queries: {
44
+ getInviteInfo: any;
45
+ };
46
+ };
47
+ polar: {
48
+ acceptTeamInvite: any;
49
+ };
42
50
  projectSecrets: {
43
51
  queries: {
44
52
  getOwnSecret: any;
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',
@@ -41,4 +41,5 @@ export declare const server: Server<{
41
41
  } | undefined;
42
42
  } | undefined;
43
43
  }>;
44
+ export declare function main(): Promise<void>;
44
45
  //# sourceMappingURL=index.d.ts.map
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-First Development
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 adding new functionality (not bug fixes or refactoring):
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
- 1. **Draft requirements** - Write in `.requirements/*.requirements.md`
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. Given -> Precondition that must be true
22
- 1. When -> Action or trigger
23
- 2. Then -> Expected outcome
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. Label -> content` (Given/When/Then structure)
31
+ - Criteria: `position. -> content` (optional label before the arrow)
29
32
  - Nesting: Indent with 2 spaces, use `x.y` position paths
30
- - Delimiter: `->` or the arrow character
33
+ - Delimiter: `->` or `→`
31
34
 
32
35
  ### Test Usage
33
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.17.2",
3
+ "version": "0.19.0",
4
4
  "description": "Requirements tracking CLI, test harness, and MCP server",
5
5
  "type": "module",
6
6
  "bin": {