Workflow

A citation-grounded, human-in-the-loop authoring pipeline. One natural-language ask flows through five stages — each produces exactly one approved artifact, and each stage hard-refuses to start until the upstream artifact is approved. The last stage writes code.

define design.epic design.story plan build

Every stage follows the same shape — context.assemble (deterministic or a coarse handoff to insrc_analyze_step) → N sequential LLM steps → checklist.verify → synthesize — and every claim in every artifact is cited to a step output or an analyze bundle. Ungrounded citations refuse to synthesize. No step is ever skipped to save turns, and no LLM call runs in parallel: accuracy first, cost last.

StageProducesWhat it isopenQuestions
defineDEF — definitionFrames the Epic: problem statement, non-goals, assumptions, constraints, and 1–10 Stories.yes
design.epicHLD — high-level designOne per Epic: chosen framework, shared contracts, Story boundaries, rollout phases, alternatives considered.yes
design.storyLLD — low-level designOne per Story: detailed contract (APIs + data model), error paths, test strategy, migration.yes
planPLAN — ordered TasksA sequenced list of implementable Tasks for a Story. Carries no open questions — the decisions were already made upstream.none
buildcode + validationThe controller implements each Task and the daemon runs a read-only verdict.
DEF, HLD, and LLD each carry an openQuestions list on their body (see src/workflow/artifacts/{define,hld,lld}.ts). PLAN deliberately does not — by plan time every design decision is settled, so a plan is pure sequencing.

Artifacts & approval gates

Each artifact is written twice: a human-readable .md under docs/ and the canonical JSON under .insrc/artifacts/. The JSON is the machine contract downstream stages read; the Markdown is what you review.

ArtifactDoc pathCanonical JSON
DEFdocs/defines/DEF-<h16>.md.insrc/artifacts/DEF-<h16>.json
HLDdocs/designs/HLD-<h16>.md.insrc/artifacts/HLD-<h16>.json
LLDdocs/designs/LLD-<h16>-<storyId>.md.insrc/artifacts/LLD-<h16>-<storyId>.json
PLANdocs/plans/PLAN-<epic-slug>-<story-id>.md.insrc/artifacts/PLAN-<epic-hash>-<story-id>.json

The gate is hard, not advisory. Every downstream workflow refuses to run against an unapproved (or rejected) upstream artifact. You approve from the CLI:

bash
$ insrc workflow approve docs/defines/DEF-<h16>.md   # unlocks design.epic
On approval the tracker can auto-push and commit. Approving an HLD auto-creates the Epic issue; approving an LLD auto-creates the Story issue; approving a PLAN can push the Task issues. The artifact docs are committed and linked back from each issue. Repeat approves are idempotent — an existing ref is detected and the push is skipped. Pass --no-tracker to opt out for one call.

Open-question resolution

DEF, HLD, and LLD each surface the questions the stage could not settle. Resolving them is a two-beat contract:

For each question you have three moves, recorded by recordResolution as a resolved | ignored | deferred status:

MoveMeaningRe-surfaces?
resolveRecord a decision (optionally a chosen option + rationale).No — closed.
ignoreLeave it to implementer judgment; no decision recorded.No — closed.
deferPark it for a dedicated review pass. A deferred question is not open, so it never auto-blocks a stage.Only in the review-deferred flow.

When you resolve, the daemon generates candidate solution options through the shaper LLM (QuestionWithOptions / QuestionOption), so you pick from grounded choices rather than inventing them. Three insrc_workflow_step phases drive this: start runs the stage-start gate, resolve_question records one resolution, and review_deferred walks every parked question across the Epic's DEF + HLD + all LLDs (src/mcp/workflow-step/phases/{start,resolve-question,review-deferred}.ts).

Hierarchical IDs

Every Epic, Story, and Task carries a single canonical identifier that encodes its place in the hierarchy. The separator between levels is :, and the slug form simply swaps each : for - (toCanonical / toSlug in src/workflow/id.ts):

Canonical E<YYYYMMDD><hash8>:S<nnn>:T<nnn> Slug E<YYYYMMDD><hash8>-S<nnn>-T<nnn> Example E20260717185807ba:S001:T003 └── Epic ──┘ Story Task

The date prefix plus 8-hex hash is separator-free, so E<8><8> parses unambiguously. The levels nest: an Epic is E…, a Story is E…:S…, and a Task is E…:S…:T…. This gives a clean two-way Epic → Story → Task relationship — parseWorkflowId reads any string back into its level, and parentId / epicOf walk upward. The minting helpers are epicWorkflowId, storyWorkflowId, and taskWorkflowId. The same ID appears in issue titles and in the stored docs, so a glance at an issue tells you exactly where it sits.

