@popoverai/dotrequirements 0.15.0 → 0.17.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/dist/commands/push.d.ts +0 -5
- package/dist/commands/push.js +87 -204
- package/dist/harness/cache.d.ts +2 -1
- package/dist/harness/cache.js +32 -1
- package/dist/mcp/convexClient.d.ts +7 -0
- package/dist/mcp/convexClient.js +50 -2
- package/dist/mcp/handlers/authoring.d.ts +41 -0
- package/dist/mcp/handlers/authoring.js +308 -0
- package/dist/mcp/handlers/coverage.d.ts +44 -0
- package/dist/mcp/handlers/coverage.js +105 -0
- package/dist/mcp/handlers/debug.d.ts +17 -0
- package/dist/mcp/handlers/debug.js +37 -0
- package/dist/mcp/handlers/get.d.ts +24 -0
- package/dist/mcp/handlers/get.js +64 -0
- package/dist/mcp/handlers/index.d.ts +28 -0
- package/dist/mcp/handlers/index.js +19 -0
- package/dist/mcp/handlers/list.d.ts +36 -0
- package/dist/mcp/handlers/list.js +65 -0
- package/dist/mcp/handlers/push.d.ts +26 -0
- package/dist/mcp/handlers/push.js +143 -0
- package/dist/mcp/handlers/review.d.ts +51 -0
- package/dist/mcp/handlers/review.js +219 -0
- package/dist/mcp/handlers/search.d.ts +30 -0
- package/dist/mcp/handlers/search.js +60 -0
- package/dist/mcp/handlers/test-mapping.d.ts +39 -0
- package/dist/mcp/handlers/test-mapping.js +133 -0
- package/dist/mcp/handlers/types.d.ts +75 -0
- package/dist/mcp/handlers/types.js +25 -0
- package/dist/mcp/index.d.ts +42 -1
- package/dist/mcp/index.js +46 -1153
- package/dist/push/core.d.ts +105 -0
- package/dist/push/core.js +216 -0
- package/dist/push/index.d.ts +8 -0
- package/dist/push/index.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
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
|
|
11
|
+
*/
|
|
12
|
+
import type { Metadata } from '../schema/index.js';
|
|
13
|
+
export interface PushCredentials {
|
|
14
|
+
projectId: string;
|
|
15
|
+
projectSecret: string;
|
|
16
|
+
convexUrl: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parsed file ready for push.
|
|
20
|
+
* SYNC-ARCH-1: Documents are the sync unit, not individual requirements.
|
|
21
|
+
*/
|
|
22
|
+
export interface ParsedFile {
|
|
23
|
+
filePath: string;
|
|
24
|
+
metadata: Metadata;
|
|
25
|
+
/** Raw file content (markdown with frontmatter stripped) */
|
|
26
|
+
markdownContent: string;
|
|
27
|
+
/** Requirement count for display */
|
|
28
|
+
requirementCount: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Cloud document metadata for conflict detection.
|
|
32
|
+
* SYNC-CLI-EDIT-2: Compare pulledAt with cloud updatedAt.
|
|
33
|
+
*/
|
|
34
|
+
export interface CloudDocumentMetadata {
|
|
35
|
+
documentId: string;
|
|
36
|
+
version: number;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Dry run result from saveWithRequirements.
|
|
41
|
+
*/
|
|
42
|
+
export interface DryRunResultItem {
|
|
43
|
+
dryRun: true;
|
|
44
|
+
action: 'create' | 'update' | 'not_found' | 'invalid';
|
|
45
|
+
documentId?: string;
|
|
46
|
+
title?: string;
|
|
47
|
+
warning?: string;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* File with its dry run result.
|
|
52
|
+
*/
|
|
53
|
+
export interface FileWithDryRun {
|
|
54
|
+
file: ParsedFile;
|
|
55
|
+
result: DryRunResultItem;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Conflict information for a file.
|
|
59
|
+
*/
|
|
60
|
+
export interface ConflictInfo {
|
|
61
|
+
item: FileWithDryRun;
|
|
62
|
+
cloudMeta: CloudDocumentMetadata;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Result of the dry run phase.
|
|
66
|
+
*/
|
|
67
|
+
export interface DryRunResult {
|
|
68
|
+
updates: FileWithDryRun[];
|
|
69
|
+
creates: FileWithDryRun[];
|
|
70
|
+
notFound: FileWithDryRun[];
|
|
71
|
+
invalid: FileWithDryRun[];
|
|
72
|
+
conflicts: ConflictInfo[];
|
|
73
|
+
totalRequirements: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Result of the execute phase.
|
|
77
|
+
*/
|
|
78
|
+
export interface PushResult {
|
|
79
|
+
created: number;
|
|
80
|
+
updated: number;
|
|
81
|
+
errors: Array<{
|
|
82
|
+
fileName: string;
|
|
83
|
+
error: string;
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract markdown content from a file, stripping YAML frontmatter.
|
|
88
|
+
*/
|
|
89
|
+
export declare function extractMarkdownContent(rawContent: string): string;
|
|
90
|
+
/**
|
|
91
|
+
* Parse files for push. Returns parsed files with metadata and content.
|
|
92
|
+
*/
|
|
93
|
+
export declare function parseFilesForPush(filePaths: string[]): {
|
|
94
|
+
parsedFiles: ParsedFile[];
|
|
95
|
+
totalRequirements: number;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Execute dry run phase: validate all files against Convex and detect conflicts.
|
|
99
|
+
*/
|
|
100
|
+
export declare function dryRunPush(parsedFiles: ParsedFile[], credentials: PushCredentials): Promise<DryRunResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Execute the push: save all pushable files to Convex and update local files.
|
|
103
|
+
*/
|
|
104
|
+
export declare function executePush(dryRunResult: DryRunResult, credentials: PushCredentials): Promise<PushResult>;
|
|
105
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1,216 @@
|
|
|
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
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { ConvexHttpClient } from 'convex/browser';
|
|
15
|
+
import { parseRequirementsFromFile, getAllRequirements, parseRequirementKey, buildRequirementsFile, } from '../schema/index.js';
|
|
16
|
+
import { api } from '../convex.js';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Parsing
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Extract markdown content from a file, stripping YAML frontmatter.
|
|
22
|
+
*/
|
|
23
|
+
export function extractMarkdownContent(rawContent) {
|
|
24
|
+
// Match YAML frontmatter: starts with ---, ends with ---
|
|
25
|
+
const frontmatterMatch = rawContent.match(/^---\n[\s\S]*?\n---\n*/);
|
|
26
|
+
if (frontmatterMatch) {
|
|
27
|
+
return rawContent.slice(frontmatterMatch[0].length);
|
|
28
|
+
}
|
|
29
|
+
return rawContent;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse files for push. Returns parsed files with metadata and content.
|
|
33
|
+
*/
|
|
34
|
+
export function parseFilesForPush(filePaths) {
|
|
35
|
+
const parsedFiles = [];
|
|
36
|
+
let totalRequirements = 0;
|
|
37
|
+
for (const filePath of filePaths) {
|
|
38
|
+
// Read raw file content
|
|
39
|
+
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
40
|
+
// Parse to get metadata and requirements
|
|
41
|
+
const parsed = parseRequirementsFromFile(filePath);
|
|
42
|
+
const flatRequirements = getAllRequirements(parsed.requirements);
|
|
43
|
+
// Extract markdown content (everything after frontmatter)
|
|
44
|
+
const markdownContent = extractMarkdownContent(rawContent);
|
|
45
|
+
// DOC-HEADER-11.1/11.2: Infer prefix from first requirement if not specified
|
|
46
|
+
const doc = parsed.metadata.document;
|
|
47
|
+
if (doc && !doc.defaultPrefix && flatRequirements.length > 0) {
|
|
48
|
+
const firstReq = flatRequirements[0];
|
|
49
|
+
if (firstReq) {
|
|
50
|
+
const parsedKey = parseRequirementKey(firstReq.id);
|
|
51
|
+
if (parsedKey) {
|
|
52
|
+
doc.defaultPrefix = parsedKey.prefix;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
parsedFiles.push({
|
|
57
|
+
filePath,
|
|
58
|
+
metadata: parsed.metadata,
|
|
59
|
+
markdownContent,
|
|
60
|
+
requirementCount: flatRequirements.length,
|
|
61
|
+
});
|
|
62
|
+
totalRequirements += flatRequirements.length;
|
|
63
|
+
}
|
|
64
|
+
return { parsedFiles, totalRequirements };
|
|
65
|
+
}
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Dry Run
|
|
68
|
+
// ============================================================================
|
|
69
|
+
/**
|
|
70
|
+
* Execute dry run phase: validate all files against Convex and detect conflicts.
|
|
71
|
+
*/
|
|
72
|
+
export async function dryRunPush(parsedFiles, credentials) {
|
|
73
|
+
const client = new ConvexHttpClient(credentials.convexUrl);
|
|
74
|
+
const dryRunResults = [];
|
|
75
|
+
// Phase 1: Dry run to categorize all files
|
|
76
|
+
for (const file of parsedFiles) {
|
|
77
|
+
const doc = file.metadata.document;
|
|
78
|
+
const fileName = path.basename(file.filePath);
|
|
79
|
+
if (!doc) {
|
|
80
|
+
// No document section - mark as invalid locally
|
|
81
|
+
dryRunResults.push({
|
|
82
|
+
file,
|
|
83
|
+
result: {
|
|
84
|
+
dryRun: true,
|
|
85
|
+
action: 'invalid',
|
|
86
|
+
title: fileName,
|
|
87
|
+
error: 'Missing document section in frontmatter',
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const result = (await client.mutation(api.documents.saveWithRequirements.saveWithRequirements, {
|
|
94
|
+
projectAuth: {
|
|
95
|
+
projectSlug: credentials.projectId,
|
|
96
|
+
projectSecret: credentials.projectSecret,
|
|
97
|
+
},
|
|
98
|
+
target: { type: 'project', slug: credentials.projectId },
|
|
99
|
+
documentId: doc.id,
|
|
100
|
+
title: doc.title,
|
|
101
|
+
markdownContent: file.markdownContent,
|
|
102
|
+
defaultPrefix: doc.defaultPrefix,
|
|
103
|
+
dryRun: true,
|
|
104
|
+
}));
|
|
105
|
+
dryRunResults.push({ file, result });
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
dryRunResults.push({
|
|
109
|
+
file,
|
|
110
|
+
result: {
|
|
111
|
+
dryRun: true,
|
|
112
|
+
action: 'invalid',
|
|
113
|
+
title: doc.title,
|
|
114
|
+
error: err.message,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Categorize results
|
|
120
|
+
const updates = dryRunResults.filter((r) => r.result.action === 'update');
|
|
121
|
+
const creates = dryRunResults.filter((r) => r.result.action === 'create');
|
|
122
|
+
const notFound = dryRunResults.filter((r) => r.result.action === 'not_found');
|
|
123
|
+
const invalid = dryRunResults.filter((r) => r.result.action === 'invalid');
|
|
124
|
+
// Check for conflicts on updates (cloud changed since last pull)
|
|
125
|
+
const conflicts = [];
|
|
126
|
+
if (updates.length > 0) {
|
|
127
|
+
const docIds = updates.map((u) => u.result.documentId);
|
|
128
|
+
const cloudMetadata = await client.query(api.documents.queries.getDocumentsMetadata, {
|
|
129
|
+
projectAuth: {
|
|
130
|
+
projectSlug: credentials.projectId,
|
|
131
|
+
projectSecret: credentials.projectSecret,
|
|
132
|
+
},
|
|
133
|
+
target: { type: 'project', slug: credentials.projectId },
|
|
134
|
+
documentIds: docIds,
|
|
135
|
+
});
|
|
136
|
+
const cloudMetaByDocId = new Map(cloudMetadata.map((m) => [m.documentId, m]));
|
|
137
|
+
for (const item of updates) {
|
|
138
|
+
const docId = item.result.documentId;
|
|
139
|
+
const cloudMeta = cloudMetaByDocId.get(docId);
|
|
140
|
+
if (cloudMeta && item.file.metadata.pulledAt) {
|
|
141
|
+
const pulledAtMs = new Date(item.file.metadata.pulledAt).getTime();
|
|
142
|
+
if (cloudMeta.updatedAt > pulledAtMs) {
|
|
143
|
+
conflicts.push({ item, cloudMeta });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
updates,
|
|
150
|
+
creates,
|
|
151
|
+
notFound,
|
|
152
|
+
invalid,
|
|
153
|
+
conflicts,
|
|
154
|
+
totalRequirements: parsedFiles.reduce((sum, f) => sum + f.requirementCount, 0),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Execute Push
|
|
159
|
+
// ============================================================================
|
|
160
|
+
/**
|
|
161
|
+
* Execute the push: save all pushable files to Convex and update local files.
|
|
162
|
+
*/
|
|
163
|
+
export async function executePush(dryRunResult, credentials) {
|
|
164
|
+
const client = new ConvexHttpClient(credentials.convexUrl);
|
|
165
|
+
const pushableFiles = [
|
|
166
|
+
...dryRunResult.updates,
|
|
167
|
+
...dryRunResult.creates,
|
|
168
|
+
...dryRunResult.notFound,
|
|
169
|
+
];
|
|
170
|
+
let created = 0;
|
|
171
|
+
let updated = 0;
|
|
172
|
+
const errors = [];
|
|
173
|
+
for (const { file, result } of pushableFiles) {
|
|
174
|
+
const doc = file.metadata.document;
|
|
175
|
+
const fileName = path.basename(file.filePath);
|
|
176
|
+
// For not_found, clear the ID so we create a new document
|
|
177
|
+
const effectiveDocId = result.action === 'not_found' ? undefined : doc.id;
|
|
178
|
+
try {
|
|
179
|
+
const pushResult = (await client.mutation(api.documents.saveWithRequirements.saveWithRequirements, {
|
|
180
|
+
projectAuth: {
|
|
181
|
+
projectSlug: credentials.projectId,
|
|
182
|
+
projectSecret: credentials.projectSecret,
|
|
183
|
+
},
|
|
184
|
+
target: { type: 'project', slug: credentials.projectId },
|
|
185
|
+
documentId: effectiveDocId,
|
|
186
|
+
title: doc.title,
|
|
187
|
+
markdownContent: file.markdownContent,
|
|
188
|
+
defaultPrefix: doc.defaultPrefix,
|
|
189
|
+
dryRun: false,
|
|
190
|
+
}));
|
|
191
|
+
const isCreate = result.action === 'create' || result.action === 'not_found';
|
|
192
|
+
if (isCreate) {
|
|
193
|
+
// New document - write ID back to file
|
|
194
|
+
doc.id = pushResult;
|
|
195
|
+
file.metadata.pulledAt = new Date().toISOString();
|
|
196
|
+
file.metadata.version = 1;
|
|
197
|
+
const updatedContent = buildRequirementsFile(file.metadata, file.markdownContent);
|
|
198
|
+
fs.writeFileSync(file.filePath, updatedContent, 'utf-8');
|
|
199
|
+
created++;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Update pulledAt to reflect this push
|
|
203
|
+
file.metadata.pulledAt = new Date().toISOString();
|
|
204
|
+
file.metadata.version = (file.metadata.version || 0) + 1;
|
|
205
|
+
const updatedContent = buildRequirementsFile(file.metadata, file.markdownContent);
|
|
206
|
+
fs.writeFileSync(file.filePath, updatedContent, 'utf-8');
|
|
207
|
+
updated++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
errors.push({ fileName, error: err.message });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { created, updated, errors };
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push Module
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export { type PushCredentials, type ParsedFile, type CloudDocumentMetadata, type DryRunResultItem, type FileWithDryRun, type ConflictInfo, type DryRunResult, type PushResult, extractMarkdownContent, parseFilesForPush, dryRunPush, executePush, } from './core.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push Module
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export {
|
|
8
|
+
// Functions
|
|
9
|
+
extractMarkdownContent, parseFilesForPush, dryRunPush, executePush, } from './core.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|