@popoverai/dotrequirements 0.26.2 → 0.27.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 (49) 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/init.js +1 -1
  6. package/dist/commands/mcp.d.ts +8 -2
  7. package/dist/commands/mcp.js +17 -6
  8. package/dist/commands/review-test.d.ts +5 -1
  9. package/dist/commands/review-test.js +101 -7
  10. package/dist/commands/style-check.d.ts +1 -0
  11. package/dist/commands/style-check.js +137 -13
  12. package/dist/convex.d.ts +1 -3
  13. package/dist/convex.js +3 -3
  14. package/dist/requirements/cloud-ai.d.ts +21 -8
  15. package/dist/requirements/cloud-ai.js +10 -8
  16. package/dist/schema/parser-core.d.ts +13 -0
  17. package/dist/schema/parser-core.js +33 -9
  18. package/dist/templates/context-file-section.md +25 -22
  19. package/dist/utils/context-file.d.ts +7 -3
  20. package/dist/utils/context-file.js +10 -7
  21. package/dist/utils/project-settings.d.ts +1 -0
  22. package/dist/utils/project-settings.js +22 -0
  23. package/package.json +3 -5
  24. package/dist/mcp/convexClient.d.ts +0 -19
  25. package/dist/mcp/convexClient.js +0 -24
  26. package/dist/mcp/handlers/authoring.d.ts +0 -41
  27. package/dist/mcp/handlers/authoring.js +0 -113
  28. package/dist/mcp/handlers/debug.d.ts +0 -16
  29. package/dist/mcp/handlers/debug.js +0 -37
  30. package/dist/mcp/handlers/get.d.ts +0 -24
  31. package/dist/mcp/handlers/get.js +0 -69
  32. package/dist/mcp/handlers/index.d.ts +0 -28
  33. package/dist/mcp/handlers/index.js +0 -19
  34. package/dist/mcp/handlers/list.d.ts +0 -7
  35. package/dist/mcp/handlers/list.js +0 -43
  36. package/dist/mcp/handlers/push.d.ts +0 -26
  37. package/dist/mcp/handlers/push.js +0 -232
  38. package/dist/mcp/handlers/report.d.ts +0 -16
  39. package/dist/mcp/handlers/report.js +0 -134
  40. package/dist/mcp/handlers/review.d.ts +0 -52
  41. package/dist/mcp/handlers/review.js +0 -243
  42. package/dist/mcp/handlers/search.d.ts +0 -30
  43. package/dist/mcp/handlers/search.js +0 -58
  44. package/dist/mcp/handlers/test-mapping.d.ts +0 -39
  45. package/dist/mcp/handlers/test-mapping.js +0 -168
  46. package/dist/mcp/handlers/types.d.ts +0 -89
  47. package/dist/mcp/handlers/types.js +0 -52
  48. package/dist/mcp/index.d.ts +0 -45
  49. package/dist/mcp/index.js +0 -638
@@ -4,14 +4,32 @@ import { DEFAULT_API_BASE_URL, fetchReviewTestFeedback, } from "../requirements/
4
4
  import { findRequirementsInFile } from "../requirements/grep.js";
5
5
  import { formatRequirementTree, getRequirementTree, loadAllRequirements, } from "../requirements/index.js";
6
6
  import { getProjectCredentials } from "../utils/project-settings.js";
