@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
@@ -11,27 +11,47 @@
11
11
  *
12
12
  * If the project has a `.requirements/STYLE.md`, callers can pass its
13
13
  * contents via `localStyleGuide` to use that body in place of the bundled
14
- * defaults. See {@link readLocalStyleGuide} for the lookup helper.
14
+ * defaults. See `readLocalStyleGuide` in ./style-guide-file.ts (CLI-only —
15
+ * this module stays free of node:fs so the web can serve the same primer).
15
16
  */
16
- import { existsSync, readFileSync } from "node:fs";
17
- import { join } from "node:path";
17
+ import { ROOT_LABEL_MARKER } from "../schema/parser-core.js";
18
18
  /**
19
- * Conventional location of a project's STYLE.md, relative to the workspace
20
- * root. Exported so callers (init, push/pull, tests) can reference one place.
19
+ * How many discovered labels or key prefixes the guide will show.
20
+ *
21
+ * These lists exist under "match the existing pattern", and past ten that
22
+ * instruction stops being actionable — a reader can't match a hundred and forty
23
+ * things. Worse, an uncapped list presents whatever sprawl a codebase has
24
+ * accumulated as the house style, so each new author reproduces it and the list
25
+ * grows: the mechanism meant to hold conventions steady is what compounds their
26
+ * drift. Ten is a sample, and the trailing count says so rather than letting the
27
+ * truncation read as the whole set.
21
28
  */
22
- export const STYLE_MD_PATH = ".requirements/STYLE.md";
29
+ const SAMPLE_LIMIT = 10;
23
30
  /**
24
- * Read `.requirements/STYLE.md` if the workspace has one. Returns the file
25
- * contents on success, `null` if the file is absent. Empty / whitespace-only
26
- * files are treated as absent so the bundled defaults still apply.
31
+ * The most-used values first, not the alphabetically-first.
32
+ *
33
+ * Sorting by name and taking the head is a trap: on this codebase it yields ten
34
+ * prefixes all beginning with "A", which under "match the existing pattern"
35
+ * teaches a convention nobody has. Frequency is what the phrase actually means —
36
+ * the prefixes carrying the most requirements are the house style. Ties break
37
+ * alphabetically so the sample is stable across runs.
27
38
  */
