This guide covers local setup, tests, and contribution requirements.
git clone https://github.com/ScriptedAlchemy/tracedecay.git
cd tracedecay
pnpm install
bazel build //...
bazel test //... --test_output=errorsBazel manages the Rust toolchain and dependencies from rust-toolchain.toml,
the workspace manifests, and Cargo.lock. It is the workspace build, test,
check, run, benchmark, and code-generation authority locally and in CI.
Use Node.js 22+ and pnpm for JavaScript packages. The pnpm version is pinned
by packageManager in package.json. Run pnpm install at the repository
root after cloning and after pnpm-lock.yaml changes. Commands below run
from the repository root unless noted.
To add, remove, or bump a Rust dependency, edit the manifests and refresh the lockfile and generated Bazel targets:
CARGO_BAZEL_REPIN=workspace bazel fetch --repo=@c
bazel run //scripts:gen_buildsFor a targeted dependency update, use CARGO_BAZEL_REPIN=<crate> instead.
Review and commit Cargo.lock and any generated target changes together.
Use bazel run //scripts:gen_builds -- --check to compare generated targets
without writing. The generator uses Bazel-declared Cargo and rustc matching
rust-toolchain.toml. Git and network credentials remain host prerequisites.
.cargo/acquisition.toml configures Git acquisition for the generator
and crate_universe. The generator passes it through Cargo's --config, which
takes precedence over environment configuration. crate_universe copies it
into its isolated splice, where environment configuration can override it.
The splice receives only this acquisition file. The generator also discovers
the checkout's .cargo/config.toml normally. Ordinary validation leaves
CARGO_BAZEL_REPIN and REPIN unset. Investigate unexpected lockfile changes
before repinning.
Dashboard contracts:generate and contracts:check run the
//sdks/codegen:generate and //sdks/codegen:dashboard_schema Bazel targets.
Use your current checkout; no particular absolute path or historical PR branch is required. See AGENTS.md for checkout safety and shared-work rules.
The dashboard bundle at dashboard/app-dist/ is generated output and is
git-ignored, so a fresh clone has none. The CLI build script
(crates/tracedecay-cli/build.rs), the only crate that embeds the bundle,
builds the frontend into an immutable, digest-named copy under its own
OUT_DIR rather than embedding app-dist: pnpm run build runs when the
frontend inputs' content fingerprint changes, and the Rust build fails if pnpm
is missing or the installed packages no longer match pnpm-lock.yaml. Setting
TRACEDECAY_SKIP_DASHBOARD_BUILD stages a prebuilt dashboard/app-dist
instead, and only when TRACEDECAY_DASHBOARD_BUNDLE_SHA256 holds that
bundle's digest (print it with python3 scripts/check-dashboard-bundle.py dashboard/app-dist --print-digest); a missing or mismatched digest fails the
build. Bazel builds the bundle as //dashboard:app_dist and passes it and
its //dashboard:bundle_sha256 digest to that skip mode; release workflows
ship only that embedded output. GitHub Releases ship prebuilt binaries;
workspace Cargo packages are private.
Do not assume a green baseline for the full suite: run it and read the failures; treat new failures in code you touched as yours.
Read the V2 operating-model summary before
changing storage, retrieval, or host ingestion, then follow the linked
authoritative roadmap plans. tracedecay-graph-db is the sole final Grafeo
boundary; SQLite is relational only. V2 persisted data is reset or recreated
when incompatible, do not add a prior-store reader, conversion, backfill,
shadow path, or dual write.
Tests and local validation must use isolated temporary home, profile, project,
and socket paths. Never start, install, or test a V2 daemon against an
installed master profile.
This tree map is for source orientation only. The V2 roadmap owns product precedence and acceptance.
crates/tracedecay/ Composition-root library and its integration suites
crates/tracedecay-cli/ Shipped tracedecay binary and CLI integration suites
crates/ Workspace members (code-extraction, graph-db, domain, hosts, …)
dashboard/ Embedded React dashboard
plugin/ Host bundles (Claude, Codex, Cursor, Kimi, OpenCode)
tests/ Shared fixtures, distribution suites, and cross-crate gates
docs/ Design docs and the V2 roadmap
tracedecay supports more than 50 languages. The root Cargo.toml is a virtual
workspace, not a package. crates/tracedecay-cli/Cargo.toml and
crates/tracedecay/Cargo.toml expose the language tiers;
crates/tracedecay-code-extraction/Cargo.toml owns grammar feature membership:
| Feature | Coverage |
|---|---|
lite |
Core extractors such as Rust, Go, Java, TypeScript/JS, Python, C/C++, Kotlin, C#, and Swift |
medium |
lite plus Dart, Pascal, PHP, Ruby, Bash, Protobuf, PowerShell, Nix, and VB.NET |
full (default) |
medium plus the remaining grammars selected by the extractor crate's full feature |
Build and test the generated Bazel targets during development:
bazel build //crates/tracedecay-cli:tracedecay
bazel test //crates/tracedecay-code-extraction:main --test_output=errorsUse Cargo for a feature combination only when scripts/bazel/gen_builds.py
does not generate an equivalent Bazel target.
- Use the task's branch, or branch from
masterfor a new contribution. Confirm the target before working on a release-channel branch. - Write tests in the owning crate. Extraction changes belong with
crates/tracedecay-code-extraction/; follow nearby inline tests or crate-local integration suites and assert on extracted nodes/edges. - Run the owning Bazel test target, then broaden for unresolved risk. Documentation-only edits do not require application builds.
The
bazel test //path/to/package:target --test_output=errorsrust_testwrapper setsTRACEDECAY_DATA_DIRunder Bazel'sTEST_TMPDIRand disables the global database. Tests that need a private profile should still override it per test withcommon::TraceDecayStorageEnvGuardorcommon::apply_tracedecay_home_env. - Format Rust code, then build the workspace:
cargo fmt bazel build //...
For a public name or wire protocol, retain compatibility only with evidence that the external contract shipped. Persisted V2 storage has no compatibility conversion. Keep compatibility work out of storage; document the retained external boundary alongside its behavior.
The CI Clippy job runs the rules_rust Clippy aspect over every Bazel target
with -D warnings:
bazel build --config=clippy //...The check is blocking: the workflow fails on any Clippy warning.
The composition-root lint policy in
crates/tracedecay/src/lib.rs currently denies clippy::all, clippy::unwrap_used, and
clippy::expect_used; new violations of those lints must be fixed or justified
with the narrowest practical #[allow(...)] at the affected item. Do not add a
broad allow or weaken the crate policy just to get CI green.
clippy::pedantic remains advisory. Pedantic diagnostics are emitted as
warnings and should be addressed when they point to a real maintainability issue,
but they do not block CI unless a future policy change promotes a specific lint
to deny.
There is no separate Clippy baseline file today. If a policy change intentionally promotes additional advisory lints to blocking, update the owning crate's lint policy, fix or narrowly allow the existing violations in the same change, and update this section so the contributor command and blocking/advisory split still match CI.
- Add a tree-sitter grammar dependency (or vendor it under
vendor/). - Create
crates/tracedecay-code-extraction/src/{lang}_extractor.rsimplementing theLanguageExtractortrait. - Register it in
LanguageRegistrywith a feature flag (e.g.,lang-{name}) in that crate'slib.rsandCargo.toml. - Add a test module under
crates/tracedecay-code-extraction/tests/main/and register it in that directory'smain.rs; follow nearby source and fixture patterns. - Update the feature flag tables in that crate's
Cargo.tomland this document.
When sharing traversal helpers, preserve child ordering, named versus anonymous
node handling, and direct-child versus descendant semantics. Compare against
crates/tracedecay-code-extraction/src/traversal.rs and keep language-specific
behavior local; cover the consuming extractor before and after consolidation.
Changes under plugin/ or crates/tracedecay-agent-hosts/ are covered
by a layered validation system: vendored JSON-schema checks, per-host skill
frontmatter contracts, cross-bundle sync/parity tests, and a CI
schema-validation workflow. plugin/skills/ is the shared source of truth for
bundled skills, do not fork host-specific copies. Before submitting, run:
bazel test //crates/tracedecay:agent_suite --test_output=errorsSee docs/PLUGIN-VALIDATION.md for the full
layer breakdown, schema refresh procedure, and how to add a skill or a new
ecosystem bundle correctly.
dashboard/src/contracts/types.ts, dashboard/src/contracts/decoders.ts,
dashboard/src/contracts/generated.ts, and
dashboard/codegen/schemas/dashboard-contracts.schema.json are generated, not
hand-written. The Rust schemars output is authoritative: the codegen CLI
exports the schema through //sdks/codegen:dashboard_schema, which calls
the tracedecay-dashboard-api library's canonical renderer. In
contracts:check mode, it generates all four dashboard files and the
TypeScript SDK sources, then compares every generated output byte-for-byte
with the committed files.
After changing any Rust type that crosses the dashboard API boundary:
cd dashboard
pnpm run contracts:generate # rewrite the generated files
pnpm run contracts:check # what CI runs; exits 1 on any driftcontracts:check is a blocking CI step in the dashboard job of ci.yml, not
an advisory one. Do not hand-edit the generated files. The check will fail and
the fix is to regenerate and commit.
The generated contract files (and the embedded Cursor extension bundle) are
marked linguist-generated in .gitattributes, so GitHub collapses them in
PR diffs and excludes them from language stats; review the Rust source of the
contract change instead.
Run the generated Bazel test target that owns the behavior:
bazel test //crates/tracedecay-code-extraction:main --test_output=errors
bazel test //crates/tracedecay:agent_suite --test_output=errorsTo pass a libtest filter to one target, add it after --test_arg, for example:
bazel test //crates/tracedecay-code-extraction:main \
--test_arg=test_rust_file_node_is_root --test_output=errorsFollow conventional commit style:
fix: handle UTF-16 encoded files in sync
feat: add Dart annotation extraction
refactor: simplify reference resolver lookup
Keep the first line under 72 characters. Add a body explaining why if the change isn't obvious.
Install the local commit-msg hook once per checkout:
scripts/install-git-hooks.shCI validates commit messages with commitlint (commitlint.config.cjs):
git show --no-patch --format=%B HEAD | pnpm run --silent lint:commit --Run the same command locally (per commit) before pushing to lint every non-merge commit in a branch range. Merge commits are skipped to match CI behavior.
- Target
masterfor bug fixes and stable features. - Confirm the target branch with the maintainer for release-channel work; do not infer it from an archived plan or PR number.
- Keep PRs focused. One logical change per PR.
- Include test coverage for new behavior.
- Do not hand-edit
CHANGELOG.md; release automation generates it from conventional commit messages.
CI runs on GitHub's free hosted runners: 20 concurrent jobs for the whole account, 5 of them macOS. Pull requests and the integration branch share that capacity. CI selects jobs by run state:
| State | Runs |
|---|---|
| Draft pull request | Repository gates and benchmark-harness self-tests. |
Ready pull request, push to master, or CI dispatch |
Repository gates, benchmark-harness self-tests, Linux and Windows Bazel build and test, Clippy, feature gates, the shipped CLI, and the dashboard. |
CI with run_os=true |
Adds the macOS Bazel build and test lane. |
CI with run_hosts=true |
Adds stock host integrations. |
Use gh workflow run ci.yml --ref <branch> to request an additional run; add
-f run_os=true or -f run_hosts=true for the optional lanes. A newer PR
head replaces its prior run. Master finishes its current snapshot and retains
only the newest pending tip, so merges faster than the long pole still produce
a complete green/red signal. Explicit run_os / run_hosts dispatches keep
separate concurrency groups so a default push cannot cancel them while they
are queued or running. Closing or merging a PR cancels its remaining runs and
drops its Actions caches. Nothing runs on a timer: the packaged-crate
distribution tests and the Hawk lint are workflow_dispatch only.
Open an issue at https://github.com/ScriptedAlchemy/tracedecay/issues with:
- tracedecay version (
tracedecay --version) - OS and architecture
- Steps to reproduce
- Expected vs. actual behavior
This project follows the Contributor Covenant. Be respectful and constructive.
By contributing, you agree that your contributions will be licensed under the MIT License.