@popoverai/dotrequirements 0.26.2 → 0.27.1

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 (56) hide show
  1. package/README.md +13 -69
  2. package/dist/cli.js +19 -7
  3. package/dist/commands/ai-setup.d.ts +8 -2
  4. package/dist/commands/ai-setup.js +153 -347
  5. package/dist/commands/create-requirement-document.js +2 -1
  6. package/dist/commands/init.js +1 -1
  7. package/dist/commands/mcp.d.ts +8 -2
  8. package/dist/commands/mcp.js +17 -6
  9. package/dist/commands/review-test.d.ts +5 -1
  10. package/dist/commands/review-test.js +101 -7
  11. package/dist/commands/style-check.d.ts +1 -0
  12. package/dist/commands/style-check.js +138 -13
  13. package/dist/convex.d.ts +1 -3
  14. package/dist/convex.js +3 -3
  15. package/dist/requirements/cloud-ai.d.ts +21 -8
  16. package/dist/requirements/cloud-ai.js +10 -8
  17. package/dist/requirements/style-guide-file.d.ts +21 -0
  18. package/dist/requirements/style-guide-file.js +30 -0
  19. package/dist/requirements/style-guide.d.ts +20 -22
  20. package/dist/requirements/style-guide.js +57 -35
  21. package/dist/schema/browser.d.ts +1 -1
  22. package/dist/schema/browser.js +4 -1
  23. package/dist/schema/parser-core.d.ts +28 -0
  24. package/dist/schema/parser-core.js +51 -12
  25. package/dist/templates/context-file-section.md +25 -22
  26. package/dist/utils/context-file.d.ts +7 -3
  27. package/dist/utils/context-file.js +10 -7
  28. package/dist/utils/project-settings.d.ts +1 -0
  29. package/dist/utils/project-settings.js +22 -0
  30. package/package.json +3 -4
  31. package/dist/mcp/convexClient.d.ts +0 -19
  32. package/dist/mcp/convexClient.js +0 -24
  33. package/dist/mcp/handlers/authoring.d.ts +0 -41
  34. package/dist/mcp/handlers/authoring.js +0 -113
  35. package/dist/mcp/handlers/debug.d.ts +0 -16
  36. package/dist/mcp/handlers/debug.js +0 -37
  37. package/dist/mcp/handlers/get.d.ts +0 -24
  38. package/dist/mcp/handlers/get.js +0 -69
  39. package/dist/mcp/handlers/index.d.ts +0 -28
  40. package/dist/mcp/handlers/index.js +0 -19
  41. package/dist/mcp/handlers/list.d.ts +0 -7
  42. package/dist/mcp/handlers/list.js +0 -43
  43. package/dist/mcp/handlers/push.d.ts +0 -26
  44. package/dist/mcp/handlers/push.js +0 -232
  45. package/dist/mcp/handlers/report.d.ts +0 -16
  46. package/dist/mcp/handlers/report.js +0 -134
  47. package/dist/mcp/handlers/review.d.ts +0 -52
  48. package/dist/mcp/handlers/review.js +0 -243
  49. package/dist/mcp/handlers/search.d.ts +0 -30
  50. package/dist/mcp/handlers/search.js +0 -58
  51. package/dist/mcp/handlers/test-mapping.d.ts +0 -39
  52. package/dist/mcp/handlers/test-mapping.js +0 -168
  53. package/dist/mcp/handlers/types.d.ts +0 -89
  54. package/dist/mcp/handlers/types.js +0 -52
  55. package/dist/mcp/index.d.ts +0 -45
  56. package/dist/mcp/index.js +0 -638
