@popoverai/dotrequirements 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -61
- package/dist/cli.js +0 -10
- package/dist/commands/init.js +167 -224
- package/dist/commands/link.d.ts +9 -10
- package/dist/commands/link.js +81 -106
- package/dist/commands/mcp-setup.js +77 -94
- package/dist/commands/pull.js +17 -45
- package/dist/commands/push.js +9 -24
- package/dist/convex.d.ts +3 -0
- package/dist/convex.js +3 -0
- package/dist/harness/cache.d.ts +0 -5
- package/dist/harness/cache.js +0 -48
- package/dist/harness/convexReporting.js +7 -14
- package/dist/harness/finalize.d.ts +8 -2
- package/dist/harness/finalize.js +48 -31
- package/dist/harness/prepare.js +7 -9
- package/dist/mcp/convexClient.d.ts +5 -1
- package/dist/mcp/convexClient.js +14 -34
- package/dist/mcp/index.js +25 -78
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +2 -3
- package/dist/schema/schemas.d.ts +14 -37
- package/dist/schema/schemas.js +7 -10
- package/dist/schema/test-schema.js +1 -1
- package/dist/templates/context-file-section.md +59 -0
- package/dist/utils/context-file.d.ts +38 -0
- package/dist/utils/context-file.js +94 -0
- package/dist/utils/env.d.ts +0 -13
- package/dist/utils/env.js +0 -19
- package/dist/utils/gitignore.d.ts +2 -2
- package/dist/utils/gitignore.js +4 -4
- package/dist/utils/oauth-flow.d.ts +0 -1
- package/dist/utils/oauth-flow.js +0 -9
- package/dist/utils/project-discovery.d.ts +3 -5
- package/dist/utils/project-discovery.js +18 -42
- package/dist/utils/project-selector.d.ts +17 -3
- package/dist/utils/project-selector.js +37 -3
- package/dist/utils/project-settings.d.ts +47 -0
- package/dist/utils/project-settings.js +110 -0
- package/dist/utils/templates.d.ts +0 -24
- package/dist/utils/templates.js +0 -39
- package/package.json +1 -1
- package/dist/harness/localReporting.d.ts +0 -6
- package/dist/harness/localReporting.js +0 -49
- package/dist/templates/antigravity-gemini.md +0 -3
- package/dist/templates/antigravity-overview-rule.md +0 -3
- package/dist/templates/antigravity-test-rule.md +0 -3
- package/dist/templates/behavioral-core.md +0 -25
- package/dist/templates/claude-code-overview-skill.md +0 -6
- package/dist/templates/claude-code-skill.md +0 -6
- package/dist/templates/claude-code-test-skill.md +0 -6
- package/dist/templates/codex-agents.md +0 -3
- package/dist/templates/codex-overview-agents.md +0 -3
- package/dist/templates/codex-test-agents.md +0 -3
- package/dist/templates/cursor-overview-rule.mdc +0 -5
- package/dist/templates/cursor-rule.mdc +0 -5
- package/dist/templates/cursor-test-rule.mdc +0 -5
- package/dist/templates/overview-core.md +0 -27
- package/dist/templates/test-writing-core.md +0 -72
- package/dist/utils/detect-existing-project.d.ts +0 -5
- package/dist/utils/detect-existing-project.js +0 -34
|
@@ -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,10 +2,12 @@ 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');
|
|
9
|
+
// AUTH-24.0: Ensure user has a default team if they don't have any
|
|
10
|
+
await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
|
|
9
11
|
const teams = await client.query(api.teams.queries.listForUserWithUsage);
|
|
10
12
|
if (teams.length === 0) {
|
|
11
13
|
console.log('You are not a member of any teams.');
|
|
@@ -22,7 +24,14 @@ export async function selectTeam(client) {
|
|
|
22
24
|
};
|
|
23
25
|
});
|
|
24
26
|
const selectedTeamId = await promptChoice('Select a team:', teamChoices);
|
|
25
|
-
|
|
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
|
+
};
|
|
26
35
|
}
|
|
27
36
|
/**
|
|
28
37
|
* Prompt user to select a project from a team.
|
|
@@ -59,11 +68,36 @@ export async function selectProject(client, teamId) {
|
|
|
59
68
|
/**
|
|
60
69
|
* Get or create a user-scoped secret for a project.
|
|
61
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.
|
|
62
74
|
*/
|
|
63
|
-
export async function getOrCreateProjectSecret(client, projectId) {
|
|
75
|
+
export async function getOrCreateProjectSecret(client, projectId, expiryDays) {
|
|
64
76
|
const result = await client.mutation(api.projectSecrets.mutations.ensureOwnSecret, {
|
|
65
77
|
target: { type: 'project', id: projectId },
|
|
78
|
+
expiryDays,
|
|
66
79
|
});
|
|
67
80
|
return result;
|
|
68
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
|
+
}
|
|
69
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`.
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Understanding Expected Behavior
|
|
2
|
-
|
|
3
|
-
ALWAYS use this when you want to understand what the system is supposed to do.
|
|
4
|
-
|
|
5
|
-
## Where to Find Expected Behavior
|
|
6
|
-
|
|
7
|
-
This project uses **requirements as the source of truth** for expected behavior.
|
|
8
|
-
|
|
9
|
-
**Location:** `.requirements/` directory contains `*.requirements.md` files
|
|
10
|
-
|
|
11
|
-
**Format:** Requirements are structured as:
|
|
12
|
-
- Requirement IDs (e.g., `AUTH-1`, `LOGIN-2`)
|
|
13
|
-
- Descriptions of expected behavior
|
|
14
|
-
- Hierarchical structure (parent requirements with child criteria)
|
|
15
|
-
|
|
16
|
-
## MCP Tools for Exploration
|
|
17
|
-
|
|
18
|
-
- `list_all_requirements` - Get overview of all requirements in the project
|
|
19
|
-
- `get_requirement` - Get detailed requirement tree with test coverage
|
|
20
|
-
- `search_requirements` - Search by text/regex across all requirements
|
|
21
|
-
- `get_requirements_by_test` - See what a test file validates
|
|
22
|
-
|
|
23
|
-
## Next Steps
|
|
24
|
-
|
|
25
|
-
Once you understand the expected behavior:
|
|
26
|
-
- **Adding new behavior?** Use the `dotreq-requirements` skill
|
|
27
|
-
- **Writing tests?** Use the `dotreq-tests` skill
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Test Writing with Requirements
|
|
2
|
-
|
|
3
|
-
ALWAYS use this guidance when writing tests based on requirements.
|
|
4
|
-
|
|
5
|
-
## Core Principle
|
|
6
|
-
|
|
7
|
-
Use `requirement()` AS the test description, not in the body or comments.
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
// ✅ Good
|
|
11
|
-
test(requirement('AUTH-1'), () => { /* test */ });
|
|
12
|
-
|
|
13
|
-
// ❌ Bad
|
|
14
|
-
test("user can log in", () => { requirement('AUTH-1'); });
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Test Structure
|
|
18
|
-
|
|
19
|
-
**Match the requirement hierarchy:**
|
|
20
|
-
|
|
21
|
-
For structured requirements, nest describe/it blocks:
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
describe(requirement('LOGIN-1'), () => {
|
|
25
|
-
describe(requirement('LOGIN-1.given'), () => {
|
|
26
|
-
// Arrange
|
|
27
|
-
});
|
|
28
|
-
describe(requirement('LOGIN-1.when'), () => {
|
|
29
|
-
// Act
|
|
30
|
-
it(requirement('LOGIN-1.then'), () => {
|
|
31
|
-
// Assert
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
For simple requirements, use a single test block.
|
|
38
|
-
|
|
39
|
-
## Comments
|
|
40
|
-
|
|
41
|
-
Comments should describe the TEST implementation, not copy the requirement text verbatim.
|
|
42
|
-
|
|
43
|
-
**NEVER document coverage in comments.** Our MCP tools track coverage automatically.
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
// ❌ Bad - creates second source of truth
|
|
47
|
-
/**
|
|
48
|
-
* Requirements coverage:
|
|
49
|
-
* - AUTH-9.0: Backend creates project ✓
|
|
50
|
-
* - AUTH-9.1: CLI writes to .env.local ✓
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
|
-
// ✅ Good - use MCP tools instead
|
|
54
|
-
// Use get_requirement, get_requirements_by_test to see coverage
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Workflow
|
|
58
|
-
|
|
59
|
-
After writing tests:
|
|
60
|
-
|
|
61
|
-
1. Run tests to ensure they don't throw errors
|
|
62
|
-
2. **ALWAYS run `review_test`** on the test file to validate tests actually check what requirements specify
|
|
63
|
-
3. Fix any issues identified by the review
|
|
64
|
-
|
|
65
|
-
## MCP Tools for Test Writing
|
|
66
|
-
|
|
67
|
-
- `get_requirement` - Get requirement tree with existing coverage
|
|
68
|
-
- `get_requirements_by_test` - See requirements referenced in a test file
|
|
69
|
-
- `list_untested_requirements` - Find requirements without tests
|
|
70
|
-
- `review_test` - Validate test actually checks what requirement specifies (run after writing tests)
|
|
71
|
-
|
|
72
|
-
Note: Users can also explicitly trigger this workflow with `/write-tests`.
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { parseRequirementsFromFile } from '../schema/index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Detect if .requirements/ directory exists and extract projectId from files
|
|
6
|
-
*/
|
|
7
|
-
export function detectExistingProject(cwd) {
|
|
8
|
-
const requirementsDir = path.join(cwd, '.requirements');
|
|
9
|
-
if (!fs.existsSync(requirementsDir)) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
// Look for .requirements.md files
|
|
13
|
-
const files = fs.readdirSync(requirementsDir)
|
|
14
|
-
.filter(f => f.endsWith('.requirements.md'));
|
|
15
|
-
if (files.length === 0) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
// Try to parse first file to get projectId
|
|
19
|
-
for (const file of files) {
|
|
20
|
-
try {
|
|
21
|
-
const filePath = path.join(requirementsDir, file);
|
|
22
|
-
const parsed = parseRequirementsFromFile(filePath);
|
|
23
|
-
if (parsed.metadata.projectId) {
|
|
24
|
-
return parsed.metadata.projectId;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
// Skip files that can't be parsed
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
//# sourceMappingURL=detect-existing-project.js.map
|