@gitgov/core 1.3.0 → 1.5.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
@@ -5,7 +5,7 @@ import { promises, existsSync, constants } from 'fs';
5
5
  import * as yaml from 'js-yaml';
6
6
  import { generateKeyPair, createHash, sign, verify } from 'crypto';
7
7
  import { promisify } from 'util';
8
- import * as pathUtils from 'path';
8
+ import * as path from 'path';
9
9
  import { EventEmitter } from 'events';
10
10
 
11
11
  var __defProp = Object.defineProperty;
@@ -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"
619
+ },
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)"
338
628
  },
339
- timestamp: {
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"
877
+ ],
878
+ tags: [
879
+ "sprint:24",
880
+ "team:backend",
881
+ "focus:performance"
882
+ ],
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"
576
910
  ],
577
911
  tags: [
578
- "roadmap:q4"
912
+ "roadmap:q4",
913
+ "strategy:growth",
914
+ "okr:scale-to-1m-users"
579
915
  ],
580
- notes: "Objetivo: Reducir la latencia p95 de la API por debajo de 200ms."
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: [
@@ -882,9 +1220,9 @@ var embedded_metadata_schema_default = {
882
1220
  {
883
1221
  keyId: "human:lead-dev",
884
1222
  role: "author",
1223
+ notes: "Initial task creation for OAuth 2.0 implementation",
885
1224
  signature: "...",
886
- timestamp: 1752274500,
887
- timestamp_iso: "2025-07-25T14:30:00Z"
1225
+ timestamp: 1752274500
888
1226
  }
889
1227
  ]
890
1228
  },
@@ -898,6 +1236,68 @@ var embedded_metadata_schema_default = {
898
1236
  "area:backend"
899
1237
  ]
900
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
+ },
1270
+ {
1271
+ header: {
1272
+ version: "1.0",
1273
+ type: "actor",
1274
+ payloadChecksum: "c3d4e5f6a1b2...",
1275
+ signatures: [
1276
+ {
1277
+ keyId: "human:admin",
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.",
1287
+ signature: "...",
1288
+ timestamp: 1752274705
1289
+ }
1290
+ ]
1291
+ },
1292
+ payload: {
1293
+ id: "human:new-developer",
1294
+ type: "human",
1295
+ displayName: "New Developer",
1296
+ publicKey: "...",
1297
+ roles: [
1298
+ "developer"
1299
+ ]
1300
+ }
901
1301
  }
902
1302
  ]
903
1303
  };
