@popoverai/dotrequirements 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -48
- package/dist/cli.js +1 -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.d.ts +1 -0
- package/dist/commands/pull.js +70 -53
- package/dist/commands/push.js +4 -13
- package/dist/config.js +0 -4
- 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 +9 -1
- package/dist/mcp/convexClient.js +15 -35
- package/dist/mcp/index.js +112 -152
- package/dist/mcp/requirements.d.ts +10 -0
- package/dist/mcp/requirements.js +15 -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 +35 -3
- package/dist/utils/project-settings.d.ts +56 -0
- package/dist/utils/project-settings.js +126 -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/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
|
package/dist/utils/oauth-flow.js
CHANGED
|
@@ -8,7 +8,6 @@ import { randomBytes } from 'crypto';
|
|
|
8
8
|
import { ConvexHttpClient } from 'convex/browser';
|
|
9
9
|
import { startCallbackServer } from './oauth-callback-server.js';
|
|
10
10
|
import { openBrowserWithFallback } from './browser-launch.js';
|
|
11
|
-
import { storeTokens } from './token-storage.js';
|
|
12
11
|
import { getConvexUrl } from '../config.js';
|
|
13
12
|
import { api } from '../convex.js';
|
|
14
13
|
const WORKOS_CLIENT_ID = 'client_01KCM6WPYX201P7DRV5DHF4TR7';
|
|
@@ -20,7 +19,6 @@ const WORKOS_AUTHORIZE_URL = 'https://api.workos.com/user_management/authorize';
|
|
|
20
19
|
* 3. Open browser for user authentication
|
|
21
20
|
* 4. Wait for callback with authorization code
|
|
22
21
|
* 5. Exchange code for tokens via WorkOS SDK
|
|
23
|
-
* 6. Store tokens securely
|
|
24
22
|
*/
|
|
25
23
|
export async function executeOAuthFlow() {
|
|
26
24
|
// Generate PKCE code verifier and challenge
|
|
@@ -58,13 +56,6 @@ export async function executeOAuthFlow() {
|
|
|
58
56
|
throw new Error(`Token exchange failed: ${result.error}`);
|
|
59
57
|
}
|
|
60
58
|
const { accessToken, refreshToken, expiresAt } = result;
|
|
61
|
-
// Store tokens securely
|
|
62
|
-
const tokens = {
|
|
63
|
-
accessToken,
|
|
64
|
-
refreshToken,
|
|
65
|
-
expiresAt,
|
|
66
|
-
};
|
|
67
|
-
await storeTokens(tokens);
|
|
68
59
|
console.log('✓ Authentication complete!');
|
|
69
60
|
return {
|
|
70
61
|
accessToken,
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
export interface DotreqProject {
|
|
5
5
|
/** Absolute path to the project root directory */
|
|
6
6
|
path: string;
|
|
7
|
-
/** Project ID from .
|
|
7
|
+
/** Project ID from project-settings.json */
|
|
8
8
|
projectId: string;
|
|
9
|
-
/** Project secret from .
|
|
9
|
+
/** Project secret from project-settings.json */
|
|
10
10
|
projectSecret: string;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
@@ -24,9 +24,7 @@ export type ProjectDiscoveryResult = {
|
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* Check if a directory is a configured dotrequirements project
|
|
27
|
-
* A project is configured if
|
|
28
|
-
* 1. It has a package.json that includes "dotreq" (in dependencies or devDependencies)
|
|
29
|
-
* 2. It has a .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET
|
|
27
|
+
* A project is configured if it has .requirements/project-settings.json with credentials
|
|
30
28
|
*/
|
|
31
29
|
export declare function isConfiguredProject(dir: string): DotreqProject | null;
|
|
32
30
|
/**
|
|
@@ -1,58 +1,34 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { glob } from 'glob';
|
|
4
|
+
import { readProjectSettings } from './project-settings.js';
|
|
4
5
|
/**
|
|
5
6
|
* Check if a directory is a configured dotrequirements project
|
|
6
|
-
* A project is configured if
|
|
7
|
-
* 1. It has a package.json that includes "dotreq" (in dependencies or devDependencies)
|
|
8
|
-
* 2. It has a .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET
|
|
7
|
+
* A project is configured if it has .requirements/project-settings.json with credentials
|
|
9
8
|
*/
|
|
10
9
|
export function isConfiguredProject(dir) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!fs.existsSync(pkgPath)) {
|
|
10
|
+
const requirementsDir = path.join(dir, '.requirements');
|
|
11
|
+
// Check for .requirements/ directory
|
|
12
|
+
if (!fs.existsSync(requirementsDir) || !fs.statSync(requirementsDir).isDirectory()) {
|
|
15
13
|
return null;
|
|
16
14
|
}
|
|
15
|
+
// Try to read credentials from project-settings.json
|
|
16
|
+
let settings;
|
|
17
17
|
try {
|
|
18
|
-
|
|
19
|
-
// Look for "dotreq" anywhere in the file (flexible matching)
|
|
20
|
-
if (!pkgContent.includes('dotreq')) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
// Check for .env.local with required vars
|
|
28
|
-
if (!fs.existsSync(envPath)) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
let projectId;
|
|
32
|
-
let projectSecret;
|
|
33
|
-
try {
|
|
34
|
-
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
35
|
-
const lines = envContent.split('\n');
|
|
36
|
-
for (const line of lines) {
|
|
37
|
-
const trimmed = line.trim();
|
|
38
|
-
if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_ID=')) {
|
|
39
|
-
projectId = trimmed.split('=')[1]?.trim().replace(/['"]/g, '');
|
|
40
|
-
}
|
|
41
|
-
if (trimmed.startsWith('DOTREQUIREMENTS_PROJECT_SECRET=')) {
|
|
42
|
-
projectSecret = trimmed.split('=')[1]?.trim().replace(/['"]/g, '');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
18
|
+
settings = readProjectSettings(dir);
|
|
45
19
|
}
|
|
46
20
|
catch {
|
|
21
|
+
// Invalid settings file
|
|
47
22
|
return null;
|
|
48
23
|
}
|
|
49
|
-
if (!
|
|
24
|
+
if (!settings) {
|
|
25
|
+
// No credentials (local-only project)
|
|
50
26
|
return null;
|
|
51
27
|
}
|
|
52
28
|
return {
|
|
53
29
|
path: dir,
|
|
54
|
-
projectId,
|
|
55
|
-
projectSecret,
|
|
30
|
+
projectId: settings.projectId,
|
|
31
|
+
projectSecret: settings.projectSecret,
|
|
56
32
|
};
|
|
57
33
|
}
|
|
58
34
|
/**
|
|
@@ -61,14 +37,14 @@ export function isConfiguredProject(dir) {
|
|
|
61
37
|
*/
|
|
62
38
|
export async function findConfiguredProjects(startDir) {
|
|
63
39
|
const projects = [];
|
|
64
|
-
// Find all
|
|
65
|
-
const
|
|
40
|
+
// Find all .requirements directories
|
|
41
|
+
const requirementsDirs = await glob('**/.requirements', {
|
|
66
42
|
cwd: startDir,
|
|
67
43
|
absolute: true,
|
|
68
44
|
ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/build/**'],
|
|
69
45
|
});
|
|
70
|
-
for (const
|
|
71
|
-
const dir = path.dirname(
|
|
46
|
+
for (const reqDir of requirementsDirs) {
|
|
47
|
+
const dir = path.dirname(reqDir);
|
|
72
48
|
const project = isConfiguredProject(dir);
|
|
73
49
|
if (project) {
|
|
74
50
|
projects.push(project);
|
|
@@ -96,7 +72,7 @@ export async function discoverProjects(workingDir = process.cwd()) {
|
|
|
96
72
|
if (childProjects.length === 0) {
|
|
97
73
|
return {
|
|
98
74
|
type: 'none',
|
|
99
|
-
message: `No dotrequirements projects found in ${workingDir} or its children.\n\nA project must have:\n1.
|
|
75
|
+
message: `No dotrequirements projects found in ${workingDir} or its children.\n\nA project must have:\n1. A .requirements/ directory\n2. Cloud credentials in .requirements/project-settings.json\n\nRun "dotrequirements init" to create a project or "dotrequirements link" to connect to cloud.`,
|
|
100
76
|
};
|
|
101
77
|
}
|
|
102
78
|
// Case 3: Exactly one child project
|
|
@@ -4,22 +4,36 @@ export interface SelectedProject {
|
|
|
4
4
|
projectName: string;
|
|
5
5
|
projectSlug: string | null;
|
|
6
6
|
}
|
|
7
|
+
export interface SelectedTeam {
|
|
8
|
+
teamId: string;
|
|
9
|
+
tier: string;
|
|
10
|
+
}
|
|
7
11
|
/**
|
|
8
12
|
* Prompt user to select a team from their available teams.
|
|
9
|
-
* Returns the team ID, or null if user cancels.
|
|
13
|
+
* Returns the team ID and tier, or null if user cancels.
|
|
10
14
|
*/
|
|
11
|
-
export declare function selectTeam(client: ConvexHttpClient): Promise<
|
|
15
|
+
export declare function selectTeam(client: ConvexHttpClient): Promise<SelectedTeam | null>;
|
|
12
16
|
/**
|
|
13
17
|
* Prompt user to select a project from a team.
|
|
14
18
|
* Returns the selected project info, or null if user cancels or no projects exist.
|
|
15
19
|
*/
|
|
16
20
|
export declare function selectProject(client: ConvexHttpClient, teamId: string): Promise<SelectedProject | null>;
|
|
21
|
+
export type ExpiryDays = 30 | 90 | 365;
|
|
17
22
|
/**
|
|
18
23
|
* Get or create a user-scoped secret for a project.
|
|
19
24
|
* Returns the secret and whether it was newly created.
|
|
25
|
+
*
|
|
26
|
+
* @param expiryDays - Optional expiry duration for paid tier users (30/90/365 days)
|
|
27
|
+
* Free tier always gets 30 days regardless of this parameter.
|
|
20
28
|
*/
|
|
21
|
-
export declare function getOrCreateProjectSecret(client: ConvexHttpClient, projectId: string): Promise<{
|
|
29
|
+
export declare function getOrCreateProjectSecret(client: ConvexHttpClient, projectId: string, expiryDays?: ExpiryDays): Promise<{
|
|
22
30
|
secret: string;
|
|
23
31
|
created: boolean;
|
|
32
|
+
expiresAt?: number;
|
|
24
33
|
}>;
|
|
34
|
+
/**
|
|
35
|
+
* Prompt paid-tier users for secret expiry duration.
|
|
36
|
+
* Returns undefined (use default) if user cancels.
|
|
37
|
+
*/
|
|
38
|
+
export declare function promptExpiryDays(tierSlug: string): Promise<ExpiryDays | undefined>;
|
|
25
39
|
//# sourceMappingURL=project-selector.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { promptChoice } from './prompts.js';
|
|
|
2
2
|
import { api } from '../convex.js';
|
|
3
3
|
/**
|
|
4
4
|
* Prompt user to select a team from their available teams.
|
|
5
|
-
* Returns the team ID, or null if user cancels.
|
|
5
|
+
* Returns the team ID and tier, or null if user cancels.
|
|
6
6
|
*/
|
|
7
7
|
export async function selectTeam(client) {
|
|
8
8
|
console.log('Fetching your teams...\n');
|
|
@@ -24,7 +24,14 @@ export async function selectTeam(client) {
|
|
|
24
24
|
};
|
|
25
25
|
});
|
|
26
26
|
const selectedTeamId = await promptChoice('Select a team:', teamChoices);
|
|
27
|
-
|
|
27
|
+
if (!selectedTeamId) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const selectedTeam = teams.find((t) => t.teamId === selectedTeamId);
|
|
31
|
+
return {
|
|
32
|
+
teamId: selectedTeamId,
|
|
33
|
+
tier: selectedTeam?.tier ?? 'free',
|
|
34
|
+
};
|
|
28
35
|
}
|
|
29
36
|
/**
|
|
30
37
|
* Prompt user to select a project from a team.
|
|
@@ -61,11 +68,36 @@ export async function selectProject(client, teamId) {
|
|
|
61
68
|
/**
|
|
62
69
|
* Get or create a user-scoped secret for a project.
|
|
63
70
|
* Returns the secret and whether it was newly created.
|
|
71
|
+
*
|
|
72
|
+
* @param expiryDays - Optional expiry duration for paid tier users (30/90/365 days)
|
|
73
|
+
* Free tier always gets 30 days regardless of this parameter.
|
|
64
74
|
*/
|
|
65
|
-
export async function getOrCreateProjectSecret(client, projectId) {
|
|
75
|
+
export async function getOrCreateProjectSecret(client, projectId, expiryDays) {
|
|
66
76
|
const result = await client.mutation(api.projectSecrets.mutations.ensureOwnSecret, {
|
|
67
77
|
target: { type: 'project', id: projectId },
|
|
78
|
+
expiryDays,
|
|
68
79
|
});
|
|
69
80
|
return result;
|
|
70
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Prompt paid-tier users for secret expiry duration.
|
|
84
|
+
* Returns undefined (use default) if user cancels.
|
|
85
|
+
*/
|
|
86
|
+
export async function promptExpiryDays(tierSlug) {
|
|
87
|
+
// Free tier users don't get a choice - always 30 days
|
|
88
|
+
if (tierSlug === 'free') {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
// AUTHZ-3.1, AUTHZ-3.2: Paid tier users can choose expiry
|
|
92
|
+
const choice = await promptChoice('How long should this secret be valid?', [
|
|
93
|
+
{ title: '365 days (Recommended)', value: '365' },
|
|
94
|
+
{ title: '90 days', value: '90' },
|
|
95
|
+
{ title: '30 days', value: '30' },
|
|
96
|
+
]);
|
|
97
|
+
// If user cancels, use default (365 for paid tier)
|
|
98
|
+
if (!choice) {
|
|
99
|
+
return 365;
|
|
100
|
+
}
|
|
101
|
+
return parseInt(choice, 10);
|
|
102
|
+
}
|
|
71
103
|
//# sourceMappingURL=project-selector.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project credentials stored in project-settings.json
|
|
3
|
+
*/
|
|
4
|
+
export interface ProjectSettings {
|
|
5
|
+
projectId: string;
|
|
6
|
+
projectSecret: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Project status indicating whether it's connected to cloud
|
|
10
|
+
*/
|
|
11
|
+
export type ProjectStatus = 'cloud-connected' | 'local-only';
|
|
12
|
+
/**
|
|
13
|
+
* Result of finding a project
|
|
14
|
+
*/
|
|
15
|
+
export interface ProjectInfo {
|
|
16
|
+
/** Absolute path to the project root (directory containing .requirements/) */
|
|
17
|
+
rootPath: string;
|
|
18
|
+
/** Whether the project has cloud credentials */
|
|
19
|
+
status: ProjectStatus;
|
|
20
|
+
/** Credentials if cloud-connected */
|
|
21
|
+
credentials?: ProjectSettings;
|
|
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;
|
|
32
|
+
/**
|
|
33
|
+
* Find the project root by walking up from startDir looking for .requirements/ folder
|
|
34
|
+
* Returns the directory containing .requirements/, or undefined if not found
|
|
35
|
+
*/
|
|
36
|
+
export declare function findProjectRoot(startDir?: string): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Read project settings from .requirements/project-settings.json
|
|
39
|
+
* Returns undefined if file doesn't exist, throws if file is invalid JSON
|
|
40
|
+
*/
|
|
41
|
+
export declare function readProjectSettings(projectRoot: string): ProjectSettings | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Write project settings to .requirements/project-settings.json
|
|
44
|
+
* Creates .requirements/ directory if it doesn't exist
|
|
45
|
+
*/
|
|
46
|
+
export declare function writeProjectSettings(projectRoot: string, settings: ProjectSettings): void;
|
|
47
|
+
/**
|
|
48
|
+
* Get project info including status and credentials
|
|
49
|
+
* Returns undefined if no project found
|
|
50
|
+
*/
|
|
51
|
+
export declare function getProjectInfo(startDir?: string): ProjectInfo | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Get project credentials, throwing helpful error if not available
|
|
54
|
+
*/
|
|
55
|
+
export declare function getProjectCredentials(startDir?: string): ProjectSettings;
|
|
56
|
+
//# sourceMappingURL=project-settings.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
const REQUIREMENTS_DIR = '.requirements';
|
|
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
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Find the project root by walking up from startDir looking for .requirements/ folder
|
|
23
|
+
* Returns the directory containing .requirements/, or undefined if not found
|
|
24
|
+
*/
|
|
25
|
+
export function findProjectRoot(startDir = process.cwd()) {
|
|
26
|
+
let currentDir = path.resolve(startDir);
|
|
27
|
+
const root = path.parse(currentDir).root;
|
|
28
|
+
while (currentDir !== root) {
|
|
29
|
+
const requirementsDir = path.join(currentDir, REQUIREMENTS_DIR);
|
|
30
|
+
if (fs.existsSync(requirementsDir) && fs.statSync(requirementsDir).isDirectory()) {
|
|
31
|
+
return currentDir;
|
|
32
|
+
}
|
|
33
|
+
currentDir = path.dirname(currentDir);
|
|
34
|
+
}
|
|
35
|
+
// Check root directory as well
|
|
36
|
+
const rootRequirementsDir = path.join(root, REQUIREMENTS_DIR);
|
|
37
|
+
if (fs.existsSync(rootRequirementsDir) && fs.statSync(rootRequirementsDir).isDirectory()) {
|
|
38
|
+
return root;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read project settings from .requirements/project-settings.json
|
|
44
|
+
* Returns undefined if file doesn't exist, throws if file is invalid JSON
|
|
45
|
+
*/
|
|
46
|
+
export function readProjectSettings(projectRoot) {
|
|
47
|
+
const settingsPath = path.join(projectRoot, REQUIREMENTS_DIR, SETTINGS_FILE);
|
|
48
|
+
if (!fs.existsSync(settingsPath)) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
let content;
|
|
52
|
+
try {
|
|
53
|
+
content = fs.readFileSync(settingsPath, 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw new Error(`Could not read project-settings.json: ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(content);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
throw new Error(`project-settings.json is invalid: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
if (typeof parsed !== 'object' ||
|
|
66
|
+
parsed === null ||
|
|
67
|
+
typeof parsed.projectId !== 'string' ||
|
|
68
|
+
typeof parsed.projectSecret !== 'string') {
|
|
69
|
+
throw new Error('project-settings.json is invalid: missing projectId or projectSecret');
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
projectId: parsed.projectId,
|
|
73
|
+
projectSecret: parsed.projectSecret,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Write project settings to .requirements/project-settings.json
|
|
78
|
+
* Creates .requirements/ directory if it doesn't exist
|
|
79
|
+
*/
|
|
80
|
+
export function writeProjectSettings(projectRoot, settings) {
|
|
81
|
+
const requirementsDir = path.join(projectRoot, REQUIREMENTS_DIR);
|
|
82
|
+
const settingsPath = path.join(requirementsDir, SETTINGS_FILE);
|
|
83
|
+
// Create .requirements/ directory if needed
|
|
84
|
+
if (!fs.existsSync(requirementsDir)) {
|
|
85
|
+
fs.mkdirSync(requirementsDir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
const content = JSON.stringify(settings, null, 2) + '\n';
|
|
88
|
+
fs.writeFileSync(settingsPath, content, 'utf-8');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get project info including status and credentials
|
|
92
|
+
* Returns undefined if no project found
|
|
93
|
+
*/
|
|
94
|
+
export function getProjectInfo(startDir = process.cwd()) {
|
|
95
|
+
const rootPath = findProjectRoot(startDir);
|
|
96
|
+
if (!rootPath) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
let credentials;
|
|
100
|
+
try {
|
|
101
|
+
credentials = readProjectSettings(rootPath);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Invalid settings file means local-only (credentials not usable)
|
|
105
|
+
credentials = undefined;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
rootPath,
|
|
109
|
+
status: credentials ? 'cloud-connected' : 'local-only',
|
|
110
|
+
credentials,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get project credentials, throwing helpful error if not available
|
|
115
|
+
*/
|
|
116
|
+
export function getProjectCredentials(startDir = process.cwd()) {
|
|
117
|
+
const info = getProjectInfo(startDir);
|
|
118
|
+
if (!info) {
|
|
119
|
+
throw new Error('No dotrequirements project found. Run "dotrequirements init" to create one.');
|
|
120
|
+
}
|
|
121
|
+
if (!info.credentials) {
|
|
122
|
+
throw new Error('Project is not connected to cloud. Run "dotrequirements link" to connect.');
|
|
123
|
+
}
|
|
124
|
+
return info.credentials;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=project-settings.js.map
|
|
@@ -2,28 +2,4 @@
|
|
|
2
2
|
* Load a template file and replace placeholders
|
|
3
3
|
*/
|
|
4
4
|
export declare function loadTemplate(templateName: string, replacements?: Record<string, string>): string;
|
|
5
|
-
/**
|
|
6
|
-
* Load the behavioral core content (requirements capture guidance)
|
|
7
|
-
*/
|
|
8
|
-
export declare function loadBehavioralCore(): string;
|
|
9
|
-
/**
|
|
10
|
-
* Load the test writing core content
|
|
11
|
-
*/
|
|
12
|
-
export declare function loadTestWritingCore(): string;
|
|
13
|
-
/**
|
|
14
|
-
* Load the overview core content
|
|
15
|
-
*/
|
|
16
|
-
export declare function loadOverviewCore(): string;
|
|
17
|
-
/**
|
|
18
|
-
* Load the requirements skill template with core content injected
|
|
19
|
-
*/
|
|
20
|
-
export declare function loadPlatformTemplate(templateName: string): string;
|
|
21
|
-
/**
|
|
22
|
-
* Load the test writing skill template with core content injected
|
|
23
|
-
*/
|
|
24
|
-
export declare function loadTestWritingTemplate(templateName: string): string;
|
|
25
|
-
/**
|
|
26
|
-
* Load the overview skill template with core content injected
|
|
27
|
-
*/
|
|
28
|
-
export declare function loadOverviewTemplate(templateName: string): string;
|
|
29
5
|
//# sourceMappingURL=templates.d.ts.map
|
package/dist/utils/templates.js
CHANGED
|
@@ -25,43 +25,4 @@ export function loadTemplate(templateName, replacements = {}) {
|
|
|
25
25
|
}
|
|
26
26
|
return content;
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Load the behavioral core content (requirements capture guidance)
|
|
30
|
-
*/
|
|
31
|
-
export function loadBehavioralCore() {
|
|
32
|
-
return loadTemplate('behavioral-core.md');
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Load the test writing core content
|
|
36
|
-
*/
|
|
37
|
-
export function loadTestWritingCore() {
|
|
38
|
-
return loadTemplate('test-writing-core.md');
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Load the overview core content
|
|
42
|
-
*/
|
|
43
|
-
export function loadOverviewCore() {
|
|
44
|
-
return loadTemplate('overview-core.md');
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Load the requirements skill template with core content injected
|
|
48
|
-
*/
|
|
49
|
-
export function loadPlatformTemplate(templateName) {
|
|
50
|
-
const behavioralCore = loadBehavioralCore();
|
|
51
|
-
return loadTemplate(templateName, { BEHAVIORAL_CORE: behavioralCore });
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Load the test writing skill template with core content injected
|
|
55
|
-
*/
|
|
56
|
-
export function loadTestWritingTemplate(templateName) {
|
|
57
|
-
const testWritingCore = loadTestWritingCore();
|
|
58
|
-
return loadTemplate(templateName, { TEST_WRITING_CORE: testWritingCore });
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Load the overview skill template with core content injected
|
|
62
|
-
*/
|
|
63
|
-
export function loadOverviewTemplate(templateName) {
|
|
64
|
-
const overviewCore = loadOverviewCore();
|
|
65
|
-
return loadTemplate(templateName, { OVERVIEW_CORE: overviewCore });
|
|
66
|
-
}
|
|
67
28
|
//# sourceMappingURL=templates.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { TrackedRequirement } from "./tracking.js";
|
|
2
|
-
/**
|
|
3
|
-
* Generate a simple console report showing which requirements were tested
|
|
4
|
-
*/
|
|
5
|
-
export declare function reportLocalCoverage(trackedReqs?: Map<string, TrackedRequirement>): void;
|
|
6
|
-
//# sourceMappingURL=localReporting.d.ts.map
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { getAllRequirements } from "./requirementsLoader.js";
|
|
2
|
-
/**
|
|
3
|
-
* Generate a simple console report showing which requirements were tested
|
|
4
|
-
*/
|
|
5
|
-
export function reportLocalCoverage(trackedReqs) {
|
|
6
|
-
const allRequirements = getAllRequirements();
|
|
7
|
-
// If no tracked requirements provided, return empty report
|
|
8
|
-
if (!trackedReqs) {
|
|
9
|
-
trackedReqs = new Map();
|
|
10
|
-
}
|
|
11
|
-
const allRequirementKeys = Array.from(allRequirements.keys());
|
|
12
|
-
const testedKeys = Array.from(trackedReqs.keys());
|
|
13
|
-
const untestedKeys = allRequirementKeys.filter((key) => !trackedReqs.has(key));
|
|
14
|
-
const coverage = allRequirementKeys.length > 0
|
|
15
|
-
? ((testedKeys.length / allRequirementKeys.length) * 100).toFixed(1)
|
|
16
|
-
: "0.0";
|
|
17
|
-
let report = "\n=== Requirements Coverage Report ===\n";
|
|
18
|
-
report += `\nTotal Requirements: ${allRequirementKeys.length}\n`;
|
|
19
|
-
report += `Tested Requirements: ${testedKeys.length}\n`;
|
|
20
|
-
report += `Untested Requirements: ${untestedKeys.length}\n`;
|
|
21
|
-
report += `Coverage: ${coverage}%\n`;
|
|
22
|
-
// Show tested requirements
|
|
23
|
-
if (testedKeys.length > 0) {
|
|
24
|
-
report += "\n✓ Tested Requirements:\n";
|
|
25
|
-
testedKeys.forEach((key) => {
|
|
26
|
-
const req = allRequirements.get(key);
|
|
27
|
-
const label = req?.label || "";
|
|
28
|
-
const content = req?.content || "";
|
|
29
|
-
const preview = content.length > 60 ? content.substring(0, 60) + "..." : content;
|
|
30
|
-
const labelPart = label ? ` (${label})` : "";
|
|
31
|
-
report += ` - ${key}${labelPart}: ${preview}\n`;
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
// Show untested requirements
|
|
35
|
-
if (untestedKeys.length > 0) {
|
|
36
|
-
report += "\n✗ Untested Requirements:\n";
|
|
37
|
-
untestedKeys.forEach((key) => {
|
|
38
|
-
const req = allRequirements.get(key);
|
|
39
|
-
const label = req?.label || "";
|
|
40
|
-
const content = req?.content || "";
|
|
41
|
-
const preview = content.length > 60 ? content.substring(0, 60) + "..." : content;
|
|
42
|
-
const labelPart = label ? ` (${label})` : "";
|
|
43
|
-
report += ` - ${key}${labelPart}: ${preview}\n`;
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
report += "\n====================================\n";
|
|
47
|
-
console.log(report);
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=localReporting.js.map
|