@popoverai/dotrequirements 0.27.3 → 0.28.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 (52) hide show
  1. package/README.md +24 -20
  2. package/dist/cli.js +34 -12
  3. package/dist/commands/aliases.d.ts +26 -0
  4. package/dist/commands/aliases.js +31 -0
  5. package/dist/commands/diff.d.ts +14 -0
  6. package/dist/commands/diff.js +62 -0
  7. package/dist/commands/init.js +5 -5
  8. package/dist/commands/link.d.ts +1 -1
  9. package/dist/commands/link.js +13 -7
  10. package/dist/commands/sync-common.d.ts +21 -0
  11. package/dist/commands/sync-common.js +24 -0
  12. package/dist/commands/sync.d.ts +26 -0
  13. package/dist/commands/sync.js +182 -0
  14. package/dist/convex.d.ts +1 -1
  15. package/dist/convex.js +2 -2
  16. package/dist/push/core.d.ts +18 -116
  17. package/dist/push/core.js +16 -267
  18. package/dist/push/index.d.ts +3 -3
  19. package/dist/push/index.js +4 -4
  20. package/dist/requirements/style-guide.js +3 -3
  21. package/dist/schema/schemas.js +1 -1
  22. package/dist/sync/compare.d.ts +21 -0
  23. package/dist/sync/compare.js +285 -0
  24. package/dist/sync/execute.d.ts +48 -0
  25. package/dist/sync/execute.js +186 -0
  26. package/dist/sync/index.d.ts +21 -0
  27. package/dist/sync/index.js +52 -0
  28. package/dist/sync/local-files.d.ts +26 -0
  29. package/dist/sync/local-files.js +91 -0
  30. package/dist/sync/plan.d.ts +39 -0
  31. package/dist/sync/plan.js +90 -0
  32. package/dist/sync/render.d.ts +17 -0
  33. package/dist/sync/render.js +46 -0
  34. package/dist/sync/segment.d.ts +40 -0
  35. package/dist/sync/segment.js +76 -0
  36. package/dist/sync/snapshot.d.ts +30 -0
  37. package/dist/sync/snapshot.js +118 -0
  38. package/dist/sync/types.d.ts +82 -0
  39. package/dist/sync/types.js +12 -0
  40. package/dist/templates/context-file-section.md +2 -1
  41. package/dist/templates/requirements-readme.js +3 -4
  42. package/dist/templates/requirements-readme.ts +3 -4
  43. package/dist/templates/skills/codebase-to-spec/SKILL.md +2 -2
  44. package/dist/utils/oauth-flow.d.ts +2 -0
  45. package/dist/utils/oauth-flow.js +8 -1
  46. package/dist/utils/project-settings.d.ts +8 -2
  47. package/dist/utils/project-settings.js +47 -24
  48. package/package.json +1 -1
  49. package/dist/commands/pull.d.ts +0 -8
  50. package/dist/commands/pull.js +0 -230
  51. package/dist/commands/push.d.ts +0 -6
  52. package/dist/commands/push.js +0 -244
