@popoverai/dotrequirements 0.27.4 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +24 -20
  2. package/dist/cli.js +34 -12
  3. package/dist/codebase-to-spec/compose.d.ts +3 -2
  4. package/dist/codebase-to-spec/compose.js +3 -3
  5. package/dist/commands/aliases.d.ts +26 -0
  6. package/dist/commands/aliases.js +31 -0
  7. package/dist/commands/diff.d.ts +14 -0
  8. package/dist/commands/diff.js +62 -0
  9. package/dist/commands/init.js +5 -5
  10. package/dist/commands/link.d.ts +1 -1
  11. package/dist/commands/link.js +10 -7
  12. package/dist/commands/sync-common.d.ts +21 -0
  13. package/dist/commands/sync-common.js +24 -0
  14. package/dist/commands/sync.d.ts +26 -0
  15. package/dist/commands/sync.js +187 -0
  16. package/dist/convex.d.ts +1 -1
  17. package/dist/convex.js +2 -2
  18. package/dist/push/core.d.ts +18 -116
  19. package/dist/push/core.js +23 -267
  20. package/dist/push/index.d.ts +3 -3
  21. package/dist/push/index.js +4 -4
  22. package/dist/requirements/index.d.ts +2 -0
  23. package/dist/requirements/index.js +6 -1
  24. package/dist/requirements/style-guide.js +4 -4
  25. package/dist/schema/browser.d.ts +1 -0
  26. package/dist/schema/browser.js +3 -0
  27. package/dist/schema/index.d.ts +1 -0
  28. package/dist/schema/index.js +1 -0
  29. package/dist/schema/parser.d.ts +3 -0
  30. package/dist/schema/parser.js +1 -1
  31. package/dist/schema/schemas.d.ts +24 -22
  32. package/dist/schema/schemas.js +9 -12
  33. package/dist/schema/title-markdown.d.ts +30 -0
  34. package/dist/schema/title-markdown.js +38 -0
  35. package/dist/sync/compare.d.ts +21 -0
  36. package/dist/sync/compare.js +285 -0
  37. package/dist/sync/execute.d.ts +53 -0
  38. package/dist/sync/execute.js +225 -0
  39. package/dist/sync/index.d.ts +21 -0
  40. package/dist/sync/index.js +52 -0
  41. package/dist/sync/local-files.d.ts +26 -0
  42. package/dist/sync/local-files.js +93 -0
  43. package/dist/sync/plan.d.ts +39 -0
  44. package/dist/sync/plan.js +90 -0
  45. package/dist/sync/render.d.ts +17 -0
  46. package/dist/sync/render.js +46 -0
  47. package/dist/sync/segment.d.ts +40 -0
  48. package/dist/sync/segment.js +76 -0
  49. package/dist/sync/snapshot.d.ts +30 -0
  50. package/dist/sync/snapshot.js +122 -0
  51. package/dist/sync/types.d.ts +82 -0
  52. package/dist/sync/types.js +12 -0
  53. package/dist/templates/context-file-section.md +2 -1
  54. package/dist/templates/example-requirements.js +0 -2
  55. package/dist/templates/example-requirements.ts +0 -2
  56. package/dist/templates/requirements-readme.js +3 -4
  57. package/dist/templates/requirements-readme.ts +3 -4
  58. package/dist/templates/skills/codebase-to-spec/SKILL.md +2 -2
  59. package/dist/utils/project-settings.d.ts +8 -2
  60. package/dist/utils/project-settings.js +47 -24
  61. package/package.json +1 -1
  62. package/dist/commands/pull.d.ts +0 -8
  63. package/dist/commands/pull.js +0 -230
  64. package/dist/commands/push.d.ts +0 -6
  65. package/dist/commands/push.js +0 -244
package/dist/push/core.js CHANGED
@@ -1,20 +1,12 @@
1
1
  /**
2
- * Shared push logic for CLI and MCP.
3
- *
4
- * This module contains the core push functionality that both the CLI push command
5
- * and MCP push_requirements tool use. It handles:
6
- * - Parsing local files
7
- * - Dry run validation with Convex
8
- * - Conflict detection
9
- * - Executing the push
10
- * - Updating local files with document IDs and timestamps
2
+ * Shared parse-and-write-back primitives for syncing local requirements files
3
+ * to the cloud. The comparator-driven sync path (`packages/cli/src/sync/`)
4
+ * consumes these; the former dry-run/execute push pipeline was retired with the
5
+ * `push` command (its behavior now lives in the sync executor).
11
6
  */
