Releases: emrecdr/devt
v0.44.0
Nine-commit wave layering four concerns on top of v0.43.0's graphify integration: alignment-drift cleanup, JSON-sidecar contract completion for the highest-traffic markdown-only artifact, memory-layer module split, and post-integration polish for graphify + claude-mem MCP routing. Two silent failure modes closed: validateConsistency was recording NO_STATUS_LINE warnings on every code-review verify-phase advance because code-reviewer.md emits ## Verdict while extractStatus() only matched ## Status; loadGraph silently degraded when graph.json exceeded the 100MB cap with no signal in /devt:forensics. One forensic-preservation gap closed: dispatch-warnings.jsonl was being deleted by state reset despite CLAUDE.md promising it survives. Plus the claude-mem harvest pre-step was routing to a tool that errors out for the canonical (worker-mode) install. Twelve new smoke gates added across the wave. Smoke: 454 passed, 0 failed (was 438 at the start of the wave).
Added — alignment cleanup (a9ecfdf)
- Documentation-discipline smoke gate in
scripts/smoke-test.sh: scansagents/,workflows/,skills/, anddocs/for devt-internal version refs (v0.X.Ypatterns andsince v[0-9]markers), excludingCHANGELOG.mdanddocs/superpowers/plans/as legitimate historical homes. Catches the class of drift where version refs leak into source-of-truth surfaces despite the existing rule. RESET_EXEMPTpreservation smoke gate: state-reset functional test asserts bothpreflight-denies.jsonlanddispatch-warnings.jsonlsurvive a reset, locking in the forensic-preservation contract.
Fixed — alignment cleanup (a9ecfdf)
bin/modules/state.cjs::RESET_EXEMPTnow preservesdispatch-warnings.jsonlalongsidepreflight-denies.jsonl. The forensic-preservation claim for/devt:forensicsalready promised this; the implementation now honours it.agents/retro.mdfrontmatter addsskills: []for contract consistency withagents/io-contracts.yaml(whereretro.frontmatter_skillsalready declared[]).docs/MEMORY.md: removed two devt-internal version refs (header note and multi-root prose), addedlessonto thedoc_typeenum comment, deleted the empty## Version Notessection header.
Added — review.json JSON sidecar (8dff301)
review.jsonsidecar completes the JSON-sidecar contract for the highest-traffic markdown-only artifact. Joins the existingimpl-summary.json,test-summary.json, andverification.json. Schema split mirrorsimpl-summary:status ∈ {DONE, BLOCKED}for workflow routing,verdict ∈ {APPROVED, APPROVED_WITH_NOTES, NEEDS_WORK}for the review outcome.- Wired end-to-end:
bin/modules/state.cjs(JSON_SIDECAR_SCHEMAS entry + SIDECAR_FOR_MARKDOWN mapping + review.md removed from ARTIFACT_SCHEMA),agents/io-contracts.yaml(code-reviewer.outputs.sidecar = review.json),agents/code-reviewer.md(stub-first protocol writingreview.jsonfirst, finalizing with status+verdict+agent+score+counts+timestamp),workflows/code-review.md(artifact pre-gate requires both.mdand.json),workflows/next.md(three review-routing branches nowstate read-sidecar review.jsoninstead of text-matching the markdown). - Seven smoke gates covering registry presence, mapping presence, ARTIFACT_SCHEMA absence, io-contracts declaration, agent emission, workflow consumption, and end-to-end schema validation of all three flags.
Fixed — review.json JSON sidecar (8dff301)
- Silent
extractStatuswarnings onreview.mdeliminated as side effect. Before the sidecar wire,validateConsistencyranextractStatus()onreview.md, which only matched## Statusheadings — but the code-reviewer template emits## Verdict. Every code-review verify-phase advance silently persistedNO_STATUS_LINEtoworkflow.yaml::validation_warnings. Sidecar routing viaSIDECAR_FOR_MARKDOWNbypassesextractStatusentirely. - Generalized the ARTIFACT_SCHEMA drift gate to recognize both "Status field is one of" and "Verdict field is one of" agent doc patterns, with the sidecar field-kind resolved from whichever matched. Prevents the same class of drift across all sidecar-routed artifacts going forward.
Added — polish pass (867c005)
- docs/COMMANDS.md scope_trust + graph_stats coverage added in the preflight section. The JSON sidecar fields (
scope_hint,scope_trust,graph_stats) were documented in CLAUDE.md (dev-facing) but absent from COMMANDS.md (user-facing). Three new paragraphs covering: sidecar shape,<scope_trust>dispatch signal semantics (low-confidence treatment whentrust ∈ {sparse, empty}orlag_commits > 10), andgraph_statssource. - End-to-end smoke gate for review.json sidecar routing: constructs the exact scenario the silent bug occurred in (review.md without
## Statusheading, valid review.json) and assertsstate validate's JSON output contains noreview.md no_status_linemismatch. Exercises the SIDECAR_FOR_MARKDOWN code path invalidateConsistency, not just wiring presence.
Fixed — polish pass (867c005)
agents/researcher.mdstatus pattern: template wroteStatus: DONE | ...as plain text under## Confidenceheading.extractStatus()only matches## Statusheadings, so the status line was unparseable.research.mdis inARTIFACT_SCHEMAand consumed byworkflows/dev-workflow.md; misalignment was latent becauseresearchis not inPHASE_ARTIFACT_MAP, but adding research as a routed phase would have re-introduced the same silent-warning class fixed forreview.md. Promoted to a proper## Statusheading with block-form value.
Changed — memory module split (af90cf0)
bin/modules/memory.cjsextracted into three files. The 1884-line module was well past the 700-line informal threshold. Two clean extraction boundaries identified after dependency mapping:bin/modules/memory-graph.cjs(135 lines): graph traversal over thelinkstable —getLinks,getSubgraphTriples,getBacklinks,findOrphans,findStaleLinks. Only needs the DB handle, obtained viawithDb.bin/modules/memory-bundle.cjs(251 lines): portable JSON bundle export/import —resolveExportPath,resolveImportPath,readDocFile,exportBundle,importBundle. Uses parser/validation helpers from the core module.bin/modules/memory.cjs(1576 lines, was 1884): everything else — paths, frontmatter parsing/validation, DB lifecycle, queries,upsertDoc, symbol validation, CLI dispatcher.
- Lazy-require pattern breaks the load-time circular dep: sub-modules
require("./memory.cjs")inside function bodies, so memory.cjs's top-level require of the sub-modules resolves cleanly. Public API unchanged via re-exports; existing consumers (devt-tools.cjs,devt-memory-mcp.cjs,discovery.cjs,preflight.cjs,health.cjs) need zero call-site changes. - Four helpers now formally exported from
memory.cjsto support the sibling-module contract:withDb,findProjectRoot,parseYamlSubset,serializeFrontmatter. These are internal-but-shared utilities that sub-modules need; the export is the contract that lets them stay in their natural homes instead of being moved to a shared base module. - Net: -308 lines from
memory.cjs(-16%), +386 lines across two sibling modules, +78 lines total codebase (import boilerplate cost — acceptable tradeoff for module health).
Fixed — claude-mem MCP harvest routing (26033b9)
claude-memharvest pre-step in three workflows (dev-workflow.md,quick-implement.md,lesson-extraction.md) was routing toobservation_search, which requiresCLAUDE_MEM_RUNTIME=server-betaand silently no-ops in the canonical worker-mode install. Re-targeted tosearch— the worker-mode equivalent exposed identically by both runtimes. Parsing instructions refined to extract only numeric-ID rows from the markdown index (thesearchtool returns observations + sessions + prompts under the same result count) and to map the emoji column (⚖️ → decision, 🔵 → discovery) toobs_type, dropping session-telemetry types that don't promote.- Negative smoke gate added; existing positive harvest gate retargeted to the new tool name.
Added — graphify polish (6ec5cf3 / 7d1f080 / 667f50b)
loadGraphsize-cap forensic record (6ec5cf3): whengraph.jsonexceeds the 100MB cap, appends one JSONL record to.devt/state/preflight-denies.jsonlwithsource="graph_loader", path, size, cap, and ISO timestamp. Per-process dedupe via a path set so one workflow that calls multiple graphify wrappers writes one record, not N. Skipping the fullreadFileSyncon oversize files is a side benefit. Two new fixture-test assertions.graphify.godNodes()public function (7d1f080):discovery.cjs::harvestGraphifyGodNodesandpreflight.cjs's Cross-Cutting Concerns renderer were both reading god-nodes by regex-scrapinggraphify-out/GRAPH_REPORT.md. That path lags the actual graph becausegraphify updaterewritesgraph.jsonbut leavesGRAPH_REPORT.mdalone unlesscluster-onlyalso runs. The local_topByDegree()already computes god-nodes fromgraph.jsonadjacency with matching filters; wrapping it as a publicgodNodes()lets both consumers read live data. CLI: newgraphify god-nodes [--limit=N]subcommand. Five new fixture-test assertions.docs/graphify-helpers/SKILL.mdMCP table aligned to v0.8.11 (667f50b): upstream graphify v0.8.11 ships 10 MCP tools; the skill's table listed 7 (pre-v0.8.8). Now lists all 10 (query_graph,get_node,get_neighbors,shortest_path,god_nodes,get_community,graph_stats,get_pr_impact,list_prs,triage_prs). Decision-tree step "Probegraphify --help-> exit 0?" was dead text (devt readsgraph.jsondirectly in-process); consolidated tographify statuswhich combines enabled-flag +graph.jsonexistence in one call.
Fixed — setup reinit reconci...
v0.43.0
Eleven-commit wave addressing structural integration drift against upstream graphify and claude-mem. Three silent failure modes closed: graphify wrappers shelling out to subcommands with --json flags that don't exist upstream; code-reviewer never consuming mcp__graphify__get_pr_impact during PR reviews; claude-mem mcp --db invocation invalid against claude-mem v13 (produced "Unknown IDE: --db" error every Claude Code session). Two new agent signals added end-to-end: graph trust verdict + freshness lag in the preflight sidecar, with workflow caching + 7 agent body paragraphs implementing low-confidence treatment on sparse / stale graphs. One budget-protection mechanism: code-reviewer applies a community filter for large PR reviews when pr-impact.md is present, deferring out-of-community files to a follow-up dispatch. Smoke: 438 passed, 0 failed (was 427 at the start of the wave). Architectural through-line: devt's Node code never reaches into upstream MCP/CLI directly — file artifacts for Node, orchestrator-mediated MCP for agents, deletion when a path is unrecoverable.
Graphify wrapper migration to direct graph.json reads (bin/modules/graphify.cjs). Every structured-query call (queryGraph, getNode, getNeighbors, shortestPath, blastRadius) previously shelled out to graphify query <text> --json / graphify query <sym> --neighbors --direction=... --depth=... — flags that don't exist in upstream's CLI surface. Verified against safishamsi/graphify upstream source: the CLI accepts only --dfs/--budget/--context/--graph for query, and the MCP server's tool handlers return types.TextContent(type="text", text=str) blobs, not structured objects. Every call was silently triggering the grep fallback via safeJsonParse failure on non-JSON output. The migration replaces all 5 functions with pure-Node algorithms over the deterministic graphify-out/graph.json NetworkX node-link artifact ({nodes, links/edges, hyperedges}). One in-process tree walk replaces 2N subprocess spawns per blastRadius call. status() decoupled from binary presence — state === "ready" now depends only on graphify.enabled + graph.json exists, so projects with a checked-in or CI-built graph work without graphify on PATH. Smoke: 428 passed, 0 failed (+1 gate over the prior 427 baseline).
Added (Phase A)
- graph.json in-process reader (
bin/modules/graphify.cjs::loadGraph): memoized by(path, mtimeMs)so repeated calls within a workflow turn parse the file once. Builds{out, inc, nodeMap}adjacency maps for O(1) neighbor lookup. Handles bothlinks(modern NetworkXnode_link_data(G, edges="links")) andedges(legacy NetworkX) field names. Caps file size at 100 MB viasafeJsonParse. - Pure-Node algorithms for the 5 structured-query functions: substring/case-insensitive label+id resolution (
_resolveOne,_resolveMany), direction-aware BFS (_bfswithdirection: in|out|both, configurabledepth), and directed shortest-path (BFS along outgoing edges only).blastRadiusnow walks depth-2 incoming in one tree traversal per symbol instead of issuing two subprocess calls per symbol. scripts/test-graphify.cjs(new, 16 assertions): fixture-based test runner matching thescripts/test-locking.cjsconvention. Builds a 4-node / 4-edge NetworkX-formatgraph.jsonin a temp project and exercises every public function:status(ready/graph_missing/disabled paths) /query(exact/substring/empty) /neighbors(in/out/both/depth=2) /path(connected/no-route) /blast-radius(shape contract + direct dependent count) / legacyedgesfield name compatibility / graph-missing degradation. Wired intoscripts/smoke-test.shso CI runs it as part of the standard gate.
Changed (Phase A)
graphify.cjs::status()no longer gates on binary presence. Previously requiredgraphify --helpto exit 0 even though devt's read path never invokes the binary. State enum collapsed to"ready" | "disabled" | "graph_missing"(removed"binary_missing"). The graphify binary is still required to generategraph.jsonviagraphify update ., but devt's consumption is now binary-independent.probeBinaryis kept forsetup.cjs's MCP-server registration logic — that path legitimately needs to know whether the binary is installed.
Removed (Phase A)
callGraphifysubprocess wrapper. The function passed--jsonand other flags that don't exist in upstream's CLI argparse — every invocation either returnedexit 2("unrecognized arguments") or non-JSON text that failedsafeJsonParse. All structured-query operations now readgraph.jsondirectly. The export is gone frommodule.exports; no other module imported it.
Added (Phase B-1 — PR-impact MCP wiring)
- Orchestrator fetches
mcp__graphify__get_pr_impactduring code-review context_init (workflows/code-review.md): whenREVIEW_SCOPEmentions a PR number ("PR #N", "pull request N", or a PR arg), the orchestrator (main session, which has the project's MCP allowlist) calls the tool once and Writes the response verbatim to.devt/state/pr-impact.md. Skip-silently semantics — no PR number / no graphify MCP / call errors all proceed without the file, and the agent falls back to scope_hint + raw file list. The orchestrator pattern is necessary because the code-reviewer agent's allowlist isRead, Bash, Glob, Grep(no MCP), so the main session does the MCP fetch and the agent consumes the persisted file. - Code-reviewer agent Reads
.devt/state/pr-impact.mdwhen present (agents/code-reviewer.md::context_loading): instructions to prioritize files in affected communities ahead of unrelated files in the scope list, and weight finding severity by structural impact rather than diff size alone. Graphify's structured map (files changed, communities affected, blast radius) is treated as authoritative for "what does this PR actually touch in the graph". - ADR Compliance section gains a PR-impact item (both
workflows/code-review.mdandagents/code-reviewer.md): when reviewing a PR, the structured impact map is consulted alongsidememory affects/memory rejected-keywords/get_neighborsso reviewers can weight findings by graph community rather than file count. - 2 new smoke-test gates that pin the wiring: workflow file references both
get_pr_impactandpr-impact.md; agent file references both. Prevents silent regression if a future audit strips the guidance.
Notes (Phase B-1)
- The companion MCP tools
mcp__graphify__list_prsandmcp__graphify__triage_prsexist in upstream but apply to PR-selection ("which PR should I review next?"), not per-review work. They are deliberately not wired into this workflow — review-selection is a separate concern handled outside/devt:review. - The fetch step does not write any tool name into Bash. The MCP call is a tool-use directive to the orchestrator (natural-language instruction), not a
mcp callshell command — devt's Node code remains MCP-client-free per the architectural invariant from Phase A.
Removed (Phase C-1 — broken claude-mem CLI integration)
discovery.cjs::harvestClaudeMem+claudeMemAvailableremoved entirely (~60 LOC). Modern claude-mem (v13.x) does not expose aqueryCLI command — its surface isstatus / search <query> / mcp <ide> / install / repair / start / stop / restart / server / worker / adopt / cleanup / transcript, all positional with no--tagsor--jsonflags. devt's invocationclaude-mem query --tags decision,discovery --jsonwas returning exit 2 ("Unknown command") on every modern install. Source #1 of the discovery harvest has been silently producing[]for any user past the v13 upgrade.- Per-project
.mcp.jsonscaffolding for claude-mem removed fromsetup.cjs::scaffoldProject. The previous entry{command: "claude-mem", args: ["mcp", "--db", ".claude-mem/mem.db"]}is doubly wrong:claude-mem mcpis an IDE-installer subcommand that takes an IDE identifier (claude-code,cursor, etc.) — not flags. The scaffolded entry triggered an "Unknown IDE: --db" error on every Claude Code session for users with both devt and claude-mem installed. Modern claude-mem self-registers as a Claude Code plugin (its package.json declaresplugin/.mcp.json+plugin/.claude-plugin), so per-project registration is also redundant. .claude-mem/mem.dbentry removed from thesetup.cjs.gitignorescaffold. Modern claude-mem uses~/.claude-mem/(per-user) for its database; the per-project path is upstream-obsolete.discovery claude-mem-statusCLI subcommand removed frombin/devt-tools.cjs+bin/modules/discovery.cjs::rundispatcher. It probed a capability that has no consumer.- Documentation cleanup: 24 references to "claude-mem ⚖️/🔵 harvest" / "claude-mem absent" / "claude-mem timeout" across
workflows/dev-workflow.md,workflows/quick-implement.md,workflows/lesson-extraction.md,workflows/memory-promote.md,workflows/uninstall.md,agents/curator.md, and the JSDoc/comment sites inbin/modules/discovery.cjs+bin/modules/memory.cjs. Replaced with accurate descriptions of the remaining 3 sources (#KNOWLEDGE-CANDIDATEscratchpad tags,.devt/state/decisions.mdDEC-xxx entries, Graphify god-nodes when available). - Stale smoke gate ("discovery claude-mem-status returns boolean") removed. It exercised the deleted CLI subcommand.
Added (Phase C-1)
- 2 smoke gates that pin the removal:
discovery.cjsMUST NOTspawnSync("claude-mem"...);setup.cjsMUST NOT scaffold aclaude-memMCP entry with--dbor"mcp"args. Prevents future regressions where someone re-adds the broken shellout. - An explanatory comment in
setup.cjsdocumenting why the per-project claude-mem entry is intentionally absent: "claude-mem v13+ self-registers as a Claude Code plugin under ~/.claude/plugins/ — no per-project entry needed."
N...
v0.42.0
Tier-aware skill preloading + Agent IO Contracts registry + inline-loading coverage completion across write-agents + memory_signal cache hoist + over-cap skill description trim. The coordinated wave extends the existing inline-prefix pattern (<governing_rules>, <guardrails_inline>) from the original 3 read-only agents to the 3 write-agents (programmer/tester/architect) that re-read CLAUDE.md + rule files on every retry iteration; adds rubric body inlining for the verifier; collapses 7 per-dispatch memory query --signal CLI calls into a single workflow-start cache; trims 8 SKILL.md descriptions back under the 1024-char soft cap. Validated via node bin/devt-tools.cjs token-report showing aggregate cache_hit_rate = 93.05% across 5 recent sessions — empirical evidence that the existing prefix patterns were already hitting the prompt cache, so this wave focused on closing the coverage gaps the original inline-loading wave intentionally restricted to read-only agents. Smoke: 401 passed, 0 failed.
Added
- Tier buckets in
skill-index.yaml. Per-agent skill assignments now split across three sibling keys at the existing indent-4 level —skills(always loaded),skills_standard(added whenstate.tierisSTANDARDorCOMPLEX),skills_complex(added only atCOMPLEX). Heavy specialist skills (strategic-analysis~8K chars,complexity-assessment~10K,autoskill~12K) demoted out of thealwaysbucket so SIMPLE/TRIVIAL dispatches skip them. The hand-rolled YAML parser atinit.cjs::parseSkillIndexalready accepted arbitrary indent-4 keys — no parser change required. User overrides at.devt/config.json::agent_skills.<agent>keep accepting a flat array (= always loaded, ignores tier) so existing project configs don't break. bin/modules/init.cjs::mergeSkillsForTier(NEW): merges the three buckets per agent against a tier (TRIVIAL/SIMPLE/STANDARD/COMPLEX/null), normalizes case, dedupes. Null/unknown tier returns the full union for backward-compatible default behavior.resolveSkillsgained atierparameter wired throughinitWorkflow; the call site seeds tier fromstate.tier(set bycomplexity-assessmentonce it runs) or falls back todetectTier(task)so the very first dispatch in a fresh workflow still gets tier-aware loading. The init payload now surfaces the resolved tier at the top level (tier: "trivial"|"simple"|"standard"|"complex") for transparency.agents/io-contracts.yaml(NEW): single source of truth declaring per-agentfrontmatter_skills,index_buckets,outputs.{primary,sidecar}, andinputs.context_blocks. Currently 10 dev agents covered. Three smoke-test gates assert no drift against (a)agents/<name>.mdfrontmatterskills:, (b) declared sidecars exist instate.cjs::JSON_SIDECAR_SCHEMAS, (c) every contracted agent has a backing.mdfile. The class of bug it catches:memory-pre-flighthad been preloaded by 9 agents via frontmatter for several releases but was missing fromskill-index.yaml— the kind of three-surface drift that's silently corrosive until something snaps.memory-pre-flightadded toskill-index.yamlfor the 9 dev agents that already preload it via frontmatter (programmer/tester/code-reviewer/docs-writer/architect/verifier/researcher/debugger — plus devt-coordinator already had it). Closes the registry-vs-reality gap that the new contracts gate would have failed on.graphify-helpersadded to architect'sskillsbucket to match its frontmatter (architect was the second drift case).- Three new smoke-test gates under
== Tier-aware skill resolution ==and== Agent IO Contracts registry drift ==: empirical verification that a typo-style task seedstier=trivialand prunes complex-tier skills from the programmer's resolved set; a refactor-style task seedstier=complexand loads the full union; the io-contracts.yaml file agrees with all three drift surfaces. bin/modules/init.cjs::loadInlineRubrics(NEW): mirrorsloadInlineGuardrailsfor the per-workflow-type pinned rubric files (references/rubrics/<filename>resolved via the same three-layer order asgrader.cjs::resolveRubricPath— absolute → project-local.devt/rubrics/→ plugin defaults). 32 KB cap. Surfaced at top-levelinline_rubricsin the init payload as a{workflow_type: content}map. Verifier dispatches indev-workflow.mdandcode-review.mdnow embed<rubric_content>{inline_rubrics.dev}/{inline_rubrics.code_review}alongside the existing<rubric_path>block — agent body instructs prefer-inline-over-path-read.<governing_rules>block extended to programmer + tester + architect dispatches. Original wave covered the 3 read-only agents (code-reviewer / verifier / researcher); this completes the coverage to write-agents that re-read CLAUDE.md + 1-3 rule files on every retry iteration. Per-agent sub-tag sets vary based on which files each agent actually needs: programmer (claude_md + coding_standards + architecture + quality_gates), tester (claude_md + quality_gates + testing_patterns), architect (claude_md + architecture). All three agent bodies updated with the prefer-inline instruction listing exactly the sub-tags they accept.<guardrails_inline>block extended to tester + architect. Tester preloads onlygolden-rules.md; architect preloadsgolden-rules.md + engineering-principles.md. Programmer already had the full 3-file inline block from the original wave.quick-implement.mdprogrammer + tester dispatches now carry inline blocks. Was the most cache-unfriendly dispatcher in the codebase pre-wave — had zero inline blocks despite being the "lightweight fast path".- Parallel-bash pairing for Step 2 (scan) + Step 2.5 (regression_baseline) in
dev-workflow.md. New<!-- parallel-bash: ... -->marker comment documents the pattern (mirrors the existing<!-- parallel-dispatch: researcher + architect -->marker for Task subagent parallelism). The two steps share no state (distinct artifacts, no overlappingstate updatekeys), so whenregression_baselinewould run a slow test suite the orchestrator can launch it withrun_in_background=trueand proceed toscanin the foreground. Wall-clock savings up to the full test-suite duration on projects with slow tests.
Changed
- Skill preload behavior is tier-conditional from dispatch #1. Previously every dispatch loaded the full per-agent skill union regardless of complexity. Now
init.cjsseeds tier viadetectTier(task)(heuristic; refined bycomplexity-assessmentonce the workflow runs). Concrete effect: a trivial typo fix gives the programmer 3 preloaded skills (codebase-scan, scratchpad, memory-pre-flight) instead of the prior 6+, shrinking the per-dispatch prefix by ~28K chars. The full union still loads for COMPLEX-tier work — no regression for non-trivial flows. memory_signalcomputation hoisted from per-dispatch CLI calls to workflow context_init cache acrossdev-workflow.md,quick-implement.md, andcode-review.md. The samememory query "<task>" --signal=3 --json-compactaggregate was previously computed 7 times across the 3 workflows (3 dispatches in dev: programmer/code-reviewer/verifier; 2 in quick-implement: programmer/code-reviewer; 2 in code-review: code-reviewer/verifier). Now computed once at context_init, persisted toworkflow.yaml::memory_signal_json, and read back viastate read | jq -r '.memory_signal_json'in each dispatch's orchestrator-prep block. Saves up to 6 subprocess calls per workflow + makes the<memory_signal>block byte-stable across iterations (no risk of mid-workflow index-mutation producing different ordering across dispatches).- 8 over-cap skill descriptions trimmed back under the 1024-char soft cap. Was 1030-1233 chars (pre-folded-scalar parser fix masked 4 of them); now 740-900 chars. Trimmed redundant trigger-phrase repetition while preserving discoverability triggers and scope-boundary statements. Each preload-injected description appears in agent system prompts on every dispatch, so even small per-skill trims compound. Skills affected: lesson-extraction (1233→825), autoskill (1131→743), verification-patterns (1076→
900), architecture-health-scanner (1049→777), code-review-guide (1049→900), memory-curation (1049→~900), council (1042→844), strategic-analysis (1030→740). - Agent body context-loading instructions updated for programmer / tester / architect / verifier. The numbered "Read X" steps now consolidate the inline-prefer language into the load step itself, listing exactly which
<governing_rules>/<guardrails_inline>/<rubric_content>sub-tags each agent recognizes. Agents fall back to disk Reads only when the inline block is absent or a specific sub-tag is empty. regression_baselineadded to the "Valid phases" enumeration indev-workflow.md(3 occurrences: --to validation, --only validation, error message template). Previously/devt:workflow --to regression_baselinewould reject as invalid even though the step is wired into the workflow.status.mdrouting entry added forphase=regression_baseline, sibling of the existingphase=scanrow. Previously/devt:statuswould show a blank suggestion line for a workflow stopped at the baseline phase.
Fixed
scripts/smoke-test.shAgent IO Contracts gate ROOT env propagation bug.ROOT="$(cd ...)"was set but neverexported; the gate'snode -e "..."subprocess sawprocess.env.ROOT === undefined, crashed withTypeError [ERR_INVALID_ARG_TYPE]: path argument must be of type stringon the firstpath.join(root, ...)call, and silently aborted the entire smoke test mid-run (at 321/401 passes, no FAIL line emitted because the abort happened before anyfail()call). Two months of green-looking smoke runs were actually hiding a hard-aborting check. Fixed by prefixing the node call withROOT="$ROOT"to scope-pass the var without mutating ...
v0.41.0
Sidecar migration wave + deterministic pre-verifier gate. Test-summary joins impl-summary and verification as a sidecar-routed artifact; impl-summary gains structured gates.{lint,typecheck,test} fields capturing the programmer's quality-gate execution; a new zero-dep grader.cjs runs as a pre-verifier gate that short-circuits the LLM verifier on red-test cycles (saves ~5–15K input tokens per failed iteration, up to ~45K per 3-iteration cycle). The grader gate's routing logic distinguishes three envelope shapes — I/O failures (sidecar missing/malformed/non-object, rubric missing, rubric ## Deterministic Gates JSON malformed, path-traversal in config) route to BLOCKED so the programmer isn't retried on something they can't fix; constraint violations route to RETRY/PRUNE under the verify_iteration cap; greens dispatch the LLM verifier. The two-call merge precedence is documented explicitly: strictest outcome wins (any ok:false → BLOCKED). Project-local rubric overrides land at .devt/rubrics/<file> and are picked up before plugin defaults; relative paths are scoped to their trusted root, absolute paths bypass the check (operator opt-in). Pre-flight deny messages now carry an explicit recovery template so agents without the memory-pre-flight skill can recover from the deny output alone. Skill frontmatter is now structurally validated at smoke time per Anthropic's official Skills guide. Smoke: 398 passed, 0 failed.
Added
test-summary.jsonsidecar registered instate.cjs::JSON_SIDECAR_SCHEMAS(status enum mirrors the prior markdown ARTIFACT_SCHEMA, verdict enum mirrors impl-summary's{PASS, FAIL, INDETERMINATE}, agent gated totester). The tester now emits both.md(human review) and.json(workflow routing) at gate time.SIDECAR_FOR_MARKDOWNmapstest-summary.md → test-summary.json;validateConsistency()reads status through the sidecar for this artifact. JSON shape includestests.{added,passed,failed,skipped}_count,test_files[],failures[], andconcerns[]— the count fields feed the new deterministic grader directly.workflows/dev-workflow.mdandquick-implement.mdupdated to read status viastate read-sidecar test-summary.jsoninstead of grepping the markdown's## Statusheader.impl-summary.json::gatesschema extends the programmer sidecar with structured quality-gate execution fields:gates.lint.{ran, passed, errors, warnings},gates.typecheck.{ran, passed, errors},gates.test.{ran, passed, passed_count, failed_count, skipped_count}. Converts "did the programmer run tests" from prose-in-the-markdown into machine-readable fields the deterministic grader inspects directly. Existingverdict/status/requirements_*fields unchanged.bin/modules/grader.cjs(NEW, zero-dep stdlib only): extracts the## Deterministic GatesJSON block from a rubric markdown, walks the constraint tree against a sidecar's parsed JSON, returns{pass, gate_failures: [{field, expected, got}]}. Constraint leaves: scalar (equality), array (oneOf). Nested objects recurse with a dotted field path. CLI:node bin/devt-tools.cjs grade <workflow_type> <sidecar.json>(exit 0 on pass, 1 on fail). Rubrics without a Deterministic Gates section short-circuit topass:true(no enforcement).references/rubrics/dev.v1.mdDeterministic Gates section declares the dev-workflow constraints:test-summary.json.verdict = "PASS"+tests.failed_count = 0;impl-summary.json.verdict = "PASS"+gates.{lint,typecheck,test}.{ran,passed} = true. Projects override per-project in.devt/config.json::rubrics.devby pointing at a customized rubric file.- Pre-verifier gate wired into
workflows/dev-workflow.md— runs the grader against test-summary + impl-summary BEFORE the LLM verifier Task dispatch. Three-way envelope routing:{ok:false}→ BLOCKED (I/O failure, not retryable);{ok:true, pass:false}→ RETRY/PRUNE underverify_iterationcap;{ok:true, pass:true}→ LLM verifier dispatches. On constraint-violationpass:false, participates in the sameverify_iterationcounter the LLM verifier path uses: underworkflow.max_iterationscap (default 3) routes to programmer re-dispatch withgate_failuresas<review_feedback>; at cap routes to PRUNE withgate_failureswritten to scratchpad andstatus=DONE_WITH_CONCERNS. Existence pre-gate extended to check JSON sidecars alongside the markdown artifacts — missing sidecars surface as BLOCKED early instead of through a generic grader I/O error. Skips the LLM verifier entirely on red-test cycles. Verifier's job under deterministic-gating narrows to semantic verification — did the implementation solve the task? — rather than re-grading test results the grader already proved. - Skill frontmatter smoke gate per Anthropic's "Complete Guide to Building Skills for Claude" (2026). Hard-fails on structural rules that would break Claude's skill loader: SKILL.md case-sensitive presence, no
README.mdinside skill folders, YAML frontmatter present,name= folder name in kebab-case, noclaude/anthropicreserved name prefix, no XML angle brackets in frontmatter (security: frontmatter loads into Claude's system prompt). Soft-warns (informational, does not fail) on description >1024 chars or body >5000 words — the PDF lists these as guidelines, not loader requirements. Current state: 12 of 16 devt skills fully clean; 4 surface as soft-warn (architecture-health-scanner / autoskill / lesson-extraction / memory-curation — all are slightly over the 1024-char soft cap because they carry rich trigger-phrase lists, which the PDF also recommends for reliable triggering). Drift prevention surface for future skill additions.
Changed
impl-summarygate-check now routes through the JSON sidecar.workflows/dev-workflow.mdandworkflows/quick-implement.mdpreviously instructed the orchestrator to "Read.devt/state/impl-summary.mdand check status", butimpl-summary.mdhas carried no## Statusheader since v0.33.0 (sidecar-only routing contract). The instruction worked anyway via implicit Claude adaptation, but the documented routing was stale. Migrated to explicitstate read-sidecar impl-summary.json— same pattern Phase 1 established fortest-summary. All 3 sidecar-covered artifacts (impl-summary, test-summary, verification) now route uniformly through the CLI helper.
Fixed
- Pre-flight deny message includes explicit recovery template.
hooks/pre-flight-guard.shreplaces the single-paragraph reason with action-led multi-line output: leads with the literalPREFLIGHT <ISO-8601-timestamp> edit <path> :: <governing-IDs>template + a single-wordungovernedfallback keyword. Agents that haven't preloadeddevt:memory-pre-flightcan recover from the deny output alone instead of looping on the bare "missing PREFLIGHT line" diagnosis (root cause of the 9-deny stuck pattern surfaced in/devt:statusfield reports). The deny-recordreasonfield inpreflight-denies.jsonlis unchanged (terse for log scanning); only the user-facing message is enriched. Smoke gate added asserting the deny stdout contains the literal template substrings. - Grader propagates I/O errors as
ok:false, not silently aspass:false.bin/modules/grader.cjs::runpreviously emitted{ok:true, pass:false}when the rubric file was missing on disk, masking an operator-level problem as a constraint violation that would re-dispatch the programmer in an infinite loop (bounded bymax_iterations, but burning 3 dispatches before PRUNE). Now propagates theerrorfield fromgradeArtifactas the top-levelok:falseenvelope. Sidecar-missing/malformed already returnedok:false; this fix completes the symmetry — all I/O failures from the grader route to BLOCKED. CLAUDE.md "Deterministic pre-verifier gate" entry expanded to document the three envelope shapes + the custom-agent / no-test-runner friction with copy-pasteable rubric override example. - Rubric path resolution supports project-local overrides.
grader.cjs::resolveRubricPathpreviously hardcodedpath.join(PLUGIN_ROOT, "references", "rubrics", file), which meantpath.isAbsolutepaths were silently mangled and../escapes resolved inside the plugin tree — the documented escape hatch was non-functional. Now uses three-layer resolution: (1) absolute paths in config → use directly, (2) project-local<projectRoot>/.devt/rubrics/<file>if it exists → that, (3) plugin default fallback. Users hitting the gates friction can drop a lenient rubric at.devt/rubrics/dev-lenient.mdand reference it by name in.devt/config.json::rubrics.dev. CLAUDE.md updated with the corrected mechanics. - Malformed
## Deterministic GatesJSON surfaces asok:false, not silent pass.extractDeterministicGatespreviously returnednullon both "section missing" (by design — no enforcement) AND "JSON malformed" (silent bug — gate enforcement disabled with zero operator visibility). Now distinguishes the two: missing section still returnsnull(silent pass:true by design), but missing fence / malformed JSON / non-object root returns{error: "..."}whichgradeArtifactpropagates aspass:false, errorandrunlifts took:false. Operator edits that break the rubric now fail loud, not silent. - Two-call merge precedence documented explicitly in workflow text.
workflows/dev-workflow.mdverify step now states the strictest-outcome-wins rule for combining GRADE_TS + GRADE_IS routing:ok:false(BLOCKED) >pass:false(RETRY/PRUNE) >pass:true(proceed). Without this, Claude could misroute when the two calls return different envelope shapes (e.g. oneok:false, otherok:true, pass:true). - Non-object sidecar payloads surface as ok:false.
state.cjs::readSidecarpreviously crashed withTypeError: Cannot read properties of null (reading 'status')when a sidecar file contained literalnull, a JSON array, or a scalar — the ...
v0.40.0
Graphify Cross-Cutting Concerns + god-node candidate seeding + CI hardening. The Pre-Flight Brief and discovery harvest now read graphify-out/GRAPH_REPORT.md to surface structural couplings before changes start and to seed the underused CON-* tier with high-fanin concept candidates. Smoke: 383 passed, 0 failed.
Added
- Pre-Flight Brief absorbs
GRAPH_REPORT.mdsections.bin/modules/graphify.cjs::parseReportSections(reportPath)is a 4 MB-capped markdown header parser that pulls God Nodes, Surprising Connections, and Knowledge Gaps out of graphify's report.bin/modules/preflight.cjs::generatecalls it once per Brief andrenderBriefemits a new## Cross-Cutting Concerns (graphify)section between Blast Radius and Recommendations — filtered to entries whose symbols overlap the topic (case-insensitive substring, ≥3 chars), capped at 5 god-nodes and 5 surprising connections. Section is omitted entirely when graphify isn't ready, the report is missing, or no entries overlap — Brief layout stays byte-stable for non-graphify projects. - Discovery seeds curator concept candidates from graphify god-nodes.
bin/modules/discovery.cjs::harvestGraphifyGodNodes()reads the sameparseReportSectionsoutput, strips trailing parens, skips private/module-shaped symbols, caps at top 10 by edge count, and filters out symbols already covered by an active CON/ADR viamemory.affectsSymbol(). Composes alongside the existing 3 sources inharvest(); REJ tombstone keyword suppression and dedup against existing memory docs apply unchanged. Closes the gap where CON-* docs starved of candidates because session-time ⚖️/🔵 signals rarely surface structural concepts.
Fixed
token-report --regressionemits a stable JSON contract. When no Claude Code session logs exist for a project (fresh CI checkout), the missing-session-dir early-return now still emits the top-levelregressionblock with zero counts, so the--fail-on-regressionconsumer and downstream automation can rely on the field shape. Previously the block was silently dropped on that branch, causing the smoke gate to fail in CI.- Release workflow promotes Latest by highest semver.
.github/workflows/release.ymlcomputes the highest stable tag including the current$TAGand passes--latest=trueonly when$TAGis that maximum. Prereleases keep their existing--prereleasepath and are never flagged Latest. Guards against retags of older versions or hotfixes of older series stealing "Latest" from a higher release. deferred list --tags=CSVfilter works (was DEF-017). The list subcommand previously read--tag(singular) and only matched the first tag; now--tags=a,b,cparses to an array, OR-filters across items whosetags[]include any requested tag, and aligns with the documented canonical form.
v0.39.0
Observability foundation. The MCP trace records now carry the active workflow's context, and mcp-stats gains three filter flags so per-workflow / per-phase / per-type slicing is possible — unlocks measuring whether the <memory_signal> extensions from v0.38.x actually save the predicted MCP round trips. Smoke: 379 passed, 0 failed. Locking: 3/3.
Added
- Workflow context on MCP trace records.
bin/devt-memory-mcp.cjsgains areadWorkflowContext()helper that reads.devt/state/workflow.yamlon demand with mtime-invalidated caching — onestat()syscall per MCP call when nothing changed, full re-read on workflow transitions. Each trace record emitted while a workflow is active now carriesworkflow_id,workflow_type, andphasefields merged into the existing schema. Records emitted outside any workflow omit these fields entirely (cleanest signal). Existing record fields (ts, tool, ok, duration_ms, …) take precedence on the unlikely collision. - Three new filter flags in
bin/devt-tools.cjs mcp-stats:--workflow-id=<UUID>,--workflow-type=<dev|code_review|…>,--phase=<implement|verify|…>. Filters compose conjunctively with the existing--sinceand--tool— e.g.mcp-stats --workflow-type=dev --phase=verify --tool=query_ftsshows verifier-phase memory lookups across all dev workflows. Trace records lacking a field are excluded when its filter is set; bare aggregate behavior is unchanged.
Internal
- 5 new smoke-test gates: bare aggregate over a synthesized 4-record fixture (counts all 4),
--workflow-id=wf-A(narrows to 2), conjunctive--workflow-type=dev --phase=verify(narrows to 1), unknown workflow_id returns 0 cleanly, and a live MCP server boot test that fires a realtools/callJSON-RPC request and asserts the resulting trace record carriesworkflow_id+workflow_type+phasefrom the active workflow.yaml. - Workflow-context regexes hardcoded (not built via
new RegExp(varName)) so Semgrep's ReDoS analysis can prove the patterns are bounded.
v0.38.1
Small composing additions: memory signal extended to more dispatches, narrow git destructive patterns added to the Bash safety hook, and a new input-JSON schema registry validates handoff.json for resume reliability.
Added
<memory_signal>extended to programmer + code-reviewer dispatches acrossworkflows/dev-workflow.md,workflows/code-review.md, andworkflows/quick-implement.md— five new dispatch sites total. Each uses the same orchestrator-prepmemory query --signal=3pattern shipped earlier for verifiers, so programmer and code-reviewer skip per-doc memory round trips on their initial scan.agents/programmer.mdandagents/code-reviewer.mdinstruct preferring the inline block over fresh queries; programmer uses it to confirm which ADRs/CONs apply to the code path, code-reviewer uses it to flag REJ-tombstone matches and ADR violations. KEEP-IN-SYNC discipline extended to cover the 5-site cluster.- Git-destructive Bash patterns (
source: "git_destructive") inbin/modules/bash-guard.cjs. Three narrow patterns with zero legitimate dev use: (1) force-push to a protected branch (main,master,release/*,prod*,develop) —--force-with-leaseto the same branches is explicitly allowed as the safer variant; (2)git clean -x(any flag combo containingx) — nukes gitignored files including.env; (3)git checkout -- .orgit checkout -- *mass-discard.git reset --hardis deliberately NOT denied so devt's own self-update flow inworkflows/update.mdcontinues to work. JSON_INPUT_SCHEMASregistry inbin/modules/state.cjswith avalidateInputJson(body, schema)helper. Distinct fromJSON_SIDECAR_SCHEMAS— sidecars validate enum membership (status/verdict/agent); input schemas validate required + recommended top-level fields.handoff.jsonis the first registered entry:required = [task, phase, paused_at],recommended = [tier, iteration, last_commit, remaining_tasks, next_action].state validatenow surfaces amissing_required_fieldmismatch whenhandoff.jsonexists but lacks a required field, catching resume-after-pause breakage before it silently corrupts a routing decision.
Internal
- 9 new smoke-test gates covering the 5
<memory_signal>dispatch sites, 3 orchestrator-prep step invocations, agent guidance presence in programmer + code-reviewer, force-push deny +--force-with-leaseallow,git clean -fdxdeny, mass-discard deny, devt self-update compatibility (regression guard), JSON_INPUT_SCHEMAS registry export,validateInputJsonhappy path, and end-to-endstate validatesurfacing missing required field. - New
MISMATCH_REASONS.MISSING_REQUIRED_FIELDentry — used byvalidateConsistencywhen an input JSON parses but lacks a contractually required field.
v0.37.0
Cache-friendliness, CI hardening, and a strict documentation discipline pass. Smoke: 340 passed, 0 failed. Locking: 3/3.
Added
- Cache-friendly dispatch ordering across every workflow. Every
Task(subagent_type="devt:*", ...)dispatch inworkflows/*.md(25 dispatches across 11 files) was reordered so the per-task dynamic block (<task>or, forworkflows/debug.md,<bug>) appears AFTER</context>. Static blocks (<governing_rules>,<guardrails_inline>,<workflow_type>,<rubric_path>) now lead the prompt so the Anthropic prompt-cache prefix is byte-stable across retry iterations within the 5-minute TTL. Subagent dispatches in a single workflow run now cache-hit each other's prefixes, paying ~10% of full input price on the static portion. - Cache-ordering smoke gate. New
scripts/check-dispatch-ordering.cjswalks every dispatch block and rejects any<task>that precedes</context>. Wired intoscripts/smoke-test.shas a new section; runs on every CI push. devt:tokens --regressionmode.bin/modules/token-report.cjsexposesdetectRegressions(records, opts)and the--regression,--regression-min-input,--regression-streakCLI flags. The detector scans per-turn JSONL records for streaks of "cold" turns (cache_read_tokens == 0withinput_tokens >= min_input_tokens, default 5000) running ≥4-in-a-row (default). A streak is a near-certain signature of a dispatch-template ordering regression. Output:sessions_with_regression,total_cold_turns,est_wasted_input_tokens,offending_sessions[].streaks[]. Documented inworkflows/tokens.md.
Changed
- Codebase-wide version/option/wave/D-NN reference removal. Every devt-internal version marker (
v0.X.Y+,since v0.A.B,Phase N (v0.X.Y+),Option N,Wave A,D-NN,CCA v27 §X, roadmap pointers) has been stripped from every.md,.cjs, and.shcomment / prose surface — agents, workflows, skills, hooks, guardrails, references, docs, READMEs, CLAUDE.md. The codebase is no longer a parallel changelog;CHANGELOG.md+git logare the canonical sources for "when did X land". Third-party version markers (Graphify v0.7.10+,Node v22, model IDs) are preserved. - CLAUDE.md "Key Conventions" extended with three new rules: cache-friendly dispatch ordering, documentation discipline (no version refs in code), and comment discipline (comments reserved for non-obvious WHY).
Fixed
- CI smoke-test exit 1 on Node 22 / Node 24. The
memory.upsertDoc + MCP write surfacesmoke check captured stderr (2>&1) which let Node 22/24'snode:sqliteExperimentalWarningcontaminateUPSERT_OUT, breaking theJSON.parsethat validates the upsert result. Switched to2>/dev/nullto match the surrounding captures. Locally on Node 26 (where the warning is silent) the check always passed; CI on Node 22/24 now matches. - GitHub Actions Node-20 deprecation warnings. Bumped
actions/checkout@v4→@v5andactions/setup-node@v4→@v5in.github/workflows/ci.ymland.github/workflows/release.yml.
v0.36.0
Two waves consolidated into one release. v0.35.0's Wave A (6 options) was authored in a prior session but never published; v0.36.0's wave adds 3 more options on top. Ships 9 architectural improvements drawn from the ticklish-mapping-backus.md 12-option roadmap. Smoke: 336 passed (was 325 pre-wave). Locking: 3/3. Plugin contract surface: stable (no breaking changes to commands, agents, or hooks).
Added — v0.36.0 wave (Options 9a, 10, 11)
- Parallel researcher + arch_health dispatch (Option 9a). COMPLEX-tier
devflows now dispatch the researcher and (when arch_health is opted-in via risk-signalAskUserQuestion) the architect in one message with twoTasktool calls fromworkflows/dev-workflow.mdStep 2.5. The arch_health architect dispatch reads.devt/state/scan-results.mdonly — theplan.mddependency dropped since the plan does not yet exist at parallel-dispatch time. Inline Auto-Plan consumes bothresearch.mdANDarch-health-scan.md. Workflow carries<!-- parallel-dispatch: researcher + architect (arch_health mode) -->marker; smoke test asserts presence + absence of regressions. - Memory Graph subgraph in Pre-Flight Brief (Option 10).
bin/modules/memory.cjs::getSubgraphTriples(seedIds, depth=2, maxTriples=50)reshapes per-seedgetLinksrows into a deduped, sorted{source, predicate, target}array.bin/modules/preflight.cjs::renderBriefemits a new## Memory Graph (2-hop subgraph)section between Governing Documentation and Rejected Approaches. Agents scan structural relationships (supersedes,depends_on,relates_to, etc.) without per-docget_docround-trips. Smoke: 2 linked ADRs produce 2 expected triples. - Pinned rubric versions (Option 11).
references/rubrics/dev.mdrenamed todev.v1.md. Newbin/modules/config.cjs::DEFAULTS.rubricsblock (default{ dev: "dev.v1.md" }) exposed at the top of the init payload asrubrics.workflows/dev-workflow.mdverifier dispatch injects<rubric_path>references/rubrics/{rubrics.dev}</rubric_path>;agents/verifier.mdprefers that block over computing the path from<workflow_type>. Future rubric updates ship as new files (dev.v2.md); projects opt in by overridingrubrics.devin.devt/config.json. Naming convention:<workflow_type>.v<N>.md.
Added — v0.35.0 carryover wave (Options 1, 2, 4, 5, 6, 8)
- Hot-path read cache: governing rules wiring (Option 1).
bin/modules/init.cjs::loadGoverningRulesreturns the project'sCLAUDE.md+.devt/rules/*.mdcontents inline in the init payload asgoverning_rules: {content, paths_included, paths_excluded, rules_hash, total_bytes}. Cap is 96 KB total. Workflowsdev-workflow.md,quick-implement.md,code-review.md,research-task.mdinject a<governing_rules rules_hash="...">block (with<claude_md>,<coding_standards>,<architecture>,<quality_gates>,<review_checklist>sub-tags) into code-reviewer, verifier, and researcher dispatches. Those agents prefer inline content over on-disk Reads when present.rules_hash(SHA-256 first 16 chars) lets agents detect mid-workflow drift. - MCP write surface for curator (Option 2).
bin/modules/memory.cjs::upsertDoc({frontmatter, body})atomically writes a.devt/memory/<subdir>/<ID>-<slug>.mdfile AND refreshes the FTS5 index in one call. Validates frontmatter BEFORE touching disk; rolls back file write if index rebuild fails.bin/devt-memory-mcp.cjsexposesmemory_upsert_doctool gated byDEVT_MCP_ALLOW_WRITES=1(set by plugin's.mcp.jsonenv block by default).listTools()filters out write tools when the flag is unset;callTool()re-checks at handler level.agents/curator.mdinstructs the curator to callmemory_upsert_docfirst and fall back to the legacy 3-tool ritual onWRITES_DISABLEDerror. - Sidecar-only status routing (Option 4).
impl-summary.md+verification.mdno longer carry a## Statusheader in their markdown templates. JSON sidecars (impl-summary.json/verification.json) are the single source of truth for workflow routing.bin/modules/state.cjs::SIDECAR_FOR_MARKDOWNmaps markdown → sidecar;validateConsistency()reads the sidecar'sstatusfield for these artifacts. Other 7 ARTIFACT_SCHEMA artifacts keep the markdown## Statusheader until backfilled with their own sidecars in a future wave (Path A of Option 4, deferred). - Stable-prefix invariant smoke test (Option 5). Asserts that the byte-prefix of
initpayloads is stable across task-string variations — guards against accidentally moving task-text into a prefix-position that would defeat cache hits. - Memory query aggregate flags (Option 6).
bin/modules/memory.cjs::queryFTSaccepts amodeoption —"full"(default),"count","top","domain-counts". CLI surfaces:memory query "<terms>" --count|--top=N|--domain-counts|--json-compact. MCP exposesquery_fts_count,query_fts_top,query_fts_by_domain. Aggregates return ~50–500 B vs ~1.5–15 KB for full payloads — memory-pre-flight skill documents the "aggregate-first" probe pattern. - Hook profile docs resync (Option 8). Updated the hook-profile table in
CLAUDE.mdto reflect the currentminimal | standard | fullset.
Changed
references/rubrics/dev.md→references/rubrics/dev.v1.md(rename, full content preserved). Future rubric revisions ship as new versioned files.workflows/dev-workflow.mdStep 2.7 deleted — its risk-signal detection + user prompt logic moved into Step 2.5's parallel-dispatch block. Step 3's architect review prompt updated to reference the parallel dispatch instead of the deleted Step 2.7.agents/verifier.md: prefers the dispatch-injected<rubric_path>over computing the path from<workflow_type>; falls back to<workflow_type>.v1.mdlookup when the block is absent.agents/code-reviewer.md,agents/verifier.md,agents/researcher.md: prefer the<governing_rules>dispatch block over on-disk Reads ofCLAUDE.md+.devt/rules/*.md.agents/programmer.md,agents/verifier.md: emit BOTH.md(narrative) AND.json(workflow-routing sidecar) per Option 4's sidecar-only contract. Markdown templates no longer carry## Statusfor these two artifacts.agents/curator.md: instructs the curator to callmemory_upsert_docfirst and fall back to the legacy 3-tool ritual onWRITES_DISABLEDerror.bin/devt-memory-mcp.cjs: addsquery_fts_count,query_fts_top,query_fts_by_domain,memory_upsert_doctools; write tools filtered out vialistTools()whenDEVT_MCP_ALLOW_WRITESis unset.bin/modules/state.cjs: newSIDECAR_FOR_MARKDOWNregistry;validateConsistency()reads sidecarstatusfor sidecar-covered artifacts.
Smoke
- +11 new assertions in
scripts/smoke-test.sh:- Option 9a (4): parallel-dispatch marker comment present; Step 2.7 deleted; arch_health dispatch reads scan-results.md only (no
plan.md); no stale "from Step 2.7" references. - Option 10 (4): preflight Brief generated with seeded ADRs; Brief contains Memory Graph section header; section renders
source → predicate → targettriples;getSubgraphTriplesreturns flat{source, predicate, target}array. - Option 11 (3): verifier rubric resolved via
DEFAULTS.rubrics.devexists (dev.v1.md); init payload exposesrubrics.dev; dev-workflow verifier dispatch injects<rubric_path>.
- Option 9a (4): parallel-dispatch marker comment present; Step 2.7 deleted; arch_health dispatch reads scan-results.md only (no
- 336 total pass (was 325 pre-wave). 3/3 locking assertions still pass.
Docs
CLAUDE.md— six new architecture doc blocks covering each shipped option, plus an updated entry for Option 11'srubricsconfig key.docs/MEMORY.md— added aggregate-flag CLI variants under "CLI Surface"; addedquery_fts_count/query_fts_top/query_fts_by_domain/memory_upsert_docrows under "MCP Server"; added Memory Graph bullet under "Tier 1 — Topic Pre-Flight".README.md— addedrubrics.devconfig row under "Basic configuration".skills/memory-pre-flight/SKILL.md— documents the aggregate-first probe pattern and the Memory Graph Brief section.
Notes for projects upgrading from v0.34.1
- No config migration required.
.devt/config.jsonkeeps working unchanged. - Projects that subclassed
references/rubrics/dev.mddirectly need to update their path — point todev.v1.md(or overriderubrics.devin.devt/config.json). - MCP write surface (Option 2) is enabled by default via
DEVT_MCP_ALLOW_WRITES=1in the plugin's.mcp.json. Set to"0"or remove the env var to disable and force the legacy 3-tool path.
v0.30.5
Added
- Forensic deny log for the pre-flight guard (
hooks/pre-flight-guard.sh,skills/memory-pre-flight/SKILL.md). Everydecision: "deny"(block mode) and every advisory (warn mode) now appends one line to.devt/state/preflight-denies.log— single-writer, append-only, gitignored under existing.devt/state/rules. Format:<mode> <ISO-ts> <action> <file_path> :: missing PREFLIGHT line. Closes the silent-stall failure mode where a subagent dispatched without thedevt:memory-pre-flightskill received a deny it didn't know how to satisfy, then went silent (no streaming output) for 600s until Claude Code's stream watchdog killed it. With the log, recovering agents read.devt/state/preflight-denies.logfirst to see their own prior denied attempts, then write the missing PREFLIGHT lines to scratchpad in order. Hook stays stateless — log is pure side-effect, never read by the hook itself. Wrapped in try-catch so a log failure can never block the deny path. Survivesstate resetvia the v0.30.4 archive ring buffer (.devt/state/.archive/<ts>/preflight-denies.log), so post-mortem of stalled workflows is possible after the workflow finishes. memory-pre-flightskill documents the deny-recovery sequence with explicit Read-log → Append-PREFLIGHT-lines → Retry steps. All 8 dev agents preload the skill, so the recovery protocol propagates without per-agent prompt edits.
Changed
workflows/dev-workflow.mdStep 1 gains a CONTRACT callout above thestate updateline: "Execute the next bash block VERBATIM. Do not paraphraseworkflow_type=devtoworkflow_type=workflow(the slash-command name)." Addresses the orchestrator-deviation bug where an agent invoked/devt:workflowand improvisedworkflow_type=workflowinstead of executing the workflow file'sworkflow_type=devliteral — the entire downstream stall traced back to this single deviation. The v0.30.4 alias hint catches drift after the fact; this callout prevents drift in the first place.CLAUDE.mdTwo-Tier Pre-Flight Protocol entry updated to mention the forensic deny log and point at the skill's recovery section.
Fixed
- Silent watchdog stalls when subagent hits a deny without the memory-pre-flight skill loaded — fixed by giving the agent its own deny history via the new log so it can break out of silent reasoning and write the missing PREFLIGHT lines on retry. Root cause was orchestrator drift (separate fix above) plus agents lacking forensic visibility into hook denies (this fix).
Smoke
- 2 new assertions (
scripts/smoke-test.sh): hook deny appends correctly topreflight-denies.log; deny JSON contract (decision: "deny") still emitted alongside the log so the existing hook protocol is preserved. 273 total pass (was 271).