@vibecheckai/cli 3.2.6 → 3.3.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/bin/registry.js +192 -5
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/analyzers.js +81 -18
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/cli-output.js +7 -1
- package/bin/runners/lib/error-handler.js +16 -9
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/global-flags.js +37 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/unified-cli-output.js +604 -0
- package/bin/runners/lib/upsell.js +148 -0
- package/bin/runners/runApprove.js +1200 -0
- package/bin/runners/runAuth.js +324 -95
- package/bin/runners/runCheckpoint.js +39 -21
- package/bin/runners/runClassify.js +859 -0
- package/bin/runners/runContext.js +136 -24
- package/bin/runners/runDoctor.js +108 -68
- package/bin/runners/runFix.js +6 -5
- package/bin/runners/runGuard.js +212 -118
- package/bin/runners/runInit.js +3 -2
- package/bin/runners/runMcp.js +130 -52
- package/bin/runners/runPolish.js +43 -20
- package/bin/runners/runProve.js +1 -2
- package/bin/runners/runReport.js +3 -2
- package/bin/runners/runScan.js +63 -44
- package/bin/runners/runShip.js +3 -4
- package/bin/runners/runValidate.js +19 -2
- package/bin/runners/runWatch.js +104 -53
- package/bin/vibecheck.js +106 -19
- package/mcp-server/HARDENING_SUMMARY.md +299 -0
- package/mcp-server/agent-firewall-interceptor.js +367 -31
- package/mcp-server/authority-tools.js +569 -0
- package/mcp-server/conductor/conflict-resolver.js +588 -0
- package/mcp-server/conductor/execution-planner.js +544 -0
- package/mcp-server/conductor/index.js +377 -0
- package/mcp-server/conductor/lock-manager.js +615 -0
- package/mcp-server/conductor/request-queue.js +550 -0
- package/mcp-server/conductor/session-manager.js +500 -0
- package/mcp-server/conductor/tools.js +510 -0
- package/mcp-server/index.js +1149 -243
- package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
- package/mcp-server/lib/logger.cjs +30 -0
- package/mcp-server/logger.js +173 -0
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/tier-auth.js +245 -35
- package/mcp-server/truth-firewall-tools.js +145 -15
- package/mcp-server/vibecheck-tools.js +2 -2
- package/package.json +2 -3
- package/mcp-server/index.old.js +0 -4137
- package/mcp-server/package-lock.json +0 -165
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assumption Extractor
|
|
3
|
+
*
|
|
4
|
+
* Auto-extracts assumptions from code content.
|
|
5
|
+
* Used when proposals don't explicitly declare their assumptions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract environment variable assumptions
|
|
12
|
+
* @param {string} content - File content
|
|
13
|
+
* @returns {Array} Env var assumptions
|
|
14
|
+
*/
|
|
15
|
+
function extractEnvAssumptions(content) {
|
|
16
|
+
const assumptions = [];
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
|
|
19
|
+
// process.env.VAR_NAME
|
|
20
|
+
const processEnvRegex = /process\.env\.([A-Z][A-Z0-9_]+)/g;
|
|
21
|
+
let match;
|
|
22
|
+
|
|
23
|
+
while ((match = processEnvRegex.exec(content)) !== null) {
|
|
24
|
+
const key = match[1];
|
|
25
|
+
if (!seen.has(key)) {
|
|
26
|
+
seen.add(key);
|
|
27
|
+
assumptions.push({
|
|
28
|
+
type: "env",
|
|
29
|
+
key,
|
|
30
|
+
reason: `Used in code: process.env.${key}`,
|
|
31
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// import.meta.env.VAR_NAME (Vite)
|
|
37
|
+
const viteEnvRegex = /import\.meta\.env\.([A-Z][A-Z0-9_]+)/g;
|
|
38
|
+
|
|
39
|
+
while ((match = viteEnvRegex.exec(content)) !== null) {
|
|
40
|
+
const key = match[1];
|
|
41
|
+
if (!seen.has(key)) {
|
|
42
|
+
seen.add(key);
|
|
43
|
+
assumptions.push({
|
|
44
|
+
type: "env",
|
|
45
|
+
key,
|
|
46
|
+
reason: `Used in code: import.meta.env.${key}`,
|
|
47
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Destructured: const { VAR } = process.env
|
|
53
|
+
const destructuredRegex = /const\s*\{\s*([^}]+)\s*\}\s*=\s*process\.env/g;
|
|
54
|
+
|
|
55
|
+
while ((match = destructuredRegex.exec(content)) !== null) {
|
|
56
|
+
const vars = match[1].split(",").map(s => s.trim().split(/\s+as\s+/)[0].trim());
|
|
57
|
+
for (const varName of vars) {
|
|
58
|
+
if (varName && /^[A-Z][A-Z0-9_]*$/.test(varName) && !seen.has(varName)) {
|
|
59
|
+
seen.add(varName);
|
|
60
|
+
assumptions.push({
|
|
61
|
+
type: "env",
|
|
62
|
+
key: varName,
|
|
63
|
+
reason: "Destructured from process.env",
|
|
64
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return assumptions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract route assumptions
|
|
75
|
+
* @param {string} content - File content
|
|
76
|
+
* @returns {Array} Route assumptions
|
|
77
|
+
*/
|
|
78
|
+
function extractRouteAssumptions(content) {
|
|
79
|
+
const assumptions = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
|
|
82
|
+
// fetch('/api/...')
|
|
83
|
+
const fetchRegex = /fetch\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
84
|
+
let match;
|
|
85
|
+
|
|
86
|
+
while ((match = fetchRegex.exec(content)) !== null) {
|
|
87
|
+
const path = match[1];
|
|
88
|
+
if (path.startsWith("/api/") || path.startsWith("/")) {
|
|
89
|
+
const key = path.split("?")[0]; // Remove query params
|
|
90
|
+
if (!seen.has(key)) {
|
|
91
|
+
seen.add(key);
|
|
92
|
+
assumptions.push({
|
|
93
|
+
type: "route",
|
|
94
|
+
path: key,
|
|
95
|
+
method: detectHttpMethod(content, match.index),
|
|
96
|
+
reason: `Fetch call to ${key}`,
|
|
97
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// axios.get('/api/...')
|
|
104
|
+
const axiosRegex = /axios\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
105
|
+
|
|
106
|
+
while ((match = axiosRegex.exec(content)) !== null) {
|
|
107
|
+
const method = match[1].toUpperCase();
|
|
108
|
+
const path = match[2];
|
|
109
|
+
if (path.startsWith("/api/") || path.startsWith("/")) {
|
|
110
|
+
const key = `${method}:${path.split("?")[0]}`;
|
|
111
|
+
if (!seen.has(key)) {
|
|
112
|
+
seen.add(key);
|
|
113
|
+
assumptions.push({
|
|
114
|
+
type: "route",
|
|
115
|
+
path: path.split("?")[0],
|
|
116
|
+
method,
|
|
117
|
+
reason: `Axios ${method} call`,
|
|
118
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return assumptions;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Detect HTTP method from context
|
|
129
|
+
*/
|
|
130
|
+
function detectHttpMethod(content, index) {
|
|
131
|
+
// Look back for method specification
|
|
132
|
+
const context = content.substring(Math.max(0, index - 200), index);
|
|
133
|
+
|
|
134
|
+
if (context.includes("method: 'POST'") || context.includes('method: "POST"')) return "POST";
|
|
135
|
+
if (context.includes("method: 'PUT'") || context.includes('method: "PUT"')) return "PUT";
|
|
136
|
+
if (context.includes("method: 'DELETE'") || context.includes('method: "DELETE"')) return "DELETE";
|
|
137
|
+
if (context.includes("method: 'PATCH'") || context.includes('method: "PATCH"')) return "PATCH";
|
|
138
|
+
|
|
139
|
+
return "GET"; // Default
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Extract import/dependency assumptions
|
|
144
|
+
* @param {string} content - File content
|
|
145
|
+
* @returns {Array} Dependency assumptions
|
|
146
|
+
*/
|
|
147
|
+
function extractDependencyAssumptions(content) {
|
|
148
|
+
const assumptions = [];
|
|
149
|
+
const seen = new Set();
|
|
150
|
+
|
|
151
|
+
// External imports
|
|
152
|
+
const importRegex = /import\s+.*?\s+from\s+['"]([^'"./][^'"]*)['"]/g;
|
|
153
|
+
let match;
|
|
154
|
+
|
|
155
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
156
|
+
const pkg = match[1].split("/")[0]; // Get package name
|
|
157
|
+
if (!seen.has(pkg) && !isBuiltinModule(pkg)) {
|
|
158
|
+
seen.add(pkg);
|
|
159
|
+
assumptions.push({
|
|
160
|
+
type: "dependency",
|
|
161
|
+
key: pkg,
|
|
162
|
+
reason: `Import statement: ${match[0].slice(0, 50)}...`,
|
|
163
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// require() calls
|
|
169
|
+
const requireRegex = /require\s*\(\s*['"]([^'"./][^'"]*)['"]\s*\)/g;
|
|
170
|
+
|
|
171
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
172
|
+
const pkg = match[1].split("/")[0];
|
|
173
|
+
if (!seen.has(pkg) && !isBuiltinModule(pkg)) {
|
|
174
|
+
seen.add(pkg);
|
|
175
|
+
assumptions.push({
|
|
176
|
+
type: "dependency",
|
|
177
|
+
key: pkg,
|
|
178
|
+
reason: "Required module",
|
|
179
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return assumptions;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if module is Node.js builtin
|
|
189
|
+
*/
|
|
190
|
+
function isBuiltinModule(name) {
|
|
191
|
+
const builtins = [
|
|
192
|
+
"fs", "path", "os", "http", "https", "crypto", "util", "events",
|
|
193
|
+
"stream", "buffer", "url", "querystring", "child_process", "net",
|
|
194
|
+
"assert", "zlib", "readline", "cluster", "dns", "tls", "dgram",
|
|
195
|
+
"process", "module", "vm", "worker_threads",
|
|
196
|
+
];
|
|
197
|
+
return builtins.includes(name) || name.startsWith("node:");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Extract service/class assumptions
|
|
202
|
+
* @param {string} content - File content
|
|
203
|
+
* @returns {Array} Service assumptions
|
|
204
|
+
*/
|
|
205
|
+
function extractServiceAssumptions(content) {
|
|
206
|
+
const assumptions = [];
|
|
207
|
+
const seen = new Set();
|
|
208
|
+
|
|
209
|
+
// new ServiceName() or ServiceName.method()
|
|
210
|
+
const serviceRegex = /(?:new\s+|(?:await\s+)?)((?:[A-Z][a-z]+)+(?:Service|Client|Provider|Manager|Controller|Repository))(?:\s*\(|\.)/g;
|
|
211
|
+
let match;
|
|
212
|
+
|
|
213
|
+
while ((match = serviceRegex.exec(content)) !== null) {
|
|
214
|
+
const service = match[1];
|
|
215
|
+
if (!seen.has(service)) {
|
|
216
|
+
seen.add(service);
|
|
217
|
+
assumptions.push({
|
|
218
|
+
type: "service",
|
|
219
|
+
key: service,
|
|
220
|
+
reason: `Service usage: ${service}`,
|
|
221
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Prisma client
|
|
227
|
+
if (content.includes("prisma.")) {
|
|
228
|
+
const prismaRegex = /prisma\.(\w+)\./g;
|
|
229
|
+
while ((match = prismaRegex.exec(content)) !== null) {
|
|
230
|
+
const model = match[1];
|
|
231
|
+
const key = `prisma.${model}`;
|
|
232
|
+
if (!seen.has(key)) {
|
|
233
|
+
seen.add(key);
|
|
234
|
+
assumptions.push({
|
|
235
|
+
type: "service",
|
|
236
|
+
key: `PrismaModel:${model}`,
|
|
237
|
+
reason: `Prisma model access: ${model}`,
|
|
238
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return assumptions;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Extract file reference assumptions
|
|
249
|
+
* @param {string} content - File content
|
|
250
|
+
* @param {string} currentFile - Current file path
|
|
251
|
+
* @returns {Array} File assumptions
|
|
252
|
+
*/
|
|
253
|
+
function extractFileAssumptions(content, currentFile = "") {
|
|
254
|
+
const assumptions = [];
|
|
255
|
+
const seen = new Set();
|
|
256
|
+
|
|
257
|
+
// Relative imports
|
|
258
|
+
const relativeImportRegex = /(?:import|require)\s*\(?['"](\.[^'"]+)['"]/g;
|
|
259
|
+
let match;
|
|
260
|
+
|
|
261
|
+
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
262
|
+
const importPath = match[1];
|
|
263
|
+
if (!seen.has(importPath)) {
|
|
264
|
+
seen.add(importPath);
|
|
265
|
+
assumptions.push({
|
|
266
|
+
type: "file",
|
|
267
|
+
path: importPath,
|
|
268
|
+
reason: `Relative import: ${importPath}`,
|
|
269
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// fs.readFileSync, fs.writeFileSync, etc.
|
|
275
|
+
const fsOpRegex = /fs(?:Promises)?\.(?:read|write|unlink|mkdir|rmdir|access)(?:File)?(?:Sync)?\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
276
|
+
|
|
277
|
+
while ((match = fsOpRegex.exec(content)) !== null) {
|
|
278
|
+
const filePath = match[1];
|
|
279
|
+
if (!seen.has(filePath) && !filePath.includes("${")) { // Skip template literals
|
|
280
|
+
seen.add(filePath);
|
|
281
|
+
assumptions.push({
|
|
282
|
+
type: "file",
|
|
283
|
+
path: filePath,
|
|
284
|
+
reason: `File system operation on: ${filePath}`,
|
|
285
|
+
line: content.substring(0, match.index).split("\n").length,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return assumptions;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Extract all assumptions from content
|
|
295
|
+
* @param {string} content - File content
|
|
296
|
+
* @param {Object} options - Extraction options
|
|
297
|
+
* @returns {Array} All extracted assumptions
|
|
298
|
+
*/
|
|
299
|
+
function extractAssumptions(content, options = {}) {
|
|
300
|
+
const { filePath = "", includeTypes = ["env", "route", "dependency", "service", "file"] } = options;
|
|
301
|
+
|
|
302
|
+
const assumptions = [];
|
|
303
|
+
|
|
304
|
+
if (includeTypes.includes("env")) {
|
|
305
|
+
assumptions.push(...extractEnvAssumptions(content));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (includeTypes.includes("route")) {
|
|
309
|
+
assumptions.push(...extractRouteAssumptions(content));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (includeTypes.includes("dependency")) {
|
|
313
|
+
assumptions.push(...extractDependencyAssumptions(content));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (includeTypes.includes("service")) {
|
|
317
|
+
assumptions.push(...extractServiceAssumptions(content));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (includeTypes.includes("file")) {
|
|
321
|
+
assumptions.push(...extractFileAssumptions(content, filePath));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Sort by line number
|
|
325
|
+
assumptions.sort((a, b) => (a.line || 0) - (b.line || 0));
|
|
326
|
+
|
|
327
|
+
return assumptions;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Extract assumptions from all operations in a proposal
|
|
332
|
+
* @param {Array} operations - Proposal operations
|
|
333
|
+
* @returns {Array} All assumptions
|
|
334
|
+
*/
|
|
335
|
+
function extractFromOperations(operations) {
|
|
336
|
+
const allAssumptions = [];
|
|
337
|
+
const seen = new Set();
|
|
338
|
+
|
|
339
|
+
for (const op of operations) {
|
|
340
|
+
if (op.content) {
|
|
341
|
+
const assumptions = extractAssumptions(op.content, { filePath: op.path });
|
|
342
|
+
|
|
343
|
+
for (const assumption of assumptions) {
|
|
344
|
+
// Deduplicate
|
|
345
|
+
const key = `${assumption.type}:${assumption.key || assumption.path}`;
|
|
346
|
+
if (!seen.has(key)) {
|
|
347
|
+
seen.add(key);
|
|
348
|
+
allAssumptions.push({
|
|
349
|
+
...assumption,
|
|
350
|
+
sourceFile: op.path,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return allAssumptions;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Merge extracted assumptions with declared assumptions
|
|
362
|
+
* @param {Array} declared - Declared assumptions
|
|
363
|
+
* @param {Array} extracted - Extracted assumptions
|
|
364
|
+
* @returns {Array} Merged assumptions
|
|
365
|
+
*/
|
|
366
|
+
function mergeAssumptions(declared, extracted) {
|
|
367
|
+
const merged = [...declared];
|
|
368
|
+
const declaredKeys = new Set(
|
|
369
|
+
declared.map(a => `${a.type}:${a.key || a.path}`)
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
for (const assumption of extracted) {
|
|
373
|
+
const key = `${assumption.type}:${assumption.key || assumption.path}`;
|
|
374
|
+
if (!declaredKeys.has(key)) {
|
|
375
|
+
merged.push({
|
|
376
|
+
...assumption,
|
|
377
|
+
autoExtracted: true,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return merged;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = {
|
|
386
|
+
extractAssumptions,
|
|
387
|
+
extractEnvAssumptions,
|
|
388
|
+
extractRouteAssumptions,
|
|
389
|
+
extractDependencyAssumptions,
|
|
390
|
+
extractServiceAssumptions,
|
|
391
|
+
extractFileAssumptions,
|
|
392
|
+
extractFromOperations,
|
|
393
|
+
mergeAssumptions,
|
|
394
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proposal Module
|
|
3
|
+
*
|
|
4
|
+
* Entry point for structured change proposal handling.
|
|
5
|
+
* Provides schema, validation, and assumption extraction.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const { proposal } = require('./proposal');
|
|
9
|
+
*
|
|
10
|
+
* // Validate a proposal
|
|
11
|
+
* const result = proposal.validate(rawProposal);
|
|
12
|
+
* if (!result.valid) {
|
|
13
|
+
* console.log('Invalid proposal:', result.errors);
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* // Use normalized proposal
|
|
17
|
+
* const normalized = result.normalized;
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
PROPOSAL_SCHEMA,
|
|
24
|
+
MINIMAL_PROPOSAL_SCHEMA,
|
|
25
|
+
DEFAULT_PROPOSAL_VALUES,
|
|
26
|
+
createProposalTemplate,
|
|
27
|
+
normalizeIntent,
|
|
28
|
+
} = require("./schema");
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
validate,
|
|
32
|
+
validateStructure,
|
|
33
|
+
validateSemantics,
|
|
34
|
+
validateCompleteness,
|
|
35
|
+
normalizeProposal,
|
|
36
|
+
isValid,
|
|
37
|
+
} = require("./validator");
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
extractAssumptions,
|
|
41
|
+
extractEnvAssumptions,
|
|
42
|
+
extractRouteAssumptions,
|
|
43
|
+
extractDependencyAssumptions,
|
|
44
|
+
extractServiceAssumptions,
|
|
45
|
+
extractFileAssumptions,
|
|
46
|
+
extractFromOperations,
|
|
47
|
+
mergeAssumptions,
|
|
48
|
+
} = require("./extractor");
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Proposal handling singleton
|
|
52
|
+
*/
|
|
53
|
+
const proposal = {
|
|
54
|
+
/**
|
|
55
|
+
* Validate a proposal
|
|
56
|
+
* @param {Object} rawProposal - Raw proposal object
|
|
57
|
+
* @param {Object} options - Validation options
|
|
58
|
+
* @returns {Object} Validation result
|
|
59
|
+
*/
|
|
60
|
+
validate(rawProposal, options = {}) {
|
|
61
|
+
return validate(rawProposal, options);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Quick validity check
|
|
66
|
+
* @param {Object} rawProposal - Proposal to check
|
|
67
|
+
* @returns {boolean} Is valid
|
|
68
|
+
*/
|
|
69
|
+
isValid(rawProposal) {
|
|
70
|
+
return isValid(rawProposal);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Normalize a proposal
|
|
75
|
+
* @param {Object} rawProposal - Raw proposal
|
|
76
|
+
* @returns {Object} Normalized proposal
|
|
77
|
+
*/
|
|
78
|
+
normalize(rawProposal) {
|
|
79
|
+
return normalizeProposal(rawProposal);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a proposal template
|
|
84
|
+
* @param {string} intent - Intent identifier
|
|
85
|
+
* @param {Array} operations - Operations
|
|
86
|
+
* @returns {Object} Proposal template
|
|
87
|
+
*/
|
|
88
|
+
create(intent, operations) {
|
|
89
|
+
return createProposalTemplate(intent, operations);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract assumptions from content
|
|
94
|
+
* @param {string} content - File content
|
|
95
|
+
* @param {Object} options - Extraction options
|
|
96
|
+
* @returns {Array} Extracted assumptions
|
|
97
|
+
*/
|
|
98
|
+
extractAssumptions(content, options = {}) {
|
|
99
|
+
return extractAssumptions(content, options);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extract assumptions from operations
|
|
104
|
+
* @param {Array} operations - Proposal operations
|
|
105
|
+
* @returns {Array} Extracted assumptions
|
|
106
|
+
*/
|
|
107
|
+
extractFromOperations(operations) {
|
|
108
|
+
return extractFromOperations(operations);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Merge declared and extracted assumptions
|
|
113
|
+
* @param {Array} declared - Declared assumptions
|
|
114
|
+
* @param {Array} extracted - Extracted assumptions
|
|
115
|
+
* @returns {Array} Merged assumptions
|
|
116
|
+
*/
|
|
117
|
+
mergeAssumptions(declared, extracted) {
|
|
118
|
+
return mergeAssumptions(declared, extracted);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Enrich proposal with auto-extracted assumptions
|
|
123
|
+
* @param {Object} rawProposal - Raw proposal
|
|
124
|
+
* @returns {Object} Enriched proposal
|
|
125
|
+
*/
|
|
126
|
+
enrich(rawProposal) {
|
|
127
|
+
const normalized = normalizeProposal(rawProposal);
|
|
128
|
+
const extracted = extractFromOperations(normalized.operations);
|
|
129
|
+
const merged = mergeAssumptions(normalized.assumptions || [], extracted);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
...normalized,
|
|
133
|
+
assumptions: merged,
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the proposal schema
|
|
139
|
+
* @returns {Object} JSON Schema
|
|
140
|
+
*/
|
|
141
|
+
getSchema() {
|
|
142
|
+
return PROPOSAL_SCHEMA;
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get default values
|
|
147
|
+
* @returns {Object} Default values
|
|
148
|
+
*/
|
|
149
|
+
getDefaults() {
|
|
150
|
+
return DEFAULT_PROPOSAL_VALUES;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Normalize an intent string
|
|
155
|
+
* @param {string} intent - Raw intent
|
|
156
|
+
* @returns {string} Normalized intent
|
|
157
|
+
*/
|
|
158
|
+
normalizeIntent(intent) {
|
|
159
|
+
return normalizeIntent(intent);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Convert legacy format to proposal format
|
|
164
|
+
* @param {Object} legacy - Legacy change object
|
|
165
|
+
* @returns {Object} Proposal format
|
|
166
|
+
*/
|
|
167
|
+
fromLegacy(legacy) {
|
|
168
|
+
// Handle old formats that just have filePath + content
|
|
169
|
+
if (legacy.filePath && !legacy.operations) {
|
|
170
|
+
return {
|
|
171
|
+
intent: legacy.intent || "unknown_change",
|
|
172
|
+
summary: legacy.summary || legacy.intent || "",
|
|
173
|
+
operations: [{
|
|
174
|
+
type: legacy.type || (legacy.oldContent ? "modify" : "create"),
|
|
175
|
+
path: legacy.filePath,
|
|
176
|
+
content: legacy.content || legacy.newContent,
|
|
177
|
+
}],
|
|
178
|
+
assumptions: legacy.assumptions || [],
|
|
179
|
+
confidence: legacy.confidence || 0.5,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Already in proposal format
|
|
184
|
+
return legacy;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
proposal,
|
|
190
|
+
// Schema exports
|
|
191
|
+
PROPOSAL_SCHEMA,
|
|
192
|
+
MINIMAL_PROPOSAL_SCHEMA,
|
|
193
|
+
DEFAULT_PROPOSAL_VALUES,
|
|
194
|
+
createProposalTemplate,
|
|
195
|
+
normalizeIntent,
|
|
196
|
+
// Validator exports
|
|
197
|
+
validate,
|
|
198
|
+
validateStructure,
|
|
199
|
+
validateSemantics,
|
|
200
|
+
validateCompleteness,
|
|
201
|
+
normalizeProposal,
|
|
202
|
+
isValid,
|
|
203
|
+
// Extractor exports
|
|
204
|
+
extractAssumptions,
|
|
205
|
+
extractEnvAssumptions,
|
|
206
|
+
extractRouteAssumptions,
|
|
207
|
+
extractDependencyAssumptions,
|
|
208
|
+
extractServiceAssumptions,
|
|
209
|
+
extractFileAssumptions,
|
|
210
|
+
extractFromOperations,
|
|
211
|
+
mergeAssumptions,
|
|
212
|
+
};
|