@gitgov/core 1.3.0 → 1.4.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/src/index.js CHANGED
@@ -76,17 +76,19 @@ var actor_record_schema_default = {
76
76
  },
77
77
  publicKey: {
78
78
  type: "string",
79
- description: "The Ed25519 public key (base64 encoded) for verifying the actor's signatures."
79
+ minLength: 44,
80
+ maxLength: 44,
81
+ description: "The Ed25519 public key (base64 encoded, 44 characters) for verifying the actor's signatures."
80
82
  },
81
83
  roles: {
82
84
  type: "array",
83
85
  items: {
84
86
  type: "string",
85
- pattern: "^[a-z0-9:]+$"
87
+ pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$"
86
88
  },
87
89
  minItems: 1,
88
90
  uniqueItems: true,
89
- description: "List of capacity roles defining the actor's skills and permissions.",
91
+ description: "List of capacity roles defining the actor's skills and permissions. Uses hierarchical format with colons.",
90
92
  examples: [
91
93
  [
92
94
  "developer:backend:go",
@@ -104,7 +106,7 @@ var actor_record_schema_default = {
104
106
  "revoked"
105
107
  ],
106
108
  default: "active",
107
- description: "The lifecycle status of the actor."
109
+ description: "Optional. The lifecycle status of the actor. Defaults to 'active' if not specified."
108
110
  },
109
111
  supersededBy: {
110
112
  type: "string",
@@ -136,7 +138,6 @@ var agent_record_schema_default = {
136
138
  type: "object",
137
139
  required: [
138
140
  "id",
139
- "guild",
140
141
  "engine"
141
142
  ],
142
143
  properties: {
@@ -153,18 +154,10 @@ var agent_record_schema_default = {
153
154
  ],
154
155
  default: "active"
155
156
  },
156
- guild: {
157
- type: "string",
158
- enum: [
159
- "design",
160
- "intelligence",
161
- "strategy",
162
- "operations",
163
- "quality"
164
- ]
165
- },
166
157
  triggers: {
167
158
  type: "array",
159
+ default: [],
160
+ description: "Optional list of triggers that activate the agent.\nAdditional fields are allowed and depend on trigger type:\n- webhook triggers: 'event' (event identifier), 'filter' (condition)\n- scheduled triggers: 'cron' (cron expression)\n- manual triggers: 'command' (example CLI command)\n",
168
161
  items: {
169
162
  type: "object",
170
163
  properties: {
@@ -174,18 +167,22 @@ var agent_record_schema_default = {
174
167
  "manual",
175
168
  "webhook",
176
169
  "scheduled"
177
- ]
170
+ ],
171
+ description: "Type of trigger that activates the agent"
178
172
  }
179
173
  },
180
174
  required: [
181
175
  "type"
182
- ]
176
+ ],
177
+ additionalProperties: true
183
178
  }
184
179
  },
185
180
  knowledge_dependencies: {
186
181
  type: "array",
182
+ default: [],
187
183
  items: {
188
- type: "string"
184
+ type: "string",
185
+ description: "Glob patterns for blueprint files this agent needs access to"
189
186
  }
190
187
  },
191
188
  prompt_engine_requirements: {
@@ -205,6 +202,11 @@ var agent_record_schema_default = {
205
202
  }
206
203
  }
207
204
  },
205
+ metadata: {
206
+ type: "object",
207
+ description: "Optional framework-specific or deployment-specific metadata for agent extensions.\nCommon use cases: framework identification (langchain, google-adk), deployment info (provider, image, region),\ncost tracking (cost_per_invocation, currency), tool capabilities, maintainer info.\nThis field does NOT affect agent execution - it is purely informational.\n",
208
+ additionalProperties: true
209
+ },
208
210
  engine: {
209
211
  type: "object",
210
212
  oneOf: [
@@ -212,63 +214,365 @@ var agent_record_schema_default = {
212
214
  required: [
213
215
  "type"
214
216
  ],
217
+ additionalProperties: false,
215
218
  properties: {
216
219
  type: {
217
220
  const: "local"
218
221
  },
219
222
  runtime: {
220
- type: "string"
223
+ type: "string",
224
+ description: "Runtime environment (typescript, python, etc.)"
221
225
  },
222
226
  entrypoint: {
223
- type: "string"
227
+ type: "string",
228
+ description: "Path to the agent entry file"
224
229
  },
225
230
  function: {
226
- type: "string"
231
+ type: "string",
232
+ description: "Function name to invoke"
227
233
  }
228
234
  }
229
235
  },
230
236
  {
231
237
  required: [
232
- "type"
238
+ "type",
239
+ "url"
233
240
  ],
241
+ additionalProperties: false,
234
242
  properties: {
235
243
  type: {
236
244
  const: "api"
237
245
  },
238
246
  url: {
239
- type: "string"
247
+ type: "string",
248
+ format: "uri",
249
+ description: "HTTP endpoint for the agent"
240
250
  },
241
251
  method: {
242
252
  type: "string",
243
253
  enum: [
244
254
  "POST",
245
255
  "GET"
246
- ]
256
+ ],
257
+ default: "POST"
247
258
  },
248
259
  auth: {
249
- type: "object"
260
+ type: "object",
261
+ description: "Authentication configuration for API requests",
262
+ additionalProperties: true,
263
+ properties: {
264
+ type: {
265
+ type: "string",
266
+ enum: [
267
+ "bearer",
268
+ "oauth",
269
+ "api-key",
270
+ "actor-signature"
271
+ ],
272
+ description: "Authentication type. 'actor-signature' uses the agent's ActorRecord keypair to sign requests."
273
+ },
274
+ secret_key: {
275
+ type: "string",
276
+ description: "Reference to secret in Secret Manager (for bearer/api-key/oauth auth types)"
277
+ },
278
+ token: {
279
+ type: "string",
280
+ description: "Direct token value (not recommended for production, use secret_key instead)"
281
+ }
282
+ }
250
283
  }
251
284
  }
252
285
  },
253
286
  {
254
287
  required: [
255
- "type"
288
+ "type",
289
+ "url"
256
290
  ],
291
+ additionalProperties: false,
257
292
  properties: {
258
293
  type: {
259
294
  const: "mcp"
260
295
  },
261
296
  url: {
262
- type: "string"
297
+ type: "string",
298
+ format: "uri",
299
+ description: "MCP server endpoint"
263
300
  },
264
301
  auth: {
265
- type: "object"
302
+ type: "object",
303
+ description: "Authentication configuration for MCP server",
304
+ additionalProperties: true,
305
+ properties: {
306
+ type: {
307
+ type: "string",
308
+ enum: [
309
+ "bearer",
310
+ "oauth",
311
+ "api-key",
312
+ "actor-signature"
313
+ ],
314
+ description: "Authentication type. 'actor-signature' uses the agent's ActorRecord keypair to sign requests."
315
+ },
316
+ secret_key: {
317
+ type: "string",
318
+ description: "Reference to secret in Secret Manager (for bearer/api-key/oauth auth types)"
319
+ },
320
+ token: {
321
+ type: "string",
322
+ description: "Direct token value (not recommended for production, use secret_key instead)"
323
+ }
324
+ }
325
+ }
326
+ }
327
+ },
328
+ {
329
+ required: [
330
+ "type"
331
+ ],
332
+ additionalProperties: false,
333
+ properties: {
334
+ type: {
335
+ const: "custom"
336
+ },
337
+ protocol: {
338
+ type: "string",
339
+ description: "Custom protocol identifier (e.g., 'a2a', 'grpc')"
340
+ },
341
+ config: {
342
+ type: "object",
343
+ description: "Protocol-specific configuration"
266
344
  }
267
345
  }
268
346
  }
269
347
  ]
270
348
  }
271
- }
349
+ },
350
+ examples: [
351
+ {
352
+ id: "agent:scribe",
353
+ status: "active",
354
+ engine: {
355
+ type: "local",
356
+ runtime: "typescript",
357
+ entrypoint: "packages/agents/scribe/index.ts",
358
+ function: "runScribe"
359
+ },
360
+ metadata: {
361
+ purpose: "documentation-generation",
362
+ maintainer: "team:platform"
363
+ },
364
+ triggers: [
365
+ {
366
+ type: "manual"
367
+ }
368
+ ],
369
+ knowledge_dependencies: [
370
+ "packages/blueprints/**/*.md"
371
+ ]
372
+ },
373
+ {
374
+ id: "agent:langchain-analyzer",
375
+ status: "active",
376
+ engine: {
377
+ type: "api",
378
+ url: "https://langchain-service-xyz.a.run.app/analyze",
379
+ method: "POST",
380
+ auth: {
381
+ type: "actor-signature"
382
+ }
383
+ },
384
+ metadata: {
385
+ framework: "langchain",
386
+ version: "0.2.0",
387
+ model: "gpt-4-turbo",
388
+ deployment: {
389
+ provider: "gcp",
390
+ service: "cloud-run",
391
+ region: "us-central1"
392
+ },
393
+ cost_per_invocation: 0.03,
394
+ currency: "USD"
395
+ },
396
+ triggers: [
397
+ {
398
+ type: "webhook",
399
+ event: "task.ready"
400
+ }
401
+ ],
402
+ knowledge_dependencies: [
403
+ "docs/architecture/**/*.md"
404
+ ]
405
+ },
406
+ {
407
+ id: "agent:sentiment-analyzer",
408
+ status: "active",
409
+ engine: {
410
+ type: "api",
411
+ url: "http://sentiment-analyzer:8082/analyze",
412
+ method: "POST",
413
+ auth: {
414
+ type: "actor-signature"
415
+ }
416
+ },
417
+ metadata: {
418
+ framework: "google-adk",
419
+ version: "1.0.0",
420
+ model: "gemini-pro",
421
+ deployment: {
422
+ runtime: "docker",
423
+ image: "gitgov/sentiment-analyzer:v1.2.0",
424
+ port: 8082
425
+ },
426
+ max_tokens: 1024
427
+ },
428
+ triggers: [
429
+ {
430
+ type: "webhook",
431
+ event: "feedback.created"
432
+ }
433
+ ]
434
+ },
435
+ {
436
+ id: "agent:cursor-reviewer",
437
+ status: "active",
438
+ engine: {
439
+ type: "mcp",
440
+ url: "http://localhost:8083/mcp",
441
+ auth: {
442
+ type: "actor-signature"
443
+ }
444
+ },
445
+ metadata: {
446
+ ide: "cursor",
447
+ tool: "code-review",
448
+ accepts_tools: [
449
+ "review",
450
+ "refactor",
451
+ "test"
452
+ ]
453
+ },
454
+ knowledge_dependencies: [
455
+ "packages/**/*.ts",
456
+ "packages/**/*.tsx"
457
+ ],
458
+ triggers: [
459
+ {
460
+ type: "webhook",
461
+ event: "task.status.ready"
462
+ }
463
+ ]
464
+ },
465
+ {
466
+ id: "agent:deepl-translator",
467
+ status: "active",
468
+ engine: {
469
+ type: "api",
470
+ url: "https://api.deepl.com/v2/translate",
471
+ method: "POST",
472
+ auth: {
473
+ type: "bearer",
474
+ secret_key: "DEEPL_API_KEY"
475
+ }
476
+ },
477
+ metadata: {
478
+ provider: "deepl",
479
+ supported_languages: [
480
+ "EN",
481
+ "ES",
482
+ "FR",
483
+ "DE",
484
+ "PT"
485
+ ],
486
+ max_chars_per_request: 5e3
487
+ },
488
+ triggers: [
489
+ {
490
+ type: "manual"
491
+ }
492
+ ]
493
+ },
494
+ {
495
+ id: "agent:coordinator",
496
+ status: "active",
497
+ engine: {
498
+ type: "custom",
499
+ protocol: "a2a",
500
+ config: {
501
+ endpoint: "https://agent-hub.gitgov.io/a2a",
502
+ version: "draft-2025-01",
503
+ capabilities: [
504
+ "task-delegation",
505
+ "status-sync",
506
+ "feedback-loop"
507
+ ]
508
+ }
509
+ },
510
+ metadata: {
511
+ purpose: "multi-agent-orchestration",
512
+ experimental: true
513
+ },
514
+ triggers: [
515
+ {
516
+ type: "scheduled",
517
+ cron: "0 */4 * * *"
518
+ }
519
+ ]
520
+ },
521
+ {
522
+ id: "agent:minimal-watcher",
523
+ engine: {
524
+ type: "local"
525
+ }
526
+ },
527
+ {
528
+ id: "agent:local-mcp-server",
529
+ status: "active",
530
+ engine: {
531
+ type: "mcp",
532
+ url: "http://localhost:9000/mcp"
533
+ },
534
+ knowledge_dependencies: [
535
+ "packages/blueprints/**/*.md"
536
+ ],
537
+ triggers: [
538
+ {
539
+ type: "manual"
540
+ }
541
+ ]
542
+ },
543
+ {
544
+ id: "agent:code-reviewer",
545
+ status: "active",
546
+ engine: {
547
+ type: "local",
548
+ runtime: "typescript",
549
+ entrypoint: "packages/agents/code-reviewer/index.ts",
550
+ function: "reviewCode"
551
+ },
552
+ prompt_engine_requirements: {
553
+ roles: [
554
+ "code-reviewer",
555
+ "security-auditor"
556
+ ],
557
+ skills: [
558
+ "typescript",
559
+ "security-best-practices",
560
+ "code-quality-analysis"
561
+ ]
562
+ },
563
+ knowledge_dependencies: [
564
+ "packages/**/*.ts",
565
+ "packages/**/*.tsx"
566
+ ],
567
+ triggers: [
568
+ {
569
+ type: "webhook",
570
+ event: "pull-request.opened",
571
+ filter: "branch:main"
572
+ }
573
+ ]
574
+ }
575
+ ]
272
576
  };
273
577
 
274
578
  // src/schemas/generated/changelog_record_schema.json
