Graph Resources — Tasks
Companion: design.md. Each phase ships alone and leaves the repo green. After any code change run the four mandatory checks (
npm run test:affected,npm run typecheck,npm run lint,npm run dev:nodetool -- harness gate --base origin/main).
Phase 1 — Types and entity nodes
- Protocol:
StoryboardRef. Add topackages/protocol/src/api-types.tsnext toScriptRef, with thewritableflag (design §2.1);storyboardRefDefaultinpackages/core-nodes/src/nodes/ref-defaults.ts;nodetool.constant.Storyboardandnodetool.constant.Entityinconstant.ts;StoryboardRef+Entityinpackages/dsl/src/types.ts; thestoryboardandentitynames inscripts/verify-backend-bundle.mjs. Test: DSL generation emitsstoryboard/entitytyped inputs for the constant nodes. - Lineage fields.
templateId/recastKeyonStoryboardDocument(packages/models/src/storyboard.ts,api-schemas/storyboards.ts),templateIdonScriptDocumentandTimelineSequence,sourceonEntityMarker(creative.tszod + reader). Tests: old fixtures parse unchanged; the fields round-trip. entityFromAssetto models. Move topackages/models/src/entity.ts; the agent capability imports it. No behaviour change; existingcapabilities-entitiestests stay green.- Model interfaces.
getStoryboard/createStoryboard/updateStoryboard,listEntities/getEntity/upsertEntity,listGameTemplatesonProcessingContextModelInterfaces(packages/runtime/src/context.ts), forwarders on the context, implementations inpackages/websocket/src/session/model-interfaces.tsand the CLI context builder.upsertEntitymatchessource.key, then (project, kind, name). Tests: ownership refusal, upsert returns the same id twice. resolveEntities.packages/runtime/src/entities.ts: fill an entity value from the library whendescriptoris empty andgetEntityis wired; pass through otherwise. Unit test both branches.nodetool.entity.*.LoadEntity,ListEntities,CreateEntityinpackages/core-nodes/src/nodes/entity.tsper design §4.1. Node tests against an in-memory context.list[entity]on generators. Retypeentitiesonnodetool.image.TextToImage,nodetool.video.ImageToVideo,nodetool.video.TextToVideo,nodetool.creative.ApplyEntities; callresolveEntitiesbeforeinjectEntities. Test: a saved graph carrying the oldlist[dict]value still runs.- Web pickers.
StoryboardProperty(overDocumentPickerProperty, newuseStoryboardshook if none) andEntityProperty(reusingEntityAssetPickerDialog) inweb/src/components/properties/;PropertyInput.resolver.tsxcases;data_types.tsentries. Jest: picker renders, selection writes the ref shape.
Phase 2 — packages/storyboard and the storyboard nodes
- Package scaffold.
packages/storyboardwithAGENTS.md+CLAUDE.md, an entry inpackages/AGENTS.md, workspace + turbo wiring, dependency orderprotocol → timeline → storyboard.npm run check:agents-docsgreen. recastStoryboard. Per design §3.1 with the fixture suite: explicitreplaces, single-of-kind, ambiguous kind appends, whole-word rename (Nova/Novakcase), explicitentity_idsrewrite, hash-based invalidation keeping product-free shots, lineage stamping,recastKeystable under cast order and different for the swapped assignment, re-derive overexisting(edited template action invalidates one shot, renamed destination entity under the same id invalidates only the shots naming it, added shot appears, dropped shot reported).planShotRenders+renderShots. Lift the plan and the IO frompackages/agents/src/capabilities/storyboards.ts; the capability’srender_storyboard_stills/render_storyboard_clips/filterStalecall the package. Test assertingfilterStaleandplan.freshagree on the capability’s fixtures; storyboard tool-loop eval unchanged.nodetool.storyboard.*.LoadStoryboard,StoryboardShots,RecastStoryboard,RenderStills,RenderClips,AssembleTimelineinpackages/video-nodes/src/nodes/storyboard.tsper design §4.2, tagged server. Node tests:reuse_existingreturns the prior copy,only_staleskips fresh shots,require_keyframeskips, the write contract (picker ref refused on first run and for a derived board,allow_writesadmits, derived refs carrywritable),AssembleTimelineclones the template cut for a copy and keeps its foreign clips, takes the linked path whenscript_idis set, and writestimeline_id.- Web lineage chip. “Recast from " in the storyboard header when `templateId` is set, linking to the source board.
Phase 3 — Script and timeline derivations
fillScript.packages/protocol/src/script-fill.ts, tests for filled/unresolved reporting and take retention.nodetool.script.WriteScript/FillScript. Inpackages/video-nodes/src/nodes/script.tsper design §4.3;WriteScriptmapscastentities to speakers (entityId,voice.voicefromvoice_id). Node tests with a stubbed provider.cloneTimelineForBoard,fillTimelineText,retargetSequence. Inpackages/timelineper design §3.2; fixtures with a text clip, a music bed on a foreign track, a keyframed transform, and the validator run on every output. Clone fixture: owned clips re-stamped and cleared, foreign clips and tracks byte-identical.nodetool.timeline.FillTimelineText/RetargetTimeline. Per design §4.4; both create a new sequence withtemplateId.
Phase 4 — Game nodes
slotPrompt.packages/protocol/src/game-slot-prompt.ts; tests per slot kind asserting sizes and thatcheckermatches eachnodetool.game.*checker’s prop names.packages/game-nodes.LoadGameTemplate,SlotPrompt,ExportGodotProjectper design §4.5, withAGENTS.md/CLAUDE.mdand thepackages/AGENTS.mdentry;base-nodesre-exports. Export test against the platformer golden fixture inpackages/godot.- Checker
slotinput.SpriteSheet,Tileset,SeamlessImage,SoundEffect,MusicLoopacceptslot: game_slotand derive their numeric props from it when connected. Tests: connected slot wins over stale hand-typed values.
Phase 5 — Examples, harness, docs
- Example workflows.
Per-SKU Ad Factory,Localized Explainer,Platformer Asset Pack,Three Ratiosunderpackages/base-nodes/nodetool/examples/nodetool-base/, models unset, each passingvalidate_workflowonce models are stamped. - Fake-mode fixtures. The same graphs with
nodetool.fake.*generators andprovider: fakeon the render nodes, runnable bynodetool debugwith no keys, E4 included. The E1 run asserts the filled overlay text is on the exported sequence; the E4 run asserts the source cut’s clip placements are unchanged on both retargets. - Harness registry.
graph-resourcesentry inpackages/cli/src/harness/registry.tswhose selfcheck runs the pure suites and the fake-mode debug runs;nodetool harness auditclean;harness gatemaps diffs underpackages/storyboard/,packages/game-nodes/, the new node files and the examples to it. - Docs and skills. Node table in
docs/creative-agent.md§ Graph templates; the new node types in.claude/skills/video-workflow/SKILL.mdand.claude/skills/godot-game/SKILL.md(the graph path next to theinvoke_nodeloop);docs/harnesses.mdentry for the harness; the AGENTS.md harness table row. Move this design’s status line to shipped per phase.
Phase 5 deviations, and why
- Models are stamped on the examples, not left unset.
validateGraphtreats a*_modelproperty as required, soRenderStills/RenderClipswith the board’s models and no override failnpm run validate:examples(--warnings-as-errors) — the shipped gate every example passes through. The four examples therefore carry the same model selections the rest of the library does. The node still prefers the board’s model only when the override is the empty selection, which a saved example can no longer be. Per-SKU Ad Factoryruns once per SKU rather than looping a CSV. Anodetool.code.Codenode is the only way to pull a typed string out ofForEachRow’srowdict, and its outputs aredynamic_outputs, which the correlation analyzer never gives a scope — every downstream node then reads “multiple values at empty scope; only the last is kept” and the fan-out collapses to one item. The example takes the SKU’s fields as inputs instead; the batch driver is a webhook or a per-row call, as design §5 already allows.- No
Localized Explainerfake-mode fixture.getScript/createScript/updateScriptare wired only inpackages/websocket/src/session/model-interfaces.ts, not indocumentModelInterfaces(), which is what the CLI installs — so everynodetool.script.*node fails undernodetool debugwith “ProcessingContext model interface ‘getScript’ is not configured”. The other three fixtures run. ExportGodotProjectis terminal in the E3 fixture. Wiring any node downstream of it makes the run never complete: the export emits, the downstream node runs, and then the runner’s promise never settles and the process exits 0 with no bundle.