12
7
  import * as fs from "node:fs";
13
- import * as path from "node:path";
14
- import { ConvexHttpClient } from "convex/browser";
15
8
  import YAML from "yaml";
16
- import { api } from "../convex.js";
17
- import { buildRequirementsFile, getAllRequirements, parseRequirementKey, parseRequirementsFromFile, } from "../schema/index.js";
9
+ import { getAllRequirements, parseRequirementKey, parseRequirementsFromFile, } from "../schema/index.js";
18
10
  import { extractFrontmatterBlock, stripFrontmatterBlock, } from "../schema/parser-core.js";
19
11
  /** Base URL of the web app, where synced documents are reviewed. */
20
12
  export const WEB_APP_URL = "https://app.dotrequirements.io";
@@ -26,7 +18,7 @@ export const WEB_APP_URL = "https://app.dotrequirements.io";
26
18
  */
27
19
  export function extractMarkdownContent(rawContent) {
28
20
  // Shared CRLF-normalizing helper (SYNC-FORMAT-1): the stripped body is
29
- // pushed as the document's canonical markdownContent (SYNC-ARCH-1), so a
21
+ // synced as the document's canonical markdownContent (SYNC-ARCH-1), so a
30
22
  // CRLF-authored file must not leak its frontmatter fence into the cloud
31
23
  // body.
32
24
  return stripFrontmatterBlock(rawContent);
@@ -55,11 +47,11 @@ function extractRawFrontmatter(rawContent) {
55
47
  return undefined;
56
48
  }
57
49
  /**
58
- * DOC-HEADER-14: merge the validated (and push-updated) metadata over the raw
50
+ * DOC-HEADER-14: merge the validated (and sync-updated) metadata over the raw
59
51
  * frontmatter so unrecognized keys survive the rewrite while the fields the
60
- * push owns (document ID, pulledAt, defaultPrefix, version) stay updated.
52
+ * sync owns (document ID, pulledAt, defaultPrefix, version) stay updated.
61
53
  */
62
- function mergeMetadataWithRawFrontmatter(metadata, rawFrontmatter) {
54
+ export function mergeMetadataWithRawFrontmatter(metadata, rawFrontmatter) {
63
55
  if (!rawFrontmatter) {
64
56
  return metadata;
65
57
  }
@@ -73,15 +65,22 @@ function mergeMetadataWithRawFrontmatter(metadata, rawFrontmatter) {
73
65
  ...rawDocument,
74
66
  ...metadata.document,
75
67
  };
68
+ // SYNC-TITLE-1.2: `document.title` is a recognized-but-retired field —
69
+ // the title lives in the body as its leading H1 — so a rewrite drops it
70
+ // rather than preserving it as if it were a user's custom field
71
+ // (DOC-HEADER-14 protects unrecognized fields, not this one).
72
+ if (!("title" in metadata.document)) {
73
+ delete merged.document.title;
74
+ }
76
75
  }
77
76
  return merged;
78
77
  }
79
78
  /**
80
- * Parse files for push. Returns parsed files with metadata and content.
79
+ * Parse files for sync. Returns parsed files with metadata and content.
81
80
  *
82
81
  * Throws (via `parseRequirementsFromFile`) if any file is syntactically
83
82
  * invalid. Callers that need one bad file not to abort the batch should parse
84
- * files individually and collect failures (see the MCP push handler, #45).
83
+ * files individually and collect failures.
85
84
  */
86
85
  export function parseFilesForPush(filePaths) {
87
86
  const parsedFiles = [];
@@ -128,11 +127,11 @@ export function parseFilesForPush(filePaths) {
128
127
  /**
129
128
  * SYNC-FAIL-4.0: parse files one at a time so a single invalid file cannot
130
129
  * abort the batch — its failure is collected per file while the valid files
131
- * still parse. Shared by the CLI push command and the MCP push handler (#45)
132
- * so their isolation semantics cannot drift.
130
+ * still parse.
133
131
  *
134
132
  * Note: cross-file checks that need the whole batch (e.g. SYNC-FAIL-3
135
- * duplicate document IDs) are enforced downstream in dryRunPush, not here.
133
+ * duplicate document IDs) are enforced by the sync path against the local
134
+ * snapshot, not here.
136
135
  */
137
136
  export function parseFilesForPushIndividually(filePaths) {
138
137
  const parsedFiles = [];
@@ -154,7 +153,7 @@ export function parseFilesForPushIndividually(filePaths) {
154
153
  return { parsedFiles, totalRequirements, parseFailures };
155
154
  }
156
155
  /**
157
- * SYNC-FAIL-3: two ParsedFiles carrying the same document.id would both push
156
+ * SYNC-FAIL-3: two ParsedFiles carrying the same document.id would both sync
158
157
  * as updates to one cloud document — last writer wins and the first spec is
159
158
  * silently destroyed. Abort instead, naming both files and the shared id.
160
159
  */
@@ -170,252 +169,9 @@ export function assertNoDuplicateDocumentIds(parsedFiles) {
170
169
  ` ${existingPath}\n` +
171
170
  ` ${file.filePath}\n` +
172
171
  `Each file must map to its own cloud document. Remove the "id:" line ` +
173
- `from the copy's frontmatter so it pushes as a new document, then push again.`);
172
+ `from the copy's frontmatter so it syncs as a new document, then sync again.`);
174
173
  }
175
174
  filesByDocId.set(docId, file.filePath);
176
175
  }
177
176
  }