@@ -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.",
989
1418
  references: [
990
- "commit:a1b2c3d4e5"
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.",
1454
+ references: [
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
 
@@ -1819,18 +2672,20 @@ var logger2 = createLogger("[CryptoModule] ");
1819
2672
  var generateKeyPairAsync = promisify(generateKeyPair);
1820
2673
  async function generateKeys() {
1821
2674
  const { publicKey, privateKey } = await generateKeyPairAsync("ed25519", {
1822
- publicKeyEncoding: { type: "spki", format: "pem" },
2675
+ publicKeyEncoding: { type: "spki", format: "der" },
1823
2676
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
1824
2677
  });
2678
+ const rawPublicKey = publicKey.subarray(-32);
1825
2679
  return {
1826
- publicKey: Buffer.from(publicKey).toString("base64"),
2680
+ publicKey: rawPublicKey.toString("base64"),
2681
+ // 32 bytes -> 44 chars
1827
2682
  privateKey: Buffer.from(privateKey).toString("base64")
1828
2683
  };
1829
2684
  }
1830
- function signPayload(payload, privateKey, keyId, role) {
2685
+ function signPayload(payload, privateKey, keyId, role, notes) {
1831
2686
  const payloadChecksum = calculatePayloadChecksum(payload);
1832
2687
  const timestamp = Math.floor(Date.now() / 1e3);
1833
- const digest = `${payloadChecksum}:${keyId}:${role}:${timestamp}`;
2688
+ const digest = `${payloadChecksum}:${keyId}:${role}:${notes}:${timestamp}`;
1834
2689
  const digestHash = createHash("sha256").update(digest).digest();
1835
2690
  const signature = sign(null, digestHash, {
1836
2691
  key: Buffer.from(privateKey, "base64"),
@@ -1840,27 +2695,43 @@ function signPayload(payload, privateKey, keyId, role) {
1840
2695
  return {
1841
2696
  keyId,
1842
2697
  role,
2698
+ notes,
1843
2699
  signature: signature.toString("base64"),
1844
- timestamp,
1845
- timestamp_iso: new Date(timestamp * 1e3).toISOString()
2700
+ timestamp
1846
2701
  };
1847
2702
  }
1848
2703
  async function verifySignatures(record, getActorPublicKey) {
1849
2704
  for (const signature of record.header.signatures) {
1850
- const publicKey = await getActorPublicKey(signature.keyId);
1851
- if (!publicKey) {
2705
+ const publicKeyBase64 = await getActorPublicKey(signature.keyId);
2706
+ if (!publicKeyBase64) {
1852
2707
  logger2.warn(`Public key not found for actor: ${signature.keyId}`);
1853
2708
  return false;
1854
2709
  }
1855
- const digest = `${record.header.payloadChecksum}:${signature.keyId}:${signature.role}:${signature.timestamp}`;
2710
+ const digest = `${record.header.payloadChecksum}:${signature.keyId}:${signature.role}:${signature.notes}:${signature.timestamp}`;
1856
2711
  const digestHash = createHash("sha256").update(digest).digest();
2712
+ const algorithmIdentifier = Buffer.from([
2713
+ 48,
2714
+ 42,
2715
+ 48,
2716
+ 5,
2717
+ 6,
2718
+ 3,
2719
+ 43,
2720
+ 101,
2721
+ 112,
2722
+ 3,
2723
+ 33,
2724
+ 0
2725
+ ]);
2726
+ const rawPublicKey = Buffer.from(publicKeyBase64, "base64");
2727
+ const spkiPublicKey = Buffer.concat([algorithmIdentifier, rawPublicKey]);
1857
2728
  const isValid = verify(
1858
2729
  null,
1859
2730
  digestHash,
1860
2731
  {
1861
- key: Buffer.from(publicKey, "base64"),
2732
+ key: spkiPublicKey,
1862
2733
  type: "spki",
1863
- format: "pem"
2734
+ format: "der"
1864
2735
  },
1865
2736
  Buffer.from(signature.signature, "base64")
1866
2737
  );
@@ -1955,13 +2826,6 @@ function validateEmbeddedMetadataBusinessRules(data) {
1955
2826
  value: data.header.signatures
1956
2827
  });
1957
2828
  }
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
2829
  return {
1966
2830
  isValid: errors.length === 0,
1967
2831
  errors
@@ -1981,8 +2845,8 @@ function isTaskRecord(data) {
1981
2845
  function validateTaskRecordDetailed(data) {
1982
2846
  const [isValid, ajvErrors] = validateTaskRecordSchema(data);
1983
2847
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
1984
- field: error.instancePath || error.schemaPath || "root",
1985
- message: error.message || "Validation failed",
2848
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2849
+ message: error.message || "Unknown validation error",
1986
2850
  value: error.data
1987
2851
  })) : [];
1988
2852
  return {
@@ -1993,9 +2857,12 @@ function validateTaskRecordDetailed(data) {
1993
2857
  async function validateFullTaskRecord(record, getActorPublicKey) {
1994
2858
  const [isValidSchema, errors] = validateTaskRecordSchema(record.payload);
1995
2859
  if (!isValidSchema) {
1996
- throw new SchemaValidationError(
1997
- `TaskRecord payload failed schema validation: ${JSON.stringify(errors)}`
1998
- );
2860
+ const formattedErrors = (errors || []).map((error) => ({
2861
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2862
+ message: error.message || "Unknown validation error",
2863
+ value: error.data
2864
+ }));
2865
+ throw new DetailedValidationError("TaskRecord", formattedErrors);
1999
2866
  }
2000
2867
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2001
2868
  }
@@ -2020,32 +2887,14 @@ function generateExecutionId(title, timestamp) {
2020
2887
  const slug = sanitizeForId(title);
2021
2888
  return `${timestamp}-exec-${slug}`;
2022
2889
  }
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}`;
2890
+ function generateChangelogId(title, timestamp) {
2891
+ const slug = sanitizeForId(title);
2892
+ return `${timestamp}-changelog-${slug}`;
2032
2893
  }
2033
2894
  function generateFeedbackId(title, timestamp) {
2034
2895
  const slug = sanitizeForId(title);
2035
2896
  return `${timestamp}-feedback-${slug}`;
2036
2897
  }
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
2898
 
2050
2899
  // src/factories/task_factory.ts
2051
2900
  async function createTaskRecord(payload) {
@@ -2083,8 +2932,8 @@ function isCycleRecord(data) {
2083
2932
  function validateCycleRecordDetailed(data) {
2084
2933
  const [isValid, ajvErrors] = validateCycleRecordSchema(data);
2085
2934
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
2086
- field: error.instancePath || error.schemaPath || "root",
2087
- message: error.message || "Validation failed",
2935
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2936
+ message: error.message || "Unknown validation error",
2088
2937
  value: error.data
2089
2938
  })) : [];
2090
2939
  return {
@@ -2095,9 +2944,12 @@ function validateCycleRecordDetailed(data) {
2095
2944
  async function validateFullCycleRecord(record, getActorPublicKey) {
2096
2945
  const [isValidSchema, errors] = validateCycleRecordSchema(record.payload);
2097
2946
  if (!isValidSchema) {
2098
- throw new SchemaValidationError(
2099
- `CycleRecord payload failed schema validation: ${JSON.stringify(errors)}`
2100
- );
2947
+ const formattedErrors = (errors || []).map((error) => ({
2948
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
2949
+ message: error.message || "Unknown validation error",
2950
+ value: error.data
2951
+ }));
2952
+ throw new DetailedValidationError("CycleRecord", formattedErrors);
2101
2953
  }
2102
2954
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2103
2955
  }
@@ -2141,8 +2993,8 @@ var ConfigManager = class _ConfigManager {
2141
2993
  configPath;
2142
2994
  sessionPath;
2143
2995
  constructor(projectRootPath = _ConfigManager.findProjectRoot() || process.cwd()) {
2144
- this.configPath = pathUtils.join(projectRootPath, ".gitgov", "config.json");
2145
- this.sessionPath = pathUtils.join(projectRootPath, ".gitgov", ".session.json");
2996
+ this.configPath = path.join(projectRootPath, ".gitgov", "config.json");
2997
+ this.sessionPath = path.join(projectRootPath, ".gitgov", ".session.json");
2146
2998
  }
2147
2999
  /**
2148
3000
  * Load GitGovernance configuration
@@ -2232,14 +3084,14 @@ var ConfigManager = class _ConfigManager {
2232
3084
  }
2233
3085
  lastSearchPath = startPath;
2234
3086
  let currentPath = startPath;
2235
- while (currentPath !== pathUtils.parse(currentPath).root) {
2236
- if (existsSync(pathUtils.join(currentPath, ".git"))) {
3087
+ while (currentPath !== path.parse(currentPath).root) {
3088
+ if (existsSync(path.join(currentPath, ".git"))) {
2237
3089
  projectRoot = currentPath;
2238
3090
  return projectRoot;
2239
3091
  }
2240
- currentPath = pathUtils.dirname(currentPath);
3092
+ currentPath = path.dirname(currentPath);
2241
3093
  }
2242
- if (existsSync(pathUtils.join(currentPath, ".git"))) {
3094
+ if (existsSync(path.join(currentPath, ".git"))) {
2243
3095
  projectRoot = currentPath;
2244
3096
  return projectRoot;
2245
3097
  }
@@ -2253,23 +3105,23 @@ var ConfigManager = class _ConfigManager {
2253
3105
  */
2254
3106
  static findGitgovRoot(startPath = process.cwd()) {
2255
3107
  let currentPath = startPath;
2256
- while (currentPath !== pathUtils.parse(currentPath).root) {
2257
- if (existsSync(pathUtils.join(currentPath, ".gitgov"))) {
3108
+ while (currentPath !== path.parse(currentPath).root) {
3109
+ if (existsSync(path.join(currentPath, ".gitgov"))) {
2258
3110
  return currentPath;
2259
3111
  }
2260
- currentPath = pathUtils.dirname(currentPath);
3112
+ currentPath = path.dirname(currentPath);
2261
3113
  }
2262
- if (existsSync(pathUtils.join(currentPath, ".gitgov"))) {
3114
+ if (existsSync(path.join(currentPath, ".gitgov"))) {
2263
3115
  return currentPath;
2264
3116
  }
2265
3117
  currentPath = startPath;
2266
- while (currentPath !== pathUtils.parse(currentPath).root) {
2267
- if (existsSync(pathUtils.join(currentPath, ".git"))) {
3118
+ while (currentPath !== path.parse(currentPath).root) {
3119
+ if (existsSync(path.join(currentPath, ".git"))) {
2268
3120
  return currentPath;
2269
3121
  }
2270
- currentPath = pathUtils.dirname(currentPath);
3122
+ currentPath = path.dirname(currentPath);
2271
3123
  }
2272
- if (existsSync(pathUtils.join(currentPath, ".git"))) {
3124
+ if (existsSync(path.join(currentPath, ".git"))) {
2273
3125
  return currentPath;
2274
3126
  }
2275
3127
  return null;
@@ -2282,7 +3134,7 @@ var ConfigManager = class _ConfigManager {
2282
3134
  if (!root) {
2283
3135
  throw new Error("Could not find project root. Make sure you are inside a GitGovernance repository.");
2284
3136
  }
2285
- return pathUtils.join(root, ".gitgov");
3137
+ return path.join(root, ".gitgov");
2286
3138
  }
2287
3139
  /**
2288
3140
  * Checks if current directory is a GitGovernance project
@@ -2311,12 +3163,12 @@ var RecordStore = class {
2311
3163
  throw new Error("Could not find project root. RecordStore requires a valid project root.");
2312
3164
  }
2313
3165
  this.recordType = recordType;
2314
- this.recordsDir = pathUtils.join(foundRoot, ".gitgov", this.recordType);
3166
+ this.recordsDir = path.join(foundRoot, ".gitgov", this.recordType);
2315
3167
  this.fs = fsDeps;
2316
3168
  }
2317
3169
  getRecordPath(recordId) {
2318
3170
  const safeId = recordId.replace(/:/g, "_");
2319
- return pathUtils.join(this.recordsDir, `${safeId}.json`);
3171
+ return path.join(this.recordsDir, `${safeId}.json`);
2320
3172
  }
2321
3173
  async ensureDirExists() {
2322
3174
  await this.fs.mkdir(this.recordsDir, { recursive: true });
@@ -2395,8 +3247,8 @@ function isActorRecord(data) {
2395
3247
  function validateActorRecordDetailed(data) {
2396
3248
  const [isValid, ajvErrors] = validateActorRecordSchema(data);
2397
3249
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
2398
- field: error.instancePath || error.schemaPath || "root",
2399
- message: error.message || "Validation failed",
3250
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3251
+ message: error.message || "Unknown validation error",
2400
3252
  value: error.data
2401
3253
  })) : [];
2402
3254
  return {
@@ -2407,9 +3259,12 @@ function validateActorRecordDetailed(data) {
2407
3259
  async function validateFullActorRecord(record, getActorPublicKey) {
2408
3260
  const [isValidSchema, errors] = validateActorRecordSchema(record.payload);
2409
3261
  if (!isValidSchema) {
2410
- throw new SchemaValidationError(
2411
- `ActorRecord payload failed schema validation: ${JSON.stringify(errors)}`
2412
- );
3262
+ const formattedErrors = (errors || []).map((error) => ({
3263
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3264
+ message: error.message || "Unknown validation error",
3265
+ value: error.data
3266
+ }));
3267
+ throw new DetailedValidationError("ActorRecord", formattedErrors);
2413
3268
  }
2414
3269
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2415
3270
  }
@@ -2445,8 +3300,8 @@ function isAgentRecord(data) {
2445
3300
  function validateAgentRecordDetailed(data) {
2446
3301
  const [isValid, ajvErrors] = validateAgentRecordSchema(data);
2447
3302
  const formattedErrors = ajvErrors ? ajvErrors.map((error) => ({
2448
- field: error.instancePath || error.schemaPath || "root",
2449
- message: error.message || "Validation failed",
3303
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3304
+ message: error.message || "Unknown validation error",
2450
3305
  value: error.data
2451
3306
  })) : [];
2452
3307
  return {
@@ -2457,9 +3312,12 @@ function validateAgentRecordDetailed(data) {
2457
3312
  async function validateFullAgentRecord(record, getActorPublicKey) {
2458
3313
  const [isValidSchema, errors] = validateAgentRecordSchema(record.payload);
2459
3314
  if (!isValidSchema) {
2460
- throw new SchemaValidationError(
2461
- `AgentRecord payload failed schema validation: ${JSON.stringify(errors)}`
2462
- );
3315
+ const formattedErrors = (errors || []).map((error) => ({
3316
+ field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3317
+ message: error.message || "Unknown validation error",
3318
+ value: error.data
3319
+ }));
3320
+ throw new DetailedValidationError("AgentRecord", formattedErrors);
2463
3321
  }
2464
3322
  await validateFullEmbeddedMetadataRecord(record, getActorPublicKey);
2465
3323
  }
@@ -2486,7 +3344,6 @@ async function validateAgentActorRelationship(agentRecord, getEffectiveActor) {
2486
3344
  async function createAgentRecord(payload) {
2487
3345
  const agent = {
2488
3346
  id: payload.id || "",
2489
- guild: payload.guild || "design",
2490
3347
  engine: payload.engine || { type: "local" },
2491
3348
  status: payload.status || "active",
2492
3349
  triggers: payload.triggers || [],
@@ -2539,7 +3396,7 @@ var IdentityAdapter = class {
2539
3396
  };
2540
3397
  const validatedPayload = await createActorRecord(completePayload);
2541
3398
  const payloadChecksum = calculatePayloadChecksum(validatedPayload);
2542
- const signature = await signPayload(validatedPayload, privateKey, actorId, "author");
3399
+ const signature = await signPayload(validatedPayload, privateKey, actorId, "author", "Actor registration");
2543
3400
  const record = {
2544
3401
  header: {
2545
3402
  version: "1.0",
@@ -2601,9 +3458,9 @@ var IdentityAdapter = class {
2601
3458
  const mockSignature = {
2602
3459
  keyId: actorId,
2603
3460
  role,
3461
+ notes: "Record signed",
2604
3462
  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()
3463
+ timestamp: Math.floor(Date.now() / 1e3)
2607
3464
  };
2608
3465
  const signedRecord = {
2609
3466
  ...record,
@@ -2711,8 +3568,8 @@ var IdentityAdapter = class {
2711
3568
  console.warn("authenticate not fully implemented yet");
2712
3569
  }
2713
3570
  async createAgentRecord(payload) {
2714
- if (!payload.id || !payload.guild || !payload.engine) {
2715
- throw new Error("AgentRecord requires id, guild and engine");
3571
+ if (!payload.id || !payload.engine) {
3572
+ throw new Error("AgentRecord requires id and engine");
2716
3573
  }
2717
3574
  const correspondingActor = await this.getActor(payload.id);
2718
3575
  if (!correspondingActor) {
@@ -2723,7 +3580,6 @@ var IdentityAdapter = class {
2723
3580
  }
2724
3581
  const completePayload = {
2725
3582
  id: payload.id,
2726
- guild: payload.guild,
2727
3583
  engine: payload.engine,
2728
3584
  status: payload.status || "active",
2729
3585
  triggers: payload.triggers || [],
@@ -2733,7 +3589,7 @@ var IdentityAdapter = class {
2733
3589
  };
2734
3590
  const validatedPayload = await createAgentRecord(completePayload);
2735
3591
  const payloadChecksum = calculatePayloadChecksum(validatedPayload);
2736
- const signature = signPayload(validatedPayload, "placeholder-private-key", payload.id, "author");
3592
+ const signature = signPayload(validatedPayload, "placeholder-private-key", payload.id, "author", "Agent registration");
2737
3593
  const record = {
2738
3594
  header: {
2739
3595
  version: "1.0",
@@ -2758,7 +3614,6 @@ var IdentityAdapter = class {
2758
3614
  source: "identity_adapter",
2759
3615
  payload: {
2760
3616
  agentId: validatedPayload.id,
2761
- guild: validatedPayload.guild,
2762
3617
  engine: validatedPayload.engine,
2763
3618
  correspondingActorId: correspondingActor.id
2764
3619
  }
@@ -2863,11 +3718,11 @@ var FeedbackAdapter = class {
2863
3718
  this.eventBus = dependencies.eventBus;
2864
3719
  }
2865
3720
  /**
2866
- * [EARS-1] Creates a new FeedbackRecord for structured communication between actors.
3721
+ * [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
3722
  *
2868
3723
  * Description: Creates a new FeedbackRecord for structured communication between actors.
2869
3724
  * 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.
3725
+ * Usage: Invoked by `gitgov feedback create` to create feedback, assignments, blocks, responses.
2871
3726
  * Returns: Complete and signed FeedbackRecord.
2872
3727
  */
2873
3728
  async create(payload, actorId) {
@@ -2875,21 +3730,30 @@ var FeedbackAdapter = class {
2875
3730
  if (!payloadWithEntityId.entityId) {
2876
3731
  throw new Error("RecordNotFoundError: entityId is required");
2877
3732
  }
2878
- if (payloadWithEntityId.entityType && !["task", "execution", "changelog", "feedback"].includes(payloadWithEntityId.entityType)) {
2879
- throw new Error("InvalidEntityTypeError: entityType must be task, execution, changelog, or feedback");
3733
+ if (payloadWithEntityId.entityType && !["task", "execution", "changelog", "feedback", "cycle"].includes(payloadWithEntityId.entityType)) {
3734
+ throw new Error("InvalidEntityTypeError: entityType must be task, execution, changelog, feedback, or cycle");
2880
3735
  }
2881
3736
  if (payload.type === "assignment" && payload.assignee) {
2882
3737
  const existingFeedbacks = await this.getFeedbackByEntity(payloadWithEntityId.entityId);
2883
- const duplicateAssignment = existingFeedbacks.find(
3738
+ const openAssignments = existingFeedbacks.filter(
2884
3739
  (feedback) => feedback.type === "assignment" && feedback.assignee === payload.assignee && feedback.status === "open"
2885
3740
  );
2886
- if (duplicateAssignment) {
2887
- throw new Error(`DuplicateAssignmentError: Task ${payloadWithEntityId.entityId} is already assigned to ${payload.assignee} (feedback: ${duplicateAssignment.id})`);
3741
+ if (openAssignments.length > 0) {
3742
+ const allFeedbacks = await this.getAllFeedback();
3743
+ for (const assignment of openAssignments) {
3744
+ const hasResolution = allFeedbacks.some(
3745
+ (feedback) => feedback.entityType === "feedback" && feedback.resolvesFeedbackId === assignment.id && feedback.status === "resolved"
3746
+ );
3747
+ if (!hasResolution) {
3748
+ throw new Error(`DuplicateAssignmentError: Task ${payloadWithEntityId.entityId} is already assigned to ${payload.assignee} (feedback: ${assignment.id})`);
3749
+ }
3750
+ }
2888
3751
  }
2889
3752
  }
2890
3753
  const enrichedPayload = {
2891
- ...payload,
2892
- status: "open"
3754
+ status: "open",
3755
+ ...payload
3756
+ // Allows payload.status to override default
2893
3757
  };
2894
3758
  try {
2895
3759
  const validatedPayload = await createFeedbackRecord(enrichedPayload);
@@ -2901,9 +3765,9 @@ var FeedbackAdapter = class {
2901
3765
  signatures: [{
2902
3766
  keyId: actorId,
2903
3767
  role: "author",
3768
+ notes: "Feedback created",
2904
3769
  signature: "placeholder",
2905
- timestamp: Date.now(),
2906
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
3770
+ timestamp: Date.now()
2907
3771
  }]
2908
3772
  },
2909
3773
  payload: validatedPayload
@@ -2922,7 +3786,8 @@ var FeedbackAdapter = class {
2922
3786
  status: validatedPayload.status,
2923
3787
  content: validatedPayload.content,
2924
3788
  triggeredBy: actorId,
2925
- assignee: validatedPayload.assignee
3789
+ assignee: validatedPayload.assignee,
3790
+ resolvesFeedbackId: validatedPayload.resolvesFeedbackId
2926
3791
  }
2927
3792
  });
2928
3793
  return validatedPayload;
@@ -2934,51 +3799,30 @@ var FeedbackAdapter = class {
2934
3799
  }
2935
3800
  }
2936
3801
  /**
2937
- * [EARS-4] Resolves an existing feedback changing its status to resolved.
3802
+ * [EARS-9, EARS-10, EARS-11, EARS-12] Helper: Creates a new feedback that "resolves" another (immutable).
2938
3803
  *
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) {
3804
+ * Description: Helper method that creates a new feedback documenting resolution of another feedback.
3805
+ * Implementation: Verifies original exists, then delegates to create() with immutable pattern.
3806
+ * Usage: Ergonomic helper for common case. For advanced cases (wontfix, approval), use create() directly.
3807
+ * Returns: New FeedbackRecord that points to the original with resolvesFeedbackId.
3808
+ */
3809
+ async resolve(feedbackId, actorId, content) {
3810
+ const originalFeedback = await this.getFeedback(feedbackId);
3811
+ if (!originalFeedback) {
2947
3812
  throw new Error(`RecordNotFoundError: Feedback not found: ${feedbackId}`);
2948
3813
  }
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
- }
3814
+ const resolveContent = content || `Feedback resolved by ${actorId}`;
3815
+ return await this.create({
3816
+ entityType: "feedback",
3817
+ entityId: feedbackId,
3818
+ type: "clarification",
3819
+ status: "resolved",
3820
+ content: resolveContent,
3821
+ resolvesFeedbackId: feedbackId
3822
+ }, actorId);
2979
3823
  }
2980
3824
  /**
2981
- * [EARS-7] Gets a specific FeedbackRecord by its ID for query.
3825
+ * [EARS-13, EARS-14] Gets a specific FeedbackRecord by its ID for query.
2982
3826
  *
2983
3827
  * Description: Gets a specific FeedbackRecord by its ID for query.
2984
3828
  * Implementation: Direct read from record store without modifications.
@@ -2990,11 +3834,11 @@ var FeedbackAdapter = class {
2990
3834
  return record ? record.payload : null;
2991
3835
  }
2992
3836
  /**
2993
- * [EARS-9] Gets all FeedbackRecords associated with a specific entity.
3837
+ * [EARS-15] Gets all FeedbackRecords associated with a specific entity.
2994
3838
  *
2995
3839
  * Description: Gets all FeedbackRecords associated with a specific entity.
2996
3840
  * Implementation: Reads all records and filters by matching entityId.
2997
- * Usage: Invoked by `gitgov feedback list` to display feedback for a task/cycle.
3841
+ * Usage: Invoked by `gitgov feedback list` to display feedback for a task/cycle/execution.
2998
3842
  * Returns: Array of FeedbackRecords filtered for the entity.
2999
3843
  */
3000
3844
  async getFeedbackByEntity(entityId) {
@@ -3009,11 +3853,11 @@ var FeedbackAdapter = class {
3009
3853
  return feedbacks;
3010
3854
  }
3011
3855
  /**
3012
- * [EARS-10] Gets all FeedbackRecords in the system for indexation.
3856
+ * [EARS-16] Gets all FeedbackRecords in the system for indexation.
3013
3857
  *
3014
3858
  * Description: Gets all FeedbackRecords in the system for complete indexation.
3015
3859
  * Implementation: Complete read from record store without filters.
3016
- * Usage: Invoked by `gitgov feedback list --all` and by MetricsAdapter for calculations.
3860
+ * Usage: Invoked by `gitgov feedback list` and by MetricsAdapter for calculations.
3017
3861
  * Returns: Complete array of all FeedbackRecords.
3018
3862
  */
3019
3863
  async getAllFeedback() {
@@ -3027,6 +3871,49 @@ var FeedbackAdapter = class {
3027
3871
  }
3028
3872
  return feedbacks;
3029
3873
  }
3874
+ /**
3875
+ * [EARS-17, EARS-18, EARS-19, EARS-20] Builds the complete conversation tree for a feedback.
3876
+ *
3877
+ * Description: Recursively constructs the conversation tree for a feedback.
3878
+ * Implementation: Reads root feedback, finds all responses, builds tree recursively until maxDepth.
3879
+ * Usage: Invoked by `gitgov feedback thread` and `gitgov feedback show --thread`.
3880
+ * Returns: FeedbackThread object with tree structure.
3881
+ */
3882
+ async getFeedbackThread(feedbackId, maxDepth = Infinity) {
3883
+ return await this.buildThread(feedbackId, maxDepth, 0);
3884
+ }
3885
+ /**
3886
+ * Private helper: Recursively builds conversation thread.
3887
+ */
3888
+ async buildThread(feedbackId, maxDepth, currentDepth) {
3889
+ if (currentDepth >= maxDepth) {
3890
+ throw new Error(`Max depth ${maxDepth} reached for feedback thread`);
3891
+ }
3892
+ const feedback = await this.getFeedback(feedbackId);
3893
+ if (!feedback) {
3894
+ throw new Error(`RecordNotFoundError: Feedback not found: ${feedbackId}`);
3895
+ }
3896
+ const allFeedbacks = await this.getAllFeedback();
3897
+ const responses = allFeedbacks.filter(
3898
+ (f) => f.entityType === "feedback" && f.entityId === feedbackId
3899
+ );
3900
+ const responseThreads = [];
3901
+ for (const response of responses) {
3902
+ try {
3903
+ const thread = await this.buildThread(response.id, maxDepth, currentDepth + 1);
3904
+ responseThreads.push(thread);
3905
+ } catch (error) {
3906
+ if (error instanceof Error && error.message.includes("Max depth")) {
3907
+ continue;
3908
+ }
3909
+ throw error;
3910
+ }
3911
+ }
3912
+ return {
3913
+ feedback,
3914
+ responses: responseThreads
3915
+ };
3916
+ }
3030
3917
  };
3031
3918
 
3032
3919
  // src/adapters/execution_adapter/index.ts
@@ -3117,16 +4004,7 @@ var ExecutionAdapter = class {
3117
4004
  * Returns: Complete and signed ExecutionRecord.
3118
4005
  */
3119
4006
  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) {
4007
+ if (this.taskStore && payload.taskId) {
3130
4008
  const taskExists = await this.taskStore.read(payload.taskId);
3131
4009
  if (!taskExists) {
3132
4010
  throw new Error(`RecordNotFoundError: Task not found: ${payload.taskId}`);
@@ -3142,9 +4020,9 @@ var ExecutionAdapter = class {
3142
4020
  signatures: [{
3143
4021
  keyId: actorId,
3144
4022
  role: "author",
4023
+ notes: "Execution recorded",
3145
4024
  signature: "placeholder",
3146
- timestamp: Date.now(),
3147
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
4025
+ timestamp: Date.now()
3148
4026
  }]
3149
4027
  },
3150
4028
  payload: validatedPayload
@@ -3164,9 +4042,6 @@ var ExecutionAdapter = class {
3164
4042
  });
3165
4043
  return validatedPayload;
3166
4044
  } catch (error) {
3167
- if (error instanceof Error && error.message.includes("DetailedValidationError")) {
3168
- throw error;
3169
- }
3170
4045
  throw error;
3171
4046
  }
3172
4047
  }
@@ -3280,31 +4155,21 @@ async function validateFullChangelogRecord(record, getPublicKey) {
3280
4155
  // src/factories/changelog_factory.ts
3281
4156
  async function createChangelogRecord(payload) {
3282
4157
  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
4158
  const changelog = {
3288
4159
  // Required fields
3289
- id: id || "",
3290
- entityType: payload.entityType || "task",
3291
- entityId: payload.entityId || "",
3292
- changeType: payload.changeType || "completion",
4160
+ id: payload.id || "",
3293
4161
  title: payload.title || "",
3294
4162
  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 },
4163
+ relatedTasks: payload.relatedTasks || [],
4164
+ completedAt: payload.completedAt || timestamp,
4165
+ // Optional fields (only include if provided)
4166
+ ...payload.relatedCycles && { relatedCycles: payload.relatedCycles },
4167
+ ...payload.relatedExecutions && { relatedExecutions: payload.relatedExecutions },
4168
+ ...payload.version && { version: payload.version },
4169
+ ...payload.tags && { tags: payload.tags },
3305
4170
  ...payload.commits && { commits: payload.commits },
3306
- ...payload.rollbackInstructions && { rollbackInstructions: payload.rollbackInstructions },
3307
- ...payload.references && { references: payload.references }
4171
+ ...payload.files && { files: payload.files },
4172
+ ...payload.notes && { notes: payload.notes }
3308
4173
  };
3309
4174
  const validation = validateChangelogRecordDetailed(changelog);
3310
4175
  if (!validation.isValid) {
@@ -3328,54 +4193,44 @@ var ChangelogAdapter = class {
3328
4193
  this.cycleStore = dependencies.cycleStore;
3329
4194
  }
3330
4195
  /**
3331
- * [EARS-1] Records a significant change in any entity with complete context and conditional validation.
4196
+ * [EARS-1] Records a deliverable/release note.
3332
4197
  *
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.
4198
+ * Description: Aggregates multiple tasks into a single deliverable/release note.
4199
+ * Implementation: Validates required fields, builds record with factory, signs, persists and emits event.
4200
+ * Usage: Invoked by `gitgov changelog add` to document deliverables.
4201
+ * Returns: Complete and signed ChangelogRecord.
3337
4202
  */
3338
4203
  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");
4204
+ if (!payload.title || payload.title.length < 10) {
4205
+ throw new Error("DetailedValidationError: title is required and must be at least 10 characters");
3350
4206
  }
3351
- if (payload.title && payload.title.length < 10) {
3352
- throw new Error("DetailedValidationError: title must be at least 10 characters");
4207
+ if (!payload.description || payload.description.length < 20) {
4208
+ throw new Error("DetailedValidationError: description is required and must be at least 20 characters");
3353
4209
  }
3354
- if (payload.description && payload.description.length < 20) {
3355
- throw new Error("DetailedValidationError: description must be at least 20 characters");
4210
+ if (!payload.relatedTasks || payload.relatedTasks.length === 0) {
4211
+ throw new Error("DetailedValidationError: relatedTasks is required and must contain at least one task ID");
3356
4212
  }
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}`);
4213
+ if (this.taskStore && payload.relatedTasks) {
4214
+ for (const taskId of payload.relatedTasks) {
4215
+ const taskExists = await this.taskStore.read(taskId);
4216
+ if (!taskExists) {
4217
+ throw new Error(`RecordNotFoundError: Task not found: ${taskId}`);
4218
+ }
3370
4219
  }
3371
4220
  }
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}`);
4221
+ if (this.cycleStore && payload.relatedCycles) {
4222
+ for (const cycleId of payload.relatedCycles) {
4223
+ const cycleExists = await this.cycleStore.read(cycleId);
4224
+ if (!cycleExists) {
4225
+ throw new Error(`RecordNotFoundError: Cycle not found: ${cycleId}`);
4226
+ }
3376
4227
  }
3377
4228
  }
3378
4229
  try {
4230
+ const timestamp = payload.completedAt || Math.floor(Date.now() / 1e3);
4231
+ if (!payload.id) {
4232
+ payload.id = generateChangelogId(payload.title, timestamp);
4233
+ }
3379
4234
  const validatedPayload = await createChangelogRecord(payload);
3380
4235
  const unsignedRecord = {
3381
4236
  header: {
@@ -3385,9 +4240,9 @@ var ChangelogAdapter = class {
3385
4240
  signatures: [{
3386
4241
  keyId: actorId,
3387
4242
  role: "author",
4243
+ notes: "Changelog entry created",
3388
4244
  signature: "placeholder",
3389
- timestamp: Date.now(),
3390
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
4245
+ timestamp: Date.now()
3391
4246
  }]
3392
4247
  },
3393
4248
  payload: validatedPayload
@@ -3400,13 +4255,9 @@ var ChangelogAdapter = class {
3400
4255
  source: "changelog_adapter",
3401
4256
  payload: {
3402
4257
  changelogId: validatedPayload.id,
3403
- entityId: validatedPayload.entityId,
3404
- entityType: validatedPayload.entityType,
3405
- changeType: validatedPayload.changeType,
3406
- triggeredBy: actorId,
3407
- riskLevel: validatedPayload.riskLevel,
4258
+ relatedTasks: validatedPayload.relatedTasks,
3408
4259
  title: validatedPayload.title,
3409
- trigger: validatedPayload.trigger
4260
+ version: validatedPayload.version
3410
4261
  }
3411
4262
  });
3412
4263
  return validatedPayload;
@@ -3418,70 +4269,78 @@ var ChangelogAdapter = class {
3418
4269
  }
3419
4270
  }
3420
4271
  /**
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.
4272
+ * [EARS-9] Gets a specific ChangelogRecord by its ID.
3427
4273
  */
3428
4274
  async getChangelog(changelogId) {
3429
4275
  const record = await this.changelogStore.read(changelogId);
3430
4276
  return record ? record.payload : null;
3431
4277
  }
3432
4278
  /**
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.
4279
+ * [EARS-11] Gets all ChangelogRecords that include a specific task.
3439
4280
  */
3440
- async getChangelogsByEntity(entityId, entityType) {
4281
+ async getChangelogsByTask(taskId) {
3441
4282
  const ids = await this.changelogStore.list();
3442
4283
  const changelogs = [];
3443
4284
  for (const id of ids) {
3444
4285
  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
- }
4286
+ if (record && record.payload.relatedTasks.includes(taskId)) {
4287
+ changelogs.push(record.payload);
3449
4288
  }
3450
4289
  }
3451
4290
  return changelogs;
3452
4291
  }
3453
4292
  /**
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.
4293
+ * [EARS-11, EARS-12, EARS-13] Gets all ChangelogRecords with optional filtering and sorting.
3460
4294
  */
3461
- async getAllChangelogs() {
4295
+ async getAllChangelogs(options) {
3462
4296
  const ids = await this.changelogStore.list();
3463
- const changelogs = [];
4297
+ let changelogs = [];
3464
4298
  for (const id of ids) {
3465
4299
  const record = await this.changelogStore.read(id);
3466
4300
  if (record) {
3467
4301
  changelogs.push(record.payload);
3468
4302
  }
3469
4303
  }
4304
+ if (options?.tags && options.tags.length > 0) {
4305
+ changelogs = changelogs.filter((changelog) => {
4306
+ if (!changelog.tags) return false;
4307
+ return options.tags.some((tag) => changelog.tags.includes(tag));
4308
+ });
4309
+ }
4310
+ if (options?.version) {
4311
+ changelogs = changelogs.filter((changelog) => changelog.version === options.version);
4312
+ }
4313
+ const sortBy = options?.sortBy || "completedAt";
4314
+ const sortOrder = options?.sortOrder || "desc";
4315
+ changelogs.sort((a, b) => {
4316
+ let compareValue = 0;
4317
+ if (sortBy === "completedAt") {
4318
+ compareValue = a.completedAt - b.completedAt;
4319
+ } else if (sortBy === "title") {
4320
+ compareValue = a.title.localeCompare(b.title);
4321
+ }
4322
+ return sortOrder === "asc" ? compareValue : -compareValue;
4323
+ });
4324
+ if (options?.limit && options.limit > 0) {
4325
+ changelogs = changelogs.slice(0, options.limit);
4326
+ }
3470
4327
  return changelogs;
3471
4328
  }
3472
4329
  /**
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.
4330
+ * [EARS-13] Gets recent ChangelogRecords ordered by completedAt.
3479
4331
  */
3480
4332
  async getRecentChangelogs(limit) {
3481
4333
  const allChangelogs = await this.getAllChangelogs();
3482
- const sortedChangelogs = allChangelogs.sort((a, b) => b.timestamp - a.timestamp);
4334
+ const sortedChangelogs = allChangelogs.sort((a, b) => b.completedAt - a.completedAt);
3483
4335
  return sortedChangelogs.slice(0, limit);
3484
4336
  }
4337
+ /**
4338
+ * Legacy method for backwards compatibility - maps to getChangelogsByTask
4339
+ * @deprecated Use getChangelogsByTask instead
4340
+ */
4341
+ async getChangelogsByEntity(entityId, _entityType) {
4342
+ return this.getChangelogsByTask(entityId);
4343
+ }
3485
4344
  };
3486
4345
 
3487
4346
  // src/adapters/metrics_adapter/index.ts
@@ -3555,13 +4414,17 @@ var MetricsAdapter = class {
3555
4414
  }
3556
4415
  const task = taskRecord.payload;
3557
4416
  let feedbacks = [];
4417
+ let allFeedbacks = [];
3558
4418
  let executions = [];
3559
4419
  if (this.feedbackStore) {
3560
4420
  const feedbackIds = await this.feedbackStore.list();
3561
4421
  for (const id of feedbackIds) {
3562
4422
  const record = await this.feedbackStore.read(id);
3563
- if (record && record.payload.entityId === taskId) {
3564
- feedbacks.push(record.payload);
4423
+ if (record) {
4424
+ allFeedbacks.push(record.payload);
4425
+ if (record.payload.entityId === taskId) {
4426
+ feedbacks.push(record.payload);
4427
+ }
3565
4428
  }
3566
4429
  }
3567
4430
  }
@@ -3576,7 +4439,13 @@ var MetricsAdapter = class {
3576
4439
  }
3577
4440
  const timeInCurrentStage = this.calculateTimeInCurrentStage(task);
3578
4441
  const stalenessIndex = this.calculateStalenessIndex([task]);
3579
- const blockingFeedbacks = feedbacks.filter((f) => f.type === "blocking" && f.status === "open").length;
4442
+ const blockingFeedbacks = feedbacks.filter((f) => {
4443
+ if (f.type !== "blocking" || f.status !== "open") return false;
4444
+ const hasResolution = allFeedbacks.some(
4445
+ (resolution) => resolution.entityType === "feedback" && resolution.resolvesFeedbackId === f.id && resolution.status === "resolved"
4446
+ );
4447
+ return !hasResolution;
4448
+ }).length;
3580
4449
  const lastActivity = executions.length > 0 ? Math.max(...executions.map((e) => this.getTimestampFromId(e.id))) : this.getTimestampFromId(task.id);
3581
4450
  const recommendations = [];
3582
4451
  if (timeInCurrentStage > 7) recommendations.push("Task has been stagnant for over 7 days");
@@ -4034,10 +4903,6 @@ var BacklogAdapter = class {
4034
4903
  "feedback.created",
4035
4904
  (event) => this.handleFeedbackCreated(event)
4036
4905
  );
4037
- this.eventBus.subscribe(
4038
- "feedback.status.changed",
4039
- (event) => this.handleFeedbackResolved(event)
4040
- );
4041
4906
  this.eventBus.subscribe(
4042
4907
  "execution.created",
4043
4908
  (event) => this.handleExecutionCreated(event)
@@ -4069,9 +4934,9 @@ var BacklogAdapter = class {
4069
4934
  signatures: [{
4070
4935
  keyId: actorId,
4071
4936
  role: "author",
4937
+ notes: "Task created",
4072
4938
  signature: "placeholder",
4073
- timestamp: Date.now(),
4074
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
4939
+ timestamp: Date.now()
4075
4940
  }]
4076
4941
  },
4077
4942
  payload: validatedPayload
@@ -4174,9 +5039,9 @@ var BacklogAdapter = class {
4174
5039
  const tempSignature = {
4175
5040
  keyId: actorId,
4176
5041
  role: "approver",
5042
+ notes: "Task approval",
4177
5043
  signature: "temp-signature",
4178
- timestamp: Date.now(),
4179
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
5044
+ timestamp: Date.now()
4180
5045
  };
4181
5046
  const context = {
4182
5047
  task,
@@ -4507,7 +5372,16 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4507
5372
  }
4508
5373
  // ===== PHASE 3: EVENT HANDLERS (NEW IMPLEMENTATION) =====
4509
5374
  /**
4510
- * [EARS-31] Handles feedback created events - pauses task if blocking
5375
+ * [EARS-31, EARS-33, EARS-34] Handles feedback created events (Immutable Pattern)
5376
+ *
5377
+ * This handler respects the immutable feedback pattern:
5378
+ * - Case 1: Blocking feedback created → pause task if active/ready
5379
+ * - Case 2: Feedback resolving another feedback → resume task if no more blocks
5380
+ *
5381
+ * The immutable pattern means:
5382
+ * - Original feedbacks NEVER change status
5383
+ * - Resolution is expressed by creating a NEW feedback pointing to the original
5384
+ * - We detect resolution via: entityType='feedback' + status='resolved' + resolvesFeedbackId
4511
5385
  */
4512
5386
  async handleFeedbackCreated(event) {
4513
5387
  try {
@@ -4517,85 +5391,72 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4517
5391
  processedAt: Date.now(),
4518
5392
  sourceAdapter: "backlog_adapter"
4519
5393
  };
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}`);
5394
+ if (event.payload.type === "blocking" && event.payload.entityType === "task") {
5395
+ const task = await this.getTask(event.payload.entityId);
5396
+ if (!task) {
5397
+ console.warn(`Task not found for feedback: ${event.payload.entityId}`);
5398
+ return;
5399
+ }
5400
+ if (!["active", "ready"].includes(task.status)) {
5401
+ return;
5402
+ }
5403
+ const updatedTask = { ...task, status: "paused" };
5404
+ const taskRecord = await this.taskStore.read(task.id);
5405
+ if (taskRecord) {
5406
+ const updatedRecord = { ...taskRecord, payload: updatedTask };
5407
+ await this.taskStore.write(updatedRecord);
5408
+ this.eventBus.publish({
5409
+ type: "task.status.changed",
5410
+ timestamp: Date.now(),
5411
+ source: "backlog_adapter",
5412
+ payload: {
5413
+ taskId: task.id,
5414
+ oldStatus: task.status,
5415
+ newStatus: "paused",
5416
+ actorId: "system"
5417
+ },
5418
+ metadata
5419
+ });
5420
+ }
4531
5421
  return;
4532
5422
  }
4533
- if (!["active", "ready"].includes(task.status)) {
5423
+ if (event.payload.entityType === "feedback" && event.payload.status === "resolved" && event.payload.resolvesFeedbackId) {
5424
+ const originalFeedback = await this.feedbackAdapter.getFeedback(event.payload.resolvesFeedbackId);
5425
+ if (!originalFeedback || originalFeedback.type !== "blocking") {
5426
+ return;
5427
+ }
5428
+ const task = await this.getTask(originalFeedback.entityId);
5429
+ if (!task || task.status !== "paused") {
5430
+ return;
5431
+ }
5432
+ const taskHealth = await this.metricsAdapter.getTaskHealth(task.id);
5433
+ if (taskHealth.blockingFeedbacks > 0) {
5434
+ return;
5435
+ }
5436
+ const updatedTask = { ...task, status: "active" };
5437
+ const taskRecord = await this.taskStore.read(task.id);
5438
+ if (taskRecord) {
5439
+ const updatedRecord = { ...taskRecord, payload: updatedTask };
5440
+ await this.taskStore.write(updatedRecord);
5441
+ this.eventBus.publish({
5442
+ type: "task.status.changed",
5443
+ timestamp: Date.now(),
5444
+ source: "backlog_adapter",
5445
+ payload: {
5446
+ taskId: task.id,
5447
+ oldStatus: "paused",
5448
+ newStatus: "active",
5449
+ actorId: "system"
5450
+ },
5451
+ metadata
5452
+ });
5453
+ }
4534
5454
  return;
4535
5455
  }
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
5456
  } catch (error) {
4555
5457
  console.error("Error in handleFeedbackCreated:", error);
4556
5458
  }
4557
5459
  }
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
5460
  /**
4600
5461
  * [EARS-35] Handles execution created events - transitions ready→active on first execution
4601
5462
  */
@@ -4649,29 +5510,31 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4649
5510
  console.warn(`Changelog not found: ${event.payload.changelogId}`);
4650
5511
  return;
4651
5512
  }
4652
- if (changelogRecord.payload.entityType !== "task") {
4653
- return;
4654
- }
4655
- const task = await this.getTask(changelogRecord.payload.entityId);
4656
- if (!task || task.status !== "done") {
5513
+ if (!changelogRecord.payload.relatedTasks || changelogRecord.payload.relatedTasks.length === 0) {
4657
5514
  return;
4658
5515
  }
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
- });
5516
+ for (const taskId of changelogRecord.payload.relatedTasks) {
5517
+ const task = await this.getTask(taskId);
5518
+ if (!task || task.status !== "done") {
5519
+ continue;
5520
+ }
5521
+ const updatedTask = { ...task, status: "archived" };
5522
+ const taskRecord = await this.taskStore.read(task.id);
5523
+ if (taskRecord) {
5524
+ const updatedRecord = { ...taskRecord, payload: updatedTask };
5525
+ await this.taskStore.write(updatedRecord);
5526
+ this.eventBus.publish({
5527
+ type: "task.status.changed",
5528
+ timestamp: Date.now(),
5529
+ source: "backlog_adapter",
5530
+ payload: {
5531
+ taskId: task.id,
5532
+ oldStatus: "done",
5533
+ newStatus: "archived",
5534
+ actorId: "system"
5535
+ }
5536
+ });
5537
+ }
4675
5538
  }
4676
5539
  } catch (error) {
4677
5540
  console.error("Error in handleChangelogCreated:", error);
@@ -4785,9 +5648,9 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
4785
5648
  signatures: [{
4786
5649
  keyId: actorId,
4787
5650
  role: "author",
5651
+ notes: "Cycle created",
4788
5652
  signature: "placeholder",
4789
- timestamp: Date.now(),
4790
- timestamp_iso: (/* @__PURE__ */ new Date()).toISOString()
5653
+ timestamp: Date.now()
4791
5654
  }]
4792
5655
  },
4793
5656
  payload: validatedPayload
@@ -5146,11 +6009,14 @@ var FileIndexerAdapter = class {
5146
6009
  metrics: { ...systemStatus, ...productivityMetrics, ...collaborationMetrics },
5147
6010
  activityHistory,
5148
6011
  // NUEVO
5149
- tasks,
6012
+ tasks: tasks.map((t) => t.payload),
6013
+ // Extract payloads for IndexData
5150
6014
  enrichedTasks,
5151
6015
  // NUEVO - Tasks with activity metadata
5152
- cycles,
5153
- actors
6016
+ cycles: cycles.map((c) => c.payload),
6017
+ // Extract payloads for IndexData
6018
+ actors: actors.map((a) => a.payload)
6019
+ // Extract payloads for IndexData
5154
6020
  };
5155
6021
  const writeStart = performance.now();
5156
6022
  await this.writeCacheFile(indexData);
@@ -5218,19 +6084,19 @@ var FileIndexerAdapter = class {
5218
6084
  ]);
