@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.
- package/README.md +66 -61
- package/dist/cli.js +0 -10
- package/dist/commands/init.js +167 -224
- package/dist/commands/link.d.ts +9 -10
- package/dist/commands/link.js +81 -106
- package/dist/commands/mcp-setup.js +77 -94
- package/dist/commands/pull.js +17 -45
- package/dist/commands/push.js +9 -24
- package/dist/convex.d.ts +3 -0
- package/dist/convex.js +3 -0
- package/dist/harness/cache.d.ts +0 -5
- package/dist/harness/cache.js +0 -48
- package/dist/harness/convexReporting.js +7 -14
- package/dist/harness/finalize.d.ts +8 -2
- package/dist/harness/finalize.js +48 -31
- package/dist/harness/prepare.js +7 -9
- package/dist/mcp/convexClient.d.ts +5 -1
- package/dist/mcp/convexClient.js +14 -34
- package/dist/mcp/index.js +25 -78
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +2 -3
- package/dist/schema/schemas.d.ts +14 -37
- package/dist/schema/schemas.js +7 -10
- package/dist/schema/test-schema.js +1 -1
- package/dist/templates/context-file-section.md +59 -0
- package/dist/utils/context-file.d.ts +38 -0
- package/dist/utils/context-file.js +94 -0
- package/dist/utils/env.d.ts +0 -13
- package/dist/utils/env.js +0 -19
- package/dist/utils/gitignore.d.ts +2 -2
- package/dist/utils/gitignore.js +4 -4
- package/dist/utils/oauth-flow.d.ts +0 -1
- package/dist/utils/oauth-flow.js +0 -9
- package/dist/utils/project-discovery.d.ts +3 -5
- package/dist/utils/project-discovery.js +18 -42
- package/dist/utils/project-selector.d.ts +17 -3
- package/dist/utils/project-selector.js +37 -3
- package/dist/utils/project-settings.d.ts +47 -0
- package/dist/utils/project-settings.js +110 -0
- package/dist/utils/templates.d.ts +0 -24
- package/dist/utils/templates.js +0 -39
- package/package.json +1 -1
- package/dist/harness/localReporting.d.ts +0 -6
- package/dist/harness/localReporting.js +0 -49
- package/dist/templates/antigravity-gemini.md +0 -3
- package/dist/templates/antigravity-overview-rule.md +0 -3
- package/dist/templates/antigravity-test-rule.md +0 -3
- package/dist/templates/behavioral-core.md +0 -25
- package/dist/templates/claude-code-overview-skill.md +0 -6
- package/dist/templates/claude-code-skill.md +0 -6
- package/dist/templates/claude-code-test-skill.md +0 -6
- package/dist/templates/codex-agents.md +0 -3
- package/dist/templates/codex-overview-agents.md +0 -3
- package/dist/templates/codex-test-agents.md +0 -3
- package/dist/templates/cursor-overview-rule.mdc +0 -5
- package/dist/templates/cursor-rule.mdc +0 -5
- package/dist/templates/cursor-test-rule.mdc +0 -5
- package/dist/templates/overview-core.md +0 -27
- package/dist/templates/test-writing-core.md +0 -72
- package/dist/utils/detect-existing-project.d.ts +0 -5
- package/dist/utils/detect-existing-project.js +0 -34
package/dist/convex.js
CHANGED
package/dist/harness/cache.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/harness/cache.js
CHANGED
|
@@ -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 {
|
|
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
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 */
|
package/dist/harness/finalize.js
CHANGED
|
@@ -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 {
|
|
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 = '
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (!credentials) {
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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) {
|
package/dist/harness/prepare.js
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Implements: HARNESS-PREPARE-1, HARNESS-PREPARE-2, HARNESS-PREPARE-3
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
*
|
|
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
|
package/dist/mcp/convexClient.js
CHANGED
|
@@ -1,45 +1,25 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|