@popoverai/dotrequirements 0.13.0 → 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 (53) hide show
  1. package/README.md +47 -47
  2. package/dist/cli.js +0 -10
  3. package/dist/commands/init.js +166 -226
  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 +7 -29
  8. package/dist/commands/push.js +4 -13
  9. package/dist/harness/cache.d.ts +0 -5
  10. package/dist/harness/cache.js +0 -48
  11. package/dist/harness/convexReporting.js +7 -14
  12. package/dist/harness/finalize.js +7 -12
  13. package/dist/harness/prepare.js +7 -9
  14. package/dist/mcp/convexClient.d.ts +5 -1
  15. package/dist/mcp/convexClient.js +14 -34
  16. package/dist/mcp/index.js +12 -73
  17. package/dist/templates/context-file-section.md +59 -0
  18. package/dist/utils/context-file.d.ts +38 -0
  19. package/dist/utils/context-file.js +94 -0
  20. package/dist/utils/env.d.ts +0 -13
  21. package/dist/utils/env.js +0 -19
  22. package/dist/utils/gitignore.d.ts +2 -2
  23. package/dist/utils/gitignore.js +4 -4
  24. package/dist/utils/oauth-flow.d.ts +0 -1
  25. package/dist/utils/oauth-flow.js +0 -9
  26. package/dist/utils/project-discovery.d.ts +3 -5
  27. package/dist/utils/project-discovery.js +18 -42
  28. package/dist/utils/project-selector.d.ts +17 -3
  29. package/dist/utils/project-selector.js +35 -3
  30. package/dist/utils/project-settings.d.ts +47 -0
  31. package/dist/utils/project-settings.js +110 -0
  32. package/dist/utils/templates.d.ts +0 -24
  33. package/dist/utils/templates.js +0 -39
  34. package/package.json +1 -1
  35. package/dist/harness/localReporting.d.ts +0 -6
  36. package/dist/harness/localReporting.js +0 -49
  37. package/dist/templates/antigravity-gemini.md +0 -3
  38. package/dist/templates/antigravity-overview-rule.md +0 -3
  39. package/dist/templates/antigravity-test-rule.md +0 -3
  40. package/dist/templates/behavioral-core.md +0 -25
  41. package/dist/templates/claude-code-overview-skill.md +0 -6
  42. package/dist/templates/claude-code-skill.md +0 -6
  43. package/dist/templates/claude-code-test-skill.md +0 -6
  44. package/dist/templates/codex-agents.md +0 -3
  45. package/dist/templates/codex-overview-agents.md +0 -3
  46. package/dist/templates/codex-test-agents.md +0 -3
  47. package/dist/templates/cursor-overview-rule.mdc +0 -5
  48. package/dist/templates/cursor-rule.mdc +0 -5
  49. package/dist/templates/cursor-test-rule.mdc +0 -5
  50. package/dist/templates/overview-core.md +0 -27
  51. package/dist/templates/test-writing-core.md +0 -72
  52. package/dist/utils/detect-existing-project.d.ts +0 -5
  53. package/dist/utils/detect-existing-project.js +0 -34
@@ -1,8 +1,7 @@
1
1
  import { execSync } from "child_process";
2
2
  import { getTrackedRequirements } from "./tracking.js";
3
3
  import { saveCache, getRequirementsToReport } from "./coverageCache.js";
4
- import { isLocalOnlyProject } from "../utils/local-project.js";
5
- import { loadEnvFile, getProjectCredentials } from "../utils/env.js";
4
+ import { getProjectInfo } from "../utils/project-settings.js";
6
5
  /**
7
6
  * Get the current git branch, or null if not in a git repo
8
7
  */
