The V2 compaction hook pushed a plain object into the compaction request's
event.messages. OpenCode's Message is a schema class at v2.2.5, and nobody has
run this path on a real 2.x host. The try/catch only guards the array push,
which cannot throw. If OpenCode rejects the object, that happens inside its own
compaction, out of the plugin's reach, so an untested note could break
compaction for every OpenCode 2 user. Losing the note costs far less.
Remove the V2 compaction registration and the sessionHooksRegistered flag it
needed. The V2 tool hook, proven on OpenCode 2.2.5, stays, and so does the
1.18.x experimental.session.compacting path. OpenCode 2 sessions still get the
session note on the first tool result of a session. A later change can add a
note after compaction together with a real 2.x run.
The replacement test pins that the generated module registers no session hook
and touches neither event.messages, a system property nor an instruction entry.
Refs #2204
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
test_daemon_application.c: the new auto-watch test passes false for the approve_broad parameter that #2017 added to cbm_workspace_grant_add, the same value the existing callers use; the merged tree did not compile without it.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Report stored recursion signals and the static self-call omission policy
alongside graph reachability totals. Preserve the metadata across cursor
pages and output-budget fallback responses, and document unknown legacy
metadata without changing CALLS indexing or traversal counts.
Fixes#2816
Signed-off-by: pcristin <xxxokzxxx@protonmail.com>
tests/test_pipeline.c: main and this branch added tests at the same spot;
both are kept unchanged.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A daemon started with `daemon start` now keeps a project watched after its
last owning MCP session closes, until the daemon stops, the project is
deleted, its directory disappears, or auto_watch is set to false. README
(the watcher paragraph and the index_repository row), the watcher_enabled
row of docs/CONFIGURATION.md and the comment above the index_status watch
provider still said every watch ends with its last session.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The search hook gives each Grep, Glob or Bash search the matching graph
symbols, but a session can keep searching text for questions the graph answers
in one call (#509). Once a session has run three searches with no
codebase-memory-mcp tool call, the next augmentation now adds one factual line
saying so. The next note comes after six searches, then twelve, and any graph
tool call resets the count.
- State: a per-session counter in the process that runs hook requests (the
daemon), keyed by the payload's session_id, in a bounded 64-slot table that
evicts the least recently used session, behind a spin lock. Only searches
that reach the graph lookup count. A search without an augmentation still
counts, and the note waits for the next augmentation.
- Reset: Claude's managed PostToolUse entry now matches
"Read|mcp__.*codebase-memory-mcp__.*", covering plain and plugin server
names. The previous Read-only entry, as installs up to v0.12 wrote it, is
recognized as owned, so install replaces it in place and uninstall removes
either.
- Scope: Claude Code only. Codex sends the same event payload with a turn_id
but has no reset hook, so its sessions are not tracked, and payloads without
a session_id are not tracked either.
- Cost: a graph tool call now runs one hook round-trip to the daemon for the
reset, about 130 ms on a local build.
Refs #509
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The Codex CLI row still listed only SessionStart and SubagentStart after the
PreToolUse Bash hook landed (#2719), and the hook section described only
Claude Code's search context. The row now names the Codex search hook, and the
section says how Codex, Copilot CLI and VS Code's Copilot runtime get search
context.
Refs #509
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Copilot CLI's preToolUse output can only decide permissions, and its command
hooks fail closed on errors, so the graph context that Claude Code and Codex
searches get could not ride a pre-tool hook there. postToolUse
additionalContext is appended to the tool result the model sees on the same
turn (github/docs content/copilot/reference/hooks-reference.md). VS Code's
agent mode shares the Copilot harness and reads the same ~/.copilot/hooks
manifest.
- Manifest: $COPILOT_HOME/hooks/codebase-memory-mcp.json adds a PascalCase
PostToolUse entry, matcher "Bash|Grep|Glob|bash|powershell|grep|rg|glob",
running hook-augment --event PostToolUse --dialect copilot. The previous
lifecycle-only manifest, as installs up to v0.12 wrote it, stays recognized
as owned: install upgrades it in place and uninstall removes either
generation. A foreign manifest still fails closed.
- hook-augment: the Copilot dialect reads either payload form
(tool_name/tool_input or toolName/toolArgs), maps runtime tool names to the
Claude names (bash/powershell to Bash, grep/rg to Grep, glob to Glob),
parses arguments passed as a JSON string, and answers with top-level
additionalContext.
- Fast path: Copilot's forced PostToolUse goes through the no-op gate before
executable-identity hashing. The gate also skips tools the augmenter does
not read, because VS Code's local hook parser ignores matchers, so a
non-search shell call or an edit exits in about 20 ms instead of paying the
full round-trip of about 130 ms.
- The context header no longer says the search results are "below": after a
post-tool hook they are above it.
- Tests: the three Copilot foreign-manifest tests now create
.copilot/mcp-config.json. Without it Copilot CLI was never detected, so
install and uninstall never reached the manifest, and the tests also passed
with the ownership checks removed.
Refs #509
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Reworks the ADR-loss-across-reindex fix to Alternative 1 (per the PR review):
remove the race rather than narrow it. ADRs live in a per-project sidecar SQLite
DB ("<graph_db>.adr.db"), separate from the graph DB a reindex rebuilds or
deletes, so there is no capture, re-read, re-apply, or lock — and no sleeps,
polling, or timeout deciding an outcome.
store.c:
- ADR writes (store/delete/update_sections) use a cached read-write handle opened
SQLITE_OPEN_READWRITE|CREATE and configured like the graph store (PRAGMA
busy_timeout=10000 + journal_mode=WAL) so cross-process access waits rather
than failing SQLITE_BUSY. A :memory: store keeps ADRs in the graph DB.
- Reads never write: cbm_store_adr_get uses the cached RW handle if open, else
opens the sidecar SQLITE_OPEN_READONLY (with the same busy timeout) when the
file exists, else reads the legacy graph row without creating anything. When
the sidecar exists but is NOT marked migrated (half-created), it falls back to
the legacy graph row so a partial sidecar never hides an ADR.
- One-time migration is atomic: adr_copy_legacy_once wraps the marker check,
legacy copy, and marker insert in a single BEGIN IMMEDIATE...COMMIT
(re-checking the marker under the lock; ROLLBACK->ERR on failure). New public
cbm_store_adr_migrate_once. Sidecar path via cbm_path_for_file_api (Windows
long-path). Dropped the write-only adr_db_path field.
pipeline.c: migrate a legacy ADR into the sidecar BEFORE the format-change and
forced-full routes unlink the old generation; preserve-and-abort on failure, and
do NOT fail open — a query-open returning NULL preserves unless the final DB
genuinely does not exist. Migration targets the FINAL path (staging suffix
stripped). The early-capture/re-apply machinery and its test hook are gone.
artifact.c: the exported artifact carries the ADR — snapshot_inject_adr always
DELETEs project_summaries first, then injects only when an ADR is present, so a
deleted ADR never resurrects through a lingering legacy row. Import restores the
ADR into the destination sidecar via cbm_store_adr_restore_from_graph_if_absent:
it copies the artifact's ADR (INSERT OR IGNORE — never clobbers a local one)
regardless of the migrated marker, so a teammate who already indexed the project
(marked sidecar, no ADR) still receives the shared decision record. store.c
factors the row copy into adr_copy_rows, shared by the one-time legacy migration
and the ungated restore.
mcp.c: delete_project removes "<db>.adr.db" (+ -wal/-shm/-journal) only after the
graph unlink succeeds; is_project_db_file skips *.adr.db. cli.c: exclude
*.adr.db from index listing/counting. README + memory-core baselines updated.
Tests (file-based): ADR written mid-reindex survives on BOTH the full and delta
routes (route-pinned FORCED_FULL / CLOSURE_REPAIR); a legacy row migrates before
a forced-full/format reindex with no prior read; migration failure (a directory
at <db>.adr.db) preserves-and-aborts with the old generation intact;
delete_project via the MCP handler removes the sidecar + journals; the artifact
export->import round trip keeps the ADR; a deleted ADR does not resurrect on
import; an import does not clobber a local ADR; an import into an already-indexed
destination (marked sidecar, no ADR) restores the artifact's ADR; a half-created
sidecar falls back to the legacy row. The import tests remove the destination
graph DB (keeping the sidecar + marker) before importing, matching production
(try_artifact_bootstrap imports only when the graph DB is missing) so the rename
succeeds on Windows, not only on POSIX.
Closes#516
Signed-off-by: 0xb007ab1e <134006168+0xb007ab1e@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make this repository a Claude Code plugin marketplace so users can find
and add the server from inside Claude Code:
claude plugin marketplace add DeusData/codebase-memory-mcp
claude plugin install codebase-memory@codebase-memory
This is slice 1 of #1188: discovery only. The plugin registers the MCP
server and nothing else; the skill, the graph agents and the context
hooks still come from `codebase-memory-mcp install`.
- .claude-plugin/marketplace.json: one plugin, sourced from ./plugin.
- plugin/.claude-plugin/plugin.json: version pinned to the current
release. Without a pinned version every commit to main would be a new
plugin version for installed users.
- plugin/.mcp.json: stdio server `npx -y codebase-memory-mcp@0.12.1`.
The pin keeps main from advertising a server version npm does not
have; the npm package downloads the release binary on first start and
verifies it against the release checksums.
- .gitignore: the root .mcp.json stays ignored; plugin/.mcp.json is
published.
- README: install section, and "use one or the other": with both the
plugin and `install`, Claude Code runs the server twice under two tool
namespaces.
- tests/test_version_metadata_contract.sh: both plugin files are
release-tracking surfaces; a new claim form reads the npm package spec
(`codebase-memory-mcp@X.Y.Z`), and the unregistered-surface sweep now
covers plugin/ and .claude-plugin/. The post-release sync that bumps
server.json must bump these pins too, or this contract goes red.
No language count is stated anywhere in the plugin metadata, so the
language-count contract has nothing new to track.
Refs #1188
Co-authored-by: Kody <kaidi.shi.1121@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Centralize recurring Windows-specific troubleshooting entries in a dedicated document linked from README.md. Each entry includes confirmed symptoms, affected version ranges, current workarounds, and canonical tracking issue references, updated to reflect v0.10.x and v0.11.0 status.
Refs #1170
Signed-off-by: Oscarbol09 <omaderabolano@correo.unicordoba.edu.co>
Review round 4 of #2677 (user decisions: R4-1..R4-4, R4-6, R4-7, R4-9;
R4-5 measured and left out). Every case ends in an UNKNOWN image, never a
wrong one.
Changes not run exactly (helm_template.c):
- A deep merge into a values map is not exact when a change was made at or
below its path (a set storing a map there): every values map a set or a
merge changes keeps its path, and such a merge leaves the images after it
UNKNOWN.
- and / or at an operand whose truth is not known -- UNKNOWN, or a map a
change made unknown -- leave the images after them UNKNOWN when an
operand after it may change maps.
- In an if / with an UNKNOWN condition skips, the else-if / else-with
conditions after it are looked at too: a change there taints, an
assignment there makes its variable UNKNOWN.
- sortAlpha sorts a []string in place: it counts as a change (a list is
never poisoned, so it taints). No other Sprig list function changes its
argument in place.
- A template, a define body or a scanned template file the grammar does not
parse may hide a change: its images are UNKNOWN.
Work (helm_template.c):
- A capture's keys leave the key set one by one; the set is no longer
cleared by walking its whole capacity after each capture.
- Template files are found by a hash of their path, and an include of a
name that is no define is answered once per render.
Known limit, documented (README): each template file renders on its own,
while Helm renders a chart's files with one shared .Values. Running them in
Helm's order is the planned follow-up; the coarse rule across files cost 83
more correct images on the 20 infra corpora and was left out.
On the 20 infra corpora: Image edges identical to round 4 (0 lost, 0
added), no budget trip.
Tests: R4-1..R4-4 and R4-9 fail on 3401def5 by their assertions (each sub
case too); R4-6 and R4-7 are correctness controls, their cost measured
outside the suite.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
v0.12.0 could not be installed through npm, pnpm, pip or the Go wrapper on
any platform (#2657): each wrapper refused an archive member above 256 MiB,
and every v0.12.0 binary is 331-335 MB. The v0.11.0 binaries were already
about 300 MB, so the Go wrapper, which has had the cap since v0.10.0, was
broken before; npm and PyPI gained it in v0.12.0. The archive is verified
against the release's checksums.txt before it is unpacked, so a byte cap
only ever refused what we ship.
- npm, PyPI, Go: delete every byte-size cap (compressed archive, per member,
aggregate, checksum manifest, download size). The checksum, the exact
member set, path and link refusal, the member count, HTTPS and the
--version check stay.
- All three honour CBM_DOWNLOAD_URL like install.sh, for testing: https, or
plain http to an exact loopback authority with no proxy and no redirect.
- Smoke: scripts/smoke-wrappers.sh installs the three wrappers from the
checkout against the release fixture and runs the binary each one
installed. smoke-test.sh Phase 13w runs it; SMOKE_REQUIRE_WRAPPERS=1 makes
it required on the PR smoke lanes and in the release smoke, which gates
publishing.
Other install paths found broken while checking every channel:
- Homebrew: the formula was still at 0.10.3 and installed the dynamic Linux
build, which needs glibc 2.38 (Ubuntu 22.04, Debian 12 and RHEL 9 lack it).
It now installs 0.12.0 with the static -portable Linux assets and tracks
the newest release in the version contract like the other surfaces.
- Go: `go install .../pkg/go/cmd/codebase-memory-mcp@vX.Y.Z` never worked
(no pkg/go/v* tags, so Go fetched the whole repository, over its 500 MB
module limit), and @latest built main's wrapper, whose version constant
lags each release. The release now also tags pkg/go/vX.Y.Z, and the
wrapper installs its own module version (the constant only for local and
untagged builds).
- npm: a stable release also moves the `next` dist-tag to itself; `next`
still pointed to 0.9.1-rc.1.
- Docs: winget and Chocolatey were listed as install channels but are not
published; they are removed until they are. Windows arm64 ships and is
now listed.
Fixes#2657.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The badge tracked dry-run.yml, which runs only when dispatched; its last
runs on main were cancelled or failed during the v0.12.0 preparation, so it
read "failing" although main passed the full matrix in the v0.12.0 release
run. release.yml runs the same full matrix on main for every release.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A config key linked to every code symbol whose name contained it. In
YAML, JSON and HCL files that guess was retired in WP0.9; TOML and INI
files kept it, and in 17 infrastructure repositories every one of their
72 key_symbol edges was a coincidence: `.gitleaks.toml useDefault` linked
to TestConfig_UseDefault and three other symbols, a toml-merge tool's test
data `config_path` to 46 `*ConfigPath` constants all over deckhouse,
`grafana.ini admin_user` to user-cluster constants, a Rust Cargo.toml
dependency to other services' go.mod requirements.
A .toml, .ini, .cfg or .conf key now links like an infrastructure key:
by exact name (camelCase and snake_case still normalize to one key) and
only to code inside the config file's component, its directory or the
one around a config/, conf/, settings/ or etc/ directory. .env and
.properties keys keep substring matching; the tests that covered it on a
TOML key now use a .properties key.
Measured on the 17 repositories: key_symbol 95 -> 23, the 23 being the
infrastructure edges WP0.9 reviewed, the 72 removed all TOML/INI and all
coincidences, no other edge or node changed; deterministic over three
runs; the incremental re-index equals a full index for INI, TOML, code
and tfvars edits.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
An external Chart was <project>.__helm_dep__.<name>: keyed by its name,
inside one project. Two repositories naming the same published chart held
two unrelated nodes, and a project naming two charts of one name from two
publishers (kubermatic's cert-manager from charts.jetstack.io and from its
own mirror) would have merged them into one.
An external Chart whose repository URL is stated is now
__chart__<repository>/<name>, the same QN in every project, the URL in a
`repository` property: scheme and host lower-cased, a trailing slash and
`.git` dropped. A Chart.yaml dependency, a kustomize helmCharts entry, a
Flux HelmRelease chartRef and a Terraform helm_release of an oci:// chart
that name one published chart meet in one node. A chart reference (a Flux
OCIRepository url, `chart = "oci://..."`) is cut to its repository; a
repository URL never is, so repository oci://public.ecr.aws/karpenter
with chart karpenter stays oci://public.ecr.aws/karpenter/karpenter. A
repository that is no URL (an @alias, no scheme, file://, credentials in
the authority, an unexpanded ${...}) or that the pass does not read (a
Flux HelmRepository source, a Terraform `repository =`, a shell
`repo/name`) keeps the per-project node: no identity is claimed for what
was not stated. A query or fragment (`?token=...`) takes the per-project
node too, so a token never becomes part of a QN or a property.
One predicate now tells an external chart from a chart of the repository
where Terraform, shell and the Go installer index the chart directories:
an external chart a Chart.yaml depends on carries that file's path, and
must never stand in for the chart the directory declares.
Measured on 17 infrastructure repositories against an independent
reading: 141 of 141 edges into external charts reach the identity their
stated repository gives (114 by URL, 27 per project). The only
differences to the previous index are the 79 external Chart nodes that
moved to their URL identity, each with its repository property, and the
114 edges into them, re-pointed with type, source and properties
unchanged. Every chart gate (Helm includes, helmCharts, Terraform, the Go
installer, shell, CI, GitOps deploys) reads the same as before; three
runs are identical; the incremental re-index equals a full index for a
shared chart's owning file edited and deleted, a URL respelled, a
dependency moved to another publisher and to an alias, a chartRef URL
edit and a new Terraform oci:// release. The index rebuilds once on
upgrade with the other version 9 changes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
kubespray applies its cluster add-ons with its own `kube` module: a
task runs kubectl apply on a manifest the play rendered from a template
a few tasks earlier. Both tasks were Functions and the template a File,
with nothing between them: no query reached the manifest a task applies.
A kube (kubespray's library module) or k8s (kubernetes.core /
community.kubernetes) task now DEPLOYS {via: ansible, module, line} the
repository file its play rendered the applied manifest from: a template
task's src (the role's templates/, else the task file's directory) or a
copy task's (its files/; never a remote_src copy, which reads the host).
The applied filename is traced through the play: a renderer whose dest
is the very string; {{ item.item.K }} over "{{ R.results }}" -> each
item of the renderer registering R; {{ item.K }} -> each of the task's
own items through the renderer of that dest; a literal filename a looped
renderer renders to; a k8s template: argument. A loop list is a literal,
a set_fact of the file, or a list of the role's defaults / vars main
file. Templates are found before copies to the same dest. remote_src is
read the way Ansible's boolean() reads it (y / yes / on / 1 / true / t,
any case): `remote_src: true` is a YAML boolean, not a string, and a
string-only read had kept every remote copy as a renderer.
The lists in a role's defaults / vars reach past the task file, so a
change to one takes the full rebuild (closure_ansible_decline); without
it, a list that names another template kept the edge to the old one. A
file holding Ansible tasks that the repair would re-walk as a dependent
-- a template it applies was deleted -- also takes the full rebuild
(closure_ansible_dependent_decline): the repair walks it without its
role's defaults, handlers and other task files, and drew none of its
list-fed edges.
Measured on 17 infrastructure repositories: 164 of 164 applied files
(52 of kubespray's 62 kube / k8s tasks, 158 templates) against an
independent reading, all in kubespray; nothing in the other 16; the rest
of the graph unchanged (+164 DEPLOYS only); deterministic over three
runs; the incremental re-index equals a full index for a template body
edit, a deleted template (with and without a same-named fallback beside
the task file), a task list edit, a copied manifest edit, a defaults
list edit and a deleted vars file. Of the 10 tasks left out, 4 act on
an object by name, one builds its definition with a template lookup,
one applies a remote_src copy, and 4 name their file through a filter, a
variable or two joined results lists.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>