@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.
Files changed (57) hide show
  1. package/README.md +92 -48
  2. package/dist/cli.js +1 -10
  3. package/dist/commands/init.js +166 -226
  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.d.ts +1 -0
  8. package/dist/commands/pull.js +70 -53
  9. package/dist/commands/push.js +4 -13
  10. package/dist/config.js +0 -4
  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.js +7 -12
  15. package/dist/harness/prepare.js +7 -9
  16. package/dist/mcp/convexClient.d.ts +9 -1
  17. package/dist/mcp/convexClient.js +15 -35
  18. package/dist/mcp/index.js +112 -152
  19. package/dist/mcp/requirements.d.ts +10 -0
  20. package/dist/mcp/requirements.js +15 -1
  21. package/dist/templates/context-file-section.md +59 -0
  22. package/dist/utils/context-file.d.ts +38 -0
  23. package/dist/utils/context-file.js +94 -0
  24. package/dist/utils/env.d.ts +0 -13
  25. package/dist/utils/env.js +0 -19
  26. package/dist/utils/gitignore.d.ts +2 -2
  27. package/dist/utils/gitignore.js +4 -4
  28. package/dist/utils/oauth-flow.d.ts +0 -1
  29. package/dist/utils/oauth-flow.js +0 -9
  30. package/dist/utils/project-discovery.d.ts +3 -5
  31. package/dist/utils/project-discovery.js +18 -42
  32. package/dist/utils/project-selector.d.ts +17 -3
  33. package/dist/utils/project-selector.js +35 -3
  34. package/dist/utils/project-settings.d.ts +56 -0
  35. package/dist/utils/project-settings.js +126 -0
  36. package/dist/utils/templates.d.ts +0 -24
  37. package/dist/utils/templates.js +0 -39
  38. package/package.json +1 -1
  39. package/dist/harness/localReporting.d.ts +0 -6
  40. package/dist/harness/localReporting.js +0 -49
  41. package/dist/templates/antigravity-gemini.md +0 -3
  42. package/dist/templates/antigravity-overview-rule.md +0 -3
  43. package/dist/templates/antigravity-test-rule.md +0 -3
  44. package/dist/templates/behavioral-core.md +0 -25
  45. package/dist/templates/claude-code-overview-skill.md +0 -6
  46. package/dist/templates/claude-code-skill.md +0 -6
  47. package/dist/templates/claude-code-test-skill.md +0 -6
  48. package/dist/templates/codex-agents.md +0 -3
  49. package/dist/templates/codex-overview-agents.md +0 -3
  50. package/dist/templates/codex-test-agents.md +0 -3
  51. package/dist/templates/cursor-overview-rule.mdc +0 -5
  52. package/dist/templates/cursor-rule.mdc +0 -5
  53. package/dist/templates/cursor-test-rule.mdc +0 -5
  54. package/dist/templates/overview-core.md +0 -27
  55. package/dist/templates/test-writing-core.md +0 -72
  56. package/dist/utils/detect-existing-project.d.ts +0 -5
  57. package/dist/utils/detect-existing-project.js +0 -34
@@ -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 .env.local is in .gitignore
2
+ * Ensure project-settings.json is in .gitignore
3
3
  * Creates .gitignore if it doesn't exist
4
- * Appends .env.local if not already present
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
@@ -1,13 +1,13 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  /**
4
- * Ensure .env.local is in .gitignore
4
+ * Ensure project-settings.json is in .gitignore
5
5
  * Creates .gitignore if it doesn't exist
6
- * Appends .env.local if not already present
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 = '.env.local';
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 .env.local is already present
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
@@ -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 .env.local */
7
+ /** Project ID from project-settings.json */
8
8
  projectId: string;
9
- /** Project secret from .env.local */
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 pkgPath = path.join(dir, 'package.json');
12
- const envPath = path.join(dir, '.env.local');
13
- // Check for package.json with dotreq
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
- const pkgContent = fs.readFileSync(pkgPath, 'utf-8');
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 (!projectId || !projectSecret) {
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 package.json files, but allow .env.local despite being in .gitignore
65
- const packageJsonFiles = await glob('**/package.json', {
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 pkgPath of packageJsonFiles) {
71
- const dir = path.dirname(pkgPath);
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. package.json with "dotreq" dependency\n2. .env.local with DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET`,
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<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,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
- 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
+ };
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
@@ -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.13.0",
3
+ "version": "0.15.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 -->