@vibecheckai/cli 3.2.5 → 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.
Files changed (197) hide show
  1. package/bin/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/registry.js +192 -5
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  6. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  7. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  8. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  11. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  12. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  13. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  14. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  15. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  16. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  17. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  18. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  19. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  20. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  21. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  22. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  23. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  24. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  25. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  26. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  27. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  28. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  29. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  30. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  31. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  32. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  35. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  36. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  37. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  38. package/bin/runners/lib/analyzers.js +81 -18
  39. package/bin/runners/lib/api-client.js +269 -0
  40. package/bin/runners/lib/auth-truth.js +193 -193
  41. package/bin/runners/lib/authority-badge.js +425 -0
  42. package/bin/runners/lib/backup.js +62 -62
  43. package/bin/runners/lib/billing.js +107 -107
  44. package/bin/runners/lib/claims.js +118 -118
  45. package/bin/runners/lib/cli-output.js +7 -1
  46. package/bin/runners/lib/cli-ui.js +540 -540
  47. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  48. package/bin/runners/lib/contracts/env-contract.js +181 -181
  49. package/bin/runners/lib/contracts/external-contract.js +206 -206
  50. package/bin/runners/lib/contracts/guard.js +168 -168
  51. package/bin/runners/lib/contracts/index.js +89 -89
  52. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  53. package/bin/runners/lib/contracts/route-contract.js +199 -199
  54. package/bin/runners/lib/contracts.js +804 -804
  55. package/bin/runners/lib/detect.js +89 -89
  56. package/bin/runners/lib/doctor/autofix.js +254 -254
  57. package/bin/runners/lib/doctor/index.js +37 -37
  58. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  59. package/bin/runners/lib/doctor/modules/index.js +46 -46
  60. package/bin/runners/lib/doctor/modules/network.js +250 -250
  61. package/bin/runners/lib/doctor/modules/project.js +312 -312
  62. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  63. package/bin/runners/lib/doctor/modules/security.js +348 -348
  64. package/bin/runners/lib/doctor/modules/system.js +213 -213
  65. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  66. package/bin/runners/lib/doctor/reporter.js +262 -262
  67. package/bin/runners/lib/doctor/service.js +262 -262
  68. package/bin/runners/lib/doctor/types.js +113 -113
  69. package/bin/runners/lib/doctor/ui.js +263 -263
  70. package/bin/runners/lib/doctor-v2.js +608 -608
  71. package/bin/runners/lib/drift.js +425 -425
  72. package/bin/runners/lib/enforcement.js +72 -72
  73. package/bin/runners/lib/enterprise-detect.js +603 -603
  74. package/bin/runners/lib/enterprise-init.js +942 -942
  75. package/bin/runners/lib/env-resolver.js +417 -417
  76. package/bin/runners/lib/env-template.js +66 -66
  77. package/bin/runners/lib/env.js +189 -189
  78. package/bin/runners/lib/error-handler.js +16 -9
  79. package/bin/runners/lib/exit-codes.js +275 -0
  80. package/bin/runners/lib/extractors/client-calls.js +990 -990
  81. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  82. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  83. package/bin/runners/lib/extractors/index.js +363 -363
  84. package/bin/runners/lib/extractors/next-routes.js +524 -524
  85. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  86. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  87. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  88. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  89. package/bin/runners/lib/findings-schema.js +281 -281
  90. package/bin/runners/lib/firewall-prompt.js +50 -50
  91. package/bin/runners/lib/global-flags.js +37 -0
  92. package/bin/runners/lib/graph/graph-builder.js +265 -265
  93. package/bin/runners/lib/graph/html-renderer.js +413 -413
  94. package/bin/runners/lib/graph/index.js +32 -32
  95. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  96. package/bin/runners/lib/graph/static-extractor.js +518 -518
  97. package/bin/runners/lib/help-formatter.js +413 -0
  98. package/bin/runners/lib/html-report.js +650 -650
  99. package/bin/runners/lib/llm.js +75 -75
  100. package/bin/runners/lib/logger.js +38 -0
  101. package/bin/runners/lib/meter.js +61 -61
  102. package/bin/runners/lib/missions/evidence.js +126 -126
  103. package/bin/runners/lib/patch.js +40 -40
  104. package/bin/runners/lib/permissions/auth-model.js +213 -213
  105. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  106. package/bin/runners/lib/permissions/index.js +45 -45
  107. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  108. package/bin/runners/lib/pkgjson.js +28 -28
  109. package/bin/runners/lib/policy.js +295 -295
  110. package/bin/runners/lib/preflight.js +142 -142
  111. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  112. package/bin/runners/lib/reality/index.js +318 -318
  113. package/bin/runners/lib/reality/request-hashing.js +416 -416
  114. package/bin/runners/lib/reality/request-mapper.js +453 -453
  115. package/bin/runners/lib/reality/safety-rails.js +463 -463
  116. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  117. package/bin/runners/lib/reality/toast-detector.js +393 -393
  118. package/bin/runners/lib/reality-findings.js +84 -84
  119. package/bin/runners/lib/receipts.js +179 -179
  120. package/bin/runners/lib/redact.js +29 -29
  121. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  122. package/bin/runners/lib/replay/index.js +263 -263
  123. package/bin/runners/lib/replay/player.js +348 -348
  124. package/bin/runners/lib/replay/recorder.js +331 -331
  125. package/bin/runners/lib/report.js +135 -135
  126. package/bin/runners/lib/route-detection.js +1140 -1140
  127. package/bin/runners/lib/sandbox/index.js +59 -59
  128. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  129. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  130. package/bin/runners/lib/sandbox/worktree.js +174 -174
  131. package/bin/runners/lib/schema-validator.js +350 -350
  132. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  133. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  134. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  135. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  136. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  137. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  138. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  139. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  140. package/bin/runners/lib/schemas/validator.js +438 -438
  141. package/bin/runners/lib/score-history.js +282 -282
  142. package/bin/runners/lib/share-pack.js +239 -239
  143. package/bin/runners/lib/snippets.js +67 -67
  144. package/bin/runners/lib/unified-cli-output.js +604 -0
  145. package/bin/runners/lib/upsell.js +658 -510
  146. package/bin/runners/lib/usage.js +153 -153
  147. package/bin/runners/lib/validate-patch.js +156 -156
  148. package/bin/runners/lib/verdict-engine.js +628 -628
  149. package/bin/runners/reality/engine.js +917 -917
  150. package/bin/runners/reality/flows.js +122 -122
  151. package/bin/runners/reality/report.js +378 -378
  152. package/bin/runners/reality/session.js +193 -193
  153. package/bin/runners/runAgent.d.ts +5 -0
  154. package/bin/runners/runApprove.js +1200 -0
  155. package/bin/runners/runAuth.js +324 -95
  156. package/bin/runners/runCheckpoint.js +39 -21
  157. package/bin/runners/runClassify.js +859 -0
  158. package/bin/runners/runContext.js +136 -24
  159. package/bin/runners/runDoctor.js +108 -68
  160. package/bin/runners/runFirewall.d.ts +5 -0
  161. package/bin/runners/runFirewallHook.d.ts +5 -0
  162. package/bin/runners/runFix.js +6 -5
  163. package/bin/runners/runGuard.js +262 -168
  164. package/bin/runners/runInit.js +3 -2
  165. package/bin/runners/runMcp.js +130 -52
  166. package/bin/runners/runPolish.js +43 -20
  167. package/bin/runners/runProve.js +1 -2
  168. package/bin/runners/runReport.js +3 -2
  169. package/bin/runners/runScan.js +145 -44
  170. package/bin/runners/runShip.js +3 -4
  171. package/bin/runners/runTruth.d.ts +5 -0
  172. package/bin/runners/runValidate.js +19 -2
  173. package/bin/runners/runWatch.js +104 -53
  174. package/bin/vibecheck.js +106 -19
  175. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  176. package/mcp-server/agent-firewall-interceptor.js +367 -31
  177. package/mcp-server/authority-tools.js +569 -0
  178. package/mcp-server/conductor/conflict-resolver.js +588 -0
  179. package/mcp-server/conductor/execution-planner.js +544 -0
  180. package/mcp-server/conductor/index.js +377 -0
  181. package/mcp-server/conductor/lock-manager.js +615 -0
  182. package/mcp-server/conductor/request-queue.js +550 -0
  183. package/mcp-server/conductor/session-manager.js +500 -0
  184. package/mcp-server/conductor/tools.js +510 -0
  185. package/mcp-server/index.js +1199 -208
  186. package/mcp-server/lib/api-client.cjs +305 -0
  187. package/mcp-server/lib/logger.cjs +30 -0
  188. package/mcp-server/logger.js +173 -0
  189. package/mcp-server/package.json +2 -2
  190. package/mcp-server/premium-tools.js +2 -2
  191. package/mcp-server/tier-auth.js +351 -136
  192. package/mcp-server/tools/index.js +72 -72
  193. package/mcp-server/truth-firewall-tools.js +145 -15
  194. package/mcp-server/vibecheck-tools.js +2 -2
  195. package/package.json +2 -3
  196. package/mcp-server/index.old.js +0 -4137
  197. package/mcp-server/package-lock.json +0 -165
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Proposal Schema
3
+ *
4
+ * JSON Schema definition for structured change proposals.
5
+ * LLMs must output this format - no raw code blobs allowed.
6
+ */
7
+
8
+ "use strict";
9
+
10
+ /**
11
+ * JSON Schema for Change Proposal
12
+ */
13
+ const PROPOSAL_SCHEMA = {
14
+ $schema: "http://json-schema.org/draft-07/schema#",
15
+ type: "object",
16
+ required: ["intent", "operations"],
17
+ properties: {
18
+ // Required fields
19
+ intent: {
20
+ type: "string",
21
+ description: "Short identifier for the change intent (e.g., 'add_auth_middleware')",
22
+ minLength: 3,
23
+ maxLength: 100,
24
+ pattern: "^[a-z][a-z0-9_]*$",
25
+ },
26
+ operations: {
27
+ type: "array",
28
+ description: "List of file operations",
29
+ minItems: 1,
30
+ items: {
31
+ type: "object",
32
+ required: ["type", "path"],
33
+ properties: {
34
+ type: {
35
+ type: "string",
36
+ enum: ["create", "modify", "delete", "rename"],
37
+ description: "Type of operation",
38
+ },
39
+ path: {
40
+ type: "string",
41
+ description: "File path relative to project root",
42
+ },
43
+ content: {
44
+ type: "string",
45
+ description: "New file content (for create/modify)",
46
+ },
47
+ newPath: {
48
+ type: "string",
49
+ description: "New path (for rename)",
50
+ },
51
+ },
52
+ },
53
+ },
54
+
55
+ // Recommended fields
56
+ summary: {
57
+ type: "string",
58
+ description: "Human-readable summary of the change",
59
+ minLength: 10,
60
+ maxLength: 500,
61
+ },
62
+ filesTouched: {
63
+ type: "array",
64
+ description: "List of all files that will be touched",
65
+ items: { type: "string" },
66
+ },
67
+ assumptions: {
68
+ type: "array",
69
+ description: "Assumptions the change relies on",
70
+ items: {
71
+ type: "object",
72
+ required: ["type"],
73
+ properties: {
74
+ type: {
75
+ type: "string",
76
+ enum: ["env", "route", "service", "file", "dependency", "permission", "config"],
77
+ description: "Type of assumption",
78
+ },
79
+ key: {
80
+ type: "string",
81
+ description: "Key/name of the assumed resource",
82
+ },
83
+ value: {
84
+ type: "string",
85
+ description: "Expected value (if applicable)",
86
+ },
87
+ reason: {
88
+ type: "string",
89
+ description: "Why this assumption is needed",
90
+ },
91
+ path: {
92
+ type: "string",
93
+ description: "File path (for file assumptions)",
94
+ },
95
+ method: {
96
+ type: "string",
97
+ description: "HTTP method (for route assumptions)",
98
+ },
99
+ },
100
+ },
101
+ },
102
+ confidence: {
103
+ type: "number",
104
+ description: "Confidence level (0-1) that the change is correct",
105
+ minimum: 0,
106
+ maximum: 1,
107
+ },
108
+
109
+ // Optional metadata
110
+ metadata: {
111
+ type: "object",
112
+ properties: {
113
+ agentId: {
114
+ type: "string",
115
+ description: "ID of the agent proposing the change",
116
+ },
117
+ sessionId: {
118
+ type: "string",
119
+ description: "Session ID for tracking",
120
+ },
121
+ timestamp: {
122
+ type: "string",
123
+ format: "date-time",
124
+ description: "Proposal timestamp",
125
+ },
126
+ parentProposalId: {
127
+ type: "string",
128
+ description: "ID of parent proposal (for iterative changes)",
129
+ },
130
+ tags: {
131
+ type: "array",
132
+ items: { type: "string" },
133
+ description: "Tags for categorization",
134
+ },
135
+ },
136
+ },
137
+
138
+ // Risk acknowledgment
139
+ riskAcknowledgment: {
140
+ type: "object",
141
+ properties: {
142
+ touchesAuth: { type: "boolean" },
143
+ touchesPayments: { type: "boolean" },
144
+ touchesDatabase: { type: "boolean" },
145
+ touchesCore: { type: "boolean" },
146
+ hasSideEffects: { type: "boolean" },
147
+ isIrreversible: { type: "boolean" },
148
+ },
149
+ },
150
+
151
+ // Verification requirements
152
+ verification: {
153
+ type: "object",
154
+ properties: {
155
+ requiresTests: { type: "boolean" },
156
+ requiresReview: { type: "boolean" },
157
+ requiresRealityCheck: { type: "boolean" },
158
+ testCases: {
159
+ type: "array",
160
+ items: { type: "string" },
161
+ },
162
+ },
163
+ },
164
+ },
165
+ };
166
+
167
+ /**
168
+ * Minimal proposal schema (for quick validation)
169
+ */
170
+ const MINIMAL_PROPOSAL_SCHEMA = {
171
+ type: "object",
172
+ required: ["intent", "operations"],
173
+ properties: {
174
+ intent: { type: "string", minLength: 3 },
175
+ operations: {
176
+ type: "array",
177
+ minItems: 1,
178
+ items: {
179
+ type: "object",
180
+ required: ["type", "path"],
181
+ },
182
+ },
183
+ },
184
+ };
185
+
186
+ /**
187
+ * Default values for optional fields
188
+ */
189
+ const DEFAULT_PROPOSAL_VALUES = {
190
+ confidence: 0.5,
191
+ assumptions: [],
192
+ filesTouched: [],
193
+ metadata: {},
194
+ riskAcknowledgment: {
195
+ touchesAuth: false,
196
+ touchesPayments: false,
197
+ touchesDatabase: false,
198
+ touchesCore: false,
199
+ hasSideEffects: false,
200
+ isIrreversible: false,
201
+ },
202
+ verification: {
203
+ requiresTests: false,
204
+ requiresReview: false,
205
+ requiresRealityCheck: false,
206
+ testCases: [],
207
+ },
208
+ };
209
+
210
+ /**
211
+ * Create a proposal template
212
+ * @param {string} intent - Intent identifier
213
+ * @param {Array} operations - Operations
214
+ * @returns {Object} Proposal template
215
+ */
216
+ function createProposalTemplate(intent, operations) {
217
+ return {
218
+ intent,
219
+ summary: "",
220
+ filesTouched: operations.map(op => op.path),
221
+ operations,
222
+ assumptions: [],
223
+ confidence: 0.5,
224
+ metadata: {
225
+ timestamp: new Date().toISOString(),
226
+ },
227
+ riskAcknowledgment: { ...DEFAULT_PROPOSAL_VALUES.riskAcknowledgment },
228
+ verification: { ...DEFAULT_PROPOSAL_VALUES.verification },
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Normalize intent string to snake_case
234
+ * @param {string} intent - Raw intent
235
+ * @returns {string} Normalized intent
236
+ */
237
+ function normalizeIntent(intent) {
238
+ return intent
239
+ .toLowerCase()
240
+ .replace(/[^a-z0-9]+/g, "_")
241
+ .replace(/^_+|_+$/g, "")
242
+ .slice(0, 100);
243
+ }
244
+
245
+ module.exports = {
246
+ PROPOSAL_SCHEMA,
247
+ MINIMAL_PROPOSAL_SCHEMA,
248
+ DEFAULT_PROPOSAL_VALUES,
249
+ createProposalTemplate,
250
+ normalizeIntent,
251
+ };
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Proposal Validator
3
+ *
4
+ * Validates change proposals against the schema and semantic rules.
5
+ * Rejects incomplete, vague, or unsafe proposals.
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const { PROPOSAL_SCHEMA, DEFAULT_PROPOSAL_VALUES, normalizeIntent } = require("./schema");
11
+ const { classifyFileDomain } = require("../reality/state");
12
+
13
+ /**
14
+ * @typedef {Object} ValidationResult
15
+ * @property {boolean} valid - Whether proposal is valid
16
+ * @property {Array} errors - Validation errors
17
+ * @property {Array} warnings - Validation warnings
18
+ * @property {Object} normalized - Normalized proposal
19
+ */
20
+
21
+ /**
22
+ * Validate proposal structure
23
+ * @param {Object} proposal - Raw proposal
24
+ * @returns {ValidationResult} Validation result
25
+ */
26
+ function validateStructure(proposal) {
27
+ const errors = [];
28
+ const warnings = [];
29
+
30
+ // Check required fields
31
+ if (!proposal) {
32
+ errors.push({ field: "proposal", message: "Proposal is required" });
33
+ return { valid: false, errors, warnings };
34
+ }
35
+
36
+ if (!proposal.intent) {
37
+ errors.push({ field: "intent", message: "Intent is required" });
38
+ } else if (typeof proposal.intent !== "string") {
39
+ errors.push({ field: "intent", message: "Intent must be a string" });
40
+ } else if (proposal.intent.length < 3) {
41
+ errors.push({ field: "intent", message: "Intent must be at least 3 characters" });
42
+ }
43
+
44
+ if (!proposal.operations) {
45
+ errors.push({ field: "operations", message: "Operations array is required" });
46
+ } else if (!Array.isArray(proposal.operations)) {
47
+ errors.push({ field: "operations", message: "Operations must be an array" });
48
+ } else if (proposal.operations.length === 0) {
49
+ errors.push({ field: "operations", message: "At least one operation is required" });
50
+ } else {
51
+ // Validate each operation
52
+ for (let i = 0; i < proposal.operations.length; i++) {
53
+ const op = proposal.operations[i];
54
+
55
+ if (!op.type) {
56
+ errors.push({ field: `operations[${i}].type`, message: "Operation type is required" });
57
+ } else if (!["create", "modify", "delete", "rename"].includes(op.type)) {
58
+ errors.push({
59
+ field: `operations[${i}].type`,
60
+ message: `Invalid operation type: ${op.type}. Must be create, modify, delete, or rename`
61
+ });
62
+ }
63
+
64
+ if (!op.path) {
65
+ errors.push({ field: `operations[${i}].path`, message: "Operation path is required" });
66
+ }
67
+
68
+ // Content required for create/modify
69
+ if ((op.type === "create" || op.type === "modify") && !op.content && op.content !== "") {
70
+ warnings.push({
71
+ field: `operations[${i}].content`,
72
+ message: "Content is recommended for create/modify operations"
73
+ });
74
+ }
75
+
76
+ // newPath required for rename
77
+ if (op.type === "rename" && !op.newPath) {
78
+ errors.push({ field: `operations[${i}].newPath`, message: "newPath is required for rename operations" });
79
+ }
80
+ }
81
+ }
82
+
83
+ // Validate optional fields
84
+ if (proposal.confidence !== undefined) {
85
+ if (typeof proposal.confidence !== "number") {
86
+ errors.push({ field: "confidence", message: "Confidence must be a number" });
87
+ } else if (proposal.confidence < 0 || proposal.confidence > 1) {
88
+ errors.push({ field: "confidence", message: "Confidence must be between 0 and 1" });
89
+ }
90
+ }
91
+
92
+ if (proposal.assumptions && !Array.isArray(proposal.assumptions)) {
93
+ errors.push({ field: "assumptions", message: "Assumptions must be an array" });
94
+ }
95
+
96
+ if (proposal.filesTouched && !Array.isArray(proposal.filesTouched)) {
97
+ errors.push({ field: "filesTouched", message: "filesTouched must be an array" });
98
+ }
99
+
100
+ return {
101
+ valid: errors.length === 0,
102
+ errors,
103
+ warnings,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Validate proposal semantics
109
+ * @param {Object} proposal - Proposal to validate
110
+ * @returns {ValidationResult} Validation result
111
+ */
112
+ function validateSemantics(proposal) {
113
+ const errors = [];
114
+ const warnings = [];
115
+
116
+ // Check for vague intent
117
+ const vagueIntents = ["fix", "update", "change", "modify", "improve", "refactor"];
118
+ const intentWords = proposal.intent.toLowerCase().split("_");
119
+ if (intentWords.length === 1 && vagueIntents.includes(intentWords[0])) {
120
+ warnings.push({
121
+ field: "intent",
122
+ message: `Intent '${proposal.intent}' is too vague. Be more specific about what is being changed.`,
123
+ });
124
+ }
125
+
126
+ // Check for missing summary on complex changes
127
+ if (proposal.operations.length > 2 && !proposal.summary) {
128
+ warnings.push({
129
+ field: "summary",
130
+ message: "Summary is recommended for changes touching multiple files",
131
+ });
132
+ }
133
+
134
+ // Check for missing assumptions when touching sensitive domains
135
+ const sensitiveDomains = ["auth", "payments", "database", "security"];
136
+ const touchedDomains = new Set();
137
+
138
+ for (const op of proposal.operations) {
139
+ const domain = classifyFileDomain(op.path);
140
+ touchedDomains.add(domain);
141
+ }
142
+
143
+ const sensitiveDomainsAffected = [...touchedDomains].filter(d => sensitiveDomains.includes(d));
144
+
145
+ if (sensitiveDomainsAffected.length > 0 && (!proposal.assumptions || proposal.assumptions.length === 0)) {
146
+ warnings.push({
147
+ field: "assumptions",
148
+ message: `Change affects sensitive domains (${sensitiveDomainsAffected.join(", ")}). Assumptions should be declared.`,
149
+ });
150
+ }
151
+
152
+ // Check for low confidence without explanation
153
+ if (proposal.confidence !== undefined && proposal.confidence < 0.5 && !proposal.summary) {
154
+ warnings.push({
155
+ field: "confidence",
156
+ message: "Low confidence proposals should include a summary explaining uncertainties",
157
+ });
158
+ }
159
+
160
+ // Check filesTouched matches operations
161
+ if (proposal.filesTouched && proposal.operations) {
162
+ const operationPaths = new Set(proposal.operations.map(op => op.path));
163
+ const declaredPaths = new Set(proposal.filesTouched);
164
+
165
+ for (const path of operationPaths) {
166
+ if (!declaredPaths.has(path)) {
167
+ warnings.push({
168
+ field: "filesTouched",
169
+ message: `Operation path '${path}' not listed in filesTouched`,
170
+ });
171
+ }
172
+ }
173
+ }
174
+
175
+ // Check assumptions have required fields
176
+ if (proposal.assumptions) {
177
+ for (let i = 0; i < proposal.assumptions.length; i++) {
178
+ const assumption = proposal.assumptions[i];
179
+
180
+ if (!assumption.type) {
181
+ errors.push({
182
+ field: `assumptions[${i}].type`,
183
+ message: "Assumption type is required",
184
+ });
185
+ }
186
+
187
+ if (!assumption.key && !assumption.path && !assumption.value) {
188
+ errors.push({
189
+ field: `assumptions[${i}]`,
190
+ message: "Assumption must have key, path, or value",
191
+ });
192
+ }
193
+
194
+ if (!assumption.reason) {
195
+ warnings.push({
196
+ field: `assumptions[${i}].reason`,
197
+ message: "Assumption should explain why it's needed",
198
+ });
199
+ }
200
+ }
201
+ }
202
+
203
+ return {
204
+ valid: errors.length === 0,
205
+ errors,
206
+ warnings,
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Validate proposal completeness
212
+ * @param {Object} proposal - Proposal to validate
213
+ * @param {Object} options - Validation options
214
+ * @returns {ValidationResult} Validation result
215
+ */
216
+ function validateCompleteness(proposal, options = {}) {
217
+ const { strict = false } = options;
218
+ const errors = [];
219
+ const warnings = [];
220
+
221
+ // In strict mode, require more fields
222
+ if (strict) {
223
+ if (!proposal.summary) {
224
+ errors.push({ field: "summary", message: "Summary is required in strict mode" });
225
+ }
226
+
227
+ if (!proposal.assumptions || proposal.assumptions.length === 0) {
228
+ errors.push({ field: "assumptions", message: "At least one assumption is required in strict mode" });
229
+ }
230
+
231
+ if (proposal.confidence === undefined) {
232
+ errors.push({ field: "confidence", message: "Confidence is required in strict mode" });
233
+ }
234
+
235
+ if (!proposal.riskAcknowledgment) {
236
+ errors.push({ field: "riskAcknowledgment", message: "Risk acknowledgment is required in strict mode" });
237
+ }
238
+ }
239
+
240
+ // Check for empty content in operations
241
+ for (let i = 0; i < proposal.operations.length; i++) {
242
+ const op = proposal.operations[i];
243
+ if (op.type === "create" && (!op.content || op.content.trim() === "")) {
244
+ warnings.push({
245
+ field: `operations[${i}].content`,
246
+ message: "Creating empty file - is this intentional?",
247
+ });
248
+ }
249
+ }
250
+
251
+ // Check for reasonable number of operations
252
+ if (proposal.operations.length > 20) {
253
+ warnings.push({
254
+ field: "operations",
255
+ message: `Large number of operations (${proposal.operations.length}). Consider breaking into smaller proposals.`,
256
+ });
257
+ }
258
+
259
+ return {
260
+ valid: errors.length === 0,
261
+ errors,
262
+ warnings,
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Normalize proposal (fill defaults, clean data)
268
+ * @param {Object} proposal - Raw proposal
269
+ * @returns {Object} Normalized proposal
270
+ */
271
+ function normalizeProposal(proposal) {
272
+ const normalized = {
273
+ ...DEFAULT_PROPOSAL_VALUES,
274
+ ...proposal,
275
+ intent: normalizeIntent(proposal.intent || "unknown"),
276
+ filesTouched: proposal.filesTouched || proposal.operations?.map(op => op.path) || [],
277
+ metadata: {
278
+ ...DEFAULT_PROPOSAL_VALUES.metadata,
279
+ ...proposal.metadata,
280
+ timestamp: proposal.metadata?.timestamp || new Date().toISOString(),
281
+ },
282
+ };
283
+
284
+ // Normalize operations
285
+ normalized.operations = (proposal.operations || []).map(op => ({
286
+ ...op,
287
+ path: op.path?.replace(/\\/g, "/"),
288
+ newPath: op.newPath?.replace(/\\/g, "/"),
289
+ }));
290
+
291
+ // Auto-detect risk acknowledgment from file paths
292
+ for (const op of normalized.operations) {
293
+ const domain = classifyFileDomain(op.path);
294
+ if (domain === "auth") normalized.riskAcknowledgment.touchesAuth = true;
295
+ if (domain === "payments") normalized.riskAcknowledgment.touchesPayments = true;
296
+ if (domain === "database") normalized.riskAcknowledgment.touchesDatabase = true;
297
+ if (domain === "core") normalized.riskAcknowledgment.touchesCore = true;
298
+ }
299
+
300
+ // Check for side effects
301
+ const hasSideEffects = normalized.operations.some(op => {
302
+ if (!op.content) return false;
303
+ return (
304
+ op.content.includes("fetch(") ||
305
+ op.content.includes("axios") ||
306
+ op.content.includes("fs.write") ||
307
+ op.content.includes("prisma.") ||
308
+ op.content.includes("exec(")
309
+ );
310
+ });
311
+ normalized.riskAcknowledgment.hasSideEffects = hasSideEffects;
312
+
313
+ // Check for irreversibility
314
+ const isIrreversible = normalized.operations.some(op =>
315
+ op.type === "delete" || op.path.includes("migration")
316
+ );
317
+ normalized.riskAcknowledgment.isIrreversible = isIrreversible;
318
+
319
+ return normalized;
320
+ }
321
+
322
+ /**
323
+ * Full proposal validation
324
+ * @param {Object} proposal - Proposal to validate
325
+ * @param {Object} options - Validation options
326
+ * @returns {ValidationResult} Full validation result
327
+ */
328
+ function validate(proposal, options = {}) {
329
+ // Structural validation
330
+ const structureResult = validateStructure(proposal);
331
+ if (!structureResult.valid) {
332
+ return {
333
+ valid: false,
334
+ errors: structureResult.errors,
335
+ warnings: structureResult.warnings,
336
+ normalized: null,
337
+ };
338
+ }
339
+
340
+ // Normalize
341
+ const normalized = normalizeProposal(proposal);
342
+
343
+ // Semantic validation
344
+ const semanticResult = validateSemantics(normalized);
345
+
346
+ // Completeness validation
347
+ const completenessResult = validateCompleteness(normalized, options);
348
+
349
+ // Combine results
350
+ const allErrors = [
351
+ ...structureResult.errors,
352
+ ...semanticResult.errors,
353
+ ...completenessResult.errors,
354
+ ];
355
+
356
+ const allWarnings = [
357
+ ...structureResult.warnings,
358
+ ...semanticResult.warnings,
359
+ ...completenessResult.warnings,
360
+ ];
361
+
362
+ return {
363
+ valid: allErrors.length === 0,
364
+ errors: allErrors,
365
+ warnings: allWarnings,
366
+ normalized,
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Quick validation (structure only)
372
+ * @param {Object} proposal - Proposal to validate
373
+ * @returns {boolean} Is valid
374
+ */
375
+ function isValid(proposal) {
376
+ return validateStructure(proposal).valid;
377
+ }
378
+
379
+ module.exports = {
380
+ validate,
381
+ validateStructure,
382
+ validateSemantics,
383
+ validateCompleteness,
384
+ normalizeProposal,
385
+ isValid,
386
+ };