@popoverai/dotrequirements 0.13.0 → 0.15.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 (57) hide show
  1. package/README.md +92 -48
  2. package/dist/cli.js +1 -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.d.ts +1 -0
  8. package/dist/commands/pull.js +70 -53
  9. package/dist/commands/push.js +4 -13
  10. package/dist/config.js +0 -4
  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.js +7 -12
  15. package/dist/harness/prepare.js +7 -9
  16. package/dist/mcp/convexClient.d.ts +9 -1
  17. package/dist/mcp/convexClient.js +15 -35
  18. package/dist/mcp/index.js +112 -152
  19. package/dist/mcp/requirements.d.ts +10 -0
  20. package/dist/mcp/requirements.js +15 -1
  21. package/dist/templates/context-file-section.md +59 -0
  22. package/dist/utils/context-file.d.ts +38 -0
  23. package/dist/utils/context-file.js +94 -0
  24. package/dist/utils/env.d.ts +0 -13
  25. package/dist/utils/env.js +0 -19
  26. package/dist/utils/gitignore.d.ts +2 -2
  27. package/dist/utils/gitignore.js +4 -4
  28. package/dist/utils/oauth-flow.d.ts +0 -1
  29. package/dist/utils/oauth-flow.js +0 -9
  30. package/dist/utils/project-discovery.d.ts +3 -5
  31. package/dist/utils/project-discovery.js +18 -42
  32. package/dist/utils/project-selector.d.ts +17 -3
  33. package/dist/utils/project-selector.js +35 -3
  34. package/dist/utils/project-settings.d.ts +56 -0
  35. package/dist/utils/project-settings.js +126 -0
  36. package/dist/utils/templates.d.ts +0 -24
  37. package/dist/utils/templates.js +0 -39
  38. package/package.json +1 -1
  39. package/dist/harness/localReporting.d.ts +0 -6
  40. package/dist/harness/localReporting.js +0 -49
  41. package/dist/templates/antigravity-gemini.md +0 -3
  42. package/dist/templates/antigravity-overview-rule.md +0 -3
  43. package/dist/templates/antigravity-test-rule.md +0 -3
  44. package/dist/templates/behavioral-core.md +0 -25
  45. package/dist/templates/claude-code-overview-skill.md +0 -6
  46. package/dist/templates/claude-code-skill.md +0 -6
  47. package/dist/templates/claude-code-test-skill.md +0 -6
  48. package/dist/templates/codex-agents.md +0 -3
  49. package/dist/templates/codex-overview-agents.md +0 -3
  50. package/dist/templates/codex-test-agents.md +0 -3
  51. package/dist/templates/cursor-overview-rule.mdc +0 -5
  52. package/dist/templates/cursor-rule.mdc +0 -5
  53. package/dist/templates/cursor-test-rule.mdc +0 -5
  54. package/dist/templates/overview-core.md +0 -27
  55. package/dist/templates/test-writing-core.md +0 -72
  56. package/dist/utils/detect-existing-project.d.ts +0 -5
  57. package/dist/utils/detect-existing-project.js +0 -34
package/dist/mcp/index.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import { loadAllRequirements, searchRequirements, getRequirementById, getRequirementTree, formatRequirementTree, } from './requirements.js';
5
+ import { loadAllRequirements, searchRequirements, getRequirementById, getRequirementTree, formatRequirementTree, filterRequirementsByKeys, } from './requirements.js';
6
6
  import { findAllTestReferences, getReferencedRequirementIds, } from './grep.js';
7
7
  import { findTestCodeForRequirement, findFilesWithRequirement, } from './testCodeExtractor.js';
8
8
  import { glob } from 'glob';
