@open-agent-toolkit/cli 0.2.6 → 0.2.8
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/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +3 -1
- package/assets/skills/explainer-kit/references/contracts.md +4 -1
- package/assets/skills/explainer-kit/references/fact-base-contract.md +6 -1
- package/assets/skills/explainer-kit/schemas/fact-base.schema.json +11 -0
- package/assets/skills/explainer-kit/scripts/lib/durability.mjs +2 -2
- package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +35 -1
- package/assets/skills/explainer-kit/scripts/lib/records.mjs +54 -1
- package/assets/skills/explainer-kit/scripts/run.mjs +33 -10
- package/assets/skills/oat-wave-execute/SKILL.md +1 -1
- package/package.json +2 -2
- package/assets/skills/oat-wave-execute/assets/program-recap.recipe.json +0 -34
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: explainer-kit
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: Use when building destination-neutral visual explainer artifacts from explicit, versioned inputs.
|
|
5
5
|
user-invocable: true
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Agent, mcp__*
|
|
@@ -55,6 +55,8 @@ discovery, theme resolution, rendering, QA, and manifest/build-record
|
|
|
55
55
|
persistence. It runs without OAT files or ambient configuration. Supplied fact
|
|
56
56
|
bases receive only lightweight consistency/freshness checks. Federated inputs
|
|
57
57
|
require a provider-neutral critic callback and invoke it exactly once.
|
|
58
|
+
Optional claim `sections` tags route facts to matching recipe narrative
|
|
59
|
+
sections; untagged claims remain shared context for every required section.
|
|
58
60
|
|
|
59
61
|
Unattended calls use explicit, already-approved source artifacts, persist their
|
|
60
62
|
review provenance in `source/content-approval.json`, and never prompt.
|
|
@@ -36,7 +36,10 @@ contract.
|
|
|
36
36
|
performs only the lightweight consistency and freshness check and never
|
|
37
37
|
invokes the critic.
|
|
38
38
|
- `factBase.mode: federated` names explicit source bindings. File locators
|
|
39
|
-
contain JSON with a `claims` array of
|
|
39
|
+
contain JSON with a `claims` array of
|
|
40
|
+
`{ "id", "text", "locator"?, "sections"? }`. Optional `sections` values
|
|
41
|
+
are recipe `requiredNarrative` IDs; untagged claims remain shared across
|
|
42
|
+
every required section.
|
|
40
43
|
Non-file bindings require a caller-supplied `sourceLoader(source)` callback.
|
|
41
44
|
Every binding names its recipe `role` and `sourceSetId`. Multiple documents
|
|
42
45
|
may share one source-set ID; recipe cardinality counts distinct sets, not
|
|
@@ -58,12 +58,17 @@ Each source document contains a schema-compatible source and extracted claims:
|
|
|
58
58
|
observedAt,
|
|
59
59
|
authoritativeFor,
|
|
60
60
|
},
|
|
61
|
-
claims: [{ id, text, locator }],
|
|
61
|
+
claims: [{ id, text, locator, sections }],
|
|
62
62
|
}],
|
|
63
63
|
overrides: [{ claimId, decision, confirmedAt }],
|
|
64
64
|
}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
Optional `sections` entries are recipe `requiredNarrative` IDs. Tagged claims
|
|
68
|
+
are routed only to those sections; untagged claims remain shared context for
|
|
69
|
+
every required section. Federated reconciliation preserves shared scope when
|
|
70
|
+
any agreeing selected observation is untagged.
|
|
71
|
+
|
|
67
72
|
For conflicting text under one claim ID, an `authoritativeFor` declaration
|
|
68
73
|
wins first. Otherwise the newest `observedAt` wins. A tie remains
|
|
69
74
|
`contradictory`. Operator overrides take final precedence, produce an
|
|
@@ -46,6 +46,15 @@
|
|
|
46
46
|
"type": "string",
|
|
47
47
|
"pattern": "^sha256:[a-f0-9]{64}$"
|
|
48
48
|
},
|
|
49
|
+
"sectionIds": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
54
|
+
},
|
|
55
|
+
"minItems": 1,
|
|
56
|
+
"uniqueItems": true
|
|
57
|
+
},
|
|
49
58
|
"source": {
|
|
50
59
|
"type": "object",
|
|
51
60
|
"additionalProperties": false,
|
|
@@ -85,6 +94,7 @@
|
|
|
85
94
|
"id": { "type": "string", "minLength": 1 },
|
|
86
95
|
"text": { "type": "string", "minLength": 1 },
|
|
87
96
|
"status": { "enum": ["confirmed", "overridden"] },
|
|
97
|
+
"sections": { "$ref": "#/$defs/sectionIds" },
|
|
88
98
|
"citations": {
|
|
89
99
|
"type": "array",
|
|
90
100
|
"items": { "$ref": "#/$defs/citation" },
|
|
@@ -108,6 +118,7 @@
|
|
|
108
118
|
"needs-confirmation"
|
|
109
119
|
]
|
|
110
120
|
},
|
|
121
|
+
"sections": { "$ref": "#/$defs/sectionIds" },
|
|
111
122
|
"citations": {
|
|
112
123
|
"type": "array",
|
|
113
124
|
"items": { "$ref": "#/$defs/citation" },
|
|
@@ -166,10 +166,10 @@ export async function verifyRebuildability(artifact, runRoot) {
|
|
|
166
166
|
await writeFileAtomic(runRoot, artifact.renderedPath, original);
|
|
167
167
|
}
|
|
168
168
|
return { verified: true, reason: null };
|
|
169
|
-
} catch (
|
|
169
|
+
} catch (caught) {
|
|
170
170
|
return {
|
|
171
171
|
verified: false,
|
|
172
|
-
reason: `Deterministic replay failed: ${errorMessage(
|
|
172
|
+
reason: `Deterministic replay failed: ${errorMessage(caught)}`,
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
}
|
|
@@ -2,6 +2,7 @@ import { canonicalHash, validateContract } from './contracts.mjs';
|
|
|
2
2
|
|
|
3
3
|
const FACT_BASE_VERSION = 'explainer-kit.fact-base/v1';
|
|
4
4
|
const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
5
|
+
const SECTION_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
5
6
|
const FINDING_REASONS = new Set([
|
|
6
7
|
'contradictory',
|
|
7
8
|
'stale',
|
|
@@ -107,6 +108,7 @@ async function processFederated(binding, { critic, now }) {
|
|
|
107
108
|
text: override.decision,
|
|
108
109
|
status: 'overridden',
|
|
109
110
|
citations: citationsFor(entries),
|
|
111
|
+
...sectionMetadata(entries),
|
|
110
112
|
});
|
|
111
113
|
continue;
|
|
112
114
|
}
|
|
@@ -251,7 +253,8 @@ function collectObservations(documents, sourceById) {
|
|
|
251
253
|
typeof claim.id !== 'string' ||
|
|
252
254
|
claim.id.length === 0 ||
|
|
253
255
|
typeof claim.text !== 'string' ||
|
|
254
|
-
claim.text.length === 0
|
|
256
|
+
claim.text.length === 0 ||
|
|
257
|
+
!validSections(claim.sections)
|
|
255
258
|
) {
|
|
256
259
|
throw new Error(
|
|
257
260
|
`Source ${document.source.id} contains an invalid claim.`,
|
|
@@ -263,6 +266,7 @@ function collectObservations(documents, sourceById) {
|
|
|
263
266
|
text: claim.text,
|
|
264
267
|
source: sourceById.get(document.source.id),
|
|
265
268
|
locator: claim.locator ?? document.source.locator,
|
|
269
|
+
...(claim.sections && { sections: [...claim.sections] }),
|
|
266
270
|
});
|
|
267
271
|
observations.set(claim.id, entries);
|
|
268
272
|
}
|
|
@@ -307,6 +311,7 @@ function resolveObservations(claimId, entries) {
|
|
|
307
311
|
text: entries[0].text,
|
|
308
312
|
status: 'confirmed',
|
|
309
313
|
citations: citationsFor(entries),
|
|
314
|
+
...sectionMetadata(entries),
|
|
310
315
|
},
|
|
311
316
|
};
|
|
312
317
|
}
|
|
@@ -341,6 +346,9 @@ function resolveObservations(claimId, entries) {
|
|
|
341
346
|
citations: citationsFor(
|
|
342
347
|
entries.filter(({ text }) => text === winner.entry.text),
|
|
343
348
|
),
|
|
349
|
+
...sectionMetadata(
|
|
350
|
+
entries.filter(({ text }) => text === winner.entry.text),
|
|
351
|
+
),
|
|
344
352
|
},
|
|
345
353
|
};
|
|
346
354
|
}
|
|
@@ -352,6 +360,7 @@ function resolveObservations(claimId, entries) {
|
|
|
352
360
|
text: `Conflicting values: ${[...byText.keys()].sort().join(' | ')}`,
|
|
353
361
|
reason: 'contradictory',
|
|
354
362
|
citations: citationsFor(entries),
|
|
363
|
+
...sectionMetadata(entries),
|
|
355
364
|
},
|
|
356
365
|
};
|
|
357
366
|
}
|
|
@@ -441,6 +450,7 @@ function integrateCriticFindings({
|
|
|
441
450
|
}
|
|
442
451
|
|
|
443
452
|
const claimIndex = claims.findIndex(({ id }) => id === finding.claimId);
|
|
453
|
+
const existingClaim = claimIndex >= 0 ? claims[claimIndex] : null;
|
|
444
454
|
if (claimIndex >= 0) {
|
|
445
455
|
claims.splice(claimIndex, 1);
|
|
446
456
|
}
|
|
@@ -449,6 +459,7 @@ function integrateCriticFindings({
|
|
|
449
459
|
text: finding.text,
|
|
450
460
|
reason: finding.classification,
|
|
451
461
|
citations,
|
|
462
|
+
...(existingClaim?.sections && { sections: existingClaim.sections }),
|
|
452
463
|
});
|
|
453
464
|
}
|
|
454
465
|
}
|
|
@@ -489,6 +500,29 @@ function uniqueCitations(citations) {
|
|
|
489
500
|
];
|
|
490
501
|
}
|
|
491
502
|
|
|
503
|
+
function validSections(sections) {
|
|
504
|
+
return (
|
|
505
|
+
sections === undefined ||
|
|
506
|
+
(Array.isArray(sections) &&
|
|
507
|
+
sections.length > 0 &&
|
|
508
|
+
new Set(sections).size === sections.length &&
|
|
509
|
+
sections.every(
|
|
510
|
+
(section) =>
|
|
511
|
+
typeof section === 'string' && SECTION_ID_PATTERN.test(section),
|
|
512
|
+
))
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function sectionMetadata(entries) {
|
|
517
|
+
if (entries.some(({ sections }) => sections === undefined)) {
|
|
518
|
+
return {};
|
|
519
|
+
}
|
|
520
|
+
const sections = [
|
|
521
|
+
...new Set(entries.flatMap((entry) => entry.sections ?? [])),
|
|
522
|
+
].sort();
|
|
523
|
+
return sections.length > 0 ? { sections } : {};
|
|
524
|
+
}
|
|
525
|
+
|
|
492
526
|
function byId(left, right) {
|
|
493
527
|
return left.id.localeCompare(right.id);
|
|
494
528
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { readFile, readdir, rm } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { validateContract } from './contracts.mjs';
|
|
@@ -70,6 +70,7 @@ export async function initializeRun(request) {
|
|
|
70
70
|
request: normalizedRequest,
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
await clearRunRoot(run);
|
|
73
74
|
await writeJsonAtomic(run.runRoot, 'build-record.json', buildRecord);
|
|
74
75
|
await writeJsonAtomic(
|
|
75
76
|
run.runRoot,
|
|
@@ -226,6 +227,58 @@ function assertRun(run) {
|
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
async function clearRunRoot(run) {
|
|
231
|
+
const entries = await readdir(run.runRoot, { withFileTypes: true });
|
|
232
|
+
if (entries.length === 0) return;
|
|
233
|
+
await assertOwnedRunRoot(run);
|
|
234
|
+
const removable = [];
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
const path = join(run.runRoot, entry.name);
|
|
237
|
+
if (!(await containsSymlink(path, entry))) {
|
|
238
|
+
removable.push(path);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
await Promise.all(
|
|
242
|
+
removable.map((path) => rm(path, { recursive: true, force: true })),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function assertOwnedRunRoot(run) {
|
|
247
|
+
try {
|
|
248
|
+
const [persistedRequest, persistedRecord] = await Promise.all([
|
|
249
|
+
readJson(join(run.runRoot, 'run-request.json')),
|
|
250
|
+
readJson(join(run.runRoot, 'build-record.json')),
|
|
251
|
+
]);
|
|
252
|
+
assertValidContract('run-request', persistedRequest);
|
|
253
|
+
assertValidContract('build-record', persistedRecord);
|
|
254
|
+
if (
|
|
255
|
+
persistedRequest.slug !== run.slug ||
|
|
256
|
+
persistedRequest.outputRoot !== run.outputRoot
|
|
257
|
+
) {
|
|
258
|
+
throw new Error('Prior run identity does not match this slug.');
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
throw new Error(
|
|
262
|
+
'Existing slug directory is not a prior Explainer Kit run; refusing to clear it.',
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function containsSymlink(path, entry) {
|
|
268
|
+
if (entry.isSymbolicLink()) return true;
|
|
269
|
+
if (!entry.isDirectory()) return false;
|
|
270
|
+
for (const child of await readdir(path, { withFileTypes: true })) {
|
|
271
|
+
if (await containsSymlink(join(path, child.name), child)) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function readJson(path) {
|
|
279
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
280
|
+
}
|
|
281
|
+
|
|
229
282
|
function isObject(value) {
|
|
230
283
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
231
284
|
}
|
|
@@ -227,7 +227,9 @@ async function loadResumableRun(request) {
|
|
|
227
227
|
persistedRequest.slug !== normalized.slug ||
|
|
228
228
|
persistedRequest.recipe?.id !== normalized.recipe.id ||
|
|
229
229
|
persistedRequest.recipe?.version !== normalized.recipe.version ||
|
|
230
|
-
persistedRequest.mode !== normalized.mode
|
|
230
|
+
persistedRequest.mode !== normalized.mode ||
|
|
231
|
+
canonicalHash(persistedRequest.factBase) !==
|
|
232
|
+
canonicalHash(normalized.factBase)
|
|
231
233
|
) {
|
|
232
234
|
throw codedError(
|
|
233
235
|
'E_APPROVAL_RESUME',
|
|
@@ -551,12 +553,25 @@ function manifestFor(state, buildRecord, createdAt, immutableHashes) {
|
|
|
551
553
|
|
|
552
554
|
function createContentModel(recipe, artifact, slug, factBase) {
|
|
553
555
|
const facts = [
|
|
554
|
-
...factBase.claims.map(({ text }) => text),
|
|
555
|
-
...factBase.unresolvedClaims.map(
|
|
556
|
-
|
|
556
|
+
...factBase.claims.map(({ text, sections }) => ({ text, sections })),
|
|
557
|
+
...factBase.unresolvedClaims.map(({ text, sections }) => ({
|
|
558
|
+
text: `Needs confirmation: ${text}`,
|
|
559
|
+
sections,
|
|
560
|
+
})),
|
|
561
|
+
];
|
|
562
|
+
const unknownSections = [
|
|
563
|
+
...new Set(
|
|
564
|
+
facts
|
|
565
|
+
.flatMap(({ sections }) => sections ?? [])
|
|
566
|
+
.filter((section) => !recipe.requiredNarrative.includes(section)),
|
|
557
567
|
),
|
|
558
568
|
];
|
|
559
|
-
|
|
569
|
+
if (unknownSections.length > 0) {
|
|
570
|
+
throw codedError(
|
|
571
|
+
'E_CONTENT',
|
|
572
|
+
`Unknown narrative section tags: ${unknownSections.join(', ')}`,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
560
575
|
return {
|
|
561
576
|
artifactId: artifact.id,
|
|
562
577
|
slug,
|
|
@@ -564,11 +579,19 @@ function createContentModel(recipe, artifact, slug, factBase) {
|
|
|
564
579
|
description: `Approved-source ${humanize(recipe.id).toLowerCase()}.`,
|
|
565
580
|
eyebrow: 'Explainer Kit',
|
|
566
581
|
footer: 'Generated from the retained reconciled fact base.',
|
|
567
|
-
sections: recipe.requiredNarrative.map((id) =>
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
582
|
+
sections: recipe.requiredNarrative.map((id) => {
|
|
583
|
+
const sectionFacts = facts
|
|
584
|
+
.filter(({ sections }) => !sections || sections.includes(id))
|
|
585
|
+
.map(({ text }) => text);
|
|
586
|
+
return {
|
|
587
|
+
id,
|
|
588
|
+
title: humanize(id),
|
|
589
|
+
content:
|
|
590
|
+
sectionFacts.length > 0
|
|
591
|
+
? sectionFacts.join(' ')
|
|
592
|
+
: 'No confirmed facts.',
|
|
593
|
+
};
|
|
594
|
+
}),
|
|
572
595
|
artifactLinks: [],
|
|
573
596
|
};
|
|
574
597
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oat-wave-execute
|
|
3
|
-
version: 1.6.
|
|
3
|
+
version: 1.6.2
|
|
4
4
|
description: Use when executing a wave of external implementation plans as a wrapper OAT project — scaffolding, drift refresh, parallel worktree groups, briefs, gates, merge choreography, and closeout.
|
|
5
5
|
argument-hint: '<wave-id> [plan-names...] (e.g. wave-2 http-listener-before-indexing ...)'
|
|
6
6
|
disable-model-invocation: false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-agent-toolkit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Open Agent Toolkit CLI",
|
|
6
6
|
"homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"ora": "^9.0.0",
|
|
35
35
|
"yaml": "2.8.2",
|
|
36
36
|
"zod": "^3.25.76",
|
|
37
|
-
"@open-agent-toolkit/control-plane": "0.2.
|
|
37
|
+
"@open-agent-toolkit/control-plane": "0.2.8"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.0",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": "explainer-kit.recipe/v1",
|
|
3
|
-
"id": "program-recap",
|
|
4
|
-
"version": "1",
|
|
5
|
-
"sourceRoles": [
|
|
6
|
-
{
|
|
7
|
-
"role": "program",
|
|
8
|
-
"required": true,
|
|
9
|
-
"accepts": ["file", "directory", "git"],
|
|
10
|
-
"minBindings": 1,
|
|
11
|
-
"maxBindings": 1
|
|
12
|
-
}
|
|
13
|
-
],
|
|
14
|
-
"requiredNarrative": [
|
|
15
|
-
"program-overview",
|
|
16
|
-
"wave-map",
|
|
17
|
-
"per-wave-outcomes",
|
|
18
|
-
"convention-evolution",
|
|
19
|
-
"aggregate-numbers",
|
|
20
|
-
"follow-up-ledger"
|
|
21
|
-
],
|
|
22
|
-
"artifacts": [
|
|
23
|
-
{
|
|
24
|
-
"id": "program-recap",
|
|
25
|
-
"type": "hub",
|
|
26
|
-
"template": "house-style",
|
|
27
|
-
"required": true
|
|
28
|
-
}
|
|
29
|
-
],
|
|
30
|
-
"discoveryLimits": {
|
|
31
|
-
"consecutiveNoNewFindingsRounds": 2,
|
|
32
|
-
"maxRounds": 8
|
|
33
|
-
}
|
|
34
|
-
}
|