@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/{agent_runner-DijNVjaF.d.ts → agent_runner-Cgle_zVX.d.ts} +2 -2
- package/dist/src/fs.d.ts +15 -176
- package/dist/src/fs.js +605 -2931
- package/dist/src/fs.js.map +1 -1
- package/dist/src/github.d.ts +115 -115
- package/dist/src/github.js +1594 -1596
- package/dist/src/github.js.map +1 -1
- package/dist/src/index.d.ts +775 -1221
- package/dist/src/index.js +815 -1196
- package/dist/src/index.js.map +1 -1
- package/dist/src/memory.d.ts +1 -1
- package/dist/src/prisma.d.ts +3 -1
- package/dist/src/prisma.js +7 -2
- package/dist/src/prisma.js.map +1 -1
- package/dist/src/{record_projection.types-D9NkQbL_.d.ts → record_projection.types-CFsl44em.d.ts} +139 -166
- package/dist/src/{sync_state-C2a2RuBQ.d.ts → sync_state-B8X4NDKF.d.ts} +8 -4
- package/package.json +1 -1
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
|
|
4
|
-
import
|
|
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,
|
|
6
|
+
import { promises, existsSync, realpathSync } from 'fs';
|
|
7
7
|
import fg from 'fast-glob';
|
|
8
|
-
import { generateKeyPair,
|
|
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 {
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
388
|
-
this.keysPath =
|
|
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
|
|
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 (
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
1717
|
-
|
|
1718
|
-
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
1788
|
-
|
|
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
|
|
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',
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
1891
|
-
|
|
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: "
|
|
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:
|
|
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: "
|
|
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:
|
|
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: "
|
|
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:
|
|
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: "
|
|
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:
|
|
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
|
-
|
|
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.
|
|
2034
|
-
type: "
|
|
2035
|
-
payloadChecksum: "
|
|
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: "
|
|
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: "
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
"
|
|
2053
|
-
"
|
|
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.
|
|
1934
|
+
version: "1.1",
|
|
2060
1935
|
type: "execution",
|
|
2061
|
-
payloadChecksum: "
|
|
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.
|
|
2090
|
-
type: "
|
|
2091
|
-
|
|
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: "
|
|
1971
|
+
keyId: "agent:deploy-bot",
|
|
2095
1972
|
role: "author",
|
|
2096
|
-
notes: "
|
|
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
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
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
|
|
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
|
|
2014
|
+
description: "ID of the parent task this execution belongs to."
|
|
2153
2015
|
},
|
|
2154
2016
|
type: {
|
|
2155
2017
|
type: "string",
|
|
2156
|
-
|
|
2157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
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:
|
|
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: "
|
|
2535
|
+
$id: "workflow_record_schema.json",
|
|
2685
2536
|
title: "WorkflowRecord",
|
|
2686
|
-
description: "
|
|
2537
|
+
description: "Schema for workflow methodology configuration that defines named state transitions, signatures, and custom rules.",
|
|
2687
2538
|
type: "object",
|
|
2688
2539
|
required: [
|
|
2689
|
-
"
|
|
2540
|
+
"id",
|
|
2690
2541
|
"name",
|
|
2691
2542
|
"state_transitions"
|
|
2692
2543
|
],
|
|
2693
2544
|
additionalProperties: false,
|
|
2694
2545
|
properties: {
|
|
2695
|
-
|
|
2696
|
-
type: "string",
|
|
2697
|
-
description: "JSON Schema reference"
|
|
2698
|
-
},
|
|
2699
|
-
version: {
|
|
2546
|
+
id: {
|
|
2700
2547
|
type: "string",
|
|
2701
|
-
pattern: "^\\d
|
|
2702
|
-
|
|
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: "
|
|
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
|
|
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:
|
|
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'
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
2748
|
+
description: "Required capability roles (matches any agent with these roles)"
|
|
2892
2749
|
},
|
|
2893
2750
|
triggers: {
|
|
2894
2751
|
type: "array",
|
|
2895
|
-
description: "
|
|
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 (
|
|
3490
|
-
return promises.readFile(
|
|
3129
|
+
readFile: async (path13, encoding) => {
|
|
3130
|
+
return promises.readFile(path13, encoding);
|
|
3491
3131
|
},
|
|
3492
|
-
writeFile: async (
|
|
3493
|
-
await promises.writeFile(
|
|
3132
|
+
writeFile: async (path13, content) => {
|
|
3133
|
+
await promises.writeFile(path13, content, "utf-8");
|
|
3494
3134
|
},
|
|
3495
|
-
exists: async (
|
|
3135
|
+
exists: async (path13) => {
|
|
3496
3136
|
try {
|
|
3497
|
-
await promises.access(
|
|
3137
|
+
await promises.access(path13);
|
|
3498
3138
|
return true;
|
|
3499
3139
|
} catch {
|
|
3500
3140
|
return false;
|
|
3501
3141
|
}
|
|
3502
3142
|
},
|
|
3503
|
-
unlink: async (
|
|
3504
|
-
await promises.unlink(
|
|
3143
|
+
unlink: async (path13) => {
|
|
3144
|
+
await promises.unlink(path13);
|
|
3505
3145
|
},
|
|
3506
|
-
readdir: async (
|
|
3507
|
-
return readdir(
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3928
|
+
const targetPrompt = path6.join(this.repoRoot, "gitgov");
|
|
4297
3929
|
const potentialSources = [];
|
|
4298
3930
|
potentialSources.push(
|
|
4299
|
-
|
|
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 =
|
|
4307
|
-
potentialSources.push(
|
|
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 =
|
|
4316
|
-
potentialSources.push(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4943
|
-
const rebaseApplyPath =
|
|
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 !==
|
|
5505
|
-
if (existsSync(
|
|
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 =
|
|
5137
|
+
currentPath = path6.dirname(currentPath);
|
|
5510
5138
|
}
|
|
5511
|
-
if (existsSync(
|
|
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
|
|
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 =
|
|
5692
|
-
const ext =
|
|
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
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
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
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
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
|
-
|
|
5781
|
-
|
|
5782
|
-
const
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
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
|
-
|
|
5790
|
-
|
|
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
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
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 =
|
|
5360
|
+
const gitignorePath = path6__default.join(this.worktreePath, ".gitignore");
|
|
7687
5361
|
if (!existsSync(gitignorePath)) return;
|
|
7688
|
-
|
|
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 =
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
5802
|
+
const gitContent = await promises.readFile(path6__default.join(this.worktreePath, ".git"), "utf8");
|
|
8129
5803
|
const gitDir = gitContent.replace("gitdir: ", "").trim();
|
|
8130
|
-
const resolvedGitDir =
|
|
8131
|
-
return existsSync(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
7018
|
+
this.indexPath = path6.join(options.basePath, "index.json");
|
|
9345
7019
|
}
|
|
9346
7020
|
async persist(data, _context) {
|
|
9347
|
-
const dir =
|
|
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,
|
|
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
|