@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.
Files changed (61) hide show
  1. package/README.md +66 -61
  2. package/dist/cli.js +0 -10
  3. package/dist/commands/init.js +167 -224
  4. package/dist/commands/link.d.ts +9 -10
  5. package/dist/commands/link.js +81 -106
  6. package/dist/commands/mcp-setup.js +77 -94
  7. package/dist/commands/pull.js +17 -45
  8. package/dist/commands/push.js +9 -24
  9. package/dist/convex.d.ts +3 -0
  10. package/dist/convex.js +3 -0
  11. package/dist/harness/cache.d.ts +0 -5
  12. package/dist/harness/cache.js +0 -48
  13. package/dist/harness/convexReporting.js +7 -14
  14. package/dist/harness/finalize.d.ts +8 -2
  15. package/dist/harness/finalize.js +48 -31
  16. package/dist/harness/prepare.js +7 -9
  17. package/dist/mcp/convexClient.d.ts +5 -1
  18. package/dist/mcp/convexClient.js +14 -34
  19. package/dist/mcp/index.js +25 -78
  20. package/dist/schema/conversions.d.ts +2 -2
  21. package/dist/schema/conversions.js +2 -3
  22. package/dist/schema/schemas.d.ts +14 -37
  23. package/dist/schema/schemas.js +7 -10
  24. package/dist/schema/test-schema.js +1 -1
  25. package/dist/templates/context-file-section.md +59 -0
  26. package/dist/utils/context-file.d.ts +38 -0
  27. package/dist/utils/context-file.js +94 -0
  28. package/dist/utils/env.d.ts +0 -13
  29. package/dist/utils/env.js +0 -19
  30. package/dist/utils/gitignore.d.ts +2 -2
  31. package/dist/utils/gitignore.js +4 -4
  32. package/dist/utils/oauth-flow.d.ts +0 -1
  33. package/dist/utils/oauth-flow.js +0 -9
  34. package/dist/utils/project-discovery.d.ts +3 -5
  35. package/dist/utils/project-discovery.js +18 -42
  36. package/dist/utils/project-selector.d.ts +17 -3
  37. package/dist/utils/project-selector.js +37 -3
  38. package/dist/utils/project-settings.d.ts +47 -0
  39. package/dist/utils/project-settings.js +110 -0
  40. package/dist/utils/templates.d.ts +0 -24
  41. package/dist/utils/templates.js +0 -39
  42. package/package.json +1 -1
  43. package/dist/harness/localReporting.d.ts +0 -6
  44. package/dist/harness/localReporting.js +0 -49
  45. package/dist/templates/antigravity-gemini.md +0 -3
  46. package/dist/templates/antigravity-overview-rule.md +0 -3
  47. package/dist/templates/antigravity-test-rule.md +0 -3
  48. package/dist/templates/behavioral-core.md +0 -25
  49. package/dist/templates/claude-code-overview-skill.md +0 -6
  50. package/dist/templates/claude-code-skill.md +0 -6
  51. package/dist/templates/claude-code-test-skill.md +0 -6
  52. package/dist/templates/codex-agents.md +0 -3
  53. package/dist/templates/codex-overview-agents.md +0 -3
  54. package/dist/templates/codex-test-agents.md +0 -3
  55. package/dist/templates/cursor-overview-rule.mdc +0 -5
  56. package/dist/templates/cursor-rule.mdc +0 -5
  57. package/dist/templates/cursor-test-rule.mdc +0 -5
  58. package/dist/templates/overview-core.md +0 -27
  59. package/dist/templates/test-writing-core.md +0 -72
  60. package/dist/utils/detect-existing-project.d.ts +0 -5
  61. 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<string | null>;
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
- return selectedTeamId ?? null;
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
@@ -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 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "Requirements tracking CLI, test harness, and MCP server",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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,3 +0,0 @@
1
- <!-- BEGIN dotreq-requirements -->
2
- {{BEHAVIORAL_CORE}}
3
- <!-- END dotreq-requirements -->
@@ -1,3 +0,0 @@
1
- <!-- BEGIN dotreq-overview -->
2
- {{OVERVIEW_CORE}}
3
- <!-- END dotreq-overview -->
@@ -1,3 +0,0 @@
1
- <!-- BEGIN dotreq-tests -->
2
- {{TEST_WRITING_CORE}}
3
- <!-- END dotreq-tests -->
@@ -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,6 +0,0 @@
1
- ---
2
- name: dotreq-overview
3
- description: Use this to understand what the system is supposed to do. ALWAYS use it when exploring expected behavior.
4
- ---
5
-
6
- {{OVERVIEW_CORE}}
@@ -1,6 +0,0 @@
1
- ---
2
- name: dotreq-requirements
3
- description: Use this when adding new expected behavior to the system. ALWAYS use it when implementing new functionality.
4
- ---
5
-
6
- {{BEHAVIORAL_CORE}}
@@ -1,6 +0,0 @@
1
- ---
2
- name: dotreq-tests
3
- description: Use this when validating expected behavior. ALWAYS use it when writing tests.
4
- ---
5
-
6
- {{TEST_WRITING_CORE}}
@@ -1,3 +0,0 @@
1
- <!-- BEGIN dotreq-requirements -->
2
- {{BEHAVIORAL_CORE}}
3
- <!-- END dotreq-requirements -->
@@ -1,3 +0,0 @@
1
- <!-- BEGIN dotreq-overview -->
2
- {{OVERVIEW_CORE}}
3
- <!-- END dotreq-overview -->
@@ -1,3 +0,0 @@
1
- <!-- BEGIN dotreq-tests -->
2
- {{TEST_WRITING_CORE}}
3
- <!-- END dotreq-tests -->
@@ -1,5 +0,0 @@
1
- ---
2
- description: Use this to understand what the system is supposed to do. ALWAYS use it when exploring expected behavior.
3
- ---
4
-
5
- {{OVERVIEW_CORE}}
@@ -1,5 +0,0 @@
1
- ---
2
- description: Use this when adding new expected behavior to the system. ALWAYS use it when implementing new functionality.
3
- ---
4
-
5
- {{BEHAVIORAL_CORE}}
@@ -1,5 +0,0 @@
1
- ---
2
- description: Use this when validating expected behavior. ALWAYS use it when writing tests.
3
- ---
4
-
5
- {{TEST_WRITING_CORE}}
@@ -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,5 +0,0 @@
1
- /**
2
- * Detect if .requirements/ directory exists and extract projectId from files
3
- */
4
- export declare function detectExistingProject(cwd: string): string | null;
5
- //# sourceMappingURL=detect-existing-project.d.ts.map
@@ -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