@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
package/dist/convex.js CHANGED
@@ -37,6 +37,9 @@ export const api = {
37
37
  queries: {
38
38
  listForUserWithUsage: 'teams/queries:listForUserWithUsage',
39
39
  },
40
+ mutations: {
41
+ ensureDefaultTeam: 'teams/mutations:ensureDefaultTeam',
42
+ },
40
43
  },
41
44
  projectSecrets: {
42
45
  queries: {
@@ -61,11 +61,6 @@ export declare function findProjectRoot(startDir?: string): string | null;
61
61
  * Get the cache directory path, creating it if necessary
62
62
  */
63
63
  export declare function getCacheDir(requirementsDir: string, create?: boolean): string;
64
- /**
65
- * Find the dotrequirements project root by looking for .env.local with credentials.
66
- * Falls back to finding .requirements directory if no credentials found.
67
- */
68
- export declare function findProjectRootWithCredentials(startDir?: string): string;
69
64
  /**
70
65
  * Find all *.requirements.md files under project root
71
66
  */
@@ -9,8 +9,6 @@
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import { globSync } from 'glob';
12
- import { findUpSync } from 'find-up';
13
- import { brandPlain } from '../utils/brand.js';
14
12
  // Cache directory structure
15
13
  const CACHE_DIR = '.cache';
16
14
  const LOOKUP_FILE = 'lookup.json';
@@ -54,52 +52,6 @@ export function getCacheDir(requirementsDir, create = false) {
54
52
  }
55
53
  return cacheDir;
56
54
  }
57
- /**
58
- * Check if an .env.local file has dotrequirements credentials.
59
- */
60
- function hasDotrequirementsCredentials(envPath) {
61
- const envContent = fs.readFileSync(envPath, 'utf-8');
62
- const hasProjectId = envContent.includes('DOTREQUIREMENTS_PROJECT_ID=');
63
- const hasProjectSecret = envContent.includes('DOTREQUIREMENTS_PROJECT_SECRET=');
64
- return hasProjectId && hasProjectSecret;
65
- }
66
- /**
67
- * Find the dotrequirements project root by looking for .env.local with credentials.
68
- * Falls back to finding .requirements directory if no credentials found.
69
- */
70
- export function findProjectRootWithCredentials(startDir = process.cwd()) {
71
- let searchDir = startDir;
72
- while (true) {
73
- const envPath = findUpSync('.env.local', { cwd: searchDir });
74
- if (!envPath) {
75
- // No .env.local found - try to find project root by .requirements directory
76
- const projectRoot = findProjectRoot(startDir);
77
- if (projectRoot) {
78
- return projectRoot;
79
- }
80
- throw new Error(`Could not find ${brandPlain} project root. ` +
81
- `Expected to find .env.local with credentials or a .requirements directory.`);
82
- }
83
- // Check if this .env.local has dotrequirements credentials
84
- if (hasDotrequirementsCredentials(envPath)) {
85
- return path.dirname(envPath);
86
- }
87
- // No credentials - continue searching from parent directory
88
- const envDir = path.dirname(envPath);
89
- const parentDir = path.dirname(envDir);
90
- // If we've reached the root, stop
91
- if (parentDir === envDir) {
92
- // Try .requirements as fallback
93
- const projectRoot = findProjectRoot(startDir);
94
- if (projectRoot) {
95
- return projectRoot;
96
- }
97
- throw new Error(`Could not find ${brandPlain} project root. ` +
98
- `Found .env.local files but none contained credentials.`);
99
- }
100
- searchDir = parentDir;
101
- }
102
- }
103
55
  /**
104
56
  * Find all *.requirements.md files under project root
105
57
  */
@@ -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());
@@ -14,10 +14,16 @@ export interface FinalizeOptions {
14
14
  cwd?: string;
15
15
  /** Whether to report to cloud (defaults to true) */
16
16
  reportToCloud?: boolean;
17
- /** Whether to print local report (defaults to true) */
18
- printLocalReport?: boolean;
19
17
  /** Whether to clean up tracking data on success (defaults to true) */
20
18
  cleanup?: boolean;
19
+ /** Whether to show coverage summary stats (defaults to true) */
20
+ showSummary?: boolean;
21
+ /** Whether to show the list of tested requirements (defaults to false) */
22
+ showTestedList?: boolean;
23
+ /** Whether to show the list of untested requirements (defaults to true) */
24
+ showUntestedList?: boolean;
25
+ /** Whether to show cloud reporting status messages (defaults to true) */
26
+ showCloudStatus?: boolean;
21
27
  }
