@popoverai/dotrequirements 0.24.3 → 0.26.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 (75) hide show
  1. package/README.md +7 -8
  2. package/dist/cli.js +8 -1
  3. package/dist/codebase-to-spec/dispatch.d.ts +60 -14
  4. package/dist/codebase-to-spec/dispatch.js +381 -15
  5. package/dist/codebase-to-spec/pack.d.ts +7 -0
  6. package/dist/codebase-to-spec/pack.js +29 -8
  7. package/dist/codebase-to-spec/present.d.ts +9 -0
  8. package/dist/codebase-to-spec/present.js +23 -2
  9. package/dist/codebase-to-spec/prompts/editor.d.ts +1 -1
  10. package/dist/codebase-to-spec/prompts/editor.js +1 -1
  11. package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
  12. package/dist/codebase-to-spec/prompts/specifier.js +3 -2
  13. package/dist/codebase-to-spec/schemas.d.ts +153 -0
  14. package/dist/codebase-to-spec/schemas.js +111 -0
  15. package/dist/codebase-to-spec/skill-install.d.ts +42 -29
  16. package/dist/codebase-to-spec/skill-install.js +122 -112
  17. package/dist/codebase-to-spec/version-check.d.ts +31 -0
  18. package/dist/codebase-to-spec/version-check.js +56 -0
  19. package/dist/commands/ai-setup.d.ts +12 -1
  20. package/dist/commands/ai-setup.js +65 -33
  21. package/dist/commands/codebase-to-spec/dispatch-context.d.ts +2 -5
  22. package/dist/commands/codebase-to-spec/dispatch-context.js +2 -5
  23. package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +0 -1
  24. package/dist/commands/codebase-to-spec/dispatch-editor.js +0 -1
  25. package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +0 -1
  26. package/dist/commands/codebase-to-spec/dispatch-planner.js +0 -1
  27. package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +3 -6
  28. package/dist/commands/codebase-to-spec/dispatch-spec.js +3 -6
  29. package/dist/commands/codebase-to-spec/index.js +3 -2
  30. package/dist/commands/codebase-to-spec/pack.d.ts +9 -0
  31. package/dist/commands/codebase-to-spec/pack.js +23 -3
  32. package/dist/commands/codebase-to-spec/skill-install.js +2 -9
  33. package/dist/commands/init.js +6 -1
  34. package/dist/commands/link-resolution.d.ts +79 -0
  35. package/dist/commands/link-resolution.js +141 -0
  36. package/dist/commands/link.d.ts +14 -4
  37. package/dist/commands/link.js +369 -16
  38. package/dist/commands/pull.js +19 -2
  39. package/dist/commands/push.js +36 -2
  40. package/dist/convex.d.ts +5 -3
  41. package/dist/convex.js +5 -3
  42. package/dist/harness/cache.d.ts +0 -14
  43. package/dist/harness/cache.js +1 -41
  44. package/dist/harness/finalize.js +2 -2
  45. package/dist/harness/prepare.js +1 -3
  46. package/dist/harness/requirementsLoader.d.ts +3 -3
  47. package/dist/harness/requirementsLoader.js +13 -8
  48. package/dist/mcp/handlers/authoring.d.ts +5 -5
  49. package/dist/mcp/handlers/authoring.js +9 -9
  50. package/dist/mcp/handlers/push.d.ts +2 -2
  51. package/dist/mcp/handlers/push.js +36 -3
  52. package/dist/mcp/handlers/review.d.ts +4 -4
  53. package/dist/mcp/handlers/review.js +4 -4
  54. package/dist/mcp/handlers/search.d.ts +1 -1
  55. package/dist/mcp/handlers/search.js +1 -1
  56. package/dist/mcp/index.js +29 -0
  57. package/dist/push/core.d.ts +18 -0
  58. package/dist/push/core.js +70 -3
  59. package/dist/push/index.d.ts +1 -1
  60. package/dist/push/index.js +1 -1
  61. package/dist/schema/parser-core.js +5 -1
  62. package/dist/schema/parser.js +5 -1
  63. package/dist/schema/run-marker.d.ts +38 -0
  64. package/dist/schema/run-marker.js +138 -0
  65. package/dist/schema/schemas.d.ts +12 -0
  66. package/dist/schema/schemas.js +1 -0
  67. package/dist/templates/agents/cts-worker.md +3 -3
  68. package/dist/templates/skills/codebase-to-spec/SKILL.md +56 -158
  69. package/dist/templates/workflows/specify-codebase.js +374 -0
  70. package/dist/utils/own-package.d.ts +10 -0
  71. package/dist/utils/own-package.js +13 -0
  72. package/dist/utils/project-selector.d.ts +5 -0
  73. package/dist/utils/project-selector.js +4 -0
  74. package/package.json +3 -3
  75. package/dist/templates/hooks/cts-worker-persona.sh +0 -76