@@ -276,226 +580,178 @@ var changelog_record_schema_default = {
276
580
  $schema: "http://json-schema.org/draft-07/schema#",
277
581
  $id: "changelog_record_schema.json",
278
582
  title: "ChangelogRecord",
279
- description: "Canonical schema for changelog records - Enterprise Grade System Historian",
583
+ description: "Canonical schema for changelog records - aggregates N tasks into 1 release note",
280
584
  additionalProperties: false,
281
585
  type: "object",
282
586
  required: [
283
587
  "id",
284
- "entityType",
285
- "entityId",
286
- "changeType",
287
588
  "title",
288
589
  "description",
289
- "timestamp",
290
- "trigger",
291
- "triggeredBy",
292
- "reason",
293
- "riskLevel"
590
+ "relatedTasks",
591
+ "completedAt"
294
592
  ],
295
593
  properties: {
296
594
  id: {
297
595
  type: "string",
298
- pattern: "^\\d{10}-changelog-[a-z]+-[a-z0-9-]{1,50}$",
299
- description: "Unique identifier for the changelog entry"
300
- },
301
- entityType: {
302
- type: "string",
303
- enum: [
304
- "task",
305
- "cycle",
306
- "agent",
307
- "system",
308
- "configuration"
309
- ],
310
- description: "Type of the primary entity that changed"
311
- },
312
- entityId: {
313
- type: "string",
314
- description: "ID of the primary entity that changed"
315
- },
316
- changeType: {
317
- type: "string",
318
- enum: [
319
- "creation",
320
- "completion",
321
- "update",
322
- "deletion",
323
- "hotfix"
324
- ],
325
- description: "The nature of the change"
596
+ pattern: "^\\d{10}-changelog-[a-z0-9-]{1,50}$",
597
+ maxLength: 71,
598
+ description: "Unique identifier for the changelog entry",
599
+ examples: [
600
+ "1752707800-changelog-sistema-autenticacion-v1",
601
+ "1752707800-changelog-sprint-24-api-performance"
602
+ ]
326
603
  },
327
604
  title: {
328
605
  type: "string",
329
606
  minLength: 10,
330
607
  maxLength: 150,
331
- description: "Executive title of the change"
608
+ description: "Executive title of the deliverable",
609
+ examples: [
610
+ "Sistema de Autenticaci\xF3n Completo v1.0",
611
+ "Sprint 24 - Performance Optimizations"
612
+ ]
332
613
  },
333
614
  description: {
334
615
  type: "string",
335
616
  minLength: 20,
336
617
  maxLength: 5e3,
337
- description: "Detailed description of the change and its impact"
618
+ description: "Detailed description of the value delivered, including key decisions and impact"
338
619
  },
339
- timestamp: {
620
+ relatedTasks: {
621
+ type: "array",
622
+ items: {
623
+ type: "string",
624
+ pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$"
625
+ },
626
+ minItems: 1,
627
+ description: "IDs of tasks that compose this deliverable (minimum 1 required)"
628
+ },
629
+ completedAt: {
340
630
  type: "number",
341
631
  minimum: 0,
342
- description: "Unix timestamp in seconds when the change occurred"
343
- },
344
- trigger: {
345
- type: "string",
346
- enum: [
347
- "manual",
348
- "automated",
349
- "emergency"
350
- ],
351
- description: "How the change was initiated"
632
+ description: "Unix timestamp in seconds when the deliverable was completed"
352
633
  },
353
- triggeredBy: {
354
- type: "string",
355
- description: "Actor or agent ID that initiated the change"
634
+ relatedCycles: {
635
+ type: "array",
636
+ items: {
637
+ type: "string",
638
+ pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$"
639
+ },
640
+ default: [],
641
+ description: "Optional IDs of cycles related to this deliverable"
356
642
  },
357
- reason: {
358
- type: "string",
359
- minLength: 10,
360
- maxLength: 1e3,
361
- description: "Why the change was made"
643
+ relatedExecutions: {
644
+ type: "array",
645
+ items: {
646
+ type: "string",
647
+ pattern: "^\\d{10}-exec-[a-z0-9-]{1,50}$"
648
+ },
649
+ default: [],
650
+ description: "Optional IDs of key execution records related to this work"
362
651
  },
363
- riskLevel: {
652
+ version: {
364
653
  type: "string",
365
- enum: [
366
- "low",
367
- "medium",
368
- "high",
369
- "critical"
370
- ],
371
- description: "Risk level of the change"
654
+ minLength: 1,
655
+ maxLength: 50,
656
+ description: "Optional version or release identifier (e.g., 'v1.0.0', 'sprint-24')",
657
+ examples: [
658
+ "v1.0.0",
659
+ "v2.1.3",
660
+ "sprint-24"
661
+ ]
372
662
  },
373
- affectedSystems: {
663
+ tags: {
374
664
  type: "array",
375
665
  items: {
376
- type: "string"
666
+ type: "string",
667
+ pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$"
377
668
  },
378
- description: "IDs of systems impacted by this change"
379
- },
380
- usersAffected: {
381
- type: "number",
382
- minimum: 0,
383
- description: "Number of users impacted by this change"
384
- },
385
- downtime: {
386
- type: "number",
387
- minimum: 0,
388
- description: "Downtime in seconds caused by this change"
669
+ default: [],
670
+ description: "Optional tags for categorization (e.g., 'feature:auth', 'bugfix', 'security')"
389
671
  },
390
- files: {
672
+ commits: {
391
673
  type: "array",
392
674
  items: {
393
- type: "string"
675
+ type: "string",
676
+ maxLength: 100
394
677
  },
395
- description: "List of main files that were created or modified"
678
+ default: [],
679
+ description: "Optional list of git commit hashes related to this deliverable"
396
680
  },
397
- commits: {
681
+ files: {
398
682
  type: "array",
399
683
  items: {
400
- type: "string"
684
+ type: "string",
685
+ maxLength: 500
401
686
  },
402
- description: "List of git commit hashes related to this change"
687
+ default: [],
688
+ description: "Optional list of main files that were created or modified"
403
689
  },
404
- rollbackInstructions: {
690
+ notes: {
405
691
  type: "string",
406
- minLength: 20,
407
- maxLength: 2e3,
408
- description: "Step-by-step instructions to rollback this change"
409
- },
410
- references: {
411
- type: "object",
412
- additionalProperties: false,
413
- properties: {
414
- tasks: {
415
- type: "array",
416
- items: {
417
- type: "string"
418
- },
419
- description: "IDs of related TaskRecords"
420
- },
421
- cycles: {
422
- type: "array",
423
- items: {
424
- type: "string"
425
- },
426
- description: "IDs of related CycleRecords"
427
- },
428
- executions: {
429
- type: "array",
430
- items: {
431
- type: "string"
432
- },
433
- description: "IDs of related ExecutionRecords"
434
- },
435
- changelogs: {
436
- type: "array",
437
- items: {
438
- type: "string"
439
- },
440
- description: "IDs of related ChangelogRecords"
441
- }
442
- },
443
- description: "Cross-references to other GitGovernance records"
692
+ maxLength: 3e3,
693
+ description: "Optional additional context, decisions, or learnings"
444
694
  }
445
695
  },
446
696
  examples: [
447
697
  {
448
- id: "1752707800-changelog-task-user-dashboard",
449
- entityType: "task",
450
- entityId: "1752274500-task-user-dashboard",
451
- changeType: "completion",
452
- title: "User Dashboard Implementation Completed",
453
- description: "Responsive dashboard with real-time analytics, user preferences, and notification center. All acceptance criteria met with 95% test coverage.",
454
- timestamp: 1752707800,
455
- trigger: "manual",
456
- triggeredBy: "human:senior-dev",
457
- reason: "All acceptance criteria met, code review passed, ready for production",
458
- riskLevel: "low",
459
- files: [
460
- "src/pages/Dashboard.tsx",
461
- "src/components/Analytics.tsx"
698
+ id: "1752707800-changelog-sistema-autenticacion-v1",
699
+ title: "Sistema de Autenticaci\xF3n Completo v1.0",
700
+ description: "Implementaci\xF3n completa del sistema de autenticaci\xF3n con OAuth2, 2FA via TOTP, recuperaci\xF3n de contrase\xF1a, y UI responsive. Incluye tests E2E completos (95% coverage) y documentaci\xF3n t\xE9cnica actualizada.",
701
+ relatedTasks: [
702
+ "1752274500-task-crear-ui-login",
703
+ "1752274600-task-integrar-oauth2-backend",
704
+ "1752274700-task-implementar-2fa-totp",
705
+ "1752274800-task-tests-e2e-auth",
706
+ "1752274900-task-documentar-flujo-auth"
707
+ ],
708
+ completedAt: 1752707800,
709
+ relatedCycles: [
710
+ "1752200000-cycle-q1-auth-milestone"
711
+ ],
712
+ relatedExecutions: [
713
+ "1752274550-exec-analisis-auth-providers",
714
+ "1752707750-exec-final-integration-test"
715
+ ],
716
+ version: "v1.0.0",
717
+ tags: [
718
+ "feature:auth",
719
+ "security",
720
+ "frontend",
721
+ "backend"
462
722
  ],
463
723
  commits: [
464
724
  "abc123def",
465
- "456ghi789"
725
+ "456ghi789",
726
+ "jkl012mno"
466
727
  ],
467
- references: {
468
- tasks: [
469
- "1752274500-task-user-dashboard"
470
- ],
471
- executions: [
472
- "1752707750-exec-dashboard-implementation"
473
- ]
474
- }
728
+ files: [
729
+ "src/pages/Login.tsx",
730
+ "src/services/auth.ts",
731
+ "src/components/TwoFactorSetup.tsx",
732
+ "e2e/auth.spec.ts"
733
+ ],
734
+ notes: "Decisi\xF3n t\xE9cnica: Usamos NextAuth.js despu\xE9s de evaluar Passport.js. El 2FA se implement\xF3 con TOTP (Google Authenticator compatible) en lugar de SMS por seguridad y costo."
475
735
  },
476
736
  {
477
- id: "1752707900-changelog-system-payment-gateway",
478
- entityType: "system",
479
- entityId: "payment-gateway",
480
- changeType: "hotfix",
481
- title: "Critical Payment Timeout Fix",
482
- description: "Fixed 15% payment failure rate by increasing timeout from 5s to 30s and adding circuit breaker pattern. Emergency response to third-party API latency spike.",
483
- timestamp: 1752707900,
484
- trigger: "emergency",
485
- triggeredBy: "human:on-call-engineer",
486
- reason: "Payment failures spiked to 15% due to third-party API latency increase",
487
- riskLevel: "critical",
488
- affectedSystems: [
489
- "payment-gateway",
490
- "order-service",
491
- "notification-service"
737
+ id: "1752707900-changelog-hotfix-payment-timeout",
738
+ title: "Hotfix: Critical Payment Timeout Fix",
739
+ description: "Fixed critical payment timeout issue affecting 15% of transactions. Increased timeout from 5s to 30s and added circuit breaker pattern for third-party API calls.",
740
+ relatedTasks: [
741
+ "1752707850-task-fix-payment-timeout",
742
+ "1752707870-task-add-circuit-breaker"
743
+ ],
744
+ completedAt: 1752707900,
745
+ version: "v1.2.1",
746
+ tags: [
747
+ "hotfix",
748
+ "critical",
749
+ "payment"
492
750
  ],
493
- usersAffected: 25e3,
494
- downtime: 0,
495
- rollbackInstructions: "1. Revert to payment-gateway:v2.1.4\n2. kubectl rollout restart deployment/payment-gateway\n3. Monitor success rate for 10 minutes",
496
751
  commits: [
497
- "abc123def"
498
- ]
752
+ "xyz789abc"
753
+ ],
754
+ notes: "Emergency response to production incident. Deployed to production within 2 hours."
499
755
  }
500
756
  ]
501
757
  };
@@ -505,7 +761,7 @@ var cycle_record_schema_default = {
505
761
  $schema: "http://json-schema.org/draft-07/schema#",
506
762
  $id: "cycle_record_schema.json",
507
763
  title: "CycleRecord",
508
- description: "Canonical schema for cycle records (sprints, milestones)",
764
+ description: "Canonical schema for cycle records - strategic grouping of work",
509
765
  additionalProperties: false,
510
766
  type: "object",
511
767
  required: [
@@ -517,13 +773,24 @@ var cycle_record_schema_default = {
517
773
  id: {
518
774
  type: "string",
519
775
  pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$",
520
- description: "Unique identifier for the cycle"
776
+ maxLength: 67,
777
+ description: "Unique identifier for the cycle (10 timestamp + 1 dash + 5 'cycle' + 1 dash + max 50 slug = 67 max)",
778
+ examples: [
779
+ "1754400000-cycle-sprint-24-api-performance",
780
+ "1754500000-cycle-auth-system-v2",
781
+ "1754600000-cycle-q4-2025-growth"
782
+ ]
521
783
  },
522
784
  title: {
523
785
  type: "string",
524
786
  minLength: 1,
525
787
  maxLength: 256,
526
- description: "Human-readable title for the cycle (e.g., 'Sprint 24.08')"
788
+ description: "Human-readable title for the cycle (e.g., 'Sprint 24', 'Auth v2.0', 'Q4 2025')",
789
+ examples: [
790
+ "Sprint 24 - API Performance",
791
+ "Authentication System v2.0",
792
+ "Q4 2025 - Growth & Scale"
793
+ ]
527
794
  },
528
795
  status: {
529
796
  type: "string",
@@ -539,45 +806,114 @@ var cycle_record_schema_default = {
539
806
  type: "array",
540
807
  items: {
541
808
  type: "string",
542
- pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$"
543
- }
809
+ pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$",
810
+ maxLength: 66
811
+ },
812
+ default: [],
813
+ description: "Optional array of Task IDs that belong to this cycle. Can be empty for cycles that only contain child cycles. (10 timestamp + 1 dash + 4 'task' + 1 dash + max 50 slug = 66 max)",
814
+ examples: [
815
+ [
816
+ "1752274500-task-optimizar-endpoint-search",
817
+ "1752360900-task-anadir-cache-a-redis"
818
+ ]
819
+ ]
544
820
  },
545
821
  childCycleIds: {
546
822
  type: "array",
547
823
  items: {
548
824
  type: "string",
549
- pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$"
825
+ pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$",
826
+ maxLength: 67
550
827
  },
551
- description: "An optional array of Cycle IDs that are children of this cycle, allowing for hierarchies."
828
+ default: [],
829
+ description: "Optional array of Cycle IDs that are children of this cycle, allowing for hierarchies (e.g., Q1 containing Sprint 1, Sprint 2, Sprint 3). (10 timestamp + 1 dash + 5 'cycle' + 1 dash + max 50 slug = 67 max)",
830
+ examples: [
831
+ [
832
+ "1754400000-cycle-sprint-24",
833
+ "1754500000-cycle-sprint-25"
834
+ ]
835
+ ]
552
836
  },
553
837
  tags: {
554
838
  type: "array",
555
839
  items: {
556
840
  type: "string",
557
- pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$"
841
+ pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$",
842
+ maxLength: 100
558
843
  },
559
844
  default: [],
560
- description: "Optional list of key:value tags for categorization (e.g., 'roadmap:q4', 'team:alpha')."
845
+ description: "Optional list of key:value tags for categorization (e.g., 'roadmap:q4', 'team:alpha', 'okr:growth').",
846
+ examples: [
847
+ [
848
+ "roadmap:q4",
849
+ "team:backend"
850
+ ],
851
+ [
852
+ "sprint:24",
853
+ "focus:performance"
854
+ ],
855
+ [
856
+ "milestone:v2",
857
+ "security"
858
+ ]
859
+ ]
561
860
  },
562
861
  notes: {
563
862
  type: "string",
863
+ minLength: 0,
564
864
  maxLength: 1e4,
565
- description: "An optional description of the cycle's goals and objectives"
865
+ description: "Optional description of the cycle's goals, objectives, and context"
566
866
  }
567
867
  },
568
868
  examples: [
569
869
  {
570
- id: "1754400000-cycle-sprint-de-q4-api-performance",
571
- title: "Sprint de Q4 - API Performance",
870
+ id: "1754400000-cycle-sprint-24-api-performance",
871
+ title: "Sprint 24 - API Performance",
572
872
  status: "active",
573
873
  taskIds: [
574
874
  "1752274500-task-optimizar-endpoint-search",
575
- "1752360900-task-anadir-cache-a-redis"
875
+ "1752360900-task-anadir-cache-a-redis",
876
+ "1752447300-task-implementar-rate-limiting"
576
877
  ],
577
878
  tags: [
578
- "roadmap:q4"
879
+ "sprint:24",
880
+ "team:backend",
881
+ "focus:performance"
579
882
  ],
580
- notes: "Objetivo: Reducir la latencia p95 de la API por debajo de 200ms."
883
+ notes: "Objetivo: Reducir la latencia p95 de la API por debajo de 200ms y preparar infraestructura para Black Friday."
884
+ },
885
+ {
886
+ id: "1754500000-cycle-auth-system-v2",
887
+ title: "Authentication System v2.0",
888
+ status: "planning",
889
+ taskIds: [
890
+ "1752274500-task-oauth2-integration",
891
+ "1752360900-task-2fa-implementation",
892
+ "1752447300-task-password-recovery",
893
+ "1752533700-task-session-management"
894
+ ],
895
+ tags: [
896
+ "milestone:v2",
897
+ "security",
898
+ "feature:auth"
899
+ ],
900
+ notes: "Milestone mayor: Sistema completo de autenticaci\xF3n con OAuth2, 2FA, y gesti\xF3n avanzada de sesiones. Cr\xEDtico para lanzamiento Q4."
901
+ },
902
+ {
903
+ id: "1754600000-cycle-q4-2025-growth",
904
+ title: "Q4 2025 - Growth & Scale",
905
+ status: "active",
906
+ childCycleIds: [
907
+ "1754400000-cycle-sprint-24-api-performance",
908
+ "1754500000-cycle-auth-system-v2",
909
+ "1754650000-cycle-mobile-app-launch"
910
+ ],
911
+ tags: [
912
+ "roadmap:q4",
913
+ "strategy:growth",
914
+ "okr:scale-to-1m-users"
915
+ ],
916
+ notes: "Objetivo trimestral: Escalar a 1M usuarios activos. Incluye mejoras de performance, nuevo sistema de auth, y lanzamiento de app m\xF3vil."
581
917
  }
582
918
  ]
583
919
  };
@@ -636,40 +972,42 @@ var embedded_metadata_schema_default = {
636
972
  properties: {
637
973
  keyId: {
638
974
  type: "string",
639
- description: "The Actor ID of the signer."
975
+ pattern: "^(human|agent)(:[a-z0-9-]+)+$",
976
+ description: "The Actor ID of the signer (must match ActorRecord.id pattern)."
640
977
  },
641
978
  role: {
642
979
  type: "string",
643
- description: "The context role of the signature (e.g., 'author')."
980
+ pattern: "^([a-z-]+|custom:[a-z0-9-]+)$",
981
+ minLength: 1,
982
+ maxLength: 50,
983
+ description: "The context role of the signature (e.g., 'author', 'reviewer', 'auditor', or 'custom:*')."
984
+ },
985
+ notes: {
986
+ type: "string",
987
+ minLength: 1,
988
+ maxLength: 1e3,
989
+ description: "Human-readable note from the signer. Part of the signature digest."
644
990
  },
645
991
  signature: {
646
992
  type: "string",
647
- description: "The Ed25519 signature (base64 encoded) of the signature digest."
993
+ pattern: "^[A-Za-z0-9+/]{86}==$",
994
+ description: "The Ed25519 signature (base64 encoded, 88 chars with padding) of the signature digest."
648
995
  },
649
996
  timestamp: {
650
997
  type: "integer",
651
998
  description: "Unix timestamp of the signature."
652
- },
653
- timestamp_iso: {
654
- type: "string",
655
- description: "ISO 8601 timestamp of the signature."
656
999
  }
657
1000
  },
658
1001
  required: [
659
1002
  "keyId",
660
1003
  "role",
1004
+ "notes",
661
1005
  "signature",
662
- "timestamp",
663
- "timestamp_iso"
664
- ]
1006
+ "timestamp"
1007
+ ],
1008
+ additionalProperties: false
665
1009
  },
666
1010
  description: "An array of one or more signature objects."
667
- },
668
- audit: {
669
- type: "string",
670
- minLength: 1,
671
- maxLength: 3e3,
672
- description: "A human-readable audit stamp (e.g., from gitgov audit)."
673
1011
  }
674
1012
  },
675
1013
  required: [
@@ -869,33 +1207,95 @@ var embedded_metadata_schema_default = {
869
1207
  }
870
1208
  }
871
1209
  },
872
- else: false
873
- }
874
- ],
875
- examples: [
1210
+ else: false
1211
+ }
1212
+ ],
1213
+ examples: [
1214
+ {
1215
+ header: {
1216
+ version: "1.0",
1217
+ type: "task",
1218
+ payloadChecksum: "a1b2c3d4e5f6...",
1219
+ signatures: [
1220
+ {
1221
+ keyId: "human:lead-dev",
1222
+ role: "author",
1223
+ notes: "Initial task creation for OAuth 2.0 implementation",
1224
+ signature: "...",
1225
+ timestamp: 1752274500
1226
+ }
1227
+ ]
1228
+ },
1229
+ payload: {
1230
+ id: "1752274500-task-implementar-auth",
1231
+ status: "pending",
1232
+ priority: "high",
1233
+ description: "Implementar autenticaci\xF3n OAuth 2.0.",
1234
+ tags: [
1235
+ "skill:go",
1236
+ "area:backend"
1237
+ ]
1238
+ }
1239
+ },
1240
+ {
1241
+ header: {
1242
+ version: "1.0",
1243
+ type: "execution",
1244
+ payloadChecksum: "b2c3d4e5f6a1...",
1245
+ signatures: [
1246
+ {
1247
+ keyId: "agent:cursor",
1248
+ role: "author",
1249
+ notes: "OAuth 2.0 flow completed with GitHub provider integration",
1250
+ signature: "...",
1251
+ timestamp: 1752274600
1252
+ },
1253
+ {
1254
+ keyId: "human:camilo",
1255
+ role: "reviewer",
1256
+ notes: "Reviewed and tested locally. LGTM.",
1257
+ signature: "...",
1258
+ timestamp: 1752274650
1259
+ }
1260
+ ]
1261
+ },
1262
+ payload: {
1263
+ id: "1752274600-exec-implement-oauth",
1264
+ taskId: "1752274500-task-implement-oauth",
1265
+ type: "progress",
1266
+ title: "OAuth 2.0 flow implemented",
1267
+ result: "Completed the OAuth 2.0 authentication flow..."
1268
+ }
1269
+ },
876
1270
  {
877
1271
  header: {
878
1272
  version: "1.0",
879
- type: "task",
880
- payloadChecksum: "a1b2c3d4e5f6...",
1273
+ type: "actor",
1274
+ payloadChecksum: "c3d4e5f6a1b2...",
881
1275
  signatures: [
882
1276
  {
883
- keyId: "human:lead-dev",
1277
+ keyId: "human:admin",
884
1278
  role: "author",
1279
+ notes: "New developer onboarded to team",
1280
+ signature: "...",
1281
+ timestamp: 1752274700
1282
+ },
1283
+ {
1284
+ keyId: "agent:aion",
1285
+ role: "auditor",
1286
+ notes: "Actor verification: 10/10. Credentials validated.",
885
1287
  signature: "...",
886
- timestamp: 1752274500,
887
- timestamp_iso: "2025-07-25T14:30:00Z"
1288
+ timestamp: 1752274705
888
1289
  }
889
1290
  ]
890
1291
  },
891
1292
  payload: {
892
- id: "1752274500-task-implementar-auth",
893
- status: "pending",
894
- priority: "high",
895
- description: "Implementar autenticaci\xF3n OAuth 2.0.",
896
- tags: [
897
- "skill:go",
898
- "area:backend"
1293
+ id: "human:new-developer",
1294
+ type: "human",
1295
+ displayName: "New Developer",
1296
+ publicKey: "...",
1297
+ roles: [
1298
+ "developer"
899
1299
  ]
900
1300
  }
901
1301
  }
@@ -907,29 +1307,32 @@ var execution_record_schema_default = {
907
1307
  $schema: "http://json-schema.org/draft-07/schema#",
908
1308
  $id: "execution_record_schema.json",
909
1309
  title: "ExecutionRecord",
910
- description: "Canonical schema for execution log records",
1310
+ description: "Canonical schema for execution log records - the universal event stream",
911
1311
  additionalProperties: false,
912
1312
  type: "object",
913
1313
  required: [
914
1314
  "id",
915
1315
  "taskId",
1316
+ "type",
1317
+ "title",
916
1318
  "result"
917
1319
  ],
918
1320
  properties: {
919
1321
  id: {
920
1322
  type: "string",
921
1323
  pattern: "^\\d{10}-exec-[a-z0-9-]{1,50}$",
922
- maxLength: 70,
923
- description: "Unique identifier for the execution log entry",
1324
+ maxLength: 66,
1325
+ description: "Unique identifier for the execution log entry (10 timestamp + 1 dash + 4 'exec' + 1 dash + max 50 slug = 66 max)",
924
1326
  examples: [
925
- "1752275000-exec-crear-schema-inicial"
1327
+ "1752275000-exec-refactor-queries",
1328
+ "1752361200-exec-api-externa-caida"
926
1329
  ]
927
1330
  },
928
1331
  taskId: {
929
1332
  type: "string",
930
1333
  pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$",
931
- maxLength: 70,
932
- description: "ID of the parent task"
1334
+ maxLength: 66,
1335
+ description: "ID of the parent task this execution belongs to (10 timestamp + 1 dash + 4 'task' + 1 dash + max 50 slug = 66 max)"
933
1336
  },
934
1337
  type: {
935
1338
  type: "string",
@@ -951,23 +1354,25 @@ var execution_record_schema_default = {
951
1354
  },
952
1355
  title: {
953
1356
  type: "string",
1357
+ minLength: 1,
954
1358
  maxLength: 256,
955
- description: "Human-readable title for the execution",
1359
+ description: "Human-readable title for the execution (used to generate ID)",
956
1360
  examples: [
957
- "Sub-Task-11",
958
- "Initial analysis"
1361
+ "Refactor de queries N+1",
1362
+ "API Externa Ca\xEDda",
1363
+ "Plan de implementaci\xF3n OAuth2"
959
1364
  ]
960
1365
  },
961
1366
  result: {
962
1367
  type: "string",
963
1368
  minLength: 10,
964
1369
  maxLength: 22e3,
965
- description: "The tangible, verifiable output or result of the execution"
1370
+ description: 'The tangible, verifiable output or result of the execution. \nThis is the "WHAT" - evidence of work or event summary.\n'
966
1371
  },
967
1372
  notes: {
968
1373
  type: "string",
969
1374
  maxLength: 6500,
970
- description: "Optional comments about decisions, blockers or context"
1375
+ description: 'Optional narrative, context and decisions behind the execution.\nThis is the "HOW" and "WHY" - the story behind the result.\n'
971
1376
  },
972
1377
  references: {
973
1378
  type: "array",
@@ -975,19 +1380,79 @@ var execution_record_schema_default = {
975
1380
  type: "string",
976
1381
  maxLength: 500
977
1382
  },
978
- description: "List of URIs to relevant commits, files, or external documents"
1383
+ default: [],
1384
+ description: "Optional list of typed references to relevant commits, files, PRs, or external documents.\nShould use typed prefixes for clarity and trazabilidad (see execution_protocol_appendix.md):\n- commit: Git commit SHA\n- pr: Pull Request number\n- file: File path (relative to repo root)\n- url: External URL\n- issue: GitHub Issue number\n- task: TaskRecord ID\n- exec: ExecutionRecord ID (for corrections or dependencies)\n- changelog: ChangelogRecord ID\n"
979
1385
  }
980
1386
  },
981
1387
  examples: [
982
1388
  {
983
- id: "1752642000-exec-subtask-9-4",
984
- taskId: "1752274500-task-integrar-adapter-configuracion",
1389
+ id: "1752275500-exec-refactor-queries",
1390
+ taskId: "1752274500-task-optimizar-api",
985
1391
  type: "progress",
986
- title: "Sub-tarea 9.4: Implementar parser de JSON",
987
- result: "Adapter implementado y pasando tests",
988
- notes: "La implementaci\xF3n inicial usa una librer\xEDa externa para el parsing.",
1392
+ title: "Refactor de queries N+1",
1393
+ result: "Refactorizados 3 queries N+1 a un solo JOIN optimizado. Performance mejor\xF3 de 2.5s a 200ms.",
1394
+ notes: "Identificados 3 N+1 queries en el endpoint /api/search. Aplicado eager loading y caching de relaciones.",
1395
+ references: [
1396
+ "commit:b2c3d4e",
1397
+ "file:src/api/search.ts"
1398
+ ]
1399
+ },
1400
+ {
1401
+ id: "1752361200-exec-api-externa-caida",
1402
+ taskId: "1752274500-task-optimizar-api",
1403
+ type: "blocker",
1404
+ title: "API Externa Ca\xEDda",
1405
+ result: "No se puede continuar con testing de integraci\xF3n. API de pagos devuelve 503.",
1406
+ notes: "La API de pagos de terceros (api.payments.com) est\xE1 devolviendo errores 503. Contactado soporte del proveedor. ETA de resoluci\xF3n: 2-3 horas.",
1407
+ references: [
1408
+ "url:https://status.payments.com"
1409
+ ]
1410
+ },
1411
+ {
1412
+ id: "1752188000-exec-plan-oauth-implementation",
1413
+ taskId: "1752274500-task-oauth-implementation",
1414
+ type: "analysis",
1415
+ title: "Plan de implementaci\xF3n OAuth2",
1416
+ result: "Documento de dise\xF1o t\xE9cnico completado. 5 sub-tareas identificadas con estimaciones de complejidad.",
1417
+ notes: "Evaluadas 3 opciones: NextAuth.js (elegida), Passport.js, custom implementation. NextAuth.js por madurez y soporte de m\xFAltiples providers.",
1418
+ references: [
1419
+ "file:docs/oauth-design.md"
1420
+ ]
1421
+ },
1422
+ {
1423
+ id: "1752707800-exec-oauth-completed",
1424
+ taskId: "1752274500-task-oauth-implementation",
1425
+ type: "completion",
1426
+ title: "OAuth Implementation Completed",
1427
+ result: "Sistema OAuth2 completamente implementado, testeado y deployado a staging. 95% test coverage. Todos los acceptance criteria cumplidos.",
1428
+ notes: "Implementaci\xF3n finalizada. Code review aprobado. Tests E2E passing. Ready para changelog y deploy a producci\xF3n.",
1429
+ references: [
1430
+ "pr:456",
1431
+ "commit:def789abc",
1432
+ "url:https://staging.app.com/login"
1433
+ ]
1434
+ },
1435
+ {
1436
+ id: "1752275600-exec-cambio-estrategia-redis",
1437
+ taskId: "1752274500-task-oauth-implementation",
1438
+ type: "info",
1439
+ title: "Cambio de estrategia: Usar Redis para sessions",
1440
+ result: "Decisi\xF3n: Migrar de JWT stateless a sessions en Redis por requisito de revocaci\xF3n inmediata.",
1441
+ notes: "Durante code review se identific\xF3 requisito cr\xEDtico: revocar sesiones inmediatamente (ej: compromiso de cuenta). JWT stateless no permite esto sin lista negra compleja. Redis sessions permite revocaci\xF3n instant\xE1nea.",
1442
+ references: [
1443
+ "issue:567",
1444
+ "url:https://redis.io/docs/manual/keyspace-notifications/"
1445
+ ]
1446
+ },
1447
+ {
1448
+ id: "1752275700-exec-correccion-metricas",
1449
+ taskId: "1752274500-task-optimizar-api",
1450
+ type: "correction",
1451
+ title: "Correcci\xF3n: M\xE9tricas de performance",
1452
+ result: "Correcci\xF3n de execution 1752275500-exec-refactor-queries: El performance fue 200ms, no 50ms como se report\xF3.",
1453
+ notes: "Error de tipeo en execution original. La mejora real fue de 2.5s a 200ms (no 50ms). Sigue siendo significativa (92% mejora) pero n\xFAmeros correctos son importantes para m\xE9tricas.",
989
1454
  references: [
990
- "commit:a1b2c3d4e5"
1455
+ "exec:1752275500-exec-refactor-queries"
991
1456
  ]
992
1457
  }
993
1458
  ]
@@ -998,7 +1463,7 @@ var feedback_record_schema_default = {
998
1463
  $schema: "http://json-schema.org/draft-07/schema#",
999
1464
  $id: "feedback_record_schema.json",
1000
1465
  title: "FeedbackRecord",
1001
- description: "Canonical schema for feedback records",
1466
+ description: "Canonical schema for feedback records - structured conversation about work",
1002
1467
  additionalProperties: false,
1003
1468
  type: "object",
1004
1469
  required: [
@@ -1013,7 +1478,12 @@ var feedback_record_schema_default = {
1013
1478
  id: {
1014
1479
  type: "string",
1015
1480
  pattern: "^\\d{10}-feedback-[a-z0-9-]{1,50}$",
1016
- description: "Unique identifier for the feedback entry"
1481
+ maxLength: 70,
1482
+ description: "Unique identifier for the feedback entry",
1483
+ examples: [
1484
+ "1752788100-feedback-blocking-rest-api",
1485
+ "1752788200-feedback-question-test-coverage"
1486
+ ]
1017
1487
  },
1018
1488
  entityType: {
1019
1489
  type: "string",
@@ -1021,13 +1491,21 @@ var feedback_record_schema_default = {
1021
1491
  "task",
1022
1492
  "execution",
1023
1493
  "changelog",
1024
- "feedback"
1494
+ "feedback",
1495
+ "cycle"
1025
1496
  ],
1026
1497
  description: "The type of entity this feedback refers to"
1027
1498
  },
1028
1499
  entityId: {
1029
1500
  type: "string",
1030
- description: "The ID of the entity this feedback refers to"
1501
+ minLength: 1,
1502
+ maxLength: 256,
1503
+ description: "The ID of the entity this feedback refers to.\nMust match the pattern for its entityType:\n- task: ^\\d{10}-task-[a-z0-9-]{1,50}$\n- execution: ^\\d{10}-exec-[a-z0-9-]{1,50}$\n- changelog: ^\\d{10}-changelog-[a-z0-9-]{1,50}$\n- feedback: ^\\d{10}-feedback-[a-z0-9-]{1,50}$\n- cycle: ^\\d{10}-cycle-[a-z0-9-]{1,50}$\n",
1504
+ examples: [
1505
+ "1752274500-task-implementar-oauth",
1506
+ "1752642000-exec-subtarea-9-4",
1507
+ "1752788100-feedback-blocking-rest-api"
1508
+ ]
1031
1509
  },
1032
1510
  type: {
1033
1511
  type: "string",
@@ -1039,7 +1517,12 @@ var feedback_record_schema_default = {
1039
1517
  "clarification",
1040
1518
  "assignment"
1041
1519
  ],
1042
- description: "The semantic intent of the feedback"
1520
+ description: "The semantic intent of the feedback",
1521
+ examples: [
1522
+ "blocking",
1523
+ "question",
1524
+ "approval"
1525
+ ]
1043
1526
  },
1044
1527
  status: {
1045
1528
  type: "string",
@@ -1049,31 +1532,69 @@ var feedback_record_schema_default = {
1049
1532
  "resolved",
1050
1533
  "wontfix"
1051
1534
  ],
1052
- description: "The lifecycle status of the feedback"
1535
+ description: 'The lifecycle status of the feedback. \nNote: FeedbackRecords are immutable. To change status, create a new feedback \nthat references this one using entityType: "feedback" and resolvesFeedbackId.\n'
1053
1536
  },
1054
1537
  content: {
1055
1538
  type: "string",
1056
1539
  minLength: 1,
1057
- maxLength: 1e4,
1058
- description: "The content of the feedback"
1540
+ maxLength: 5e3,
1541
+ description: "The content of the feedback. Reduced from 10000 to 5000 chars for practical use."
1059
1542
  },
1060
1543
  assignee: {
1061
1544
  type: "string",
1062
- description: "The Actor ID of the agent responsible for addressing the feedback"
1545
+ pattern: "^(human|agent)(:[a-z0-9-]+)+$",
1546
+ maxLength: 256,
1547
+ description: "Optional. The Actor ID responsible for addressing the feedback (e.g., 'human:maria', 'agent:camilo:cursor')",
1548
+ examples: [
1549
+ "human:maria",
1550
+ "agent:code-reviewer",
1551
+ "agent:camilo:cursor"
1552
+ ]
1063
1553
  },
1064
1554
  resolvesFeedbackId: {
1065
1555
  type: "string",
1066
- description: "The ID of another feedback record that this one resolves or responds to"
1556
+ pattern: "^\\d{10}-feedback-[a-z0-9-]{1,50}$",
1557
+ maxLength: 70,
1558
+ description: "Optional. The ID of another feedback record that this one resolves or responds to",
1559
+ examples: [
1560
+ "1752788100-feedback-blocking-rest-api"
1561
+ ]
1067
1562
  }
1068
1563
  },
1069
1564
  examples: [
1070
1565
  {
1071
- id: "1752788100-feedback-blocking-comment",
1566
+ id: "1752788100-feedback-blocking-rest-api",
1072
1567
  entityType: "execution",
1073
1568
  entityId: "1752642000-exec-subtarea-9-4",
1074
1569
  type: "blocking",
1075
1570
  status: "open",
1076
- content: "Esta implementaci\xF3n no cumple el est\xE1ndar de rutas REST. Sugiero revisar el spec."
1571
+ content: "Esta implementaci\xF3n no cumple el est\xE1ndar de rutas REST. Los endpoints deben seguir el patr\xF3n /api/v1/{resource}/{id}. Actualmente usa /get-user?id=X que no es RESTful."
1572
+ },
1573
+ {
1574
+ id: "1752788200-feedback-rest-api-fixed",
1575
+ entityType: "feedback",
1576
+ entityId: "1752788100-feedback-blocking-rest-api",
1577
+ type: "clarification",
1578
+ status: "resolved",
1579
+ content: "Implementada la correcci\xF3n. Ahora todos los endpoints siguen el est\xE1ndar REST: GET /api/v1/users/:id, POST /api/v1/users, etc. Tests actualizados y passing.",
1580
+ resolvesFeedbackId: "1752788100-feedback-blocking-rest-api"
1581
+ },
1582
+ {
1583
+ id: "1752788300-feedback-assign-auth-task",
1584
+ entityType: "task",
1585
+ entityId: "1752274500-task-implementar-oauth",
1586
+ type: "assignment",
1587
+ status: "open",
1588
+ content: "Asignando esta tarea a Mar\xEDa por su experiencia con OAuth2. Prioridad alta para el sprint actual.",
1589
+ assignee: "human:maria"
1590
+ },
1591
+ {
1592
+ id: "1752788400-feedback-question-test-coverage",
1593
+ entityType: "task",
1594
+ entityId: "1752274500-task-implementar-oauth",
1595
+ type: "question",
1596
+ status: "open",
1597
+ content: "\xBFCu\xE1l es el nivel de test coverage esperado para esta feature? El spec no lo menciona expl\xEDcitamente. \xBFDebemos apuntar a 80% como el resto del proyecto?"
1077
1598
  }
1078
1599
  ]
1079
1600
  };
@@ -1091,15 +1612,14 @@ var task_record_schema_default = {
1091
1612
  "title",
1092
1613
  "status",
1093
1614
  "priority",
1094
- "description",
1095
- "tags"
1615
+ "description"
1096
1616
  ],
1097
1617
  properties: {
1098
1618
  id: {
1099
1619
  type: "string",
1100
1620
  pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$",
1101
- maxLength: 70,
1102
- description: "Unique identifier for the task",
1621
+ maxLength: 66,
1622
+ description: "Unique identifier for the task (10 timestamp + 1 dash + 4 'task' + 1 dash + max 50 slug = 66 max)",
1103
1623
  examples: [
1104
1624
  "1752274500-task-implementar-auth",
1105
1625
  "1752347700-task-fix-logging"
@@ -1115,9 +1635,12 @@ var task_record_schema_default = {
1115
1635
  type: "array",
1116
1636
  items: {
1117
1637
  type: "string",
1118
- pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$"
1638
+ minLength: 1,
1639
+ pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$",
1640
+ maxLength: 67
1119
1641
  },
1120
- description: "Optional. The IDs of the strategic cycles this task belongs to."
1642
+ default: [],
1643
+ description: "Optional. The IDs of the strategic cycles this task belongs to. (10 timestamp + 1 dash + 5 'cycle' + 1 dash + max 50 slug = 67 max)"
1121
1644
  },
1122
1645
  status: {
1123
1646
  type: "string",
@@ -1155,30 +1678,102 @@ var task_record_schema_default = {
1155
1678
  type: "array",
1156
1679
  items: {
1157
1680
  type: "string",
1681
+ minLength: 1,
1158
1682
  pattern: "^[a-z0-9-]+(:[a-z0-9-:]+)*$"
1159
1683
  },
1160
- description: "List of key:value tags for categorization and role suggestion (e.g., 'skill:react', 'role:agent:developer'). Can be an empty array."
1684
+ default: [],
1685
+ description: "Optional. List of key:value tags for categorization and role suggestion (e.g., 'skill:react', 'role:agent:developer')."
1161
1686
  },
1162
1687
  references: {
1163
1688
  type: "array",
1164
1689
  items: {
1165
1690
  type: "string",
1691
+ minLength: 1,
1166
1692
  maxLength: 500
1167
1693
  },
1694
+ default: [],
1168
1695
  description: "Valid links or files, when mentioned"
1169
1696
  },
1170
1697
  notes: {
1171
1698
  type: "string",
1699
+ minLength: 0,
1172
1700
  maxLength: 3e3,
1173
1701
  description: "Additional comments, decisions made or added context"
1174
1702
  }
1175
1703
  },
1176
1704
  examples: [
1177
1705
  {
1178
- id: "1752274500-task-implementar-adapter-github",
1179
- status: "in_progress",
1706
+ id: "1752274500-task-implement-oauth-flow",
1707
+ title: "Implement OAuth 2.0 authentication flow",
1708
+ status: "draft",
1180
1709
  priority: "high",
1181
- description: "Implementar adapter completo para GitHub Issues como TaskLifecycle Store"
1710
+ description: "Implement complete OAuth 2.0 flow with GitHub provider. Include token refresh, session management, and secure storage. Must support both web and CLI flows.",
1711
+ cycleIds: [
1712
+ "1752270000-cycle-auth-mvp"
1713
+ ],
1714
+ tags: [
1715
+ "skill:security",
1716
+ "category:feature",
1717
+ "package:core"
1718
+ ],
1719
+ references: [
1720
+ "url:https://docs.github.com/en/apps/oauth-apps/building-oauth-apps"
1721
+ ],
1722
+ notes: "Consider using proven library like passport.js or implement from scratch for learning."
1723
+ },
1724
+ {
1725
+ id: "1752347700-task-fix-memory-leak-indexer",
1726
+ title: "Fix memory leak in indexer process",
1727
+ status: "active",
1728
+ priority: "critical",
1729
+ description: "Indexer process consuming >2GB RAM after 24h uptime. Profiling shows EventEmitter listeners not being cleaned up properly. Fix leak and add monitoring.",
1730
+ cycleIds: [],
1731
+ tags: [
1732
+ "category:bug",
1733
+ "skill:performance",
1734
+ "package:core"
1735
+ ],
1736
+ references: [
1737
+ "file:packages/core/src/adapters/indexer_adapter/index.ts",
1738
+ "commit:a1b2c3d4",
1739
+ "issue:789"
1740
+ ],
1741
+ notes: "Discovered during production monitoring. Affects long-running processes only."
1742
+ },
1743
+ {
1744
+ id: "1752448900-task-add-typescript-linting",
1745
+ title: "Add TypeScript strict mode and ESLint rules",
1746
+ status: "done",
1747
+ priority: "medium",
1748
+ description: "Enable TypeScript strict mode across all packages. Configure ESLint with recommended rules. Fix all existing violations. Add pre-commit hook.",
1749
+ cycleIds: [
1750
+ "1752440000-cycle-code-quality-q1"
1751
+ ],
1752
+ tags: [
1753
+ "category:tooling",
1754
+ "skill:typescript",
1755
+ "epic:code-quality"
1756
+ ],
1757
+ references: [
1758
+ "pr:123",
1759
+ "file:.eslintrc.js",
1760
+ "file:tsconfig.json"
1761
+ ],
1762
+ notes: "Approved by tech lead. All 47 violations fixed in PR #123."
1763
+ },
1764
+ {
1765
+ id: "1752550000-task-research-graphql-migration",
1766
+ title: "Research GraphQL migration feasibility",
1767
+ status: "draft",
1768
+ priority: "low",
1769
+ description: "Evaluate feasibility of migrating REST API to GraphQL. Document pros/cons, effort estimation, and recommended approach. Focus on developer experience and performance.",
1770
+ cycleIds: [],
1771
+ tags: [
1772
+ "category:research",
1773
+ "skill:graphql",
1774
+ "skill:backend"
1775
+ ],
1776
+ references: []
1182
1777
  }
1183
1778
  ]
1184
1779
  };
@@ -1260,7 +1855,7 @@ var workflow_methodology_record_schema_default = {
1260
1855
  },
1261
1856
  signatures: {
1262
1857
  type: "object",
1263
- description: "Signature requirements keyed by guild",
1858
+ description: "Signature requirements keyed by role (e.g., 'approver:quality', 'developer:backend')",
1264
1859
  additionalProperties: {
1265
1860
  type: "object",
1266
1861
  required: [
@@ -1428,27 +2023,35 @@ var workflow_methodology_record_schema_default = {
1428
2023
  items: {
1429
2024
  type: "object",
1430
2025
  required: [
1431
- "id",
1432
- "gremio",
1433
2026
  "engine"
1434
2027
  ],
2028
+ anyOf: [
2029
+ {
2030
+ required: [
2031
+ "id"
2032
+ ]
2033
+ },
2034
+ {
2035
+ required: [
2036
+ "required_roles"
2037
+ ]
2038
+ }
2039
+ ],
1435
2040
  additionalProperties: false,
1436
2041
  properties: {
1437
2042
  id: {
1438
2043
  type: "string",
1439
2044
  pattern: "^agent:[a-z0-9:-]+$",
1440
- description: "Unique agent identifier"
2045
+ description: "Optional: Specific agent ID. If provided, uses this exact agent."
1441
2046
  },
1442
- gremio: {
1443
- type: "string",
1444
- enum: [
1445
- "design",
1446
- "intelligence",
1447
- "strategy",
1448
- "operations",
1449
- "quality"
1450
- ],
1451
- description: "Agent guild classification"
2047
+ required_roles: {
2048
+ type: "array",
2049
+ items: {
2050
+ type: "string",
2051
+ pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$"
2052
+ },
2053
+ minItems: 1,
2054
+ description: "Optional: Required capability roles. Matches any agent with these roles (from ActorRecord)."
1452
2055
  },
1453
2056
  engine: {
1454
2057
  type: "object",
@@ -1575,7 +2178,257 @@ var workflow_methodology_record_schema_default = {
1575
2178
  }
1576
2179
  }
1577
2180
  }
1578
- }
2181
+ },
2182
+ examples: [
2183
+ {
2184
+ version: "1.0.0",
2185
+ name: "Simple Kanban",
2186
+ description: "Basic workflow for small teams",
2187
+ state_transitions: {
2188
+ review: {
2189
+ from: [
2190
+ "draft"
2191
+ ],
2192
+ requires: {
2193
+ command: "gitgov task submit"
2194
+ }
2195
+ },
2196
+ ready: {
2197
+ from: [
2198
+ "review"
2199
+ ],
2200
+ requires: {
2201
+ command: "gitgov task approve",
2202
+ signatures: {
2203
+ __default__: {
2204
+ role: "approver",
2205
+ capability_roles: [
2206
+ "approver:product"
2207
+ ],
2208
+ min_approvals: 1
2209
+ }
2210
+ }
2211
+ }
2212
+ },
2213
+ active: {
2214
+ from: [
2215
+ "ready"
2216
+ ],
2217
+ requires: {
2218
+ event: "first_execution_record_created"
2219
+ }
2220
+ },
2221
+ done: {
2222
+ from: [
2223
+ "active"
2224
+ ],
2225
+ requires: {
2226
+ command: "gitgov task complete"
2227
+ }
2228
+ }
2229
+ },
2230
+ view_configs: {
2231
+ "kanban-3col": {
2232
+ columns: {
2233
+ "To Do": [
2234
+ "draft",
2235
+ "review"
2236
+ ],
2237
+ "In Progress": [
2238
+ "ready",
2239
+ "active"
2240
+ ],
2241
+ Done: [
2242
+ "done"
2243
+ ]
2244
+ },
2245
+ theme: "minimal",
2246
+ layout: "horizontal"
2247
+ }
2248
+ }
2249
+ },
2250
+ {
2251
+ version: "1.0.0",
2252
+ name: "GitGovernance Default Methodology",
2253
+ description: "Standard GitGovernance workflow with quality gates and agent collaboration",
2254
+ state_transitions: {
2255
+ review: {
2256
+ from: [
2257
+ "draft"
2258
+ ],
2259
+ requires: {
2260
+ command: "gitgov task submit",
2261
+ signatures: {
2262
+ __default__: {
2263
+ role: "submitter",
2264
+ capability_roles: [
2265
+ "author"
2266
+ ],
2267
+ min_approvals: 1
2268
+ }
2269
+ }
2270
+ }
2271
+ },
2272
+ ready: {
2273
+ from: [
2274
+ "review"
2275
+ ],
2276
+ requires: {
2277
+ command: "gitgov task approve",
2278
+ signatures: {
2279
+ __default__: {
2280
+ role: "approver",
2281
+ capability_roles: [
2282
+ "approver:product"
2283
+ ],
2284
+ min_approvals: 1
2285
+ },
2286
+ design: {
2287
+ role: "approver",
2288
+ capability_roles: [
2289
+ "approver:design"
2290
+ ],
2291
+ min_approvals: 1
2292
+ },
2293
+ quality: {
2294
+ role: "approver",
2295
+ capability_roles: [
2296
+ "approver:quality"
2297
+ ],
2298
+ min_approvals: 1
2299
+ }
2300
+ }
2301
+ }
2302
+ },
2303
+ active: {
2304
+ from: [
2305
+ "ready",
2306
+ "paused"
2307
+ ],
2308
+ requires: {
2309
+ event: "first_execution_record_created",
2310
+ custom_rules: [
2311
+ "task_must_have_valid_assignment_for_executor"
2312
+ ]
2313
+ }
2314
+ },
2315
+ done: {
2316
+ from: [
2317
+ "active"
2318
+ ],
2319
+ requires: {
2320
+ command: "gitgov task complete",
2321
+ signatures: {
2322
+ __default__: {
2323
+ role: "approver",
2324
+ capability_roles: [
2325
+ "approver:quality"
2326
+ ],
2327
+ min_approvals: 1
2328
+ }
2329
+ }
2330
+ }
2331
+ },
2332
+ archived: {
2333
+ from: [
2334
+ "done"
2335
+ ],
2336
+ requires: {
2337
+ event: "changelog_record_created"
2338
+ }
2339
+ },
2340
+ paused: {
2341
+ from: [
2342
+ "active",
2343
+ "review"
2344
+ ],
2345
+ requires: {
2346
+ event: "feedback_blocking_created"
2347
+ }
2348
+ },
2349
+ discarded: {
2350
+ from: [
2351
+ "ready",
2352
+ "active"
2353
+ ],
2354
+ requires: {
2355
+ command: "gitgov task cancel",
2356
+ signatures: {
2357
+ __default__: {
2358
+ role: "canceller",
2359
+ capability_roles: [
2360
+ "approver:product",
2361
+ "approver:quality"
2362
+ ],
2363
+ min_approvals: 1
2364
+ }
2365
+ }
2366
+ }
2367
+ }
2368
+ },
2369
+ custom_rules: {
2370
+ task_must_have_valid_assignment_for_executor: {
2371
+ description: "Task must have a valid assignment before execution can begin",
2372
+ validation: "assignment_required"
2373
+ }
2374
+ },
2375
+ view_configs: {
2376
+ "kanban-4col": {
2377
+ columns: {
2378
+ Draft: [
2379
+ "draft"
2380
+ ],
2381
+ "In Progress": [
2382
+ "review",
2383
+ "ready",
2384
+ "active"
2385
+ ],
2386
+ Review: [
2387
+ "done"
2388
+ ],
2389
+ Done: [
2390
+ "archived"
2391
+ ],
2392
+ Cancelled: [
2393
+ "discarded"
2394
+ ]
2395
+ },
2396
+ theme: "minimal",
2397
+ layout: "horizontal"
2398
+ },
2399
+ "kanban-7col": {
2400
+ columns: {
2401
+ Draft: [
2402
+ "draft"
2403
+ ],
2404
+ Review: [
2405
+ "review"
2406
+ ],
2407
+ Ready: [
2408
+ "ready"
2409
+ ],
2410
+ Active: [
2411
+ "active"
2412
+ ],
2413
+ Done: [
2414
+ "done"
2415
+ ],
2416
+ Archived: [
2417
+ "archived"
2418
+ ],
2419
+ Blocked: [
2420
+ "paused"
2421
+ ],
2422
+ Cancelled: [
2423
+ "discarded"
2424
+ ]
2425
+ },
2426
+ theme: "corporate",
2427
+ layout: "vertical"
2428
+ }
2429
+ }
2430
+ }
2431
+ ]
1579
2432
  };
1580
2433
 
1581
2434
  // src/schemas/generated/index.ts
@@ -1717,13 +2570,13 @@ var SchemaValidationError = class extends Error {
1717
2570
  }
1718
2571
  };
1719
2572
  var DetailedValidationError = class extends GitGovError {
1720
- constructor(recordType, ajvErrors) {
1721
- const errorSummary = ajvErrors.map((err) => `${err.field}: ${err.message}`).join(", ");
2573
+ constructor(recordType, errors) {
2574
+ const errorSummary = errors.map((err) => `${err.field}: ${err.message}`).join(", ");
1722
2575
  super(
1723
2576
  `${recordType} validation failed: ${errorSummary}`,
1724
2577
  "DETAILED_VALIDATION_ERROR"
1725
2578
  );
1726
- this.ajvErrors = ajvErrors;
2579
+ this.errors = errors;
1727
2580
  }
1728
2581
  };
1729
2582
 
@@ -1827,10 +2680,10 @@ async function generateKeys() {
1827
2680
  privateKey: Buffer.from(privateKey).toString("base64")
1828
2681
  };
1829
2682
  }
1830
- function signPayload(payload, privateKey, keyId, role) {
2683
+ function signPayload(payload, privateKey, keyId, role, notes) {
1831
2684
  const payloadChecksum = calculatePayloadChecksum(payload);
1832
2685
  const timestamp = Math.floor(Date.now() / 1e3);
1833
- const digest = `${payloadChecksum}:${keyId}:${role}:${timestamp}`;
2686
+ const digest = `${payloadChecksum}:${keyId}:${role}:${notes}:${timestamp}`;
1834
2687
  const digestHash = createHash("sha256").update(digest).digest();
1835
2688
  const signature = sign(null, digestHash, {
1836
2689
  key: Buffer.from(privateKey, "base64"),
@@ -1840,9 +2693,9 @@ function signPayload(payload, privateKey, keyId, role) {
1840
2693
  return {
1841
2694
  keyId,
1842
2695
  role,
2696
+ notes,
1843
2697
  signature: signature.toString("base64"),
1844
- timestamp,
1845
- timestamp_iso: new Date(timestamp * 1e3).toISOString()
2698
+ timestamp
1846
2699
  };
1847
2700
  }
1848
2701
  async function verifySignatures(record, getActorPublicKey) {
@@ -1852,7 +2705,7 @@ async function verifySignatures(record, getActorPublicKey) {
1852
2705
  logger2.warn(`Public key not found for actor: ${signature.keyId}`);
1853
2706
  return false;
1854
2707
  }
1855
- const digest = `${record.header.payloadChecksum}:${signature.keyId}:${signature.role}:${signature.timestamp}`;
2708
+ const digest = `${record.header.payloadChecksum}:${signature.keyId}:${signature.role}:${signature.notes}:${signature.timestamp}`;
1856
2709
  const digestHash = createHash("sha256").update(digest).digest();
1857
2710
  const isValid = verify(
1858
2711
  null,
@@ -1955,13 +2808,6 @@ function validateEmbeddedMetadataBusinessRules(data) {
1955
2808
  value: data.header.signatures
1956
2809
  });
1957
2810
  }
1958
- if (data.header.audit && (data.header.audit.length < 1 || data.header.audit.length > 3e3)) {
1959
- errors.push({
1960
- field: "header.audit",
1961
- message: "audit field must be between 1 and 3000 characters",
1962
- value: data.header.audit
1963
- });
1964
- }
1965
2811
  return {
1966
2812
  isValid: errors.length === 0,
1967
2813
  errors
@@ -1981,8 +2827,8 @@ function isTaskRecord(data) {
1981
2827
  function validateTaskRecordDetailed(data) {
1982
2828
  const [isValid, ajvErrors] = validateTaskRecordSchema(data);
1983
2829
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
1984
- field: error.instancePath || error.schemaPath || "root",
1985
- message: error.message || "Validation failed",
2830
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2831
+ message: error.message || "Unknown validation error",
1986
2832
  value: error.data
1987
2833
  })) : [];
1988
2834
  return {
@@ -1993,9 +2839,12 @@ function validateTaskRecordDetailed(data) {
1993
2839
  async function validateFullTaskRecord(record, getActorPublicKey) {
1994
2840
  const [isValidSchema, errors] = validateTaskRecordSchema(record.payload);
1995
2841
  if (!isValidSchema) {
1996
- throw new SchemaValidationError(
1997
- `TaskRecord payload failed schema validation: ${JSON.stringify(errors)}`
1998
- );
2842
+ const formattedErrors = (errors || []).map((error) => ({
2843
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2844
+ message: error.message || "Unknown validation error",
2845
+ value: error.data
2846
+ }));
2847
+ throw new DetailedValidationError("TaskRecord", formattedErrors);
1999
2848
  }
2000
2849
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2001
2850
  }
@@ -2020,32 +2869,14 @@ function generateExecutionId(title, timestamp) {
2020
2869
  const slug = sanitizeForId(title);
2021
2870
  return `${timestamp}-exec-${slug}`;
2022
2871
  }
2023
- function generateChangelogId(entityType, entityId, timestamp) {
2024
- let entitySlug;
2025
- if (entityType === "system" || entityType === "configuration") {
2026
- entitySlug = sanitizeForId(entityId);
2027
- } else {
2028
- const parsed = parseTimestampedId(entityId);
2029
- entitySlug = parsed ? parsed.slug : sanitizeForId(entityId);
2030
- }
2031
- return `${timestamp}-changelog-${entityType}-${entitySlug}`;
2872
+ function generateChangelogId(title, timestamp) {
2873
+ const slug = sanitizeForId(title);
2874
+ return `${timestamp}-changelog-${slug}`;
2032
2875
  }
2033
2876
  function generateFeedbackId(title, timestamp) {
2034
2877
  const slug = sanitizeForId(title);
2035
2878
  return `${timestamp}-feedback-${slug}`;
2036
2879
  }
2037
- function parseTimestampedId(id) {
2038
- if (typeof id !== "string") return null;
2039
- const match = id.match(/^(\d+)-(\w+)-(.+)$/);
2040
- if (!match || !match[1] || !match[2] || !match[3]) {
2041
- return null;
2042
- }
2043
- return {
2044
- timestamp: parseInt(match[1], 10),
2045
- prefix: match[2],
2046
- slug: match[3]
2047
- };
2048
- }
2049
2880
 
2050
2881
  // src/factories/task_factory.ts
2051
2882
  async function createTaskRecord(payload) {
@@ -2083,8 +2914,8 @@ function isCycleRecord(data) {
2083
2914
  function validateCycleRecordDetailed(data) {
2084
2915
  const [isValid, ajvErrors] = validateCycleRecordSchema(data);
2085
2916
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
2086
- field: error.instancePath || error.schemaPath || "root",
2087
- message: error.message || "Validation failed",
2917
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2918
+ message: error.message || "Unknown validation error",
2088
2919
  value: error.data
2089
2920
  })) : [];
2090
2921
  return {
@@ -2095,9 +2926,12 @@ function validateCycleRecordDetailed(data) {
2095
2926
  async function validateFullCycleRecord(record, getActorPublicKey) {
2096
2927
  const [isValidSchema, errors] = validateCycleRecordSchema(record.payload);
2097
2928
  if (!isValidSchema) {
2098
- throw new SchemaValidationError(
2099
- `CycleRecord payload failed schema validation: ${JSON.stringify(errors)}`
2100
- );
2929
+ const formattedErrors = (errors || []).map((error) => ({
2930
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2931
+ message: error.message || "Unknown validation error",
2932
+ value: error.data
2933
+ }));
2934
+ throw new DetailedValidationError("CycleRecord", formattedErrors);
2101
2935
  }
2102
2936
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2103
2937
  }
@@ -2395,8 +3229,8 @@ function isActorRecord(data) {
2395
3229
  function validateActorRecordDetailed(data) {
2396
3230
  const [isValid, ajvErrors] = validateActorRecordSchema(data);
2397
3231
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
2398
- field: error.instancePath || error.schemaPath || "root",
2399
- message: error.message || "Validation failed",
3232
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3233
+ message: error.message || "Unknown validation error",
2400
3234
  value: error.data
2401
3235
  })) : [];
2402
3236
  return {
@@ -2406,10 +3240,13 @@ function validateActorRecordDetailed(data) {
2406
3240
  }
2407
3241
  async function validateFullActorRecord(record, getActorPublicKey) {
2408
3242
  const [isValidSchema, errors] = validateActorRecordSchema(record.payload);
2409
- if (!isValidSchema) {
2410
- throw new SchemaValidationError(
2411
- `ActorRecord payload failed schema validation: ${JSON.stringify(errors)}`
2412
- );
3243
+ if (!isValidSchema) {
3244
+ const formattedErrors = (errors || []).map((error) => ({
3245
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3246
+ message: error.message || "Unknown validation error",
3247
+ value: error.data
3248
+ }));
3249
+ throw new DetailedValidationError("ActorRecord", formattedErrors);
2413
3250
  }
2414
3251
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2415
3252
  }
@@ -2445,8 +3282,8 @@ function isAgentRecord(data) {
2445
3282
  function validateAgentRecordDetailed(data) {
2446
3283
  const [isValid, ajvErrors] = validateAgentRecordSchema(data);
2447
3284
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
2448
- field: error.instancePath || error.schemaPath || "root",
2449
- message: error.message || "Validation failed",
3285
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3286
+ message: error.message || "Unknown validation error",
2450
3287
  value: error.data
2451
3288
  })) : [];
2452
3289
  return {
@@ -2457,9 +3294,12 @@ function validateAgentRecordDetailed(data) {
2457
3294
  async function validateFullAgentRecord(record, getActorPublicKey) {
2458
3295
  const [isValidSchema, errors] = validateAgentRecordSchema(record.payload);
2459
3296
  if (!isValidSchema) {
2460
- throw new SchemaValidationError(
2461
- `AgentRecord payload failed schema validation: ${JSON.stringify(errors)}`
2462
- );
3297
+ const formattedErrors = (errors || []).map((error) => ({
3298
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3299
+ message: error.message || "Unknown validation error",
3300
+ value: error.data
3301
+ }));
3302
+ throw new DetailedValidationError("AgentRecord", formattedErrors);
2463
3303
  }
2464
3304
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2465
3305
  }
@@ -2486,7 +3326,6 @@ async function validateAgentActorRelationship(agentRecord, getEffectiveActor) {
2486
3326
  async function createAgentRecord(payload) {
2487
3327
  const agent = {
2488
3328
  id: payload.id || "",
2489
- guild: payload.guild || "design",
2490
3329
  engine: payload.engine || { type: "local" },
2491
3330
  status: payload.status || "active",
2492
3331
  triggers: payload.triggers || [],
@@ -2539,7 +3378,7 @@ var IdentityAdapter = class {
2539
3378
  };
2540
3379
  const validatedPayload = await createActorRecord(completePayload);
2541
3380
  const payloadChecksum = calculatePayloadChecksum(validatedPayload);
2542
- const signature = await signPayload(validatedPayload, privateKey, actorId, "author");
3381
+ const signature = await signPayload(validatedPayload, privateKey, actorId, "author", "Actor registration");
2543
3382
  const record = {
2544
3383
  header: {
2545
3384
  version: "1.0",
@@ -2601,9 +3440,9 @@ var IdentityAdapter = class {
2601
3440
  const mockSignature = {
2602
3441
  keyId: actorId,
2603
3442
  role,
3443
+ notes: "Record signed",
2604
3444
  signature: `mock-signature-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
2605
- timestamp: Math.floor(Date.now() / 1e3),
2606
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
3445
+ timestamp: Math.floor(Date.now() / 1e3)
2607
3446
  };
2608
3447
  const signedRecord = {
2609
3448
  ...record,
@@ -2711,8 +3550,8 @@ var IdentityAdapter = class {
2711
3550
  console.warn("authenticate not fully implemented yet");
2712
3551
  }
2713
3552
  async createAgentRecord(payload) {
2714
- if (!payload.id || !payload.guild || !payload.engine) {
2715
- throw new Error("AgentRecord requires id, guild and engine");
3553
+ if (!payload.id || !payload.engine) {
3554
+ throw new Error("AgentRecord requires id and engine");
2716
3555
  }
2717
3556
  const correspondingActor = await this.getActor(payload.id);
2718
3557
  if (!correspondingActor) {
@@ -2723,7 +3562,6 @@ var IdentityAdapter = class {
2723
3562
  }
2724
3563
  const completePayload = {
2725
3564
  id: payload.id,
2726
- guild: payload.guild,
2727
3565
  engine: payload.engine,
2728
3566
  status: payload.status || "active",
2729
3567
  triggers: payload.triggers || [],
@@ -2733,7 +3571,7 @@ var IdentityAdapter = class {
2733
3571
  };
2734
3572
  const validatedPayload = await createAgentRecord(completePayload);
2735
3573
  const payloadChecksum = calculatePayloadChecksum(validatedPayload);
2736
- const signature = signPayload(validatedPayload, "placeholder-private-key", payload.id, "author");
3574
+ const signature = signPayload(validatedPayload, "placeholder-private-key", payload.id, "author", "Agent registration");
2737
3575
  const record = {
2738
3576
  header: {
2739
3577
  version: "1.0",
@@ -2758,7 +3596,6 @@ var IdentityAdapter = class {
2758
3596
  source: "identity_adapter",
2759
3597
  payload: {
2760
3598
  agentId: validatedPayload.id,
2761
- guild: validatedPayload.guild,
2762
3599
  engine: validatedPayload.engine,
2763
3600
  correspondingActorId: correspondingActor.id
2764
3601
  }
@@ -2863,11 +3700,11 @@ var FeedbackAdapter = class {
2863
3700
  this.eventBus = dependencies.eventBus;
2864
3701
  }
2865
3702
  /**
2866
- * [EARS-1] Creates a new FeedbackRecord for structured communication between actors.
3703
+ * [EARS-1, EARS-2, EARS-3, EARS-4, EARS-5, EARS-6, EARS-7, EARS-8] Creates a new FeedbackRecord for structured communication between actors.
2867
3704
  *
2868
3705
  * Description: Creates a new FeedbackRecord for structured communication between actors.
2869
3706
  * Implementation: Builds record with status: "open", signs with actorId, persists and emits event.
2870
- * Usage: Invoked by `gitgov feedback add` to create feedback, assignments, blocks.
3707
+ * Usage: Invoked by `gitgov feedback create` to create feedback, assignments, blocks, responses.
2871
3708
  * Returns: Complete and signed FeedbackRecord.
2872
3709
  */
2873
3710
  async create(payload, actorId) {
@@ -2875,21 +3712,30 @@ var FeedbackAdapter = class {
2875
3712
  if (!payloadWithEntityId.entityId) {
2876
3713
  throw new Error("RecordNotFoundError: entityId is required");
2877
3714
  }
2878
- if (payloadWithEntityId.entityType && !["task", "execution", "changelog", "feedback"].includes(payloadWithEntityId.entityType)) {
2879
- throw new Error("InvalidEntityTypeError: entityType must be task, execution, changelog, or feedback");
3715
+ if (payloadWithEntityId.entityType && !["task", "execution", "changelog", "feedback", "cycle"].includes(payloadWithEntityId.entityType)) {
3716
+ throw new Error("InvalidEntityTypeError: entityType must be task, execution, changelog, feedback, or cycle");
2880
3717
  }
2881
3718
  if (payload.type === "assignment" && payload.assignee) {
2882
3719
  const existingFeedbacks = await this.getFeedbackByEntity(payloadWithEntityId.entityId);
2883
- const duplicateAssignment = existingFeedbacks.find(
3720
+ const openAssignments = existingFeedbacks.filter(
2884
3721
  (feedback) => feedback.type === "assignment" && feedback.assignee === payload.assignee && feedback.status === "open"
2885
3722
  );
2886
- if (duplicateAssignment) {
2887
- throw new Error(`DuplicateAssignmentError: Task ${payloadWithEntityId.entityId} is already assigned to ${payload.assignee} (feedback: ${duplicateAssignment.id})`);
3723
+ if (openAssignments.length > 0) {
3724
+ const allFeedbacks = await this.getAllFeedback();
3725
+ for (const assignment of openAssignments) {
3726
+ const hasResolution = allFeedbacks.some(
3727
+ (feedback) => feedback.entityType === "feedback" && feedback.resolvesFeedbackId === assignment.id && feedback.status === "resolved"
3728
+ );
3729
+ if (!hasResolution) {
3730
+ throw new Error(`DuplicateAssignmentError: Task ${payloadWithEntityId.entityId} is already assigned to ${payload.assignee} (feedback: ${assignment.id})`);
3731
+ }
3732
+ }
2888
3733
  }
2889
3734
  }
2890
3735
  const enrichedPayload = {
2891
- ...payload,
2892
- status: "open"
3736
+ status: "open",
3737
+ ...payload
3738
+ // Allows payload.status to override default
2893
3739
  };
2894
3740
  try {
2895
3741
  const validatedPayload = await createFeedbackRecord(enrichedPayload);
@@ -2901,9 +3747,9 @@ var FeedbackAdapter = class {
2901
3747
  signatures: [{
2902
3748
  keyId: actorId,
2903
3749
  role: "author",
3750
+ notes: "Feedback created",
2904
3751
  signature: "placeholder",
2905
- timestamp: Date.now(),
2906
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
3752
+ timestamp: Date.now()
2907
3753
  }]
2908
3754
  },
2909
3755
  payload: validatedPayload
@@ -2922,7 +3768,8 @@ var FeedbackAdapter = class {
2922
3768
  status: validatedPayload.status,
2923
3769
  content: validatedPayload.content,
2924
3770
  triggeredBy: actorId,
2925
- assignee: validatedPayload.assignee
3771
+ assignee: validatedPayload.assignee,
3772
+ resolvesFeedbackId: validatedPayload.resolvesFeedbackId
2926
3773
  }
2927
3774
  });
2928
3775
  return validatedPayload;
@@ -2934,51 +3781,30 @@ var FeedbackAdapter = class {
2934
3781
  }
2935
3782
  }
2936
3783
  /**
2937
- * [EARS-4] Resolves an existing feedback changing its status to resolved.
3784
+ * [EARS-9, EARS-10, EARS-11, EARS-12] Helper: Creates a new feedback that "resolves" another (immutable).
2938
3785
  *
2939
- * Description: Resolves an existing feedback changing its status to resolved.
2940
- * Implementation: Reads feedback, validates permissions (future), transitions state, signs and emits event.
2941
- * Usage: Invoked by `gitgov feedback resolve` to close conversations and blocks.
2942
- * Returns: Updated FeedbackRecord with new signature.
2943
- */
2944
- async resolve(feedbackId, actorId) {
2945
- const existingRecord = await this.feedbackStore.read(feedbackId);
2946
- if (!existingRecord) {
3786
+ * Description: Helper method that creates a new feedback documenting resolution of another feedback.
3787
+ * Implementation: Verifies original exists, then delegates to create() with immutable pattern.
3788
+ * Usage: Ergonomic helper for common case. For advanced cases (wontfix, approval), use create() directly.
3789
+ * Returns: New FeedbackRecord that points to the original with resolvesFeedbackId.
3790
+ */
3791
+ async resolve(feedbackId, actorId, content) {
3792
+ const originalFeedback = await this.getFeedback(feedbackId);
3793
+ if (!originalFeedback) {
2947
3794
  throw new Error(`RecordNotFoundError: Feedback not found: ${feedbackId}`);
2948
3795
  }
2949
- if (existingRecord.payload.status === "resolved") {
2950
- throw new Error(`ProtocolViolationError: Feedback ${feedbackId} is already resolved`);
2951
- }
2952
- const updatedPayload = {
2953
- ...existingRecord.payload,
2954
- status: "resolved"
2955
- };
2956
- try {
2957
- const updatedRecord = {
2958
- ...existingRecord,
2959
- payload: updatedPayload
2960
- };
2961
- const signedRecord = await this.identity.signRecord(updatedRecord, actorId, "resolver");
2962
- await this.feedbackStore.write(signedRecord);
2963
- this.eventBus.publish({
2964
- type: "feedback.status.changed",
2965
- timestamp: Date.now(),
2966
- source: "feedback_adapter",
2967
- payload: {
2968
- feedbackId: updatedPayload.id,
2969
- oldStatus: existingRecord.payload.status,
2970
- newStatus: updatedPayload.status,
2971
- triggeredBy: actorId,
2972
- assignee: updatedPayload.assignee
2973
- }
2974
- });
2975
- return updatedPayload;
2976
- } catch (error) {
2977
- throw error;
2978
- }
3796
+ const resolveContent = content || `Feedback resolved by ${actorId}`;
3797
+ return await this.create({
3798
+ entityType: "feedback",
3799
+ entityId: feedbackId,
3800
+ type: "clarification",
3801
+ status: "resolved",
3802
+ content: resolveContent,
3803
+ resolvesFeedbackId: feedbackId
3804
+ }, actorId);
2979
3805
  }
2980
3806
  /**
2981
- * [EARS-7] Gets a specific FeedbackRecord by its ID for query.
3807
+ * [EARS-13, EARS-14] Gets a specific FeedbackRecord by its ID for query.
2982
3808
  *
2983
3809
  * Description: Gets a specific FeedbackRecord by its ID for query.
2984
3810
  * Implementation: Direct read from record store without modifications.
@@ -2990,11 +3816,11 @@ var FeedbackAdapter = class {
2990
3816
  return record ? record.payload : null;
2991
3817
  }
2992
3818
  /**
2993
- * [EARS-9] Gets all FeedbackRecords associated with a specific entity.
3819
+ * [EARS-15] Gets all FeedbackRecords associated with a specific entity.
2994
3820
  *
2995
3821
  * Description: Gets all FeedbackRecords associated with a specific entity.
2996
3822
  * Implementation: Reads all records and filters by matching entityId.
2997
- * Usage: Invoked by `gitgov feedback list` to display feedback for a task/cycle.
3823
+ * Usage: Invoked by `gitgov feedback list` to display feedback for a task/cycle/execution.
2998
3824
  * Returns: Array of FeedbackRecords filtered for the entity.
2999
3825
  */
3000
3826
  async getFeedbackByEntity(entityId) {
@@ -3009,11 +3835,11 @@ var FeedbackAdapter = class {
3009
3835
  return feedbacks;
3010
3836
  }
3011
3837
  /**
3012
- * [EARS-10] Gets all FeedbackRecords in the system for indexation.
3838
+ * [EARS-16] Gets all FeedbackRecords in the system for indexation.
3013
3839
  *
3014
3840
  * Description: Gets all FeedbackRecords in the system for complete indexation.
3015
3841
  * Implementation: Complete read from record store without filters.
3016
- * Usage: Invoked by `gitgov feedback list --all` and by MetricsAdapter for calculations.
3842
+ * Usage: Invoked by `gitgov feedback list` and by MetricsAdapter for calculations.
3017
3843
  * Returns: Complete array of all FeedbackRecords.
3018
3844
  */
3019
3845
  async getAllFeedback() {
@@ -3027,6 +3853,49 @@ var FeedbackAdapter = class {
3027
3853
  }
3028
3854
  return feedbacks;
3029
3855
  }
3856
+ /**
3857
+ * [EARS-17, EARS-18, EARS-19, EARS-20] Builds the complete conversation tree for a feedback.
3858
+ *
3859
+ * Description: Recursively constructs the conversation tree for a feedback.
3860
+ * Implementation: Reads root feedback, finds all responses, builds tree recursively until maxDepth.
3861
+ * Usage: Invoked by `gitgov feedback thread` and `gitgov feedback show --thread`.
3862
+ * Returns: FeedbackThread object with tree structure.
3863
+ */
3864
+ async getFeedbackThread(feedbackId, maxDepth = Infinity) {
3865
+ return await this.buildThread(feedbackId, maxDepth, 0);
3866
+ }
3867
+ /**
3868
+ * Private helper: Recursively builds conversation thread.
3869
+ */
3870
+ async buildThread(feedbackId, maxDepth, currentDepth) {
3871
+ if (currentDepth >= maxDepth) {
3872
+ throw new Error(`Max depth ${maxDepth} reached for feedback thread`);
3873
+ }
3874
+ const feedback = await this.getFeedback(feedbackId);
3875
+ if (!feedback) {
3876
+ throw new Error(`RecordNotFoundError: Feedback not found: ${feedbackId}`);
3877
+ }
3878
+ const allFeedbacks = await this.getAllFeedback();
3879
+ const responses = allFeedbacks.filter(
3880
+ (f) => f.entityType === "feedback" && f.entityId === feedbackId
3881
+ );
3882
+ const responseThreads = [];
3883
+ for (const response of responses) {
3884
+ try {
3885
+ const thread = await this.buildThread(response.id, maxDepth, currentDepth + 1);
3886
+ responseThreads.push(thread);
3887
+ } catch (error) {
3888
+ if (error instanceof Error && error.message.includes("Max depth")) {
3889
+ continue;
3890
+ }
3891
+ throw error;
3892
+ }
3893
+ }
3894
+ return {
3895
+ feedback,
3896
+ responses: responseThreads
3897
+ };
3898
+ }
3030
3899
  };
3031
3900
 
3032
3901
  // src/adapters/execution_adapter/index.ts
@@ -3117,16 +3986,7 @@ var ExecutionAdapter = class {
3117
3986
  * Returns: Complete and signed ExecutionRecord.
3118
3987
  */
3119
3988
  async create(payload, actorId) {
3120
- if (!payload.taskId) {
3121
- throw new Error("DetailedValidationError: taskId is required");
3122
- }
3123
- if (!payload.result) {
3124
- throw new Error("DetailedValidationError: result is required");
3125
- }
3126
- if (payload.result && payload.result.length < 10) {
3127
- throw new Error("DetailedValidationError: result must be at least 10 characters");
3128
- }
3129
- if (this.taskStore) {
3989
+ if (this.taskStore && payload.taskId) {
3130
3990
  const taskExists = await this.taskStore.read(payload.taskId);
3131
3991
  if (!taskExists) {
3132
3992
  throw new Error(`RecordNotFoundError: Task not found: ${payload.taskId}`);
@@ -3142,9 +4002,9 @@ var ExecutionAdapter = class {
3142
4002
  signatures: [{
3143
4003
  keyId: actorId,
3144
4004
  role: "author",
4005
+ notes: "Execution recorded",
3145
4006
  signature: "placeholder",
3146
- timestamp: Date.now(),
3147
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
4007
+ timestamp: Date.now()
3148
4008
  }]
3149
4009
  },
3150
4010
  payload: validatedPayload
@@ -3164,9 +4024,6 @@ var ExecutionAdapter = class {
3164
4024
  });
3165
4025
  return validatedPayload;
3166
4026
  } catch (error) {
3167
- if (error instanceof Error && error.message.includes("DetailedValidationError")) {
3168
- throw error;
3169
- }
3170
4027
  throw error;
3171
4028
  }
3172
4029
  }
@@ -3280,31 +4137,21 @@ async function validateFullChangelogRecord(record, getPublicKey) {
3280
4137
  // src/factories/changelog_factory.ts
3281
4138
  async function createChangelogRecord(payload) {
3282
4139
  const timestamp = Math.floor(Date.now() / 1e3);
3283
- let id = payload.id;
3284
- if (!id && payload.entityType && payload.entityId) {
3285
- id = generateChangelogId(payload.entityType, payload.entityId, timestamp);
3286
- }
3287
4140
  const changelog = {
3288
4141
  // Required fields
3289
- id: id || "",
3290
- entityType: payload.entityType || "task",
3291
- entityId: payload.entityId || "",
3292
- changeType: payload.changeType || "completion",
4142
+ id: payload.id || "",
3293
4143
  title: payload.title || "",
3294
4144
  description: payload.description || "",
3295
- timestamp: payload.timestamp || timestamp,
3296
- trigger: payload.trigger || "manual",
3297
- triggeredBy: payload.triggeredBy || "",
3298
- reason: payload.reason || "",
3299
- riskLevel: payload.riskLevel || "low",
3300
- // Optional fields (preserve if provided)
3301
- ...payload.affectedSystems && { affectedSystems: payload.affectedSystems },
3302
- ...payload.usersAffected !== void 0 && { usersAffected: payload.usersAffected },
3303
- ...payload.downtime !== void 0 && { downtime: payload.downtime },
3304
- ...payload.files && { files: payload.files },
4145
+ relatedTasks: payload.relatedTasks || [],
4146
+ completedAt: payload.completedAt || timestamp,
4147
+ // Optional fields (only include if provided)
4148
+ ...payload.relatedCycles && { relatedCycles: payload.relatedCycles },
4149
+ ...payload.relatedExecutions && { relatedExecutions: payload.relatedExecutions },
4150
+ ...payload.version && { version: payload.version },
4151
+ ...payload.tags && { tags: payload.tags },
3305
4152
  ...payload.commits && { commits: payload.commits },
3306
- ...payload.rollbackInstructions && { rollbackInstructions: payload.rollbackInstructions },
3307
- ...payload.references && { references: payload.references }
4153
+ ...payload.files && { files: payload.files },
4154
+ ...payload.notes && { notes: payload.notes }
3308
4155
  };
3309
4156
  const validation = validateChangelogRecordDetailed(changelog);
3310
4157
  if (!validation.isValid) {
@@ -3328,54 +4175,44 @@ var ChangelogAdapter = class {
3328
4175
  this.cycleStore = dependencies.cycleStore;
3329
4176
  }
3330
4177
  /**
3331
- * [EARS-1] Records a significant change in any entity with complete context and conditional validation.
4178
+ * [EARS-1] Records a deliverable/release note.
3332
4179
  *
3333
- * Description: Records a significant change in any entity of the ecosystem with complete context and conditional validation.
3334
- * Implementation: Validates entity existence (optional), builds record with factory, validates conditional fields, signs with actorId, persists and emits event.
3335
- * Usage: Invoked by `gitgov changelog add` to document changes in tasks, cycles, agents, systems, configurations.
3336
- * Returns: Complete and signed ChangelogRecord with 19 fields.
4180
+ * Description: Aggregates multiple tasks into a single deliverable/release note.
4181
+ * Implementation: Validates required fields, builds record with factory, signs, persists and emits event.
4182
+ * Usage: Invoked by `gitgov changelog add` to document deliverables.
4183
+ * Returns: Complete and signed ChangelogRecord.
3337
4184
  */
3338
4185
  async create(payload, actorId) {
3339
- if (!payload.entityType) {
3340
- throw new Error("DetailedValidationError: entityType is required");
3341
- }
3342
- if (!payload.entityId) {
3343
- throw new Error("DetailedValidationError: entityId is required");
3344
- }
3345
- if (!["task", "cycle", "agent", "system", "configuration"].includes(payload.entityType)) {
3346
- throw new Error("DetailedValidationError: entityType must be task, cycle, agent, system, or configuration");
3347
- }
3348
- if (payload.changeType && !["creation", "completion", "update", "deletion", "hotfix"].includes(payload.changeType)) {
3349
- throw new Error("DetailedValidationError: changeType must be creation, completion, update, deletion, or hotfix");
4186
+ if (!payload.title || payload.title.length < 10) {
4187
+ throw new Error("DetailedValidationError: title is required and must be at least 10 characters");
3350
4188
  }
3351
- if (payload.title && payload.title.length < 10) {
3352
- throw new Error("DetailedValidationError: title must be at least 10 characters");
4189
+ if (!payload.description || payload.description.length < 20) {
4190
+ throw new Error("DetailedValidationError: description is required and must be at least 20 characters");
3353
4191
  }
3354
- if (payload.description && payload.description.length < 20) {
3355
- throw new Error("DetailedValidationError: description must be at least 20 characters");
4192
+ if (!payload.relatedTasks || payload.relatedTasks.length === 0) {
4193
+ throw new Error("DetailedValidationError: relatedTasks is required and must contain at least one task ID");
3356
4194
  }
3357
- if (payload.riskLevel === "high" && !payload.rollbackInstructions) {
3358
- throw new Error("DetailedValidationError: rollbackInstructions is required when riskLevel is high");
3359
- }
3360
- if (payload.riskLevel === "critical" && !payload.rollbackInstructions) {
3361
- throw new Error("DetailedValidationError: rollbackInstructions is required when riskLevel is critical");
3362
- }
3363
- if (payload.changeType === "completion" && (!payload.references?.tasks || payload.references.tasks.length === 0)) {
3364
- throw new Error("DetailedValidationError: references.tasks is required when changeType is completion");
3365
- }
3366
- if (payload.entityType === "task" && this.taskStore) {
3367
- const taskExists = await this.taskStore.read(payload.entityId);
3368
- if (!taskExists) {
3369
- throw new Error(`RecordNotFoundError: Task not found: ${payload.entityId}`);
4195
+ if (this.taskStore && payload.relatedTasks) {
4196
+ for (const taskId of payload.relatedTasks) {
4197
+ const taskExists = await this.taskStore.read(taskId);
4198
+ if (!taskExists) {
4199
+ throw new Error(`RecordNotFoundError: Task not found: ${taskId}`);
4200
+ }
3370
4201
  }
3371
4202
  }
3372
- if (payload.entityType === "cycle" && this.cycleStore) {
3373
- const cycleExists = await this.cycleStore.read(payload.entityId);
3374
- if (!cycleExists) {
3375
- throw new Error(`RecordNotFoundError: Cycle not found: ${payload.entityId}`);
4203
+ if (this.cycleStore && payload.relatedCycles) {
4204
+ for (const cycleId of payload.relatedCycles) {
4205
+ const cycleExists = await this.cycleStore.read(cycleId);
4206
+ if (!cycleExists) {
4207
+ throw new Error(`RecordNotFoundError: Cycle not found: ${cycleId}`);
4208
+ }
3376
4209
  }
3377
4210
  }
3378
4211
  try {
4212
+ const timestamp = payload.completedAt || Math.floor(Date.now() / 1e3);
4213
+ if (!payload.id) {
4214
+ payload.id = generateChangelogId(payload.title, timestamp);
4215
+ }
3379
4216
  const validatedPayload = await createChangelogRecord(payload);
3380
4217
  const unsignedRecord = {
3381
4218
  header: {
@@ -3385,9 +4222,9 @@ var ChangelogAdapter = class {
3385
4222
  signatures: [{
3386
4223
  keyId: actorId,
3387
4224
  role: "author",
4225
+ notes: "Changelog entry created",
3388
4226
  signature: "placeholder",
3389
- timestamp: Date.now(),
3390
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
4227
+ timestamp: Date.now()
3391
4228
  }]
3392
4229
  },
3393
4230
  payload: validatedPayload
@@ -3400,13 +4237,9 @@ var ChangelogAdapter = class {
3400
4237
  source: "changelog_adapter",
3401
4238
  payload: {
3402
4239
  changelogId: validatedPayload.id,
3403
- entityId: validatedPayload.entityId,
3404
- entityType: validatedPayload.entityType,
3405
- changeType: validatedPayload.changeType,
3406
- triggeredBy: actorId,
3407
- riskLevel: validatedPayload.riskLevel,
4240
+ relatedTasks: validatedPayload.relatedTasks,
3408
4241
  title: validatedPayload.title,
3409
- trigger: validatedPayload.trigger
4242
+ version: validatedPayload.version
3410
4243
  }
3411
4244
  });
3412
4245
  return validatedPayload;
@@ -3418,70 +4251,78 @@ var ChangelogAdapter = class {
3418
4251
  }
3419
4252
  }
3420
4253
  /**
3421
- * [EARS-9] Gets a specific ChangelogRecord by its ID for historical query.
3422
- *
3423
- * Description: Gets a specific ChangelogRecord by its ID for historical query.
3424
- * Implementation: Direct read from record store without modifications.
3425
- * Usage: Invoked by `gitgov changelog show` to display change details.
3426
- * Returns: ChangelogRecord found or null if it doesn't exist.
4254
+ * [EARS-9] Gets a specific ChangelogRecord by its ID.
3427
4255
  */
3428
4256
  async getChangelog(changelogId) {
3429
4257
  const record = await this.changelogStore.read(changelogId);
3430
4258
  return record ? record.payload : null;
3431
4259
  }
3432
4260
  /**
3433
- * [EARS-11] Gets all ChangelogRecords associated with a specific entity.
3434
- *
3435
- * Description: Gets all ChangelogRecords associated with a specific entity with optional type filtering.
3436
- * Implementation: Reads all records and filters by matching entityId, optionally by entityType.
3437
- * Usage: Invoked by `gitgov changelog list` to display history for any system entity.
3438
- * Returns: Array of ChangelogRecords filtered for the entity.
4261
+ * [EARS-11] Gets all ChangelogRecords that include a specific task.
3439
4262
  */
3440
- async getChangelogsByEntity(entityId, entityType) {
4263
+ async getChangelogsByTask(taskId) {
3441
4264
  const ids = await this.changelogStore.list();
3442
4265
  const changelogs = [];
3443
4266
  for (const id of ids) {
3444
4267
  const record = await this.changelogStore.read(id);
3445
- if (record && record.payload.entityId === entityId) {
3446
- if (!entityType || record.payload.entityType === entityType) {
3447
- changelogs.push(record.payload);
3448
- }
4268
+ if (record && record.payload.relatedTasks.includes(taskId)) {
4269
+ changelogs.push(record.payload);
3449
4270
  }
3450
4271
  }
3451
4272
  return changelogs;
3452
4273
  }
3453
4274
  /**
3454
- * [EARS-12] Gets all ChangelogRecords in the system for complete indexation.
3455
- *
3456
- * Description: Gets all ChangelogRecords in the system for complete indexation.
3457
- * Implementation: Complete read from record store without filters.
3458
- * Usage: Invoked by `gitgov changelog list --all` and MetricsAdapter for activity analysis.
3459
- * Returns: Complete array of all ChangelogRecords.
4275
+ * [EARS-11, EARS-12, EARS-13] Gets all ChangelogRecords with optional filtering and sorting.
3460
4276
  */
3461
- async getAllChangelogs() {
4277
+ async getAllChangelogs(options) {
3462
4278
  const ids = await this.changelogStore.list();
3463
- const changelogs = [];
4279
+ let changelogs = [];
3464
4280
  for (const id of ids) {
3465
4281
  const record = await this.changelogStore.read(id);
3466
4282
  if (record) {
3467
4283
  changelogs.push(record.payload);
3468
4284
  }
3469
4285
  }
4286
+ if (options?.tags && options.tags.length > 0) {
4287
+ changelogs = changelogs.filter((changelog) => {
4288
+ if (!changelog.tags) return false;
4289
+ return options.tags.some((tag) => changelog.tags.includes(tag));
4290
+ });
4291
+ }
4292
+ if (options?.version) {
4293
+ changelogs = changelogs.filter((changelog) => changelog.version === options.version);
4294
+ }
4295
+ const sortBy = options?.sortBy || "completedAt";
4296
+ const sortOrder = options?.sortOrder || "desc";
4297
+ changelogs.sort((a, b) => {
4298
+ let compareValue = 0;
4299
+ if (sortBy === "completedAt") {
4300
+ compareValue = a.completedAt - b.completedAt;
4301
+ } else if (sortBy === "title") {
4302
+ compareValue = a.title.localeCompare(b.title);
4303
+ }
4304
+ return sortOrder === "asc" ? compareValue : -compareValue;
4305
+ });
4306
+ if (options?.limit && options.limit > 0) {
4307
+ changelogs = changelogs.slice(0, options.limit);
4308
+ }
3470
4309
  return changelogs;
3471
4310
  }
3472
4311
  /**
3473
- * [EARS-13] Gets recent ChangelogRecords ordered by timestamp for dashboard and monitoring.
3474
- *
3475
- * Description: Gets recent ChangelogRecords ordered by timestamp for dashboard and monitoring.
3476
- * Implementation: Reads all records, sorts by timestamp descending and applies limit.
3477
- * Usage: Invoked by `gitgov changelog list --recent` and dashboard for activity monitoring.
3478
- * Returns: Array of ChangelogRecords limited and ordered by timestamp.
4312
+ * [EARS-13] Gets recent ChangelogRecords ordered by completedAt.
3479
4313
  */
3480
4314
  async getRecentChangelogs(limit) {
3481
4315
  const allChangelogs = await this.getAllChangelogs();
3482
- const sortedChangelogs = allChangelogs.sort((a, b) => b.timestamp - a.timestamp);
4316
+ const sortedChangelogs = allChangelogs.sort((a, b) => b.completedAt - a.completedAt);
3483
4317
  return sortedChangelogs.slice(0, limit);
3484
4318
  }
4319
+ /**
4320
+ * Legacy method for backwards compatibility - maps to getChangelogsByTask
4321
+ * @deprecated Use getChangelogsByTask instead
4322
+ */
4323
+ async getChangelogsByEntity(entityId, _entityType) {
4324
+ return this.getChangelogsByTask(entityId);
4325
+ }
3485
4326
  };
3486
4327
 
3487
4328
  // src/adapters/metrics_adapter/index.ts
@@ -3555,13 +4396,17 @@ var MetricsAdapter = class {
3555
4396
  }
3556
4397
  const task = taskRecord.payload;
3557
4398
  let feedbacks = [];
4399
+ let allFeedbacks = [];
3558
4400
  let executions = [];
3559
4401
  if (this.feedbackStore) {
3560
4402
  const feedbackIds = await this.feedbackStore.list();
3561
4403
  for (const id of feedbackIds) {
3562
4404
  const record = await this.feedbackStore.read(id);
3563
- if (record && record.payload.entityId === taskId) {
3564
- feedbacks.push(record.payload);
4405
+ if (record) {
4406
+ allFeedbacks.push(record.payload);
4407
+ if (record.payload.entityId === taskId) {
4408
+ feedbacks.push(record.payload);
4409
+ }
3565
4410
  }
3566
4411
  }
3567
4412
  }
@@ -3576,7 +4421,13 @@ var MetricsAdapter = class {
3576
4421
  }
3577
4422
  const timeInCurrentStage = this.calculateTimeInCurrentStage(task);
3578
4423
  const stalenessIndex = this.calculateStalenessIndex([task]);
3579
- const blockingFeedbacks = feedbacks.filter((f) => f.type === "blocking" && f.status === "open").length;
4424
+ const blockingFeedbacks = feedbacks.filter((f) => {
4425
+ if (f.type !== "blocking" || f.status !== "open") return false;
4426
+ const hasResolution = allFeedbacks.some(
4427
+ (resolution) => resolution.entityType === "feedback" && resolution.resolvesFeedbackId === f.id && resolution.status === "resolved"
4428
+ );
4429
+ return !hasResolution;
4430
+ }).length;
3580
4431
  const lastActivity = executions.length > 0 ? Math.max(...executions.map((e) => this.getTimestampFromId(e.id))) : this.getTimestampFromId(task.id);
3581
4432
  const recommendations = [];
3582
4433
  if (timeInCurrentStage > 7) recommendations.push("Task has been stagnant for over 7 days");
@@ -4034,10 +4885,6 @@ var BacklogAdapter = class {
4034
4885
  "feedback.created",
4035
4886
  (event) => this.handleFeedbackCreated(event)
4036
4887
  );
4037
- this.eventBus.subscribe(
4038
- "feedback.status.changed",
4039
- (event) => this.handleFeedbackResolved(event)
4040
- );
4041
4888
  this.eventBus.subscribe(
4042
4889
  "execution.created",
4043
4890
  (event) => this.handleExecutionCreated(event)
@@ -4069,9 +4916,9 @@ var BacklogAdapter = class {
4069
4916
  signatures: [{
4070
4917
  keyId: actorId,
4071
4918
  role: "author",
4919
+ notes: "Task created",
4072
4920
  signature: "placeholder",
4073
- timestamp: Date.now(),
4074
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
4921
+ timestamp: Date.now()
4075
4922
  }]
4076
4923
  },
4077
4924
  payload: validatedPayload
@@ -4174,9 +5021,9 @@ var BacklogAdapter = class {
4174
5021
  const tempSignature = {
4175
5022
  keyId: actorId,
4176
5023
  role: "approver",
5024
+ notes: "Task approval",
4177
5025
  signature: "temp-signature",
4178
- timestamp: Date.now(),
4179
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
5026
+ timestamp: Date.now()
4180
5027
  };
4181
5028
  const context = {
4182
5029
  task,
@@ -4507,7 +5354,16 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4507
5354
  }
4508
5355
  // ===== PHASE 3: EVENT HANDLERS (NEW IMPLEMENTATION) =====
4509
5356
  /**
4510
- * [EARS-31] Handles feedback created events - pauses task if blocking
5357
+ * [EARS-31, EARS-33, EARS-34] Handles feedback created events (Immutable Pattern)
5358
+ *
5359
+ * This handler respects the immutable feedback pattern:
5360
+ * - Case 1: Blocking feedback created → pause task if active/ready
5361
+ * - Case 2: Feedback resolving another feedback → resume task if no more blocks
5362
+ *
5363
+ * The immutable pattern means:
5364
+ * - Original feedbacks NEVER change status
5365
+ * - Resolution is expressed by creating a NEW feedback pointing to the original
5366
+ * - We detect resolution via: entityType='feedback' + status='resolved' + resolvesFeedbackId
4511
5367
  */
4512
5368
  async handleFeedbackCreated(event) {
4513
5369
  try {
@@ -4517,85 +5373,72 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4517
5373
  processedAt: Date.now(),
4518
5374
  sourceAdapter: "backlog_adapter"
4519
5375
  };
4520
- if (event.payload.type !== "blocking") {
4521
- return;
4522
- }
4523
- const feedbackRecord = await this.feedbackStore.read(event.payload.feedbackId);
4524
- if (!feedbackRecord) {
4525
- console.warn(`Feedback not found: ${event.payload.feedbackId}`);
4526
- return;
4527
- }
4528
- const task = await this.getTask(feedbackRecord.payload.entityId);
4529
- if (!task) {
4530
- console.warn(`Task not found for feedback: ${feedbackRecord.payload.entityId}`);
5376
+ if (event.payload.type === "blocking" && event.payload.entityType === "task") {
5377
+ const task = await this.getTask(event.payload.entityId);
5378
+ if (!task) {
5379
+ console.warn(`Task not found for feedback: ${event.payload.entityId}`);
5380
+ return;
5381
+ }
5382
+ if (!["active", "ready"].includes(task.status)) {
5383
+ return;
5384
+ }
5385
+ const updatedTask = { ...task, status: "paused" };
5386
+ const taskRecord = await this.taskStore.read(task.id);
5387
+ if (taskRecord) {
5388
+ const updatedRecord = { ...taskRecord, payload: updatedTask };
5389
+ await this.taskStore.write(updatedRecord);
5390
+ this.eventBus.publish({
5391
+ type: "task.status.changed",
5392
+ timestamp: Date.now(),
5393
+ source: "backlog_adapter",
5394
+ payload: {
5395
+ taskId: task.id,
5396
+ oldStatus: task.status,
5397
+ newStatus: "paused",
5398
+ actorId: "system"
5399
+ },
5400
+ metadata
5401
+ });
5402
+ }
4531
5403
  return;
4532
5404
  }
4533
- if (!["active", "ready"].includes(task.status)) {
5405
+ if (event.payload.entityType === "feedback" && event.payload.status === "resolved" && event.payload.resolvesFeedbackId) {
5406
+ const originalFeedback = await this.feedbackAdapter.getFeedback(event.payload.resolvesFeedbackId);
5407
+ if (!originalFeedback || originalFeedback.type !== "blocking") {
5408
+ return;
5409
+ }
5410
+ const task = await this.getTask(originalFeedback.entityId);
5411
+ if (!task || task.status !== "paused") {
5412
+ return;
5413
+ }
5414
+ const taskHealth = await this.metricsAdapter.getTaskHealth(task.id);
5415
+ if (taskHealth.blockingFeedbacks > 0) {
5416
+ return;
5417
+ }
5418
+ const updatedTask = { ...task, status: "active" };
5419
+ const taskRecord = await this.taskStore.read(task.id);
5420
+ if (taskRecord) {
5421
+ const updatedRecord = { ...taskRecord, payload: updatedTask };
5422
+ await this.taskStore.write(updatedRecord);
5423
+ this.eventBus.publish({
5424
+ type: "task.status.changed",
5425
+ timestamp: Date.now(),
5426
+ source: "backlog_adapter",
5427
+ payload: {
5428
+ taskId: task.id,
5429
+ oldStatus: "paused",
5430
+ newStatus: "active",
5431
+ actorId: "system"
5432
+ },
5433
+ metadata
5434
+ });
5435
+ }
4534
5436
  return;
4535
5437
  }
4536
- const updatedTask = { ...task, status: "paused" };
4537
- const taskRecord = await this.taskStore.read(task.id);
4538
- if (taskRecord) {
4539
- const updatedRecord = { ...taskRecord, payload: updatedTask };
4540
- await this.taskStore.write(updatedRecord);
4541
- this.eventBus.publish({
4542
- type: "task.status.changed",
4543
- timestamp: Date.now(),
4544
- source: "backlog_adapter",
4545
- payload: {
4546
- taskId: task.id,
4547
- oldStatus: task.status,
4548
- newStatus: "paused",
4549
- actorId: "system"
4550
- },
4551
- metadata
4552
- });
4553
- }
4554
5438
  } catch (error) {
4555
5439
  console.error("Error in handleFeedbackCreated:", error);
4556
5440
  }
4557
5441
  }
4558
- /**
4559
- * [EARS-33] Handles feedback resolved events - resumes task if no more blocks
4560
- */
4561
- async handleFeedbackResolved(event) {
4562
- try {
4563
- if (event.payload.oldStatus !== "open" || event.payload.newStatus !== "resolved") {
4564
- return;
4565
- }
4566
- const feedbackRecord = await this.feedbackStore.read(event.payload.feedbackId);
4567
- if (!feedbackRecord) {
4568
- return;
4569
- }
4570
- const task = await this.getTask(feedbackRecord.payload.entityId);
4571
- if (!task || task.status !== "paused") {
4572
- return;
4573
- }
4574
- const taskHealth = await this.metricsAdapter.getTaskHealth(task.id);
4575
- if (taskHealth.blockingFeedbacks > 0) {
4576
- return;
4577
- }
4578
- const updatedTask = { ...task, status: "active" };
4579
- const taskRecord = await this.taskStore.read(task.id);
4580
- if (taskRecord) {
4581
- const updatedRecord = { ...taskRecord, payload: updatedTask };
4582
- await this.taskStore.write(updatedRecord);
4583
- this.eventBus.publish({
4584
- type: "task.status.changed",
4585
- timestamp: Date.now(),
4586
- source: "backlog_adapter",
4587
- payload: {
4588
- taskId: task.id,
4589
- oldStatus: "paused",
4590
- newStatus: "active",
4591
- actorId: "system"
4592
- }
4593
- });
4594
- }
4595
- } catch (error) {
4596
- console.error("Error in handleFeedbackResolved:", error);
4597
- }
4598
- }
4599
5442
  /**
4600
5443
  * [EARS-35] Handles execution created events - transitions ready→active on first execution
4601
5444
  */
@@ -4649,29 +5492,31 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4649
5492
  console.warn(`Changelog not found: ${event.payload.changelogId}`);
4650
5493
  return;
4651
5494
  }
4652
- if (changelogRecord.payload.entityType !== "task") {
4653
- return;
4654
- }
4655
- const task = await this.getTask(changelogRecord.payload.entityId);
4656
- if (!task || task.status !== "done") {
5495
+ if (!changelogRecord.payload.relatedTasks || changelogRecord.payload.relatedTasks.length === 0) {
4657
5496
  return;
4658
5497
  }
4659
- const updatedTask = { ...task, status: "archived" };
4660
- const taskRecord = await this.taskStore.read(task.id);
4661
- if (taskRecord) {
4662
- const updatedRecord = { ...taskRecord, payload: updatedTask };
4663
- await this.taskStore.write(updatedRecord);
4664
- this.eventBus.publish({
4665
- type: "task.status.changed",
4666
- timestamp: Date.now(),
4667
- source: "backlog_adapter",
4668
- payload: {
4669
- taskId: task.id,
4670
- oldStatus: "done",
4671
- newStatus: "archived",
4672
- actorId: "system"
4673
- }
4674
- });
5498
+ for (const taskId of changelogRecord.payload.relatedTasks) {
5499
+ const task = await this.getTask(taskId);
5500
+ if (!task || task.status !== "done") {
5501
+ continue;
5502
+ }
5503
+ const updatedTask = { ...task, status: "archived" };
5504
+ const taskRecord = await this.taskStore.read(task.id);
5505
+ if (taskRecord) {
5506
+ const updatedRecord = { ...taskRecord, payload: updatedTask };
5507
+ await this.taskStore.write(updatedRecord);
5508
+ this.eventBus.publish({
5509
+ type: "task.status.changed",
5510
+ timestamp: Date.now(),
5511
+ source: "backlog_adapter",
5512
+ payload: {
5513
+ taskId: task.id,
5514
+ oldStatus: "done",
5515
+ newStatus: "archived",
5516
+ actorId: "system"
5517
+ }
5518
+ });
5519
+ }
4675
5520
  }
4676
5521
  } catch (error) {
4677
5522
  console.error("Error in handleChangelogCreated:", error);
@@ -4785,9 +5630,9 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4785
5630
  signatures: [{
4786
5631
  keyId: actorId,
4787
5632
  role: "author",
5633
+ notes: "Cycle created",
4788
5634
  signature: "placeholder",
4789
- timestamp: Date.now(),
4790
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
5635
+ timestamp: Date.now()
4791
5636
  }]
4792
5637
  },
4793
5638
  payload: validatedPayload
@@ -5146,11 +5991,14 @@ var FileIndexerAdapter = class {
5146
5991
  metrics: { ...systemStatus, ...productivityMetrics, ...collaborationMetrics },
5147
5992
  activityHistory,
5148
5993
  // NUEVO
5149
- tasks,
5994
+ tasks: tasks.map((t) => t.payload),
5995
+ // Extract payloads for IndexData
5150
5996
  enrichedTasks,
5151
5997
  // NUEVO - Tasks with activity metadata
5152
- cycles,
5153
- actors
5998
+ cycles: cycles.map((c) => c.payload),
5999
+ // Extract payloads for IndexData
6000
+ actors: actors.map((a) => a.payload)
6001
+ // Extract payloads for IndexData
5154
6002
  };
5155
6003
  const writeStart = performance.now();
5156
6004
  await this.writeCacheFile(indexData);
@@ -5218,19 +6066,19 @@ var FileIndexerAdapter = class {
5218
6066
  ]);
5219
6067
  recordsScanned = tasks.length + cycles.length;
5220
6068
  for (const task of tasks) {
5221
- if (!task.id || !task.description) {
6069
+ if (!task.payload.id || !task.payload.description) {
5222
6070
  errors.push({
5223
6071
  type: "schema_violation",
5224
- recordId: task.id || "unknown",
6072
+ recordId: task.payload.id || "unknown",
5225
6073
  message: "Task missing required fields"
5226
6074
  });
5227
6075
  }
5228
6076
  }
5229
6077
  for (const cycle of cycles) {
5230
- if (!cycle.id || !cycle.title) {
6078
+ if (!cycle.payload.id || !cycle.payload.title) {
5231
6079
  errors.push({
5232
6080
  type: "schema_violation",
5233
- recordId: cycle.id || "unknown",
6081
+ recordId: cycle.payload.id || "unknown",
5234
6082
  message: "Cycle missing required fields"
5235
6083
  });
5236
6084
  }
@@ -5303,7 +6151,8 @@ var FileIndexerAdapter = class {
5303
6151
  }
5304
6152
  // ===== HELPER METHODS =====
5305
6153
  /**
5306
- * Reads all tasks from taskStore
6154
+ * Reads all tasks from taskStore with full metadata (headers + payloads).
6155
+ * Returns complete GitGovTaskRecord objects including signatures for author/lastModifier extraction.
5307
6156
  */
5308
6157
  async readAllTasks() {
5309
6158
  const taskIds = await this.taskStore.list();
@@ -5311,13 +6160,13 @@ var FileIndexerAdapter = class {
5311
6160
  for (const id of taskIds) {
5312
6161
  const record = await this.taskStore.read(id);
5313
6162
  if (record) {
5314
- tasks.push(record.payload);
6163
+ tasks.push(record);
5315
6164
  }
5316
6165
  }
5317
6166
  return tasks;
5318
6167
  }
5319
6168
  /**
5320
- * Reads all cycles from cycleStore
6169
+ * Reads all cycles from cycleStore with full metadata.
5321
6170
  */
5322
6171
  async readAllCycles() {
5323
6172
  const cycleIds = await this.cycleStore.list();
@@ -5325,13 +6174,13 @@ var FileIndexerAdapter = class {
5325
6174
  for (const id of cycleIds) {
5326
6175
  const record = await this.cycleStore.read(id);
5327
6176
  if (record) {
5328
- cycles.push(record.payload);
6177
+ cycles.push(record);
5329
6178
  }
5330
6179
  }
5331
6180
  return cycles;
5332
6181
  }
5333
6182
  /**
5334
- * Reads all actors from actorStore (graceful degradation)
6183
+ * Reads all actors from actorStore (graceful degradation) with full metadata.
5335
6184
  */
5336
6185
  async readAllActors() {
5337
6186
  if (!this.actorStore) {
@@ -5342,13 +6191,13 @@ var FileIndexerAdapter = class {
5342
6191
  for (const id of actorIds) {
5343
6192
  const record = await this.actorStore.read(id);
5344
6193
  if (record) {
5345
- actors.push(record.payload);
6194
+ actors.push(record);
5346
6195
  }
5347
6196
  }
5348
6197
  return actors;
5349
6198
  }
5350
6199
  /**
5351
- * Reads all feedback from feedbackStore (graceful degradation)
6200
+ * Reads all feedback from feedbackStore (graceful degradation) with full metadata.
5352
6201
  */
5353
6202
  async readAllFeedback() {
5354
6203
  if (!this.feedbackStore) {
@@ -5359,13 +6208,13 @@ var FileIndexerAdapter = class {
5359
6208
  for (const id of feedbackIds) {
5360
6209
  const record = await this.feedbackStore.read(id);
5361
6210
  if (record) {
5362
- feedback.push(record.payload);
6211
+ feedback.push(record);
5363
6212
  }
5364
6213
  }
5365
6214
  return feedback;
5366
6215
  }
5367
6216
  /**
5368
- * Reads all executions from executionStore (graceful degradation)
6217
+ * Reads all executions from executionStore (graceful degradation) with full metadata.
5369
6218
  */
5370
6219
  async readAllExecutions() {
5371
6220
  if (!this.executionStore) {
@@ -5376,13 +6225,13 @@ var FileIndexerAdapter = class {
5376
6225
  for (const id of executionIds) {
5377
6226
  const record = await this.executionStore.read(id);
5378
6227
  if (record) {
5379
- executions.push(record.payload);
6228
+ executions.push(record);
5380
6229
  }
5381
6230
  }
5382
6231
  return executions;
5383
6232
  }
5384
6233
  /**
5385
- * Reads all changelogs from changelogStore (graceful degradation)
6234
+ * Reads all changelogs from changelogStore (graceful degradation) with full metadata.
5386
6235
  */
5387
6236
  async readAllChangelogs() {
5388
6237
  if (!this.changelogStore) {
@@ -5393,7 +6242,7 @@ var FileIndexerAdapter = class {
5393
6242
  for (const id of changelogIds) {
5394
6243
  const record = await this.changelogStore.read(id);
5395
6244
  if (record) {
5396
- changelogs.push(record.payload);
6245
+ changelogs.push(record);
5397
6246
  }
5398
6247
  }
5399
6248
  return changelogs;
@@ -5446,96 +6295,96 @@ var FileIndexerAdapter = class {
5446
6295
  const events = [];
5447
6296
  try {
5448
6297
  allRecords.tasks.forEach((task) => {
5449
- const timestampPart = task.id.split("-")[0];
6298
+ const timestampPart = task.payload.id.split("-")[0];
5450
6299
  if (timestampPart) {
5451
6300
  events.push({
5452
6301
  timestamp: parseInt(timestampPart),
5453
6302
  type: "task_created",
5454
- entityId: task.id,
5455
- entityTitle: task.title,
5456
- actorId: "human:camilo",
5457
- // TODO: Extraer del primer signature
5458
- metadata: { priority: task.priority, status: task.status }
6303
+ entityId: task.payload.id,
6304
+ entityTitle: task.payload.title,
6305
+ actorId: task.header.signatures[0]?.keyId || "unknown",
6306
+ // Extract from first signature
6307
+ metadata: { priority: task.payload.priority, status: task.payload.status }
5459
6308
  });
5460
6309
  }
5461
6310
  });
5462
6311
  allRecords.cycles.forEach((cycle) => {
5463
- const timestampPart = cycle.id.split("-")[0];
6312
+ const timestampPart = cycle.payload.id.split("-")[0];
5464
6313
  if (timestampPart) {
5465
6314
  events.push({
5466
6315
  timestamp: parseInt(timestampPart),
5467
6316
  type: "cycle_created",
5468
- entityId: cycle.id,
5469
- entityTitle: cycle.title,
5470
- actorId: "human:scrum-master",
5471
- // TODO: Extraer del primer signature
5472
- metadata: { status: cycle.status }
6317
+ entityId: cycle.payload.id,
6318
+ entityTitle: cycle.payload.title,
6319
+ actorId: cycle.header.signatures[0]?.keyId || "unknown",
6320
+ // Extract from first signature
6321
+ metadata: { status: cycle.payload.status }
5473
6322
  });
5474
6323
  }
5475
6324
  });
5476
6325
  allRecords.feedback.forEach((feedback) => {
5477
- const timestampPart = feedback.id.split("-")[0];
6326
+ const timestampPart = feedback.payload.id.split("-")[0];
5478
6327
  if (timestampPart) {
5479
6328
  const metadata = {
5480
- type: feedback.type,
5481
- resolution: feedback.status
6329
+ type: feedback.payload.type,
6330
+ resolution: feedback.payload.status
5482
6331
  };
5483
- if (feedback.assignee) {
5484
- metadata.assignee = feedback.assignee;
6332
+ if (feedback.payload.assignee) {
6333
+ metadata.assignee = feedback.payload.assignee;
5485
6334
  }
5486
6335
  const event = {
5487
6336
  timestamp: parseInt(timestampPart),
5488
6337
  type: "feedback_created",
5489
- entityId: feedback.id,
5490
- entityTitle: `${feedback.type}: ${feedback.content.slice(0, 40)}...`,
6338
+ entityId: feedback.payload.id,
6339
+ entityTitle: `${feedback.payload.type}: ${feedback.payload.content.slice(0, 40)}...`,
6340
+ actorId: feedback.header.signatures[0]?.keyId || feedback.payload.assignee || "unknown",
5491
6341
  metadata
5492
6342
  };
5493
- if (feedback.assignee) {
5494
- event.actorId = feedback.assignee;
5495
- }
5496
6343
  events.push(event);
5497
6344
  }
5498
6345
  });
5499
6346
  allRecords.changelogs.forEach((changelog) => {
5500
- const timestampPart = changelog.id.split("-")[0];
6347
+ const timestampPart = changelog.payload.id.split("-")[0];
5501
6348
  if (timestampPart) {
5502
- events.push({
6349
+ const event = {
5503
6350
  timestamp: parseInt(timestampPart),
5504
6351
  type: "changelog_created",
5505
- entityId: changelog.id,
5506
- entityTitle: changelog.title || "Release notes",
5507
- actorId: "agent:api-dev",
5508
- // TODO: Extraer del primer signature
5509
- metadata: { type: changelog.changeType }
5510
- });
6352
+ entityId: changelog.payload.id,
6353
+ entityTitle: changelog.payload.title || "Release notes",
6354
+ actorId: changelog.header.signatures[0]?.keyId || "unknown"
6355
+ };
6356
+ if (changelog.payload.version) {
6357
+ event.metadata = { version: changelog.payload.version };
6358
+ }
6359
+ events.push(event);
5511
6360
  }
5512
6361
  });
5513
6362
  allRecords.executions.forEach((execution) => {
5514
- const timestampPart = execution.id.split("-")[0];
6363
+ const timestampPart = execution.payload.id.split("-")[0];
5515
6364
  if (timestampPart) {
5516
6365
  events.push({
5517
6366
  timestamp: parseInt(timestampPart),
5518
6367
  type: "execution_created",
5519
- entityId: execution.id,
5520
- entityTitle: execution.title || `Working on ${execution.taskId.slice(-8)}`,
5521
- actorId: "human:developer",
5522
- // TODO: Extraer del primer signature
6368
+ entityId: execution.payload.id,
6369
+ entityTitle: execution.payload.title || `Working on ${execution.payload.taskId.slice(-8)}`,
6370
+ actorId: execution.header.signatures[0]?.keyId || "unknown",
6371
+ // Extract from first signature
5523
6372
  metadata: {
5524
- executionType: execution.type || "development",
5525
- taskId: execution.taskId
6373
+ executionType: execution.payload.type || "development",
6374
+ taskId: execution.payload.taskId
5526
6375
  }
5527
6376
  });
5528
6377
  }
5529
6378
  });
5530
6379
  allRecords.actors.forEach((actor) => {
5531
- const timestampPart = actor.id.split("-")[0];
6380
+ const timestampPart = actor.payload.id.split("-")[0];
5532
6381
  if (timestampPart) {
5533
6382
  events.push({
5534
6383
  timestamp: parseInt(timestampPart),
5535
6384
  type: "actor_created",
5536
- entityId: actor.id,
5537
- entityTitle: `${actor.displayName} joined (${actor.type})`,
5538
- metadata: { type: actor.type }
6385
+ entityId: actor.payload.id,
6386
+ entityTitle: `${actor.payload.displayName} joined (${actor.payload.type})`,
6387
+ metadata: { type: actor.payload.type }
5539
6388
  });
5540
6389
  }
5541
6390
  });
@@ -5572,34 +6421,34 @@ var FileIndexerAdapter = class {
5572
6421
  } catch (error) {
5573
6422
  }
5574
6423
  const relatedFeedback = relatedRecords.feedback.filter(
5575
- (f) => f.entityId === task.id || f.content.includes(task.id)
6424
+ (f) => f.payload.entityId === task.id || f.payload.content.includes(task.id)
5576
6425
  );
5577
6426
  for (const feedback of relatedFeedback) {
5578
- const feedbackTime = this.getTimestampFromId(feedback.id) * 1e3;
6427
+ const feedbackTime = this.getTimestampFromId(feedback.payload.id) * 1e3;
5579
6428
  if (feedbackTime > lastUpdated) {
5580
6429
  lastUpdated = feedbackTime;
5581
6430
  lastActivityType = "feedback_received";
5582
- recentActivity = `${feedback.type} feedback: ${feedback.content.slice(0, 30)}...`;
6431
+ recentActivity = `${feedback.payload.type} feedback: ${feedback.payload.content.slice(0, 30)}...`;
5583
6432
  }
5584
6433
  }
5585
- const relatedExecutions = relatedRecords.executions.filter((e) => e.taskId === task.id);
6434
+ const relatedExecutions = relatedRecords.executions.filter((e) => e.payload.taskId === task.id);
5586
6435
  for (const execution of relatedExecutions) {
5587
- const executionTime = this.getTimestampFromId(execution.id) * 1e3;
6436
+ const executionTime = this.getTimestampFromId(execution.payload.id) * 1e3;
5588
6437
  if (executionTime > lastUpdated) {
5589
6438
  lastUpdated = executionTime;
5590
6439
  lastActivityType = "execution_added";
5591
- recentActivity = `Execution: ${execution.title || "Work logged"}`;
6440
+ recentActivity = `Execution: ${execution.payload.title || "Work logged"}`;
5592
6441
  }
5593
6442
  }
5594
6443
  const relatedChangelogs = relatedRecords.changelogs.filter(
5595
- (c) => c.entityId === task.id || c.references?.tasks?.includes(task.id) || c.description?.includes(task.id)
6444
+ (c) => c.payload.relatedTasks.includes(task.id) || c.payload.description?.includes(task.id)
5596
6445
  );
5597
6446
  for (const changelog of relatedChangelogs) {
5598
- const changelogTime = this.getTimestampFromId(changelog.id) * 1e3;
6447
+ const changelogTime = this.getTimestampFromId(changelog.payload.id) * 1e3;
5599
6448
  if (changelogTime > lastUpdated) {
5600
6449
  lastUpdated = changelogTime;
5601
6450
  lastActivityType = "changelog_created";
5602
- recentActivity = `Changelog: ${changelog.title}`;
6451
+ recentActivity = `Changelog: ${changelog.payload.title}`;
5603
6452
  }
5604
6453
  }
5605
6454
  return { lastUpdated, lastActivityType, recentActivity };
@@ -5614,11 +6463,13 @@ var FileIndexerAdapter = class {
5614
6463
  }
5615
6464
  /**
5616
6465
  * [EARS-22] Enrich a TaskRecord with activity metadata
6466
+ * @param task - Full GitGovTaskRecord with header.signatures for author/lastModifier extraction
6467
+ * @param relatedRecords - All related records with full metadata
5617
6468
  */
5618
6469
  async enrichTaskRecord(task, relatedRecords) {
5619
- const { lastUpdated, lastActivityType, recentActivity } = await this.calculateLastUpdated(task, relatedRecords);
6470
+ const { lastUpdated, lastActivityType, recentActivity } = await this.calculateLastUpdated(task.payload, relatedRecords);
5620
6471
  return {
5621
- ...task,
6472
+ ...task.payload,
5622
6473
  lastUpdated,
5623
6474
  lastActivityType,
5624
6475
  recentActivity
@@ -6459,7 +7310,6 @@ var workflow_methodology_scrum_default = {
6459
7310
  required_agents: [
6460
7311
  {
6461
7312
  id: "agent:scrum-master",
6462
- gremio: "operations",
6463
7313
  engine: {
6464
7314
  type: "local",
6465
7315
  entrypoint: "@gitgov/agent-scrum-master"
@@ -6485,7 +7335,6 @@ var workflow_methodology_scrum_default = {
6485
7335
  },
6486
7336
  {
6487
7337
  id: "agent:product-owner-assistant",
6488
- gremio: "strategy",
6489
7338
  engine: {
6490
7339
  type: "mcp",
6491
7340
  url: "http://localhost:8080/product-owner-mcp"
@@ -6561,11 +7410,19 @@ var WorkflowMethodologyAdapter = class _WorkflowMethodologyAdapter {
6561
7410
  return this.config;
6562
7411
  }
6563
7412
  /**
6564
- * Gets the guild tag from a task's tags array
7413
+ * Determines which signature group to use for validation.
7414
+ * Checks all available signature groups and returns the first one where
7415
+ * the actor has matching capability roles.
6565
7416
  */
6566
- getTaskGuild(context) {
6567
- const guildTag = context.task.tags?.find((tag) => tag.startsWith("guild:"));
6568
- return guildTag ? guildTag.replace("guild:", "") : "__default__";
7417
+ getApplicableSignatureGroup(signatureRules, actor) {
7418
+ for (const [groupName, ruleSet] of Object.entries(signatureRules)) {
7419
+ if (groupName === "__default__") continue;
7420
+ const hasMatchingRole = actor.roles?.some((role) => ruleSet.capability_roles?.includes(role));
7421
+ if (hasMatchingRole) {
7422
+ return groupName;
7423
+ }
7424
+ }
7425
+ return "__default__";
6569
7426
  }
6570
7427
  /**
6571
7428
  * Determines if a state transition is legal according to the methodology
@@ -6589,7 +7446,6 @@ var WorkflowMethodologyAdapter = class _WorkflowMethodologyAdapter {
6589
7446
  */
6590
7447
  async validateSignature(signature, context) {
6591
7448
  const config = this.getConfig();
6592
- const guild = this.getTaskGuild(context);
6593
7449
  if (!context.transitionTo) {
6594
7450
  throw new Error('ValidationContext must include "transitionTo" for signature validation.');
6595
7451
  }
@@ -6605,7 +7461,8 @@ var WorkflowMethodologyAdapter = class _WorkflowMethodologyAdapter {
6605
7461
  }
6606
7462
  const signatureRules = transitionConfig.requires.signatures;
6607
7463
  if (!signatureRules) return true;
6608
- const ruleSet = signatureRules[guild] || signatureRules["__default__"];
7464
+ const signatureGroup = this.getApplicableSignatureGroup(signatureRules, actor);
7465
+ const ruleSet = signatureRules[signatureGroup];
6609
7466
  if (!ruleSet) return false;
6610
7467
  if (signature.role !== ruleSet.role) {
6611
7468
  return false;
@@ -6720,9 +7577,11 @@ __export(factories_exports, {
6720
7577
  createChangelogRecord: () => createChangelogRecord,
6721
7578
  createCycleRecord: () => createCycleRecord,
6722
7579
  createDefaultWorkflowMethodologyConfig: () => createDefaultWorkflowMethodologyConfig,
7580
+ createEmbeddedMetadataRecord: () => createEmbeddedMetadataRecord,
6723
7581
  createExecutionRecord: () => createExecutionRecord,
6724
7582
  createFeedbackRecord: () => createFeedbackRecord,
6725
7583
  createTaskRecord: () => createTaskRecord,
7584
+ createTestSignature: () => createTestSignature,
6726
7585
  createWorkflowMethodologyConfig: () => createWorkflowMethodologyConfig
6727
7586
  });
6728
7587
 
@@ -6962,6 +7821,68 @@ async function createDefaultWorkflowMethodologyConfig() {
6962
7821
  });
6963
7822
  }
6964
7823
 
7824
+ // src/factories/embedded_metadata_factory.ts
7825
+ function createTestSignature(keyId = "human:test-user", role = "author", notes = "Test signature - unsigned") {
7826
+ const timestamp = Math.floor(Date.now() / 1e3);
7827
+ return {
7828
+ keyId,
7829
+ role,
7830
+ notes,
7831
+ signature: "dGVzdHNpZ25hdHVyZWJhc2U2NGVuY29kZWRkdW1teWZvcnRlc3RpbmdwdXJwb3Nlc29ubHlub3RyZWFsY3J5cHRvZ3JhcGh5PT0=",
7832
+ // Dummy 88-char base64 for testing (matches Ed25519 signature length)
7833
+ timestamp
7834
+ };
7835
+ }
7836
+ function inferTypeFromPayload(payload) {
7837
+ if ("engine" in payload) return "agent";
7838
+ if ("taskId" in payload && "result" in payload) return "execution";
7839
+ if ("relatedTasks" in payload && "completedAt" in payload) return "changelog";
7840
+ if ("entityType" in payload && "entityId" in payload) return "feedback";
7841
+ if ("status" in payload && "taskIds" in payload) return "cycle";
7842
+ if ("priority" in payload && "description" in payload) return "task";
7843
+ if ("displayName" in payload && "publicKey" in payload) return "actor";
7844
+ return "custom";
7845
+ }
7846
+ async function createEmbeddedMetadataRecord(payload, options = {}) {
7847
+ const inferredType = inferTypeFromPayload(payload);
7848
+ const type = options.header?.type || inferredType;
7849
+ const payloadChecksum = calculatePayloadChecksum(payload);
7850
+ let signatures;
7851
+ if (options.signatures) {
7852
+ signatures = options.signatures;
7853
+ } else if (options.signature?.privateKey) {
7854
+ const keyId = options.signature.keyId || "human:test-user";
7855
+ const role = options.signature.role || "author";
7856
+ const notes = options.signature.notes || "Created via factory";
7857
+ signatures = [signPayload(payload, options.signature.privateKey, keyId, role, notes)];
7858
+ } else {
7859
+ const keyId = options.signature?.keyId || "human:test-user";
7860
+ const role = options.signature?.role || "author";
7861
+ const notes = options.signature?.notes || "Test signature - unsigned";
7862
+ signatures = [createTestSignature(keyId, role, notes)];
7863
+ }
7864
+ const header = {
7865
+ version: "1.0",
7866
+ // Always 1.0 (schema enforces this)
7867
+ type,
7868
+ payloadChecksum,
7869
+ signatures,
7870
+ ...type === "custom" && {
7871
+ schemaUrl: options.header?.schemaUrl,
7872
+ schemaChecksum: options.header?.schemaChecksum
7873
+ }
7874
+ };
7875
+ const embeddedRecord = {
7876
+ header,
7877
+ payload
7878
+ };
7879
+ const validation = validateEmbeddedMetadataDetailed(embeddedRecord);
7880
+ if (!validation.isValid) {
7881
+ throw new DetailedValidationError("EmbeddedMetadataRecord", validation.errors);
7882
+ }
7883
+ return embeddedRecord;
7884
+ }
7885
+
6965
7886
  // src/validation/index.ts
6966
7887
  var validation_exports = {};
6967
7888
  __export(validation_exports, {
@@ -7025,9 +7946,11 @@ function generateSubscriptionId() {
7025
7946
  var EventBus = class {
7026
7947
  emitter;
7027
7948
  subscriptions;
7949
+ pendingHandlers;
7028
7950
  constructor() {
7029
7951
  this.emitter = new EventEmitter();
7030
7952
  this.subscriptions = /* @__PURE__ */ new Map();
7953
+ this.pendingHandlers = /* @__PURE__ */ new Set();
7031
7954
  this.emitter.setMaxListeners(100);
7032
7955
  }
7033
7956
  /**
@@ -7058,11 +7981,17 @@ var EventBus = class {
7058
7981
  subscribe(eventType, handler) {
7059
7982
  const subscriptionId = generateSubscriptionId();
7060
7983
  const wrappedHandler = async (event) => {
7061
- try {
7062
- await handler(event);
7063
- } catch (error) {
7064
- console.error(`Error in event handler for ${eventType}:`, error);
7065
- }
7984
+ const handlerPromise = (async () => {
7985
+ try {
7986
+ await handler(event);
7987
+ } catch (error) {
7988
+ console.error(`Error in event handler for ${eventType}:`, error);
7989
+ }
7990
+ })();
7991
+ this.pendingHandlers.add(handlerPromise);
7992
+ handlerPromise.finally(() => {
7993
+ this.pendingHandlers.delete(handlerPromise);
7994
+ });
7066
7995
  };
7067
7996
  const subscription = {
7068
7997
  id: subscriptionId,
@@ -7134,6 +8063,43 @@ var EventBus = class {
7134
8063
  subscribeToAll(handler) {
7135
8064
  return this.subscribe("*", handler);
7136
8065
  }
8066
+ /**
8067
+ * Wait for all pending event handlers to complete.
8068
+ * This is primarily useful for testing to ensure event handlers finish before assertions.
8069
+ *
8070
+ * In production, events are fire-and-forget for performance.
8071
+ * In tests, use this to synchronize and avoid race conditions.
8072
+ *
8073
+ * @param options - Optional configuration
8074
+ * @param options.timeout - Maximum time to wait in ms (default: 5000)
8075
+ * @returns Promise that resolves when all handlers complete or timeout occurs
8076
+ *
8077
+ * @example
8078
+ * ```typescript
8079
+ * await feedbackAdapter.create(...); // publishes event
8080
+ * await eventBus.waitForIdle(); // wait for BacklogAdapter.handleFeedbackCreated()
8081
+ * const task = await backlogAdapter.getTask(taskId);
8082
+ * expect(task.status).toBe('paused'); // now safe to assert
8083
+ * ```
8084
+ */
8085
+ async waitForIdle(options = {}) {
8086
+ const timeout = options.timeout ?? 5e3;
8087
+ const startTime = Date.now();
8088
+ while (this.pendingHandlers.size > 0) {
8089
+ if (Date.now() - startTime > timeout) {
8090
+ const pendingCount = this.pendingHandlers.size;
8091
+ console.warn(`EventBus.waitForIdle() timeout after ${timeout}ms with ${pendingCount} handlers still pending`);
8092
+ break;
8093
+ }
8094
+ if (this.pendingHandlers.size > 0) {
8095
+ await Promise.race([
8096
+ Promise.all(Array.from(this.pendingHandlers)),
8097
+ new Promise((resolve) => setTimeout(resolve, 10))
8098
+ // Re-check every 10ms
8099
+ ]);
8100
+ }
8101
+ }
8102
+ }
7137
8103
  };
7138
8104
  var eventBus = new EventBus();
7139
8105
  function publishEvent(event) {
@@ -7327,14 +8293,17 @@ var RelationshipAnalyzer = class {
7327
8293
  for (const task of tasks) {
7328
8294
  const isEpic = this.isEpicTask(task);
7329
8295
  const title = task.title || "Untitled Task";
7330
- nodes.push({
8296
+ const node = {
7331
8297
  id: this.generateNodeId(task),
7332
8298
  type: isEpic ? "epic-task" : "task",
7333
8299
  title,
7334
8300
  status: task.status,
7335
- tags: task.tags,
7336
8301
  originalId: task.id
7337
- });
8302
+ };
8303
+ if (task.tags) {
8304
+ node.tags = task.tags;
8305
+ }
8306
+ nodes.push(node);
7338
8307
  }
7339
8308
  return nodes;
7340
8309
  }