@codmir/contracts 0.1.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/index.js ADDED
@@ -0,0 +1,1924 @@
1
+ // src/schema.ts
2
+ var DURATION_SYNC_THRESHOLDS = {
3
+ instant: 5e3,
4
+ // 5 seconds
5
+ quick: 3e4,
6
+ // 30 seconds
7
+ medium: 12e4,
8
+ // 2 minutes
9
+ long: 3e5
10
+ // 5 minutes (but usually async)
11
+ };
12
+
13
+ // src/grace.ts
14
+ var DEFAULT_GRACE_EXTENSIONS = {
15
+ graceVersion: "grace.v1",
16
+ ethics: {
17
+ impacts: [],
18
+ overallRiskScore: 0,
19
+ requiresHumanReview: false
20
+ },
21
+ reversibility: {
22
+ fullyReversible: true,
23
+ snapshotBeforeExecution: true,
24
+ snapshotRetentionMs: 7 * 24 * 60 * 60 * 1e3,
25
+ // 7 days
26
+ rollbackStrategy: "full"
27
+ },
28
+ humanOversight: {
29
+ minCheckpoints: 0,
30
+ allowAutonomous: true
31
+ },
32
+ transparency: {
33
+ logDecisions: true,
34
+ logsAccessible: true,
35
+ auditRetentionDays: 90
36
+ }
37
+ };
38
+ function calculateRiskScore(impacts) {
39
+ const severityWeights = {
40
+ none: 0,
41
+ low: 10,
42
+ medium: 25,
43
+ high: 50,
44
+ critical: 100
45
+ };
46
+ if (impacts.length === 0) return 0;
47
+ const totalWeight = impacts.reduce(
48
+ (sum, impact) => sum + severityWeights[impact.severity],
49
+ 0
50
+ );
51
+ return Math.min(100, totalWeight);
52
+ }
53
+ function requiresHumanReview(impacts) {
54
+ return impacts.some(
55
+ (i) => i.severity === "high" || i.severity === "critical"
56
+ );
57
+ }
58
+ var GRACE_PRINCIPLES = {
59
+ transparency: "Every action logged, every decision traceable",
60
+ consent: "Explicit permission scope, no implicit access",
61
+ reversibility: "Every AI action must be undoable",
62
+ accountability: "Task attribution, versioned modifications",
63
+ economicBoundaries: "Token usage limit, execution cost cap",
64
+ humanDignity: "AI augments, never replaces human judgment on critical decisions",
65
+ fairness: "Distributed power must not centralize control",
66
+ sustainability: "Prefer renewable-powered compute when available"
67
+ };
68
+
69
+ // src/templates.ts
70
+ function getByPath(obj, path) {
71
+ const parts = path.split(".").filter(Boolean);
72
+ let cur = obj;
73
+ for (const p of parts) {
74
+ if (cur == null || typeof cur !== "object") return void 0;
75
+ cur = cur[p];
76
+ }
77
+ return cur;
78
+ }
79
+ function setByPath(obj, path, value) {
80
+ const parts = path.split(".").filter(Boolean);
81
+ if (parts.length === 0) return;
82
+ let cur = obj;
83
+ for (let i = 0; i < parts.length - 1; i++) {
84
+ const p = parts[i];
85
+ if (cur[p] == null || typeof cur[p] !== "object") {
86
+ cur[p] = {};
87
+ }
88
+ cur = cur[p];
89
+ }
90
+ cur[parts[parts.length - 1]] = value;
91
+ }
92
+ function deleteByPath(obj, path) {
93
+ const parts = path.split(".").filter(Boolean);
94
+ if (parts.length === 0) return;
95
+ let cur = obj;
96
+ for (let i = 0; i < parts.length - 1; i++) {
97
+ const p = parts[i];
98
+ if (cur[p] == null || typeof cur[p] !== "object") return;
99
+ cur = cur[p];
100
+ }
101
+ delete cur[parts[parts.length - 1]];
102
+ }
103
+ var TEMPLATE_PATTERN = /^\{\{(.+)\}\}$/;
104
+ function isTemplate(value) {
105
+ return TEMPLATE_PATTERN.test(value);
106
+ }
107
+ function extractTemplatePath(value) {
108
+ const match = value.match(TEMPLATE_PATTERN);
109
+ return match ? match[1].trim() : null;
110
+ }
111
+ function resolveTemplates(ctx, input) {
112
+ if (typeof input === "string") {
113
+ const path = extractTemplatePath(input);
114
+ if (path) {
115
+ const v = getByPath(ctx, path);
116
+ return v === void 0 ? null : v;
117
+ }
118
+ return input;
119
+ }
120
+ if (Array.isArray(input)) {
121
+ return input.map((v) => resolveTemplates(ctx, v));
122
+ }
123
+ if (input && typeof input === "object") {
124
+ const out = {};
125
+ for (const [k, v] of Object.entries(input)) {
126
+ out[k] = resolveTemplates(ctx, v);
127
+ }
128
+ return out;
129
+ }
130
+ return input;
131
+ }
132
+ function resolveStepInput(ctx, input) {
133
+ return resolveTemplates(ctx, input);
134
+ }
135
+ function cloneJson(value) {
136
+ return JSON.parse(JSON.stringify(value));
137
+ }
138
+ function mergeJson(target, source) {
139
+ return { ...target, ...source };
140
+ }
141
+ function deepMergeJson(target, source) {
142
+ const result = { ...target };
143
+ for (const [key, value] of Object.entries(source)) {
144
+ if (value && typeof value === "object" && !Array.isArray(value) && result[key] && typeof result[key] === "object" && !Array.isArray(result[key])) {
145
+ result[key] = deepMergeJson(
146
+ result[key],
147
+ value
148
+ );
149
+ } else {
150
+ result[key] = value;
151
+ }
152
+ }
153
+ return result;
154
+ }
155
+
156
+ // src/conditions.ts
157
+ function evalCondition(ctx, expr) {
158
+ switch (expr.op) {
159
+ case "eq": {
160
+ const actual = getByPath(ctx, expr.path);
161
+ return jsonEquals(actual, expr.value);
162
+ }
163
+ case "neq": {
164
+ const actual = getByPath(ctx, expr.path);
165
+ return !jsonEquals(actual, expr.value);
166
+ }
167
+ case "gt": {
168
+ const actual = getByPath(ctx, expr.path);
169
+ return typeof actual === "number" && actual > expr.value;
170
+ }
171
+ case "gte": {
172
+ const actual = getByPath(ctx, expr.path);
173
+ return typeof actual === "number" && actual >= expr.value;
174
+ }
175
+ case "lt": {
176
+ const actual = getByPath(ctx, expr.path);
177
+ return typeof actual === "number" && actual < expr.value;
178
+ }
179
+ case "lte": {
180
+ const actual = getByPath(ctx, expr.path);
181
+ return typeof actual === "number" && actual <= expr.value;
182
+ }
183
+ case "exists": {
184
+ return getByPath(ctx, expr.path) !== void 0;
185
+ }
186
+ case "contains": {
187
+ const actual = getByPath(ctx, expr.path);
188
+ if (typeof actual === "string") {
189
+ return actual.includes(expr.value);
190
+ }
191
+ if (Array.isArray(actual)) {
192
+ return actual.some((item) => jsonEquals(item, expr.value));
193
+ }
194
+ return false;
195
+ }
196
+ case "startsWith": {
197
+ const actual = getByPath(ctx, expr.path);
198
+ return typeof actual === "string" && actual.startsWith(expr.value);
199
+ }
200
+ case "endsWith": {
201
+ const actual = getByPath(ctx, expr.path);
202
+ return typeof actual === "string" && actual.endsWith(expr.value);
203
+ }
204
+ case "and": {
205
+ return expr.exprs.every((e) => evalCondition(ctx, e));
206
+ }
207
+ case "or": {
208
+ return expr.exprs.some((e) => evalCondition(ctx, e));
209
+ }
210
+ case "not": {
211
+ return !evalCondition(ctx, expr.expr);
212
+ }
213
+ default: {
214
+ return false;
215
+ }
216
+ }
217
+ }
218
+ function jsonEquals(a, b) {
219
+ if (a === b) return true;
220
+ if (a === void 0 || a === null || b === null) return a === b;
221
+ if (typeof a !== typeof b) return false;
222
+ if (Array.isArray(a) && Array.isArray(b)) {
223
+ if (a.length !== b.length) return false;
224
+ return a.every((item, index) => jsonEquals(item, b[index]));
225
+ }
226
+ if (typeof a === "object" && typeof b === "object") {
227
+ const keysA = Object.keys(a);
228
+ const keysB = Object.keys(b);
229
+ if (keysA.length !== keysB.length) return false;
230
+ return keysA.every(
231
+ (key) => jsonEquals(
232
+ a[key],
233
+ b[key]
234
+ )
235
+ );
236
+ }
237
+ return a === b;
238
+ }
239
+ function validateCondition(expr) {
240
+ switch (expr.op) {
241
+ case "eq":
242
+ case "neq":
243
+ if (typeof expr.path !== "string" || !expr.path) {
244
+ throw new Error(`Condition ${expr.op} requires a non-empty path`);
245
+ }
246
+ break;
247
+ case "gt":
248
+ case "gte":
249
+ case "lt":
250
+ case "lte":
251
+ if (typeof expr.path !== "string" || !expr.path) {
252
+ throw new Error(`Condition ${expr.op} requires a non-empty path`);
253
+ }
254
+ if (typeof expr.value !== "number") {
255
+ throw new Error(`Condition ${expr.op} requires a numeric value`);
256
+ }
257
+ break;
258
+ case "exists":
259
+ if (typeof expr.path !== "string" || !expr.path) {
260
+ throw new Error(`Condition ${expr.op} requires a non-empty path`);
261
+ }
262
+ break;
263
+ case "contains":
264
+ case "startsWith":
265
+ case "endsWith":
266
+ if (typeof expr.path !== "string" || !expr.path) {
267
+ throw new Error(`Condition ${expr.op} requires a non-empty path`);
268
+ }
269
+ if (typeof expr.value !== "string") {
270
+ throw new Error(`Condition ${expr.op} requires a string value`);
271
+ }
272
+ break;
273
+ case "and":
274
+ case "or":
275
+ if (!Array.isArray(expr.exprs) || expr.exprs.length === 0) {
276
+ throw new Error(`Condition ${expr.op} requires a non-empty array of expressions`);
277
+ }
278
+ for (const subExpr of expr.exprs) {
279
+ validateCondition(subExpr);
280
+ }
281
+ break;
282
+ case "not":
283
+ if (!expr.expr) {
284
+ throw new Error(`Condition not requires an expression`);
285
+ }
286
+ validateCondition(expr.expr);
287
+ break;
288
+ default: {
289
+ throw new Error(`Unknown condition operator: ${expr.op}`);
290
+ }
291
+ }
292
+ }
293
+ function eq(path, value) {
294
+ return { op: "eq", path, value };
295
+ }
296
+ function neq(path, value) {
297
+ return { op: "neq", path, value };
298
+ }
299
+ function exists(path) {
300
+ return { op: "exists", path };
301
+ }
302
+ function and(...exprs) {
303
+ return { op: "and", exprs };
304
+ }
305
+ function or(...exprs) {
306
+ return { op: "or", exprs };
307
+ }
308
+ function not(expr) {
309
+ return { op: "not", expr };
310
+ }
311
+ function gt(path, value) {
312
+ return { op: "gt", path, value };
313
+ }
314
+ function gte(path, value) {
315
+ return { op: "gte", path, value };
316
+ }
317
+ function lt(path, value) {
318
+ return { op: "lt", path, value };
319
+ }
320
+ function lte(path, value) {
321
+ return { op: "lte", path, value };
322
+ }
323
+ function contains(path, value) {
324
+ return { op: "contains", path, value };
325
+ }
326
+ function startsWith(path, value) {
327
+ return { op: "startsWith", path, value };
328
+ }
329
+ function endsWith(path, value) {
330
+ return { op: "endsWith", path, value };
331
+ }
332
+
333
+ // src/validation.ts
334
+ var ContractValidationError = class extends Error {
335
+ constructor(message, field, details) {
336
+ super(message);
337
+ this.field = field;
338
+ this.details = details;
339
+ this.name = "ContractValidationError";
340
+ }
341
+ };
342
+ function validateContract(contract) {
343
+ const errors = collectValidationErrors(contract);
344
+ if (errors.length > 0) {
345
+ throw errors[0];
346
+ }
347
+ }
348
+ function validateContractSafe(contract) {
349
+ const errors = collectValidationErrors(contract);
350
+ if (errors.length === 0) {
351
+ return { valid: true };
352
+ }
353
+ return { valid: false, errors };
354
+ }
355
+ function collectValidationErrors(contract) {
356
+ const errors = [];
357
+ if (contract.schemaVersion !== "codmir.contract.v1") {
358
+ errors.push(
359
+ new ContractValidationError(
360
+ `Unsupported schemaVersion: ${contract.schemaVersion}`,
361
+ "schemaVersion"
362
+ )
363
+ );
364
+ }
365
+ if (!contract.id || typeof contract.id !== "string") {
366
+ errors.push(new ContractValidationError("Contract id is required", "id"));
367
+ }
368
+ if (!contract.version || typeof contract.version !== "string") {
369
+ errors.push(new ContractValidationError("Contract version is required", "version"));
370
+ }
371
+ if (!contract.title || typeof contract.title !== "string") {
372
+ errors.push(new ContractValidationError("Contract title is required", "title"));
373
+ }
374
+ if (!Array.isArray(contract.triggers) || contract.triggers.length === 0) {
375
+ errors.push(
376
+ new ContractValidationError("Contract must have at least one trigger", "triggers")
377
+ );
378
+ } else {
379
+ for (let i = 0; i < contract.triggers.length; i++) {
380
+ const trigger = contract.triggers[i];
381
+ if (trigger.type !== "event") {
382
+ errors.push(
383
+ new ContractValidationError(
384
+ `Trigger ${i} has unsupported type: ${trigger.type}`,
385
+ `triggers[${i}].type`
386
+ )
387
+ );
388
+ }
389
+ if (!trigger.event || typeof trigger.event !== "string") {
390
+ errors.push(
391
+ new ContractValidationError(
392
+ `Trigger ${i} requires an event pattern`,
393
+ `triggers[${i}].event`
394
+ )
395
+ );
396
+ }
397
+ }
398
+ }
399
+ if (!Array.isArray(contract.steps) || contract.steps.length === 0) {
400
+ errors.push(
401
+ new ContractValidationError("Contract must have at least one step", "steps")
402
+ );
403
+ }
404
+ const stepIds = /* @__PURE__ */ new Set();
405
+ for (const step of contract.steps) {
406
+ if (!step.id || typeof step.id !== "string") {
407
+ errors.push(new ContractValidationError("Step id is required", "steps[].id"));
408
+ continue;
409
+ }
410
+ if (stepIds.has(step.id)) {
411
+ errors.push(
412
+ new ContractValidationError(`Duplicate step id: ${step.id}`, `steps.${step.id}`)
413
+ );
414
+ }
415
+ stepIds.add(step.id);
416
+ const stepErrors = validateStep(step);
417
+ errors.push(...stepErrors);
418
+ }
419
+ if (!contract.entryStepId) {
420
+ errors.push(
421
+ new ContractValidationError("entryStepId is required", "entryStepId")
422
+ );
423
+ } else if (!stepIds.has(contract.entryStepId)) {
424
+ errors.push(
425
+ new ContractValidationError(
426
+ `entryStepId not found in steps: ${contract.entryStepId}`,
427
+ "entryStepId"
428
+ )
429
+ );
430
+ }
431
+ for (const step of contract.steps) {
432
+ if (step.next != null && !stepIds.has(step.next)) {
433
+ errors.push(
434
+ new ContractValidationError(
435
+ `Step ${step.id} has invalid next: ${step.next}`,
436
+ `steps.${step.id}.next`
437
+ )
438
+ );
439
+ }
440
+ if (step.kind === "branch") {
441
+ const branchStep = step;
442
+ for (let i = 0; i < branchStep.branches.length; i++) {
443
+ const branch = branchStep.branches[i];
444
+ if (!stepIds.has(branch.next)) {
445
+ errors.push(
446
+ new ContractValidationError(
447
+ `Branch step ${step.id} has invalid branch next: ${branch.next}`,
448
+ `steps.${step.id}.branches[${i}].next`
449
+ )
450
+ );
451
+ }
452
+ }
453
+ if (!stepIds.has(branchStep.defaultNext)) {
454
+ errors.push(
455
+ new ContractValidationError(
456
+ `Branch step ${step.id} has invalid defaultNext: ${branchStep.defaultNext}`,
457
+ `steps.${step.id}.defaultNext`
458
+ )
459
+ );
460
+ }
461
+ }
462
+ if (step.kind === "wait") {
463
+ const waitStep = step;
464
+ if (waitStep.nextOnTimeout != null && !stepIds.has(waitStep.nextOnTimeout)) {
465
+ errors.push(
466
+ new ContractValidationError(
467
+ `Wait step ${step.id} has invalid nextOnTimeout: ${waitStep.nextOnTimeout}`,
468
+ `steps.${step.id}.nextOnTimeout`
469
+ )
470
+ );
471
+ }
472
+ }
473
+ }
474
+ if (!contract.limits) {
475
+ errors.push(new ContractValidationError("Contract limits are required", "limits"));
476
+ } else {
477
+ if (typeof contract.limits.maxRunMs !== "number" || contract.limits.maxRunMs <= 0) {
478
+ errors.push(
479
+ new ContractValidationError(
480
+ "limits.maxRunMs must be a positive number",
481
+ "limits.maxRunMs"
482
+ )
483
+ );
484
+ }
485
+ if (typeof contract.limits.maxSteps !== "number" || contract.limits.maxSteps <= 0) {
486
+ errors.push(
487
+ new ContractValidationError(
488
+ "limits.maxSteps must be a positive number",
489
+ "limits.maxSteps"
490
+ )
491
+ );
492
+ }
493
+ }
494
+ if (!Array.isArray(contract.permissions)) {
495
+ errors.push(
496
+ new ContractValidationError("Contract permissions must be an array", "permissions")
497
+ );
498
+ }
499
+ return errors;
500
+ }
501
+ function validateStep(step, _stepIds) {
502
+ const errors = [];
503
+ if (!step.name || typeof step.name !== "string") {
504
+ errors.push(
505
+ new ContractValidationError(`Step ${step.id} requires a name`, `steps.${step.id}.name`)
506
+ );
507
+ }
508
+ if (step.if) {
509
+ try {
510
+ validateCondition(step.if);
511
+ } catch (err) {
512
+ errors.push(
513
+ new ContractValidationError(
514
+ `Step ${step.id} has invalid if condition: ${err.message}`,
515
+ `steps.${step.id}.if`
516
+ )
517
+ );
518
+ }
519
+ }
520
+ switch (step.kind) {
521
+ case "task": {
522
+ const taskStep = step;
523
+ if (!taskStep.task || typeof taskStep.task !== "string") {
524
+ errors.push(
525
+ new ContractValidationError(
526
+ `Task step ${step.id} requires a task name`,
527
+ `steps.${step.id}.task`
528
+ )
529
+ );
530
+ }
531
+ if (!taskStep.input || typeof taskStep.input !== "object") {
532
+ errors.push(
533
+ new ContractValidationError(
534
+ `Task step ${step.id} requires an input object`,
535
+ `steps.${step.id}.input`
536
+ )
537
+ );
538
+ }
539
+ break;
540
+ }
541
+ case "llm": {
542
+ const llmStep = step;
543
+ if (!llmStep.prompt || typeof llmStep.prompt !== "object") {
544
+ errors.push(
545
+ new ContractValidationError(
546
+ `LLM step ${step.id} requires a prompt object`,
547
+ `steps.${step.id}.prompt`
548
+ )
549
+ );
550
+ }
551
+ break;
552
+ }
553
+ case "emit": {
554
+ const emitStep = step;
555
+ if (!emitStep.event || typeof emitStep.event !== "string") {
556
+ errors.push(
557
+ new ContractValidationError(
558
+ `Emit step ${step.id} requires an event name`,
559
+ `steps.${step.id}.event`
560
+ )
561
+ );
562
+ }
563
+ if (!emitStep.payload || typeof emitStep.payload !== "object") {
564
+ errors.push(
565
+ new ContractValidationError(
566
+ `Emit step ${step.id} requires a payload object`,
567
+ `steps.${step.id}.payload`
568
+ )
569
+ );
570
+ }
571
+ break;
572
+ }
573
+ case "wait": {
574
+ const waitStep = step;
575
+ if (!waitStep.event || typeof waitStep.event !== "string") {
576
+ errors.push(
577
+ new ContractValidationError(
578
+ `Wait step ${step.id} requires an event pattern`,
579
+ `steps.${step.id}.event`
580
+ )
581
+ );
582
+ }
583
+ break;
584
+ }
585
+ case "branch": {
586
+ const branchStep = step;
587
+ if (!Array.isArray(branchStep.branches) || branchStep.branches.length === 0) {
588
+ errors.push(
589
+ new ContractValidationError(
590
+ `Branch step ${step.id} requires at least one branch`,
591
+ `steps.${step.id}.branches`
592
+ )
593
+ );
594
+ } else {
595
+ for (let i = 0; i < branchStep.branches.length; i++) {
596
+ const branch = branchStep.branches[i];
597
+ if (!branch.when) {
598
+ errors.push(
599
+ new ContractValidationError(
600
+ `Branch ${i} in step ${step.id} requires a when condition`,
601
+ `steps.${step.id}.branches[${i}].when`
602
+ )
603
+ );
604
+ } else {
605
+ try {
606
+ validateCondition(branch.when);
607
+ } catch (err) {
608
+ errors.push(
609
+ new ContractValidationError(
610
+ `Branch ${i} in step ${step.id} has invalid when condition: ${err.message}`,
611
+ `steps.${step.id}.branches[${i}].when`
612
+ )
613
+ );
614
+ }
615
+ }
616
+ }
617
+ }
618
+ if (!branchStep.defaultNext || typeof branchStep.defaultNext !== "string") {
619
+ errors.push(
620
+ new ContractValidationError(
621
+ `Branch step ${step.id} requires a defaultNext`,
622
+ `steps.${step.id}.defaultNext`
623
+ )
624
+ );
625
+ }
626
+ break;
627
+ }
628
+ case "transform": {
629
+ const transformStep = step;
630
+ if (!transformStep.transforms || typeof transformStep.transforms !== "object") {
631
+ errors.push(
632
+ new ContractValidationError(
633
+ `Transform step ${step.id} requires a transforms object`,
634
+ `steps.${step.id}.transforms`
635
+ )
636
+ );
637
+ }
638
+ break;
639
+ }
640
+ case "approval": {
641
+ const approvalStep = step;
642
+ if (!approvalStep.prompt || typeof approvalStep.prompt !== "string") {
643
+ errors.push(
644
+ new ContractValidationError(
645
+ `Approval step ${step.id} requires a prompt`,
646
+ `steps.${step.id}.prompt`
647
+ )
648
+ );
649
+ }
650
+ if (!approvalStep.nextOnApproved || typeof approvalStep.nextOnApproved !== "string") {
651
+ errors.push(
652
+ new ContractValidationError(
653
+ `Approval step ${step.id} requires nextOnApproved`,
654
+ `steps.${step.id}.nextOnApproved`
655
+ )
656
+ );
657
+ }
658
+ break;
659
+ }
660
+ default: {
661
+ const unknownStep = step;
662
+ errors.push(
663
+ new ContractValidationError(
664
+ `Step ${unknownStep.id} has unknown kind: ${unknownStep.kind}`,
665
+ `steps.${unknownStep.id}.kind`
666
+ )
667
+ );
668
+ }
669
+ }
670
+ return errors;
671
+ }
672
+ function hasPermission(contract, kind, scope) {
673
+ return contract.permissions.some((p) => {
674
+ if (p.kind !== kind) return false;
675
+ if (!scope) return true;
676
+ switch (p.kind) {
677
+ case "event:emit":
678
+ case "event:read":
679
+ return matchPattern(p.pattern, scope);
680
+ case "storage:read":
681
+ case "storage:write":
682
+ case "repo:read":
683
+ case "repo:write":
684
+ return matchPattern(p.scope, scope);
685
+ case "http:request":
686
+ return p.domains.some((d) => matchPattern(d, scope));
687
+ case "secrets:read":
688
+ return p.keys.includes(scope);
689
+ case "llm:invoke":
690
+ return !p.models || p.models.includes(scope);
691
+ default:
692
+ return false;
693
+ }
694
+ });
695
+ }
696
+ function matchPattern(pattern, value) {
697
+ if (pattern === "*") return true;
698
+ if (pattern.endsWith(".*")) {
699
+ const prefix = pattern.slice(0, -2);
700
+ return value === prefix || value.startsWith(prefix + ".");
701
+ }
702
+ if (pattern.endsWith("*")) {
703
+ const prefix = pattern.slice(0, -1);
704
+ return value.startsWith(prefix);
705
+ }
706
+ return pattern === value;
707
+ }
708
+
709
+ // src/examples/ticket-triage.ts
710
+ var ticketTriageContract = {
711
+ schemaVersion: "codmir.contract.v1",
712
+ id: "codmir.ticket-triage",
713
+ version: "1.0.0",
714
+ title: "Ticket Triage Agent",
715
+ description: "Automatically analyzes and triages incoming support tickets using AI classification",
716
+ triggers: [
717
+ {
718
+ type: "event",
719
+ event: "ticket.created"
720
+ }
721
+ ],
722
+ permissions: [
723
+ { kind: "event:emit", pattern: "ticket.*" },
724
+ { kind: "event:emit", pattern: "agent.*" },
725
+ { kind: "event:read", pattern: "ticket.*" },
726
+ { kind: "llm:invoke", models: ["gpt-5-mini-2025-08-07", "gpt-5"] }
727
+ ],
728
+ limits: {
729
+ maxRunMs: 6e4,
730
+ // 1 minute
731
+ maxSteps: 10,
732
+ maxLlmCalls: 3,
733
+ maxTokens: 5e3
734
+ },
735
+ entryStepId: "analyze",
736
+ steps: [
737
+ // Step 1: Analyze ticket with LLM
738
+ {
739
+ id: "analyze",
740
+ name: "Analyze Ticket",
741
+ kind: "llm",
742
+ description: "Use AI to analyze ticket content and extract key information",
743
+ model: "gpt-5-mini-2025-08-07",
744
+ prompt: {
745
+ system: "You are a support ticket analyst. Analyze the ticket and extract: priority (low/medium/high/urgent), category (bug/feature/question/other), sentiment (positive/neutral/negative), and a brief summary.",
746
+ user: "Ticket Title: {{trigger.title}}\n\nTicket Body:\n{{trigger.body}}"
747
+ },
748
+ saveAs: "steps.analysis",
749
+ temperature: 0.3,
750
+ maxTokens: 500,
751
+ next: "classify"
752
+ },
753
+ // Step 2: Transform analysis into structured data
754
+ {
755
+ id: "classify",
756
+ name: "Classify Ticket",
757
+ kind: "transform",
758
+ description: "Structure the analysis results",
759
+ transforms: {
760
+ "classification.priority": "{{steps.analysis.priority}}",
761
+ "classification.category": "{{steps.analysis.category}}",
762
+ "classification.sentiment": "{{steps.analysis.sentiment}}",
763
+ "classification.summary": "{{steps.analysis.summary}}"
764
+ },
765
+ next: "check-urgent"
766
+ },
767
+ // Step 3: Branch on urgency
768
+ {
769
+ id: "check-urgent",
770
+ name: "Check Urgency",
771
+ kind: "branch",
772
+ description: "Route urgent tickets to fast path",
773
+ branches: [
774
+ {
775
+ when: { op: "eq", path: "classification.priority", value: "urgent" },
776
+ next: "escalate"
777
+ },
778
+ {
779
+ when: { op: "eq", path: "classification.priority", value: "high" },
780
+ next: "assign-senior"
781
+ }
782
+ ],
783
+ defaultNext: "assign-team"
784
+ },
785
+ // Step 4a: Escalate urgent tickets
786
+ {
787
+ id: "escalate",
788
+ name: "Escalate Urgent",
789
+ kind: "emit",
790
+ description: "Emit escalation event for urgent tickets",
791
+ event: "ticket.escalated",
792
+ payload: {
793
+ ticketId: "{{trigger.id}}",
794
+ priority: "urgent",
795
+ reason: "Auto-escalated by triage agent",
796
+ analysis: "{{classification}}"
797
+ },
798
+ next: "notify-complete"
799
+ },
800
+ // Step 4b: Assign to senior team
801
+ {
802
+ id: "assign-senior",
803
+ name: "Assign to Senior",
804
+ kind: "task",
805
+ description: "Assign high-priority ticket to senior team",
806
+ task: "assign-ticket",
807
+ input: {
808
+ ticketId: "{{trigger.id}}",
809
+ team: "senior-support",
810
+ priority: "high",
811
+ notes: "{{classification.summary}}"
812
+ },
813
+ saveAs: "steps.assignment",
814
+ next: "notify-complete"
815
+ },
816
+ // Step 4c: Assign to regular team
817
+ {
818
+ id: "assign-team",
819
+ name: "Assign to Team",
820
+ kind: "task",
821
+ description: "Assign ticket to appropriate team based on category",
822
+ task: "assign-ticket",
823
+ input: {
824
+ ticketId: "{{trigger.id}}",
825
+ team: "{{classification.category}}-team",
826
+ priority: "{{classification.priority}}",
827
+ notes: "{{classification.summary}}"
828
+ },
829
+ saveAs: "steps.assignment",
830
+ next: "notify-complete"
831
+ },
832
+ // Step 5: Emit completion event
833
+ {
834
+ id: "notify-complete",
835
+ name: "Notify Complete",
836
+ kind: "emit",
837
+ description: "Emit triage completion event",
838
+ event: "ticket.triaged",
839
+ payload: {
840
+ ticketId: "{{trigger.id}}",
841
+ classification: "{{classification}}",
842
+ assignment: "{{steps.assignment}}"
843
+ },
844
+ next: null
845
+ }
846
+ ],
847
+ outputs: {
848
+ onCompleted: [
849
+ {
850
+ event: "agent.task.completed",
851
+ payload: {
852
+ agent: "ticket-triage",
853
+ ticketId: "{{trigger.id}}",
854
+ result: "{{classification}}"
855
+ }
856
+ }
857
+ ],
858
+ onFailed: [
859
+ {
860
+ event: "agent.task.failed",
861
+ payload: {
862
+ agent: "ticket-triage",
863
+ ticketId: "{{trigger.id}}"
864
+ }
865
+ }
866
+ ]
867
+ },
868
+ tags: ["support", "triage", "ai", "automation"],
869
+ author: "codmir-team"
870
+ };
871
+
872
+ // src/examples/code-review.ts
873
+ var codeReviewContract = {
874
+ schemaVersion: "codmir.contract.v1",
875
+ id: "codmir.code-review",
876
+ version: "1.0.0",
877
+ title: "Code Review Agent",
878
+ description: "Automatically reviews pull requests, checks code quality, and suggests improvements",
879
+ triggers: [
880
+ {
881
+ type: "event",
882
+ event: "github.pull_request.opened"
883
+ },
884
+ {
885
+ type: "event",
886
+ event: "github.pull_request.synchronize"
887
+ }
888
+ ],
889
+ permissions: [
890
+ { kind: "event:emit", pattern: "github.*" },
891
+ { kind: "event:emit", pattern: "review.*" },
892
+ { kind: "event:read", pattern: "github.*" },
893
+ { kind: "repo:read", scope: "*" },
894
+ { kind: "llm:invoke", models: ["gpt-5", "claude-3-sonnet"] },
895
+ { kind: "http:request", domains: ["api.github.com"] }
896
+ ],
897
+ limits: {
898
+ maxRunMs: 3e5,
899
+ // 5 minutes
900
+ maxSteps: 20,
901
+ maxLlmCalls: 10,
902
+ maxTokens: 5e4
903
+ },
904
+ entryStepId: "fetch-diff",
905
+ steps: [
906
+ // Step 1: Fetch PR diff
907
+ {
908
+ id: "fetch-diff",
909
+ name: "Fetch PR Diff",
910
+ kind: "task",
911
+ description: "Fetch the pull request diff from GitHub",
912
+ task: "github-fetch-diff",
913
+ input: {
914
+ owner: "{{trigger.repository.owner}}",
915
+ repo: "{{trigger.repository.name}}",
916
+ pullNumber: "{{trigger.pull_request.number}}"
917
+ },
918
+ saveAs: "steps.diff",
919
+ timeout: { ms: 3e4 },
920
+ retry: { maxAttempts: 2, backoffMs: 1e3 },
921
+ next: "check-size"
922
+ },
923
+ // Step 2: Check PR size
924
+ {
925
+ id: "check-size",
926
+ name: "Check PR Size",
927
+ kind: "branch",
928
+ description: "Route large PRs differently",
929
+ branches: [
930
+ {
931
+ when: { op: "gt", path: "steps.diff.linesChanged", value: 1e3 },
932
+ next: "large-pr-warning"
933
+ }
934
+ ],
935
+ defaultNext: "analyze-code"
936
+ },
937
+ // Step 2a: Warn about large PR
938
+ {
939
+ id: "large-pr-warning",
940
+ name: "Large PR Warning",
941
+ kind: "emit",
942
+ description: "Emit warning about large PR size",
943
+ event: "review.warning",
944
+ payload: {
945
+ pullNumber: "{{trigger.pull_request.number}}",
946
+ reason: "PR is too large for effective review",
947
+ linesChanged: "{{steps.diff.linesChanged}}",
948
+ recommendation: "Consider breaking into smaller PRs"
949
+ },
950
+ next: "analyze-code"
951
+ },
952
+ // Step 3: Analyze code with LLM
953
+ {
954
+ id: "analyze-code",
955
+ name: "Analyze Code",
956
+ kind: "llm",
957
+ description: "Use AI to analyze code changes for issues",
958
+ model: "gpt-5",
959
+ systemPrompt: `You are an expert code reviewer. Analyze the diff and identify:
960
+ 1. Potential bugs or logic errors
961
+ 2. Security vulnerabilities
962
+ 3. Performance issues
963
+ 4. Code style violations
964
+ 5. Missing tests
965
+ 6. Documentation gaps
966
+
967
+ For each issue, provide:
968
+ - File and line number
969
+ - Severity (critical/warning/info)
970
+ - Description
971
+ - Suggested fix
972
+
973
+ Respond in JSON format with an "issues" array.`,
974
+ prompt: {
975
+ diff: "{{steps.diff.content}}",
976
+ prTitle: "{{trigger.pull_request.title}}",
977
+ prDescription: "{{trigger.pull_request.body}}"
978
+ },
979
+ saveAs: "steps.analysis",
980
+ temperature: 0.2,
981
+ maxTokens: 4e3,
982
+ next: "check-critical"
983
+ },
984
+ // Step 4: Check for critical issues
985
+ {
986
+ id: "check-critical",
987
+ name: "Check Critical Issues",
988
+ kind: "branch",
989
+ description: "Route critical issues to blocking review",
990
+ branches: [
991
+ {
992
+ when: {
993
+ op: "exists",
994
+ path: "steps.analysis.issues"
995
+ },
996
+ next: "filter-critical"
997
+ }
998
+ ],
999
+ defaultNext: "approve"
1000
+ },
1001
+ // Step 4a: Filter critical issues
1002
+ {
1003
+ id: "filter-critical",
1004
+ name: "Filter Critical",
1005
+ kind: "transform",
1006
+ description: "Extract critical issues for blocking",
1007
+ transforms: {
1008
+ "review.hasCritical": true,
1009
+ "review.issues": "{{steps.analysis.issues}}"
1010
+ },
1011
+ next: "post-comments"
1012
+ },
1013
+ // Step 5: Post review comments
1014
+ {
1015
+ id: "post-comments",
1016
+ name: "Post Comments",
1017
+ kind: "task",
1018
+ description: "Post review comments on GitHub",
1019
+ task: "github-post-review",
1020
+ input: {
1021
+ owner: "{{trigger.repository.owner}}",
1022
+ repo: "{{trigger.repository.name}}",
1023
+ pullNumber: "{{trigger.pull_request.number}}",
1024
+ body: "## AI Code Review\n\nI found {{review.issues.length}} issue(s) in this PR.",
1025
+ comments: "{{review.issues}}",
1026
+ event: "REQUEST_CHANGES"
1027
+ },
1028
+ saveAs: "steps.reviewPost",
1029
+ next: "wait-author-response"
1030
+ },
1031
+ // Step 6: Wait for author response
1032
+ {
1033
+ id: "wait-author-response",
1034
+ name: "Wait for Response",
1035
+ kind: "wait",
1036
+ description: "Wait for author to address issues or respond",
1037
+ event: "github.pull_request.synchronize",
1038
+ saveEventAs: "events.authorResponse",
1039
+ timeout: { ms: 864e5 },
1040
+ // 24 hours
1041
+ next: "re-analyze",
1042
+ nextOnTimeout: "timeout-reminder"
1043
+ },
1044
+ // Step 6a: Re-analyze after author changes
1045
+ {
1046
+ id: "re-analyze",
1047
+ name: "Re-analyze",
1048
+ kind: "task",
1049
+ description: "Re-fetch and re-analyze after author changes",
1050
+ task: "github-fetch-diff",
1051
+ input: {
1052
+ owner: "{{trigger.repository.owner}}",
1053
+ repo: "{{trigger.repository.name}}",
1054
+ pullNumber: "{{trigger.pull_request.number}}"
1055
+ },
1056
+ saveAs: "steps.newDiff",
1057
+ next: "analyze-code"
1058
+ },
1059
+ // Step 6b: Timeout reminder
1060
+ {
1061
+ id: "timeout-reminder",
1062
+ name: "Timeout Reminder",
1063
+ kind: "emit",
1064
+ description: "Remind author about pending review",
1065
+ event: "review.reminder",
1066
+ payload: {
1067
+ pullNumber: "{{trigger.pull_request.number}}",
1068
+ author: "{{trigger.pull_request.user.login}}",
1069
+ message: "Code review feedback is still pending"
1070
+ },
1071
+ next: null
1072
+ },
1073
+ // Step 7: Approve PR
1074
+ {
1075
+ id: "approve",
1076
+ name: "Approve PR",
1077
+ kind: "task",
1078
+ description: "Approve the PR with positive review",
1079
+ task: "github-post-review",
1080
+ input: {
1081
+ owner: "{{trigger.repository.owner}}",
1082
+ repo: "{{trigger.repository.name}}",
1083
+ pullNumber: "{{trigger.pull_request.number}}",
1084
+ body: "## AI Code Review\n\n\u2705 LGTM! No issues found.",
1085
+ event: "APPROVE"
1086
+ },
1087
+ saveAs: "steps.approval",
1088
+ next: "complete"
1089
+ },
1090
+ // Step 8: Complete
1091
+ {
1092
+ id: "complete",
1093
+ name: "Complete",
1094
+ kind: "emit",
1095
+ description: "Emit review completion event",
1096
+ event: "review.completed",
1097
+ payload: {
1098
+ pullNumber: "{{trigger.pull_request.number}}",
1099
+ repository: "{{trigger.repository.full_name}}",
1100
+ result: "approved"
1101
+ },
1102
+ next: null
1103
+ }
1104
+ ],
1105
+ outputs: {
1106
+ onCompleted: [
1107
+ {
1108
+ event: "agent.task.completed",
1109
+ payload: {
1110
+ agent: "code-review",
1111
+ repository: "{{trigger.repository.full_name}}",
1112
+ pullNumber: "{{trigger.pull_request.number}}"
1113
+ }
1114
+ }
1115
+ ],
1116
+ onFailed: [
1117
+ {
1118
+ event: "agent.task.failed",
1119
+ payload: {
1120
+ agent: "code-review",
1121
+ repository: "{{trigger.repository.full_name}}",
1122
+ pullNumber: "{{trigger.pull_request.number}}"
1123
+ }
1124
+ }
1125
+ ]
1126
+ },
1127
+ tags: ["github", "code-review", "ai", "automation"],
1128
+ author: "codmir-team"
1129
+ };
1130
+
1131
+ // src/examples/pr-analysis.ts
1132
+ var prAnalysisContract = {
1133
+ schemaVersion: "codmir.contract.v1",
1134
+ id: "codmir.pr-analysis",
1135
+ version: "1.0.0",
1136
+ title: "PR Analysis Agent",
1137
+ description: "Automatically analyzes pull requests for code quality, secrets, and best practices when triggered by GitHub App webhook",
1138
+ triggers: [
1139
+ {
1140
+ type: "event",
1141
+ event: "github.pull_request.opened"
1142
+ },
1143
+ {
1144
+ type: "event",
1145
+ event: "github.pull_request.synchronize"
1146
+ },
1147
+ {
1148
+ type: "event",
1149
+ event: "github.pull_request.reopened"
1150
+ },
1151
+ {
1152
+ type: "event",
1153
+ event: "github.pull_request.ready_for_review"
1154
+ }
1155
+ ],
1156
+ permissions: [
1157
+ { kind: "event:emit", pattern: "github.*" },
1158
+ { kind: "event:emit", pattern: "pr.*" },
1159
+ { kind: "event:read", pattern: "github.*" },
1160
+ { kind: "repo:read", scope: "*" },
1161
+ { kind: "http:request", domains: ["api.github.com"] },
1162
+ { kind: "secrets:read", keys: ["GITHUB_TOKEN", "GITHUB_APP_*"] }
1163
+ ],
1164
+ limits: {
1165
+ maxRunMs: 12e4,
1166
+ // 2 minutes
1167
+ maxSteps: 10,
1168
+ maxLlmCalls: 0,
1169
+ // No LLM calls needed for basic analysis
1170
+ maxTokens: 0
1171
+ },
1172
+ entryStepId: "run-analysis",
1173
+ steps: [
1174
+ // Step 1: Run full PR analysis (fetch + analyze + post comment)
1175
+ {
1176
+ id: "run-analysis",
1177
+ name: "Run PR Analysis",
1178
+ kind: "task",
1179
+ description: "Fetch PR info, analyze diff, and post analysis comment",
1180
+ task: "pr-run-analysis",
1181
+ input: {
1182
+ owner: "{{trigger.repository.owner.login}}",
1183
+ repo: "{{trigger.repository.name}}",
1184
+ pullNumber: "{{trigger.pull_request.number}}",
1185
+ installationId: "{{trigger.installation.id}}"
1186
+ },
1187
+ saveAs: "steps.analysis",
1188
+ timeout: { ms: 6e4 },
1189
+ retry: { maxAttempts: 2, backoffMs: 2e3 },
1190
+ next: "check-secrets"
1191
+ },
1192
+ // Step 2: Check for critical issues (secrets detected)
1193
+ {
1194
+ id: "check-secrets",
1195
+ name: "Check for Secrets",
1196
+ kind: "branch",
1197
+ description: "Check if secrets were detected in the PR",
1198
+ branches: [
1199
+ {
1200
+ when: {
1201
+ op: "gt",
1202
+ path: "steps.analysis.analysis.secrets.length",
1203
+ value: 0
1204
+ },
1205
+ next: "emit-secret-alert"
1206
+ }
1207
+ ],
1208
+ defaultNext: "emit-completed"
1209
+ },
1210
+ // Step 2a: Emit secret alert
1211
+ {
1212
+ id: "emit-secret-alert",
1213
+ name: "Emit Secret Alert",
1214
+ kind: "emit",
1215
+ description: "Emit alert about potential secrets detected",
1216
+ event: "pr.secrets.detected",
1217
+ payload: {
1218
+ pullNumber: "{{trigger.pull_request.number}}",
1219
+ repository: "{{trigger.repository.full_name}}",
1220
+ secretCount: "{{steps.analysis.analysis.secrets.length}}",
1221
+ secrets: "{{steps.analysis.analysis.secrets}}",
1222
+ severity: "critical"
1223
+ },
1224
+ next: "emit-completed"
1225
+ },
1226
+ // Step 3: Emit completion event
1227
+ {
1228
+ id: "emit-completed",
1229
+ name: "Emit Completed",
1230
+ kind: "emit",
1231
+ description: "Emit PR analysis completion event",
1232
+ event: "pr.analysis.completed",
1233
+ payload: {
1234
+ pullNumber: "{{trigger.pull_request.number}}",
1235
+ repository: "{{trigger.repository.full_name}}",
1236
+ analysis: "{{steps.analysis.analysis}}",
1237
+ commentPosted: "{{steps.analysis.commentPosted}}",
1238
+ commentId: "{{steps.analysis.commentId}}"
1239
+ },
1240
+ next: null
1241
+ }
1242
+ ],
1243
+ outputs: {
1244
+ onCompleted: [
1245
+ {
1246
+ event: "agent.task.completed",
1247
+ payload: {
1248
+ agent: "pr-analysis",
1249
+ repository: "{{trigger.repository.full_name}}",
1250
+ pullNumber: "{{trigger.pull_request.number}}",
1251
+ result: "{{steps.analysis}}"
1252
+ }
1253
+ }
1254
+ ],
1255
+ onFailed: [
1256
+ {
1257
+ event: "agent.task.failed",
1258
+ payload: {
1259
+ agent: "pr-analysis",
1260
+ repository: "{{trigger.repository.full_name}}",
1261
+ pullNumber: "{{trigger.pull_request.number}}"
1262
+ }
1263
+ }
1264
+ ]
1265
+ },
1266
+ tags: ["github", "pr-analysis", "automation", "webhook"],
1267
+ author: "codmir-team"
1268
+ };
1269
+ var prAnalysisWithLlmContract = {
1270
+ schemaVersion: "codmir.contract.v1",
1271
+ id: "codmir.pr-analysis-llm",
1272
+ version: "1.0.0",
1273
+ title: "PR Analysis Agent (with AI Review)",
1274
+ description: "Analyzes pull requests with AI-powered code review for deeper insights",
1275
+ triggers: [
1276
+ {
1277
+ type: "event",
1278
+ event: "github.pull_request.opened"
1279
+ },
1280
+ {
1281
+ type: "event",
1282
+ event: "github.pull_request.synchronize"
1283
+ }
1284
+ ],
1285
+ permissions: [
1286
+ { kind: "event:emit", pattern: "github.*" },
1287
+ { kind: "event:emit", pattern: "pr.*" },
1288
+ { kind: "event:read", pattern: "github.*" },
1289
+ { kind: "repo:read", scope: "*" },
1290
+ { kind: "http:request", domains: ["api.github.com"] },
1291
+ { kind: "secrets:read", keys: ["GITHUB_TOKEN", "GITHUB_APP_*"] },
1292
+ { kind: "llm:invoke", models: ["claude-3-sonnet", "claude-3-haiku"] }
1293
+ ],
1294
+ limits: {
1295
+ maxRunMs: 3e5,
1296
+ // 5 minutes
1297
+ maxSteps: 15,
1298
+ maxLlmCalls: 5,
1299
+ maxTokens: 5e4
1300
+ },
1301
+ entryStepId: "fetch-pr",
1302
+ steps: [
1303
+ // Step 1: Fetch PR info
1304
+ {
1305
+ id: "fetch-pr",
1306
+ name: "Fetch PR Info",
1307
+ kind: "task",
1308
+ description: "Fetch pull request metadata from GitHub",
1309
+ task: "pr-fetch-info",
1310
+ input: {
1311
+ owner: "{{trigger.repository.owner.login}}",
1312
+ repo: "{{trigger.repository.name}}",
1313
+ pullNumber: "{{trigger.pull_request.number}}"
1314
+ },
1315
+ saveAs: "steps.pr",
1316
+ timeout: { ms: 15e3 },
1317
+ next: "fetch-files"
1318
+ },
1319
+ // Step 2: Fetch PR files
1320
+ {
1321
+ id: "fetch-files",
1322
+ name: "Fetch PR Files",
1323
+ kind: "task",
1324
+ description: "Fetch changed files with patches",
1325
+ task: "pr-fetch-files",
1326
+ input: {
1327
+ owner: "{{trigger.repository.owner.login}}",
1328
+ repo: "{{trigger.repository.name}}",
1329
+ pullNumber: "{{trigger.pull_request.number}}"
1330
+ },
1331
+ saveAs: "steps.files",
1332
+ timeout: { ms: 3e4 },
1333
+ next: "static-analysis"
1334
+ },
1335
+ // Step 3: Run static analysis
1336
+ {
1337
+ id: "static-analysis",
1338
+ name: "Static Analysis",
1339
+ kind: "task",
1340
+ description: "Run heuristic analysis on PR diff",
1341
+ task: "pr-analyze-diff",
1342
+ input: {
1343
+ pr: "{{steps.pr}}",
1344
+ files: "{{steps.files}}",
1345
+ owner: "{{trigger.repository.owner.login}}",
1346
+ repo: "{{trigger.repository.name}}"
1347
+ },
1348
+ saveAs: "steps.staticAnalysis",
1349
+ timeout: { ms: 3e4 },
1350
+ next: "llm-review"
1351
+ },
1352
+ // Step 4: AI-powered code review
1353
+ {
1354
+ id: "llm-review",
1355
+ name: "AI Code Review",
1356
+ kind: "llm",
1357
+ description: "Use AI to review code changes for issues and improvements",
1358
+ model: "claude-3-sonnet",
1359
+ systemPrompt: `You are an expert code reviewer. Analyze the PR diff and identify:
1360
+ 1. Potential bugs or logic errors
1361
+ 2. Security vulnerabilities
1362
+ 3. Performance issues
1363
+ 4. Code style violations
1364
+ 5. Missing error handling
1365
+ 6. Suggestions for improvement
1366
+
1367
+ Be concise and actionable. Focus on the most important issues.
1368
+ Respond in JSON format with an "issues" array containing objects with:
1369
+ - severity: "critical" | "warning" | "info"
1370
+ - file: string (filename)
1371
+ - line: number (approximate line if known)
1372
+ - issue: string (description)
1373
+ - suggestion: string (how to fix)`,
1374
+ prompt: {
1375
+ prTitle: "{{steps.pr.title}}",
1376
+ prDescription: "{{trigger.pull_request.body}}",
1377
+ staticIssues: "{{steps.staticAnalysis.analysis.issues}}",
1378
+ changedFiles: "{{steps.files}}"
1379
+ },
1380
+ saveAs: "steps.llmReview",
1381
+ temperature: 0.2,
1382
+ maxTokens: 4e3,
1383
+ next: "post-comment"
1384
+ },
1385
+ // Step 5: Post combined analysis comment
1386
+ {
1387
+ id: "post-comment",
1388
+ name: "Post Comment",
1389
+ kind: "task",
1390
+ description: "Post analysis and AI review comment to PR",
1391
+ task: "pr-post-comment",
1392
+ input: {
1393
+ owner: "{{trigger.repository.owner.login}}",
1394
+ repo: "{{trigger.repository.name}}",
1395
+ pullNumber: "{{trigger.pull_request.number}}",
1396
+ markdown: "{{steps.staticAnalysis.markdown}}\n\n### \u{1F916} AI Review\n\n{{steps.llmReview.content}}"
1397
+ },
1398
+ saveAs: "steps.comment",
1399
+ timeout: { ms: 15e3 },
1400
+ next: "emit-completed"
1401
+ },
1402
+ // Step 6: Emit completion
1403
+ {
1404
+ id: "emit-completed",
1405
+ name: "Complete",
1406
+ kind: "emit",
1407
+ description: "Emit completion event",
1408
+ event: "pr.analysis.completed",
1409
+ payload: {
1410
+ pullNumber: "{{trigger.pull_request.number}}",
1411
+ repository: "{{trigger.repository.full_name}}",
1412
+ staticAnalysis: "{{steps.staticAnalysis.analysis}}",
1413
+ llmReview: "{{steps.llmReview}}",
1414
+ commentPosted: true
1415
+ },
1416
+ next: null
1417
+ }
1418
+ ],
1419
+ outputs: {
1420
+ onCompleted: [
1421
+ {
1422
+ event: "agent.task.completed",
1423
+ payload: {
1424
+ agent: "pr-analysis-llm",
1425
+ repository: "{{trigger.repository.full_name}}",
1426
+ pullNumber: "{{trigger.pull_request.number}}"
1427
+ }
1428
+ }
1429
+ ],
1430
+ onFailed: [
1431
+ {
1432
+ event: "agent.task.failed",
1433
+ payload: {
1434
+ agent: "pr-analysis-llm",
1435
+ repository: "{{trigger.repository.full_name}}",
1436
+ pullNumber: "{{trigger.pull_request.number}}"
1437
+ }
1438
+ }
1439
+ ]
1440
+ },
1441
+ tags: ["github", "pr-analysis", "ai-review", "automation", "webhook"],
1442
+ author: "codmir-team"
1443
+ };
1444
+
1445
+ // src/voice/create-ticket.ts
1446
+ var voiceCreateTicketContract = {
1447
+ schemaVersion: "codmir.contract.v1",
1448
+ id: "voice.create-ticket",
1449
+ version: "1.0.0",
1450
+ title: "Create Ticket via Voice",
1451
+ description: "Creates a new ticket/issue from a voice command",
1452
+ execution: {
1453
+ expectedDuration: "instant",
1454
+ mode: "sync",
1455
+ asyncAckMessage: "Creating your ticket now..."
1456
+ },
1457
+ triggers: [
1458
+ {
1459
+ type: "event",
1460
+ event: "voice.action.requested",
1461
+ where: { action: "create_ticket" }
1462
+ }
1463
+ ],
1464
+ permissions: [
1465
+ { kind: "storage:write", scope: "tickets" },
1466
+ { kind: "event:emit", pattern: "ticket.created" }
1467
+ ],
1468
+ limits: {
1469
+ maxRunMs: 1e4,
1470
+ maxSteps: 5
1471
+ },
1472
+ entryStepId: "validate",
1473
+ steps: [
1474
+ {
1475
+ id: "validate",
1476
+ name: "Validate Input",
1477
+ kind: "transform",
1478
+ transforms: {
1479
+ "input.title": "{{trigger.payload.params.title}}",
1480
+ "input.description": "{{trigger.payload.params.description}}",
1481
+ "input.priority": "{{trigger.payload.params.priority}}",
1482
+ "input.projectId": "{{trigger.payload.params.projectId}}",
1483
+ "input.labels": "{{trigger.payload.params.labels}}",
1484
+ "input.userId": "{{trigger.payload.userId}}"
1485
+ },
1486
+ next: "create"
1487
+ },
1488
+ {
1489
+ id: "create",
1490
+ name: "Create Ticket",
1491
+ kind: "task",
1492
+ task: "voice.create-ticket",
1493
+ input: {
1494
+ title: "{{input.title}}",
1495
+ description: "{{input.description}}",
1496
+ priority: "{{input.priority}}",
1497
+ projectId: "{{input.projectId}}",
1498
+ labels: "{{input.labels}}",
1499
+ userId: "{{input.userId}}"
1500
+ },
1501
+ saveAs: "ticket",
1502
+ next: "emit"
1503
+ },
1504
+ {
1505
+ id: "emit",
1506
+ name: "Emit Ticket Created Event",
1507
+ kind: "emit",
1508
+ event: "ticket.created",
1509
+ payload: {
1510
+ ticketId: "{{ticket.id}}",
1511
+ title: "{{ticket.title}}",
1512
+ priority: "{{ticket.priority}}",
1513
+ source: "voice",
1514
+ userId: "{{input.userId}}"
1515
+ },
1516
+ next: "output"
1517
+ },
1518
+ {
1519
+ id: "output",
1520
+ name: "Prepare Output",
1521
+ kind: "transform",
1522
+ transforms: {
1523
+ "output.success": true,
1524
+ "output.ticketId": "{{ticket.id}}",
1525
+ "output.title": "{{ticket.title}}",
1526
+ "output.url": "{{ticket.url}}",
1527
+ "output.message": "Ticket created successfully"
1528
+ },
1529
+ next: null
1530
+ }
1531
+ ],
1532
+ outputs: {
1533
+ onCompleted: [
1534
+ {
1535
+ event: "voice.action.completed",
1536
+ payload: {
1537
+ action: "create_ticket",
1538
+ success: true,
1539
+ result: "{{output}}"
1540
+ }
1541
+ }
1542
+ ],
1543
+ onFailed: [
1544
+ {
1545
+ event: "voice.action.failed",
1546
+ payload: {
1547
+ action: "create_ticket",
1548
+ error: "{{error}}"
1549
+ }
1550
+ }
1551
+ ]
1552
+ },
1553
+ tags: ["voice", "tickets", "instant"]
1554
+ };
1555
+
1556
+ // src/voice/search-codebase.ts
1557
+ var voiceSearchCodebaseContract = {
1558
+ schemaVersion: "codmir.contract.v1",
1559
+ id: "voice.search-codebase",
1560
+ version: "1.0.0",
1561
+ title: "Search Codebase via Voice",
1562
+ description: "Searches the codebase for specific patterns, functions, or files",
1563
+ execution: {
1564
+ expectedDuration: "quick",
1565
+ mode: "sync",
1566
+ syncThreshold: 15e3,
1567
+ // 15 seconds max wait
1568
+ asyncAckMessage: "Searching the codebase now..."
1569
+ },
1570
+ triggers: [
1571
+ {
1572
+ type: "event",
1573
+ event: "voice.action.requested",
1574
+ where: { action: "search_codebase" }
1575
+ }
1576
+ ],
1577
+ permissions: [
1578
+ { kind: "repo:read", scope: "*" },
1579
+ { kind: "event:emit", pattern: "search.completed" }
1580
+ ],
1581
+ limits: {
1582
+ maxRunMs: 3e4,
1583
+ maxSteps: 5
1584
+ },
1585
+ entryStepId: "validate",
1586
+ steps: [
1587
+ {
1588
+ id: "validate",
1589
+ name: "Validate Input",
1590
+ kind: "transform",
1591
+ transforms: {
1592
+ "input.query": "{{trigger.payload.params.query}}",
1593
+ "input.fileTypes": "{{trigger.payload.params.file_types}}",
1594
+ "input.pathFilter": "{{trigger.payload.params.path_filter}}",
1595
+ "input.maxResults": "{{trigger.payload.params.max_results}}",
1596
+ "input.userId": "{{trigger.payload.userId}}"
1597
+ },
1598
+ next: "search"
1599
+ },
1600
+ {
1601
+ id: "search",
1602
+ name: "Search Codebase",
1603
+ kind: "task",
1604
+ task: "voice.search-codebase",
1605
+ input: {
1606
+ query: "{{input.query}}",
1607
+ fileTypes: "{{input.fileTypes}}",
1608
+ pathFilter: "{{input.pathFilter}}",
1609
+ maxResults: "{{input.maxResults}}"
1610
+ },
1611
+ saveAs: "searchResults",
1612
+ next: "output"
1613
+ },
1614
+ {
1615
+ id: "output",
1616
+ name: "Prepare Output",
1617
+ kind: "transform",
1618
+ transforms: {
1619
+ "output.success": true,
1620
+ "output.query": "{{input.query}}",
1621
+ "output.results": "{{searchResults.results}}",
1622
+ "output.total": "{{searchResults.total}}",
1623
+ "output.message": "Found {{searchResults.total}} results"
1624
+ },
1625
+ next: null
1626
+ }
1627
+ ],
1628
+ outputs: {
1629
+ onCompleted: [
1630
+ {
1631
+ event: "voice.action.completed",
1632
+ payload: {
1633
+ action: "search_codebase",
1634
+ success: true,
1635
+ result: "{{output}}"
1636
+ }
1637
+ }
1638
+ ],
1639
+ onFailed: [
1640
+ {
1641
+ event: "voice.action.failed",
1642
+ payload: {
1643
+ action: "search_codebase",
1644
+ error: "{{error}}"
1645
+ }
1646
+ }
1647
+ ]
1648
+ },
1649
+ tags: ["voice", "search", "code", "quick"]
1650
+ };
1651
+
1652
+ // src/voice/deploy-preview.ts
1653
+ var voiceDeployPreviewContract = {
1654
+ schemaVersion: "codmir.contract.v1",
1655
+ id: "voice.deploy-preview",
1656
+ version: "1.0.0",
1657
+ title: "Deploy Preview via Voice",
1658
+ description: "Deploys a preview environment for testing",
1659
+ execution: {
1660
+ expectedDuration: "long",
1661
+ mode: "async",
1662
+ interruptible: true,
1663
+ asyncAckMessage: "I'll start the deployment. This may take a few minutes - I'll notify you when it's ready."
1664
+ },
1665
+ triggers: [
1666
+ {
1667
+ type: "event",
1668
+ event: "voice.action.requested",
1669
+ where: { action: "deploy_preview" }
1670
+ }
1671
+ ],
1672
+ permissions: [
1673
+ { kind: "http:request", domains: ["api.vercel.com", "api.netlify.com", "api.railway.app"] },
1674
+ { kind: "repo:read", scope: "*" },
1675
+ { kind: "event:emit", pattern: "deploy.*" }
1676
+ ],
1677
+ limits: {
1678
+ maxRunMs: 6e5,
1679
+ // 10 minutes
1680
+ maxSteps: 10
1681
+ },
1682
+ entryStepId: "validate",
1683
+ steps: [
1684
+ {
1685
+ id: "validate",
1686
+ name: "Validate Input",
1687
+ kind: "transform",
1688
+ transforms: {
1689
+ "input.branch": "{{trigger.payload.params.branch}}",
1690
+ "input.environment": "{{trigger.payload.params.environment}}",
1691
+ "input.projectId": "{{trigger.payload.params.projectId}}",
1692
+ "input.userId": "{{trigger.payload.userId}}"
1693
+ },
1694
+ next: "confirm"
1695
+ },
1696
+ {
1697
+ id: "confirm",
1698
+ name: "Confirm Deployment",
1699
+ kind: "approval",
1700
+ prompt: "Deploy branch '{{input.branch}}' to {{input.environment}} environment?",
1701
+ approvers: ["{{input.userId}}"],
1702
+ nextOnApproved: "deploy",
1703
+ nextOnDenied: "cancelled",
1704
+ timeout: { ms: 6e4 },
1705
+ timeoutAction: "deny",
1706
+ context: {
1707
+ branch: "{{input.branch}}",
1708
+ environment: "{{input.environment}}"
1709
+ }
1710
+ },
1711
+ {
1712
+ id: "deploy",
1713
+ name: "Deploy to Preview",
1714
+ kind: "task",
1715
+ task: "voice.deploy-preview",
1716
+ input: {
1717
+ branch: "{{input.branch}}",
1718
+ environment: "{{input.environment}}",
1719
+ projectId: "{{input.projectId}}"
1720
+ },
1721
+ saveAs: "deployment",
1722
+ timeout: { ms: 3e5 },
1723
+ // 5 min timeout for deployment
1724
+ next: "notify"
1725
+ },
1726
+ {
1727
+ id: "notify",
1728
+ name: "Emit Deployment Event",
1729
+ kind: "emit",
1730
+ event: "deploy.completed",
1731
+ payload: {
1732
+ deploymentId: "{{deployment.id}}",
1733
+ url: "{{deployment.url}}",
1734
+ branch: "{{input.branch}}",
1735
+ environment: "{{input.environment}}",
1736
+ source: "voice",
1737
+ userId: "{{input.userId}}"
1738
+ },
1739
+ next: "output"
1740
+ },
1741
+ {
1742
+ id: "output",
1743
+ name: "Prepare Output",
1744
+ kind: "transform",
1745
+ transforms: {
1746
+ "output.success": true,
1747
+ "output.deploymentId": "{{deployment.id}}",
1748
+ "output.url": "{{deployment.url}}",
1749
+ "output.branch": "{{input.branch}}",
1750
+ "output.message": "Deployment complete! Preview available at {{deployment.url}}"
1751
+ },
1752
+ next: null
1753
+ },
1754
+ {
1755
+ id: "cancelled",
1756
+ name: "Handle Cancellation",
1757
+ kind: "transform",
1758
+ transforms: {
1759
+ "output.success": false,
1760
+ "output.message": "Deployment was cancelled"
1761
+ },
1762
+ next: null
1763
+ }
1764
+ ],
1765
+ outputs: {
1766
+ onCompleted: [
1767
+ {
1768
+ event: "voice.action.completed",
1769
+ payload: {
1770
+ action: "deploy_preview",
1771
+ success: true,
1772
+ result: "{{output}}"
1773
+ }
1774
+ }
1775
+ ],
1776
+ onFailed: [
1777
+ {
1778
+ event: "voice.action.failed",
1779
+ payload: {
1780
+ action: "deploy_preview",
1781
+ error: "{{error}}"
1782
+ }
1783
+ }
1784
+ ],
1785
+ onCancelled: [
1786
+ {
1787
+ event: "voice.action.cancelled",
1788
+ payload: {
1789
+ action: "deploy_preview",
1790
+ reason: "User cancelled or approval timeout"
1791
+ }
1792
+ }
1793
+ ]
1794
+ },
1795
+ tags: ["voice", "deploy", "preview", "long", "approval"]
1796
+ };
1797
+
1798
+ // src/voice/save-note.ts
1799
+ var voiceSaveNoteContract = {
1800
+ schemaVersion: "codmir.contract.v1",
1801
+ id: "voice.save-note",
1802
+ version: "1.0.0",
1803
+ title: "Save Note via Voice",
1804
+ description: "Saves a note or snippet for later reference",
1805
+ execution: {
1806
+ expectedDuration: "instant",
1807
+ mode: "sync",
1808
+ asyncAckMessage: "Saving your note..."
1809
+ },
1810
+ triggers: [
1811
+ {
1812
+ type: "event",
1813
+ event: "voice.action.requested",
1814
+ where: { action: "save_note" }
1815
+ }
1816
+ ],
1817
+ permissions: [
1818
+ { kind: "storage:write", scope: "notes" },
1819
+ { kind: "event:emit", pattern: "note.saved" }
1820
+ ],
1821
+ limits: {
1822
+ maxRunMs: 1e4,
1823
+ maxSteps: 4
1824
+ },
1825
+ entryStepId: "validate",
1826
+ steps: [
1827
+ {
1828
+ id: "validate",
1829
+ name: "Validate Input",
1830
+ kind: "transform",
1831
+ transforms: {
1832
+ "input.content": "{{trigger.payload.params.content}}",
1833
+ "input.title": "{{trigger.payload.params.title}}",
1834
+ "input.tags": "{{trigger.payload.params.tags}}",
1835
+ "input.userId": "{{trigger.payload.userId}}"
1836
+ },
1837
+ next: "save"
1838
+ },
1839
+ {
1840
+ id: "save",
1841
+ name: "Save Note",
1842
+ kind: "task",
1843
+ task: "voice.save-note",
1844
+ input: {
1845
+ content: "{{input.content}}",
1846
+ title: "{{input.title}}",
1847
+ tags: "{{input.tags}}",
1848
+ userId: "{{input.userId}}"
1849
+ },
1850
+ saveAs: "note",
1851
+ next: "output"
1852
+ },
1853
+ {
1854
+ id: "output",
1855
+ name: "Prepare Output",
1856
+ kind: "transform",
1857
+ transforms: {
1858
+ "output.success": true,
1859
+ "output.noteId": "{{note.id}}",
1860
+ "output.title": "{{note.title}}",
1861
+ "output.message": "Note saved successfully"
1862
+ },
1863
+ next: null
1864
+ }
1865
+ ],
1866
+ outputs: {
1867
+ onCompleted: [
1868
+ {
1869
+ event: "voice.action.completed",
1870
+ payload: {
1871
+ action: "save_note",
1872
+ success: true,
1873
+ result: "{{output}}"
1874
+ }
1875
+ }
1876
+ ],
1877
+ onFailed: [
1878
+ {
1879
+ event: "voice.action.failed",
1880
+ payload: {
1881
+ action: "save_note",
1882
+ error: "{{error}}"
1883
+ }
1884
+ }
1885
+ ]
1886
+ },
1887
+ tags: ["voice", "notes", "instant"]
1888
+ };
1889
+
1890
+ // src/voice/registry.ts
1891
+ var ACTION_TO_CONTRACT_ID = {
1892
+ create_ticket: "voice.create-ticket",
1893
+ update_ticket: "voice.update-ticket",
1894
+ search_codebase: "voice.search-codebase",
1895
+ search_documentation: "voice.search-documentation",
1896
+ analyze_code: "voice.analyze-code",
1897
+ suggest_fix: "voice.suggest-fix",
1898
+ run_query: "voice.run-query",
1899
+ get_metrics: "voice.get-metrics",
1900
+ deploy_preview: "voice.deploy-preview",
1901
+ check_deploy_status: "voice.check-deploy-status",
1902
+ send_notification: "voice.send-notification",
1903
+ create_summary: "voice.create-summary",
1904
+ set_reminder: "voice.set-reminder",
1905
+ save_note: "voice.save-note"
1906
+ };
1907
+ var VOICE_CONTRACTS = {
1908
+ "voice.create-ticket": voiceCreateTicketContract,
1909
+ "voice.search-codebase": voiceSearchCodebaseContract,
1910
+ "voice.deploy-preview": voiceDeployPreviewContract,
1911
+ "voice.save-note": voiceSaveNoteContract
1912
+ };
1913
+ function getVoiceContract(contractId) {
1914
+ return VOICE_CONTRACTS[contractId];
1915
+ }
1916
+ function getVoiceContractForAction(actionName) {
1917
+ const contractId = ACTION_TO_CONTRACT_ID[actionName];
1918
+ if (!contractId) return void 0;
1919
+ return VOICE_CONTRACTS[contractId];
1920
+ }
1921
+
1922
+ export { ContractValidationError, DEFAULT_GRACE_EXTENSIONS, DURATION_SYNC_THRESHOLDS, GRACE_PRINCIPLES, VOICE_CONTRACTS, and, calculateRiskScore, cloneJson, codeReviewContract, contains, deepMergeJson, deleteByPath, endsWith, eq, evalCondition, exists, extractTemplatePath, getByPath, getVoiceContract, getVoiceContractForAction, gt, gte, hasPermission, isTemplate, lt, lte, mergeJson, neq, not, or, prAnalysisContract, prAnalysisWithLlmContract, requiresHumanReview, resolveStepInput, resolveTemplates, setByPath, startsWith, ticketTriageContract, validateCondition, validateContract, validateContractSafe, voiceCreateTicketContract, voiceDeployPreviewContract, voiceSaveNoteContract, voiceSearchCodebaseContract };
1923
+ //# sourceMappingURL=index.js.map
1924
+ //# sourceMappingURL=index.js.map