@fitlab-ai/agent-infra 0.9.3 → 0.9.4

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.
Files changed (175) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -123
  6. package/dist/lib/agent-clients/adapter.js +92 -2
  7. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  8. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  9. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  10. package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
  11. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  12. package/dist/lib/agent-clients/adapters/opencode.js +15 -3
  13. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  14. package/dist/lib/agent-clients/registry.js +9 -1
  15. package/dist/lib/internal/sandbox-control.js +24 -0
  16. package/dist/lib/internal/task-activity.js +45 -13
  17. package/dist/lib/internal/task-ledger.js +12 -3
  18. package/dist/lib/internal/task-lifecycle.js +19 -1
  19. package/dist/lib/internal/task-orchestration.js +122 -18
  20. package/dist/lib/platform/github-client.js +19 -2
  21. package/dist/lib/platform/pr-checks.js +1 -1
  22. package/dist/lib/platform/pull-requests.js +47 -1
  23. package/dist/lib/platform/releases.js +13 -5
  24. package/dist/lib/process-data/index.js +239 -0
  25. package/dist/lib/process-data/normalize.js +198 -0
  26. package/dist/lib/process-data/privacy.js +33 -0
  27. package/dist/lib/process-data/reconcile.js +95 -0
  28. package/dist/lib/process-data/repair.js +58 -0
  29. package/dist/lib/process-data/sources.js +345 -0
  30. package/dist/lib/process-data/store.js +282 -0
  31. package/dist/lib/process-data/types.js +2 -0
  32. package/dist/lib/run/index.js +4 -3
  33. package/dist/lib/run/tui.js +7 -8
  34. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  35. package/dist/lib/sandbox/capture.js +7 -2
  36. package/dist/lib/sandbox/commands/create.js +109 -228
  37. package/dist/lib/sandbox/commands/enter.js +15 -47
  38. package/dist/lib/sandbox/commands/list-running.js +16 -6
  39. package/dist/lib/sandbox/commands/ls.js +10 -6
  40. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  41. package/dist/lib/sandbox/commands/refresh.js +4 -84
  42. package/dist/lib/sandbox/commands/rm.js +24 -3
  43. package/dist/lib/sandbox/commands/show.js +12 -3
  44. package/dist/lib/sandbox/commands/start.js +7 -4
  45. package/dist/lib/sandbox/config.js +2 -0
  46. package/dist/lib/sandbox/constants.js +6 -0
  47. package/dist/lib/sandbox/control/client.js +44 -0
  48. package/dist/lib/sandbox/control/protocol.js +35 -0
  49. package/dist/lib/sandbox/control/server.js +109 -0
  50. package/dist/lib/sandbox/dockerfile.js +36 -13
  51. package/dist/lib/sandbox/mounts.js +17 -4
  52. package/dist/lib/sandbox/recovery.js +128 -5
  53. package/dist/lib/sandbox/redaction.js +12 -0
  54. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  55. package/dist/lib/sandbox/workspace-identity.js +95 -0
  56. package/dist/lib/sandbox/workspace-view.js +103 -0
  57. package/dist/lib/task/activity-intent.js +257 -51
  58. package/dist/lib/task/commit-finalization.js +267 -0
  59. package/dist/lib/task/commit-intent.js +165 -0
  60. package/dist/lib/task/decision-intents.js +31 -17
  61. package/dist/lib/task/delegation-receipts.js +2 -2
  62. package/dist/lib/task/implementation-inputs.js +54 -7
  63. package/dist/lib/task/ledger-intents.js +35 -1
  64. package/dist/lib/task/orchestration.js +363 -28
  65. package/dist/lib/task/resolve-ref.js +41 -1
  66. package/lib/agent-clients/adapter.ts +126 -2
  67. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  68. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  69. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  70. package/lib/agent-clients/adapters/claude-code.ts +21 -3
  71. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  72. package/lib/agent-clients/adapters/opencode.ts +18 -3
  73. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  74. package/lib/agent-clients/registry.ts +9 -1
  75. package/lib/internal/sandbox-control.ts +24 -0
  76. package/lib/internal/task-activity.ts +50 -14
  77. package/lib/internal/task-ledger.ts +12 -3
  78. package/lib/internal/task-lifecycle.ts +22 -1
  79. package/lib/internal/task-orchestration.ts +81 -11
  80. package/lib/platform/github-client.ts +20 -2
  81. package/lib/platform/pr-checks.ts +1 -1
  82. package/lib/platform/pull-requests.ts +49 -1
  83. package/lib/platform/releases.ts +16 -4
  84. package/lib/process-data/index.ts +224 -0
  85. package/lib/process-data/normalize.ts +202 -0
  86. package/lib/process-data/privacy.ts +41 -0
  87. package/lib/process-data/reconcile.ts +110 -0
  88. package/lib/process-data/repair.ts +64 -0
  89. package/lib/process-data/sources.ts +354 -0
  90. package/lib/process-data/store.ts +315 -0
  91. package/lib/process-data/types.ts +135 -0
  92. package/lib/run/index.ts +5 -3
  93. package/lib/run/tui.ts +11 -8
  94. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  95. package/lib/sandbox/capture.ts +9 -2
  96. package/lib/sandbox/commands/create.ts +126 -260
  97. package/lib/sandbox/commands/enter.ts +13 -59
  98. package/lib/sandbox/commands/list-running.ts +23 -6
  99. package/lib/sandbox/commands/ls.ts +22 -6
  100. package/lib/sandbox/commands/rebuild.ts +4 -3
  101. package/lib/sandbox/commands/refresh.ts +4 -126
  102. package/lib/sandbox/commands/rm.ts +24 -3
  103. package/lib/sandbox/commands/show.ts +29 -3
  104. package/lib/sandbox/commands/start.ts +6 -4
  105. package/lib/sandbox/config.ts +4 -0
  106. package/lib/sandbox/constants.ts +8 -0
  107. package/lib/sandbox/control/client.ts +55 -0
  108. package/lib/sandbox/control/protocol.ts +75 -0
  109. package/lib/sandbox/control/server.ts +117 -0
  110. package/lib/sandbox/dockerfile.ts +66 -13
  111. package/lib/sandbox/mounts.ts +24 -4
  112. package/lib/sandbox/recovery.ts +163 -6
  113. package/lib/sandbox/redaction.ts +14 -0
  114. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  115. package/lib/sandbox/tool-types.ts +16 -0
  116. package/lib/sandbox/workspace-identity.ts +123 -0
  117. package/lib/sandbox/workspace-view.ts +143 -0
  118. package/lib/task/activity-intent.ts +350 -61
  119. package/lib/task/commit-finalization.ts +356 -0
  120. package/lib/task/commit-intent.ts +230 -0
  121. package/lib/task/decision-intents.ts +32 -12
  122. package/lib/task/delegation-receipts.ts +2 -2
  123. package/lib/task/implementation-inputs.ts +62 -8
  124. package/lib/task/ledger-intents.ts +43 -3
  125. package/lib/task/orchestration.ts +501 -33
  126. package/lib/task/resolve-ref.ts +46 -1
  127. package/package.json +1 -1
  128. package/templates/.agents/rules/README.en.md +1 -1
  129. package/templates/.agents/rules/README.zh-CN.md +1 -1
  130. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  131. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  132. package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
  133. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  134. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  135. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
  136. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  137. package/templates/.agents/rules/task-management.en.md +2 -0
  138. package/templates/.agents/rules/task-management.zh-CN.md +2 -0
  139. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/commit/SKILL.en.md +21 -11
  142. package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -11
  143. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  144. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  145. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  146. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  147. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  149. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  150. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  151. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  152. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  153. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  155. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  156. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  157. package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
  158. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
  159. package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
  160. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
  161. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
  162. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  163. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  164. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  165. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  166. package/templates/.agents/templates/task.en.md +1 -1
  167. package/templates/.agents/templates/task.zh-CN.md +1 -1
  168. package/templates/.claude/commands/watch-pr.en.md +1 -1
  169. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  170. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  171. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  172. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  173. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  174. package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
  175. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
