@popoverai/dotrequirements 0.22.0 → 0.24.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 (230) hide show
  1. package/README.md +173 -23
  2. package/dist/cli.js +121 -61
  3. package/dist/codebase-to-spec/budget.d.ts +53 -0
  4. package/dist/codebase-to-spec/budget.js +80 -0
  5. package/dist/codebase-to-spec/cache.d.ts +49 -0
  6. package/dist/codebase-to-spec/cache.js +54 -0
  7. package/dist/codebase-to-spec/claude.d.ts +69 -0
  8. package/dist/codebase-to-spec/claude.js +126 -0
  9. package/dist/codebase-to-spec/compose.d.ts +49 -0
  10. package/dist/codebase-to-spec/compose.js +124 -0
  11. package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
  12. package/dist/codebase-to-spec/edit-loop.js +195 -0
  13. package/dist/codebase-to-spec/editor.d.ts +54 -0
  14. package/dist/codebase-to-spec/editor.js +74 -0
  15. package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
  16. package/dist/codebase-to-spec/exit-codes.js +58 -0
  17. package/dist/codebase-to-spec/fan-out.d.ts +63 -0
  18. package/dist/codebase-to-spec/fan-out.js +215 -0
  19. package/dist/codebase-to-spec/interactive.d.ts +30 -0
  20. package/dist/codebase-to-spec/interactive.js +48 -0
  21. package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
  22. package/dist/codebase-to-spec/outline-review-loop.js +187 -0
  23. package/dist/codebase-to-spec/pack.d.ts +51 -0
  24. package/dist/codebase-to-spec/pack.js +127 -0
  25. package/dist/codebase-to-spec/planner.d.ts +41 -0
  26. package/dist/codebase-to-spec/planner.js +76 -0
  27. package/dist/codebase-to-spec/present.d.ts +94 -0
  28. package/dist/codebase-to-spec/present.js +288 -0
  29. package/dist/codebase-to-spec/progress.d.ts +33 -0
  30. package/dist/codebase-to-spec/progress.js +28 -0
  31. package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
  32. package/dist/codebase-to-spec/prompts/editor.js +57 -0
  33. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
  34. package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
  35. package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
  36. package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
  37. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
  38. package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
  39. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
  40. package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
  41. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
  42. package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
  43. package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
  44. package/dist/codebase-to-spec/prompts/specifier.js +100 -0
  45. package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
  46. package/dist/codebase-to-spec/prompts/style-check.js +78 -0
  47. package/dist/codebase-to-spec/schemas.d.ts +257 -0
  48. package/dist/codebase-to-spec/schemas.js +183 -0
  49. package/dist/codebase-to-spec/skill-install.d.ts +57 -0
  50. package/dist/codebase-to-spec/skill-install.js +79 -0
  51. package/dist/codebase-to-spec/slice.d.ts +49 -0
  52. package/dist/codebase-to-spec/slice.js +111 -0
  53. package/dist/codebase-to-spec/specifier.d.ts +60 -0
  54. package/dist/codebase-to-spec/specifier.js +79 -0
  55. package/dist/codebase-to-spec/style-check.d.ts +29 -0
  56. package/dist/codebase-to-spec/style-check.js +33 -0
  57. package/dist/codebase-to-spec/summary.d.ts +51 -0
  58. package/dist/codebase-to-spec/summary.js +183 -0
  59. package/dist/codebase-to-spec/validate.d.ts +46 -0
  60. package/dist/codebase-to-spec/validate.js +130 -0
  61. package/dist/commands/acceptance-test.d.ts +6 -0
  62. package/dist/commands/acceptance-test.js +212 -0
  63. package/dist/commands/ai-setup.d.ts +5 -0
  64. package/dist/commands/ai-setup.js +441 -0
  65. package/dist/commands/browsertest.d.ts +0 -1
  66. package/dist/commands/browsertest.js +51 -26
  67. package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
  68. package/dist/commands/codebase-to-spec/compose.js +57 -0
  69. package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
  70. package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
  71. package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
  72. package/dist/commands/codebase-to-spec/fan-out.js +77 -0
  73. package/dist/commands/codebase-to-spec/index.d.ts +9 -0
  74. package/dist/commands/codebase-to-spec/index.js +135 -0
  75. package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
  76. package/dist/commands/codebase-to-spec/pack.js +76 -0
  77. package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
  78. package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
  79. package/dist/commands/codebase-to-spec/present.d.ts +21 -0
  80. package/dist/commands/codebase-to-spec/present.js +92 -0
  81. package/dist/commands/codebase-to-spec/run.d.ts +20 -0
  82. package/dist/commands/codebase-to-spec/run.js +85 -0
  83. package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
  84. package/dist/commands/codebase-to-spec/skill-install.js +51 -0
  85. package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
  86. package/dist/commands/codebase-to-spec/specify-area.js +82 -0
  87. package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
  88. package/dist/commands/codebase-to-spec/style-check.js +42 -0
  89. package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
  90. package/dist/commands/codebase-to-spec/validate.js +38 -0
  91. package/dist/commands/create-requirement-document.d.ts +2 -0
  92. package/dist/commands/create-requirement-document.js +41 -0
  93. package/dist/commands/finalize.js +7 -7
  94. package/dist/commands/get.d.ts +2 -0
  95. package/dist/commands/get.js +55 -0
  96. package/dist/commands/init.js +132 -117
  97. package/dist/commands/link.js +27 -27
  98. package/dist/commands/list.d.ts +6 -0
  99. package/dist/commands/list.js +43 -0
  100. package/dist/commands/mcp-setup.js +159 -149
  101. package/dist/commands/mcp.js +1 -1
  102. package/dist/commands/prepare.js +4 -4
  103. package/dist/commands/pull.js +116 -121
  104. package/dist/commands/push.js +106 -112
  105. package/dist/commands/report.d.ts +6 -2
  106. package/dist/commands/report.js +177 -122
  107. package/dist/commands/requirements-for.d.ts +2 -0
  108. package/dist/commands/requirements-for.js +29 -0
  109. package/dist/commands/review-test.d.ts +2 -0
  110. package/dist/commands/review-test.js +75 -0
  111. package/dist/commands/search.d.ts +6 -0
  112. package/dist/commands/search.js +39 -0
  113. package/dist/commands/style-check.d.ts +7 -0
  114. package/dist/commands/style-check.js +75 -0
  115. package/dist/commands/test.js +53 -59
  116. package/dist/commands/tests-for.d.ts +2 -0
  117. package/dist/commands/tests-for.js +80 -0
  118. package/dist/commands/validate.d.ts +6 -0
  119. package/dist/commands/validate.js +72 -0
  120. package/dist/config.js +1 -1
  121. package/dist/convex.d.ts +34 -22
  122. package/dist/convex.js +38 -22
  123. package/dist/harness/cache.d.ts +1 -5
  124. package/dist/harness/cache.js +49 -59
  125. package/dist/harness/convexReporting.d.ts +1 -1
  126. package/dist/harness/convexReporting.js +9 -7
  127. package/dist/harness/coverageCache.js +3 -3
  128. package/dist/harness/finalize.js +59 -46
  129. package/dist/harness/index.d.ts +6 -7
  130. package/dist/harness/index.js +9 -10
  131. package/dist/harness/prepare.js +6 -5
  132. package/dist/harness/requirementsLoader.d.ts +2 -2
  133. package/dist/harness/requirementsLoader.js +13 -35
  134. package/dist/harness/tracking.js +18 -18
  135. package/dist/harness/types.d.ts +1 -1
  136. package/dist/mcp/convexClient.d.ts +0 -39
  137. package/dist/mcp/convexClient.js +2 -107
  138. package/dist/mcp/grep.d.ts +1 -1
  139. package/dist/mcp/grep.js +87 -42
  140. package/dist/mcp/handlers/authoring.d.ts +1 -1
  141. package/dist/mcp/handlers/authoring.js +30 -234
  142. package/dist/mcp/handlers/coverage.d.ts +1 -1
  143. package/dist/mcp/handlers/coverage.js +13 -15
  144. package/dist/mcp/handlers/debug.d.ts +2 -3
  145. package/dist/mcp/handlers/debug.js +10 -10
  146. package/dist/mcp/handlers/get.d.ts +1 -1
  147. package/dist/mcp/handlers/get.js +11 -10
  148. package/dist/mcp/handlers/index.d.ts +20 -20
  149. package/dist/mcp/handlers/index.js +10 -10
  150. package/dist/mcp/handlers/list.d.ts +4 -33
  151. package/dist/mcp/handlers/list.js +16 -38
  152. package/dist/mcp/handlers/push.d.ts +1 -1
  153. package/dist/mcp/handlers/push.js +28 -18
  154. package/dist/mcp/handlers/report.d.ts +16 -0
  155. package/dist/mcp/handlers/report.js +134 -0
  156. package/dist/mcp/handlers/review.d.ts +1 -1
  157. package/dist/mcp/handlers/review.js +40 -59
  158. package/dist/mcp/handlers/search.d.ts +1 -1
  159. package/dist/mcp/handlers/search.js +7 -9
  160. package/dist/mcp/handlers/test-mapping.d.ts +1 -1
  161. package/dist/mcp/handlers/test-mapping.js +14 -14
  162. package/dist/mcp/handlers/types.d.ts +3 -3
  163. package/dist/mcp/handlers/types.js +2 -2
  164. package/dist/mcp/index.d.ts +1 -1
  165. package/dist/mcp/index.js +156 -178
  166. package/dist/mcp/requirements.d.ts +2 -2
  167. package/dist/mcp/requirements.js +30 -30
  168. package/dist/mcp/testCodeExtractor.js +24 -26
  169. package/dist/mcp/types.d.ts +1 -1
  170. package/dist/push/core.d.ts +2 -2
  171. package/dist/push/core.js +20 -20
  172. package/dist/push/index.d.ts +1 -1
  173. package/dist/push/index.js +2 -2
  174. package/dist/requirements/cloud-ai.d.ts +57 -0
  175. package/dist/requirements/cloud-ai.js +104 -0
  176. package/dist/requirements/cloud-coverage.d.ts +41 -0
  177. package/dist/requirements/cloud-coverage.js +60 -0
  178. package/dist/requirements/coverage.d.ts +45 -0
  179. package/dist/requirements/coverage.js +114 -0
  180. package/dist/requirements/grep.d.ts +33 -0
  181. package/dist/requirements/grep.js +306 -0
  182. package/dist/requirements/index.d.ts +73 -0
  183. package/dist/requirements/index.js +174 -0
  184. package/dist/requirements/style-guide.d.ts +67 -0
  185. package/dist/requirements/style-guide.js +299 -0
  186. package/dist/requirements/testCodeExtractor.d.ts +22 -0
  187. package/dist/requirements/testCodeExtractor.js +150 -0
  188. package/dist/schema/browser.d.ts +8 -6
  189. package/dist/schema/browser.js +14 -14
  190. package/dist/schema/builder.d.ts +1 -1
  191. package/dist/schema/builder.js +13 -44
  192. package/dist/schema/conversions.d.ts +2 -2
  193. package/dist/schema/conversions.js +11 -11
  194. package/dist/schema/index.d.ts +9 -7
  195. package/dist/schema/index.js +15 -13
  196. package/dist/schema/parser-core.d.ts +1 -1
  197. package/dist/schema/parser-core.js +23 -22
  198. package/dist/schema/parser.d.ts +3 -3
  199. package/dist/schema/parser.js +27 -31
  200. package/dist/schema/resolver.d.ts +1 -1
  201. package/dist/schema/resolver.js +9 -9
  202. package/dist/schema/scenario.d.ts +91 -0
  203. package/dist/schema/scenario.js +82 -0
  204. package/dist/schema/schemas.d.ts +3 -3
  205. package/dist/schema/schemas.js +41 -28
  206. package/dist/schema/test-schema.js +27 -27
  207. package/dist/templates/context-file-section.md +3 -2
  208. package/dist/templates/example-requirements.js +1 -1
  209. package/dist/templates/example-requirements.ts +3 -1
  210. package/dist/templates/requirements-readme.js +1 -1
  211. package/dist/templates/requirements-readme.ts +1 -1
  212. package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
  213. package/dist/utils/brand.js +3 -3
  214. package/dist/utils/browser-launch.js +4 -4
  215. package/dist/utils/context-file.d.ts +1 -1
  216. package/dist/utils/context-file.js +26 -26
  217. package/dist/utils/env.js +7 -7
  218. package/dist/utils/gitignore.js +7 -7
  219. package/dist/utils/oauth-callback-server.d.ts +1 -1
  220. package/dist/utils/oauth-callback-server.js +27 -25
  221. package/dist/utils/oauth-flow.js +32 -29
  222. package/dist/utils/project-discovery.d.ts +3 -3
  223. package/dist/utils/project-discovery.js +18 -17
  224. package/dist/utils/project-name.js +8 -8
  225. package/dist/utils/project-selector.d.ts +1 -1
  226. package/dist/utils/project-selector.js +24 -21
  227. package/dist/utils/project-settings.d.ts +5 -4
  228. package/dist/utils/project-settings.js +28 -20
  229. package/dist/utils/templates.js +6 -6
  230. package/package.json +2 -1
