Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/AUTHORING-ARTIFACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ The core package exports `EditorialPlan`, `validateEditorialPlan()`, and
`formatEditorialPlanMarkdown()` for tools that need structured authoring data.
The Markdown artifact remains the normal review surface.

An EditorialPlan may select up to two soft explanation patterns: one primary
and one supporting pattern. Seqvio provides `causal-diagnosis`,
`mechanism-trace`, `system-flow`, `evidence-demonstration`,
`misconception-reframe`, and `progressive-model`. They are optional editorial
recipes, not executable templates. An agent may adapt their suggested stages or
omit pattern selection entirely when a custom structure better fits the source.
Pattern metadata does not change the ExplainerDocument schema or renderer.

## Visual design brief

`VISUAL-DESIGN.md` records concrete visual decisions: canvas, palette,
Expand Down
16 changes: 16 additions & 0 deletions examples/authoring/native-module-ci/EDITORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ Node.js maintainers who understand npm and CI but do not routinely debug native

The rebuild command can succeed while npm blocks the install script that produces pty.node, so verification must test both the artifact and the import.

## Explanation Strategy

### Causal diagnosis

- ID: `causal-diagnosis`
- Role: **primary**
- Reason: The source contains an observed failure, an expected install path, a break point, a root cause, and a verifiable repair.
- Adaptations:
- Combine repair and verification into the final section to stay within the duration budget.

### Evidence demonstration

- ID: `evidence-demonstration`
- Role: **supporting**
- Reason: The conclusion must be supported by checking the native artifact and importing the module, not by trusting the rebuild message.

## Content Decisions