178
- // ============================================================================
179
- // Dry Run
180
- // ============================================================================
181
- /**
182
- * Execute dry run phase: validate all files against Convex and detect conflicts.
183
- */
184
- export async function dryRunPush(parsedFiles, credentials) {
185
- // SYNC-FAIL-3.0: callers that assemble ParsedFiles themselves (e.g. the
186
- // MCP handler parses files one at a time) still abort before any cloud write
187
- assertNoDuplicateDocumentIds(parsedFiles);
188
- const client = new ConvexHttpClient(credentials.convexUrl);
189
- const dryRunResults = [];
190
- // Phase 1: Dry run to categorize all files
191
- for (const file of parsedFiles) {
192
- const doc = file.metadata.document;
193
- const fileName = path.basename(file.filePath);
194
- if (!doc) {
195
- // No document section - mark as invalid locally
196
- dryRunResults.push({
197
- file,
198
- result: {
199
- dryRun: true,
200
- action: "invalid",
201
- title: fileName,
202
- error: "Missing document section in frontmatter",
203
- },
204
- });
205
- continue;
206
- }
207
- try {
208
- const result = (await client.mutation(api.documents.saveWithRequirements.saveWithRequirements, {
209
- projectAuth: {
210
- projectSlug: credentials.projectId,
211
- projectSecret: credentials.projectSecret,
212
- },
213
- target: { type: "project", slug: credentials.projectId },
214
- documentId: doc.id,
215
- title: doc.title,
216
- markdownContent: file.markdownContent,
217
- defaultPrefix: doc.defaultPrefix,
218
- dryRun: true,
219
- }));
220
- dryRunResults.push({ file, result });
221
- }
222
- catch (err) {
223
- dryRunResults.push({
224
- file,
225
- result: {
226
- dryRun: true,
227
- action: "invalid",
228
- title: doc.title,
229
- error: err.message,
230
- },
231
- });
232
- }
233
- }
234
- // Categorize results
235
- const updates = dryRunResults.filter((r) => r.result.action === "update");
236
- const creates = dryRunResults.filter((r) => r.result.action === "create");
237
- const notFound = dryRunResults.filter((r) => r.result.action === "not_found");
238
- const invalid = dryRunResults.filter((r) => r.result.action === "invalid");
239
- // Check for conflicts on updates (cloud changed since last pull)
240
- const conflicts = [];
241
- if (updates.length > 0) {
242
- const docIds = updates.map((u) => u.result.documentId);
243
- const cloudMetadata = await client.query(api.documents.queries.getDocumentsMetadata, {
244
- projectAuth: {
245
- projectSlug: credentials.projectId,
246
- projectSecret: credentials.projectSecret,
247
- },
248
- target: { type: "project", slug: credentials.projectId },
249
- documentIds: docIds,
250
- });
251
- const cloudMetaByDocId = new Map(cloudMetadata.map((m) => [m.documentId, m]));
252
- for (const item of updates) {
253
- const docId = item.result.documentId;
254
- const cloudMeta = cloudMetaByDocId.get(docId);
255
- if (cloudMeta && item.file.metadata.pulledAt) {
256
- const pulledAtMs = new Date(item.file.metadata.pulledAt).getTime();
257
- if (cloudMeta.updatedAt > pulledAtMs) {
258
- conflicts.push({ item, cloudMeta });
259
- }
260
- }
261
- }
262
- }
263
- return {
264
- updates,
265
- creates,
266
- notFound,
267
- invalid,
268
- conflicts,
269
- totalRequirements: parsedFiles.reduce((sum, f) => sum + f.requirementCount, 0),
270
- };
271
- }
272
- // ============================================================================
273
- // Execute Push
274
- // ============================================================================
275
- /**
276
- * Execute the push: save all pushable files to Convex and update local files.
277
- */
278
- export async function executePush(dryRunResult, credentials) {
279
- const client = new ConvexHttpClient(credentials.convexUrl);
280
- const pushableFiles = [
281
- ...dryRunResult.updates,
282
- ...dryRunResult.creates,
283
- ...dryRunResult.notFound,
284
- ];
285
- let created = 0;
286
- let updated = 0;
287
- const errors = [];
288
- const importWarnings = [];
289
- const synced = [];
290
- const writeBackWarnings = [];
291
- for (const { file, result } of pushableFiles) {
292
- const doc = file.metadata.document;
293
- const fileName = path.basename(file.filePath);
294
- // For not_found, clear the ID so we create a new document
295
- const effectiveDocId = result.action === "not_found" ? undefined : doc.id;
296
- try {
297
- const rawResult = (await client.mutation(api.documents.saveWithRequirements.saveWithRequirements, {
298
- projectAuth: {
299
- projectSlug: credentials.projectId,
300
- projectSecret: credentials.projectSecret,
301
- },
302
- target: { type: "project", slug: credentials.projectId },
303
- documentId: effectiveDocId,
304
- title: doc.title,
305
- markdownContent: file.markdownContent,
306
- defaultPrefix: doc.defaultPrefix,
307
- // IMPORT-2: forward the CTS run marker when the file is stamped
308
- runMarker: file.metadata.ctsRun,
309
- dryRun: false,
310
- }));
311
- // The server returns a structured result iff we sent a runMarker
312
- const pushResult = typeof rawResult === "string" ? rawResult : rawResult.documentId;
313
- if (typeof rawResult !== "string") {
314
- const status = rawResult.importStatus;
315
- if (status === "invalid_marker" ||
316
- status === "unsupported_version" ||
317
- status === "already_used") {
318
- // IMPORT-3.2/3.3: loud, never fatal
319
- importWarnings.push({ fileName, status });
320
- }
321
- }
322
- const isCreate = result.action === "create" || result.action === "not_found";
323
- if (isCreate) {
324
- // New document - write ID back to file
325
- doc.id = pushResult;
326
- file.metadata.pulledAt = new Date().toISOString();
327
- file.metadata.version = 1;
328
- created++;
329
- }
330
- else {
331
- // Update pulledAt to reflect this push
332
- file.metadata.pulledAt = new Date().toISOString();
333
- file.metadata.version = (file.metadata.version || 0) + 1;
334
- updated++;
335
- }
336
- // SYNC-FAIL-2: the local write-back is separate from the cloud save —
337
- // the cloud document already exists at this point, so a write-back
338
- // failure is a warning on a synced document, never a push failure
339
- // (which would invite a retry that mints a duplicate cloud document).
340
- try {
341
- // DOC-HEADER-14: unrecognized frontmatter keys survive the rewrite
342
- const updatedContent = buildRequirementsFile(mergeMetadataWithRawFrontmatter(file.metadata, file.rawFrontmatter), file.markdownContent);
343
- fs.writeFileSync(file.filePath, updatedContent, "utf-8");
344
- }
345
- catch (writeErr) {
346
- writeBackWarnings.push({
347
- fileName,
348
- filePath: file.filePath,
349
- documentId: pushResult,
350
- error: writeErr instanceof Error ? writeErr.message : String(writeErr),
351
- });
352
- }
353
- // SYNC-LAND-1: every synced document gets its web URL in the result
354
- synced.push({
355
- fileName,
356
- documentId: pushResult,
357
- url: `${WEB_APP_URL}/documents/${pushResult}`,
358
- });
359
- }
360
- catch (err) {
361
- errors.push({
362
- fileName,
363
- filePath: file.filePath,
364
- error: errorDisplayMessage(err),
365
- });
366
- }
367
- }
368
- return {
369
- created,
370
- updated,
371
- errors,
372
- importWarnings,
373
- synced,
374
- writeBackWarnings,
375
- };
376
- }
377
- /**
378
- * Servers throw ConvexError({kind, message}) for expected failures (e.g.
379
- * LIMITS-4.2's limit error); the client-side Error message embeds that data
380
- * as JSON. Surface the human-readable message it carries instead of the blob.
381
- */
382
- function errorDisplayMessage(err) {
383
- const data = err.data;
384
- const fromData = humanMessage(data);
385
- if (fromData)
386
- return fromData;
387
- const message = err instanceof Error ? err.message : String(err);
388
- // ConvexError messages may embed the data JSON directly or after a
389
- // "ConvexError:" prefix — try the trailing {...} chunk
390
- const jsonStart = message.indexOf("{");
391
- if (jsonStart !== -1) {
392
- try {
393
- const parsed = JSON.parse(message.slice(jsonStart));
394
- const fromMessage = humanMessage(parsed);
395
- if (fromMessage)
396
- return fromMessage;
397
- }
398
- catch {
399
- // fall through to the raw message
400
- }
401
- }
402
- return message;
403
- }
404
- function humanMessage(data) {
405
- if (typeof data === "string") {
406
- try {
407
- return humanMessage(JSON.parse(data));
408
- }
409
- catch {
410
- return undefined;
411
- }
412
- }
413
- if (data &&
414
- typeof data === "object" &&
415
- "message" in data &&
416
- typeof data.message === "string") {
417
- return data.message;
418
- }
419
- return undefined;
420
- }
421
177
  //# sourceMappingURL=core.js.map
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Push Module
3
3
  *