@@ -1,65 +1,43 @@
1
- /**
2
- * list_all_requirements and list_untested_requirements handlers
3
- *
4
- * Lists requirements in a project with optional filtering for untested requirements.
5
- */
6
- import { textResponse } from './types.js';
7
- import { getReferencedRequirementIds } from '../grep.js';
8
- /**
9
- * Handler for list_all_requirements tool
10
- *
11
- * Requirements covered:
12
- * - MCP-LIST-1.0: Response includes count of total requirements and total nodes
13
- * - MCP-LIST-1.1: Each requirement displays its key, label, and child count
14
- * - MCP-LIST-1.2: Requirements are listed from all files discovered in the project
15
- */
16
- export async function handleListAllRequirements(args, context) {
17
- const { projectId } = args;
1
+ import { getReferencedRequirementIds } from "../../requirements/grep.js";
2
+ import { textResponse } from "./types.js";
3
+ export async function handleList(args, context) {
4
+ const { untested = false, projectId } = args;
18
5
  const project = await context.getProjectFromDiscovery(projectId);
19
6
  const requirements = await context.getRequirements(projectId);
20
7
  if (requirements.length === 0) {
21
8
  return textResponse(`No requirements found in project: ${project.path}\n\nMake sure .requirements/ directory exists with Markdown files (*.requirements.md).`);
22
9
  }
23
- // MCP-LIST-1.2: Group requirements from all discovered files by root ID
10
+ if (untested) {
11
+ return formatUntested(requirements, project.path);
12
+ }
13
+ return formatAll(requirements);
14
+ }
15
+ function formatAll(requirements) {
24
16
  const grouped = new Map();
25
17
  for (const req of requirements) {
26
18
  const existing = grouped.get(req.rootId) || [];
27
19
  existing.push(req);
28
20
  grouped.set(req.rootId, existing);
29
21
  }
30
- // MCP-LIST-1.1: Each requirement displays key, label (from content), and child count
31
22
  const formatted = Array.from(grouped.entries())
32
23
  .map(([rootId, reqs]) => {
33
24
  const root = reqs.find((r) => r.path.length === 0);
34
25
  const childCount = reqs.length - 1;
35
- return `- **${rootId}**: ${root?.content || '(no content)'} (${childCount} children)`;
26
+ return `- **${rootId}**: ${root?.content || "(no content)"} (${childCount} children)`;
36
27
  })
37
- .join('\n');
38
- // MCP-LIST-1.0: Response includes count of total requirements and total nodes
28
+ .join("\n");
39
29
  return textResponse(`Found ${grouped.size} requirement(s) with ${requirements.length} total nodes:\n\n${formatted}`);
40
30
  }
41
- /**
42
- * Handler for list_untested_requirements tool
43
- *
44
- * Requirements covered:
45
- * - MCP-LIST-2.0: Only requirements with no test references are included
46
- * - MCP-LIST-2.1: Response indicates count of requirements lacking coverage
47
- */
48
- export async function handleListUntestedRequirements(args, context) {
49
- const { projectId } = args;
50
- const project = await context.getProjectFromDiscovery(projectId);
51
- const requirements = await context.getRequirements(projectId);
52
- const referencedIds = await getReferencedRequirementIds(project.path);
53
- // MCP-LIST-2.0: Filter to root requirements without test references
31
+ async function formatUntested(requirements, projectPath) {
32
+ const referencedIds = await getReferencedRequirementIds(projectPath);
54
33
  const rootRequirements = requirements.filter((r) => r.path.length === 0);
55
34
  const untested = rootRequirements.filter((r) => !referencedIds.has(r.id));
56
35
  if (untested.length === 0) {
57
- return textResponse(`All ${rootRequirements.length} requirements have test references!`);
36
+ return textResponse(`All ${rootRequirements.length} requirement(s) have test references — coverage is complete.`);
58
37
  }
59
38
  const formatted = untested
60
39
  .map((r) => `- **${r.id}**: ${r.content}`)
61
- .join('\n');
62
- // MCP-LIST-2.1: Response indicates count of requirements lacking coverage
40
+ .join("\n");
63
41
  return textResponse(`Found ${untested.length} untested requirement(s) out of ${rootRequirements.length} total:\n\n${formatted}`);
64
42
  }
65
43
  //# sourceMappingURL=list.js.map
@@ -4,7 +4,7 @@
4
4
  * Provides requirements push functionality:
5
5
  * - push_requirements: Push local requirements to cloud
6
6
  */
7
- import type { HandlerContext, ToolResponse } from './types.js';
7
+ import type { HandlerContext, ToolResponse } from "./types.js";
8
8
  /**
9
9
  * Arguments for push_requirements tool
10
10
  */
@@ -4,12 +4,12 @@
4
4
  * Provides requirements push functionality:
5
5
  * - push_requirements: Push local requirements to cloud
6
6
  */
7
- import { textResponse, errorResponse } from './types.js';
8
- import { existsSync } from 'fs';
9
- import { resolve, basename } from 'path';
10
- import { CONVEX_URL } from '../convexClient.js';
11
- import { findRequirementsFiles } from '../requirements.js';
12
- import { parseFilesForPush, dryRunPush, executePush, } from '../../push/index.js';
7
+ import { existsSync } from "node:fs";
8
+ import { basename, resolve } from "node:path";
9
+ import { dryRunPush, executePush, parseFilesForPush, } from "../../push/index.js";
10
+ import { findRequirementsFiles } from "../../requirements/index.js";
11
+ import { CONVEX_URL } from "../convexClient.js";
12
+ import { errorResponse, textResponse } from "./types.js";
13
13
  /**
14
14
  * Handler for push_requirements tool
15
15
  *
@@ -43,7 +43,7 @@ export async function handlePushRequirements(args, context) {
43
43
  // Use same file discovery as CLI - searches entire workspace
44
44
  filesToPush = await findRequirementsFiles(project.path);
45
45
  if (filesToPush.length === 0) {
46
- return textResponse('No *.requirements.md files found. Nothing to push.');
46
+ return textResponse("No *.requirements.md files found. Nothing to push.");
47
47
  }
48
48
  }
49
49
  // Parse files
@@ -64,9 +64,14 @@ export async function handlePushRequirements(args, context) {
64
64
  // Only invalid files
65
65
  const invalidList = dryRunResult.invalid
66
66
  .map(({ file, result }) => `- ${basename(file.filePath)}: ${result.error}`)
67
- .join('\n');
67
+ .join("\n");
68
68
  return {
69
- content: [{ type: 'text', text: `✗ No valid documents to push.\n\n**Invalid files:**\n${invalidList}` }],
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: `✗ No valid documents to push.\n\n**Invalid files:**\n${invalidList}`,
73
+ },
74
+ ],
70
75
  isError: true,
71
76
  };
72
77
  }
@@ -78,20 +83,20 @@ export async function handlePushRequirements(args, context) {
78
83
  for (const { file, result } of dryRunResult.updates) {
79
84
  const fileName = basename(file.filePath);
80
85
  const hasConflict = dryRunResult.conflicts.some((c) => c.item.file === file);
81
- const conflictNote = hasConflict ? ' ⚠️ [cloud changed since pull]' : '';
82
- const warningNote = result.warning ? ` (${result.warning})` : '';
86
+ const conflictNote = hasConflict ? " ⚠️ [cloud changed since pull]" : "";
87
+ const warningNote = result.warning ? ` (${result.warning})` : "";
83
88
  summary += `- ~ ${fileName}${conflictNote}${warningNote}\n`;
84
89
  }
85
- summary += '\n';
90
+ summary += "\n";
86
91
  }
87
92
  if (dryRunResult.creates.length > 0) {
88
93
  summary += `## New Documents (${dryRunResult.creates.length})\n`;
89
94
  for (const { file, result } of dryRunResult.creates) {
90
95
  const fileName = basename(file.filePath);
91
- const warningNote = result.warning ? ` (${result.warning})` : '';
96
+ const warningNote = result.warning ? ` (${result.warning})` : "";
92
97
  summary += `- + ${fileName}${warningNote}\n`;
93
98
  }
94
- summary += '\n';
99
+ summary += "\n";
95
100
  }
96
101
  if (dryRunResult.notFound.length > 0) {
97
102
  summary += `## Not Found in Cloud (${dryRunResult.notFound.length}) - will create new\n`;
@@ -99,7 +104,7 @@ export async function handlePushRequirements(args, context) {
99
104
  const fileName = basename(file.filePath);
100
105
  summary += `- ! ${fileName} (ID: ${result.documentId})\n`;
101
106
  }
102
- summary += '\n';
107
+ summary += "\n";
103
108
  }
104
109
  if (dryRunResult.invalid.length > 0) {
105
110
  summary += `## Skipped - Invalid (${dryRunResult.invalid.length})\n`;
@@ -107,7 +112,7 @@ export async function handlePushRequirements(args, context) {
107
112
  const fileName = basename(file.filePath);
108
113
  summary += `- ✗ ${fileName}: ${result.error}\n`;
109
114
  }
110
- summary += '\n';
115
+ summary += "\n";
111
116
  }
112
117
  if (dryRunResult.conflicts.length > 0) {
113
118
  summary += `⚠️ **Warning:** ${dryRunResult.conflicts.length} file(s) have cloud changes since last pull. Pushing will overwrite those changes.\n\n`;
@@ -118,7 +123,7 @@ export async function handlePushRequirements(args, context) {
118
123
  // MCP-PUSH-1.1: When confirmed, execute push
119
124
  try {
120
125
  const result = await executePush(dryRunResult, credentials);
121
- let output = '✓ Push complete!\n\n';
126
+ let output = "✓ Push complete!\n\n";
122
127
  if (result.created > 0) {
123
128
  output += `**Created:** ${result.created} document(s)\n`;
124
129
  }
@@ -135,7 +140,12 @@ export async function handlePushRequirements(args, context) {
135
140
  }
136
141
  catch (error) {
137
142
  return {
138
- content: [{ type: 'text', text: `✗ Push failed:\n\n${error instanceof Error ? error.message : String(error)}` }],
143
+ content: [
144
+ {
145
+ type: "text",
146
+ text: `✗ Push failed:\n\n${error instanceof Error ? error.message : String(error)}`,
147
+ },
148
+ ],
139
149
  isError: true,
140
150
  };
141
151
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Unified coverage handler for the MCP `report_coverage` tool. Replaces
3
+ * `get_requirement_coverage` and `get_project_coverage_summary` with a
4
+ * single tool that takes a `source: "local" | "cloud"` argument and
5
+ * mirrors the CLI `dotreq report` semantics.
6
+ */
7
+ import type { HandlerContext, ToolResponse } from "./types.js";
8
+ export interface ReportArgs {
9
+ source?: "local" | "cloud";
10
+ requirementKey?: string;
11
+ branch?: string;
12
+ sinceTimestamp?: number;
13
+ projectId?: string;
14
+ }
15
+ export declare function handleReport(args: ReportArgs, context: HandlerContext): Promise<ToolResponse>;
16
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Unified coverage handler for the MCP `report_coverage` tool. Replaces
3
+ * `get_requirement_coverage` and `get_project_coverage_summary` with a
4
+ * single tool that takes a `source: "local" | "cloud"` argument and
5
+ * mirrors the CLI `dotreq report` semantics.
6
+ */
7
+ import { getProjectCoverage as queryProjectCoverage, getRequirementCoverage as queryRequirementCoverage, } from "../../requirements/cloud-coverage.js";
8
+ import { buildLocalReport, LocalReportCacheMissingError, } from "../../requirements/coverage.js";
9
+ import { CONVEX_URL } from "../convexClient.js";
10
+ import { errorResponse, textResponse } from "./types.js";
11
+ export async function handleReport(args, context) {
12
+ const source = args.source ?? "local";
13
+ if (source === "local") {
14
+ if (args.branch || args.sinceTimestamp !== undefined) {
15
+ return errorResponse("branch and sinceTimestamp require source=cloud. Local reports cover only the most recent test run on this machine.");
16
+ }
17
+ try {
18
+ const report = buildLocalReport({
19
+ projectRoot: context.workspaceRoot,
20
+ requirementFilter: args.requirementKey,
21
+ });
22
+ return textResponse(formatLocalReport(report));
23
+ }
24
+ catch (error) {
25
+ if (error instanceof LocalReportCacheMissingError) {
26
+ return errorResponse(error.message);
27
+ }
28
+ throw error;
29
+ }
30
+ }
31
+ // source === "cloud"
32
+ let project;
33
+ try {
34
+ project = await context.getProjectFromDiscovery(args.projectId);
35
+ }
36
+ catch (error) {
37
+ return errorResponse(`Cloud report requires cloud credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
38
+ }
39
+ if (args.requirementKey) {
40
+ const record = await queryRequirementCoverage(args.requirementKey, project.projectId, project.projectSecret, CONVEX_URL);
41
+ return textResponse(formatCloudRequirement(record));
42
+ }
43
+ const record = await queryProjectCoverage(project.projectId, project.projectSecret, CONVEX_URL, {
44
+ branch: args.branch,
45
+ sinceTimestamp: args.sinceTimestamp,
46
+ });
47
+ return textResponse(formatCloudProject(record, {
48
+ branch: args.branch,
49
+ sinceTimestamp: args.sinceTimestamp,
50
+ }));
51
+ }
52
+ function formatLocalReport(report) {
53
+ const { total, tested, untested, coveragePercent } = report.summary;
54
+ let out = `# Coverage Report (local)\n\n`;
55
+ if (report.warnings.length > 0) {
56
+ for (const w of report.warnings) {
57
+ out += `> ⚠️ ${w}\n`;
58
+ }
59
+ out += "\n";
60
+ }
61
+ out += `**Total:** ${total} | **Tested:** ${tested} | **Untested:** ${untested} | **Coverage:** ${coveragePercent}%\n\n`;
62
+ const testedEntries = report.entries.filter((c) => c.tested);
63
+ if (testedEntries.length > 0) {
64
+ out += `## Tested (${testedEntries.length})\n\n`;
65
+ for (const c of testedEntries) {
66
+ out += `- **${c.key}**: ${c.content}\n`;
67
+ for (const loc of c.locations) {
68
+ out += ` - \`${loc}\`\n`;
69
+ }
70
+ }
71
+ out += "\n";
72
+ }
73
+ const untestedEntries = report.entries.filter((c) => !c.tested);
74
+ if (untestedEntries.length > 0) {
75
+ out += `## Untested (${untestedEntries.length})\n\n`;
76
+ for (const c of untestedEntries) {
77
+ out += `- **${c.key}**: ${c.content}\n`;
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ function formatCloudRequirement(record) {
83
+ if (!record.lastTestedAt) {
84
+ return `**${record.requirementKey}** has no cloud coverage records.`;
85
+ }
86
+ const lastTested = new Date(record.lastTestedAt).toISOString();
87
+ let out = `# Coverage for ${record.requirementKey} (cloud)\n\n`;
88
+ out += `**Last tested:** ${lastTested}\n`;
89
+ if (record.branch)
90
+ out += `**Branch:** ${record.branch}\n`;
91
+ if (record.testFile) {
92
+ out += `**Test location:** \`${record.testFile}${record.testLine ? `:${record.testLine}` : ""}\`\n`;
93
+ }
94
+ const otherBranches = record.allBranches.filter((b) => b.branch !== record.branch);
95
+ if (otherBranches.length > 0) {
96
+ out += `\n**Also tested on:**\n`;
97
+ for (const b of otherBranches) {
98
+ out += `- ${b.branch}: ${new Date(b.lastTestedAt).toISOString()}\n`;
99
+ }
100
+ }
101
+ return out;
102
+ }
103
+ function formatCloudProject(record, filters) {
104
+ const tested = record.tested.length;
105
+ const untested = record.untested.length;
106
+ const total = tested + untested;
107
+ const coveragePercent = total > 0 ? ((tested / total) * 100).toFixed(1) : "0.0";
108
+ let filterNote = "";
109
+ if (filters.branch)
110
+ filterNote += `\n**Branch:** ${filters.branch}`;
111
+ if (filters.sinceTimestamp) {
112
+ filterNote += `\n**Since:** ${new Date(filters.sinceTimestamp).toISOString()}`;
113
+ }
114
+ let out = `# Project Coverage Report (cloud)\n\n`;
115
+ out += `**Total Requirements:** ${total}\n**Coverage:** ${coveragePercent}%${filterNote}\n`;
116
+ if (record.tested.length > 0) {
117
+ out += `\n## Tested Requirements (${record.tested.length})\n\n`;
118
+ for (const t of record.tested) {
119
+ const date = new Date(t.lastTestedAt).toISOString();
120
+ const location = t.testFile
121
+ ? ` (\`${t.testFile}${t.testLine ? `:${t.testLine}` : ""}\`)`
122
+ : "";
123
+ out += `- **${t.requirementKey}** [${t.branch}]: ${date}${location}\n`;
124
+ }
125
+ }
126
+ if (record.untested.length > 0) {
127
+ out += `\n## Untested Requirements (${record.untested.length})\n\n`;
128
+ for (const key of record.untested) {
129
+ out += `- ${key}\n`;
130
+ }
131
+ }
132
+ return out;
133
+ }
134
+ //# sourceMappingURL=report.js.map
@@ -5,7 +5,7 @@
5
5
  * - style_check: Check requirements or test files for style issues
6
6
  * - review_test: Comprehensively review tests for semantic correctness
7
7
  */
8
- import type { HandlerContext, ToolResponse } from './types.js';
8
+ import type { HandlerContext, ToolResponse } from "./types.js";
9
9
  /**
10
10
  * Arguments for style_check tool
11
11
  */
@@ -5,11 +5,12 @@
5
5
  * - style_check: Check requirements or test files for style issues
6
6
  * - review_test: Comprehensively review tests for semantic correctness
7
7
  */
8
- import { textResponse, errorResponse } from './types.js';
9
- import { existsSync, readFileSync } from 'fs';
10
- import { resolve, dirname } from 'path';
11
- import { filterRequirementsByKeys, getRequirementTree, formatRequirementTree, } from '../requirements.js';
12
- import { discoverProjects, } from '../../utils/project-discovery.js';
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { dirname, resolve } from "node:path";
10
+ import { fetchReviewTestFeedback, fetchStyleCheckFeedback, } from "../../requirements/cloud-ai.js";
11
+ import { filterRequirementsByKeys, formatRequirementTree, getRequirementTree, } from "../../requirements/index.js";
12
+ import { discoverProjects, } from "../../utils/project-discovery.js";
13
+ import { errorResponse, textResponse } from "./types.js";
13
14
  const STYLE_CHECK_GUIDANCE = `
14
15
 
15
16
  ---
@@ -32,24 +33,24 @@ const STYLE_CHECK_GUIDANCE = `
32
33
  */
33
34
  export async function handleStyleCheck(args, context, options) {
34
35
  const { filePath, requirementKeys, model } = args;
35
- const { useEnvAuth = false, apiBaseUrl = 'https://app.dotrequirements.io' } = options ?? {};
36
+ const { useEnvAuth = false, apiBaseUrl = "https://app.dotrequirements.io" } = options ?? {};
36
37
  const fullPath = resolve(context.workspaceRoot, filePath);
37
38
  if (!existsSync(fullPath)) {
38
39
  return errorResponse(`File not found: ${filePath}`);
39
40
  }
40
41
  // Read file contents
41
- const fileContents = readFileSync(fullPath, 'utf-8');
42
+ const fileContents = readFileSync(fullPath, "utf-8");
42
43
  // Detect file type
43
44
  // MCP-REVIEW-1.0 & MCP-REVIEW-1.1: Different feedback for different file types
44
- const isRequirementsFile = filePath.endsWith('.requirements.md');
45
+ const isRequirementsFile = filePath.endsWith(".requirements.md");
45
46
  const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(filePath);
46
47
  if (!isRequirementsFile && !isTestFile) {
47
48
  return errorResponse(`Unsupported file type. File must be:\n- Requirements file: *.requirements.md\n- Test file: *.test.{js,jsx,ts,tsx} or *.spec.{js,jsx,ts,tsx}`);
48
49
  }
49
- const fileType = isRequirementsFile ? 'requirements' : 'test';
50
+ const fileType = isRequirementsFile ? "requirements" : "test";
50
51
  // If requirementKeys provided, filter the file to only those requirements
51
52
  let fileContentsToCheck = fileContents;
52
- let scopeNote = '';
53
+ let scopeNote = "";
53
54
  if (requirementKeys && requirementKeys.length > 0) {
54
55
  if (!isRequirementsFile) {
55
56
  return errorResponse(`requirementKeys can only be used with requirements files (*.requirements.md), not test files.`);
@@ -57,11 +58,11 @@ export async function handleStyleCheck(args, context, options) {
57
58
  try {
58
59
  const { filteredContent, foundKeys, missingKeys } = filterRequirementsByKeys(fileContents, requirementKeys);
59
60
  if (foundKeys.length === 0) {
60
- return errorResponse(`None of the specified requirement keys were found in ${filePath}: ${requirementKeys.join(', ')}`);
61
+ return errorResponse(`None of the specified requirement keys were found in ${filePath}: ${requirementKeys.join(", ")}`);
61
62
  }
62
63
  fileContentsToCheck = filteredContent;
63
64
  if (missingKeys.length > 0) {
64
- scopeNote = `\n\n> **Note:** Some specified keys were not found in the file: ${missingKeys.join(', ')}`;
65
+ scopeNote = `\n\n> **Note:** Some specified keys were not found in the file: ${missingKeys.join(", ")}`;
65
66
  }
66
67
  }
67
68
  catch (error) {
@@ -79,50 +80,40 @@ export async function handleStyleCheck(args, context, options) {
79
80
  else {
80
81
  // Try to find a project starting from the file's directory
81
82
  const result = await discoverProjects(fileDir);
82
- if (result.type === 'none') {
83
+ if (result.type === "none") {
83
84
  // If no project found from file dir, try from context
84
85
  project = await context.getProjectFromDiscovery();
85
86
  }
86
- else if (result.type === 'single') {
87
+ else if (result.type === "single") {
87
88
  project = result.project;
88
89
  }
89
90
  else {
90
91
  // Multiple projects - can't auto-detect which one to use
91
- throw new Error('Multiple projects found - cannot auto-detect for this file');
92
+ throw new Error("Multiple projects found - cannot auto-detect for this file");
92
93
  }
93
94
  }
94
95
  }
95
96
  catch (error) {
96
97
  return errorResponse(`Style check requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
97
98
  }
98
- // Call API endpoint for style checking
99
+ // Call hosted endpoint via the shared library helper
99
100
  try {
100
- const response = await fetch(`${apiBaseUrl}/api/style-check`, {
101
- method: 'POST',
102
- headers: {
103
- 'Content-Type': 'application/json',
104
- },
105
- body: JSON.stringify({
106
- projectId: project.projectId,
107
- projectSecret: project.projectSecret,
108
- fileContents: fileContentsToCheck,
109
- fileType,
110
- model,
111
- }),
101
+ const feedback = await fetchStyleCheckFeedback({
102
+ apiBaseUrl,
103
+ projectId: project.projectId,
104
+ projectSecret: project.projectSecret,
105
+ fileContents: fileContentsToCheck,
106
+ fileType,
107
+ model,
112
108
  });
113
- if (!response.ok) {
114
- const errorData = await response.json();
115
- return errorResponse(`Style check failed: ${errorData.error || response.statusText}`);
116
- }
117
- const data = await response.json();
118
109
  const scopeLabel = requirementKeys && requirementKeys.length > 0
119
- ? ` (${requirementKeys.join(', ')})`
120
- : '';
110
+ ? ` (${requirementKeys.join(", ")})`
111
+ : "";
121
112
  // MCP-REVIEW-1.2: Feedback includes severity categories (in the API response)
122
- return textResponse(`# Style Check Results for \`${filePath}\`${scopeLabel}\n\n${data.feedback}${scopeNote}${STYLE_CHECK_GUIDANCE}`);
113
+ return textResponse(`# Style Check Results for \`${filePath}\`${scopeLabel}\n\n${feedback}${scopeNote}${STYLE_CHECK_GUIDANCE}`);
123
114
  }
124
115
  catch (error) {
125
- return errorResponse(`Style check error: ${error instanceof Error ? error.message : String(error)}`);
116
+ return errorResponse(`Style check failed: ${error instanceof Error ? error.message : String(error)}`);
126
117
  }
127
118
  }
128
119
  /**
@@ -138,7 +129,7 @@ export async function handleStyleCheck(args, context, options) {
138
129
  */
139
130
  export async function handleReviewTest(args, context, options) {
140
131
  const { testFilePath, projectId } = args;
141
- const { apiBaseUrl = 'https://app.dotrequirements.io' } = options ?? {};
132
+ const { apiBaseUrl = "https://app.dotrequirements.io" } = options ?? {};
142
133
  const fullPath = resolve(context.workspaceRoot, testFilePath);
143
134
  if (!existsSync(fullPath)) {
144
135
  return errorResponse(`File not found: ${testFilePath}`);
@@ -149,7 +140,7 @@ export async function handleReviewTest(args, context, options) {
149
140
  return errorResponse(`File must be a test file: *.test.{js,jsx,ts,tsx} or *.spec.{js,jsx,ts,tsx}`);
150
141
  }
151
142
  // Read test file contents
152
- const testFileContents = readFileSync(fullPath, 'utf-8');
143
+ const testFileContents = readFileSync(fullPath, "utf-8");
153
144
  // Extract requirement IDs from the test file using regex
154
145
  const requirementIdMatches = testFileContents.matchAll(/requirement\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
155
146
  const requirementIds = new Set();
@@ -170,7 +161,7 @@ export async function handleReviewTest(args, context, options) {
170
161
  // e.g., NAV-3.0, NAV-3.1, NAV-3.2 all belong to root NAV-3
171
162
  const rootToTestedIds = new Map();
172
163
  for (const reqId of requirementIds) {
173
- const req = allRequirements.find(r => r.id === reqId || r.rootId === reqId);
164
+ const req = allRequirements.find((r) => r.id === reqId || r.rootId === reqId);
174
165
  if (req) {
175
166
  if (!rootToTestedIds.has(req.rootId)) {
176
167
  rootToTestedIds.set(req.rootId, new Set());
@@ -190,30 +181,20 @@ export async function handleReviewTest(args, context, options) {
190
181
  testedIds: Array.from(testedIds).sort(),
191
182
  });
192
183
  }
193
- // Call API endpoint for test review
184
+ // Call hosted endpoint via the shared library helper
194
185
  // MCP-REVIEW-2.3 & 2.4: API identifies coverage gaps and non-existent references
195
186
  try {
196
- const response = await fetch(`${apiBaseUrl}/api/review-test`, {
197
- method: 'POST',
198
- headers: {
199
- 'Content-Type': 'application/json',
200
- },
201
- body: JSON.stringify({
202
- projectId: project.projectId,
203
- projectSecret: project.projectSecret,
204
- testFileContents,
205
- requirements,
206
- }),
187
+ const feedback = await fetchReviewTestFeedback({
188
+ apiBaseUrl,
189
+ projectId: project.projectId,
190
+ projectSecret: project.projectSecret,
191
+ testFileContents,
192
+ requirements,
207
193
  });
208
- if (!response.ok) {
209
- const errorData = await response.json();
210
- return errorResponse(`Test review failed: ${errorData.error || response.statusText}`);
211
- }
212
- const data = await response.json();
213
- return textResponse(`# Test Review Results for \`${testFilePath}\`\n\n${data.feedback}`);
194
+ return textResponse(`# Test Review Results for \`${testFilePath}\`\n\n${feedback}`);
214
195
  }
215
196
  catch (error) {
216
- return errorResponse(`Test review error: ${error instanceof Error ? error.message : String(error)}`);
197
+ return errorResponse(`Test review failed: ${error instanceof Error ? error.message : String(error)}`);
217
198
  }
218
199
  }
219
200
  //# sourceMappingURL=review.js.map
@@ -4,7 +4,7 @@
4
4
  * Searches requirements by text or regex query across IDs, content, and labels.
5
5
  * Returns matching requirements with their full tree context.
6
6
  */
7
- import type { HandlerContext, ToolResponse } from './types.js';
7
+ import type { HandlerContext, ToolResponse } from "./types.js";
8
8
  /**
9
9
  * Arguments for search_requirements tool
10
10
  */
@@ -4,8 +4,8 @@
4
4
  * Searches requirements by text or regex query across IDs, content, and labels.
5
5
  * Returns matching requirements with their full tree context.
6
6
  */
7
- import { textResponse, errorResponse } from './types.js';
8
- import { searchRequirements, getRequirementTree, formatRequirementTree, } from '../requirements.js';
7
+ import { formatRequirementTree, getRequirementTree, searchRequirements, } from "../../requirements/index.js";
8
+ import { errorResponse, textResponse } from "./types.js";
9
9
  /**
10
10
  * Handler for search_requirements tool
11
11
  *
@@ -27,10 +27,8 @@ export async function handleSearchRequirements(args, context) {
27
27
  // MCP-SEARCH-1.3: Interpret query as case-insensitive regex
28
28
  // MCP-SEARCH-1.4: Return error for invalid regex
29
29
  try {
30
- const regex = new RegExp(query, 'i');
31
- results = requirements.filter((r) => regex.test(r.id) ||
32
- regex.test(r.content) ||
33
- regex.test(r.label));
30
+ const regex = new RegExp(query, "i");
31
+ results = requirements.filter((r) => regex.test(r.id) || regex.test(r.content) || regex.test(r.label));
34
32
  }
35
33
  catch (error) {
36
34
  return errorResponse(`Invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
@@ -45,7 +43,7 @@ export async function handleSearchRequirements(args, context) {
45
43
  const rootResults = requirements.filter((r) => r.path.length === 0 && matchedRootIds.has(r.rootId));
46
44
  // MCP-SEARCH-1.5: No matches returns informative message
47
45
  if (rootResults.length === 0) {
48
- return textResponse(`No requirements found matching "${query}"${useRegex ? ' (regex)' : ''}`);
46
+ return textResponse(`No requirements found matching "${query}"${useRegex ? " (regex)" : ""}`);
49
47
  }
50
48
  // MCP-SEARCH-1.7: Each result includes full tree as code block
51
49
  const formatted = rootResults
@@ -54,7 +52,7 @@ export async function handleSearchRequirements(args, context) {
54
52
  const treeFormatted = formatRequirementTree(tree);
55
53
  return `**${r.id}** (${r.label})\n${r.content}\n_Source: ${r.documentTitle}_\n\n\`\`\`\n${treeFormatted}\n\`\`\``;
56
54
  })
57
- .join('\n\n---\n\n');
58
- return textResponse(`Found ${rootResults.length} requirement(s) matching "${query}"${useRegex ? ' (regex)' : ''}:\n\n${formatted}`);
55
+ .join("\n\n---\n\n");
56
+ return textResponse(`Found ${rootResults.length} requirement(s) matching "${query}"${useRegex ? " (regex)" : ""}:\n\n${formatted}`);
59
57
  }
60
58
  //# sourceMappingURL=search.js.map
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Maps between test files and requirements in both directions.
5
5
  */
6
- import type { HandlerContext, ToolResponse } from './types.js';
6
+ import type { HandlerContext, ToolResponse } from "./types.js";
7
7
  /**
8
8
  * Arguments for get_requirements_by_test tool
9
9
  */