@@ -100,16 +100,50 @@ export async function pushCommand(file, options) {
100
100
  console.log(` ✓ Updated: ${fileName}`);
101
101
  }
102
102
  }
103
- console.log(`\n✓ Push complete!`);
103
+ // IMPORT-3: marker failures are loud but never fatal
104
+ if (result.importWarnings.length > 0) {
105
+ console.log();
106
+ for (const { fileName, status } of result.importWarnings) {
107
+ if (status === "already_used") {
108
+ console.log(`⚠ ${fileName}: this file's import marker was already used, so its requirements were saved as natively authored (they count toward your plan's requirement limit). Re-running codebase-to-spec produces a fresh marker.`);
109
+ }
110
+ else {
111
+ console.log(`⚠ ${fileName}: this file's import marker was not recognized — your CLI may need updating. Its requirements were saved as natively authored (they count toward your plan's requirement limit).`);
112
+ }
113
+ }
114
+ }
115
+ // SYNC-FAIL-1: a failed push must not masquerade as success — agents and
116
+ // scripts drive this command and rely on the exit code to tell the truth
117
+ const failed = result.errors.length;
118
+ const syncedCount = result.created + result.updated;
119
+ if (failed > 0) {
120
+ process.exitCode = 1;
121
+ if (syncedCount === 0) {
122
+ console.log(`\n✗ Push failed: ${failed} document(s) could not be saved.`);
123
+ }
124
+ else {
125
+ console.log(`\n⚠ Push incomplete: ${syncedCount} document(s) synced, ${failed} failed.`);
126
+ }
127
+ }
128
+ else {
129
+ console.log(`\n✓ Push complete!`);
130
+ }
104
131
  if (result.created > 0) {
105
132
  console.log(` Created: ${result.created} document(s)`);
106
133
  }
107
134
  if (result.updated > 0) {
108
135
  console.log(` Updated: ${result.updated} document(s)`);
109
136
  }
110
- if (result.created === 0 && result.updated === 0) {
137
+ if (failed === 0 && syncedCount === 0) {
111
138
  console.log(" No documents were pushed.");
112
139
  }
140
+ // SYNC-LAND-1: land the user on their spec, not at a dead end
141
+ if (result.synced.length > 0) {
142
+ console.log("\nReview in dot•requirements:");
143
+ for (const doc of result.synced) {
144
+ console.log(` ${doc.fileName} → ${doc.url}`);
145
+ }
146
+ }
113
147
  }
114
148
  /**
115
149
  * Display the dry run summary.
package/dist/convex.d.ts CHANGED
@@ -34,13 +34,11 @@ export declare const api: {
34
34
  queries: {
35
35
  listByProject: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
36
36
  };
37
- mutations: {
38
- bulkUpdate: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
39
- };
40
37
  };
41
38
  projects: {
42
39
  mutations: {
43
40
  create: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
41
+ update: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
44
42
  };
45
43
  };
46
44
  teams: {
@@ -55,6 +53,9 @@ export declare const api: {
55
53
  queries: {
56
54
  getInviteInfo: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
57
55
  };
56
+ mutations: {
57
+ createInvite: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
58
+ };
58
59
  };
59
60
  polar: {
60
61
  acceptTeamInvite: import("convex/server").FunctionReference<"action", "public", any, any, string | undefined>;
@@ -67,6 +68,7 @@ export declare const api: {
67
68
  mutations: {
68
69
  ensureOwnSecret: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
69
70
  revokeOwnSecret: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
71
+ ensureShareToken: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
70
72
  };
71
73
  };
72
74
  lib: {
package/dist/convex.js CHANGED
@@ -40,13 +40,11 @@ export const api = {
40
40
  queries: {
41
41
  listByProject: query("requirements/queries:listByProject"),
42
42
  },
43
- mutations: {
44
- bulkUpdate: mutation("requirements/mutations:bulkUpdate"),
45
- },
46
43
  },
47
44
  projects: {
48
45
  mutations: {
49
46
  create: mutation("projects/mutations:create"),
47
+ update: mutation("projects/mutations:update"),
50
48
  },
51
49
  },
52
50
  teams: {
@@ -61,6 +59,9 @@ export const api = {
61
59
  queries: {
62
60
  getInviteInfo: query("teamInvites/queries:getInviteInfo"),
63
61
  },
62
+ mutations: {
63
+ createInvite: mutation("teamInvites/mutations:createInvite"),
64
+ },
64
65
  },
65
66
  polar: {
66
67
  acceptTeamInvite: action("polar:acceptTeamInvite"),
@@ -73,6 +74,7 @@ export const api = {
73
74
  mutations: {
74
75
  ensureOwnSecret: mutation("projectSecrets/mutations:ensureOwnSecret"),
75
76
  revokeOwnSecret: mutation("projectSecrets/mutations:revokeOwnSecret"),
77
+ ensureShareToken: mutation("projectSecrets/mutations:ensureShareToken"),
76
78
  },
77
79
  },
78
80
  lib: {
@@ -94,20 +94,6 @@ export declare function getTestRunId(requirementsDir: string): string | null;
94
94
  * Clean up the test run ID file
95
95
  */
