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