@open-agent-toolkit/cli 0.2.72 → 0.2.74

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 (165) hide show
  1. package/assets/agents/oat-reviewer.md +4 -3
  2. package/assets/agents/recon-worker.md +42 -8
  3. package/assets/bundle-metadata.json +1 -1
  4. package/assets/docs/cli-utilities/configuration.md +3 -56
  5. package/assets/docs/cli-utilities/tool-packs.md +4 -4
  6. package/assets/docs/contributing/code.md +2 -2
  7. package/assets/docs/contributing/index.md +0 -1
  8. package/assets/docs/reference/cli-reference.md +1 -1
  9. package/assets/docs/reference/troubleshooting.md +17 -40
  10. package/assets/docs/workflows/projects/artifacts.md +16 -34
  11. package/assets/docs/workflows/projects/lifecycle.md +20 -19
  12. package/assets/docs/workflows/skills/explainer-kit.md +180 -440
  13. package/assets/docs/workflows/skills/index.md +1 -2
  14. package/assets/docs/workflows/skills/recon.md +114 -23
  15. package/assets/public-package-versions.json +4 -4
  16. package/assets/skills/explainer-kit/SKILL.md +96 -171
  17. package/assets/skills/explainer-kit/recipes/engineer-tour.json +1 -22
  18. package/assets/skills/explainer-kit/recipes/program-recap.json +1 -29
  19. package/assets/skills/explainer-kit/recipes/project-explainer.json +1 -22
  20. package/assets/skills/explainer-kit/recipes/project-recap.v2.json +1 -43
  21. package/assets/skills/explainer-kit/references/fact-base-contract.md +76 -101
  22. package/assets/skills/explainer-kit/references/recap-authoring.md +99 -0
  23. package/assets/skills/explainer-kit/schemas/manifest.schema.json +13 -135
  24. package/assets/skills/explainer-kit/scripts/bundle.mjs +712 -0
  25. package/assets/skills/explainer-kit/scripts/lib/claim-subject.mjs +40 -0
  26. package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +113 -1504
  27. package/assets/skills/explainer-kit/scripts/lib/failure.mjs +13 -0
  28. package/assets/skills/explainer-kit/scripts/lib/package-coverage.mjs +57 -403
  29. package/assets/skills/explainer-kit/scripts/lib/qa-result.mjs +168 -0
  30. package/assets/skills/explainer-kit/scripts/lib/qa.mjs +90 -488
  31. package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +28 -545
  32. package/assets/skills/explainer-kit/scripts/lib/run-package.mjs +120 -0
  33. package/assets/skills/explainer-kit/scripts/lib/sanitize.mjs +42 -0
  34. package/assets/skills/explainer-kit/scripts/record.mjs +335 -0
  35. package/assets/skills/explainer-kit/scripts/verify.mjs +648 -0
  36. package/assets/skills/oat-explainer-kit/SKILL.md +57 -117
  37. package/assets/skills/oat-explainer-kit/references/config-contract.md +20 -64
  38. package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +85 -237
  39. package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +18 -10
  40. package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +1 -0
  41. package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +256 -21
  42. package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +82 -3
  43. package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +0 -162
  44. package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +57 -123
  45. package/assets/skills/oat-project-autonomous/SKILL.md +14 -13
  46. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +165 -165
  47. package/assets/skills/oat-project-complete/SKILL.md +168 -349
  48. package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +125 -0
  49. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +0 -30
  50. package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +4 -37
  51. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +17 -16
  52. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +9 -174
  53. package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +1 -2
  54. package/assets/skills/oat-project-document/SKILL.md +1 -1
  55. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +165 -165
  56. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  57. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +44 -56
  58. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +165 -165
  59. package/assets/skills/oat-project-lite/SKILL.md +1 -1
  60. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +165 -165
  61. package/assets/skills/oat-project-plan/SKILL.md +5 -6
  62. package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
  63. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +165 -165
  64. package/assets/skills/oat-project-quick-start/SKILL.md +1 -1
  65. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +165 -165
  66. package/assets/skills/oat-project-review-receive/SKILL.md +14 -1
  67. package/assets/skills/oat-project-summary/SKILL.md +35 -25
  68. package/assets/skills/oat-wave-execute/SKILL.md +11 -36
  69. package/assets/skills/oat-wave-program/SKILL.md +13 -37
  70. package/assets/skills/recon/SKILL.md +103 -40
  71. package/assets/skills/recon/references/packet-contract.md +150 -67
  72. package/assets/skills/recon/references/profiles.md +38 -16
  73. package/assets/skills/recon/references/worker-contract.md +39 -8
  74. package/assets/skills/recon/scripts/lib/contracts.mjs +703 -55
  75. package/assets/skills/recon/scripts/lib/routing.mjs +588 -0
  76. package/assets/skills/recon/scripts/lib/validated-run.mjs +2 -0
  77. package/assets/skills/recon/scripts/prepare-routing.mjs +88 -0
  78. package/assets/skills/recon/scripts/render-packet.mjs +69 -1
  79. package/assets/skills/recon/scripts/validate-packet.mjs +473 -56
  80. package/assets/skills/subagent-orchestration/SKILL.md +18 -9
  81. package/assets/skills/subagent-orchestration/references/model-selection-principles.md +17 -8
  82. package/dist/commands/config/index.d.ts.map +1 -1
  83. package/dist/commands/config/index.js +9 -113
  84. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  85. package/dist/commands/project/archive/archive-utils.js +32 -184
  86. package/dist/commands/project/archive/explainer-package-coverage.d.ts +3 -8
  87. package/dist/commands/project/archive/explainer-package-coverage.d.ts.map +1 -1
  88. package/dist/commands/project/archive/explainer-package-coverage.js +1 -1
  89. package/dist/config/oat-config.d.ts +0 -11
  90. package/dist/config/oat-config.d.ts.map +1 -1
  91. package/dist/config/oat-config.js +0 -34
  92. package/dist/config/resolve.d.ts.map +1 -1
  93. package/dist/config/resolve.js +0 -8
  94. package/dist/release/public-package-contract.d.ts.map +1 -1
  95. package/dist/release/public-package-contract.js +2 -10
  96. package/dist/validation/project-state.d.ts.map +1 -1
  97. package/dist/validation/project-state.js +42 -3
  98. package/package.json +2 -2
  99. package/assets/docs/contributing/explainer-kit-verification.md +0 -133
  100. package/assets/docs/workflows/skills/explainer-kit-providers.md +0 -155
  101. package/assets/skills/explainer-kit/briefs/deep-dive.md +0 -35
  102. package/assets/skills/explainer-kit/briefs/project-page.md +0 -38
  103. package/assets/skills/explainer-kit/briefs/supporting-diagram.md +0 -28
  104. package/assets/skills/explainer-kit/briefs/walkthrough-deck.md +0 -35
  105. package/assets/skills/explainer-kit/examples/project-explainer/content.md +0 -31
  106. package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +0 -22
  107. package/assets/skills/explainer-kit/examples/project-recap/content.md +0 -77
  108. package/assets/skills/explainer-kit/examples/project-recap/fact-base.json +0 -104
  109. package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +0 -44
  110. package/assets/skills/explainer-kit/examples/theme-bundle.json +0 -87
  111. package/assets/skills/explainer-kit/recipes/project-recap.v1.json +0 -93
  112. package/assets/skills/explainer-kit/references/contracts.md +0 -324
  113. package/assets/skills/explainer-kit/references/destination-contract.md +0 -210
  114. package/assets/skills/explainer-kit/references/extension-contract.md +0 -80
  115. package/assets/skills/explainer-kit/references/golden-conformance.md +0 -80
  116. package/assets/skills/explainer-kit/references/visual-review.md +0 -71
  117. package/assets/skills/explainer-kit/schemas/author-request.v2.schema.json +0 -212
  118. package/assets/skills/explainer-kit/schemas/author-request.v3.schema.json +0 -241
  119. package/assets/skills/explainer-kit/schemas/author-result.v2.schema.json +0 -52
  120. package/assets/skills/explainer-kit/schemas/build-record.schema.json +0 -98
  121. package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +0 -53
  122. package/assets/skills/explainer-kit/schemas/publish-receipt.v1.schema.json +0 -97
  123. package/assets/skills/explainer-kit/schemas/publish-receipt.v2.schema.json +0 -215
  124. package/assets/skills/explainer-kit/schemas/publish-request.v1.schema.json +0 -32
  125. package/assets/skills/explainer-kit/schemas/publish-request.v2.schema.json +0 -34
  126. package/assets/skills/explainer-kit/schemas/run-request.schema.json +0 -147
  127. package/assets/skills/explainer-kit/schemas/set-plan.v1.schema.json +0 -149
  128. package/assets/skills/explainer-kit/schemas/terminal-evidence.v1.schema.json +0 -81
  129. package/assets/skills/explainer-kit/schemas/visual-review-evidence.v1.schema.json +0 -66
  130. package/assets/skills/explainer-kit/schemas/visual-review-request.v1.schema.json +0 -117
  131. package/assets/skills/explainer-kit/schemas/visual-review-result.v1.schema.json +0 -80
  132. package/assets/skills/explainer-kit/scripts/lib/catalog.mjs +0 -349
  133. package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +0 -418
  134. package/assets/skills/explainer-kit/scripts/lib/diagram.mjs +0 -514
  135. package/assets/skills/explainer-kit/scripts/lib/durability.mjs +0 -643
  136. package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +0 -664
  137. package/assets/skills/explainer-kit/scripts/lib/internal-references.mjs +0 -538
  138. package/assets/skills/explainer-kit/scripts/lib/markdown.mjs +0 -414
  139. package/assets/skills/explainer-kit/scripts/lib/publication-policy.mjs +0 -54
  140. package/assets/skills/explainer-kit/scripts/lib/records.mjs +0 -954
  141. package/assets/skills/explainer-kit/scripts/lib/render.mjs +0 -703
  142. package/assets/skills/explainer-kit/scripts/lib/s3-roots.mjs +0 -353
  143. package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +0 -759
  144. package/assets/skills/explainer-kit/scripts/lib/set-plan.mjs +0 -209
  145. package/assets/skills/explainer-kit/scripts/lib/source-backlinks.mjs +0 -218
  146. package/assets/skills/explainer-kit/scripts/lib/terminal-evidence.mjs +0 -157
  147. package/assets/skills/explainer-kit/scripts/lib/visual-review.mjs +0 -400
  148. package/assets/skills/explainer-kit/scripts/publish.mjs +0 -76
  149. package/assets/skills/explainer-kit/scripts/record-durability.mjs +0 -64
  150. package/assets/skills/explainer-kit/scripts/render-qa.mjs +0 -461
  151. package/assets/skills/explainer-kit/scripts/run.mjs +0 -3038
  152. package/assets/skills/explainer-kit/scripts/validate.mjs +0 -59
  153. package/assets/skills/oat-explainer-kit/references/author-callback.md +0 -53
  154. package/assets/skills/oat-explainer-kit/references/migration.md +0 -144
  155. package/assets/skills/oat-explainer-kit/references/visual-review-callback.md +0 -83
  156. package/assets/skills/oat-explainer-kit/scripts/derive-destination.mjs +0 -91
  157. package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +0 -531
  158. package/assets/skills/oat-explainer-kit/scripts/probe-recap-seams.mjs +0 -371
  159. package/assets/skills/oat-explainer-kit/scripts/run.mjs +0 -905
  160. package/dist/commands/project/archive/explainer-source-backlinks.d.ts +0 -18
  161. package/dist/commands/project/archive/explainer-source-backlinks.d.ts.map +0 -1
  162. package/dist/commands/project/archive/explainer-source-backlinks.js +0 -27
  163. package/dist/commands/project/archive/explainer-terminal-evidence.d.ts +0 -29
  164. package/dist/commands/project/archive/explainer-terminal-evidence.d.ts.map +0 -1
  165. package/dist/commands/project/archive/explainer-terminal-evidence.js +0 -37