@@ -51,20 +50,14 @@ function parseCallerLocation(location) {
51
50
  */
52
51
  export async function reportCoverageToConvex(cwd = process.cwd(), trackedReqs) {
53
52
  try {
54
- // Load credentials from .env.local (standard credential discovery)
55
- loadEnvFile(cwd);
56
- const credentials = getProjectCredentials();
57
- // Skip if credentials are missing
58
- if (!credentials) {
59
- console.log("\nℹ️ Skipping cloud coverage reporting (DOTREQUIREMENTS_PROJECT_ID or DOTREQUIREMENTS_PROJECT_SECRET not configured)");
60
- return;
61
- }
62
- const { projectId, projectSecret } = credentials;
63
- // AUTHZ-1.2: Skip cloud reporting for local-only projects (silent)
64
- if (isLocalOnlyProject(projectId)) {
65
- // Silently skip - local-only projects track coverage in-memory only
53
+ // Get project info from .requirements/project-settings.json
54
+ const projectInfo = getProjectInfo(cwd);
55
+ // Skip if no project found or not connected to cloud
56
+ if (!projectInfo || projectInfo.status !== 'cloud-connected' || !projectInfo.credentials) {
57
+ console.log("\nℹ️ Skipping cloud coverage reporting (project not connected to cloud)");
66
58
  return;
67
59
  }
60
+ const { projectId, projectSecret } = projectInfo.credentials;
68
61
  // Use provided trackedReqs or fall back to in-memory tracking
69
62
  const reqs = trackedReqs ?? getTrackedRequirements();
70
63
  const requirementKeys = Array.from(reqs.keys());
@@ -11,8 +11,7 @@
11
11
  */
12
12
  import { execSync } from 'child_process';
13
13
  import { findRequirementsDir, findProjectRoot, getTestRunId, cleanupTestRunId, readTrackingEntries, deleteTrackingFile, readLookupCache, readCoverageCache, updateCoverageCache, needsReporting, } from './cache.js';
14
- import { loadEnvFile, getProjectCredentials } from '../utils/env.js';
15
- import { isLocalOnlyProject } from '../utils/local-project.js';
14
+ import { getProjectInfo } from '../utils/project-settings.js';
16
15
  /**
17
16
  * Aggregate tracking entries by requirement key
18
17
  */
@@ -115,20 +114,16 @@ const CONVEX_URL = 'https://data.dotrequirements.io';
115
114
  */
116
115
  async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatus) {
117
116
  try {
118
- // Load credentials
119
- loadEnvFile(projectRoot);
120
- const credentials = getProjectCredentials();
121
- if (!credentials) {
117
+ // Get project info from .requirements/project-settings.json
118
+ const projectInfo = getProjectInfo(projectRoot);
119
+ // Skip if no project found or not connected to cloud
120
+ if (!projectInfo || projectInfo.status !== 'cloud-connected' || !projectInfo.credentials) {
122
121
  if (showCloudStatus) {
123
- console.log('\nℹ️ Skipping cloud coverage reporting (DOTREQUIREMENTS_PROJECT_ID or DOTREQUIREMENTS_PROJECT_SECRET not configured)');
122
+ console.log('\nℹ️ Skipping cloud coverage reporting (project not connected to cloud)');
124
123
  }
125
124
  return { sent: false, count: 0 };
126
125
  }
127
- const { projectId, projectSecret } = credentials;
128
- // Skip for local-only projects
129
- if (isLocalOnlyProject(projectId)) {
130
- return { sent: false, count: 0 };
131
- }
126
+ const { projectId, projectSecret } = projectInfo.credentials;
132
127
  const requirementsDir = findRequirementsDir(projectRoot);
133
128
  // Get all tracked requirement keys
134
129
  const requirementKeys = Array.from(aggregated.keys());
@@ -8,8 +8,9 @@
8
8
  *
9
9
  * Implements: HARNESS-PREPARE-1, HARNESS-PREPARE-2, HARNESS-PREPARE-3
10
10
  */
11
- import { findProjectRootWithCredentials, findRequirementsDir, findRequirementsFiles, writeLookupCache, initTestRunId, clearTrackingFile, writeProjectRoot, } from './cache.js';
11
+ import { findRequirementsDir, findRequirementsFiles, writeLookupCache, initTestRunId, clearTrackingFile, writeProjectRoot, } from './cache.js';
12
12
  import { parseRequirementsFromFile, } from '../schema/index.js';
13
+ import { findProjectRoot } from '../utils/project-settings.js';
13
14
  /**
14
15
  * Prepare the test harness for a test run.
15
16
  *
@@ -22,14 +23,11 @@ import { parseRequirementsFromFile, } from '../schema/index.js';
22
23
  */
23
24
  export function prepare(options = {}) {
24
25
  const { cwd = process.cwd(), logWarnings = true } = options;
25
- // Find the project root - prefer root with credentials (.env.local) for monorepo support
26
- let projectRoot;
27
- try {
28
- projectRoot = findProjectRootWithCredentials(cwd);
29
- }
30
- catch {
31
- throw new Error('Could not find .requirements directory or .env.local with credentials. ' +
32
- 'Please create a .requirements directory in your project root.');
26
+ // Find the project root by looking for .requirements directory
27
+ const projectRoot = findProjectRoot(cwd);
28
+ if (!projectRoot) {
29
+ throw new Error('Could not find .requirements directory. ' +
30
+ 'Please run "dotrequirements init" to initialize your project.');
33
31
  }
34
32
  // Set environment variable for cross-process persistence (globalSetup → test workers)
35
33
  process.env.DOTREQUIREMENTS_PROJECT_ROOT = projectRoot;
@@ -1,11 +1,15 @@
1
1
  /**
2
- * Load Convex configuration from .env file or environment variables
2
+ * Convex configuration for cloud operations
3
3
  */
4
4
  interface ConvexConfig {
5
5
  convexUrl: string;
6
6
  projectId: string;
7
7
  projectSecret: string;
8
8
  }
9
+ /**
10
+ * Load Convex configuration from project settings.
11
+ * Returns null if credentials are not configured.
12
+ */
9
13
  export declare function loadConvexConfig(cwd?: string): ConvexConfig | null;
10
14
  /**
11
15
  * Query Convex for requirement coverage
@@ -1,45 +1,25 @@
1
- import { readFileSync, existsSync } from 'fs';
2
- import { join } from 'path';
1
+ import { getProjectCredentials } from '../utils/project-settings.js';
3
2
  /**
4
3
  * Production Convex deployment URL
5
4
  */
6
5
  const CONVEX_URL = 'https://data.dotrequirements.io';
6
+ /**
7
+ * Load Convex configuration from project settings.
8
+ * Returns null if credentials are not configured.
9
+ */
7
10
  export function loadConvexConfig(cwd = process.cwd()) {
8
- let projectId;
9
- let projectSecret;
10
- // Try to load from .env files (.env.local takes precedence over .env)
11
- const envFiles = ['.env.local', '.env'];
12
- for (const envFile of envFiles) {
13
- const envPath = join(cwd, envFile);
14
- if (existsSync(envPath)) {
15
- const envContent = readFileSync(envPath, 'utf-8');
16
- const lines = envContent.split('\n');
17
- for (const line of lines) {
18
- const trimmed = line.trim();
19
- if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_ID=')) {
20
- projectId = trimmed.split('=')[1].trim().replace(/['"]/g, '');
21
- }
22
- if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_SECRET=')) {
23
- projectSecret = trimmed.split('=')[1].trim().replace(/['"]/g, '');
24
- }
25
- }
26
- // If we found values, stop looking (precedence: .env.local > .env)
27
- if (projectId && projectSecret) {
28
- break;
29
- }
30
- }
31
- }
32
- // Environment variables take precedence
33
- if (process.env.DOTREQUIREMENTS_PROJECT_ID) {
34
- projectId = process.env.DOTREQUIREMENTS_PROJECT_ID;
35
- }
36
- if (process.env.DOTREQUIREMENTS_PROJECT_SECRET) {
37
- projectSecret = process.env.DOTREQUIREMENTS_PROJECT_SECRET;
11
+ try {
12
+ const credentials = getProjectCredentials(cwd);
13
+ return {
14
+ convexUrl: CONVEX_URL,
15
+ projectId: credentials.projectId,
16
+ projectSecret: credentials.projectSecret,
17
+ };
38
18
  }
39
- if (!projectId || !projectSecret) {
19
+ catch {
20
+ // Credentials not found or project not connected to cloud
40
21
  return null;
41
22
  }
42
- return { convexUrl: CONVEX_URL, projectId, projectSecret };
43
23
  }
44
24
  /**
45
25
  * Query Convex for requirement coverage
package/dist/mcp/index.js CHANGED
@@ -8,7 +8,6 @@ import { findTestCodeForRequirement, findFilesWithRequirement, } from './testCod
8
8
  import { glob } from 'glob';
9
9
  import { loadConvexConfig, getRequirementCoverage as queryRequirementCoverage, getProjectCoverage as queryProjectCoverage, } from './convexClient.js';
10
10
  import { discoverProjects, resolveProject, } from '../utils/project-discovery.js';
11
- import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
12
11
  import { readFileSync } from 'fs';
13
12
  import { fileURLToPath } from 'url';
14
13
  import { dirname, join } from 'path';
@@ -211,7 +210,7 @@ const tools = [
211
210
  },
212
211
  {
213
212
  name: 'get_requirement_coverage',
214
- description: 'Get test coverage information for a specific requirement from dot•requirements cloud. Shows when the requirement was last tested, on which branch, and in which test file. Requires DOTREQUIREMENTS_PROJECT_SECRET to be configured.',
213
+ description: 'Get test coverage information for a specific requirement from dot•requirements cloud. Shows when the requirement was last tested, on which branch, and in which test file. Requires project to be linked to cloud (run `dotrequirements link`).',
215
214
  inputSchema: {
216
215
  type: 'object',
217
216
  properties: {
@@ -229,7 +228,7 @@ const tools = [
229
228
  },
230
229
  {
231
230
  name: 'get_project_coverage_summary',
232
- description: 'Get a summary of test coverage for all requirements in the project from dot•requirements cloud. Shows which requirements have been tested and which haven\'t. Optionally filter by branch or time range. Requires DOTREQUIREMENTS_PROJECT_SECRET to be configured.',
231
+ description: 'Get a summary of test coverage for all requirements in the project from dot•requirements cloud. Shows which requirements have been tested and which haven\'t. Optionally filter by branch or time range. Requires project to be linked to cloud (run `dotrequirements link`).',
233
232
  inputSchema: {
234
233
  type: 'object',
235
234
  properties: {
@@ -279,7 +278,7 @@ const tools = [
279
278
  },
280
279
  {
281
280
  name: 'push_requirements',
282
- description: 'Push local requirements from .requirements/ directory to dot•requirements cloud. First call returns diff summary for user review. Second call with confirmed=true executes the push. Requires DOTREQUIREMENTS_PROJECT_SECRET environment variable.',
281
+ description: 'Push local requirements from .requirements/ directory to dot•requirements cloud. First call returns diff summary for user review. Second call with confirmed=true executes the push. Requires project to be linked to cloud (run `dotrequirements link`).',
283
282
  inputSchema: {
284
283
  type: 'object',
285
284
  properties: {
@@ -301,7 +300,7 @@ const tools = [
301
300
  },
302
301
  {
303
302
  name: 'style_check',
304
- description: 'Check requirements files or test files for style issues and best practices. Uses AI to provide actionable feedback on writing style, clarity, and conventions. Supports requirements files (*.requirements.md) and test files (*.test.*, *.spec.*). Requires DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured.',
303
+ description: 'Check requirements files or test files for style issues and best practices. Uses AI to provide actionable feedback on writing style, clarity, and conventions. Supports requirements files (*.requirements.md) and test files (*.test.*, *.spec.*). Requires project to be linked to cloud (run `dotrequirements link`).',
305
304
  inputSchema: {
306
305
  type: 'object',
307
306
  properties: {
@@ -319,7 +318,7 @@ const tools = [
319
318
  },
320
319
  {
321
320
  name: 'review_test',
322
- description: 'Comprehensively review a test file for both style and semantic correctness. Checks if tests actually validate what the requirements specify (not just style). Loads referenced requirements and validates that test setup, actions, and assertions match requirement preconditions, triggers, and outcomes. Requires DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured.',
321
+ description: 'Comprehensively review a test file for both style and semantic correctness. Checks if tests actually validate what the requirements specify (not just style). Loads referenced requirements and validates that test setup, actions, and assertions match requirement preconditions, triggers, and outcomes. Requires project to be linked to cloud (run `dotrequirements link`).',
323
322
  inputSchema: {
324
323
  type: 'object',
325
324
  properties: {
@@ -416,7 +415,7 @@ Fill in:
416
415
  After capturing requirements:
417
416
  1. Implement the feature
418
417
  2. Write tests that reference requirements using \`requirement('REQ-ID')\`
419
- 3. Use the test writing skill/prompt for guidance on test structure`,
418
+ 3. See your project's context file (CLAUDE.md/AGENTS.md) for test structure guidance`,
420
419
  },
421
420
  },
422
421
  ],
@@ -906,19 +905,7 @@ ${projectList || '(none found)'}`,
906
905
  content: [
907
906
  {
908
907
  type: 'text',
909
- text: 'Coverage queries require DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env file.',
910
- },
911
- ],
912
- isError: true,
913
- };
914
- }
915
- // AUTHZ-2.1: Check if project is local-only
916
- if (isLocalOnlyProject(config.projectId)) {
917
- return {
918
- content: [
919
- {
920
- type: 'text',
921
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
908
+ text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
922
909
  },
923
910
  ],
924
911
  isError: true,
@@ -965,19 +952,7 @@ ${projectList || '(none found)'}`,
965
952
  content: [
966
953
  {
967
954
  type: 'text',
968
- text: 'Coverage queries require DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env file.',
969
- },
970
- ],
971
- isError: true,
972
- };
973
- }
974
- // AUTHZ-2.1: Check if project is local-only
975
- if (isLocalOnlyProject(config.projectId)) {
976
- return {
977
- content: [
978
- {
979
- type: 'text',
980
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
955
+ text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
981
956
  },
982
957
  ],
983
958
  isError: true,
@@ -1319,7 +1294,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1319
1294
  content: [
1320
1295
  {
1321
1296
  type: 'text',
1322
- text: 'Push requires environment variables:\n- DOTREQUIREMENTS_PROJECT_ID\n- DOTREQUIREMENTS_PROJECT_SECRET',
1297
+ text: 'Push requires project credentials. Run `dotrequirements link` to connect to cloud.',
1323
1298
  },
1324
1299
  ],
1325
1300
  isError: true,
@@ -1328,18 +1303,6 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1328
1303
  const convexUrl = config.convexUrl;
1329
1304
  const projectId = config.projectId;
1330
1305
  const projectSecret = config.projectSecret;
1331
- // AUTHZ-2.1: Check if project is local-only
1332
- if (isLocalOnlyProject(projectId)) {
1333
- return {
1334
- content: [
1335
- {
1336
- type: 'text',
1337
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
1338
- },
1339
- ],
1340
- isError: true,
1341
- };
1342
- }
1343
1306
  // Determine files to push
1344
1307
  const requirementsDir = path.resolve(project.path, '.requirements');
1345
1308
  let filesToPush;
@@ -1503,7 +1466,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1503
1466
  content: [
1504
1467
  {
1505
1468
  type: 'text',
1506
- text: `Style check requires DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env or .env.local file.\n\nError: ${error instanceof Error ? error.message : String(error)}`,
1469
+ text: `Style check requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`,
1507
1470
  },
1508
1471
  ],
1509
1472
  isError: true,
@@ -1515,19 +1478,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1515
1478
  content: [
1516
1479
  {
1517
1480
  type: 'text',
1518
- text: 'Style check requires DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env or .env.local file.',
1519
- },
1520
- ],
1521
- isError: true,
1522
- };
1523
- }
1524
- // AUTHZ-2.1: Check if project is local-only
1525
- if (isLocalOnlyProject(config.projectId)) {
1526
- return {
1527
- content: [
1528
- {
1529
- type: 'text',
1530
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
1481
+ text: 'Style check requires project credentials. Run `dotrequirements link` to connect to cloud.',
1531
1482
  },
1532
1483
  ],
1533
1484
  isError: true,
@@ -1669,19 +1620,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1669
1620
  content: [
1670
1621
  {
1671
1622
  type: 'text',
1672
- text: 'Test review requires DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env or .env.local file.',
1673
- },
1674
- ],
1675
- isError: true,
1676
- };
1677
- }
1678
- // AUTHZ-2.1: Check if project is local-only
1679
- if (isLocalOnlyProject(config.projectId)) {
1680
- return {
1681
- content: [
1682
- {
1683
- type: 'text',
1684
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
1623
+ text: 'Test review requires project credentials. Run `dotrequirements link` to connect to cloud.',
1685
1624
  },
1686
1625
  ],
1687
1626
  isError: true,
@@ -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