@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
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,47 @@
|
|
|
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
|
+
* Find the project root by walking up from startDir looking for .requirements/ folder
|
|
25
|
+
* Returns the directory containing .requirements/, or undefined if not found
|
|
26
|
+
*/
|
|
27
|
+
export declare function findProjectRoot(startDir?: string): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Read project settings from .requirements/project-settings.json
|
|
30
|
+
* Returns undefined if file doesn't exist, throws if file is invalid JSON
|
|
31
|
+
*/
|
|
32
|
+
export declare function readProjectSettings(projectRoot: string): ProjectSettings | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Write project settings to .requirements/project-settings.json
|
|
35
|
+
* Creates .requirements/ directory if it doesn't exist
|
|
36
|
+
*/
|
|
37
|
+
export declare function writeProjectSettings(projectRoot: string, settings: ProjectSettings): void;
|
|
38
|
+
/**
|
|
39
|
+
* Get project info including status and credentials
|
|
40
|
+
* Returns undefined if no project found
|
|
41
|
+
*/
|
|
42
|
+
export declare function getProjectInfo(startDir?: string): ProjectInfo | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Get project credentials, throwing helpful error if not available
|
|
45
|
+
*/
|
|
46
|
+
export declare function getProjectCredentials(startDir?: string): ProjectSettings;
|
|
47
|
+
//# sourceMappingURL=project-settings.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
* Find the project root by walking up from startDir looking for .requirements/ folder
|
|
7
|
+
* Returns the directory containing .requirements/, or undefined if not found
|
|
8
|
+
*/
|
|
9
|
+
export function findProjectRoot(startDir = process.cwd()) {
|
|
10
|
+
let currentDir = path.resolve(startDir);
|
|
11
|
+
const root = path.parse(currentDir).root;
|
|
12
|
+
while (currentDir !== root) {
|
|
13
|
+
const requirementsDir = path.join(currentDir, REQUIREMENTS_DIR);
|
|
14
|
+
if (fs.existsSync(requirementsDir) && fs.statSync(requirementsDir).isDirectory()) {
|
|
15
|
+
return currentDir;
|
|
16
|
+
}
|
|
17
|
+
currentDir = path.dirname(currentDir);
|
|
18
|
+
}
|
|
19
|
+
// Check root directory as well
|
|
20
|
+
const rootRequirementsDir = path.join(root, REQUIREMENTS_DIR);
|
|
21
|
+
if (fs.existsSync(rootRequirementsDir) && fs.statSync(rootRequirementsDir).isDirectory()) {
|
|
22
|
+
return root;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read project settings from .requirements/project-settings.json
|
|
28
|
+
* Returns undefined if file doesn't exist, throws if file is invalid JSON
|
|
29
|
+
*/
|
|
30
|
+
export function readProjectSettings(projectRoot) {
|
|
31
|
+
const settingsPath = path.join(projectRoot, REQUIREMENTS_DIR, SETTINGS_FILE);
|
|
32
|
+
if (!fs.existsSync(settingsPath)) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
let content;
|
|
36
|
+
try {
|
|
37
|
+
content = fs.readFileSync(settingsPath, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
throw new Error(`Could not read project-settings.json: ${err.message}`);
|
|
41
|
+
}
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(content);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
throw new Error(`project-settings.json is invalid: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
if (typeof parsed !== 'object' ||
|
|
50
|
+
parsed === null ||
|
|
51
|
+
typeof parsed.projectId !== 'string' ||
|
|
52
|
+
typeof parsed.projectSecret !== 'string') {
|
|
53
|
+
throw new Error('project-settings.json is invalid: missing projectId or projectSecret');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
projectId: parsed.projectId,
|
|
57
|
+
projectSecret: parsed.projectSecret,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Write project settings to .requirements/project-settings.json
|
|
62
|
+
* Creates .requirements/ directory if it doesn't exist
|
|
63
|
+
*/
|
|
64
|
+
export function writeProjectSettings(projectRoot, settings) {
|
|
65
|
+
const requirementsDir = path.join(projectRoot, REQUIREMENTS_DIR);
|
|
66
|
+
const settingsPath = path.join(requirementsDir, SETTINGS_FILE);
|
|
67
|
+
// Create .requirements/ directory if needed
|
|
68
|
+
if (!fs.existsSync(requirementsDir)) {
|
|
69
|
+
fs.mkdirSync(requirementsDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
const content = JSON.stringify(settings, null, 2) + '\n';
|
|
72
|
+
fs.writeFileSync(settingsPath, content, 'utf-8');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get project info including status and credentials
|
|
76
|
+
* Returns undefined if no project found
|
|
77
|
+
*/
|
|
78
|
+
export function getProjectInfo(startDir = process.cwd()) {
|
|
79
|
+
const rootPath = findProjectRoot(startDir);
|
|
80
|
+
if (!rootPath) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
let credentials;
|
|
84
|
+
try {
|
|
85
|
+
credentials = readProjectSettings(rootPath);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Invalid settings file means local-only (credentials not usable)
|
|
89
|
+
credentials = undefined;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
rootPath,
|
|
93
|
+
status: credentials ? 'cloud-connected' : 'local-only',
|
|
94
|
+
credentials,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get project credentials, throwing helpful error if not available
|
|
99
|
+
*/
|
|
100
|
+
export function getProjectCredentials(startDir = process.cwd()) {
|
|
101
|
+
const info = getProjectInfo(startDir);
|
|
102
|
+
if (!info) {
|
|
103
|
+
throw new Error('No dotrequirements project found. Run "dotrequirements init" to create one.');
|
|
104
|
+
}
|
|
105
|
+
if (!info.credentials) {
|
|
106
|
+
throw new Error('Project is not connected to cloud. Run "dotrequirements link" to connect.');
|
|
107
|
+
}
|
|
108
|
+
return info.credentials;
|
|
109
|
+
}
|
|
110
|
+
//# 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
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Requirements Capture
|
|
2
|
-
|
|
3
|
-
ALWAYS ask about capturing requirements when the user requests new functionality (not bug fixes or refactoring).
|
|
4
|
-
|
|
5
|
-
Ask: "Should we capture what this should do before we build it?"
|
|
6
|
-
|
|
7
|
-
If yes, use the MCP tools for requirements writing.
|
|
8
|
-
|
|
9
|
-
## Workflow
|
|
10
|
-
|
|
11
|
-
1. **Ask questions** only if there are major gaps in expected behavior
|
|
12
|
-
2. **Use requirements as alignment** - Get assumptions on paper quickly instead of playing twenty questions
|
|
13
|
-
3. **Think MVP** - Don't expand scope without consulting the user
|
|
14
|
-
4. **After building** - Suggest testing against the requirements using the project's standard testing tools
|
|
15
|
-
|
|
16
|
-
Be helpful, consultative, and encouraging; not obstructive.
|
|
17
|
-
|
|
18
|
-
## MCP Tools for Requirements
|
|
19
|
-
|
|
20
|
-
- `create_requirement_document` - Get template with codebase-aware format guidance and style principles
|
|
21
|
-
- `validate_requirements` - Check syntax before pushing (works offline)
|
|
22
|
-
- `style_check` - Get AI feedback on writing quality and clarity
|
|
23
|
-
- `push_requirements` - Sync to cloud (shows diff preview, then confirms)
|
|
24
|
-
|
|
25
|
-
Note: Users can also explicitly trigger this workflow with `/capture-requirements`.
|