5219
6085
  recordsScanned = tasks.length + cycles.length;
5220
6086
  for (const task of tasks) {
5221
- if (!task.id || !task.description) {
6087
+ if (!task.payload.id || !task.payload.description) {
5222
6088
  errors.push({
5223
6089
  type: "schema_violation",
5224
- recordId: task.id || "unknown",
6090
+ recordId: task.payload.id || "unknown",
5225
6091
  message: "Task missing required fields"
5226
6092
  });
5227
6093
  }
5228
6094
  }
5229
6095
  for (const cycle of cycles) {
5230
- if (!cycle.id || !cycle.title) {
6096
+ if (!cycle.payload.id || !cycle.payload.title) {
5231
6097
  errors.push({
5232
6098
  type: "schema_violation",
5233
- recordId: cycle.id || "unknown",
6099
+ recordId: cycle.payload.id || "unknown",
5234
6100
  message: "Cycle missing required fields"
5235
6101
  });
5236
6102
  }
@@ -5303,7 +6169,8 @@ var FileIndexerAdapter = class {
5303
6169
  }
5304
6170
  // ===== HELPER METHODS =====
5305
6171
  /**
5306
- * Reads all tasks from taskStore
6172
+ * Reads all tasks from taskStore with full metadata (headers + payloads).
6173
+ * Returns complete GitGovTaskRecord objects including signatures for author/lastModifier extraction.
5307
6174
  */
5308
6175
  async readAllTasks() {
5309
6176
  const taskIds = await this.taskStore.list();
@@ -5311,13 +6178,13 @@ var FileIndexerAdapter = class {
5311
6178
  for (const id of taskIds) {
5312
6179
  const record = await this.taskStore.read(id);
5313
6180
  if (record) {
5314
- tasks.push(record.payload);
6181
+ tasks.push(record);
5315
6182
  }
5316
6183
  }
5317
6184
  return tasks;
5318
6185
  }
5319
6186
  /**
5320
- * Reads all cycles from cycleStore
6187
+ * Reads all cycles from cycleStore with full metadata.
5321
6188
  */
5322
6189
  async readAllCycles() {
5323
6190
  const cycleIds = await this.cycleStore.list();
@@ -5325,13 +6192,13 @@ var FileIndexerAdapter = class {
5325
6192
  for (const id of cycleIds) {
5326
6193
  const record = await this.cycleStore.read(id);
5327
6194
  if (record) {
5328
- cycles.push(record.payload);
6195
+ cycles.push(record);
5329
6196
  }
5330
6197
  }
5331
6198
  return cycles;
5332
6199
  }
5333
6200
  /**
5334
- * Reads all actors from actorStore (graceful degradation)
6201
+ * Reads all actors from actorStore (graceful degradation) with full metadata.
5335
6202
  */
5336
6203
  async readAllActors() {
5337
6204
  if (!this.actorStore) {
@@ -5342,13 +6209,13 @@ var FileIndexerAdapter = class {
5342
6209
  for (const id of actorIds) {
5343
6210
  const record = await this.actorStore.read(id);
5344
6211
  if (record) {
5345
- actors.push(record.payload);
6212
+ actors.push(record);
5346
6213
  }
5347
6214
  }
5348
6215
  return actors;
5349
6216
  }
5350
6217
  /**
5351
- * Reads all feedback from feedbackStore (graceful degradation)
6218
+ * Reads all feedback from feedbackStore (graceful degradation) with full metadata.
5352
6219
  */
5353
6220
  async readAllFeedback() {
5354
6221
  if (!this.feedbackStore) {
@@ -5359,13 +6226,13 @@ var FileIndexerAdapter = class {
5359
6226
  for (const id of feedbackIds) {
5360
6227
  const record = await this.feedbackStore.read(id);
5361
6228
  if (record) {
5362
- feedback.push(record.payload);
6229
+ feedback.push(record);
5363
6230
  }
5364
6231
  }
5365
6232
  return feedback;
5366
6233
  }
5367
6234
  /**
5368
- * Reads all executions from executionStore (graceful degradation)
6235
+ * Reads all executions from executionStore (graceful degradation) with full metadata.
5369
6236
  */
5370
6237
  async readAllExecutions() {
5371
6238
  if (!this.executionStore) {
@@ -5376,13 +6243,13 @@ var FileIndexerAdapter = class {
5376
6243
  for (const id of executionIds) {
5377
6244
  const record = await this.executionStore.read(id);
5378
6245
  if (record) {
5379
- executions.push(record.payload);
6246
+ executions.push(record);
5380
6247
  }
5381
6248
  }
5382
6249
  return executions;
5383
6250
  }
5384
6251
  /**
5385
- * Reads all changelogs from changelogStore (graceful degradation)
6252
+ * Reads all changelogs from changelogStore (graceful degradation) with full metadata.
5386
6253
  */
5387
6254
  async readAllChangelogs() {
5388
6255
  if (!this.changelogStore) {
@@ -5393,7 +6260,7 @@ var FileIndexerAdapter = class {
5393
6260
  for (const id of changelogIds) {
5394
6261
  const record = await this.changelogStore.read(id);
5395
6262
  if (record) {
5396
- changelogs.push(record.payload);
6263
+ changelogs.push(record);
5397
6264
  }
5398
6265
  }
5399
6266
  return changelogs;
@@ -5402,7 +6269,7 @@ var FileIndexerAdapter = class {
5402
6269
  * Writes cache data to file (Phase 1: JSON)
5403
6270
  */
5404
6271
  async writeCacheFile(indexData) {
5405
- const cacheDir = pathUtils.dirname(this.cachePath);
6272
+ const cacheDir = path.dirname(this.cachePath);
5406
6273
  await promises.mkdir(cacheDir, { recursive: true });
5407
6274
  const jsonContent = JSON.stringify(indexData, null, 2);
5408
6275
  await promises.writeFile(this.cachePath, jsonContent, "utf-8");
@@ -5446,96 +6313,96 @@ var FileIndexerAdapter = class {
5446
6313
  const events = [];
5447
6314
  try {
5448
6315
  allRecords.tasks.forEach((task) => {
5449
- const timestampPart = task.id.split("-")[0];
6316
+ const timestampPart = task.payload.id.split("-")[0];
5450
6317
  if (timestampPart) {
5451
6318
  events.push({
5452
6319
  timestamp: parseInt(timestampPart),
5453
6320
  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 }
6321
+ entityId: task.payload.id,
6322
+ entityTitle: task.payload.title,
6323
+ actorId: task.header.signatures[0]?.keyId || "unknown",
6324
+ // Extract from first signature
6325
+ metadata: { priority: task.payload.priority, status: task.payload.status }
5459
6326
  });
5460
6327
  }
5461
6328
  });
5462
6329
  allRecords.cycles.forEach((cycle) => {
5463
- const timestampPart = cycle.id.split("-")[0];
6330
+ const timestampPart = cycle.payload.id.split("-")[0];
5464
6331
  if (timestampPart) {
5465
6332
  events.push({
5466
6333
  timestamp: parseInt(timestampPart),
5467
6334
  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 }
6335
+ entityId: cycle.payload.id,
6336
+ entityTitle: cycle.payload.title,
6337
+ actorId: cycle.header.signatures[0]?.keyId || "unknown",
6338
+ // Extract from first signature
6339
+ metadata: { status: cycle.payload.status }
5473
6340
  });
5474
6341
  }
5475
6342
  });
5476
6343
  allRecords.feedback.forEach((feedback) => {
5477
- const timestampPart = feedback.id.split("-")[0];
6344
+ const timestampPart = feedback.payload.id.split("-")[0];
5478
6345
  if (timestampPart) {
5479
6346
  const metadata = {
5480
- type: feedback.type,
5481
- resolution: feedback.status
6347
+ type: feedback.payload.type,
6348
+ resolution: feedback.payload.status
5482
6349
  };
5483
- if (feedback.assignee) {
5484
- metadata.assignee = feedback.assignee;
6350
+ if (feedback.payload.assignee) {
6351
+ metadata.assignee = feedback.payload.assignee;
5485
6352
  }
5486
6353
  const event = {
5487
6354
  timestamp: parseInt(timestampPart),
5488
6355
  type: "feedback_created",
5489
- entityId: feedback.id,
5490
- entityTitle: `${feedback.type}: ${feedback.content.slice(0, 40)}...`,
6356
+ entityId: feedback.payload.id,
6357
+ entityTitle: `${feedback.payload.type}: ${feedback.payload.content.slice(0, 40)}...`,
6358
+ actorId: feedback.header.signatures[0]?.keyId || feedback.payload.assignee || "unknown",
5491
6359
  metadata
5492
6360
  };
5493
- if (feedback.assignee) {
5494
- event.actorId = feedback.assignee;
5495
- }
5496
6361
  events.push(event);
5497
6362
  }
5498
6363
  });
5499
6364
  allRecords.changelogs.forEach((changelog) => {
5500
- const timestampPart = changelog.id.split("-")[0];
6365
+ const timestampPart = changelog.payload.id.split("-")[0];
5501
6366
  if (timestampPart) {
5502
- events.push({
6367
+ const event = {
5503
6368
  timestamp: parseInt(timestampPart),
5504
6369
  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
- });
6370
+ entityId: changelog.payload.id,
6371
+ entityTitle: changelog.payload.title || "Release notes",
6372
+ actorId: changelog.header.signatures[0]?.keyId || "unknown"
6373
+ };
6374
+ if (changelog.payload.version) {
6375
+ event.metadata = { version: changelog.payload.version };
6376
+ }
6377
+ events.push(event);
5511
6378
  }
5512
6379
  });
5513
6380
  allRecords.executions.forEach((execution) => {
5514
- const timestampPart = execution.id.split("-")[0];
6381
+ const timestampPart = execution.payload.id.split("-")[0];
5515
6382
  if (timestampPart) {
5516
6383
  events.push({
5517
6384
  timestamp: parseInt(timestampPart),
5518
6385
  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
6386
+ entityId: execution.payload.id,
6387
+ entityTitle: execution.payload.title || `Working on ${execution.payload.taskId.slice(-8)}`,
6388
+ actorId: execution.header.signatures[0]?.keyId || "unknown",
6389
+ // Extract from first signature
5523
6390
  metadata: {
5524
- executionType: execution.type || "development",
5525
- taskId: execution.taskId
6391
+ executionType: execution.payload.type || "development",
6392
+ taskId: execution.payload.taskId
5526
6393
  }
5527
6394
  });
5528
6395
  }
5529
6396
  });
5530
6397
  allRecords.actors.forEach((actor) => {
5531
- const timestampPart = actor.id.split("-")[0];
6398
+ const timestampPart = actor.payload.id.split("-")[0];
5532
6399
  if (timestampPart) {
5533
6400
  events.push({
5534
6401
  timestamp: parseInt(timestampPart),
5535
6402
  type: "actor_created",
5536
- entityId: actor.id,
5537
- entityTitle: `${actor.displayName} joined (${actor.type})`,
5538
- metadata: { type: actor.type }
6403
+ entityId: actor.payload.id,
6404
+ entityTitle: `${actor.payload.displayName} joined (${actor.payload.type})`,
6405
+ metadata: { type: actor.payload.type }
5539
6406
  });
5540
6407
  }
5541
6408
  });
@@ -5556,10 +6423,10 @@ var FileIndexerAdapter = class {
5556
6423
  let recentActivity = "Task created";
5557
6424
  try {
5558
6425
  let projectRoot2 = process.cwd();
5559
- while (!fs.existsSync(pathUtils.join(projectRoot2, ".gitgov")) && projectRoot2 !== "/") {
5560
- projectRoot2 = pathUtils.dirname(projectRoot2);
6426
+ while (!fs.existsSync(path.join(projectRoot2, ".gitgov")) && projectRoot2 !== "/") {
6427
+ projectRoot2 = path.dirname(projectRoot2);
5561
6428
  }
5562
- const taskFilePath = pathUtils.join(projectRoot2, ".gitgov", "tasks", `${task.id}.json`);
6429
+ const taskFilePath = path.join(projectRoot2, ".gitgov", "tasks", `${task.id}.json`);
5563
6430
  const stats = await promises.stat(taskFilePath);
5564
6431
  const fileModTime = stats.mtime.getTime();
5565
6432
  const creationTime = this.getTimestampFromId(task.id) * 1e3;
@@ -5572,34 +6439,34 @@ var FileIndexerAdapter = class {
5572
6439
  } catch (error) {
5573
6440
  }
5574
6441
  const relatedFeedback = relatedRecords.feedback.filter(
5575
- (f) => f.entityId === task.id || f.content.includes(task.id)
6442
+ (f) => f.payload.entityId === task.id || f.payload.content.includes(task.id)
5576
6443
  );
5577
6444
  for (const feedback of relatedFeedback) {
5578
- const feedbackTime = this.getTimestampFromId(feedback.id) * 1e3;
6445
+ const feedbackTime = this.getTimestampFromId(feedback.payload.id) * 1e3;
5579
6446
  if (feedbackTime > lastUpdated) {
5580
6447
  lastUpdated = feedbackTime;
5581
6448
  lastActivityType = "feedback_received";
5582
- recentActivity = `${feedback.type} feedback: ${feedback.content.slice(0, 30)}...`;
6449
+ recentActivity = `${feedback.payload.type} feedback: ${feedback.payload.content.slice(0, 30)}...`;
5583
6450
  }
5584
6451
  }
5585
- const relatedExecutions = relatedRecords.executions.filter((e) => e.taskId === task.id);
6452
+ const relatedExecutions = relatedRecords.executions.filter((e) => e.payload.taskId === task.id);
5586
6453
  for (const execution of relatedExecutions) {
5587
- const executionTime = this.getTimestampFromId(execution.id) * 1e3;
6454
+ const executionTime = this.getTimestampFromId(execution.payload.id) * 1e3;
5588
6455
  if (executionTime > lastUpdated) {
5589
6456
  lastUpdated = executionTime;
5590
6457
  lastActivityType = "execution_added";
5591
- recentActivity = `Execution: ${execution.title || "Work logged"}`;
6458
+ recentActivity = `Execution: ${execution.payload.title || "Work logged"}`;
5592
6459
  }
5593
6460
  }
5594
6461
  const relatedChangelogs = relatedRecords.changelogs.filter(
5595
- (c) => c.entityId === task.id || c.references?.tasks?.includes(task.id) || c.description?.includes(task.id)
6462
+ (c) => c.payload.relatedTasks.includes(task.id) || c.payload.description?.includes(task.id)
5596
6463
  );
5597
6464
  for (const changelog of relatedChangelogs) {
5598
- const changelogTime = this.getTimestampFromId(changelog.id) * 1e3;
6465
+ const changelogTime = this.getTimestampFromId(changelog.payload.id) * 1e3;
5599
6466
  if (changelogTime > lastUpdated) {
5600
6467
  lastUpdated = changelogTime;
5601
6468
  lastActivityType = "changelog_created";
5602
- recentActivity = `Changelog: ${changelog.title}`;
6469
+ recentActivity = `Changelog: ${changelog.payload.title}`;
5603
6470
  }
5604
6471
  }
5605
6472
  return { lastUpdated, lastActivityType, recentActivity };
@@ -5614,11 +6481,13 @@ var FileIndexerAdapter = class {
5614
6481
  }
5615
6482
  /**
5616
6483
  * [EARS-22] Enrich a TaskRecord with activity metadata
6484
+ * @param task - Full GitGovTaskRecord with header.signatures for author/lastModifier extraction
6485
+ * @param relatedRecords - All related records with full metadata
5617
6486
  */
5618
6487
  async enrichTaskRecord(task, relatedRecords) {
5619
- const { lastUpdated, lastActivityType, recentActivity } = await this.calculateLastUpdated(task, relatedRecords);
6488
+ const { lastUpdated, lastActivityType, recentActivity } = await this.calculateLastUpdated(task.payload, relatedRecords);
5620
6489
  return {
5621
- ...task,
6490
+ ...task.payload,
5622
6491
  lastUpdated,
5623
6492
  lastActivityType,
5624
6493
  recentActivity
@@ -5681,8 +6550,9 @@ var ProjectAdapter = class {
5681
6550
  throw new Error(`Environment validation failed: ${envValidation.warnings.join(", ")}`);
5682
6551
  }
5683
6552
  const projectRoot2 = process.env["GITGOV_ORIGINAL_DIR"] || process.cwd();
5684
- const gitgovPath = pathUtils.join(projectRoot2, ".gitgov");
6553
+ const gitgovPath = path.join(projectRoot2, ".gitgov");
5685
6554
  await this.createDirectoryStructure(gitgovPath);
6555
+ await this.copyAgentPrompt(gitgovPath);
5686
6556
  const actor = await this.identityAdapter.createActor(
5687
6557
  {
5688
6558
  type: "human",
@@ -5735,7 +6605,6 @@ var ProjectAdapter = class {
5735
6605
  await this.persistConfiguration(config, gitgovPath);
5736
6606
  await this.initializeSession(actor.id, gitgovPath);
5737
6607
  await this.setupGitIntegration(projectRoot2);
5738
- await this.setupKiroIntegration(projectRoot2);
5739
6608
  const initializationTime = Date.now() - startTime;
5740
6609
  return {
5741
6610
  success: true,
@@ -5745,7 +6614,7 @@ var ProjectAdapter = class {
5745
6614
  actor: {
5746
6615
  id: actor.id,
5747
6616
  displayName: actor.displayName,
5748
- publicKeyPath: pathUtils.join(gitgovPath, "actors", `${actor.id}.json`)
6617
+ publicKeyPath: path.join(gitgovPath, "actors", `${actor.id}.json`)
5749
6618
  },
5750
6619
  template: templateResult ? {
5751
6620
  processed: true,
@@ -5772,7 +6641,7 @@ var ProjectAdapter = class {
5772
6641
  const warnings = [];
5773
6642
  const suggestions = [];
5774
6643
  try {
5775
- const gitPath = pathUtils.join(targetPath, ".git");
6644
+ const gitPath = path.join(targetPath, ".git");
5776
6645
  const isGitRepo = existsSync(gitPath);
5777
6646
  if (!isGitRepo) {
5778
6647
  warnings.push(`Not a Git repository in directory: ${targetPath}`);
@@ -5780,7 +6649,7 @@ var ProjectAdapter = class {
5780
6649
  }
5781
6650
  let hasWritePermissions = false;
5782
6651
  try {
5783
- const testFile = pathUtils.join(targetPath, ".gitgov-test");
6652
+ const testFile = path.join(targetPath, ".gitgov-test");
5784
6653
  await promises.writeFile(testFile, "test");
5785
6654
  await promises.unlink(testFile);
5786
6655
  hasWritePermissions = true;
@@ -5788,7 +6657,7 @@ var ProjectAdapter = class {
5788
6657
  warnings.push("No write permissions in target directory");
5789
6658
  suggestions.push("Ensure you have write permissions in the target directory");
5790
6659
  }
5791
- const gitgovPath = pathUtils.join(targetPath, ".gitgov");
6660
+ const gitgovPath = path.join(targetPath, ".gitgov");
5792
6661
  let isAlreadyInitialized = false;
5793
6662
  try {
5794
6663
  await promises.access(gitgovPath);
@@ -5877,59 +6746,13 @@ var ProjectAdapter = class {
5877
6746
  ]);
5878
6747
  }
5879
6748
  }
5880
- /**
5881
- * Sets up Kiro IDE integration by always copying GitGovernance hooks
5882
- */
5883
- async setupKiroIntegration(projectRoot2) {
5884
- const kiroDir = pathUtils.join(projectRoot2, ".kiro");
5885
- const kiroHooksDir = pathUtils.join(kiroDir, "hooks");
5886
- try {
5887
- await promises.mkdir(kiroHooksDir, { recursive: true });
5888
- } catch {
5889
- }
5890
- const sourceHooksDir = pathUtils.join(ConfigManager.findProjectRoot() || process.cwd(), ".kiro/hooks");
5891
- try {
5892
- await promises.access(sourceHooksDir);
5893
- const essentialHooks = [
5894
- "gitgov-auto-indexer.kiro.hook",
5895
- "git-diagnostics-commit.kiro.hook",
5896
- "gitgov-file-analyzer.kiro.hook",
5897
- "code-quality-analyzer.kiro.hook",
5898
- "gitgov-quick-status.kiro.hook",
5899
- "gitgov-task-creator.kiro.hook",
5900
- "gitgov-work-session.kiro.hook"
5901
- ];
5902
- let copiedHooks = 0;
5903
- for (const hookFile of essentialHooks) {
5904
- try {
5905
- const sourcePath = pathUtils.join(sourceHooksDir, hookFile);
5906
- const targetPath = pathUtils.join(kiroHooksDir, hookFile);
5907
- await promises.copyFile(sourcePath, targetPath);
5908
- copiedHooks++;
5909
- } catch {
5910
- }
5911
- }
5912
- try {
5913
- const sourceGitgovPath = pathUtils.join(ConfigManager.findProjectRoot() || process.cwd(), ".gitgov/gitgov");
5914
- const targetGitgovPath = pathUtils.join(projectRoot2, ".gitgov/gitgov");
5915
- await promises.copyFile(sourceGitgovPath, targetGitgovPath);
5916
- await promises.chmod(targetGitgovPath, 493);
5917
- console.log(`\u{1F4CB} GitGovernance executable copied to .gitgov/gitgov`);
5918
- } catch {
5919
- }
5920
- if (copiedHooks > 0) {
5921
- console.log(`\u{1F527} Kiro IDE Integration: ${copiedHooks} GitGovernance hooks installed`);
5922
- }
5923
- } catch {
5924
- }
5925
- }
5926
6749
  /**
5927
6750
  * [EARS-4] Cleans up partial setup artifacts if initialization fails
5928
6751
  */
5929
6752
  async rollbackPartialSetup(setupId) {
5930
6753
  try {
5931
6754
  const projectRoot2 = process.env["GITGOV_ORIGINAL_DIR"] || process.cwd();
5932
- const gitgovPath = pathUtils.join(projectRoot2, ".gitgov");
6755
+ const gitgovPath = path.join(projectRoot2, ".gitgov");
5933
6756
  try {
5934
6757
  await promises.access(gitgovPath);
5935
6758
  await promises.rm(gitgovPath, { recursive: true, force: true });
@@ -5993,18 +6816,28 @@ var ProjectAdapter = class {
5993
6816
  ];
5994
6817
  await promises.mkdir(gitgovPath, { recursive: true });
5995
6818
  for (const dir of directories) {
5996
- await promises.mkdir(pathUtils.join(gitgovPath, dir), { recursive: true });
6819
+ await promises.mkdir(path.join(gitgovPath, dir), { recursive: true });
6820
+ }
6821
+ }
6822
+ async copyAgentPrompt(gitgovPath) {
6823
+ try {
6824
+ const sourcePrompt = path.join(ConfigManager.findProjectRoot() || process.cwd(), "docs/gitgov_agent_prompt.md");
6825
+ const targetPrompt = path.join(gitgovPath, "gitgov");
6826
+ await promises.copyFile(sourcePrompt, targetPrompt);
6827
+ console.log(`\u{1F4CB} @gitgov agent prompt copied to .gitgov/gitgov`);
6828
+ } catch {
6829
+ console.warn("Warning: Could not copy @gitgov agent prompt. Project will work but AI assistant may not have local instructions.");
5997
6830
  }
5998
6831
  }
5999
6832
  generateProjectId(name) {
6000
6833
  return name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-");
6001
6834
  }
6002
6835
  async persistConfiguration(config, gitgovPath) {
6003
- const configPath = pathUtils.join(gitgovPath, "config.json");
6836
+ const configPath = path.join(gitgovPath, "config.json");
6004
6837
  await promises.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
6005
6838
  }
6006
6839
  async initializeSession(actorId, gitgovPath) {
6007
- const sessionPath = pathUtils.join(gitgovPath, ".session.json");
6840
+ const sessionPath = path.join(gitgovPath, ".session.json");
6008
6841
  const session = {
6009
6842
  lastSession: {
6010
6843
  actorId,
@@ -6019,7 +6852,7 @@ var ProjectAdapter = class {
6019
6852
  await promises.writeFile(sessionPath, JSON.stringify(session, null, 2), "utf-8");
6020
6853
  }
6021
6854
  async setupGitIntegration(projectRoot2) {
6022
- const gitignorePath = pathUtils.join(projectRoot2, ".gitignore");
6855
+ const gitignorePath = path.join(projectRoot2, ".gitignore");
6023
6856
  const gitignoreContent = `
6024
6857
  # GitGovernance
6025
6858
  .gitgov/.session.json
@@ -6459,7 +7292,6 @@ var workflow_methodology_scrum_default = {
6459
7292
  required_agents: [
6460
7293
  {
6461
7294
  id: "agent:scrum-master",
6462
- gremio: "operations",
6463
7295
  engine: {
6464
7296
  type: "local",
6465
7297
  entrypoint: "@gitgov/agent-scrum-master"
@@ -6485,7 +7317,6 @@ var workflow_methodology_scrum_default = {
6485
7317
  },
6486
7318
  {
6487
7319
  id: "agent:product-owner-assistant",
6488
- gremio: "strategy",
6489
7320
  engine: {
6490
7321
  type: "mcp",
6491
7322
  url: "http://localhost:8080/product-owner-mcp"
@@ -6561,11 +7392,19 @@ var WorkflowMethodologyAdapter = class _WorkflowMethodologyAdapter {
6561
7392
  return this.config;
6562
7393
  }
6563
7394
  /**
6564
- * Gets the guild tag from a task's tags array
7395
+ * Determines which signature group to use for validation.
7396
+ * Checks all available signature groups and returns the first one where
7397
+ * the actor has matching capability roles.
6565
7398
  */
6566
- getTaskGuild(context) {
6567
- const guildTag = context.task.tags?.find((tag) => tag.startsWith("guild:"));
6568
- return guildTag ? guildTag.replace("guild:", "") : "__default__";
7399
+ getApplicableSignatureGroup(signatureRules, actor) {
7400
+ for (const [groupName, ruleSet] of Object.entries(signatureRules)) {
7401
+ if (groupName === "__default__") continue;
7402
+ const hasMatchingRole = actor.roles?.some((role) => ruleSet.capability_roles?.includes(role));
7403
+ if (hasMatchingRole) {
7404
+ return groupName;
7405
+ }
7406
+ }
7407
+ return "__default__";
6569
7408
  }
6570
7409
  /**
6571
7410
  * Determines if a state transition is legal according to the methodology
@@ -6589,7 +7428,6 @@ var WorkflowMethodologyAdapter = class _WorkflowMethodologyAdapter {
6589
7428
  */
6590
7429
  async validateSignature(signature, context) {
6591
7430
  const config = this.getConfig();
6592
- const guild = this.getTaskGuild(context);
6593
7431
  if (!context.transitionTo) {
6594
7432
  throw new Error('ValidationContext must include "transitionTo" for signature validation.');
6595
7433
  }
@@ -6605,7 +7443,8 @@ var WorkflowMethodologyAdapter = class _WorkflowMethodologyAdapter {
6605
7443
  }
6606
7444
  const signatureRules = transitionConfig.requires.signatures;
6607
7445
  if (!signatureRules) return true;
6608
- const ruleSet = signatureRules[guild] || signatureRules["__default__"];
7446
+ const signatureGroup = this.getApplicableSignatureGroup(signatureRules, actor);
7447
+ const ruleSet = signatureRules[signatureGroup];
6609
7448
  if (!ruleSet) return false;
6610
7449
  if (signature.role !== ruleSet.role) {
6611
7450
  return false;
@@ -6720,9 +7559,11 @@ __export(factories_exports, {
6720
7559
  createChangelogRecord: () => createChangelogRecord,
6721
7560
  createCycleRecord: () => createCycleRecord,
6722
7561
  createDefaultWorkflowMethodologyConfig: () => createDefaultWorkflowMethodologyConfig,
7562
+ createEmbeddedMetadataRecord: () => createEmbeddedMetadataRecord,
6723
7563
  createExecutionRecord: () => createExecutionRecord,
6724
7564
  createFeedbackRecord: () => createFeedbackRecord,
6725
7565
  createTaskRecord: () => createTaskRecord,
7566
+ createTestSignature: () => createTestSignature,
6726
7567
  createWorkflowMethodologyConfig: () => createWorkflowMethodologyConfig
6727
7568
  });
6728
7569
 
@@ -6962,6 +7803,68 @@ async function createDefaultWorkflowMethodologyConfig() {
6962
7803
  });
6963
7804
  }
6964
7805
 
7806
+ // src/factories/embedded_metadata_factory.ts
7807
+ function createTestSignature(keyId = "human:test-user", role = "author", notes = "Test signature - unsigned") {
7808
+ const timestamp = Math.floor(Date.now() / 1e3);
7809
+ return {
7810
+ keyId,
7811
+ role,
7812
+ notes,
7813
+ signature: "dGVzdHNpZ25hdHVyZWJhc2U2NGVuY29kZWRkdW1teWZvcnRlc3RpbmdwdXJwb3Nlc29ubHlub3RyZWFsY3J5cHRvZ3JhcGh5PT0=",
7814
+ // Dummy 88-char base64 for testing (matches Ed25519 signature length)
7815
+ timestamp
7816
+ };
7817
+ }
7818
+ function inferTypeFromPayload(payload) {
7819
+ if ("engine" in payload) return "agent";
7820
+ if ("taskId" in payload && "result" in payload) return "execution";
7821
+ if ("relatedTasks" in payload && "completedAt" in payload) return "changelog";
7822
+ if ("entityType" in payload && "entityId" in payload) return "feedback";
7823
+ if ("status" in payload && "taskIds" in payload) return "cycle";
7824
+ if ("priority" in payload && "description" in payload) return "task";
7825
+ if ("displayName" in payload && "publicKey" in payload) return "actor";
7826
+ return "custom";
7827
+ }
7828
+ async function createEmbeddedMetadataRecord(payload, options = {}) {
7829
+ const inferredType = inferTypeFromPayload(payload);
7830
+ const type = options.header?.type || inferredType;
7831
+ const payloadChecksum = calculatePayloadChecksum(payload);
7832
+ let signatures;
7833
+ if (options.signatures) {
7834
+ signatures = options.signatures;
7835
+ } else if (options.signature?.privateKey) {
7836
+ const keyId = options.signature.keyId || "human:test-user";
7837
+ const role = options.signature.role || "author";
7838
+ const notes = options.signature.notes || "Created via factory";
7839
+ signatures = [signPayload(payload, options.signature.privateKey, keyId, role, notes)];
7840
+ } else {
7841
+ const keyId = options.signature?.keyId || "human:test-user";
7842
+ const role = options.signature?.role || "author";
7843
+ const notes = options.signature?.notes || "Test signature - unsigned";
7844
+ signatures = [createTestSignature(keyId, role, notes)];
7845
+ }
7846
+ const header = {
7847
+ version: "1.0",
7848
+ // Always 1.0 (schema enforces this)
7849
+ type,
7850
+ payloadChecksum,
7851
+ signatures,
7852
+ ...type === "custom" && {
7853
+ schemaUrl: options.header?.schemaUrl,
7854
+ schemaChecksum: options.header?.schemaChecksum
7855
+ }
7856
+ };
7857
+ const embeddedRecord = {
7858
+ header,
7859
+ payload
7860
+ };
7861
+ const validation = validateEmbeddedMetadataDetailed(embeddedRecord);
7862
+ if (!validation.isValid) {
7863
+ throw new DetailedValidationError("EmbeddedMetadataRecord", validation.errors);
7864
+ }
7865
+ return embeddedRecord;
7866
+ }
7867
+
6965
7868
  // src/validation/index.ts
6966
7869
  var validation_exports = {};
6967
7870
  __export(validation_exports, {
@@ -7025,9 +7928,11 @@ function generateSubscriptionId() {
7025
7928
  var EventBus = class {
7026
7929
  emitter;
7027
7930
  subscriptions;
7931
+ pendingHandlers;
7028
7932
  constructor() {
7029
7933
  this.emitter = new EventEmitter();
7030
7934
  this.subscriptions = /* @__PURE__ */ new Map();
7935
+ this.pendingHandlers = /* @__PURE__ */ new Set();
7031
7936
  this.emitter.setMaxListeners(100);
7032
7937
  }
7033
7938
  /**
@@ -7058,11 +7963,17 @@ var EventBus = class {
7058
7963
  subscribe(eventType, handler) {
7059
7964
  const subscriptionId = generateSubscriptionId();
7060
7965
  const wrappedHandler = async (event) => {
7061
- try {
7062
- await handler(event);
7063
- } catch (error) {
7064
- console.error(`Error in event handler for ${eventType}:`, error);
7065
- }
7966
+ const handlerPromise = (async () => {
7967
+ try {
7968
+ await handler(event);
7969
+ } catch (error) {
7970
+ console.error(`Error in event handler for ${eventType}:`, error);
7971
+ }
7972
+ })();
7973
+ this.pendingHandlers.add(handlerPromise);
7974
+ handlerPromise.finally(() => {
7975
+ this.pendingHandlers.delete(handlerPromise);
7976
+ });
7066
7977
  };
7067
7978
  const subscription = {
7068
7979
  id: subscriptionId,
@@ -7134,6 +8045,43 @@ var EventBus = class {
7134
8045
  subscribeToAll(handler) {
7135
8046
  return this.subscribe("*", handler);
7136
8047
  }
8048
+ /**
8049
+ * Wait for all pending event handlers to complete.
8050
+ * This is primarily useful for testing to ensure event handlers finish before assertions.
8051
+ *
8052
+ * In production, events are fire-and-forget for performance.
8053
+ * In tests, use this to synchronize and avoid race conditions.
8054
+ *
8055
+ * @param options - Optional configuration
8056
+ * @param options.timeout - Maximum time to wait in ms (default: 5000)
8057
+ * @returns Promise that resolves when all handlers complete or timeout occurs
8058
+ *
8059
+ * @example
8060
+ * ```typescript
8061
+ * await feedbackAdapter.create(...); // publishes event
8062
+ * await eventBus.waitForIdle(); // wait for BacklogAdapter.handleFeedbackCreated()
8063
+ * const task = await backlogAdapter.getTask(taskId);
8064
+ * expect(task.status).toBe('paused'); // now safe to assert
8065
+ * ```
8066
+ */
8067
+ async waitForIdle(options = {}) {
8068
+ const timeout = options.timeout ?? 5e3;
8069
+ const startTime = Date.now();
8070
+ while (this.pendingHandlers.size > 0) {
8071
+ if (Date.now() - startTime > timeout) {
8072
+ const pendingCount = this.pendingHandlers.size;
8073
+ console.warn(`EventBus.waitForIdle() timeout after ${timeout}ms with ${pendingCount} handlers still pending`);
8074
+ break;
8075
+ }
8076
+ if (this.pendingHandlers.size > 0) {
8077
+ await Promise.race([
8078
+ Promise.all(Array.from(this.pendingHandlers)),
8079
+ new Promise((resolve) => setTimeout(resolve, 10))
8080
+ // Re-check every 10ms
8081
+ ]);
8082
+ }
8083
+ }
8084
+ }
7137
8085
  };
7138
8086
  var eventBus = new EventBus();
7139
8087
  function publishEvent(event) {
@@ -7327,14 +8275,17 @@ var RelationshipAnalyzer = class {
7327
8275
  for (const task of tasks) {
7328
8276
  const isEpic = this.isEpicTask(task);
7329
8277
  const title = task.title || "Untitled Task";
7330
- nodes.push({
8278
+ const node = {
7331
8279
  id: this.generateNodeId(task),
7332
8280
  type: isEpic ? "epic-task" : "task",
7333
8281
  title,
7334
8282
  status: task.status,
7335
- tags: task.tags,
7336
8283
  originalId: task.id
7337
- });
8284
+ };
8285
+ if (task.tags) {
8286
+ node.tags = task.tags;
8287
+ }
8288
+ nodes.push(node);
7338
8289
  }
7339
8290
  return nodes;
7340
8291
  }
@@ -8014,14 +8965,14 @@ var DiagramGenerator = class {
8014
8965
  * Loads all cycle records from the filesystem
8015
8966
  */
8016
8967
  async loadCycleRecords(gitgovPath) {
8017
- const cyclesDir = pathUtils.join(gitgovPath, "cycles");
8968
+ const cyclesDir = path.join(gitgovPath, "cycles");
8018
8969
  try {
8019
8970
  const files = await promises.readdir(cyclesDir);
8020
8971
  const jsonFiles = files.filter((file) => file.endsWith(".json"));
8021
8972
  const cycles = [];
8022
8973
  for (const file of jsonFiles) {
8023
8974
  try {
8024
- const filePath = pathUtils.join(cyclesDir, file);
8975
+ const filePath = path.join(cyclesDir, file);
8025
8976
  const content = await promises.readFile(filePath, "utf-8");
8026
8977
  const record = JSON.parse(content);
8027
8978
  if (record.payload && record.payload.id) {
@@ -8050,14 +9001,14 @@ var DiagramGenerator = class {
8050
9001
  * Loads all task records from the filesystem
8051
9002
  */
8052
9003
  async loadTaskRecords(gitgovPath) {
8053
- const tasksDir = pathUtils.join(gitgovPath, "tasks");
9004
+ const tasksDir = path.join(gitgovPath, "tasks");
8054
9005
  try {
8055
9006
  const files = await promises.readdir(tasksDir);
8056
9007
  const jsonFiles = files.filter((file) => file.endsWith(".json"));
8057
9008
  const tasks = [];
8058
9009
  for (const file of jsonFiles) {
8059
9010
  try {
8060
- const filePath = pathUtils.join(tasksDir, file);
9011
+ const filePath = path.join(tasksDir, file);
8061
9012
  const content = await promises.readFile(filePath, "utf-8");
8062
9013
  const record = JSON.parse(content);
8063
9014
  if (record.payload && record.payload.id) {