[package]
name = "doctrine"
version = "0.15.2"
edition = "2024"
description = "Project tooling CLI"
license = "GPL-3.0-only"
repository = "https://github.com/davidlee/doctrine"
keywords = ["cli", "project"]
categories = ["command-line-utilities"]
# include (not exclude): the package MUST carry generated web/map/dist/, which is
# gitignored — git-based listing (exclude-mode) drops it from the tarball and the
# embedded map server ships broken. An explicit allow-list also never walks the
# .direnv symlink-loop cache (no "File system loop" warnings). Every compile-time
# embed root must be listed: web/map/dist (RustEmbed map — run `just web-build`
# before publish), install/ plugins/ memory/ (RustEmbed), .pi/.../mcp.ts
# (include_str! in boot.rs). Cargo.toml + Cargo.lock are always included.
include = [
  "/src/**",
  "/web/map/dist/**",
  "/install/**",
  "/plugins/**",
  "/memory/**",
  "/templates/mcp.ts",
  "/README.md",
  "/LICENSE-GPL-3.0",
  "/LICENSE-MIT",
]

# cargo-binstall: fetch the prebuilt release asset instead of compiling (SL-174).
# Asset names are the §5.2 contract shared with install.sh + release.yml — change
# all three together (R2). `{ target }` is the rustc triple, matching the
# release.yml tarball names; the archive holds a single `doctrine` executable.
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/doctrine-{ target }.tar.gz"
pkg-fmt = "tgz"
bin-dir = "doctrine"

[dependencies]
anyhow.workspace = true
thiserror.workspace = true
tokio.workspace = true
axum.workspace = true
async-trait.workspace = true
serde.workspace = true
serde_yaml.workspace = true
serde_json.workspace = true
uuid.workspace = true
time.workspace = true
sha2.workspace = true
hex.workspace = true
clap.workspace = true
rust-embed = { workspace = true }
toml.workspace = true
toml_edit.workspace = true
glob.workspace = true
base64.workspace = true
bm25.workspace = true
regex.workspace = true
regex-lite.workspace = true
comfy-table.workspace = true
owo-colors.workspace = true
# Direct dep for nameability only: terminal::size() (impure width probe, src/tty.rs,
# SL-054 PHASE-03). Unifies with the existing transitive v0.29.0 (via comfy-table) —
# default-features = false is REQUIRED: crossterm's default `events` feature drags in
# mio/signal-hook/derive_more (9 new compiled crates) that comfy-table's
# `default-features = false` build never activated. terminal::size() is not feature-
# gated, so OFF keeps zero new compiled weight (single crossterm v0.29.0 in the lock).
crossterm = { version = "0.29", default-features = false }
cordage = { path = "crates/cordage", version = "0.2.0" }
walkdir.workspace = true
webbrowser = "1"
ignore = "0.4.26"

[dev-dependencies]
tower = { workspace = true }
http-body-util = { workspace = true }
tempfile = { workspace = true }
which = "7"
syn = { workspace = true }

[lints]
workspace = true

# Cargo.toml at workspace root
[workspace]
resolver = "3"
members = [
    ".",
    "crates/cordage",
]

[workspace.package]
edition = "2024"
rust-version = "1.85"

# Shared dependency policy: agent-added deps should be explicit and centralized.
[workspace.dependencies]
anyhow = "1"
thiserror = "2"
tracing = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "fs", "process", "io-util", "io-std"] }
sqlx = { version = "0.8", default-features = false, features = [
    "runtime-tokio",
    "postgres",
    "uuid",
    "time",
    "migrate",
    # `macros` is required for the `sqlx::migrate!` embed macro; we do NOT use
    # the compile-time `query!` macros (DEC-005 — sqlx runtime queries only).
    "macros",
] }
axum = "0.8"
async-trait = "0.1"
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"
serde_json = "1"
uuid = { version = "1", features = ["v4", "v5", "v7", "serde"] }
time = { version = "0.3", features = ["formatting", "parsing", "serde", "serde-well-known"] }
sha2 = "0.10"
hex = "0.4"
clap = { version = "4", features = ["derive", "env"] }
rust-embed = { version = "8", features = ["debug-embed"] }
toml = "0.8"
toml_edit = "0.22"
glob = "0.3"
# Leaf-legal external (cf. glob) — pure base64 for the opaque bwrap command wrap
# (SL-182 PHASE-02, INV-5). Standard alphabet matches the probe's `base64 -w0`/`-d`.
base64 = "0.22"
regex = "1"
# Desktop OS directory tree walking hot path; already a transitive dep via
# rust-embed's walkdir → same-file chain.
walkdir = "2"
# Pure-Rust regex for the shared list `--regexp` filter (SL-025 D9). regex-lite
# over full `regex` deliberately: no regex-automata/aho-corasick — id/slug/title
# matching needs nothing fancy, and it keeps compile time + binary size down.
regex-lite = "0.1"
# Table layout + width measurement for every `list` surface (SL-053). The sole
# authority behind listing::render_table — default-features OFF, custom_styling ON
# (transitively pulls crossterm via its tty feature, accepted per SL-053 D1/path A;
# force_no_tty makes piped output terminal-size-independent, D6).
comfy-table = { version = "7", default-features = false, features = ["custom_styling"] }
# Unconditional ANSI colorize methods (`.green()`/`.bold()`/`.color(..)`) for the
# list colour seam (SL-053 PHASE-02). We use ONLY the unconditional API gated on an
# injected `color` bool — never `if_supports_color`, which would re-read env+tty at
# apply-time and reintroduce impurity in the pure listing leaf (D3).
owo-colors = "4"
# Corpus-relative BM25 lexical scorer (SL-017). default-features = false drops the
# DefaultTokenizer/Language stack (stemming/stopwords/deunicode + 5 deps); we drive
# it with a custom Tokenizer delegating to doctrine's lexer (design D2).
bm25 = { version = "2.3.2", default-features = false }
# Dev-only: drive the assembled axum Router in-process (no bound socket) for the
# HTTP VTs — tower's oneshot + http-body-util to collect response bodies.
tower = { version = "0.5", default-features = false, features = ["util"] }
http-body-util = "0.1"
tempfile = "3"
syn = { version = "2", features = ["full", "visit"] }