### Command success and native artifact existence are different conditions.
Expand Down
17 changes: 17 additions & 0 deletions examples/authoring/native-module-ci/authoring-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@
},
"thesis": "The rebuild command can succeed while npm blocks the install script that produces pty.node, so verification must test both the artifact and the import.",
"durationBudgetSec": 75,
"explanationStrategy": {
"patterns": [
{
"id": "causal-diagnosis",
"role": "primary",
"reason": "The source contains an observed failure, an expected install path, a break point, a root cause, and a verifiable repair.",
"adaptations": [
"Combine repair and verification into the final section to stay within the duration budget."
]
},
{
"id": "evidence-demonstration",
"role": "supporting",
"reason": "The conclusion must be supported by checking the native artifact and importing the module, not by trusting the rebuild message."
}
]
},
"concepts": [
{
"id": "success-is-not-artifact",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/authoring/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './schema';
export * from './patterns';
export * from './validate';
export * from './markdown';
12 changes: 11 additions & 1 deletion packages/core/src/authoring/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import type { EditorialPlan, VisualDesignBrief } from './schema';
import { getExplanationPattern } from './patterns';

function bullets(values: string[] | undefined, empty = 'None declared.'): string {
return values?.length ? values.map((value) => `- ${value}`).join('\n') : empty;
}
export function formatEditorialPlanMarkdown(plan: EditorialPlan): string {
const strategy = plan.explanationStrategy?.patterns.length
? plan.explanationStrategy.patterns.map((selection) => {
const pattern = getExplanationPattern(selection.id);
const adaptations = selection.adaptations?.length
? `\n- Adaptations:\n${selection.adaptations.map((item) => ` - ${item}`).join('\n')}`
: '';
return `### ${pattern.name}\n\n- ID: \`${selection.id}\`\n- Role: **${selection.role}**\n- Reason: ${selection.reason}${adaptations}`;
}).join('\n\n')
: 'Custom structure; no library pattern selected.';
const concepts = plan.concepts.map((concept) => {
const lines = [
`### ${concept.claim}`,
Expand Down Expand Up @@ -31,7 +41,7 @@ export function formatEditorialPlanMarkdown(plan: EditorialPlan): string {
return lines.join('\n');
}).join('\n\n');

return `---\nformat: ${plan.format}\nid: ${plan.id}\nduration_budget_sec: ${plan.durationBudgetSec}\n---\n\n# Editorial Plan: ${plan.title}\n\n## Objective\n\n${plan.objective}\n\n## Audience\n\n${plan.audience.description}\n\n### Prior Knowledge\n\n${bullets(plan.audience.priorKnowledge)}\n\n### Likely Misconceptions\n\n${bullets(plan.audience.likelyMisconceptions)}\n\n## Thesis\n\n${plan.thesis}\n\n## Content Decisions\n\n${concepts}\n\n## Explanation Structure\n\n${sections}\n`;
return `---\nformat: ${plan.format}\nid: ${plan.id}\nduration_budget_sec: ${plan.durationBudgetSec}\n---\n\n# Editorial Plan: ${plan.title}\n\n## Objective\n\n${plan.objective}\n\n## Audience\n\n${plan.audience.description}\n\n### Prior Knowledge\n\n${bullets(plan.audience.priorKnowledge)}\n\n### Likely Misconceptions\n\n${bullets(plan.audience.likelyMisconceptions)}\n\n## Thesis\n\n${plan.thesis}\n\n## Explanation Strategy\n\n${strategy}\n\n## Content Decisions\n\n${concepts}\n\n## Explanation Structure\n\n${sections}\n`;
}

export function formatVisualDesignBriefMarkdown(brief: VisualDesignBrief): string {
Expand Down
114 changes: 114 additions & 0 deletions packages/core/src/authoring/patterns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
EXPLANATION_PATTERN_IDS,
type EditorialSectionPurpose,
type ExplanationPatternId,
} from './schema';

export interface ExplanationPatternStage {
id: string;
title: string;
purpose: EditorialSectionPurpose;
outcome: string;
}

export interface ExplanationPatternDefinition {
id: ExplanationPatternId;
name: string;
intent: string;
stages: readonly ExplanationPatternStage[];
advisoryChecks: readonly string[];
}

export const EXPLANATION_PATTERNS = {
'causal-diagnosis': {
id: 'causal-diagnosis',
name: 'Causal diagnosis',
intent: 'Explain a failure from observed symptom through root cause and verified repair.',
stages: [
{ id: 'symptom', title: 'Observed symptom', purpose: 'hook', outcome: 'The audience can state the failure without assuming its cause.' },
{ id: 'expected-path', title: 'Expected mechanism', purpose: 'establish-model', outcome: 'The audience understands the path that should have succeeded.' },
{ id: 'break-point', title: 'Break point', purpose: 'explain-mechanism', outcome: 'The audience can locate where actual behavior diverged.' },
{ id: 'root-cause', title: 'Root cause', purpose: 'explain-mechanism', outcome: 'The audience can connect evidence to the causal explanation.' },
{ id: 'repair', title: 'Repair', purpose: 'demonstrate', outcome: 'The audience understands the change and why it addresses the cause.' },
{ id: 'verification', title: 'Verification', purpose: 'summarize', outcome: 'The audience can distinguish a verified result from a successful command.' },
],
advisoryChecks: ['Keep symptoms separate from inferred causes.', 'End with evidence that verifies the repaired behavior.'],
},
'mechanism-trace': {
id: 'mechanism-trace',
name: 'Mechanism trace',
intent: 'Explain how inputs are transformed through state changes into outputs.',
stages: [
{ id: 'input', title: 'Input and actors', purpose: 'establish-model', outcome: 'The audience knows the starting state and relevant objects.' },
{ id: 'transformations', title: 'Transformations', purpose: 'explain-mechanism', outcome: 'The audience can follow the ordered changes.' },
{ id: 'state', title: 'State change', purpose: 'explain-mechanism', outcome: 'The audience understands what is different after each operation.' },
{ id: 'output', title: 'Output', purpose: 'demonstrate', outcome: 'The audience connects the mechanism to its observable result.' },
{ id: 'boundary', title: 'Boundary conditions', purpose: 'summarize', outcome: 'The audience knows when the model does and does not apply.' },
],
advisoryChecks: ['Name the state changed by each important operation.', 'Do not replace mechanism with a list of component names.'],
},
'system-flow': {
id: 'system-flow',
name: 'System flow',
intent: 'Follow a request, event, or data item across actors and system boundaries.',
stages: [
{ id: 'origin', title: 'Origin', purpose: 'hook', outcome: 'The audience knows what starts the flow.' },
{ id: 'actors', title: 'Actors and boundaries', purpose: 'establish-model', outcome: 'The audience can identify ownership and boundaries.' },
{ id: 'forward-path', title: 'Forward path', purpose: 'explain-mechanism', outcome: 'The audience can trace the ordered handoffs.' },
{ id: 'handling', title: 'Critical handling', purpose: 'explain-mechanism', outcome: 'The audience understands the important processing point.' },
{ id: 'result', title: 'Response or terminal state', purpose: 'demonstrate', outcome: 'The audience can connect the path to its result.' },
],
advisoryChecks: ['Make every boundary crossing explicit.', 'Keep actor ownership stable throughout the explanation.'],
},
'evidence-demonstration': {
id: 'evidence-demonstration',
name: 'Evidence demonstration',
intent: 'Support a claim with an operation, observed state, interpretation, and conclusion.',
stages: [
{ id: 'claim', title: 'Claim', purpose: 'hook', outcome: 'The audience knows what the demonstration will establish.' },
{ id: 'setup', title: 'Setup', purpose: 'establish-model', outcome: 'The audience knows the relevant conditions.' },
{ id: 'operation', title: 'Operation', purpose: 'demonstrate', outcome: 'The audience can reproduce or inspect the action.' },
{ id: 'observation', title: 'Observed state', purpose: 'demonstrate', outcome: 'The audience sees the result independently of the plan.' },
{ id: 'interpretation', title: 'Interpretation', purpose: 'explain-mechanism', outcome: 'The audience understands why the observation supports the claim.' },
{ id: 'conclusion', title: 'Conclusion', purpose: 'summarize', outcome: 'The audience can apply the demonstrated result.' },
],
advisoryChecks: ['Do not treat an intended operation as observed evidence.', 'Explain what the observation proves, not only what appeared.'],
},
'misconception-reframe': {
id: 'misconception-reframe',
name: 'Misconception reframe',
intent: 'Replace a plausible but incomplete model with one that explains conflicting evidence.',
stages: [
{ id: 'prior-model', title: 'Plausible prior model', purpose: 'hook', outcome: 'The audience recognizes why the misconception is attractive.' },
{ id: 'conflict', title: 'Conflicting evidence', purpose: 'correct-misconception', outcome: 'The audience sees what the prior model cannot explain.' },
{ id: 'replacement', title: 'Replacement model', purpose: 'establish-model', outcome: 'The audience gains a coherent alternative.' },
{ id: 'reapply', title: 'Reapply the model', purpose: 'demonstrate', outcome: 'The audience can explain the original evidence correctly.' },
{ id: 'rule', title: 'Decision rule', purpose: 'summarize', outcome: 'The audience can avoid the misconception in a new case.' },
],
advisoryChecks: ['Represent the prior model fairly before correcting it.', 'Provide a usable replacement model, not only a negation.'],
},
'progressive-model': {
id: 'progressive-model',
name: 'Progressive model',
intent: 'Introduce a complex system from a small stable overview, then deepen and reintegrate it.',
stages: [
{ id: 'overview', title: 'Small overview', purpose: 'establish-model', outcome: 'The audience has a compact map of the main objects.' },
{ id: 'focus', title: 'Focus one subsystem', purpose: 'explain-mechanism', outcome: 'The audience knows which part is being expanded.' },
{ id: 'deep-dive', title: 'Deep dive', purpose: 'explain-mechanism', outcome: 'The audience understands the focused mechanism.' },
{ id: 'reintegrate', title: 'Reintegrate', purpose: 'summarize', outcome: 'The audience connects local detail back to the whole.' },
],
advisoryChecks: ['Keep object identity stable between overview and detail.', 'Do not reveal the complete complex model before its parts are introduced.'],
},
} as const satisfies Record<ExplanationPatternId, ExplanationPatternDefinition>;

export function isExplanationPatternId(value: unknown): value is ExplanationPatternId {
return typeof value === 'string' && EXPLANATION_PATTERN_IDS.includes(value as ExplanationPatternId);
}

export function getExplanationPattern(id: ExplanationPatternId): ExplanationPatternDefinition {
return EXPLANATION_PATTERNS[id];
}

export function listExplanationPatterns(): ExplanationPatternDefinition[] {
return EXPLANATION_PATTERN_IDS.map((id) => EXPLANATION_PATTERNS[id]);
}
27 changes: 27 additions & 0 deletions packages/core/src/authoring/schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
export const EDITORIAL_PLAN_FORMAT = 'seqvio-editorial-plan' as const;
export const VISUAL_DESIGN_BRIEF_FORMAT = 'seqvio-visual-design' as const;

export const EXPLANATION_PATTERN_IDS = [
'causal-diagnosis',
'mechanism-trace',
'system-flow',
'evidence-demonstration',
'misconception-reframe',
'progressive-model',
] as const;

export type ExplanationPatternId = (typeof EXPLANATION_PATTERN_IDS)[number];
export type ExplanationPatternRole = 'primary' | 'supporting';

export interface ExplanationPatternSelection {
id: ExplanationPatternId;
role: ExplanationPatternRole;
reason: string;
/** Explicit departures from the suggested pattern arc. */
adaptations?: string[];
}

export interface EditorialExplanationStrategy {
/** Omit explanationStrategy entirely when a custom structure is better. */
patterns: ExplanationPatternSelection[];
}

export type EditorialConceptRole =
| 'essential'
| 'evidence'
Expand Down Expand Up @@ -28,6 +53,8 @@ export interface EditorialPlan {
};
thesis: string;
durationBudgetSec: number;
/** Optional, composable structural guidance. It does not constrain executable IR. */
explanationStrategy?: EditorialExplanationStrategy;
concepts: Array<{
id: string;
claim: string;
Expand Down
44 changes: 44 additions & 0 deletions packages/core/src/authoring/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type EditorialPlan,
type VisualDesignBrief,
} from './schema';
import { isExplanationPatternId } from './patterns';
import {
EXPLAINER_DOCUMENT_DEFAULTS,
type ExplainerDocument,
Expand Down Expand Up @@ -32,6 +33,49 @@ export function validateEditorialPlan(plan: EditorialPlan): AuthoringIssue[] {
issues.push({ severity: 'error', path: 'durationBudgetSec', code: 'invalid_duration_budget', message: 'Duration budget must be greater than zero.' });
}

const strategy = plan.explanationStrategy;
if (strategy) {
if (!Array.isArray(strategy.patterns) || strategy.patterns.length === 0) {
issues.push({ severity: 'warning', path: 'explanationStrategy.patterns', code: 'empty_explanation_strategy', message: 'Omit explanationStrategy when no library pattern improves the plan.' });
} else {
const selections = strategy.patterns as unknown[];
if (strategy.patterns.length > 2) {
issues.push({ severity: 'warning', path: 'explanationStrategy.patterns', code: 'too_many_explanation_patterns', message: 'More than two patterns usually weakens the editorial focus; keep only patterns that materially shape the explanation.' });
}
const ids = selections.map((selection) => (
selection && typeof selection === 'object' && 'id' in selection
? String((selection as { id?: unknown }).id)
: ''
));
for (const id of duplicateIds(ids)) {
issues.push({ severity: 'error', path: 'explanationStrategy.patterns', code: 'duplicate_explanation_pattern', message: `Explanation pattern "${id}" is selected more than once.` });
}
const primaryCount = selections.filter((selection) => (
selection && typeof selection === 'object' && (selection as { role?: unknown }).role === 'primary'
)).length;
if (primaryCount !== 1) {
issues.push({ severity: 'error', path: 'explanationStrategy.patterns', code: 'invalid_primary_pattern_count', message: 'A selected strategy must contain exactly one primary pattern.' });
}
selections.forEach((rawSelection, index) => {
const selectionPath = `explanationStrategy.patterns[${index}]`;
if (!rawSelection || typeof rawSelection !== 'object') {
issues.push({ severity: 'error', path: selectionPath, code: 'invalid_explanation_pattern_selection', message: 'Pattern selection must be an object.' });
return;
}
const selection = rawSelection as { id?: unknown; role?: unknown; reason?: unknown };
if (!isExplanationPatternId(selection.id)) {
issues.push({ severity: 'error', path: `${selectionPath}.id`, code: 'unknown_explanation_pattern', message: `Unknown explanation pattern "${String(selection.id)}".` });
}
if (selection.role !== 'primary' && selection.role !== 'supporting') {
issues.push({ severity: 'error', path: `${selectionPath}.role`, code: 'invalid_explanation_pattern_role', message: 'Pattern role must be primary or supporting.' });
}
if (typeof selection.reason !== 'string' || !selection.reason.trim()) {
issues.push({ severity: 'warning', path: `${selectionPath}.reason`, code: 'missing_explanation_pattern_reason', message: 'Explain why this pattern fits the source, or remove the selection.' });
}
});
}
}

for (const id of duplicateIds(plan.concepts.map((concept) => concept.id))) {
issues.push({ severity: 'error', path: 'concepts', code: 'duplicate_concept_id', message: `Concept id "${id}" is duplicated.` });
}
Expand Down
Loading
Loading