9
- import { loadConvexConfig, getRequirementCoverage as queryRequirementCoverage, getProjectCoverage as queryProjectCoverage, } from './convexClient.js';
9
+ import { CONVEX_URL, 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';
11
+ import { getCredentialsFromEnv } from '../utils/project-settings.js';
12
12
  import { readFileSync } from 'fs';
13
13
  import { fileURLToPath } from 'url';
14
14
  import { dirname, join } from 'path';
@@ -33,6 +33,9 @@ function getProjectPathsFromEnv() {
33
33
  // Get workspace root from environment or default to cwd
34
34
  const WORKSPACE_ROOT = process.env.REQUIREMENTS_DIR || process.cwd();
35
35
  const PROJECT_PATHS = getProjectPathsFromEnv();
36
+ // Parse --auth-from-env flag for CI/CD environments
37
+ // When set, credentials are read from DOTREQ_PROJECT_ID and DOTREQ_PROJECT_SECRET env vars
38
+ const USE_ENV_AUTH = process.argv.includes('--auth-from-env');
36
39
  const STYLE_CHECK_GUIDANCE = `
37
40
 
38
41
  ---
@@ -58,6 +61,20 @@ async function getRequirements(projectId) {
58
61
  }
59
62
  async function getProjectFromDiscovery(projectId) {
60
63
  const { isConfiguredProject } = await import('../utils/project-discovery.js');
64
+ // If --auth-from-env flag is set, use credentials from environment variables
65
+ // This is the explicit opt-in for CI/CD environments
66
+ if (USE_ENV_AUTH) {
67
+ const envCredentials = getCredentialsFromEnv();
68
+ if (!envCredentials) {
69
+ throw new Error('--auth-from-env flag requires DOTREQ_PROJECT_ID and DOTREQ_PROJECT_SECRET environment variables to be set');
70
+ }
71
+ // Return a synthetic project using env credentials and workspace root
72
+ return {
73
+ path: WORKSPACE_ROOT,
74
+ projectId: envCredentials.projectId,
75
+ projectSecret: envCredentials.projectSecret,
76
+ };
77
+ }
61
78
  // If we have PROJ_* env vars, use those instead of filesystem discovery
62
79
  if (PROJECT_PATHS.size > 0) {
63
80
  const projects = [];
@@ -211,7 +228,7 @@ const tools = [
211
228
  },
212
229
  {
213
230
  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.',
231
+ 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
232
  inputSchema: {
216
233
  type: 'object',
217
234
  properties: {
@@ -229,7 +246,7 @@ const tools = [
229
246
  },
230
247
  {
231
248
  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.',
249
+ 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
250
  inputSchema: {
234
251
  type: 'object',
235
252
  properties: {
@@ -279,7 +296,7 @@ const tools = [
279
296
  },
280
297
  {
281
298
  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.',
299
+ 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
300
  inputSchema: {
284
301
  type: 'object',
285
302
  properties: {
@@ -301,7 +318,7 @@ const tools = [
301
318
  },
302
319
  {
303
320
  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.',
321
+ 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.*). For requirements files, you can optionally specify requirement keys to check only those requirements instead of the entire file. Requires project to be linked to cloud (run `dotrequirements link`).',
305
322
  inputSchema: {
306
323
  type: 'object',
307
324
  properties: {
@@ -309,6 +326,11 @@ const tools = [
309
326
  type: 'string',
310
327
  description: 'Path to the file to check (e.g., ".requirements/auth.requirements.md" or "src/auth.test.ts")',
311
328
  },
329
+ requirementKeys: {
330
+ type: 'array',
331
+ items: { type: 'string' },
332
+ description: 'Optional: Array of requirement keys to check (e.g., ["AUTH-1", "AUTH-2"]). Only valid for requirements files (*.requirements.md). When provided, only these requirements are checked instead of the entire file.',
333
+ },
312
334
  model: {
313
335
  type: 'string',
314
336
  description: 'Optional: AI model to use for style checking (default: "anthropic/claude-haiku-4.5"). Supported models: "anthropic/claude-haiku-4.5", "google/gemini-3-flash"',
@@ -319,7 +341,7 @@ const tools = [
319
341
  },
320
342
  {
321
343
  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.',
344
+ 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
345
  inputSchema: {
324
346
  type: 'object',
325
347
  properties: {
@@ -416,7 +438,7 @@ Fill in:
416
438
  After capturing requirements:
417
439
  1. Implement the feature
418
440
  2. Write tests that reference requirements using \`requirement('REQ-ID')\`
419
- 3. Use the test writing skill/prompt for guidance on test structure`,
441
+ 3. See your project's context file (CLAUDE.md/AGENTS.md) for test structure guidance`,
420
442
  },
421
443
  },
422
444
  ],
