@gitgov/core 2.7.2 → 2.9.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/fs.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import * as fs from 'fs/promises';
2
2
  import { readdir, readFile } from 'fs/promises';
3
- import * as path9 from 'path';
4
- import path9__default, { join, dirname, basename, relative } from 'path';
3
+ import * as path6 from 'path';
4
+ import path6__default, { join, dirname, basename, relative } from 'path';
5
5
  import * as fs8 from 'fs';
6
- import { promises, existsSync, readFileSync, writeFileSync } from 'fs';
6
+ import { promises, existsSync, realpathSync } from 'fs';
7
7
  import fg from 'fast-glob';
8
- import { generateKeyPair, randomUUID, createHash } from 'crypto';
8
+ import { generateKeyPair, createHash, randomUUID } from 'crypto';
9
9
  import Ajv from 'ajv';
10
10
  import addFormats from 'ajv-formats';
11
11
  import { promisify } from 'util';
12
- import { exec, execSync } from 'child_process';
12
+ import { execSync } from 'child_process';
13
13
  import { createRequire } from 'module';
14
14
  import { fileURLToPath } from 'url';
15
- import os from 'os';
15
+ import * as os from 'os';
16
16
  import chokidar from 'chokidar';
17
17
 
18
18
  // src/record_store/fs/fs_record_store.ts
