Repository navigation
DevCouncil v0.2.3 Release Notes
DevCouncil v0.2.3 and DevMap v0.2.3 are the same product version. The npm package,
the Go host (devcouncil / dev), and the Rust workspace (devmap, dcstore,
dcverify, dcgrep) all report 0.2.3.
This is a portability and correctness release, plus one adoption change. It makes
the Windows half of CI able to run at all — it had been red on every push since
2026-09-10, and the failure was masking five crates whose tests had never
executed on that platform — and it fixes the three defects that ran out from
under that mask, two of which reach production rather than only the test suite.
It also fixes the reason a commit, branch switch or rebase could become invisible
to a running daemon.
The store schema stays at 22. No migration, no rebuild: a v0.2.2 store opens
unchanged.
Highlights
1. Windows checkouts were rewriting the bytes dc-evidence digests
components (windows-latest) had been red on every push since 2026-09-10.
Eleven dc-evidence tests asserted Passed and got Failed, on Windows only,
naming no cause.
The cause was not in the crate. dc-evidence binds a bundle to its contract by
digesting the contract's exact bytes, and fixtures/v1/bundle*.json record
those digests as literals. The repository had no .gitattributes; Git for
Windows installs with core.autocrlf=true in its system config, and
actions/checkout does not override it. So the Windows checkout wrote
contract.json with CRLF, its sha256 changed, and verify() took the
contract_hash != expected.contract_sha256 arm. The JSON still parsed and still
meant the same thing — which is exactly why the failure was silent about its
own reason.
Reproduced on macOS by converting the fixtures and running the suite: the same
eleven test names at the same line numbers, 18 passed / 11 failed, matching the
Windows log exactly. Converting each fixture alone isolates it further —
contract.json accounts for 10 and contract-not-found.json for 1. The other
six fixtures are unaffected, because only the contracts are digested over raw
bytes.
The fix is a repository .gitattributes with * -text, not text=auto eol=lf:
benchmarks/results/** holds recorded competitor stdout that genuinely contains
CRLF, and normalising would rewrite that evidence. git check-attr confirms
diff and merge stay unspecified, so nothing is treated as binary. The file
carries a comment saying so, because a text entry added there later re-arms
this bug for whatever it covers.
A new guard checks the whole fixture directory and names line endings and
.gitattributes when it trips, so the next reader does not get eleven bare
left: Failed assertions.
2. That failure was hiding five crates, and three defects came out from under it
cargo test aborts at the first failing binary, and dc-evidence sorts first.
So dc-glob, dc-proc, dc-grep, dc-store and dc-verify had never run
on components (windows-latest) — most of what that job's own comment says it
exists to answer. Only Build had been covering them.
With dc-evidence green they ran, and three defects surfaced on their first ever
execution. None is a regression from the fixtures fix; each is a defect that fix
made visible.
Absolute LCOV paths were never reduced on Windows. normalise_lcov_path
tested absoluteness with starts_with('/'), which is only ever true on Unix. So
every C:\... path llvm-cov and grcov emit took the early return, was never
reduced against the supplied root, and never matched a repo-relative diff path —
every file landed in coverage_unmeasured. That is the precise wall the
function's own doc comment says it exists to remove, left standing on the one
platform whose tests did not run. It reaches production through
dcverify.rs:189, not just the test.
has_root replaces the check: true for /x, C:\x, \\?\C:\x and
\\server\share. The prefix match becomes Path::strip_prefix compared by
component, so reduction no longer requires the producer and the root to agree on
a separator — the old code built its prefix with a hard-coded /, which a
backslash path could never match even after getting past the early return. The
remainder is rejoined with /, rebuilt from components rather than by replacing
separators, so a Unix filename that legitimately contains a backslash is not
rewritten.
Component-wise stripping introduces one case the string prefix did not have: an
SF: line naming the root itself now strips to nothing. Returning "" would
file that record's lines under an empty path and read as some other file's
coverage, so it is kept verbatim and reported unmeasured, like every other
unreducible path.
The symlink walk refused everything on Windows. With coverage red,
evidence_cli.rs had never run either; with it green, 4 of its 8 Windows tests
failed on first execution, all with
{"ok":false,"error":"cannot inspect input: Incorrect function. (os error 1)"}.
reject_symlinks walks a path component by component and stats each accumulated
prefix. The first component of an absolute Windows path is the drive prefix,
and symlink_metadata("\\?\C:") is ERROR_INVALID_FUNCTION — a prefix is an
anchor, not an entry. The fixtures reach it via temp_dir().canonicalize(),
which returns the verbatim \\?\C:\... form, so every --contract, --bundle
and artifact read failed closed. Failing closed is the right direction; it
refused everything.
The walk now stats only Component::Normal, skipping the prefix and the root.
Security impact: this does not widen access. Every named component is still
inspected and still refused if it is a symlink; theParentDirrejection is
untouched and still runs first. What is skipped is the prefix and the root — on
Windows unstattable, on Unix/— neither of which can be a symlink
redirecting the walk. Nothing that was refused before is accepted now.
Proving that needed a new test, because the existing coverage did not hold the
function down: with the component walk disabled outright, every test in the file
still passed. A symlinked final component is already refused by the
regular-file check in read_regular, and a symlinked artifact directory by the
escapes-root check in checked_child. The gap is a symlinked intermediate
directory on --contract, which takes an arbitrary path under no root — there
the walk is the only guard. The new test covers exactly that, and was verified to
fail when the walk is disabled and pass when it is not.
Six portability defects in the devmap crates. Windows Clippy had failed since
its first run on 2026-09-11 and Build/Test since 2026-09-13, on defects unrelated
to the CRLF class: PermissionsExt::from_mode and std::os::unix::fs::symlink
in digest_cache tests; parse_etime, reachable only from stale_server_warning's
unix arm and therefore dead code on Windows (with a needless_return in the one
body where that arm is the whole function); serving_or_dead, whose six callers
are all cfg(unix); an inventory.rs test module whose use super::* had nothing
to name; and a Write import in worktree_stress used only by ipc_unix. Each is
now gated to match what actually calls it. The read-only-directory test is gated
whole — Windows denies writes through ACLs, which std::fs cannot set — while the
symlink shapes are gated inside their test so its directory case still runs
everywhere.
3. A commit, branch switch or rebase could go unseen by the daemon
The daemon's git-HEAD sentinel never reached the pending queue.
canonical_pending_entry short-circuits control tokens correctly, but it
returned them as PendingEntry::Canonical, and
enqueue_pending_paths_under_root then ran the K7 build-cache check on the
result. root.join("\0devmap:git-head-changed") cannot be handed to the OS — a
path string cannot carry an interior NUL — so is_cache_directory returned
Err, and the Unreadable verdict refused the token at the door with
enqueued 0 changed path(s).
So HEAD moved and the daemon never learned. It is why rust.yml's Native
worktree capacity and process recovery step failed on macOS and Ubuntu alike,
the harness waiting out its full 90 s deadline for a stored HEAD that could never
move.
Every existing test of the sentinel called enqueue_pending_paths, which has no
cache check, while the daemon calls enqueue_pending_paths_under_root, which
does. The suite was green against a producer the daemon does not use.
Fixed as a type rather than a fourth guard: PendingEntry::ControlToken makes
Canonical mean "a repo-relative path" and nothing else, and forces every caller
to branch — so the next path-shaped check cannot swallow a control token in
silence. reconcile_pending_paths_with drops its separate is_control_token
pre-check and handles the variant instead, leaving one owner of the rule.
4. The serve stress test drains until the work settles, instead of once
fifty_spawn_retire_cycles_and_a_kill_mid_drain_leave_the_store_usable failed on
CI under load (run 34904461297 attempt 1, green on attempt 2 of the same commit)
and reproduced locally under verify.sh's whole-workspace load.
It is not a timing flake. The abort does not terminate the killed daemon's own
spawn_blocking drain, so two drains are live. When the surviving one touches a
row this one has already claimed, that row's revision moves, and
clear_claimed_pending_paths deletes WHERE path = ?1 AND revision = ?2 — the
acknowledgement matches nothing and the row stays. That is the store working as
designed, the same rule equal_clock_ticks_cannot_acknowledge_a_new_edit pins: a
drain must not retire an edit that arrived after it looked. The work is in the
generation; the row is merely unretired, and only another drain retires it. A
daemon does that on its next idle tick; this test has no daemon left.
Waiting cannot substitute, and a first attempt proved it: with nothing draining,
60 s of sleep reported late.py indexed=true, still queued=["late.py"]
unchanged. The test now drains until the queue is empty. The property is
unchanged and still fails closed — late.py ends in the generation and out of
the queue, or the bound expires and the message names what it saw, so the next
failure is diagnosable rather than a rerun.
5. The index now speaks once at the first navigation tool call
Across 37 measured sessions, 36 made zero DevMap queries. The SessionStart
briefing is read once and then scrolls out of context, so by the time an agent
reaches for Read or Grep, the index is out of mind.
devmap hook pre-tool-use is a new hook event that restates the directive once
per session, at the first navigation tool call, and stays silent when the index
cannot answer.
It is deliberately not installed by devmap integrate. On Claude Code and
Cursor, PreToolUse decides an authorization outcome, and claude::hooks_block
refuses to emit a handler onto any such event: a code index has nothing to
contribute to a permission decision, and a table that could place one there is
one edit away from widening what this tool can approve. Wiring it is therefore a
deliberate, hand-made choice in your own host configuration:
"PreToolUse": [{ "matcher": "Read|Grep|Glob", "hooks": [
{ "type": "command", "timeout": 5,
"command": "\"/abs/path/to/devmap\" hook pre-tool-use" }]}]The handler only ever emits additionalContext, never a decision field, which
pre_tool_use_never_emits_a_permission_decision asserts against the document it
actually produces.
Cursor is the exception that proves the rule: its preToolUse is a
permission hook, where a schema mismatch blocks the tool outright. So
devmap integrate installs the same handler on Cursor's postToolUse with a
Read|Grep matcher — the directive arrives after the first read instead of
before it, and cannot block anything. A test asserts the written
.cursor/hooks.json names none of preToolUse, beforeReadFile,
beforeShellExecution, beforeMCPExecution, beforeTabFileRead or
subagentStart, and sets no failClosed — which would turn a crash into a
blocked Read.
render_host_stdout replaces the ad-hoc additionalContext unwrapping in
run_hook_command, so each host gets the stdout shape it actually parses, from
one owner.
27 conformance tests cover both hosts: once-per-session under concurrency,
bounded markers across many sessions, hostile characters in the briefing,
never blocking a tool call, and no permission decision in the emitted document.
6. The DevCouncil MCP server this repo's own agents assume is registered
The three .claude/agents/devcouncil-* agents list mcp__devcouncil__* tools in
their frontmatter, but .mcp.json declared no servers — so every one of those
tools was unavailable in a fresh checkout and the agents failed on first use.
.mcp.json now registers the stdio server they expect.
The accompanying .devcouncil-skills.json refresh is the manifest
devcouncil integrate writes. It now records the 12 role skills (backend, ios,
security, systems, web, and the rest) alongside the devmap ones across all three
host directories, and corrects one stale .claude/skills/devmap hash that no
longer matched the installed file.
7. The MCP server reported version 0.1.0 through three releases
serverInfo is the one field through which every host learns which build
answered it, and it had been sitting at 0.1.0 while the product went to 0.2.3.
The cause is structural, not a typo. devcouncil/mcp cannot import package main, so it could not read the host's Version constant and was written with
its own literal instead. Nothing could have caught the drift, because there was
no shared constant for it to disagree with — two literals that must agree, in
packages that cannot see each other.
devcouncil/version is now the single Go-side owner, cmd/devcouncil's
Version is an alias rather than a second literal, and dcgrep, dcstore and
dcverify each report the workspace version through a component_version test
per crate that pins the binary's reported version against the manifest rather
than against a copy of it. scripts/check-release.mjs gained the Go arm: it
parses devcouncil/version/version.go and compares it with package.json,
package-lock.json, the Rust workspace version and the tag before any build
minutes are spent.
8. The plugin bundle showed a slug, and had nowhere to put a mark
The plugin list showed devmap beside products that showed a name, because
name is the install identifier and has to stay a kebab-case slug.
displayName is the only field Claude Code renders as a human name. It is set
in both the plugin manifest and the marketplace entry, because the browser
reads the entry's own copy — setting one and not the other leaves the slug on
screen.
The logo could not go in the portable manifest at all. Claude Code documents no
field for one, and the Agent Plugins 1.0.0 schema sets
additionalProperties: false, so the key makes a conformant client reject the
package. Cursor is the only one of the three hosts that renders a logo, so the
mark lives in .cursor-plugin/plugin.json and the bytes ship through
include_bytes! — a cargo install binary carries no repository beside it, and
a manifest naming a logo that was never written is worse than one with no logo.
Validation now follows the file rather than one key set. The bundle emits three
manifests in three dialects, and checking all of them against Claude's keys
reported the emitter's own Cursor manifest as wrong: a warning correct about
Claude and false about the file it was aimed at, which is how a warning list
stops being read. ManifestDialect::of_path picks the dialect from the
directory, defaults to the stricter Claude set when provenance is unknown, and
the diagnostic names which host ignores the field.
9. A --json contract gate had been running nothing and reporting ok
dc-verify's json_contract calls skip_or_fail no longer. Its manifest had
named a Python module deleted in 3286db5, and turning the skip into a failure
needed DC_JSON_REQUIRE_CLI=1, which no workflow ever set — so for four days
the gate ran nothing and reported ok.
It now drives the real Go host as a process for every invocation in
json_stdout_manifest.tsv, and a missing binary, an unscaffoldable project or a
harness that could not start all fail. No in-process test can settle this: the
Python CliRunner it replaced merged the two streams, so a banner leaking onto
stdout was indistinguishable there from one going to stderr — which is how dev cost budget --json --set 5.00 shipped emitting unparseable stdout with a green
suite. The Go host's own handler tests swap os.Stdout, which is closer but
still cannot see what console.Session renders around the handler. Two real
file descriptors settle it.
Running the manifest against the host found a real defect, which is fixed here:
dev hook status and dev hook disable took the protocol bypass along with the
retired lifecycle events that share the word hook. An event call must stay
inert, but those two are ordinary commands a person or a script runs, and
--json owes them one JSON value on stdout like any other. Only the session can
keep that promise where the handler has no receipt to write —
integrate.Uninstall returns none for an unsupported --client, and the bypass
left stdout empty while a caller was parsing it.
10. A directory of tests nobody ran
tests/unit/test_devmap_skill_delivery.py imported devcouncil.cli.main and
devcouncil.skills.registry, both deleted in 3286db5 when the orchestrator
moved to Go. It could not be imported, let alone pass, for months, and nothing
noticed: no workflow ever collected that directory.
Its assertions are ported to the two ports that now own them —
devcouncil/skills/delivery_test.go and cmd/devcouncil/skills_cli_test.go for
the Go scaffolder and CLI, rust/devmap-cli/src/skills.rs for the DevMap
skills — and the file is deleted. ci.yml now runs what remains in
tests/unit/, and a README records the rule that makes this non-recurring:
anything added there must be run by a workflow, because a test nobody runs
reports the same result as a test that ran and passed.
11. The local gate chain was red by construction
Section 9's gate is a fixture-carrying test, and the workflows that run it
gained a go build step for the host. rust/verify.sh did not, and its gate 3
is cargo test --workspace — which includes that crate. So every local run of
the chain failed two tests on a message about a missing path: measured here
before the fix as VERIFY_EXIT=101.
verify.sh now builds the host the way analysis-plane.yml and rust.yml do,
honours DEVCOUNCIL_BIN when it is set, and names a missing Go toolchain itself
rather than letting it surface as two gate-3 failures about a path. The module
has no third-party dependencies and links in about a second, so gate 3 pays
nothing and no tree-sitter grammar is compiled for it. scripts/ci-local.mjs
runs ./verify.sh --quick, so it is covered by the same change rather than
needing its own copy; rust.yml's workspace job already excludes dc-verify
and was never affected.
12. The gates are now measured, and a diff is asked whether it says anything
Five borrows from a comparison against reverify, which makes deterministic tools
the judge of a model's claims about a binary. DevCouncil's subject is a change
rather than an artifact, so the mechanisms differ; the epistemology is the same,
and these are the places it had not yet been applied.
The gates are measured as classifiers. dc-verify/tests/corpus/cases.txt is
23 diffs, each declaring what every gate must say about it — so a fixture
written for secret_scan is a standing negative control for stub_detection.
false_accept.rs prints a confusion matrix and fails the build on a single
false accept or false positive: currently 0 and 0 over 12 known-positive and
34 known-negative expectations. It was falsified in both directions before
landing — raising the AWS key floor by one character reports a FALSE ACCEPT, and
removing the comment guard reports two false positives.
A substance measurement. Every other gate asks whether something is wrong
with a change; none asked whether there is anything in it. A diff of relocated
functions, braces and a regenerated lockfile trips nothing and arrives as
findings: []. Added lines are now classified substantive / trivial / moved /
repeated / generated, the classes partition the total, and no ratio goes on the
wire so a consumer always sees the denominator. It never blocks: a pure refactor
measures low by construction, and that is the correct reading of one. The 1/4
threshold is measured rather than chosen — 200 commits of this repository, 161
judged, median 0.68, 5th percentile 0.40, and the single commit below it is a
version bump that is 141/182 regenerated Cargo.lock. Reproduce with
rust/tools/substance_calibration.sh.
A strength axis on findings. Severity says how much a finding matters;
strength says how much it can be trusted — proven / observed / derived. Both
findings gates report derived, because secret_scan matches a credential's
shape and stub_detection matches text. That is an accurate and
uncomfortable description of the rigor layer, which is exactly why it belongs on
the wire: this repository's standing rule that a check which could not run must
not look like one that ran, carried one step further — a guess must not look
like a measurement.
13. A successful checkout handed every agent six tools this host does not serve
AllowedNextTools in devcouncil/lease.go still spelled out the retired Python
host's surface. Every task checkout answered with eight tool names, and six of
them — devcouncil_read_file, devcouncil_get_evidence, devcouncil_run_command,
devcouncil_apply_patch, devcouncil_write_file, devcouncil_update_task_scope
— are served by nothing here. Checked against the registry on main: only
devcouncil_get_diff and devcouncil_verify_task of that list exist. An agent
that did what the checkout told it to do next called into a tool the host does
not have.
This is GAP-P7-NEXT-TOOLS-DRIFT, which the verify path was corrected for
earlier in this release. The checkout path kept its hand-written copy because,
unlike verify's, nothing held it to the registry. Two lists spelled separately
drift; that is not a prediction, it is what happened twice.
The fix is at the canonical owner rather than the call site. Specs() and the
new ServedToolNames() both read one literal list, toolSpecs(), so the served
surface cannot be restated anywhere inside the package. verify.AllowedNextToolsForVerify
still keeps a copy — devcouncil imports verify, so reading back would cycle —
and that copy is safe only because allowed_next_tools_test.go holds it to this
list in both directions.
The stored envelopes under testdata/golden/{cli,mcp}/lease/ had recorded the
wrong list for as long as it was wrong, without ever contradicting it, because
nothing replays them. They are re-recorded here. A golden that is written and
never compared is a description, not a check.
TestCheckoutAllowedNextToolsAreAllServedByThisHost enumerates all eight status
values against both gap states rather than sampling one: each status is a
separate branch of the switch, and a branch that is never asked cannot be said to
have passed.
14. Three copies of each agent definition, maintained by hand, had drifted onto the same retired tools
The same root cause, one layer up. Each DevCouncil agent existed three times —
.claude/agents/<name>.md, the Codex role at .codex/agents/<name>.toml, and the
plugin copy — all maintained by hand. Two of the three still told agents to call
devcouncil_write_file, devcouncil_apply_patch and devcouncil_run_command.
Section 13 fixes a list. This fixes the shape that produces the list: the Codex
role and the plugin copy are now rendered from the .claude/agents definition
by scripts/sync-agent-defs.mjs, and npm run agents:check reports drift
between the three.
The definitions are now tracked, which is the part worth stating plainly. They
were local state before. On a clean clone an untracked file reads as missing,
so the check would have reported the same thing whether the copies agreed or
were absent — and a check that could not run must never report what a check that
ran and passed reports. The .gitignore re-inclusions are scoped to those nine
files; settings.local.json, commands/, worktrees/ and the rest of the
plugin stay ignored.
Fifteen tests cover the renderer, and the ones that matter are the refusals: an
absent source is refused rather than called "nothing to do", a deleted copy is
reported missing rather than skipped, and frontmatter whose name disagrees with
its filename is refused outright.
15. A soak that could not write its samples still reported success
All three CSV writes in rust/tools/soak.sh were bare redirects. A write that
could not land left FAILS at zero, so the soak ran its full cycle count and
reported success over a sample file it had never produced — the failure mode this
repository has a standing rule against.
Each write now propagates. SoakCsvAdmission drives all three sites against both
a writable target and an unwritable one.
The test was checked against the pre-fix script before it was accepted: all
three blocks exit 0 where the test requires 1. It fails against the code it
describes, which is the only thing that separates a regression test from a
restatement of the fix.
16. The generated guide described the code and never mentioned the other sessions in the repository
DevMap answers what the code is. It has nothing to say about the other
worktrees, the running agent sessions, or the file two lanes are both holding —
and an agent that navigates the graph perfectly and then edits a file a sibling
lane owns has still collided. This consolidation found two such files contended
across two worktrees, which is the ordinary state of this repository rather than
an unusual one.
The generated agent guide and the Cursor always-apply rule now open with
gitpulse_insights and name the collision, change-context, provenance and ledger
tools, carrying the rule that GitPulse's facets fail independently: a facet that
could not scan is not a facet that came back clean.
DELEGATION_RULE exists for a narrower reason. A subagent reads the prompt its
parent wrote, not this file. Without a rule telling the parent to carry both
tools forward, the guide binds exactly one agent and every lane it spawns starts
by grepping.
The four tests quote the guide's wording literally instead of comparing against
the constants they cover. A test that reads the constant under test agrees with
whatever that constant says, which is precisely the property these have to be
able to falsify.
17. Learned ranking and lexical search for dc-grep
dc-grep now supports learned sparse ranking (SPLADE/WordPiece) alongside BM25 lexical ranking.
The indexer and query engines in rust/dc-grep and backend/go_orchestrator/dc/dcgrep provide:
- Dual-mode index support: standard inverted index with BM25 term weighting and learned vocabulary token weighting.
- WordPiece tokenization with strict token length and count caps to prevent runaway memory use on adversarial queries.
- Integrity verification and tamper resistance on persisted index postings.
- Seamless interop between the Go orchestrator child process and native Rust binary, pinned by comprehensive fuzz and unit tests (
learned_ranking.rs,ranked_search.rs, and Gointerop_test.go). - Complete architecture documentation in
docs/lexical-search.md.
18. Navigation nudge expansion for shell read commands
The navigation nudge hook in rust/devmap-cli/src/hook.rs sniffed shell search commands (grep, rg, find) but missed agents reading files sequentially via shell commands (cat, head, tail, sed, awk, bat).
However, naive detection of read commands caused false-positive nudges on pipe tails (e.g. npm test | tail -25) and configuration or secrets inspections (e.g. cat .env).
- Added
READ_COMMANDSwithsegment_reads_source()checking whether the targeted file matches known programming language extensions indevmap_extract::languages. - Preserved exclusion of destination files in redirects (
> file.rs) and piped summary commands.
19. Agent definitions synchronization and MCP prefix alignment
- Centralized agent definitions with automated sync across Claude Code (
.claude/agents/), the DevCouncil Claude plugin (.devcouncil/claude-plugin/), and Codex roles (.codex/). - Added
scripts/mcp-served-tools.mjsandnpm run agents:prefixesto dynamically map and expand tool grants across varying MCP server prefix configurations.
20. Raw logs on a work item, kept out of everything that rewrites text
A task's description is a drafting surface: items.enhance hands it to Manvi
to be rewritten, and briefs.get title-extracts from it. A stack trace pasted
there is therefore evidence handed to a paraphraser. GitPulse had been carrying
the fix as a local edit to its vendored copy of this crate, which its own
vendor gate reports as drift and which the next re-vendor would have silently
reverted; it belongs here.
logs is a separate field on a work item with its own bound and its own
handling:
- Bounded at 256 KiB, matching the terminal output window that produces the
paste, and the request envelope ininput.rsrises from 256 KiB to 1 MiB so
a task carrying a full-size log plus its own fields still fits. Both numbers
are named constants (MAX_LOGS_BYTES,MAX_REQUEST_BYTES) rather than
literals, because the pair only makes sense read together. - Preserved when omitted, exactly as
locked_fieldsalready is. An older
host that does not know the field must not wipe evidence a newer one stored;
only an explicit empty string clears it. - Stripped from list payloads.
json_removedrops$.logsalongside
$.descriptionand$.acceptance_criteriain the card query, so a board
listing does not carry a terminal dump per row. - Stripped from the enhancement source.
items.enhanceremoves$.logs
before building the input, so rewriting a title cannot send a log to Manvi.
This is the whole point of the field existing. - Fenced by measurement, not by assumption. Logs reach the brief's markdown
inside a code fence whose backtick run is computed from the longest run in the
text itself, so a log containing ``` cannot break out of its own fence.
Four tests in workbench/logs_tests.rs cover the round trip through
items.put/items.get/briefs.get, the preserve-on-omission and
clear-on-empty pair, refusal of NUL bytes, wrong types and oversize, and that
an enhancement source never carries logs.
Upgrade
No action required. The store schema is unchanged at 22, so a v0.2.2 store opens
as-is.
To pick up the first-navigation directive you must add the PreToolUse entry
above to your own host configuration by hand; devmap integrate will not write
it for you, by design. Cursor users get the equivalent automatically on
postToolUse the next time they run devmap integrate.
Verification
Run on macOS (Darwin 27.0.0, aarch64) via rust/verify.sh on the merged tree
with the 0.2.3 version bump in place. ALL GATES GREEN, VERIFY_EXIT=0.
The figures below are from the final run, on the fully merged tree described in
"Repository and install state" — not from the earlier run this section carried
at the v0.2.3 tag. Pass/fail is summed from the individual test result: lines
rather than read off the banner.
| gate | result |
|---|---|
| 1/9 format | cargo fmt --all --check clean |
| 2/9 clippy | --workspace --all-targets -D warnings clean |
| 3/9 tests | 3379 passed, 0 failed, 8 ignored, across 314 test binaries |
| 4/9 determinism | OK — two clean builds, identical graph digest 6f624b6b98b0… |
| 5/9 self-build | build 1724 ms · 1143 files · db 113 MiB cold · gate 196 MiB · peak RSS 621 MiB (measured, not assumed) |
| 6/9 memory model | OK — 666214 milli-B/candidate at cap (cap 1200000), above-cap ledger 5000, ratio −1% |
| 7/9 growth | repeated builds plateau, 2 generations retained |
| 8/9 incremental-vs-cold | equivalence OK over 5 cycles |
| 9/9 mutation | not run — optional, requires --mutants |
Gate 3 is the gate that moved: the same chain failed with VERIFY_EXIT=101
before section 11's fix, on exactly the two json_contract tests section 9
describes. That pair of runs is the evidence for both sections.
Go's side was run separately, since verify.sh covers only the Rust workspace:
go test -C backend/go_orchestrator ./... → 30 packages ok, 0 failed (three
more report no test files, one of them the new devcouncil/version, whose
behaviour is pinned by mcp/server_identity_test.go and
check-release.test.mjs instead).
Gate 9 is the one gate this release did not exercise. It is opt-in in
verify.sh and was not passed here, so nothing in these notes rests on a
mutation score. Gate 8 asserts equivalence across five cycles; it does not
assert growth, which is gate 7's job.
The release-identity gate passes independently:
node scripts/check-release.mjs --tag v0.2.3 → OK: product version 0.2.3 is consistent across package.json, package-lock.json,
rust/Cargo.toml [workspace.package], rust/Cargo.lock devmap-cli, and
version.go.
git check-attr text diff merge was run against representative source files
after the .gitattributes merge: text: unset, diff: unspecified,
merge: unspecified. Nothing in the tree is treated as binary, and the working
tree did not change on re-checkout.
What is not verified here
Every Windows claim in sections 1 and 2 rests on reasoning, not on a Windows
run. No Windows host was available. Specifically:
- The CRLF cause was reproduced on macOS by converting the fixtures, and the
reproduction matches the Windows log test-for-test and line-for-line. That is
strong evidence for the cause; it is not a green Windows job. absolute_lcov_paths_reduce_against_the_supplied_rootis the test that fails
against the pre-fix code, and it can only fail on Windows — the bug is in
path semantics that do not exist on Unix, so there is no macOS or Linux
reproduction to add. CI is the verification.- The six portability gates in section 2 were each matched to the error rustc
reported in the Windows log.fmt, the CI Clippy invocation, and
cargo test --workspaceare clean on macOS; that is what was run.
The symlink-walk fix in section 2 is the exception: its new test covers a
symlinked intermediate directory, which is reproducible on Unix, and was verified
to fail with the walk disabled and pass with it enabled.
Sections 7 through 11 were each verified directly, and two of them end to end on
the installed binary rather than on a test double:
- 7 —
devcouncil mcp-serverwas driven with a realinitializerequest
after installing, and answered
"serverInfo":{"name":"devcouncil","version":"0.2.3"}. The same field read
0.1.0before the change.check-release.mjsreportsOK: product version 0.2.3 is consistentwith the Go arm now in the comparison, and each of
dcstore,dcverifyanddcgrepanswers--versionwith
{"ok":true,"component":…,"version":"0.2.3"}. - 9 and 11 — the same
verify.shchain, run twice on the same tree:
VERIFY_EXIT=101with the two json_contract tests failing, then ALL GATES
GREEN withVERIFY_EXIT=0. The gate that could not run no longer reports
what a gate that ran reports, and the chain that could not pass now can. - 8 and 10 are covered by the suites that ship with them — the plugin bundle
byclaude_integrationandhook_conformance, the skill delivery contracts by
delivery_test.go,skills_cli_test.goanddevmap-cli/src/skills.rs— all
inside gate 3's 3379.
Repository and install state
Branches. main is the only local branch. The two lanes that existed at
v0.2.2 — claude/agitated-perlman-6308d8 (4 commits) and
claude/cranky-babbage-4c6cee (3 commits) — were merged with --no-ff and
deleted with git branch -d, which refuses anything not already merged. Both
were checked out in worktrees with live sessions, so those worktrees were
detached at their existing commit rather than removed; neither working tree
changed. origin/claude/agitated-perlman-6308d8 is left in place on the remote,
and main is left unpushed, as in previous consolidations here.
Recovery anchors (all reachable, none of this work is only in a working
tree):
| tag | what it holds |
|---|---|
safety/tip-main-20260914-v023 |
main at v0.2.2 (b366f42), before any merge |
safety/wip-main-20260914-v023 |
the 1930-line working-tree state the hook work came from, snapshotted before it was committed |
safety/tip-agitated-perlman-20260914-v023 |
that lane's tip, 128dfc1 |
safety/tip-cranky-babbage-20260914-v023 |
that lane's tip, 5847ebc |
Build hygiene. 24 GB of dead-lane build output was removed:
combined-lane/target-lane (20 G), combined-lane/target-win (320 M),
pretooluse-lane/target-lane (4 G). Each was confirmed cargo-produced
(CACHEDIR.TAG plus .rustc_info.json), untracked, ignored, not a symlink, free
of nested repositories, with no open files and no process holding it as a working
directory. Free space went from 90 GiB to 114 GiB on a 926 GiB volume.
Three target directories were deliberately left alone —
cranky-babbage-4c6cee/rust/target (36 G),
devmap-unresolved-calls-ff83ca/rust/target (25 G) and
agitated-perlman-6308d8/rust/target (1.1 G). Live sessions are working in all
three; a build cache in use is not a stale build.
Consolidation of 2026-09-15
Sections 7 through 11 landed after the v0.2.3 tag was cut. main is d5f1bb33.
Superseded by the second pass below. This paragraph originally read that
the tag stays atdea408e1as this document's pre-consolidation anchor. That
is no longer tenable:release taginscripts/ci-local.mjsrefuses a tag
that would publish this file as it stood at the tagged commit, which is the
defectv0.2.2was itself retagged for. See Retag below.
Loose work, committed before anything was merged. Two working trees held work
that existed on no branch — the state that costs a session a duplicate
reimplementation when it is cited as "on my branch":
| where | what | commit |
|---|---|---|
| the main checkout | section 7's version owner, 12 files / 511 insertions | 1b016590 |
devmap-gitpulse-integration-4916ac |
section 8's bundle, 805 insertions | d3cb407d |
Four other working trees also held uncommitted diffs, and none of it was
committed, because none of it was new. combined-lane (1869 lines) and
pretooluse-lane (839 lines) hold copies of the hook work already on main as
9ca9e27f — byte-identical, confirmed by diffing each dirty path against main
rather than against the lane's fork point. objective-edison-d2b058's store
change and its new test are both already on main. priceless-lichterman-63a07a
is the instructive one: it looks like 357 new lines and is in fact older than
main, missing both the #[cfg(unix)] on serving_or_dead and the
drain-until-settled loop in serve_stress. A lane's WIP is usually the
superseded half.
Branches. main is the only local branch again. Three were merged with
--no-ff and deleted with git branch -d, which refuses anything not already
merged:
| branch | tip | what it carried |
|---|---|---|
claude/devmap-gitpulse-integration-4916ac |
d3cb407d |
section 8 |
claude/compassionate-montalcini-7413e4 |
b0e0c05f |
section 9 |
claude/exciting-tesla-a472d4 |
49f69180 |
section 10 |
All three merged with zero conflicts, which is exactly when to go looking:
the contradiction they left is section 11, where two workflows had gained a build
step and the local chain had not. A clean auto-merge is not a verified one.
All three were checked out in worktrees with live sessions, so those worktrees
were detached at their existing commit rather than removed —
git update-ref --no-deref HEAD <same sha> against each worktree's own git
dir, which moves the ref without touching the index. All three trees reported
zero changed files afterwards. main was updated from the lane that carried the
merges via receive.denyCurrentBranch=updateInstead, which refuses a dirty
target and so proves the main checkout was in sync; git worktree list confirmed
the primary tree moved with it.
Recovery anchors. None were created, and none are needed: every deleted
branch tip is an ancestor of main, and v0.2.3 (dea408e1) anchors main as
it stood before this consolidation. Nothing here survives only in a working tree.
origin/claude/agitated-perlman-6308d8 is still in place on the remote and
main is still unpushed, as in previous consolidations here.
Build hygiene. 62 GB of stale build output was removed, and this corrects the
paragraph above it. Those three target directories were left alone at the tag on
the stated grounds that live sessions were working in them. Re-checked today,
that was no longer true of any of them: lsof +D reported no open files under
any, no process held one as a working directory, no cargo or rustc was
running anywhere, and each worktree's detached HEAD is an ancestor of main.
| removed | size |
|---|---|
cranky-babbage-4c6cee/rust/target |
36 G |
devmap-unresolved-calls-ff83ca/rust/target |
25 G |
agitated-perlman-6308d8/rust/target |
1.1 G |
~/.cargo/bin/devmap |
63 MB |
Free space went from 90 GiB to 150 GiB on a 926 GiB volume.
The ~/.cargo/bin/devmap entry is the one worth naming. It reported build dea408e14d42 — a generation behind the installed one — and was shadowed in
PATH by ~/.local/bin, so it could not be reached by name and could only be
reached by something that went looking for it. That is precisely the failure
internal/mapcli carries two comments about: a stale ~/.cargo/bin install
answering for a binary the operator had named. Nothing in the tree reads that
path today — the two references are historical comments, and
TestAnExplicitBinaryThatFailsTheProbeIsRefusedNotSubstituted points PATH at
its own temp directory — so removing it retires the hazard without retiring a
caller.
Two target directories were kept: compassionate-montalcini-7413e4 and
devmap-parallel-processing-43118a (478 M each). Both still have live sessions,
and a build cache in use is not a stale build.
Install. Built from main at d5f1bb33 into ~/.local/bin, release
profile.
| binary | source | reports |
|---|---|---|
dcstore |
scripts/install-components.sh |
{"ok":true,"component":"dc-store","version":"0.2.3"} |
dcverify |
same | {"ok":true,"component":"dc-verify","version":"0.2.3"} |
dcgrep |
same | {"ok":true,"component":"dc-grep","version":"0.2.3"} |
devmap |
same | devmap 0.2.3 (store schema 22, code graph schema 2) |
devcouncil + dev |
scripts/install.sh host |
devcouncil 0.2.3 |
All four components reported healthy from the installer's own health check,
and it confirmed that every one of them resolves from ~/.local/bin. The host
was re-signed on install (replacing existing signature), which is the step that
keeps macOS from SIGKILLing a binary overwritten in place.
Consolidation of 2026-09-15, second pass
One branch, the loose work in the main checkout, and the retag the first pass
deferred.
Loose work, committed before anything was merged. The main checkout held the
DevMap screenshot and the two documents that reference it — README.md and
docs/devmap/README.md, both of which described the dependency map in prose and
showed nothing. Committed as b3c239ec before the merge, so a merge conflict
could not be confused with uncommitted work.
Branches. main is the only local branch again, and now the only branch on
the remote.
| branch | tip | what it carried |
|---|---|---|
claude/dcverify-devcouncil-comparison-8af040 |
19c33031 |
section 12 |
It merged with zero conflicts, which by this document's own standing rule is
when to go looking rather than when to relax. What the look found was not in the
merge: release tag in scripts/ci-local.mjs was already red on main, and had
been since the first pass chose to leave the tag at dea408e1. See Retag.
The worktree was checked out with a live session, so it was detached at its
existing commit rather than removed — git update-ref --no-deref HEAD <same sha>
against the worktree's own git dir, which moves the ref without touching the
index — and it reported zero changed files afterwards. The branch was then
deleted with git branch -d, which refuses anything not already merged.
origin/claude/agitated-perlman-6308d8 was deleted at the operator's explicit
instruction, reversing the standing decision recorded in the first pass. It
carried zero commits that were not already in main (128dfc1b,
fix(verify): stop the symlink walk statting the drive prefix on Windows), so
nothing survived only there. origin now carries main alone. main itself
remains unpushed, 24 commits ahead.
Recovery anchors. safety/v0.2.3-at-dea408e1-before-retag pins the commit
the tag named before this pass. Beyond that none are needed: every deleted
branch tip is an ancestor of main.
Retag. v0.2.3 moved from dea408e1 to the tip of this pass. The first pass
kept it in place as a pre-consolidation anchor; that is not compatible with the
gate this repository added in v0.2.2 — catch a tag that would publish notes
older than itself. Sections 7 through 12 and both consolidation records live in
this file, and a tag at dea408e1 would have published the version of it that
predates all of them. The tag was never pushed (origin carried v0.2.0,
v0.2.1 and v0.2.2 only), so moving it rewrote nothing anyone else holds. The
anchor above preserves what it named.
Build hygiene. Four worktree rust/target directories were removed, about
21 GB. Free space went from 109 GiB to 129 GiB.
| removed | size |
|---|---|
merge-branches-update-builds-e69010/rust/target |
19 G |
dcverify-devcouncil-comparison-8af040/rust/target |
1.1 G |
compassionate-montalcini-7413e4/rust/target |
478 M |
devmap-parallel-processing-43118a/rust/target |
478 M |
This reverses the first pass's rule that "a build cache in use is not a stale
build" for the two 478 M trees it kept on that ground, and the reversal is
narrower than it looks: what was checked this time was not whether a session
exists but whether a build does. No cargo or rustc was running anywhere,
and every one of the twelve worktree HEADs was confirmed an ancestor of main
before anything was deleted. Two of the four directories still have live
sessions with a working directory inside them; those sessions keep their source,
their index and their shell, and pay a cold rebuild if they build again. No
worktree was removed.
Install. Built from main at the tip of this pass into ~/.local/bin,
release profile.
| binary | source | reports |
|---|---|---|
dcstore |
scripts/install.sh |
{"ok":true,"component":"dc-store","version":"0.2.3"} |
dcverify |
same | {"ok":true,"component":"dc-verify","version":"0.2.3"} |
dcgrep |
same | {"ok":true,"component":"dc-grep","version":"0.2.3"} |
devmap |
same | devmap 0.2.3 (store schema 22, code graph schema 2) |
devcouncil + dev |
same | devcouncil 0.2.3 |
All four components reported healthy from the installer's own health check,
and it confirmed that every one resolves from ~/.local/bin. The host was
re-signed on install (replacing existing signature).
Plugin bundle. The installed Claude bundle was a release behind the binary —
cache devmap-local/devmap/0.2.2 against devmap 0.2.3 — which is the state the
session brief reports as installed plugin version does not match binary. The
bundle source already declared 0.2.3; it was the install that had not been
refreshed. devmap claude plugin rewrote 4 of 13 files and claude plugin update devmap@devmap-local moved the install to 0.2.3. The superseded cache
directory was removed.
Consolidation of 2026-09-16
Sections 13 through 16 landed after the second pass. The code is f84b06d9 —
five commits and one merge above f7af3294, 26 files, 1167 insertions, 78
deletions — and main is one commit further, at this record.
Every branch was already merged before this pass began. git branch --no-merged main was empty and the one other branch,
claude/angry-payne-5ee611, sat on main's exact commit. The work this pass
consolidates was therefore not on any branch at all — it was uncommitted, in six
working trees. That is the state this repository keeps rediscovering, and it is
why the first question here is never "which branches are unmerged".
Loose work, committed before anything was merged.
| where | what | commit |
|---|---|---|
| the main checkout | section 15's soak admission | cae8469e |
| the main checkout | section 16's guide step and delegation rule | e112bd21 |
| the main checkout | a gofmt-only fix under policy/ |
07dc75ca |
devmap-unresolved-calls-ff83ca |
section 13's next-tools fix | 753dd210 |
devmap-unresolved-calls-ff83ca |
section 14's generated agent definitions | ce7bccef |
Four other trees held uncommitted diffs, and none of it was committed, because
none of it was new. The test is not the size of the diff — combined-lane
showed 1869 changed lines and pretooluse-lane 839 — but whether main already
contains it. Each dirty path was compared against main rather than against the
lane's own fork point, and then confirmed a second way, by checking whether any
function in the lane is absent from main:
| lane | apparent diff | hook.rs / subject lines vs main |
symbols missing from main |
|---|---|---|---|
combined-lane |
1869 lines | 3071 vs 3131 | none |
pretooluse-lane |
839 lines | 2779 vs 3131 | none |
priceless-lichterman-63a07a |
7 files | 4452 vs 4457 (daemon.rs) |
none |
objective-edison-d2b058 |
2 files | byte-identical to main |
none |
main is larger in every file and contains every symbol; combined-lane's
hook_conformance.rs carries 27 tests against main's 31. Two independent
signals agreeing is what makes this a deletion decision rather than a guess. A
lane's WIP is usually the superseded half — this is the third consolidation in a
row where that held.
All four were snapshotted anyway, via git stash create, which writes a commit
object without touching the working tree or the stash list:
safety/wip-combined-lane-20260916-consolidation,
safety/wip-pretooluse-lane-20260916-consolidation,
safety/wip-objective-edison-d2b058-20260916-consolidation,
safety/wip-priceless-lichterman-63a07a-20260916-consolidation.
Branches. main is the only local branch. claude/angry-payne-5ee611 was
merged with --no-ff and deleted with git branch -d, which refuses anything
not already merged. git branch --no-merged main is empty as the last step, not
the first — a sibling session created a branch twenty minutes into a previous
consolidation.
The merge was clean, with zero conflicts, which is when to go looking rather
than when to relax. The repository was swept for the claim the merge could have
falsified: every remaining mention of the six retired tool names was read, and
all of them are explaining the retirement rather than advertising the tools.
IMPROVEMENTS.md still names devcouncil_get_task_provenance, correctly — it is
a Python-era changelog describing what that host did at the time.
Recovery anchors.
| tag | what it holds |
|---|---|
safety/tip-main-20260916-before-lane-merge |
main at f7af3294, before this pass |
safety/tip-angry-payne-20260916 |
that lane's tip, ce7bccef |
safety/v0.2.3-at-f7af3294-before-retag |
the commit v0.2.3 named before this pass |
Worktrees. Fourteen existed, counting the main checkout. Twelve of the
thirteen others were removed. All twelve were confirmed to have a HEAD that is
an ancestor of main, and lsof -a -d cwd confirmed no process held any of them
as a working directory — ps cannot answer that question, which is why the sweep
is by open file descriptor.
The thirteenth, devmap-unresolved-calls-ff83ca, has a live Claude session in it
— fifteen processes, including its own MCP stack — so it was detached rather
than removed: git update-ref --no-deref HEAD <same sha> against that worktree's
own git dir, which moves the ref without touching the index. Verified afterwards
at the same commit, with zero changed files and an unchanged mtime on the file it
had most recently written. That session keeps its source, its index and its
shell.
Build hygiene. rust/target/debug was removed: 48 GB, of which
deps/ was 35 G and incremental/ 11 G. Free space went from 81 GiB to 128
GiB on a 926 GiB volume (91% to 86%).
The figure that actually describes the end state is lower, and is given here
rather than left as the flattering one: the verification below then rebuilt
target/debug from cold to 18 G, so the volume settled at 110 GiB free
(88%). The lasting reclaim is the ~30 GB of accumulated stale artifacts that
no current build re-creates, not the full 48.
It was confirmed cargo-produced (CACHEDIR.TAG and .rustc_info.json at the
target root), untracked, ignored by rust/.gitignore, free of symlinks, and with
no process holding it open or as a working directory. The two nested Git
repositories under rust/target/tmp are dc-verify JSON-contract fixtures and
were left in place, along with target/release.
The cost is stated rather than elided: the next cargo test or verify.sh run
pays a full debug rebuild. That is also why this removal came after the release
build and install, and it is the reason the verification below rebuilt from
nothing.
~/.claude/plugins/cache/devmap-local/devmap was already at 0.2.3 and needed no
refresh. The untracked DevCouncil plugin manifest under
.devcouncil/claude-plugin/ still declares 0.2.0; it is local scaffolding
outside the release identity, and check:release passes without it.
Install. Built from main at f84b06d9 into ~/.local/bin, release
profile, via scripts/install.sh.
| binary | reports |
|---|---|
devmap |
devmap 0.2.3 (store schema 22, code graph schema 2, build f84b06d9c1db) |
devcouncil + dev |
devcouncil 0.2.3 |
dcstore |
{"ok":true,"component":"dc-store","version":"0.2.3"} |
dcverify |
{"ok":true,"component":"dc-verify","version":"0.2.3"} |
dcgrep |
{"ok":true,"component":"dc-grep","version":"0.2.3"} |
All four analysis components reported healthy from the installer's own health
check, and it confirmed every one resolves from ~/.local/bin. The host was
re-signed on install. devmap's build stamp is the decisive line: it read
f7af3294a8de before this pass and reads f84b06d9c1db after, so the installed
binary is this tree and not the previous one.
Verification. rust/verify.sh on the merged tree, from a cold target/ —
ALL GATES GREEN, VERIFY_EXIT=0, all nine gates reached. 317 suites, 3413
passed, 0 failed, 8 ignored, summed from the individual test result: lines
rather than read off the banner. Gate 9 (mutation) is opt-in and reported
skipped; it was not run, and is not being counted as passed.
Alongside it, on the same tree: the Go host at 27 packages ok with a real exit
of 0; node --test at 51 of 51; tests/unit at 19 passed with 21 subtests; and
npm run agents:check reporting the three agent definitions in sync.
Both exit codes above were read from the command itself rather than from a
pipeline. The first attempt at the Rust suite piped through tail, which reports
tail's status and shows only the last sixty lines — enough to hide a failed
suite entirely. That run was discarded and re-taken.
Retag. v0.2.3 moved from f7af3294 to the tip of this pass, for the same
reason the second pass moved it: tagNamesHead in scripts/check-release.mjs
refuses a tag that would publish docs/releases/v0.2.3.md as it stood at the
tagged commit, and a tag at f7af3294 would publish this file without sections
13 through 16 or this record. The tag therefore names this document's own commit
rather than f84b06d9, which is the last commit that changed a binary; the two
differ by documentation only. The tag has never been pushed — origin carries v0.2.0, v0.2.1 and
v0.2.2 only — so moving it rewrites nothing anyone else holds.
safety/v0.2.3-at-f7af3294-before-retag preserves what it named. main is left
unpushed, as in every previous consolidation here.
Consolidation pass (2026-09-17)
Hygiene & Stale Builds. Removed stale debug and testing build artifacts:
rust/target/debug(~26 GB)rust/target/tmp(~232 KB)rust/target/manvi-testbin(~140 MB)rust/mutants.out(~30 MB)- Detached worktree
.claude/worktrees/devmap-unresolved-calls-ff83capruned. - Confirmed no secondary branches exist (
mainis the sole local and remote branch).
Total disk space reclaimed: ~28 GB (bringingrust/targetfrom 29.3 GB down to 1.3 GB).
Release Notes & Changes.
- Recorded sections 17 (learned ranking and lexical search for
dcgrep), 18 (shell read-command navigation detection indevmap-clihook), and 19 (agent definitions sync and MCP served tools expansion). - Recorded section 20: Universal Version and Component ID Hardening across
dcstore,dcverify,dcgrep,devmap, anddevcouncil.
20. Universal Version & Component ID Hardening across dcstore, dcverify, dcgrep, devmap, and devcouncil
Component identity (id) and product version (version) are now uniformly reported across all DevCouncil analysis and host binaries:
- Universal JSON reporting: When invoked with
--version,-V,-v, or subcommandversion, every binary outputs a standardized JSON payload:dcstore:{"ok":true,"id":"dcstore","component":"dc-store","version":"0.2.3"}dcverify:{"ok":true,"id":"dcverify","component":"dc-verify","version":"0.2.3"}dcgrep:{"ok":true,"id":"dcgrep","component":"dc-grep","version":"0.2.3"}devmap version --json:{"ok":true,"id":"devmap","component":"devmap","version":"0.2.3","store_schema":22,"code_graph_schema":2,"build":"..."}devcouncil version --json:{"ok":true,"id":"host","component":"devcouncil","version":"0.2.3"}
- Universal flags & subcommands:
dcstore,dcverify,dcgrep, anddevcouncilaccept--version,-V,-v, andversion.dcstoreversion requests execute immediately without requiring--dbor touching SQLite.dcverifyversion requests do not block on stdin or evaluate empty diffs.dcgrepversion requests do not block on stdin for search queries.devmapacceptsdevmap version(printing the human version line) anddevmap version --json/devmap --json version.
- Health check enhancements:
dcstore health: includes"id":"dcstore","component":"dc-store","version":"0.2.3"while preserving"store","schema_version","exclusion_index", and"active_leases".dcverify health: includes"id":"dcverify","component":"dc-verify","version":"0.2.3"while preserving"verifier","schema_version", and"evidence_schema_versions".dcgrep health: includes"id":"dcgrep","component":"dc-grep","version":"0.2.3"while preserving"searcher","schema_version","engine","index_engine","ranked_engines", and"ranked_vocabularies".
- Go client polymorphism:
- In
backend/go_orchestrator/dc/store/store.go,type responseimplemented customUnmarshalJSONto decode polymorphicidvalues (string"dcstore"fromhealthand integer IDs fromevidence-append), eliminating unmarshaling failures while preservingEvidenceID int64.
- In
- Pre-install verification:
scripts/install-components.shupdated to verify both--versionandhealthresponses report valididandversionbefore installing binaries.
21. WordPiece Tokenizer Conformance & Lexical Search Concurrency Hardening
- WordPiece Tokenizer Unicode Normalization:
- Replaced ad-hoc transliteration tables and punctuation character ranges with canonical Unicode normalization (
unicode-normalization) and standard character categories (unicode-properties). - Resolves a 22% divergence on accented Latin and combining marks compared to HuggingFace's BertTokenizer reference implementation.
- Added full 30,522-token conformance test suite (
tests/fixtures/wordpiece-conformance.jsonlandwordpiece-vocab.txt) covering 14,810 test vectors across 30 Unicode ranges, asserting exact parity against the model tokenizer. - Confined known differences to
TABLE_SKEW(98 codepoints assigned in Unicode 10–14 where the reference emits[UNK]), validated bidirectionally inthe_tokeniser_reproduces_the_model_s_own.
- Replaced ad-hoc transliteration tables and punctuation character ranges with canonical Unicode normalization (
- Search vs List Limit Separation:
- Separated match line sampling (
max_results = 5,000) from repository file listing (max_list_results = 200,000). - Increased Go client
MaxListResultsto 200,000 and expanded child stdout buffermaxListOutputto 64 MiB, ensuring full repository scans do not encounter truncation. - Extended
dcgrep healthto report"limits": {"max_results": 5000, "max_list_results": 200000}.
- Separated match line sampling (
- Index Reader Publishing Concurrency Protection:
- Readers encountering an actively writing publisher wait briefly for slot publication (
WRITER_LOCK_SETTLES), eliminating spurious "unindexed" error returns during concurrent search queries.
- Readers encountering an actively writing publisher wait briefly for slot publication (
- Posting Ceiling Bounds:
- Enforced a 32,000,000 posting limit on sparse encodings in
dcgrep index, preventing memory exhaustion on pathological candidate sets. - Added
--recordand parity verification flags toscripts/encode-sparse.py.
- Enforced a 32,000,000 posting limit on sparse encodings in
- Repository Hygiene & Ignore Hardening:
- Updated workspace
.gitignoreacross Go and Rust trees to cover modern IDE workspace folders, agent scratch environments, coverage artifacts, and nested index caches (/.devcouncil/).
- Updated workspace
Install & Tagging.
Native binaries built and installed via scripts/install.sh to /Users/bharath/.local/bin:
dcstore:{"ok":true,"id":"dcstore","component":"dc-store","version":"0.2.3"}dcverify:{"ok":true,"id":"dcverify","component":"dc-verify","version":"0.2.3"}dcgrep:{"ok":true,"id":"dcgrep","component":"dc-grep","version":"0.2.3"}devmap:devmap 0.2.3 (store schema 22, code graph schema 2)/{"ok":true,"id":"devmap",...}devcouncil:devcouncil 0.2.3/{"ok":true,"id":"host","component":"devcouncil","version":"0.2.3"}
Tagv0.2.3updated at HEAD.
22. Markup and Stylesheet Indexing (DevMap)
The reported gap. On a current index of GitPulse (149 Svelte components),
devmap search toggleAddMenu returned the <script> function while
devmap search data-add-repo and devmap search nav-heading returned 0 hits with
truncated=false — an empty answer, on a built index, about two names
TaskBoard.svelte plainly declares. The cause was structural rather than
Svelte-specific: devmap-extract routed only <script> regions back through a
grammar, and html/css sat in fallback::NON_DECLARATIVE_LANGUAGES, so the
markup and <style> halves of every template file, and the whole of every
standalone .css and .html file, were indexed nowhere.
- New reader (
devmap-extract/src/markup.rs), serving.svelte,.vue,
.astro,.liquid,.htmland.css/.scss/.lessfrom one owner:SymbolKind::MarkupAnchor— an identity markup declares (id="x"→#x, a
data-*attribute →[data-x]).SymbolKind::StyleRule— a name a stylesheet declares: each named component
of each selector,@keyframes, and custom properties.ReferenceKind::Selector— a use: aclassattribute, a Svelteclass:
directive, a spec-defined IDREF attribute (for,aria-controls,
aria-labelledby, …), avar(--x)read, and a selector string in a script.- Names carry their sigil, so the FTS
unicode61tokenizer answers
data-add-repoandnav-headingwith no store or schema change.
- Resolution is a separate namespace with its own two-rung ladder. A
Selectornever enters the code ladder: its unique-global rung would bind a
class calledmenutofunction menuatHIGHconfidence. Rungs are
this file (SameFile, deterministic) and exactly one declaring file
(Resolution::UniqueSelector, persisted under the existingUniqueGlobal
label, so no schema or stored generation changes meaning). Cross-file resolution
additionally requires the declaration to be global (.css/.html) or the name
to be anid— a component's<style>is scoped, so a class it declares cannot
style another component. - Neither new kind is a dead-code candidate. A class reaches an element
throughclass={expr},classList.add(name)or a template string, none of
which this reader follows: it can prove a name is used and cannot prove one is
not. DevMap therefore does not report unused CSS, and says so. - Unresolvable selectors are not filed as failed code attributions. A class
whose rule lives in a global stylesheet outside the tree, a framework, or a CDN
is the ordinary frontend case; 0 selector rows reachgeneration_unresolved.
Measured (GitPulse frontend, 933 files). 921 new symbols (688 StyleRule,
233 MarkupAnchor) on a 7,355-symbol graph, matching the 135-id/94-hook/686-rule
estimate taken from source beforehand; 1,301 new References edges (632
SameFile, 669 cross-file, 668 of those into the global src/app.css); 0
selector rows in the dead-symbol ledger; 0 in the unresolved ledger; 0 parse
failures. On DevCouncil itself the change is inert outside real markup: 433
symbols, all four from the repository's only HTML files.
Five defects found by the new stress corpus and by inspecting the real graph,
each fixed:
- Uses from the
.css/.htmlpath carried noenclosing_symbol, leaving their
edge source to whichever consumer read them next. - The close-tag search lowercased the rest of the file per
<script>element —
O(n²) time and O(n) allocation each on a page with a thousand of them. - An attribute name was folded to lowercase in the markup reader and not in the
stylesheet reader, so the graph held a name absent from the file and the two
halves of one contract silently stopped joining. Both readers now fold
attribute names (HTML matches them ASCII case-insensitively) and neither folds
a class, an id or a custom property (those are case-sensitive). - Three of four cross-component edges on the real corpus were wrong:
.selected,
.sheet-bodyand.status, each declared in one component's scoped<style>
and named by a different component's markup. 672 → 669 cross-file edges, with
the one correct cross-component edge (a document-global#idnamed by
aria-controls) preserved. - Moving
html/cssout of the non-declarative list made a page with no ids
reportExtractionEngine::Unavailable— i.e. a parse failure, the K5 defect
that once counted 294 of 1,310 files and buried the 16 real ones.
fallback::has_dedicated_readernow distinguishes a reader ran and found
nothing from a grammar was expected and missing.
Verification. 31 new tests: 10 in devmap-extract/tests/markup_and_style.rs,
9 in devmap-extract/tests/markup_under_stress.rs, 11 in
devmap-resolve/tests/markup_selectors_resolve.rs, and one in fallback's own
module pinning that the line scanner no longer runs over a page or a stylesheet. The
behaviour tests were confirmed to fail against the pre-fix code by disabling the
merge — 6 of 8 went red, and the two that stayed green were strengthened, one
having passed vacuously over an empty list. The stress corpus asserts extraction
returns inside a bound, every span lies inside the source and on a character
boundary, identical bytes give identical answers, every emitted name is present in
its file, and every cap discloses what it cut; 48,000 fuzz cases over 12 seeds
(DEVMAP_MARKUP_FUZZ_SEED) plus markup nested 100,000 deep, a minified stylesheet,
a thousand <script> elements, and unterminated tags, strings, comments and
<style> blocks. probe.html/probe.css in testdata/capabilities were extended
so the newly declared REFERENCES capability is exercised rather than asserted
vacuously; html/css keep LivenessUnit::Data, which is what stops a
stylesheet nothing imports becoming a delete-this candidate.
23. Dead-Code Audit: every confident finding on this repository was false
With the markup reader landed, devmap --json dead was run over DevCouncil itself
and every finding in the confident tier (0.9) was checked against source. All
eight were false positives, from two unrelated causes, and both are fixed here.
The confident tier is the one a reviewer is meant to act on, so a false positive in
it costs more than a missing finding: it proposes deleting live code.
Six were Python module-level values mistaken for re-export aliases.
python_module_aliases exists so TestEvidence = VerificationEvidence survives the
__all__ filter — it is a name twenty other modules import, and dropping it left
those bindings resolving against a file that, as far as the resolver could see,
declares nothing of the sort. Its guard on the right-hand side was
matches!(right.kind(), "identifier" | "attribute"), which asks only what the
outermost node is. OUT = Path(__file__).resolve().parent / "out" is an
attribute whose object is a call: it passed, was emitted as a symbol, and then
had no possible caller, because nothing can import it. Measured on this repository
before the fix, 54 of 54 Python Variable symbols were computed values of this
shape. After: 3, and all three are real aliases — SEM = S.SEM,
PROJECT = S.PROJECT, DEFAULT_MONITOR_MODEL = DEFAULT_OPENROUTER_MODEL.
is_python_dotted_name now walks the object chain and admits a node only if every
step of it is a name, iteratively, because an adversarially deep a.b.c.… must not
overflow the stack.
Two were Go functions used as values rather than called.
Command{Run: runStatus} never writes runStatus(...). The extractor records the
mention as a bare ReferenceKind::Name, and resolve_reference returns None for
a bare Name rather than continuing down the ladder — deliberately, because the
unique-global rung would bind except Exception as e to some unrelated def e. The
Go package rung sits below that refusal and its comment says as much in as many
words, so the fix is on the liveness side, where the epistemic state can be stated,
rather than by widening a ladder whose own documentation forbids it.
GO_VALUE_MENTION_REASON exempts a SymbolKind::Function when some other file
of its own Go package names it in value position with no receiver. Each clause is
load-bearing and tested: the declaring file is excluded because a same-file mention
is answered by the same-file rung and is live by an edge, with no exemption involved;
a receiver means the reference is a member access (cfg.runDoctor names a struct
field); the package join is exact by Go's own visibility rule, since an unexported
name resolves only within its directory and an exported one is spared earlier; and
methods are excluded because a method is reported under Type.Method, so matching a
bare mention would mean stripping the type, which over-exempts every same-named
method of every type in the package.
The one over-approximation is named and tested rather than left to be discovered:
Go spells a composite-literal field key as a bare identifier with no receiver, so
Command{run: nil} is indistinguishable at this layer from naming a package-level
func run. An unexported function colliding with a field key is therefore exempted
although nothing uses it. That is the direction an exemption may be wrong in — it
withholds a finding it cannot prove rather than asserting one it cannot support —
and narrowing it belongs in the extractor, where a field key could carry its
composite type as a receiver.
Scope is Go on purpose, not by omission. Outside Go, a cross-file use requires an
import, so the same-file and import-scoped rungs already answer it; Go's package
block is the construct that makes a cross-file mention neither. Whether Kotlin's
top-level functions have the same gap is unmeasured and is not claimed here.
Result on this repository: confident non-exempt dead findings 8 → 0. Total rows
48 → 46, every remaining row at 0.4 — the degraded tier, which already says the
answer is not confident. Repository-wide, the new exemption fires exactly twice, on
runDoctor and runStatus in internal/mapcli/commands.go, with no collateral
exemption anywhere else.
Verification. 10 new tests. a_computed_module_binding_is_not_an_alias pins the
two Python shapes apart in one file, so a future widening of the guard cannot pass by
only checking that real aliases still survive. The nine in
devmap-analyze/tests/go_function_value_liveness.rs were confirmed to fail against
the unmodified analyzer; besides the subject and each guard clause, they include an
order-independence property — the same corpus walked forwards and reversed must give
a byte-identical dead list, since the mention map is a HashMap — and a boundary
test that a file with no package clause cannot join the ("", "") package and spare
a namesake in every other such file.
24. Consolidation pass (2026-09-19): one branch, and a contradiction the merge could not raise
Two branches and two worktrees went into this pass; one branch and one worktree
came out. lane/devmap-markup-index carried the three DevMap changes recorded
in sections 22 and 23 — markup and stylesheet indexing, the Python alias guard,
and the Go value-mention exemption — and both checkouts also held uncommitted
work that had never been recorded anywhere.
The merge was textually clean and that was the problem. The lane's file set
and main's were disjoint: the lane touched devmap-* and the release notes,
main touched the Go host, dc-store and the capabilities corpus. Git had
nothing to resolve. But main's uncommitted tree held a rewrite of
internal/mapcli dispatch whose stated purpose — in its own comment — was that
"the code graph can see that status and doctor run": it deleted
Run: runStatus and Run: runDoctor from buildRegistry and replaced them
with a switch that calls both by name. That is the same defect section 23
fixes, addressed one layer lower, and merging both would have shipped the
workaround and the fix while quietly falsifying section 23's own claim, since
the workaround removes the only two value-position mentions in the repository.
Resolved by measurement, not by preference. With the original registry
restored, an index built from this commit reports runStatus and runDoctor
with zero indexed callers, and neither appears in the dead list; repository
wide that list is 46 rows, every one at confidence 0.4, with zero
confident non-exempt findings. The exemption is therefore doing the work the
workaround was written to do, at the canonical owner, and the workaround is
redundant. It was retired rather than deleted: the tag
retired/mapcli-analyzer-workaround-20260919 holds it, and
git show retired/mapcli-analyzer-workaround-20260919 recovers it whole.
Keeping the analyzer fix is also the cheaper invariant. The workaround
duplicated the registry — every new subcommand would have needed an entry in
buildRegistry and an arm in runCommand, with nothing enforcing that the
two agree — and it made Command.Run nullable, turning a shape the compiler
used to guarantee into a runtime internal: %s has no runner.
Also in this pass. The host gained a grep subcommand, the first production
inbound to dc/dcgrep, which until now no production path imported. A missing
binary there exits non-zero and names the binary rather than printing an empty
result, because a search that could not run must never read as a search that
found nothing. runs.prepare now accepts grok and agy alongside codex
and claude. The capabilities corpus gained a lockfile probe, so the one JSON
shape the liveness filters name explicitly has a fixture standing behind its
exclusion.
Two gates were red and are now green. verify.sh --quick step 1/9 is
cargo fmt --all -- --check, and it failed on nine files. Seven arrived with
the markup merge, which was never formatted on its lane. Two —
devmap-cli/src/hook.rs and dc-verify/tests/component_version.rs — were
already red on main before this pass and are untouched by it; the second is the
exact sibling of the dc-store copy reformatted earlier in the same series, so
what was one instance is now the closed class. Step 2/9 then caught a
single-argument concat! in the new liveness test — a real -D warnings
failure under clippy::useless_concat, not a formatting one.
Verified. verify.sh --quick steps 1/9 through 3/9 pass: 323 suites,
3560 tests, 0 failed, 8 ignored. The count moved by exactly the four
workbench/logs_tests.rs tests added in section 20; re-run on the tree
carrying them. Steps 4/9–9/9 (determinism, self-build,
memory-model, growth, incremental equivalence, mutants) are skipped by
--quick by design and did not run in this pass; they are unverified here
rather than passing. go test ./... across the orchestrator passes, as do the
66 Node script tests, workflow lint over 6 files, the agent-definition sync
check, release identity, and npm pack.
Reclaimed. Removing the merged lane worktree returned 26 GiB, almost all
of it that checkout's own rust/target, plus 132 MB of orphaned install
snapshots from 2026-09-11/12 that the installer has no retention logic for and
that two later installs had already superseded. Main's rust/target, the shared
cargo registry and the Go build cache were left intact: they are valid caches
with their own expiry, and clearing them would only buy space back at the cost
of the next build.
An existing store needs devmap build --full to see the Python fix. The
alias guard of section 23 lives in the extractor, and an incremental build
reuses cached extractions for every unchanged file — devmap build --manifest
on this repository reported 1171 unchanged · 1124 cached and re-parsed five
files. The 1171 kept the symbols a pre-fix binary emitted, so the freshly
installed kernel still answered with 4 confident (0.9) findings, all of the
exact shape the fix removes: OUT and HERE in four
benchmarks/results/competition/** scripts, each a
Path(__file__).resolve().parent / … computed value. A cold
devmap build --full --manifest over the same tree returns the 46-row, all-0.4
answer this release claims.
This is worth stating plainly because the incremental answer is not marked as
degraded anywhere: generation #2982 reported the same envelope shape as
generation #2984 and differed only in the findings. An extractor change is not
retroactive over a cache, so after installing this version the first build
should be --full; every build after that can go back to incremental.
25. Consolidation pass (2026-09-20): no branches, and 46 files of uncommitted work
There was nothing to merge. main was the only branch — locally, and on
origin after a --dry-run fetch confirmed nothing new. The consolidation work
this time was not branch reconciliation but the working tree: 46 uncommitted
files, 6,629 insertions and 1,667 deletions, which gitpulse_insights
reported across one worktree with zero contended files and zero live agent
sessions. All of it is now committed in seven commits, each one concern:
-
refactor(contracts)— HEAD carried the schema contract twice.
backend/contracts/was current and a rootcontracts/was left behind by an
incomplete move, having missed therust-port→rustrename and two rule
IDs the policy package emits, so the two copies disagreed about the
vocabulary this repository shares with Manvi. Nothing read the stale copy:
policy.contractsDirresolves../../contractsfrom
backend/go_orchestrator/policy, which lands onbackend/contracts, while
its comment claimed the directory sat at the repository root and that the
package wasmanvi/policy— both false since the move. Two errors that
cancelled, so the test passed while documenting a layout that no longer
existed, which is exactly why the duplicate survived unnoticed. −1,512 lines. -
refactor(safefile)—dc/dcgrepanddc/dcverifyeach carried a
byte-identicalvalidPath: the same five containment rules at two subprocess
boundaries. The failure mode of a duplicated containment check is not a wrong
answer but a silent asymmetry — tighten one and the other keeps the old rule.
safefile.ValidRepoPathis now the one owner; both call sites become thin
adapters, error text preserved verbatim. -
docs(testsupport)—allow-unwireddeclared, including the half no
single-repository analyzer can see: Manvi symlinks these crates and drives
the same helpers from its own tree, so "nothing in DevCouncil depends on
this" is true and is not evidence the package is stranded. -
refactor(store)—latest_generation_headand
latest_generation_head_shawere two SQL reads of one question, and had
drifted: one carried a stray#[cfg(feature = "parse")]on a pure SQL read,
so an embedder linking withdefault-features = falsecould reach the head
through one name and not the other. -
fix(guides)— the generated guide told agents to write
.devcouncil/codeintel/sessions/gaps.jsonldirectly, a path inside the
protected state directory where a redirect is refused; it now names
devmap gap-record. The guard meant to catch this could not have:
every_flag_the_guide_names_is_accepted_by_the_parserstripped placeholders
instead of substituting them, handing clap a bare--tool, so it could not
cover a value-taking flag at all. -
fix(devmap)— four import-resolution holes closed (Rust cross-crate
use,super::against an inline module,pub usere-exports with bounded
glob expansion, and shellsource), plus the fabricated Go edge the first one
exposed: opening the Go package-scope rung made&Command{run: nil}'s
field-name key resolve as aSamePackagecall tofunc runat
DETERMINISTIC — a fabricated caller for a function nothing calls. Struct
composite-literal keys are now excluded; map keys are kept, becausem[k]
names a real value. -
feat(dc-regress)— two new crates anddevmap suspects. See below.
devmap suspects: which commits could have caused this symptom. The cone
runs forward — the symptom names a symbol, the graph names what that symbol
transitively depends on, and only those byte spans are blamed. Walking the
inbound blast radius instead yields a cone containing the symptom and nothing
else: a silent empty answer rather than a loud wrong one. The join is the hard
part, because a graph records byte offsets and blame answers in line numbers,
and converting between them against the wrong blob does not fail — it returns a
plausible, ordered, wrong line range that no consumer can detect. BlobIdentity
travels with every span set and blame result, and join::attribute refuses a
mismatch rather than clamping past it.
Measured effect of the resolution fixes. A cold devmap build --full --manifest (generation #3163, 0 cached, so a genuine re-extraction) returned
13,046 symbols and 44,092 edges, against 12,654 and 39,180 from the
pre-fix generation over the same 1,187 files — 4,912 edges that previously
resolved to nothing.
Verification. cargo fmt --all --check and
cargo clippy --workspace --all-targets -- -D warnings both exit 0, confirmed
unpiped. Go: go build ./... clean and 27 packages pass. Rust: 3,243
tests across 282 binaries, with one failure per run — and it was a
different test each run.
What is not green, stated plainly. Two spawn/deadline-bound tests flake
under full-suite parallel load:
test_progress.rs:501a_closed_progress_pipe_does_not_signal_the_indexing_process
—progress_output.incompletecame backfalse. Detecting the closed pipe
requires a progress frame to actually be attempted, and on a 1-file corpus
the build can finish before the progress thread is scheduled.devmap-servedaemon::tests::a_daemon_that_cannot_bind_reports_why_instead_of_timing_out
— overran its 30s deadline, against ~0.5s in isolation.
Each passed 5/5 in isolation, test_progress passed 8/8 as a whole
binary (240 executions), and each passed in the run where the other failed.
Neither is attributable to this pass: devmap-serve is untouched by all seven
commits, and the devmap-cli diff is +400/−0, purely the new subcommand.
This is a pre-existing class — commits 41da117b and d71deef0 already fixed
instances of it — and the remaining two are filed rather than papered over. A
suite that is green only because nobody re-ran it is not a green suite.
Installed. All five binaries rebuilt at --release and installed to
~/.local/bin, reporting build 8a73698dbffd — HEAD exactly, where the
previously installed set reported 539fd406 and predated every change above.
The installer copies to $dest.new and mvs over the target, so the 27 live
devmap mcp servers holding the old inode — at least one serving a different
repository — kept running rather than being killed mid-session.
Reclaimed: 53 GiB, from 123 GiB free to 176 GiB. rust/target went from
54 GiB to 1.5 GiB: 52 GiB of target/debug (41 GiB of deps across 299,367
files, 9.9 GiB of incremental), plus 1.04 GiB of stale DevMap store snapshots
left in target/tmp by an earlier debugging session. target/release is kept —
it is what was just installed — as is the 27 MB manvi-testbin copy the
cross-repo test helper hands out.
The cost, so it is not a surprise. target/debug was removed whole, so the
next cargo test is a full debug rebuild rather than an incremental one.
Cargo has no GC for a target directory, and without cargo-sweep the orphaned
artifacts of many sessions cannot be separated from the live ones — 299,367
files in deps for a workspace of this size is almost entirely orphans. The
shared ~/.cargo/registry (2.7 GiB) and the Go build cache (7.1 GiB) were left
intact: both are shared with every other project on this machine and have their
own expiry, so clearing them is not this repository's call.
Two stashes were left alone. Both are 2026-09-13 docs snapshots, and main
is strictly ahead of them — it carries the v0.2.2 and v0.2.3 release notes,
docs/lexical-search.md and the DevMap docs that the stashes predate. They are
superseded safety snapshots, not unmerged work, and deleting them was not asked
for.
26. Consolidation pass (2026-09-20 evening): smart pointer deref transparency, framework dispatch, and search query precision
There was nothing to merge or delete. main was the only branch — locally, and on origin after a --dry-run fetch confirmed no other branches exist. No other branches existed to merge or delete.
The consolidation committed the remaining active changes in four atomic commits, followed by local release build and installation:
fix(devmap-resolve)(a0301f62) — Member calls through deref-transparent smart pointers (Arc<T>,Rc<T>,Box<T>in Rust;std::shared_ptr<T>,std::unique_ptr<T>in C++) now unwrap to the inner pointee type across both the tree-sitter parse extractor and the nominal type resolver. Resolves calls and member accesses through wrappers without fabricating edges for methods owned by containers (Vec,Option,weak_ptrstay untouched).fix(devmap-analyze)(acbf53e8) — Addedpython_base_dispatch_reasonfortorch.autograd.Function(methodsforward,backward,setup_context,jvp,vmap) andtorch.nn.Module(methodforward). Exempts framework-dispatched methods on known bases from dead-symbol candidate reports so methods called indirectly by the runtime through.apply()or__call__are not misclassified as dead code.fix(devmap-query)(cb958d75) —fts_match_querynow splits multi-term search queries into individually quoted prefix terms joined byANDrather than quoting the entire phrase as one literal string, fixing multi-term searches whose tokens appear in different columns or non-adjacent order. On empty multi-term search results,walk_incompletenow discloses that search only indexes symbol names, qualified names, and file paths (not file bodies/constructs), so zero results are not misinterpreted as evidence of absence.test(devmap)(221d8707) — Stabilizeddaemon_that_cannot_bind_reports_why_instead_of_timing_outby anchoring the refusal budget to the observed liveness probe rather than process spawn, and hardenedtest_progress.rsclosed-pipe detection against premature completion on single-file fixtures under concurrent test load.
Verification. Go test suite green (go test ./... across all 27 packages). Rust tests for all modified crates passed (100% clean), cargo fmt --all --check passed, and cargo clippy --workspace --all-targets -- -D warnings clean with zero warnings. npm run check:release confirmed release consistency.
Installed. All binaries rebuilt at --release and installed locally to ~/.local/bin using scripts/install.sh:
devcouncilanddevsymlink: v0.2.3devmap: v0.2.3 (store schema 22, code graph schema 2, build 221d87077f58)dcstore,dcverify,dcgrep: v0.2.3
Reclaimed: ~64 GiB, from 198 GiB free to 262 GiB free. rust/target/debug (36 GiB, 238,407 files) was removed along with rust/target/tmp (11 MiB, 250 files) and scratch artifacts (qwen-*, verify.log, 60 MiB). rust/target/release (1.5 GiB) and manvi-testbin (48 MiB) were preserved.
27. Hardening pass (2026-09-20 night): concurrent session log atomicity and append-only prefix reading
There was nothing to merge or delete. main was the only branch — locally, and on origin after verifying with git for-each-ref. No other branches existed to merge or delete.
The hardening committed the active session logging changes in an atomic commit, followed by stale build cleanup, test verification, and local release installation:
fix(devmap-serve)(fc1a2b63) — Prevent torn session log records and tolerate append growth under multi-daemon load:- Atomic write-all (
devmap-serve/src/session_log.rs):append_linewrites body plus newline in a singlewrite_allcall. Previouslywriteln!split the record body and newline into separate unbuffered write calls, allowing concurrent MCP daemons writing to the same shared log underO_APPENDto interleave and produce torn lines. - Append-only prefix reading (
devmap-extract/src/safe_fs.rs): IntroducedSafeFile::read_text_prefixandcheck_appended_only. Reads the snapshot length present at open and ignores subsequent append growth from active daemons, eliminating false-positive tampering errors on busy repositories while still strictly enforcing refusal on inode swapping or file truncation. - Resilient ledger parsing (
devmap-serve/src/session_log.rs): IntroducedLedger<T>andparse_jsonlto parse surviving records and count malformed lines rather than failing closed on the entire file. Prevents a single torn line from abortingsession-reportand stalling log rotation until the log hitsMAX_SESSION_BYTES. - Damaged line disclosure (
devmap-cli/src/session.rs): Disclosesunreadable_linescount directly indevmap session-reportoutput so lost records are visible in session summaries. - Concurrency and fault tests (
devmap-serve/tests/session_log_concurrency.rs,devmap-extract/tests/safe_fs_controls.rs): 6 dedicated tests asserting concurrent appends never tear records across 8 threads, torn lines are counted rather than fatal, reading during concurrent append is accepted, rotation under load loses zero records, and file replacement/truncation remains refused.
- Atomic write-all (
Verification. Go test suite green (go test ./... across all 27 packages). Rust tests for all modified crates passed (devmap-extract, devmap-serve, devmap-cli 100% clean), cargo fmt --all --check passed, and cargo clippy --workspace --all-targets -- -D warnings clean with zero warnings. npm run check:release and npm run test:scripts (66 tests) confirmed release consistency.
Reclaimed: ~45 GiB, from 213 GiB free to 258 GiB free. rust/target/debug (27 GiB), rust/target/llvm-cov-target (18 GiB), rust/target/tmp (168 KiB), and rust/lcov.info (3.4 MiB) were removed. rust/target/manvi-testbin (70 MiB) and rust/target/release were preserved.
Installed. All binaries rebuilt at --release and installed locally to ~/.local/bin using scripts/install.sh:
devcouncilanddevsymlink: v0.2.3devmap: v0.2.3dcstore,dcverify,dcgrep: v0.2.3
28. Forward change blast radius and regression analysis (devmap blast, devmap_blast MCP tool) (2026-09-21)
There was nothing to merge or delete. main was the only branch — locally, and on origin after verifying with git branch -a and git ls-remote --heads origin. No other branches existed to merge or delete.
The release consolidates the forward change impact and regression analysis subsystem:
feat(dc-regress)— Forward change tracking and blast analysis:- Post-image hunk parsing (
dc-regress/src/change.rs): Readsgit diff --unified=0hunk headers and tracks post-image line spans pinned to the indexed head revision. Widens zero-count deletion hunks to boundary lines, refuses merge commits and C-escaped control paths, and bounds output. - Inbound call graph walk (
dc-regress/src/blast.rs): Walks inbound call edges from seed symbols touched by changes to identify downstream impacted symbols, files, modules, and affected test suites. - Unattributed lines disclosure: Changed lines outside symbol spans (imports, constants, macros) are reported in
unattributedwith failure reasons (complete: false). - Adversarial & integration tests (
tests/change_to_impact.rs,tests/hostile_change.rs): 38 dedicated tests covering deleted files, binary files, unplaced lines, multibyte shifts, hunk parsing, and edge cases.
- Post-image hunk parsing (
feat(dc-regress-store)— Store graph query integration:- Implemented
StoreGraph::impacted(layered inbound traversal by shortest distance) andStoreGraph::affected_tests(chunked neighbor queries acrossMAX_NEIGHBOR_TARGETS).
- Implemented
feat(devmap-cli)— CLIdevmap blastcommand:- Supports
--since <rev>and--at <path>:<start>-<end>. - Rich human formatting disclosing seeds, unattributed lines, affected items, and JSON output (
tests/blast_names_what_a_change_reaches.rs, 14 tests).
- Supports
feat(devmap-serve)— MCPdevmap_blasttool:- Published read-only
devmap_blasttool with input/output schema validation, repository scoping, and protocol tests.
- Published read-only
Verification. Go test suite green (go test ./... across all 27 packages). Rust tests for all modified crates passed 100% clean (dc-regress, dc-regress-store, devmap-cli, devmap-serve), cargo fmt --all --check passed, and cargo clippy --workspace --all-targets -- -D warnings clean. npm run check:release and npm run test:scripts confirmed release consistency.
Stale build cleanup. Removed rust/target/debug (~26 GiB), rust/target/tmp, rust/target/*.log, rust/devmap-cli/*.profraw, and backend/go_orchestrator/bin/devcouncil, while preserving rust/target/manvi-testbin.
Installed. Binaries rebuilt at --release and installed to ~/.local/bin using scripts/install.sh: devcouncil, dev, devmap, dcstore, dcverify, dcgrep (v0.2.3).
29. Parity hardening pass (2026-09-21): direct fnmatch backtracking engine, unified CPython fixture, and Gusset engine verification
There was nothing to merge or delete. main was the only branch — locally, and on origin after verifying with git branch -a, git show-ref --heads, and git fetch --dry-run --prune.
The release consolidates the fnmatch parity engine and Gusset linkage verification:
feat(fnmatch)— Direct backtracking matcher in Go (backend/go_orchestrator/fnmatch):- Replaced regexp translation with an in-memory direct unit walk matching Rust
dc-globsemantics. - Enforces
MAX_UNITS(16,384 units) and a quadratic step budgetlimit(4 * (len(pat)+1) * (len(text)+1) + 8) to fail closed against pathological backtracking. - Preserves CPython
fnmatchcaseinverted-range behavior ([c-a-e],[a--c]) without regex compilation failures.
- Replaced regexp translation with an in-memory direct unit walk matching Rust
feat(parity)— Unified CPython parity fixture (testdata/fnmatch-parity.tsv):- Single root fixture generated by
scripts/gen-fnmatch-parity.py; removed duplicates frombackend/testdata/andrust/testdata/.
- Single root fixture generated by
feat(dc-glob)— Parity hardening and unit caps (rust/dc-glob):- Bounded character allocation and matching with
MAX_UNITScap and quadratic step limit. Verified against root fixture.
- Bounded character allocation and matching with
feat(gusset)— Gusset engine linkage and CLI check:- Added umbrella staticlib crate
rust/gusset-engineintegratinggussetcore anddc-glob. - Added
backend/go_orchestrator/gussetfnlinking Gusset via cgo, verified withdevcouncil gusset-check.
- Added umbrella staticlib crate
Verification. go test ./... (27 packages), Rust (dc-glob, dc-verify, workspace), cargo fmt --all --check, and clippy -D warnings were clean. check:release and test:scripts (66) passed.
Stale build cleanup. Removed rust/target/debug (~19 GiB), rust/target/tmp, rust/gusset-engine/target (~146 MiB), and backend/go_orchestrator/bin/devcouncil, while preserving rust/target/manvi-testbin.
Installed. Binaries rebuilt at --release and installed to ~/.local/bin using scripts/install.sh: devcouncil, dev, devmap, dcstore, dcverify, dcgrep (v0.2.3).
30. Consolidation (2026-09-21 evening)
Branches. Only main. git ls-remote --heads origin returned refs/heads/main; local main is 66 commits ahead. Nothing to merge or delete. Two 2026-09-13 docs stashes stay.
Stale builds. cargo clean --release removed rust/target/release (2942 files, 1.6 GiB). Kept manvi-testbin (92 MiB) and .gocache (242 MiB). Rebuilt gusset-engine. New release profile: 857 MiB.
Installed. scripts/install.sh to ~/.local/bin: devcouncil 0.2.3 (dev symlink, signature verified), devmap 0.2.3 (build 3d21ec1dda3d), dcstore, dcverify, dcgrep 0.2.3. gusset-check ok.