@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.
Files changed (61) hide show
  1. package/README.md +66 -61
  2. package/dist/cli.js +0 -10
  3. package/dist/commands/init.js +167 -224
  4. package/dist/commands/link.d.ts +9 -10
  5. package/dist/commands/link.js +81 -106
  6. package/dist/commands/mcp-setup.js +77 -94
  7. package/dist/commands/pull.js +17 -45
  8. package/dist/commands/push.js +9 -24
  9. package/dist/convex.d.ts +3 -0
  10. package/dist/convex.js +3 -0
  11. package/dist/harness/cache.d.ts +0 -5
  12. package/dist/harness/cache.js +0 -48
  13. package/dist/harness/convexReporting.js +7 -14
  14. package/dist/harness/finalize.d.ts +8 -2
  15. package/dist/harness/finalize.js +48 -31
  16. package/dist/harness/prepare.js +7 -9
  17. package/dist/mcp/convexClient.d.ts +5 -1
  18. package/dist/mcp/convexClient.js +14 -34
  19. package/dist/mcp/index.js +25 -78
  20. package/dist/schema/conversions.d.ts +2 -2
  21. package/dist/schema/conversions.js +2 -3
  22. package/dist/schema/schemas.d.ts +14 -37
  23. package/dist/schema/schemas.js +7 -10
  24. package/dist/schema/test-schema.js +1 -1
  25. package/dist/templates/context-file-section.md +59 -0
  26. package/dist/utils/context-file.d.ts +38 -0
  27. package/dist/utils/context-file.js +94 -0
  28. package/dist/utils/env.d.ts +0 -13
  29. package/dist/utils/env.js +0 -19
  30. package/dist/utils/gitignore.d.ts +2 -2
  31. package/dist/utils/gitignore.js +4 -4
  32. package/dist/utils/oauth-flow.d.ts +0 -1
  33. package/dist/utils/oauth-flow.js +0 -9
  34. package/dist/utils/project-discovery.d.ts +3 -5
  35. package/dist/utils/project-discovery.js +18 -42
  36. package/dist/utils/project-selector.d.ts +17 -3
  37. package/dist/utils/project-selector.js +37 -3
  38. package/dist/utils/project-settings.d.ts +47 -0
  39. package/dist/utils/project-settings.js +110 -0
  40. package/dist/utils/templates.d.ts +0 -24
  41. package/dist/utils/templates.js +0 -39
  42. package/package.json +1 -1
  43. package/dist/harness/localReporting.d.ts +0 -6
  44. package/dist/harness/localReporting.js +0 -49
  45. package/dist/templates/antigravity-gemini.md +0 -3
  46. package/dist/templates/antigravity-overview-rule.md +0 -3
  47. package/dist/templates/antigravity-test-rule.md +0 -3
  48. package/dist/templates/behavioral-core.md +0 -25
  49. package/dist/templates/claude-code-overview-skill.md +0 -6
  50. package/dist/templates/claude-code-skill.md +0 -6
  51. package/dist/templates/claude-code-test-skill.md +0 -6
  52. package/dist/templates/codex-agents.md +0 -3
  53. package/dist/templates/codex-overview-agents.md +0 -3
  54. package/dist/templates/codex-test-agents.md +0 -3
  55. package/dist/templates/cursor-overview-rule.mdc +0 -5
  56. package/dist/templates/cursor-rule.mdc +0 -5
  57. package/dist/templates/cursor-test-rule.mdc +0 -5
  58. package/dist/templates/overview-core.md +0 -27
  59. package/dist/templates/test-writing-core.md +0 -72
  60. package/dist/utils/detect-existing-project.d.ts +0 -5
  61. package/dist/utils/detect-existing-project.js +0 -34
@@ -48,10 +48,10 @@ export declare function convexToRequirements(convexReqs: ConvexRequirement[]): R
48
48
  */
49
49
  export declare function requirementsToConvex(nodes: RequirementNode[], projectId: string, documentId: string): ConvexRequirement[];
50
50
  /**
51
- * Build metadata from project info and pull time.
51
+ * Build metadata for a requirements file.
52
52
  * DOC-HEADER-11.3: When pulling, include defaultPrefix in the frontmatter.
53
53
  */