@@ -1,664 +0,0 @@
1
- import { canonicalHash, validateContract } from './contracts.mjs';
2
- import {
3
- canonicalGithubBlobBacklink,
4
- validateCanonicalGithubBlobTuple,
5
- } from './source-backlinks.mjs';
6
-
7
- const FACT_BASE_VERSION = 'explainer-kit.fact-base/v1';
8
- const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
9
- const SECTION_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
10
- const FINDING_REASONS = new Set([
11
- 'contradictory',
12
- 'stale',
13
- 'missing-evidence',
14
- 'needs-confirmation',
15
- ]);
16
-
17
- export async function processFactBase(binding, options = {}) {
18
- assertBinding(binding);
19
- const now = options.now ?? new Date().toISOString();
20
-
21
- if (binding.mode === 'supplied') {
22
- return processSupplied(binding, {
23
- now,
24
- maxAgeMs: options.maxAgeMs ?? DEFAULT_MAX_AGE_MS,
25
- });
26
- }
27
-
28
- if (typeof options.critic !== 'function') {
29
- throw new Error(
30
- 'Federated fact-base processing requires a provider-neutral critic callback.',
31
- );
32
- }
33
-
34
- return processFederated(binding, { critic: options.critic, now });
35
- }
36
-
37
- function processSupplied(binding, { now, maxAgeMs }) {
38
- const factBase = normalizeFactBaseBacklinks(
39
- structuredClone(binding.factBase),
40
- );
41
- if (!factBase || typeof factBase !== 'object') {
42
- throw new Error('Supplied mode requires a factBase object.');
43
- }
44
- if (
45
- factBase.mode !== 'supplied' ||
46
- factBase.freshnessPolicy !== binding.freshnessPolicy
47
- ) {
48
- throw new Error(
49
- 'Supplied fact base mode and freshness policy must match its binding.',
50
- );
51
- }
52
-
53
- assertValidFactBase(factBase);
54
- assertCitationConsistency(factBase);
55
-
56
- const nowMs = Date.parse(now);
57
- const staleInputs = [
58
- { label: 'fact base', timestamp: factBase.generatedAt },
59
- ...factBase.sources
60
- .filter(({ observedAt }) => observedAt)
61
- .map(({ id, observedAt }) => ({
62
- label: `source ${id}`,
63
- timestamp: observedAt,
64
- })),
65
- ].filter(({ timestamp }) => {
66
- const ageMs = nowMs - Date.parse(timestamp);
67
- return Number.isFinite(ageMs) && ageMs > maxAgeMs;
68
- });
69
-
70
- return {
71
- factBase,
72
- checks: {
73
- kind: 'lightweight-consistency-freshness',
74
- consistency: 'passed',
75
- freshness: staleInputs.length > 0 ? 'warned' : 'passed',
76
- warnings: staleInputs.map(
77
- ({ label, timestamp }) =>
78
- `Supplied ${label} is stale: ${timestamp} exceeds the ${maxAgeMs}ms freshness window.`,
79
- ),
80
- },
81
- critic: {
82
- invoked: false,
83
- reason: 'supplied-mode-lightweight-check-only',
84
- },
85
- };
86
- }
87
-
88
- async function processFederated(binding, { critic, now }) {
89
- const documents = binding.sourceDocuments;
90
- if (!Array.isArray(documents) || documents.length === 0) {
91
- throw new Error(
92
- 'Federated mode requires at least one source document with claims.',
93
- );
94
- }
95
-
96
- const sources = documents.map(({ source }) =>
97
- normalizeSourceBacklink(structuredClone(source)),
98
- );
99
- assertUniqueNonEmptyIds(sources, 'source');
100
- const sourceById = new Map(sources.map((source) => [source.id, source]));
101
- const observations = collectObservations(documents, sourceById);
102
- const overrides = normalizeOverrides(binding.overrides ?? []);
103
- const overrideByClaim = new Map(
104
- overrides.map((override) => [override.claimId, override]),
105
- );
106
-
107
- const claims = [];
108
- const unresolvedClaims = [];
109
- for (const [claimId, entries] of [...observations].sort(([left], [right]) =>
110
- left.localeCompare(right),
111
- )) {
112
- const override = overrideByClaim.get(claimId);
113
- if (override) {
114
- claims.push({
115
- id: claimId,
116
- text: override.decision,
117
- status: 'overridden',
118
- citations: citationsFor(entries),
119
- ...sectionMetadata(entries),
120
- });
121
- continue;
122
- }
123
-
124
- const resolved = resolveObservations(claimId, entries);
125
- if (resolved.status === 'confirmed') {
126
- claims.push(resolved.claim);
127
- } else {
128
- unresolvedClaims.push(resolved.claim);
129
- }
130
- }
131
-
132
- for (const override of overrides) {
133
- if (!observations.has(override.claimId)) {
134
- throw new Error(
135
- `Operator override references unknown claim ${override.claimId}.`,
136
- );
137
- }
138
- }
139
-
140
- const criticRequest = {
141
- freshnessPolicy: binding.freshnessPolicy,
142
- sources: structuredClone(sources),
143
- claims: structuredClone(claims),
144
- overrides: structuredClone(overrides),
145
- };
146
- const criticResult = await critic(criticRequest);
147
- const criticProvenance = validateCriticResult(criticResult, now, sources);
148
- sources.push(criticProvenance.source);
149
-
150
- integrateCriticFindings({
151
- findings: criticResult.findings,
152
- claims,
153
- unresolvedClaims,
154
- overrides: overrideByClaim,
155
- sourceById: new Map(sources.map((source) => [source.id, source])),
156
- criticSourceId: criticProvenance.source.id,
157
- });
158
-
159
- const factBase = {
160
- schemaVersion: FACT_BASE_VERSION,
161
- generatedAt: now,
162
- mode: 'federated',
163
- freshnessPolicy: binding.freshnessPolicy,
164
- sources,
165
- claims: claims.sort(byId),
166
- unresolvedClaims: unresolvedClaims.sort(byId),
167
- overrides,
168
- };
169
- assertValidFactBase(factBase);
170
- assertCitationConsistency(factBase);
171
-
172
- return {
173
- factBase,
174
- checks: {
175
- kind: 'federated-reconciliation',
176
- consistency: 'passed',
177
- freshness: 'passed',
178
- warnings: [],
179
- },
180
- critic: criticProvenance.record,
181
- };
182
- }
183
-
184
- function assertBinding(binding) {
185
- if (!binding || typeof binding !== 'object') {
186
- throw new Error('Fact-base binding must be an object.');
187
- }
188
- if (!['supplied', 'federated'].includes(binding.mode)) {
189
- throw new Error(`Unsupported fact-base mode: ${binding.mode}`);
190
- }
191
- if (binding.freshnessPolicy !== 'live-wins') {
192
- throw new Error('Fact-base freshnessPolicy must be live-wins.');
193
- }
194
- }
195
-
196
- function assertValidFactBase(factBase) {
197
- const validation = validateContract('fact-base', factBase);
198
- if (!validation.valid) {
199
- throw new Error(
200
- `Invalid fact-base contract: ${validation.errors
201
- .map(({ path, message }) => `${path}: ${message}`)
202
- .join('; ')}`,
203
- );
204
- }
205
- }
206
-
207
- function assertCitationConsistency(factBase) {
208
- const sourceIds = new Set(factBase.sources.map(({ id }) => id));
209
- assertUniqueNonEmptyIds(factBase.sources, 'source');
210
- assertUniqueNonEmptyIds(
211
- [...factBase.claims, ...factBase.unresolvedClaims],
212
- 'claim',
213
- );
214
-
215
- for (const claim of [...factBase.claims, ...factBase.unresolvedClaims]) {
216
- for (const citation of claim.citations) {
217
- if (!sourceIds.has(citation.sourceId)) {
218
- throw new Error(
219
- `Claim ${claim.id} cites unknown source ${citation.sourceId}.`,
220
- );
221
- }
222
- }
223
- }
224
-
225
- for (const override of factBase.overrides) {
226
- if (!factBase.claims.some(({ id }) => id === override.claimId)) {
227
- throw new Error(
228
- `Override references unknown confirmed claim ${override.claimId}.`,
229
- );
230
- }
231
- }
232
- }
233
-
234
- function assertUniqueNonEmptyIds(values, label) {
235
- const ids = values.map(({ id }) => id);
236
- if (
237
- ids.some((id) => typeof id !== 'string' || id.length === 0) ||
238
- new Set(ids).size !== ids.length
239
- ) {
240
- throw new Error(`${label} IDs must be non-empty and unique.`);
241
- }
242
- }
243
-
244
- function collectObservations(documents, sourceById) {
245
- const observations = new Map();
246
- for (const document of documents) {
247
- if (
248
- !document ||
249
- typeof document !== 'object' ||
250
- !document.source ||
251
- !sourceById.has(document.source.id)
252
- ) {
253
- throw new Error('Every source document requires a valid source.');
254
- }
255
- if (!Array.isArray(document.claims)) {
256
- throw new Error(`Source ${document.source.id} requires a claims array.`);
257
- }
258
- for (const claim of document.claims) {
259
- if (
260
- !claim ||
261
- typeof claim.id !== 'string' ||
262
- claim.id.length === 0 ||
263
- typeof claim.text !== 'string' ||
264
- claim.text.length === 0 ||
265
- !validSections(claim.sections)
266
- ) {
267
- throw new Error(
268
- `Source ${document.source.id} contains an invalid claim.`,
269
- );
270
- }
271
- const entries = observations.get(claim.id) ?? [];
272
- entries.push({
273
- claimId: claim.id,
274
- text: claim.text,
275
- source: sourceById.get(document.source.id),
276
- locator: claim.locator ?? document.source.locator,
277
- ...(claim.lineRange && { lineRange: structuredClone(claim.lineRange) }),
278
- ...(claim.sections && { sections: [...claim.sections] }),
279
- });
280
- observations.set(claim.id, entries);
281
- }
282
- }
283
- return observations;
284
- }
285
-
286
- function normalizeOverrides(overrides) {
287
- const normalized = structuredClone(overrides);
288
- const claimIds = normalized.map(({ claimId }) => claimId);
289
- if (new Set(claimIds).size !== claimIds.length) {
290
- throw new Error('Operator overrides must have unique claim IDs.');
291
- }
292
- for (const override of normalized) {
293
- if (
294
- typeof override.claimId !== 'string' ||
295
- override.claimId.length === 0 ||
296
- typeof override.decision !== 'string' ||
297
- override.decision.length === 0 ||
298
- Number.isNaN(Date.parse(override.confirmedAt))
299
- ) {
300
- throw new Error('Operator override is invalid.');
301
- }
302
- }
303
- return normalized.sort((left, right) =>
304
- left.claimId.localeCompare(right.claimId),
305
- );
306
- }
307
-
308
- function resolveObservations(claimId, entries) {
309
- const byText = new Map();
310
- for (const entry of entries) {
311
- const values = byText.get(entry.text) ?? [];
312
- values.push(entry);
313
- byText.set(entry.text, values);
314
- }
315
- if (byText.size === 1) {
316
- return {
317
- status: 'confirmed',
318
- claim: {
319
- id: claimId,
320
- text: entries[0].text,
321
- status: 'confirmed',
322
- citations: citationsFor(entries),
323
- ...sectionMetadata(entries),
324
- },
325
- };
326
- }
327
-
328
- const ranked = entries
329
- .map((entry, index) => ({
330
- entry,
331
- index,
332
- authoritative:
333
- entry.source.authoritativeFor?.includes(claimId) === true ? 1 : 0,
334
- observedAt: Date.parse(entry.source.observedAt ?? '') || 0,
335
- }))
336
- .sort(
337
- (left, right) =>
338
- right.authoritative - left.authoritative ||
339
- right.observedAt - left.observedAt ||
340
- left.index - right.index,
341
- );
342
- const winner = ranked[0];
343
- const runnerUp = ranked[1];
344
- const decisive =
345
- winner.authoritative > runnerUp.authoritative ||
346
- winner.observedAt > runnerUp.observedAt;
347
-
348
- if (decisive) {
349
- return {
350
- status: 'confirmed',
351
- claim: {
352
- id: claimId,
353
- text: winner.entry.text,
354
- status: 'confirmed',
355
- citations: citationsFor(
356
- entries.filter(({ text }) => text === winner.entry.text),
357
- ),
358
- ...sectionMetadata(
359
- entries.filter(({ text }) => text === winner.entry.text),
360
- ),
361
- },
362
- };
363
- }
364
-
365
- return {
366
- status: 'unresolved',
367
- claim: {
368
- id: claimId,
369
- text: `Conflicting values: ${[...byText.keys()].sort().join(' | ')}`,
370
- reason: 'contradictory',
371
- citations: citationsFor(entries),
372
- ...sectionMetadata(entries),
373
- },
374
- };
375
- }
376
-
377
- function citationsFor(entries) {
378
- const citations = entries.map(({ source, locator, lineRange }) =>
379
- citationForSource(source, { locator, lineRange }),
380
- );
381
- return uniqueCitations(citations);
382
- }
383
-
384
- function validateCriticResult(result, now, sources) {
385
- if (
386
- !result ||
387
- typeof result !== 'object' ||
388
- typeof result.criticId !== 'string' ||
389
- !/^[a-z0-9][a-z0-9._-]*$/.test(result.criticId) ||
390
- !Array.isArray(result.findings)
391
- ) {
392
- throw new Error(
393
- 'Critic callback must return criticId and a findings array.',
394
- );
395
- }
396
- const executedAt = result.executedAt ?? now;
397
- if (Number.isNaN(Date.parse(executedAt))) {
398
- throw new Error('Critic executedAt must be an ISO timestamp.');
399
- }
400
- const sourceId = `critic:${result.criticId}`;
401
- if (sources.some(({ id }) => id === sourceId)) {
402
- throw new Error(`Critic provenance source ID collides: ${sourceId}.`);
403
- }
404
- const resultHash = canonicalHash(result);
405
- return {
406
- source: {
407
- id: sourceId,
408
- kind: 'other',
409
- locator: `critic-callback:${result.criticId}`,
410
- hash: resultHash,
411
- observedAt: executedAt,
412
- },
413
- record: {
414
- invoked: true,
415
- criticId: result.criticId,
416
- executedAt,
417
- sourceId,
418
- resultHash,
419
- },
420
- };
421
- }
422
-
423
- function integrateCriticFindings({
424
- findings,
425
- claims,
426
- unresolvedClaims,
427
- overrides,
428
- sourceById,
429
- criticSourceId,
430
- }) {
431
- for (const finding of findings) {
432
- validateFinding(finding, sourceById);
433
- if (overrides.has(finding.claimId)) {
434
- continue;
435
- }
436
-
437
- const citations = uniqueCitations([
438
- {
439
- sourceId: criticSourceId,
440
- locator: `critic-finding:${finding.claimId}`,
441
- },
442
- ...finding.sourceIds.map((sourceId) => ({
443
- ...citationForSource(sourceById.get(sourceId)),
444
- })),
445
- ]);
446
- const existingUnresolved = unresolvedClaims.find(
447
- ({ id }) => id === finding.claimId,
448
- );
449
- if (existingUnresolved) {
450
- existingUnresolved.reason = finding.classification;
451
- existingUnresolved.text = finding.text;
452
- existingUnresolved.citations = uniqueCitations([
453
- ...existingUnresolved.citations,
454
- ...citations,
455
- ]);
456
- continue;
457
- }
458
-
459
- const claimIndex = claims.findIndex(({ id }) => id === finding.claimId);
460
- const existingClaim = claimIndex >= 0 ? claims[claimIndex] : null;
461
- if (claimIndex >= 0) {
462
- claims.splice(claimIndex, 1);
463
- }
464
- unresolvedClaims.push({
465
- id: finding.claimId,
466
- text: finding.text,
467
- reason: finding.classification,
468
- citations,
469
- ...(existingClaim?.sections && { sections: existingClaim.sections }),
470
- });
471
- }
472
- }
473
-
474
- function validateFinding(finding, sourceById) {
475
- if (
476
- !finding ||
477
- typeof finding.claimId !== 'string' ||
478
- finding.claimId.length === 0 ||
479
- typeof finding.text !== 'string' ||
480
- finding.text.length === 0 ||
481
- !FINDING_REASONS.has(finding.classification) ||
482
- !Array.isArray(finding.sourceIds)
483
- ) {
484
- throw new Error('Critic returned an invalid finding.');
485
- }
486
- for (const sourceId of finding.sourceIds) {
487
- if (!sourceById.has(sourceId)) {
488
- throw new Error(`Critic finding cites unknown source ${sourceId}.`);
489
- }
490
- }
491
- }
492
-
493
- function uniqueCitations(citations) {
494
- return [
495
- ...new Map(
496
- citations
497
- .sort(
498
- (left, right) =>
499
- left.sourceId.localeCompare(right.sourceId) ||
500
- left.locator.localeCompare(right.locator),
501
- )
502
- .map((citation) => [
503
- `${citation.sourceId}\0${citation.locator}`,
504
- citation,
505
- ]),
506
- ).values(),
507
- ];
508
- }
509
-
510
- function normalizeFactBaseBacklinks(factBase) {
511
- if (!factBase || typeof factBase !== 'object') return factBase;
512
- if (
513
- !Array.isArray(factBase.sources) ||
514
- !Array.isArray(factBase.claims) ||
515
- !Array.isArray(factBase.unresolvedClaims)
516
- ) {
517
- return factBase;
518
- }
519
- factBase.sources = factBase.sources.map((source) =>
520
- source && typeof source === 'object'
521
- ? normalizeSourceBacklink(source)
522
- : source,
523
- );
524
- const sourceById = new Map(
525
- factBase.sources
526
- .filter((source) => source && typeof source === 'object')
527
- .map((source) => [source.id, source]),
528
- );
529
- for (const claim of [...factBase.claims, ...factBase.unresolvedClaims]) {
530
- if (
531
- !claim ||
532
- typeof claim !== 'object' ||
533
- !Array.isArray(claim.citations)
534
- ) {
535
- continue;
536
- }
537
- claim.citations = claim.citations.map((citation) => {
538
- if (!citation || typeof citation !== 'object') return citation;
539
- const source = sourceById.get(citation.sourceId);
540
- return source ? citationForSource(source, citation) : citation;
541
- });
542
- }
543
- return factBase;
544
- }
545
-
546
- function normalizeSourceBacklink(source) {
547
- const declaresBacklink = declaresBacklinkTuple(source);
548
- if (!declaresBacklink) return source;
549
- const url = canonicalGithubBlobBacklink(source);
550
- if (source.url !== undefined && source.url !== url) {
551
- throw new Error(
552
- 'GitHub backlink provenance URL does not match its canonical tuple.',
553
- );
554
- }
555
- return {
556
- ...source,
557
- kind: 'github',
558
- locator: url,
559
- url,
560
- };
561
- }
562
-
563
- function citationForSource(source, citation = {}) {
564
- if (source.url === undefined && !declaresBacklinkTuple(citation)) {
565
- return {
566
- sourceId: source.id,
567
- locator: citation.locator ?? source.locator,
568
- };
569
- }
570
- if (source.url === undefined) {
571
- if (!validateCanonicalGithubBlobTuple(citation)) {
572
- throw new Error(
573
- 'GitHub citation backlink requires one complete canonical provenance tuple.',
574
- );
575
- }
576
- return {
577
- sourceId: source.id,
578
- locator: citation.url,
579
- repository: citation.repository,
580
- revision: citation.revision,
581
- path: citation.path,
582
- lineRange: structuredClone(citation.lineRange),
583
- url: citation.url,
584
- };
585
- }
586
- if (
587
- declaresBacklinkTuple(citation) &&
588
- ['repository', 'revision', 'path', 'url'].some(
589
- (field) => citation[field] !== undefined,
590
- )
591
- ) {
592
- const supplied = {
593
- repository: citation.repository,
594
- revision: citation.revision,
595
- path: citation.path,
596
- lineRange: citation.lineRange,
597
- url: citation.url,
598
- };
599
- if (
600
- !validateCanonicalGithubBlobTuple(supplied) ||
601
- supplied.repository !== source.repository ||
602
- supplied.revision !== source.revision ||
603
- supplied.path !== source.path
604
- ) {
605
- throw new Error(
606
- 'GitHub citation backlink does not match its canonical source tuple.',
607
- );
608
- }
609
- }
610
- const range = citation.lineRange ?? source.lineRange;
611
- const tuple = {
612
- repository: source.repository,
613
- revision: source.revision,
614
- path: source.path,
615
- lineRange: range,
616
- };
617
- const url = canonicalGithubBlobBacklink(tuple);
618
- return {
619
- sourceId: source.id,
620
- locator: url,
621
- repository: source.repository,
622
- revision: source.revision,
623
- path: source.path,
624
- lineRange: structuredClone(range),
625
- url,
626
- };
627
- }
628
-
629
- function declaresBacklinkTuple(value) {
630
- return (
631
- value &&
632
- typeof value === 'object' &&
633
- ['repository', 'path', 'lineRange', 'url'].some(
634
- (field) => value[field] !== undefined,
635
- )
636
- );
637
- }
638
-
639
- function validSections(sections) {
640
- return (
641
- sections === undefined ||
642
- (Array.isArray(sections) &&
643
- sections.length > 0 &&
644
- new Set(sections).size === sections.length &&
645
- sections.every(
646
- (section) =>
647
- typeof section === 'string' && SECTION_ID_PATTERN.test(section),
648
- ))
649
- );
650
- }
651
-
652
- function sectionMetadata(entries) {
653
- if (entries.some(({ sections }) => sections === undefined)) {
654
- return {};
655
- }
656
- const sections = [
657
- ...new Set(entries.flatMap((entry) => entry.sections ?? [])),
658
- ].sort();
659
- return sections.length > 0 ? { sections } : {};
660
- }
661
-
662
- function byId(left, right) {
663
- return left.id.localeCompare(right.id);
664
- }