@open-agent-toolkit/cli 0.2.10 → 0.2.11
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/docs/cli-utilities/configuration.md +28 -14
- package/assets/docs/reference/cli-reference.md +1 -1
- package/assets/docs/workflows/projects/artifacts.md +16 -4
- package/assets/docs/workflows/skills/explainer-kit.md +70 -27
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +9 -3
- package/assets/skills/explainer-kit/references/contracts.md +22 -7
- package/assets/skills/explainer-kit/schemas/author-request.schema.json +85 -0
- package/assets/skills/explainer-kit/schemas/author-result.schema.json +65 -0
- package/assets/skills/explainer-kit/schemas/manifest.schema.json +7 -1
- package/assets/skills/explainer-kit/schemas/run-request.schema.json +8 -0
- package/assets/skills/explainer-kit/schemas/theme.schema.json +8 -0
- package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +26 -1
- package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +61 -0
- package/assets/skills/explainer-kit/scripts/lib/durability.mjs +2 -14
- package/assets/skills/explainer-kit/scripts/lib/qa.mjs +56 -0
- package/assets/skills/explainer-kit/scripts/lib/theme.mjs +102 -4
- package/assets/skills/explainer-kit/scripts/run.mjs +144 -11
- package/assets/skills/explainer-kit/styles/business-corporate.json +77 -0
- package/assets/skills/explainer-kit/styles/clean-neutral.json +77 -0
- package/assets/skills/explainer-kit/styles/dark-edgy.json +77 -0
- package/assets/skills/explainer-kit/styles/navy-ocean.json +82 -0
- package/assets/skills/explainer-kit/templates/deck-shell.html +57 -2
- package/assets/skills/oat-explainer-kit/SKILL.md +15 -9
- package/assets/skills/oat-explainer-kit/references/config-contract.md +11 -6
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +13 -0
- package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +10 -14
- package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +53 -9
- package/assets/skills/oat-explainer-kit/scripts/run.mjs +68 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +34 -8
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +14 -5
- package/dist/config/oat-config.d.ts +2 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +11 -0
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +3 -2
- package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
- package/dist/providers/codex/codec/config-merge.js +39 -1
- package/package.json +2 -2
|
@@ -12,6 +12,8 @@ const SCHEMA_FILES = {
|
|
|
12
12
|
'durability-evidence': 'durability-evidence.schema.json',
|
|
13
13
|
'publish-request': 'publish-request.schema.json',
|
|
14
14
|
'publish-receipt': 'publish-receipt.schema.json',
|
|
15
|
+
'author-request': 'author-request.schema.json',
|
|
16
|
+
'author-result': 'author-result.schema.json',
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
const SCHEMAS = Object.fromEntries(
|
|
@@ -450,6 +452,46 @@ function validateCrossRecord(kind, value, context, errors) {
|
|
|
450
452
|
}
|
|
451
453
|
}
|
|
452
454
|
|
|
455
|
+
if (kind === 'author-request') {
|
|
456
|
+
const requiredNarrative = Array.isArray(value.recipe?.requiredNarrative)
|
|
457
|
+
? value.recipe.requiredNarrative
|
|
458
|
+
: [];
|
|
459
|
+
const outlineIds = Array.isArray(value.narrativeOutline)
|
|
460
|
+
? value.narrativeOutline.map((section) => section?.id)
|
|
461
|
+
: [];
|
|
462
|
+
if (
|
|
463
|
+
requiredNarrative.length !== outlineIds.length ||
|
|
464
|
+
requiredNarrative.some((id, index) => outlineIds[index] !== id)
|
|
465
|
+
) {
|
|
466
|
+
add(
|
|
467
|
+
errors,
|
|
468
|
+
'$.narrativeOutline',
|
|
469
|
+
'narrative-outline-mismatch',
|
|
470
|
+
'Author request narrative outline must exactly match recipe requiredNarrative order.',
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (kind === 'author-result') {
|
|
476
|
+
for (const [index, section] of (Array.isArray(value.content?.sections)
|
|
477
|
+
? value.content.sections
|
|
478
|
+
: []
|
|
479
|
+
).entries()) {
|
|
480
|
+
if (
|
|
481
|
+
isObject(section) &&
|
|
482
|
+
typeof section.prose === 'string' &&
|
|
483
|
+
section.prose.trim().length === 0
|
|
484
|
+
) {
|
|
485
|
+
add(
|
|
486
|
+
errors,
|
|
487
|
+
`$.content.sections[${index}].prose`,
|
|
488
|
+
'empty-prose',
|
|
489
|
+
'Authored section prose must contain non-whitespace text.',
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
453
495
|
if (kind === 'manifest') {
|
|
454
496
|
const paths = [];
|
|
455
497
|
for (const artifact of Array.isArray(value.artifacts)
|
|
@@ -502,9 +544,17 @@ function validateCrossRecord(kind, value, context, errors) {
|
|
|
502
544
|
);
|
|
503
545
|
}
|
|
504
546
|
|
|
547
|
+
const requiredProvenance = [
|
|
548
|
+
'run-request.json',
|
|
549
|
+
'source/content-approval.json',
|
|
550
|
+
];
|
|
505
551
|
const expectedImmutable = new Set([
|
|
552
|
+
...requiredProvenance,
|
|
506
553
|
value.source?.factBasePath,
|
|
507
554
|
'source/fact-base.md',
|
|
555
|
+
...(Array.isArray(value.source?.authorResultPaths)
|
|
556
|
+
? value.source.authorResultPaths
|
|
557
|
+
: []),
|
|
508
558
|
value.theme?.path,
|
|
509
559
|
...(Array.isArray(value.artifacts)
|
|
510
560
|
? value.artifacts.flatMap((artifact) => [
|
|
@@ -520,6 +570,17 @@ function validateCrossRecord(kind, value, context, errors) {
|
|
|
520
570
|
const recordedImmutable = isObject(value.immutableHashes)
|
|
521
571
|
? new Set(Object.keys(value.immutableHashes))
|
|
522
572
|
: new Set();
|
|
573
|
+
const missingLegacyPaths = requiredProvenance.filter(
|
|
574
|
+
(path) => !recordedImmutable.has(path),
|
|
575
|
+
);
|
|
576
|
+
if (missingLegacyPaths.length > 0) {
|
|
577
|
+
add(
|
|
578
|
+
errors,
|
|
579
|
+
'$.immutableHashes',
|
|
580
|
+
'legacy-manifest-incomplete',
|
|
581
|
+
`Legacy manifest is missing immutable coverage for ${missingLegacyPaths.join(', ')}; regenerate the recap package before archival.`,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
523
584
|
if (
|
|
524
585
|
expectedImmutable.size !== recordedImmutable.size ||
|
|
525
586
|
[...expectedImmutable].some((path) => !recordedImmutable.has(path))
|
|
@@ -457,21 +457,9 @@ function requiredArtifacts(manifest) {
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
function immutablePackage(manifest) {
|
|
460
|
-
|
|
461
|
-
manifest.source.factBasePath,
|
|
462
|
-
'source/fact-base.md',
|
|
463
|
-
...manifest.artifacts.map(({ contentPath }) => contentPath),
|
|
464
|
-
manifest.theme.path,
|
|
465
|
-
...manifest.artifacts
|
|
466
|
-
.filter(
|
|
467
|
-
({ status, renderedPath }) =>
|
|
468
|
-
status === 'built' && typeof renderedPath === 'string',
|
|
469
|
-
)
|
|
470
|
-
.map(({ renderedPath }) => renderedPath),
|
|
471
|
-
];
|
|
472
|
-
return [...new Set(expectedPaths)].map((path) => ({
|
|
460
|
+
return Object.entries(manifest.immutableHashes).map(([path, hash]) => ({
|
|
473
461
|
path,
|
|
474
|
-
hash
|
|
462
|
+
hash,
|
|
475
463
|
}));
|
|
476
464
|
}
|
|
477
465
|
|
|
@@ -23,6 +23,62 @@ const ARROW_KEYS = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
|
|
23
23
|
|
|
24
24
|
export const REPRESENTATIVE_WIDTHS = Object.freeze([320, 768, 1440]);
|
|
25
25
|
|
|
26
|
+
export function checkSourceDumping({
|
|
27
|
+
authoredText,
|
|
28
|
+
authoredSections,
|
|
29
|
+
sourceTexts,
|
|
30
|
+
shingleSize = 8,
|
|
31
|
+
maxOverlapRatio = 0.6,
|
|
32
|
+
minMatchedShingles = 3,
|
|
33
|
+
}) {
|
|
34
|
+
const sourceShingles = new Set(
|
|
35
|
+
(Array.isArray(sourceTexts) ? sourceTexts : []).flatMap((text) =>
|
|
36
|
+
shingles(text, shingleSize),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
const sections = Array.isArray(authoredSections)
|
|
40
|
+
? authoredSections
|
|
41
|
+
: [{ text: authoredText }];
|
|
42
|
+
const issues = sections.flatMap(({ id, text }) => {
|
|
43
|
+
const authoredShingles = shingles(text, shingleSize);
|
|
44
|
+
const matchedShingles = authoredShingles.filter((value) =>
|
|
45
|
+
sourceShingles.has(value),
|
|
46
|
+
).length;
|
|
47
|
+
const overlapRatio =
|
|
48
|
+
authoredShingles.length === 0
|
|
49
|
+
? 0
|
|
50
|
+
: matchedShingles / authoredShingles.length;
|
|
51
|
+
return matchedShingles >= minMatchedShingles &&
|
|
52
|
+
overlapRatio > maxOverlapRatio
|
|
53
|
+
? [
|
|
54
|
+
{
|
|
55
|
+
code: 'source-dump',
|
|
56
|
+
message:
|
|
57
|
+
'Authored narrative contains too much verbatim source text; rewrite it as audience-ready prose.',
|
|
58
|
+
details: {
|
|
59
|
+
...(typeof id === 'string' && { sectionId: id }),
|
|
60
|
+
matchedShingles,
|
|
61
|
+
authoredShingles: authoredShingles.length,
|
|
62
|
+
overlapRatio,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
: [];
|
|
67
|
+
});
|
|
68
|
+
return { valid: issues.length === 0, issues };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function shingles(value, size) {
|
|
72
|
+
const words =
|
|
73
|
+
typeof value === 'string'
|
|
74
|
+
? (value.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [])
|
|
75
|
+
: [];
|
|
76
|
+
if (words.length < size) return [];
|
|
77
|
+
return Array.from({ length: words.length - size + 1 }, (_, index) =>
|
|
78
|
+
words.slice(index, index + size).join(' '),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
26
82
|
export const BROWSER_PROBE_EVALUATE = `(() => {
|
|
27
83
|
const root = document.documentElement;
|
|
28
84
|
const clippedX = [...document.querySelectorAll('body *')]
|
|
@@ -10,6 +10,12 @@ const PALETTE_NAMES = new Set([
|
|
|
10
10
|
'violet',
|
|
11
11
|
]);
|
|
12
12
|
const PROFILE_NAMES = new Set(['clean', 'editorial', 'technical']);
|
|
13
|
+
const STYLE_NAMES = new Set([
|
|
14
|
+
'clean-neutral',
|
|
15
|
+
'business-corporate',
|
|
16
|
+
'navy-ocean',
|
|
17
|
+
'dark-edgy',
|
|
18
|
+
]);
|
|
13
19
|
const RENDER_STRATEGIES = new Set(['default-only', 'user-switchable']);
|
|
14
20
|
const DEFAULT_MODES = new Set(['light', 'dark']);
|
|
15
21
|
const MODE_ROLES = ['surface', 'ink', 'accent', 'status', 'diagramSeries'];
|
|
@@ -20,20 +26,28 @@ export async function resolveTheme(selection = {}) {
|
|
|
20
26
|
const renderStrategy = selection.renderStrategy ?? 'default-only';
|
|
21
27
|
const warnings = [];
|
|
22
28
|
let theme;
|
|
29
|
+
const hasLegacySelection = ['palette', 'visualProfile'].some(
|
|
30
|
+
(key) => selection[key] !== undefined,
|
|
31
|
+
);
|
|
32
|
+
if (hasLegacySelection) {
|
|
33
|
+
warnings.push(
|
|
34
|
+
'Palette and visual profile selection is deprecated; use a curated style.',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
23
37
|
|
|
24
38
|
if (selection.suppliedBundlePath !== undefined) {
|
|
25
39
|
theme = await resolveSuppliedBundle(selection.suppliedBundlePath);
|
|
26
40
|
if (
|
|
27
|
-
['palette', 'visualProfile', 'artDirection', 'defaultMode'].some(
|
|
41
|
+
['style', 'palette', 'visualProfile', 'artDirection', 'defaultMode'].some(
|
|
28
42
|
(key) => selection[key] !== undefined,
|
|
29
43
|
)
|
|
30
44
|
) {
|
|
31
45
|
warnings.push(
|
|
32
|
-
'Supplied bundle wins over palette, visual profile, art direction, and default mode selections.',
|
|
46
|
+
'Supplied bundle wins over style, palette, visual profile, art direction, and default mode selections.',
|
|
33
47
|
);
|
|
34
48
|
}
|
|
35
49
|
} else {
|
|
36
|
-
theme = await resolveNamedTheme(selection);
|
|
50
|
+
theme = await resolveNamedTheme(selection, warnings);
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
assertResolvedTheme(theme);
|
|
@@ -48,7 +62,49 @@ export async function resolveTheme(selection = {}) {
|
|
|
48
62
|
};
|
|
49
63
|
}
|
|
50
64
|
|
|
51
|
-
async function resolveNamedTheme(selection) {
|
|
65
|
+
async function resolveNamedTheme(selection, warnings) {
|
|
66
|
+
if (
|
|
67
|
+
selection.style !== undefined ||
|
|
68
|
+
(selection.palette === undefined && selection.visualProfile === undefined)
|
|
69
|
+
) {
|
|
70
|
+
const styleName = selection.style ?? 'clean-neutral';
|
|
71
|
+
assertNamedSelection(styleName, STYLE_NAMES, 'style');
|
|
72
|
+
if (selection.style === undefined) {
|
|
73
|
+
warnings.push(
|
|
74
|
+
'No explicit style was selected; defaulted to clean-neutral.',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
selection.style !== undefined &&
|
|
79
|
+
(selection.palette !== undefined || selection.visualProfile !== undefined)
|
|
80
|
+
) {
|
|
81
|
+
warnings.push(
|
|
82
|
+
'Curated style wins over deprecated palette and visual profile selections.',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const style = await loadBundledJson('styles', styleName);
|
|
86
|
+
assertStyle(style, styleName);
|
|
87
|
+
const theme = {
|
|
88
|
+
schemaVersion: 'explainer-kit.theme/v1',
|
|
89
|
+
name: style.name,
|
|
90
|
+
defaultMode: selection.defaultMode ?? style.defaultMode,
|
|
91
|
+
modes: structuredClone(style.modes),
|
|
92
|
+
provenance: { style: styleName, derived: false },
|
|
93
|
+
typography: structuredClone(style.typography),
|
|
94
|
+
spacing: structuredClone(style.spacing),
|
|
95
|
+
geometry: structuredClone(style.geometry),
|
|
96
|
+
elevation: structuredClone(style.elevation),
|
|
97
|
+
density: style.density,
|
|
98
|
+
motion: structuredClone(style.motion),
|
|
99
|
+
diagrams: structuredClone(style.diagrams),
|
|
100
|
+
};
|
|
101
|
+
if (selection.artDirection !== undefined) {
|
|
102
|
+
applyArtDirection(theme, selection.artDirection);
|
|
103
|
+
}
|
|
104
|
+
theme.bundleHash = canonicalHash(theme);
|
|
105
|
+
return theme;
|
|
106
|
+
}
|
|
107
|
+
|
|
52
108
|
const paletteName = selection.palette ?? 'neutral';
|
|
53
109
|
const profileName = selection.visualProfile ?? 'clean';
|
|
54
110
|
assertNamedSelection(paletteName, PALETTE_NAMES, 'palette');
|
|
@@ -135,6 +191,7 @@ function assertSelection(selection) {
|
|
|
135
191
|
throw new TypeError('Theme selection must be an object.');
|
|
136
192
|
}
|
|
137
193
|
const allowed = new Set([
|
|
194
|
+
'style',
|
|
138
195
|
'palette',
|
|
139
196
|
'visualProfile',
|
|
140
197
|
'suppliedBundlePath',
|
|
@@ -160,6 +217,47 @@ function assertSelection(selection) {
|
|
|
160
217
|
}
|
|
161
218
|
}
|
|
162
219
|
|
|
220
|
+
function assertStyle(style, expectedName) {
|
|
221
|
+
const expectedKeys = [
|
|
222
|
+
'name',
|
|
223
|
+
'defaultMode',
|
|
224
|
+
'modes',
|
|
225
|
+
'typography',
|
|
226
|
+
'spacing',
|
|
227
|
+
'geometry',
|
|
228
|
+
'elevation',
|
|
229
|
+
'density',
|
|
230
|
+
'motion',
|
|
231
|
+
'diagrams',
|
|
232
|
+
];
|
|
233
|
+
if (
|
|
234
|
+
!isObject(style) ||
|
|
235
|
+
style.name !== expectedName ||
|
|
236
|
+
!hasExactKeys(style, expectedKeys) ||
|
|
237
|
+
!DEFAULT_MODES.has(style.defaultMode) ||
|
|
238
|
+
!isObject(style.modes) ||
|
|
239
|
+
!hasExactKeys(style.modes, ['light', 'dark'])
|
|
240
|
+
) {
|
|
241
|
+
throw new Error(`Bundled style ${expectedName} has an invalid shape.`);
|
|
242
|
+
}
|
|
243
|
+
for (const mode of Object.values(style.modes)) {
|
|
244
|
+
assertMode(mode);
|
|
245
|
+
}
|
|
246
|
+
assertProfile(
|
|
247
|
+
{
|
|
248
|
+
name: expectedName,
|
|
249
|
+
typography: style.typography,
|
|
250
|
+
spacing: style.spacing,
|
|
251
|
+
geometry: style.geometry,
|
|
252
|
+
elevation: style.elevation,
|
|
253
|
+
density: style.density,
|
|
254
|
+
motion: style.motion,
|
|
255
|
+
diagrams: style.diagrams,
|
|
256
|
+
},
|
|
257
|
+
expectedName,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
163
261
|
function assertNamedSelection(value, allowed, label) {
|
|
164
262
|
if (typeof value !== 'string' || !allowed.has(value)) {
|
|
165
263
|
throw new Error(`Unknown ${label}: ${String(value)}.`);
|
|
@@ -9,7 +9,7 @@ import { resolveContentApproval } from './lib/content-approval.mjs';
|
|
|
9
9
|
import { canonicalHash, validateContract } from './lib/contracts.mjs';
|
|
10
10
|
import { processFactBase } from './lib/fact-base.mjs';
|
|
11
11
|
import { writeJsonAtomic, writeTextAtomic } from './lib/fs-safe.mjs';
|
|
12
|
-
import { auditArtifactSet } from './lib/qa.mjs';
|
|
12
|
+
import { auditArtifactSet, checkSourceDumping } from './lib/qa.mjs';
|
|
13
13
|
import {
|
|
14
14
|
loadRecipe,
|
|
15
15
|
shouldStopDiscovery,
|
|
@@ -38,6 +38,7 @@ export async function runExplainer(request, options = {}) {
|
|
|
38
38
|
inputHashes: {},
|
|
39
39
|
contentModels: [],
|
|
40
40
|
contentPaths: new Map(),
|
|
41
|
+
authorResultPaths: [],
|
|
41
42
|
theme: null,
|
|
42
43
|
renderStrategy: run.request.theme.renderStrategy,
|
|
43
44
|
rendered: [],
|
|
@@ -83,9 +84,15 @@ export async function runExplainer(request, options = {}) {
|
|
|
83
84
|
});
|
|
84
85
|
await executeStage(run, 'content', options, async () => {
|
|
85
86
|
state.discovery = await runDiscovery(recipe, state.factBase, options);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (run.request.mode === 'unattended') {
|
|
88
|
+
const authored = await createAuthoredContent(state, options.author);
|
|
89
|
+
state.contentModels = authored.models;
|
|
90
|
+
state.authorResultPaths = authored.resultPaths;
|
|
91
|
+
} else {
|
|
92
|
+
state.contentModels = recipe.artifacts.map((artifact) =>
|
|
93
|
+
createContentModel(recipe, artifact, run.slug, state.factBase),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
89
96
|
for (const model of state.contentModels) {
|
|
90
97
|
const validation = validateContentModel(recipe, model);
|
|
91
98
|
if (!validation.valid) {
|
|
@@ -106,6 +113,7 @@ export async function runExplainer(request, options = {}) {
|
|
|
106
113
|
run,
|
|
107
114
|
run.request.mode,
|
|
108
115
|
options.reviewedSource,
|
|
116
|
+
state.authorResultPaths,
|
|
109
117
|
);
|
|
110
118
|
if (!state.approval.canResume) {
|
|
111
119
|
return resultFor(state);
|
|
@@ -251,9 +259,14 @@ async function loadResumableRun(request) {
|
|
|
251
259
|
}
|
|
252
260
|
|
|
253
261
|
async function hydrateResumableState(state) {
|
|
254
|
-
|
|
255
|
-
join(state.run.runRoot, 'source/fact-base.json'),
|
|
256
|
-
|
|
262
|
+
const [factBase, approval] = await Promise.all([
|
|
263
|
+
readJson(join(state.run.runRoot, 'source/fact-base.json')),
|
|
264
|
+
readJson(join(state.run.runRoot, 'source/content-approval.json')),
|
|
265
|
+
]);
|
|
266
|
+
state.factBase = factBase;
|
|
267
|
+
state.authorResultPaths = Array.isArray(approval.authorResultPaths)
|
|
268
|
+
? [...approval.authorResultPaths]
|
|
269
|
+
: [];
|
|
257
270
|
state.inputHashes = inputHashes(state.factBase);
|
|
258
271
|
state.factBaseHash = canonicalHash(state.factBase);
|
|
259
272
|
state.contentModels = [];
|
|
@@ -534,6 +547,9 @@ function manifestFor(state, buildRecord, createdAt, immutableHashes) {
|
|
|
534
547
|
factBasePath: 'source/fact-base.json',
|
|
535
548
|
factBaseHash: state.factBaseHash,
|
|
536
549
|
inputHashes: state.inputHashes,
|
|
550
|
+
...(state.authorResultPaths.length > 0 && {
|
|
551
|
+
authorResultPaths: state.authorResultPaths,
|
|
552
|
+
}),
|
|
537
553
|
},
|
|
538
554
|
theme: {
|
|
539
555
|
path: 'theme.resolved.json',
|
|
@@ -551,6 +567,117 @@ function manifestFor(state, buildRecord, createdAt, immutableHashes) {
|
|
|
551
567
|
};
|
|
552
568
|
}
|
|
553
569
|
|
|
570
|
+
async function createAuthoredContent(state, author) {
|
|
571
|
+
if (typeof author !== 'function') {
|
|
572
|
+
throw codedError(
|
|
573
|
+
'E_AUTHOR_REQUIRED',
|
|
574
|
+
'Unattended runs require an explicit author callback.',
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const authored = [];
|
|
579
|
+
for (const artifact of state.recipe.artifacts) {
|
|
580
|
+
const resultPath = `source/author/${artifact.id}.json`;
|
|
581
|
+
const authorRequest = {
|
|
582
|
+
schemaVersion: 'explainer-kit.author-request/v1',
|
|
583
|
+
run: { runId: state.run.runId, slug: state.run.slug },
|
|
584
|
+
recipe: {
|
|
585
|
+
id: state.recipe.id,
|
|
586
|
+
version: state.recipe.version,
|
|
587
|
+
requiredNarrative: [...state.recipe.requiredNarrative],
|
|
588
|
+
},
|
|
589
|
+
artifact: {
|
|
590
|
+
id: artifact.id,
|
|
591
|
+
type: artifact.type,
|
|
592
|
+
},
|
|
593
|
+
narrativeOutline: state.recipe.requiredNarrative.map((id) => ({
|
|
594
|
+
id,
|
|
595
|
+
title: humanize(id),
|
|
596
|
+
})),
|
|
597
|
+
factBase: structuredClone(state.factBase),
|
|
598
|
+
discovery: structuredClone(state.discovery),
|
|
599
|
+
};
|
|
600
|
+
const requestValidation = validateContract('author-request', authorRequest);
|
|
601
|
+
if (!requestValidation.valid) {
|
|
602
|
+
throw codedError(
|
|
603
|
+
'E_AUTHOR_REQUEST',
|
|
604
|
+
contractErrorMessage('author request', requestValidation.errors),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const result = await author(structuredClone(authorRequest));
|
|
609
|
+
const resultValidation = validateContract('author-result', result);
|
|
610
|
+
if (!resultValidation.valid) {
|
|
611
|
+
throw codedError(
|
|
612
|
+
'E_AUTHOR_RESULT',
|
|
613
|
+
contractErrorMessage('author result', resultValidation.errors),
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
const sectionIds = result.content.sections.map(({ id }) => id);
|
|
617
|
+
if (
|
|
618
|
+
result.artifactId !== artifact.id ||
|
|
619
|
+
sectionIds.length !== state.recipe.requiredNarrative.length ||
|
|
620
|
+
state.recipe.requiredNarrative.some(
|
|
621
|
+
(id, index) => sectionIds[index] !== id,
|
|
622
|
+
)
|
|
623
|
+
) {
|
|
624
|
+
throw codedError(
|
|
625
|
+
'E_AUTHOR_RESULT',
|
|
626
|
+
`Author result for ${artifact.id} must contain the exact required section IDs in order.`,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const dumpCheck = checkSourceDumping({
|
|
630
|
+
authoredSections: result.content.sections.map(({ id, prose }) => ({
|
|
631
|
+
id,
|
|
632
|
+
text: prose,
|
|
633
|
+
})),
|
|
634
|
+
sourceTexts: [
|
|
635
|
+
...state.factBase.claims,
|
|
636
|
+
...state.factBase.unresolvedClaims,
|
|
637
|
+
].map(({ text }) => text),
|
|
638
|
+
});
|
|
639
|
+
if (!dumpCheck.valid) {
|
|
640
|
+
throw codedError(
|
|
641
|
+
'E_SOURCE_DUMP',
|
|
642
|
+
dumpCheck.issues.map(({ message }) => message).join('; '),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
authored.push({
|
|
647
|
+
result: structuredClone(result),
|
|
648
|
+
resultPath,
|
|
649
|
+
model: {
|
|
650
|
+
artifactId: artifact.id,
|
|
651
|
+
slug: state.run.slug,
|
|
652
|
+
title: result.content.title,
|
|
653
|
+
description: result.content.description,
|
|
654
|
+
...(result.content.eyebrow && { eyebrow: result.content.eyebrow }),
|
|
655
|
+
...(result.content.footer && { footer: result.content.footer }),
|
|
656
|
+
sections: result.content.sections.map(({ id, title, prose }) => ({
|
|
657
|
+
id,
|
|
658
|
+
title,
|
|
659
|
+
content: prose,
|
|
660
|
+
})),
|
|
661
|
+
artifactLinks: result.content.artifactLinks ?? [],
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
for (const item of authored) {
|
|
667
|
+
await writeJsonAtomic(state.run.runRoot, item.resultPath, item.result);
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
models: authored.map(({ model }) => model),
|
|
671
|
+
resultPaths: authored.map(({ resultPath }) => resultPath),
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function contractErrorMessage(label, errors) {
|
|
676
|
+
return `Invalid ${label}: ${errors
|
|
677
|
+
.map(({ path, message }) => `${path}: ${message}`)
|
|
678
|
+
.join('; ')}`;
|
|
679
|
+
}
|
|
680
|
+
|
|
554
681
|
function createContentModel(recipe, artifact, slug, factBase) {
|
|
555
682
|
const facts = [
|
|
556
683
|
...factBase.claims.map(({ text, sections }) => ({ text, sections })),
|
|
@@ -614,8 +741,11 @@ function validateRecipeSources(recipe, binding) {
|
|
|
614
741
|
|
|
615
742
|
async function immutableHashesFor(state) {
|
|
616
743
|
const paths = [
|
|
744
|
+
'run-request.json',
|
|
617
745
|
'source/fact-base.json',
|
|
618
746
|
'source/fact-base.md',
|
|
747
|
+
'source/content-approval.json',
|
|
748
|
+
...state.authorResultPaths,
|
|
619
749
|
...state.contentPaths.values(),
|
|
620
750
|
...(state.theme ? ['theme.resolved.json'] : []),
|
|
621
751
|
...state.artifacts
|
|
@@ -727,9 +857,12 @@ async function parseCli(argv) {
|
|
|
727
857
|
if (!path) throw new Error('--reviewed-source requires a JSON path.');
|
|
728
858
|
options.reviewedSource = JSON.parse(await readFile(path, 'utf8'));
|
|
729
859
|
} else if (
|
|
730
|
-
[
|
|
731
|
-
|
|
732
|
-
|
|
860
|
+
[
|
|
861
|
+
'--author-module',
|
|
862
|
+
'--critic-module',
|
|
863
|
+
'--publish-module',
|
|
864
|
+
'--durability-module',
|
|
865
|
+
].includes(value)
|
|
733
866
|
) {
|
|
734
867
|
const path = argv[++index];
|
|
735
868
|
if (!path) throw new Error(`${value} requires a module path.`);
|
|
@@ -745,7 +878,7 @@ async function parseCli(argv) {
|
|
|
745
878
|
}
|
|
746
879
|
if (!requestPath) {
|
|
747
880
|
throw new Error(
|
|
748
|
-
'Usage: run.mjs --request <json> [--reviewed-source <json>] [--critic-module <mjs>] [--publish-module <mjs>] [--durability-module <mjs>]',
|
|
881
|
+
'Usage: run.mjs --request <json> [--reviewed-source <json>] [--author-module <mjs>] [--critic-module <mjs>] [--publish-module <mjs>] [--durability-module <mjs>]',
|
|
749
882
|
);
|
|
750
883
|
}
|
|
751
884
|
return { requestPath, options };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "business-corporate",
|
|
3
|
+
"defaultMode": "light",
|
|
4
|
+
"modes": {
|
|
5
|
+
"light": {
|
|
6
|
+
"surface": {
|
|
7
|
+
"canvas": "#ffffff",
|
|
8
|
+
"panel": "#f7f8fa",
|
|
9
|
+
"elevated": "#eef0f4"
|
|
10
|
+
},
|
|
11
|
+
"ink": { "primary": "#1a1f29", "muted": "#4a5568", "inverse": "#ffffff" },
|
|
12
|
+
"accent": { "primary": "#1b4f8a", "secondary": "#2f6fce" },
|
|
13
|
+
"status": {
|
|
14
|
+
"success": "#197a56",
|
|
15
|
+
"warning": "#965108",
|
|
16
|
+
"danger": "#b83226",
|
|
17
|
+
"info": "#1b4f8a"
|
|
18
|
+
},
|
|
19
|
+
"diagramSeries": ["#2f6fce", "#965108", "#0f7180", "#6b4fbf"]
|
|
20
|
+
},
|
|
21
|
+
"dark": {
|
|
22
|
+
"surface": {
|
|
23
|
+
"canvas": "#121a26",
|
|
24
|
+
"panel": "#1b2533",
|
|
25
|
+
"elevated": "#293649"
|
|
26
|
+
},
|
|
27
|
+
"ink": { "primary": "#f4f7fb", "muted": "#c5cfdb", "inverse": "#121a26" },
|
|
28
|
+
"accent": { "primary": "#8fc2ff", "secondary": "#75d6df" },
|
|
29
|
+
"status": {
|
|
30
|
+
"success": "#8ce0b9",
|
|
31
|
+
"warning": "#ffd17a",
|
|
32
|
+
"danger": "#ffaaa3",
|
|
33
|
+
"info": "#8fc2ff"
|
|
34
|
+
},
|
|
35
|
+
"diagramSeries": ["#8fc2ff", "#ffd17a", "#75d6df", "#c5a9ff"]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"typography": {
|
|
39
|
+
"sans": ["system-ui", "-apple-system", "BlinkMacSystemFont", "sans-serif"],
|
|
40
|
+
"serif": ["Charter", "Bitstream Charter", "Georgia", "serif"],
|
|
41
|
+
"mono": ["ui-monospace", "SFMono-Regular", "Consolas", "monospace"],
|
|
42
|
+
"scale": {
|
|
43
|
+
"caption": "0.875rem",
|
|
44
|
+
"body": "1.0625rem",
|
|
45
|
+
"title": "1.75rem",
|
|
46
|
+
"display": "2.75rem"
|
|
47
|
+
},
|
|
48
|
+
"lineHeight": { "caption": 1.5, "body": 1.7, "title": 1.2, "display": 1.05 }
|
|
49
|
+
},
|
|
50
|
+
"spacing": {
|
|
51
|
+
"unit": 5,
|
|
52
|
+
"scale": { "xs": 5, "sm": 10, "md": 20, "lg": 30, "xl": 50 }
|
|
53
|
+
},
|
|
54
|
+
"geometry": { "radius": { "sm": 4, "md": 8, "lg": 12 }, "borderWidth": 1 },
|
|
55
|
+
"elevation": {
|
|
56
|
+
"shadows": {
|
|
57
|
+
"low": "0 1px 2px rgb(26 31 41 / 0.08)",
|
|
58
|
+
"high": "0 10px 28px rgb(26 31 41 / 0.18)"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"density": "spacious",
|
|
62
|
+
"motion": {
|
|
63
|
+
"enabled": true,
|
|
64
|
+
"durationMs": { "fast": 140, "normal": 220, "slow": 360 },
|
|
65
|
+
"easing": {
|
|
66
|
+
"standard": "ease-out",
|
|
67
|
+
"emphasized": "cubic-bezier(0.16, 1, 0.3, 1)"
|
|
68
|
+
},
|
|
69
|
+
"reducedMotion": "disable-nonessential"
|
|
70
|
+
},
|
|
71
|
+
"diagrams": {
|
|
72
|
+
"lineWidth": 1.5,
|
|
73
|
+
"nodeGap": 40,
|
|
74
|
+
"arrowStyle": "curved",
|
|
75
|
+
"labelTreatment": "inline"
|
|
76
|
+
}
|
|
77
|
+
}
|