@popoverai/dotrequirements 0.11.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 +478 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +82 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +355 -0
- package/dist/commands/link.d.ts +15 -0
- package/dist/commands/link.js +156 -0
- package/dist/commands/login.d.ts +12 -0
- package/dist/commands/login.js +117 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/mcp-setup.d.ts +5 -0
- package/dist/commands/mcp-setup.js +367 -0
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +10 -0
- package/dist/commands/pull.d.ts +7 -0
- package/dist/commands/pull.js +171 -0
- package/dist/commands/push.d.ts +11 -0
- package/dist/commands/push.js +310 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.js +78 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +16 -0
- package/dist/convex.d.ts +56 -0
- package/dist/convex.js +58 -0
- package/dist/harness/cache.d.ts +135 -0
- package/dist/harness/cache.js +342 -0
- package/dist/harness/convexReporting.d.ts +15 -0
- package/dist/harness/convexReporting.js +136 -0
- package/dist/harness/coverageCache.d.ts +30 -0
- package/dist/harness/coverageCache.js +70 -0
- package/dist/harness/finalize.d.ts +48 -0
- package/dist/harness/finalize.js +299 -0
- package/dist/harness/index.d.ts +70 -0
- package/dist/harness/index.js +103 -0
- package/dist/harness/localReporting.d.ts +6 -0
- package/dist/harness/localReporting.js +49 -0
- package/dist/harness/prepare.d.ts +41 -0
- package/dist/harness/prepare.js +83 -0
- package/dist/harness/requirementsLoader.d.ts +45 -0
- package/dist/harness/requirementsLoader.js +201 -0
- package/dist/harness/tracking.d.ts +49 -0
- package/dist/harness/tracking.js +179 -0
- package/dist/harness/types.d.ts +12 -0
- package/dist/harness/types.js +6 -0
- package/dist/mcp/convexClient.d.ts +43 -0
- package/dist/mcp/convexClient.js +101 -0
- package/dist/mcp/grep.d.ts +24 -0
- package/dist/mcp/grep.js +261 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +1758 -0
- package/dist/mcp/requirements.d.ts +47 -0
- package/dist/mcp/requirements.js +141 -0
- package/dist/mcp/testCodeExtractor.d.ts +22 -0
- package/dist/mcp/testCodeExtractor.js +152 -0
- package/dist/mcp/types.d.ts +27 -0
- package/dist/mcp/types.js +2 -0
- package/dist/schema/browser.d.ts +12 -0
- package/dist/schema/browser.js +24 -0
- package/dist/schema/builder.d.ts +25 -0
- package/dist/schema/builder.js +125 -0
- package/dist/schema/conversions.d.ts +69 -0
- package/dist/schema/conversions.js +201 -0
- package/dist/schema/index.d.ts +14 -0
- package/dist/schema/index.js +24 -0
- package/dist/schema/parser-core.d.ts +61 -0
- package/dist/schema/parser-core.js +247 -0
- package/dist/schema/parser.d.ts +44 -0
- package/dist/schema/parser.js +295 -0
- package/dist/schema/resolver.d.ts +66 -0
- package/dist/schema/resolver.js +185 -0
- package/dist/schema/schemas.d.ts +312 -0
- package/dist/schema/schemas.js +258 -0
- package/dist/schema/test-schema.d.ts +5 -0
- package/dist/schema/test-schema.js +81 -0
- package/dist/templates/antigravity-gemini.md +3 -0
- package/dist/templates/antigravity-overview-rule.md +3 -0
- package/dist/templates/antigravity-test-rule.md +3 -0
- package/dist/templates/behavioral-core.md +25 -0
- package/dist/templates/claude-code-overview-skill.md +6 -0
- package/dist/templates/claude-code-skill.md +6 -0
- package/dist/templates/claude-code-test-skill.md +6 -0
- package/dist/templates/codex-agents.md +3 -0
- package/dist/templates/codex-overview-agents.md +3 -0
- package/dist/templates/codex-test-agents.md +3 -0
- package/dist/templates/cursor-overview-rule.mdc +5 -0
- package/dist/templates/cursor-rule.mdc +5 -0
- package/dist/templates/cursor-test-rule.mdc +5 -0
- package/dist/templates/example-requirements.d.ts +8 -0
- package/dist/templates/example-requirements.js +88 -0
- package/dist/templates/example-requirements.ts +88 -0
- package/dist/templates/overview-core.md +27 -0
- package/dist/templates/requirements-readme.d.ts +5 -0
- package/dist/templates/requirements-readme.js +31 -0
- package/dist/templates/requirements-readme.ts +30 -0
- package/dist/templates/test-writing-core.md +72 -0
- package/dist/utils/brand.d.ts +5 -0
- package/dist/utils/brand.js +8 -0
- package/dist/utils/browser-launch.d.ts +19 -0
- package/dist/utils/browser-launch.js +36 -0
- package/dist/utils/detect-existing-project.d.ts +5 -0
- package/dist/utils/detect-existing-project.js +34 -0
- package/dist/utils/env.d.ts +19 -0
- package/dist/utils/env.js +56 -0
- package/dist/utils/gitignore.d.ts +7 -0
- package/dist/utils/gitignore.js +29 -0
- package/dist/utils/local-project.d.ts +31 -0
- package/dist/utils/local-project.js +33 -0
- package/dist/utils/oauth-callback-server.d.ts +28 -0
- package/dist/utils/oauth-callback-server.js +156 -0
- package/dist/utils/oauth-flow.d.ts +22 -0
- package/dist/utils/oauth-flow.js +120 -0
- package/dist/utils/project-discovery.d.ts +57 -0
- package/dist/utils/project-discovery.js +146 -0
- package/dist/utils/project-name.d.ts +8 -0
- package/dist/utils/project-name.js +48 -0
- package/dist/utils/project-selector.d.ts +25 -0
- package/dist/utils/project-selector.js +69 -0
- package/dist/utils/prompts.d.ts +33 -0
- package/dist/utils/prompts.js +60 -0
- package/dist/utils/templates.d.ts +29 -0
- package/dist/utils/templates.js +67 -0
- package/dist/utils/token-refresh.d.ts +24 -0
- package/dist/utils/token-refresh.js +69 -0
- package/dist/utils/token-storage.d.ts +31 -0
- package/dist/utils/token-storage.js +57 -0
- package/package.json +82 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { ConvexHttpClient } from 'convex/browser';
|
|
4
|
+
import { getConvexUrl } from '../config.js';
|
|
5
|
+
import { parseRequirementsFromFile, getAllRequirements, parseRequirementKey, buildRequirementsFile } from '../schema/index.js';
|
|
6
|
+
import { api } from '../convex.js';
|
|
7
|
+
import * as readline from 'readline';
|
|
8
|
+
import { isLocalOnlyProject, CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE } from '../utils/local-project.js';
|
|
9
|
+
import { brand } from '../utils/brand.js';
|
|
10
|
+
export async function pushCommand(file, options) {
|
|
11
|
+
try {
|
|
12
|
+
console.log(`Preparing to push requirements to ${brand} cloud...\n`);
|
|
13
|
+
// Verify project credentials are configured
|
|
14
|
+
const projectId = process.env.DOTREQUIREMENTS_PROJECT_ID;
|
|
15
|
+
const projectSecret = process.env.DOTREQUIREMENTS_PROJECT_SECRET;
|
|
16
|
+
if (!projectId || !projectSecret) {
|
|
17
|
+
throw new Error('Project credentials not found. Either:\n' +
|
|
18
|
+
' 1. Run "dotreq init" to initialize the project, or\n' +
|
|
19
|
+
' 2. Set DOTREQUIREMENTS_PROJECT_ID and DOTREQUIREMENTS_PROJECT_SECRET in .env.local');
|
|
20
|
+
}
|
|
21
|
+
// AUTHZ-2.0: Check if project is local-only
|
|
22
|
+
if (isLocalOnlyProject(projectId)) {
|
|
23
|
+
throw new Error(CLOUD_FEATURES_REQUIRE_AUTH_MESSAGE);
|
|
24
|
+
}
|
|
25
|
+
// 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
|
+
let filesToPush;
|
|
32
|
+
if (file) {
|
|
33
|
+
// Push specific file
|
|
34
|
+
const filePath = path.resolve(file);
|
|
35
|
+
if (!fs.existsSync(filePath)) {
|
|
36
|
+
throw new Error(`File not found: ${file}`);
|
|
37
|
+
}
|
|
38
|
+
filesToPush = [filePath];
|
|
39
|
+
}
|
|
40
|
+
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));
|
|
46
|
+
if (filesToPush.length === 0) {
|
|
47
|
+
console.log('No *.requirements.md files found in .requirements/. Nothing to push.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Parse all local files
|
|
52
|
+
console.log('Parsing local requirements...');
|
|
53
|
+
const parsedFiles = [];
|
|
54
|
+
let totalRequirements = 0;
|
|
55
|
+
for (const filePath of filesToPush) {
|
|
56
|
+
const fileName = path.basename(filePath);
|
|
57
|
+
console.log(` Reading ${fileName}...`);
|
|
58
|
+
// Read raw file content
|
|
59
|
+
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
60
|
+
// Parse to get metadata and requirements
|
|
61
|
+
const parsed = parseRequirementsFromFile(filePath);
|
|
62
|
+
const flatRequirements = getAllRequirements(parsed.requirements);
|
|
63
|
+
// Extract markdown content (everything after frontmatter)
|
|
64
|
+
const markdownContent = extractMarkdownContent(rawContent);
|
|
65
|
+
// DOC-HEADER-11.1/11.2: Infer prefix from first requirement if not specified
|
|
66
|
+
const doc = parsed.metadata.document;
|
|
67
|
+
if (doc && !doc.defaultPrefix && flatRequirements.length > 0) {
|
|
68
|
+
const firstReq = flatRequirements[0];
|
|
69
|
+
if (firstReq) {
|
|
70
|
+
const parsedKey = parseRequirementKey(firstReq.id);
|
|
71
|
+
if (parsedKey) {
|
|
72
|
+
doc.defaultPrefix = parsedKey.prefix;
|
|
73
|
+
console.log(` Inferred defaultPrefix "${doc.defaultPrefix}" from first requirement`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
parsedFiles.push({
|
|
78
|
+
filePath,
|
|
79
|
+
metadata: parsed.metadata,
|
|
80
|
+
markdownContent,
|
|
81
|
+
requirementCount: flatRequirements.length,
|
|
82
|
+
});
|
|
83
|
+
totalRequirements += flatRequirements.length;
|
|
84
|
+
}
|
|
85
|
+
console.log(`\nFound ${totalRequirements} requirement(s) in ${parsedFiles.length} document(s).\n`);
|
|
86
|
+
// Connect to Convex
|
|
87
|
+
const convexUrl = getConvexUrl();
|
|
88
|
+
const client = new ConvexHttpClient(convexUrl);
|
|
89
|
+
// Phase 1: Dry run to categorize all files
|
|
90
|
+
console.log('Validating documents...');
|
|
91
|
+
const dryRunResults = [];
|
|
92
|
+
for (const file of parsedFiles) {
|
|
93
|
+
const doc = file.metadata.document;
|
|
94
|
+
const fileName = path.basename(file.filePath);
|
|
95
|
+
if (!doc) {
|
|
96
|
+
// No document section - mark as invalid locally
|
|
97
|
+
dryRunResults.push({
|
|
98
|
+
file,
|
|
99
|
+
result: {
|
|
100
|
+
dryRun: true,
|
|
101
|
+
action: 'invalid',
|
|
102
|
+
title: fileName,
|
|
103
|
+
error: 'Missing document section in frontmatter',
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const result = await client.mutation(api.documents.saveWithRequirements.saveWithRequirements, {
|
|
110
|
+
projectAuth: {
|
|
111
|
+
projectSlug: projectId,
|
|
112
|
+
projectSecret,
|
|
113
|
+
},
|
|
114
|
+
target: { type: 'project', slug: projectId },
|
|
115
|
+
documentId: doc.id,
|
|
116
|
+
title: doc.title,
|
|
117
|
+
markdownContent: file.markdownContent,
|
|
118
|
+
defaultPrefix: doc.defaultPrefix,
|
|
119
|
+
dryRun: true,
|
|
120
|
+
});
|
|
121
|
+
dryRunResults.push({ file, result });
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
dryRunResults.push({
|
|
125
|
+
file,
|
|
126
|
+
result: {
|
|
127
|
+
dryRun: true,
|
|
128
|
+
action: 'invalid',
|
|
129
|
+
title: doc.title,
|
|
130
|
+
error: err.message,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Categorize results
|
|
136
|
+
const updates = dryRunResults.filter(r => r.result.action === 'update');
|
|
137
|
+
const creates = dryRunResults.filter(r => r.result.action === 'create');
|
|
138
|
+
const notFound = dryRunResults.filter(r => r.result.action === 'not_found');
|
|
139
|
+
const invalid = dryRunResults.filter(r => r.result.action === 'invalid');
|
|
140
|
+
// Check for conflicts on updates (cloud changed since last pull)
|
|
141
|
+
const conflicts = [];
|
|
142
|
+
if (updates.length > 0) {
|
|
143
|
+
const docIds = updates.map(u => u.result.documentId);
|
|
144
|
+
const cloudMetadata = await client.query(api.documents.queries.getDocumentsMetadata, {
|
|
145
|
+
projectAuth: {
|
|
146
|
+
projectSlug: projectId,
|
|
147
|
+
projectSecret,
|
|
148
|
+
},
|
|
149
|
+
target: { type: 'project', slug: projectId },
|
|
150
|
+
documentIds: docIds,
|
|
151
|
+
});
|
|
152
|
+
const cloudMetaByDocId = new Map(cloudMetadata.map((m) => [m.documentId, m]));
|
|
153
|
+
for (const item of updates) {
|
|
154
|
+
const docId = item.result.documentId;
|
|
155
|
+
const cloudMeta = cloudMetaByDocId.get(docId);
|
|
156
|
+
if (cloudMeta && item.file.metadata.pulledAt) {
|
|
157
|
+
const pulledAtMs = new Date(item.file.metadata.pulledAt).getTime();
|
|
158
|
+
if (cloudMeta.updatedAt > pulledAtMs) {
|
|
159
|
+
conflicts.push({ item, cloudMeta });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Display unified summary
|
|
165
|
+
console.log('\n=== Push Summary ===\n');
|
|
166
|
+
if (updates.length > 0) {
|
|
167
|
+
console.log(`Updated documents (${updates.length}):`);
|
|
168
|
+
for (const { file, result } of updates) {
|
|
169
|
+
const fileName = path.basename(file.filePath);
|
|
170
|
+
const hasConflict = conflicts.some(c => c.item.file === file);
|
|
171
|
+
const conflictNote = hasConflict ? ' ⚠️ [cloud changed since pull]' : '';
|
|
172
|
+
const warningNote = result.warning ? ` (${result.warning})` : '';
|
|
173
|
+
console.log(` ~ ${fileName}${conflictNote}${warningNote}`);
|
|
174
|
+
}
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
|
177
|
+
if (creates.length > 0) {
|
|
178
|
+
console.log(`New documents (${creates.length}):`);
|
|
179
|
+
for (const { file, result } of creates) {
|
|
180
|
+
const fileName = path.basename(file.filePath);
|
|
181
|
+
const warningNote = result.warning ? ` (${result.warning})` : '';
|
|
182
|
+
console.log(` + ${fileName}${warningNote}`);
|
|
183
|
+
}
|
|
184
|
+
console.log();
|
|
185
|
+
}
|
|
186
|
+
if (notFound.length > 0) {
|
|
187
|
+
console.log(`Documents not found in cloud (${notFound.length}) - will create new:`);
|
|
188
|
+
for (const { file, result } of notFound) {
|
|
189
|
+
const fileName = path.basename(file.filePath);
|
|
190
|
+
console.log(` ! ${fileName} (ID: ${result.documentId})`);
|
|
191
|
+
}
|
|
192
|
+
console.log();
|
|
193
|
+
}
|
|
194
|
+
if (invalid.length > 0) {
|
|
195
|
+
console.log(`Skipped - invalid files (${invalid.length}):`);
|
|
196
|
+
for (const { file, result } of invalid) {
|
|
197
|
+
const fileName = path.basename(file.filePath);
|
|
198
|
+
console.log(` ✗ ${fileName}: ${result.error}`);
|
|
199
|
+
}
|
|
200
|
+
console.log();
|
|
201
|
+
}
|
|
202
|
+
// Check if there's anything to push
|
|
203
|
+
const pushableFiles = [...updates, ...creates, ...notFound];
|
|
204
|
+
if (pushableFiles.length === 0) {
|
|
205
|
+
console.log('No valid documents to push.');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Single confirmation prompt
|
|
209
|
+
if (!options.yes) {
|
|
210
|
+
const hasConflicts = conflicts.length > 0;
|
|
211
|
+
const prompt = hasConflicts
|
|
212
|
+
? 'Continue with push? (will overwrite cloud changes) [y/N] '
|
|
213
|
+
: 'Continue with push? [y/N] ';
|
|
214
|
+
const confirmed = await confirm(prompt);
|
|
215
|
+
if (!confirmed) {
|
|
216
|
+
console.log('Push cancelled.');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Phase 2: Execute push
|
|
221
|
+
console.log('\nPushing documents to cloud...');
|
|
222
|
+
let created = 0;
|
|
223
|
+
let updated = 0;
|
|
224
|
+
for (const { file, result } of pushableFiles) {
|
|
225
|
+
const doc = file.metadata.document;
|
|
226
|
+
const fileName = path.basename(file.filePath);
|
|
227
|
+
// For not_found, clear the ID so we create a new document
|
|
228
|
+
const effectiveDocId = result.action === 'not_found' ? undefined : doc.id;
|
|
229
|
+
try {
|
|
230
|
+
const pushResult = await client.mutation(api.documents.saveWithRequirements.saveWithRequirements, {
|
|
231
|
+
projectAuth: {
|
|
232
|
+
projectSlug: projectId,
|
|
233
|
+
projectSecret,
|
|
234
|
+
},
|
|
235
|
+
target: { type: 'project', slug: projectId },
|
|
236
|
+
documentId: effectiveDocId,
|
|
237
|
+
title: doc.title,
|
|
238
|
+
markdownContent: file.markdownContent,
|
|
239
|
+
defaultPrefix: doc.defaultPrefix,
|
|
240
|
+
dryRun: false,
|
|
241
|
+
});
|
|
242
|
+
const isCreate = result.action === 'create' || result.action === 'not_found';
|
|
243
|
+
if (isCreate) {
|
|
244
|
+
// New document - write ID back to file
|
|
245
|
+
doc.id = pushResult;
|
|
246
|
+
file.metadata.pulledAt = new Date().toISOString();
|
|
247
|
+
file.metadata.version = 1;
|
|
248
|
+
const updatedContent = buildRequirementsFile(file.metadata, file.markdownContent);
|
|
249
|
+
fs.writeFileSync(file.filePath, updatedContent, 'utf-8');
|
|
250
|
+
console.log(` ✓ Created: ${fileName} → ${pushResult}`);
|
|
251
|
+
created++;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Update pulledAt to reflect this push
|
|
255
|
+
file.metadata.pulledAt = new Date().toISOString();
|
|
256
|
+
file.metadata.version = (file.metadata.version || 0) + 1;
|
|
257
|
+
const updatedContent = buildRequirementsFile(file.metadata, file.markdownContent);
|
|
258
|
+
fs.writeFileSync(file.filePath, updatedContent, 'utf-8');
|
|
259
|
+
console.log(` ✓ Updated: ${fileName}`);
|
|
260
|
+
updated++;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
console.error(` ✗ Failed: ${fileName} - ${err.message}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
console.log(`\n✓ Push complete!`);
|
|
268
|
+
if (created > 0) {
|
|
269
|
+
console.log(` Created: ${created} document(s)`);
|
|
270
|
+
}
|
|
271
|
+
if (updated > 0) {
|
|
272
|
+
console.log(` Updated: ${updated} document(s)`);
|
|
273
|
+
}
|
|
274
|
+
if (created === 0 && updated === 0) {
|
|
275
|
+
console.log(' No documents were pushed.');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
// Re-throw to let the CLI error handler display it cleanly
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Extract markdown content from a file, stripping YAML frontmatter.
|
|
285
|
+
* Exported for testing.
|
|
286
|
+
*/
|
|
287
|
+
export function extractMarkdownContent(rawContent) {
|
|
288
|
+
// Match YAML frontmatter: starts with ---, ends with ---
|
|
289
|
+
const frontmatterMatch = rawContent.match(/^---\n[\s\S]*?\n---\n*/);
|
|
290
|
+
if (frontmatterMatch) {
|
|
291
|
+
return rawContent.slice(frontmatterMatch[0].length);
|
|
292
|
+
}
|
|
293
|
+
return rawContent;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Prompt user for confirmation
|
|
297
|
+
*/
|
|
298
|
+
function confirm(question) {
|
|
299
|
+
const rl = readline.createInterface({
|
|
300
|
+
input: process.stdin,
|
|
301
|
+
output: process.stdout,
|
|
302
|
+
});
|
|
303
|
+
return new Promise((resolve) => {
|
|
304
|
+
rl.question(question, (answer) => {
|
|
305
|
+
rl.close();
|
|
306
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { parseRequirementsFromFile } from '../schema/index.js';
|
|
4
|
+
export async function testCommand(options) {
|
|
5
|
+
try {
|
|
6
|
+
console.log('Parsing and validating requirements...');
|
|
7
|
+
const requirementsDir = path.join(process.cwd(), '.requirements');
|
|
8
|
+
if (!fs.existsSync(requirementsDir)) {
|
|
9
|
+
console.error('Error: .requirements/ directory not found.');
|
|
10
|
+
console.log('Run "dotrequirements pull" first to sync requirements.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
// Get files to test
|
|
14
|
+
let files;
|
|
15
|
+
if (options.file) {
|
|
16
|
+
files = [options.file];
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
files = fs
|
|
20
|
+
.readdirSync(requirementsDir)
|
|
21
|
+
.filter((f) => f.endsWith('.requirements.md'))
|
|
22
|
+
.map((f) => path.join(requirementsDir, f));
|
|
23
|
+
}
|
|
24
|
+
if (files.length === 0) {
|
|
25
|
+
console.log('No Markdown requirements files found to test.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let totalRequirements = 0;
|
|
29
|
+
let passCount = 0;
|
|
30
|
+
let failCount = 0;
|
|
31
|
+
for (const filePath of files) {
|
|
32
|
+
const fileName = path.basename(filePath);
|
|
33
|
+
console.log(`\nTesting: ${fileName}`);
|
|
34
|
+
try {
|
|
35
|
+
// Parse and validate using shared schema module
|
|
36
|
+
const { metadata, requirements } = parseRequirementsFromFile(filePath);
|
|
37
|
+
// Count total requirements (root + all descendants)
|
|
38
|
+
let count = 0;
|
|
39
|
+
for (const req of requirements) {
|
|
40
|
+
count += countRequirementsInTree(req);
|
|
41
|
+
}
|
|
42
|
+
totalRequirements += count;
|
|
43
|
+
console.log(` ✓ Valid structure (${count} requirement(s))`);
|
|
44
|
+
console.log(` ✓ Version: ${metadata.version}`);
|
|
45
|
+
console.log(` ✓ Last sync: ${metadata.pulledAt}`);
|
|
46
|
+
passCount++;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(` ✗ Validation failed:`, error);
|
|
50
|
+
failCount++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
console.log(`\n${'='.repeat(50)}`);
|
|
54
|
+
console.log(`Total files: ${files.length}`);
|
|
55
|
+
console.log(`Passed: ${passCount}`);
|
|
56
|
+
console.log(`Failed: ${failCount}`);
|
|
57
|
+
console.log(`Total requirements: ${totalRequirements}`);
|
|
58
|
+
if (failCount > 0) {
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// Re-throw to let the CLI error handler display it cleanly
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Recursively count requirements in a tree (1 for root + all descendants)
|
|
69
|
+
*/
|
|
70
|
+
function countRequirementsInTree(node) {
|
|
71
|
+
let count = 1; // Count this node
|
|
72
|
+
// Count all children
|
|
73
|
+
for (const child of node.children) {
|
|
74
|
+
count += countRequirementsInTree(child);
|
|
75
|
+
}
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=test.js.map
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Convex deployment URL
|
|
3
|
+
* All users connect to the same backend
|
|
4
|
+
*/
|
|
5
|
+
const CONVEX_URL = 'https://data.dotrequirements.io';
|
|
6
|
+
/**
|
|
7
|
+
* Get the Convex deployment URL
|
|
8
|
+
*/
|
|
9
|
+
export function getConvexUrl() {
|
|
10
|
+
// Show deprecation warning if user has CONVEX_URL in their env
|
|
11
|
+
if (process.env.CONVEX_URL) {
|
|
12
|
+
console.warn('⚠️ CONVEX_URL is no longer needed and will be ignored. You can safely remove it from your .env.local');
|
|
13
|
+
}
|
|
14
|
+
return CONVEX_URL;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=config.js.map
|
package/dist/convex.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convex API stub for CLI usage
|
|
3
|
+
* This will be replaced with actual generated types when linked to the Convex backend
|
|
4
|
+
*/
|
|
5
|
+
export declare const api: {
|
|
6
|
+
documents: {
|
|
7
|
+
queries: {
|
|
8
|
+
exportForCli: any;
|
|
9
|
+
exportProjectForCli: any;
|
|
10
|
+
checkDocumentsExist: any;
|
|
11
|
+
getDocumentsMetadata: any;
|
|
12
|
+
};
|
|
13
|
+
mutations: {
|
|
14
|
+
create: any;
|
|
15
|
+
update: any;
|
|
16
|
+
};
|
|
17
|
+
saveWithRequirements: {
|
|
18
|
+
saveWithRequirements: any;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
requirements: {
|
|
22
|
+
queries: {
|
|
23
|
+
listByProject: any;
|
|
24
|
+
};
|
|
25
|
+
mutations: {
|
|
26
|
+
bulkUpdate: any;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
projects: {
|
|
30
|
+
mutations: {
|
|
31
|
+
create: any;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
teams: {
|
|
35
|
+
queries: {
|
|
36
|
+
listForUserWithUsage: any;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
projectSecrets: {
|
|
40
|
+
queries: {
|
|
41
|
+
getOwnSecret: any;
|
|
42
|
+
listOwnSecretsForTeam: any;
|
|
43
|
+
};
|
|
44
|
+
mutations: {
|
|
45
|
+
ensureOwnSecret: any;
|
|
46
|
+
revokeOwnSecret: any;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
lib: {
|
|
50
|
+
authActions: {
|
|
51
|
+
exchangeAuthorizationCode: any;
|
|
52
|
+
refreshAccessToken: any;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=convex.d.ts.map
|
package/dist/convex.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convex API stub for CLI usage
|
|
3
|
+
* This will be replaced with actual generated types when linked to the Convex backend
|
|
4
|
+
*/
|
|
5
|
+
export const api = {
|
|
6
|
+
documents: {
|
|
7
|
+
queries: {
|
|
8
|
+
exportForCli: 'documents/queries:exportForCli',
|
|
9
|
+
exportProjectForCli: 'documents/queries:exportProjectForCli',
|
|
10
|
+
checkDocumentsExist: 'documents/queries:checkDocumentsExist',
|
|
11
|
+
// SYNC-CLI-EDIT-2: Get document metadata for conflict detection
|
|
12
|
+
getDocumentsMetadata: 'documents/queries:getDocumentsMetadata',
|
|
13
|
+
},
|
|
14
|
+
mutations: {
|
|
15
|
+
create: 'documents/mutations:create',
|
|
16
|
+
update: 'documents/mutations:update',
|
|
17
|
+
},
|
|
18
|
+
// SYNC-ARCH-1: Save document with requirements derivation
|
|
19
|
+
saveWithRequirements: {
|
|
20
|
+
saveWithRequirements: 'documents/saveWithRequirements:saveWithRequirements',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
requirements: {
|
|
24
|
+
queries: {
|
|
25
|
+
listByProject: 'requirements/queries:listByProject',
|
|
26
|
+
},
|
|
27
|
+
mutations: {
|
|
28
|
+
bulkUpdate: 'requirements/mutations:bulkUpdate',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
projects: {
|
|
32
|
+
mutations: {
|
|
33
|
+
create: 'projects/mutations:create',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
teams: {
|
|
37
|
+
queries: {
|
|
38
|
+
listForUserWithUsage: 'teams/queries:listForUserWithUsage',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
projectSecrets: {
|
|
42
|
+
queries: {
|
|
43
|
+
getOwnSecret: 'projectSecrets/queries:getOwnSecret',
|
|
44
|
+
listOwnSecretsForTeam: 'projectSecrets/queries:listOwnSecretsForTeam',
|
|
45
|
+
},
|
|
46
|
+
mutations: {
|
|
47
|
+
ensureOwnSecret: 'projectSecrets/mutations:ensureOwnSecret',
|
|
48
|
+
revokeOwnSecret: 'projectSecrets/mutations:revokeOwnSecret',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
lib: {
|
|
52
|
+
authActions: {
|
|
53
|
+
exchangeAuthorizationCode: 'lib/authActions:exchangeAuthorizationCode',
|
|
54
|
+
refreshAccessToken: 'lib/authActions:refreshAccessToken',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=convex.js.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache management for the test harness.
|
|
3
|
+
*
|
|
4
|
+
* Manages the .requirements/.cache/ directory with:
|
|
5
|
+
* - lookup.json: Pre-resolved requirements for fast path resolution
|
|
6
|
+
* - tracking.jsonl: Append-only tracking of requirement() calls (cross-process safe)
|
|
7
|
+
* - coverage.json: Deduplication cache for cloud reporting
|
|
8
|
+
*/
|
|
9
|
+
import { type RequirementNode } from '../schema/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Tracking entry for a single requirement() call.
|
|
12
|
+
* Stored as one JSON object per line in tracking.jsonl
|
|
13
|
+
*/
|
|
14
|
+
export interface TrackingEntry {
|
|
15
|
+
/** Full requirement path (e.g., 'REQ-123.given' or 'REQ-123.0.1') */
|
|
16
|
+
requirementKey: string;
|
|
17
|
+
/** Source file and line (e.g., 'auth.test.ts:42') */
|
|
18
|
+
callerLocation: string;
|
|
19
|
+
/** Timestamp of the call */
|
|
20
|
+
timestamp: number;
|
|
21
|
+
/** Test run ID for grouping */
|
|
22
|
+
testRunId: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Structure of the lookup.json cache
|
|
26
|
+
*/
|
|
27
|
+
export interface LookupCache {
|
|
28
|
+
/** When the cache was generated */
|
|
29
|
+
generatedAt: string;
|
|
30
|
+
/** Map of requirement ID to flattened requirement data */
|
|
31
|
+
requirements: Record<string, {
|
|
32
|
+
id: string;
|
|
33
|
+
label: string;
|
|
34
|
+
content: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Structure of the coverage.json cache (for cloud deduplication)
|
|
39
|
+
*/
|
|
40
|
+
export interface CoverageCache {
|
|
41
|
+
current: {
|
|
42
|
+
testRunId: string;
|
|
43
|
+
timestamp: number;
|
|
44
|
+
requirementKeys: string[];
|
|
45
|
+
} | null;
|
|
46
|
+
previous: {
|
|
47
|
+
testRunId: string;
|
|
48
|
+
timestamp: number;
|
|
49
|
+
requirementKeys: string[];
|
|
50
|
+
} | null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Find the nearest .requirements directory by walking up from startDir
|
|
54
|
+
*/
|
|
55
|
+
export declare function findRequirementsDir(startDir?: string): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Find the project root (directory containing .requirements)
|
|
58
|
+
*/
|
|
59
|
+
export declare function findProjectRoot(startDir?: string): string | null;
|
|
60
|
+
/**
|
|
61
|
+
* Get the cache directory path, creating it if necessary
|
|
62
|
+
*/
|
|
63
|
+
export declare function getCacheDir(requirementsDir: string, create?: boolean): string;
|
|
64
|
+
/**
|
|
65
|
+
* Find the dotrequirements project root by looking for .env.local with credentials.
|
|
66
|
+
* Falls back to finding .requirements directory if no credentials found.
|
|
67
|
+
*/
|
|
68
|
+
export declare function findProjectRootWithCredentials(startDir?: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Find all *.requirements.md files under project root
|
|
71
|
+
*/
|
|
72
|
+
export declare function findRequirementsFiles(projectRoot: string): string[];
|
|
73
|
+
/**
|
|
74
|
+
* Write the lookup cache with all pre-parsed requirements
|
|
75
|
+
*/
|
|
76
|
+
export declare function writeLookupCache(requirementsDir: string, requirements: RequirementNode[]): void;
|
|
77
|
+
/**
|
|
78
|
+
* Read the lookup cache, returning null if not found or invalid
|
|
79
|
+
*/
|
|
80
|
+
export declare function readLookupCache(requirementsDir: string): LookupCache | null;
|
|
81
|
+
/**
|
|
82
|
+
* Generate and store a unique test run ID
|
|
83
|
+
*/
|
|
84
|
+
export declare function initTestRunId(requirementsDir: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Get the current test run ID, or null if not initialized
|
|
87
|
+
*/
|
|
88
|
+
export declare function getTestRunId(requirementsDir: string): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Clean up the test run ID file
|
|
91
|
+
*/
|
|
92
|
+
export declare function cleanupTestRunId(requirementsDir: string): void;
|
|
93
|
+
/**
|
|
94
|
+
* Write the project root to cache (for cross-process persistence).
|
|
95
|
+
* This allows test workers to find the lookup cache even after cwd changes.
|
|
96
|
+
*/
|
|
97
|
+
export declare function writeProjectRoot(requirementsDir: string, projectRoot: string): void;
|
|
98
|
+
/**
|
|
99
|
+
* Read the project root from cache, returning null if not found.
|
|
100
|
+
*/
|
|
101
|
+
export declare function readProjectRoot(requirementsDir: string): string | null;
|
|
102
|
+
/**
|
|
103
|
+
* Try to find the project root from any known .requirements cache directory.
|
|
104
|
+
* Walks up from cwd looking for .requirements/.cache/project-root file.
|
|
105
|
+
*/
|
|
106
|
+
export declare function findCachedProjectRoot(startDir?: string): string | null;
|
|
107
|
+
/**
|
|
108
|
+
* Clear tracking data for a new test run
|
|
109
|
+
*/
|
|
110
|
+
export declare function clearTrackingFile(requirementsDir: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Append a tracking entry to the JSONL file (cross-process safe)
|
|
113
|
+
*/
|
|
114
|
+
export declare function appendTrackingEntry(requirementsDir: string, entry: TrackingEntry): void;
|
|
115
|
+
/**
|
|
116
|
+
* Read all tracking entries from the JSONL file
|
|
117
|
+
*/
|
|
118
|
+
export declare function readTrackingEntries(requirementsDir: string): TrackingEntry[];
|
|
119
|
+
/**
|
|
120
|
+
* Delete the tracking file after finalize
|
|
121
|
+
*/
|
|
122
|
+
export declare function deleteTrackingFile(requirementsDir: string): void;
|
|
123
|
+
/**
|
|
124
|
+
* Read the coverage cache for cloud deduplication
|
|
125
|
+
*/
|
|
126
|
+
export declare function readCoverageCache(requirementsDir: string): CoverageCache;
|
|
127
|
+
/**
|
|
128
|
+
* Update the coverage cache after reporting
|
|
129
|
+
*/
|
|
130
|
+
export declare function updateCoverageCache(requirementsDir: string, testRunId: string, requirementKeys: string[]): void;
|
|
131
|
+
/**
|
|
132
|
+
* Check if a requirement needs reporting based on coverage cache
|
|
133
|
+
*/
|
|
134
|
+
export declare function needsReporting(requirementKey: string, cache: CoverageCache): boolean;
|
|
135
|
+
//# sourceMappingURL=cache.d.ts.map
|