7
- export async function reviewTestCommand(testFilePath) {
7
+ /**
8
+ * Judgment instructions for local-mode test review. Mirrors the hosted
9
+ * review's semantic framing: test anatomy (setup, actions, assertions)
10
+ * checked against requirement anatomy (preconditions, triggers, outcomes).
11
+ */
12
+ const REVIEW_INSTRUCTIONS = `You are the reviewer. Judge whether the test file below validates what its referenced requirements specify, then report findings under the headings MUST FIX / SHOULD FIX / COULD IMPROVE (semantic gaps are MUST FIX; categorize other findings by their own severity). Focus on gaps and give actionable suggestions.
13
+
14
+ For each requirement reference, check:
15
+ - **Setup vs preconditions** — does the test establish the state the requirement describes (e.g. "a registered user" backed by actual data, not mocked away)?
16
+ - **Actions vs triggers** — does the test exercise the specified behavior with the specified inputs?
17
+ - **Assertions vs outcomes** — does the test verify the specified outcomes (no tautologies), and does it cover every distinct outcome the requirement lists?
18
+ - Note over-testing (validating behavior the requirement doesn't specify) without treating it as an error.
19
+ - When a test's level doesn't fit its requirement (e.g. a unit test referencing an end-to-end requirement), suggest scoping the reference or a different test level — with specific guidance.`;
20
+ export async function reviewTestCommand(testFilePath, options = {}) {
8
21
  const workspaceRoot = process.cwd();
9
22
  const fullPath = resolve(workspaceRoot, testFilePath);
10
- // CLI-REVIEW-1.5: missing file → error + non-zero exit
23
+ // CLI-REVIEW-1.5: --source accepts only local|cloud
24
+ const source = options.source ?? "local";
25
+ if (source !== "local" && source !== "cloud") {
26
+ throw new Error(`Invalid --source value: ${options.source}. Expected "local" or "cloud".`);
27
+ }
28
+ // CLI-REVIEW-1.3: missing file → error + non-zero exit
11
29
  if (!existsSync(fullPath)) {
12
30
  throw new Error(`File not found: ${testFilePath}`);
13
31
  }
14
- // CLI-REVIEW-1.6: must be a test file
32
+ // CLI-REVIEW-1.4: must be a test file
15
33
  if (!/\.(test|spec)\.(js|jsx|ts|tsx)$/.test(testFilePath)) {
16
34
  throw new Error(`File must be a test file: *.{test,spec}.{js,jsx,ts,tsx} — got ${testFilePath}`);
17
35
  }
@@ -30,6 +48,9 @@ export async function reviewTestCommand(testFilePath) {
30
48
  // resolve instead of being dropped.
31
49
  const { flattened } = await loadAllRequirements(workspaceRoot);
32
50
  const rootToTestedIds = new Map();
51
+ // CLI-REVIEW-1.2: references whose root resolves to no workspace
52
+ // requirement are reported as missing (in every mode)
53
+ const missingIds = [];
33
54
  for (const reqId of requirementIds) {
34
55
  const dotIndex = reqId.indexOf(".");
35
56
  const refRootId = dotIndex > 0 ? reqId.substring(0, dotIndex) : reqId;
@@ -40,7 +61,11 @@ export async function reviewTestCommand(testFilePath) {
40
61
  }
41
62
  rootToTestedIds.get(req.rootId).add(reqId);
42
63
  }
64
+ else {
65
+ missingIds.push(reqId);
66
+ }
43
67
  }
68
+ missingIds.sort();
44
69
  const requirements = [];
45
70
  for (const [rootId, testedIds] of rootToTestedIds) {
46
71
  const tree = getRequirementTree(flattened, rootId);
@@ -50,7 +75,64 @@ export async function reviewTestCommand(testFilePath) {
50
75
  testedIds: Array.from(testedIds).sort(),
51
76
  });
52
77
  }
53
- // CLI-REVIEW-1.6: cloud credentials required
78
+ if (source === "cloud") {
79
+ await runCloudReview({
80
+ workspaceRoot,
81
+ testFilePath,
82
+ testFileContents,
83
+ requirements,
84
+ missingIds,
85
+ });
86
+ return;
87
+ }
88
+ emitReviewMaterials({
89
+ testFilePath,
90
+ testFileContents,
91
+ requirements,
92
+ missingIds,
93
+ });
94
+ }
95
+ /**
96
+ * CLI-REVIEW-2: local (default) mode — emit judgment-ready review materials
97
+ * for the calling agent (typically a dispatched review subagent). No cloud
98
+ * credentials required.
99
+ */
100
+ function emitReviewMaterials(params) {
101
+ const { testFilePath, testFileContents, requirements, missingIds } = params;
102
+ console.log(`# Test Review Materials for ${testFilePath}\n`);
103
+ console.log(`## Judgment instructions\n`);
104
+ console.log(REVIEW_INSTRUCTIONS);
105
+ // CLI-REVIEW-2.0: resolved requirement trees bundled with the test contents
106
+ console.log(`\n## Referenced requirements\n`);
107
+ if (requirements.length === 0) {
108
+ console.log("(none resolved — the test file references no workspace requirements)");
109
+ }
110
+ else {
111
+ for (const req of requirements) {
112
+ console.log(`### ${req.id} (tested: ${req.testedIds.join(", ")})\n`);
113
+ console.log(req.content);
114
+ console.log("");
115
+ }
116
+ }
117
+ // CLI-REVIEW-1.2: missing references reported in the output
118
+ if (missingIds.length > 0) {
119
+ console.log(`## Missing requirement references\n`);
120
+ console.log(`These references resolve to no requirement in this workspace — flag them in your findings:`);
121
+ for (const id of missingIds) {
122
+ console.log(`- ${id}`);
123
+ }
124
+ console.log("");
125
+ }
126
+ console.log(`## Test file under review (${testFilePath})\n`);
127
+ console.log(testFileContents);
128
+ }
129
+ /**
130
+ * CLI-REVIEW-3: --source cloud — send the bundle to the hosted review
131
+ * endpoint and print the returned feedback. Requires cloud credentials.
132
+ */
133
+ async function runCloudReview(params) {
134
+ const { workspaceRoot, testFilePath, testFileContents, requirements, missingIds, } = params;
135
+ // CLI-REVIEW-3.2: cloud credentials required
54
136
  let projectId;
55
137
  let projectSecret;
56
138
  try {
@@ -62,22 +144,34 @@ export async function reviewTestCommand(testFilePath) {
62
144
  throw new Error(`Test review requires cloud credentials. Run \`dotrequirements link\` to connect this project to the cloud.\n` +
63
145
  `(${error instanceof Error ? error.message : String(error)})`);
64
146
  }
65
- // CLI-REVIEW-1.2 / .3 / .4 / .7 / .8: dispatch to the hosted endpoint
147
+ // CLI-REVIEW-3.0 / .1 / .3: dispatch to the hosted endpoint
66
148
  const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
67
149
  let feedback;
150
+ let quotaWarning;
68
151
  try {
69
- feedback = await fetchReviewTestFeedback({
152
+ ({ feedback, quotaWarning } = await fetchReviewTestFeedback({
70
153
  apiBaseUrl,
71
154
  projectId,
72
155
  projectSecret,
73
156
  testFileContents,
74
157
  requirements,
75
- });
158
+ }));
76
159
  }
77
160
  catch (error) {
78
161
  throw new Error(`Test review failed: ${error instanceof Error ? error.message : String(error)}`);
79
162
  }
80
163
  console.log(`Test Review Results for ${testFilePath}\n`);
81
164
  console.log(feedback);
165
+ // LIMITS-5.2 / 5.2.0: the 80–99% quota warning prints with the result
166
+ if (quotaWarning) {
167
+ console.log(`\n⚠ ${quotaWarning}`);
168
+ }
169
+ // CLI-REVIEW-1.2: missing references reported in every mode
170
+ if (missingIds.length > 0) {
171
+ console.log(`\nMissing requirement references (not in this workspace):`);
172
+ for (const id of missingIds) {
173
+ console.log(`- ${id}`);
174
+ }
175
+ }
82
176
  }
83
177
  //# sourceMappingURL=review-test.js.map
@@ -1,6 +1,7 @@
1
1
  interface StyleCheckOptions {
2
2
  keys?: string[];
3
3
  model?: string;
4
+ source?: string;
4
5
  }
5
6
  export declare function styleCheckCommand(filePath: string, options: StyleCheckOptions): Promise<void>;
6
7
  export {};
@@ -1,16 +1,44 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { DEFAULT_API_BASE_URL, fetchStyleCheckFeedback, } from "../requirements/cloud-ai.js";
4
- import { filterRequirementsByKeys } from "../requirements/index.js";
5
- import { getProjectCredentials } from "../utils/project-settings.js";
3
+ import { DEFAULT_API_BASE_URL, fetchStyleCheckFeedback, getProjectContext, } from "../requirements/cloud-ai.js";
4
+ import { filterRequirementsByKeys, loadAllRequirements, } from "../requirements/index.js";
5
+ import { generateStyleGuideBody, readLocalStyleGuide, } from "../requirements/style-guide.js";
6
+ import { findProjectRoot, getProjectCredentials, } from "../utils/project-settings.js";
7
+ const CONVEX_URL = "https://data.dotrequirements.io";
8
+ /**
9
+ * Judgment instructions for test-file style review in local mode. Mirrors the
10
+ * hosted test-style rubric (PROMPT-STYLE-TESTS-*): requirement() usage,
11
+ * comment discipline, structure, coverage-comment red flags, and semantic
12
+ * alignment between test anatomy and requirement anatomy.
13
+ */
14
+ const TEST_STYLE_INSTRUCTIONS = `You are the reviewer. Judge the test file below against these test-style conventions, then report findings under the headings MUST FIX / SHOULD FIX / COULD IMPROVE (categorize each finding by its own severity; semantic gaps are MUST FIX). Focus on gaps and give actionable suggestions.
15
+
16
+ - \`requirement()\` is used AS the description parameter of \`test()\`, \`describe()\`, or \`it()\` — flag requirement() calls placed in test bodies, and plain-string descriptions that should reference a requirement.
17
+ - Comments describe what the test implementation does — flag comments that copy requirement text verbatim (the requirement() reference already carries that meaning). Tests need no comments at all.
18
+ - Structured requirements (Given/When/Then trees) are tested with nested describe/it blocks; simple requirements need no extra nesting.
19
+ - Flag comments that document requirement coverage (e.g. "Requirements coverage: AUTH-9.0 ✓") — coverage lives in the harness, not comments; a second source of truth drifts.
20
+ - Test setup should establish the preconditions the referenced requirement describes; actions should exercise the specified behavior with the specified inputs; assertions should verify the specified outcomes (not tautologies like expect(true).toBe(true)). Flag requirements whose distinct outcomes are only partially validated; note over-testing without treating it as an error.
21
+ - When a test's level doesn't fit its requirement (e.g. a unit test referencing an end-to-end requirement), suggest scoping the reference or a different test level — with specific guidance, not generic observations.
22
+
23
+ The referenced requirement trees are not bundled here; if you need one, read it from \`.requirements/\` or run \`dotreq get <KEY>\`. For a full semantic review of tests against their requirements, \`dotreq review-test <file>\` is the dedicated verb.`;
24
+ const REQUIREMENTS_STYLE_INSTRUCTIONS = `You are the reviewer. Judge the requirements below against the style guide above, then report findings under the headings MUST FIX / SHOULD FIX / COULD IMPROVE (categorize each finding by its own severity). Focus on gaps and give actionable suggestions rather than just listing problems.`;
6
25
  export async function styleCheckCommand(filePath, options) {
7
26
  const workspaceRoot = process.cwd();
8
27
  const fullPath = resolve(workspaceRoot, filePath);
9
- // CLI-STYLE-1.5: missing file → error + non-zero exit
28
+ // CLI-STYLE-1.6: --source accepts only local|cloud
29
+ const source = options.source ?? "local";
30
+ if (source !== "local" && source !== "cloud") {
31
+ throw new Error(`Invalid --source value: ${options.source}. Expected "local" or "cloud".`);
32
+ }
33
+ // CLI-STYLE-1.7: --model configures the hosted review only
34
+ if (options.model && source !== "cloud") {
35
+ throw new Error(`--model applies to cloud review only. Add --source cloud to choose a review model.`);
36
+ }
37
+ // CLI-STYLE-1.4: missing file → error + non-zero exit
10
38
  if (!existsSync(fullPath)) {
11
39
  throw new Error(`File not found: ${filePath}`);
12
40
  }
13
- // CLI-STYLE-1.0 / .1 / .6: file-type detection
41
+ // CLI-STYLE-1.0 / .1 / .5: file-type detection
14
42
  const isRequirementsFile = filePath.endsWith(".requirements.md");
15
43
  const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(filePath);
16
44
  if (!isRequirementsFile && !isTestFile) {
@@ -35,7 +63,100 @@ export async function styleCheckCommand(filePath, options) {
35
63
  scopeNote = `\nNote: Some specified keys were not found in the file: ${missingKeys.join(", ")}`;
36
64
  }
37
65
  }
38
- // CLI-STYLE-1.7: credentials are required
66
+ const scopeLabel = options.keys && options.keys.length > 0
67
+ ? ` (${options.keys.join(", ")})`
68
+ : "";
69
+ if (source === "cloud") {
70
+ await runCloudStyleCheck({
71
+ workspaceRoot,
72
+ filePath,
73
+ fileContentsToCheck,
74
+ fileType,
75
+ model: options.model,
76
+ scopeLabel,
77
+ scopeNote,
78
+ });
79
+ return;
80
+ }
81
+ await emitStyleMaterials({
82
+ workspaceRoot,
83
+ filePath,
84
+ fileContentsToCheck,
85
+ fileType,
86
+ scopeLabel,
87
+ scopeNote,
88
+ });
89
+ }
90
+ /**
91
+ * CLI-STYLE-2: local (default) mode — emit judgment-ready review materials
92
+ * for the calling agent (typically a dispatched review subagent). No cloud
93
+ * credentials required.
94
+ */
95
+ async function emitStyleMaterials(params) {
96
+ const { workspaceRoot, filePath, fileContentsToCheck, fileType, scopeLabel, scopeNote, } = params;
97
+ console.log(`# Style Review Materials for ${filePath}${scopeLabel}\n`);
98
+ if (fileType === "requirements") {
99
+ // CLI-STYLE-2.0: compose the style guide the same way
100
+ // create-requirement-document does.
101
+ // CLI-STYLE-2.0.0: a project STYLE.md, when present, IS the guide.
102
+ const localStyleGuide = readLocalStyleGuide(workspaceRoot);
103
+ let guide;
104
+ if (localStyleGuide?.trim()) {
105
+ guide = localStyleGuide;
106
+ }
107
+ else {
108
+ // CLI-STYLE-2.0.1: otherwise generate from bundled defaults, workspace
109
+ // patterns, and cloud custom guidance when linked.
110
+ let requirements = [];
111
+ try {
112
+ const result = await loadAllRequirements(workspaceRoot);
113
+ requirements = result.flattened;
114
+ }
115
+ catch {
116
+ // No requirements in the workspace yet — defaults still apply
117
+ }
118
+ // CLI-STYLE-2.3 / .4: credentials are optional; cloud custom guidance is
119
+ // silently omitted when unlinked or unreachable.
120
+ let customStyleGuidance = null;
121
+ const projectRoot = findProjectRoot(workspaceRoot);
122
+ if (projectRoot) {
123
+ try {
124
+ const { projectId, projectSecret } = getProjectCredentials(workspaceRoot);
125
+ const contextData = await getProjectContext(projectId, projectSecret, CONVEX_URL);
126
+ customStyleGuidance = contextData?.requirementsStyleContext ?? null;
127
+ }
128
+ catch {
129
+ // Unlinked or cloud unavailable — fall through with defaults
130
+ }
131
+ }
132
+ guide = generateStyleGuideBody({ requirements, customStyleGuidance });
133
+ }
134
+ console.log(`## Style guide\n`);
135
+ console.log(guide);
136
+ console.log(`\n## Judgment instructions\n`);
137
+ console.log(REQUIREMENTS_STYLE_INSTRUCTIONS);
138
+ }
139
+ else {
140
+ console.log(`## Judgment instructions\n`);
141
+ console.log(TEST_STYLE_INSTRUCTIONS);
142
+ }
143
+ // CLI-STYLE-2.1: the content under review (keys-filtered when --keys given)
144
+ const heading = fileType === "requirements"
145
+ ? "Requirements under review"
146
+ : "Test file under review";
147
+ console.log(`\n## ${heading} (${filePath})\n`);
148
+ console.log(fileContentsToCheck);
149
+ if (scopeNote) {
150
+ console.log(scopeNote);
151
+ }
152
+ }
153
+ /**
154
+ * CLI-STYLE-3: --source cloud — send the file to the hosted review endpoint
155
+ * and print the returned feedback. Requires cloud credentials.
156
+ */
157
+ async function runCloudStyleCheck(params) {
158
+ const { workspaceRoot, filePath, fileContentsToCheck, fileType, model, scopeLabel, scopeNote, } = params;
159
+ // CLI-STYLE-3.2: credentials are required
39
160
  let projectId;
40
161
  let projectSecret;
41
162
  try {
@@ -47,29 +168,32 @@ export async function styleCheckCommand(filePath, options) {
47
168
  throw new Error(`Style check requires cloud credentials. Run \`dotrequirements link\` to connect this project to the cloud.\n` +
48
169
  `(${error instanceof Error ? error.message : String(error)})`);
49
170
  }
50
- // CLI-STYLE-1.4 / .8 / .9: dispatch to the hosted endpoint
171
+ // CLI-STYLE-3.0 / .1 / .3: dispatch to the hosted endpoint
51
172
  const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
52
173
  let feedback;
174
+ let quotaWarning;
53
175
  try {
54
- feedback = await fetchStyleCheckFeedback({
176
+ ({ feedback, quotaWarning } = await fetchStyleCheckFeedback({
55
177
  apiBaseUrl,
56
178
  projectId,
57
179
  projectSecret,
58
180
  fileContents: fileContentsToCheck,
59
181
  fileType,
60
- model: options.model,
61
- });
182
+ model,
183
+ }));
62
184
  }
63
185
  catch (error) {
64
186
  throw new Error(`Style check failed: ${error instanceof Error ? error.message : String(error)}`);
65
187
  }
66
- const scopeLabel = options.keys && options.keys.length > 0
67
- ? ` (${options.keys.join(", ")})`
68
- : "";
188
+ // CLI-STYLE-3.4: hosted feedback arrives severity-categorized
69
189
  console.log(`Style Check Results for ${filePath}${scopeLabel}\n`);
70
190
  console.log(feedback);
71
191
  if (scopeNote) {
72
192
  console.log(scopeNote);
73
193
  }
194
+ // LIMITS-5.2 / 5.2.0: the 80–99% quota warning prints with the result
195
+ if (quotaWarning) {
196
+ console.log(`\n⚠ ${quotaWarning}`);
197
+ }
74
198
  }
75
199
  //# sourceMappingURL=style-check.js.map
package/dist/convex.d.ts CHANGED
@@ -55,11 +55,9 @@ export declare const api: {
55
55
  };
56
56
  mutations: {
57
57
  createInvite: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
58
+ acceptInvite: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
58
59
  };
59
60
  };
60
- polar: {
61
- acceptTeamInvite: import("convex/server").FunctionReference<"action", "public", any, any, string | undefined>;
62
- };
63
61
  projectSecrets: {
64
62
  queries: {
65
63
  getOwnSecret: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
package/dist/convex.js CHANGED
@@ -61,11 +61,11 @@ export const api = {
61
61
  },
62
62
  mutations: {
63
63
  createInvite: mutation("teamInvites/mutations:createInvite"),
64
+ // Joins the team and reconciles paid-team seats via a scheduled sync
65
+ // (POLAR-SEATS-3). Replaced the deleted polar:acceptTeamInvite action.
66
+ acceptInvite: mutation("teamInvites/mutations:acceptInvite"),
64
67
  },
65
68
  },
66
- polar: {
67
- acceptTeamInvite: action("polar:acceptTeamInvite"),
68
- },
69
69
  projectSecrets: {
70
70
  queries: {
71
71
  getOwnSecret: query("projectSecrets/queries:getOwnSecret"),
@@ -22,11 +22,23 @@ export interface StyleCheckParams {
22
22
  model?: string;
23
23
  }
24
24
  /**
25
- * POST to `/api/style-check`. Returns the feedback text on success;
26
- * throws an Error with the underlying failure reason on a non-OK
27
- * response or a network error.
25
+ * Feedback plus the optional LIMITS-5.2 quota warning ("You've used X% of
26
+ * your AI quota this billing cycle."). The warning is present when the
27
+ * team is at 80–99% of its AI allowance; callers print it alongside the
28
+ * feedback so the eventual quota wall never arrives unannounced. Older
29
+ * servers simply omit the field.
28
30
  */
29
- export declare function fetchStyleCheckFeedback(params: StyleCheckParams): Promise<string>;
31
+ export interface CloudAIFeedback {
32
+ feedback: string;
33
+ quotaWarning: string | null;
34
+ }
35
+ /**
36
+ * POST to `/api/style-check`. Returns the feedback (and any quota warning)
37
+ * on success; throws an Error with the underlying failure reason on a
38
+ * non-OK response or a network error — including the LIMITS-5.1
39
+ * quota-exceeded error, whose message carries the billing-page upgrade link.
40
+ */
41
+ export declare function fetchStyleCheckFeedback(params: StyleCheckParams): Promise<CloudAIFeedback>;
30
42
  export interface ReviewTestParams {
31
43
  apiBaseUrl: string;
32
44
  projectId: string;
@@ -39,11 +51,12 @@ export interface ReviewTestParams {
39
51
  }>;
40
52
  }
41
53
  /**
42
- * POST to `/api/review-test`. Returns the feedback text on success;
43
- * throws an Error with the underlying failure reason on a non-OK
44
- * response or a network error.
54
+ * POST to `/api/review-test`. Returns the feedback (and any quota warning)
55
+ * on success; throws an Error with the underlying failure reason on a
56
+ * non-OK response or a network error — including the LIMITS-5.1
57
+ * quota-exceeded error, whose message carries the billing-page upgrade link.
45
58
  */
46
- export declare function fetchReviewTestFeedback(params: ReviewTestParams): Promise<string>;
59
+ export declare function fetchReviewTestFeedback(params: ReviewTestParams): Promise<CloudAIFeedback>;
47
60
  export interface ProjectContext {
48
61
  projectContext: string | null;
49
62
  requirementsStyleContext: string | null;
@@ -14,9 +14,10 @@
14
14
  */
15
15
  export const DEFAULT_API_BASE_URL = "https://app.dotrequirements.io";
16
16
  /**
17
- * POST to `/api/style-check`. Returns the feedback text on success;
18
- * throws an Error with the underlying failure reason on a non-OK
19
- * response or a network error.
17
+ * POST to `/api/style-check`. Returns the feedback (and any quota warning)
18
+ * on success; throws an Error with the underlying failure reason on a
19
+ * non-OK response or a network error — including the LIMITS-5.1
20
+ * quota-exceeded error, whose message carries the billing-page upgrade link.
20
21
  */
21
22
  export async function fetchStyleCheckFeedback(params) {
22
23
  const response = await fetch(`${params.apiBaseUrl}/api/style-check`, {
@@ -35,12 +36,13 @@ export async function fetchStyleCheckFeedback(params) {
35
36
  throw new Error(errorData.error || response.statusText);
36
37
  }
37
38
  const data = (await response.json());
38
- return data.feedback;
39
+ return { feedback: data.feedback, quotaWarning: data.quotaWarning ?? null };
39
40
  }
40
41
  /**
41
- * POST to `/api/review-test`. Returns the feedback text on success;
42
- * throws an Error with the underlying failure reason on a non-OK
43
- * response or a network error.
42
+ * POST to `/api/review-test`. Returns the feedback (and any quota warning)
43
+ * on success; throws an Error with the underlying failure reason on a
44
+ * non-OK response or a network error — including the LIMITS-5.1
45
+ * quota-exceeded error, whose message carries the billing-page upgrade link.
44
46
  */
45
47
  export async function fetchReviewTestFeedback(params) {
46
48
  const response = await fetch(`${params.apiBaseUrl}/api/review-test`, {
@@ -58,7 +60,7 @@ export async function fetchReviewTestFeedback(params) {
58
60
  throw new Error(errorData.error || response.statusText);
59
61
  }
60
62
  const data = (await response.json());
61
- return data.feedback;
63
+ return { feedback: data.feedback, quotaWarning: data.quotaWarning ?? null };
62
64
  }
63
65
  /**
64
66
  * Query Convex for project-level AI context (project description and
@@ -15,6 +15,19 @@ export declare const DELIMITER_PATTERN = "(?:\u2192|->)";
15
15
  * Parse a criterion line in "position. Label → content" format.
16
16
  * Example: "0. Given → user has valid credentials"
17
17
  * Also supports optional label: "0. → user has valid credentials"
18
+ *
19
+ * This is the canonical criterion grammar (SYNC-KEY-1.4). The web editor's
20
+ * markdown-conversion parser mirrors this exact behavior; keep them in sync.
21
+ *
22
+ * Two behaviors are load-bearing:
23
+ * - Split on the FIRST delimiter only, so an arrow inside the content
24
+ * ("maps A -> B") is preserved verbatim rather than being re-consumed as a
25
+ * label boundary. Everything before the first delimiter is the (optional,
26
+ * freeform — custom labels are supported, see MARKDOWN_SCHEMA) label;
27
+ * everything after is content.
28
+ * - An empty criterion ("0. → ") is a valid criterion with content "", not a
29
+ * parse failure. Returning null here would make parseRequirementBlocksFromMarkdown
30
+ * reject the whole block, and would drop the criterion (and its subtree) on load.
18
31
  */
19
32
  export declare function parseCriterionLine(line: string, delimiter?: string): ParsedCriterion | null;
20
33
  /**
@@ -11,24 +11,48 @@ import { ValidationError, validateKey, } from "./schemas.js";
11
11
  */
12
12
  export const DEFAULT_DELIMITER = "→";
13
13
  export const DELIMITER_PATTERN = "(?:→|->)"; // Non-capturing group for both Unicode and ASCII
14
+ // Compiled once for the common (default-delimiter) path so parseCriterionLine
15
+ // doesn't rebuild the same RegExp on every line.
16
+ const DEFAULT_DELIMITER_REGEX = new RegExp(DELIMITER_PATTERN);
14
17
  /**
15
18
  * Parse a criterion line in "position. Label → content" format.
16
19
  * Example: "0. Given → user has valid credentials"
17
20
  * Also supports optional label: "0. → user has valid credentials"
21
+ *
22
+ * This is the canonical criterion grammar (SYNC-KEY-1.4). The web editor's
23
+ * markdown-conversion parser mirrors this exact behavior; keep them in sync.
24
+ *
25
+ * Two behaviors are load-bearing:
26
+ * - Split on the FIRST delimiter only, so an arrow inside the content
27
+ * ("maps A -> B") is preserved verbatim rather than being re-consumed as a
28
+ * label boundary. Everything before the first delimiter is the (optional,
29
+ * freeform — custom labels are supported, see MARKDOWN_SCHEMA) label;
30
+ * everything after is content.
31
+ * - An empty criterion ("0. → ") is a valid criterion with content "", not a
32
+ * parse failure. Returning null here would make parseRequirementBlocksFromMarkdown
33
+ * reject the whole block, and would drop the criterion (and its subtree) on load.
18
34
  */
19
35
  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) {
36
+ // Position prefix: "0.", "1.0.", "2.3.1." — required for a criterion line.
37
+ const positionMatch = line.match(/^\s*(\d+(?:\.\d+)*)\.\s*/);
38
+ if (!positionMatch) {
39
+ return null;
40
+ }
41
+ const rest = line.slice(positionMatch[0].length);
42
+ const delimiterMatch = rest.match(delimiter === DELIMITER_PATTERN
43
+ ? DEFAULT_DELIMITER_REGEX
44
+ : new RegExp(delimiter));
45
+ if (!delimiterMatch || delimiterMatch.index === undefined) {
26
46
  return null;
27
47
  }
48
+ const label = rest.slice(0, delimiterMatch.index).trim();
49
+ const content = rest
50
+ .slice(delimiterMatch.index + delimiterMatch[0].length)
51
+ .trim();
28
52
  return {
29
- position: match[1],
30
- label: match[2] ? match[2].trim() : "", // Preserve original case, empty if no label
31
- content: match[3].trim(),
53
+ position: positionMatch[1],
54
+ label, // Preserve original case; freeform (empty when no label)
55
+ content, // May be "" for an empty criterion
32
56
  };
33
57
  }
34
58
  /**
@@ -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