@@ -1,244 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import * as readline from "node:readline";
4
- import { getConvexUrl } from "../config.js";
5
- import { dryRunPush, executePush, parseFilesForPushIndividually, } from "../push/index.js";
6
- import { findRequirementsFiles } from "../requirements/index.js";
7
- import { brand } from "../utils/brand.js";
8
- import { getProjectCredentials } from "../utils/project-settings.js";
9
- export async function pushCommand(file, options) {
10
- console.log(`Preparing to push requirements to ${brand} cloud...\n`);
11
- // Get project credentials from .requirements/project-settings.json
12
- // This throws helpful errors if project not found or not connected to cloud
13
- const { projectId, projectSecret } = getProjectCredentials();
14
- // Determine which files to push
15
- let filesToPush;
16
- if (file) {
17
- // Push specific file
18
- const filePath = path.resolve(file);
19
- if (!fs.existsSync(filePath)) {
20
- throw new Error(`File not found: ${file}`);
21
- }
22
- filesToPush = [filePath];
23
- }
24
- else {
25
- // SYNC-DISCOVERY-1.0: Push all *.requirements.md files in workspace (not just .requirements/)
26
- const workspaceRoot = process.cwd();
27
- filesToPush = await findRequirementsFiles(workspaceRoot);
28
- if (filesToPush.length === 0) {
29
- console.log("No *.requirements.md files found. Nothing to push.");
30
- return;
31
- }
32
- }
33
- // Parse local files. SYNC-FAIL-4: one invalid file is skipped while the
34
- // rest of the batch still pushes — same helper as the MCP push handler.
35
- console.log("Parsing local requirements...");
36
- const { parsedFiles, totalRequirements, parseFailures } = parseFilesForPushIndividually(filesToPush);
37
- // Log file parsing progress
38
- for (const file of parsedFiles) {
39
- const fileName = path.basename(file.filePath);
40
- const doc = file.metadata.document;
41
- console.log(` Reading ${fileName}...`);
42
- // DOC-HEADER-11.4: parseFilesForPush mutates the metadata when it infers
43
- // the prefix, so the flag it returns is the only record of the inference
44
- if (doc?.defaultPrefix && file.inferredDefaultPrefix) {
45
- console.log(` Inferred defaultPrefix "${doc.defaultPrefix}" from first requirement`);
46
- }
47
- }
48
- // SYNC-FAIL-4.1: every file in the push failed to parse — report each
49
- // skipped file, then fail honestly instead of dry-running nothing
50
- if (parsedFiles.length === 0) {
51
- for (const failure of parseFailures) {
52
- console.log(` ✗ Skipped ${path.basename(failure.filePath)}: ${failure.error}`);
53
- }
54
- console.log("\nNo valid documents to push.");
55
- process.exitCode = 1;
56
- return;
57
- }
58
- console.log(`\nFound ${totalRequirements} requirement(s) in ${parsedFiles.length} document(s).\n`);
59
- // Build credentials
60
- const credentials = {
61
- projectId,
62
- projectSecret,
63
- convexUrl: getConvexUrl(),
64
- };
65
- // Phase 1: Dry run
66
- console.log("Validating documents...");
67
- const dryRunResult = await dryRunPush(parsedFiles, credentials);
68
- // Display unified summary
69
- displayDryRunSummary(dryRunResult, parseFailures);
70
- // Check if there's anything to push
71
- const pushableCount = dryRunResult.updates.length +
72
- dryRunResult.creates.length +
73
- dryRunResult.notFound.length;
74
- if (pushableCount === 0) {
75
- console.log("No valid documents to push.");
76
- // SYNC-FAIL-4.1: every file in the push was invalid — whether it failed
77
- // local parse or dry-run validation — so the command must fail honestly,
78
- // matching the MCP handler's merged rule
79
- if (parseFailures.length > 0 || dryRunResult.invalid.length > 0) {
80
- process.exitCode = 1;
81
- }
82
- return;
83
- }
84
- // Single confirmation prompt
85
- if (!options.yes) {
86
- const hasConflicts = dryRunResult.conflicts.length > 0;
87
- const prompt = hasConflicts
88
- ? "Continue with push? (will overwrite cloud changes) [y/N] "
89
- : "Continue with push? [y/N] ";
90
- const confirmed = await confirm(prompt);
91
- if (!confirmed) {
92
- console.log("Push cancelled.");
93
- return;
94
- }
95
- }
96
- // Phase 2: Execute push
97
- console.log("\nPushing documents to cloud...");
98
- const result = await executePush(dryRunResult, credentials);
99
- // Display results
100
- for (const { file, result: dryResult } of [
101
- ...dryRunResult.updates,
102
- ...dryRunResult.creates,
103
- ...dryRunResult.notFound,
104
- ]) {
105
- const fileName = path.basename(file.filePath);
106
- const isCreate = dryResult.action === "create" || dryResult.action === "not_found";
107
- // Check if this file had an error — match by full path, since two pushed
108
- // files can share a basename
109
- const error = result.errors.find((e) => e.filePath === file.filePath);
110
- if (error) {
111
- console.log(` ✗ Failed: ${fileName} - ${error.error}`);
112
- }
113
- else if (isCreate) {
114
- console.log(` ✓ Created: ${fileName} → ${file.metadata.document?.id}`);
115
- }
116
- else {
117
- console.log(` ✓ Updated: ${fileName}`);
118
- }
119
- }
120
- // SYNC-FAIL-2.1: the cloud save succeeded but the local file couldn't be
121
- // updated — name the file, say the cloud is fine, and give the recovery
122
- // step that re-links the file instead of minting a duplicate on retry
123
- if (result.writeBackWarnings?.length > 0) {
124
- console.log();
125
- for (const warning of result.writeBackWarnings) {
126
- console.log(`⚠ ${warning.fileName}: saved to the cloud, but the local file could not be updated (${warning.error}). ` +
127
- `To avoid creating a duplicate, add "id: ${warning.documentId}" under "document:" in the frontmatter of ${warning.filePath}, then push again.`);
128
- }
129
- }
130
- // IMPORT-3: marker failures are loud but never fatal
131
- if (result.importWarnings.length > 0) {
132
- console.log();
133
- for (const { fileName, status } of result.importWarnings) {
134
- if (status === "already_used") {
135
- console.log(`⚠ ${fileName}: this file's import marker was already used, so its requirements were saved as natively authored (they count toward your plan's requirement limit). Re-running codebase-to-spec produces a fresh marker.`);
136
- }
137
- else {
138
- console.log(`⚠ ${fileName}: this file's import marker was not recognized — your CLI may need updating. Its requirements were saved as natively authored (they count toward your plan's requirement limit).`);
139
- }
140
- }
141
- }
142
- // SYNC-FAIL-1: a failed push must not masquerade as success — agents and
143
- // scripts drive this command and rely on the exit code to tell the truth
144
- const failed = result.errors.length;
145
- const syncedCount = result.created + result.updated;
146
- if (failed > 0) {
147
- process.exitCode = 1;
148
- if (syncedCount === 0) {
149
- console.log(`\n✗ Push failed: ${failed} document(s) could not be saved.`);
150
- }
151
- else {
152
- console.log(`\n⚠ Push incomplete: ${syncedCount} document(s) synced, ${failed} failed.`);
153
- }
154
- }
155
- else {
156
- console.log(`\n✓ Push complete!`);
157
- }
158
- if (result.created > 0) {
159
- console.log(` Created: ${result.created} document(s)`);
160
- }
161
- if (result.updated > 0) {
162
- console.log(` Updated: ${result.updated} document(s)`);
163
- }
164
- if (failed === 0 && syncedCount === 0) {
165
- console.log(" No documents were pushed.");
166
- }
167
- // SYNC-LAND-1: land the user on their spec, not at a dead end
168
- if (result.synced.length > 0) {
169
- console.log("\nReview in dot•requirements:");
170
- for (const doc of result.synced) {
171
- console.log(` ${doc.fileName} → ${doc.url}`);
172
- }
173
- }
174
- }
175
- /**
176
- * Display the dry run summary.
177
- */
178
- function displayDryRunSummary(dryRunResult, parseFailures = []) {
179
- const { updates, creates, notFound, invalid, conflicts } = dryRunResult;
180
- console.log("\n=== Push Summary ===\n");
181
- if (updates.length > 0) {
182
- console.log(`Updated documents (${updates.length}):`);
183
- for (const { file, result } of updates) {
184
- const fileName = path.basename(file.filePath);
185
- const hasConflict = conflicts.some((c) => c.item.file === file);
186
- const conflictNote = hasConflict ? " ⚠️ [cloud changed since pull]" : "";
187
- const warningNote = result.warning ? ` (${result.warning})` : "";
188
- console.log(` ~ ${fileName}${conflictNote}${warningNote}`);
189
- }
190
- console.log();
191
- }
192
- if (creates.length > 0) {
193
- console.log(`New documents (${creates.length}):`);
194
- for (const { file, result } of creates) {
195
- const fileName = path.basename(file.filePath);
196
- const warningNote = result.warning ? ` (${result.warning})` : "";
197
- console.log(` + ${fileName}${warningNote}`);
198
- }
199
- console.log();
200
- }
201
- if (notFound.length > 0) {
202
- console.log(`Documents not found in cloud (${notFound.length}) - will create new:`);
203
- for (const { file, result } of notFound) {
204
- const fileName = path.basename(file.filePath);
205
- console.log(` ! ${fileName} (ID: ${result.documentId})`);
206
- }
207
- console.log();
208
- }
209
- // SYNC-FAIL-4.0: local parse failures surface alongside dry-run invalids,
210
- // each named by file, while the rest of the batch proceeds
211
- const skipped = [
212
- ...parseFailures.map((failure) => ({
213
- fileName: path.basename(failure.filePath),
214
- error: failure.error,
215
- })),
216
- ...invalid.map(({ file, result }) => ({
217
- fileName: path.basename(file.filePath),
218
- error: result.error,
219
- })),
220
- ];
221
- if (skipped.length > 0) {
222
- console.log(`Skipped - invalid files (${skipped.length}):`);
223
- for (const { fileName, error } of skipped) {
224
- console.log(` ✗ ${fileName}: ${error}`);
225
- }
226
- console.log();
227
- }
228
- }
229
- /**
230
- * Prompt user for confirmation
231
- */
232
- function confirm(question) {
233
- const rl = readline.createInterface({
234
- input: process.stdin,
235
- output: process.stdout,
236
- });
237
- return new Promise((resolve) => {
238
- rl.question(question, (answer) => {
239
- rl.close();
240
- resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
241
- });
242
- });
243
- }
244
- //# sourceMappingURL=push.js.map