@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.
- package/README.md +24 -20
- package/dist/cli.js +34 -12
- package/dist/codebase-to-spec/compose.d.ts +3 -2
- package/dist/codebase-to-spec/compose.js +3 -3
- package/dist/commands/aliases.d.ts +26 -0
- package/dist/commands/aliases.js +31 -0
- package/dist/commands/diff.d.ts +14 -0
- package/dist/commands/diff.js +62 -0
- package/dist/commands/init.js +5 -5
- package/dist/commands/link.d.ts +1 -1
- package/dist/commands/link.js +10 -7
- package/dist/commands/sync-common.d.ts +21 -0
- package/dist/commands/sync-common.js +24 -0
- package/dist/commands/sync.d.ts +26 -0
- package/dist/commands/sync.js +187 -0
- package/dist/convex.d.ts +1 -1
- package/dist/convex.js +2 -2
- package/dist/push/core.d.ts +18 -116
- package/dist/push/core.js +23 -267
- package/dist/push/index.d.ts +3 -3
- package/dist/push/index.js +4 -4
- package/dist/requirements/index.d.ts +2 -0
- package/dist/requirements/index.js +6 -1
- package/dist/requirements/style-guide.js +4 -4
- package/dist/schema/browser.d.ts +1 -0
- package/dist/schema/browser.js +3 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/parser.d.ts +3 -0
- package/dist/schema/parser.js +1 -1
- package/dist/schema/schemas.d.ts +24 -22
- package/dist/schema/schemas.js +9 -12
- package/dist/schema/title-markdown.d.ts +30 -0
- package/dist/schema/title-markdown.js +38 -0
- package/dist/sync/compare.d.ts +21 -0
- package/dist/sync/compare.js +285 -0
- package/dist/sync/execute.d.ts +53 -0
- package/dist/sync/execute.js +225 -0
- package/dist/sync/index.d.ts +21 -0
- package/dist/sync/index.js +52 -0
- package/dist/sync/local-files.d.ts +26 -0
- package/dist/sync/local-files.js +93 -0
- package/dist/sync/plan.d.ts +39 -0
- package/dist/sync/plan.js +90 -0
- package/dist/sync/render.d.ts +17 -0
- package/dist/sync/render.js +46 -0
- package/dist/sync/segment.d.ts +40 -0
- package/dist/sync/segment.js +76 -0
- package/dist/sync/snapshot.d.ts +30 -0
- package/dist/sync/snapshot.js +122 -0
- package/dist/sync/types.d.ts +82 -0
- package/dist/sync/types.js +12 -0
- package/dist/templates/context-file-section.md +2 -1
- package/dist/templates/example-requirements.js +0 -2
- package/dist/templates/example-requirements.ts +0 -2
- package/dist/templates/requirements-readme.js +3 -4
- package/dist/templates/requirements-readme.ts +3 -4
- package/dist/templates/skills/codebase-to-spec/SKILL.md +2 -2
- package/dist/utils/project-settings.d.ts +8 -2
- package/dist/utils/project-settings.js +47 -24
- package/package.json +1 -1
- package/dist/commands/pull.d.ts +0 -8
- package/dist/commands/pull.js +0 -230
- package/dist/commands/push.d.ts +0 -6
- package/dist/commands/push.js +0 -244
package/dist/push/core.js
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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 {
|
|
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
|
-
//
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/push/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push Module
|
|
3
3
|
*
|
|
4
|
-
* Shared
|
|
5
|
-
*
|
|
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 {
|
|
7
|
+
export { assertNoDuplicateDocumentIds, extractMarkdownContent, mergeMetadataWithRawFrontmatter, type ParsedFile, type ParseFailure, parseFilesForPush, parseFilesForPushIndividually, WEB_APP_URL, } from "./core.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/push/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push Module
|
|
3
3
|
*
|
|
4
|
-
* Shared
|
|
5
|
-
*
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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\`
|
|
316
|
-
3. **Validate syntax**: Run \`validate\`
|
|
317
|
-
4. **
|
|
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
|
}
|
package/dist/schema/browser.d.ts
CHANGED
|
@@ -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
|
package/dist/schema/browser.js
CHANGED
|
@@ -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
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -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
|
package/dist/schema/index.js
CHANGED
|
@@ -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
|
package/dist/schema/parser.d.ts
CHANGED
|
@@ -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
|
package/dist/schema/parser.js
CHANGED
|
@@ -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.
|
package/dist/schema/schemas.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
}>;
|