@popoverai/dotrequirements 0.25.0 → 0.26.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.
- package/dist/cli.js +8 -1
- package/dist/codebase-to-spec/area-name.d.ts +13 -0
- package/dist/codebase-to-spec/area-name.js +18 -0
- package/dist/codebase-to-spec/cache.d.ts +31 -0
- package/dist/codebase-to-spec/cache.js +29 -1
- package/dist/codebase-to-spec/compose.d.ts +7 -4
- package/dist/codebase-to-spec/compose.js +10 -21
- package/dist/codebase-to-spec/dispatch.js +1 -1
- package/dist/codebase-to-spec/present.d.ts +9 -0
- package/dist/codebase-to-spec/present.js +30 -4
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +3 -2
- package/dist/codebase-to-spec/prompts/planner-initial.js +3 -2
- package/dist/codebase-to-spec/renumber.d.ts +52 -0
- package/dist/codebase-to-spec/renumber.js +105 -0
- package/dist/codebase-to-spec/schemas.d.ts +35 -240
- package/dist/codebase-to-spec/schemas.js +5 -173
- package/dist/codebase-to-spec/skill-install.d.ts +16 -6
- package/dist/codebase-to-spec/skill-install.js +109 -47
- package/dist/codebase-to-spec/version-check.d.ts +31 -0
- package/dist/codebase-to-spec/version-check.js +56 -0
- package/dist/commands/ai-setup.d.ts +12 -1
- package/dist/commands/ai-setup.js +65 -33
- package/dist/commands/codebase-to-spec/dispatch-editor.js +1 -1
- package/dist/commands/codebase-to-spec/dispatch-spec.js +1 -1
- package/dist/commands/codebase-to-spec/index.js +7 -103
- package/dist/commands/codebase-to-spec/pack.d.ts +4 -0
- package/dist/commands/codebase-to-spec/pack.js +25 -1
- package/dist/commands/codebase-to-spec/present-orchestrator.d.ts +3 -1
- package/dist/commands/codebase-to-spec/present-orchestrator.js +11 -4
- package/dist/commands/init.js +6 -1
- package/dist/commands/link-resolution.d.ts +79 -0
- package/dist/commands/link-resolution.js +141 -0
- package/dist/commands/link.d.ts +14 -4
- package/dist/commands/link.js +369 -16
- package/dist/commands/pull.js +19 -2
- package/dist/commands/push.js +36 -2
- package/dist/convex.d.ts +5 -3
- package/dist/convex.js +5 -3
- package/dist/harness/cache.d.ts +0 -14
- package/dist/harness/cache.js +1 -41
- package/dist/harness/finalize.js +2 -2
- package/dist/harness/prepare.js +1 -3
- package/dist/harness/requirementsLoader.d.ts +3 -3
- package/dist/harness/requirementsLoader.js +13 -8
- package/dist/mcp/handlers/authoring.d.ts +5 -5
- package/dist/mcp/handlers/authoring.js +9 -9
- package/dist/mcp/handlers/push.d.ts +2 -2
- package/dist/mcp/handlers/push.js +36 -3
- package/dist/mcp/handlers/review.d.ts +4 -4
- package/dist/mcp/handlers/review.js +4 -4
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +1 -1
- package/dist/mcp/index.js +29 -0
- package/dist/push/core.d.ts +18 -0
- package/dist/push/core.js +70 -3
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +1 -1
- package/dist/schema/parser-core.js +5 -1
- package/dist/schema/parser.js +5 -1
- package/dist/schema/run-marker.d.ts +38 -0
- package/dist/schema/run-marker.js +138 -0
- package/dist/schema/schemas.d.ts +12 -0
- package/dist/schema/schemas.js +1 -0
- package/dist/templates/agents/cts-worker.md +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +39 -11
- package/dist/templates/workflows/specify-codebase.js +4 -2
- package/dist/utils/own-package.d.ts +10 -0
- package/dist/utils/own-package.js +13 -0
- package/dist/utils/project-selector.d.ts +5 -0
- package/dist/utils/project-selector.js +4 -0
- package/package.json +2 -2
- package/dist/codebase-to-spec/edit-loop.d.ts +0 -54
- package/dist/codebase-to-spec/edit-loop.js +0 -195
- package/dist/codebase-to-spec/editor.d.ts +0 -54
- package/dist/codebase-to-spec/editor.js +0 -74
- package/dist/codebase-to-spec/fan-out.d.ts +0 -63
- package/dist/codebase-to-spec/fan-out.js +0 -215
- package/dist/codebase-to-spec/outline-review-loop.d.ts +0 -51
- package/dist/codebase-to-spec/outline-review-loop.js +0 -187
- package/dist/codebase-to-spec/planner.d.ts +0 -41
- package/dist/codebase-to-spec/planner.js +0 -76
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +0 -12
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +0 -89
- package/dist/codebase-to-spec/slice.d.ts +0 -49
- package/dist/codebase-to-spec/slice.js +0 -111
- package/dist/codebase-to-spec/specifier.d.ts +0 -60
- package/dist/codebase-to-spec/specifier.js +0 -85
- package/dist/codebase-to-spec/summary.d.ts +0 -51
- package/dist/codebase-to-spec/summary.js +0 -183
- package/dist/commands/codebase-to-spec/compose.d.ts +0 -14
- package/dist/commands/codebase-to-spec/compose.js +0 -57
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +0 -16
- package/dist/commands/codebase-to-spec/edit-loop.js +0 -83
- package/dist/commands/codebase-to-spec/fan-out.d.ts +0 -19
- package/dist/commands/codebase-to-spec/fan-out.js +0 -77
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +0 -26
- package/dist/commands/codebase-to-spec/plan-loop.js +0 -105
- package/dist/commands/codebase-to-spec/present.d.ts +0 -26
- package/dist/commands/codebase-to-spec/present.js +0 -97
- package/dist/commands/codebase-to-spec/run.d.ts +0 -20
- package/dist/commands/codebase-to-spec/run.js +0 -86
- package/dist/commands/codebase-to-spec/specify-area.d.ts +0 -18
- package/dist/commands/codebase-to-spec/specify-area.js +0 -82
package/dist/harness/cache.js
CHANGED
|
@@ -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
|
*/
|
package/dist/harness/finalize.js
CHANGED
|
@@ -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())) {
|
package/dist/harness/prepare.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
|
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.
|
|
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 {
|
|
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.
|
|
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:
|
|
37
|
-
return
|
|
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(
|
|
101
|
-
"
|
|
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
|
|
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.
|
|
36
|
-
* - MCP-AUTHOR-2.
|
|
37
|
-
* - MCP-AUTHOR-2.
|
|
38
|
-
* - MCP-AUTHOR-2.
|
|
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
|
|
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.
|
|
57
|
-
* - MCP-AUTHOR-2.
|
|
58
|
-
* - MCP-AUTHOR-2.
|
|
59
|
-
* - MCP-AUTHOR-2.
|
|
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.
|
|
63
|
+
// MCP-AUTHOR-2.3: Validation works offline - just use workspaceRoot
|
|
64
64
|
const fullPath = resolve(context.workspaceRoot, filePath);
|
|
65
|
-
// MCP-AUTHOR-2.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
42
|
-
* - MCP-REVIEW-2.1:
|
|
43
|
-
* - MCP-REVIEW-2.2:
|
|
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
|
|
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:
|
|
124
|
-
* - MCP-REVIEW-2.1:
|
|
125
|
-
* - MCP-REVIEW-2.2:
|
|
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,
|
package/dist/push/core.d.ts
CHANGED
|
@@ -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.
|
package/dist/push/core.js
CHANGED
|
@@ -14,6 +14,8 @@ import * as path from "node:path";
|
|
|
14
14
|
import { ConvexHttpClient } from "convex/browser";
|
|
15
15
|
import { api } from "../convex.js";
|
|
16
16
|
import { buildRequirementsFile, getAllRequirements, parseRequirementKey, parseRequirementsFromFile, } from "../schema/index.js";
|
|
17
|
+
/** Base URL of the web app, where synced documents are reviewed. */
|
|
18
|
+
export const WEB_APP_URL = "https://app.dotrequirements.io";
|
|
17
19
|
// ============================================================================
|
|
18
20
|
// Parsing
|
|
19
21
|
// ============================================================================
|
|
@@ -170,13 +172,15 @@ export async function executePush(dryRunResult, credentials) {
|
|
|
170
172
|
let created = 0;
|
|
171
173
|
let updated = 0;
|
|
172
174
|
const errors = [];
|
|
175
|
+
const importWarnings = [];
|
|
176
|
+
const synced = [];
|
|
173
177
|
for (const { file, result } of pushableFiles) {
|
|
174
178
|
const doc = file.metadata.document;
|
|
175
179
|
const fileName = path.basename(file.filePath);
|
|
176
180
|
// For not_found, clear the ID so we create a new document
|
|
177
181
|
const effectiveDocId = result.action === "not_found" ? undefined : doc.id;
|
|
178
182
|
try {
|
|
179
|
-
const
|
|
183
|
+
const rawResult = (await client.mutation(api.documents.saveWithRequirements.saveWithRequirements, {
|
|
180
184
|
projectAuth: {
|
|
181
185
|
projectSlug: credentials.projectId,
|
|
182
186
|
projectSecret: credentials.projectSecret,
|
|
@@ -186,8 +190,21 @@ export async function executePush(dryRunResult, credentials) {
|
|
|
186
190
|
title: doc.title,
|
|
187
191
|
markdownContent: file.markdownContent,
|
|
188
192
|
defaultPrefix: doc.defaultPrefix,
|
|
193
|
+
// IMPORT-2: forward the CTS run marker when the file is stamped
|
|
194
|
+
runMarker: file.metadata.ctsRun,
|
|
189
195
|
dryRun: false,
|
|
190
196
|
}));
|
|
197
|
+
// The server returns a structured result iff we sent a runMarker
|
|
198
|
+
const pushResult = typeof rawResult === "string" ? rawResult : rawResult.documentId;
|
|
199
|
+
if (typeof rawResult !== "string") {
|
|
200
|
+
const status = rawResult.importStatus;
|
|
201
|
+
if (status === "invalid_marker" ||
|
|
202
|
+
status === "unsupported_version" ||
|
|
203
|
+
status === "already_used") {
|
|
204
|
+
// IMPORT-3.2/3.3: loud, never fatal
|
|
205
|
+
importWarnings.push({ fileName, status });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
191
208
|
const isCreate = result.action === "create" || result.action === "not_found";
|
|
192
209
|
if (isCreate) {
|
|
193
210
|
// New document - write ID back to file
|
|
@@ -206,11 +223,61 @@ export async function executePush(dryRunResult, credentials) {
|
|
|
206
223
|
fs.writeFileSync(file.filePath, updatedContent, "utf-8");
|
|
207
224
|
updated++;
|
|
208
225
|
}
|
|
226
|
+
// SYNC-LAND-1: every synced document gets its web URL in the result
|
|
227
|
+
synced.push({
|
|
228
|
+
fileName,
|
|
229
|
+
documentId: pushResult,
|
|
230
|
+
url: `${WEB_APP_URL}/documents/${pushResult}`,
|
|
231
|
+
});
|
|
209
232
|
}
|
|
210
233
|
catch (err) {
|
|
211
|
-
errors.push({ fileName, error: err
|
|
234
|
+
errors.push({ fileName, error: errorDisplayMessage(err) });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { created, updated, errors, importWarnings, synced };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Servers throw ConvexError({kind, message}) for expected failures (e.g.
|
|
241
|
+
* LIMITS-4.2's limit error); the client-side Error message embeds that data
|
|
242
|
+
* as JSON. Surface the human-readable message it carries instead of the blob.
|
|
243
|
+
*/
|
|
244
|
+
function errorDisplayMessage(err) {
|
|
245
|
+
const data = err.data;
|
|
246
|
+
const fromData = humanMessage(data);
|
|
247
|
+
if (fromData)
|
|
248
|
+
return fromData;
|
|
249
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
250
|
+
// ConvexError messages may embed the data JSON directly or after a
|
|
251
|
+
// "ConvexError:" prefix — try the trailing {...} chunk
|
|
252
|
+
const jsonStart = message.indexOf("{");
|
|
253
|
+
if (jsonStart !== -1) {
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(message.slice(jsonStart));
|
|
256
|
+
const fromMessage = humanMessage(parsed);
|
|
257
|
+
if (fromMessage)
|
|
258
|
+
return fromMessage;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// fall through to the raw message
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return message;
|
|
265
|
+
}
|
|
266
|
+
function humanMessage(data) {
|
|
267
|
+
if (typeof data === "string") {
|
|
268
|
+
try {
|
|
269
|
+
return humanMessage(JSON.parse(data));
|
|
212
270
|
}
|
|
271
|
+
catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (data &&
|
|
276
|
+
typeof data === "object" &&
|
|
277
|
+
"message" in data &&
|
|
278
|
+
typeof data.message === "string") {
|
|
279
|
+
return data.message;
|
|
213
280
|
}
|
|
214
|
-
return
|
|
281
|
+
return undefined;
|
|
215
282
|
}
|
|
216
283
|
//# sourceMappingURL=core.js.map
|
package/dist/push/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Shared push logic for syncing local requirements files to the cloud.
|
|
5
5
|
* Used by both CLI push command and MCP push_requirements tool.
|
|
6
6
|
*/
|
|
7
|
-
export { type CloudDocumentMetadata, type ConflictInfo, type DryRunResult, type DryRunResultItem, dryRunPush, executePush, extractMarkdownContent, type FileWithDryRun, type ParsedFile, type PushCredentials, type PushResult, parseFilesForPush, } from "./core.js";
|
|
7
|
+
export { type CloudDocumentMetadata, type ConflictInfo, type DryRunResult, type DryRunResultItem, dryRunPush, executePush, extractMarkdownContent, type FileWithDryRun, type ParsedFile, type PushCredentials, type PushResult, parseFilesForPush, WEB_APP_URL, } from "./core.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/push/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This module contains pure parsing functions with no Node.js dependencies,
|
|
5
5
|
* making it safe to import in Convex runtime or browser environments.
|
|
6
6
|
*/
|
|
7
|
-
import { ValidationError, } from "./schemas.js";
|
|
7
|
+
import { ValidationError, validateKey, } from "./schemas.js";
|
|
8
8
|
/**
|
|
9
9
|
* Default delimiter for requirements.
|
|
10
10
|
* Can be overridden for organization-specific preferences.
|
|
@@ -186,6 +186,10 @@ export function extractRequirementBlocks(body) {
|
|
|
186
186
|
throw new ValidationError(`Invalid requirement block format - first line must be "KEY: content"`, blockContent.substring(0, 50));
|
|
187
187
|
}
|
|
188
188
|
const key = firstLineMatch[1];
|
|
189
|
+
// SYNC-KEY-1: reject malformed keys at parse time so push/validate fail
|
|
190
|
+
// locally instead of erroring server-side. Validation only — the original
|
|
191
|
+
// key is preserved (normalization happens downstream).
|
|
192
|
+
validateKey(key);
|
|
189
193
|
blocks.push({ key, blockContent: blockContent.trim() });
|
|
190
194
|
match = blockRegex.exec(body);
|
|
191
195
|
}
|
package/dist/schema/parser.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import YAML from "yaml";
|
|
6
|
-
import { ValidationError, validateMetadata, } from "./schemas.js";
|
|
6
|
+
import { ValidationError, validateKey, validateMetadata, } from "./schemas.js";
|
|
7
7
|
const DELIMITER_PATTERN = "(?:→|->)"; // Non-capturing group for both Unicode and ASCII
|
|
8
8
|
/**
|
|
9
9
|
* Parse a criterion line in "position. Label → content" format.
|
|
@@ -198,6 +198,10 @@ function extractRequirementBlocks(body) {
|
|
|
198
198
|
throw new ValidationError(`Invalid requirement block format - first line must be "KEY: content"`, blockContent.substring(0, 50));
|
|
199
199
|
}
|
|
200
200
|
const key = firstLineMatch[1];
|
|
201
|
+
// SYNC-KEY-1: reject malformed keys at parse time so push/validate fail
|
|
202
|
+
// locally instead of erroring server-side. Validation only — the original
|
|
203
|
+
// key is preserved (normalization happens downstream).
|
|
204
|
+
validateKey(key);
|
|
201
205
|
// Try to find a heading immediately before this block for the title
|
|
202
206
|
// Look backwards from the match position to find the nearest heading
|
|
203
207
|
const textBeforeBlock = body.substring(0, match.index);
|