@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.
- 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/create-requirement-document.js +2 -1
- 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 +138 -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/requirements/style-guide-file.d.ts +21 -0
- package/dist/requirements/style-guide-file.js +30 -0
- package/dist/requirements/style-guide.d.ts +20 -22
- package/dist/requirements/style-guide.js +57 -35
- package/dist/schema/browser.d.ts +1 -1
- package/dist/schema/browser.js +4 -1
- package/dist/schema/parser-core.d.ts +28 -0
- package/dist/schema/parser-core.js +51 -12
- 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 -4
- 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,45 @@
|
|
|
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 } from "../requirements/style-guide.js";
|
|
6
|
+
import { readLocalStyleGuide } from "../requirements/style-guide-file.js";
|
|
7
|
+
import { findProjectRoot, getProjectCredentials, } from "../utils/project-settings.js";
|
|
8
|
+
const CONVEX_URL = "https://data.dotrequirements.io";
|
|
9
|
+
/**
|
|
10
|
+
* Judgment instructions for test-file style review in local mode. Mirrors the
|
|
11
|
+
* hosted test-style rubric (PROMPT-STYLE-TESTS-*): requirement() usage,
|
|
12
|
+
* comment discipline, structure, coverage-comment red flags, and semantic
|
|
13
|
+
* alignment between test anatomy and requirement anatomy.
|
|
14
|
+
*/
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
- \`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.
|
|
18
|
+
- 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.
|
|
19
|
+
- Structured requirements (Given/When/Then trees) are tested with nested describe/it blocks; simple requirements need no extra nesting.
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
- 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.
|
|
23
|
+
|
|
24
|
+
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.`;
|
|
25
|
+
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
26
|
export async function styleCheckCommand(filePath, options) {
|
|
7
27
|
const workspaceRoot = process.cwd();
|
|
8
28
|
const fullPath = resolve(workspaceRoot, filePath);
|
|
9
|
-
// CLI-STYLE-1.
|
|
29
|
+
// CLI-STYLE-1.6: --source accepts only local|cloud
|
|
30
|
+
const source = options.source ?? "local";
|
|
31
|
+
if (source !== "local" && source !== "cloud") {
|
|
32
|
+
throw new Error(`Invalid --source value: ${options.source}. Expected "local" or "cloud".`);
|
|
33
|
+
}
|
|
34
|
+
// CLI-STYLE-1.7: --model configures the hosted review only
|
|
35
|
+
if (options.model && source !== "cloud") {
|
|
36
|
+
throw new Error(`--model applies to cloud review only. Add --source cloud to choose a review model.`);
|
|
37
|
+
}
|
|
38
|
+
// CLI-STYLE-1.4: missing file → error + non-zero exit
|
|
10
39
|
if (!existsSync(fullPath)) {
|
|
11
40
|
throw new Error(`File not found: ${filePath}`);
|
|
12
41
|
}
|
|
13
|
-
// CLI-STYLE-1.0 / .1 / .
|
|
42
|
+
// CLI-STYLE-1.0 / .1 / .5: file-type detection
|
|
14
43
|
const isRequirementsFile = filePath.endsWith(".requirements.md");
|
|
15
44
|
const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(filePath);
|
|
16
45
|
if (!isRequirementsFile && !isTestFile) {
|
|
@@ -35,7 +64,100 @@ export async function styleCheckCommand(filePath, options) {
|
|
|
35
64
|
scopeNote = `\nNote: Some specified keys were not found in the file: ${missingKeys.join(", ")}`;
|
|
36
65
|
}
|
|
37
66
|
}
|
|
38
|
-
|
|
67
|
+
const scopeLabel = options.keys && options.keys.length > 0
|
|
68
|
+
? ` (${options.keys.join(", ")})`
|
|
69
|
+
: "";
|
|
70
|
+
if (source === "cloud") {
|
|
71
|
+
await runCloudStyleCheck({
|
|
72
|
+
workspaceRoot,
|
|
73
|
+
filePath,
|
|
74
|
+
fileContentsToCheck,
|
|
75
|
+
fileType,
|
|
76
|
+
model: options.model,
|
|
77
|
+
scopeLabel,
|
|
78
|
+
scopeNote,
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await emitStyleMaterials({
|
|
83
|
+
workspaceRoot,
|
|
84
|
+
filePath,
|
|
85
|
+
fileContentsToCheck,
|
|
86
|
+
fileType,
|
|
87
|
+
scopeLabel,
|
|
88
|
+
scopeNote,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* CLI-STYLE-2: local (default) mode — emit judgment-ready review materials
|
|
93
|
+
* for the calling agent (typically a dispatched review subagent). No cloud
|
|
94
|
+
* credentials required.
|
|
95
|
+
*/
|
|
96
|
+
async function emitStyleMaterials(params) {
|
|
97
|
+
const { workspaceRoot, filePath, fileContentsToCheck, fileType, scopeLabel, scopeNote, } = params;
|
|
98
|
+
console.log(`# Style Review Materials for ${filePath}${scopeLabel}\n`);
|
|
99
|
+
if (fileType === "requirements") {
|
|
100
|
+
// CLI-STYLE-2.0: compose the style guide the same way
|
|
101
|
+
// create-requirement-document does.
|
|
102
|
+
// CLI-STYLE-2.0.0: a project STYLE.md, when present, IS the guide.
|
|
103
|
+
const localStyleGuide = readLocalStyleGuide(workspaceRoot);
|
|
104
|
+
let guide;
|
|
105
|
+
if (localStyleGuide?.trim()) {
|
|
106
|
+
guide = localStyleGuide;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// CLI-STYLE-2.0.1: otherwise generate from bundled defaults, workspace
|
|
110
|
+
// patterns, and cloud custom guidance when linked.
|
|
111
|
+
let requirements = [];
|
|
112
|
+
try {
|
|
113
|
+
const result = await loadAllRequirements(workspaceRoot);
|
|
114
|
+
requirements = result.flattened;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// No requirements in the workspace yet — defaults still apply
|
|
118
|
+
}
|
|
119
|
+
// CLI-STYLE-2.3 / .4: credentials are optional; cloud custom guidance is
|
|
120
|
+
// silently omitted when unlinked or unreachable.
|
|
121
|
+
let customStyleGuidance = null;
|
|
122
|
+
const projectRoot = findProjectRoot(workspaceRoot);
|
|
123
|
+
if (projectRoot) {
|
|
124
|
+
try {
|
|
125
|
+
const { projectId, projectSecret } = getProjectCredentials(workspaceRoot);
|
|
126
|
+
const contextData = await getProjectContext(projectId, projectSecret, CONVEX_URL);
|
|
127
|
+
customStyleGuidance = contextData?.requirementsStyleContext ?? null;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Unlinked or cloud unavailable — fall through with defaults
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
guide = generateStyleGuideBody({ requirements, customStyleGuidance });
|
|
134
|
+
}
|
|
135
|
+
console.log(`## Style guide\n`);
|
|
136
|
+
console.log(guide);
|
|
137
|
+
console.log(`\n## Judgment instructions\n`);
|
|
138
|
+
console.log(REQUIREMENTS_STYLE_INSTRUCTIONS);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log(`## Judgment instructions\n`);
|
|
142
|
+
console.log(TEST_STYLE_INSTRUCTIONS);
|
|
143
|
+
}
|
|
144
|
+
// CLI-STYLE-2.1: the content under review (keys-filtered when --keys given)
|
|
145
|
+
const heading = fileType === "requirements"
|
|
146
|
+
? "Requirements under review"
|
|
147
|
+
: "Test file under review";
|
|
148
|
+
console.log(`\n## ${heading} (${filePath})\n`);
|
|
149
|
+
console.log(fileContentsToCheck);
|
|
150
|
+
if (scopeNote) {
|
|
151
|
+
console.log(scopeNote);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* CLI-STYLE-3: --source cloud — send the file to the hosted review endpoint
|
|
156
|
+
* and print the returned feedback. Requires cloud credentials.
|
|
157
|
+
*/
|
|
158
|
+
async function runCloudStyleCheck(params) {
|
|
159
|
+
const { workspaceRoot, filePath, fileContentsToCheck, fileType, model, scopeLabel, scopeNote, } = params;
|
|
160
|
+
// CLI-STYLE-3.2: credentials are required
|
|
39
161
|
let projectId;
|
|
40
162
|
let projectSecret;
|
|
41
163
|
try {
|
|
@@ -47,29 +169,32 @@ export async function styleCheckCommand(filePath, options) {
|
|
|
47
169
|
throw new Error(`Style check requires cloud credentials. Run \`dotrequirements link\` to connect this project to the cloud.\n` +
|
|
48
170
|
`(${error instanceof Error ? error.message : String(error)})`);
|
|
49
171
|
}
|
|
50
|
-
// CLI-STYLE-
|
|
172
|
+
// CLI-STYLE-3.0 / .1 / .3: dispatch to the hosted endpoint
|
|
51
173
|
const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
|
|
52
174
|
let feedback;
|
|
175
|
+
let quotaWarning;
|
|
53
176
|
try {
|
|
54
|
-
feedback = await fetchStyleCheckFeedback({
|
|
177
|
+
({ feedback, quotaWarning } = await fetchStyleCheckFeedback({
|
|
55
178
|
apiBaseUrl,
|
|
56
179
|
projectId,
|
|
57
180
|
projectSecret,
|
|
58
181
|
fileContents: fileContentsToCheck,
|
|
59
182
|
fileType,
|
|
60
|
-
model
|
|
61
|
-
});
|
|
183
|
+
model,
|
|
184
|
+
}));
|
|
62
185
|
}
|
|
63
186
|
catch (error) {
|
|
64
187
|
throw new Error(`Style check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
188
|
}
|
|
66
|
-
|
|
67
|
-
? ` (${options.keys.join(", ")})`
|
|
68
|
-
: "";
|
|
189
|
+
// CLI-STYLE-3.4: hosted feedback arrives severity-categorized
|
|
69
190
|
console.log(`Style Check Results for ${filePath}${scopeLabel}\n`);
|
|
70
191
|
console.log(feedback);
|
|
71
192
|
if (scopeNote) {
|
|
72
193
|
console.log(scopeNote);
|
|
73
194
|
}
|
|
195
|
+
// LIMITS-5.2 / 5.2.0: the 80–99% quota warning prints with the result
|
|
196
|
+
if (quotaWarning) {
|
|
197
|
+
console.log(`\n⚠ ${quotaWarning}`);
|
|
198
|
+
}
|
|
74
199
|
}
|
|
75
200
|
//# 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
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem access for the style guide — kept apart from the generator itself.
|
|
3
|
+
*
|
|
4
|
+
* `style-guide.ts` is imported by the web app to serve the same primer to chat
|
|
5
|
+
* agents (REMOTE-MCP-10), which have no repo to read. Everything in that module
|
|
6
|
+
* must therefore stay free of `node:fs`; the workspace lookups live here, where
|
|
7
|
+
* only the CLI reaches them. This mirrors the split the schema module already
|
|
8
|
+
* makes between its node and browser entry points.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Conventional location of a project's STYLE.md, relative to the workspace
|
|
12
|
+
* root. Exported so callers (init, push/pull, tests) can reference one place.
|
|
13
|
+
*/
|
|
14
|
+
export declare const STYLE_MD_PATH = ".requirements/STYLE.md";
|
|
15
|
+
/**
|
|
16
|
+
* Read `.requirements/STYLE.md` if the workspace has one. Returns the file
|
|
17
|
+
* contents on success, `null` if the file is absent. Empty / whitespace-only
|
|
18
|
+
* files are treated as absent so the bundled defaults still apply.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readLocalStyleGuide(workspaceRoot: string): string | null;
|
|
21
|
+
//# sourceMappingURL=style-guide-file.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem access for the style guide — kept apart from the generator itself.
|
|
3
|
+
*
|
|
4
|
+
* `style-guide.ts` is imported by the web app to serve the same primer to chat
|
|
5
|
+
* agents (REMOTE-MCP-10), which have no repo to read. Everything in that module
|
|
6
|
+
* must therefore stay free of `node:fs`; the workspace lookups live here, where
|
|
7
|
+
* only the CLI reaches them. This mirrors the split the schema module already
|
|
8
|
+
* makes between its node and browser entry points.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
/**
|
|
13
|
+
* Conventional location of a project's STYLE.md, relative to the workspace
|
|
14
|
+
* root. Exported so callers (init, push/pull, tests) can reference one place.
|
|
15
|
+
*/
|
|
16
|
+
export const STYLE_MD_PATH = ".requirements/STYLE.md";
|
|
17
|
+
/**
|
|
18
|
+
* Read `.requirements/STYLE.md` if the workspace has one. Returns the file
|
|
19
|
+
* contents on success, `null` if the file is absent. Empty / whitespace-only
|
|
20
|
+
* files are treated as absent so the bundled defaults still apply.
|
|
21
|
+
*/
|
|
22
|
+
export function readLocalStyleGuide(workspaceRoot) {
|
|
23
|
+
const fullPath = join(workspaceRoot, STYLE_MD_PATH);
|
|
24
|
+
if (!existsSync(fullPath)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const contents = readFileSync(fullPath, "utf-8");
|
|
28
|
+
return contents.trim().length > 0 ? contents : null;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=style-guide-file.js.map
|
|
@@ -11,37 +11,35 @@
|
|
|
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
|
|
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
17
|
import type { FlattenedRequirement } from "./index.js";
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* The only fields the guide reads off a requirement.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately narrower than {@link FlattenedRequirement}, which is file-shaped
|
|
22
|
+
* (`sourceFile`, `documentTitle`). The web serves this same guide from cloud
|
|
23
|
+
* requirements, which have no file to name (REMOTE-MCP-10) — asking only for
|
|
24
|
+
* what's used lets both callers pass what they actually have instead of
|
|
25
|
+
* inventing filenames.
|
|
20
26
|
*/
|
|
21
|
-
export
|
|
27
|
+
export type StyleGuideRequirement = Pick<FlattenedRequirement, "id" | "label" | "path">;
|
|
22
28
|
export interface GenerateStyleGuideParams {
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* cloud project's `requirementsStyleContext` field). Pass null/undefined
|
|
29
|
-
* to omit. */
|
|
29
|
+
/** The existing requirements to discover label and key-prefix patterns from.
|
|
30
|
+
* Pass an empty array when none exist yet. */
|
|
31
|
+
requirements: StyleGuideRequirement[];
|
|
32
|
+
/** Project-owner-supplied custom guidance (sourced from the cloud project's
|
|
33
|
+
* `requirementsStyleContext` field). Pass null/undefined to omit. */
|
|
30
34
|
customStyleGuidance?: string | null;
|
|
31
|
-
/** Suggested target path for the new file. Used only in the preamble
|
|
32
|
-
*
|
|
35
|
+
/** Suggested target path for the new file. Used only in the preamble and
|
|
36
|
+
* "Next Steps" section. Defaults to a generic example path. */
|
|
33
37
|
filePath?: string;
|
|
34
|
-
/** Project-local STYLE.md contents. When provided and non-empty, this
|
|
35
|
-
*
|
|
36
|
-
*
|
|
38
|
+
/** Project-local STYLE.md contents. When provided and non-empty, this body
|
|
39
|
+
* replaces the bundled default. CLI-only — see `readLocalStyleGuide` in
|
|
40
|
+
* ./style-guide-file.ts. */
|
|
37
41
|
localStyleGuide?: string | null;
|
|
38
42
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Read `.requirements/STYLE.md` if the workspace has one. Returns the file
|
|
41
|
-
* contents on success, `null` if the file is absent. Empty / whitespace-only
|
|
42
|
-
* files are treated as absent so the bundled defaults still apply.
|
|
43
|
-
*/
|
|
44
|
-
export declare function readLocalStyleGuide(workspaceRoot: string): string | null;
|
|
45
43
|
/**
|
|
46
44
|
* Build the body of the bundled default style guide. This is the content
|
|
47
45
|
* that lives inside the "# Requirements File Template" preamble — the
|