@@ -900,31 +922,7 @@ ${projectList || '(none found)'}`,
900
922
  case 'get_requirement_coverage': {
901
923
  const { requirementKey, projectId } = args;
902
924
  const project = await getProjectFromDiscovery(projectId);
903
- const config = loadConvexConfig(project.path);
904
- if (!config) {
905
- return {
906
- content: [
907
- {
908
- 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,
922
- },
923
- ],
924
- isError: true,
925
- };
926
- }
927
- const coverage = await queryRequirementCoverage(requirementKey, config.projectId, config.projectSecret, config.convexUrl);
925
+ const coverage = await queryRequirementCoverage(requirementKey, project.projectId, project.projectSecret, CONVEX_URL);
928
926
  if (!coverage.lastTestedAt) {
929
927
  return {
930
928
  content: [
@@ -959,31 +957,7 @@ ${projectList || '(none found)'}`,
959
957
  case 'get_project_coverage_summary': {
960
958
  const { branch, sinceTimestamp, projectId } = args;
961
959
  const project = await getProjectFromDiscovery(projectId);
962
- const config = loadConvexConfig(project.path);
963
- if (!config) {
964
- return {
965
- content: [
966
- {
967
- 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,
981
- },
982
- ],
983
- isError: true,
984
- };
985
- }
986
- const coverage = await queryProjectCoverage(config.projectId, config.projectSecret, config.convexUrl, { branch, sinceTimestamp });
960
+ const coverage = await queryProjectCoverage(project.projectId, project.projectSecret, CONVEX_URL, { branch, sinceTimestamp });
987
961
  const total = coverage.tested.length + coverage.untested.length;
988
962
  const percentage = total > 0 ? ((coverage.tested.length / total) * 100).toFixed(1) : '0.0';
989
963
  let filterInfo = '';
@@ -1311,35 +1285,11 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1311
1285
  const path = await import('path');
1312
1286
  const { parseRequirementsFromFile, getAllRequirements } = await import('../schema/index.js');
1313
1287
  const { ConvexHttpClient } = await import('convex/browser');
1314
- // Get project
1288
+ // Get project (credentials come from project object, works with both file and env auth)
1315
1289
  const project = await getProjectFromDiscovery(projectIdParam);
1316
- const config = loadConvexConfig(project.path);
1317
- if (!config) {
1318
- return {
1319
- content: [
1320
- {
1321
- type: 'text',
1322
- text: 'Push requires environment variables:\n- DOTREQUIREMENTS_PROJECT_ID\n- DOTREQUIREMENTS_PROJECT_SECRET',
1323
- },
1324
- ],
1325
- isError: true,
1326
- };
1327
- }
1328
- const convexUrl = config.convexUrl;
1329
- const projectId = config.projectId;
1330
- 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
- }
1290
+ const convexUrl = CONVEX_URL;
1291
+ const projectId = project.projectId;
1292
+ const projectSecret = project.projectSecret;
1343
1293
  // Determine files to push
1344
1294
  const requirementsDir = path.resolve(project.path, '.requirements');
1345
1295
  let filesToPush;
@@ -1448,7 +1398,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1448
1398
  }
1449
1399
  }
1450
1400
  case 'style_check': {
1451
- const { filePath, model } = args;
1401
+ const { filePath, model, requirementKeys } = args;
1452
1402
  const fs = await import('fs');
1453
1403
  const path = await import('path');
1454
1404
  const fullPath = path.resolve(WORKSPACE_ROOT, filePath);
@@ -1480,22 +1430,73 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1480
1430
  };
1481
1431
  }
1482
1432
  const fileType = isRequirementsFile ? 'requirements' : 'test';