96
96
  export declare function cleanupTestRunId(requirementsDir: string): void;
97
- /**
98
- * Write the project root to cache (for cross-process persistence).
99
- * This allows test workers to find the lookup cache even after cwd changes.
100
- */
101
- export declare function writeProjectRoot(requirementsDir: string, projectRoot: string): void;
102
- /**
103
- * Read the project root from cache, returning null if not found.
104
- */
105
- export declare function readProjectRoot(requirementsDir: string): string | null;
106
- /**
107
- * Try to find the project root from any known .requirements cache directory.
108
- * Walks up from cwd looking for .requirements/.cache/project-root file.
109
- */
110
- export declare function findCachedProjectRoot(startDir?: string): string | null;
111
97
  /**
112
98
  * Clear tracking data for a new test run
113
99
  */
@@ -14,7 +14,6 @@ const CACHE_DIR = ".cache";
14
14
  const LOOKUP_FILE = "lookup.json";
15
15
  const TRACKING_FILE = "tracking.jsonl";
16
16
  const COVERAGE_FILE = "coverage.json";
17
- const PROJECT_ROOT_FILE = "project-root";
18
17
  // Test run ID file (outside cache, in .requirements/)
19
18
  const TEST_RUN_ID_FILE = ".test-run-id";
20
19
  /**
@@ -70,7 +69,7 @@ export function writeLookupCache(requirementsDir, requirements) {
70
69
  label: node.label,
71
70
  content: node.content,
72
71
  };
73
- // Always add the numeric path (e.g. "AUTH-LOGIN.0")
72
+ // Always add the numeric path (e.g. "AUTH-LOGIN-1.0")
74
73
  lookup.requirements[node.id] = entry;
75
74
  // Add label path as an alias if it differs from the numeric path
76
75
  if (labelPath && labelPath !== node.id) {
@@ -174,45 +173,6 @@ export function cleanupTestRunId(requirementsDir) {
174
173
  fs.unlinkSync(testRunIdPath);
175
174
  }
176
175
  }
177
- /**
178
- * Write the project root to cache (for cross-process persistence).
179
- * This allows test workers to find the lookup cache even after cwd changes.
180
- */
181
- export function writeProjectRoot(requirementsDir, projectRoot) {
182
- const cacheDir = getCacheDir(requirementsDir, true);
183
- const projectRootPath = path.join(cacheDir, PROJECT_ROOT_FILE);
184
- fs.writeFileSync(projectRootPath, projectRoot);
185
- }
186
- /**
187
- * Read the project root from cache, returning null if not found.
188
- */
189
- export function readProjectRoot(requirementsDir) {
190
- const cacheDir = getCacheDir(requirementsDir);
191
- const projectRootPath = path.join(cacheDir, PROJECT_ROOT_FILE);
192
- if (!fs.existsSync(projectRootPath)) {
193
- return null;
194
- }
195
- return fs.readFileSync(projectRootPath, "utf-8").trim();
196
- }
197
- /**
198
- * Try to find the project root from any known .requirements cache directory.
199
- * Walks up from cwd looking for .requirements/.cache/project-root file.
200
- */
201
- export function findCachedProjectRoot(startDir = process.cwd()) {
202
- let currentDir = path.resolve(startDir);
203
- while (true) {
204
- const requirementsDir = path.join(currentDir, ".requirements");
205
- const projectRootPath = path.join(requirementsDir, CACHE_DIR, PROJECT_ROOT_FILE);
206
- if (fs.existsSync(projectRootPath)) {
207
- return fs.readFileSync(projectRootPath, "utf-8").trim();
208
- }
209
- const parentDir = path.dirname(currentDir);
210
- if (parentDir === currentDir) {
211
- return null;
212
- }
213
- currentDir = parentDir;
214
- }
215
- }
216
176
  /**
217
177
  * Clear tracking data for a new test run
218
178
  */