4
- * Shared push logic for syncing local requirements files to the cloud.
5
- * Used by both CLI push command and MCP push_requirements tool.
4
+ * Shared parse-and-write-back primitives for syncing local requirements files
5
+ * to the cloud, consumed by the comparator-driven sync path.
6
6
  */
7
- export { type CloudDocumentMetadata, type ConflictInfo, type DryRunResult, type DryRunResultItem, dryRunPush, executePush, extractMarkdownContent, type FileWithDryRun, type ParsedFile, type ParseFailure, type PushCredentials, type PushResult, parseFilesForPush, parseFilesForPushIndividually, WEB_APP_URL, } from "./core.js";
7
+ export { assertNoDuplicateDocumentIds, extractMarkdownContent, mergeMetadataWithRawFrontmatter, type ParsedFile, type ParseFailure, parseFilesForPush, parseFilesForPushIndividually, WEB_APP_URL, } from "./core.js";
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Push Module
3
3
  *
4
- * Shared push logic for syncing local requirements files to the cloud.
5
- * Used by both CLI push command and MCP push_requirements tool.
4
+ * Shared parse-and-write-back primitives for syncing local requirements files
5
+ * to the cloud, consumed by the comparator-driven sync path.
6
6
  */
7
- export { dryRunPush, executePush,
7
+ export { assertNoDuplicateDocumentIds,
8
8
  // Functions
9
- extractMarkdownContent, parseFilesForPush, parseFilesForPushIndividually, WEB_APP_URL, } from "./core.js";
9
+ extractMarkdownContent, mergeMetadataWithRawFrontmatter, parseFilesForPush, parseFilesForPushIndividually, WEB_APP_URL, } from "./core.js";
10
10
  //# sourceMappingURL=index.js.map