@@ -0,0 +1,198 @@
1
+ import { sha256 } from './store.js';
2
+ const TASK_ID_RE = /TASK-\d{8}-\d{6}/;
3
+ const ACTIVITY_RE = /^- (\d{4}-\d{2}-\d{2} [^ ]+) — \*\*(.+?)\*\* by ([^ ]+) — (.+)$/gm;
4
+ const LEDGER_RE = /^\|\s*((?:AN|PL|CD)-\d+)\s*\|\s*(analysis|plan|code)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|$/gm;
5
+ const RULING_RE = /^###\s+(HDR-\d+)\s*$([\s\S]*?)(?=^###\s+|^##\s+|(?![\s\S]))/gm;
6
+ const CONFLICT_STATUSES = new Set(['open', 'accepted', 'adjusted', 'refuted', 'cannot-judge', 'needs-human-decision']);
7
+ function recordId(kind, identity, suffix = '') {
8
+ return `${kind}:${sha256(`${identity}\0${suffix}`).slice(0, 24)}`;
9
+ }
10
+ function canonicalActor(raw) {
11
+ const lowered = raw.toLowerCase();
12
+ const aliases = {
13
+ 'claude-code': 'claude',
14
+ 'antigravity-cli': 'antigravity',
15
+ 'gemini-cli': 'gemini',
16
+ gemini: 'gemini'
17
+ };
18
+ if (['claude', 'codex', 'antigravity', 'opencode', 'cursor', 'human'].includes(lowered))
19
+ return lowered;
20
+ return aliases[lowered] ?? null;
21
+ }
22
+ function nextOccurrence(occurrences, identity) {
23
+ const occurrence = (occurrences.get(identity) ?? 0) + 1;
24
+ occurrences.set(identity, occurrence);
25
+ return occurrence === 1 ? '' : `occurrence=${occurrence}`;
26
+ }
27
+ function normalizePlatformPage(object) {
28
+ if (!object.content)
29
+ return [];
30
+ try {
31
+ const page = JSON.parse(object.content);
32
+ if (!Array.isArray(page))
33
+ return [];
34
+ return page.map((entry, index) => {
35
+ const value = entry;
36
+ const identity = String(value.id ?? value.node_id ?? value.sha ?? value.number ?? `${object.sourceIdentity}:${index}`);
37
+ const platformIdentity = /\/issues\?/.test(object.sourceIdentity) && typeof value.number === 'number'
38
+ ? `issue:${value.number}`
39
+ : /\/pulls\?/.test(object.sourceIdentity) && typeof value.number === 'number'
40
+ ? `pr:${value.number}`
41
+ : /\/issues\/\d+\/comments/.test(object.sourceIdentity)
42
+ ? `issue-comment:${identity}`
43
+ : /\/pulls\/\d+\/reviews/.test(object.sourceIdentity)
44
+ ? `review:${identity}`
45
+ : /\/pulls\/\d+\/commits/.test(object.sourceIdentity)
46
+ ? `commit:${identity}`
47
+ : `platform:${identity}`;
48
+ const binding = typeof value.body === 'string' ? value.body.match(TASK_ID_RE)?.[0] : undefined;
49
+ return {
50
+ recordId: recordId('platform-resource', object.sourceIdentity, identity),
51
+ kind: 'platform-resource',
52
+ sourceIdentity: platformIdentity,
53
+ sourceSha256: object.sha256,
54
+ ...(binding ? { binding } : {}),
55
+ data: entry
56
+ };
57
+ });
58
+ }
59
+ catch {
60
+ return [];
61
+ }
62
+ }
63
+ function normalizeObjects(objects) {
64
+ const records = [];
65
+ for (const object of objects) {
66
+ if (object.disposition?.state && object.disposition.state !== 'included') {
67
+ records.push({
68
+ recordId: recordId('missing', object.sourceIdentity),
69
+ kind: 'missing',
70
+ sourceIdentity: object.sourceIdentity,
71
+ sourceSha256: object.sha256,
72
+ data: object.disposition.state === 'excluded-sensitive'
73
+ ? { reason: 'privacy-excluded', ruleId: object.disposition.ruleId }
74
+ : { reason: object.disposition.reason }
75
+ });
76
+ continue;
77
+ }
78
+ if (object.sourceKind === 'github-rest') {
79
+ records.push(...normalizePlatformPage(object));
80
+ continue;
81
+ }
82
+ if (object.sourceKind === 'operational-report') {
83
+ records.push({
84
+ recordId: recordId('operational-report', object.sourceIdentity),
85
+ kind: 'operational-report',
86
+ sourceIdentity: object.sourceIdentity,
87
+ sourceSha256: object.sha256
88
+ });
89
+ continue;
90
+ }
91
+ if (object.sourceKind === 'structured-telemetry') {
92
+ let data = { availability: 'unknown' };
93
+ try {
94
+ data = JSON.parse(object.content ?? '{}');
95
+ }
96
+ catch {
97
+ data = { availability: 'unavailable', reason: 'invalid receipt JSON' };
98
+ }
99
+ records.push({
100
+ recordId: recordId('telemetry', object.sourceIdentity),
101
+ kind: 'telemetry',
102
+ sourceIdentity: object.sourceIdentity,
103
+ sourceSha256: object.sha256,
104
+ data
105
+ });
106
+ continue;
107
+ }
108
+ const taskId = object.sourceIdentity.match(TASK_ID_RE)?.[0];
109
+ if (/\/task\.md$/.test(object.sourceIdentity) && taskId) {
110
+ const activityOccurrences = new Map();
111
+ const findingOccurrences = new Map();
112
+ const issueNumber = object.content?.match(/^issue_number:\s*(\d+)$/m)?.[1];
113
+ const agentInfraVersion = object.content?.match(/^agent_infra_version:\s*(\S+)$/m)?.[1];
114
+ records.push({
115
+ recordId: recordId('task', taskId),
116
+ kind: 'task',
117
+ sourceIdentity: taskId,
118
+ sourceSha256: object.sha256,
119
+ ...(issueNumber ? { binding: `issue:${issueNumber}` } : {}),
120
+ data: {
121
+ agentInfraVersion: agentInfraVersion
122
+ ? { state: 'known', value: agentInfraVersion }
123
+ : { state: 'unknown', reason: 'legacy-task-missing-agent-infra-version' }
124
+ }
125
+ });
126
+ for (const match of object.content?.matchAll(ACTIVITY_RE) ?? []) {
127
+ const rawActor = match[3];
128
+ const identity = `${match[1]}:${match[2]}`;
129
+ const occurrence = nextOccurrence(activityOccurrences, identity);
130
+ records.push({
131
+ recordId: recordId('lifecycle-event', taskId, occurrence ? `${identity}\0${occurrence}` : identity),
132
+ kind: 'lifecycle-event',
133
+ sourceIdentity: `${taskId}#activity=${match[1]}`,
134
+ sourceSha256: object.sha256,
135
+ data: {
136
+ timestamp: match[1],
137
+ action: match[2],
138
+ actorRaw: rawActor,
139
+ actorCanonical: canonicalActor(rawActor),
140
+ note: match[4]
141
+ }
142
+ });
143
+ }
144
+ for (const match of object.content?.matchAll(LEDGER_RE) ?? []) {
145
+ const findingId = match[1].trim();
146
+ const stage = match[2].trim();
147
+ const round = match[3].trim();
148
+ const severity = match[4].trim();
149
+ const status = match[5].trim();
150
+ const evidence = match[6].trim();
151
+ const identity = `${taskId}#finding=${findingId}`;
152
+ const occurrence = nextOccurrence(findingOccurrences, identity);
153
+ const data = { findingId, stage, round, severity, status, evidence };
154
+ records.push({
155
+ recordId: recordId('review-finding', identity, occurrence),
156
+ kind: 'review-finding',
157
+ sourceIdentity: identity,
158
+ sourceSha256: object.sha256,
159
+ binding: taskId,
160
+ data
161
+ });
162
+ if (status && CONFLICT_STATUSES.has(status)) {
163
+ records.push({
164
+ recordId: recordId('conflict', identity, occurrence),
165
+ kind: 'conflict',
166
+ sourceIdentity: `${identity}#status=${status}`,
167
+ sourceSha256: object.sha256,
168
+ binding: taskId,
169
+ data
170
+ });
171
+ }
172
+ }
173
+ for (const match of object.content?.matchAll(RULING_RE) ?? []) {
174
+ const rulingId = match[1];
175
+ const identity = `${taskId}#ruling=${rulingId}`;
176
+ records.push({
177
+ recordId: recordId('human-ruling', identity),
178
+ kind: 'human-ruling',
179
+ sourceIdentity: identity,
180
+ sourceSha256: object.sha256,
181
+ binding: taskId,
182
+ data: { rulingId, evidence: match[2].trim() }
183
+ });
184
+ }
185
+ continue;
186
+ }
187
+ records.push({
188
+ recordId: recordId('artifact', object.sourceIdentity),
189
+ kind: 'artifact',
190
+ sourceIdentity: object.sourceIdentity,
191
+ sourceSha256: object.sha256,
192
+ ...(taskId ? { binding: taskId } : {})
193
+ });
194
+ }
195
+ return records.sort((left, right) => left.recordId.localeCompare(right.recordId));
196
+ }
197
+ export { normalizeObjects };
198
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1,33 @@
1
+ import { sha256 } from './store.js';
2
+ const RULES = [
3
+ { id: 'private-key', pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
4
+ { id: 'github-token', pattern: /\b(?:gh[opusr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/ },
5
+ { id: 'bearer-token', pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i },
6
+ { id: 'aws-access-key', pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/ },
7
+ { id: 'slack-token', pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/ },
8
+ { id: 'slack-webhook', pattern: /https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]{20,}/i },
9
+ { id: 'credential-field', pattern: /(?:api[_-]?key|password|secret)\s*[:=]\s*["']?[^\s"']{8,}/i }
10
+ ];
11
+ function inspectPrivacy(content) {
12
+ for (const rule of RULES) {
13
+ if (rule.pattern.test(content))
14
+ return { state: 'excluded-sensitive', ruleId: rule.id };
15
+ }
16
+ return { state: 'included' };
17
+ }
18
+ function captureText(sourceKind, sourceIdentity, content) {
19
+ const bytes = Buffer.byteLength(content);
20
+ const digest = sha256(Buffer.from(content, 'utf8'));
21
+ const disposition = inspectPrivacy(content);
22
+ return disposition.state === 'included'
23
+ ? { sourceKind, sourceIdentity, sha256: digest, bytes, content, disposition }
24
+ : { sourceKind, sourceIdentity, sha256: digest, bytes, disposition };
25
+ }
26
+ function redactExcerpt(content, maxBytes = 4096) {
27
+ if (inspectPrivacy(content).state !== 'included')
28
+ return null;
29
+ const buffer = Buffer.from(content, 'utf8');
30
+ return buffer.subarray(0, maxBytes).toString('utf8').replace(/\b(token|password|secret)=\S+/gi, '$1=[REDACTED]');
31
+ }
32
+ export { captureText, inspectPrivacy, redactExcerpt };
33
+ //# sourceMappingURL=privacy.js.map
@@ -0,0 +1,95 @@
1
+ import { sha256 } from './store.js';
2
+ function reconcileRecords(records, scope) {
3
+ const findings = [];
4
+ const repairs = [];
5
+ const findingKeys = new Set();
6
+ const addFinding = (category, severity, identities, repairable, reason) => {
7
+ const sortedIdentities = [...identities].sort();
8
+ const key = `${category}:${sortedIdentities.join('\0')}`;
9
+ if (findingKeys.has(key))
10
+ return;
11
+ findingKeys.add(key);
12
+ findings.push({
13
+ findingId: `quality:${sha256(key).slice(0, 20)}`,
14
+ category,
15
+ severity,
16
+ identities: sortedIdentities,
17
+ repairable,
18
+ reason
19
+ });
20
+ };
21
+ const byIdentity = new Map();
22
+ for (const record of records) {
23
+ const group = byIdentity.get(record.sourceIdentity) ?? [];
24
+ group.push(record);
25
+ byIdentity.set(record.sourceIdentity, group);
26
+ if (record.kind === 'missing' &&
27
+ record.data !== null &&
28
+ typeof record.data === 'object' &&
29
+ !Array.isArray(record.data) &&
30
+ record.data.reason === 'privacy-excluded') {
31
+ addFinding('privacy-excluded', 'warning', [record.sourceIdentity], false, 'Sensitive content was excluded by policy');
32
+ }
33
+ else if (record.kind === 'missing') {
34
+ addFinding('unrecoverable', 'info', [record.sourceIdentity], false, 'Source body is unavailable under the capture policy');
35
+ }
36
+ }
37
+ for (const [identity, group] of byIdentity) {
38
+ if (group.length < 2)
39
+ continue;
40
+ addFinding('duplicate-identity', 'error', group.map((record) => record.recordId), false, 'Multiple records claim the same stable identity');
41
+ if (group.some((record) => record.kind === 'platform-resource') && new Set(group.map((record) => record.sourceSha256)).size > 1) {
42
+ addFinding('mutable-remote', 'warning', group.map((record) => record.recordId), false, `Remote identity changed content: ${identity}`);
43
+ }
44
+ }
45
+ const tasks = records.filter((record) => record.kind === 'task');
46
+ const resources = records.filter((record) => record.kind === 'platform-resource');
47
+ const taskByIdentity = new Map(tasks.map((task) => [task.sourceIdentity, task]));
48
+ for (const task of tasks) {
49
+ const data = task.data && typeof task.data === 'object' && !Array.isArray(task.data) ? task.data : null;
50
+ const version = data?.agentInfraVersion;
51
+ if (!version || typeof version !== 'object' || Array.isArray(version) || version.state !== 'known') {
52
+ addFinding('schema-difference', 'info', [task.recordId], false, 'Task uses a legacy schema without a known agent_infra_version');
53
+ }
54
+ if (scope !== 'all' || !task.binding)
55
+ continue;
56
+ const identityCandidates = resources.filter((record) => record.sourceIdentity === task.binding);
57
+ if (identityCandidates.length === 0) {
58
+ addFinding('missing-remote', 'error', [task.recordId, task.binding], false, 'Task binding has no captured platform resource');
59
+ }
60
+ if (identityCandidates.length > 1) {
61
+ addFinding('binding-conflict', 'error', [task.recordId, ...identityCandidates.map((record) => record.recordId)], false, 'Multiple platform resources bind to one task');
62
+ }
63
+ for (const remote of identityCandidates) {
64
+ if (remote.binding && remote.binding !== task.sourceIdentity) {
65
+ addFinding('content-mismatch', 'warning', [task.recordId, remote.recordId], false, 'Local and remote binding identities disagree');
66
+ }
67
+ }
68
+ const candidates = identityCandidates.filter((record) => record.binding === task.sourceIdentity);
69
+ if (candidates.length === 1 && (byIdentity.get(candidates[0].sourceIdentity)?.length ?? 0) === 1) {
70
+ const target = candidates[0];
71
+ repairs.push({
72
+ repairId: `repair:${sha256(`${task.recordId}:${target.recordId}`).slice(0, 20)}`,
73
+ operation: 'link',
74
+ sourceRecordId: task.recordId,
75
+ targetRecordId: target.recordId,
76
+ preconditionSha256: sha256(`${task.sourceSha256}:${target.sourceSha256}`)
77
+ });
78
+ }
79
+ }
80
+ for (const resource of scope === 'all' ? resources : []) {
81
+ if (resource.binding?.startsWith('TASK-') && !taskByIdentity.has(resource.binding)) {
82
+ addFinding('missing-local', 'error', [resource.recordId, resource.binding], false, 'Platform resource binds to a task that is not captured locally');
83
+ }
84
+ const localClaims = tasks.filter((task) => task.binding === resource.sourceIdentity);
85
+ if (localClaims.length > 1 || (resource.binding && localClaims.length > 0 && !localClaims.some((task) => task.sourceIdentity === resource.binding))) {
86
+ addFinding('binding-conflict', 'error', [resource.recordId, ...localClaims.map((task) => task.recordId)], false, 'Local and remote records claim incompatible bindings');
87
+ }
88
+ }
89
+ return {
90
+ findings: findings.sort((left, right) => left.findingId.localeCompare(right.findingId)),
91
+ repairs: repairs.sort((left, right) => left.repairId.localeCompare(right.repairId))
92
+ };
93
+ }
94
+ export { reconcileRecords };
95
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1,58 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { canonicalJsonBytes, sha256, verifySnapshot } from './store.js';
4
+ function readRepairActions(snapshotPath) {
5
+ const filePath = path.join(snapshotPath, 'repair-manifest.jsonl');
6
+ if (!fs.existsSync(filePath))
7
+ return [];
8
+ return fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean).map((line) => {
9
+ const { schema: _schema, ...action } = JSON.parse(line);
10
+ return action;
11
+ });
12
+ }
13
+ function repairSnapshot(root, snapshotId, apply) {
14
+ const verified = verifySnapshot(root, snapshotId);
15
+ if (!verified.ok || !verified.snapshotPath)
16
+ throw new Error(`snapshot verification failed: ${verified.errors.join('; ')}`);
17
+ const actions = readRepairActions(verified.snapshotPath);
18
+ const records = new Map(fs.readFileSync(path.join(verified.snapshotPath, 'normalized.jsonl'), 'utf8').split('\n').filter(Boolean).map((line) => {
19
+ const record = JSON.parse(line);
20
+ return [record.recordId, record];
21
+ }));
22
+ for (const action of actions) {
23
+ const source = records.get(action.sourceRecordId);
24
+ const target = records.get(action.targetRecordId);
25
+ if (!source || !target)
26
+ throw new Error(`repair references a missing record: ${action.repairId}`);
27
+ if (sha256(`${source.sourceSha256}:${target.sourceSha256}`) !== action.preconditionSha256) {
28
+ throw new Error(`repair precondition failed: ${action.repairId}`);
29
+ }
30
+ }
31
+ if (!apply || actions.length === 0)
32
+ return { applied: false, repairId: null, actionCount: actions.length };
33
+ const digest = sha256(canonicalJsonBytes(actions));
34
+ const repairId = `repair-${digest.slice(0, 16)}`;
35
+ const repairDir = path.join(root, 'repairs', snapshotId, repairId);
36
+ const manifest = `${canonicalJsonBytes({ schema: 'repair-application/v1', snapshotId, repairId, actionCount: actions.length, overlaySha256: digest }).toString('utf8')}\n`;
37
+ const overlay = actions.map((action) => `${canonicalJsonBytes({ schema: 'normalized-overlay/v1', ...action }).toString('utf8')}\n`).join('');
38
+ if (fs.existsSync(repairDir)) {
39
+ if (fs.readFileSync(path.join(repairDir, 'manifest.json'), 'utf8') !== manifest)
40
+ throw new Error(`repair collision: ${repairId}`);
41
+ return { applied: false, repairId, actionCount: actions.length, path: repairDir };
42
+ }
43
+ fs.mkdirSync(repairDir, { recursive: true, mode: 0o700 });
44
+ fs.writeFileSync(path.join(repairDir, 'manifest.json'), manifest, { mode: 0o600, flag: 'wx' });
45
+ fs.writeFileSync(path.join(repairDir, 'overlay.jsonl'), overlay, { mode: 0o600, flag: 'wx' });
46
+ return { applied: true, repairId, actionCount: actions.length, path: repairDir };
47
+ }
48
+ function readAppliedOverlays(root, snapshotId) {
49
+ const base = path.join(root, 'repairs', snapshotId);
50
+ if (!fs.existsSync(base))
51
+ return '';
52
+ return fs.readdirSync(base).sort().map((entry) => {
53
+ const overlay = path.join(base, entry, 'overlay.jsonl');
54
+ return fs.existsSync(overlay) ? fs.readFileSync(overlay, 'utf8') : '';
55
+ }).join('');
56
+ }
57
+ export { readAppliedOverlays, repairSnapshot };
58
+ //# sourceMappingURL=repair.js.map