Changelog

All notable changes to dippin-lang. Versions follow semver.

[v0.44.0] — 2026-07-01

Added

  • Opt-in file-shipping pack modedippin pack --no-inline [--include <file-or-dir>...] (#73, #176). By default dippin pack inlines every command_file: / prompt_file: / system_prompt_file: body into the packed .dip, so a script that sources a sibling by relative path (e.g. . "${graph.workflow_dir}/scripts/lib/bootstrap.sh") has no file on disk in a packed run. --no-inline instead ships those directive targets as separate bundle entries under workflows/ and keeps the *_file: directives, so parser.ResolveFileDirectives resolves against the extracted tree exactly as in a source-tree run. --include <path> (repeatable, requires --no-inline) declares sibling assets the parser can’t discover statically — files referenced only from inside shell bodies — as a single file or a whole directory (recursive, via a symlink-refusing WalkDir); an --include that resolves to a .dip is an error. Assets ship under workflows/ as siblings of their referencing .dip (so relative resolution is identical source-vs-packed) and the bundle is stamped format_version 2. The library surface is dipx.Pack(ctx, entry, w, dipx.PackOptions{NoInline, Include}); the zero value reproduces the default inline behavior byte-for-byte (still format_version 1), and an older reader rejects a v2 bundle loudly with ErrUnsupportedFormatVersion. Path-safety is preserved throughout: every collected/included candidate is re-validated (containment + symlink refusal), directive targets are validated against their declaring .dip’s own directory (rejecting absolute paths and parent-escapes, matching source/inline behavior), assets ship 0644 (no mode carried), reserved manifest.json/manifest.sig names can’t be assets, and pack-time file-count + total-size caps mirror the open-time caps. Docs: docs/integration.md, the site cli page. Motivating use-case: collapsing many byte-identical inlined bootstrap preambles into one sourced helper (pipelines#49).

Changed

  • Website & docs freshness sweep — updated the cli page’s pack reference for --no-inline / --include and format_version 2; corrected the LSP hover/autocomplete capability descriptions (hover shows model/provider/command by node kind, not a prompt preview; completion offers node IDs + field names, not keywords) and the glossary’s human mode set (choice, freeform, interview, yes_noconfirm was never a mode); refreshed the editor-setup blog’s diagnostic range to DIP001–DIP010 + DIP101–DIP151; and added the missing agent/tool field keywords (writable_paths, last_response_truncate, response_format, response_schema, compaction_threshold, backend, working_dir, cmd_timeout) to the VS Code TextMate grammar.

Runtime pairing (requires an enforcing runtime)

  • --no-inline produces a bundle whose extracted tree mirrors the source tree under workflows/. For a packed run to resolve ${graph.workflow_dir}/<relpath> identically to a source-tree run, the runtime must seed graph.workflow_dir to the unpacked-bundle root (tracker#430). dippin ships this independently and is not gated on the runtime (per never-gate-dippin-on-tracker); until the paired runtime lands, the dippin-side deliverable is purely that the extracted bundle byte-mirrors the source tree.

[v0.43.0] — 2026-06-22

Added

  • Section-level else -> <node> edge default — the error-funnel collapse (#154, implements the decision in #135 / epic #127). One line at the bottom of the edges block, else -> Cleanup, is the graph’s success-side default destination: any node whose guard edges all fail to match, and which has no explicit unconditional edge of its own, routes there. It collapses the dominant line-count cost in real workflows — the per-marker failure funnel — from ~20 hand-routed edges to one. Stored as a graph-level ir.Workflow.ElseTarget (not a synthetic ir.Edge, so the edge list and the tools that read it — EdgesFrom, DOT export, edge-keyed analyses — are untouched); the validator seeds the else target into its reachability analysis explicitly (so DIP004 treats it as reachable and DIP105 sees it as a success route). At most one else per block (a second is a parse error). Lint: DIP102 (no default edge) and DIP101 (reachable only via conditional edges) treat a node covered by else as having its unconditional fallback, so they stand down. Structural validation: the else target must exist (DIP003) and is treated as reachable (DIP004), mirroring defaults.on_failure. Success-side onlyelse never intercepts a genuine node failure (tool exit ≠ 0, agent error), which routes via the failure cascade (on faildefaults.on_failure); the two are distinct, non-competing channels. Formatter emits else last in the block; tree-sitter grammar + corpus updated (regen via npx tree-sitter generate). Docs: docs/edges.md, docs/GRAMMAR.ebnf, docs/llm-reference.md.

Changed

  • dippin explain / diagnostic-text accuracy (#168, #172) — corrected diagnostic output that misdescribed behavior or suggested invalid values: DIP106 reworded “undefined variable” → “unrecognized variable reference” (it’s a namespace/shape check, not an upstream-write check); DIP109 retitled “duplicate subgraph reference” with the real trigger (two subgraph nodes sharing a ref:) and a remediation that actually silences it (different ref: / consolidate, not “distinct params”); DIP113/DIP114/DIP116 fix-hints corrected to the validator’s actually-valid value sets (retry-policy, fidelity, on_resume); explain examples now use valid .dip syntax — keyword edge form (when ctx.outcome = success) instead of the rejected bracket form ([success]), and # comments instead of //. Also fixed a stale simulate doc comment (“fail” is never emitted). Detection/text only — no rule-firing behavior changed.
  • Documentation, website & editor-grammar accuracy sync (#171) — content-only sweep bringing every hand-maintained surface in line with the current language: the language/validation/analysis/architecture/cli/testing pages, glossary, skill.md + llm-reference (+ regenerated spec), and the tree-sitter/Zed/VS Code grammars. Added else syntax highlighting (and refreshed the stale Zed grammar pin), corrected DIP ranges/counts to DIP101–DIP151, documented previously-missing attributes (*_file: directives, tool_access, writable_paths, parallel block form, the four human modes), and fixed numerous stale command/flag/diagnostic descriptions.

Internal

  • Tech-debt cleanup batch (#164, #165, #166, #167, #169, #170) — removed dead code and duplication across the toolchain: test-only/zero-reference exported functions, hand-populated Condition.Parsed test fixtures (a DIP101 anti-pattern), unreachable defensive branches, de-facto-constant config knobs / single-use params / hand-rolled stdlib reimplementations, and duplicated simulate event-construction. No behavior change; net ~1000 lines removed.

Runtime pairing (requires an enforcing runtime)

  • else adds one terminal step to the engine’s routing resolution: when a node’s guards do not match and it has no unconditional edge of its own, route to the section else target (success side). The failure cascade is unchanged and runs in its own channel. Inert on the dippin side until the paired runtime reads ir.Workflow.ElseTarget (per never-gate-dippin-on-tracker).

[v0.42.0] — 2026-06-16

Routing-syntax Phase 0 complete — the human-gate choice: routing key and the weight: soft-deprecation lint. All changes are dippin-side only; no behavior is gated on a paired runtime (per never-gate-dippin-on-tracker). Non-breaking: every valid v1 .dip file parses, validates, and formats unchanged — weight: still parses (carry-only) and merely warns now.

Added

  • choice: edge attribute — a dedicated human-gate routing key that disambiguates the routing intent from a display-only label: (#130, #151). Carry-only: dippin parses, validates, formats, exports (a distinct DOT attribute), and round-trips choice: (ir.Edge.Choice) but assigns it no execution semantics — a paired runtime interprets it. Mirrors the carry-only override: shape (#125). DIP150 (Hint) flags a label-routing human gate (mode choice, yes_no, or the unset default) whose outgoing edge sets a non-empty label: but no choice: — in Phase 0 that label doubles as the load-bearing routing key, so the hint suggests marking it explicitly; freeform/interview gates (which route by text/answers, not labels) are exempt. Phase-0 source-compatibility (no dip 2 bump): label: is NOT made display-only — choice: wins as the routing key when present, and when absent label: still serves as the routing key, so existing human-choice workflows route unchanged. Editor surfaces (tree-sitter grammar + highlights, zed, vscode tmLanguage) updated.
  • DIP151 (Warning) — soft-deprecation of the weight: edge attribute (#131, #152). weight: was tier 4 of the 5-level routing cascade — a speculative priority hint — but the cascade never consults it and no real .dip workflow uses it. The lint fires one diagnostic per edge with a non-zero weight:, steering authors toward conditions / on / a single unconditional fallback. Carry-only / no breakage: weight: still parses exactly as before — dippin only now warns. Removing the keyword and the cascade tier is deferred to dip 2 (#134). Surfaces in lint/check/watch/doctor.

Runtime pairing (requires an enforcing runtime)

  • choice: unblocks the tracker’s convertEdge follow-up: once this is tagged, the engine prefers ir.Edge.Choice over Label as the human-gate matching key (falling back to Label when choice: is absent). Inert on the dippin side until the paired runtime reads it.

Docs

  • Website, editor, and grammar surfaces resynced to the v0.41.0 language surface (#150) — content-only, no behavior change: site/content/{validation,language,cli}.md counts/ranges and the DIP148/DIP149 entries, on/loop/dip N/single-quote notes, tree-sitter/zed/vscode keyword highlighting, and the single-quoted STRING alternative in GRAMMAR.ebnf.

[v0.41.0] — 2026-06-16

Routing-syntax Phase 0 plus the dip 2 version foundation. All changes are dippin-side only — no behavior is gated on a paired runtime (per never-gate-dippin-on-tracker). Non-breaking: every valid v1 .dip file parses, validates, and formats unchanged (the one intentional rejection is a previously-silently-dropped unknown edge attribute, now diagnosed — see below).

Added

  • on <token> edge routing shorthand — sugar for an equality test against a node’s natural outcome channel (#128, #140). A -> B on success desugars to when ctx.outcome = success for an agent source, and to when ctx.tool_marker = <token> for a tool source that declares marker_grep; nodes without a defined outcome channel (human gates, conditional, tool without marker_grep) get a located diagnostic suggesting when. IR-preservingon X produces the identical ir.Condition as the equivalent when, so DIP102/DIP103, DOT export, and every other edge consumer reason over it unchanged. The channel is a single source of truth (ir.Node.OutcomeChannel()) shared by parser and formatter, so what the parser accepts is exactly what the formatter emits; dippin fmt rewrites an eligible when to on (shape-recognition, no new IR field) and is idempotent.
  • loop keyword — a scannable, value-less synonym for restart: true on back-edges (#129, #143). A back-edge is the heaviest-semantics construct in the language (restart counter, max_restarts failure, downstream completion-state reset), so it earns a word: QualityGate -> Refactor on fail loop. Sets the same ir.Edge.Restart field — carried-only, no new IR, no semantics change; restart: true still parses and dippin fmt rewrites it to the canonical loop. DOT export (dashed) and DIP005 (cycle exemption) work for free via the shared field. Because loop is value-less it terminates a preceding condition unconditionally, so a bare loop is reserved on a condition RHS — write when ctx.x = "loop" (quoted) for the literal.
  • DIP149 (Warning) — ambiguous routing: a node with ≥2 unconditional outgoing forward edges, whose winner is decided only by the cascade’s lexical tiebreak on target node IDs (so renaming a target can silently change which edge fires) (#132, #144). Conservative (favors false negatives): restart/loop back-edges are excluded; a guarded edge plus a single unconditional fallback is the intended pattern and is not flagged; duplicate same-var/same-value guards stay covered by DIP103. parallel fan-out (all edges fire) and human choice gates (the human picks) are exempt; fan_in is not exempt — its out-edges route by the ordinary cascade. Surfaces in lint/check/watch/doctor. Sets up the Phase 1 cascade-collapse where the lexical tier is removed.
  • dip N format-version declaration + fmt --migrate scaffolding (#133, #148). A leading line-1 dip N sets the parsed format version (default 1), wiring the previously-dead ir.Workflow.Version; the formatter emits the line only for N>1, so existing v1 files stay declaration-free and round-trip byte-identical. fmt --migrate lands as a no-op v1→v1 identity pass reusing the existing parse/format machinery. Foundation onlyp.version is now reachable from parse_edges.go; the v1→v2 transforms (edges own their destinations) ship with #134 and are explicitly NOT in this release.
  • Single-quoted strings accepted in edge label: attributes and when conditions (#146, closes #120). Extends the v0.39.0 single-quote scalar support to the edge token-stream surface that #117 deliberately deferred. YAML-style (surrounding quotes stripped, ''', backslashes literal); the token-stream comment stripper now treats ' as a delimiter only at a token-start boundary, so a prose apostrophe (it's) still strips its trailing comment while a # inside a quoted token stays literal. Normalizes to double-quoted output on fmt.
  • Edge-attribute robustness — unknown edge attributes are now diagnosed instead of silently swallowed, and keyword termination is colon-gated (#126, #139). A -> B bogus: true previously parsed with bogus dropped on the floor (the same inert-attribute class that hid the pre-#124 override); it now emits exactly one located diagnostic. Colon-gating means label/weight/restart/override terminate a when condition only when followed by :, so a bare attribute word as a condition’s right-hand value (when ctx.reason = override) is no longer truncated. Source-compatible but not strictly non-breaking — a file with a typo’d or runtime-only edge attribute that parsed before is now rejected; this is the intended hardening that makes the Phase 0 keyword surface safe.

Fixed

  • Extra/preview models registered via --extra-models are now scoped out of the global model registry (#105, #145). validator.RegisterExtraModels mutated a package-level catalog, so models from one lint/doctor run leaked into every later validate/doctor/lint call in the same process (making TestCmdLint_ExtraModels_SuppressesDIP108 non-idempotent under -count>1). Replaced with a pure ParseExtraModels threaded through a scoped validator.Options/LintWithOptions (and doctor.DiagnoseWithOptions); Lint/Diagnose keep their signatures and delegate with empty options.
  • startswith edges now count as covering printf-format markers in coverage analysis (#141, #147). dippin coverage (which drives dippin doctor’s “uncovered outputs” check) did exact-match of each tool-output literal against edge-condition values, discarding the operator — so a when ctx.tool_stdout startswith citations-fail edge never covered the printf-format output citations-fail-%s, capping doctor grades on otherwise-clean files. The operator is now carried through and an output is covered if some startswith term is a prefix of it (conservative — only startswith is extended). The public EdgeConditions []string report field is unchanged.
  • Playground (dippin.org): corrected the lint JSON shape and initial syntax highlighting (#142). cmd/wasm marshalled the raw validator.Diagnostic (PascalCase keys, numeric severity), so the playground JS read undefined for code/severity/message and crashed on the default workflow’s DIP144 — mislabeled as “Failed to load WASM”; diagnostics now map to the camelCase/string-severity DTO matching the CLI contract. Separately, the initial highlight is deferred to DOMContentLoaded so the default source is no longer black-on-black until the first keystroke.

[v0.40.0] — 2026-06-11

Carry + detection work pairing with downstream runtimes — no dippin behavior is gated on runtime readiness (per never-gate-dippin-on-tracker). Both attributes are carried through every .dip path; their semantics are owned by a paired runtime.

Added

  • last_response_truncate (agent node + parallel branch override, integer) — a character-count cap on the auto-injected ${ctx.last_response} handoff, a carry-only chain-attack mitigation (#56, #123). Carried through parse → IR → formatter → DOT export → migrate; DIP148 (Warning) flags a negative value (0/unset = disabled). Carried, not interpreted — a paired runtime performs the truncation. Mitigates the ${ctx.last_response} auto-injection vector of #56 when an enforcing runtime honors this field; DIP147 (explicit-key handoff) is unaffected and there is no DIP147↔truncate interaction.
  • override: true / override: false edge attribute → ir.Edge.Override (bool) — carried, not interpreted (the same policy as restart:) (#124). Parsed in any order alongside when / label / weight / restart (no longer silently swallowed), and round-tripped through the .dip formatter, DOT export, and migrate. No dippin-side semantics — a paired runtime maps it onward and owns the rule that override edges originate from human nodes. Same shape as the params: carry shipped in v0.39.0 (#113).

Runtime pairing (requires an enforcing runtime)

  • Edge override unblocks the workflow-author syntax for 2389-research/tracker#271, whose engine half (the validation_overridden terminal status, pipeline.Edge.Override, sticky checkpoint persistence, --fail-on-override, lint TRK102) shipped in tracker v0.35.0. Once this is tagged, tracker maps ir.Edge.Overridepipeline.Edge.Override in convertEdge, adds the human-origin validation (tracker spec D11), and migrates the catalogued override edges (tracker#271 finisher).
  • last_response_truncate is carried + linted by dippin but the truncation is performed by the runtime — inert until a paired runtime reads it.

[v0.39.0] — 2026-06-10

Detection, carry, and correctness work — no paired runtime release required (per never-gate-dippin-on-tracker). DIP147 and the params: carry are dippin-side detection / round-trip only; the single-quote fix is a parser correctness fix; the catalog refresh is data.

Added

  • DIP147 (Hint) — chain-attack detection: a restricted agent’s output is laundered through an explicitly-keyed context handoff into a downstream tool-bearing agent, re-granting capability the upstream node was denied (#56, #115). Covers the explicit-key handoff vector only — a partial #56. The other laundering vectors remain open follow-ups: ${ctx.last_response} auto-injection (and a last_response_truncate: mitigation), attribute, cross-file, and branch-override. Detection, not runtime enforcement.
  • params: on parallel / fan_in nodes are now carried through parse → IR → formatter round-trip (#110, #113). Formatter round-trip only (not DOT export or migrate); a paired runtime owns any fan-in policy semantics.
  • Model catalog + pricing refresh for Anthropic’s 2026-05/06 releases (#116): claude-opus-4-8, claude-fable-5, claude-mythos-5, and claude-mythos-preview are recognized models (clearing a spurious DIP108), with claude-opus-4-8 ($5/$25), claude-fable-5 ($10/$50), and claude-mythos-5 ($10/$50) priced so dippin cost can estimate them. Deprecation metadata refreshed (claude-opus-4-1 retires 2026-08-05; claude-opus-4-0 migration target → claude-opus-4-8). The richer cache/batch/fast-mode pricing schema is an explicit non-goal; tracker-side runtime support is a cross-repo follow-up.

Fixed

  • Single-quoted scalar values are no longer corrupted (#114). The parser stripped only double quotes, so marker_grep: '^(a|b)$' was stored with the literal ' chars — and the formatter then re-wrapped it in double quotes — silently breaking the runtime regex. Single quotes are now YAML-style (surrounding quotes stripped, ''', backslashes literal), and a # inside '...' is no longer mistaken for a trailing comment. Fixed in the parser so every path (lint / pack / format / DOT / migrate / runtime) benefits; the tree-sitter grammar admits single-quoted strings too.

[v0.38.0] — 2026-06-09

Cross-file tool_access advisory completeness. Both changes are dippin-side detection only — they refine the DIP146/DIP143 cross-file analysis shipped in v0.37.0 and require no paired runtime release (per never-gate-dippin-on-tracker, no dippin behavior is gated on runtime readiness).

Added

  • DIP143 (Hint) deep cross-file advisory — the native dippin lint cross-file pass now emits DIP143 for a partial-audit or unresolvable child found at depth ≥ 1 (behind an already-audited intermediate), gated by path tool_access intent (#102). Previously this fallback came only from the entry-file lint, so a partial/unresolvable gap more than one hop deep was silent — DIP146 already traversed transitively for zero-intent children, but the partial/unresolvable advisory did not. Reuses DIP143 (no new code); the message distinguishes partial-audit (resolved — a tool-bearing agent lacks its own tool_access) from unresolvable (missing / unparseable / refused). Entry boundaries (depth 0) keep their existing validator.Lint DIP143. Emitted from dippin lint/check/watch; no wasm/validator-detection change.

Fixed

  • Intent-aware cross-file re-walk — a child reached first via a path with no tool_access intent and later via a restricting path was not re-walked under the restricting intent, so a zero-intent / partial-audit / unresolvable grandchild below the shared child was silently missed (#109). The recursion guard is now intent-aware (re-walk once on a no-intent → intent upgrade; bounded and cycle-safe, with the entry file never re-walked). Pre-existing in DIP146 since its introduction (#89) and inherited by the #102 deep advisory; both now catch the mixed-intent shared-child shape regardless of traversal order.

Docs

  • Documented the deep DIP143 advisory and the intent-aware cross-file traversal in the validation reference (docs/validation.md + the website validation page).

[v0.37.0] — 2026-06-08

Mixed release. The cross-file tool_access detection + resolver hardening and the DIP010 edge-condition error are dippin-side detection only (no paired runtime release required). The graph-level on_failure route and the declarable budget guards are an authoring surface dippin carries + lints while a paired runtime enforces — inert until the runtime reads them (per never-gate-dippin-on-tracker, no dippin behavior is gated on runtime readiness).

Added

  • DIP146 (Hint) — cross-file tool_access gap: a manager_loop (subgraph_ref) or subgraph (ref) boundary delegates to a child .dip whose agents re-grant tools a parent on the path restricted (#89). Completes the DIP143 (#59) arc by resolving and classifying the referenced child instead of only flagging the boundary: zero-intent child → DIP146 (DIP143 superseded); full-restrict or agent-less → silent (confirmed safe); partial-audit or unresolved → DIP143 retained (“unknown” never reads as “checked & safe”). Full transitive DFS with EvalSymlinks-keyed cycle termination and a depth cap. Emitted from the native CLI pass — dippin lint/check/watch apply it with DIP143 supersession; validate stays structural-only. Detection, not runtime enforcement.
  • DIP010 (Error) — an edge when condition that cannot be parsed (e.g. an unknown operator, or a tool-node field like marker_grep used in operator position) (#98). Previously the parse error was discarded by Lint() and EnsureConditionsParsed stopped at the first bad edge, so validate/lint/check/doctor greenlit a workflow that hard-fails at dippin simulate — and every edge after the first bad one silently lost its AST-dependent lints (DIP103/120/121/122). DIP010 is emitted from validator.Validate(), so every command path catches it, and parsing now continues past failures (one diagnostic per bad edge; later edges keep getting linted). Edge conditions only; manager_loop node conditions are a separate follow-up.
  • on_failure: <NodeID> — a graph-level (defaults:) catch-all failure route (#92). Structurally validated (DIP003 existence with a “did you mean?” suggestion; the target is seeded into DIP004 reachability so a recovery node reachable only via the catch-all is not falsely flagged unreachable) and round-tripped through the .dip formatter, DOT export, and migrate.
  • DIP144 (Warning) — an agent node has no failure route (#93). Suppressed by an explicit ctx.outcome = fail|failure edge, fallback_target, bounded retry (retry_target + max_retries > 0), or a graph on_failure; an unconditional/success edge does not suppress it.
  • stall_timeout (graph defaults:, duration) — abort/route when no forward progress is made for a wall-clock span (#94); 0/unset = disabled. Round-trips through parser → formatter → DOT export → migrate (this also closed a pre-existing DOT-export bug that silently dropped every budget ceiling). max_turns now has defined exhaustion semantics: outcome = fail, routed through the failure cascade (docs-only; no new action field).
  • DIP145 (Warning) — a graph budget default is negative (#94); 0 = unset = no warning.
  • examples/on_failure_route.dip and examples/budget_guards.dip demonstrators.

Hardened

  • The DIP146 cross-file resolver now refuses symlinked and root-escaping child .dip refs, reaching parity with the pack walker (#100). Fail-soft: a refused child is treated as unresolvable (DIP143 retained), so linting never hard-errors or aborts. Reuses dipx’s ReadNoFollowSymlinks (leaf + ancestor-directory symlink refusal) and a fixed entry-file-directory containment root; absolute refs are re-rooted under that root rather than read out of tree. Lstat-based parity with the pack walker — no O_NOFOLLOW/build-tag complexity and no wasm impact. Detection parity, not runtime enforcement.

Changed

  • dippin doctor now exits non-zero when the report contains errors (e.g. DIP010 or any structural DIP001–DIP010), rather than always exiting 0. The report still renders in full; only the exit code changes, so doctor no longer greenlights a workflow that cannot execute.

Runtime pairing (requires an enforcing runtime)

  • on_failure, stall_timeout, and max_turns exhaustion are carried + linted by dippin but enforced by the runtime — inert until a paired runtime reads them. The runtime owns the failure-cascade ordering: matching fail edge → bounded node retry → node fallback_target → graph on_failure → halt.

Docs

  • Audited and refreshed all reference docs to the current language surface (#97).

[v0.36.0] — 2026-06-03

Dippin-side only — no paired runtime release required.

Added

  • DIP143 (Hint) — a manager_loop (subgraph_ref) or subgraph (ref) node references a child .dip that does not inherit the parent workflow’s tool_access restrictions (#59). Fires only when the workflow declares tool_access intent (any non-empty value on an agent or parallel branch) and references an external subgraph, reminding the author to give the child’s agents their own tool_access. tool_access is per-node and does not cross a file boundary. A direct self-reference (a node whose ref resolves to its own source file) is not flagged. The lint never parses the child file (the validator may not import the parser); real cross-file effective-access enforcement, plus transitive cross-file cycles, are deferred to #89.

Hardened

  • The @file directive resolver is hardened against a leaf TOCTOU race (#79). The path was validated and then read via a separate os.ReadFile, so a concurrent symlink/rename swap of the final component could redirect the read outside baseDir, bypassing the #67/#77 containment under a race. Now a single open-once path — os.OpenFile(O_RDONLY|O_NOFOLLOW)f.Stat() (fstat the fd) → containment check → io.ReadAll — operates entirely on one fd, closing the check-to-read race. On Unix, O_NOFOLLOW makes leaf-symlink rejection atomic (ELOOP); the resolved path is never leaked in the error. The residual parent-directory swap race is documented as out of scope.

Docs

  • Clarified that tool_access is node-scoped — it constrains the executor of a single node and does not taint downstream nodes — resolving #57 via documentation rather than a graph-topology lint (#87).

Changed

  • Internal: removed the consumer product name “tracker” from dippin sources — dippin is consumer-agnostic (#85).

[v0.35.0] — 2026-06-02

Added

  • writable_paths: — a comma-separated glob list on agent nodes and parallel branches that bounds where an agent’s tools may write (e.g. writable_paths: workspace/**, .ai/sprints/**) (#75). Absent = unbounded; a per-branch empty value inherits the target agent’s (never resets to unbounded). dippin carries + lints; the runtime enforces an fs-level write jail on the native backend (covering Write/Edit/ApplyPatch, Bash and any process Bash spawns), resolved against an immutable session root. Distinct from the advisory writes: field (context keys). Carried through parse → IR → format → DOT export → migrate.
  • DIP141writable_paths nullified by tool_access: none (dead config; nothing left to bound).
  • DIP142 — unsafe writable_paths entry (absolute / ~ / Windows-drive / ..-escape / brace mis-split). Author-clarity lint; the runtime fs-jail is the real boundary.
  • examples/agent_writable_paths.dip demonstrating the five motivating failure/recovery-recorder shapes.

Runtime pairing (requires an enforcing runtime)

  • Enforcement ships in a coordinated runtime release: native fs-jail incl. Bash children, runtime symlink-chain resolution, immutable session-root anchor (working_dir/Params cannot relocate it), Params/working_dir bypass defense, and a single-turn red-team suite.
  • Fail-closed. A present-but-empty or comma-only writable_paths: is rejected by dippin validate/pack (parse error). A malformed or runtime-unrecognized value → the runtime denies all writes or refuses to start, never unbounded. On claude-code/acp, the runtime refuses to start when writable_paths is set (native-only enforcement) — never a prompt-level pretend-jail.
  • Version-skew is a safety requirement, not a suggestion: a runtime that does not enforce writable_paths must refuse to start rather than run unbounded. Pin an enforcing runtime, never @latest.

Notes

  • The primitive bounds write location, not network (curl/cargo fetch), reads/exfil-by-read, or content within an allowed path (a workspace/** grant can still poison workspace/Cargo.toml). Chain laundering is tracked in #56.
  • Sequenced follow-ups: #55 (tool-name allowlists — now an orthogonal axis), #53 (defaults cascade).
  • Design spec: docs/superpowers/specs/2026-05-29-issue-75-writable-paths-design.md.

[v0.34.0] — 2026-05-28

Added

  • prompt_file: <path> and system_prompt_file: <path> directives on agent nodes (#65). Symmetric extension of v0.33.0’s command_file:; same path-relative-to-.dip rules and security cap. dippin pack inlines the content, so no runtime coordination is required.
  • examples/external_prompts.dip demonstrating both directives.

Fixed

  • dippin fmt --write previously stripped inline system_prompt: from agent nodes on save (latent since the field was introduced). If you’ve been re-adding system_prompt: after running fmt, this fix is for you.

Notes

  • Parser stays pure (no FS I/O); resolver runs at CLI entry points only. LSP and WASM consumers see the unresolved IR view (*File set, content empty), which is the correct view for those contexts.
  • Per-node only; defaults agent does not currently support prompt / system_prompt fields, so file-form-in-defaults requires a larger design — filed as #72.
  • Bundled-files .dipx redesign considered and explicitly deferred so v0.34 stays dippin-only; filed as #73.

[v0.33.0] — 2026-05-27

New command_file: directive on tool nodes replaces inline command: heredocs with external file references. Solves the heredoc-bloat pattern seen in long runtime workflows. Dippin-only release — no runtime coordination required (the runtime reads inlined Command from .dipx bundles unchanged).

Added

  • command_file: <path> directive on tool nodes. Path is relative to the .dip source directory.
  • parser.ResolveFileDirectives(w, baseDir) — separate-pass file loader called by CLI entry points. Parser itself stays pure.
  • Path security in the resolver: absolute-path reject, parent-tree-escape reject, symlink reject (via Lstat), 4 MiB size cap. Error messages reference user-written paths, not resolved absolute paths.
  • Parser-time error when both command: and command_file: are set on the same tool node.
  • examples/external_files.dip + examples/external_files/setup.sh demonstrate the directive.

Notes

  • LSP and cmd/wasm (playground) skip the resolver. They see cfg.CommandFile != "" && cfg.Command == "", which is the correct unresolved-IR view.
  • DOT round-trip is lossy for the directive form — pack-then-unpack rewrites command_file: to inline command:. The runtime reads from .dipx bundles where content is already inlined; no current consumer needs the path preserved through DOT. Deferred to a follow-up issue (#69).

[v0.32.0] — 2026-05-27

New agent-node safety primitive: tool_access: none strips an LLM’s tool catalog. Coordinated runtime release — the dippin field is meaningless without runtime enforcement, so they ship together (see #41 for context, including the v0.28.2 runaway-agent incident this bounds).

Added

  • tool_access: field on agent nodes. One explicit value: none (no LLM tools). Omitted = full catalog (current behavior).
  • DIP139 lint warns on invalid tool_access values.
  • examples/agent_tool_access.dip demonstrates the field on a summarizer node.

Runtime requirement

  • Requires an enforcing runtime. Without runtime enforcement the tool_access: field is a no-op (parking decision: lint-validated runtime-no-op safety fields ship as worse-than-nothing — the coordinated release prevents this).

Runtime-side

  • Tool registry returns empty when tool_access: none is set.
  • Anthropic translator strips the tools array via tool_choice: none.
  • System prompt scrubbed of tool-naming text when tools are disabled.
  • Params keys (allowed_tools, disallowed_tools, tool_choice, permission_mode) are not honored when tool_access: none is set — Params bypass defense.
  • Backend-compat tests: every supported backend honors tool_access: none or refuses session creation with a clear error.
  • Red-team test: multi-tool-call LLM response under tool_access: none produces zero executions (the actual v0.28.2 shape).

[v0.31.0] — 2026-05-22

Two small dippin-internal fixes. Closes #45, #49.

Fixed

  • mode: yes_no on human nodes no longer trips DIP127. The runtime supports yes_no as a documented mode; dippin’s validator now accepts it alongside choice, freeform, and interview. The four-mode list is cascaded through the validator help text, the DIP127 explanation, docs/validation.md, docs/nodes.md, docs/llm-reference.md, the hosted skill (site/static/skill.md), the LSP completion tooltip, the integration guide, and the IR field comment.
  • Tool nodes used as the workflow’s start: node no longer collapse to AgentConfig after a .dip → DOT → .dip round-trip. migrate.resolveStartExitKind now recovers NodeTool from the start-marker Mdiamond shape by sniffing tool-specific DOT attributes (tool_command, marker_grep, outputs, route_required, output_limit).

Runtime requirement

None. Both fixes are dippin-internal; the runtime is unaffected.

[v0.30.0] — 2026-05-21

Coverage extractor now respects shell redirection. Closes #40.

Fixed

  • dippin coverage no longer flags file-redirected echo/printf statements as uncovered tool outputs. The extractor switched from regex matching to an AST walker built on mvdan.cc/sh/v3/syntax.
  • Statements that redirect to files (>, >>, &>, >&), feed into pipes, or are nested inside command substitution are now correctly skipped. Pipelines using the “log to file, printf marker on stdout” pattern flip from partial to covered.

[v0.29.0] — 2026-05-19

Three follow-ups to v0.28.0’s tool-routing surface. Closes #42, #43, #44.

Added

  • DIP138 reserved for a future advisory: “tool node routes on stdout but declares no marker_grep / outputs”. Code + description + explanation entry only; no firing logic in this release.
  • outputs: now survives a .dip → DOT → .dip round-trip. DOT export emits outputs="a,b,c" on tool nodes that declare outputs, and dippin migrate dot→.dip reads it back.

Changed

  • DIP101 / DIP102 no longer fire on tool nodes that declare marker_grep:. Those nodes route via the typed ctx.tool_marker channel — outgoing conditional edges on them are intentional routing, not unsafe reachability. Removes the false-positive coverage hit that the runtime’s TRK101 option (d) guidance triggered.
  • Parser bool fields (goal_gate, auto_status, cache_tools, route_required) now accept true/false, 1/0, yes/no, on/off case-insensitively via a new shared parseBoolAttr helper. Anything else now produces a parse diagnostic instead of silently coercing to false. The migrate (DOT-input) path keeps strict equality since DOT attrs are machine-emitted.

Runtime requirement

None. All changes are dippin-internal; the runtime is unaffected.

Docs

  • docs/nodes.md “Markers and Verbose Output” notes the DIP101/DIP102 suppression and the accepted boolean forms.
  • docs/llm-reference.md common-mistakes table cross-references the suppression behavior.
  • Hosted skill (site/static/skill.md) updated to match.

[v0.28.0] — 2026-05-19

Tool-node routing fields land in the parser and IR. Authors following the runtime’s TRK101 recommendation can now declare marker_grep, route_required, and output_limit directly in .dip source. Closes #39.

Added

  • tool.marker_grep — regex matched line-by-line against captured stdout; populates ctx.tool_marker at runtime.
  • tool.route_required — boolean; when true, the node fails with EventToolRouteMissing if the command emits no routing signal recognized by the runtime.
  • tool.output_limit — non-negative integer (bytes); 0 uses the engine default stdout tail-window. dippin fmt omits the field when the value is zero.
  • Reserved context variables: ctx.tool_marker, ctx.tool_route.

Changed

  • migrate/parity.go compareToolConfigs now compares all ToolConfig fields. Pre-existing Timeout / Outputs parity gaps backfilled.

Runtime requirement

These fields pass through DOT export to the runtime. Routing semantics require the runtime to ship the matching extractToolAttrs change; see issue #39 for details.

Docs

  • New blog post: site/content/blog/whats-new-v028.md.
  • docs/nodes.md gains a “Best” subsection in Markers and Verbose Output demonstrating marker_grep.
  • Hosted skill (site/static/skill.md) updated with new context variables and best-practice bullet.

[v0.27.0] — 2026-05-18

Model catalog and pricing verification pass against canonical provider docs. Last verified: 2026-05-18 in validator/lint_model.go and cost/pricing.go. No breaking changes to public APIs — but the cost table values move and the catalog accepts new IDs, so downstream tooling that snapshots dippin’s data should re-snapshot.

Added

  • OpenAI: gpt-5.5, gpt-5.5-pro, gpt-5.4-pro, gpt-5.2-pro, gpt-5, gpt-5-pro, gpt-5-mini, gpt-5-nano.
  • xAI: grok-4.3 ($1.25/$2.50, current flagship).
  • DeepSeek: deepseek-v4-flash ($0.14/$0.28), deepseek-v4-pro ($1.74/$3.48 list — 75% launch discount through 2026-05-31).
  • Gemini: gemini-3.1-flash-lite (GA promotion of the preview variant, same price).
  • Mistral: mistral-medium-3-5-2604 ($1.50/$7.50, new flagship-class), mistral-medium-3-1-2508 ($0.40/$2.00), Ministral 3 generation (ministral-3-3b-2512 $0.10/$0.10, ministral-3-8b-2512 $0.15/$0.15, ministral-3-14b-2512 $0.20/$0.20).
  • Cohere: dated IDs command-r-08-2024, command-r-plus-08-2024, command-r7b-12-2024 (the canonical recommended form; bare aliases kept callable but flagged as resolving to versions deprecated 2025-09-15).

Changed

  • OpenAI prices doubled for three legacy IDs: gpt-5.2 $0.875/$7 → $1.75/$14, gpt-5.1 $0.625/$5 → $1.25/$10, gpt-4.1-mini $0.20/$0.80 → $0.40/$1.60. Newer mini/nano tiers and the o-series held steady.
  • xAI fleet-wide price cut: grok-4.20-0309-reasoning, grok-4.20-0309-non-reasoning, grok-4.20-multi-agent-0309 all $2/$6 → $1.25/$2.50, matching grok-4.3’s tier.
  • DeepSeek alias repricing: deepseek-chat and deepseek-reasoner are compatibility aliases resolving to V4-Flash; priced at $0.14/$0.28 (down from $0.28/$0.42) to match the redirect target.
  • Anthropic claude-haiku-3-5 repriced to $0.80/$4.00 (Bedrock/Vertex passthrough rate; was $0.25/$1.25 in the catalog). Model was retired on the first-party API 2026-02-19; remains available via Bedrock and Vertex AI.

Removed

Hard-retired models that the provider returns errors for — calling them is a real bug, DIP108 surfaces:

  • Mistral: pixtral-large (deprecated 2026-02-27), mistral-small-3.2 (deprecated 2026-04-30, past sunset).
  • Gemini: gemini-3-pro-preview (shut down 2026-03-09).

Soft-retired models that the provider silently redirects (kept in the catalog, priced at the redirect target so cost analysis stays accurate):

  • xAI: grok-4-1-fast-reasoning and grok-4-1-fast-non-reasoning (retired 2026-05-15; xAI redirects to grok-4.3 server-side and bills at grok-4.3 rates).

Deprecation calendar

Still in the catalog with deprecation comments:

  • 2026-06-01: gemini-2.0-flash.
  • 2026-06-15: claude-sonnet-4-0, claude-opus-4-0.
  • 2026-07-24: deepseek-chat, deepseek-reasoner aliases.
  • 2026-10-23: gpt-4o, gpt-4.1-nano, o3-mini, o4-mini.

Documented uncertainties

Inline comments in cost/pricing.go flag values held pending re-verification:

  • Mistral nemo and mistral-small-2603: official pricing tab is JS-rendered; third-party sources conflict.
  • Cohere command-a-03-2025 and command-r7b-12-2024: per-token pricing removed from the public page.
  • Gemini Pro >200K-tier and OpenAI gpt-5.5 / gpt-5.4 family >272K-tier: modeled at the base tier only.

Docs

  • Blog post site/content/blog/whats-new-v027.md covers the refresh.
  • Blog post site/content/blog/whats-new-v026.md retrospective on the v0.26 requires: keyword.
  • Homepage “Latest” slot and v0.25/v0.26 related: cross-references updated.

[v0.26.0] — 2026-05-15

Added

  • Workflow header requires: keyword. New optional workflow-header field for declaring workflow-level prerequisites (e.g., tools, MCP servers, env vars) as a comma-separated identifier list. Advisory in v1 — parsed, round-tripped by the formatter, and exposed as ir.Workflow.Requires []string, but not yet validated by lint. Mirrors the shape of node-level reads: / writes:. Filed to unblock the --git= preflight mechanism in the runtime. Canonical formatter order is goal → requires → start → exit. Editor support (tree-sitter, VS Code, Zed) and the hosted skill (site/static/skill.md) updated.

[v0.25.0] — 2026-05-11

.dipx format v1.1. The spec at docs/superpowers/specs/2026-05-06-dipx-bundle-format-design.md is the canonical contract; this release closes ambiguities in it (Bundle 6), brings the implementation in line with the documented contract, and adds genuine cancellation through Pack/Open hot paths.

Breaking changes for downstream consumers:

  • Source.Workflow now takes context.Context as its first argument. Bump your dippin-lang import via go install [email protected] (or @latest) and update call sites.
  • dippin inspect --format=json status field is now an object, not a bare "VALID" string. If you parse the JSON in scripts, decode status as an object with valid, verify_skipped, file_count, byte_total, format_version.

Fixed

  • Cycle detection now covers every manifest-listed workflow. dipx.Open previously DFS’d the ref graph rooted only at m.Entry, while parseAllWorkflows already parsed every manifest-listed workflow. A cycle in a manifest-listed-but-entry-unreachable workflow could slip through. walkRefs now iterates detectCycles over m.Files. (Bundle 6 / Phase 5 L2/L3.)
  • dippin pack/unpack/inspect exit code 2 (integrity failure) now matches the spec contract. isIntegrityErr previously routed only 5 sentinels to exit 2; 7 others (ErrUnsupportedFormatVersion, ErrFileMissing, ErrFileUnexpected, ErrEntryNotInManifest, ErrRefEscape, ErrRefCycle, ErrCapExceeded, ErrPathUnsafe) defaulted to user-error 1. Refactored to a sentinel-slice + loop covering all 12 spec-enumerated sentinels. (Bundle 6 / Phase 8 M1.)
  • Open enriches manifest-decode errors with the bundle path. BundleError.Path for ErrManifestInvalid and ErrUnsupportedFormatVersion was previously empty or a JSON field name (e.g., "format_version"); external callers now always observe the bundle file path. The original Path is preserved in Detail when non-empty. (Bundle 5 / Phase 3 manifest-decoder error-context.)
  • Pack subgraph parse failures attribute to ErrSubgraphParse. Previously every parse failure surfaced as ErrEntryParse regardless of which workflow failed; subgraph failures now correctly classify as ErrSubgraphParse with the subgraph’s filesystem path. (Bundle 5 / P10.9.)
  • dippin inspect emits a structured status object. JSON output’s status was previously a bare string "VALID"; it is now an object with valid, verify_skipped, file_count, byte_total, format_version per spec § “CLI / inspect command”. Text footer now includes the byte total. Breaking for JSON consumers parsing status as a string. (Bundle 2 / Phase 8 M4, L1, L2.)
  • dippin inspect --no-verify actually skips hash verification. Previously a no-op (warning printed, full verification still ran). Now routes through a new dipx.OpenManifest API that performs only structural-admission steps; tampered bundles can be inspected without integrity errors firing. (Bundle 2 / Phase 10 P10.4.)
  • Source.Workflow now takes context.Context as its first argument. dirSource.Workflow checks ctx before disk I/O; Bundle.Workflow checks ctx at entry for interface consistency. Breaking for external callers (the runtime) — bump your dippin-lang import to pick up the new signature. (Bundle 1 / Phase 6 L4.)
  • Open and Pack are cancellable mid-loop. verifyAllHashes, walkSourceTree, and writeBundle now check ctx.Err() between each entry/iteration. A long Open against a many-entry bundle or a long Pack against a deep source tree can be canceled within one entry’s processing time instead of running to completion. (Bundle 1 / Phase 10 P10.2, P10.7, P10.10.)

Spec

Seven .dipx bundle-format spec clarifications (no behavior change beyond the two Fixed items above). Each is described in detail in the per-commit messages on this branch.

  • Path canonicalization rule 2 narrowed to “Backslash \ MUST be rejected” (was “Backslash \ and any other separator…”). The implementation already rejects only backslash; the spec wording was over-broad.
  • Per-sentinel error context preamble added to disambiguate BundleError.Path semantics across three real cases: bundle-relative (read-side, post-Open), JSON field name (manifest decode pre-bundle-context), source filesystem path (Pack-side). Spec now requires Open to enrich (b) → (a) before returning.
  • Open ordering step 5 (“Verify no extra zip entries”) inserted as a normative step between manifest-shape validation and hash verification; subsequent steps renumbered. ErrFileUnexpected added to the error precedence list at category 4.
  • Cycle detection scope documented: spec § “Open ordering” step 8 now specifies “every manifest-listed workflow,” matching parseAllWorkflows.
  • Integrity-failure sentinel set for CLI exit code 2 expanded from 5 to all 12 spec-enumerated sentinels.
  • inspect --format=json status object schema documented with a concrete JSON example (valid, verify_skipped, file_count, byte_total, format_version).
  • Runtime integration migration example updated: Source.Workflow(ctx, sub.Ref, parentPath) (was missing ctx). Bundle 1 will land the matching Go signature change.

[v0.24.0] — 2026-05-08

Added

  • .dipx bundle format — deterministic, content-addressed ZIP that packages a .dip entry workflow plus every transitively-reachable subgraph into a single integrity-verified artifact. Bundles carry a SHA-256-per-file manifest and a workflow-tree identity hash; integrity is verified on every Open. New package dipx/ exposes Open, OpenLax, OpenReader, Pack, Extract, Validate, and Load, plus the Source interface (Entry, Workflow) for runtime consumers.
  • New CLI commands: dippin pack <entry.dip> (build a bundle, with -o, --dry-run); dippin unpack <bundle.dipx> (atomic extract via staging + rename, with -o, --force); dippin inspect <bundle.dipx> (print manifest, identity hash, file list; --format text|json).
  • Existing commands accept .dipxvalidate, lint, doctor, parse, cost, coverage, simulate, optimize, unused, graph, diff, check, explain, export-dot now transparently load a .dipx via dipx.Load, hash-verify it, and analyze the entry workflow.
  • Distinct exit codes for bundle commands: 0 (ok), 1 (user error), 2 (integrity error), 3 (I/O error), 4 (cancelled).
  • CLAUDE.md loader-tier exemption: dipx may import ir + parser + simulate but is forbidden from importing validator, cost, formatter, or any other analysis package. Pack-time structural validation runs at the CLI layer (cmd/dippin/cmd_pack.go).

Fixed

  • Extract --force no longer destroys the existing destination directory when the staging-into-place rename fails on a cross-device boundary (EXDEV). The new backup-aside / rename-into-place / remove-aside sequence preserves the original on failure.
  • Pack rejects symlinked parent directories anywhere between the entry’s source root and a leaf .dip, closing a host-file exfiltration vector when packing untrusted source trees (CI-runner contributor builds, mono-repo subdirs).
  • Pack’s ref-escape check no longer false-positives on legitimate filenames whose component name begins with .. (e.g., ..foo/bar.dip). The check now requires the literal .. component, not a .. substring.
  • dippin pack -o foo.dipx no longer races two parallel invocations against the same temp filename — uses os.CreateTemp for a unique staging path.

Internal

  • Upgraded golang.org/x/text from v0.3.3 to v0.37.0 (defensive — only unicode/norm is consumed; the v0.3.3 CVEs were in x/text/language).

[v0.23.0] — 2026-04-22

Added

  • WorkflowDefaults tool-safety fields: tool_commands_allow (glob allowlist for tool-node shell commands) and tool_denylist_add (globs appended to the runtime’s default denylist). Both round-trip through parser → formatter → DOT export → migrate. Values pass through verbatim — the runtime owns split and glob semantics. (#28)

Changed

  • DOT export header formatExportDOT now emits graph-level attributes (rankdir, tool-safety defaults, workflow vars) as a single graph [key=val, ...]; block instead of separate bare statements (rankdir=TB;). This is the form the migrate DOT parser accepts, enabling true .dip → DOT → .dip round-trips. The output remains valid DOT; consumers that only render via Graphviz are unaffected.
  • tool_commands_allow / tool_denylist_add in vars: no longer emitted — before this release, these keys weren’t reserved, so a workflow that smuggled them through vars: would have them emitted as graph attributes. They are now reserved in favor of the dedicated defaults: fields. Any workflow that previously set either key via vars: should move it into defaults:; otherwise the value is silently dropped from DOT output (the runtime would see no allowlist). This path was never documented — issue #28 filed specifically because defaults: rejected the keys — so the affected population is expected to be zero, but calling it out explicitly for anyone who found the workaround.

[v0.22.0] — 2026-04-22

Added

  • manager_loop node kind for supervising a child sub-pipeline with polling and mid-run context steering. Maps to the runtime’s stack.manager_loop and DOT shape=house. Fields: subgraph_ref, poll_interval, max_cycles, stop_condition, steer_condition, steer_context (inline k=v,k=v or block form). Round-trips losslessly through parser → formatter → DOT export → migrate. Requires the parallel runtime adapter update. (#26, #27)
  • DIP135-137 lint codes for manager_loop validation: missing/nonexistent subgraph_ref (DIP135), invalid control field — negative poll_interval or max_cycles (DIP136), unbounded supervision with no stop_condition and no max_cycles (DIP137 — the manager_loop analog of DIP104).
  • stack.* namespace recognized by DIP120 so stop_condition and steer_condition can reference stack.child.cycles, stack.child.outcome, stack.child.status without namespace warnings.
  • dippin scaffold manager_loop template emits a starter supervisor workflow.
  • Tree-sitter grammarmanager_loop node rule, highlights coverage, corpus test, committed generated parser (src/parser.c et al.), new just tree-sitter-generate / just tree-sitter-test recipes, and CI drift check so generated files can’t drift from grammar.js without being caught.
  • VS Code TextMate grammarmanager_loop keyword, new field names, stack.* namespace recognition.

Fixed

  • Parser steer_context block-form routing — a single-entry block-form steer_context (one k: v line under the indent) lexes without an embedded newline; the previous newline-based heuristic mis-routed it to the inline CSV handler. Replaced with a separator-position check (: before = means block, = before : means inline).

[v0.21.0] — 2026-04-20

Added

  • HumanConfig.Timeout / TimeoutAction on human nodes. Pairs with edge labels like when: timeout for auto-advance semantics. Round-trips through parser, formatter, DOT export, and migrate. (#22)
  • WorkflowDefaults budget fields: max_total_tokens, max_cost_cents, max_wall_time. Allow workflows to declare global budget caps consumed by the runtime. (#22)
  • Scoped context readsctx.node.<id>.* now validates as a legitimate read pattern in DIP121/DIP122, eliminating lint false-positives for cross-node state access. (#23)
  • Agent-readiness discovery endpoints on the docs site: .well-known/agent-skills/index.json, .well-known/mcp/server-card.json, .well-known/api-catalog, robots.txt, and hosted skill.md. Lets coding agents auto-discover dippin-lang tooling. (#24)
  • reasoning_effort expansion — DIP119 now accepts none, minimal, low, medium, high, xhigh, and max to cover Opus 4.7 and GPT-5.4.
  • Model catalog update (verified 2026-04-17): claude-opus-4-7 (Anthropic, $5/$25), mistral-small-2603 (Mistral Small 4), command-a-03-2025 (Cohere flagship, $2.50/$10).

Fixed

  • Syntax grammars (VS Code TextMate, language-configuration, site highlight.js) updated to cover the conditional node kind and vars section introduced in v0.20.0.
  • claude-haiku-3-5 deprecation comment corrected — retired 2026-02-19, not 2026-04-19.

[v0.20.0] — 2026-04-17

Added

  • vars block at the workflow level for declaring user-defined variables. Vars export as DOT graph-level attributes and round-trip through parse → format → export → migrate.
  • DIP134 lint rule: warns when max_retries is set in defaults with restart: true edges but no max_restarts — catches the common confusion between per-node LLM retries and loop restart budget.
  • Release invariant checks (releasecheck/) — validates the embedded spec is tracked, current, and buildable from a source tree without .git.

Fixed

  • DIP125 false positives on shell variable assignments. Replaced regex-based binary extraction with proper shell AST parsing via mvdan.cc/sh/v3. Variable assignments, command substitutions, and command -v checks are now correctly identified. Preamble commands (mkdir) are skipped to find the real tool binary.
  • go install ...@latest brokencmd/dippin/generated-spec.md is now checked into the repo so the go:embed directive resolves from module proxy downloads.
  • Pre-commit hook and just check now mirror CI exactly — spec freshness, release checks, complexity exclusions all aligned.

[v0.19.1] — 2026-04-16

Added

  • working_dir field on agent nodes for per-node working directory override (e.g., .ai/worktrees/claude). Wired through parser, formatter, DOT export, and migrate.

[v0.19.0] — 2026-04-16

Added

  • backend field on agent nodes for per-node backend selection (e.g., native, claude-code, acp). Previously this value was silently dropped by the parser.
  • working_dir field on agent nodes for per-node working directory override.

Fixed

  • Unrecognized node fields now emit a parse diagnostic suggesting the user put the field under params:, instead of being silently discarded.

[v0.18.0] — 2026-04-06

Added

  • flatten package for resolving subgraph refs into flat workflows. Recursive resolution with cycle detection and configurable depth limit (default 10). Underscore-prefixed node IDs (Parent_Child).
  • dippin export-dip command — exports a flattened workflow as canonical .dip text with all subgraph refs inlined.
  • dippin export-dot now automatically flattens subgraph refs before export, producing valid DOT without external references.
  • Example workflowsorchestrator.dip (parent with subgraph ref) and phases/code_review.dip (child workflow).
  • TestLintExamples now recurses into examples/*/ subdirectories.

Fixed

  • Start/Exit rewrite — workflow Start/Exit fields are now correctly remapped when they point to inlined subgraph nodes.
  • Nil resolver guardflatten.Flatten returns a clear error instead of panicking when the resolver is nil but subgraph refs are present.
  • export-dot error rendering — flatten errors now use renderError for JSON output consistency.

[v0.17.0] — 2026-04-03

Added

  • conditional node kind for pure branching without LLM calls. Evaluates outgoing edge conditions only — no prompt, no token cost. Maps to diamond shape in DOT export. DOT migration auto-detects: bare diamondconditional, diamond + promptagent, diamond + tool_commandtool.
  • --extra-models CLI flag on lint and doctor commands. Extends the DIP108 model catalog at runtime for private or newly-released models. Format: --extra-models "provider:model1,model2;provider2:model3".

Fixed

  • Bracket edge syntax ([label: ...]) now emits a clear parse error with a hint to use when/label: keyword syntax, instead of silently discarding annotations.
  • Nested retry blocks now emit a clear parse error suggesting flat attributes (retry_policy, max_retries, retry_target, fallback_target, base_delay), instead of a confusing indent mismatch error.

[v0.16.0] — 2026-03-31

Added

  • Structured output support for agent nodes. New fields:
    • response_format: force LLM to produce structured JSON output (json_object or json_schema)
    • response_schema: inline JSON Schema definition (multiline block, like prompt:)
    • params: generic key-value pass-through for runtime features (same syntax as subgraph params)
  • DIP130: lint warning for invalid response_format value.
  • DIP131: lint warning when response_schema is set without response_format: json_schema (schema ignored); hint when json_schema is set without a schema.
  • DIP132: lint warning when response_schema is not valid JSON.
  • DIP133: lint hint when agent params key shadows a first-class field (e.g., model, provider).
  • cmd_timeout field now parsed and formatted on agent nodes (previously only populated by DOT migrator).

Fixed

  • Duplicate params keys now emit a parse diagnostic instead of silently last-write-wins.
  • Unknown defaults fields now emit a parse diagnostic instead of being silently discarded.
  • AgentConfig.Params initialized to empty map (matching SubgraphConfig), preventing nil-pointer issues in downstream consumers.
  • Cyclomatic/cognitive complexity violations resolved across 6 files (lint_response.go, lint_human.go, parse_nodes.go, format.go, interactive.go).

[v0.15.0] — 2026-03-31

Added

  • Interview mode for human nodes (mode: interview). Runtimes extract questions from upstream agent output and present each as an individual form field with optional suggested answers. New fields: questions_key, answers_key.
  • DIP127: lint warning for invalid human node mode values.
  • DIP128: lint warning when interview mode has a meaningless default value.
  • DIP129: lint warning when interview mode has conflicting choice-style labeled edges.
  • Integration guide updated with interview mode implementation guidance and recommended answer JSON schema.
  • api_design.dip example updated to use interview mode for Q&A collection.
  • interview_loop.dip example: reusable interview subgraph with iterative Q&A. Parameterized by topic and focus areas. LLM generates questions with suggested options, human answers via interview mode, assessor loops until requirements are clear. Grade A, ~$0.92/run.
  • 3 blog posts: Multi-line Prompts Without Escaping (deep dive), Conditional Edges (tutorial), Cost Estimation (tutorial). Hub-and-spokes model with cross-links.
  • Auto-deploy: CI now deploys site/ to GitHub Pages on successful main builds.

Fixed

  • --version / -version flags now work (previously failed with “flag provided but not defined”).
  • Formatter idempotency: subgraph param values with quotes (e.g., "API design") were double-quoted on each format pass. Parser now strips surrounding quotes from param values.

[v0.14.0] — 2026-03-27

Added

  • code_health_check.dip example: self-contained pipeline that audits a Go repo. Gathers context with shell tools, runs vet/staticcheck/tests in parallel, three-model independent review, synthesized report with quality gate and retry loop. 5 test scenarios. Grade A, ~$1/run.

[v0.13.2] — 2026-03-27

Changed

  • Single-source nav: site/_layout/nav.html is the one source of truth. scripts/sync-nav.sh propagates it to all 16 pages with correct prefixes and active states. Pre-commit hook runs it automatically. No more editing nav in 16 files.
  • scripts/gen-changelog-html.sh emits a placeholder nav that sync-nav.sh fills.
  • just sync-nav recipe added.

[v0.13.1] — 2026-03-27

Fixed

  • just install and just build now inject commit hash and build timestamp via ldflags. dippin version shows dev (commit: abc1234, built: 2026-03-27T18:45:10Z) instead of dev (commit: none, built: unknown).

[v0.13.0] — 2026-03-27

Changed

  • Two-tier navigation across all site pages. Top row: Docs, Playground, Blog, GitHub. Bottom row: CLI, Language, Testing, Validation, Analysis, Architecture, Editors, Changelog. Mobile collapses to hamburger with divider-separated groups.

Fixed

  • Mobile nav menu no longer renders as unstyled text on desktop (missing display: none).
  • Blog index only shows the 5 published posts — removed 20 dead links to unwritten articles.
  • Playground content no longer overlaps the nav bar (padding adjusted for two-tier height).
  • Playground now has the floating dots background matching all other pages.
  • Homepage “See all 25 posts” corrected to “All posts”.
  • Section spacing tightened (6rem → 4.5rem padding).
  • Downstream consumer field report response written (.tracker/field-report-response-2026-03-27.md).

[v0.12.0] — 2026-03-27

Added

  • Blog section with 25 planned post cards and topic filtering (Guides, Tutorials, Deep Dives, Reference).
  • 5 blog posts published: Getting Started, Scenario Testing, Migrating from DOT, CI Integration, Editor Setup. Edited for voice, clarity, and inline links.
  • Homepage “From the Blog” section featuring 3 latest posts below the fold.
  • SEO meta tags on all 12 site pages: Open Graph, Twitter Cards, descriptions, canonical URLs. Pages render rich previews when shared.
  • Blog ideas doc (docs/blog-ideas.md) with 25 post synopses, coverage plans, and approach notes.
  • Blog nav link added to all site pages.

[v0.11.2] — 2026-03-27

Fixed

  • Playground: syntax-highlighted editor with transparent textarea over colored <pre> overlay. Tab key inserts 2 spaces.
  • Playground: parse output shows highlighted JSON (keys, strings, booleans, numbers). Format output shows highlighted Dippin. Lint errors display with severity coloring.
  • Playground: WASM race condition — polls for function registration before auto-linting on load. Returns [] not null for zero diagnostics.
  • Site: syntax highlighting CSS selectors changed from pre .hl-* to .hl-* so colors work in playground output div, not just <pre> blocks.
  • Site: JSON blocks inside compare-code divs (like gate.test.json on Testing page) now get highlighted — skip logic checks for existing <span> tags instead of parent class.
  • Site: highlight.js token protection via \x00N\x00 placeholders prevents regex passes from matching inside previously generated <span> class attributes.
  • Site: JSON inside terminal output blocks (e.g. $ dippin --format json test) gets JSON highlighting applied to the embedded body.
  • Site: changelog auto-generated from CHANGELOG.md via scripts/gen-changelog-html.sh. Pre-commit hook runs it when CHANGELOG.md is staged.

[v0.11.1] — 2026-03-27

Fixed

  • Playground: WASM files (dippin.wasm, wasm_exec.js) now deployed to gh-pages so the playground actually loads.
  • Playground: auto-runs lint on WASM load instead of showing a confusing “Ready” message while the Lint button appears active.
  • Site: syntax highlighting (highlight.js) for all code blocks — Dippin, shell, terminal, and diagnostic output.
  • Site: changelog page added at changelog.html with full version history.

[v0.11.0] — 2026-03-27

Added

  • DIP126 lint rule: subgraph ref: file validation — warns when referenced workflow file does not exist on disk.
  • dippin watch command: file watcher that re-runs lint on .dip changes with 200ms debounce. Uses fsnotify.
  • dippin test --coverage flag: edge coverage summary showing which workflow edges were/weren’t traversed by test scenarios.
  • Tree-sitter grammar scaffolding in editors/tree-sitter-dippin/ — grammar.js, external scanner for indentation, highlight queries, and test corpus. Enables proper syntax highlighting in Neovim, Helix, and Zed.
  • WASM playground at site/playground.html — browser-based editor with live parse, lint, and format via WebAssembly. Build with just wasm.
  • gemini-3.1-pro-preview-customtools added to model catalog and pricing tables.
  • 35 diagnostic codes total (was 34).

Fixed

  • CI failures: golangci-lint errcheck on f.Close(), funlen on buildLintExplanations (split into 4 functions), misspell false positive in DIP118 example.
  • Migration parity: consensus_task_parity.dip and semport_thematic.dip model names now match DOT originals (gemini-3.1-pro-preview-customtools).

Changed

  • validator/lint_tool_cmd.go split with //go:build !wasm / wasm tags — bash -n syntax check and exec.LookPath binary check are no-ops in WASM.
  • validator/lint_subgraph.go similarly gated for WASM (no os.Stat).
  • Site mobile CSS improvements: table overflow handling, code word-break, install-cmd sizing.
  • Site nav updated with Playground link across all pages.

Documentation

  • All references updated from 34→35 codes, DIP101–DIP125→DIP101–DIP126 across README, CLAUDE.md, docs/, and site/.
  • docs/validation.md — full entry for DIP126.
  • docs/cli.mdwatch command section, test --coverage flag.
  • docs/editor-setup.md — tree-sitter grammar availability.
  • dippin explain DIP126 — explanation with trigger and fix guidance.
  • mode: labeled documented as not supported in docs/nodes.md.

[v0.10.0] — 2026-03-26

Added

  • DIP123 lint rule: tool command shell syntax errors detected via bash -n.
  • DIP124 lint rule: ${ctx.*} references in tool commands that expand to empty at runtime.
  • DIP125 lint rule (hint): tool command binary not found on PATH (environment-dependent).
  • Brochure site with 8 pages: home, CLI, Language Reference, Testing, Validation, Analysis, Architecture, Editor Setup. Hosted on GitHub Pages.
  • 34 diagnostic codes total (was 31).

Documentation

  • All references updated from 31→34 codes, DIP101–DIP122→DIP101–DIP125 across README, CLAUDE.md, docs/, and site/.
  • docs/validation.md — full entries for DIP123, DIP124, DIP125.
  • dippin explain DIP123/DIP124/DIP125 — explanations with triggers and fix guidance.

[v0.9.0] — 2026-03-25

Fixed

  • preferred_label now works on human gates — scenario key preferred_label (or per-node Gate.preferred_label) matches against edge labels (case-insensitive substring). Previously silently ignored on freeform gates.
  • prompt: blocks now parse on human nodesHumanConfig gained a Prompt field. Multiline prompt blocks work the same as on agent nodes. Formatter round-trips correctly.
  • Tool auto-defaults no longer mask fallback edges — empty-string scenario values ("Node.tool_stdout": "") now suppress the auto-seeded success default, allowing unconditional fallback edges to fire.

Added

  • immediately_after test assertion — assert adjacency in the execution path: "immediately_after": {"NodeX": "NodeY"} checks that NodeY is the very next node after NodeX.
  • branch field for targeted parallel testing"branch": ["WorkerA"] limits which parallel fan-out branches are simulated. Without it, all branches are walked (new default).
  • Simulator walks all parallel branches — parallel fan-out now visits all targets, not just the first. Matches real runtime behavior.
  • Example test suites.test.json files for vulnerability_analyzer, consensus_task, code_quality_sweep, and sprint_exec (20 tests across 4 workflows).
  • Test coverage at 95.7% — up from 85.6%. Six packages at 100%.
  • just cover now excludes untestable files (main.go, cmd_lsp.go) from coverage reports.

Documentation

  • docs/testing.md — added Caveats section (not_visited fragility with loop-breaking), Clearing Defaults section (empty-string technique), immediately_after field documentation.

v0.8.0 — 2026-03-25

Fixed

  • Graph truncation on pipelines with restart edgesbuildAdjacency() included restart (back) edges, creating cycles that prevented Kahn’s algorithm from assigning layers to downstream nodes. All nodes are now rendered. Affects both full and compact modes.
  • Simulator infinite loop on tool-gated loops — pipelines with when ctx.tool_stdout not contains all-done loops would spin to the 500-step limit. New MaxNodeVisits option forces the loop-exit edge after N visits. The test runner sets this to 3 by default.
  • Per-node scenario injection in dippin testNodeName.key=value scenarios now work reliably because the loop-breaking fix allows the simulation to reach the target node.
  • Testrunner accepts empty/invalid schemas silentlyLoadTestFile now rejects .test.json files with zero tests.

Added

  • CLI integration tests — 32 new tests covering 10 previously untested commands (cost, coverage, doctor, optimize, unused, graph, diff, feedback, explain, test). cmd/dippin coverage: 44.9% → 79.3%.
  • Graph tests for parallel and restart-loop fixtures.
  • DIP121 compound condition test — verifies and/or conditions correctly fire per-variable.
  • Unused clean-workflow test — verifies no false positives on linear workflows.
  • just release tag msg recipe for tagging releases.
  • DIP121/DIP122 added to README warnings table; explain, unused, graph, test added to commands table.

Fixed (cosmetic)

  • README stale numbers — diagnostic codes 30→34, DIP120→DIP125, examples 15→17, lint rules 21→25.
  • appendConnector dead branch — identical if/else branches collapsed.

v0.7.0 — 2026-03-25

Added

  • dippin test — scenario test runner for workflow assertions. Define .test.json files alongside .dip workflows with expected status, visited/not-visited nodes, and path ordering. Supports --verbose flag for path tracing and JSON output for CI integration.
  • New package: testrunner/ — loads .test.json suites, runs each case through the simulator with injected scenario values, checks assertions against results.
  • New doc: docs/testing.md — documents the .test.json format and test runner usage.

v0.6.0 — 2026-03-25

Added

  • DIP121 lint rule: condition references variable not produced by source node’s IO.Writes. Skips when writes are empty (advisory) or variable is a reserved runtime key (ctx.outcome, ctx.status, ctx.internal.*, graph.*, params.*).
  • DIP122 lint rule: condition tests value not declared in source tool’s ToolConfig.Outputs. Only fires for tool nodes with explicitly declared outputs.
  • Explanations for DIP121/DIP122 in dippin explain.

v0.5.0 — 2026-03-25

Added

  • dippin explain <DIPxxx> — rich explanations for all 34 diagnostic codes. Shows trigger conditions, fix guidance, and example snippets. Supports text and JSON output.
  • dippin unused <file> — detects dead-branch nodes (reachable from start but no path to exit) and estimates wasted cost per run. Reuses coverage.Analyze() sink detection + cost.Analyze() for cost enrichment.
  • dippin graph [--compact] <file> — terminal-rendered ASCII DAG visualization. Full mode renders box-drawing nodes with connectors; compact mode outputs single-line [A] → [B] → [C] format. JSON mode outputs layer structure.
  • New packages: unused/, graph/, testrunner/
  • New files: validator/explanations.go with Explanation struct for all DIP codes.

v0.4.3 — 2026-03-25

Fixed

  • DIP101 suppressed for mixed routing — when a source node has both unconditional and conditional outgoing edges, the conditional branches are intentional routing. DIP101 no longer fires on their destinations. Covers all four reported patterns: compound inequality conditions, exhaustive set + fallback, mixed unconditional/conditional, and labeled fallback edges.

v0.4.2 — 2026-03-25

Fixed

  • DIP101/DIP102 exhaustive detection now recognizes any complete partition — if all conditional edges from a node test the same variable with equality (2+ values), the conditions are treated as exhaustive. No longer limited to hardcoded {success, fail} pairs. Handles done/more_questions, tasks_remain/all_done, and any custom value set.

v0.4.1 — 2026-03-25

Fixed

  • EBNF grammar audited against parser — added infix negation, tool outputs field, removed undocumented numeric operators (<, >, <=, >= parsed but silently returned false)
  • Docs accuracy — removed state.* namespace (not implemented), removed ctx.preferred_label (not in codebase), added == as = alias, added not contains infix syntax
  • Condition parser — removed <, >, <=, >= from valid operators (never evaluated, silent false was a trap)

Added

  • CHANGELOG.md with retroactive history for all versions
  • docs/CONTRIBUTING.md — documentation accuracy protocol with persona matrix
  • CLAUDE.md — project conventions, gotchas, versioning policy
  • Integration test (TestLintExamples) — lints all examples through real parser
  • “Last verified” dates on model catalog and pricing table
  • Tool outputs field documented in nodes.md and README.md

v0.4.0 — 2026-03-25

Added

  • New commands: dippin cost, dippin coverage, dippin doctor, dippin optimize, dippin diff, dippin feedback, dippin lsp
  • New providers: DeepSeek, xAI (Grok), Mistral, Cohere — model validation and cost estimation
  • DIP116–DIP120 lint rules: compaction threshold, on_resume, stylesheet refs, reasoning_effort, namespace prefix
  • LSP server with diagnostics, hover, go-to-definition, autocomplete, document symbols
  • Condition parser: infix negation syntax (var not contains val)
  • Complementary pair detection: contains X + not contains X recognized as exhaustive
  • New docs: docs/analysis.md, docs/editor-setup.md

Fixed

  • DIP101 false positives — conditions were never parsed into ASTs; Lint() now calls EnsureConditionsParsed() before running checks
  • DIP101 exhaustive suppression works for all three real-world patterns: exhaustive + fallback, exhaustive + extra variables, complementary pairs
  • DIP103 no longer flags contains X / not contains X as overlapping
  • DIP110 exempts start/exit lifecycle nodes from empty prompt warnings
  • Model pricing corrected against official docs (claude-opus-4-6 was $15/$75, actually $5/$25; o3 was $10/$40, actually $2/$8)
  • Model IDs corrected: gemini-3-progemini-3.1-pro-preview, removed nonexistent IDs
  • Gemini pricing added (was missing entirely from cost estimates)

Changed

  • Full documentation rewrite — all docs updated for post-v0.3.0 toolchain
  • Mermaid diagrams use <br> instead of <br/>

v0.3.0 — 2026-03-21

Added

  • dippin cost and dippin coverage commands
  • DIP119 (reasoning_effort validation), DIP120 (namespace prefix)
  • DIP114 extended to parallel branch fidelity

Fixed

  • Scenario-injected values protected from node default overwrite
  • errcheck and staticcheck lint errors in coverage package

Changed

  • Four largest files decomposed into focused modules

v0.2.0 — 2026-03-20

Added

  • dippin check, dippin new commands with 5 scaffold templates
  • DIP113–DIP115 lint rules (retry policy, fidelity, goal gate)
  • base_delay field for retry override
  • Subgraph params, compaction, fidelity degradation, parallel branches, stylesheets
  • dippin version command
  • Justfile for dev workflows
  • GoReleaser + GitHub Actions release pipeline
  • Homebrew tap

Changed

  • All functions reduced to cyclomatic ≤ 5, cognitive ≤ 7

v0.1.0 — 2026-03-19

Added

  • Initial release
  • Parser (indentation-aware lexer + recursive descent)
  • Validator (DIP001–DIP009 structural checks)
  • Linter (DIP101–DIP112 semantic warnings)
  • Formatter (canonical idempotent output)
  • DOT exporter with shape mapping
  • DOT → Dippin migration with parity checker
  • Simulator with JSONL event streaming
  • 15 example workflows including 5 stress tests
  • VS Code extension (syntax highlighting)