@@ -1,113 +0,0 @@
1
- /**
2
- * Authoring handlers for MCP tools
3
- *
4
- * Provides document creation and validation:
5
- * - create_requirement_document: Generate requirements template with format guidance
6
- * - validate_requirements: Validate requirements file syntax offline
7
- */
8
- import { existsSync } from "node:fs";
9
- import { getProjectContext } from "../../requirements/cloud-ai.js";
10
- import { generateStyleGuide, readLocalStyleGuide, } from "../../requirements/style-guide.js";
11
- import { parseRequirementsFromFile, validateForPush, } from "../../schema/index.js";
12
- import { CONVEX_URL } from "../convexClient.js";
13
- import { errorResponse, resolveProjectFilePath, textResponse, } from "./types.js";
14
- /**
15
- * Handler for create_requirement_document tool
16
- *
17
- * Requirements covered:
18
- * - MCP-AUTHOR-1.0: The template includes format guidance with code block examples
19
- * - MCP-AUTHOR-1.1: The template includes style guidance (concrete examples, concise prose, testable conditions)
20
- * - MCP-AUTHOR-1.2: When the project has requirementsStyleContext configured, it is included in the template
21
- * - MCP-AUTHOR-1.3: When cloud credentials are unavailable, the template works without the custom context
22
- */
23
- export async function handleCreateRequirementDocument(args, context) {
24
- const { filePath = ".requirements/example.requirements.md" } = args;
25
- // MCP-AUTHOR-1.3: Try to get requirements, but don't fail if no project is configured
26
- let requirements = [];
27
- try {
28
- requirements = await context.getRequirements();
29
- }
30
- catch {
31
- // No project configured — discovered patterns will be empty
32
- }
33
- // MCP-AUTHOR-1.2: Try to fetch user-provided style context from cloud
34
- let customStyleGuidance = null;
35
- try {
36
- const project = await context.getProjectFromDiscovery();
37
- const contextData = await getProjectContext(project.projectId, project.projectSecret, CONVEX_URL);
38
- customStyleGuidance = contextData?.requirementsStyleContext ?? null;
39
- }
40
- catch {
41
- // MCP-AUTHOR-1.3: No credentials or cloud unavailable - continue without user context
42
- }
43
- const localStyleGuide = readLocalStyleGuide(context.workspaceRoot);
44
- return textResponse(generateStyleGuide({
45
- requirements,
46
- customStyleGuidance,
47
- filePath,
48
- localStyleGuide,
49
- }));
50
- }
51
- /**
52
- * Handler for validate_requirements tool
53
- *
54
- * Requirements covered:
55
- * - MCP-AUTHOR-2.1: When the file has valid syntax, the response confirms validation passed
56
- * - MCP-AUTHOR-2.2: When the file has syntax errors, the response lists each error with location
57
- * - MCP-AUTHOR-2.3: Validation does not require network access or cloud credentials
58
- * - MCP-AUTHOR-2.4: When the file does not exist, an error is returned
59
- */
60
- export async function handleValidateRequirements(args, context) {
61
- const { filePath } = args;
62
- // #49: Resolve against the discovered project.path (matching push) with a
63
- // workspaceRoot fallback. MCP-AUTHOR-2.3: validation stays offline — if
64
- // discovery has no credentials it simply throws and we fall back to
65
- // workspaceRoot resolution.
66
- let projectPath;
67
- try {
68
- projectPath = (await context.getProjectFromDiscovery()).path;
69
- }
70
- catch {
71
- projectPath = undefined;
72
- }
73
- const fullPath = resolveProjectFilePath(filePath, projectPath, context.workspaceRoot);
74
- // MCP-AUTHOR-2.4: Check if file exists
75
- if (!existsSync(fullPath)) {
76
- return errorResponse(`File not found: ${filePath}`);
77
- }
78
- try {
79
- // MCP-AUTHOR-2.1 & MCP-AUTHOR-2.2: Parse and validate
80
- const parsed = parseRequirementsFromFile(fullPath);
81
- const reqCount = Object.keys(parsed.requirements).length;
82
- // Check push readiness
83
- const pushValidation = validateForPush(parsed.metadata);
84
- let headline;
85
- let pushDetails = "";
86
- if (!pushValidation.valid) {
87
- headline = `❌ \`${filePath}\` - Not push-ready\n- ${pushValidation.reason}`;
88
- }
89
- else {
90
- const actionLabel = pushValidation.action === "create"
91
- ? "Will create new document"
92
- : "Will update existing document";
93
- headline = `✓ \`${filePath}\` - ${actionLabel}`;
94
- if (pushValidation.warning) {
95
- pushDetails = `\n- ⚠️ ${pushValidation.warning}`;
96
- }
97
- }
98
- return textResponse(`${headline}${pushDetails}\n\n**Metadata:**\n- Version: ${parsed.metadata.version ?? "(none)"}\n- Document: ${parsed.metadata.document?.title || "(none)"}\n- Document ID: ${parsed.metadata.document?.id || "(none)"}\n\n**Requirements:** ${reqCount} root requirement(s) found`);
99
- }
100
- catch (error) {
101
- // MCP-AUTHOR-2.2: Syntax errors are returned with details
102
- return {
103
- content: [
104
- {
105
- type: "text",
106
- text: `✗ Validation failed for \`${filePath}\`:\n\n${error instanceof Error ? error.message : String(error)}`,
107
- },
108
- ],
109
- isError: true,
110
- };
111
- }
112
- }
113
- //# sourceMappingURL=authoring.js.map
@@ -1,16 +0,0 @@
1
- /**
2
- * debug_mcp_environment handler
3
- *
4
- * Returns diagnostic information about the MCP server environment.
5
- * Useful for debugging configuration issues.
6
- */
7
- import type { HandlerContext, ToolResponse } from "./types.js";
8
- /**
9
- * Arguments for debug_mcp_environment tool
10
- */
11
- export type DebugMcpEnvironmentArgs = Record<string, never>;
12
- /**
13
- * Handler for debug_mcp_environment tool
14
- */
15
- export declare function handleDebugMcpEnvironment(_args: DebugMcpEnvironmentArgs, context: HandlerContext): Promise<ToolResponse>;
16
- //# sourceMappingURL=debug.d.ts.map
@@ -1,37 +0,0 @@
1
- /**
2
- * debug_mcp_environment handler
3
- *
4
- * Returns diagnostic information about the MCP server environment.
5
- * Useful for debugging configuration issues.
6
- */
7
- import { textResponse } from "./types.js";
8
- /**
9
- * Handler for debug_mcp_environment tool
10
- */
11
- export async function handleDebugMcpEnvironment(_args, context) {
12
- // Check for Antigravity-specific env vars
13
- const antigravityVars = Object.entries(context.env)
14
- .filter(([key]) => key.startsWith("GEMINI_") || key.startsWith("ANTIGRAVITY_"))
15
- .map(([key, value]) => ` ${key}: ${value}`)
16
- .join("\n");
17
- // Show discovered projects from env vars
18
- const projectList = Array.from(context.projectPaths.entries())
19
- .map(([id, path]) => ` ${id}: ${path}`)
20
- .join("\n");
21
- const text = `# MCP Server Environment Debug Info
22
-
23
- **process.cwd():** ${process.cwd()}
24
- **WORKSPACE_ROOT:** ${context.workspaceRoot}
25
- **REQUIREMENTS_DIR env:** ${context.env.REQUIREMENTS_DIR || "(not set)"}
26
- **HOME:** ${context.env.HOME || "(not set)"}
27
- **PWD:** ${context.env.PWD || "(not set)"}
28
- **OLDPWD:** ${context.env.OLDPWD || "(not set)"}
29
-
30
- **Antigravity/Gemini env vars:**
31
- ${antigravityVars || "(none found)"}
32
-
33
- **Projects from PROJ_* env vars:**
34
- ${projectList || "(none found)"}`;
35
- return textResponse(text);
36
- }
37
- //# sourceMappingURL=debug.js.map
@@ -1,24 +0,0 @@
1
- /**
2
- * get_requirement handler
3
- *
4
- * Retrieves a specific requirement by ID with its full tree and test coverage.
5
- */
6
- import type { HandlerContext, ToolResponse } from "./types.js";
7
- /**
8
- * Arguments for get_requirement tool
9
- */
10
- export interface GetRequirementArgs {
11
- id: string;
12
- projectId?: string;
13
- }
14
- /**
15
- * Handler for get_requirement tool
16
- *
17
- * Requirements covered:
18
- * - MCP-GET-1.0: When the ID matches a root requirement, the full tree is returned
19
- * - MCP-GET-1.1: When the ID matches a child requirement, that subtree is returned
20
- * - MCP-GET-1.2: When the ID does not exist, an error message is returned
21
- * - MCP-GET-1.3: When a requirement is retrieved, test references are shown with file path and line number
22
- */
23
- export declare function handleGetRequirement(args: GetRequirementArgs, context: HandlerContext): Promise<ToolResponse>;
24
- //# sourceMappingURL=get.d.ts.map
@@ -1,69 +0,0 @@
1
- /**
2
- * get_requirement handler
3
- *
4
- * Retrieves a specific requirement by ID with its full tree and test coverage.
5
- */
6
- import { glob } from "glob";
7
- import { formatRequirementTree } from "../../requirements/index.js";
8
- import { findFilesWithRequirement, findTestCodeForRequirement, } from "../../requirements/testCodeExtractor.js";
9
- import { textResponse } from "./types.js";
10
- /**
11
- * Handler for get_requirement tool
12
- *
13
- * Requirements covered:
14
- * - MCP-GET-1.0: When the ID matches a root requirement, the full tree is returned
15
- * - MCP-GET-1.1: When the ID matches a child requirement, that subtree is returned
16
- * - MCP-GET-1.2: When the ID does not exist, an error message is returned
17
- * - MCP-GET-1.3: When a requirement is retrieved, test references are shown with file path and line number
18
- */
19
- export async function handleGetRequirement(args, context) {
20
- const { id, projectId } = args;
21
- const project = await context.getProjectFromDiscovery(projectId);
22
- const requirements = await context.getRequirements(projectId);
23
- // Get the tree starting from this ID.
24
- // MCP-GET-1.0: Full tree for root requirement
25
- // MCP-GET-1.1: Subtree for child requirement
26
- //
27
- // #46: Match the node itself plus all of its descendants by id prefix. The
28
- // shared getRequirementTree only matches on rootId/id equality, which drops
29
- // grandchildren (e.g. REQ-123.0.0) when a child id (REQ-123.0) is requested.
30
- const tree = requirements.filter((r) => r.id === id || r.id.startsWith(`${id}.`));
31
- // MCP-GET-1.2: Return error if not found
32
- if (tree.length === 0) {
33
- return textResponse(`Requirement "${id}" not found`);
34
- }
35
- const root = tree[0];
36
- const formatted = formatRequirementTree(tree);
37
- // MCP-GET-1.3: Find test files that reference this requirement
38
- const testFiles = await glob("**/*.{test,spec}.{js,jsx,ts,tsx}", {
39
- cwd: project.path,
40
- absolute: true,
41
- ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
42
- });
43
- const matchingFiles = findFilesWithRequirement(testFiles, id);
44
- const testCodeSections = [];
45
- for (const file of matchingFiles) {
46
- const testRefs = findTestCodeForRequirement(file, id);
47
- // Deduplicate by removing nested blocks - keep only outermost blocks
48
- const deduplicated = testRefs.filter((ref, i) => {
49
- // Check if this ref is contained within any other ref
50
- const isNested = testRefs.some((other, j) => {
51
- if (i === j)
52
- return false;
53
- // other contains ref if it starts before or at the same line and ends after or at the same line
54
- return (other.startLine <= ref.startLine &&
55
- other.endLine >= ref.endLine &&
56
- (other.startLine < ref.startLine || other.endLine > ref.endLine));
57
- });
58
- return !isNested;
59
- });
60
- for (const ref of deduplicated) {
61
- testCodeSections.push(`**${ref.requirementId}** tested in \`${ref.file}:${ref.startLine}-${ref.endLine}\`:\n\n\`\`\`typescript\n${ref.code}\n\`\`\``);
62
- }
63
- }
64
- const testSection = testCodeSections.length > 0
65
- ? `\n\n## Test Coverage (${testCodeSections.length} reference(s)):\n\n${testCodeSections.join("\n\n---\n\n")}`
66
- : "\n\n## Test Coverage\n\nNo tests found referencing this requirement.";
67
- return textResponse(`# ${id}\n\n**Document:** ${root.documentTitle}\n**Source:** ${root.sourceFile}\n\n## Requirement Tree (${tree.length} node(s)):\n\n\`\`\`\n${formatted}\n\`\`\`${testSection}`);
68
- }
69
- //# sourceMappingURL=get.js.map
@@ -1,28 +0,0 @@
1
- /**
2
- * MCP Tool Handlers
3
- *
4
- * Each handler is a pure function that processes tool arguments
5
- * and returns an MCP response. Handlers receive a context object
6
- * that provides access to shared dependencies.
7
- */
8
- export type { CreateRequirementDocumentArgs, ValidateRequirementsArgs, } from "./authoring.js";
9
- export { handleCreateRequirementDocument, handleValidateRequirements, } from "./authoring.js";
10
- export type { DebugMcpEnvironmentArgs } from "./debug.js";
11
- export { handleDebugMcpEnvironment } from "./debug.js";
12
- export type { GetRequirementArgs } from "./get.js";
13
- export { handleGetRequirement } from "./get.js";
14
- export type { ListArgs } from "./list.js";
15
- export { handleList } from "./list.js";
16
- export type { PushRequirementsArgs } from "./push.js";
17
- export { handlePushRequirements } from "./push.js";
18
- export type { ReportArgs } from "./report.js";
19
- export { handleReport } from "./report.js";
20
- export type { ReviewTestArgs, StyleCheckArgs } from "./review.js";
21
- export { handleReviewTest, handleStyleCheck } from "./review.js";
22
- export type { SearchRequirementsArgs } from "./search.js";
23
- export { handleSearchRequirements } from "./search.js";
24
- export type { GetRequirementsByTestArgs, GetTestsByRequirementArgs, } from "./test-mapping.js";
25
- export { handleGetRequirementsByTest, handleGetTestsByRequirement, } from "./test-mapping.js";
26
- export type { HandlerContext, ToolContentItem, ToolHandler, ToolResponse, } from "./types.js";
27
- export { errorResponse, textResponse } from "./types.js";
28
- //# sourceMappingURL=index.d.ts.map
@@ -1,19 +0,0 @@
1
- /**
2
- * MCP Tool Handlers
3
- *
4
- * Each handler is a pure function that processes tool arguments
5
- * and returns an MCP response. Handlers receive a context object
6
- * that provides access to shared dependencies.
7
- */
8
- export { handleCreateRequirementDocument, handleValidateRequirements, } from "./authoring.js";
9
- // Handlers
10
- export { handleDebugMcpEnvironment } from "./debug.js";
11
- export { handleGetRequirement } from "./get.js";
12
- export { handleList } from "./list.js";
13
- export { handlePushRequirements } from "./push.js";
14
- export { handleReport } from "./report.js";
15
- export { handleReviewTest, handleStyleCheck } from "./review.js";
16
- export { handleSearchRequirements } from "./search.js";
17
- export { handleGetRequirementsByTest, handleGetTestsByRequirement, } from "./test-mapping.js";
18
- export { errorResponse, textResponse } from "./types.js";
19
- //# sourceMappingURL=index.js.map
@@ -1,7 +0,0 @@
1
- import type { HandlerContext, ToolResponse } from "./types.js";
2
- export interface ListArgs {
3
- untested?: boolean;
4
- projectId?: string;
5
- }
6
- export declare function handleList(args: ListArgs, context: HandlerContext): Promise<ToolResponse>;
7
- //# sourceMappingURL=list.d.ts.map
@@ -1,43 +0,0 @@
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;
5
- const project = await context.getProjectFromDiscovery(projectId);
6
- const requirements = await context.getRequirements(projectId);
7
- if (requirements.length === 0) {
8
- return textResponse(`No requirements found in project: ${project.path}\n\nMake sure .requirements/ directory exists with Markdown files (*.requirements.md).`);
9
- }
10
- if (untested) {
11
- return formatUntested(requirements, project.path);
12
- }
13
- return formatAll(requirements);
14
- }
15
- function formatAll(requirements) {
16
- const grouped = new Map();
17
- for (const req of requirements) {
18
- const existing = grouped.get(req.rootId) || [];
19
- existing.push(req);
20
- grouped.set(req.rootId, existing);
21
- }
22
- const formatted = Array.from(grouped.entries())
23
- .map(([rootId, reqs]) => {
24
- const root = reqs.find((r) => r.path.length === 0);
25
- const childCount = reqs.length - 1;
26
- return `- **${rootId}**: ${root?.content || "(no content)"} (${childCount} children)`;
27
- })
28
- .join("\n");
29
- return textResponse(`Found ${grouped.size} requirement(s) with ${requirements.length} total nodes:\n\n${formatted}`);
30
- }
31
- async function formatUntested(requirements, projectPath) {
32
- const referencedIds = await getReferencedRequirementIds(projectPath);
33
- const rootRequirements = requirements.filter((r) => r.path.length === 0);
34
- const untested = rootRequirements.filter((r) => !referencedIds.has(r.id));
35
- if (untested.length === 0) {
36
- return textResponse(`All ${rootRequirements.length} requirement(s) have test references — coverage is complete.`);
37
- }
38
- const formatted = untested
39
- .map((r) => `- **${r.id}**: ${r.content}`)
40
- .join("\n");
41
- return textResponse(`Found ${untested.length} untested requirement(s) out of ${rootRequirements.length} total:\n\n${formatted}`);
42
- }
43
- //# sourceMappingURL=list.js.map
@@ -1,26 +0,0 @@
1
- /**
2
- * Push handler for MCP tools
3
- *
4
- * Provides requirements push functionality:
5
- * - push_requirements: Push local requirements to cloud
6
- */
7
- import type { HandlerContext, ToolResponse } from "./types.js";
8
- /**
9
- * Arguments for push_requirements tool
10
- */
11
- export interface PushRequirementsArgs {
12
- filePath?: string;
13
- confirmed?: boolean;
14
- projectId?: string;
15
- }
16
- /**
17
- * Handler for push_requirements tool
18
- *
19
- * Requirements covered:
20
- * - MCP-PUSH-1.0: When not confirmed, no changes are made and a preview of creates/updates is returned
21
- * - MCP-PUSH-1.1: When confirmed is true, the push executes and returns success message
22
- * - MCP-PUSH-1.2: When credentials are missing or invalid, an error explains how to authenticate
23
- * - MCP-PUSH-1.3: When filePath is provided, only that file is pushed (MCP-PUSH-1.3.0: missing file is an error)
24
- */
25
- export declare function handlePushRequirements(args: PushRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
26
- //# sourceMappingURL=push.d.ts.map
@@ -1,232 +0,0 @@
1
- /**
2
- * Push handler for MCP tools
3
- *
4
- * Provides requirements push functionality:
5
- * - push_requirements: Push local requirements to cloud
6
- */
7
- import { existsSync } from "node:fs";
8
- import { basename, resolve } from "node:path";
9
- import { dryRunPush, executePush, parseFilesForPushIndividually, } 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
- /**
14
- * Handler for push_requirements tool
15
- *
16
- * Requirements covered:
17
- * - MCP-PUSH-1.0: When not confirmed, no changes are made and a preview of creates/updates is returned
18
- * - MCP-PUSH-1.1: When confirmed is true, the push executes and returns success message
19
- * - MCP-PUSH-1.2: When credentials are missing or invalid, an error explains how to authenticate
20
- * - MCP-PUSH-1.3: When filePath is provided, only that file is pushed (MCP-PUSH-1.3.0: missing file is an error)
21
- */
22
- export async function handlePushRequirements(args, context) {
23
- const { filePath, confirmed = false, projectId } = args;
24
- // MCP-PUSH-1.2: Get project credentials (throws if not configured)
25
- let project;
26
- try {
27
- project = await context.getProjectFromDiscovery(projectId);
28
- }
29
- catch (error) {
30
- return errorResponse(`Push requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
31
- }
32
- // Determine files to push
33
- let filesToPush;
34
- // MCP-PUSH-1.3: When filePath is provided, only that file is pushed
35
- if (filePath) {
36
- const fullPath = resolve(project.path, filePath);
37
- if (!existsSync(fullPath)) {
38
- return errorResponse(`File not found: ${filePath}`);
39
- }
40
- filesToPush = [fullPath];
41
- }
42
- else {
43
- // Use same file discovery as CLI - searches entire workspace
44
- filesToPush = await findRequirementsFiles(project.path);
45
- if (filesToPush.length === 0) {
46
- return textResponse("No *.requirements.md files found. Nothing to push.");
47
- }
48
- }
49
- // Parse files. #45/SYNC-FAIL-4: one invalid file does not abort the whole
50
- // push — its failure is surfaced in the preview's "Skipped - Invalid"
51
- // section while valid files still push. Same helper as the CLI command.
52
- const { parsedFiles, totalRequirements, parseFailures } = parseFilesForPushIndividually(filesToPush);
53
- // Build credentials
54
- const credentials = {
55
- projectId: project.projectId,
56
- projectSecret: project.projectSecret,
57
- convexUrl: CONVEX_URL,
58
- };
59
- // Run dry run (always, even when confirmed - ensures fresh state).
60
- // SYNC-FAIL-3: two files claiming the same document ID abort the push
61
- // before any cloud write — surface that as a clean error result naming
62
- // both files rather than an unhandled throw.
63
- let dryRunResult;
64
- try {
65
- dryRunResult = await dryRunPush(parsedFiles, credentials);
66
- }
67
- catch (error) {
68
- const message = error instanceof Error ? error.message : String(error);
69
- if (message.includes("claim the same document ID")) {
70
- return {
71
- content: [
72
- {
73
- type: "text",
74
- text: `✗ Push aborted: ${message}`,
75
- },
76
- ],
77
- isError: true,
78
- };
79
- }
80
- throw error;
81
- }
82
- // #45: Merge dry-run invalids (missing document section, cloud validation)
83
- // with local parse failures so both are surfaced together, each named by
84
- // filename.
85
- const allInvalid = [
86
- ...dryRunResult.invalid.map(({ file, result }) => ({
87
- fileName: basename(file.filePath),
88
- error: result.error ?? "invalid",
89
- })),
90
- ...parseFailures.map(({ filePath, error }) => ({
91
- fileName: basename(filePath),
92
- error,
93
- })),
94
- ];
95
- // Check if there's anything to push
96
- const pushableCount = dryRunResult.updates.length +
97
- dryRunResult.creates.length +
98
- dryRunResult.notFound.length;
99
- if (pushableCount === 0 && allInvalid.length > 0) {
100
- // Only invalid files
101
- const invalidList = allInvalid
102
- .map(({ fileName, error }) => `- ${fileName}: ${error}`)
103
- .join("\n");
104
- return {
105
- content: [
106
- {
107
- type: "text",
108
- text: `✗ No valid documents to push.\n\n**Invalid files:**\n${invalidList}`,
109
- },
110
- ],
111
- isError: true,
112
- };
113
- }
114
- // MCP-PUSH-1.0: When not confirmed, show preview
115
- if (!confirmed) {
116
- let summary = `# Push Preview\n\n**Files:** ${parsedFiles.length}\n**Requirements:** ${totalRequirements}\n\n`;
117
- if (dryRunResult.updates.length > 0) {
118
- summary += `## Updates (${dryRunResult.updates.length})\n`;
119
- for (const { file, result } of dryRunResult.updates) {
120
- const fileName = basename(file.filePath);
121
- const hasConflict = dryRunResult.conflicts.some((c) => c.item.file === file);
122
- const conflictNote = hasConflict ? " ⚠️ [cloud changed since pull]" : "";
123
- const warningNote = result.warning ? ` (${result.warning})` : "";
124
- summary += `- ~ ${fileName}${conflictNote}${warningNote}\n`;
125
- }
126
- summary += "\n";
127
- }
128
- if (dryRunResult.creates.length > 0) {
129
- summary += `## New Documents (${dryRunResult.creates.length})\n`;
130
- for (const { file, result } of dryRunResult.creates) {
131
- const fileName = basename(file.filePath);
132
- const warningNote = result.warning ? ` (${result.warning})` : "";
133
- summary += `- + ${fileName}${warningNote}\n`;
134
- }
135
- summary += "\n";
136
- }
137
- if (dryRunResult.notFound.length > 0) {
138
- summary += `## Not Found in Cloud (${dryRunResult.notFound.length}) - will create new\n`;
139
- for (const { file, result } of dryRunResult.notFound) {
140
- const fileName = basename(file.filePath);
141
- summary += `- ! ${fileName} (ID: ${result.documentId})\n`;
142
- }
143
- summary += "\n";
144
- }
145
- if (allInvalid.length > 0) {
146
- summary += `## Skipped - Invalid (${allInvalid.length})\n`;
147
- for (const { fileName, error } of allInvalid) {
148
- summary += `- ✗ ${fileName}: ${error}\n`;
149
- }
150
- summary += "\n";
151
- }
152
- if (dryRunResult.conflicts.length > 0) {
153
- summary += `⚠️ **Warning:** ${dryRunResult.conflicts.length} file(s) have cloud changes since last pull. Pushing will overwrite those changes.\n\n`;
154
- }
155
- summary += `**To proceed:** Call this tool again with \`confirmed: true\``;
156
- return textResponse(summary);
157
- }
158
- // MCP-PUSH-1.1: When confirmed, execute push
159
- try {
160
- const result = await executePush(dryRunResult, credentials);
161
- // SYNC-FAIL-1: a failed push must not masquerade as success
162
- const failed = result.errors.length;
163
- const syncedCount = result.created + result.updated;
164
- let output;
165
- if (failed > 0 && syncedCount === 0) {
166
- output = `✗ Push failed: ${failed} document(s) could not be saved.\n\n`;
167
- }
168
- else if (failed > 0) {
169
- output = `⚠ Push incomplete: ${syncedCount} document(s) synced, ${failed} failed.\n\n`;
170
- }
171
- else {
172
- output = "✓ Push complete!\n\n";
173
- }
174
- if (result.created > 0) {
175
- output += `**Created:** ${result.created} document(s)\n`;
176
- }
177
- if (result.updated > 0) {
178
- output += `**Updated:** ${result.updated} document(s)\n`;
179
- }
180
- // IMPORT-3: marker failures are loud but never fatal
181
- if (result.importWarnings.length > 0) {
182
- output += `\n**Import marker warnings:**\n`;
183
- for (const { fileName, status } of result.importWarnings) {
184
- output +=
185
- status === "already_used"
186
- ? `- ⚠ ${fileName}: import marker already used — requirements were saved as natively authored (they count toward the plan's requirement limit). Re-running codebase-to-spec produces a fresh marker.\n`
187
- : `- ⚠ ${fileName}: import marker not recognized — the CLI may need updating. Requirements were saved as natively authored (they count toward the plan's requirement limit).\n`;
188
- }
189
- }
190
- if (result.errors.length > 0) {
191
- output += `\n**Errors:**\n`;
192
- for (const { fileName, error } of result.errors) {
193
- output += `- ${fileName}: ${error}\n`;
194
- }
195
- }
196
- // SYNC-FAIL-2.1: cloud save succeeded but the local file couldn't be
197
- // updated — name the file, say the cloud is fine, and give the recovery
198
- // step so a retry doesn't mint a duplicate document.
199
- if (result.writeBackWarnings?.length > 0) {
200
- output += `\n**Warnings:**\n`;
201
- for (const warning of result.writeBackWarnings) {
202
- output +=
203
- `- ⚠ ${warning.fileName}: saved to the cloud, but the local file could not be updated (${warning.error}). ` +
204
- `To avoid creating a duplicate, add "id: ${warning.documentId}" under "document:" in the frontmatter of ${warning.filePath}, then push again.\n`;
205
- }
206
- }
207
- // SYNC-LAND-1: where each synced document lives in the web app
208
- if (result.synced.length > 0) {
209
- output += `\n**Review in dot•requirements:**\n`;
210
- for (const doc of result.synced) {
211
- output += `- ${doc.fileName}: ${doc.url}\n`;
212
- }
213
- }
214
- // SYNC-FAIL-1.2: total failure is an error result, not a success story
215
- if (failed > 0 && syncedCount === 0) {
216
- return { content: [{ type: "text", text: output }], isError: true };
217
- }
218
- return textResponse(output);
219
- }
220
- catch (error) {
221
- return {
222
- content: [
223
- {
224
- type: "text",
225
- text: `✗ Push failed:\n\n${error instanceof Error ? error.message : String(error)}`,
226
- },
227
- ],
228
- isError: true,
229
- };
230
- }
231
- }
232
- //# sourceMappingURL=push.js.map
@@ -1,16 +0,0 @@
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