28
- export function readLocalStyleGuide(workspaceRoot) {
29
- const fullPath = join(workspaceRoot, STYLE_MD_PATH);
30
- if (!existsSync(fullPath)) {
31
- return null;
32
- }
33
- const contents = readFileSync(fullPath, "utf-8");
34
- return contents.trim().length > 0 ? contents : null;
39
+ function sample(counts) {
40
+ const ranked = Array.from(counts.entries())
41
+ .sort(([aName, aCount], [bName, bCount]) => bCount === aCount ? aName.localeCompare(bName) : bCount - aCount)
42
+ .map(([name]) => name);
43
+ return {
44
+ list: ranked
45
+ .slice(0, SAMPLE_LIMIT)
46
+ .map((v) => `"${v}"`)
47
+ .join(", "),
48
+ more: ranked.length > SAMPLE_LIMIT
49
+ ? ` (and ${ranked.length - SAMPLE_LIMIT} more)`
50
+ : "",
51
+ };
52
+ }
53
+ function tally(counts, key) {
54
+ counts.set(key, (counts.get(key) ?? 0) + 1);
35
55
  }
36
56
  /**
37
57
  * Build the body of the bundled default style guide. This is the content
@@ -46,43 +66,45 @@ export function readLocalStyleGuide(workspaceRoot) {
46
66
  */
47
67
  export function generateStyleGuideBody(params) {
48
68
  const { requirements, customStyleGuidance } = params;
49
- const labels = new Set();
50
- const keyPrefixes = new Set();
69
+ const labels = new Map();
70
+ const keyPrefixes = new Map();
51
71
  for (const req of requirements) {
52
- if (req.label?.trim()) {
53
- labels.add(req.label);
72
+ // The root marker is on every root in every workspace and chosen by nobody,
73
+ // so counting it would hand back a storage detail as house style. Worst for
74
+ // a project that took this guide's own advice to leave criteria unlabelled:
75
+ // it would be the only label found, and would suppress the "default to
76
+ // unlabeled" branch below in favour of matching a label nobody types.
77
+ if (req.label?.trim() && req.label !== ROOT_LABEL_MARKER) {
78
+ tally(labels, req.label);
54
79
  }
55
80
  if (req.path.length === 0 && req.id) {
56
81
  const lastDashIndex = req.id.lastIndexOf("-");
57
82
  if (lastDashIndex > 0) {
58
- keyPrefixes.add(req.id.substring(0, lastDashIndex));
83
+ tally(keyPrefixes, req.id.substring(0, lastDashIndex));
59
84
  }
60
85
  }
61
86
  }
62
- const discoveredLabels = Array.from(labels).sort();
63
- const discoveredPrefixes = Array.from(keyPrefixes).sort();
64
87
  let labelGuidance;
65
- if (discoveredLabels.length > 0) {
66
- const labelList = discoveredLabels
67
- .slice(0, 10)
68
- .map((l) => `"${l}"`)
69
- .join(", ");
70
- const more = discoveredLabels.length > 10
71
- ? ` (and ${discoveredLabels.length - 10} more)`
72
- : "";
88
+ if (labels.size > 0) {
89
+ const { list: labelList, more } = sample(labels);
73
90
  labelGuidance = `**Existing labels in this codebase:** ${labelList}${more}
74
91
 
75
92
  **Use these existing labels** to maintain consistency. If you're unsure which labels to use for a new requirement, ask the user.`;
76
93
  }
77
94
  else {
78
- labelGuidance = `**No existing requirements found in this codebase.**
95
+ // Says *labels*, not requirements. Reaching here means nobody has labelled
96
+ // a criterion — which is what a project following the advice below looks
97
+ // like, not an empty one. The key-prefix branch shares this sentence and is
98
+ // right to: there, an empty set really does mean no requirements. Here it
99
+ // would contradict the prefix list printed two headings above.
100
+ labelGuidance = `**No labelled criteria found in this codebase.**
79
101
 
80
102
  **Default to unlabeled requirements** (\`0. → content\`). If the user wants labels, ask them which format they prefer. Do not choose an opinionated framework like Given/When/Then without explicit user consent.`;
81
103
  }
82
104
  let keyGuidance;
83
- if (discoveredPrefixes.length > 0) {
84
- const prefixList = discoveredPrefixes.map((p) => `"${p}"`).join(", ");
85
- keyGuidance = `**Existing requirement key prefixes in this codebase:** ${prefixList}
105
+ if (keyPrefixes.size > 0) {
106
+ const { list: prefixList, more } = sample(keyPrefixes);
107
+ keyGuidance = `**Existing requirement key prefixes in this codebase:** ${prefixList}${more}
86
108
 
87
109
  **Match the existing pattern** when creating new requirement keys. Use the same domain prefixes and sequential numbering style.`;
88
110
  }
@@ -6,7 +6,7 @@
6
6
  export { buildRequirementMarkdown, buildRequirementsMarkdown, DEFAULT_DELIMITER, } from "./builder.js";
7
7
  export type { ConvexRequirement } from "./conversions.js";
8
8
  export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
9
- export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, } from "./parser-core.js";
9
+ export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, ROOT_LABEL_MARKER, } from "./parser-core.js";
10
10
  export type { Metadata, ParsedCriterion, RequirementKey, RequirementNode, RequirementPrefix, RequirementsFile, } from "./schemas.js";
11
11
  export { buildRequirementKey, MetadataSchema, normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN, REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema, ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
12
12
  export type { BuildScenarioOptions, Scenario, ScenarioAssertionSource, ScenarioStep, } from "./scenario.js";
@@ -8,7 +8,10 @@ export { buildRequirementMarkdown, buildRequirementsMarkdown, DEFAULT_DELIMITER,
8
8
  export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
9
9
  // Parser core (pure TypeScript - browser-safe, no fs dependency)
10
10
  // These functions parse markdown strings directly without file I/O
11
- export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, } from "./parser-core.js";
11
+ export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine,
12
+ // The web reaches parser-core only through this entry point, so without this
13
+ // its readers have no way to import the marker and must hard-code it.
14
+ ROOT_LABEL_MARKER, } from "./parser-core.js";
12
15
  // Schemas and types (uses zod - browser-safe)
13
16
  export { buildRequirementKey,
14
17
  // Zod schemas
@@ -5,6 +5,21 @@
5
5
  * making it safe to import in Convex runtime or browser environments.
6
6
  */
7
7
  import { type ParsedCriterion, type RequirementNode } from "./schemas.js";
8
+ /**
9
+ * The label stamped on every requirement's root, for indexing and querying.
10
+ *
11
+ * An internal marker, not a convention: no author writes it, the markdown
12
+ * format has no syntax for it, and consumers that show labels to a human or an
13
+ * agent must exclude it — the harness's output filters, Convex's label queries,
14
+ * and the style guide's discovered-label list all do.
15
+ *
16
+ * Declared here because this is where parsing stamps it, but be warned: most of
17
+ * those readers still hard-code the literal, and `web/lib/markdown-conversion`
18
+ * stamps it independently for the BlockNote path. So renaming this does NOT yet
19
+ * move everyone with it — you would have to grep. Each reader migrated here is
20
+ * one that a rename can no longer silently strand.
21
+ */
22
+ export declare const ROOT_LABEL_MARKER = "requirementHeader";
8
23
  /**
9
24
  * Default delimiter for requirements.
10
25
  * Can be overridden for organization-specific preferences.
@@ -15,6 +30,19 @@ export declare const DELIMITER_PATTERN = "(?:\u2192|->)";
15
30
  * Parse a criterion line in "position. Label → content" format.
16
31
  * Example: "0. Given → user has valid credentials"
17
32
  * Also supports optional label: "0. → user has valid credentials"
33
+ *
34
+ * This is the canonical criterion grammar (SYNC-KEY-1.4). The web editor's
35
+ * markdown-conversion parser mirrors this exact behavior; keep them in sync.
36
+ *
37
+ * Two behaviors are load-bearing:
38
+ * - Split on the FIRST delimiter only, so an arrow inside the content
39
+ * ("maps A -> B") is preserved verbatim rather than being re-consumed as a
40
+ * label boundary. Everything before the first delimiter is the (optional,
41
+ * freeform — custom labels are supported, see MARKDOWN_SCHEMA) label;
42
+ * everything after is content.
43
+ * - An empty criterion ("0. → ") is a valid criterion with content "", not a
44
+ * parse failure. Returning null here would make parseRequirementBlocksFromMarkdown
45
+ * reject the whole block, and would drop the criterion (and its subtree) on load.
18
46
  */
19
47
  export declare function parseCriterionLine(line: string, delimiter?: string): ParsedCriterion | null;
20
48
  /**
@@ -5,30 +5,69 @@
5
5
  * making it safe to import in Convex runtime or browser environments.
6
6
  */
7
7
  import { ValidationError, validateKey, } from "./schemas.js";
8
+ /**
9
+ * The label stamped on every requirement's root, for indexing and querying.
10
+ *
11
+ * An internal marker, not a convention: no author writes it, the markdown
12
+ * format has no syntax for it, and consumers that show labels to a human or an
13
+ * agent must exclude it — the harness's output filters, Convex's label queries,
14
+ * and the style guide's discovered-label list all do.
15
+ *
16
+ * Declared here because this is where parsing stamps it, but be warned: most of
17
+ * those readers still hard-code the literal, and `web/lib/markdown-conversion`
18
+ * stamps it independently for the BlockNote path. So renaming this does NOT yet
19
+ * move everyone with it — you would have to grep. Each reader migrated here is
20
+ * one that a rename can no longer silently strand.
21
+ */
22
+ export const ROOT_LABEL_MARKER = "requirementHeader";
8
23
  /**
9
24
  * Default delimiter for requirements.
10
25
  * Can be overridden for organization-specific preferences.
11
26
  */
12
27
  export const DEFAULT_DELIMITER = "→";
13
28
  export const DELIMITER_PATTERN = "(?:→|->)"; // Non-capturing group for both Unicode and ASCII
29
+ // Compiled once for the common (default-delimiter) path so parseCriterionLine
30
+ // doesn't rebuild the same RegExp on every line.
31
+ const DEFAULT_DELIMITER_REGEX = new RegExp(DELIMITER_PATTERN);
14
32
  /**
15
33
  * Parse a criterion line in "position. Label → content" format.
16
34
  * Example: "0. Given → user has valid credentials"
17
35
  * Also supports optional label: "0. → user has valid credentials"
36
+ *
37
+ * This is the canonical criterion grammar (SYNC-KEY-1.4). The web editor's
38
+ * markdown-conversion parser mirrors this exact behavior; keep them in sync.
39
+ *
40
+ * Two behaviors are load-bearing:
41
+ * - Split on the FIRST delimiter only, so an arrow inside the content
42
+ * ("maps A -> B") is preserved verbatim rather than being re-consumed as a
43
+ * label boundary. Everything before the first delimiter is the (optional,
44
+ * freeform — custom labels are supported, see MARKDOWN_SCHEMA) label;
45
+ * everything after is content.
46
+ * - An empty criterion ("0. → ") is a valid criterion with content "", not a
47
+ * parse failure. Returning null here would make parseRequirementBlocksFromMarkdown
48
+ * reject the whole block, and would drop the criterion (and its subtree) on load.
18
49
  */
19
50
  export function parseCriterionLine(line, delimiter = DELIMITER_PATTERN) {
20
- // Match: position + period + space + optional(label + space) + delimiter + space + content
21
- // Position can be: 0, 1.0, 2.3.1, etc.
22
- // Label can contain any characters except the delimiter
23
- const pattern = new RegExp(`^\\s*(\\d+(?:\\.\\d+)*)\\.\\s*(?:(.+?)\\s+)?${delimiter}\\s*(.+)$`);
24
- const match = line.match(pattern);
25
- if (!match) {
51
+ // Position prefix: "0.", "1.0.", "2.3.1." — required for a criterion line.
52
+ const positionMatch = line.match(/^\s*(\d+(?:\.\d+)*)\.\s*/);
53
+ if (!positionMatch) {
54
+ return null;
55
+ }
56
+ const rest = line.slice(positionMatch[0].length);
57
+ const delimiterMatch = rest.match(delimiter === DELIMITER_PATTERN
58
+ ? DEFAULT_DELIMITER_REGEX
59
+ : new RegExp(delimiter));
60
+ if (!delimiterMatch || delimiterMatch.index === undefined) {
26
61
  return null;
27
62
  }
63
+ const label = rest.slice(0, delimiterMatch.index).trim();
64
+ const content = rest
65
+ .slice(delimiterMatch.index + delimiterMatch[0].length)
66
+ .trim();
28
67
  return {
29
- position: match[1],
30
- label: match[2] ? match[2].trim() : "", // Preserve original case, empty if no label
31
- content: match[3].trim(),
68
+ position: positionMatch[1],
69
+ label, // Preserve original case; freeform (empty when no label)
70
+ content, // May be "" for an empty criterion
32
71
  };
33
72
  }
34
73
  /**
@@ -79,9 +118,9 @@ export function parseRequirementBlock(key, blockContent) {
79
118
  }
80
119
  const root = {
81
120
  id: key,
82
- // Root requirements always use "requirementHeader" label for indexing/querying
83
- // The markdown format doesn't preserve root labels, but we standardize on this value
84
- label: "requirementHeader",
121
+ // Root requirements always use this label for indexing/querying. The
122
+ // markdown format doesn't preserve root labels, so we standardize on it.
123
+ label: ROOT_LABEL_MARKER,
85
124
  content: rootParsed.content,
86
125
  children: [],
87
126
  };
@@ -10,13 +10,14 @@ requirements, so your plan should too.
10
10
 
11
11
  **ALWAYS follow this workflow when changing system behavior** (new features, bug fixes, any behavioral change). Only pure refactoring (same behavior, different code) may skip requirements.
12
12
 
13
- 1. **Find or write requirements** - Check `.requirements/` for existing specs; write new ones if needed
14
- 2. **Style-check** - Run `mcp__dotrequirements__style_check` on the file
15
- 3. **Get approval** - Present requirements, wait for go-ahead
16
- 4. **Implement** - Build the feature
17
- 5. **Write tests** - Reference requirements with `requirement()`
18
- 6. **Run tests** - Verify everything passes
19
- 7. **Review tests** - Run `mcp__dotrequirements__review_test` to validate coverage
13
+ 1. **Find or write requirements** - Check `.requirements/` for existing specs (`dotreq search`, `dotreq get`, `dotreq list`); write new ones if needed (`dotreq create-requirement-document` prints the template and style guide)
14
+ 2. **Style-review** - Dispatch a subagent to run `dotreq style-check <file>` and judge the requirements against the style guide it emits. If this platform cannot dispatch subagents, run the command and perform the review in this conversation.
15
+ 3. **Validate** - Run `dotreq validate` to check syntax
16
+ 4. **Get approval** - Present requirements, wait for go-ahead
17
+ 5. **Implement** - Build the feature
18
+ 6. **Write tests** - Reference requirements with `requirement()`
19
+ 7. **Run tests** - Verify everything passes
20
+ 8. **Test-review** - Dispatch a subagent to run `dotreq review-test <test-file>` and judge the tests against the materials it emits. Same fallback: no subagent support, review in this conversation.
20
21
 
21
22
  ### Requirements Syntax
22
23
 
@@ -31,7 +32,7 @@ DOMAIN-1: Short description of expected behavior
31
32
  - Criteria: `position. -> content` (optional label before the arrow)
32
33
  - Nesting: Indent with 2 spaces, use `x.y` position paths
33
34
  - Delimiter: `->` or `→`
34
- - **Key style**: Use sequential keys with a short domain prefix (`ORCHESTRATOR-1`, `ORCHESTRATOR-2`, ...) rather than semantic keys (`AUTONOMOUS-ADVANCE`). Sequential keys stay stable when a requirement gets reworded, so test references don't break. Call `create_requirement_document` for full key-naming guidance.
35
+ - **Key style**: Use sequential keys with a short domain prefix (`ORCHESTRATOR-1`, `ORCHESTRATOR-2`, ...) rather than semantic keys (`AUTONOMOUS-ADVANCE`). Sequential keys stay stable when a requirement gets reworded, so test references don't break. Run `dotreq create-requirement-document` for full key-naming guidance.
35
36
 
36
37
  ### Test Usage
37
38
 
@@ -44,21 +45,23 @@ test(requirement('REQ-ID'), () => { /* test the requirement */ });
44
45
  test(requirement('REQ-ID.0'), () => { /* test specific criterion */ });
45
46
  ```
46
47
 
47
- ### MCP Tools
48
+ ### CLI Verbs
48
49
 
49
50
  **Exploration:**
50
- - `list_requirements` - Overview of all requirements (set `untested: true` to filter to coverage gaps)
51
- - `get_requirement` - Requirement tree with test coverage
52
- - `search_requirements` - Search by text/regex
51
+ - `dotreq list [--untested]` - Overview of all requirements (`--untested` filters to coverage gaps)
52
+ - `dotreq get <id>` - Requirement tree with test coverage
53
+ - `dotreq search <query> [--regex]` - Search by text/regex
54
+ - `dotreq requirements-for <test-file>` - See requirements a test file covers
55
+ - `dotreq tests-for <req-file>` - See test coverage for a requirements file
53
56
 
54
57
  **Authoring:**
55
- - `create_requirement_document` - Get template with format guidance
56
- - `validate_requirements` - Check syntax (works offline)
57
- - `style_check` - AI feedback on clarity
58
- - `push_requirements` - Sync to cloud
59
-
60
- **Testing:**
61
- - `get_requirements_by_test` - See requirements a test file covers
62
- - `list_requirements` (with `untested: true`) - Find requirements without tests
63
- - `report_coverage` - Get test coverage from local cache or cloud
64
- - `review_test` - Validate tests match requirement intent
58
+ - `dotreq create-requirement-document [path]` - Print the template and style guide
59
+ - `dotreq validate [glob]` - Check syntax (works offline)
60
+ - `dotreq push [file]` - Sync to cloud (shows diff preview, then confirms)
61
+
62
+ **Review (judgment runs in the dispatched subagent):**
63
+ - `dotreq style-check <file>` - Emits the style guide + content for the reviewer to judge (`--source cloud` for hosted review)
64
+ - `dotreq review-test <test-file>` - Emits referenced requirement trees + test content for the reviewer to judge (`--source cloud` for hosted review)
65
+
66
+ **Coverage:**
67
+ - `dotreq report [--source local|cloud]` - Test coverage report
@@ -3,7 +3,11 @@
3
3
  */
4
4
  export declare function getContextFileName(platform: string): string | null;
5
5
  /**
6
- * Find the git root directory
6
+ * Find the git root directory.
7
+ *
8
+ * CONTEXT-FILE-6: in a worktree, .git is a FILE pointing at the main
9
+ * checkout — matching directories only would walk past it and resolve to
10
+ * the main checkout's root, landing the context file in the wrong tree.
7
11
  */
8
12
  export declare function findGitRoot(): Promise<string | null>;
9
13
  /**
@@ -39,8 +43,8 @@ export declare function getContextFilePath(platform: string): Promise<string | n
39
43
  * Build a user-facing message explaining that context file installation
40
44
  * was skipped because the current directory is not inside a git repository.
41
45
  *
42
- * The MCP server itself is configured separately, so this message is only
43
- * about the second step (writing the platform's context file).
46
+ * Writing the platform's context file is the whole of setup now, so a missing
47
+ * git root means nothing was installed.
44
48
  */
45
49
  export declare function buildNoGitRepoMessage(fileName: string): string;
46
50
  //# sourceMappingURL=context-file.d.ts.map
@@ -11,7 +11,8 @@ const PLATFORM_CONTEXT_FILES = {
11
11
  cursor: "AGENTS.md",
12
12
  codex: "AGENTS.md",
13
13
  "github-copilot": "AGENTS.md",
14
- antigravity: "GEMINI.md",
14
+ // Antigravity 2.0 retired the GEMINI.md/.gemini conventions for AGENTS.md
15
+ antigravity: "AGENTS.md",
15
16
  };
16
17
  /**
17
18
  * Get the appropriate context file name for a platform
@@ -20,10 +21,14 @@ export function getContextFileName(platform) {
20
21
  return PLATFORM_CONTEXT_FILES[platform] ?? null;
21
22
  }
22
23
  /**
23
- * Find the git root directory
24
+ * Find the git root directory.
25
+ *
26
+ * CONTEXT-FILE-6: in a worktree, .git is a FILE pointing at the main
27
+ * checkout — matching directories only would walk past it and resolve to
28
+ * the main checkout's root, landing the context file in the wrong tree.
24
29
  */
25
30
  export async function findGitRoot() {
26
- const gitDir = await findUp(".git", { type: "directory" });
31
+ const gitDir = await findUp(".git", { type: "both" });
27
32
  return gitDir ? dirname(gitDir) : null;
28
33
  }
29
34
  /**
@@ -95,8 +100,8 @@ export async function getContextFilePath(platform) {
95
100
  * Build a user-facing message explaining that context file installation
96
101
  * was skipped because the current directory is not inside a git repository.
97
102
  *
98
- * The MCP server itself is configured separately, so this message is only
99
- * about the second step (writing the platform's context file).
103
+ * Writing the platform's context file is the whole of setup now, so a missing
104
+ * git root means nothing was installed.
100
105
  */
101
106
  export function buildNoGitRepoMessage(fileName) {
102
107
  return [
@@ -106,8 +111,6 @@ export function buildNoGitRepoMessage(fileName) {
106
111
  " To finish setup, either:",
107
112
  " • run `git init` here, then re-run `dotreq ai-setup`, or",
108
113
  " • `cd` into an existing project directory and run `dotreq ai-setup` there.",
109
- "",
110
- " Note: the MCP server itself was configured successfully — only the context file step was skipped.",
111
114
  ].join("\n");
112
115
  }
113
116
  //# sourceMappingURL=context-file.js.map
@@ -41,6 +41,7 @@ export interface ProjectInfo {
41
41
  * Returns undefined if either is missing.
42
42
  */
43
43
  export declare function getCredentialsFromEnv(): ProjectSettings | undefined;
44
+ export declare function setAuthFromEnv(enabled: boolean): void;
44
45
  /**
45
46
  * Find the project root by walking up from startDir looking for .requirements/ folder
46
47
  * Returns the directory containing .requirements/, or undefined if not found
@@ -18,6 +18,19 @@ export function getCredentialsFromEnv() {
18
18
  }
19
19
  return undefined;
20
20
  }
21
+ /**
22
+ * AUTHZ-6: explicit CI/CD credential injection. Set by the global
23
+ * --auth-from-env flag (cli.ts preAction hook); when enabled,
24
+ * getProjectCredentials reads DOTREQ_PROJECT_ID / DOTREQ_PROJECT_SECRET
25
+ * instead of file-based discovery. Without the flag those variables are
26
+ * ignored entirely — the explicit opt-in prevents credential conflicts
27
+ * between local and CI environments. (Previously the retired MCP server's
28
+ * --auth-from-env; ported to the CLI with identical semantics.)
29
+ */
30
+ let authFromEnv = false;
31
+ export function setAuthFromEnv(enabled) {
32
+ authFromEnv = enabled;
33
+ }
21
34
  /**
22
35
  * Find the project root by walking up from startDir looking for .requirements/ folder
23
36
  * Returns the directory containing .requirements/, or undefined if not found
@@ -157,6 +170,15 @@ export function getProjectInfo(startDir = process.cwd()) {
157
170
  * Get project credentials, throwing helpful error if not available
158
171
  */
159
172
  export function getProjectCredentials(startDir = process.cwd()) {
173
+ // AUTHZ-6.0 / 6.1: with the explicit flag, env credentials replace
174
+ // file-based discovery entirely
175
+ if (authFromEnv) {
176
+ const envCredentials = getCredentialsFromEnv();
177
+ if (!envCredentials) {
178
+ throw new Error("--auth-from-env requires both DOTREQ_PROJECT_ID and DOTREQ_PROJECT_SECRET environment variables to be set.");
179
+ }
180
+ return envCredentials;
181
+ }
160
182
  const info = getProjectInfo(startDir);
161
183
  if (!info) {
162
184
  throw new Error('No dotrequirements project found. Run "dotrequirements init" to create one.');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.26.2",
4
- "description": "Requirements tracking CLI, test harness, and MCP server",
3
+ "version": "0.27.1",
4
+ "description": "Requirements tracking CLI and test harness",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "dotrequirements": "./dist/cli.js",
@@ -12,7 +12,7 @@
12
12
  "./test": "./dist/harness/index.js",
13
13
  "./schema": "./dist/schema/index.js",
14
14
  "./schema/browser": "./dist/schema/browser.js",
15
- "./mcp": "./dist/mcp/index.js"
15
+ "./style-guide": "./dist/requirements/style-guide.js"
16
16
  },
17
17
  "publishConfig": {
18
18
  "access": "public"
@@ -34,7 +34,6 @@
34
34
  "test-coverage",
35
35
  "bdd",
36
36
  "tdd",
37
- "mcp",
38
37
  "ai-assistant"
39
38
  ],
40
39
  "author": "Will Raymer @Popover",
@@ -1,19 +0,0 @@
1
- /**
2
- * Production Convex deployment URL
3
- */
4
- export declare const CONVEX_URL = "https://data.dotrequirements.io";
5
- /**
6
- * Convex configuration for cloud operations
7
- */
8
- interface ConvexConfig {
9
- convexUrl: string;
10
- projectId: string;
11
- projectSecret: string;
12
- }
13
- /**
14
- * Load Convex configuration from project settings.
15
- * Returns null if credentials are not configured.
16
- */
17
- export declare function loadConvexConfig(cwd?: string): ConvexConfig | null;
18
- export {};
19
- //# sourceMappingURL=convexClient.d.ts.map
@@ -1,24 +0,0 @@
1
- import { getProjectCredentials } from "../utils/project-settings.js";
2
- /**
3
- * Production Convex deployment URL
4
- */
5
- export const CONVEX_URL = "https://data.dotrequirements.io";
6
- /**
7
- * Load Convex configuration from project settings.
8
- * Returns null if credentials are not configured.
9
- */
10
- export function loadConvexConfig(cwd = process.cwd()) {
11
- try {
12
- const credentials = getProjectCredentials(cwd);
13
- return {
14
- convexUrl: CONVEX_URL,
15
- projectId: credentials.projectId,
16
- projectSecret: credentials.projectSecret,
17
- };
18
- }
19
- catch {
20
- // Credentials not found or project not connected to cloud
21
- return null;
22
- }
23
- }
24
- //# sourceMappingURL=convexClient.js.map
@@ -1,41 +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 type { HandlerContext, ToolResponse } from "./types.js";
9
- /**
10
- * Arguments for create_requirement_document tool
11
- */
12
- export interface CreateRequirementDocumentArgs {
13
- filePath?: string;
14
- }
15
- /**
16
- * Arguments for validate_requirements tool
17
- */
18
- export interface ValidateRequirementsArgs {
19
- filePath: string;
20
- }
21
- /**
22
- * Handler for create_requirement_document tool
23
- *
24
- * Requirements covered:
25
- * - MCP-AUTHOR-1.0: The template includes format guidance with code block examples
26
- * - MCP-AUTHOR-1.1: The template includes style guidance (concrete examples, concise prose, testable conditions)
27
- * - MCP-AUTHOR-1.2: When the project has requirementsStyleContext configured, it is included in the template
28
- * - MCP-AUTHOR-1.3: When cloud credentials are unavailable, the template works without the custom context
29
- */
30
- export declare function handleCreateRequirementDocument(args: CreateRequirementDocumentArgs, context: HandlerContext): Promise<ToolResponse>;
31
- /**
32
- * Handler for validate_requirements tool
33
- *
34
- * Requirements covered:
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
- */
40
- export declare function handleValidateRequirements(args: ValidateRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
41
- //# sourceMappingURL=authoring.d.ts.map