@@ -17,6 +17,8 @@ export interface FlattenedRequirement {
17
17
  export interface ParsedRequirementsFile {
18
18
  metadata: Metadata;
19
19
  requirements: RequirementNode[];
20
+ /** Markdown body; its leading H1 is the document's title (SYNC-TITLE-1). */
21
+ body?: string;
20
22
  }
21
23
  /**
22
24
  * Find all *.requirements.md files in the workspace.
@@ -1,6 +1,7 @@
1
1
  import * as path from "node:path";
2
2
  import { glob, globSync } from "glob";
3
3
  import { buildRequirementMarkdown, getAllRequirements, parseRequirementsFile, parseRequirementsFromFile, } from "../schema/index.js";
4
+ import { splitLeadingH1 } from "../schema/title-markdown.js";
4
5
  /**
5
6
  * Glob pattern matching *.requirements.md files.
6
7
  */
@@ -90,7 +91,11 @@ export function flattenRequirementsFile(file, sourceFile) {
90
91
  content: req.content,
91
92
  path: idParts.slice(1), // ["0", "1"] for "REQ123.0.1"
92
93
  sourceFile: sourceFile,
93
- documentTitle: file.metadata.document?.title || rootId,
94
+ // SYNC-TITLE-1: the body's leading H1 is the title; legacy
95
+ // frontmatter title as fallback for pre-H1 files
96
+ documentTitle: splitLeadingH1(file.body ?? "").title ||
97
+ file.metadata.document?.title ||
98
+ rootId,
94
99
  });