# ---- rustc lints ----
[workspace.lints.rust]
# Absolute nasties.
warnings = "deny"
unsafe_code = "forbid"

# Public API hygiene.
# missing_docs paused: doc-comment absence is rarely the defect in agent code,
# and the tax slows exploratory work. Re-enable before publishing.
# missing_docs = "deny"
missing_debug_implementations = "deny"

# Dead/unused things should not survive agent iterations.
unused = { level = "deny", priority = -1 }
# unused_crate_dependencies paused: false-positives on test/bench/feature-only
# deps; fights early scaffolding. Re-enable once the dep graph stabilises.
# unused_crate_dependencies = "deny"
unreachable_pub = "deny"

# Future-proofing / edition hygiene.
rust_2018_idioms = { level = "deny", priority = -1 }
rust_2021_compatibility = { level = "warn", priority = -1 }
rust_2024_compatibility = { level = "warn", priority = -1 }

# Safety-sensitive.
unsafe_op_in_unsafe_fn = "deny"
elided_lifetimes_in_paths = "deny"
explicit_outlives_requirements = "deny"

# Avoid accidental cfg drift.
unexpected_cfgs = "deny"

# ---- Clippy lints ----
[workspace.lints.clippy]
# Broad groups first, with lower priority so individual overrides below win.
all = { level = "deny", priority = -2 }
pedantic = { level = "deny", priority = -2 }
cargo = { level = "deny", priority = -2 }
# nursery dropped: warnings="deny" promotes it to hard error, and nursery lints
# are unstable/false-positive-prone. Re-add deliberately if you want that churn.
# nursery = { level = "warn", priority = -3 }
allow_attributes = "deny"
allow_attributes_without_reason = "deny"

# Correctness / panic avoidance.
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"
todo = "deny"
unimplemented = "deny"
unreachable = "deny"
indexing_slicing = "deny"
integer_division = "deny"

# Debug leftovers.
dbg_macro = "deny"
print_stdout = "deny"
print_stderr = "deny"
use_debug = "deny"

# Sloppy error handling.
map_err_ignore = "deny"
let_underscore_must_use = "deny"
let_underscore_future = "deny"
# Doc-comment lints paused alongside missing_docs (see above). Re-enable before publishing.
# missing_errors_doc = "deny"
# missing_panics_doc = "deny"
# missing_safety_doc = "deny"

# Suspicious conversions / casts.
checked_conversions = "deny"
as_conversions = "deny"
cast_possible_truncation = "deny"
cast_possible_wrap = "deny"
cast_precision_loss = "deny"
cast_sign_loss = "deny"

# API/design pressure.
module_name_repetitions = "deny"
pub_use = "deny"
wildcard_imports = "deny"
enum_glob_use = "deny"
default_trait_access = "deny"
implicit_clone = "deny"
clone_on_ref_ptr = "deny"

# Performance and allocation discipline.
large_futures = "deny"
large_stack_arrays = "deny"
large_types_passed_by_value = "deny"
needless_pass_by_value = "deny"
redundant_clone = "deny"

# Async nastiness.
await_holding_lock = "deny"
await_holding_refcell_ref = "deny"
future_not_send = "deny"
async_yields_async = "deny"

# Tests should not be garbage either.
tests_outside_test_module = "deny"
unwrap_in_result = "deny"

# Agent-hostile ambiguity.
shadow_unrelated = "deny"
same_name_method = "deny"
disallowed_methods = "deny"
disallowed_types = "deny"

# Some pedantic lints are too noisy for most real projects.
must_use_candidate = "allow"
missing_const_for_fn = "allow"
return_self_not_must_use = "allow"
similar_names = "allow"
too_many_lines = "allow"
struct_excessive_bools = "allow"
multiple_crate_versions = "allow"