@@ -333,8 +333,8 @@ export async function finalize(options = {}) {
333
333
  // Read lookup cache for report
334
334
  const lookup = readLookupCache(requirementsDir);
335
335
  // Normalize alias keys to canonical numeric keys.
336
- // Non-JS consumers may write label paths (e.g. "AUTH-LOGIN.given") to tracking.jsonl.
337
- // We resolve those to their canonical numeric key (e.g. "AUTH-LOGIN.0") so that
336
+ // Non-JS consumers may write label paths (e.g. "AUTH-LOGIN-1.given") to tracking.jsonl.
337
+ // We resolve those to their canonical numeric key (e.g. "AUTH-LOGIN-1.0") so that
338
338
  // coverage counting, display, and cloud reporting all use consistent keys.
339
339
  if (lookup) {
340
340
  for (const [key, trackingEntries] of Array.from(aggregated.entries())) {
@@ -11,7 +11,7 @@
11
11
  import { findRequirementsFilesSync } from "../requirements/index.js";
12
12
  import { parseRequirementsFromFile, } from "../schema/index.js";
13
13
  import { findProjectRoot } from "../utils/project-settings.js";
14
- import { clearTrackingFile, findRequirementsDir, initTestRunId, writeLookupCache, writeProjectRoot, } from "./cache.js";
14
+ import { clearTrackingFile, findRequirementsDir, initTestRunId, writeLookupCache, } from "./cache.js";
15
15
  /**
16
16
  * Prepare the test harness for a test run.
17
17
  *
@@ -54,8 +54,6 @@ export function prepare(options = {}) {
54
54
  }
55
55
  // HARNESS-PREPARE-1: Write lookup cache for fast resolution
56
56
  writeLookupCache(requirementsDir, allRequirements);
57
- // Write project root to cache (for cross-process persistence in test workers)
58
- writeProjectRoot(requirementsDir, projectRoot);
59
57
  // HARNESS-PREPARE-3.0: Initialize cache directory (done by writeLookupCache)
60
58
  // HARNESS-PREPARE-3.1: Clear any previous tracking data
61
59
  clearTrackingFile(requirementsDir);
@@ -4,14 +4,14 @@
4
4
  *
5
5
  * Project root discovery (in priority order):
6
6
  * 1. Explicit projectRoot option passed to loadRequirements()
7
- * 2. DOTREQUIREMENTS_PROJECT_ROOT environment variable
8
- * 3. Cached project root file (.requirements/.cache/project-root) written by prepare()
7
+ * 2. DOTREQUIREMENTS_PROJECT_ROOT environment variable (set by prepare())
8
+ * 3. Walk up from the working directory to the nearest .requirements/ folder
9
9
  */
10
10
  import { type RequirementNode } from "../schema/index.js";
11
11
  import type { Requirement } from "./types.js";
12
12
  export declare const loadedRequirements: Map<string, RequirementNode>;
13
13
  export interface LoadOptions {
14
- /** Explicit project root path. Takes precedence over env var and cached file. */
14
+ /** Explicit project root path. Takes precedence over env var and walk-up discovery. */
15
15
  projectRoot?: string;
16
16
  }
17
17
  /**
@@ -4,14 +4,14 @@
4
4
  *
5
5
  * Project root discovery (in priority order):
6
6
  * 1. Explicit projectRoot option passed to loadRequirements()
7
- * 2. DOTREQUIREMENTS_PROJECT_ROOT environment variable
8
- * 3. Cached project root file (.requirements/.cache/project-root) written by prepare()
7
+ * 2. DOTREQUIREMENTS_PROJECT_ROOT environment variable (set by prepare())
8
+ * 3. Walk up from the working directory to the nearest .requirements/ folder
9
9
  */
10
10
  import * as fs from "node:fs";
11
11
  import * as path from "node:path";
12
12
  import { findRequirementsFilesSync } from "../requirements/index.js";
13
13
  import { parseRequirementsFromFile, resolveRequirementPath, } from "../schema/index.js";
14
- import { findCachedProjectRoot, readLookupCache } from "./cache.js";
14
+ import { findProjectRoot as findProjectRootByWalkUp, readLookupCache, } from "./cache.js";
15
15
  // Cache: Maps full requirement IDs (e.g., "REQ-123.0.1") to RequirementNode
16
16
  export const loadedRequirements = new Map();
17
17
  // Cache: Maps root requirement IDs (e.g., "REQ-123") to their tree
@@ -22,7 +22,12 @@ let warnedAboutFallback = false;
22
22
  * Find the project root using the priority order:
23
23
  * 1. Explicit projectRoot option
24
24
  * 2. DOTREQUIREMENTS_PROJECT_ROOT env var
25
- * 3. Cached project root file
25
+ * 3. Walk up from cwd to the nearest .requirements/ folder
26
+ *
27
+ * The walk-up means the nearest enclosing project wins: tests running in a
28
+ * worktree nested inside another checkout load that worktree's requirements,
29
+ * not the outer checkout's (HARNESS-REQUIREMENT-4.3). It also matches how
30
+ * tracking.ts discovers the requirements directory.
26
31
  */
27
32
  function findProjectRoot(options = {}) {
28
33
  // Priority 1: Explicit option
@@ -33,8 +38,8 @@ function findProjectRoot(options = {}) {
33
38
  if (process.env.DOTREQUIREMENTS_PROJECT_ROOT) {
34
39
  return process.env.DOTREQUIREMENTS_PROJECT_ROOT;
35
40
  }
36
- // Priority 3: Cached file (written by prepare())
37
- return findCachedProjectRoot(process.cwd());
41
+ // Priority 3: Nearest .requirements/ folder (HARNESS-REQUIREMENT-4.2)
42
+ return findProjectRootByWalkUp(process.cwd());
38
43
  }
39
44
  /**
40
45
  * Try to load requirements from lookup cache (fast path).
@@ -97,8 +102,8 @@ export function loadRequirements(options = {}) {
97
102
  // Find project root
98
103
  const projectRoot = findProjectRoot(options);
99
104
  if (!projectRoot) {
100
- throw new Error("Could not find project root. " +
101
- "Set DOTREQUIREMENTS_PROJECT_ROOT environment variable or run prepare() in globalSetup.");
105
+ throw new Error(`Could not find project root: no .requirements/ directory in ${process.cwd()} or any parent directory. ` +
106
+ "Create one with `dotrequirements init`, or set the DOTREQUIREMENTS_PROJECT_ROOT environment variable.");
102
107
  }
103
108
  // Try cache first (fast path)
104
109
  if (tryLoadFromCache(projectRoot)) {
@@ -23,7 +23,7 @@ export interface ValidateRequirementsArgs {
23
23
  *
24
24
  * Requirements covered:
25
25
  * - MCP-AUTHOR-1.0: The template includes format guidance with code block examples
26
- * - MCP-AUTHOR-1.1: The template includes guidance on concrete examples, concise prose, and testable conditions
26
+ * - MCP-AUTHOR-1.1: The template includes style guidance (concrete examples, concise prose, testable conditions)
27
27
  * - MCP-AUTHOR-1.2: When the project has requirementsStyleContext configured, it is included in the template
28
28
  * - MCP-AUTHOR-1.3: When cloud credentials are unavailable, the template works without the custom context
29
29
  */
@@ -32,10 +32,10 @@ export declare function handleCreateRequirementDocument(args: CreateRequirementD
32
32
  * Handler for validate_requirements tool
33
33
  *
34
34
  * Requirements covered:
35
- * - MCP-AUTHOR-2.0: When the file has valid syntax, the response confirms validation passed
36
- * - MCP-AUTHOR-2.1: When the file has syntax errors, the response lists each error with location
37
- * - MCP-AUTHOR-2.2: Validation does not require network access or cloud credentials
38
- * - MCP-AUTHOR-2.3: When the file does not exist, an error is returned
35
+ * - MCP-AUTHOR-2.1: When the file has valid syntax, the response confirms validation passed
36
+ * - MCP-AUTHOR-2.2: When the file has syntax errors, the response lists each error with location
37
+ * - MCP-AUTHOR-2.3: Validation does not require network access or cloud credentials
38
+ * - MCP-AUTHOR-2.4: When the file does not exist, an error is returned
39
39
  */
40
40
  export declare function handleValidateRequirements(args: ValidateRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
41
41
  //# sourceMappingURL=authoring.d.ts.map
@@ -17,7 +17,7 @@ import { errorResponse, textResponse } from "./types.js";
17
17
  *
18
18
  * Requirements covered:
19
19
  * - MCP-AUTHOR-1.0: The template includes format guidance with code block examples
20
- * - MCP-AUTHOR-1.1: The template includes guidance on concrete examples, concise prose, and testable conditions
20
+ * - MCP-AUTHOR-1.1: The template includes style guidance (concrete examples, concise prose, testable conditions)
21
21
  * - MCP-AUTHOR-1.2: When the project has requirementsStyleContext configured, it is included in the template
22
22
  * - MCP-AUTHOR-1.3: When cloud credentials are unavailable, the template works without the custom context
23
23
  */
@@ -53,21 +53,21 @@ export async function handleCreateRequirementDocument(args, context) {
53
53
  * Handler for validate_requirements tool
54
54
  *
55
55
  * Requirements covered:
56
- * - MCP-AUTHOR-2.0: When the file has valid syntax, the response confirms validation passed
57
- * - MCP-AUTHOR-2.1: When the file has syntax errors, the response lists each error with location
58
- * - MCP-AUTHOR-2.2: Validation does not require network access or cloud credentials
59
- * - MCP-AUTHOR-2.3: When the file does not exist, an error is returned
56
+ * - MCP-AUTHOR-2.1: When the file has valid syntax, the response confirms validation passed
57
+ * - MCP-AUTHOR-2.2: When the file has syntax errors, the response lists each error with location
58
+ * - MCP-AUTHOR-2.3: Validation does not require network access or cloud credentials
59
+ * - MCP-AUTHOR-2.4: When the file does not exist, an error is returned
60
60
  */
61
61
  export async function handleValidateRequirements(args, context) {
62
62
  const { filePath } = args;
63
- // MCP-AUTHOR-2.2: Validation works offline - just use workspaceRoot
63
+ // MCP-AUTHOR-2.3: Validation works offline - just use workspaceRoot
64
64
  const fullPath = resolve(context.workspaceRoot, filePath);
65
- // MCP-AUTHOR-2.3: Check if file exists
65
+ // MCP-AUTHOR-2.4: Check if file exists
66
66
  if (!existsSync(fullPath)) {
67
67
  return errorResponse(`File not found: ${filePath}`);
68
68
  }
69
69
  try {
70
- // MCP-AUTHOR-2.0 & MCP-AUTHOR-2.1: Parse and validate
70
+ // MCP-AUTHOR-2.1 & MCP-AUTHOR-2.2: Parse and validate
71
71
  const parsed = parseRequirementsFromFile(fullPath);
72
72
  const reqCount = Object.keys(parsed.requirements).length;
73
73
  // Check push readiness
@@ -89,7 +89,7 @@ export async function handleValidateRequirements(args, context) {
89
89
  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`);
90
90
  }
91
91
  catch (error) {
92
- // MCP-AUTHOR-2.1: Syntax errors are returned with details
92
+ // MCP-AUTHOR-2.2: Syntax errors are returned with details
93
93
  return {
94
94
  content: [
95
95
  {
@@ -17,10 +17,10 @@ export interface PushRequirementsArgs {
17
17
  * Handler for push_requirements tool
18
18
  *
19
19
  * Requirements covered:
20
- * - MCP-PUSH-1.0: When a push is requested without confirmed flag, a diff preview is returned
20
+ * - MCP-PUSH-1.0: When not confirmed, no changes are made and a preview of creates/updates is returned
21
21
  * - MCP-PUSH-1.1: When confirmed is true, the push executes and returns success message
22
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
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
24
  */
25
25
  export declare function handlePushRequirements(args: PushRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
26
26
  //# sourceMappingURL=push.d.ts.map
@@ -14,10 +14,10 @@ import { errorResponse, textResponse } from "./types.js";
14
14
  * Handler for push_requirements tool
15
15
  *
16
16
  * Requirements covered:
17
- * - MCP-PUSH-1.0: When a push is requested without confirmed flag, a diff preview is returned
17
+ * - MCP-PUSH-1.0: When not confirmed, no changes are made and a preview of creates/updates is returned
18
18
  * - MCP-PUSH-1.1: When confirmed is true, the push executes and returns success message
19
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
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
21
  */
22
22
  export async function handlePushRequirements(args, context) {
23
23
  const { filePath, confirmed = false, projectId } = args;
@@ -123,19 +123,52 @@ export async function handlePushRequirements(args, context) {
123
123
  // MCP-PUSH-1.1: When confirmed, execute push
124
124
  try {
125
125
  const result = await executePush(dryRunResult, credentials);
126
- let output = "✓ Push complete!\n\n";
126
+ // SYNC-FAIL-1: a failed push must not masquerade as success
127
+ const failed = result.errors.length;
128
+ const syncedCount = result.created + result.updated;
129
+ let output;
130
+ if (failed > 0 && syncedCount === 0) {
131
+ output = `✗ Push failed: ${failed} document(s) could not be saved.\n\n`;
132
+ }
133
+ else if (failed > 0) {
134
+ output = `⚠ Push incomplete: ${syncedCount} document(s) synced, ${failed} failed.\n\n`;
135
+ }
136
+ else {
137
+ output = "✓ Push complete!\n\n";
138
+ }
127
139
  if (result.created > 0) {
128
140
  output += `**Created:** ${result.created} document(s)\n`;
129
141
  }
130
142
  if (result.updated > 0) {
131
143
  output += `**Updated:** ${result.updated} document(s)\n`;
132
144
  }
145
+ // IMPORT-3: marker failures are loud but never fatal
146
+ if (result.importWarnings.length > 0) {
147
+ output += `\n**Import marker warnings:**\n`;
148
+ for (const { fileName, status } of result.importWarnings) {
149
+ output +=
150
+ status === "already_used"
151
+ ? `- ⚠ ${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`
152
+ : `- ⚠ ${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`;
153
+ }
154
+ }
133
155
  if (result.errors.length > 0) {
134
156
  output += `\n**Errors:**\n`;
135
157
  for (const { fileName, error } of result.errors) {
136
158
  output += `- ${fileName}: ${error}\n`;
137
159
  }
138
160
  }
161
+ // SYNC-LAND-1: where each synced document lives in the web app
162
+ if (result.synced.length > 0) {
163
+ output += `\n**Review in dot•requirements:**\n`;
164
+ for (const doc of result.synced) {
165
+ output += `- ${doc.fileName}: ${doc.url}\n`;
166
+ }
167
+ }
168
+ // SYNC-FAIL-1.2: total failure is an error result, not a success story
169
+ if (failed > 0 && syncedCount === 0) {
170
+ return { content: [{ type: "text", text: output }], isError: true };
171
+ }
139
172
  return textResponse(output);
140
173
  }
141
174
  catch (error) {
@@ -25,7 +25,7 @@ export interface ReviewTestArgs {
25
25
  * Handler for style_check tool
26
26
  *
27
27
  * Requirements covered:
28
- * - MCP-REVIEW-1.0: For requirements files, feedback identifies vague language, missing preconditions, and style violations
28
+ * - MCP-REVIEW-1.0: For requirements files, feedback identifies clarity issues such as vague language and missing preconditions
29
29
  * - MCP-REVIEW-1.1: For test files, feedback identifies incorrect requirement() usage and missing test coverage
30
30
  * - MCP-REVIEW-1.2: Feedback is categorized by severity: must fix, should fix, could improve
31
31
  * - MCP-REVIEW-1.3: When cloud credentials are unavailable, an error explains how to authenticate
@@ -38,9 +38,9 @@ export declare function handleStyleCheck(args: StyleCheckArgs, context: HandlerC
38
38
  * Handler for review_test tool
39
39
  *
40
40
  * Requirements covered:
41
- * - MCP-REVIEW-2.0: The review validates that test setup matches requirement preconditions
42
- * - MCP-REVIEW-2.1: The review validates that test actions match requirement triggers
43
- * - MCP-REVIEW-2.2: The review validates that test assertions match requirement outcomes
41
+ * - MCP-REVIEW-2.0: Feedback identifies test setup that does not match requirement preconditions
42
+ * - MCP-REVIEW-2.1: Feedback identifies test actions that do not match requirement triggers
43
+ * - MCP-REVIEW-2.2: Feedback identifies test assertions that do not match requirement outcomes
44
44
  * - MCP-REVIEW-2.3: Feedback identifies requirements without test coverage
45
45
  * - MCP-REVIEW-2.4: Feedback identifies tests that reference non-existent requirements
46
46
  * - MCP-REVIEW-2.5: When cloud credentials are unavailable, an error explains how to authenticate
@@ -26,7 +26,7 @@ const STYLE_CHECK_GUIDANCE = `
26
26
  * Handler for style_check tool
27
27
  *
28
28
  * Requirements covered:
29
- * - MCP-REVIEW-1.0: For requirements files, feedback identifies vague language, missing preconditions, and style violations
29
+ * - MCP-REVIEW-1.0: For requirements files, feedback identifies clarity issues such as vague language and missing preconditions
30
30
  * - MCP-REVIEW-1.1: For test files, feedback identifies incorrect requirement() usage and missing test coverage
31
31
  * - MCP-REVIEW-1.2: Feedback is categorized by severity: must fix, should fix, could improve
32
32
  * - MCP-REVIEW-1.3: When cloud credentials are unavailable, an error explains how to authenticate
@@ -120,9 +120,9 @@ export async function handleStyleCheck(args, context, options) {
120
120
  * Handler for review_test tool
121
121
  *
122
122
  * Requirements covered:
123
- * - MCP-REVIEW-2.0: The review validates that test setup matches requirement preconditions
124
- * - MCP-REVIEW-2.1: The review validates that test actions match requirement triggers
125
- * - MCP-REVIEW-2.2: The review validates that test assertions match requirement outcomes
123
+ * - MCP-REVIEW-2.0: Feedback identifies test setup that does not match requirement preconditions
124
+ * - MCP-REVIEW-2.1: Feedback identifies test actions that do not match requirement triggers
125
+ * - MCP-REVIEW-2.2: Feedback identifies test assertions that do not match requirement outcomes
126
126
  * - MCP-REVIEW-2.3: Feedback identifies requirements without test coverage
127
127
  * - MCP-REVIEW-2.4: Feedback identifies tests that reference non-existent requirements
128
128
  * - MCP-REVIEW-2.5: When cloud credentials are unavailable, an error explains how to authenticate
@@ -23,7 +23,7 @@ export interface SearchRequirementsArgs {
23
23
  * - MCP-SEARCH-1.3: useRegex interprets query as case-insensitive regex
24
24
  * - MCP-SEARCH-1.4: Invalid regex returns error
25
25
  * - MCP-SEARCH-1.5: No matches returns informative message
26
- * - MCP-SEARCH-1.6: Nested match returns root requirement
26
+ * - MCP-SEARCH-1.6: Nested match returns the full root requirement tree
27
27
  * - MCP-SEARCH-1.7: Results include full tree as code block
28
28
  */
29
29
  export declare function handleSearchRequirements(args: SearchRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
@@ -16,7 +16,7 @@ import { errorResponse, textResponse } from "./types.js";
16
16
  * - MCP-SEARCH-1.3: useRegex interprets query as case-insensitive regex
17
17
  * - MCP-SEARCH-1.4: Invalid regex returns error
18
18
  * - MCP-SEARCH-1.5: No matches returns informative message
19
- * - MCP-SEARCH-1.6: Nested match returns root requirement
19
+ * - MCP-SEARCH-1.6: Nested match returns the full root requirement tree
20
20
  * - MCP-SEARCH-1.7: Results include full tree as code block
21
21
  */
22
22
  export async function handleSearchRequirements(args, context) {
package/dist/mcp/index.js CHANGED
@@ -523,11 +523,40 @@ After writing tests:
523
523
  }
524
524
  throw new Error(`Unknown prompt: ${name}`);
525
525
  });
526
+ /**
527
+ * MCP-ARGS-1: find required arguments (per the tool's inputSchema) that are
528
+ * missing from a call, so we can fail with a self-explaining error before the
529
+ * handler runs — a missing argument must never surface as an internal crash.
530
+ */
531
+ function findMissingRequiredArgs(toolName, args) {
532
+ const tool = tools.find((t) => t.name === toolName);
533
+ if (!tool)
534
+ return undefined; // unknown tools get their own error downstream
535
+ const schema = tool.inputSchema;
536
+ const required = schema.required ?? [];
537
+ const missing = required.filter((key) => args?.[key] === undefined || args?.[key] === null);
538
+ if (missing.length === 0)
539
+ return undefined;
540
+ return { missing, accepted: Object.keys(schema.properties ?? {}) };
541
+ }
526
542
  // Handle tool calls
527
543
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
528
544
  const { name, arguments: args } = request.params;
529
545
  // Refresh cache for each request to pick up changes
530
546
  invalidateCache();
547
+ // MCP-ARGS-1.2: reject calls missing required arguments before dispatch
548
+ const argCheck = findMissingRequiredArgs(name, args);
549
+ if (argCheck) {
550
+ return {
551
+ content: [
552
+ {
553
+ type: "text",
554
+ text: `Missing required argument(s) for ${name}: ${argCheck.missing.join(", ")}. Accepted arguments: ${argCheck.accepted.join(", ")}.`,
555
+ },
556
+ ],
557
+ isError: true,
558
+ };
559
+ }
531
560
  // Create handler context for extracted handlers
532
561
  const handlerContext = {
533
562
  getRequirements,
@@ -72,6 +72,13 @@ export interface DryRunResult {
72
72
  conflicts: ConflictInfo[];
73
73
  totalRequirements: number;
74
74
  }
75
+ /**
76
+ * Outcome of run-marker handling for one pushed file (IMPORT-2/3).
77
+ * Mirrors the server's ImportStatus.
78
+ */
79
+ export type ImportStatus = "imported" | "invalid_marker" | "unsupported_version" | "already_used" | "ignored_update" | "none";
80
+ /** Base URL of the web app, where synced documents are reviewed. */
81
+ export declare const WEB_APP_URL = "https://app.dotrequirements.io";
75
82
  /**
76
83
  * Result of the execute phase.
77
84
  */
@@ -82,6 +89,17 @@ export interface PushResult {
82
89
  fileName: string;
83
90
  error: string;
84
91
  }>;
92
+ /** IMPORT-3: marker failures that must be surfaced to the user, per file */
93
+ importWarnings: Array<{
94
+ fileName: string;
95
+ status: Extract<ImportStatus, "invalid_marker" | "unsupported_version" | "already_used">;
96
+ }>;
97
+ /** SYNC-LAND-1: each successfully synced document, with its web URL */
98
+ synced: Array<{
99
+ fileName: string;
100
+ documentId: string;
101
+ url: string;
102
+ }>;
85
103
  }
86
104
  /**
87
105
  * Extract markdown content from a file, stripping YAML frontmatter.