95
100
  }
96
101
  return results;
@@ -125,7 +125,7 @@ ${customStyleGuidance.trim()}`;
125
125
  }
126
126
  const template = `---
127
127
  document:
128
- title: "Example Requirements"
128
+ defaultPrefix: EXAMPLE
129
129
  ---
130
130
 
131
131
  # Example Requirements
@@ -312,9 +312,9 @@ ${body}
312
312
  ## Next Steps
313
313
 
314
314
  1. **Create file**: Save this template as \`${filePath}\` and edit it for your feature
315
- 2. **Refine style** (optional): Run \`style-check\` (or call the \`style_check\` MCP tool) for AI feedback
316
- 3. **Validate syntax**: Run \`validate\` (or call the \`validate\` MCP tool) to verify format
317
- 4. **Push to cloud**: Run \`dotrequirements push\` (or call the \`push_requirements\` MCP tool) to sync
315
+ 2. **Refine style** (optional): Run \`style-check\` for AI feedback
316
+ 3. **Validate syntax**: Run \`validate\` to verify format
317
+ 4. **Sync to cloud**: Run \`dotrequirements sync\` to reconcile the repo and the cloud
318
318
 
319
319
  **Note**: Requirements files can be colocated with code (\`src/auth.requirements.md\`) or centralized in \`.requirements/\` directory.`;
320
320
  }
@@ -9,6 +9,7 @@ export { buildMetadata, constructKey, convexToRequirements, extractRequirementKe
9
9
  export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, ROOT_LABEL_MARKER, } from "./parser-core.js";
10
10
  export type { Metadata, ParsedCriterion, RequirementKey, RequirementNode, RequirementPrefix, RequirementsFile, } from "./schemas.js";