The GitHub tracker

The tracker is opt-in and never stores a token — it shells out to gh, which owns your OAuth session. When enabled, approval-time auto-push turns each approved artifact into a typed, natively-nested GitHub issue (src/workflow/tracker-auto.ts):

On approval of…FunctionCreates
HLDautoPushEpicOnHldAn Epic issue (native issue type Epic).
LLDautoPushStoryOnLldA Story issue, linked as a native sub-issue of its Epic.
PLANautoPushTasksOnPlanOne Task issue per PlanTask, each a native sub-issue of its Story.
Epic E20260717185807ba #typed: Epic ▸ Story E…:S001 #native sub-issue · typed: Story ▸ Task E…:S001:T001 #native sub-issue · typed: Task ▸ Task E…:S001:T002 ▸ Story E…:S002 ▸ Task E…:S002:T001

The nesting is real GitHub sub-issue linkage via ghLinkSubIssue, not just a label convention. Alongside the issues, the tracker commits the artifact docs and links the committed blobs back from each issue, and can seed a GitHub Projects v2 dashboard so you get Epic → Story → Task drill-down on a board. Issue-type names and Task pushing are configurable per repo via src/workflow/config/github.ts (epicIssueType / storyIssueType / taskIssueType, pushTasks, useMilestones); types fail open to untyped issues when the org lacks them.

Bootstrap the automatable GitHub setup once with:

bash
$ insrc tracker setup # config + labels + issue types
$ insrc tracker setup --project   # also create a Projects v2 board

runTrackerSetup (src/workflow/tracker/setup.ts) checks that gh is authenticated, verifies the OAuth scopes it needs (admin:org for issue types, project for the board), writes the resolved config, upserts the fixed tracker labels, ensures the Epic/Story issue types exist, and — behind --project — creates the board. Steps whose prerequisite is missing come back as manual with the exact command to run, so nothing is done behind your back.

The tracker is opt-in even when your git origin points at GitHub. With no matching ~/.insrc/github.json entry, resolveGithubConfig returns { type: 'none' }, auto-push short-circuits, and the manual tracker.* workflows refuse with a clear message. You must explicitly enable it (via insrc tracker setup or by writing the config).

The build stage

Build is a controller-driven MCP tool, insrc_build_step, with two phases (src/mcp/build-step/):

The admission gate is strict: admitBuild (src/workflow/runners/build/admission.ts) requires an approved PLAN for the Story. An unapproved or rejected plan short-circuits to plan-unapproved; only an approved plan reaches the freshness/drift check. Because a PLAN carries no open questions, build inherits the decisions already settled at plan-start rather than re-litigating them.

insrc_build_step — implement
# controller calls insrc_build_step { phase: 'implement', ref: 'E…:S001:T003' }
▚ admission  plan E20260717185807ba:S001 approved · fresh
▚ task      T003  add tag index to the query planner
▚ next      implement → controller runs the templated prompt
# …controller edits files, then:
# insrc_build_step { phase: 'validate', ref: 'E…:S001:T003' }
▚ verdict   read-only session · PASS

Two ways to run

The same five stages run either from your MCP client turn-by-turn, or entirely inside the daemon over the socket. The only visible difference in the output is what meta.model gets stamped with.

Client-drivenDaemon-driven
Surfaceinsrc_workflow_step MCP toolworkflow.run IPC (Unix socket)
Who runs the LLMYour client (Claude Code / Codex CLI) — every turn stays in your sessionThe daemon, via its resolved provider (config → client CLI → Ollama)
ShapeMulti-turn: startplanstepsynthesize (plus resolve_question / review_deferred)Single call that streams progress frames per phase, then a terminal frame
ProgressPer tool-call, in-bandIncremental frames streamed over the socket (decompose, plan-ready, …)
meta.model'client'The resolved provider id (e.g. ollama:qwen3… / claude / codex)
Both paths write the same artifacts to the same locations and honour the same approval gates. Client-driven is the more accurate default (no subprocess spawn, no separate billing path); daemon-driven is the hands-off runner for long jobs where you want streamed progress. Structured-output support is required — workflow.run refuses a provider that cannot emit structured output.
insrc_workflow_step — define
# in your MCP client:
> Use insrc_workflow_step to define an Epic for "Add tag filtering to todos".

phase=start      → decomposer prompt
phase=plan       → 4-step plan  · context.assemble uses insrc_analyze_step
phase=step ×N    → epic.frame → stories.compose → checklist.verify
phase=synthesize → docs/defines/DEF-3f9c…e2.md   meta.model: client

$ insrc workflow approve docs/defines/DEF-3f9c…e2.md
  approved · design.epic unlocked

Where next