@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 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
- ✓ AUTH-LOGIN-1 (A registered user, Jamie, can log in to their account)
300
- ✓ AUTH-LOGIN-1.0 (When Jamie provides a valid username and password...)
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
- Untested:
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 (`projectId`, `version`, `document`)
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
@@ -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) {
@@ -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(requirementsDir);
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(requirementsDir) {
127
+ async function buildDocumentIdIndex(workspaceRoot) {
128
128
  const index = new Map();
129
- if (!fs.existsSync(requirementsDir)) {
130
- return index;
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);
@@ -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 files = fs.readdirSync(requirementsDir);
43
- filesToPush = files
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 in .requirements/. Nothing to push.');
41
+ console.log('No *.requirements.md files found. Nothing to push.');
48
42
  return;
49
43
  }
50
44
  }
package/dist/convex.d.ts CHANGED
@@ -35,6 +35,9 @@ export declare const api: {
35
35
  queries: {
36
36
  listForUserWithUsage: any;
37
37
  };
38
+ mutations: {
39
+ ensureDefaultTeam: any;
40
+ };
38
41
  };
39
42
  projectSecrets: {
40
43
  queries: {
package/dist/convex.js CHANGED
@@ -37,6 +37,9 @@ export const api = {
37
37
  queries: {
38
38
  listForUserWithUsage: 'teams/queries:listForUserWithUsage',
39
39
  },
40
+ mutations: {
41
+ ensureDefaultTeam: 'teams/mutations:ensureDefaultTeam',
42
+ },
40
43
  },
41
44
  projectSecrets: {
42
45
  queries: {
@@ -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 */
@@ -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 = '\n=== Requirements Coverage Report ===\n';
56
- report += `\nTotal Requirements: ${total}\n`;
57
- report += `Tested Requirements: ${tested}\n`;
58
- report += `Untested Requirements: ${untestedKeys.length}\n`;
59
- report += `Coverage: ${coverage}%\n`;
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
- report += '\n====================================\n';
95
- console.log(report);
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
- console.log('\nℹ️ Skipping cloud coverage reporting (DOTREQUIREMENTS_PROJECT_ID or DOTREQUIREMENTS_PROJECT_SECRET not configured)');
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
- console.log('\n✓ Coverage unchanged since last run (skipping cloud report)');
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
- console.warn(`\n⚠️ Failed to report coverage to cloud: ${response.status} ${error}`);
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
- console.warn(`\n⚠️ Failed to report coverage to cloud: ${errorMsg}`);
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
- console.log(`\n✓ Reported ${keysToReport.length} requirement(s) to cloud (branch: ${branch})`);
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
- console.warn(`\n⚠️ Error reporting coverage to cloud: ${errorMessage}`);
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, printLocalReport: shouldPrintLocal = true, cleanup: shouldCleanup = true, } = options;
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- Project ID: ${parsed.metadata.projectId}\n- Version: ${parsed.metadata.version}\n- Document: ${parsed.metadata.document?.title || '(none)'}\n- Document ID: ${parsed.metadata.document?.id || '(none)'}\n\n**Requirements:** ${reqCount} root requirement(s) found`,
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 from project info and pull time.
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(projectId: string, version?: number, document?: {
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 from project info and pull time.
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(projectId, version = 1, document) {
172
+ export function buildMetadata(version = 1, document) {
173
173
  return {
174
- projectId,
175
174
  pulledAt: new Date().toISOString(),
176
175
  version,
177
176
  document,
@@ -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
- projectId: z.ZodString;
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: number;
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: number;
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
- projectId: z.ZodString;
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: number;
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: number;
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
- projectId: z.ZodString;
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: number;
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: number;
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
- projectId: z.ZodString;
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: number;
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: number;
261
- projectId: string;
262
- projectSlug?: string | undefined;
239
+ version?: number | undefined;
263
240
  pulledAt?: string | undefined;
264
241
  document?: {
265
242
  title: string;
@@ -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
- projectId: z.string(),
111
- projectSlug: z.string().optional(), // User-friendly slug (e.g., "quick-fox-123")
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(), // Optional - filled by push when creating new document
117
- title: z.string(),
118
- // DOC-HEADER-11: Default prefix for new requirement keys in this document
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
  // =============================================================================
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { buildRequirementsMarkdown, parseRequirementsFile, buildMetadata } from './index.js';
5
5
  // Create test data
6
- const metadata = buildMetadata('test-project-123', 1);
6
+ const metadata = buildMetadata(1);
7
7
  const testRequirements = [
8
8
  {
9
9
  id: 'REQ123',
@@ -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.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Requirements tracking CLI, test harness, and MCP server",
5
5
  "type": "module",
6
6
  "bin": {