1433
+ // If requirementKeys provided, filter the file to only those requirements
1434
+ let fileContentsToCheck = fileContents;
1435
+ let scopeNote = '';
1436
+ if (requirementKeys && requirementKeys.length > 0) {
1437
+ if (!isRequirementsFile) {
1438
+ return {
1439
+ content: [
1440
+ {
1441
+ type: 'text',
1442
+ text: `requirementKeys can only be used with requirements files (*.requirements.md), not test files.`,
1443
+ },
1444
+ ],
1445
+ isError: true,
1446
+ };
1447
+ }
1448
+ try {
1449
+ const { filteredContent, foundKeys, missingKeys } = filterRequirementsByKeys(fileContents, requirementKeys);
1450
+ if (foundKeys.length === 0) {
1451
+ return {
1452
+ content: [
1453
+ {
1454
+ type: 'text',
1455
+ text: `None of the specified requirement keys were found in ${filePath}: ${requirementKeys.join(', ')}`,
1456
+ },
1457
+ ],
1458
+ isError: true,
1459
+ };
1460
+ }
1461
+ fileContentsToCheck = filteredContent;
1462
+ if (missingKeys.length > 0) {
1463
+ scopeNote = `\n\n> **Note:** Some specified keys were not found in the file: ${missingKeys.join(', ')}`;
1464
+ }
1465
+ }
1466
+ catch (error) {
1467
+ return {
1468
+ content: [
1469
+ {
1470
+ type: 'text',
1471
+ text: `Failed to filter requirements: ${error instanceof Error ? error.message : String(error)}`,
1472
+ },
1473
+ ],
1474
+ isError: true,
1475
+ };
1476
+ }
1477
+ }
1483
1478
  // Get Convex config for credentials - walk up from file's directory to find project
1484
1479
  const fileDir = path.dirname(fullPath);
1485
1480
  let project;
1486
1481
  try {
1487
- // Try to find a project starting from the file's directory
1488
- const result = await discoverProjects(fileDir);
1489
- if (result.type === 'none') {
1490
- // If no project found from file dir, try from WORKSPACE_ROOT
1482
+ // If using env auth, go straight to getProjectFromDiscovery (handles env credentials)
1483
+ if (USE_ENV_AUTH) {
1491
1484
  project = await getProjectFromDiscovery();
1492
1485
  }
1493
- else if (result.type === 'single') {
1494
- project = result.project;
1495
- }
1496
1486
  else {
1497
- // Multiple projects - can't auto-detect which one to use
1498
- throw new Error('Multiple projects found - cannot auto-detect for this file');
1487
+ // Try to find a project starting from the file's directory
1488
+ const result = await discoverProjects(fileDir);
1489
+ if (result.type === 'none') {
1490
+ // If no project found from file dir, try from WORKSPACE_ROOT
1491
+ project = await getProjectFromDiscovery();
1492
+ }
1493
+ else if (result.type === 'single') {
1494
+ project = result.project;
1495
+ }
1496
+ else {
1497
+ // Multiple projects - can't auto-detect which one to use
1498
+ throw new Error('Multiple projects found - cannot auto-detect for this file');
1499
+ }
1499
1500
  }
1500
1501
  }
1501
1502
  catch (error) {
@@ -1503,36 +1504,14 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1503
1504
  content: [
1504
1505
  {
1505
1506
  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)}`,
1507
- },
1508
- ],
1509
- isError: true,
1510
- };
1511
- }
1512
- const config = loadConvexConfig(project.path);
1513
- if (!config) {
1514
- return {
1515
- content: [
1516
- {
1517
- 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,
1507
+ text: `Style check requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`,
1531
1508
  },
1532
1509
  ],
1533
1510
  isError: true,
1534
1511
  };
1535
1512
  }
1513
+ // Use credentials from project (works with both file-based and env-based auth)
1514
+ const { projectId: projId, projectSecret: projSecret } = project;
1536
1515
  // Call Vercel API endpoint for style checking
1537
1516
  // Default to production, allow override via env var for local dev
1538
1517
  const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL || 'https://app.dotrequirements.io';
@@ -1543,9 +1522,9 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1543
1522
  'Content-Type': 'application/json',
1544
1523
  },
1545
1524
  body: JSON.stringify({
1546
- projectId: config.projectId,
1547
- projectSecret: config.projectSecret,
1548
- fileContents,
1525
+ projectId: projId,
1526
+ projectSecret: projSecret,
1527
+ fileContents: fileContentsToCheck,
1549
1528
  fileType,
1550
1529
  model,
1551
1530
  }),
@@ -1563,11 +1542,14 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1563
1542
  };
1564
1543
  }
1565
1544
  const data = await response.json();
1545
+ const scopeLabel = requirementKeys && requirementKeys.length > 0
1546
+ ? ` (${requirementKeys.join(', ')})`
1547
+ : '';
1566
1548
  return {
1567
1549
  content: [
1568
1550
  {
1569
1551
  type: 'text',
1570
- text: `# Style Check Results for \`${filePath}\`\n\n${data.feedback}${STYLE_CHECK_GUIDANCE}`,
1552
+ text: `# Style Check Results for \`${filePath}\`${scopeLabel}\n\n${data.feedback}${scopeNote}${STYLE_CHECK_GUIDANCE}`,
1571
1553
  },
1572
1554
  ],