22
28
  export interface FinalizeResult {
23
29
  /** Number of unique requirements exercised */
@@ -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
  */
@@ -46,19 +45,24 @@ function getCurrentBranch(cwd) {
46
45
  *
47
46
  * HARNESS-FINALIZE-1: Shows which requirements were tested
48
47
  */
49
- function printLocalReport(testedKeys, lookup) {
48
+ function printLocalReport(testedKeys, lookup, options) {
49
+ const { showSummary, showTestedList, showUntestedList } = options;
50
50
  const allKeys = lookup ? Object.keys(lookup.requirements) : [];
51
51
  const untestedKeys = allKeys.filter(key => !testedKeys.includes(key));
52
52
  const total = allKeys.length;
53
53
  const tested = testedKeys.length;
54
54
  const coverage = total > 0 ? ((tested / total) * 100).toFixed(1) : '0.0';
55
- let report = '\n=== Requirements Coverage Report ===\n';
56
- report += `\nTotal Requirements: ${total}\n`;
57
- report += `Tested Requirements: ${tested}\n`;
58
- report += `Untested Requirements: ${untestedKeys.length}\n`;
59
- report += `Coverage: ${coverage}%\n`;
55
+ let report = '';
56
+ // Show summary stats
57
+ if (showSummary) {
58
+ report += '\n=== Requirements Coverage Report ===\n';
59
+ report += `\nTotal Requirements: ${total}\n`;
60
+ report += `Tested Requirements: ${tested}\n`;
61
+ report += `Untested Requirements: ${untestedKeys.length}\n`;
62
+ report += `Coverage: ${coverage}%\n`;
63
+ }
60
64
  // Show tested requirements
61
- if (testedKeys.length > 0) {
65
+ if (showTestedList && testedKeys.length > 0) {
62
66
  report += '\n✓ Tested Requirements:\n';
63
67
  for (const key of testedKeys) {
64
68
  const req = lookup?.requirements[key];
@@ -75,7 +79,7 @@ function printLocalReport(testedKeys, lookup) {
75
79
  }
76
80
  }
77
81
  // Show untested requirements
78
- if (untestedKeys.length > 0) {
82
+ if (showUntestedList && untestedKeys.length > 0) {
79
83
  report += '\n✗ Untested Requirements:\n';
80
84
  for (const key of untestedKeys) {
81
85
  const req = lookup?.requirements[key];
@@ -91,8 +95,12 @@ function printLocalReport(testedKeys, lookup) {
91
95
  }
92
96
  }
93
97
  }
94
- report += '\n====================================\n';
95
- console.log(report);
98
+ if (showSummary) {
99
+ report += '\n====================================\n';
100
+ }
101
+ if (report) {
102
+ console.log(report);
103
+ }
96
104
  }
97
105
  /**
98
106
  * Production Convex deployment URL
@@ -104,20 +112,18 @@ const CONVEX_URL = 'https://data.dotrequirements.io';
104
112
  * HARNESS-FINALIZE-2: Cloud reporting with error tolerance
105
113
  * HARNESS-FINALIZE-3: Coverage records include requirement, file, line, branch
106
114
  */
107
- async function reportToConvex(projectRoot, testRunId, aggregated) {
115
+ async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatus) {
108
116
  try {
109
- // Load credentials
110
- loadEnvFile(projectRoot);
111
- const credentials = getProjectCredentials();
112
- if (!credentials) {
113
- console.log('\nℹ️ Skipping cloud coverage reporting (DOTREQUIREMENTS_PROJECT_ID or DOTREQUIREMENTS_PROJECT_SECRET not configured)');
114
- return { sent: false, count: 0 };
115
- }
116
- const { projectId, projectSecret } = credentials;
117
- // Skip for local-only projects
118
- if (isLocalOnlyProject(projectId)) {
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) {
121
+ if (showCloudStatus) {
122
+ console.log('\nℹ️ Skipping cloud coverage reporting (project not connected to cloud)');
123
+ }
119
124
  return { sent: false, count: 0 };
120
125
  }
126
+ const { projectId, projectSecret } = projectInfo.credentials;
121
127
  const requirementsDir = findRequirementsDir(projectRoot);
122
128
  // Get all tracked requirement keys
123
129
  const requirementKeys = Array.from(aggregated.keys());
@@ -125,7 +131,9 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
125
131
  const coverageCache = readCoverageCache(requirementsDir);
126
132
  const keysToReport = requirementKeys.filter(key => needsReporting(key, coverageCache));
127
133
  if (keysToReport.length === 0) {
128
- console.log('\n✓ Coverage unchanged since last run (skipping cloud report)');
134
+ if (showCloudStatus) {
135
+ console.log('\n✓ Coverage unchanged since last run (skipping cloud report)');
136
+ }
129
137
  // Still update the cache
130
138
  updateCoverageCache(requirementsDir, testRunId, requirementKeys);
131
139
  return { sent: false, count: 0 };
@@ -184,7 +192,9 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
184
192
  });
185
193
  if (!response.ok) {
186
194
  const error = await response.text();
187
- console.warn(`\n⚠️ Failed to report coverage to cloud: ${response.status} ${error}`);
195
+ if (showCloudStatus) {
196
+ console.warn(`\n⚠️ Failed to report coverage to cloud: ${response.status} ${error}`);
197
+ }
188
198
  return { sent: false, count: 0, error: `${response.status} ${error}` };
189
199
  }
190
200
  // Check for Convex-level errors in the response body
@@ -201,18 +211,24 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
201
211
  const convexResponse = responseData;
202
212
  if (convexResponse.status === 'error') {
203
213
  const errorMsg = convexResponse.errorMessage || 'Unknown Convex error';
204
- console.warn(`\n⚠️ Failed to report coverage to cloud: ${errorMsg}`);
214
+ if (showCloudStatus) {
215
+ console.warn(`\n⚠️ Failed to report coverage to cloud: ${errorMsg}`);
216
+ }
205
217
  return { sent: false, count: 0, error: errorMsg };
206
218
  }
207
219
  }
208
- console.log(`\n✓ Reported ${keysToReport.length} requirement(s) to cloud (branch: ${branch})`);
220
+ if (showCloudStatus) {
221
+ console.log(`\n✓ Reported ${keysToReport.length} requirement(s) to cloud (branch: ${branch})`);
222
+ }
209
223
  // Update coverage cache
210
224
  updateCoverageCache(requirementsDir, testRunId, requirementKeys);
211
225
  return { sent: true, count: keysToReport.length };
212
226
  }
213
227
  catch (error) {
214
228
  const errorMessage = error instanceof Error ? error.message : String(error);
215
- console.warn(`\n⚠️ Error reporting coverage to cloud: ${errorMessage}`);
229
+ if (showCloudStatus) {
230
+ console.warn(`\n⚠️ Error reporting coverage to cloud: ${errorMessage}`);
231
+ }
216
232
  return { sent: false, count: 0, error: errorMessage };
217
233
  }
218
234
  }
@@ -228,7 +244,7 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
228
244
  * HARNESS-FINALIZE-4: Finalize cleans up after itself
229
245
  */
230
246
  export async function finalize(options = {}) {
231
- const { cwd = process.cwd(), reportToCloud: shouldReportToCloud = true, printLocalReport: shouldPrintLocal = true, cleanup: shouldCleanup = true, } = options;
247
+ const { cwd = process.cwd(), reportToCloud: shouldReportToCloud = true, cleanup: shouldCleanup = true, showSummary = true, showTestedList = false, showUntestedList = true, showCloudStatus = true, } = options;
232
248
  // Priority 1: Use environment variable (cross-process persistence from globalSetup)
233
249
  let projectRoot = process.env.DOTREQUIREMENTS_PROJECT_ROOT || null;
234
250
  // Priority 2: Find from cwd
@@ -270,13 +286,14 @@ export async function finalize(options = {}) {
270
286
  ? (testedKeys.length / totalRequirements) * 100
271
287
  : 0;
272
288
  // HARNESS-FINALIZE-1: Print local report
289
+ const shouldPrintLocal = showSummary || showTestedList || showUntestedList;
273
290
  if (shouldPrintLocal) {
274
- printLocalReport(testedKeys, lookup);
291
+ printLocalReport(testedKeys, lookup, { showSummary, showTestedList, showUntestedList });
275
292
  }
276
293
  // HARNESS-FINALIZE-2, HARNESS-FINALIZE-3: Report to cloud
277
294
  let cloudResult = { sent: false, count: 0 };
278
295
  if (shouldReportToCloud) {
279
- cloudResult = await reportToConvex(projectRoot, testRunId, aggregated);
296
+ cloudResult = await reportToConvex(projectRoot, testRunId, aggregated, showCloudStatus);
280
297
  }
281
298
  // HARNESS-FINALIZE-4: Clean up tracking data
282
299
  if (shouldCleanup && !cloudResult.error) {
@@ -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';
@@ -33,6 +32,17 @@ function getProjectPathsFromEnv() {
33
32
  // Get workspace root from environment or default to cwd
34
33
  const WORKSPACE_ROOT = process.env.REQUIREMENTS_DIR || process.cwd();
35
34
  const PROJECT_PATHS = getProjectPathsFromEnv();
35
+ const STYLE_CHECK_GUIDANCE = `
36
+
37
+ ---
38
+
39
+ ## How to use this feedback
40
+
41
+ 1. **This check is stateless.** It is a "new set of eyes" from any previous feedback rounds — it has no memory of prior suggestions or changes you've already made.
42
+
43
+ 2. **Act on feedback from the posture of "what will make the requirements better."** The severity categories (MUST FIX / SHOULD FIX / COULD IMPROVE) are prioritized for convenience, but unless the user has explicitly told you to ignore lower-priority feedback, go ahead and make any improvements — minor or otherwise. If you believe acting on a specific piece of feedback would make the requirements *worse*, surface that feedback to the user rather than silently ignoring it. Otherwise, just act.
44
+
45
+ 3. **Next steps:** Always run \`validate_requirements\` again after making updates. If you make significant changes based on this feedback, you can run \`style_check\` again — but be aware that will be a fresh set of eyes, not a continued conversation.`;
36
46
  // Cache for loaded requirements (refreshed on each tool call for now)
37
47
  let cachedRequirements = new Map();
38
48
  let cachedDiscoveryResult = null;
@@ -200,7 +210,7 @@ const tools = [
200
210
  },
201
211
  {
202
212
  name: 'get_requirement_coverage',
203
- 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`).',
204
214
  inputSchema: {
205
215
  type: 'object',
206
216
  properties: {
@@ -218,7 +228,7 @@ const tools = [
218
228
  },
219
229
  {
220
230
  name: 'get_project_coverage_summary',
221
- 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`).',
222
232
  inputSchema: {
223
233
  type: 'object',
224
234
  properties: {
@@ -268,7 +278,7 @@ const tools = [
268
278
  },
269
279
  {
270
280
  name: 'push_requirements',
271
- 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`).',
272
282
  inputSchema: {
273
283
  type: 'object',
274
284
  properties: {
@@ -290,7 +300,7 @@ const tools = [
290
300
  },
291
301
  {
292
302
  name: 'style_check',
293
- 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`).',
294
304
  inputSchema: {
295
305
  type: 'object',
296
306
  properties: {
@@ -308,7 +318,7 @@ const tools = [
308
318
  },
309
319
  {
310
320
  name: 'review_test',
311
- 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`).',
312
322
  inputSchema: {
313
323
  type: 'object',
314
324
  properties: {
@@ -405,7 +415,7 @@ Fill in:
405
415
  After capturing requirements:
406
416
  1. Implement the feature
407
417
  2. Write tests that reference requirements using \`requirement('REQ-ID')\`
408
- 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`,
409
419
  },
410
420
  },
411
421
  ],
@@ -895,19 +905,7 @@ ${projectList || '(none found)'}`,
895
905
  content: [
896
906
  {
897
907
  type: 'text',
898
- text: 'Coverage queries require DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env file.',
899
- },
900
- ],
901
- isError: true,
902
- };
903
- }
904
- // AUTHZ-2.1: Check if project is local-only
905
- if (isLocalOnlyProject(config.projectId)) {
906
- return {
907
- content: [
908
- {
909
- type: 'text',
910
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
908
+ text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
911
909
  },
912
910
  ],
913
911
  isError: true,
@@ -954,19 +952,7 @@ ${projectList || '(none found)'}`,
954
952
  content: [
955
953
  {
956
954
  type: 'text',
957
- text: 'Coverage queries require DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env file.',
958
- },
959
- ],
960
- isError: true,
961
- };
962
- }
963
- // AUTHZ-2.1: Check if project is local-only
964
- if (isLocalOnlyProject(config.projectId)) {
965
- return {
966
- content: [
967
- {
968
- type: 'text',
969
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
955
+ text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
970
956
  },
971
957
  ],
972
958
  isError: true,
@@ -1070,9 +1056,6 @@ ${projectList || '(none found)'}`,
1070
1056
  **Use concise domain prefixes** like \`AUTH-1\`, \`LOGIN-1\`, etc. Start numbering at 1 and increment sequentially.`;
1071
1057
  }
1072
1058
  const template = `---
1073
- projectId: "your-project-id"
1074
- pulledAt: "${new Date().toISOString()}"
1075
- version: 1
1076
1059
  document:
1077
1060
  title: "Example Requirements"
1078
1061
  ---
@@ -1280,7 +1263,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1280
1263
  content: [
1281
1264
  {
1282
1265
  type: 'text',
1283
- text: `${headline}${pushDetails}\n\n**Metadata:**\n- Project ID: ${parsed.metadata.projectId}\n- Version: ${parsed.metadata.version}\n- Document: ${parsed.metadata.document?.title || '(none)'}\n- Document ID: ${parsed.metadata.document?.id || '(none)'}\n\n**Requirements:** ${reqCount} root requirement(s) found`,
1266
+ text: `${headline}${pushDetails}\n\n**Metadata:**\n- Version: ${parsed.metadata.version ?? '(none)'}\n- Document: ${parsed.metadata.document?.title || '(none)'}\n- Document ID: ${parsed.metadata.document?.id || '(none)'}\n\n**Requirements:** ${reqCount} root requirement(s) found`,
1284
1267
  },
1285
1268
  ],
1286
1269
  };
@@ -1311,7 +1294,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1311
1294
  content: [
1312
1295
  {
1313
1296
  type: 'text',
1314
- 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.',
1315
1298
  },
1316
1299
  ],
1317
1300
  isError: true,
@@ -1320,18 +1303,6 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1320
1303
  const convexUrl = config.convexUrl;
1321
1304
  const projectId = config.projectId;
1322
1305
  const projectSecret = config.projectSecret;
1323
- // AUTHZ-2.1: Check if project is local-only
1324
- if (isLocalOnlyProject(projectId)) {
1325
- return {
1326
- content: [
1327
- {
1328
- type: 'text',
1329
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
1330
- },
1331
- ],
1332
- isError: true,
1333
- };
1334
- }
1335
1306
  // Determine files to push
1336
1307
  const requirementsDir = path.resolve(project.path, '.requirements');
1337
1308
  let filesToPush;
@@ -1495,7 +1466,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1495
1466
  content: [
1496
1467
  {
1497
1468
  type: 'text',
1498
- 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)}`,
1499
1470
  },
1500
1471
  ],
1501
1472
  isError: true,
@@ -1507,19 +1478,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1507
1478
  content: [
1508
1479
  {
1509
1480
  type: 'text',
1510
- text: 'Style check requires DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env or .env.local file.',
1511
- },
1512
- ],
1513
- isError: true,
1514
- };
1515
- }
1516
- // AUTHZ-2.1: Check if project is local-only
1517
- if (isLocalOnlyProject(config.projectId)) {
1518
- return {
1519
- content: [
1520
- {
1521
- type: 'text',
1522
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
1481
+ text: 'Style check requires project credentials. Run `dotrequirements link` to connect to cloud.',
1523
1482
  },
1524
1483
  ],
1525
1484
  isError: true,
@@ -1559,7 +1518,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1559
1518
  content: [
1560
1519
  {
1561
1520
  type: 'text',
1562
- text: `# Style Check Results for \`${filePath}\`\n\n${data.feedback}`,
1521
+ text: `# Style Check Results for \`${filePath}\`\n\n${data.feedback}${STYLE_CHECK_GUIDANCE}`,
1563
1522
  },
1564
1523
  ],
1565
1524
  };
@@ -1661,19 +1620,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
1661
1620
  content: [
1662
1621
  {
1663
1622
  type: 'text',
1664
- text: 'Test review requires DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET to be configured in your .env or .env.local file.',
1665
- },
1666
- ],
1667
- isError: true,
1668
- };
1669
- }
1670
- // AUTHZ-2.1: Check if project is local-only
1671
- if (isLocalOnlyProject(config.projectId)) {
1672
- return {
1673
- content: [
1674
- {
1675
- type: 'text',
1676
- text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
1623
+ text: 'Test review requires project credentials. Run `dotrequirements link` to connect to cloud.',
1677
1624
  },
1678
1625
  ],
1679
1626
  isError: true,