54
- export declare function buildMetadata(projectId: string, version?: number, document?: {
54
+ export declare function buildMetadata(version?: number, document?: {
55
55
  id?: string;
56
56
  title: string;
57
57
  defaultPrefix?: string;
@@ -166,12 +166,11 @@ function flattenNode(node, projectId, rootId, position, result, documentId) {
166
166
  });
167
167
  }
168
168
  /**
169
- * Build metadata from project info and pull time.
169
+ * Build metadata for a requirements file.
170
170
  * DOC-HEADER-11.3: When pulling, include defaultPrefix in the frontmatter.
171
171
  */
172
- export function buildMetadata(projectId, version = 1, document) {
172
+ export function buildMetadata(version = 1, document) {
173
173
  return {
174
- projectId,
175
174
  pulledAt: new Date().toISOString(),
176
175
  version,
177
176
  document,
@@ -54,13 +54,12 @@ export declare function parseRequirementKey(key: string): {
54
54
  */
55
55
  export declare function buildRequirementKey(prefix: string, number: number): string;
56
56
  /**
57
- * Metadata block at the top of every requirements file
57
+ * Metadata block at the top of every requirements file.
58
+ * Only document.title is required for push; everything else is optional or auto-filled.
58
59
  */
59
60
  export declare const MetadataSchema: z.ZodObject<{
60
- projectId: z.ZodString;
61
- projectSlug: z.ZodOptional<z.ZodString>;
61
+ version: z.ZodOptional<z.ZodNumber>;
62
62
  pulledAt: z.ZodOptional<z.ZodString>;
63
- version: z.ZodNumber;
64
63
  document: z.ZodOptional<z.ZodObject<{
65
64
  id: z.ZodOptional<z.ZodString>;
66
65
  title: z.ZodString;
@@ -75,9 +74,7 @@ export declare const MetadataSchema: z.ZodObject<{
75
74
  defaultPrefix?: string | undefined;
76
75
  }>>;
77
76
  }, "strip", z.ZodTypeAny, {
78
- version: number;
79
- projectId: string;
80
- projectSlug?: string | undefined;
77
+ version?: number | undefined;
81
78
  pulledAt?: string | undefined;
82
79
  document?: {
83
80
  title: string;
@@ -85,9 +82,7 @@ export declare const MetadataSchema: z.ZodObject<{
85
82
  defaultPrefix?: string | undefined;
86
83
  } | undefined;
87
84
  }, {
88
- version: number;
89
- projectId: string;
90
- projectSlug?: string | undefined;
85
+ version?: number | undefined;
91
86
  pulledAt?: string | undefined;
92
87
  document?: {
93
88
  title: string;
@@ -149,10 +144,8 @@ export type RequirementNode = {
149
144
  */
150
145
  export declare const RequirementsFileSchema: z.ZodObject<{
151
146
  _meta: z.ZodObject<{
152
- projectId: z.ZodString;
153
- projectSlug: z.ZodOptional<z.ZodString>;
147
+ version: z.ZodOptional<z.ZodNumber>;
154
148
  pulledAt: z.ZodOptional<z.ZodString>;
155
- version: z.ZodNumber;
156
149
  document: z.ZodOptional<z.ZodObject<{
157
150
  id: z.ZodOptional<z.ZodString>;
158
151
  title: z.ZodString;
@@ -167,9 +160,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
167
160
  defaultPrefix?: string | undefined;
168
161
  }>>;
169
162
  }, "strip", z.ZodTypeAny, {
170
- version: number;
171
- projectId: string;
172
- projectSlug?: string | undefined;
163
+ version?: number | undefined;
173
164
  pulledAt?: string | undefined;
174
165
  document?: {
175
166
  title: string;
@@ -177,9 +168,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
177
168
  defaultPrefix?: string | undefined;
178
169
  } | undefined;
179
170
  }, {
180
- version: number;
181
- projectId: string;
182
- projectSlug?: string | undefined;
171
+ version?: number | undefined;
183
172
  pulledAt?: string | undefined;
184
173
  document?: {
185
174
  title: string;
@@ -189,10 +178,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
189
178
  }>;
190
179
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
191
180
  _meta: z.ZodObject<{
192
- projectId: z.ZodString;
193
- projectSlug: z.ZodOptional<z.ZodString>;
181
+ version: z.ZodOptional<z.ZodNumber>;
194
182
  pulledAt: z.ZodOptional<z.ZodString>;
195
- version: z.ZodNumber;
196
183
  document: z.ZodOptional<z.ZodObject<{
197
184
  id: z.ZodOptional<z.ZodString>;
198
185
  title: z.ZodString;
@@ -207,9 +194,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
207
194
  defaultPrefix?: string | undefined;
208
195
  }>>;
209
196
  }, "strip", z.ZodTypeAny, {
210
- version: number;
211
- projectId: string;
212
- projectSlug?: string | undefined;
197
+ version?: number | undefined;
213
198
  pulledAt?: string | undefined;
214
199
  document?: {
215
200
  title: string;
@@ -217,9 +202,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
217
202
  defaultPrefix?: string | undefined;
218
203
  } | undefined;
219
204
  }, {
220
- version: number;
221
- projectId: string;
222
- projectSlug?: string | undefined;
205
+ version?: number | undefined;
223
206
  pulledAt?: string | undefined;
224
207
  document?: {
225
208
  title: string;
@@ -229,10 +212,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
229
212
  }>;
230
213
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
231
214
  _meta: z.ZodObject<{
232
- projectId: z.ZodString;
233
- projectSlug: z.ZodOptional<z.ZodString>;
215
+ version: z.ZodOptional<z.ZodNumber>;
234
216
  pulledAt: z.ZodOptional<z.ZodString>;
235
- version: z.ZodNumber;
236
217
  document: z.ZodOptional<z.ZodObject<{
237
218
  id: z.ZodOptional<z.ZodString>;
238
219
  title: z.ZodString;
@@ -247,9 +228,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
247
228
  defaultPrefix?: string | undefined;
248
229
  }>>;
249
230
  }, "strip", z.ZodTypeAny, {
250
- version: number;
251
- projectId: string;
252
- projectSlug?: string | undefined;
231
+ version?: number | undefined;
253
232
  pulledAt?: string | undefined;
254
233
  document?: {
255
234
  title: string;
@@ -257,9 +236,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
257
236
  defaultPrefix?: string | undefined;
258
237
  } | undefined;
259
238
  }, {
260
- version: number;
261
- projectId: string;
262
- projectSlug?: string | undefined;
239
+ version?: number | undefined;
263
240
  pulledAt?: string | undefined;
264
241
  document?: {
265
242
  title: string;
@@ -104,19 +104,16 @@ export function buildRequirementKey(prefix, number) {
104
104
  // Metadata Schema
105
105
  // =============================================================================
106
106
  /**
107
- * Metadata block at the top of every requirements file
107
+ * Metadata block at the top of every requirements file.
108
+ * Only document.title is required for push; everything else is optional or auto-filled.
108
109
  */
109
110
  export const MetadataSchema = z.object({
110
- projectId: z.string(),
111
- projectSlug: z.string().optional(), // User-friendly slug (e.g., "quick-fox-123")
112
- pulledAt: z.string().optional(), // ISO 8601 timestamp - required for CLI files, optional for IDE
113
- version: z.number(),
114
- // Optional document association (one file = one document)
111
+ version: z.number().optional(), // Informational - incremented on push
112
+ pulledAt: z.string().optional(), // ISO 8601 timestamp - used for conflict detection
115
113
  document: z.object({
116
- id: z.string().optional(), // Optional - filled by push when creating new document
117
- title: z.string(),
118
- // DOC-HEADER-11: Default prefix for new requirement keys in this document
119
- defaultPrefix: z.string().optional(),
114
+ id: z.string().optional(), // Cloud document link - filled by push when creating
115
+ title: z.string(), // Required for push
116
+ defaultPrefix: z.string().optional(), // Editor hint for new requirement keys
120
117
  }).optional(),
121
118
  });
122
119
  // =============================================================================
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { buildRequirementsMarkdown, parseRequirementsFile, buildMetadata } from './index.js';
5
5
  // Create test data
6
- const metadata = buildMetadata('test-project-123', 1);
6
+ const metadata = buildMetadata(1);
7
7
  const testRequirements = [
8
8
  {
9
9
  id: 'REQ123',
@@ -0,0 +1,59 @@
1
+ ## Requirements-First Development
2
+
3
+ This project uses dotrequirements for requirements tracking. Requirements live in `.requirements/*.requirements.md`.
4
+
5
+ ### Workflow
6
+
7
+ When adding new functionality (not bug fixes or refactoring):
8
+
9
+ 1. **Draft requirements** - Write in `.requirements/*.requirements.md`
10
+ 2. **Style-check** - Run `mcp__dotrequirements__style_check` on the file
11
+ 3. **Get approval** - Present requirements, wait for go-ahead
12
+ 4. **Implement** - Build the feature
13
+ 5. **Write tests** - Reference requirements with `requirement()`
14
+ 6. **Run tests** - Verify everything passes
15
+ 7. **Review tests** - Run `mcp__dotrequirements__review_test` to validate coverage
16
+
17
+ ### Requirements Syntax
18
+
19
+ ```dotrequirements
20
+ 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
+ ```
26
+
27
+ - First line: `KEY: description`
28
+ - Criteria: `position. Label -> content` (Given/When/Then structure)
29
+ - Nesting: Indent with 2 spaces, use `x.y` position paths
30
+ - Delimiter: `->` or the arrow character
31
+
32
+ ### Test Usage
33
+
34
+ Use `requirement()` AS the test description:
35
+
36
+ ```typescript
37
+ import { requirement } from '@popoverai/dotrequirements/test';
38
+
39
+ test(requirement('REQ-ID'), () => { /* test the requirement */ });
40
+ test(requirement('REQ-ID.0'), () => { /* test specific criterion */ });
41
+ ```
42
+
43
+ ### MCP Tools
44
+
45
+ **Exploration:**
46
+ - `list_all_requirements` - Overview of all requirements
47
+ - `get_requirement` - Requirement tree with test coverage
48
+ - `search_requirements` - Search by text/regex
49
+
50
+ **Authoring:**
51
+ - `create_requirement_document` - Get template with format guidance
52
+ - `validate_requirements` - Check syntax (works offline)
53
+ - `style_check` - AI feedback on clarity
54
+ - `push_requirements` - Sync to cloud
55
+
56
+ **Testing:**
57
+ - `get_requirements_by_test` - See requirements a test file covers
58
+ - `list_untested_requirements` - Find gaps in coverage
59
+ - `review_test` - Validate tests match requirement intent
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Get the appropriate context file name for a platform
3
+ */
4
+ export declare function getContextFileName(platform: string): string | null;
5
+ /**
6
+ * Find the git root directory
7
+ */
8
+ export declare function findGitRoot(): Promise<string | null>;
9
+ /**
10
+ * Find the dotrequirements section in file content
11
+ * Returns the start/end indices and content, or null if not found
12
+ */
13
+ export declare function findDotrequirementsSection(content: string): {
14
+ start: number;
15
+ end: number;
16
+ content: string;
17
+ } | null;
18
+ /**
19
+ * Wrap content in section markers
20
+ */
21
+ export declare function wrapInSectionMarkers(content: string): string;
22
+ /**
23
+ * Append or update the dotrequirements section in a context file
24
+ * - If file doesn't exist, creates it with just the section
25
+ * - If file exists without section, appends section at end
26
+ * - If file exists with section, replaces the section
27
+ *
28
+ * Returns: { action: 'created' | 'appended' | 'updated', existingContent?: string }
29
+ */
30
+ export declare function appendOrUpdateSection(filePath: string, sectionContent: string): {
31
+ action: 'created' | 'appended' | 'updated';
32
+ existingContent?: string;
33
+ };
34
+ /**
35
+ * Get the full path to the context file for a platform
36
+ */
37
+ export declare function getContextFilePath(platform: string): Promise<string | null>;
38
+ //# sourceMappingURL=context-file.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { findUp } from 'find-up';
4
+ const SECTION_START = '<!-- dotrequirements:start -->';
5
+ const SECTION_END = '<!-- dotrequirements:end -->';
6
+ /**
7
+ * Platform to context file mapping
8
+ */
9
+ const PLATFORM_CONTEXT_FILES = {
10
+ 'claude-code': 'CLAUDE.md',
11
+ 'cursor': 'AGENTS.md',
12
+ 'codex': 'AGENTS.md',
13
+ 'github-copilot': 'AGENTS.md',
14
+ 'antigravity': 'GEMINI.md',
15
+ };
16
+ /**
17
+ * Get the appropriate context file name for a platform
18
+ */
19
+ export function getContextFileName(platform) {
20
+ return PLATFORM_CONTEXT_FILES[platform] ?? null;
21
+ }
22
+ /**
23
+ * Find the git root directory
24
+ */
25
+ export async function findGitRoot() {
26
+ const gitDir = await findUp('.git', { type: 'directory' });
27
+ return gitDir ? dirname(gitDir) : null;
28
+ }
29
+ /**
30
+ * Find the dotrequirements section in file content
31
+ * Returns the start/end indices and content, or null if not found
32
+ */
33
+ export function findDotrequirementsSection(content) {
34
+ const startIdx = content.indexOf(SECTION_START);
35
+ if (startIdx === -1)
36
+ return null;
37
+ const endIdx = content.indexOf(SECTION_END, startIdx);
38
+ if (endIdx === -1)
39
+ return null;
40
+ return {
41
+ start: startIdx,
42
+ end: endIdx + SECTION_END.length,
43
+ content: content.slice(startIdx, endIdx + SECTION_END.length),
44
+ };
45
+ }
46
+ /**
47
+ * Wrap content in section markers
48
+ */
49
+ export function wrapInSectionMarkers(content) {
50
+ return `${SECTION_START}\n${content}\n${SECTION_END}`;
51
+ }
52
+ /**
53
+ * Append or update the dotrequirements section in a context file
54
+ * - If file doesn't exist, creates it with just the section
55
+ * - If file exists without section, appends section at end
56
+ * - If file exists with section, replaces the section
57
+ *
58
+ * Returns: { action: 'created' | 'appended' | 'updated', existingContent?: string }
59
+ */
60
+ export function appendOrUpdateSection(filePath, sectionContent) {
61
+ const wrappedContent = wrapInSectionMarkers(sectionContent);
62
+ if (!existsSync(filePath)) {
63
+ // Create new file with just the section
64
+ writeFileSync(filePath, wrappedContent + '\n', 'utf-8');
65
+ return { action: 'created' };
66
+ }
67
+ const existingFile = readFileSync(filePath, 'utf-8');
68
+ const existingSection = findDotrequirementsSection(existingFile);
69
+ if (!existingSection) {
70
+ // Append section to end of file
71
+ const separator = existingFile.endsWith('\n') ? '\n' : '\n\n';
72
+ writeFileSync(filePath, existingFile + separator + wrappedContent + '\n', 'utf-8');
73
+ return { action: 'appended' };
74
+ }
75
+ // Replace existing section
76
+ const newContent = existingFile.slice(0, existingSection.start) +
77
+ wrappedContent +
78
+ existingFile.slice(existingSection.end);
79
+ writeFileSync(filePath, newContent, 'utf-8');
80
+ return { action: 'updated', existingContent: existingSection.content };
81
+ }
82
+ /**
83
+ * Get the full path to the context file for a platform
84
+ */
85
+ export async function getContextFilePath(platform) {
86
+ const fileName = getContextFileName(platform);
87
+ if (!fileName)
88
+ return null;
89
+ const gitRoot = await findGitRoot();
90
+ if (!gitRoot)
91
+ return null;
92
+ return join(gitRoot, fileName);
93
+ }
94
+ //# sourceMappingURL=context-file.js.map
@@ -5,17 +5,4 @@
5
5
  * @param force - If true, overwrite existing process.env values (used when reloading after init)
6
6
  */
7
7
  export declare function loadEnvFile(cwd?: string, force?: boolean): void;
8
- /**
9
- * Get project credentials from environment
10
- * Returns undefined if not found
11
- */
12
- export declare function getProjectCredentials(): {
13
- projectId: string;
14
- projectSecret: string;
15
- } | undefined;
16
- /**
17
- * Get project ID from environment
18
- * Returns undefined if not found
19
- */
20
- export declare function getProjectId(): string | undefined;
21
8
  //# sourceMappingURL=env.d.ts.map
package/dist/utils/env.js CHANGED
@@ -36,23 +36,4 @@ export function loadEnvFile(cwd = process.cwd(), force = false) {
36
36
  // The commands will error appropriately if env vars are missing
37
37
  }
38
38
  }
39
- /**
40
- * Get project credentials from environment
41
- * Returns undefined if not found
42
- */
43
- export function getProjectCredentials() {
44
- const projectId = process.env.DOTREQUIREMENTS_PROJECT_ID;
45
- const projectSecret = process.env.DOTREQUIREMENTS_PROJECT_SECRET;
46
- if (!projectId || !projectSecret) {
47
- return undefined;
48
- }
49
- return { projectId, projectSecret };
50
- }
51
- /**
52
- * Get project ID from environment
53
- * Returns undefined if not found
54
- */
55
- export function getProjectId() {
56
- return process.env.DOTREQUIREMENTS_PROJECT_ID;
57
- }
58
39
  //# sourceMappingURL=env.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Ensure .env.local is in .gitignore
2
+ * Ensure project-settings.json is in .gitignore
3
3
  * Creates .gitignore if it doesn't exist
4
- * Appends .env.local if not already present
4
+ * Appends the entry if not already present
5
5
  */
6
6
  export declare function ensureGitignore(cwd?: string): void;
7
7
  //# sourceMappingURL=gitignore.d.ts.map
@@ -1,13 +1,13 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  /**
4
- * Ensure .env.local is in .gitignore
4
+ * Ensure project-settings.json is in .gitignore
5
5
  * Creates .gitignore if it doesn't exist
6
- * Appends .env.local if not already present
6
+ * Appends the entry if not already present
7
7
  */
8
8
  export function ensureGitignore(cwd = process.cwd()) {
9
9
  const gitignorePath = path.join(cwd, '.gitignore');
10
- const entryToAdd = '.env.local';
10
+ const entryToAdd = '.requirements/project-settings.json';
11
11
  const comment = '# dotrequirements credentials';
12
12
  // Check if .gitignore exists
13
13
  if (!fs.existsSync(gitignorePath)) {
@@ -17,7 +17,7 @@ export function ensureGitignore(cwd = process.cwd()) {
17
17
  }
18
18
  // Read existing .gitignore
19
19
  const content = fs.readFileSync(gitignorePath, 'utf-8');
20
- // Check if .env.local is already present
20
+ // Check if entry is already present
21
21
  if (content.includes(entryToAdd)) {
22
22
  return; // Already present, nothing to do
23
23
  }
@@ -16,7 +16,6 @@ export interface OAuthResult {
16
16
  * 3. Open browser for user authentication
17
17
  * 4. Wait for callback with authorization code
18
18
  * 5. Exchange code for tokens via WorkOS SDK
19
- * 6. Store tokens securely
20
19
  */
21
20
  export declare function executeOAuthFlow(): Promise<OAuthResult>;
22
21
  //# sourceMappingURL=oauth-flow.d.ts.map
@@ -8,7 +8,6 @@ import { randomBytes } from 'crypto';
8
8
  import { ConvexHttpClient } from 'convex/browser';
9
9
  import { startCallbackServer } from './oauth-callback-server.js';
10
10
  import { openBrowserWithFallback } from './browser-launch.js';
11
- import { storeTokens } from './token-storage.js';
12
11
  import { getConvexUrl } from '../config.js';
13
12
  import { api } from '../convex.js';
14
13
  const WORKOS_CLIENT_ID = 'client_01KCM6WPYX201P7DRV5DHF4TR7';
@@ -20,7 +19,6 @@ const WORKOS_AUTHORIZE_URL = 'https://api.workos.com/user_management/authorize';
20
19
  * 3. Open browser for user authentication
21
20
  * 4. Wait for callback with authorization code
22
21
  * 5. Exchange code for tokens via WorkOS SDK
23
- * 6. Store tokens securely
24
22
  */
25
23
  export async function executeOAuthFlow() {
26
24
  // Generate PKCE code verifier and challenge
@@ -58,13 +56,6 @@ export async function executeOAuthFlow() {
58
56
  throw new Error(`Token exchange failed: ${result.error}`);
59
57
  }
60
58
  const { accessToken, refreshToken, expiresAt } = result;
61
- // Store tokens securely
62
- const tokens = {
63
- accessToken,
64
- refreshToken,
65
- expiresAt,
66
- };
67
- await storeTokens(tokens);
68
59
  console.log('✓ Authentication complete!');
69
60
  return {
70
61
  accessToken,
@@ -4,9 +4,9 @@
4
4
  export interface DotreqProject {
5
5
  /** Absolute path to the project root directory */
6
6
  path: string;
7
- /** Project ID from .env.local */
7
+ /** Project ID from project-settings.json */
8
8
  projectId: string;
9
- /** Project secret from .env.local */
9
+ /** Project secret from project-settings.json */
10
10
  projectSecret: string;
11
11
  }
12
12
  /**
@@ -24,9 +24,7 @@ export type ProjectDiscoveryResult = {
24
24
  };
25
25
  /**
26
26
  * Check if a directory is a configured dotrequirements project
27
- * A project is configured if:
28
- * 1. It has a package.json that includes "dotreq" (in dependencies or devDependencies)
29
- * 2. It has a .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET
27
+ * A project is configured if it has .requirements/project-settings.json with credentials
30
28
  */
31
29
  export declare function isConfiguredProject(dir: string): DotreqProject | null;
32
30
  /**
@@ -1,58 +1,34 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { glob } from 'glob';
4
+ import { readProjectSettings } from './project-settings.js';
4
5
  /**
5
6
  * Check if a directory is a configured dotrequirements project
6
- * A project is configured if:
7
- * 1. It has a package.json that includes "dotreq" (in dependencies or devDependencies)
8
- * 2. It has a .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET
7
+ * A project is configured if it has .requirements/project-settings.json with credentials
9
8
  */
10
9
  export function isConfiguredProject(dir) {
11
- const pkgPath = path.join(dir, 'package.json');
12
- const envPath = path.join(dir, '.env.local');
13
- // Check for package.json with dotreq
14
- if (!fs.existsSync(pkgPath)) {
10
+ const requirementsDir = path.join(dir, '.requirements');
11
+ // Check for .requirements/ directory
12
+ if (!fs.existsSync(requirementsDir) || !fs.statSync(requirementsDir).isDirectory()) {
15
13
  return null;
16
14
  }
15
+ // Try to read credentials from project-settings.json
16
+ let settings;
17
17
  try {
18
- const pkgContent = fs.readFileSync(pkgPath, 'utf-8');
19
- // Look for "dotreq" anywhere in the file (flexible matching)
20
- if (!pkgContent.includes('dotreq')) {
21
- return null;
22
- }
23
- }
24
- catch {
25
- return null;
26
- }
27
- // Check for .env.local with required vars
28
- if (!fs.existsSync(envPath)) {
29
- return null;
30
- }
31
- let projectId;
32
- let projectSecret;
33
- try {
34
- const envContent = fs.readFileSync(envPath, 'utf-8');
35
- const lines = envContent.split('\n');
36
- for (const line of lines) {
37
- const trimmed = line.trim();
38
- if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_ID=')) {
39
- projectId = trimmed.split('=')[1]?.trim().replace(/['"]/g, '');
40
- }
41
- if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_SECRET=')) {
42
- projectSecret = trimmed.split('=')[1]?.trim().replace(/['"]/g, '');
43
- }
44
- }
18
+ settings = readProjectSettings(dir);
45
19
  }
46
20
  catch {
21
+ // Invalid settings file
47
22
  return null;
48
23
  }
49
- if (!projectId || !projectSecret) {
24
+ if (!settings) {
25
+ // No credentials (local-only project)
50
26
  return null;
51
27
  }
52
28
  return {
53
29
  path: dir,
54
- projectId,
55
- projectSecret,
30
+ projectId: settings.projectId,
31
+ projectSecret: settings.projectSecret,
56
32
  };
57
33
  }
58
34
  /**
@@ -61,14 +37,14 @@ export function isConfiguredProject(dir) {
61
37
  */
62
38
  export async function findConfiguredProjects(startDir) {
63
39
  const projects = [];
64
- // Find all package.json files, but allow .env.local despite being in .gitignore
65
- const packageJsonFiles = await glob('**/package.json', {
40
+ // Find all .requirements directories
41
+ const requirementsDirs = await glob('**/.requirements', {
66
42
  cwd: startDir,
67
43
  absolute: true,
68
44
  ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/build/**'],
69
45
  });
70
- for (const pkgPath of packageJsonFiles) {
71
- const dir = path.dirname(pkgPath);
46
+ for (const reqDir of requirementsDirs) {
47
+ const dir = path.dirname(reqDir);
72
48
  const project = isConfiguredProject(dir);
73
49
  if (project) {
74
50
  projects.push(project);
@@ -96,7 +72,7 @@ export async function discoverProjects(workingDir = process.cwd()) {
96
72
  if (childProjects.length === 0) {
97
73
  return {
98
74
  type: 'none',
99
- message: `No dotrequirements projects found in ${workingDir} or its children.\n\nA project must have:\n1. package.json with "dotreq" dependency\n2. .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET`,
75
+ message: `No dotrequirements projects found in ${workingDir} or its children.\n\nA project must have:\n1. A .requirements/ directory\n2. Cloud credentials in .requirements/project-settings.json\n\nRun "dotrequirements init" to create a project or "dotrequirements link" to connect to cloud.`,
100
76
  };
101
77
  }
102
78
  // Case 3: Exactly one child project