@popoverai/dotrequirements 0.14.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.
- package/README.md +45 -1
- package/dist/cli.js +1 -0
- package/dist/commands/pull.d.ts +1 -0
- package/dist/commands/pull.js +70 -31
- package/dist/config.js +0 -4
- package/dist/mcp/convexClient.d.ts +4 -0
- package/dist/mcp/convexClient.js +1 -1
- package/dist/mcp/index.js +106 -85
- package/dist/mcp/requirements.d.ts +10 -0
- package/dist/mcp/requirements.js +15 -1
- package/dist/utils/project-settings.d.ts +9 -0
- package/dist/utils/project-settings.js +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,8 +114,19 @@ Sync requirements from dot•requirements cloud to local `.requirements/` files.
|
|
|
114
114
|
dotreq pull
|
|
115
115
|
dotreq pull --project <project-id>
|
|
116
116
|
dotreq pull --document <document-id>
|
|
117
|
+
dotreq pull --share <token>
|
|
117
118
|
```
|
|
118
119
|
|
|
120
|
+
**First-time setup with a share token:**
|
|
121
|
+
|
|
122
|
+
If a team member shares a pull command with you, you can pull requirements without creating an account:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx @popoverai/dotrequirements pull --share drt_abc123...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This gives you read-only access to view requirements. To push changes or report coverage, run `dotreq link` afterward.
|
|
129
|
+
|
|
119
130
|
### `dotreq push`
|
|
120
131
|
|
|
121
132
|
Push local requirements to dot•requirements cloud.
|
|
@@ -421,7 +432,7 @@ Follow the prompts to configure for your AI assistant (Claude Code, Cursor, etc.
|
|
|
421
432
|
**Authoring:**
|
|
422
433
|
- `create_requirement_document` — Get a Markdown template with format examples
|
|
423
434
|
- `validate_requirements` — Validate file schema (works offline)
|
|
424
|
-
- `style_check` — AI-powered style feedback on requirements
|
|
435
|
+
- `style_check` — AI-powered style feedback on requirements (supports optional `requirementKeys` filter)
|
|
425
436
|
- `review_test` — Comprehensive test review (style + semantic correctness)
|
|
426
437
|
|
|
427
438
|
**Cloud:**
|
|
@@ -432,6 +443,39 @@ Follow the prompts to configure for your AI assistant (Claude Code, Cursor, etc.
|
|
|
432
443
|
**Diagnostic:**
|
|
433
444
|
- `debug_mcp_environment` — Debug MCP server configuration
|
|
434
445
|
|
|
446
|
+
### CI/CD Mode
|
|
447
|
+
|
|
448
|
+
For CI/CD environments (GitHub Actions, etc.), use the `--auth-from-env` flag to read credentials from environment variables instead of `project-settings.json`:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
node packages/cli/dist/mcp/index.js --auth-from-env
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Required environment variables:
|
|
455
|
+
- `DOTREQ_PROJECT_ID` — Your project slug
|
|
456
|
+
- `DOTREQ_PROJECT_SECRET` — Your project secret
|
|
457
|
+
|
|
458
|
+
**Important:** Without `--auth-from-env`, these environment variables are ignored. This explicit opt-in prevents credential conflicts between local and CI environments.
|
|
459
|
+
|
|
460
|
+
Example MCP config for GitHub Actions:
|
|
461
|
+
|
|
462
|
+
```json
|
|
463
|
+
{
|
|
464
|
+
"mcpServers": {
|
|
465
|
+
"dotrequirements": {
|
|
466
|
+
"command": "node",
|
|
467
|
+
"args": ["packages/cli/dist/mcp/index.js", "--auth-from-env"],
|
|
468
|
+
"env": {
|
|
469
|
+
"DOTREQ_PROJECT_ID": "${DOTREQ_PROJECT_ID}",
|
|
470
|
+
"DOTREQ_PROJECT_SECRET": "${DOTREQ_PROJECT_SECRET}"
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
See the [CI/CD Integration docs](https://dotrequirements.io/tools/ai/ci-cd) for complete examples.
|
|
478
|
+
|
|
435
479
|
---
|
|
436
480
|
|
|
437
481
|
## Package Exports
|
package/dist/cli.js
CHANGED
|
@@ -57,6 +57,7 @@ program
|
|
|
57
57
|
.description('Sync requirements from cloud to local .requirements/ files')
|
|
58
58
|
.option('-p, --project <id>', 'Project ID to sync')
|
|
59
59
|
.option('-d, --document <id>', 'Specific document ID to sync')
|
|
60
|
+
.option('-s, --share <token>', 'Read-only share token for quick onboarding (no setup required)')
|
|
60
61
|
.action(wrapCommand(pullCommand));
|
|
61
62
|
program
|
|
62
63
|
.command('push [file]')
|
package/dist/commands/pull.d.ts
CHANGED
package/dist/commands/pull.js
CHANGED
|
@@ -9,43 +9,75 @@ import { findRequirementsFiles } from '../mcp/requirements.js';
|
|
|
9
9
|
import { getProjectCredentials } from '../utils/project-settings.js';
|
|
10
10
|
export async function pullCommand(options) {
|
|
11
11
|
try {
|
|
12
|
-
console.log(`Syncing requirements from ${brand} cloud...`);
|
|
13
12
|
// Connect to Convex
|
|
14
13
|
const convexUrl = getConvexUrl();
|
|
15
14
|
const client = new ConvexHttpClient(convexUrl);
|
|
16
|
-
// Get project credentials from .requirements/project-settings.json
|
|
17
|
-
// This throws helpful errors if project not found or not connected to cloud
|
|
18
|
-
const credentials = getProjectCredentials();
|
|
19
|
-
// Allow --project flag to override, but still need secret from settings
|
|
20
|
-
const projectId = options.project ?? credentials.projectId;
|
|
21
|
-
const projectSecret = credentials.projectSecret;
|
|
22
|
-
// Fetch documents
|
|
23
15
|
let documents;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
16
|
+
let projectName;
|
|
17
|
+
let isShareTokenFlow = false;
|
|
18
|
+
// SHARE-TOKEN-CLI-1: Handle share token flow (no project config needed)
|
|
19
|
+
if (options.share) {
|
|
20
|
+
isShareTokenFlow = true;
|
|
21
|
+
console.log(`Pulling requirements with share token...`);
|
|
22
|
+
try {
|
|
23
|
+
// SHARE-TOKEN-CLI-1.1: Pass only projectSecret, no projectSlug
|
|
24
|
+
// The authzQueryReadOnly wrapper will resolve project from the token
|
|
25
|
+
const result = await client.query(api.documents.queries.exportProjectForCli, {
|
|
26
|
+
projectAuth: {
|
|
27
|
+
projectSecret: options.share,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
documents = result.documents;
|
|
31
|
+
projectName = result.projectName;
|
|
32
|
+
// SHARE-TOKEN-CLI-1.2: Display project name in output
|
|
33
|
+
console.log(`\nProject: "${projectName}"`);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// SHARE-TOKEN-CLI-2: Handle invalid/revoked tokens gracefully
|
|
37
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
38
|
+
if (errorMessage.includes('Invalid share token')) {
|
|
39
|
+
throw new Error('Invalid share token. The token may be incorrect or has been revoked.\n' +
|
|
40
|
+
'Please request a new share token from your team.');
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
46
44
|
}
|
|
47
45
|
else {
|
|
48
|
-
|
|
46
|
+
console.log(`Syncing requirements from ${brand} cloud...`);
|
|
47
|
+
// Get project credentials from .requirements/project-settings.json
|
|
48
|
+
// This throws helpful errors if project not found or not connected to cloud
|
|
49
|
+
const credentials = getProjectCredentials();
|
|
50
|
+
// Allow --project flag to override, but still need secret from settings
|
|
51
|
+
const projectId = options.project ?? credentials.projectId;
|
|
52
|
+
const projectSecret = credentials.projectSecret;
|
|
53
|
+
// Fetch documents
|
|
54
|
+
if (options.document) {
|
|
55
|
+
// Fetch specific document
|
|
56
|
+
const doc = await client.query(api.documents.queries.exportForCli, {
|
|
57
|
+
projectAuth: {
|
|
58
|
+
projectSlug: projectId,
|
|
59
|
+
projectSecret,
|
|
60
|
+
},
|
|
61
|
+
target: { type: 'document', id: options.document },
|
|
62
|
+
documentId: options.document,
|
|
63
|
+
});
|
|
64
|
+
documents = doc ? [doc] : [];
|
|
65
|
+
}
|
|
66
|
+
else if (projectId) {
|
|
67
|
+
// Fetch all documents for project
|
|
68
|
+
// SYNC-ARCH-3.1: Use projectSlug (not projectId) since env stores the slug
|
|
69
|
+
const result = await client.query(api.documents.queries.exportProjectForCli, {
|
|
70
|
+
projectAuth: {
|
|
71
|
+
projectSlug: projectId,
|
|
72
|
+
projectSecret,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
documents = result.documents;
|
|
76
|
+
projectName = result.projectName;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw new Error('Either --project or --document must be specified');
|
|
80
|
+
}
|
|
49
81
|
}
|
|
50
82
|
if (documents.length === 0) {
|
|
51
83
|
console.log('No documents found.');
|
|
@@ -82,6 +114,13 @@ export async function pullCommand(options) {
|
|
|
82
114
|
console.log(`✓ Synced: ${fileName} (version ${doc.version})`);
|
|
83
115
|
}
|
|
84
116
|
console.log(`\nSuccessfully synced ${documents.length} document(s) to .requirements/`);
|
|
117
|
+
// SHARE-TOKEN-CLI-3: Show next steps for share token users
|
|
118
|
+
if (isShareTokenFlow) {
|
|
119
|
+
console.log('\n─────────────────────────────────────────────────────────');
|
|
120
|
+
console.log('Next steps to get full access (push, coverage):');
|
|
121
|
+
console.log(' Run: dotrequirements link');
|
|
122
|
+
console.log('─────────────────────────────────────────────────────────');
|
|
123
|
+
}
|
|
85
124
|
}
|
|
86
125
|
catch (error) {
|
|
87
126
|
// Re-throw to let the CLI error handler display it cleanly
|
package/dist/config.js
CHANGED
|
@@ -7,10 +7,6 @@ const CONVEX_URL = 'https://data.dotrequirements.io';
|
|
|
7
7
|
* Get the Convex deployment URL
|
|
8
8
|
*/
|
|
9
9
|
export function getConvexUrl() {
|
|
10
|
-
// Show deprecation warning if user has CONVEX_URL in their env
|
|
11
|
-
if (process.env.CONVEX_URL) {
|
|
12
|
-
console.warn('⚠️ CONVEX_URL is no longer needed and will be ignored. You can safely remove it from your .env.local');
|
|
13
|
-
}
|
|
14
10
|
return CONVEX_URL;
|
|
15
11
|
}
|
|
16
12
|
//# sourceMappingURL=config.js.map
|
package/dist/mcp/convexClient.js
CHANGED
|
@@ -2,7 +2,7 @@ import { getProjectCredentials } from '../utils/project-settings.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Production Convex deployment URL
|
|
4
4
|
*/
|
|
5
|
-
const CONVEX_URL = 'https://data.dotrequirements.io';
|
|
5
|
+
export const CONVEX_URL = 'https://data.dotrequirements.io';
|
|
6
6
|
/**
|
|
7
7
|
* Load Convex configuration from project settings.
|
|
8
8
|
* Returns null if credentials are not configured.
|
package/dist/mcp/index.js
CHANGED
|
@@ -2,12 +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 {
|
|
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 { getCredentialsFromEnv } from '../utils/project-settings.js';
|
|
11
12
|
import { readFileSync } from 'fs';
|
|
12
13
|
import { fileURLToPath } from 'url';
|
|
13
14
|
import { dirname, join } from 'path';
|
|
@@ -32,6 +33,9 @@ function getProjectPathsFromEnv() {
|
|
|
32
33
|
// Get workspace root from environment or default to cwd
|
|
33
34
|
const WORKSPACE_ROOT = process.env.REQUIREMENTS_DIR || process.cwd();
|
|
34
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');
|
|
35
39
|
const STYLE_CHECK_GUIDANCE = `
|
|
36
40
|
|
|
37
41
|
---
|
|
@@ -57,6 +61,20 @@ async function getRequirements(projectId) {
|
|
|
57
61
|
}
|
|
58
62
|
async function getProjectFromDiscovery(projectId) {
|
|
59
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
|
+
}
|
|
60
78
|
// If we have PROJ_* env vars, use those instead of filesystem discovery
|
|
61
79
|
if (PROJECT_PATHS.size > 0) {
|
|
62
80
|
const projects = [];
|
|
@@ -300,7 +318,7 @@ const tools = [
|
|
|
300
318
|
},
|
|
301
319
|
{
|
|
302
320
|
name: 'style_check',
|
|
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`).',
|
|
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`).',
|
|
304
322
|
inputSchema: {
|
|
305
323
|
type: 'object',
|
|
306
324
|
properties: {
|
|
@@ -308,6 +326,11 @@ const tools = [
|
|
|
308
326
|
type: 'string',
|
|
309
327
|
description: 'Path to the file to check (e.g., ".requirements/auth.requirements.md" or "src/auth.test.ts")',
|
|
310
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
|
+
},
|
|
311
334
|
model: {
|
|
312
335
|
type: 'string',
|
|
313
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"',
|
|
@@ -899,19 +922,7 @@ ${projectList || '(none found)'}`,
|
|
|
899
922
|
case 'get_requirement_coverage': {
|
|
900
923
|
const { requirementKey, projectId } = args;
|
|
901
924
|
const project = await getProjectFromDiscovery(projectId);
|
|
902
|
-
const
|
|
903
|
-
if (!config) {
|
|
904
|
-
return {
|
|
905
|
-
content: [
|
|
906
|
-
{
|
|
907
|
-
type: 'text',
|
|
908
|
-
text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
909
|
-
},
|
|
910
|
-
],
|
|
911
|
-
isError: true,
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
const coverage = await queryRequirementCoverage(requirementKey, config.projectId, config.projectSecret, config.convexUrl);
|
|
925
|
+
const coverage = await queryRequirementCoverage(requirementKey, project.projectId, project.projectSecret, CONVEX_URL);
|
|
915
926
|
if (!coverage.lastTestedAt) {
|
|
916
927
|
return {
|
|
917
928
|
content: [
|
|
@@ -946,19 +957,7 @@ ${projectList || '(none found)'}`,
|
|
|
946
957
|
case 'get_project_coverage_summary': {
|
|
947
958
|
const { branch, sinceTimestamp, projectId } = args;
|
|
948
959
|
const project = await getProjectFromDiscovery(projectId);
|
|
949
|
-
const
|
|
950
|
-
if (!config) {
|
|
951
|
-
return {
|
|
952
|
-
content: [
|
|
953
|
-
{
|
|
954
|
-
type: 'text',
|
|
955
|
-
text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
956
|
-
},
|
|
957
|
-
],
|
|
958
|
-
isError: true,
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
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 });
|
|
962
961
|
const total = coverage.tested.length + coverage.untested.length;
|
|
963
962
|
const percentage = total > 0 ? ((coverage.tested.length / total) * 100).toFixed(1) : '0.0';
|
|
964
963
|
let filterInfo = '';
|
|
@@ -1286,23 +1285,11 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1286
1285
|
const path = await import('path');
|
|
1287
1286
|
const { parseRequirementsFromFile, getAllRequirements } = await import('../schema/index.js');
|
|
1288
1287
|
const { ConvexHttpClient } = await import('convex/browser');
|
|
1289
|
-
// Get project
|
|
1288
|
+
// Get project (credentials come from project object, works with both file and env auth)
|
|
1290
1289
|
const project = await getProjectFromDiscovery(projectIdParam);
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
content: [
|
|
1295
|
-
{
|
|
1296
|
-
type: 'text',
|
|
1297
|
-
text: 'Push requires project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
1298
|
-
},
|
|
1299
|
-
],
|
|
1300
|
-
isError: true,
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
const convexUrl = config.convexUrl;
|
|
1304
|
-
const projectId = config.projectId;
|
|
1305
|
-
const projectSecret = config.projectSecret;
|
|
1290
|
+
const convexUrl = CONVEX_URL;
|
|
1291
|
+
const projectId = project.projectId;
|
|
1292
|
+
const projectSecret = project.projectSecret;
|
|
1306
1293
|
// Determine files to push
|
|
1307
1294
|
const requirementsDir = path.resolve(project.path, '.requirements');
|
|
1308
1295
|
let filesToPush;
|
|
@@ -1411,7 +1398,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1411
1398
|
}
|
|
1412
1399
|
}
|
|
1413
1400
|
case 'style_check': {
|
|
1414
|
-
const { filePath, model } = args;
|
|
1401
|
+
const { filePath, model, requirementKeys } = args;
|
|
1415
1402
|
const fs = await import('fs');
|
|
1416
1403
|
const path = await import('path');
|
|
1417
1404
|
const fullPath = path.resolve(WORKSPACE_ROOT, filePath);
|
|
@@ -1443,22 +1430,73 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1443
1430
|
};
|
|
1444
1431
|
}
|
|
1445
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
|
+
}
|
|
1446
1478
|
// Get Convex config for credentials - walk up from file's directory to find project
|
|
1447
1479
|
const fileDir = path.dirname(fullPath);
|
|
1448
1480
|
let project;
|
|
1449
1481
|
try {
|
|
1450
|
-
//
|
|
1451
|
-
|
|
1452
|
-
if (result.type === 'none') {
|
|
1453
|
-
// 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) {
|
|
1454
1484
|
project = await getProjectFromDiscovery();
|
|
1455
1485
|
}
|
|
1456
|
-
else if (result.type === 'single') {
|
|
1457
|
-
project = result.project;
|
|
1458
|
-
}
|
|
1459
1486
|
else {
|
|
1460
|
-
//
|
|
1461
|
-
|
|
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
|
+
}
|
|
1462
1500
|
}
|
|
1463
1501
|
}
|
|
1464
1502
|
catch (error) {
|
|
@@ -1472,18 +1510,8 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1472
1510
|
isError: true,
|
|
1473
1511
|
};
|
|
1474
1512
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
return {
|
|
1478
|
-
content: [
|
|
1479
|
-
{
|
|
1480
|
-
type: 'text',
|
|
1481
|
-
text: 'Style check requires project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
1482
|
-
},
|
|
1483
|
-
],
|
|
1484
|
-
isError: true,
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1513
|
+
// Use credentials from project (works with both file-based and env-based auth)
|
|
1514
|
+
const { projectId: projId, projectSecret: projSecret } = project;
|
|
1487
1515
|
// Call Vercel API endpoint for style checking
|
|
1488
1516
|
// Default to production, allow override via env var for local dev
|
|
1489
1517
|
const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL || 'https://app.dotrequirements.io';
|
|
@@ -1494,9 +1522,9 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1494
1522
|
'Content-Type': 'application/json',
|
|
1495
1523
|
},
|
|
1496
1524
|
body: JSON.stringify({
|
|
1497
|
-
projectId:
|
|
1498
|
-
projectSecret:
|
|
1499
|
-
fileContents,
|
|
1525
|
+
projectId: projId,
|
|
1526
|
+
projectSecret: projSecret,
|
|
1527
|
+
fileContents: fileContentsToCheck,
|
|
1500
1528
|
fileType,
|
|
1501
1529
|
model,
|
|
1502
1530
|
}),
|
|
@@ -1514,11 +1542,14 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1514
1542
|
};
|
|
1515
1543
|
}
|
|
1516
1544
|
const data = await response.json();
|
|
1545
|
+
const scopeLabel = requirementKeys && requirementKeys.length > 0
|
|
1546
|
+
? ` (${requirementKeys.join(', ')})`
|
|
1547
|
+
: '';
|
|
1517
1548
|
return {
|
|
1518
1549
|
content: [
|
|
1519
1550
|
{
|
|
1520
1551
|
type: 'text',
|
|
1521
|
-
text: `# Style Check Results for \`${filePath}
|
|
1552
|
+
text: `# Style Check Results for \`${filePath}\`${scopeLabel}\n\n${data.feedback}${scopeNote}${STYLE_CHECK_GUIDANCE}`,
|
|
1522
1553
|
},
|
|
1523
1554
|
],
|
|
1524
1555
|
};
|
|
@@ -1614,18 +1645,8 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1614
1645
|
});
|
|
1615
1646
|
}
|
|
1616
1647
|
// Get Convex config
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
return {
|
|
1620
|
-
content: [
|
|
1621
|
-
{
|
|
1622
|
-
type: 'text',
|
|
1623
|
-
text: 'Test review requires project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
1624
|
-
},
|
|
1625
|
-
],
|
|
1626
|
-
isError: true,
|
|
1627
|
-
};
|
|
1628
|
-
}
|
|
1648
|
+
// Use credentials from project (works with both file-based and env-based auth)
|
|
1649
|
+
const { projectId: projId, projectSecret: projSecret } = project;
|
|
1629
1650
|
// Call Vercel API endpoint for test review
|
|
1630
1651
|
// Default to production, allow override via env var for local dev
|
|
1631
1652
|
const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL || 'https://app.dotrequirements.io';
|
|
@@ -1636,8 +1657,8 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1636
1657
|
'Content-Type': 'application/json',
|
|
1637
1658
|
},
|
|
1638
1659
|
body: JSON.stringify({
|
|
1639
|
-
projectId:
|
|
1640
|
-
projectSecret:
|
|
1660
|
+
projectId: projId,
|
|
1661
|
+
projectSecret: projSecret,
|
|
1641
1662
|
testFileContents,
|
|
1642
1663
|
requirements,
|
|
1643
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
|
*/
|
package/dist/mcp/requirements.js
CHANGED
|
@@ -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
|
*/
|
|
@@ -20,6 +20,15 @@ export interface ProjectInfo {
|
|
|
20
20
|
/** Credentials if cloud-connected */
|
|
21
21
|
credentials?: ProjectSettings;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Get project credentials from environment variables.
|
|
25
|
+
* Used in CI/CD environments where credentials are passed via env vars
|
|
26
|
+
* rather than stored in project-settings.json.
|
|
27
|
+
*
|
|
28
|
+
* Requires both DOTREQ_PROJECT_ID and DOTREQ_PROJECT_SECRET to be set.
|
|
29
|
+
* Returns undefined if either is missing.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getCredentialsFromEnv(): ProjectSettings | undefined;
|
|
23
32
|
/**
|
|
24
33
|
* Find the project root by walking up from startDir looking for .requirements/ folder
|
|
25
34
|
* Returns the directory containing .requirements/, or undefined if not found
|
|
@@ -2,6 +2,22 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
const REQUIREMENTS_DIR = '.requirements';
|
|
4
4
|
const SETTINGS_FILE = 'project-settings.json';
|
|
5
|
+
/**
|
|
6
|
+
* Get project credentials from environment variables.
|
|
7
|
+
* Used in CI/CD environments where credentials are passed via env vars
|
|
8
|
+
* rather than stored in project-settings.json.
|
|
9
|
+
*
|
|
10
|
+
* Requires both DOTREQ_PROJECT_ID and DOTREQ_PROJECT_SECRET to be set.
|
|
11
|
+
* Returns undefined if either is missing.
|
|
12
|
+
*/
|
|
13
|
+
export function getCredentialsFromEnv() {
|
|
14
|
+
const projectId = process.env.DOTREQ_PROJECT_ID;
|
|
15
|
+
const projectSecret = process.env.DOTREQ_PROJECT_SECRET;
|
|
16
|
+
if (projectId && projectSecret) {
|
|
17
|
+
return { projectId, projectSecret };
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
5
21
|
/**
|
|
6
22
|
* Find the project root by walking up from startDir looking for .requirements/ folder
|
|
7
23
|
* Returns the directory containing .requirements/, or undefined if not found
|