@popoverai/dotrequirements 0.12.0 → 0.13.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 +19 -14
- package/dist/commands/init.js +4 -0
- package/dist/commands/pull.js +10 -16
- package/dist/commands/push.js +5 -11
- package/dist/convex.d.ts +3 -0
- package/dist/convex.js +3 -0
- package/dist/harness/finalize.d.ts +8 -2
- package/dist/harness/finalize.js +42 -20
- package/dist/mcp/index.js +13 -5
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +2 -3
- package/dist/schema/schemas.d.ts +14 -37
- package/dist/schema/schemas.js +7 -10
- package/dist/schema/test-schema.js +1 -1
- package/dist/utils/project-selector.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,10 +32,7 @@ Create `*.requirements.md` files in `.requirements/` or colocate them with your
|
|
|
32
32
|
|
|
33
33
|
```markdown
|
|
34
34
|
---
|
|
35
|
-
projectId: my-project
|
|
36
|
-
version: 1
|
|
37
35
|
document:
|
|
38
|
-
id: auth-requirements
|
|
39
36
|
title: "Authentication Requirements"
|
|
40
37
|
---
|
|
41
38
|
|
|
@@ -210,6 +207,15 @@ export async function teardown() {
|
|
|
210
207
|
|
|
211
208
|
Alternatively, you can use the default export pattern that returns a teardown function—see the [Vitest globalSetup docs](https://vitest.dev/config/globalsetup).
|
|
212
209
|
|
|
210
|
+
**Customizing output:** `finalize()` accepts options to control verbosity:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
await finalize({ showTestedList: true }); // Include tested requirements
|
|
214
|
+
await finalize({ showSummary: false, showUntestedList: false }); // Quiet mode
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
By default, only untested requirements are shown. See the [test harness docs](https://dotrequirements.io/tools/test-harness) for all options.
|
|
218
|
+
|
|
213
219
|
### Setup with Jest
|
|
214
220
|
|
|
215
221
|
**jest.config.cjs:**
|
|
@@ -290,19 +296,21 @@ After tests complete, a coverage summary prints to the console:
|
|
|
290
296
|
|
|
291
297
|
```
|
|
292
298
|
=== Requirements Coverage Report ===
|
|
299
|
+
|
|
293
300
|
Total Requirements: 12
|
|
294
301
|
Tested Requirements: 10
|
|
295
302
|
Untested Requirements: 2
|
|
296
303
|
Coverage: 83.3%
|
|
297
304
|
|
|
298
|
-
Tested:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
305
|
+
✓ Tested Requirements:
|
|
306
|
+
- AUTH-LOGIN-1: A registered user, Jamie, can log in to their account
|
|
307
|
+
- AUTH-LOGIN-1.0: When Jamie provides a valid username and password...
|
|
308
|
+
|
|
309
|
+
✗ Untested Requirements:
|
|
310
|
+
- AUTH-LOGIN-2: A user with two-factor auth must provide an OTP
|
|
311
|
+
- AUTH-SECURITY-1: Session tokens expire after 8 hours
|
|
302
312
|
|
|
303
|
-
|
|
304
|
-
✗ AUTH-LOGIN-2
|
|
305
|
-
✗ AUTH-SECURITY-1
|
|
313
|
+
====================================
|
|
306
314
|
```
|
|
307
315
|
|
|
308
316
|
#### Cloud Reporting
|
|
@@ -346,10 +354,7 @@ src/components/
|
|
|
346
354
|
|
|
347
355
|
```markdown
|
|
348
356
|
---
|
|
349
|
-
projectId: my-project
|
|
350
|
-
version: 1
|
|
351
357
|
document:
|
|
352
|
-
id: unique-doc-id
|
|
353
358
|
title: "Document Title"
|
|
354
359
|
---
|
|
355
360
|
|
|
@@ -380,7 +385,7 @@ AUTH-LOGIN-2: A user with two-factor auth must provide an OTP
|
|
|
380
385
|
|
|
381
386
|
### Format Details
|
|
382
387
|
|
|
383
|
-
- **Frontmatter**: YAML metadata (
|
|
388
|
+
- **Frontmatter**: YAML metadata (only `document.title` required for push)
|
|
384
389
|
- **Headings**: Optional documentation (not parsed as requirement data)
|
|
385
390
|
- **Fenced blocks**: `dotrequirements` blocks contain structured requirement data
|
|
386
391
|
- **First line**: `KEY: content` — the requirement identifier and summary
|
package/dist/commands/init.js
CHANGED
|
@@ -48,6 +48,7 @@ export async function initCommand(options = {}) {
|
|
|
48
48
|
await connectExistingProjectFlow(cwd, envPath);
|
|
49
49
|
}
|
|
50
50
|
console.log('\nDocumentation: https://docs.dotrequirements.io/getting-started');
|
|
51
|
+
console.log('By using this service, you agree to our Terms: https://app.dotrequirements.io/terms');
|
|
51
52
|
}
|
|
52
53
|
catch (error) {
|
|
53
54
|
console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -67,6 +68,9 @@ export async function initCommand(options = {}) {
|
|
|
67
68
|
*/
|
|
68
69
|
async function selectTeam(client) {
|
|
69
70
|
console.log('Fetching your teams...\n');
|
|
71
|
+
// AUTH-24.0: Ensure user has a default team if they don't have any
|
|
72
|
+
// This handles new users who haven't joined via invite
|
|
73
|
+
await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
|
|
70
74
|
// INIT-13.0: Query all teams for the authenticated user
|
|
71
75
|
const teams = await client.query(api.teams.queries.listForUserWithUsage);
|
|
72
76
|
if (teams.length === 0) {
|
package/dist/commands/pull.js
CHANGED
|
@@ -6,6 +6,7 @@ import { buildRequirementsFile } from '../schema/index.js';
|
|
|
6
6
|
import { api } from '../convex.js';
|
|
7
7
|
import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
|
|
8
8
|
import { brand } from '../utils/brand.js';
|
|
9
|
+
import { findRequirementsFiles } from '../mcp/requirements.js';
|
|
9
10
|
export async function pullCommand(options) {
|
|
10
11
|
try {
|
|
11
12
|
console.log(`Syncing requirements from ${brand} cloud...`);
|
|
@@ -66,13 +67,13 @@ export async function pullCommand(options) {
|
|
|
66
67
|
console.log('No documents found.');
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
|
-
// Ensure .requirements/ directory exists
|
|
70
|
+
// Ensure .requirements/ directory exists (for new documents)
|
|
70
71
|
const requirementsDir = path.join(process.cwd(), '.requirements');
|
|
71
72
|
if (!fs.existsSync(requirementsDir)) {
|
|
72
73
|
fs.mkdirSync(requirementsDir, { recursive: true });
|
|
73
74
|
}
|
|
74
|
-
// Build index of existing files by document ID
|
|
75
|
-
const existingFilesByDocId = buildDocumentIdIndex(
|
|
75
|
+
// Build index of existing files by document ID (search entire workspace)
|
|
76
|
+
const existingFilesByDocId = await buildDocumentIdIndex(process.cwd());
|
|
76
77
|
// Write each document as a Markdown file
|
|
77
78
|
for (const doc of documents) {
|
|
78
79
|
// Check if an existing file has this document ID
|
|
@@ -81,9 +82,7 @@ export async function pullCommand(options) {
|
|
|
81
82
|
const fileName = path.basename(filePath);
|
|
82
83
|
// Build metadata with pulledAt for conflict detection
|
|
83
84
|
// SYNC-CLI-EDIT-2: pulledAt is compared with cloud updatedAt during push
|
|
84
|
-
// Use the slug we authenticated with (not the Convex ID from the response)
|
|
85
85
|
const metadata = {
|
|
86
|
-
projectId: projectId,
|
|
87
86
|
pulledAt: new Date().toISOString(),
|
|
88
87
|
version: doc.version,
|
|
89
88
|
document: {
|
|
@@ -122,19 +121,14 @@ function loadProjectIdFromEnv() {
|
|
|
122
121
|
}
|
|
123
122
|
/**
|
|
124
123
|
* Build an index mapping document IDs to their file paths.
|
|
125
|
-
* Scans existing .requirements.md files and extracts document.id from frontmatter.
|
|
124
|
+
* Scans existing .requirements.md files across the entire workspace and extracts document.id from frontmatter.
|
|
125
|
+
* SYNC-DISCOVERY-2.0: Uses the same discovery logic as push and MCP.
|
|
126
126
|
*/
|
|
127
|
-
function buildDocumentIdIndex(
|
|
127
|
+
async function buildDocumentIdIndex(workspaceRoot) {
|
|
128
128
|
const index = new Map();
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const files = fs.readdirSync(requirementsDir);
|
|
133
|
-
for (const file of files) {
|
|
134
|
-
if (!file.endsWith('.requirements.md')) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
const filePath = path.join(requirementsDir, file);
|
|
129
|
+
// Use the same file discovery as MCP and push
|
|
130
|
+
const allFiles = await findRequirementsFiles(workspaceRoot);
|
|
131
|
+
for (const filePath of allFiles) {
|
|
138
132
|
const docId = extractDocumentIdFromFile(filePath);
|
|
139
133
|
if (docId) {
|
|
140
134
|
index.set(docId, filePath);
|
package/dist/commands/push.js
CHANGED
|
@@ -7,6 +7,7 @@ import { api } from '../convex.js';
|
|
|
7
7
|
import * as readline from 'readline';
|
|
8
8
|
import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
|
|
9
9
|
import { brand } from '../utils/brand.js';
|
|
10
|
+
import { findRequirementsFiles } from '../mcp/requirements.js';
|
|
10
11
|
export async function pushCommand(file, options) {
|
|
11
12
|
try {
|
|
12
13
|
console.log(`Preparing to push requirements to ${brand} cloud...\n`);
|
|
@@ -23,11 +24,6 @@ export async function pushCommand(file, options) {
|
|
|
23
24
|
throw new Error(CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE);
|
|
24
25
|
}
|
|
25
26
|
// Determine which files to push
|
|
26
|
-
const requirementsDir = path.join(process.cwd(), '.requirements');
|
|
27
|
-
if (!fs.existsSync(requirementsDir)) {
|
|
28
|
-
console.log('No .requirements/ directory found. Nothing to push.');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
27
|
let filesToPush;
|
|
32
28
|
if (file) {
|
|
33
29
|
// Push specific file
|
|
@@ -38,13 +34,11 @@ export async function pushCommand(file, options) {
|
|
|
38
34
|
filesToPush = [filePath];
|
|
39
35
|
}
|
|
40
36
|
else {
|
|
41
|
-
// Push all *.requirements.md files in .requirements/
|
|
42
|
-
const
|
|
43
|
-
filesToPush =
|
|
44
|
-
.filter(f => f.endsWith('.requirements.md'))
|
|
45
|
-
.map(f => path.join(requirementsDir, f));
|
|
37
|
+
// SYNC-DISCOVERY-1.0: Push all *.requirements.md files in workspace (not just .requirements/)
|
|
38
|
+
const workspaceRoot = process.cwd();
|
|
39
|
+
filesToPush = await findRequirementsFiles(workspaceRoot);
|
|
46
40
|
if (filesToPush.length === 0) {
|
|
47
|
-
console.log('No *.requirements.md files found
|
|
41
|
+
console.log('No *.requirements.md files found. Nothing to push.');
|
|
48
42
|
return;
|
|
49
43
|
}
|
|
50
44
|
}
|
package/dist/convex.d.ts
CHANGED
package/dist/convex.js
CHANGED
|
@@ -14,10 +14,16 @@ export interface FinalizeOptions {
|
|
|
14
14
|
cwd?: string;
|
|
15
15
|
/** Whether to report to cloud (defaults to true) */
|
|
16
16
|
reportToCloud?: boolean;
|
|
17
|
-
/** Whether to print local report (defaults to true) */
|
|
18
|
-
printLocalReport?: boolean;
|
|
19
17
|
/** Whether to clean up tracking data on success (defaults to true) */
|
|
20
18
|
cleanup?: boolean;
|
|
19
|
+
/** Whether to show coverage summary stats (defaults to true) */
|
|
20
|
+
showSummary?: boolean;
|
|
21
|
+
/** Whether to show the list of tested requirements (defaults to false) */
|
|
22
|
+
showTestedList?: boolean;
|
|
23
|
+
/** Whether to show the list of untested requirements (defaults to true) */
|
|
24
|
+
showUntestedList?: boolean;
|
|
25
|
+
/** Whether to show cloud reporting status messages (defaults to true) */
|
|
26
|
+
showCloudStatus?: boolean;
|
|
21
27
|
}
|
|
22
28
|
export interface FinalizeResult {
|
|
23
29
|
/** Number of unique requirements exercised */
|
package/dist/harness/finalize.js
CHANGED
|
@@ -46,19 +46,24 @@ function getCurrentBranch(cwd) {
|
|
|
46
46
|
*
|
|
47
47
|
* HARNESS-FINALIZE-1: Shows which requirements were tested
|
|
48
48
|
*/
|
|
49
|
-
function printLocalReport(testedKeys, lookup) {
|
|
49
|
+
function printLocalReport(testedKeys, lookup, options) {
|
|
50
|
+
const { showSummary, showTestedList, showUntestedList } = options;
|
|
50
51
|
const allKeys = lookup ? Object.keys(lookup.requirements) : [];
|
|
51
52
|
const untestedKeys = allKeys.filter(key => !testedKeys.includes(key));
|
|
52
53
|
const total = allKeys.length;
|
|
53
54
|
const tested = testedKeys.length;
|
|
54
55
|
const coverage = total > 0 ? ((tested / total) * 100).toFixed(1) : '0.0';
|
|
55
|
-
let report = '
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
let report = '';
|
|
57
|
+
// Show summary stats
|
|
58
|
+
if (showSummary) {
|
|
59
|
+
report += '\n=== Requirements Coverage Report ===\n';
|
|
60
|
+
report += `\nTotal Requirements: ${total}\n`;
|
|
61
|
+
report += `Tested Requirements: ${tested}\n`;
|
|
62
|
+
report += `Untested Requirements: ${untestedKeys.length}\n`;
|
|
63
|
+
report += `Coverage: ${coverage}%\n`;
|
|
64
|
+
}
|
|
60
65
|
// Show tested requirements
|
|
61
|
-
if (testedKeys.length > 0) {
|
|
66
|
+
if (showTestedList && testedKeys.length > 0) {
|
|
62
67
|
report += '\n✓ Tested Requirements:\n';
|
|
63
68
|
for (const key of testedKeys) {
|
|
64
69
|
const req = lookup?.requirements[key];
|
|
@@ -75,7 +80,7 @@ function printLocalReport(testedKeys, lookup) {
|
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
// Show untested requirements
|
|
78
|
-
if (untestedKeys.length > 0) {
|
|
83
|
+
if (showUntestedList && untestedKeys.length > 0) {
|
|
79
84
|
report += '\n✗ Untested Requirements:\n';
|
|
80
85
|
for (const key of untestedKeys) {
|
|
81
86
|
const req = lookup?.requirements[key];
|
|
@@ -91,8 +96,12 @@ function printLocalReport(testedKeys, lookup) {
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
if (showSummary) {
|
|
100
|
+
report += '\n====================================\n';
|
|
101
|
+
}
|
|
102
|
+
if (report) {
|
|
103
|
+
console.log(report);
|
|
104
|
+
}
|
|
96
105
|
}
|
|
97
106
|
/**
|
|
98
107
|
* Production Convex deployment URL
|
|
@@ -104,13 +113,15 @@ const CONVEX_URL = 'https://data.dotrequirements.io';
|
|
|
104
113
|
* HARNESS-FINALIZE-2: Cloud reporting with error tolerance
|
|
105
114
|
* HARNESS-FINALIZE-3: Coverage records include requirement, file, line, branch
|
|
106
115
|
*/
|
|
107
|
-
async function reportToConvex(projectRoot, testRunId, aggregated) {
|
|
116
|
+
async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatus) {
|
|
108
117
|
try {
|
|
109
118
|
// Load credentials
|
|
110
119
|
loadEnvFile(projectRoot);
|
|
111
120
|
const credentials = getProjectCredentials();
|
|
112
121
|
if (!credentials) {
|
|
113
|
-
|
|
122
|
+
if (showCloudStatus) {
|
|
123
|
+
console.log('\nℹ️ Skipping cloud coverage reporting (DOTREQUIREMENTS_PROJECT_ID or DOTREQUIREMENTS_PROJECT_SECRET not configured)');
|
|
124
|
+
}
|
|
114
125
|
return { sent: false, count: 0 };
|
|
115
126
|
}
|
|
116
127
|
const { projectId, projectSecret } = credentials;
|
|
@@ -125,7 +136,9 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
|
|
|
125
136
|
const coverageCache = readCoverageCache(requirementsDir);
|
|
126
137
|
const keysToReport = requirementKeys.filter(key => needsReporting(key, coverageCache));
|
|
127
138
|
if (keysToReport.length === 0) {
|
|
128
|
-
|
|
139
|
+
if (showCloudStatus) {
|
|
140
|
+
console.log('\n✓ Coverage unchanged since last run (skipping cloud report)');
|
|
141
|
+
}
|
|
129
142
|
// Still update the cache
|
|
130
143
|
updateCoverageCache(requirementsDir, testRunId, requirementKeys);
|
|
131
144
|
return { sent: false, count: 0 };
|
|
@@ -184,7 +197,9 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
|
|
|
184
197
|
});
|
|
185
198
|
if (!response.ok) {
|
|
186
199
|
const error = await response.text();
|
|
187
|
-
|
|
200
|
+
if (showCloudStatus) {
|
|
201
|
+
console.warn(`\n⚠️ Failed to report coverage to cloud: ${response.status} ${error}`);
|
|
202
|
+
}
|
|
188
203
|
return { sent: false, count: 0, error: `${response.status} ${error}` };
|
|
189
204
|
}
|
|
190
205
|
// Check for Convex-level errors in the response body
|
|
@@ -201,18 +216,24 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
|
|
|
201
216
|
const convexResponse = responseData;
|
|
202
217
|
if (convexResponse.status === 'error') {
|
|
203
218
|
const errorMsg = convexResponse.errorMessage || 'Unknown Convex error';
|
|
204
|
-
|
|
219
|
+
if (showCloudStatus) {
|
|
220
|
+
console.warn(`\n⚠️ Failed to report coverage to cloud: ${errorMsg}`);
|
|
221
|
+
}
|
|
205
222
|
return { sent: false, count: 0, error: errorMsg };
|
|
206
223
|
}
|
|
207
224
|
}
|
|
208
|
-
|
|
225
|
+
if (showCloudStatus) {
|
|
226
|
+
console.log(`\n✓ Reported ${keysToReport.length} requirement(s) to cloud (branch: ${branch})`);
|
|
227
|
+
}
|
|
209
228
|
// Update coverage cache
|
|
210
229
|
updateCoverageCache(requirementsDir, testRunId, requirementKeys);
|
|
211
230
|
return { sent: true, count: keysToReport.length };
|
|
212
231
|
}
|
|
213
232
|
catch (error) {
|
|
214
233
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
215
|
-
|
|
234
|
+
if (showCloudStatus) {
|
|
235
|
+
console.warn(`\n⚠️ Error reporting coverage to cloud: ${errorMessage}`);
|
|
236
|
+
}
|
|
216
237
|
return { sent: false, count: 0, error: errorMessage };
|
|
217
238
|
}
|
|
218
239
|
}
|
|
@@ -228,7 +249,7 @@ async function reportToConvex(projectRoot, testRunId, aggregated) {
|
|
|
228
249
|
* HARNESS-FINALIZE-4: Finalize cleans up after itself
|
|
229
250
|
*/
|
|
230
251
|
export async function finalize(options = {}) {
|
|
231
|
-
const { cwd = process.cwd(), reportToCloud: shouldReportToCloud = true,
|
|
252
|
+
const { cwd = process.cwd(), reportToCloud: shouldReportToCloud = true, cleanup: shouldCleanup = true, showSummary = true, showTestedList = false, showUntestedList = true, showCloudStatus = true, } = options;
|
|
232
253
|
// Priority 1: Use environment variable (cross-process persistence from globalSetup)
|
|
233
254
|
let projectRoot = process.env.DOTREQUIREMENTS_PROJECT_ROOT || null;
|
|
234
255
|
// Priority 2: Find from cwd
|
|
@@ -270,13 +291,14 @@ export async function finalize(options = {}) {
|
|
|
270
291
|
? (testedKeys.length / totalRequirements) * 100
|
|
271
292
|
: 0;
|
|
272
293
|
// HARNESS-FINALIZE-1: Print local report
|
|
294
|
+
const shouldPrintLocal = showSummary || showTestedList || showUntestedList;
|
|
273
295
|
if (shouldPrintLocal) {
|
|
274
|
-
printLocalReport(testedKeys, lookup);
|
|
296
|
+
printLocalReport(testedKeys, lookup, { showSummary, showTestedList, showUntestedList });
|
|
275
297
|
}
|
|
276
298
|
// HARNESS-FINALIZE-2, HARNESS-FINALIZE-3: Report to cloud
|
|
277
299
|
let cloudResult = { sent: false, count: 0 };
|
|
278
300
|
if (shouldReportToCloud) {
|
|
279
|
-
cloudResult = await reportToConvex(projectRoot, testRunId, aggregated);
|
|
301
|
+
cloudResult = await reportToConvex(projectRoot, testRunId, aggregated, showCloudStatus);
|
|
280
302
|
}
|
|
281
303
|
// HARNESS-FINALIZE-4: Clean up tracking data
|
|
282
304
|
if (shouldCleanup && !cloudResult.error) {
|
package/dist/mcp/index.js
CHANGED
|
@@ -33,6 +33,17 @@ function getProjectPathsFromEnv() {
|
|
|
33
33
|
// Get workspace root from environment or default to cwd
|
|
34
34
|
const WORKSPACE_ROOT = process.env.REQUIREMENTS_DIR || process.cwd();
|
|
35
35
|
const PROJECT_PATHS = getProjectPathsFromEnv();
|
|
36
|
+
const STYLE_CHECK_GUIDANCE = `
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## How to use this feedback
|
|
41
|
+
|
|
42
|
+
1. **This check is stateless.** It is a "new set of eyes" from any previous feedback rounds — it has no memory of prior suggestions or changes you've already made.
|
|
43
|
+
|
|
44
|
+
2. **Act on feedback from the posture of "what will make the requirements better."** The severity categories (MUST FIX / SHOULD FIX / COULD IMPROVE) are prioritized for convenience, but unless the user has explicitly told you to ignore lower-priority feedback, go ahead and make any improvements — minor or otherwise. If you believe acting on a specific piece of feedback would make the requirements *worse*, surface that feedback to the user rather than silently ignoring it. Otherwise, just act.
|
|
45
|
+
|
|
46
|
+
3. **Next steps:** Always run \`validate_requirements\` again after making updates. If you make significant changes based on this feedback, you can run \`style_check\` again — but be aware that will be a fresh set of eyes, not a continued conversation.`;
|
|
36
47
|
// Cache for loaded requirements (refreshed on each tool call for now)
|
|
37
48
|
let cachedRequirements = new Map();
|
|
38
49
|
let cachedDiscoveryResult = null;
|
|
@@ -1070,9 +1081,6 @@ ${projectList || '(none found)'}`,
|
|
|
1070
1081
|
**Use concise domain prefixes** like \`AUTH-1\`, \`LOGIN-1\`, etc. Start numbering at 1 and increment sequentially.`;
|
|
1071
1082
|
}
|
|
1072
1083
|
const template = `---
|
|
1073
|
-
projectId: "your-project-id"
|
|
1074
|
-
pulledAt: "${new Date().toISOString()}"
|
|
1075
|
-
version: 1
|
|
1076
1084
|
document:
|
|
1077
1085
|
title: "Example Requirements"
|
|
1078
1086
|
---
|
|
@@ -1280,7 +1288,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1280
1288
|
content: [
|
|
1281
1289
|
{
|
|
1282
1290
|
type: 'text',
|
|
1283
|
-
text: `${headline}${pushDetails}\n\n**Metadata:**\n-
|
|
1291
|
+
text: `${headline}${pushDetails}\n\n**Metadata:**\n- Version: ${parsed.metadata.version ?? '(none)'}\n- Document: ${parsed.metadata.document?.title || '(none)'}\n- Document ID: ${parsed.metadata.document?.id || '(none)'}\n\n**Requirements:** ${reqCount} root requirement(s) found`,
|
|
1284
1292
|
},
|
|
1285
1293
|
],
|
|
1286
1294
|
};
|
|
@@ -1559,7 +1567,7 @@ describe(requirement('AUTH-LOGIN-1'), () => {
|
|
|
1559
1567
|
content: [
|
|
1560
1568
|
{
|
|
1561
1569
|
type: 'text',
|
|
1562
|
-
text: `# Style Check Results for \`${filePath}\`\n\n${data.feedback}`,
|
|
1570
|
+
text: `# Style Check Results for \`${filePath}\`\n\n${data.feedback}${STYLE_CHECK_GUIDANCE}`,
|
|
1563
1571
|
},
|
|
1564
1572
|
],
|
|
1565
1573
|
};
|
|
@@ -48,10 +48,10 @@ export declare function convexToRequirements(convexReqs: ConvexRequirement[]): R
|
|
|
48
48
|
*/
|
|
49
49
|
export declare function requirementsToConvex(nodes: RequirementNode[], projectId: string, documentId: string): ConvexRequirement[];
|
|
50
50
|
/**
|
|
51
|
-
* Build metadata
|
|
51
|
+
* Build metadata for a requirements file.
|
|
52
52
|
* DOC-HEADER-11.3: When pulling, include defaultPrefix in the frontmatter.
|
|
53
53
|
*/
|
|
54
|
-
export declare function buildMetadata(
|
|
54
|
+
export declare function buildMetadata(version?: number, document?: {
|
|
55
55
|
id?: string;
|
|
56
56
|
title: string;
|
|
57
57
|
defaultPrefix?: string;
|
|
@@ -166,12 +166,11 @@ function flattenNode(node, projectId, rootId, position, result, documentId) {
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
/**
|
|
169
|
-
* Build metadata
|
|
169
|
+
* Build metadata for a requirements file.
|
|
170
170
|
* DOC-HEADER-11.3: When pulling, include defaultPrefix in the frontmatter.
|
|
171
171
|
*/
|
|
172
|
-
export function buildMetadata(
|
|
172
|
+
export function buildMetadata(version = 1, document) {
|
|
173
173
|
return {
|
|
174
|
-
projectId,
|
|
175
174
|
pulledAt: new Date().toISOString(),
|
|
176
175
|
version,
|
|
177
176
|
document,
|
package/dist/schema/schemas.d.ts
CHANGED
|
@@ -54,13 +54,12 @@ export declare function parseRequirementKey(key: string): {
|
|
|
54
54
|
*/
|
|
55
55
|
export declare function buildRequirementKey(prefix: string, number: number): string;
|
|
56
56
|
/**
|
|
57
|
-
* Metadata block at the top of every requirements file
|
|
57
|
+
* Metadata block at the top of every requirements file.
|
|
58
|
+
* Only document.title is required for push; everything else is optional or auto-filled.
|
|
58
59
|
*/
|
|
59
60
|
export declare const MetadataSchema: z.ZodObject<{
|
|
60
|
-
|
|
61
|
-
projectSlug: z.ZodOptional<z.ZodString>;
|
|
61
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
62
62
|
pulledAt: z.ZodOptional<z.ZodString>;
|
|
63
|
-
version: z.ZodNumber;
|
|
64
63
|
document: z.ZodOptional<z.ZodObject<{
|
|
65
64
|
id: z.ZodOptional<z.ZodString>;
|
|
66
65
|
title: z.ZodString;
|
|
@@ -75,9 +74,7 @@ export declare const MetadataSchema: z.ZodObject<{
|
|
|
75
74
|
defaultPrefix?: string | undefined;
|
|
76
75
|
}>>;
|
|
77
76
|
}, "strip", z.ZodTypeAny, {
|
|
78
|
-
version
|
|
79
|
-
projectId: string;
|
|
80
|
-
projectSlug?: string | undefined;
|
|
77
|
+
version?: number | undefined;
|
|
81
78
|
pulledAt?: string | undefined;
|
|
82
79
|
document?: {
|
|
83
80
|
title: string;
|
|
@@ -85,9 +82,7 @@ export declare const MetadataSchema: z.ZodObject<{
|
|
|
85
82
|
defaultPrefix?: string | undefined;
|
|
86
83
|
} | undefined;
|
|
87
84
|
}, {
|
|
88
|
-
version
|
|
89
|
-
projectId: string;
|
|
90
|
-
projectSlug?: string | undefined;
|
|
85
|
+
version?: number | undefined;
|
|
91
86
|
pulledAt?: string | undefined;
|
|
92
87
|
document?: {
|
|
93
88
|
title: string;
|
|
@@ -149,10 +144,8 @@ export type RequirementNode = {
|
|
|
149
144
|
*/
|
|
150
145
|
export declare const RequirementsFileSchema: z.ZodObject<{
|
|
151
146
|
_meta: z.ZodObject<{
|
|
152
|
-
|
|
153
|
-
projectSlug: z.ZodOptional<z.ZodString>;
|
|
147
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
154
148
|
pulledAt: z.ZodOptional<z.ZodString>;
|
|
155
|
-
version: z.ZodNumber;
|
|
156
149
|
document: z.ZodOptional<z.ZodObject<{
|
|
157
150
|
id: z.ZodOptional<z.ZodString>;
|
|
158
151
|
title: z.ZodString;
|
|
@@ -167,9 +160,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
167
160
|
defaultPrefix?: string | undefined;
|
|
168
161
|
}>>;
|
|
169
162
|
}, "strip", z.ZodTypeAny, {
|
|
170
|
-
version
|
|
171
|
-
projectId: string;
|
|
172
|
-
projectSlug?: string | undefined;
|
|
163
|
+
version?: number | undefined;
|
|
173
164
|
pulledAt?: string | undefined;
|
|
174
165
|
document?: {
|
|
175
166
|
title: string;
|
|
@@ -177,9 +168,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
177
168
|
defaultPrefix?: string | undefined;
|
|
178
169
|
} | undefined;
|
|
179
170
|
}, {
|
|
180
|
-
version
|
|
181
|
-
projectId: string;
|
|
182
|
-
projectSlug?: string | undefined;
|
|
171
|
+
version?: number | undefined;
|
|
183
172
|
pulledAt?: string | undefined;
|
|
184
173
|
document?: {
|
|
185
174
|
title: string;
|
|
@@ -189,10 +178,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
189
178
|
}>;
|
|
190
179
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
191
180
|
_meta: z.ZodObject<{
|
|
192
|
-
|
|
193
|
-
projectSlug: z.ZodOptional<z.ZodString>;
|
|
181
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
194
182
|
pulledAt: z.ZodOptional<z.ZodString>;
|
|
195
|
-
version: z.ZodNumber;
|
|
196
183
|
document: z.ZodOptional<z.ZodObject<{
|
|
197
184
|
id: z.ZodOptional<z.ZodString>;
|
|
198
185
|
title: z.ZodString;
|
|
@@ -207,9 +194,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
207
194
|
defaultPrefix?: string | undefined;
|
|
208
195
|
}>>;
|
|
209
196
|
}, "strip", z.ZodTypeAny, {
|
|
210
|
-
version
|
|
211
|
-
projectId: string;
|
|
212
|
-
projectSlug?: string | undefined;
|
|
197
|
+
version?: number | undefined;
|
|
213
198
|
pulledAt?: string | undefined;
|
|
214
199
|
document?: {
|
|
215
200
|
title: string;
|
|
@@ -217,9 +202,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
217
202
|
defaultPrefix?: string | undefined;
|
|
218
203
|
} | undefined;
|
|
219
204
|
}, {
|
|
220
|
-
version
|
|
221
|
-
projectId: string;
|
|
222
|
-
projectSlug?: string | undefined;
|
|
205
|
+
version?: number | undefined;
|
|
223
206
|
pulledAt?: string | undefined;
|
|
224
207
|
document?: {
|
|
225
208
|
title: string;
|
|
@@ -229,10 +212,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
229
212
|
}>;
|
|
230
213
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
231
214
|
_meta: z.ZodObject<{
|
|
232
|
-
|
|
233
|
-
projectSlug: z.ZodOptional<z.ZodString>;
|
|
215
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
234
216
|
pulledAt: z.ZodOptional<z.ZodString>;
|
|
235
|
-
version: z.ZodNumber;
|
|
236
217
|
document: z.ZodOptional<z.ZodObject<{
|
|
237
218
|
id: z.ZodOptional<z.ZodString>;
|
|
238
219
|
title: z.ZodString;
|
|
@@ -247,9 +228,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
247
228
|
defaultPrefix?: string | undefined;
|
|
248
229
|
}>>;
|
|
249
230
|
}, "strip", z.ZodTypeAny, {
|
|
250
|
-
version
|
|
251
|
-
projectId: string;
|
|
252
|
-
projectSlug?: string | undefined;
|
|
231
|
+
version?: number | undefined;
|
|
253
232
|
pulledAt?: string | undefined;
|
|
254
233
|
document?: {
|
|
255
234
|
title: string;
|
|
@@ -257,9 +236,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
257
236
|
defaultPrefix?: string | undefined;
|
|
258
237
|
} | undefined;
|
|
259
238
|
}, {
|
|
260
|
-
version
|
|
261
|
-
projectId: string;
|
|
262
|
-
projectSlug?: string | undefined;
|
|
239
|
+
version?: number | undefined;
|
|
263
240
|
pulledAt?: string | undefined;
|
|
264
241
|
document?: {
|
|
265
242
|
title: string;
|
package/dist/schema/schemas.js
CHANGED
|
@@ -104,19 +104,16 @@ export function buildRequirementKey(prefix, number) {
|
|
|
104
104
|
// Metadata Schema
|
|
105
105
|
// =============================================================================
|
|
106
106
|
/**
|
|
107
|
-
* Metadata block at the top of every requirements file
|
|
107
|
+
* Metadata block at the top of every requirements file.
|
|
108
|
+
* Only document.title is required for push; everything else is optional or auto-filled.
|
|
108
109
|
*/
|
|
109
110
|
export const MetadataSchema = z.object({
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
pulledAt: z.string().optional(), // ISO 8601 timestamp - required for CLI files, optional for IDE
|
|
113
|
-
version: z.number(),
|
|
114
|
-
// Optional document association (one file = one document)
|
|
111
|
+
version: z.number().optional(), // Informational - incremented on push
|
|
112
|
+
pulledAt: z.string().optional(), // ISO 8601 timestamp - used for conflict detection
|
|
115
113
|
document: z.object({
|
|
116
|
-
id: z.string().optional(), //
|
|
117
|
-
title: z.string(),
|
|
118
|
-
//
|
|
119
|
-
defaultPrefix: z.string().optional(),
|
|
114
|
+
id: z.string().optional(), // Cloud document link - filled by push when creating
|
|
115
|
+
title: z.string(), // Required for push
|
|
116
|
+
defaultPrefix: z.string().optional(), // Editor hint for new requirement keys
|
|
120
117
|
}).optional(),
|
|
121
118
|
});
|
|
122
119
|
// =============================================================================
|
|
@@ -6,6 +6,8 @@ import { api } from '../convex.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export async function selectTeam(client) {
|
|
8
8
|
console.log('Fetching your teams...\n');
|
|
9
|
+
// AUTH-24.0: Ensure user has a default team if they don't have any
|
|
10
|
+
await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
|
|
9
11
|
const teams = await client.query(api.teams.queries.listForUserWithUsage);
|
|
10
12
|
if (teams.length === 0) {
|
|
11
13
|
console.log('You are not a member of any teams.');
|