@popoverai/dotrequirements 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -47
- package/dist/cli.js +0 -10
- package/dist/commands/init.js +166 -226
- 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 +7 -29
- package/dist/commands/push.js +4 -13
- 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.js +7 -12
- 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 +12 -73
- 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 +35 -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
|
@@ -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());
|
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
|
*/
|
|
@@ -115,20 +114,16 @@ const CONVEX_URL = 'https://data.dotrequirements.io';
|
|
|
115
114
|
*/
|
|
116
115
|
async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatus) {
|
|
117
116
|
try {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!credentials) {
|
|
117
|
+
// Get project info from .requirements/project-settings.json
|
|
118
|
+
const projectInfo = getProjectInfo(projectRoot);
|
|
119
|
+
// Skip if no project found or not connected to cloud
|
|
120
|
+
if (!projectInfo || projectInfo.status !== 'cloud-connected' || !projectInfo.credentials) {
|
|
122
121
|
if (showCloudStatus) {
|
|
123
|
-
console.log('\nℹ️ Skipping cloud coverage reporting (
|
|
122
|
+
console.log('\nℹ️ Skipping cloud coverage reporting (project not connected to cloud)');
|
|
124
123
|
}
|
|
125
124
|
return { sent: false, count: 0 };
|
|
126
125
|
}
|
|
127
|
-
const { projectId, projectSecret } = credentials;
|
|
128
|
-
// Skip for local-only projects
|
|
129
|
-
if (isLocalOnlyProject(projectId)) {
|
|
130
|
-
return { sent: false, count: 0 };
|
|
131
|
-
}
|
|
126
|
+
const { projectId, projectSecret } = projectInfo.credentials;
|
|
132
127
|
const requirementsDir = findRequirementsDir(projectRoot);
|
|
133
128
|
// Get all tracked requirement keys
|
|
134
129
|
const requirementKeys = Array.from(aggregated.keys());
|
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';
|
|
@@ -211,7 +210,7 @@ const tools = [
|
|
|
211
210
|
},
|
|
212
211
|
{
|
|
213
212
|
name: 'get_requirement_coverage',
|
|
214
|
-
description: 'Get test coverage information for a specific requirement from dot•requirements cloud. Shows when the requirement was last tested, on which branch, and in which test file. Requires
|
|
213
|
+
description: 'Get test coverage information for a specific requirement from dot•requirements cloud. Shows when the requirement was last tested, on which branch, and in which test file. Requires project to be linked to cloud (run `dotrequirements link`).',
|
|
215
214
|
inputSchema: {
|
|
216
215
|
type: 'object',
|
|
217
216
|
properties: {
|
|
@@ -229,7 +228,7 @@ const tools = [
|
|
|
229
228
|
},
|
|
230
229
|
{
|
|
231
230
|
name: 'get_project_coverage_summary',
|
|
232
|
-
description: 'Get a summary of test coverage for all requirements in the project from dot•requirements cloud. Shows which requirements have been tested and which haven\'t. Optionally filter by branch or time range. Requires
|
|
231
|
+
description: 'Get a summary of test coverage for all requirements in the project from dot•requirements cloud. Shows which requirements have been tested and which haven\'t. Optionally filter by branch or time range. Requires project to be linked to cloud (run `dotrequirements link`).',
|
|
233
232
|
inputSchema: {
|
|
234
233
|
type: 'object',
|
|
235
234
|
properties: {
|
|
@@ -279,7 +278,7 @@ const tools = [
|
|
|
279
278
|
},
|
|
280
279
|
{
|
|
281
280
|
name: 'push_requirements',
|
|
282
|
-
description: 'Push local requirements from .requirements/ directory to dot•requirements cloud. First call returns diff summary for user review. Second call with confirmed=true executes the push. Requires
|
|
281
|
+
description: 'Push local requirements from .requirements/ directory to dot•requirements cloud. First call returns diff summary for user review. Second call with confirmed=true executes the push. Requires project to be linked to cloud (run `dotrequirements link`).',
|
|
283
282
|
inputSchema: {
|
|
284
283
|
type: 'object',
|
|
285
284
|
properties: {
|
|
@@ -301,7 +300,7 @@ const tools = [
|
|
|
301
300
|
},
|
|
302
301
|
{
|
|
303
302
|
name: 'style_check',
|
|
304
|
-
description: 'Check requirements files or test files for style issues and best practices. Uses AI to provide actionable feedback on writing style, clarity, and conventions. Supports requirements files (*.requirements.md) and test files (*.test.*, *.spec.*). Requires
|
|
303
|
+
description: 'Check requirements files or test files for style issues and best practices. Uses AI to provide actionable feedback on writing style, clarity, and conventions. Supports requirements files (*.requirements.md) and test files (*.test.*, *.spec.*). Requires project to be linked to cloud (run `dotrequirements link`).',
|
|
305
304
|
inputSchema: {
|
|
306
305
|
type: 'object',
|
|
307
306
|
properties: {
|
|
@@ -319,7 +318,7 @@ const tools = [
|
|
|
319
318
|
},
|
|
320
319
|
{
|
|
321
320
|
name: 'review_test',
|
|
322
|
-
description: 'Comprehensively review a test file for both style and semantic correctness. Checks if tests actually validate what the requirements specify (not just style). Loads referenced requirements and validates that test setup, actions, and assertions match requirement preconditions, triggers, and outcomes. Requires
|
|
321
|
+
description: 'Comprehensively review a test file for both style and semantic correctness. Checks if tests actually validate what the requirements specify (not just style). Loads referenced requirements and validates that test setup, actions, and assertions match requirement preconditions, triggers, and outcomes. Requires project to be linked to cloud (run `dotrequirements link`).',
|
|
323
322
|
inputSchema: {
|
|
324
323
|
type: 'object',
|
|
325
324
|
properties: {
|
|
@@ -416,7 +415,7 @@ Fill in:
|
|
|
416
415
|
After capturing requirements:
|
|
417
416
|
1. Implement the feature
|
|
418
417
|
2. Write tests that reference requirements using \`requirement('REQ-ID')\`
|
|
419
|
-
3.
|
|
418
|
+
3. See your project's context file (CLAUDE.md/AGENTS.md) for test structure guidance`,
|
|
420
419
|
},
|
|
421
420
|
},
|
|
422
421
|
],
|
|
@@ -906,19 +905,7 @@ ${projectList || '(none found)'}`,
|
|
|
906
905
|
content: [
|
|
907
906
|
{
|
|
908
907
|
type: 'text',
|
|
909
|
-
text: 'Coverage queries require
|
|
910
|
-
},
|
|
911
|
-
],
|
|
912
|
-
isError: true,
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
// AUTHZ-2.1: Check if project is local-only
|
|
916
|
-
if (isLocalOnlyProject(config.projectId)) {
|
|
917
|
-
return {
|
|
918
|
-
content: [
|
|
919
|
-
{
|
|
920
|
-
type: 'text',
|
|
921
|
-
text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
|
|
908
|
+
text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
922
909
|
},
|
|
923
910
|
],
|
|
924
911
|
isError: true,
|
|
@@ -965,19 +952,7 @@ ${projectList || '(none found)'}`,
|
|
|
965
952
|
content: [
|
|
966
953
|
{
|
|
967
954
|
type: 'text',
|
|
968
|
-
text: 'Coverage queries require
|
|
969
|
-
},
|
|
970
|
-
],
|
|
971
|
-
isError: true,
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
// AUTHZ-2.1: Check if project is local-only
|
|
975
|
-
if (isLocalOnlyProject(config.projectId)) {
|
|
976
|
-
return {
|
|
977
|
-
content: [
|
|
978
|
-
{
|
|
979
|
-
type: 'text',
|
|
980
|
-
text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
|
|
955
|
+
text: 'Coverage queries require project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
981
956
|
},
|
|
982
957
|
],
|
|
983
958
|
isError: true,
|
|
@@ -1319,7 +1294,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1319
1294
|
content: [
|
|
1320
1295
|
{
|
|
1321
1296
|
type: 'text',
|
|
1322
|
-
text: 'Push requires
|
|
1297
|
+
text: 'Push requires project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
1323
1298
|
},
|
|
1324
1299
|
],
|
|
1325
1300
|
isError: true,
|
|
@@ -1328,18 +1303,6 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1328
1303
|
const convexUrl = config.convexUrl;
|
|
1329
1304
|
const projectId = config.projectId;
|
|
1330
1305
|
const projectSecret = config.projectSecret;
|
|
1331
|
-
// AUTHZ-2.1: Check if project is local-only
|
|
1332
|
-
if (isLocalOnlyProject(projectId)) {
|
|
1333
|
-
return {
|
|
1334
|
-
content: [
|
|
1335
|
-
{
|
|
1336
|
-
type: 'text',
|
|
1337
|
-
text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
|
|
1338
|
-
},
|
|
1339
|
-
],
|
|
1340
|
-
isError: true,
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
1306
|
// Determine files to push
|
|
1344
1307
|
const requirementsDir = path.resolve(project.path, '.requirements');
|
|
1345
1308
|
let filesToPush;
|
|
@@ -1503,7 +1466,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1503
1466
|
content: [
|
|
1504
1467
|
{
|
|
1505
1468
|
type: 'text',
|
|
1506
|
-
text: `Style check requires
|
|
1469
|
+
text: `Style check requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`,
|
|
1507
1470
|
},
|
|
1508
1471
|
],
|
|
1509
1472
|
isError: true,
|
|
@@ -1515,19 +1478,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1515
1478
|
content: [
|
|
1516
1479
|
{
|
|
1517
1480
|
type: 'text',
|
|
1518
|
-
text: 'Style check requires
|
|
1519
|
-
},
|
|
1520
|
-
],
|
|
1521
|
-
isError: true,
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
// AUTHZ-2.1: Check if project is local-only
|
|
1525
|
-
if (isLocalOnlyProject(config.projectId)) {
|
|
1526
|
-
return {
|
|
1527
|
-
content: [
|
|
1528
|
-
{
|
|
1529
|
-
type: 'text',
|
|
1530
|
-
text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
|
|
1481
|
+
text: 'Style check requires project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
1531
1482
|
},
|
|
1532
1483
|
],
|
|
1533
1484
|
isError: true,
|
|
@@ -1669,19 +1620,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1669
1620
|
content: [
|
|
1670
1621
|
{
|
|
1671
1622
|
type: 'text',
|
|
1672
|
-
text: 'Test review requires
|
|
1673
|
-
},
|
|
1674
|
-
],
|
|
1675
|
-
isError: true,
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
// AUTHZ-2.1: Check if project is local-only
|
|
1679
|
-
if (isLocalOnlyProject(config.projectId)) {
|
|
1680
|
-
return {
|
|
1681
|
-
content: [
|
|
1682
|
-
{
|
|
1683
|
-
type: 'text',
|
|
1684
|
-
text: CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE,
|
|
1623
|
+
text: 'Test review requires project credentials. Run `dotrequirements link` to connect to cloud.',
|
|
1685
1624
|
},
|
|
1686
1625
|
],
|
|
1687
1626
|
isError: true,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
## Requirements-First Development
|
|
2
|
+
|
|
3
|
+
This project uses dotrequirements for requirements tracking. Requirements live in `.requirements/*.requirements.md`.
|
|
4
|
+
|
|
5
|
+
### Workflow
|
|
6
|
+
|
|
7
|
+
When adding new functionality (not bug fixes or refactoring):
|
|
8
|
+
|
|
9
|
+
1. **Draft requirements** - Write in `.requirements/*.requirements.md`
|
|
10
|
+
2. **Style-check** - Run `mcp__dotrequirements__style_check` on the file
|
|
11
|
+
3. **Get approval** - Present requirements, wait for go-ahead
|
|
12
|
+
4. **Implement** - Build the feature
|
|
13
|
+
5. **Write tests** - Reference requirements with `requirement()`
|
|
14
|
+
6. **Run tests** - Verify everything passes
|
|
15
|
+
7. **Review tests** - Run `mcp__dotrequirements__review_test` to validate coverage
|
|
16
|
+
|
|
17
|
+
### Requirements Syntax
|
|
18
|
+
|
|
19
|
+
```dotrequirements
|
|
20
|
+
REQ-ID: Short description of expected behavior
|
|
21
|
+
0. Given -> Precondition that must be true
|
|
22
|
+
1. When -> Action or trigger
|
|
23
|
+
2. Then -> Expected outcome
|
|
24
|
+
2.0. And -> Additional outcome detail
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- First line: `KEY: description`
|
|
28
|
+
- Criteria: `position. Label -> content` (Given/When/Then structure)
|
|
29
|
+
- Nesting: Indent with 2 spaces, use `x.y` position paths
|
|
30
|
+
- Delimiter: `->` or the arrow character
|
|
31
|
+
|
|
32
|
+
### Test Usage
|
|
33
|
+
|
|
34
|
+
Use `requirement()` AS the test description:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { requirement } from '@popoverai/dotrequirements/test';
|
|
38
|
+
|
|
39
|
+
test(requirement('REQ-ID'), () => { /* test the requirement */ });
|
|
40
|
+
test(requirement('REQ-ID.0'), () => { /* test specific criterion */ });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### MCP Tools
|
|
44
|
+
|
|
45
|
+
**Exploration:**
|
|
46
|
+
- `list_all_requirements` - Overview of all requirements
|
|
47
|
+
- `get_requirement` - Requirement tree with test coverage
|
|
48
|
+
- `search_requirements` - Search by text/regex
|
|
49
|
+
|
|
50
|
+
**Authoring:**
|
|
51
|
+
- `create_requirement_document` - Get template with format guidance
|
|
52
|
+
- `validate_requirements` - Check syntax (works offline)
|
|
53
|
+
- `style_check` - AI feedback on clarity
|
|
54
|
+
- `push_requirements` - Sync to cloud
|
|
55
|
+
|
|
56
|
+
**Testing:**
|
|
57
|
+
- `get_requirements_by_test` - See requirements a test file covers
|
|
58
|
+
- `list_untested_requirements` - Find gaps in coverage
|
|
59
|
+
- `review_test` - Validate tests match requirement intent
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the appropriate context file name for a platform
|
|
3
|
+
*/
|
|
4
|
+
export declare function getContextFileName(platform: string): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Find the git root directory
|
|
7
|
+
*/
|
|
8
|
+
export declare function findGitRoot(): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Find the dotrequirements section in file content
|
|
11
|
+
* Returns the start/end indices and content, or null if not found
|
|
12
|
+
*/
|
|
13
|
+
export declare function findDotrequirementsSection(content: string): {
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
content: string;
|
|
17
|
+
} | null;
|
|
18
|
+
/**
|
|
19
|
+
* Wrap content in section markers
|
|
20
|
+
*/
|
|
21
|
+
export declare function wrapInSectionMarkers(content: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Append or update the dotrequirements section in a context file
|
|
24
|
+
* - If file doesn't exist, creates it with just the section
|
|
25
|
+
* - If file exists without section, appends section at end
|
|
26
|
+
* - If file exists with section, replaces the section
|
|
27
|
+
*
|
|
28
|
+
* Returns: { action: 'created' | 'appended' | 'updated', existingContent?: string }
|
|
29
|
+
*/
|
|
30
|
+
export declare function appendOrUpdateSection(filePath: string, sectionContent: string): {
|
|
31
|
+
action: 'created' | 'appended' | 'updated';
|
|
32
|
+
existingContent?: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Get the full path to the context file for a platform
|
|
36
|
+
*/
|
|
37
|
+
export declare function getContextFilePath(platform: string): Promise<string | null>;
|
|
38
|
+
//# sourceMappingURL=context-file.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { findUp } from 'find-up';
|
|
4
|
+
const SECTION_START = '<!-- dotrequirements:start -->';
|
|
5
|
+
const SECTION_END = '<!-- dotrequirements:end -->';
|
|
6
|
+
/**
|
|
7
|
+
* Platform to context file mapping
|
|
8
|
+
*/
|
|
9
|
+
const PLATFORM_CONTEXT_FILES = {
|
|
10
|
+
'claude-code': 'CLAUDE.md',
|
|
11
|
+
'cursor': 'AGENTS.md',
|
|
12
|
+
'codex': 'AGENTS.md',
|
|
13
|
+
'github-copilot': 'AGENTS.md',
|
|
14
|
+
'antigravity': 'GEMINI.md',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Get the appropriate context file name for a platform
|
|
18
|
+
*/
|
|
19
|
+
export function getContextFileName(platform) {
|
|
20
|
+
return PLATFORM_CONTEXT_FILES[platform] ?? null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Find the git root directory
|
|
24
|
+
*/
|
|
25
|
+
export async function findGitRoot() {
|
|
26
|
+
const gitDir = await findUp('.git', { type: 'directory' });
|
|
27
|
+
return gitDir ? dirname(gitDir) : null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Find the dotrequirements section in file content
|
|
31
|
+
* Returns the start/end indices and content, or null if not found
|
|
32
|
+
*/
|
|
33
|
+
export function findDotrequirementsSection(content) {
|
|
34
|
+
const startIdx = content.indexOf(SECTION_START);
|
|
35
|
+
if (startIdx === -1)
|
|
36
|
+
return null;
|
|
37
|
+
const endIdx = content.indexOf(SECTION_END, startIdx);
|
|
38
|
+
if (endIdx === -1)
|
|
39
|
+
return null;
|
|
40
|
+
return {
|
|
41
|
+
start: startIdx,
|
|
42
|
+
end: endIdx + SECTION_END.length,
|
|
43
|
+
content: content.slice(startIdx, endIdx + SECTION_END.length),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Wrap content in section markers
|
|
48
|
+
*/
|
|
49
|
+
export function wrapInSectionMarkers(content) {
|
|
50
|
+
return `${SECTION_START}\n${content}\n${SECTION_END}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Append or update the dotrequirements section in a context file
|
|
54
|
+
* - If file doesn't exist, creates it with just the section
|
|
55
|
+
* - If file exists without section, appends section at end
|
|
56
|
+
* - If file exists with section, replaces the section
|
|
57
|
+
*
|
|
58
|
+
* Returns: { action: 'created' | 'appended' | 'updated', existingContent?: string }
|
|
59
|
+
*/
|
|
60
|
+
export function appendOrUpdateSection(filePath, sectionContent) {
|
|
61
|
+
const wrappedContent = wrapInSectionMarkers(sectionContent);
|
|
62
|
+
if (!existsSync(filePath)) {
|
|
63
|
+
// Create new file with just the section
|
|
64
|
+
writeFileSync(filePath, wrappedContent + '\n', 'utf-8');
|
|
65
|
+
return { action: 'created' };
|
|
66
|
+
}
|
|
67
|
+
const existingFile = readFileSync(filePath, 'utf-8');
|
|
68
|
+
const existingSection = findDotrequirementsSection(existingFile);
|
|
69
|
+
if (!existingSection) {
|
|
70
|
+
// Append section to end of file
|
|
71
|
+
const separator = existingFile.endsWith('\n') ? '\n' : '\n\n';
|
|
72
|
+
writeFileSync(filePath, existingFile + separator + wrappedContent + '\n', 'utf-8');
|
|
73
|
+
return { action: 'appended' };
|
|
74
|
+
}
|
|
75
|
+
// Replace existing section
|
|
76
|
+
const newContent = existingFile.slice(0, existingSection.start) +
|
|
77
|
+
wrappedContent +
|
|
78
|
+
existingFile.slice(existingSection.end);
|
|
79
|
+
writeFileSync(filePath, newContent, 'utf-8');
|
|
80
|
+
return { action: 'updated', existingContent: existingSection.content };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get the full path to the context file for a platform
|
|
84
|
+
*/
|
|
85
|
+
export async function getContextFilePath(platform) {
|
|
86
|
+
const fileName = getContextFileName(platform);
|
|
87
|
+
if (!fileName)
|
|
88
|
+
return null;
|
|
89
|
+
const gitRoot = await findGitRoot();
|
|
90
|
+
if (!gitRoot)
|
|
91
|
+
return null;
|
|
92
|
+
return join(gitRoot, fileName);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=context-file.js.map
|
package/dist/utils/env.d.ts
CHANGED
|
@@ -5,17 +5,4 @@
|
|
|
5
5
|
* @param force - If true, overwrite existing process.env values (used when reloading after init)
|
|
6
6
|
*/
|
|
7
7
|
export declare function loadEnvFile(cwd?: string, force?: boolean): void;
|
|
8
|
-
/**
|
|
9
|
-
* Get project credentials from environment
|
|
10
|
-
* Returns undefined if not found
|
|
11
|
-
*/
|
|
12
|
-
export declare function getProjectCredentials(): {
|
|
13
|
-
projectId: string;
|
|
14
|
-
projectSecret: string;
|
|
15
|
-
} | undefined;
|
|
16
|
-
/**
|
|
17
|
-
* Get project ID from environment
|
|
18
|
-
* Returns undefined if not found
|
|
19
|
-
*/
|
|
20
|
-
export declare function getProjectId(): string | undefined;
|
|
21
8
|
//# sourceMappingURL=env.d.ts.map
|
package/dist/utils/env.js
CHANGED
|
@@ -36,23 +36,4 @@ export function loadEnvFile(cwd = process.cwd(), force = false) {
|
|
|
36
36
|
// The commands will error appropriately if env vars are missing
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Get project credentials from environment
|
|
41
|
-
* Returns undefined if not found
|
|
42
|
-
*/
|
|
43
|
-
export function getProjectCredentials() {
|
|
44
|
-
const projectId = process.env.DOTREQUIREMENTS_PROJECT_ID;
|
|
45
|
-
const projectSecret = process.env.DOTREQUIREMENTS_PROJECT_SECRET;
|
|
46
|
-
if (!projectId || !projectSecret) {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
return { projectId, projectSecret };
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Get project ID from environment
|
|
53
|
-
* Returns undefined if not found
|
|
54
|
-
*/
|
|
55
|
-
export function getProjectId() {
|
|
56
|
-
return process.env.DOTREQUIREMENTS_PROJECT_ID;
|
|
57
|
-
}
|
|
58
39
|
//# sourceMappingURL=env.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ensure .
|
|
2
|
+
* Ensure project-settings.json is in .gitignore
|
|
3
3
|
* Creates .gitignore if it doesn't exist
|
|
4
|
-
* Appends
|
|
4
|
+
* Appends the entry if not already present
|
|
5
5
|
*/
|
|
6
6
|
export declare function ensureGitignore(cwd?: string): void;
|
|
7
7
|
//# sourceMappingURL=gitignore.d.ts.map
|
package/dist/utils/gitignore.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
/**
|
|
4
|
-
* Ensure .
|
|
4
|
+
* Ensure project-settings.json is in .gitignore
|
|
5
5
|
* Creates .gitignore if it doesn't exist
|
|
6
|
-
* Appends
|
|
6
|
+
* Appends the entry if not already present
|
|
7
7
|
*/
|
|
8
8
|
export function ensureGitignore(cwd = process.cwd()) {
|
|
9
9
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
10
|
-
const entryToAdd = '.
|
|
10
|
+
const entryToAdd = '.requirements/project-settings.json';
|
|
11
11
|
const comment = '# dotrequirements credentials';
|
|
12
12
|
// Check if .gitignore exists
|
|
13
13
|
if (!fs.existsSync(gitignorePath)) {
|
|
@@ -17,7 +17,7 @@ export function ensureGitignore(cwd = process.cwd()) {
|
|
|
17
17
|
}
|
|
18
18
|
// Read existing .gitignore
|
|
19
19
|
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
20
|
-
// Check if
|
|
20
|
+
// Check if entry is already present
|
|
21
21
|
if (content.includes(entryToAdd)) {
|
|
22
22
|
return; // Already present, nothing to do
|
|
23
23
|
}
|
|
@@ -16,7 +16,6 @@ export interface OAuthResult {
|
|
|
16
16
|
* 3. Open browser for user authentication
|
|
17
17
|
* 4. Wait for callback with authorization code
|
|
18
18
|
* 5. Exchange code for tokens via WorkOS SDK
|
|
19
|
-
* 6. Store tokens securely
|
|
20
19
|
*/
|
|
21
20
|
export declare function executeOAuthFlow(): Promise<OAuthResult>;
|
|
22
21
|
//# sourceMappingURL=oauth-flow.d.ts.map
|