@@ -52,7 +52,7 @@ var FsRecordStore = class {
52
52
  getFilePath(id) {
53
53
  validateId(id);
54
54
  const fileId = this.idEncoder ? this.idEncoder.encode(id) : id;
55
- return path9.join(this.basePath, `${fileId}${this.extension}`);
55
+ return path6.join(this.basePath, `${fileId}${this.extension}`);
56
56
  }
57
57
  async get(id) {
58
58
  const filePath = this.getFilePath(id);
@@ -69,7 +69,7 @@ var FsRecordStore = class {
69
69
  async put(id, value) {
70
70
  const filePath = this.getFilePath(id);
71
71
  if (this.createIfMissing) {
72
- await fs.mkdir(path9.dirname(filePath), { recursive: true });
72
+ await fs.mkdir(path6.dirname(filePath), { recursive: true });
73
73
  }
74
74
  const content = this.serializer.stringify(value);
75
75
  await fs.writeFile(filePath, content, "utf-8");
@@ -219,7 +219,7 @@ var ConfigManager = class {
219
219
  var FsConfigStore = class {
220
220
  configPath;
221
221
  constructor(projectRootPath) {
222
- this.configPath = path9.join(projectRootPath, ".gitgov", "config.json");
222
+ this.configPath = path6.join(projectRootPath, ".gitgov", "config.json");
223
223
  }
224
224
  /**
225
225
  * Load project configuration from .gitgov/config.json
@@ -384,8 +384,8 @@ var FsSessionStore = class {
384
384
  sessionPath;
385
385
  keysPath;
386
386
  constructor(projectRootPath) {
387
- this.sessionPath = path9.join(projectRootPath, ".gitgov", ".session.json");
388
- this.keysPath = path9.join(projectRootPath, ".gitgov", "keys");
387
+ this.sessionPath = path6.join(projectRootPath, ".gitgov", ".session.json");
388
+ this.keysPath = path6.join(projectRootPath, ".gitgov", "keys");
389
389
  }
390
390
  /**
391
391
  * Load local session from .gitgov/.session.json
@@ -547,7 +547,7 @@ var FsKeyProvider = class {
547
547
  */
548
548
  getKeyPath(actorId) {
549
549
  const sanitized = this.sanitizeActorId(actorId);
550
- return path9.join(this.keysDir, `${sanitized}${this.extension}`);
550
+ return path6.join(this.keysDir, `${sanitized}${this.extension}`);
551
551
  }
552
552
  /**
553
553
  * [EARS-FKP04] Sanitizes actorId to prevent directory traversal.
@@ -605,7 +605,7 @@ var FsFileLister = class {
605
605
  pattern
606
606
  );
607
607
  }
608
- if (path9.isAbsolute(pattern)) {
608
+ if (path6.isAbsolute(pattern)) {
609
609
  throw new FileListerError(
610
610
  `Invalid pattern: absolute paths not allowed: ${pattern}`,
611
611
  "INVALID_PATH",
@@ -613,6 +613,7 @@ var FsFileLister = class {
613
613
  );
614
614
  }
615
615
  }
616
+ const normalized = patterns.map((p) => p.endsWith("/") ? `${p}**` : p);
616
617
  const fgOptions = {
617
618
  cwd: this.cwd,
618
619
  ignore: options?.ignore ?? [],
@@ -623,7 +624,7 @@ var FsFileLister = class {
623
624
  if (options?.maxDepth !== void 0) {
624
625
  fgOptions.deep = options.maxDepth;
625
626
  }
626
- return fg(patterns, fgOptions);
627
+ return fg(normalized, fgOptions);
627
628
  }
628
629
  /**
629
630
  * [EARS-FL02] Checks if a file exists.
@@ -631,7 +632,7 @@ var FsFileLister = class {
631
632
  async exists(filePath) {
632
633
  this.validatePath(filePath);
633
634
  try {
634
- const fullPath = path9.join(this.cwd, filePath);
635
+ const fullPath = path6.join(this.cwd, filePath);
635
636
  await fs.access(fullPath);
636
637
  return true;
637
638
  } catch {
@@ -644,7 +645,7 @@ var FsFileLister = class {
644
645
  */
645
646
  async read(filePath) {
646
647
  this.validatePath(filePath);
647
- const fullPath = path9.join(this.cwd, filePath);
648
+ const fullPath = path6.join(this.cwd, filePath);
648
649
  try {
649
650
  return await fs.readFile(fullPath, "utf-8");
650
651
  } catch (err) {
@@ -676,7 +677,7 @@ var FsFileLister = class {
676
677
  */
677
678
  async stat(filePath) {
678
679
  this.validatePath(filePath);
679
- const fullPath = path9.join(this.cwd, filePath);
680
+ const fullPath = path6.join(this.cwd, filePath);
680
681
  try {
681
682
  const stats = await fs.stat(fullPath);
682
683
  return {
@@ -712,7 +713,7 @@ var FsFileLister = class {
712
713
  filePath
713
714
  );
714
715
  }
715
- if (path9.isAbsolute(filePath)) {
716
+ if (path6.isAbsolute(filePath)) {
716
717
  throw new FileListerError(
717
718
  `Invalid path: absolute paths not allowed: ${filePath}`,
718
719
  "INVALID_PATH",
@@ -730,12 +731,11 @@ function isCyclePayload(payload) {
730
731
  return "title" in payload && "status" in payload && !("priority" in payload);
731
732
  }
732
733
 
733
- // src/utils/id_parser.ts
734
+ // src/record_types/common.types.ts
734
735
  var DIR_TO_TYPE = {
735
736
  "tasks": "task",
736
737
  "cycles": "cycle",
737
738
  "executions": "execution",
738
- "changelogs": "changelog",
739
739
  "feedbacks": "feedback",
740
740
  "actors": "actor",
741
741
  "agents": "agent"
@@ -743,6 +743,15 @@ var DIR_TO_TYPE = {
743
743
  Object.fromEntries(
744
744
  Object.entries(DIR_TO_TYPE).map(([dir, type]) => [type, dir])
745
745
  );
746
+ var GitGovError = class extends Error {
747
+ constructor(message, code) {
748
+ super(message);
749
+ this.code = code;
750
+ this.name = this.constructor.name;
751
+ }
752
+ };
753
+
754
+ // src/utils/id_parser.ts
746
755
  var VALID_DIRS = Object.keys(DIR_TO_TYPE);
747
756
  function extractRecordIdFromPath(filePath) {
748
757
  const parts = filePath.split("/");
@@ -762,9 +771,6 @@ function inferEntityTypeFromId(recordId) {
762
771
  if (recordId.match(/^\d+-exec-/) || recordId.includes("-execution-")) {
763
772
  return "execution";
764
773
  }
765
- if (recordId.match(/^\d+-changelog-/)) {
766
- return "changelog";
767
- }
768
774
  if (recordId.match(/^\d+-feedback-/)) {
769
775
  return "feedback";
770
776
  }
@@ -861,15 +867,6 @@ function calculatePayloadChecksum(payload) {
861
867
  return createHash("sha256").update(jsonString, "utf8").digest("hex");
862
868
  }
863
869
 
864
- // src/record_types/common.types.ts
865
- var GitGovError = class extends Error {
866
- constructor(message, code) {
867
- super(message);
868
- this.code = code;
869
- this.name = this.constructor.name;
870
- }
871
- };
872
-
873
870
  // src/record_schemas/errors.ts
874
871
  var DetailedValidationError = class extends GitGovError {
875
872
  constructor(recordType, errors) {
@@ -887,7 +884,7 @@ var actor_record_schema_default = {
887
884
  $schema: "http://json-schema.org/draft-07/schema#",
888
885
  $id: "actor_record_schema.json",
889
886
  title: "ActorRecord",
890
- description: "Canonical schema for actor records as defined in actor_protocol.md",
887
+ description: "Canonical schema for actor records as defined in 02_actor.md",
891
888
  additionalProperties: false,
892
889
  type: "object",
893
890
  required: [
@@ -982,8 +979,9 @@ var agent_record_schema_default = {
982
979
  $schema: "http://json-schema.org/draft-07/schema#",
983
980
  $id: "agent_record_schema.json",
984
981
  title: "AgentRecord",
985
- description: "Canonical schema for agent operational manifests.",
982
+ description: "Canonical schema for agent operational manifests \u2014 the work contract that defines how an agent is invoked.",
986
983
  type: "object",
984
+ additionalProperties: false,
987
985
  required: [
988
986
  "id",
989
987
  "engine"
@@ -991,8 +989,13 @@ var agent_record_schema_default = {
991
989
  properties: {
992
990
  id: {
993
991
  type: "string",
994
- pattern: "^agent:[a-z0-9:-]+$",
995
- description: "Unique identifier for the agent, linking to an ActorRecord."
992
+ pattern: "^agent(:[a-z0-9-]+)+$",
993
+ description: "Unique identifier for the agent, linking 1:1 to an ActorRecord of type agent.",
994
+ examples: [
995
+ "agent:scribe",
996
+ "agent:aion",
997
+ "agent:camilo:cursor"
998
+ ]
996
999
  },
997
1000
  status: {
998
1001
  type: "string",
@@ -1000,63 +1003,12 @@ var agent_record_schema_default = {
1000
1003
  "active",
1001
1004
  "archived"
1002
1005
  ],
1003
- default: "active"
1004
- },
1005
- triggers: {
1006
- type: "array",
1007
- default: [],
1008
- 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",
1009
- items: {
1010
- type: "object",
1011
- properties: {
1012
- type: {
1013
- type: "string",
1014
- enum: [
1015
- "manual",
1016
- "webhook",
1017
- "scheduled"
1018
- ],
1019
- description: "Type of trigger that activates the agent"
1020
- }
1021
- },
1022
- required: [
1023
- "type"
1024
- ],
1025
- additionalProperties: true
1026
- }
1027
- },
1028
- knowledge_dependencies: {
1029
- type: "array",
1030
- default: [],
1031
- items: {
1032
- type: "string",
1033
- description: "Glob patterns for blueprint files this agent needs access to"
1034
- }
1035
- },
1036
- prompt_engine_requirements: {
1037
- type: "object",
1038
- properties: {
1039
- roles: {
1040
- type: "array",
1041
- items: {
1042
- type: "string"
1043
- }
1044
- },
1045
- skills: {
1046
- type: "array",
1047
- items: {
1048
- type: "string"
1049
- }
1050
- }
1051
- }
1052
- },
1053
- metadata: {
1054
- type: "object",
1055
- 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",
1056
- additionalProperties: true
1006
+ default: "active",
1007
+ description: "Operational status. An archived agent cannot be invoked."
1057
1008
  },
1058
1009
  engine: {
1059
1010
  type: "object",
1011
+ description: "Invocation specification \u2014 defines how the agent is executed. Uses oneOf with 4 variants: local, api, mcp, custom.",
1060
1012
  oneOf: [
1061
1013
  {
1062
1014
  required: [
@@ -1103,7 +1055,8 @@ var agent_record_schema_default = {
1103
1055
  "GET",
1104
1056
  "PUT"
1105
1057
  ],
1106
- default: "POST"
1058
+ default: "POST",
1059
+ description: "HTTP method"
1107
1060
  },
1108
1061
  auth: {
1109
1062
  type: "object",
@@ -1149,7 +1102,7 @@ var agent_record_schema_default = {
1149
1102
  },
1150
1103
  tool: {
1151
1104
  type: "string",
1152
- description: "Name of the MCP tool to invoke. If not specified, defaults to agentId without 'agent:' prefix."
1105
+ description: "Name of the MCP tool to invoke. If omitted, the agent has access to all tools on the server."
1153
1106
  },
1154
1107
  auth: {
1155
1108
  type: "object",
@@ -1198,6 +1151,60 @@ var agent_record_schema_default = {
1198
1151
  }
1199
1152
  }
1200
1153
  ]
1154
+ },
1155
+ triggers: {
1156
+ type: "array",
1157
+ default: [],
1158
+ description: "Optional list of triggers that activate the agent.\nAdditional fields are allowed and depend on trigger type:\n- manual: 'command' (example CLI command)\n- webhook: 'event' (event identifier), 'filter' (condition)\n- scheduled: 'cron' (cron expression)\n",
1159
+ items: {
1160
+ type: "object",
1161
+ properties: {
1162
+ type: {
1163
+ type: "string",
1164
+ enum: [
1165
+ "manual",
1166
+ "webhook",
1167
+ "scheduled"
1168
+ ],
1169
+ description: "Type of trigger that activates the agent"
1170
+ }
1171
+ },
1172
+ required: [
1173
+ "type"
1174
+ ],
1175
+ additionalProperties: true
1176
+ }
1177
+ },
1178
+ knowledge_dependencies: {
1179
+ type: "array",
1180
+ default: [],
1181
+ items: {
1182
+ type: "string",
1183
+ description: "Glob patterns for files this agent needs access to"
1184
+ }
1185
+ },
1186
+ prompt_engine_requirements: {
1187
+ type: "object",
1188
+ description: "Requirements for prompt composition \u2014 roles and skills the agent needs.",
1189
+ properties: {
1190
+ roles: {
1191
+ type: "array",
1192
+ items: {
1193
+ type: "string"
1194
+ }
1195
+ },
1196
+ skills: {
1197
+ type: "array",
1198
+ items: {
1199
+ type: "string"
1200
+ }
1201
+ }
1202
+ }
1203
+ },
1204
+ metadata: {
1205
+ type: "object",
1206
+ description: "Optional framework-specific or deployment-specific metadata.\nCommon use cases: framework identification (langchain, google-adk), deployment info,\ncost tracking, tool capabilities, maintainer info.\nThis field does NOT affect agent execution \u2014 it is purely informational.\n",
1207
+ additionalProperties: true
1201
1208
  }
1202
1209
  },
1203
1210
  examples: [
@@ -1428,193 +1435,12 @@ var agent_record_schema_default = {
1428
1435
  ]
1429
1436
  };
1430
1437
 
1431
- // src/record_schemas/generated/changelog_record_schema.json
1432
- var changelog_record_schema_default = {
1433
- $schema: "http://json-schema.org/draft-07/schema#",
1434
- $id: "changelog_record_schema.json",
1435
- title: "ChangelogRecord",
1436
- description: "Canonical schema for changelog records - aggregates N tasks into 1 release note",
1437
- additionalProperties: false,
1438
- type: "object",
1439
- required: [
1440
- "id",
1441
- "title",
1442
- "description",
1443
- "relatedTasks",
1444
- "completedAt"
1445
- ],
1446
- properties: {
1447
- id: {
1448
- type: "string",
1449
- pattern: "^\\d{10}-changelog-[a-z0-9-]{1,50}$",
1450
- maxLength: 71,
1451
- description: "Unique identifier for the changelog entry",
1452
- examples: [
1453
- "1752707800-changelog-sistema-autenticacion-v1",
1454
- "1752707800-changelog-sprint-24-api-performance"
1455
- ]
1456
- },
1457
- title: {
1458
- type: "string",
1459
- minLength: 10,
1460
- maxLength: 150,
1461
- description: "Executive title of the deliverable",
1462
- examples: [
1463
- "Sistema de Autenticaci\xF3n Completo v1.0",
1464
- "Sprint 24 - Performance Optimizations"
1465
- ]
1466
- },
1467
- description: {
1468
- type: "string",
1469
- minLength: 20,
1470
- maxLength: 5e3,
1471
- description: "Detailed description of the value delivered, including key decisions and impact"
1472
- },
1473
- relatedTasks: {
1474
- type: "array",
1475
- items: {
1476
- type: "string",
1477
- pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$"
1478
- },
1479
- minItems: 1,
1480
- description: "IDs of tasks that compose this deliverable (minimum 1 required)"
1481
- },
1482
- completedAt: {
1483
- type: "number",
1484
- minimum: 0,
1485
- description: "Unix timestamp in seconds when the deliverable was completed"
1486
- },
1487
- relatedCycles: {
1488
- type: "array",
1489
- items: {
1490
- type: "string",
1491
- pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$"
1492
- },
1493
- default: [],
1494
- description: "Optional IDs of cycles related to this deliverable"
1495
- },
1496
- relatedExecutions: {
1497
- type: "array",
1498
- items: {
1499
- type: "string",
1500
- pattern: "^\\d{10}-exec-[a-z0-9-]{1,50}$"
1501
- },
1502
- default: [],
1503
- description: "Optional IDs of key execution records related to this work"
1504
- },
1505
- version: {
1506
- type: "string",
1507
- minLength: 1,
1508
- maxLength: 50,
1509
- description: "Optional version or release identifier (e.g., 'v1.0.0', 'sprint-24')",
1510
- examples: [
1511
- "v1.0.0",
1512
- "v2.1.3",
1513
- "sprint-24"
1514
- ]
1515
- },
1516
- tags: {
1517
- type: "array",
1518
- items: {
1519
- type: "string",
1520
- pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$"
1521
- },
1522
- default: [],
1523
- description: "Optional tags for categorization (e.g., 'feature:auth', 'bugfix', 'security')"
1524
- },
1525
- commits: {
1526
- type: "array",
1527
- items: {
1528
- type: "string",
1529
- maxLength: 100
1530
- },
1531
- default: [],
1532
- description: "Optional list of git commit hashes related to this deliverable"
1533
- },
1534
- files: {
1535
- type: "array",
1536
- items: {
1537
- type: "string",
1538
- maxLength: 500
1539
- },
1540
- default: [],
1541
- description: "Optional list of main files that were created or modified"
1542
- },
1543
- notes: {
1544
- type: "string",
1545
- maxLength: 3e3,
1546
- description: "Optional additional context, decisions, or learnings"
1547
- }
1548
- },
1549
- examples: [
1550
- {
1551
- id: "1752707800-changelog-sistema-autenticacion-v1",
1552
- title: "Sistema de Autenticaci\xF3n Completo v1.0",
1553
- 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.",
1554
- relatedTasks: [
1555
- "1752274500-task-crear-ui-login",
1556
- "1752274600-task-integrar-oauth2-backend",
1557
- "1752274700-task-implementar-2fa-totp",
1558
- "1752274800-task-tests-e2e-auth",
1559
- "1752274900-task-documentar-flujo-auth"
1560
- ],
1561
- completedAt: 1752707800,
1562
- relatedCycles: [
1563
- "1752200000-cycle-q1-auth-milestone"
1564
- ],
1565
- relatedExecutions: [
1566
- "1752274550-exec-analisis-auth-providers",
1567
- "1752707750-exec-final-integration-test"
1568
- ],
1569
- version: "v1.0.0",
1570
- tags: [
1571
- "feature:auth",
1572
- "security",
1573
- "frontend",
1574
- "backend"
1575
- ],
1576
- commits: [
1577
- "abc123def",
1578
- "456ghi789",
1579
- "jkl012mno"
1580
- ],
1581
- files: [
1582
- "src/pages/Login.tsx",
1583
- "src/services/auth.ts",
1584
- "src/components/TwoFactorSetup.tsx",
1585
- "e2e/auth.spec.ts"
1586
- ],
1587
- 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."
1588
- },
1589
- {
1590
- id: "1752707900-changelog-hotfix-payment-timeout",
1591
- title: "Hotfix: Critical Payment Timeout Fix",
1592
- 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.",
1593
- relatedTasks: [
1594
- "1752707850-task-fix-payment-timeout",
1595
- "1752707870-task-add-circuit-breaker"
1596
- ],
1597
- completedAt: 1752707900,
1598
- version: "v1.2.1",
1599
- tags: [
1600
- "hotfix",
1601
- "critical",
1602
- "payment"
1603
- ],
1604
- commits: [
1605
- "xyz789abc"
1606
- ],
1607
- notes: "Emergency response to production incident. Deployed to production within 2 hours."
1608
- }
1609
- ]
1610
- };
1611
-
1612
1438
  // src/record_schemas/generated/cycle_record_schema.json
1613
1439
  var cycle_record_schema_default = {
1614
1440
  $schema: "http://json-schema.org/draft-07/schema#",
1615
1441
  $id: "cycle_record_schema.json",
1616
1442
  title: "CycleRecord",
1617
- description: "Canonical schema for cycle records - strategic grouping of work",
1443
+ description: "Canonical schema for cycle records \u2014 strategic grouping of work into sprints, milestones, or roadmaps.",
1618
1444
  additionalProperties: false,
1619
1445
  type: "object",
1620
1446
  required: [
@@ -1627,7 +1453,7 @@ var cycle_record_schema_default = {
1627
1453
  type: "string",
1628
1454
  pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$",
1629
1455
  maxLength: 67,
1630
- description: "Unique identifier for the cycle (10 timestamp + 1 dash + 5 'cycle' + 1 dash + max 50 slug = 67 max)",
1456
+ description: "Unique identifier for the cycle (10 timestamp + 1 dash + 5 'cycle' + 1 dash + max 50 slug = 67 max).",
1631
1457
  examples: [
1632
1458
  "1754400000-cycle-sprint-24-api-performance",
1633
1459
  "1754500000-cycle-auth-system-v2",
@@ -1638,7 +1464,7 @@ var cycle_record_schema_default = {
1638
1464
  type: "string",
1639
1465
  minLength: 1,
1640
1466
  maxLength: 256,
1641
- description: "Human-readable title for the cycle (e.g., 'Sprint 24', 'Auth v2.0', 'Q4 2025')",
1467
+ description: "Human-readable title for the cycle (e.g., 'Sprint 24', 'Auth v2.0', 'Q4 2025').",
1642
1468
  examples: [
1643
1469
  "Sprint 24 - API Performance",
1644
1470
  "Authentication System v2.0",
@@ -1653,7 +1479,7 @@ var cycle_record_schema_default = {
1653
1479
  "completed",
1654
1480
  "archived"
1655
1481
  ],
1656
- description: "The lifecycle status of the cycle"
1482
+ description: "The lifecycle status of the cycle."
1657
1483
  },
1658
1484
  taskIds: {
1659
1485
  type: "array",
@@ -1663,7 +1489,7 @@ var cycle_record_schema_default = {
1663
1489
  maxLength: 66
1664
1490
  },
1665
1491
  default: [],
1666
- 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)",
1492
+ description: "Optional array of Task IDs that belong to this cycle. Bidirectional with TaskRecord.cycleIds. Can be empty for container cycles.",
1667
1493
  examples: [
1668
1494
  [
1669
1495
  "1752274500-task-optimizar-endpoint-search",
@@ -1679,7 +1505,7 @@ var cycle_record_schema_default = {
1679
1505
  maxLength: 67
1680
1506
  },
1681
1507
  default: [],
1682
- 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)",
1508
+ description: "Optional array of child Cycle IDs for hierarchical composition (e.g., Q1 containing Sprint 1, Sprint 2, Sprint 3).",
1683
1509
  examples: [
1684
1510
  [
1685
1511
  "1754400000-cycle-sprint-24",
@@ -1713,9 +1539,29 @@ var cycle_record_schema_default = {
1713
1539
  },
1714
1540
  notes: {
1715
1541
  type: "string",
1716
- minLength: 0,
1717
- maxLength: 1e4,
1718
- description: "Optional description of the cycle's goals, objectives, and context"
1542
+ minLength: 1,
1543
+ description: "Optional description of the cycle's goals, objectives, and context."
1544
+ },
1545
+ metadata: {
1546
+ type: "object",
1547
+ additionalProperties: true,
1548
+ description: "Optional structured data for machine consumption.\nUse this field for domain-specific data that needs to be programmatically processed.\nExtends the strategic grouping with structured, queryable attributes.\nCommon use cases: epic lifecycle, sprint configuration, OKR tracking, budget allocation.\n",
1549
+ examples: [
1550
+ {
1551
+ epic: true,
1552
+ phase: "active",
1553
+ files: {
1554
+ overview: "overview.md",
1555
+ roadmap: "roadmap.md",
1556
+ plan: "implementation_plan.md"
1557
+ }
1558
+ },
1559
+ {
1560
+ sprint: 24,
1561
+ velocity: 42,
1562
+ team: "backend"
1563
+ }
1564
+ ]
1719
1565
  }
1720
1566
  },
1721
1567
  examples: [
@@ -1733,7 +1579,7 @@ var cycle_record_schema_default = {
1733
1579
  "team:backend",
1734
1580
  "focus:performance"
1735
1581
  ],
1736
- notes: "Objetivo: Reducir la latencia p95 de la API por debajo de 200ms y preparar infraestructura para Black Friday."
1582
+ notes: "Objective: Reduce API p95 latency below 200ms and prepare infrastructure for Black Friday."
1737
1583
  },
1738
1584
  {
1739
1585
  id: "1754500000-cycle-auth-system-v2",
@@ -1750,7 +1596,7 @@ var cycle_record_schema_default = {
1750
1596
  "security",
1751
1597
  "feature:auth"
1752
1598
  ],
1753
- notes: "Milestone mayor: Sistema completo de autenticaci\xF3n con OAuth2, 2FA, y gesti\xF3n avanzada de sesiones. Cr\xEDtico para lanzamiento Q4."
1599
+ notes: "Major milestone: Complete authentication system with OAuth2, 2FA, and advanced session management. Critical for Q4 launch."
1754
1600
  },
1755
1601
  {
1756
1602
  id: "1754600000-cycle-q4-2025-growth",
@@ -1766,7 +1612,7 @@ var cycle_record_schema_default = {
1766
1612
  "strategy:growth",
1767
1613
  "okr:scale-to-1m-users"
1768
1614
  ],
1769
- notes: "Objetivo trimestral: Escalar a 1M usuarios activos. Incluye mejoras de performance, nuevo sistema de auth, y lanzamiento de app m\xF3vil."
1615
+ notes: "Quarterly objective: Scale to 1M active users. Includes performance improvements, new auth system, and mobile app launch."
1770
1616
  }
1771
1617
  ]
1772
1618
  };
@@ -1784,10 +1630,8 @@ var embedded_metadata_schema_default = {
1784
1630
  properties: {
1785
1631
  version: {
1786
1632
  type: "string",
1787
- enum: [
1788
- "1.0"
1789
- ],
1790
- description: "Version of the embedded metadata format."
1633
+ pattern: "^\\d+\\.\\d+$",
1634
+ description: 'Protocol version in MAJOR.MINOR format (e.g. "1.1", "2.0").'
1791
1635
  },
1792
1636
  type: {
1793
1637
  type: "string",
@@ -1796,12 +1640,23 @@ var embedded_metadata_schema_default = {
1796
1640
  "agent",
1797
1641
  "task",
1798
1642
  "execution",
1799
- "changelog",
1800
1643
  "feedback",
1801
- "cycle"
1644
+ "cycle",
1645
+ "workflow",
1646
+ "custom"
1802
1647
  ],
1803
1648
  description: "The type of the record contained in the payload."
1804
1649
  },
1650
+ schemaUrl: {
1651
+ type: "string",
1652
+ format: "uri",
1653
+ description: "URL to a custom schema for the payload. Required when type is 'custom'."
1654
+ },
1655
+ schemaChecksum: {
1656
+ type: "string",
1657
+ pattern: "^[a-fA-F0-9]{64}$",
1658
+ description: "SHA-256 checksum of the custom schema. Required when type is 'custom'."
1659
+ },
1805
1660
  payloadChecksum: {
1806
1661
  type: "string",
1807
1662
  pattern: "^[a-fA-F0-9]{64}$",
@@ -1816,14 +1671,14 @@ var embedded_metadata_schema_default = {
1816
1671
  keyId: {
1817
1672
  type: "string",
1818
1673
  pattern: "^(human|agent)(:[a-z0-9-]+)+$",
1819
- description: "The Actor ID of the signer (must match ActorRecord.id pattern)."
1674
+ description: "The Actor ID of the signer. Supports scoped identifiers (e.g. agent:camilo:cursor)."
1820
1675
  },
1821
1676
  role: {
1822
1677
  type: "string",
1823
1678
  pattern: "^([a-z-]+|custom:[a-z0-9-]+)$",
1824
1679
  minLength: 1,
1825
1680
  maxLength: 50,
1826
- description: "The context role of the signature (e.g., 'author', 'reviewer', 'auditor', or 'custom:*')."
1681
+ description: "The context role of the signature (e.g., 'author', 'reviewer', or 'custom:*')."
1827
1682
  },
1828
1683
  notes: {
1829
1684
  type: "string",
@@ -1838,6 +1693,7 @@ var embedded_metadata_schema_default = {
1838
1693
  },
1839
1694
  timestamp: {
1840
1695
  type: "integer",
1696
+ minimum: 16e8,
1841
1697
  description: "Unix timestamp of the signature."
1842
1698
  }
1843
1699
  },
@@ -1871,7 +1727,7 @@ var embedded_metadata_schema_default = {
1871
1727
  "payload"
1872
1728
  ],
1873
1729
  additionalProperties: false,
1874
- oneOf: [
1730
+ allOf: [
1875
1731
  {
1876
1732
  if: {
1877
1733
  properties: {
@@ -1879,7 +1735,7 @@ var embedded_metadata_schema_default = {
1879
1735
  type: "object",
1880
1736
  properties: {
1881
1737
  type: {
1882
- const: "actor"
1738
+ const: "custom"
1883
1739
  }
1884
1740
  }
1885
1741
  }
@@ -1887,12 +1743,15 @@ var embedded_metadata_schema_default = {
1887
1743
  },
1888
1744
  then: {
1889
1745
  properties: {
1890
- payload: {
1891
- $ref: "ref:actor_record_schema"
1746
+ header: {
1747
+ type: "object",
1748
+ required: [
1749
+ "schemaUrl",
1750
+ "schemaChecksum"
1751
+ ]
1892
1752
  }
1893
1753
  }
1894
- },
1895
- else: false
1754
+ }
1896
1755
  },
1897
1756
  {
1898
1757
  if: {
@@ -1901,7 +1760,7 @@ var embedded_metadata_schema_default = {
1901
1760
  type: "object",
1902
1761
  properties: {
1903
1762
  type: {
1904
- const: "agent"
1763
+ const: "actor"
1905
1764
  }
1906
1765
  }
1907
1766
  }
@@ -1910,11 +1769,10 @@ var embedded_metadata_schema_default = {
1910
1769
  then: {
1911
1770
  properties: {
1912
1771
  payload: {
1913
- $ref: "ref:agent_record_schema"
1772
+ $ref: "ref:actor_record_schema"
1914
1773
  }
1915
1774
  }
1916
- },
1917
- else: false
1775
+ }
1918
1776
  },
1919
1777
  {
1920
1778
  if: {
@@ -1923,7 +1781,7 @@ var embedded_metadata_schema_default = {
1923
1781
  type: "object",
1924
1782
  properties: {
1925
1783
  type: {
1926
- const: "task"
1784
+ const: "agent"
1927
1785
  }
1928
1786
  }
1929
1787
  }
@@ -1932,11 +1790,10 @@ var embedded_metadata_schema_default = {
1932
1790
  then: {
1933
1791
  properties: {
1934
1792
  payload: {
1935
- $ref: "ref:task_record_schema"
1793
+ $ref: "ref:agent_record_schema"
1936
1794
  }
1937
1795
  }
1938
- },
1939
- else: false
1796
+ }
1940
1797
  },
1941
1798
  {
1942
1799
  if: {
@@ -1945,7 +1802,7 @@ var embedded_metadata_schema_default = {
1945
1802
  type: "object",
1946
1803
  properties: {
1947
1804
  type: {
1948
- const: "execution"
1805
+ const: "task"
1949
1806
  }
1950
1807
  }
1951
1808
  }
@@ -1954,11 +1811,10 @@ var embedded_metadata_schema_default = {
1954
1811
  then: {
1955
1812
  properties: {
1956
1813
  payload: {
1957
- $ref: "ref:execution_record_schema"
1814
+ $ref: "ref:task_record_schema"
1958
1815
  }
1959
1816
  }
1960
- },
1961
- else: false
1817
+ }
1962
1818
  },
1963
1819
  {
1964
1820
  if: {
@@ -1967,7 +1823,7 @@ var embedded_metadata_schema_default = {
1967
1823
  type: "object",
1968
1824
  properties: {
1969
1825
  type: {
1970
- const: "changelog"
1826
+ const: "execution"
1971
1827
  }
1972
1828
  }
1973
1829
  }
@@ -1976,11 +1832,10 @@ var embedded_metadata_schema_default = {
1976
1832
  then: {
1977
1833
  properties: {
1978
1834
  payload: {
1979
- $ref: "ref:changelog_record_schema"
1835
+ $ref: "ref:execution_record_schema"
1980
1836
  }
1981
1837
  }
1982
- },
1983
- else: false
1838
+ }
1984
1839
  },
1985
1840
  {
1986
1841
  if: {
@@ -2001,8 +1856,7 @@ var embedded_metadata_schema_default = {
2001
1856
  $ref: "ref:feedback_record_schema"
2002
1857
  }
2003
1858
  }
2004
- },
2005
- else: false
1859
+ }
2006
1860
  },
2007
1861
  {
2008
1862
  if: {
@@ -2023,55 +1877,76 @@ var embedded_metadata_schema_default = {
2023
1877
  $ref: "ref:cycle_record_schema"
2024
1878
  }
2025
1879
  }
1880
+ }
1881
+ },
1882
+ {
1883
+ if: {
1884
+ properties: {
1885
+ header: {
1886
+ type: "object",
1887
+ properties: {
1888
+ type: {
1889
+ const: "workflow"
1890
+ }
1891
+ }
1892
+ }
1893
+ }
2026
1894
  },
2027
- else: false
1895
+ then: {
1896
+ properties: {
1897
+ payload: {
1898
+ $ref: "ref:workflow_record_schema"
1899
+ }
1900
+ }
1901
+ }
2028
1902
  }
2029
1903
  ],
2030
1904
  examples: [
2031
1905
  {
2032
1906
  header: {
2033
- version: "1.0",
2034
- type: "task",
2035
- payloadChecksum: "a1b2c3d4e5f6...",
1907
+ version: "1.1",
1908
+ type: "actor",
1909
+ payloadChecksum: "063d4ba3505e4d2d3852f6063cbd0b98a8728b2afb4a26a323c5c5c512137398",
2036
1910
  signatures: [
2037
1911
  {
2038
1912
  keyId: "human:lead-dev",
2039
1913
  role: "author",
2040
- notes: "Initial task creation for OAuth 2.0 implementation",
2041
- signature: "...",
1914
+ notes: "Self-registration of lead developer account",
1915
+ signature: "yEtlWOGAek8ukP8fycqYZOyogQBudO5XUf4v4BUGaOTogDH4wraanhLvutaJBM7rdilFUS2VvmxZmIy0KjTZAg==",
2042
1916
  timestamp: 1752274500
2043
1917
  }
2044
1918
  ]
2045
1919
  },
2046
1920
  payload: {
2047
- id: "1752274500-task-implementar-auth",
2048
- status: "pending",
2049
- priority: "high",
2050
- description: "Implementar autenticaci\xF3n OAuth 2.0.",
2051
- tags: [
2052
- "skill:go",
2053
- "area:backend"
2054
- ]
1921
+ id: "human:lead-dev",
1922
+ type: "human",
1923
+ displayName: "Lead Developer",
1924
+ publicKey: "0yyrCETtVql51Id+nRKGmpbfsxNxOz+eCYLpWDoutV0=",
1925
+ roles: [
1926
+ "developer",
1927
+ "reviewer"
1928
+ ],
1929
+ status: "active"
2055
1930
  }
2056
1931
  },
2057
1932
  {
2058
1933
  header: {
2059
- version: "1.0",
1934
+ version: "1.1",
2060
1935
  type: "execution",
2061
- payloadChecksum: "b2c3d4e5f6a1...",
1936
+ payloadChecksum: "bd667ddc8698a50594592ac15d0761e62a9f05cc3ba10a9a853ef0819e5fb2ad",
2062
1937
  signatures: [
2063
1938
  {
2064
- keyId: "agent:cursor",
1939
+ keyId: "agent:camilo:cursor",
2065
1940
  role: "author",
2066
1941
  notes: "OAuth 2.0 flow completed with GitHub provider integration",
2067
- signature: "...",
1942
+ signature: "8d9LWTtMlK/Ct4+QWGFpH4iFdZb9T/hlFThAAGKqz8UOPe9qDwPFcv3b4qz9G+NQXh1/PgB1pl8YiQCe6fnjAQ==",
2068
1943
  timestamp: 1752274600
2069
1944
  },
2070
1945
  {
2071
1946
  keyId: "human:camilo",
2072
1947
  role: "reviewer",
2073
1948
  notes: "Reviewed and tested locally. LGTM.",
2074
- signature: "...",
1949
+ signature: "17xsA75W0zzNZI3DXa8iHmxS5NwedfCwu9DoXwk/vArk9yaHcFsY6EgJHNPUtIX+XeKSVF/lOg6CvVIkcXjjAA==",
2075
1950
  timestamp: 1752274650
2076
1951
  }
2077
1952
  ]
@@ -2081,39 +1956,30 @@ var embedded_metadata_schema_default = {
2081
1956
  taskId: "1752274500-task-implement-oauth",
2082
1957
  type: "progress",
2083
1958
  title: "OAuth 2.0 flow implemented",
2084
- result: "Completed the OAuth 2.0 authentication flow..."
1959
+ result: "Completed the OAuth 2.0 authentication flow with GitHub provider. Token refresh and session management included."
2085
1960
  }
2086
1961
  },
2087
1962
  {
2088
1963
  header: {
2089
- version: "1.0",
2090
- type: "actor",
2091
- payloadChecksum: "c3d4e5f6a1b2...",
1964
+ version: "1.1",
1965
+ type: "custom",
1966
+ schemaUrl: "https://example.com/schemas/deployment-record-v1.json",
1967
+ schemaChecksum: "d4e5f6a1b2c3789012345678901234567890123456789012345678901234abcd",
1968
+ payloadChecksum: "1f9598081fcfcf34732de647de25c8445e68e9320e0c10d3a4bd911c7274a1b3",
2092
1969
  signatures: [
2093
1970
  {
2094
- keyId: "human:admin",
1971
+ keyId: "agent:deploy-bot",
2095
1972
  role: "author",
2096
- notes: "New developer onboarded to team",
2097
- signature: "...",
1973
+ notes: "Production deployment of v2.1.0",
1974
+ signature: "W3cSLJnEp+OmKVOwFqjuLTL1S55/OlQyFDzmmxg+vUfETIiQWNr7aDH06/rHUM11g2BLEGRfXZPQPFry6FJeAw==",
2098
1975
  timestamp: 1752274700
2099
- },
2100
- {
2101
- keyId: "agent:aion",
2102
- role: "auditor",
2103
- notes: "Actor verification: 10/10. Credentials validated.",
2104
- signature: "...",
2105
- timestamp: 1752274705
2106
1976
  }
2107
1977
  ]
2108
1978
  },
2109
1979
  payload: {
2110
- id: "human:new-developer",
2111
- type: "human",
2112
- displayName: "New Developer",
2113
- publicKey: "...",
2114
- roles: [
2115
- "developer"
2116
- ]
1980
+ deploymentId: "deploy-2025-07-12-v2.1.0",
1981
+ environment: "production",
1982
+ status: "success"
2117
1983
  }
2118
1984
  }
2119
1985
  ]
@@ -2124,7 +1990,7 @@ var execution_record_schema_default = {
2124
1990
  $schema: "http://json-schema.org/draft-07/schema#",
2125
1991
  $id: "execution_record_schema.json",
2126
1992
  title: "ExecutionRecord",
2127
- description: "Canonical schema for execution log records - the universal event stream",
1993
+ description: "Canonical schema for execution log records - the universal event stream.",
2128
1994
  additionalProperties: false,
2129
1995
  type: "object",
2130
1996
  required: [
@@ -2139,209 +2005,50 @@ var execution_record_schema_default = {
2139
2005
  type: "string",
2140
2006
  pattern: "^\\d{10}-exec-[a-z0-9-]{1,50}$",
2141
2007
  maxLength: 66,
2142
- description: "Unique identifier for the execution log entry (10 timestamp + 1 dash + 4 'exec' + 1 dash + max 50 slug = 66 max)",
2143
- examples: [
2144
- "1752275000-exec-refactor-queries",
2145
- "1752361200-exec-api-externa-caida"
2146
- ]
2008
+ description: "Unique identifier for the execution log entry."
2147
2009
  },
2148
2010
  taskId: {
2149
2011
  type: "string",
2150
2012
  pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$",
2151
2013
  maxLength: 66,
2152
- description: "ID of the parent task this execution belongs to (10 timestamp + 1 dash + 4 'task' + 1 dash + max 50 slug = 66 max)"
2014
+ description: "ID of the parent task this execution belongs to."
2153
2015
  },
2154
2016
  type: {
2155
2017
  type: "string",
2156
- enum: [
2157
- "analysis",
2158
- "progress",
2159
- "blocker",
2160
- "completion",
2161
- "info",
2162
- "correction"
2163
- ],
2164
- description: "Semantic classification of the execution event",
2165
- examples: [
2166
- "progress",
2167
- "analysis",
2168
- "blocker",
2169
- "completion"
2170
- ]
2018
+ pattern: "^(analysis|decision|progress|blocker|completion|correction|info|custom:[a-z0-9-]+(:[a-z0-9-]+)*)$",
2019
+ description: "Classifies what happened in this execution event. Primitive types cover the fundamental kinds of events that occur during any collaborative work. Extend with 'custom:' for your domain.\nPrimitive types:\n - analysis: Investigation, research, or evaluation before acting.\n - decision: A choice that changes the direction of work.\n - progress: Incremental advancement of work.\n - blocker: An impediment preventing further progress.\n - completion: Work on the task is finished.\n - correction: A fix to something previously done incorrectly.\n - info: Informational note or status update.\n\nCustom types use the 'custom:' prefix for industry-specific extensions. Software development examples:\n - custom:review (code review, design review, QA)\n - custom:deployment (deploy to staging/production)\n - custom:rollback (revert a deployment or change)\n - custom:release (version release, PR merge to main)\n - custom:hotfix (emergency fix in production)\nImplementations that encounter an unrecognized custom type MUST treat it as 'info' for display purposes.\n"
2171
2020
  },
2172
2021
  title: {
2173
2022
  type: "string",
2174
2023
  minLength: 1,
2175
2024
  maxLength: 256,
2176
- description: "Human-readable title for the execution (used to generate ID)",
2177
- examples: [
2178
- "Refactor de queries N+1",
2179
- "API Externa Ca\xEDda",
2180
- "Plan de implementaci\xF3n OAuth2"
2181
- ]
2025
+ description: "Human-readable title for the execution (used to generate ID slug)."
2182
2026
  },
2183
2027
  result: {
2184
2028
  type: "string",
2185
2029
  minLength: 10,
2186
- maxLength: 22e3,
2187
- description: 'The tangible, verifiable output or result of the execution. \nThis is the "WHAT" - evidence of work or event summary.\n'
2030
+ description: 'The tangible, verifiable output or result of the execution. This is the "WHAT" - evidence of work or event summary.\n'
2188
2031
  },
2189
2032
  notes: {
2190
2033
  type: "string",
2191
- maxLength: 6500,
2192
- description: 'Optional narrative, context and decisions behind the execution.\nThis is the "HOW" and "WHY" - the story behind the result.\n'
2034
+ description: 'Optional narrative, context and decisions behind the execution. This is the "HOW" and "WHY" - the story behind the result.\n'
2193
2035
  },
2194
2036
  references: {
2195
2037
  type: "array",
2196
2038
  items: {
2197
2039
  type: "string",
2040
+ minLength: 1,
2198
2041
  maxLength: 500
2199
2042
  },
2200
2043
  default: [],
2201
- 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"
2044
+ description: "Optional list of typed references to relevant commits, files, PRs, or external documents. Standard prefixes: commit:, pr:, issue:, file:, url:, task:, exec:.\n"
2202
2045
  },
2203
2046
  metadata: {
2204
2047
  type: "object",
2205
2048
  additionalProperties: true,
2206
- description: "Optional structured data for machine consumption.\nUse this field for data that needs to be programmatically processed (e.g., audit findings,\nperformance metrics, scan results). This complements result (human-readable WHAT) and\nnotes (narrative HOW/WHY) by providing structured, queryable data.\nCommon use cases: audit findings arrays, performance metrics, tool outputs, scan summaries.\n",
2207
- examples: [
2208
- {
2209
- findings: [
2210
- {
2211
- type: "PII",
2212
- file: "src/user.ts",
2213
- line: 42
2214
- }
2215
- ],
2216
- scannedFiles: 245
2217
- },
2218
- {
2219
- metrics: {
2220
- duration_ms: 1250,
2221
- memory_mb: 512
2222
- }
2223
- }
2224
- ]
2225
- }
2226
- },
2227
- examples: [
2228
- {
2229
- id: "1752275500-exec-refactor-queries",
2230
- taskId: "1752274500-task-optimizar-api",
2231
- type: "progress",
2232
- title: "Refactor de queries N+1",
2233
- result: "Refactorizados 3 queries N+1 a un solo JOIN optimizado. Performance mejor\xF3 de 2.5s a 200ms.",
2234
- notes: "Identificados 3 N+1 queries en el endpoint /api/search. Aplicado eager loading y caching de relaciones.",
2235
- references: [
2236
- "commit:b2c3d4e",
2237
- "file:src/api/search.ts"
2238
- ]
2239
- },
2240
- {
2241
- id: "1752361200-exec-api-externa-caida",
2242
- taskId: "1752274500-task-optimizar-api",
2243
- type: "blocker",
2244
- title: "API Externa Ca\xEDda",
2245
- result: "No se puede continuar con testing de integraci\xF3n. API de pagos devuelve 503.",
2246
- 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.",
2247
- references: [
2248
- "url:https://status.payments.com"
2249
- ]
2250
- },
2251
- {
2252
- id: "1752188000-exec-plan-oauth-implementation",
2253
- taskId: "1752274500-task-oauth-implementation",
2254
- type: "analysis",
2255
- title: "Plan de implementaci\xF3n OAuth2",
2256
- result: "Documento de dise\xF1o t\xE9cnico completado. 5 sub-tareas identificadas con estimaciones de complejidad.",
2257
- notes: "Evaluadas 3 opciones: NextAuth.js (elegida), Passport.js, custom implementation. NextAuth.js por madurez y soporte de m\xFAltiples providers.",
2258
- references: [
2259
- "file:docs/oauth-design.md"
2260
- ]
2261
- },
2262
- {
2263
- id: "1752707800-exec-oauth-completed",
2264
- taskId: "1752274500-task-oauth-implementation",
2265
- type: "completion",
2266
- title: "OAuth Implementation Completed",
2267
- result: "Sistema OAuth2 completamente implementado, testeado y deployado a staging. 95% test coverage. Todos los acceptance criteria cumplidos.",
2268
- notes: "Implementaci\xF3n finalizada. Code review aprobado. Tests E2E passing. Ready para changelog y deploy a producci\xF3n.",
2269
- references: [
2270
- "pr:456",
2271
- "commit:def789abc",
2272
- "url:https://staging.app.com/login"
2273
- ]
2274
- },
2275
- {
2276
- id: "1752275600-exec-cambio-estrategia-redis",
2277
- taskId: "1752274500-task-oauth-implementation",
2278
- type: "info",
2279
- title: "Cambio de estrategia: Usar Redis para sessions",
2280
- result: "Decisi\xF3n: Migrar de JWT stateless a sessions en Redis por requisito de revocaci\xF3n inmediata.",
2281
- 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.",
2282
- references: [
2283
- "issue:567",
2284
- "url:https://redis.io/docs/manual/keyspace-notifications/"
2285
- ]
2286
- },
2287
- {
2288
- id: "1752275700-exec-correccion-metricas",
2289
- taskId: "1752274500-task-optimizar-api",
2290
- type: "correction",
2291
- title: "Correcci\xF3n: M\xE9tricas de performance",
2292
- result: "Correcci\xF3n de execution 1752275500-exec-refactor-queries: El performance fue 200ms, no 50ms como se report\xF3.",
2293
- 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.",
2294
- references: [
2295
- "exec:1752275500-exec-refactor-queries"
2296
- ]
2297
- },
2298
- {
2299
- id: "1752276000-exec-source-audit-scan",
2300
- taskId: "1752274500-task-audit-compliance",
2301
- type: "analysis",
2302
- title: "Source Audit Scan - 2025-01-15",
2303
- result: "Escaneados 245 archivos. Encontrados 10 findings (3 critical, 4 high, 3 medium). Ver metadata para detalles estructurados.",
2304
- notes: "Scan ejecutado con RegexDetector + HeuristicDetector. LLM calls: 0 (tier free).",
2305
- references: [
2306
- "file:src/config/db.ts",
2307
- "file:src/auth/keys.ts"
2308
- ],
2309
- metadata: {
2310
- scannedFiles: 245,
2311
- scannedLines: 18420,
2312
- duration_ms: 1250,
2313
- findings: [
2314
- {
2315
- id: "SEC-001",
2316
- severity: "critical",
2317
- file: "src/config/db.ts",
2318
- line: 5,
2319
- type: "api_key"
2320
- },
2321
- {
2322
- id: "SEC-003",
2323
- severity: "critical",
2324
- file: "src/auth/keys.ts",
2325
- line: 2,
2326
- type: "private_key"
2327
- },
2328
- {
2329
- id: "PII-003",
2330
- severity: "critical",
2331
- file: "src/payments/stripe.ts",
2332
- line: 8,
2333
- type: "credit_card"
2334
- }
2335
- ],
2336
- summary: {
2337
- critical: 3,
2338
- high: 4,
2339
- medium: 3,
2340
- low: 0
2341
- }
2342
- }
2049
+ description: "Optional structured data for machine consumption. Use this field for data that needs to be programmatically processed (e.g., audit findings, performance metrics, scan results). Complements result (WHAT) and notes (HOW/WHY) with structured, queryable data.\n"
2343
2050
  }
2344
- ]
2051
+ }
2345
2052
  };
2346
2053
 
2347
2054
  // src/record_schemas/generated/feedback_record_schema.json
@@ -2349,7 +2056,7 @@ var feedback_record_schema_default = {
2349
2056
  $schema: "http://json-schema.org/draft-07/schema#",
2350
2057
  $id: "feedback_record_schema.json",
2351
2058
  title: "FeedbackRecord",
2352
- description: "Canonical schema for feedback records - structured conversation about work",
2059
+ description: "Canonical schema for feedback records \u2014 the structured conversation about work.",
2353
2060
  additionalProperties: false,
2354
2061
  type: "object",
2355
2062
  required: [
@@ -2365,32 +2072,35 @@ var feedback_record_schema_default = {
2365
2072
  type: "string",
2366
2073
  pattern: "^\\d{10}-feedback-[a-z0-9-]{1,50}$",
2367
2074
  maxLength: 70,
2368
- description: "Unique identifier for the feedback entry",
2075
+ description: "Unique identifier for the feedback entry (10 timestamp + 1 dash + 8 'feedback' + 1 dash + max 50 slug = 70 max)",
2369
2076
  examples: [
2370
2077
  "1752788100-feedback-blocking-rest-api",
2371
- "1752788200-feedback-question-test-coverage"
2078
+ "1752788400-feedback-question-test-coverage"
2372
2079
  ]
2373
2080
  },
2374
2081
  entityType: {
2375
2082
  type: "string",
2376
2083
  enum: [
2084
+ "actor",
2085
+ "agent",
2377
2086
  "task",
2378
2087
  "execution",
2379
- "changelog",
2380
2088
  "feedback",
2381
- "cycle"
2089
+ "cycle",
2090
+ "workflow"
2382
2091
  ],
2383
- description: "The type of entity this feedback refers to"
2092
+ description: "The type of entity this feedback refers to."
2384
2093
  },
2385
2094
  entityId: {
2386
2095
  type: "string",
2387
2096
  minLength: 1,
2388
2097
  maxLength: 256,
2389
- 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",
2098
+ description: "The ID of the entity this feedback refers to.\nMust match the ID pattern for its entityType:\n- actor: ^(human|agent)(:[a-z0-9-]+)+$\n- agent: ^agent(:[a-z0-9-]+)+$\n- task: ^\\d{10}-task-[a-z0-9-]{1,50}$\n- execution: ^\\d{10}-exec-[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- workflow: ^\\d{10}-workflow-[a-z0-9-]{1,50}$\n",
2390
2099
  examples: [
2391
2100
  "1752274500-task-implementar-oauth",
2392
2101
  "1752642000-exec-subtarea-9-4",
2393
- "1752788100-feedback-blocking-rest-api"
2102
+ "1752788100-feedback-blocking-rest-api",
2103
+ "human:camilo"
2394
2104
  ]
2395
2105
  },
2396
2106
  type: {
@@ -2403,12 +2113,7 @@ var feedback_record_schema_default = {
2403
2113
  "clarification",
2404
2114
  "assignment"
2405
2115
  ],
2406
- description: "The semantic intent of the feedback",
2407
- examples: [
2408
- "blocking",
2409
- "question",
2410
- "approval"
2411
- ]
2116
+ description: "The semantic intent of the feedback."
2412
2117
  },
2413
2118
  status: {
2414
2119
  type: "string",
@@ -2418,19 +2123,18 @@ var feedback_record_schema_default = {
2418
2123
  "resolved",
2419
2124
  "wontfix"
2420
2125
  ],
2421
- 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'
2126
+ description: 'The lifecycle status of the feedback.\nFeedbackRecords are immutable. To change status, create a new FeedbackRecord\nthat references this one using entityType: "feedback" and resolvesFeedbackId.\n'
2422
2127
  },
2423
2128
  content: {
2424
2129
  type: "string",
2425
2130
  minLength: 1,
2426
- maxLength: 5e3,
2427
- description: "The content of the feedback. Reduced from 10000 to 5000 chars for practical use."
2131
+ description: "The content of the feedback."
2428
2132
  },
2429
2133
  assignee: {
2430
2134
  type: "string",
2431
2135
  pattern: "^(human|agent)(:[a-z0-9-]+)+$",
2432
2136
  maxLength: 256,
2433
- description: "Optional. The Actor ID responsible for addressing the feedback (e.g., 'human:maria', 'agent:camilo:cursor')",
2137
+ description: "Optional. The Actor ID responsible for addressing the feedback (e.g., 'human:maria', 'agent:camilo:cursor').",
2434
2138
  examples: [
2435
2139
  "human:maria",
2436
2140
  "agent:code-reviewer",
@@ -2441,7 +2145,7 @@ var feedback_record_schema_default = {
2441
2145
  type: "string",
2442
2146
  pattern: "^\\d{10}-feedback-[a-z0-9-]{1,50}$",
2443
2147
  maxLength: 70,
2444
- description: "Optional. The ID of another feedback record that this one resolves or responds to",
2148
+ description: "Optional. The ID of another FeedbackRecord that this one resolves or responds to.",
2445
2149
  examples: [
2446
2150
  "1752788100-feedback-blocking-rest-api"
2447
2151
  ]
@@ -2449,18 +2153,151 @@ var feedback_record_schema_default = {
2449
2153
  metadata: {
2450
2154
  type: "object",
2451
2155
  additionalProperties: true,
2452
- description: "Optional structured data for machine consumption.\nUse this field for domain-specific data that needs to be programmatically processed.\nCommon use cases: waiver details (fingerprint, ruleId, file, line), approval context, assignment metadata.\n",
2453
- examples: [
2454
- {
2455
- fingerprint: "abc123def456",
2456
- ruleId: "PII-001",
2457
- file: "src/user.ts",
2458
- line: 42,
2459
- expiresAt: "2025-12-31T23:59:59Z"
2460
- }
2461
- ]
2156
+ description: "Optional structured data for machine consumption.\nUse this field for domain-specific data that needs to be programmatically processed.\nCommon use cases: waiver details (fingerprint, ruleId, file, line), approval context, assignment metadata.\n"
2462
2157
  }
2463
2158
  },
2159
+ allOf: [
2160
+ {
2161
+ if: {
2162
+ required: [
2163
+ "entityType"
2164
+ ],
2165
+ properties: {
2166
+ entityType: {
2167
+ const: "actor"
2168
+ }
2169
+ }
2170
+ },
2171
+ then: {
2172
+ properties: {
2173
+ entityId: {
2174
+ type: "string",
2175
+ pattern: "^(human|agent)(:[a-z0-9-]+)+$"
2176
+ }
2177
+ }
2178
+ }
2179
+ },
2180
+ {
2181
+ if: {
2182
+ required: [
2183
+ "entityType"
2184
+ ],
2185
+ properties: {
2186
+ entityType: {
2187
+ const: "agent"
2188
+ }
2189
+ }
2190
+ },
2191
+ then: {
2192
+ properties: {
2193
+ entityId: {
2194
+ type: "string",
2195
+ pattern: "^agent(:[a-z0-9-]+)+$"
2196
+ }
2197
+ }
2198
+ }
2199
+ },
2200
+ {
2201
+ if: {
2202
+ required: [
2203
+ "entityType"
2204
+ ],
2205
+ properties: {
2206
+ entityType: {
2207
+ const: "task"
2208
+ }
2209
+ }
2210
+ },
2211
+ then: {
2212
+ properties: {
2213
+ entityId: {
2214
+ type: "string",
2215
+ pattern: "^\\d{10}-task-[a-z0-9-]{1,50}$"
2216
+ }
2217
+ }
2218
+ }
2219
+ },
2220
+ {
2221
+ if: {
2222
+ required: [
2223
+ "entityType"
2224
+ ],
2225
+ properties: {
2226
+ entityType: {
2227
+ const: "execution"
2228
+ }
2229
+ }
2230
+ },
2231
+ then: {
2232
+ properties: {
2233
+ entityId: {
2234
+ type: "string",
2235
+ pattern: "^\\d{10}-exec-[a-z0-9-]{1,50}$"
2236
+ }
2237
+ }
2238
+ }
2239
+ },
2240
+ {
2241
+ if: {
2242
+ required: [
2243
+ "entityType"
2244
+ ],
2245
+ properties: {
2246
+ entityType: {
2247
+ const: "feedback"
2248
+ }
2249
+ }
2250
+ },
2251
+ then: {
2252
+ properties: {
2253
+ entityId: {
2254
+ type: "string",
2255
+ pattern: "^\\d{10}-feedback-[a-z0-9-]{1,50}$"
2256
+ }
2257
+ }
2258
+ }
2259
+ },
2260
+ {
2261
+ if: {
2262
+ required: [
2263
+ "entityType"
2264
+ ],
2265
+ properties: {
2266
+ entityType: {
2267
+ const: "cycle"
2268
+ }
2269
+ }
2270
+ },
2271
+ then: {
2272
+ properties: {
2273
+ entityId: {
2274
+ type: "string",
2275
+ pattern: "^\\d{10}-cycle-[a-z0-9-]{1,50}$"
2276
+ }
2277
+ }
2278
+ }
2279
+ },
2280
+ {
2281
+ if: {
2282
+ required: [
2283
+ "entityType"
2284
+ ],
2285
+ properties: {
2286
+ entityType: {
2287
+ const: "workflow"
2288
+ }
2289
+ }
2290
+ },
2291
+ then: {
2292
+ properties: {
2293
+ entityId: {
2294
+ type: "string",
2295
+ pattern: "^\\d{10}-workflow-[a-z0-9-]{1,50}$"
2296
+ }
2297
+ }
2298
+ }
2299
+ }
2300
+ ],
2464
2301
  examples: [
2465
2302
  {
2466
2303
  id: "1752788100-feedback-blocking-rest-api",
@@ -2468,7 +2305,7 @@ var feedback_record_schema_default = {
2468
2305
  entityId: "1752642000-exec-subtarea-9-4",
2469
2306
  type: "blocking",
2470
2307
  status: "open",
2471
- 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."
2308
+ content: "This implementation does not comply with the REST endpoint standard. Endpoints must follow the /api/v1/{resource}/{id} pattern. Currently uses /get-user?id=X which is not RESTful."
2472
2309
  },
2473
2310
  {
2474
2311
  id: "1752788200-feedback-rest-api-fixed",
@@ -2476,7 +2313,7 @@ var feedback_record_schema_default = {
2476
2313
  entityId: "1752788100-feedback-blocking-rest-api",
2477
2314
  type: "clarification",
2478
2315
  status: "resolved",
2479
- 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.",
2316
+ content: "Fix implemented. All endpoints now follow REST standard: GET /api/v1/users/:id, POST /api/v1/users, etc. Tests updated and passing.",
2480
2317
  resolvesFeedbackId: "1752788100-feedback-blocking-rest-api"
2481
2318
  },
2482
2319
  {
@@ -2485,7 +2322,7 @@ var feedback_record_schema_default = {
2485
2322
  entityId: "1752274500-task-implementar-oauth",
2486
2323
  type: "assignment",
2487
2324
  status: "open",
2488
- content: "Asignando esta tarea a Mar\xEDa por su experiencia con OAuth2. Prioridad alta para el sprint actual.",
2325
+ content: "Assigning this task to Maria for her experience with OAuth2. High priority for the current sprint.",
2489
2326
  assignee: "human:maria"
2490
2327
  },
2491
2328
  {
@@ -2494,7 +2331,7 @@ var feedback_record_schema_default = {
2494
2331
  entityId: "1752274500-task-implementar-oauth",
2495
2332
  type: "question",
2496
2333
  status: "open",
2497
- 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?"
2334
+ content: "What level of test coverage is expected for this feature? The spec does not mention it explicitly. Should we aim for 80% like the rest of the project?"
2498
2335
  }
2499
2336
  ]
2500
2337
  };
@@ -2504,7 +2341,7 @@ var task_record_schema_default = {
2504
2341
  $schema: "http://json-schema.org/draft-07/schema#",
2505
2342
  $id: "task_record_schema.json",
2506
2343
  title: "TaskRecord",
2507
- description: "Canonical schema for task records as defined in task_protocol.md",
2344
+ description: "Canonical schema for task records as defined in 04_task.md",
2508
2345
  additionalProperties: false,
2509
2346
  type: "object",
2510
2347
  required: [
@@ -2554,7 +2391,6 @@ var task_record_schema_default = {
2554
2391
  "paused",
2555
2392
  "discarded"
2556
2393
  ],
2557
- maxLength: 40,
2558
2394
  description: "Current state of the task in the institutional flow"
2559
2395
  },
2560
2396
  priority: {
@@ -2565,13 +2401,11 @@ var task_record_schema_default = {
2565
2401
  "high",
2566
2402
  "critical"
2567
2403
  ],
2568
- maxLength: 40,
2569
2404
  description: "Strategic or tactical priority level"
2570
2405
  },
2571
2406
  description: {
2572
2407
  type: "string",
2573
2408
  minLength: 10,
2574
- maxLength: 22e3,
2575
2409
  description: "Functional, technical or strategic summary of the objective"
2576
2410
  },
2577
2411
  tags: {
@@ -2579,7 +2413,7 @@ var task_record_schema_default = {
2579
2413
  items: {
2580
2414
  type: "string",
2581
2415
  minLength: 1,
2582
- pattern: "^[a-z0-9-]+(:[a-z0-9-:]+)*$"
2416
+ pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$"
2583
2417
  },
2584
2418
  default: [],
2585
2419
  description: "Optional. List of key:value tags for categorization and role suggestion (e.g., 'skill:react', 'role:agent:developer')."
@@ -2596,9 +2430,26 @@ var task_record_schema_default = {
2596
2430
  },
2597
2431
  notes: {
2598
2432
  type: "string",
2599
- minLength: 0,
2600
- maxLength: 3e3,
2433
+ minLength: 1,
2601
2434
  description: "Additional comments, decisions made or added context"
2435
+ },
2436
+ metadata: {
2437
+ type: "object",
2438
+ additionalProperties: true,
2439
+ description: "Optional structured data for machine consumption.\nUse this field for domain-specific data that needs to be programmatically processed.\nComplements tags (classification) and notes (free text) with structured, queryable data.\nCommon use cases: epic metadata, external tool references, agent metrics, compliance tags.\n",
2440
+ examples: [
2441
+ {
2442
+ epic: true,
2443
+ phase: "implementation",
2444
+ files: {
2445
+ overview: "overview.md"
2446
+ }
2447
+ },
2448
+ {
2449
+ jira: "AUTH-42",
2450
+ storyPoints: 5
2451
+ }
2452
+ ]
2602
2453
  }
2603
2454
  },
2604
2455
  examples: [
@@ -2681,25 +2532,22 @@ var task_record_schema_default = {
2681
2532
  // src/record_schemas/generated/workflow_record_schema.json
2682
2533
  var workflow_record_schema_default = {
2683
2534
  $schema: "http://json-schema.org/draft-07/schema#",
2684
- $id: "workflow_schema.json",
2535
+ $id: "workflow_record_schema.json",
2685
2536
  title: "WorkflowRecord",
2686
- description: "Complete schema for workflow methodology configuration files that define state transitions, signatures, and custom rules",
2537
+ description: "Schema for workflow methodology configuration that defines named state transitions, signatures, and custom rules.",
2687
2538
  type: "object",
2688
2539
  required: [
2689
- "version",
2540
+ "id",
2690
2541
  "name",
2691
2542
  "state_transitions"
2692
2543
  ],
2693
2544
  additionalProperties: false,
2694
2545
  properties: {
2695
- $schema: {
2696
- type: "string",
2697
- description: "JSON Schema reference"
2698
- },
2699
- version: {
2546
+ id: {
2700
2547
  type: "string",
2701
- pattern: "^\\d+\\.\\d+\\.\\d+$",
2702
- description: "Semantic version of the methodology configuration"
2548
+ pattern: "^\\d{10}-workflow-[a-z0-9-]{1,50}$",
2549
+ maxLength: 70,
2550
+ description: "Unique identifier for the workflow record (10 timestamp + 1 dash + 8 'workflow' + 1 dash + max 50 slug = 70 max)"
2703
2551
  },
2704
2552
  name: {
2705
2553
  type: "string",
@@ -2714,11 +2562,15 @@ var workflow_record_schema_default = {
2714
2562
  },
2715
2563
  state_transitions: {
2716
2564
  type: "object",
2717
- description: "Defines valid state transitions and their requirements",
2565
+ description: "Map of named transitions to their rules. Keys are transition names (e.g., submit, approve, activate, resume), not target states.",
2566
+ propertyNames: {
2567
+ pattern: "^[a-z][a-z0-9_]{0,49}$"
2568
+ },
2718
2569
  additionalProperties: {
2719
2570
  type: "object",
2720
2571
  required: [
2721
2572
  "from",
2573
+ "to",
2722
2574
  "requires"
2723
2575
  ],
2724
2576
  additionalProperties: false,
@@ -2732,21 +2584,26 @@ var workflow_record_schema_default = {
2732
2584
  minItems: 1,
2733
2585
  description: "Valid source states for this transition"
2734
2586
  },
2587
+ to: {
2588
+ type: "string",
2589
+ pattern: "^[a-z][a-z0-9_]{0,49}$",
2590
+ description: "Target state for this transition"
2591
+ },
2735
2592
  requires: {
2736
2593
  type: "object",
2737
2594
  additionalProperties: false,
2738
2595
  properties: {
2739
2596
  command: {
2740
2597
  type: "string",
2741
- description: "CLI command that triggers this transition"
2598
+ description: "CLI command that triggers this transition (Command Gate)"
2742
2599
  },
2743
2600
  event: {
2744
2601
  type: "string",
2745
- description: "System event that triggers this transition"
2602
+ description: "System event that triggers this transition (Event Gate)"
2746
2603
  },
2747
2604
  signatures: {
2748
2605
  type: "object",
2749
- description: "Signature requirements keyed by role (e.g., 'approver:quality', 'developer:backend')",
2606
+ description: "Signature group requirements (Signature Gate)",
2750
2607
  additionalProperties: {
2751
2608
  type: "object",
2752
2609
  required: [
@@ -2786,7 +2643,7 @@ var workflow_record_schema_default = {
2786
2643
  items: {
2787
2644
  type: "string"
2788
2645
  },
2789
- description: "Optional: specific actors that can sign"
2646
+ description: "Optional: restrict to specific actor IDs"
2790
2647
  }
2791
2648
  }
2792
2649
  }
@@ -2835,7 +2692,7 @@ var workflow_record_schema_default = {
2835
2692
  },
2836
2693
  expression: {
2837
2694
  type: "string",
2838
- description: "Inline validation expression for 'custom' validation type. Implementation determines the runtime and language. Must return boolean or Promise<boolean>."
2695
+ description: "Inline validation expression for 'custom' type. Must return boolean."
2839
2696
  },
2840
2697
  module_path: {
2841
2698
  type: "string",
@@ -2846,7 +2703,7 @@ var workflow_record_schema_default = {
2846
2703
  },
2847
2704
  agent_integration: {
2848
2705
  type: "object",
2849
- description: "Optional agent automation configuration for methodology",
2706
+ description: "Optional agent automation configuration",
2850
2707
  additionalProperties: false,
2851
2708
  properties: {
2852
2709
  description: {
@@ -2856,7 +2713,7 @@ var workflow_record_schema_default = {
2856
2713
  },
2857
2714
  required_agents: {
2858
2715
  type: "array",
2859
- description: "References to agents required for this methodology. Agent details (engine, knowledge, etc.) live in their AgentRecord.",
2716
+ description: "Agents required for this methodology",
2860
2717
  items: {
2861
2718
  type: "object",
2862
2719
  required: [
@@ -2878,8 +2735,8 @@ var workflow_record_schema_default = {
2878
2735
  properties: {
2879
2736
  id: {
2880
2737
  type: "string",
2881
- pattern: "^agent:[a-z0-9:-]+$",
2882
- description: "Specific agent ID. References an existing AgentRecord."
2738
+ pattern: "^agent(:[a-z0-9-]+)+$",
2739
+ description: "Specific agent ID (references an AgentRecord)"
2883
2740
  },
2884
2741
  required_roles: {
2885
2742
  type: "array",
@@ -2888,11 +2745,11 @@ var workflow_record_schema_default = {
2888
2745
  pattern: "^[a-z0-9-]+(:[a-z0-9-]+)*$"
2889
2746
  },
2890
2747
  minItems: 1,
2891
- description: "Required capability roles. Matches any agent with these roles (from ActorRecord)."
2748
+ description: "Required capability roles (matches any agent with these roles)"
2892
2749
  },
2893
2750
  triggers: {
2894
2751
  type: "array",
2895
- description: "When this agent activates within this methodology",
2752
+ description: "Events that activate this agent",
2896
2753
  items: {
2897
2754
  type: "object",
2898
2755
  required: [
@@ -2921,190 +2778,13 @@ var workflow_record_schema_default = {
2921
2778
  }
2922
2779
  }
2923
2780
  }
2924
- },
2925
- examples: [
2926
- {
2927
- version: "1.0.0",
2928
- name: "Simple Kanban",
2929
- description: "Basic workflow for small teams",
2930
- state_transitions: {
2931
- review: {
2932
- from: [
2933
- "draft"
2934
- ],
2935
- requires: {
2936
- command: "gitgov task submit"
2937
- }
2938
- },
2939
- ready: {
2940
- from: [
2941
- "review"
2942
- ],
2943
- requires: {
2944
- command: "gitgov task approve",
2945
- signatures: {
2946
- __default__: {
2947
- role: "approver",
2948
- capability_roles: [
2949
- "approver:product"
2950
- ],
2951
- min_approvals: 1
2952
- }
2953
- }
2954
- }
2955
- },
2956
- active: {
2957
- from: [
2958
- "ready"
2959
- ],
2960
- requires: {
2961
- event: "first_execution_record_created"
2962
- }
2963
- },
2964
- done: {
2965
- from: [
2966
- "active"
2967
- ],
2968
- requires: {
2969
- command: "gitgov task complete"
2970
- }
2971
- }
2972
- }
2973
- },
2974
- {
2975
- version: "1.0.0",
2976
- name: "GitGovernance Default Methodology",
2977
- description: "Standard GitGovernance workflow with quality gates and agent collaboration",
2978
- state_transitions: {
2979
- review: {
2980
- from: [
2981
- "draft"
2982
- ],
2983
- requires: {
2984
- command: "gitgov task submit",
2985
- signatures: {
2986
- __default__: {
2987
- role: "submitter",
2988
- capability_roles: [
2989
- "author"
2990
- ],
2991
- min_approvals: 1
2992
- }
2993
- }
2994
- }
2995
- },
2996
- ready: {
2997
- from: [
2998
- "review"
2999
- ],
3000
- requires: {
3001
- command: "gitgov task approve",
3002
- signatures: {
3003
- __default__: {
3004
- role: "approver",
3005
- capability_roles: [
3006
- "approver:product"
3007
- ],
3008
- min_approvals: 1
3009
- },
3010
- design: {
3011
- role: "approver",
3012
- capability_roles: [
3013
- "approver:design"
3014
- ],
3015
- min_approvals: 1
3016
- },
3017
- quality: {
3018
- role: "approver",
3019
- capability_roles: [
3020
- "approver:quality"
3021
- ],
3022
- min_approvals: 1
3023
- }
3024
- }
3025
- }
3026
- },
3027
- active: {
3028
- from: [
3029
- "ready",
3030
- "paused"
3031
- ],
3032
- requires: {
3033
- event: "first_execution_record_created",
3034
- custom_rules: [
3035
- "task_must_have_valid_assignment_for_executor"
3036
- ]
3037
- }
3038
- },
3039
- done: {
3040
- from: [
3041
- "active"
3042
- ],
3043
- requires: {
3044
- command: "gitgov task complete",
3045
- signatures: {
3046
- __default__: {
3047
- role: "approver",
3048
- capability_roles: [
3049
- "approver:quality"
3050
- ],
3051
- min_approvals: 1
3052
- }
3053
- }
3054
- }
3055
- },
3056
- archived: {
3057
- from: [
3058
- "done"
3059
- ],
3060
- requires: {
3061
- event: "changelog_record_created"
3062
- }
3063
- },
3064
- paused: {
3065
- from: [
3066
- "active",
3067
- "review"
3068
- ],
3069
- requires: {
3070
- event: "feedback_blocking_created"
3071
- }
3072
- },
3073
- discarded: {
3074
- from: [
3075
- "ready",
3076
- "active"
3077
- ],
3078
- requires: {
3079
- command: "gitgov task cancel",
3080
- signatures: {
3081
- __default__: {
3082
- role: "canceller",
3083
- capability_roles: [
3084
- "approver:product",
3085
- "approver:quality"
3086
- ],
3087
- min_approvals: 1
3088
- }
3089
- }
3090
- }
3091
- }
3092
- },
3093
- custom_rules: {
3094
- task_must_have_valid_assignment_for_executor: {
3095
- description: "Task must have a valid assignment before execution can begin",
3096
- validation: "assignment_required"
3097
- }
3098
- }
3099
- }
3100
- ]
2781
+ }
3101
2782
  };
3102
2783
 
3103
2784
  // src/record_schemas/generated/index.ts
3104
2785
  var Schemas = {
3105
2786
  ActorRecord: actor_record_schema_default,
3106
2787
  AgentRecord: agent_record_schema_default,
3107
- ChangelogRecord: changelog_record_schema_default,
3108
2788
  CycleRecord: cycle_record_schema_default,
3109
2789
  EmbeddedMetadata: embedded_metadata_schema_default,
3110
2790
  ExecutionRecord: execution_record_schema_default,
@@ -3146,7 +2826,6 @@ var SchemaValidationCache = class {
3146
2826
  const schemaRefMap = {
3147
2827
  "ActorRecord": "ref:actor_record_schema",
3148
2828
  "AgentRecord": "ref:agent_record_schema",
3149
- "ChangelogRecord": "ref:changelog_record_schema",
3150
2829
  "CycleRecord": "ref:cycle_record_schema",
3151
2830
  "ExecutionRecord": "ref:execution_record_schema",
3152
2831
  "FeedbackRecord": "ref:feedback_record_schema",
@@ -3373,45 +3052,6 @@ function loadExecutionRecord(data) {
3373
3052
  return record;
3374
3053
  }
3375
3054
 
3376
- // src/record_validations/changelog_validator.ts
3377
- function validateChangelogRecordSchema(data) {
3378
- const validator = SchemaValidationCache.getValidatorFromSchema(Schemas.ChangelogRecord);
3379
- const isValid = validator(data);
3380
- return [isValid, validator.errors];
3381
- }
3382
- function validateChangelogRecordDetailed(data) {
3383
- const [isValid, errors] = validateChangelogRecordSchema(data);
3384
- if (!isValid && errors) {
3385
- const formattedErrors = errors.map((error) => ({
3386
- field: error.instancePath?.replace("/", "") || error.params?.["missingProperty"] || "root",
3387
- message: error.message || "Unknown validation error",
3388
- value: error.data
3389
- }));
3390
- return {
3391
- isValid: false,
3392
- errors: formattedErrors
3393
- };
3394
- }
3395
- return {
3396
- isValid: true,
3397
- errors: []
3398
- };
3399
- }
3400
-
3401
- // src/record_factories/changelog_factory.ts
3402
- function loadChangelogRecord(data) {
3403
- const embeddedValidation = validateEmbeddedMetadataDetailed(data);
3404
- if (!embeddedValidation.isValid) {
3405
- throw new DetailedValidationError("GitGovRecord (ChangelogRecord)", embeddedValidation.errors);
3406
- }
3407
- const record = data;
3408
- const payloadValidation = validateChangelogRecordDetailed(record.payload);
3409
- if (!payloadValidation.isValid) {
3410
- throw new DetailedValidationError("ChangelogRecord payload", payloadValidation.errors);
3411
- }
3412
- return record;
3413
- }
3414
-
3415
3055
  // src/record_validations/feedback_validator.ts
3416
3056
  function validateFeedbackRecordSchema(data) {
3417
3057
  const validator = SchemaValidationCache.getValidatorFromSchema(Schemas.FeedbackRecord);
@@ -3486,25 +3126,25 @@ var FsLintModule = class {
3486
3126
  this.projectRoot = dependencies.projectRoot;
3487
3127
  this.lintModule = dependencies.lintModule;
3488
3128
  this.fileSystem = dependencies.fileSystem ?? {
3489
- readFile: async (path14, encoding) => {
3490
- return promises.readFile(path14, encoding);
3129
+ readFile: async (path13, encoding) => {
3130
+ return promises.readFile(path13, encoding);
3491
3131
  },
3492
- writeFile: async (path14, content) => {
3493
- await promises.writeFile(path14, content, "utf-8");
3132
+ writeFile: async (path13, content) => {
3133
+ await promises.writeFile(path13, content, "utf-8");
3494
3134
  },
3495
- exists: async (path14) => {
3135
+ exists: async (path13) => {
3496
3136
  try {
3497
- await promises.access(path14);
3137
+ await promises.access(path13);
3498
3138
  return true;
3499
3139
  } catch {
3500
3140
  return false;
3501
3141
  }
3502
3142
  },
3503
- unlink: async (path14) => {
3504
- await promises.unlink(path14);
3143
+ unlink: async (path13) => {
3144
+ await promises.unlink(path13);
3505
3145
  },
3506
- readdir: async (path14) => {
3507
- return readdir(path14);
3146
+ readdir: async (path13) => {
3147
+ return readdir(path13);
3508
3148
  }
3509
3149
  };
3510
3150
  }
@@ -3516,7 +3156,7 @@ var FsLintModule = class {
3516
3156
  return this.lintModule.lintRecord(record, context);
3517
3157
  }
3518
3158
  /**
3519
- * Delegates to LintModule.lintRecordReferences() for prefix validation.
3159
+ * Delegates to LintModule.lintRecordReferences() for reference validation.
3520
3160
  */
3521
3161
  lintRecordReferences(record, context) {
3522
3162
  return this.lintModule.lintRecordReferences(record, context);
@@ -3754,9 +3394,6 @@ var FsLintModule = class {
3754
3394
  case "execution":
3755
3395
  record = loadExecutionRecord(raw);
3756
3396
  break;
3757
- case "changelog":
3758
- record = loadChangelogRecord(raw);
3759
- break;
3760
3397
  case "feedback":
3761
3398
  record = loadFeedbackRecord(raw);
3762
3399
  break;
@@ -3769,10 +3406,6 @@ var FsLintModule = class {
3769
3406
  filePath
3770
3407
  });
3771
3408
  results.push(...lintResults);
3772
- if (options.validateFileNaming) {
3773
- const namingResults = this.validateFileNaming(record, recordId, filePath, entityType);
3774
- results.push(...namingResults);
3775
- }
3776
3409
  if (options.validateReferences) {
3777
3410
  const refResults = this.lintModule.lintRecordReferences(record, {
3778
3411
  recordId,
@@ -3781,6 +3414,10 @@ var FsLintModule = class {
3781
3414
  });
3782
3415
  results.push(...refResults);
3783
3416
  }
3417
+ if (options.validateFileNaming) {
3418
+ const namingResults = this.validateFileNaming(record, recordId, filePath, entityType);
3419
+ results.push(...namingResults);
3420
+ }
3784
3421
  } catch (error) {
3785
3422
  if (error instanceof DetailedValidationError) {
3786
3423
  const hasAdditionalProperties = error.errors.some(
@@ -3833,7 +3470,6 @@ var FsLintModule = class {
3833
3470
  "task": "tasks",
3834
3471
  "cycle": "cycles",
3835
3472
  "execution": "executions",
3836
- "changelog": "changelogs",
3837
3473
  "feedback": "feedbacks",
3838
3474
  "actor": "actors",
3839
3475
  "agent": "agents"
@@ -3876,7 +3512,6 @@ var FsLintModule = class {
3876
3512
  "cycle",
3877
3513
  "task",
3878
3514
  "execution",
3879
- "changelog",
3880
3515
  "feedback"
3881
3516
  ];
3882
3517
  const allRecords = [];
@@ -3884,7 +3519,6 @@ var FsLintModule = class {
3884
3519
  "task": "tasks",
3885
3520
  "cycle": "cycles",
3886
3521
  "execution": "executions",
3887
- "changelog": "changelogs",
3888
3522
  "feedback": "feedbacks",
3889
3523
  "actor": "actors",
3890
3524
  "agent": "agents"
@@ -3910,7 +3544,6 @@ var FsLintModule = class {
3910
3544
  "task": "tasks",
3911
3545
  "cycle": "cycles",
3912
3546
  "execution": "executions",
3913
- "changelog": "changelogs",
3914
3547
  "feedback": "feedbacks",
3915
3548
  "actor": "actors",
3916
3549
  "agent": "agents"
@@ -4126,8 +3759,7 @@ var GITGOV_DIRECTORIES = [
4126
3759
  "cycles",
4127
3760
  "tasks",
4128
3761
  "executions",
4129
- "feedbacks",
4130
- "changelogs"
3762
+ "feedbacks"
4131
3763
  ];
4132
3764
  var FsProjectInitializer = class {
4133
3765
  projectRoot;
@@ -4141,17 +3773,17 @@ var FsProjectInitializer = class {
4141
3773
  * Creates the .gitgov/ directory structure.
4142
3774
  */
4143
3775
  async createProjectStructure() {
4144
- const gitgovPath = path9.join(this.projectRoot, ".gitgov");
3776
+ const gitgovPath = path6.join(this.projectRoot, ".gitgov");
4145
3777
  await promises.mkdir(gitgovPath, { recursive: true });
4146
3778
  for (const dir of GITGOV_DIRECTORIES) {
4147
- await promises.mkdir(path9.join(gitgovPath, dir), { recursive: true });
3779
+ await promises.mkdir(path6.join(gitgovPath, dir), { recursive: true });
4148
3780
  }
4149
3781
  }
4150
3782
  /**
4151
3783
  * Checks if .gitgov/config.json exists.
4152
3784
  */
4153
3785
  async isInitialized() {
4154
- const configPath = path9.join(this.projectRoot, ".gitgov", "config.json");
3786
+ const configPath = path6.join(this.projectRoot, ".gitgov", "config.json");
4155
3787
  try {
4156
3788
  await promises.access(configPath);
4157
3789
  return true;
@@ -4163,14 +3795,14 @@ var FsProjectInitializer = class {
4163
3795
  * Writes config.json to .gitgov/
4164
3796
  */
4165
3797
  async writeConfig(config) {
4166
- const configPath = path9.join(this.projectRoot, ".gitgov", "config.json");
3798
+ const configPath = path6.join(this.projectRoot, ".gitgov", "config.json");
4167
3799
  await promises.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
4168
3800
  }
4169
3801
  /**
4170
3802
  * Creates .session.json with initial actor state.
4171
3803
  */
4172
3804
  async initializeSession(actorId) {
4173
- const sessionPath = path9.join(this.projectRoot, ".gitgov", ".session.json");
3805
+ const sessionPath = path6.join(this.projectRoot, ".gitgov", ".session.json");
4174
3806
  const session = {
4175
3807
  lastSession: {
4176
3808
  actorId,
@@ -4197,7 +3829,7 @@ var FsProjectInitializer = class {
4197
3829
  * Gets the path for an actor record.
4198
3830
  */
4199
3831
  getActorPath(actorId) {
4200
- return path9.join(this.projectRoot, ".gitgov", "actors", `${actorId}.json`);
3832
+ return path6.join(this.projectRoot, ".gitgov", "actors", `${actorId}.json`);
4201
3833
  }
4202
3834
  /**
4203
3835
  * Validates environment for GitGovernance initialization.
@@ -4207,7 +3839,7 @@ var FsProjectInitializer = class {
4207
3839
  const warnings = [];
4208
3840
  const suggestions = [];
4209
3841
  try {
4210
- const gitPath = path9.join(this.projectRoot, ".git");
3842
+ const gitPath = path6.join(this.projectRoot, ".git");
4211
3843
  const isGitRepo = existsSync(gitPath);
4212
3844
  if (!isGitRepo) {
4213
3845
  warnings.push(`Not a Git repository in directory: ${this.projectRoot}`);
@@ -4215,7 +3847,7 @@ var FsProjectInitializer = class {
4215
3847
  }
4216
3848
  let hasWritePermissions = false;
4217
3849
  try {
4218
- const testFile = path9.join(this.projectRoot, ".gitgov-test");
3850
+ const testFile = path6.join(this.projectRoot, ".gitgov-test");
4219
3851
  await promises.writeFile(testFile, "test");
4220
3852
  await promises.unlink(testFile);
4221
3853
  hasWritePermissions = true;
@@ -4273,7 +3905,7 @@ var FsProjectInitializer = class {
4273
3905
  currentBranch
4274
3906
  };
4275
3907
  if (isAlreadyInitialized) {
4276
- result.gitgovPath = path9.join(this.projectRoot, ".gitgov");
3908
+ result.gitgovPath = path6.join(this.projectRoot, ".gitgov");
4277
3909
  }
4278
3910
  return result;
4279
3911
  } catch (error) {
@@ -4293,18 +3925,18 @@ var FsProjectInitializer = class {
4293
3925
  * Copies the @gitgov agent prompt to project root for IDE access.
4294
3926
  */
4295
3927
  async copyAgentPrompt() {
4296
- const targetPrompt = path9.join(this.repoRoot, "gitgov");
3928
+ const targetPrompt = path6.join(this.repoRoot, "gitgov");
4297
3929
  const potentialSources = [];
4298
3930
  potentialSources.push(
4299
- path9.join(process.cwd(), "src/docs/generated/gitgov_agent.md")
3931
+ path6.join(process.cwd(), "src/docs/generated/gitgov_agent.md")
4300
3932
  );
4301
3933
  try {
4302
3934
  const metaUrl = getImportMetaUrl();
4303
3935
  if (metaUrl) {
4304
3936
  const require2 = createRequire(metaUrl);
4305
3937
  const pkgJsonPath = require2.resolve("@gitgov/core/package.json");
4306
- const pkgRoot = path9.dirname(pkgJsonPath);
4307
- potentialSources.push(path9.join(pkgRoot, "dist/src/docs/generated/gitgov_agent.md"));
3938
+ const pkgRoot = path6.dirname(pkgJsonPath);
3939
+ potentialSources.push(path6.join(pkgRoot, "dist/src/docs/generated/gitgov_agent.md"));
4308
3940
  }
4309
3941
  } catch {
4310
3942
  }
@@ -4312,8 +3944,8 @@ var FsProjectInitializer = class {
4312
3944
  const metaUrl = getImportMetaUrl();
4313
3945
  if (metaUrl) {
4314
3946
  const __filename = fileURLToPath(metaUrl);
4315
- const __dirname = path9.dirname(__filename);
4316
- potentialSources.push(path9.resolve(__dirname, "../../docs/generated/gitgov_agent.md"));
3947
+ const __dirname = path6.dirname(__filename);
3948
+ potentialSources.push(path6.resolve(__dirname, "../../docs/generated/gitgov_agent.md"));
4317
3949
  }
4318
3950
  } catch {
4319
3951
  }
@@ -4336,7 +3968,7 @@ var FsProjectInitializer = class {
4336
3968
  * Sets up .gitignore for GitGovernance files.
4337
3969
  */
4338
3970
  async setupGitIntegration() {
4339
- const gitignorePath = path9.join(this.repoRoot, ".gitignore");
3971
+ const gitignorePath = path6.join(this.repoRoot, ".gitignore");
4340
3972
  const gitignoreContent = `
4341
3973
  # GitGovernance
4342
3974
  # Ignore entire .gitgov/ directory (state lives in gitgov-state branch)
@@ -4364,7 +3996,7 @@ gitgov
4364
3996
  * Removes .gitgov/ directory (for rollback on failed init).
4365
3997
  */
4366
3998
  async rollback() {
4367
- const gitgovPath = path9.join(this.projectRoot, ".gitgov");
3999
+ const gitgovPath = path6.join(this.projectRoot, ".gitgov");
4368
4000
  try {
4369
4001
  await promises.access(gitgovPath);
4370
4002
  await promises.rm(gitgovPath, { recursive: true, force: true });
@@ -4599,7 +4231,7 @@ var LocalGitModule = class {
4599
4231
  if (result.exitCode !== 0) {
4600
4232
  try {
4601
4233
  const repoRoot = await this.ensureRepoRoot();
4602
- const headPath = path9.join(repoRoot, ".git", "HEAD");
4234
+ const headPath = path6.join(repoRoot, ".git", "HEAD");
4603
4235
  const headContent = fs8.readFileSync(headPath, "utf-8").trim();
4604
4236
  if (headContent.startsWith("ref: refs/heads/")) {
4605
4237
  return headContent.replace("ref: refs/heads/", "");
@@ -4939,8 +4571,8 @@ var LocalGitModule = class {
4939
4571
  */
4940
4572
  async isRebaseInProgress() {
4941
4573
  const repoRoot = await this.ensureRepoRoot();
4942
- const rebaseMergePath = path9.join(repoRoot, ".git", "rebase-merge");
4943
- const rebaseApplyPath = path9.join(repoRoot, ".git", "rebase-apply");
4574
+ const rebaseMergePath = path6.join(repoRoot, ".git", "rebase-merge");
4575
+ const rebaseApplyPath = path6.join(repoRoot, ".git", "rebase-apply");
4944
4576
  return fs8.existsSync(rebaseMergePath) || fs8.existsSync(rebaseApplyPath);
4945
4577
  }
4946
4578
  /**
@@ -5488,10 +5120,6 @@ var LocalGitModule = class {
5488
5120
  var projectRootCache = null;
5489
5121
  var lastSearchPath = null;
5490
5122
  function findProjectRoot(startPath = process.cwd()) {
5491
- if (typeof global.projectRoot !== "undefined" && global.projectRoot === null) {
5492
- projectRootCache = null;
5493
- lastSearchPath = null;
5494
- }
5495
5123
  if (lastSearchPath && lastSearchPath !== startPath) {
5496
5124
  projectRootCache = null;
5497
5125
  lastSearchPath = null;
@@ -5501,61 +5129,67 @@ function findProjectRoot(startPath = process.cwd()) {
5501
5129
  }
5502
5130
  lastSearchPath = startPath;
5503
5131
  let currentPath = startPath;
5504
- while (currentPath !== path9.parse(currentPath).root) {
5505
- if (existsSync(path9.join(currentPath, ".git"))) {
5132
+ while (currentPath !== path6.parse(currentPath).root) {
5133
+ if (existsSync(path6.join(currentPath, ".git"))) {
5506
5134
  projectRootCache = currentPath;
5507
5135
  return projectRootCache;
5508
5136
  }
5509
- currentPath = path9.dirname(currentPath);
5137
+ currentPath = path6.dirname(currentPath);
5510
5138
  }
5511
- if (existsSync(path9.join(currentPath, ".git"))) {
5139
+ if (existsSync(path6.join(currentPath, ".git"))) {
5512
5140
  projectRootCache = currentPath;
5513
5141
  return projectRootCache;
5514
5142
  }
5515
5143
  return null;
5516
5144
  }
5517
- function findGitgovRoot(startPath = process.cwd()) {
5518
- let currentPath = startPath;
5519
- while (currentPath !== path9.parse(currentPath).root) {
5520
- if (existsSync(path9.join(currentPath, ".gitgov"))) {
5521
- return currentPath;
5522
- }
5523
- currentPath = path9.dirname(currentPath);
5524
- }
5525
- if (existsSync(path9.join(currentPath, ".gitgov"))) {
5526
- return currentPath;
5527
- }
5528
- currentPath = startPath;
5529
- while (currentPath !== path9.parse(currentPath).root) {
5530
- if (existsSync(path9.join(currentPath, ".git"))) {
5531
- return currentPath;
5532
- }
5533
- currentPath = path9.dirname(currentPath);
5534
- }
5535
- if (existsSync(path9.join(currentPath, ".git"))) {
5536
- return currentPath;
5537
- }
5538
- return null;
5539
- }
5540
- function getGitgovPath() {
5541
- const root = findGitgovRoot();
5542
- if (!root) {
5543
- throw new Error("Could not find project root. Make sure you are inside a GitGovernance repository.");
5544
- }
5545
- return path9.join(root, ".gitgov");
5546
- }
5547
- function isGitgovProject() {
5548
- try {
5549
- const gitgovPath = getGitgovPath();
5550
- return existsSync(gitgovPath);
5551
- } catch {
5552
- return false;
5553
- }
5554
- }
5555
5145
  function resetDiscoveryCache() {
5556
5146
  projectRootCache = null;
5557
5147
  lastSearchPath = null;
5558
5148
  }
5149
+ function getWorktreeBasePath(repoRoot) {
5150
+ const resolvedPath = realpathSync(repoRoot);
5151
+ const hash = createHash("sha256").update(resolvedPath).digest("hex").slice(0, 12);
5152
+ return path6.join(os.homedir(), ".gitgov", "worktrees", hash);
5153
+ }
5154
+
5155
+ // src/sync_state/sync_state.types.ts
5156
+ var SYNC_DIRECTORIES = [
5157
+ "tasks",
5158
+ "cycles",
5159
+ "actors",
5160
+ "agents",
5161
+ "feedbacks",
5162
+ "executions",
5163
+ "workflows"
5164
+ ];
5165
+ var SYNC_ROOT_FILES = [
5166
+ "config.json"
5167
+ ];
5168
+ var SYNC_ALLOWED_EXTENSIONS = [".json"];
5169
+ var SYNC_EXCLUDED_PATTERNS = [
5170
+ /\.key$/,
5171
+ // Private keys (e.g., keys/*.key)
5172
+ /\.backup$/,
5173
+ // Backup files from lint
5174
+ /\.backup-\d+$/,
5175
+ // Numbered backup files
5176
+ /\.tmp$/,
5177
+ // Temporary files
5178
+ /\.bak$/
5179
+ // Backup files
5180
+ ];
5181
+ var LOCAL_ONLY_FILES = [
5182
+ "index.json",
5183
+ // Generated index, rebuilt on each machine
5184
+ ".session.json",
5185
+ // Local session state for current user/agent
5186
+ "gitgov"
5187
+ // Local binary/script
5188
+ ];
5189
+
5190
+ // src/sync_state/fs_worktree/fs_worktree_sync_state.types.ts
5191
+ var WORKTREE_DIR_NAME = ".gitgov-worktree";
5192
+ var DEFAULT_STATE_BRANCH = "gitgov-state";
5559
5193
 
5560
5194
  // src/sync_state/sync_state.errors.ts
5561
5195
  var SyncStateError = class _SyncStateError extends Error {
@@ -5565,17 +5199,6 @@ var SyncStateError = class _SyncStateError extends Error {
5565
5199
  Object.setPrototypeOf(this, _SyncStateError.prototype);
5566
5200
  }
5567
5201
  };
5568
- var PushFromStateBranchError = class _PushFromStateBranchError extends SyncStateError {
5569
- branch;
5570
- constructor(branchName) {
5571
- super(
5572
- `Cannot push from ${branchName} branch. Please switch to a working branch before pushing state.`
5573
- );
5574
- this.name = "PushFromStateBranchError";
5575
- this.branch = branchName;
5576
- Object.setPrototypeOf(this, _PushFromStateBranchError.prototype);
5577
- }
5578
- };
5579
5202
  var ConflictMarkersPresentError = class _ConflictMarkersPresentError extends SyncStateError {
5580
5203
  constructor(filesWithMarkers) {
5581
5204
  super(
@@ -5636,60 +5259,10 @@ var RebaseAlreadyInProgressError = class _RebaseAlreadyInProgressError extends S
5636
5259
  Object.setPrototypeOf(this, _RebaseAlreadyInProgressError.prototype);
5637
5260
  }
5638
5261
  };
5639
- var UncommittedChangesError = class _UncommittedChangesError extends SyncStateError {
5640
- branch;
5641
- constructor(branchName) {
5642
- super(
5643
- `Uncommitted changes detected in ${branchName}. Please commit or stash changes before synchronizing.`
5644
- );
5645
- this.name = "UncommittedChangesError";
5646
- this.branch = branchName;
5647
- Object.setPrototypeOf(this, _UncommittedChangesError.prototype);
5648
- }
5649
- };
5650
-
5651
- // src/sync_state/sync_state.types.ts
5652
- var SYNC_DIRECTORIES = [
5653
- "tasks",
5654
- "cycles",
5655
- "actors",
5656
- "agents",
5657
- "feedbacks",
5658
- "executions",
5659
- "changelogs",
5660
- "workflows"
5661
- ];
5662
- var SYNC_ROOT_FILES = [
5663
- "config.json"
5664
- ];
5665
- var SYNC_ALLOWED_EXTENSIONS = [".json"];
5666
- var SYNC_EXCLUDED_PATTERNS = [
5667
- /\.key$/,
5668
- // Private keys (e.g., keys/*.key)
5669
- /\.backup$/,
5670
- // Backup files from lint
5671
- /\.backup-\d+$/,
5672
- // Numbered backup files
5673
- /\.tmp$/,
5674
- // Temporary files
5675
- /\.bak$/
5676
- // Backup files
5677
- ];
5678
- var LOCAL_ONLY_FILES = [
5679
- "index.json",
5680
- // Generated index, rebuilt on each machine
5681
- ".session.json",
5682
- // Local session state for current user/agent
5683
- "gitgov"
5684
- // Local binary/script
5685
- ];
5686
-
5687
- // src/sync_state/fs/fs_sync_state.ts
5688
- var execAsync = promisify(exec);
5689
- var logger6 = createLogger("[SyncStateModule] ");
5262
+ var logger6 = createLogger("[WorktreeSyncState] ");
5690
5263
  function shouldSyncFile(filePath) {
5691
- const fileName = path9__default.basename(filePath);
5692
- const ext = path9__default.extname(filePath);
5264
+ const fileName = path6__default.basename(filePath);
5265
+ const ext = path6__default.extname(filePath);
5693
5266
  if (!SYNC_ALLOWED_EXTENSIONS.includes(ext)) {
5694
5267
  return false;
5695
5268
  }
@@ -5727,1955 +5300,56 @@ function shouldSyncFile(filePath) {
5727
5300
  }
5728
5301
  return false;
5729
5302
  }
5730
- async function getAllFiles(dir, baseDir = dir) {
5731
- const files = [];
5732
- try {
5733
- const entries = await promises.readdir(dir, { withFileTypes: true });
5734
- for (const entry of entries) {
5735
- const fullPath = path9__default.join(dir, entry.name);
5736
- if (entry.isDirectory()) {
5737
- const subFiles = await getAllFiles(fullPath, baseDir);
5738
- files.push(...subFiles);
5739
- } else if (entry.isFile()) {
5740
- files.push(path9__default.relative(baseDir, fullPath));
5741
- }
5742
- }
5743
- } catch {
5303
+ var FsWorktreeSyncStateModule = class {
5304
+ deps;
5305
+ repoRoot;
5306
+ stateBranchName;
5307
+ worktreePath;
5308
+ gitgovPath;
5309
+ constructor(deps, config) {
5310
+ if (!deps.git) throw new Error("GitModule is required for FsWorktreeSyncStateModule");
5311
+ if (!deps.config) throw new Error("ConfigManager is required for FsWorktreeSyncStateModule");
5312
+ if (!deps.identity) throw new Error("IdentityAdapter is required for FsWorktreeSyncStateModule");
5313
+ if (!deps.lint) throw new Error("LintModule is required for FsWorktreeSyncStateModule");
5314
+ if (!deps.indexer) throw new Error("IndexerAdapter is required for FsWorktreeSyncStateModule");
5315
+ if (!config.repoRoot) throw new Error("repoRoot is required");
5316
+ this.deps = deps;
5317
+ this.repoRoot = config.repoRoot;
5318
+ this.stateBranchName = config.stateBranchName ?? DEFAULT_STATE_BRANCH;
5319
+ this.worktreePath = config.worktreePath ?? path6__default.join(this.repoRoot, WORKTREE_DIR_NAME);
5320
+ this.gitgovPath = path6__default.join(this.worktreePath, ".gitgov");
5744
5321
  }
5745
- return files;
5746
- }
5747
- async function copySyncableFiles(sourceDir, destDir, log, excludeFiles = /* @__PURE__ */ new Set()) {
5748
- let copiedCount = 0;
5749
- for (const dirName of SYNC_DIRECTORIES) {
5750
- const sourcePath = path9__default.join(sourceDir, dirName);
5751
- const destPath = path9__default.join(destDir, dirName);
5752
- try {
5753
- const stat2 = await promises.stat(sourcePath);
5754
- if (!stat2.isDirectory()) continue;
5755
- const allFiles = await getAllFiles(sourcePath);
5756
- for (const relativePath of allFiles) {
5757
- const fullSourcePath = path9__default.join(sourcePath, relativePath);
5758
- const fullDestPath = path9__default.join(destPath, relativePath);
5759
- const gitgovRelativePath = `.gitgov/${dirName}/${relativePath}`;
5760
- if (excludeFiles.has(gitgovRelativePath)) {
5761
- log(`[EARS-B23] Skipped (remote-only change preserved): ${gitgovRelativePath}`);
5762
- continue;
5763
- }
5764
- if (shouldSyncFile(fullSourcePath)) {
5765
- await promises.mkdir(path9__default.dirname(fullDestPath), { recursive: true });
5766
- await promises.copyFile(fullSourcePath, fullDestPath);
5767
- log(`Copied: ${dirName}/${relativePath}`);
5768
- copiedCount++;
5769
- } else {
5770
- log(`Skipped (not syncable): ${dirName}/${relativePath}`);
5771
- }
5772
- }
5773
- } catch (error) {
5774
- const errCode = error.code;
5775
- if (errCode !== "ENOENT") {
5776
- log(`Error processing ${dirName}: ${error}`);
5777
- }
5778
- }
5322
+ // ═══════════════════════════════════════════════
5323
+ // Section A: Worktree Management (WTSYNC-A1..A7)
5324
+ // ═══════════════════════════════════════════════
5325
+ /** [WTSYNC-A4] Returns the worktree path */
5326
+ getWorktreePath() {
5327
+ return this.worktreePath;
5779
5328
  }
5780
- for (const fileName of SYNC_ROOT_FILES) {
5781
- const sourcePath = path9__default.join(sourceDir, fileName);
5782
- const destPath = path9__default.join(destDir, fileName);
5783
- const gitgovRelativePath = `.gitgov/${fileName}`;
5784
- if (excludeFiles.has(gitgovRelativePath)) {
5785
- log(`[EARS-B23] Skipped (remote-only change preserved): ${gitgovRelativePath}`);
5786
- continue;
5329
+ /** [WTSYNC-A1..A6] Ensures worktree exists and is healthy */
5330
+ async ensureWorktree() {
5331
+ const health = await this.checkWorktreeHealth();
5332
+ if (health.healthy) {
5333
+ logger6.debug("Worktree is healthy");
5334
+ await this.removeLegacyGitignore();
5335
+ return;
5336
+ }
5337
+ if (health.exists && !health.healthy) {
5338
+ logger6.warn(`Worktree corrupted: ${health.error}. Recreating...`);
5339
+ await this.removeWorktree();
5787
5340
  }
5341
+ await this.ensureStateBranch();
5788
5342
  try {
5789
- await promises.copyFile(sourcePath, destPath);
5790
- log(`Copied root file: ${fileName}`);
5791
- copiedCount++;
5343
+ logger6.info(`Creating worktree at ${this.worktreePath}`);
5344
+ await this.execGit(["worktree", "add", this.worktreePath, this.stateBranchName]);
5792
5345
  } catch (error) {
5793
- const errCode = error.code;
5794
- if (errCode !== "ENOENT") {
5795
- log(`Error copying ${fileName}: ${error}`);
5796
- }
5346
+ throw new WorktreeSetupError(
5347
+ "Failed to create worktree",
5348
+ this.worktreePath,
5349
+ error instanceof Error ? error : void 0
5350
+ );
5797
5351
  }
5798
- }
5799
- return copiedCount;
5800
- }
5801
- var FsSyncStateModule = class {
5802
- git;
5803
- config;
5804
- identity;
5805
- lint;
5806
- indexer;
5807
- /**
5808
- * Constructor with dependency injection
5809
- */
5810
- constructor(dependencies) {
5811
- if (!dependencies.git) {
5812
- throw new Error("IGitModule is required for SyncStateModule");
5813
- }
5814
- if (!dependencies.config) {
5815
- throw new Error("ConfigManager is required for SyncStateModule");
5816
- }
5817
- if (!dependencies.identity) {
5818
- throw new Error("IdentityAdapter is required for SyncStateModule");
5819
- }
5820
- if (!dependencies.lint) {
5821
- throw new Error("LintModule is required for SyncStateModule");
5822
- }
5823
- if (!dependencies.indexer) {
5824
- throw new Error("RecordProjector is required for SyncStateModule");
5825
- }
5826
- this.git = dependencies.git;
5827
- this.config = dependencies.config;
5828
- this.identity = dependencies.identity;
5829
- this.lint = dependencies.lint;
5830
- this.indexer = dependencies.indexer;
5831
- }
5832
- /**
5833
- * Static method to bootstrap .gitgov/ from gitgov-state branch.
5834
- * Used when cloning a repo that has gitgov-state but .gitgov/ is not in the work branch.
5835
- *
5836
- * This method only requires GitModule and can be called before full SyncStateModule initialization.
5837
- *
5838
- * @param gitModule - GitModule instance for git operations
5839
- * @param stateBranch - Name of the state branch (default: "gitgov-state")
5840
- * @returns Promise<{ success: boolean; error?: string }>
5841
- */
5842
- static async bootstrapFromStateBranch(gitModule, stateBranch = "gitgov-state") {
5843
- try {
5844
- const repoRoot = await gitModule.getRepoRoot();
5845
- const hasLocalBranch = await gitModule.branchExists(stateBranch);
5846
- let hasRemoteBranch = false;
5847
- try {
5848
- const remoteBranches = await gitModule.listRemoteBranches("origin");
5849
- hasRemoteBranch = remoteBranches.includes(stateBranch);
5850
- } catch {
5851
- }
5852
- if (!hasLocalBranch && !hasRemoteBranch) {
5853
- return {
5854
- success: false,
5855
- error: `State branch '${stateBranch}' does not exist locally or remotely`
5856
- };
5857
- }
5858
- if (!hasLocalBranch && hasRemoteBranch) {
5859
- try {
5860
- const currentBranch = await gitModule.getCurrentBranch();
5861
- await gitModule.fetch("origin");
5862
- await execAsync(`git checkout -b ${stateBranch} origin/${stateBranch}`, { cwd: repoRoot });
5863
- if (currentBranch && currentBranch !== stateBranch) {
5864
- await gitModule.checkoutBranch(currentBranch);
5865
- }
5866
- } catch (error) {
5867
- return {
5868
- success: false,
5869
- error: `Failed to fetch state branch: ${error.message}`
5870
- };
5871
- }
5872
- }
5873
- try {
5874
- const { stdout } = await execAsync(`git ls-tree -r ${stateBranch} --name-only .gitgov/`, { cwd: repoRoot });
5875
- if (!stdout.trim()) {
5876
- return {
5877
- success: false,
5878
- error: `No .gitgov/ directory found in '${stateBranch}' branch`
5879
- };
5880
- }
5881
- } catch {
5882
- return {
5883
- success: false,
5884
- error: `Failed to check .gitgov/ in '${stateBranch}' branch`
5885
- };
5886
- }
5887
- try {
5888
- await execAsync(`git checkout ${stateBranch} -- .gitgov/`, { cwd: repoRoot });
5889
- await execAsync("git reset HEAD .gitgov/", { cwd: repoRoot });
5890
- logger6.info(`[bootstrapFromStateBranch] Successfully restored .gitgov/ from ${stateBranch}`);
5891
- } catch (error) {
5892
- return {
5893
- success: false,
5894
- error: `Failed to copy .gitgov/ from state branch: ${error.message}`
5895
- };
5896
- }
5897
- return { success: true };
5898
- } catch (error) {
5899
- return {
5900
- success: false,
5901
- error: `Bootstrap failed: ${error.message}`
5902
- };
5903
- }
5904
- }
5905
- /**
5906
- * Gets the state branch name from configuration.
5907
- * Default: "gitgov-state"
5908
- *
5909
- * [EARS-A4]
5910
- */
5911
- async getStateBranchName() {
5912
- try {
5913
- const config = await this.config.loadConfig();
5914
- return config?.state?.branch ?? "gitgov-state";
5915
- } catch {
5916
- return "gitgov-state";
5917
- }
5918
- }
5919
- /**
5920
- * Ensures that the gitgov-state branch exists both locally and remotely.
5921
- * If it doesn't exist, creates it as an orphan branch.
5922
- *
5923
- * Use cases (4 edge cases):
5924
- * 1. Doesn't exist locally or remotely → Create orphan branch + initial commit + push
5925
- * 2. Exists remotely, not locally → Fetch + create local + set tracking
5926
- * 3. Exists locally, not remotely → Push + set tracking
5927
- * 4. Exists both → Verify tracking
5928
- *
5929
- * [EARS-A1, EARS-A2, EARS-A3]
5930
- */
5931
- async ensureStateBranch() {
5932
- const stateBranch = await this.getStateBranchName();
5933
- const remoteName = "origin";
5934
- try {
5935
- const existsLocal = await this.git.branchExists(stateBranch);
5936
- try {
5937
- await this.git.fetch(remoteName);
5938
- } catch {
5939
- }
5940
- const remoteBranches = await this.git.listRemoteBranches(remoteName);
5941
- const existsRemote = remoteBranches.includes(stateBranch);
5942
- if (!existsLocal && !existsRemote) {
5943
- await this.createOrphanStateBranch(stateBranch, remoteName);
5944
- return;
5945
- }
5946
- if (!existsLocal && existsRemote) {
5947
- const currentBranch = await this.git.getCurrentBranch();
5948
- const repoRoot = await this.git.getRepoRoot();
5949
- try {
5950
- await execAsync(`git checkout -b ${stateBranch} ${remoteName}/${stateBranch}`, { cwd: repoRoot });
5951
- if (currentBranch !== stateBranch) {
5952
- await this.git.checkoutBranch(currentBranch);
5953
- }
5954
- } catch (checkoutError) {
5955
- try {
5956
- await this.git.checkoutBranch(currentBranch);
5957
- } catch {
5958
- }
5959
- throw checkoutError;
5960
- }
5961
- return;
5962
- }
5963
- if (existsLocal && !existsRemote) {
5964
- const currentBranch = await this.git.getCurrentBranch();
5965
- if (currentBranch !== stateBranch) {
5966
- await this.git.checkoutBranch(stateBranch);
5967
- }
5968
- try {
5969
- await this.git.pushWithUpstream(remoteName, stateBranch);
5970
- } catch {
5971
- }
5972
- if (currentBranch !== stateBranch) {
5973
- await this.git.checkoutBranch(currentBranch);
5974
- }
5975
- return;
5976
- }
5977
- if (existsLocal && existsRemote) {
5978
- const upstreamBranch = await this.git.getBranchRemote(stateBranch);
5979
- if (!upstreamBranch || upstreamBranch !== `${remoteName}/${stateBranch}`) {
5980
- await this.git.setUpstream(stateBranch, remoteName, stateBranch);
5981
- }
5982
- return;
5983
- }
5984
- } catch (error) {
5985
- const errorMessage = error instanceof Error ? error.message : String(error);
5986
- throw new StateBranchSetupError(
5987
- `Failed to ensure state branch ${stateBranch}: ${errorMessage}`,
5988
- error
5989
- );
5990
- }
5991
- }
5992
- /**
5993
- * Creates the gitgov-state orphan branch with an empty initial commit.
5994
- * Used by ensureStateBranch when the branch doesn't exist locally or remotely.
5995
- *
5996
- * [EARS-A1]
5997
- */
5998
- async createOrphanStateBranch(stateBranch, remoteName) {
5999
- const currentBranch = await this.git.getCurrentBranch();
6000
- const repoRoot = await this.git.getRepoRoot();
6001
- const currentBranchHasCommits = await this.git.branchExists(currentBranch);
6002
- if (!currentBranchHasCommits) {
6003
- throw new Error(
6004
- `Cannot initialize GitGovernance: branch '${currentBranch}' has no commits. Please create an initial commit first (e.g., 'git commit --allow-empty -m "Initial commit"').`
6005
- );
6006
- }
6007
- try {
6008
- await this.git.checkoutOrphanBranch(stateBranch);
6009
- try {
6010
- await execAsync("git rm -rf . 2>/dev/null || true", { cwd: repoRoot });
6011
- const gitignoreContent = `# GitGovernance State Branch .gitignore
6012
- # This file is auto-generated during gitgov init
6013
- # These files are machine-specific and should NOT be synced
6014
-
6015
- # Local-only files (regenerated/machine-specific)
6016
- index.json
6017
- .session.json
6018
- gitgov
6019
-
6020
- # Private keys (never synced for security)
6021
- *.key
6022
-
6023
- # Backup and temporary files
6024
- *.backup
6025
- *.backup-*
6026
- *.tmp
6027
- *.bak
6028
- `;
6029
- const gitignorePath = path9__default.join(repoRoot, ".gitignore");
6030
- await promises.writeFile(gitignorePath, gitignoreContent, "utf-8");
6031
- await execAsync("git add .gitignore", { cwd: repoRoot });
6032
- await execAsync('git commit -m "Initialize state branch with .gitignore"', { cwd: repoRoot });
6033
- } catch (commitError) {
6034
- const error = commitError;
6035
- throw new Error(`Failed to create initial commit on orphan branch: ${error.stderr || error.message}`);
6036
- }
6037
- const hasRemote = await this.git.isRemoteConfigured(remoteName);
6038
- if (hasRemote) {
6039
- try {
6040
- await this.git.pushWithUpstream(remoteName, stateBranch);
6041
- } catch (pushError) {
6042
- const pushErrorMsg = pushError instanceof Error ? pushError.message : String(pushError);
6043
- const isRemoteError = pushErrorMsg.includes("does not appear to be") || pushErrorMsg.includes("Could not read from remote") || pushErrorMsg.includes("repository not found");
6044
- if (!isRemoteError) {
6045
- throw new Error(`Failed to push state branch to remote: ${pushErrorMsg}`);
6046
- }
6047
- logger6.info(`Remote '${remoteName}' not reachable, gitgov-state branch created locally only`);
6048
- }
6049
- } else {
6050
- logger6.info(`No remote '${remoteName}' configured, gitgov-state branch created locally only`);
6051
- }
6052
- await this.git.checkoutBranch(currentBranch);
6053
- } catch (error) {
6054
- try {
6055
- await this.git.checkoutBranch(currentBranch);
6056
- } catch {
6057
- }
6058
- throw error;
6059
- }
6060
- }
6061
- /** Returns pending local changes not yet synced (delegates to calculateStateDelta) */
6062
- async getPendingChanges() {
6063
- try {
6064
- return await this.calculateStateDelta("HEAD");
6065
- } catch {
6066
- return [];
6067
- }
6068
- }
6069
- /**
6070
- * Calculates the file delta in .gitgov/ between the current branch and gitgov-state.
6071
- *
6072
- * [EARS-A5]
6073
- */
6074
- async calculateStateDelta(sourceBranch) {
6075
- const stateBranch = await this.getStateBranchName();
6076
- if (!stateBranch) {
6077
- throw new SyncStateError("Failed to get state branch name");
6078
- }
6079
- try {
6080
- const changedFiles = await this.git.getChangedFiles(
6081
- stateBranch,
6082
- sourceBranch,
6083
- ".gitgov/"
6084
- );
6085
- return changedFiles.map((file) => ({
6086
- status: file.status,
6087
- file: file.file
6088
- }));
6089
- } catch (error) {
6090
- throw new SyncStateError(
6091
- `Failed to calculate state delta: ${error.message}`
6092
- );
6093
- }
6094
- }
6095
- /**
6096
- * [EARS-B23] Detect file-level conflicts and identify remote-only changes.
6097
- *
6098
- * A conflict exists when:
6099
- * 1. A file was modified by the remote during implicit pull
6100
- * 2. AND the LOCAL USER also modified that same file (content in tempDir differs from what was in git before pull)
6101
- *
6102
- * This catches conflicts that git rebase can't detect because we copy files AFTER the pull.
6103
- *
6104
- * @param tempDir - Directory containing local .gitgov/ files (preserved before checkout)
6105
- * @param repoRoot - Repository root path
6106
- /**
6107
- * Checks if a rebase is in progress.
6108
- *
6109
- * [EARS-D6]
6110
- */
6111
- async isRebaseInProgress() {
6112
- return await this.git.isRebaseInProgress();
6113
- }
6114
- /**
6115
- * Checks for absence of conflict markers in specified files.
6116
- * Returns list of files that still have markers.
6117
- *
6118
- * [EARS-D7]
6119
- */
6120
- async checkConflictMarkers(filePaths) {
6121
- const repoRoot = await this.git.getRepoRoot();
6122
- const filesWithMarkers = [];
6123
- for (const filePath of filePaths) {
6124
- try {
6125
- const fullPath = join(repoRoot, filePath);
6126
- if (!existsSync(fullPath)) {
6127
- continue;
6128
- }
6129
- const content = readFileSync(fullPath, "utf-8");
6130
- const hasMarkers = content.includes("<<<<<<<") || content.includes("=======") || content.includes(">>>>>>>");
6131
- if (hasMarkers) {
6132
- filesWithMarkers.push(filePath);
6133
- }
6134
- } catch {
6135
- continue;
6136
- }
6137
- }
6138
- return filesWithMarkers;
6139
- }
6140
- /**
6141
- * Gets the diff of conflicted files for manual analysis.
6142
- * Useful so the actor can analyze conflicted changes before resolving.
6143
- *
6144
- * [EARS-E8]
6145
- */
6146
- async getConflictDiff(filePaths) {
6147
- try {
6148
- let conflictedFiles;
6149
- if (filePaths && filePaths.length > 0) {
6150
- conflictedFiles = filePaths;
6151
- } else {
6152
- conflictedFiles = await this.git.getConflictedFiles();
6153
- }
6154
- if (conflictedFiles.length === 0) {
6155
- return {
6156
- files: [],
6157
- message: "No conflicted files found",
6158
- resolutionSteps: []
6159
- };
6160
- }
6161
- const repoRoot = await this.git.getRepoRoot();
6162
- const files = [];
6163
- for (const filePath of conflictedFiles) {
6164
- const fullPath = join(repoRoot, filePath);
6165
- try {
6166
- const localContent = existsSync(fullPath) ? readFileSync(fullPath, "utf-8") : "";
6167
- const remoteContent = "";
6168
- const baseContent = null;
6169
- const conflictMarkers = [];
6170
- const lines = localContent.split("\n");
6171
- lines.forEach((line, index) => {
6172
- if (line.startsWith("<<<<<<<")) {
6173
- conflictMarkers.push({ line: index + 1, marker: "<<<<<<" });
6174
- } else if (line.startsWith("=======")) {
6175
- conflictMarkers.push({ line: index + 1, marker: "=======" });
6176
- } else if (line.startsWith(">>>>>>>")) {
6177
- conflictMarkers.push({ line: index + 1, marker: ">>>>>>>" });
6178
- }
6179
- });
6180
- const fileDiff = {
6181
- filePath,
6182
- localContent,
6183
- remoteContent,
6184
- baseContent
6185
- };
6186
- if (conflictMarkers.length > 0) {
6187
- fileDiff.conflictMarkers = conflictMarkers;
6188
- }
6189
- files.push(fileDiff);
6190
- } catch {
6191
- continue;
6192
- }
6193
- }
6194
- return {
6195
- files,
6196
- message: `${files.length} file(s) in conflict`,
6197
- resolutionSteps: [
6198
- "1. Review the conflict diff for each file",
6199
- "2. Manually edit conflicted files to resolve conflicts",
6200
- "3. Remove all conflict markers (<<<<<<<, =======, >>>>>>>)",
6201
- "4. Run 'gitgov sync resolve' to complete the resolution"
6202
- ]
6203
- };
6204
- } catch (error) {
6205
- throw new SyncStateError(
6206
- `Failed to get conflict diff: ${error.message}`
6207
- );
6208
- }
6209
- }
6210
- /**
6211
- * Verifies integrity of previous resolutions in gitgov-state history.
6212
- * Returns list of violations if any exist.
6213
- *
6214
- * [EARS-E1, EARS-E2, EARS-E3]
6215
- */
6216
- async verifyResolutionIntegrity() {
6217
- const stateBranch = await this.getStateBranchName();
6218
- if (!stateBranch) {
6219
- throw new SyncStateError("Failed to get state branch name");
6220
- }
6221
- const violations = [];
6222
- try {
6223
- const branchExists = await this.git.branchExists(stateBranch);
6224
- if (!branchExists) {
6225
- return violations;
6226
- }
6227
- let commits;
6228
- try {
6229
- commits = await this.git.getCommitHistory(stateBranch, {
6230
- maxCount: 1e3
6231
- // Analyze last 1000 commits
6232
- });
6233
- } catch (error) {
6234
- return violations;
6235
- }
6236
- for (let i = 0; i < commits.length; i++) {
6237
- const commit = commits[i];
6238
- if (!commit) continue;
6239
- const message = commit.message.toLowerCase();
6240
- if (message.startsWith("resolution:")) {
6241
- continue;
6242
- }
6243
- if (message.startsWith("sync:")) {
6244
- continue;
6245
- }
6246
- const isExplicitRebaseCommit = message.includes("rebase") || message.includes("pick ");
6247
- if (isExplicitRebaseCommit) {
6248
- const nextCommit = commits[i + 1];
6249
- const isResolutionNext = nextCommit && nextCommit.message.toLowerCase().startsWith("resolution:");
6250
- if (!isResolutionNext) {
6251
- violations.push({
6252
- rebaseCommitHash: commit.hash,
6253
- commitMessage: commit.message,
6254
- timestamp: commit.date,
6255
- author: commit.author
6256
- });
6257
- }
6258
- }
6259
- }
6260
- return violations;
6261
- } catch (error) {
6262
- return violations;
6263
- }
6264
- }
6265
- /**
6266
- * Complete audit of gitgov-state status.
6267
- * Verifies integrity of resolutions, signatures in Records, checksums and expected files.
6268
- *
6269
- * [EARS-E4, EARS-E5, EARS-E6, EARS-E7]
6270
- */
6271
- async auditState(options = {}) {
6272
- const scope = options.scope ?? "all";
6273
- const verifySignatures2 = options.verifySignatures ?? true;
6274
- const verifyChecksums = options.verifyChecksums ?? true;
6275
- const report = {
6276
- passed: true,
6277
- scope,
6278
- totalCommits: 0,
6279
- rebaseCommits: 0,
6280
- resolutionCommits: 0,
6281
- integrityViolations: [],
6282
- summary: ""
6283
- };
6284
- try {
6285
- const integrityViolations = await this.verifyResolutionIntegrity();
6286
- report.integrityViolations = integrityViolations;
6287
- if (integrityViolations.length > 0) {
6288
- report.passed = false;
6289
- }
6290
- const stateBranch = await this.getStateBranchName();
6291
- const branchExists = await this.git.branchExists(stateBranch);
6292
- if (branchExists) {
6293
- try {
6294
- const commits = await this.git.getCommitHistory(stateBranch, {
6295
- maxCount: 1e3
6296
- });
6297
- report.totalCommits = commits.length;
6298
- report.rebaseCommits = commits.filter(
6299
- (c) => c.message.toLowerCase().includes("rebase")
6300
- ).length;
6301
- report.resolutionCommits = commits.filter(
6302
- (c) => c.message.toLowerCase().startsWith("resolution:")
6303
- ).length;
6304
- } catch {
6305
- report.totalCommits = 0;
6306
- report.rebaseCommits = 0;
6307
- report.resolutionCommits = 0;
6308
- }
6309
- }
6310
- if (verifySignatures2 || verifyChecksums) {
6311
- const lintReport = await this.lint.lint({
6312
- validateChecksums: verifyChecksums,
6313
- validateSignatures: verifySignatures2,
6314
- validateReferences: false,
6315
- concurrent: true
6316
- });
6317
- report.lintReport = lintReport;
6318
- if (lintReport.summary.errors > 0) {
6319
- report.passed = false;
6320
- }
6321
- }
6322
- const lintErrorCount = report.lintReport?.summary.errors || 0;
6323
- const violationCount = report.integrityViolations.length + lintErrorCount;
6324
- report.summary = report.passed ? `Audit passed. No violations found (scope: ${scope}).` : `Audit failed. Found ${violationCount} violation(s): ${report.integrityViolations.length} integrity + ${lintErrorCount} structural (scope: ${scope}).`;
6325
- return report;
6326
- } catch (error) {
6327
- throw new SyncStateError(
6328
- `Failed to audit state: ${error.message}`
6329
- );
6330
- }
6331
- }
6332
- /**
6333
- * Publishes local state changes to gitgov-state.
6334
- * Implements 3 phases: verification, reconciliation, publication.
6335
- *
6336
- * [EARS-B1 through EARS-B7]
6337
- */
6338
- async pushState(options) {
6339
- const { actorId, dryRun = false } = options;
6340
- const stateBranch = await this.getStateBranchName();
6341
- if (!stateBranch) {
6342
- throw new SyncStateError("Failed to get state branch name");
6343
- }
6344
- let sourceBranch = options.sourceBranch;
6345
- const log = (msg) => logger6.debug(`[pushState] ${msg}`);
6346
- const result = {
6347
- success: false,
6348
- filesSynced: 0,
6349
- sourceBranch: "",
6350
- commitHash: null,
6351
- commitMessage: null,
6352
- conflictDetected: false
6353
- };
6354
- let stashHash = null;
6355
- let savedBranch = sourceBranch || "";
6356
- try {
6357
- log("=== STARTING pushState ===");
6358
- if (!sourceBranch) {
6359
- sourceBranch = await this.git.getCurrentBranch();
6360
- log(`Got current branch: ${sourceBranch}`);
6361
- }
6362
- result.sourceBranch = sourceBranch;
6363
- if (sourceBranch === stateBranch) {
6364
- log(`ERROR: Attempting to push from state branch ${stateBranch}`);
6365
- throw new PushFromStateBranchError(stateBranch);
6366
- }
6367
- log(`Pre-check passed: pushing from ${sourceBranch} to ${stateBranch}`);
6368
- const currentActor = await this.identity.getCurrentActor();
6369
- if (currentActor.id !== actorId) {
6370
- log(`ERROR: Actor identity mismatch: requested '${actorId}' but authenticated as '${currentActor.id}'`);
6371
- throw new ActorIdentityMismatchError(actorId, currentActor.id);
6372
- }
6373
- log(`Pre-check passed: actorId '${actorId}' matches authenticated identity`);
6374
- const remoteName = "origin";
6375
- const hasRemote = await this.git.isRemoteConfigured(remoteName);
6376
- if (!hasRemote) {
6377
- log(`ERROR: No remote '${remoteName}' configured`);
6378
- throw new SyncStateError(
6379
- `No remote repository configured. State sync requires a remote for multi-machine collaboration.
6380
- Add a remote with: git remote add origin <url>
6381
- Then push your changes: git push -u origin ${sourceBranch}`
6382
- );
6383
- }
6384
- log(`Pre-check passed: remote '${remoteName}' configured`);
6385
- const hasCommits = await this.git.branchExists(sourceBranch);
6386
- if (!hasCommits) {
6387
- log(`ERROR: Branch '${sourceBranch}' has no commits`);
6388
- throw new SyncStateError(
6389
- `Cannot sync: branch '${sourceBranch}' has no commits. Please create an initial commit first (e.g., 'git commit --allow-empty -m "Initial commit"').`
6390
- );
6391
- }
6392
- log(`Pre-check passed: branch '${sourceBranch}' has commits`);
6393
- log("Phase 0: Starting audit...");
6394
- const auditReport = await this.auditState({ scope: "current" });
6395
- log(`Audit result: ${auditReport.passed ? "PASSED" : "FAILED"}`);
6396
- if (!auditReport.passed) {
6397
- log(`Audit violations: ${auditReport.summary}`);
6398
- const affectedFiles = [];
6399
- const detailedErrors = [];
6400
- if (auditReport.lintReport?.results) {
6401
- for (const r of auditReport.lintReport.results) {
6402
- if (r.level === "error") {
6403
- if (!affectedFiles.includes(r.filePath)) {
6404
- affectedFiles.push(r.filePath);
6405
- }
6406
- detailedErrors.push(` \u2022 ${r.filePath}: [${r.validator}] ${r.message}`);
6407
- }
6408
- }
6409
- }
6410
- for (const v of auditReport.integrityViolations) {
6411
- detailedErrors.push(` \u2022 Commit ${v.rebaseCommitHash.slice(0, 8)}: ${v.commitMessage} (by ${v.author})`);
6412
- }
6413
- const detailSection = detailedErrors.length > 0 ? `
6414
-
6415
- Details:
6416
- ${detailedErrors.join("\n")}` : "";
6417
- result.conflictDetected = true;
6418
- result.conflictInfo = {
6419
- type: "integrity_violation",
6420
- affectedFiles,
6421
- message: auditReport.summary + detailSection,
6422
- resolutionSteps: [
6423
- "Run 'gitgov lint --fix' to auto-fix signature/checksum issues",
6424
- "If issues persist, manually review the affected files",
6425
- "Then retry: gitgov sync push"
6426
- ]
6427
- };
6428
- result.error = "Integrity violations detected. Cannot push.";
6429
- return result;
6430
- }
6431
- log("Ensuring state branch exists...");
6432
- await this.ensureStateBranch();
6433
- log("State branch confirmed");
6434
- log("=== Phase 1: Reconciliation ===");
6435
- savedBranch = sourceBranch;
6436
- log(`Saved branch: ${savedBranch}`);
6437
- const isCurrentBranch = sourceBranch === await this.git.getCurrentBranch();
6438
- let hasUntrackedGitgovFiles = false;
6439
- let tempDir = null;
6440
- if (isCurrentBranch) {
6441
- const repoRoot2 = await this.git.getRepoRoot();
6442
- const gitgovPath = path9__default.join(repoRoot2, ".gitgov");
6443
- hasUntrackedGitgovFiles = existsSync(gitgovPath);
6444
- log(`[EARS-B10] .gitgov/ exists on filesystem: ${hasUntrackedGitgovFiles}`);
6445
- if (hasUntrackedGitgovFiles) {
6446
- log("[EARS-B10] Copying ENTIRE .gitgov/ to temp directory for preservation...");
6447
- tempDir = await promises.mkdtemp(path9__default.join(os.tmpdir(), "gitgov-sync-"));
6448
- log(`[EARS-B10] Created temp directory: ${tempDir}`);
6449
- await promises.cp(gitgovPath, tempDir, { recursive: true });
6450
- log("[EARS-B10] Entire .gitgov/ copied to temp");
6451
- }
6452
- }
6453
- log("[EARS-B10] Checking for uncommitted changes before checkout...");
6454
- const hasUncommittedBeforeCheckout = await this.git.hasUncommittedChanges();
6455
- if (hasUncommittedBeforeCheckout) {
6456
- log("[EARS-B10] Uncommitted changes detected, stashing before checkout...");
6457
- stashHash = await this.git.stash("gitgov-sync-temp-stash");
6458
- log(`[EARS-B10] Changes stashed: ${stashHash || "none"}`);
6459
- }
6460
- const restoreStashAndReturn = async (returnResult) => {
6461
- await this.git.checkoutBranch(savedBranch);
6462
- if (tempDir) {
6463
- try {
6464
- const repoRoot2 = await this.git.getRepoRoot();
6465
- const gitgovDir = path9__default.join(repoRoot2, ".gitgov");
6466
- if (returnResult.implicitPull?.hasChanges) {
6467
- log("[EARS-B21] Implicit pull detected in early return - preserving new files from gitgov-state...");
6468
- try {
6469
- await this.git.checkoutFilesFromBranch(stateBranch, [".gitgov/"]);
6470
- await execAsync("git reset HEAD .gitgov/ 2>/dev/null || true", { cwd: repoRoot2 });
6471
- log("[EARS-B21] Synced files copied from gitgov-state (unstaged)");
6472
- } catch (checkoutError) {
6473
- log(`[EARS-B21] Warning: Failed to checkout from gitgov-state: ${checkoutError}`);
6474
- }
6475
- for (const fileName of LOCAL_ONLY_FILES) {
6476
- const tempFilePath = path9__default.join(tempDir, fileName);
6477
- const destFilePath = path9__default.join(gitgovDir, fileName);
6478
- try {
6479
- await promises.access(tempFilePath);
6480
- await promises.cp(tempFilePath, destFilePath, { force: true });
6481
- log(`[EARS-B21] Restored LOCAL_ONLY_FILE: ${fileName}`);
6482
- } catch {
6483
- }
6484
- }
6485
- const restoreExcludedFilesEarly = async (dir, destDir) => {
6486
- try {
6487
- const entries = await promises.readdir(dir, { withFileTypes: true });
6488
- for (const entry of entries) {
6489
- const srcPath = path9__default.join(dir, entry.name);
6490
- const dstPath = path9__default.join(destDir, entry.name);
6491
- if (entry.isDirectory()) {
6492
- await restoreExcludedFilesEarly(srcPath, dstPath);
6493
- } else {
6494
- const isExcluded = SYNC_EXCLUDED_PATTERNS.some((pattern) => pattern.test(entry.name));
6495
- if (isExcluded) {
6496
- await promises.mkdir(path9__default.dirname(dstPath), { recursive: true });
6497
- await promises.copyFile(srcPath, dstPath);
6498
- log(`[EARS-B22] Restored excluded file (early return): ${entry.name}`);
6499
- }
6500
- }
6501
- }
6502
- } catch {
6503
- }
6504
- };
6505
- await restoreExcludedFilesEarly(tempDir, gitgovDir);
6506
- } else {
6507
- log("[EARS-B14] Restoring .gitgov/ from temp directory (early return)...");
6508
- await promises.cp(tempDir, gitgovDir, { recursive: true, force: true });
6509
- log("[EARS-B14] .gitgov/ restored from temp (early return)");
6510
- }
6511
- await promises.rm(tempDir, { recursive: true, force: true });
6512
- log("[EARS-B14] Temp directory cleaned up (early return)");
6513
- } catch (tempRestoreError) {
6514
- log(`[EARS-B14] WARNING: Failed to restore tempDir: ${tempRestoreError}`);
6515
- returnResult.error = returnResult.error ? `${returnResult.error}. Failed to restore .gitgov/ from temp.` : `Failed to restore .gitgov/ from temp. Check /tmp for gitgov-sync-* directory.`;
6516
- }
6517
- }
6518
- if (stashHash) {
6519
- try {
6520
- await this.git.stashPop();
6521
- log("[EARS-B10] Stashed changes restored");
6522
- } catch (stashError) {
6523
- log(`[EARS-B10] Failed to restore stash: ${stashError}`);
6524
- returnResult.error = returnResult.error ? `${returnResult.error}. Failed to restore stashed changes.` : "Failed to restore stashed changes. Run 'git stash pop' manually.";
6525
- }
6526
- }
6527
- if (returnResult.implicitPull?.hasChanges) {
6528
- log("[EARS-B21] Regenerating index after implicit pull (early return)...");
6529
- try {
6530
- await this.indexer.generateIndex();
6531
- returnResult.implicitPull.reindexed = true;
6532
- log("[EARS-B21] Index regenerated successfully");
6533
- } catch (indexError) {
6534
- log(`[EARS-B21] Warning: Failed to regenerate index: ${indexError}`);
6535
- returnResult.implicitPull.reindexed = false;
6536
- }
6537
- }
6538
- return returnResult;
6539
- };
6540
- log(`Checking out to ${stateBranch}...`);
6541
- await this.git.checkoutBranch(stateBranch);
6542
- log(`Now on branch: ${await this.git.getCurrentBranch()}`);
6543
- let filesBeforeChanges = /* @__PURE__ */ new Set();
6544
- try {
6545
- const repoRoot2 = await this.git.getRepoRoot();
6546
- const { stdout: filesOutput } = await execAsync(
6547
- `git ls-files ".gitgov" 2>/dev/null || true`,
6548
- { cwd: repoRoot2 }
6549
- );
6550
- filesBeforeChanges = new Set(filesOutput.trim().split("\n").filter((f) => f && shouldSyncFile(f)));
6551
- log(`[EARS-B19] Files in gitgov-state before changes: ${filesBeforeChanges.size}`);
6552
- } catch {
6553
- }
6554
- log("=== Phase 2: Publication ===");
6555
- log("Checking if .gitgov/ exists in gitgov-state...");
6556
- let isFirstPush = false;
6557
- try {
6558
- const repoRoot2 = await this.git.getRepoRoot();
6559
- const { stdout } = await execAsync(
6560
- `git ls-tree -d ${stateBranch} .gitgov`,
6561
- { cwd: repoRoot2 }
6562
- );
6563
- isFirstPush = !stdout.trim();
6564
- log(`First push detected: ${isFirstPush}`);
6565
- } catch (error) {
6566
- log("Error checking .gitgov/ existence, assuming first push");
6567
- isFirstPush = true;
6568
- }
6569
- let delta = [];
6570
- if (!isFirstPush) {
6571
- log("Calculating state delta...");
6572
- delta = await this.calculateStateDelta(sourceBranch);
6573
- log(`Delta: ${delta.length} file(s) changed`);
6574
- if (delta.length === 0) {
6575
- log("No changes detected, returning without commit");
6576
- result.success = true;
6577
- result.filesSynced = 0;
6578
- return await restoreStashAndReturn(result);
6579
- }
6580
- } else {
6581
- log("First push: will copy all whitelisted files");
6582
- }
6583
- log(`Copying syncable .gitgov/ files from ${sourceBranch}...`);
6584
- log(`Sync directories: ${SYNC_DIRECTORIES.join(", ")}`);
6585
- log(`Sync root files: ${SYNC_ROOT_FILES.join(", ")}`);
6586
- const repoRoot = await this.git.getRepoRoot();
6587
- if (tempDir) {
6588
- log("[EARS-B10] Copying ONLY syncable files from temp directory...");
6589
- const gitgovDir = path9__default.join(repoRoot, ".gitgov");
6590
- await promises.mkdir(gitgovDir, { recursive: true });
6591
- log(`[EARS-B10] Ensured .gitgov/ directory exists: ${gitgovDir}`);
6592
- const copiedCount = await copySyncableFiles(tempDir, gitgovDir, log);
6593
- log(`[EARS-B10] Syncable files copy complete: ${copiedCount} files copied`);
6594
- } else {
6595
- log("Copying syncable files from git...");
6596
- const existingPaths = [];
6597
- for (const dirName of SYNC_DIRECTORIES) {
6598
- const fullPath = `.gitgov/${dirName}`;
6599
- try {
6600
- const { stdout } = await execAsync(
6601
- `git ls-tree -r ${sourceBranch} -- ${fullPath}`,
6602
- { cwd: repoRoot }
6603
- );
6604
- const lines = stdout.trim().split("\n").filter((l) => l);
6605
- for (const line of lines) {
6606
- const parts = line.split(" ");
6607
- const filePath = parts[1];
6608
- if (filePath && shouldSyncFile(filePath)) {
6609
- existingPaths.push(filePath);
6610
- } else if (filePath) {
6611
- log(`Skipped (not syncable): ${filePath}`);
6612
- }
6613
- }
6614
- } catch {
6615
- log(`Directory ${dirName} does not exist in ${sourceBranch}, skipping`);
6616
- }
6617
- }
6618
- for (const fileName of SYNC_ROOT_FILES) {
6619
- const fullPath = `.gitgov/${fileName}`;
6620
- try {
6621
- const { stdout } = await execAsync(
6622
- `git ls-tree ${sourceBranch} -- ${fullPath}`,
6623
- { cwd: repoRoot }
6624
- );
6625
- if (stdout.trim()) {
6626
- existingPaths.push(fullPath);
6627
- }
6628
- } catch {
6629
- log(`File ${fileName} does not exist in ${sourceBranch}, skipping`);
6630
- }
6631
- }
6632
- log(`Syncable paths found: ${existingPaths.length}`);
6633
- if (existingPaths.length === 0) {
6634
- log("No syncable files to sync, aborting");
6635
- result.success = true;
6636
- result.filesSynced = 0;
6637
- return await restoreStashAndReturn(result);
6638
- }
6639
- await this.git.checkoutFilesFromBranch(sourceBranch, existingPaths);
6640
- log("Syncable files checked out successfully");
6641
- }
6642
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6643
- if (isFirstPush) {
6644
- await this.git.add([".gitgov"], { force: true });
6645
- const repoRoot2 = await this.git.getRepoRoot();
6646
- try {
6647
- const { stdout } = await execAsync(
6648
- "git diff --cached --name-status",
6649
- { cwd: repoRoot2 }
6650
- );
6651
- const lines = stdout.trim().split("\n").filter((l) => l);
6652
- delta = lines.map((line) => {
6653
- const [status, file] = line.split(" ");
6654
- if (!file) return null;
6655
- return {
6656
- status,
6657
- file
6658
- };
6659
- }).filter((item) => item !== null);
6660
- log(`First push delta calculated: ${delta.length} file(s)`);
6661
- } catch (error) {
6662
- log("Error calculating first push delta, using empty delta");
6663
- delta = [];
6664
- }
6665
- }
6666
- const commitMessage = `sync: ${isFirstPush ? "Initial state" : "Publish state"} from ${sourceBranch}
6667
-
6668
- Actor: ${actorId}
6669
- Timestamp: ${timestamp}
6670
- Files: ${delta.length} file(s) ${isFirstPush ? "synced (initial)" : "changed"}
6671
-
6672
- ` + delta.map((d) => `${d.status} ${d.file}`).join("\n");
6673
- result.commitMessage = commitMessage;
6674
- if (!dryRun) {
6675
- if (!isFirstPush) {
6676
- log("Staging all .gitgov/ files before cleanup...");
6677
- await this.git.add([".gitgov"], { force: true });
6678
- log("All files staged, proceeding to cleanup");
6679
- }
6680
- log("[EARS-B14] Scanning for non-syncable files in gitgov-state...");
6681
- try {
6682
- const { stdout: trackedFiles } = await execAsync(
6683
- `git ls-files ".gitgov" 2>/dev/null || true`,
6684
- { cwd: repoRoot }
6685
- );
6686
- const allTrackedFiles = trackedFiles.trim().split("\n").filter((f) => f);
6687
- log(`[EARS-B14] Found ${allTrackedFiles.length} staged/tracked files in .gitgov/`);
6688
- for (const trackedFile of allTrackedFiles) {
6689
- if (!shouldSyncFile(trackedFile)) {
6690
- try {
6691
- await execAsync(`git rm -f "${trackedFile}"`, { cwd: repoRoot });
6692
- log(`[EARS-B14] Removed non-syncable file: ${trackedFile}`);
6693
- } catch {
6694
- }
6695
- }
6696
- }
6697
- } catch {
6698
- }
6699
- log("[EARS-B14] Non-syncable files cleanup complete");
6700
- log("[EARS-B19] Checking for deleted files to sync...");
6701
- try {
6702
- const sourceFiles = /* @__PURE__ */ new Set();
6703
- const findSourceFiles = async (dir, prefix = ".gitgov") => {
6704
- try {
6705
- const entries = await promises.readdir(dir, { withFileTypes: true });
6706
- for (const entry of entries) {
6707
- const fullPath = path9__default.join(dir, entry.name);
6708
- const relativePath = `${prefix}/${entry.name}`;
6709
- if (entry.isDirectory()) {
6710
- await findSourceFiles(fullPath, relativePath);
6711
- } else if (shouldSyncFile(relativePath)) {
6712
- sourceFiles.add(relativePath);
6713
- }
6714
- }
6715
- } catch {
6716
- }
6717
- };
6718
- const sourceDir = tempDir || path9__default.join(repoRoot, ".gitgov");
6719
- await findSourceFiles(sourceDir);
6720
- log(`[EARS-B19] Found ${sourceFiles.size} syncable files in source (user's local state)`);
6721
- log(`[EARS-B19] Files that existed before changes: ${filesBeforeChanges.size}`);
6722
- let deletedCount = 0;
6723
- for (const fileBeforeChange of filesBeforeChanges) {
6724
- if (!sourceFiles.has(fileBeforeChange)) {
6725
- try {
6726
- await execAsync(`git rm -f "${fileBeforeChange}"`, { cwd: repoRoot });
6727
- log(`[EARS-B19] Deleted (user removed): ${fileBeforeChange}`);
6728
- deletedCount++;
6729
- } catch {
6730
- }
6731
- }
6732
- }
6733
- log(`[EARS-B19] Deleted ${deletedCount} files that user removed locally`);
6734
- } catch (err) {
6735
- log(`[EARS-B19] Warning: Failed to sync deleted files: ${err}`);
6736
- }
6737
- const hasStaged = await this.git.hasUncommittedChanges();
6738
- log(`Has staged changes: ${hasStaged}`);
6739
- if (!hasStaged) {
6740
- log("No staged changes detected, returning without commit");
6741
- result.success = true;
6742
- result.filesSynced = 0;
6743
- return await restoreStashAndReturn(result);
6744
- }
6745
- log("Creating local commit...");
6746
- try {
6747
- const commitHash = await this.git.commit(commitMessage);
6748
- log(`Local commit created: ${commitHash}`);
6749
- result.commitHash = commitHash;
6750
- } catch (commitError) {
6751
- const errorMsg = commitError instanceof Error ? commitError.message : String(commitError);
6752
- const stdout = commitError.stdout || "";
6753
- const stderr = commitError.stderr || "";
6754
- log(`Commit attempt output - stdout: ${stdout}, stderr: ${stderr}`);
6755
- const isNothingToCommit = stdout.includes("nothing to commit") || stderr.includes("nothing to commit") || stdout.includes("nothing added to commit") || stderr.includes("nothing added to commit");
6756
- if (isNothingToCommit) {
6757
- log("Nothing to commit - files are identical to gitgov-state HEAD");
6758
- result.success = true;
6759
- result.filesSynced = 0;
6760
- return await restoreStashAndReturn(result);
6761
- }
6762
- log(`ERROR: Commit failed: ${errorMsg}`);
6763
- log(`ERROR: Git stderr: ${stderr}`);
6764
- throw new Error(`Failed to create commit: ${errorMsg} | stderr: ${stderr}`);
6765
- }
6766
- log("=== Phase 3: Reconcile with Remote (Git-Native) ===");
6767
- let hashBeforePull = null;
6768
- try {
6769
- const { stdout: beforeHash } = await execAsync(`git rev-parse HEAD`, { cwd: repoRoot });
6770
- hashBeforePull = beforeHash.trim();
6771
- log(`Hash before pull: ${hashBeforePull}`);
6772
- } catch {
6773
- }
6774
- log("Attempting git pull --rebase origin gitgov-state...");
6775
- try {
6776
- await this.git.pullRebase("origin", stateBranch);
6777
- log("Pull rebase successful - no conflicts");
6778
- if (hashBeforePull) {
6779
- try {
6780
- const { stdout: afterHash } = await execAsync(`git rev-parse HEAD`, { cwd: repoRoot });
6781
- const hashAfterPull = afterHash.trim();
6782
- if (hashAfterPull !== hashBeforePull) {
6783
- const pulledChangedFiles = await this.git.getChangedFiles(hashBeforePull, hashAfterPull, ".gitgov/");
6784
- result.implicitPull = {
6785
- hasChanges: true,
6786
- filesUpdated: pulledChangedFiles.length,
6787
- reindexed: false
6788
- // Will be set to true after actual reindex
6789
- };
6790
- log(`[EARS-B16] Implicit pull: ${pulledChangedFiles.length} files from remote were rebased`);
6791
- }
6792
- } catch (e) {
6793
- log(`[EARS-B16] Could not capture implicit pull details: ${e}`);
6794
- }
6795
- }
6796
- } catch (pullError) {
6797
- const errorMsg = pullError instanceof Error ? pullError.message : String(pullError);
6798
- log(`Pull rebase result: ${errorMsg}`);
6799
- const isAlreadyUpToDate = errorMsg.includes("up to date") || errorMsg.includes("up-to-date");
6800
- const isNoRemote = errorMsg.includes("does not appear to be") || errorMsg.includes("Could not read from remote");
6801
- const isNoUpstream = errorMsg.includes("no tracking information") || errorMsg.includes("There is no tracking information");
6802
- if (isAlreadyUpToDate || isNoRemote || isNoUpstream) {
6803
- log("Pull not needed or no remote - continuing to push");
6804
- } else {
6805
- const isRebaseInProgress = await this.isRebaseInProgress();
6806
- const conflictedFiles = await this.git.getConflictedFiles();
6807
- if (isRebaseInProgress || conflictedFiles.length > 0) {
6808
- log(`[GIT-NATIVE] Conflict detected! Files: ${conflictedFiles.join(", ")}`);
6809
- result.conflictDetected = true;
6810
- const fileWord = conflictedFiles.length === 1 ? "file" : "files";
6811
- const stageCommand = conflictedFiles.length === 1 ? `git add ${conflictedFiles[0]}` : "git add .gitgov/";
6812
- result.conflictInfo = {
6813
- type: "rebase_conflict",
6814
- affectedFiles: conflictedFiles,
6815
- message: "Conflict detected during sync - Git has paused the rebase for manual resolution",
6816
- resolutionSteps: [
6817
- `1. Edit the conflicted ${fileWord} to resolve conflicts (remove <<<<<<, ======, >>>>>> markers)`,
6818
- `2. Stage resolved ${fileWord}: ${stageCommand}`,
6819
- "3. Complete sync: gitgov sync resolve --reason 'your reason'",
6820
- "(This will continue the rebase, re-sign the record, and return you to your original branch)"
6821
- ]
6822
- };
6823
- result.error = `Conflict detected: ${conflictedFiles.length} file(s) need manual resolution. Use 'git status' to see details.`;
6824
- if (stashHash) {
6825
- try {
6826
- await this.git.checkoutBranch(sourceBranch);
6827
- await execAsync("git stash pop", { cwd: repoRoot });
6828
- await this.git.checkoutBranch(stateBranch);
6829
- log("Restored stash to original branch during conflict");
6830
- } catch (stashErr) {
6831
- log(`Warning: Could not restore stash: ${stashErr}`);
6832
- }
6833
- }
6834
- if (tempDir) {
6835
- log("Restoring local files (.key, .session.json, etc.) for conflict resolution...");
6836
- const gitgovInState = path9__default.join(repoRoot, ".gitgov");
6837
- for (const fileName of LOCAL_ONLY_FILES) {
6838
- const srcPath = path9__default.join(tempDir, fileName);
6839
- const destPath = path9__default.join(gitgovInState, fileName);
6840
- try {
6841
- await promises.access(srcPath);
6842
- await promises.cp(srcPath, destPath, { force: true });
6843
- log(`Restored LOCAL_ONLY_FILE for conflict resolution: ${fileName}`);
6844
- } catch {
6845
- }
6846
- }
6847
- const restoreExcluded = async (srcDir, destDir) => {
6848
- try {
6849
- const entries = await promises.readdir(srcDir, { withFileTypes: true });
6850
- for (const entry of entries) {
6851
- const srcPath = path9__default.join(srcDir, entry.name);
6852
- const dstPath = path9__default.join(destDir, entry.name);
6853
- if (entry.isDirectory()) {
6854
- await restoreExcluded(srcPath, dstPath);
6855
- } else {
6856
- const isExcluded = SYNC_EXCLUDED_PATTERNS.some((pattern) => pattern.test(entry.name));
6857
- if (isExcluded) {
6858
- await promises.mkdir(path9__default.dirname(dstPath), { recursive: true });
6859
- await promises.copyFile(srcPath, dstPath);
6860
- log(`Restored EXCLUDED file for conflict resolution: ${entry.name}`);
6861
- }
6862
- }
6863
- }
6864
- } catch {
6865
- }
6866
- };
6867
- await restoreExcluded(tempDir, gitgovInState);
6868
- log("Local files restored for conflict resolution");
6869
- }
6870
- return result;
6871
- }
6872
- throw pullError;
6873
- }
6874
- }
6875
- log("=== Phase 4: Push to Remote ===");
6876
- log("Pushing to remote...");
6877
- try {
6878
- await this.git.push("origin", stateBranch);
6879
- log("Push successful");
6880
- } catch (pushError) {
6881
- const pushErrorMsg = pushError instanceof Error ? pushError.message : String(pushError);
6882
- log(`Push failed: ${pushErrorMsg}`);
6883
- const isNoRemote = pushErrorMsg.includes("does not appear to be") || pushErrorMsg.includes("Could not read from remote");
6884
- if (!isNoRemote) {
6885
- log("ERROR: Push failed with non-remote error");
6886
- throw pushError;
6887
- }
6888
- log("Push failed due to no remote, continuing (local commit succeeded)");
6889
- }
6890
- }
6891
- log(`Returning to ${savedBranch}...`);
6892
- await this.git.checkoutBranch(savedBranch);
6893
- log(`Back on ${await this.git.getCurrentBranch()}`);
6894
- if (stashHash) {
6895
- log("[EARS-B10] Restoring stashed changes...");
6896
- try {
6897
- await this.git.stashPop();
6898
- log("[EARS-B10] Stashed changes restored successfully");
6899
- } catch (stashError) {
6900
- log(`[EARS-B10] Warning: Failed to restore stashed changes: ${stashError}`);
6901
- result.error = `Push succeeded but failed to restore stashed changes. Run 'git stash pop' manually. Error: ${stashError}`;
6902
- }
6903
- }
6904
- if (tempDir) {
6905
- const repoRoot2 = await this.git.getRepoRoot();
6906
- const gitgovDir = path9__default.join(repoRoot2, ".gitgov");
6907
- if (result.implicitPull?.hasChanges) {
6908
- log("[EARS-B18] Implicit pull detected - copying synced files from gitgov-state first...");
6909
- try {
6910
- await this.git.checkoutFilesFromBranch(stateBranch, [".gitgov/"]);
6911
- await execAsync("git reset HEAD .gitgov/ 2>/dev/null || true", { cwd: repoRoot2 });
6912
- log("[EARS-B18] Synced files copied from gitgov-state to work branch (unstaged)");
6913
- } catch (checkoutError) {
6914
- log(`[EARS-B18] Warning: Failed to checkout from gitgov-state: ${checkoutError}`);
6915
- await promises.cp(tempDir, gitgovDir, { recursive: true, force: true });
6916
- log("[EARS-B18] Fallback: Entire .gitgov/ restored from temp");
6917
- }
6918
- log("[EARS-B18] Restoring local-only files from temp directory...");
6919
- for (const fileName of LOCAL_ONLY_FILES) {
6920
- const tempFilePath = path9__default.join(tempDir, fileName);
6921
- const destFilePath = path9__default.join(gitgovDir, fileName);
6922
- try {
6923
- await promises.access(tempFilePath);
6924
- await promises.cp(tempFilePath, destFilePath, { force: true });
6925
- log(`[EARS-B18] Restored LOCAL_ONLY_FILE: ${fileName}`);
6926
- } catch {
6927
- }
6928
- }
6929
- const restoreExcludedFiles = async (dir, destDir) => {
6930
- try {
6931
- const entries = await promises.readdir(dir, { withFileTypes: true });
6932
- for (const entry of entries) {
6933
- const srcPath = path9__default.join(dir, entry.name);
6934
- const dstPath = path9__default.join(destDir, entry.name);
6935
- if (entry.isDirectory()) {
6936
- await restoreExcludedFiles(srcPath, dstPath);
6937
- } else {
6938
- const isExcluded = SYNC_EXCLUDED_PATTERNS.some((pattern) => pattern.test(entry.name));
6939
- if (isExcluded) {
6940
- await promises.mkdir(path9__default.dirname(dstPath), { recursive: true });
6941
- await promises.copyFile(srcPath, dstPath);
6942
- log(`[EARS-B22] Restored excluded file: ${entry.name}`);
6943
- }
6944
- }
6945
- }
6946
- } catch {
6947
- }
6948
- };
6949
- await restoreExcludedFiles(tempDir, gitgovDir);
6950
- log("[EARS-B22] Local-only and excluded files restored from temp");
6951
- } else {
6952
- log("[EARS-B10] Restoring ENTIRE .gitgov/ from temp directory to working tree...");
6953
- await promises.cp(tempDir, gitgovDir, { recursive: true, force: true });
6954
- log("[EARS-B10] Entire .gitgov/ restored from temp");
6955
- }
6956
- log("[EARS-B10] Cleaning up temp directory...");
6957
- try {
6958
- await promises.rm(tempDir, { recursive: true, force: true });
6959
- log("[EARS-B10] Temp directory cleaned up");
6960
- } catch (cleanupError) {
6961
- log(`[EARS-B10] Warning: Failed to cleanup temp directory: ${cleanupError}`);
6962
- }
6963
- }
6964
- if (result.implicitPull?.hasChanges) {
6965
- log("[EARS-B16] Regenerating index after implicit pull...");
6966
- try {
6967
- await this.indexer.generateIndex();
6968
- result.implicitPull.reindexed = true;
6969
- log("[EARS-B16] Index regenerated successfully after implicit pull");
6970
- } catch (indexError) {
6971
- log(`[EARS-B16] Warning: Failed to regenerate index after implicit pull: ${indexError}`);
6972
- result.implicitPull.reindexed = false;
6973
- }
6974
- }
6975
- result.success = true;
6976
- result.filesSynced = delta.length;
6977
- log(`=== pushState COMPLETED SUCCESSFULLY: ${delta.length} files synced ===`);
6978
- return result;
6979
- } catch (error) {
6980
- log(`=== pushState FAILED: ${error.message} ===`);
6981
- try {
6982
- const currentBranch = await this.git.getCurrentBranch();
6983
- if (currentBranch !== savedBranch && savedBranch) {
6984
- log(`[EARS-B10] Restoring original branch: ${savedBranch}...`);
6985
- await this.git.checkoutBranch(savedBranch);
6986
- log(`[EARS-B10] Restored to ${savedBranch}`);
6987
- }
6988
- } catch (branchError) {
6989
- log(`[EARS-B10] Failed to restore original branch: ${branchError}`);
6990
- }
6991
- if (stashHash) {
6992
- log("[EARS-B10] Attempting to restore stashed changes after error...");
6993
- try {
6994
- await this.git.stashPop();
6995
- log("[EARS-B10] Stashed changes restored after error");
6996
- } catch (stashError) {
6997
- log(`[EARS-B10] Failed to restore stashed changes after error: ${stashError}`);
6998
- const originalError = error.message;
6999
- error.message = `${originalError}. Additionally, failed to restore stashed changes. Run 'git stash pop' manually.`;
7000
- }
7001
- }
7002
- if (error instanceof PushFromStateBranchError || error instanceof UncommittedChangesError) {
7003
- throw error;
7004
- }
7005
- result.error = error.message;
7006
- return result;
7007
- }
7008
- }
7009
- /**
7010
- * Pulls remote changes from gitgov-state to the local environment.
7011
- * Includes automatic re-indexing if there are new changes.
7012
- *
7013
- * [EARS-C1 through EARS-C4]
7014
- * [EARS-C5] Requires remote to be configured (pull without remote makes no sense)
7015
- */
7016
- async pullState(options = {}) {
7017
- const { forceReindex = false, force = false } = options;
7018
- const stateBranch = await this.getStateBranchName();
7019
- if (!stateBranch) {
7020
- throw new SyncStateError("Failed to get state branch name");
7021
- }
7022
- const log = (msg) => logger6.debug(`[pullState] ${msg}`);
7023
- const result = {
7024
- success: false,
7025
- hasChanges: false,
7026
- filesUpdated: 0,
7027
- reindexed: false,
7028
- conflictDetected: false
7029
- };
7030
- try {
7031
- log("=== STARTING pullState ===");
7032
- log("Phase 0: Pre-flight checks...");
7033
- const remoteName = "origin";
7034
- const hasRemote = await this.git.isRemoteConfigured(remoteName);
7035
- if (!hasRemote) {
7036
- throw new SyncStateError(
7037
- `No remote '${remoteName}' configured. Pull requires a remote repository. Add a remote with: git remote add origin <url>`
7038
- );
7039
- }
7040
- await this.git.fetch(remoteName);
7041
- const remoteBranches = await this.git.listRemoteBranches(remoteName);
7042
- const existsRemote = remoteBranches.includes(stateBranch);
7043
- if (!existsRemote) {
7044
- const existsLocal = await this.git.branchExists(stateBranch);
7045
- if (!existsLocal) {
7046
- const repoRoot = await this.git.getRepoRoot();
7047
- const gitgovPath2 = path9__default.join(repoRoot, ".gitgov");
7048
- const gitgovExists2 = existsSync(gitgovPath2);
7049
- if (gitgovExists2) {
7050
- throw new SyncStateError(
7051
- `State branch '${stateBranch}' does not exist remotely yet. Run 'gitgov sync push' to publish your local state to the remote.`
7052
- );
7053
- } else {
7054
- throw new SyncStateError(
7055
- `State branch '${stateBranch}' does not exist locally or remotely. Run 'gitgov init' first to initialize GitGovernance, then 'gitgov sync push' to publish.`
7056
- );
7057
- }
7058
- }
7059
- result.success = true;
7060
- result.hasChanges = false;
7061
- result.filesUpdated = 0;
7062
- logger6.info(`[pullState] State branch exists locally but not remotely. Nothing to pull.`);
7063
- return result;
7064
- }
7065
- log("Pre-flight checks complete");
7066
- log("Phase 1: Setting up branches...");
7067
- const pullRepoRoot = await this.git.getRepoRoot();
7068
- await this.ensureStateBranch();
7069
- const savedBranch = await this.git.getCurrentBranch();
7070
- const savedLocalFiles = /* @__PURE__ */ new Map();
7071
- try {
7072
- for (const fileName of LOCAL_ONLY_FILES) {
7073
- const filePath = path9__default.join(pullRepoRoot, ".gitgov", fileName);
7074
- try {
7075
- const content = await promises.readFile(filePath, "utf-8");
7076
- savedLocalFiles.set(fileName, content);
7077
- log(`[EARS-C8] Saved local-only file: ${fileName}`);
7078
- } catch {
7079
- }
7080
- }
7081
- } catch (error) {
7082
- log(`[EARS-C8] Warning: Could not save local files: ${error.message}`);
7083
- }
7084
- const savedSyncableFiles = /* @__PURE__ */ new Map();
7085
- try {
7086
- const gitgovPath2 = path9__default.join(pullRepoRoot, ".gitgov");
7087
- const gitgovExists2 = await promises.access(gitgovPath2).then(() => true).catch(() => false);
7088
- if (gitgovExists2) {
7089
- const readSyncableFilesRecursive = async (dir, baseDir) => {
7090
- try {
7091
- const entries = await promises.readdir(dir, { withFileTypes: true });
7092
- for (const entry of entries) {
7093
- const fullPath = path9__default.join(dir, entry.name);
7094
- const relativePath = path9__default.relative(baseDir, fullPath);
7095
- const gitgovRelativePath = `.gitgov/${relativePath}`;
7096
- if (entry.isDirectory()) {
7097
- await readSyncableFilesRecursive(fullPath, baseDir);
7098
- } else if (shouldSyncFile(gitgovRelativePath)) {
7099
- try {
7100
- const content = await promises.readFile(fullPath, "utf-8");
7101
- savedSyncableFiles.set(gitgovRelativePath, content);
7102
- } catch {
7103
- }
7104
- }
7105
- }
7106
- } catch {
7107
- }
7108
- };
7109
- await readSyncableFilesRecursive(gitgovPath2, gitgovPath2);
7110
- log(`[EARS-C10] Saved ${savedSyncableFiles.size} syncable files before checkout for conflict detection`);
7111
- }
7112
- } catch (error) {
7113
- log(`[EARS-C10] Warning: Could not save syncable files: ${error.message}`);
7114
- }
7115
- try {
7116
- await this.git.checkoutBranch(stateBranch);
7117
- } catch (checkoutError) {
7118
- log(`[EARS-C8] Normal checkout failed, trying with force: ${checkoutError.message}`);
7119
- try {
7120
- await execAsync(`git checkout -f ${stateBranch}`, { cwd: pullRepoRoot });
7121
- log(`[EARS-C8] Force checkout successful`);
7122
- } catch (forceError) {
7123
- throw checkoutError;
7124
- }
7125
- }
7126
- try {
7127
- const { stdout } = await execAsync("git status --porcelain", { cwd: pullRepoRoot });
7128
- const lines = stdout.trim().split("\n").filter((l) => l);
7129
- const hasStagedOrModified = lines.some((line) => {
7130
- const status = line.substring(0, 2);
7131
- return status !== "??" && status.trim().length > 0;
7132
- });
7133
- if (hasStagedOrModified) {
7134
- await this.git.checkoutBranch(savedBranch);
7135
- throw new UncommittedChangesError(stateBranch);
7136
- }
7137
- } catch (error) {
7138
- if (error instanceof UncommittedChangesError) {
7139
- throw error;
7140
- }
7141
- }
7142
- log("Branch setup complete");
7143
- log("Phase 2: Pulling remote changes...");
7144
- const commitBefore = await this.git.getCommitHistory(stateBranch, {
7145
- maxCount: 1
7146
- });
7147
- const hashBefore = commitBefore[0]?.hash;
7148
- await this.git.fetch("origin");
7149
- log("[EARS-C10] Checking for local changes that would be overwritten...");
7150
- let remoteChangedFiles = [];
7151
- try {
7152
- const { stdout: remoteChanges } = await execAsync(
7153
- `git diff --name-only ${stateBranch} origin/${stateBranch} -- .gitgov/ 2>/dev/null || true`,
7154
- { cwd: pullRepoRoot }
7155
- );
7156
- remoteChangedFiles = remoteChanges.trim().split("\n").filter((f) => f && shouldSyncFile(f));
7157
- log(`[EARS-C10] Remote changed files: ${remoteChangedFiles.length} - ${remoteChangedFiles.join(", ")}`);
7158
- } catch {
7159
- log("[EARS-C10] Could not determine remote changes, continuing...");
7160
- }
7161
- let localModifiedFiles = [];
7162
- if (remoteChangedFiles.length > 0 && savedSyncableFiles.size > 0) {
7163
- try {
7164
- for (const remoteFile of remoteChangedFiles) {
7165
- const savedContent = savedSyncableFiles.get(remoteFile);
7166
- if (savedContent !== void 0) {
7167
- try {
7168
- const { stdout: gitStateContent } = await execAsync(
7169
- `git show HEAD:${remoteFile} 2>/dev/null`,
7170
- { cwd: pullRepoRoot }
7171
- );
7172
- if (savedContent !== gitStateContent) {
7173
- localModifiedFiles.push(remoteFile);
7174
- log(`[EARS-C10] Local file was modified since last sync: ${remoteFile}`);
7175
- }
7176
- } catch {
7177
- localModifiedFiles.push(remoteFile);
7178
- log(`[EARS-C10] Local file is new (not in gitgov-state): ${remoteFile}`);
7179
- }
7180
- }
7181
- }
7182
- log(`[EARS-C10] Local modified files that overlap with remote: ${localModifiedFiles.length}`);
7183
- } catch (error) {
7184
- log(`[EARS-C10] Warning: Could not check local modifications: ${error.message}`);
7185
- }
7186
- }
7187
- if (localModifiedFiles.length > 0) {
7188
- if (force) {
7189
- log(`[EARS-C11] Force flag set - will overwrite ${localModifiedFiles.length} local file(s)`);
7190
- logger6.warn(`[pullState] Force pull: overwriting local changes to ${localModifiedFiles.length} file(s)`);
7191
- result.forcedOverwrites = localModifiedFiles;
7192
- } else {
7193
- log(`[EARS-C10] CONFLICT: Local changes would be overwritten by pull`);
7194
- await this.git.checkoutBranch(savedBranch);
7195
- for (const [filePath, content] of savedSyncableFiles) {
7196
- const fullPath = path9__default.join(pullRepoRoot, filePath);
7197
- try {
7198
- await promises.mkdir(path9__default.dirname(fullPath), { recursive: true });
7199
- await promises.writeFile(fullPath, content, "utf-8");
7200
- log(`[EARS-C10] Restored syncable file: ${filePath}`);
7201
- } catch {
7202
- }
7203
- }
7204
- for (const [fileName, content] of savedLocalFiles) {
7205
- const filePath = path9__default.join(pullRepoRoot, ".gitgov", fileName);
7206
- try {
7207
- await promises.mkdir(path9__default.dirname(filePath), { recursive: true });
7208
- await promises.writeFile(filePath, content, "utf-8");
7209
- log(`[EARS-C10] Restored local-only file: ${fileName}`);
7210
- } catch {
7211
- }
7212
- }
7213
- result.success = false;
7214
- result.conflictDetected = true;
7215
- result.conflictInfo = {
7216
- type: "local_changes_conflict",
7217
- affectedFiles: localModifiedFiles,
7218
- message: `Your local changes to the following files would be overwritten by pull.
7219
- You have modified these files locally, and they were also modified remotely.
7220
- To avoid losing your changes, push first or use --force to overwrite.`,
7221
- resolutionSteps: [
7222
- "1. Run 'gitgov sync push' to push your local changes first",
7223
- " \u2192 This will trigger a rebase and let you resolve conflicts properly",
7224
- "2. Or run 'gitgov sync pull --force' to discard your local changes"
7225
- ]
7226
- };
7227
- result.error = "Aborting pull: local changes would be overwritten by remote changes";
7228
- logger6.warn(`[pullState] Aborting: local changes to ${localModifiedFiles.length} file(s) would be overwritten by pull`);
7229
- return result;
7230
- }
7231
- }
7232
- log("[EARS-C10] No conflicting local changes (or force enabled), proceeding with pull...");
7233
- try {
7234
- await this.git.pullRebase("origin", stateBranch);
7235
- } catch (error) {
7236
- const conflictedFiles = await this.git.getConflictedFiles();
7237
- if (conflictedFiles.length > 0) {
7238
- await this.git.checkoutBranch(savedBranch);
7239
- result.conflictDetected = true;
7240
- result.conflictInfo = {
7241
- type: "rebase_conflict",
7242
- affectedFiles: conflictedFiles,
7243
- message: "Conflict detected during pull",
7244
- resolutionSteps: [
7245
- "Review conflicted files",
7246
- "Manually resolve conflicts",
7247
- "Run 'gitgov sync resolve' to complete"
7248
- ]
7249
- };
7250
- result.error = "Conflict detected during pull";
7251
- return result;
7252
- }
7253
- throw error;
7254
- }
7255
- log("Pull rebase successful");
7256
- log("Phase 3: Checking for changes and re-indexing...");
7257
- const commitAfter = await this.git.getCommitHistory(stateBranch, {
7258
- maxCount: 1
7259
- });
7260
- const hashAfter = commitAfter[0]?.hash;
7261
- const hasNewChanges = hashBefore !== hashAfter;
7262
- result.hasChanges = hasNewChanges;
7263
- const indexPath = path9__default.join(pullRepoRoot, ".gitgov", "index.json");
7264
- const indexExists = await promises.access(indexPath).then(() => true).catch(() => false);
7265
- const shouldReindex = hasNewChanges || forceReindex || !indexExists;
7266
- if (shouldReindex) {
7267
- result.reindexed = true;
7268
- if (hasNewChanges && hashBefore && hashAfter) {
7269
- const changedFiles = await this.git.getChangedFiles(
7270
- hashBefore,
7271
- hashAfter,
7272
- ".gitgov/"
7273
- );
7274
- result.filesUpdated = changedFiles.length;
7275
- }
7276
- }
7277
- const gitgovPath = path9__default.join(pullRepoRoot, ".gitgov");
7278
- const gitgovExists = await promises.access(gitgovPath).then(() => true).catch(() => false);
7279
- if (gitgovExists && hasNewChanges) {
7280
- logger6.debug("[pullState] Copying .gitgov/ to filesystem for work branch access");
7281
- }
7282
- log("Phase 4: Restoring working branch...");
7283
- await this.git.checkoutBranch(savedBranch);
7284
- if (gitgovExists) {
7285
- try {
7286
- logger6.debug("[pullState] Restoring .gitgov/ to filesystem from gitgov-state (preserving local-only files)");
7287
- const pathsToCheckout = [];
7288
- for (const dirName of SYNC_DIRECTORIES) {
7289
- pathsToCheckout.push(`.gitgov/${dirName}`);
7290
- }
7291
- for (const fileName of SYNC_ROOT_FILES) {
7292
- pathsToCheckout.push(`.gitgov/${fileName}`);
7293
- }
7294
- for (const checkoutPath of pathsToCheckout) {
7295
- try {
7296
- await execAsync(`git checkout ${stateBranch} -- "${checkoutPath}"`, { cwd: pullRepoRoot });
7297
- logger6.debug(`[pullState] Checked out: ${checkoutPath}`);
7298
- } catch {
7299
- logger6.debug(`[pullState] Skipped (not in gitgov-state): ${checkoutPath}`);
7300
- }
7301
- }
7302
- try {
7303
- await execAsync("git reset HEAD .gitgov/", { cwd: pullRepoRoot });
7304
- } catch {
7305
- }
7306
- for (const [fileName, content] of savedLocalFiles) {
7307
- try {
7308
- const filePath = path9__default.join(pullRepoRoot, ".gitgov", fileName);
7309
- await promises.writeFile(filePath, content, "utf-8");
7310
- logger6.debug(`[EARS-C8] Restored local-only file: ${fileName}`);
7311
- } catch (writeError) {
7312
- logger6.warn(`[EARS-C8] Failed to restore ${fileName}: ${writeError.message}`);
7313
- }
7314
- }
7315
- logger6.debug("[pullState] .gitgov/ restored to filesystem successfully (local-only files preserved)");
7316
- } catch (error) {
7317
- logger6.warn(`[pullState] Failed to restore .gitgov/ to filesystem: ${error.message}`);
7318
- }
7319
- }
7320
- if (shouldReindex) {
7321
- logger6.info("Invoking RecordProjector.generateIndex() after pull...");
7322
- try {
7323
- await this.indexer.generateIndex();
7324
- logger6.info("Index regenerated successfully");
7325
- } catch (error) {
7326
- logger6.warn(`Failed to regenerate index: ${error.message}`);
7327
- }
7328
- }
7329
- result.success = true;
7330
- log(`=== pullState COMPLETED: ${hasNewChanges ? "new changes pulled" : "no changes"}, reindexed: ${result.reindexed} ===`);
7331
- return result;
7332
- } catch (error) {
7333
- log(`=== pullState FAILED: ${error.message} ===`);
7334
- if (error instanceof UncommittedChangesError) {
7335
- throw error;
7336
- }
7337
- result.error = error.message;
7338
- return result;
7339
- }
7340
- }
7341
- /**
7342
- * Resolves state conflicts in a governed manner (Git-Native).
7343
- *
7344
- * Git-Native Flow:
7345
- * 1. User resolves conflicts using standard Git tools (edit files, remove markers)
7346
- * 2. User stages resolved files: git add .gitgov/
7347
- * 3. User runs: gitgov sync resolve --reason "reason"
7348
- *
7349
- * This method:
7350
- * - Verifies that a rebase is in progress
7351
- * - Checks that no conflict markers remain in staged files
7352
- * - Updates resolved Records with new checksums and signatures
7353
- * - Continues the git rebase (git rebase --continue)
7354
- * - Creates a signed resolution commit
7355
- * - Regenerates the index
7356
- *
7357
- * [EARS-D1 through EARS-D7]
7358
- */
7359
- async resolveConflict(options) {
7360
- const { reason, actorId } = options;
7361
- const log = (msg) => logger6.debug(`[resolveConflict] ${msg}`);
7362
- log("=== STARTING resolveConflict (Git-Native) ===");
7363
- log("Phase 0: Verifying rebase in progress...");
7364
- const rebaseInProgress = await this.isRebaseInProgress();
7365
- if (!rebaseInProgress) {
7366
- throw new NoRebaseInProgressError();
7367
- }
7368
- const authenticatedActor = await this.identity.getCurrentActor();
7369
- if (authenticatedActor.id !== actorId) {
7370
- log(`ERROR: Actor identity mismatch: requested '${actorId}' but authenticated as '${authenticatedActor.id}'`);
7371
- throw new ActorIdentityMismatchError(actorId, authenticatedActor.id);
7372
- }
7373
- log(`Pre-check passed: actorId '${actorId}' matches authenticated identity`);
7374
- log("Conflict mode: rebase_conflict (Git-Native)");
7375
- console.log("[resolveConflict] Getting staged files...");
7376
- const allStagedFiles = await this.git.getStagedFiles();
7377
- console.log("[resolveConflict] All staged files:", allStagedFiles);
7378
- let resolvedRecords = allStagedFiles.filter(
7379
- (f) => f.startsWith(".gitgov/") && f.endsWith(".json")
7380
- );
7381
- console.log("[resolveConflict] Resolved Records (staged .gitgov/*.json):", resolvedRecords);
7382
- console.log("[resolveConflict] Checking for conflict markers...");
7383
- const filesWithMarkers = await this.checkConflictMarkers(resolvedRecords);
7384
- console.log("[resolveConflict] Files with markers:", filesWithMarkers);
7385
- if (filesWithMarkers.length > 0) {
7386
- throw new ConflictMarkersPresentError(filesWithMarkers);
7387
- }
7388
- let rebaseCommitHash = "";
7389
- console.log("[resolveConflict] Step 4: Calling git.rebaseContinue()...");
7390
- await this.git.rebaseContinue();
7391
- console.log("[resolveConflict] rebaseContinue completed successfully");
7392
- const currentBranch = await this.git.getCurrentBranch();
7393
- const rebaseCommit = await this.git.getCommitHistory(currentBranch, {
7394
- maxCount: 1
7395
- });
7396
- rebaseCommitHash = rebaseCommit[0]?.hash ?? "";
7397
- if (resolvedRecords.length === 0 && rebaseCommitHash) {
7398
- console.log("[resolveConflict] No staged files detected, getting files from rebase commit...");
7399
- const repoRoot2 = await this.git.getRepoRoot();
7400
- try {
7401
- const { stdout } = await execAsync(
7402
- `git diff-tree --no-commit-id --name-only -r ${rebaseCommitHash}`,
7403
- { cwd: repoRoot2 }
7404
- );
7405
- const commitFiles = stdout.trim().split("\n").filter((f) => f);
7406
- resolvedRecords = commitFiles.filter(
7407
- (f) => f.startsWith(".gitgov/") && f.endsWith(".json")
7408
- );
7409
- console.log("[resolveConflict] Files from rebase commit:", resolvedRecords);
7410
- } catch (e) {
7411
- console.log("[resolveConflict] Could not get files from rebase commit:", e);
7412
- }
7413
- }
7414
- console.log("[resolveConflict] Updating resolved Records with signatures...");
7415
- if (resolvedRecords.length > 0) {
7416
- const currentActor = await this.identity.getCurrentActor();
7417
- console.log("[resolveConflict] Current actor:", currentActor);
7418
- console.log("[resolveConflict] Processing", resolvedRecords.length, "resolved Records");
7419
- for (const filePath of resolvedRecords) {
7420
- console.log("[resolveConflict] Processing Record:", filePath);
7421
- try {
7422
- const repoRoot2 = await this.git.getRepoRoot();
7423
- const fullPath = join(repoRoot2, filePath);
7424
- const content = readFileSync(fullPath, "utf-8");
7425
- const record = JSON.parse(content);
7426
- if (!record.header || !record.payload) {
7427
- continue;
7428
- }
7429
- const signedRecord = await this.identity.signRecord(
7430
- record,
7431
- currentActor.id,
7432
- "resolver",
7433
- `Conflict resolved: ${reason}`
7434
- );
7435
- writeFileSync(fullPath, JSON.stringify(signedRecord, null, 2) + "\n", "utf-8");
7436
- logger6.info(`Updated Record: ${filePath} (new checksum + resolver signature)`);
7437
- console.log("[resolveConflict] Successfully updated Record:", filePath);
7438
- } catch (error) {
7439
- logger6.debug(`Skipping file ${filePath}: ${error.message}`);
7440
- console.log("[resolveConflict] Error updating Record:", filePath, error);
7441
- }
7442
- }
7443
- console.log("[resolveConflict] All Records updated, staging...");
7444
- }
7445
- console.log("[resolveConflict] Staging .gitgov/ with updated metadata...");
7446
- await this.git.add([".gitgov"], { force: true });
7447
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
7448
- const resolutionMessage = `resolution: conflict resolved by ${actorId}
7449
-
7450
- Actor: ${actorId}
7451
- Timestamp: ${timestamp}
7452
- Reason: ${reason}
7453
- Files: ${resolvedRecords.length} file(s) resolved
7454
-
7455
- Signed-off-by: ${actorId}`;
7456
- let resolutionCommitHash = "";
7457
- try {
7458
- resolutionCommitHash = await this.git.commit(resolutionMessage);
7459
- } catch (commitError) {
7460
- const stdout = commitError.stdout || "";
7461
- const stderr = commitError.stderr || "";
7462
- const isNothingToCommit = stdout.includes("nothing to commit") || stderr.includes("nothing to commit") || stdout.includes("nothing added to commit") || stderr.includes("nothing added to commit");
7463
- if (isNothingToCommit) {
7464
- log("No additional changes to commit (no records needed re-signing)");
7465
- resolutionCommitHash = rebaseCommitHash;
7466
- } else {
7467
- throw commitError;
7468
- }
7469
- }
7470
- log("Pushing resolved state to remote...");
7471
- try {
7472
- await this.git.push("origin", "gitgov-state");
7473
- log("Push successful");
7474
- } catch (pushError) {
7475
- const pushErrorMsg = pushError instanceof Error ? pushError.message : String(pushError);
7476
- log(`Push failed (non-fatal): ${pushErrorMsg}`);
7477
- }
7478
- log("Returning to original branch and restoring .gitgov/ files...");
7479
- const repoRoot = await this.git.getRepoRoot();
7480
- const gitgovDir = path9__default.join(repoRoot, ".gitgov");
7481
- const tempDir = path9__default.join(os.tmpdir(), `gitgov-resolve-${Date.now()}`);
7482
- await promises.mkdir(tempDir, { recursive: true });
7483
- log(`Created temp directory for local files: ${tempDir}`);
7484
- for (const fileName of LOCAL_ONLY_FILES) {
7485
- const srcPath = path9__default.join(gitgovDir, fileName);
7486
- const destPath = path9__default.join(tempDir, fileName);
7487
- try {
7488
- await promises.access(srcPath);
7489
- await promises.cp(srcPath, destPath, { force: true });
7490
- log(`Saved LOCAL_ONLY_FILE to temp: ${fileName}`);
7491
- } catch {
7492
- }
7493
- }
7494
- const saveExcludedFiles = async (srcDir, destDir) => {
7495
- try {
7496
- const entries = await promises.readdir(srcDir, { withFileTypes: true });
7497
- for (const entry of entries) {
7498
- const srcPath = path9__default.join(srcDir, entry.name);
7499
- const dstPath = path9__default.join(destDir, entry.name);
7500
- if (entry.isDirectory()) {
7501
- await promises.mkdir(dstPath, { recursive: true });
7502
- await saveExcludedFiles(srcPath, dstPath);
7503
- } else {
7504
- const isExcluded = SYNC_EXCLUDED_PATTERNS.some((pattern) => pattern.test(entry.name));
7505
- if (isExcluded) {
7506
- await promises.mkdir(path9__default.dirname(dstPath), { recursive: true });
7507
- await promises.copyFile(srcPath, dstPath);
7508
- log(`Saved EXCLUDED file to temp: ${entry.name}`);
7509
- }
7510
- }
7511
- }
7512
- } catch {
7513
- }
7514
- };
7515
- await saveExcludedFiles(gitgovDir, tempDir);
7516
- try {
7517
- await execAsync("git checkout -", { cwd: repoRoot });
7518
- log("Returned to original branch");
7519
- } catch (checkoutError) {
7520
- log(`Warning: Could not return to original branch: ${checkoutError}`);
7521
- }
7522
- log("Restoring .gitgov/ from gitgov-state...");
7523
- try {
7524
- await this.git.checkoutFilesFromBranch("gitgov-state", [".gitgov/"]);
7525
- await execAsync("git reset HEAD .gitgov/ 2>/dev/null || true", { cwd: repoRoot });
7526
- log("Restored .gitgov/ from gitgov-state (unstaged)");
7527
- } catch (checkoutFilesError) {
7528
- log(`Warning: Could not restore .gitgov/ from gitgov-state: ${checkoutFilesError}`);
7529
- }
7530
- for (const fileName of LOCAL_ONLY_FILES) {
7531
- const srcPath = path9__default.join(tempDir, fileName);
7532
- const destPath = path9__default.join(gitgovDir, fileName);
7533
- try {
7534
- await promises.access(srcPath);
7535
- await promises.cp(srcPath, destPath, { force: true });
7536
- log(`Restored LOCAL_ONLY_FILE from temp: ${fileName}`);
7537
- } catch {
7538
- }
7539
- }
7540
- const restoreExcludedFiles = async (srcDir, destDir) => {
7541
- try {
7542
- const entries = await promises.readdir(srcDir, { withFileTypes: true });
7543
- for (const entry of entries) {
7544
- const srcPath = path9__default.join(srcDir, entry.name);
7545
- const dstPath = path9__default.join(destDir, entry.name);
7546
- if (entry.isDirectory()) {
7547
- await restoreExcludedFiles(srcPath, dstPath);
7548
- } else {
7549
- const isExcluded = SYNC_EXCLUDED_PATTERNS.some((pattern) => pattern.test(entry.name));
7550
- if (isExcluded) {
7551
- await promises.mkdir(path9__default.dirname(dstPath), { recursive: true });
7552
- await promises.copyFile(srcPath, dstPath);
7553
- log(`Restored EXCLUDED file from temp: ${entry.name}`);
7554
- }
7555
- }
7556
- }
7557
- } catch {
7558
- }
7559
- };
7560
- await restoreExcludedFiles(tempDir, gitgovDir);
7561
- try {
7562
- await promises.rm(tempDir, { recursive: true, force: true });
7563
- log("Temp directory cleaned up");
7564
- } catch {
7565
- }
7566
- logger6.info("Invoking RecordProjector.generateIndex() after conflict resolution...");
7567
- try {
7568
- await this.indexer.generateIndex();
7569
- logger6.info("Index regenerated successfully after conflict resolution");
7570
- } catch (error) {
7571
- logger6.warn(`Failed to regenerate index after resolution: ${error.message}`);
7572
- }
7573
- log(`=== resolveConflict COMPLETED: ${resolvedRecords.length} conflicts resolved ===`);
7574
- return {
7575
- success: true,
7576
- rebaseCommitHash,
7577
- resolutionCommitHash,
7578
- conflictsResolved: resolvedRecords.length,
7579
- resolvedBy: actorId,
7580
- reason
7581
- };
7582
- }
7583
- };
7584
-
7585
- // src/sync_state/fs_worktree/fs_worktree_sync_state.types.ts
7586
- var WORKTREE_DIR_NAME = ".gitgov-worktree";
7587
- var DEFAULT_STATE_BRANCH = "gitgov-state";
7588
- var logger7 = createLogger("[WorktreeSyncState] ");
7589
- function shouldSyncFile2(filePath) {
7590
- const fileName = path9__default.basename(filePath);
7591
- const ext = path9__default.extname(filePath);
7592
- if (!SYNC_ALLOWED_EXTENSIONS.includes(ext)) {
7593
- return false;
7594
- }
7595
- for (const pattern of SYNC_EXCLUDED_PATTERNS) {
7596
- if (pattern.test(fileName)) {
7597
- return false;
7598
- }
7599
- }
7600
- if (LOCAL_ONLY_FILES.includes(fileName)) {
7601
- return false;
7602
- }
7603
- const normalizedPath = filePath.replace(/\\/g, "/");
7604
- const parts = normalizedPath.split("/");
7605
- const gitgovIndex = parts.findIndex((p) => p === ".gitgov");
7606
- let relativeParts;
7607
- if (gitgovIndex !== -1) {
7608
- relativeParts = parts.slice(gitgovIndex + 1);
7609
- } else {
7610
- const syncDirIndex = parts.findIndex(
7611
- (p) => SYNC_DIRECTORIES.includes(p)
7612
- );
7613
- if (syncDirIndex !== -1) {
7614
- relativeParts = parts.slice(syncDirIndex);
7615
- } else if (SYNC_ROOT_FILES.includes(fileName)) {
7616
- return true;
7617
- } else {
7618
- return false;
7619
- }
7620
- }
7621
- if (relativeParts.length === 1) {
7622
- return SYNC_ROOT_FILES.includes(relativeParts[0]);
7623
- } else if (relativeParts.length >= 2) {
7624
- const dirName = relativeParts[0];
7625
- return SYNC_DIRECTORIES.includes(dirName);
7626
- }
7627
- return false;
7628
- }
7629
- var FsWorktreeSyncStateModule = class {
7630
- deps;
7631
- repoRoot;
7632
- stateBranchName;
7633
- worktreePath;
7634
- gitgovPath;
7635
- constructor(deps, config) {
7636
- if (!deps.git) throw new Error("GitModule is required for FsWorktreeSyncStateModule");
7637
- if (!deps.config) throw new Error("ConfigManager is required for FsWorktreeSyncStateModule");
7638
- if (!deps.identity) throw new Error("IdentityAdapter is required for FsWorktreeSyncStateModule");
7639
- if (!deps.lint) throw new Error("LintModule is required for FsWorktreeSyncStateModule");
7640
- if (!deps.indexer) throw new Error("IndexerAdapter is required for FsWorktreeSyncStateModule");
7641
- if (!config.repoRoot) throw new Error("repoRoot is required");
7642
- this.deps = deps;
7643
- this.repoRoot = config.repoRoot;
7644
- this.stateBranchName = config.stateBranchName ?? DEFAULT_STATE_BRANCH;
7645
- this.worktreePath = config.worktreePath ?? path9__default.join(this.repoRoot, WORKTREE_DIR_NAME);
7646
- this.gitgovPath = path9__default.join(this.worktreePath, ".gitgov");
7647
- }
7648
- // ═══════════════════════════════════════════════
7649
- // Section A: Worktree Management (WTSYNC-A1..A7)
7650
- // ═══════════════════════════════════════════════
7651
- /** [WTSYNC-A4] Returns the worktree path */
7652
- getWorktreePath() {
7653
- return this.worktreePath;
7654
- }
7655
- /** [WTSYNC-A1..A6] Ensures worktree exists and is healthy */
7656
- async ensureWorktree() {
7657
- const health = await this.checkWorktreeHealth();
7658
- if (health.healthy) {
7659
- logger7.debug("Worktree is healthy");
7660
- await this.removeLegacyGitignore();
7661
- return;
7662
- }
7663
- if (health.exists && !health.healthy) {
7664
- logger7.warn(`Worktree corrupted: ${health.error}. Recreating...`);
7665
- await this.removeWorktree();
7666
- }
7667
- await this.ensureStateBranch();
7668
- try {
7669
- logger7.info(`Creating worktree at ${this.worktreePath}`);
7670
- await this.execGit(["worktree", "add", this.worktreePath, this.stateBranchName]);
7671
- } catch (error) {
7672
- throw new WorktreeSetupError(
7673
- "Failed to create worktree",
7674
- this.worktreePath,
7675
- error instanceof Error ? error : void 0
7676
- );
7677
- }
7678
- await this.removeLegacyGitignore();
5352
+ await this.removeLegacyGitignore();
7679
5353
  }
7680
5354
  /**
7681
5355
  * [WTSYNC-A7] Remove .gitignore from state branch if it exists.
@@ -7683,9 +5357,9 @@ var FsWorktreeSyncStateModule = class {
7683
5357
  * Legacy state branches initialized by FsSyncState may have a .gitignore — remove it.
7684
5358
  */
7685
5359
  async removeLegacyGitignore() {
7686
- const gitignorePath = path9__default.join(this.worktreePath, ".gitignore");
5360
+ const gitignorePath = path6__default.join(this.worktreePath, ".gitignore");
7687
5361
  if (!existsSync(gitignorePath)) return;
7688
- logger7.info("Removing legacy .gitignore from state branch");
5362
+ logger6.info("Removing legacy .gitignore from state branch");
7689
5363
  try {
7690
5364
  await this.execInWorktree(["rm", ".gitignore"]);
7691
5365
  await this.execInWorktree(["commit", "-m", "gitgov: remove legacy .gitignore (filtering is in code)"]);
@@ -7701,7 +5375,7 @@ var FsWorktreeSyncStateModule = class {
7701
5375
  if (!existsSync(this.worktreePath)) {
7702
5376
  return { exists: false, healthy: false, path: this.worktreePath };
7703
5377
  }
7704
- const gitFile = path9__default.join(this.worktreePath, ".git");
5378
+ const gitFile = path6__default.join(this.worktreePath, ".git");
7705
5379
  if (!existsSync(gitFile)) {
7706
5380
  return {
7707
5381
  exists: true,
@@ -7745,7 +5419,7 @@ var FsWorktreeSyncStateModule = class {
7745
5419
  /** [WTSYNC-B1..B16] Push local state to remote */
7746
5420
  async pushState(options) {
7747
5421
  const { actorId, dryRun = false, force = false } = options;
7748
- const log = (msg) => logger7.debug(`[pushState] ${msg}`);
5422
+ const log = (msg) => logger6.debug(`[pushState] ${msg}`);
7749
5423
  if (await this.isRebaseInProgress()) {
7750
5424
  throw new RebaseAlreadyInProgressError();
7751
5425
  }
@@ -7767,7 +5441,7 @@ var FsWorktreeSyncStateModule = class {
7767
5441
  };
7768
5442
  }
7769
5443
  const rawDelta = await this.calculateFileDelta();
7770
- const delta = rawDelta.filter((f) => shouldSyncFile2(f.file));
5444
+ const delta = rawDelta.filter((f) => shouldSyncFile(f.file));
7771
5445
  log(`Delta: ${delta.length} syncable files (${rawDelta.length} total)`);
7772
5446
  if (delta.length === 0) {
7773
5447
  const { ahead: aheadOfRemote, remoteExists } = await this.isLocalAheadOfRemote();
@@ -7916,7 +5590,7 @@ var FsWorktreeSyncStateModule = class {
7916
5590
  /** [WTSYNC-C1..C9] Pull remote state */
7917
5591
  async pullState(options) {
7918
5592
  const { forceReindex = false, force = false } = options ?? {};
7919
- const log = (msg) => logger7.debug(`[pullState] ${msg}`);
5593
+ const log = (msg) => logger6.debug(`[pullState] ${msg}`);
7920
5594
  if (await this.isRebaseInProgress()) {
7921
5595
  throw new RebaseAlreadyInProgressError();
7922
5596
  }
@@ -7924,7 +5598,7 @@ var FsWorktreeSyncStateModule = class {
7924
5598
  if (!force) {
7925
5599
  const statusRaw = await this.execInWorktree(["status", "--porcelain", "-uall", ".gitgov/"]);
7926
5600
  const statusLines = statusRaw.split("\n").filter((line) => line.length >= 4);
7927
- const syncableChanges = statusLines.filter((l) => shouldSyncFile2(l.slice(3)));
5601
+ const syncableChanges = statusLines.filter((l) => shouldSyncFile(l.slice(3)));
7928
5602
  if (syncableChanges.length > 0) {
7929
5603
  log(`Auto-committing ${syncableChanges.length} local changes before pull`);
7930
5604
  for (const line of syncableChanges) {
@@ -8103,7 +5777,7 @@ var FsWorktreeSyncStateModule = class {
8103
5777
  async getPendingChanges() {
8104
5778
  await this.ensureWorktree();
8105
5779
  const allChanges = await this.calculateFileDelta();
8106
- return allChanges.filter((f) => shouldSyncFile2(f.file));
5780
+ return allChanges.filter((f) => shouldSyncFile(f.file));
8107
5781
  }
8108
5782
  /** Calculate delta between source and worktree state branch */
8109
5783
  async calculateStateDelta(_sourceBranch) {
@@ -8125,10 +5799,10 @@ var FsWorktreeSyncStateModule = class {
8125
5799
  /** [WTSYNC-E6] Check if rebase is in progress in worktree */
8126
5800
  async isRebaseInProgress() {
8127
5801
  try {
8128
- const gitContent = await promises.readFile(path9__default.join(this.worktreePath, ".git"), "utf8");
5802
+ const gitContent = await promises.readFile(path6__default.join(this.worktreePath, ".git"), "utf8");
8129
5803
  const gitDir = gitContent.replace("gitdir: ", "").trim();
8130
- const resolvedGitDir = path9__default.resolve(this.worktreePath, gitDir);
8131
- return existsSync(path9__default.join(resolvedGitDir, "rebase-merge")) || existsSync(path9__default.join(resolvedGitDir, "rebase-apply"));
5804
+ const resolvedGitDir = path6__default.resolve(this.worktreePath, gitDir);
5805
+ return existsSync(path6__default.join(resolvedGitDir, "rebase-merge")) || existsSync(path6__default.join(resolvedGitDir, "rebase-apply"));
8132
5806
  } catch {
8133
5807
  return false;
8134
5808
  }
@@ -8137,7 +5811,7 @@ var FsWorktreeSyncStateModule = class {
8137
5811
  async checkConflictMarkers(filePaths) {
8138
5812
  const filesWithMarkers = [];
8139
5813
  for (const filePath of filePaths) {
8140
- const fullPath = path9__default.join(this.gitgovPath, filePath);
5814
+ const fullPath = path6__default.join(this.gitgovPath, filePath);
8141
5815
  try {
8142
5816
  const content = await promises.readFile(fullPath, "utf8");
8143
5817
  if (content.includes("<<<<<<<") || content.includes(">>>>>>>")) {
@@ -8153,7 +5827,7 @@ var FsWorktreeSyncStateModule = class {
8153
5827
  const files = filePaths ?? await this.getConflictedFiles();
8154
5828
  const diffFiles = [];
8155
5829
  for (const file of files) {
8156
- const fullPath = path9__default.join(this.worktreePath, file);
5830
+ const fullPath = path6__default.join(this.worktreePath, file);
8157
5831
  try {
8158
5832
  const content = await promises.readFile(fullPath, "utf8");
8159
5833
  let localContent = "";
@@ -8251,7 +5925,7 @@ var FsWorktreeSyncStateModule = class {
8251
5925
  filePaths
8252
5926
  } = options ?? {};
8253
5927
  if (expectedFilesScope === "all-commits") {
8254
- logger7.debug('expectedFilesScope "all-commits" treated as "head" in worktree module');
5928
+ logger6.debug('expectedFilesScope "all-commits" treated as "head" in worktree module');
8255
5929
  }
8256
5930
  await this.ensureWorktree();
8257
5931
  const integrityViolations = await this.verifyResolutionIntegrity();
@@ -8267,7 +5941,7 @@ var FsWorktreeSyncStateModule = class {
8267
5941
  if (verifyExpectedFiles) {
8268
5942
  if (filePaths && filePaths.length > 0) {
8269
5943
  for (const fp of filePaths) {
8270
- if (!existsSync(path9__default.join(this.gitgovPath, fp))) {
5944
+ if (!existsSync(path6__default.join(this.gitgovPath, fp))) {
8271
5945
  integrityViolations.push({
8272
5946
  rebaseCommitHash: "",
8273
5947
  commitMessage: `Missing expected file: ${fp}`,
@@ -8279,7 +5953,7 @@ var FsWorktreeSyncStateModule = class {
8279
5953
  } else {
8280
5954
  const expectedDirs = ["tasks", "cycles", "actors"];
8281
5955
  for (const dir of expectedDirs) {
8282
- if (!existsSync(path9__default.join(this.gitgovPath, dir))) {
5956
+ if (!existsSync(path6__default.join(this.gitgovPath, dir))) {
8283
5957
  integrityViolations.push({
8284
5958
  rebaseCommitHash: "",
8285
5959
  commitMessage: `Missing expected directory: ${dir}`,
@@ -8288,7 +5962,7 @@ var FsWorktreeSyncStateModule = class {
8288
5962
  });
8289
5963
  }
8290
5964
  }
8291
- if (!existsSync(path9__default.join(this.gitgovPath, "config.json"))) {
5965
+ if (!existsSync(path6__default.join(this.gitgovPath, "config.json"))) {
8292
5966
  integrityViolations.push({
8293
5967
  rebaseCommitHash: "",
8294
5968
  commitMessage: "Missing expected file: config.json",
@@ -8385,7 +6059,7 @@ var FsWorktreeSyncStateModule = class {
8385
6059
  async stageSyncableFiles(delta, log) {
8386
6060
  let stagedCount = 0;
8387
6061
  for (const file of delta) {
8388
- if (!shouldSyncFile2(file.file)) {
6062
+ if (!shouldSyncFile(file.file)) {
8389
6063
  log(`Skipped (not syncable): ${file.file}`);
8390
6064
  continue;
8391
6065
  }
@@ -8411,7 +6085,7 @@ var FsWorktreeSyncStateModule = class {
8411
6085
  /** Re-sign records after conflict resolution */
8412
6086
  async resignResolvedRecords(filePaths, actorId, reason) {
8413
6087
  for (const filePath of filePaths) {
8414
- const fullPath = path9__default.join(this.gitgovPath, filePath);
6088
+ const fullPath = path6__default.join(this.gitgovPath, filePath);
8415
6089
  try {
8416
6090
  const content = await promises.readFile(fullPath, "utf8");
8417
6091
  const record = JSON.parse(content);
@@ -8426,7 +6100,7 @@ var FsWorktreeSyncStateModule = class {
8426
6100
  try {
8427
6101
  await this.deps.indexer.generateIndex();
8428
6102
  } catch {
8429
- logger7.warn("Re-index failed");
6103
+ logger6.warn("Re-index failed");
8430
6104
  }
8431
6105
  }
8432
6106
  /** Parse git diff --name-status output */
@@ -8724,7 +6398,7 @@ var LocalBackend = class {
8724
6398
  * Executes via entrypoint (dynamic import) and captures output.
8725
6399
  */
8726
6400
  async executeEntrypoint(engine, ctx) {
8727
- const absolutePath = path9__default.join(this.projectRoot, engine.entrypoint);
6401
+ const absolutePath = path6__default.join(this.projectRoot, engine.entrypoint);
8728
6402
  const mod = await import(absolutePath);
8729
6403
  const fnName = engine.function || "runAgent";
8730
6404
  const fn = mod[fnName];
@@ -9163,7 +6837,7 @@ var FsAgentRunner = class {
9163
6837
  throw new MissingDependencyError("ExecutionAdapter", "required");
9164
6838
  }
9165
6839
  this.projectRoot = deps.projectRoot;
9166
- this.gitgovPath = deps.gitgovPath ?? path9__default.join(this.projectRoot, ".gitgov");
6840
+ this.gitgovPath = deps.gitgovPath ?? path6__default.join(this.projectRoot, ".gitgov");
9167
6841
  this.identityAdapter = deps.identityAdapter ?? void 0;
9168
6842
  this.executionAdapter = deps.executionAdapter;
9169
6843
  this.eventBus = deps.eventBus ?? void 0;
@@ -9309,7 +6983,7 @@ var FsAgentRunner = class {
9309
6983
  */
9310
6984
  async loadAgent(agentId) {
9311
6985
  const id = agentId.startsWith("agent:") ? agentId.slice(6) : agentId;
9312
- const agentPath = path9__default.join(this.gitgovPath, "agents", `agent-${id}.json`);
6986
+ const agentPath = path6__default.join(this.gitgovPath, "agents", `agent-${id}.json`);
9313
6987
  try {
9314
6988
  const content = await promises.readFile(agentPath, "utf-8");
9315
6989
  const record = JSON.parse(content);
@@ -9341,10 +7015,10 @@ function createAgentRunner(deps) {
9341
7015
  var FsRecordProjection = class {
9342
7016
  indexPath;
9343
7017
  constructor(options) {
9344
- this.indexPath = path9.join(options.basePath, "index.json");
7018
+ this.indexPath = path6.join(options.basePath, "index.json");
9345
7019
  }
9346
7020
  async persist(data, _context) {
9347
- const dir = path9.dirname(this.indexPath);
7021
+ const dir = path6.dirname(this.indexPath);
9348
7022
  await fs.mkdir(dir, { recursive: true });
9349
7023
  const tmpPath = `${this.indexPath}.tmp`;
9350
7024
  const content = JSON.stringify(data, null, 2);
@@ -9382,6 +7056,6 @@ var FsRecordProjection = class {
9382
7056
  }
9383
7057
  };
9384
7058
 
9385
- export { DEFAULT_ID_ENCODER, FsAgentRunner, FsConfigStore, FsFileLister, FsKeyProvider, FsLintModule, FsProjectInitializer, FsRecordProjection, FsRecordStore, FsSessionStore, FsSyncStateModule, FsWatcherStateModule, FsWorktreeSyncStateModule, LocalGitModule as GitModule, LocalGitModule, createAgentRunner, createConfigManager, createSessionManager, findGitgovRoot, findProjectRoot, getGitgovPath, isGitgovProject, resetDiscoveryCache };
7059
+ export { DEFAULT_ID_ENCODER, FsAgentRunner, FsConfigStore, FsFileLister, FsKeyProvider, FsLintModule, FsProjectInitializer, FsRecordProjection, FsRecordStore, FsSessionStore, FsWatcherStateModule, FsWorktreeSyncStateModule, LocalGitModule as GitModule, LocalGitModule, createAgentRunner, createConfigManager, createSessionManager, findProjectRoot, getWorktreeBasePath, resetDiscoveryCache };
9386
7060
  //# sourceMappingURL=fs.js.map
9387
7061
  //# sourceMappingURL=fs.js.map