@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.
- package/README.md +13 -69
- package/dist/cli.js +19 -7
- package/dist/commands/ai-setup.d.ts +8 -2
- package/dist/commands/ai-setup.js +153 -347
- package/dist/commands/init.js +1 -1
- package/dist/commands/mcp.d.ts +8 -2
- package/dist/commands/mcp.js +17 -6
- package/dist/commands/review-test.d.ts +5 -1
- package/dist/commands/review-test.js +101 -7
- package/dist/commands/style-check.d.ts +1 -0
- package/dist/commands/style-check.js +137 -13
- package/dist/convex.d.ts +1 -3
- package/dist/convex.js +3 -3
- package/dist/requirements/cloud-ai.d.ts +21 -8
- package/dist/requirements/cloud-ai.js +10 -8
- package/dist/schema/parser-core.d.ts +13 -0
- package/dist/schema/parser-core.js +33 -9
- package/dist/templates/context-file-section.md +25 -22
- package/dist/utils/context-file.d.ts +7 -3
- package/dist/utils/context-file.js +10 -7
- package/dist/utils/project-settings.d.ts +1 -0
- package/dist/utils/project-settings.js +22 -0
- package/package.json +3 -5
- package/dist/mcp/convexClient.d.ts +0 -19
- package/dist/mcp/convexClient.js +0 -24
- package/dist/mcp/handlers/authoring.d.ts +0 -41
- package/dist/mcp/handlers/authoring.js +0 -113
- package/dist/mcp/handlers/debug.d.ts +0 -16
- package/dist/mcp/handlers/debug.js +0 -37
- package/dist/mcp/handlers/get.d.ts +0 -24
- package/dist/mcp/handlers/get.js +0 -69
- package/dist/mcp/handlers/index.d.ts +0 -28
- package/dist/mcp/handlers/index.js +0 -19
- package/dist/mcp/handlers/list.d.ts +0 -7
- package/dist/mcp/handlers/list.js +0 -43
- package/dist/mcp/handlers/push.d.ts +0 -26
- package/dist/mcp/handlers/push.js +0 -232
- package/dist/mcp/handlers/report.d.ts +0 -16
- package/dist/mcp/handlers/report.js +0 -134
- package/dist/mcp/handlers/review.d.ts +0 -52
- package/dist/mcp/handlers/review.js +0 -243
- package/dist/mcp/handlers/search.d.ts +0 -30
- package/dist/mcp/handlers/search.js +0 -58
- package/dist/mcp/handlers/test-mapping.d.ts +0 -39
- package/dist/mcp/handlers/test-mapping.js +0 -168
- package/dist/mcp/handlers/types.d.ts +0 -89
- package/dist/mcp/handlers/types.js +0 -52
- package/dist/mcp/index.d.ts +0 -45
- 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
|
-
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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-
|
|
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,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 {
|
|
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.
|
|
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 / .
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
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
|
|
43
|
-
* throws an Error with the underlying failure reason on a
|
|
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<
|
|
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
|
|
18
|
-
* throws an Error with the underlying failure reason on a
|
|
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
|
|
42
|
-
* throws an Error with the underlying failure reason on a
|
|
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
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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:
|
|
30
|
-
label
|
|
31
|
-
content
|
|
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-
|
|
15
|
-
3. **
|
|
16
|
-
4. **
|
|
17
|
-
5. **
|
|
18
|
-
6. **
|
|
19
|
-
7. **
|
|
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.
|
|
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
|
-
###
|
|
48
|
+
### CLI Verbs
|
|
48
49
|
|
|
49
50
|
**Exploration:**
|
|
50
|
-
- `
|
|
51
|
-
- `
|
|
52
|
-
- `
|
|
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
|
-
- `
|
|
56
|
-
- `
|
|
57
|
-
- `
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- `
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- `
|
|
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
|
-
*
|
|
43
|
-
*
|
|
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
|