1573
1555
  };
@@ -1663,30 +1645,8 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1663
1645
  });
1664
1646
  }
1665
1647
  // Get Convex config
1666
- const config = loadConvexConfig(project.path);
1667
- if (!config) {
1668
- return {
1669
- content: [
1670
- {
1671
- 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,
1685
- },
1686
- ],
1687
- isError: true,
1688
- };
1689
- }
1648
+ // Use credentials from project (works with both file-based and env-based auth)
1649
+ const { projectId: projId, projectSecret: projSecret } = project;
1690
1650
  // Call Vercel API endpoint for test review
1691
1651
  // Default to production, allow override via env var for local dev
1692
1652
  const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL || 'https://app.dotrequirements.io';
@@ -1697,8 +1657,8 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1697
1657
  'Content-Type': 'application/json',
1698
1658
  },
1699
1659
  body: JSON.stringify({
1700
- projectId: config.projectId,
1701
- projectSecret: config.projectSecret,
1660
+ projectId: projId,
1661
+ projectSecret: projSecret,
1702
1662
  testFileContents,
1703
1663
  requirements,
1704
1664
  }),
@@ -40,6 +40,16 @@ export declare function getRequirementTree(requirements: FlattenedRequirement[],
40
40
  * Format a requirement for display
41
41
  */
42
42
  export declare function formatRequirement(req: FlattenedRequirement): string;
43
+ /**
44
+ * Filter a requirements file's content to only include specified requirement keys.
45
+ * Parses the file content, filters top-level requirements by key, and rebuilds as
46
+ * markdown requirement blocks (without frontmatter).
47
+ */
48
+ export declare function filterRequirementsByKeys(fileContents: string, keys: string[]): {
49
+ filteredContent: string;
50
+ foundKeys: string[];
51
+ missingKeys: string[];
52
+ };
43
53
  /**
44
54
  * Format a requirement tree for display
45
55
  */
@@ -1,6 +1,6 @@
1
1
  import * as path from 'path';
2
2
  import { glob } from 'glob';
3
- import { parseRequirementsFromFile, getAllRequirements, } from '../schema/index.js';
3
+ import { parseRequirementsFromFile, parseRequirementsFile, buildRequirementMarkdown, getAllRequirements, } from '../schema/index.js';
4
4
  /**
5
5
  * Find all *.requirements.md files in the workspace.
6
6
  * Supports both .requirements/ directories and colocated files.
@@ -114,6 +114,20 @@ export function formatRequirement(req) {
114
114
  const label = req.label ? ` (${req.label})` : '';
115
115
  return `${indent}${req.id}${label}: ${req.content}`;
116
116
  }
117
+ /**
118
+ * Filter a requirements file's content to only include specified requirement keys.
119
+ * Parses the file content, filters top-level requirements by key, and rebuilds as
120
+ * markdown requirement blocks (without frontmatter).
121
+ */
122
+ export function filterRequirementsByKeys(fileContents, keys) {
123
+ const { requirements } = parseRequirementsFile(fileContents);
124
+ const upperKeys = keys.map(k => k.toUpperCase());
125
+ const filtered = requirements.filter(r => upperKeys.includes(r.id.toUpperCase()));
126
+ const foundKeys = filtered.map(r => r.id);
127
+ const missingKeys = upperKeys.filter(k => !foundKeys.map(f => f.toUpperCase()).includes(k));
128
+ const filteredContent = filtered.map(buildRequirementMarkdown).join('\n');
129
+ return { filteredContent, foundKeys, missingKeys };
130
+ }
117
131
  /**
118
132
  * Format a requirement tree for display
119
133
  */
@@ -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