11
11
  export { buildRequirementKey, MetadataSchema, normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN, REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema, ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
12
+ export { composeMarkdownWithTitle, type SplitMarkdown, splitLeadingH1, } from "./title-markdown.js";
12
13
  export type { BuildScenarioOptions, Scenario, ScenarioAssertionSource, ScenarioStep, } from "./scenario.js";
13
14
  export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
14
15
  //# sourceMappingURL=browser.d.ts.map
@@ -22,6 +22,9 @@ normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PAT
22
22
  REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema,
23
23
  // Validation
24
24
  ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
25
+ // Title semantics (pure TypeScript - browser-safe): the document title and
26
+ // the markdown's leading H1 are the same thing (DOC-TITLE-3)
27
+ export { composeMarkdownWithTitle, splitLeadingH1, } from "./title-markdown.js";
25
28
  // Scenario building (pure TypeScript - browser-safe, used by Convex Node actions)
26
29
  export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
27
30
  //# sourceMappingURL=browser.js.map
@@ -13,4 +13,5 @@ export type { BuildScenarioOptions, Scenario, ScenarioAssertionSource, ScenarioS
13
13
  export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
14
14
  export type { Metadata, ParsedCriterion, PushValidationResult, RequirementKey, RequirementNode, RequirementPrefix, RequirementsFile, } from "./schemas.js";
15
15
  export { buildRequirementKey, CONVEX_ID_PATTERN, MetadataSchema, normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN, REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema, ValidationError, validateForPush, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
16
+ export { composeMarkdownWithTitle, type SplitMarkdown, splitLeadingH1, } from "./title-markdown.js";
16
17
  //# sourceMappingURL=index.d.ts.map
@@ -23,4 +23,5 @@ normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PAT
23
23
  REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema,
24
24
  // Validation
25
25
  ValidationError, validateForPush, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
26
+ export { composeMarkdownWithTitle, splitLeadingH1, } from "./title-markdown.js";
26
27
  //# sourceMappingURL=index.js.map
@@ -15,6 +15,8 @@ export { findRequirementById, flattenRequirementTree, getAllRequirements, parseC
15
15
  export declare function parseRequirementsFile(markdownContent: string): {
16
16
  metadata: RequirementsFile["_meta"];
17
17
  requirements: RequirementNode[];
18
+ /** The markdown body (frontmatter stripped) — its leading H1 is the document's title (SYNC-TITLE-1). */
19
+ body: string;
18
20
  };
19
21
  /**
20
22
  * Parse requirements file from disk.
@@ -22,5 +24,6 @@ export declare function parseRequirementsFile(markdownContent: string): {
22
24
  export declare function parseRequirementsFromFile(filePath: string): {
23
25
  metadata: RequirementsFile["_meta"];
24
26
  requirements: RequirementNode[];
27
+ body: string;
25
28
  };
26
29
  //# sourceMappingURL=parser.d.ts.map
@@ -47,7 +47,7 @@ export function parseRequirementsFile(markdownContent) {
47
47
  if (requirements.length === 0) {
48
48
  throw new ValidationError("No requirement blocks found in document");
49
49
  }
50
- return { metadata, requirements };
50
+ return { metadata, requirements, body };
51
51
  }
52
52
  /**
53
53
  * Parse requirements file from disk.
@@ -54,8 +54,10 @@ 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.
58
- * Only document.title is required for push; everything else is optional or auto-filled.
57
+ * Metadata block at the top of every requirements file. Everything is
58
+ * optional or auto-filled; the document's title lives in the BODY as its
59
+ * leading H1 (SYNC-TITLE-1), with a legacy frontmatter title tolerated as a
60
+ * fallback for files from before that convention.
59
61
  */
60
62
  export declare const MetadataSchema: z.ZodObject<{
61
63
  version: z.ZodOptional<z.ZodNumber>;
@@ -63,15 +65,15 @@ export declare const MetadataSchema: z.ZodObject<{
63
65
  ctsRun: z.ZodOptional<z.ZodString>;
64
66
  document: z.ZodOptional<z.ZodObject<{
65
67
  id: z.ZodOptional<z.ZodString>;
66
- title: z.ZodString;
68
+ title: z.ZodOptional<z.ZodString>;
67
69
  defaultPrefix: z.ZodOptional<z.ZodString>;
68
70
  }, "strip", z.ZodTypeAny, {
69
- title: string;
70
71
  id?: string | undefined;
72
+ title?: string | undefined;
71
73
  defaultPrefix?: string | undefined;
72
74
  }, {
73
- title: string;
74
75
  id?: string | undefined;
76
+ title?: string | undefined;
75
77
  defaultPrefix?: string | undefined;
76
78
  }>>;
77
79
  }, "strip", z.ZodTypeAny, {
@@ -79,8 +81,8 @@ export declare const MetadataSchema: z.ZodObject<{
79
81
  pulledAt?: string | undefined;
80
82
  ctsRun?: string | undefined;
81
83
  document?: {
82
- title: string;
83
84
  id?: string | undefined;
85
+ title?: string | undefined;
84
86
  defaultPrefix?: string | undefined;
85
87
  } | undefined;
86
88
  }, {
@@ -88,8 +90,8 @@ export declare const MetadataSchema: z.ZodObject<{
88
90
  pulledAt?: string | undefined;
89
91
  ctsRun?: string | undefined;
90
92
  document?: {
91
- title: string;
92
93
  id?: string | undefined;
94
+ title?: string | undefined;
93
95
  defaultPrefix?: string | undefined;
94
96
  } | undefined;
95
97
  }>;
@@ -152,15 +154,15 @@ export declare const RequirementsFileSchema: z.ZodObject<{
152
154
  ctsRun: z.ZodOptional<z.ZodString>;
153
155
  document: z.ZodOptional<z.ZodObject<{
154
156
  id: z.ZodOptional<z.ZodString>;
155
- title: z.ZodString;
157
+ title: z.ZodOptional<z.ZodString>;
156
158
  defaultPrefix: z.ZodOptional<z.ZodString>;
157
159
  }, "strip", z.ZodTypeAny, {
158
- title: string;
159
160
  id?: string | undefined;
161
+ title?: string | undefined;
160
162
  defaultPrefix?: string | undefined;
161
163
  }, {
162
- title: string;
163
164
  id?: string | undefined;
165
+ title?: string | undefined;
164
166
  defaultPrefix?: string | undefined;
165
167
  }>>;
166
168
  }, "strip", z.ZodTypeAny, {
@@ -168,8 +170,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
168
170
  pulledAt?: string | undefined;
169
171
  ctsRun?: string | undefined;
170
172
  document?: {
171
- title: string;
172
173
  id?: string | undefined;
174
+ title?: string | undefined;
173
175
  defaultPrefix?: string | undefined;
174
176
  } | undefined;
175
177
  }, {
@@ -177,8 +179,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
177
179
  pulledAt?: string | undefined;
178
180
  ctsRun?: string | undefined;
179
181
  document?: {
180
- title: string;
181
182
  id?: string | undefined;
183
+ title?: string | undefined;
182
184
  defaultPrefix?: string | undefined;
183
185
  } | undefined;
184
186
  }>;
@@ -189,15 +191,15 @@ export declare const RequirementsFileSchema: z.ZodObject<{
189
191
  ctsRun: z.ZodOptional<z.ZodString>;
190
192
  document: z.ZodOptional<z.ZodObject<{
191
193
  id: z.ZodOptional<z.ZodString>;
192
- title: z.ZodString;
194
+ title: z.ZodOptional<z.ZodString>;
193
195
  defaultPrefix: z.ZodOptional<z.ZodString>;
194
196
  }, "strip", z.ZodTypeAny, {
195
- title: string;
196
197
  id?: string | undefined;
198
+ title?: string | undefined;
197
199
  defaultPrefix?: string | undefined;
198
200
  }, {
199
- title: string;
200
201
  id?: string | undefined;
202
+ title?: string | undefined;
201
203
  defaultPrefix?: string | undefined;
202
204
  }>>;
203
205
  }, "strip", z.ZodTypeAny, {
@@ -205,8 +207,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
205
207
  pulledAt?: string | undefined;
206
208
  ctsRun?: string | undefined;
207
209
  document?: {
208
- title: string;
209
210
  id?: string | undefined;
211
+ title?: string | undefined;
210
212
  defaultPrefix?: string | undefined;
211
213
  } | undefined;
212
214
  }, {
@@ -214,8 +216,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
214
216
  pulledAt?: string | undefined;
215
217
  ctsRun?: string | undefined;
216
218
  document?: {
217
- title: string;
218
219
  id?: string | undefined;
220
+ title?: string | undefined;
219
221
  defaultPrefix?: string | undefined;
220
222
  } | undefined;
221
223
  }>;
@@ -226,15 +228,15 @@ export declare const RequirementsFileSchema: z.ZodObject<{
226
228
  ctsRun: z.ZodOptional<z.ZodString>;
227
229
  document: z.ZodOptional<z.ZodObject<{
228
230
  id: z.ZodOptional<z.ZodString>;
229
- title: z.ZodString;
231
+ title: z.ZodOptional<z.ZodString>;
230
232
  defaultPrefix: z.ZodOptional<z.ZodString>;
231
233
  }, "strip", z.ZodTypeAny, {
232
- title: string;
233
234
  id?: string | undefined;
235
+ title?: string | undefined;
234
236
  defaultPrefix?: string | undefined;
235
237
  }, {
236
- title: string;
237
238
  id?: string | undefined;
239
+ title?: string | undefined;
238
240
  defaultPrefix?: string | undefined;
239
241
  }>>;
240
242
  }, "strip", z.ZodTypeAny, {
@@ -242,8 +244,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
242
244
  pulledAt?: string | undefined;
243
245
  ctsRun?: string | undefined;
244
246
  document?: {
245
- title: string;
246
247
  id?: string | undefined;
248
+ title?: string | undefined;
247
249
  defaultPrefix?: string | undefined;
248
250
  } | undefined;
249
251
  }, {
@@ -251,8 +253,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
251
253
  pulledAt?: string | undefined;
252
254
  ctsRun?: string | undefined;
253
255
  document?: {
254
- title: string;
255
256
  id?: string | undefined;
257
+ title?: string | undefined;
256
258
  defaultPrefix?: string | undefined;
257
259
  } | undefined;
258
260
  }>;