diff --git a/.agents/skills/clarion-workflow/SKILL.md b/.agents/skills/loomweave-workflow/SKILL.md
similarity index 90%
rename from .agents/skills/clarion-workflow/SKILL.md
rename to .agents/skills/loomweave-workflow/SKILL.md
index db6925b6..1b074574 100644
--- a/.agents/skills/clarion-workflow/SKILL.md
+++ b/.agents/skills/loomweave-workflow/SKILL.md
@@ -1,21 +1,21 @@
---
-name: clarion-workflow
+name: loomweave-workflow
description: >
Use when orienting in an unfamiliar or large codebase and you want to avoid
re-reading or grepping the whole source tree: answering "what calls X",
"where is X defined", "what does X depend on", "what subsystem is X in", or
- "find the function/class/module that does Y". Applies whenever a Clarion
- code-archaeology MCP server (clarion serve / mcp__clarion__* tools) is
+ "find the function/class/module that does Y". Applies whenever a Loomweave
+ code-archaeology MCP server (loomweave serve / mcp__loomweave__* tools) is
available for the project.
---
-# Clarion Workflow
+# Loomweave Workflow
## Overview
-Clarion pre-extracts a codebase into a queryable map — entities (functions,
+Loomweave pre-extracts a codebase into a queryable map — entities (functions,
classes, modules, files), the call/reference/import edges between them, and
-subsystem clusters — and serves it over MCP. **Ask Clarion instead of
+subsystem clusters — and serves it over MCP. **Ask Loomweave instead of
re-exploring the tree.** One `find_entity` + one `callers_of` answers "what
calls this?" without reading a single file.
@@ -26,7 +26,7 @@ calls this?" without reading a single file.
- You need a function's neighborhood, execution paths, or which subsystem it belongs to.
**Not for:** editing code, reading exact implementation bodies (use `summary` or
-read the file once you have its path), or codebases with no `.clarion/` index.
+read the file once you have its path), or codebases with no `.loomweave/` index.
## Entity IDs — the model
@@ -44,10 +44,10 @@ They are not interchangeable:
- **`id`** is the entity's *locator* — a mutable address. It changes when the
code is renamed or moved, and it's the right thing to feed into the next
- Clarion tool call (above).
+ Loomweave tool call (above).
- **`sei`** is the entity's *durable, stable identity*. It survives renames and
moves. **When you record a cross-tool binding** — e.g. attaching a Filigree
- issue to a Clarion entity — **bind on the `sei`, not the `id`.** A binding
+ issue to a Loomweave entity — **bind on the `sei`, not the `id`.** A binding
keyed on the mutable `id` silently breaks the first time the entity moves.
`sei` is `null` when the index predates SEI support or the entity has no binding
@@ -99,7 +99,7 @@ re-reading each path element. `truncated`/`truncation_reason` report `edge-cap`
## Catalogue tools — inspection · faceted search · shortcuts
-Beyond navigation, Clarion serves a **stateless catalogue** of read tools. All
+Beyond navigation, Loomweave serves a **stateless catalogue** of read tools. All
of them: take explicit ids/scopes (no cursor/session — there is no `goto`/`back`
state to manage); **paginate** (`limit`/`offset`, with a `page` block reporting
`total`/`returned`/`truncated` — no silent caps); carry `sei` on every entity
@@ -151,14 +151,14 @@ honest-empty unless a plugin emits those tags. Likewise `high_churn` and
`index_diff` for repo-level freshness).
`search_semantic` is also in the catalogue. It is opt-in under
-`semantic_search:`; when enabled, `clarion analyze` populates the git-ignored
-`.clarion/embeddings.db` sidecar and the query path filters stale vectors by
+`semantic_search:`; when enabled, `loomweave analyze` populates the git-ignored
+`.loomweave/embeddings.db` sidecar and the query path filters stale vectors by
content hash.
> Not in this catalogue: `emit_observation` as a general-purpose write surface.
**Guidance authoring has an operator boundary.** Operators can manage sheets via
-`clarion guidance create/edit/show/list/delete/promote` (plus `export`/`import`
+`loomweave guidance create/edit/show/list/delete/promote` (plus `export`/`import`
for team sharing). Agents may call `propose_guidance` to create a Filigree
observation, but that proposal is inert until an operator promotes it through
`promote_guidance` or the CLI. Promoted sheets reach you through `guidance_for`
@@ -192,10 +192,10 @@ and are composed into `summary` prompts with a real guidance fingerprint.
## Launch
-`clarion serve --path
` where `` contains `.clarion/clarion.db`
-(built by `clarion analyze `). In an MCP client the tools appear as
-`mcp__clarion__find_entity`, etc.
+`loomweave serve --path ` where `` contains `.loomweave/loomweave.db`
+(built by `loomweave analyze `). In an MCP client the tools appear as
+`mcp__loomweave__find_entity`, etc.
-Besides the tools, the server exposes a `clarion://context` **resource** — live
+Besides the tools, the server exposes a `loomweave://context` **resource** — live
entity/subsystem/finding counts and index freshness as JSON, a lightweight read
when you only want the numbers (`project_status` is the fuller tool-based view).
diff --git a/.codex/config.toml b/.codex/config.toml
index 4214a24b..e735a39e 100644
--- a/.codex/config.toml
+++ b/.codex/config.toml
@@ -5,7 +5,7 @@
command = "/home/john/.local/bin/filigree-mcp"
args = ["--project", "/home/john/clarion"]
-[mcp_servers.clarion-dogfood]
-command = "/home/john/clarion/target/release/clarion"
+[mcp_servers.loomweave-dogfood]
+command = "/home/john/clarion/target/release/loomweave"
args = ["serve", "--path", "/home/john/clarion"]
env = { PATH = "/home/john/clarion/plugins/python/.venv/bin:/home/john/.local/bin:/home/john/elspeth/.venv/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/home/john/.cargo/bin:/home/john/.nvm/versions/node/v20.19.3/bin:/home/john/miniconda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
diff --git a/.config/nextest.toml b/.config/nextest.toml
index e7d60791..585efb1c 100644
--- a/.config/nextest.toml
+++ b/.config/nextest.toml
@@ -2,5 +2,5 @@
serve-http = { max-threads = 1 }
[[profile.default.overrides]]
-filter = 'package(clarion-cli) and binary(=serve)'
+filter = 'package(loomweave-cli) and binary(=serve)'
test-group = 'serve-http'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 04d831f4..64c1cee1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -73,7 +73,7 @@ jobs:
- name: install cargo-nextest
uses: taiki-e/install-action@e310bff3ef77234d477d6bb655da153a5c49d1db
- # Ensure all workspace binaries (notably clarion-plugin-fixture) are built
+ # Ensure all workspace binaries (notably loomweave-plugin-fixture) are built
# before nextest runs. wp2_e2e tests need the fixture binary on disk and
# nextest's CARGO_BIN_EXE_* propagation is not reliably set for cross-package
# dev-dep binaries (deferred issue clarion-adeff0916d).
@@ -167,9 +167,9 @@ jobs:
run: |
uv export --project plugins/python --locked --extra dev --no-emit-project \
--format requirements.txt \
- --output-file /tmp/clarion-python-dev-requirements.txt
+ --output-file /tmp/loomweave-python-dev-requirements.txt
uv run --project plugins/python --extra dev pip-audit \
- -r /tmp/clarion-python-dev-requirements.txt
+ -r /tmp/loomweave-python-dev-requirements.txt
- name: check B.4*/B.5 performance gates
run: uv run --project plugins/python --extra dev python scripts/check-b4-gate-result.py --run-b5-smoke
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 89383254..f9eabd03 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,7 +1,7 @@
name: Docs
-# Build and deploy the Clarion documentation site (web/) to GitHub Pages.
-# The site is served at https://clarion.foundryside.dev via the CNAME in
+# Build and deploy the Loomweave documentation site (web/) to GitHub Pages.
+# The site is served at https://loomweave.foundryside.dev via the CNAME in
# web/docs/CNAME. Deploys on push to main; PRs get a strict build check only.
on:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6f835378..3d2fcd59 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,7 +12,20 @@ on:
# The `release` job is gated on event_name == 'push', so workflow_dispatch
# never publishes a public Release — it only validates that the build/verify
# plumbing is healthy before a tag push.
+ #
+ # `target_index: testpypi` additionally runs `publish-pypi` against TestPyPI
+ # (the Phase-C8 dry-run). Real PyPI is NEVER reachable via dispatch — it
+ # publishes solely on a pushed `v*` tag. Leave it `none` for a plain
+ # build/verify dry-run.
workflow_dispatch:
+ inputs:
+ target_index:
+ description: "Publish dry-run target (real PyPI only ever publishes on a pushed v* tag)"
+ type: choice
+ options:
+ - none
+ - testpypi
+ default: none
env:
CARGO_TERM_COLOR: always
@@ -123,9 +136,9 @@ jobs:
run: |
uv export --project plugins/python --locked --extra dev --no-emit-project \
--format requirements.txt \
- --output-file /tmp/clarion-python-dev-requirements.txt
+ --output-file /tmp/loomweave-python-dev-requirements.txt
uv run --project plugins/python --extra dev pip-audit \
- -r /tmp/clarion-python-dev-requirements.txt
+ -r /tmp/loomweave-python-dev-requirements.txt
- name: check B.4*/B.5 performance gates
run: uv run --project plugins/python --extra dev python scripts/check-b4-gate-result.py --run-b5-smoke
@@ -159,7 +172,7 @@ jobs:
build-rust:
needs: [verify]
- name: Build clarion (${{ matrix.target }})
+ name: Build loomweave (${{ matrix.target }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
@@ -188,15 +201,15 @@ jobs:
- name: build release
run: |
set -euo pipefail
- cargo build --release --locked --bin clarion --target ${{ matrix.target }}
+ cargo build --release --locked --bin loomweave --target ${{ matrix.target }}
- name: package
id: package
run: |
set -euo pipefail
- STAGE="clarion-${{ matrix.target }}"
+ STAGE="loomweave-${{ matrix.target }}"
mkdir -p "$STAGE"
- cp "target/${{ matrix.target }}/release/clarion" "$STAGE/"
+ cp "target/${{ matrix.target }}/release/loomweave" "$STAGE/"
cp README.md "$STAGE/"
if [ -f LICENSE ]; then cp LICENSE "$STAGE/"; fi
ARCHIVE="$STAGE.tar.gz"
@@ -210,19 +223,70 @@ jobs:
fi
SMOKE_DIR="$(mktemp -d)"
tar xzf "$ARCHIVE" -C "$SMOKE_DIR"
- "$SMOKE_DIR/$STAGE/clarion" --version
- "$SMOKE_DIR/$STAGE/clarion" --help >/dev/null
+ "$SMOKE_DIR/$STAGE/loomweave" --version
+ "$SMOKE_DIR/$STAGE/loomweave" --help >/dev/null
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
- name: clarion-${{ matrix.target }}
+ name: loomweave-${{ matrix.target }}
path: |
${{ steps.package.outputs.archive }}
${{ steps.package.outputs.archive }}.sha256
if-no-files-found: error
retention-days: 7
+ build-wheels:
+ needs: [verify]
+ name: Build loomweave wheel (${{ matrix.target }})
+ runs-on: ${{ matrix.runner }}
+ # maturin bin-wheels for PyPI. Matrix mirrors `build-rust` (Linux x86_64 +
+ # macOS aarch64); the Intel-macOS leg stays dropped until those runners
+ # recover (same note as build-rust). Off-matrix platforms fall back to the
+ # cosign-signed GitHub Release tarballs / cargo-binstall.
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: x86_64-unknown-linux-gnu
+ runner: ubuntu-latest
+ manylinux: "2_28"
+ - target: aarch64-apple-darwin
+ runner: macos-14
+ manylinux: auto
+ steps:
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
+
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
+ with:
+ python-version: "3.11"
+
+ - name: build wheel (maturin)
+ # Reads crates/loomweave-cli/pyproject.toml ([tool.maturin] bindings="bin").
+ # On Linux maturin-action runs inside a manylinux_2_28 container so the
+ # wheel is broadly installable (a bare host build tags the runner's glibc).
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b
+ with:
+ command: build
+ target: ${{ matrix.target }}
+ manylinux: ${{ matrix.manylinux }}
+ args: --release --out dist --manifest-path crates/loomweave-cli/Cargo.toml
+
+ - name: smoke-check wheel carries the binary
+ run: |
+ set -euo pipefail
+ WHL=$(ls dist/loomweave-*.whl | head -1)
+ echo "built: $WHL"
+ python -m zipfile -l "$WHL" | grep -E 'data/scripts/loomweave' \
+ || { echo "::error::wheel $WHL missing data/scripts/loomweave"; exit 1; }
+
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
+ with:
+ name: loomweave-wheel-${{ matrix.target }}
+ path: dist/*.whl
+ if-no-files-found: error
+ retention-days: 7
+
build-plugin:
needs: [verify]
name: Build Python plugin sdist
@@ -243,29 +307,34 @@ jobs:
- name: build sdist
run: uv run --project plugins/python --extra dev python -m build --sdist --outdir dist plugins/python
+ - name: build wheel
+ # Pure-Python wheel (py3-none-any). Published to PyPI alongside the
+ # sdist by `publish-pypi`; the sdist remains the GitHub-Release asset.
+ run: uv build --wheel --project plugins/python --out-dir dist
+
- name: export locked Python runtime dependencies
run: |
uv export --project plugins/python --locked --no-dev --no-emit-project \
--format requirements.txt \
- --output-file dist/clarion-plugin-python-runtime-requirements.txt
+ --output-file dist/loomweave-plugin-python-runtime-requirements.txt
uv export --project plugins/python --locked --no-dev --no-emit-project \
--format cyclonedx1.5 \
- --output-file dist/clarion-plugin-python-sbom.cdx.json
+ --output-file dist/loomweave-plugin-python-sbom.cdx.json
- name: smoke install sdist
run: |
set -euo pipefail
python -m venv .venv-smoke
. .venv-smoke/bin/activate
- python -m pip install --require-hashes -r dist/clarion-plugin-python-runtime-requirements.txt
+ python -m pip install --require-hashes -r dist/loomweave-plugin-python-runtime-requirements.txt
python -m pip install --no-deps dist/*.tar.gz
# Version-agnostic: assert the module constant matches the installed
# distribution metadata (the sdist's packaged version) rather than a
# hardcoded literal, so a release version bump can't silently break
# this smoke gate (clarion-12667da9f5: a hardcoded '1.0.0' here blocked
# the v1.0.1 publish after the binaries built fine).
- python -c "import clarion_plugin_python, importlib.metadata as m; v = clarion_plugin_python.__version__; d = m.version('clarion-plugin-python'); assert v == d, f'module {v!r} != dist {d!r}'"
- command -v clarion-plugin-python
+ python -c "import loomweave_plugin_python, importlib.metadata as m; v = loomweave_plugin_python.__version__; d = m.version('loomweave-plugin-python'); assert v == d, f'module {v!r} != dist {d!r}'"
+ command -v loomweave-plugin-python
- name: checksum
run: |
@@ -277,13 +346,83 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
- name: clarion-plugin-python-sdist
+ name: loomweave-plugin-python-sdist
path: |
dist/*.tar.gz
dist/*.tar.gz.sha256
+ dist/*.whl
if-no-files-found: error
retention-days: 7
+ publish-pypi:
+ name: Publish to PyPI (plugin first, then loomweave)
+ needs: [build-wheels, build-plugin]
+ runs-on: ubuntu-latest
+ # Real PyPI: ONLY on a pushed v* tag — already gated on the full `verify`
+ # job via build-wheels/build-plugin (`needs: [verify]`). TestPyPI dry-run
+ # (Phase C8): workflow_dispatch with target_index=testpypi. Dispatch can
+ # NEVER reach real PyPI.
+ #
+ # PRECONDITIONS before the first real run (Phase D2 — owner, on pypi.org):
+ # * Register a Trusted Publisher for BOTH projects (`loomweave` and
+ # `loomweave-plugin-python`): owner `foundryside-dev`, repo `loomweave`,
+ # workflow `release.yml`, environment `pypi` (and a `testpypi`
+ # environment + TestPyPI publishers for the dry-run).
+ # * Recommended: add required reviewers to the `pypi` environment so the
+ # upload pauses for a human before going live.
+ # Until then this job fails the OIDC exchange (fail-safe: it cannot
+ # mis-publish, only error).
+ #
+ # Publish order is plugin-first so that by the time `loomweave` is on the
+ # index its `loomweave-plugin-python==` pin already resolves.
+ if: >-
+ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
+ (github.event_name == 'workflow_dispatch' && inputs.target_index == 'testpypi')
+ environment: ${{ github.event_name == 'workflow_dispatch' && 'testpypi' || 'pypi' }}
+ permissions:
+ id-token: write
+ steps:
+ - name: download plugin dists
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
+ with:
+ name: loomweave-plugin-python-sdist
+ path: dl-plugin
+
+ - name: stage plugin package dir (wheel + sdist only)
+ run: |
+ set -euo pipefail
+ mkdir -p pkg-plugin
+ cp dl-plugin/*.whl dl-plugin/*.tar.gz pkg-plugin/
+ ls -la pkg-plugin
+
+ - name: publish loomweave-plugin-python (FIRST)
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
+ with:
+ packages-dir: pkg-plugin
+ attestations: true
+ repository-url: ${{ github.event_name == 'workflow_dispatch' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
+
+ - name: download loomweave wheels
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
+ with:
+ pattern: loomweave-wheel-*
+ path: dl-loomweave
+ merge-multiple: true
+
+ - name: stage loomweave package dir
+ run: |
+ set -euo pipefail
+ mkdir -p pkg-loomweave
+ cp dl-loomweave/*.whl pkg-loomweave/
+ ls -la pkg-loomweave
+
+ - name: publish loomweave (SECOND)
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
+ with:
+ packages-dir: pkg-loomweave
+ attestations: true
+ repository-url: ${{ github.event_name == 'workflow_dispatch' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
+
release:
name: Create GitHub Release
runs-on: ubuntu-latest
@@ -362,25 +501,25 @@ jobs:
# bump or normalization-rule change would invalidate a literal URL.
# `|| true` so `set -e` does not abort the script when the glob
# is empty — we want to fall through to the explicit error below.
- SDIST_PATH=$(ls release/clarion[-_]plugin[-_]python*.tar.gz 2>/dev/null | head -n1 || true)
+ SDIST_PATH=$(ls release/loomweave[-_]plugin[-_]python*.tar.gz 2>/dev/null | head -n1 || true)
if [ -z "$SDIST_PATH" ]; then
echo "::error::no Python plugin sdist found in release/ — refusing to publish notes with a broken install link" >&2
exit 1
fi
SDIST_NAME=$(basename "$SDIST_PATH")
{
- echo "## Clarion ${GITHUB_REF_NAME}"
+ echo "## Loomweave ${GITHUB_REF_NAME}"
echo ""
echo "### Install"
echo ""
echo "Pick the archive for your platform from the Assets list below."
- echo "Verify the SHA256, extract, and place \`clarion\` on your \`\$PATH\`."
+ echo "Verify the SHA256, extract, and place \`loomweave\` on your \`\$PATH\`."
echo ""
echo "\`\`\`bash"
- echo "curl -L -o clarion.tar.gz \\"
- echo " https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/clarion-x86_64-unknown-linux-gnu.tar.gz"
- echo "tar xzf clarion.tar.gz"
- echo "install clarion-x86_64-unknown-linux-gnu/clarion ~/.local/bin/"
+ echo "curl -L -o loomweave.tar.gz \\"
+ echo " https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/loomweave-x86_64-unknown-linux-gnu.tar.gz"
+ echo "tar xzf loomweave.tar.gz"
+ echo "install loomweave-x86_64-unknown-linux-gnu/loomweave ~/.local/bin/"
echo ""
echo "pipx install \\"
echo " https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/${SDIST_NAME}"
@@ -402,7 +541,7 @@ jobs:
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda
with:
tag_name: ${{ github.ref_name }}
- name: Clarion ${{ github.ref_name }}
+ name: Loomweave ${{ github.ref_name }}
body_path: ${{ steps.notes.outputs.notes-file }}
draft: false
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') }}
diff --git a/.gitignore b/.gitignore
index d0293346..9b5e95da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@
**/*.rs.bk
Cargo.lock.bak
-# SQLite working files (project-level .clarion/ is tracked per ADR-005)
+# SQLite working files (project-level .loomweave/ is tracked per ADR-005)
*.db-journal
*.db-wal
@@ -27,15 +27,15 @@ htmlcov/
# Smoke-test result artifacts (per-run; archived separately at tag-cut)
tests/e2e/external-operator-smoke-results-*.md
-# Generated skill fingerprint (rewritten by `clarion install --skills`).
-.agents/skills/clarion-workflow/.fingerprint
+# Generated skill fingerprint (rewritten by `loomweave install --skills`).
+.agents/skills/loomweave-workflow/.fingerprint
-# Clarion runtime artifacts — the index DB, per-project instance fingerprint,
+# Loomweave runtime artifacts — the index DB, per-project instance fingerprint,
# and analyze lock change on every run, so they are not tracked
-# (see .clarion/.gitignore).
-.clarion/clarion.db
-.clarion/instance_id
-.clarion/clarion.lock
+# (see .loomweave/.gitignore).
+.loomweave/loomweave.db
+.loomweave/instance_id
+.loomweave/loomweave.lock
# Documentation site build output (mkdocs `site_dir`, web/mkdocs.yml).
/site-build/
diff --git a/.jules/bolt.md b/.jules/bolt.md
index 2c736dc8..06bfcd1e 100644
--- a/.jules/bolt.md
+++ b/.jules/bolt.md
@@ -1,3 +1,3 @@
## 2026-06-04 - Borrowing strings during clustering graph traversal
-**Learning:** In `crates/clarion-analysis/src/lib.rs`, the fallback algorithm `local_weighted_components` cloned module string IDs deeply while populating neighbor lists (`neighbors`), seen sets (`seen`), and graph traversal stacks (`stack`). For a typical graph with hundreds or thousands of nodes, this causes extensive unnecessary memory allocations and CPU overhead during clustering.
+**Learning:** In `crates/loomweave-analysis/src/lib.rs`, the fallback algorithm `local_weighted_components` cloned module string IDs deeply while populating neighbor lists (`neighbors`), seen sets (`seen`), and graph traversal stacks (`stack`). For a typical graph with hundreds or thousands of nodes, this causes extensive unnecessary memory allocations and CPU overhead during clustering.
**Action:** Replace `String` with `&str` references inside internal clustering structures. Rust's borrow checker can perfectly track the lifetimes bound to the original `ModuleGraph`, and `.to_owned()` only needs to be called when pushing a module into the final partitioned `Vec` results. This dramatically reduces heap allocations.
diff --git a/.clarion/.gitignore b/.loomweave/.gitignore
similarity index 80%
rename from .clarion/.gitignore
rename to .loomweave/.gitignore
index 2944180f..d1d0e32b 100644
--- a/.clarion/.gitignore
+++ b/.loomweave/.gitignore
@@ -1,13 +1,13 @@
-# Clarion .gitignore — ADR-005 tracked-vs-excluded list.
+# Loomweave .gitignore — ADR-005 tracked-vs-excluded list.
# Tracked (committed): config.json, .gitignore itself.
-# Excluded (ignored): clarion.db + instance_id (runtime artifacts, also pinned
+# Excluded (ignored): loomweave.db + instance_id (runtime artifacts, also pinned
# in the repo-root .gitignore), WAL sidecars, shadow DB, per-run logs, tmp.
# Runtime artifacts: the index DB and the per-project instance fingerprint
# change on every analyze run, so they are NOT tracked (untracked 2026-06-02).
-clarion.db
+loomweave.db
instance_id
-clarion.lock
+loomweave.lock
# SQLite write-ahead files never belong in the repo.
*-wal
diff --git a/.clarion/config.json b/.loomweave/config.json
similarity index 100%
rename from .clarion/config.json
rename to .loomweave/config.json
diff --git a/.mcp.json b/.mcp.json
index e123da4a..6b84d9ec 100644
--- a/.mcp.json
+++ b/.mcp.json
@@ -1,10 +1,10 @@
{
"mcpServers": {
- "clarion": {
+ "loomweave": {
"args": [
"serve"
],
- "command": "/home/john/.local/bin/clarion",
+ "command": "/home/john/.local/bin/loomweave",
"env": {},
"type": "stdio"
},
@@ -18,10 +18,10 @@
"mcp",
"--root",
".",
- "--clarion-url",
+ "--loomweave-url",
"http://127.0.0.1:9111",
"--filigree-url",
- "http://127.0.0.1:8542/api/loom/scan-results"
+ "http://127.0.0.1:8542/api/weft/scan-results"
],
"command": "/home/john/.local/bin/wardline",
"type": "stdio"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6de20c7..1e5ec4e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,8 @@
# Changelog
-All notable changes to Clarion are documented here. The format is loosely based
-on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and Clarion
-follows [Semantic Versioning](https://semver.org/) for the `clarion` binary,
+All notable changes to Loomweave are documented here. The format is loosely based
+on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and Loomweave
+follows [Semantic Versioning](https://semver.org/) for the `loomweave` binary,
the workspace crates, and the Python plugin.
API versioning for the federation HTTP read API (`/api/v1/...`) is independent
@@ -12,6 +12,63 @@ only when an incompatible change is made to that surface. See
## [Unreleased]
+## [1.0.0] — Loomweave — 2026-06-05
+
+**This release renames the product and re-baselines its version.** What shipped
+as **Clarion 1.3.0** is now **Loomweave 1.0.0**. The entries below this header
+(`[1.3.0]` and earlier) are the pre-rename Clarion lineage, preserved verbatim
+as history; they describe the same software under its former name.
+
+This is a **clean break with no users yet** — there are no migration shims,
+on-disk path auto-detection, dual-magic fallbacks, binary symlinks, plugin-prefix
+fallbacks, or MCP server-name aliases. Existing local state (a `.clarion/` index)
+is not migrated; re-run `loomweave analyze` to rebuild under the new paths.
+
+### Changed — naming
+
+- **Product `Clarion` → `Loomweave`** and **framework/suite `Loom` → `Weft`**.
+ Hierarchy: the **Weft** framework comprises **Loomweave** (this product, the
+ flagship code-archaeology tool), Filigree, Wardline, and Legis (+ Shuttle planned).
+- **Binary** `clarion` → `loomweave`; **workspace crates** `clarion-{core,cli,mcp,
+ storage,analysis,federation,scanner,plugin-fixture}` → `loomweave-*`.
+- **Python plugin** package `clarion-plugin-python` → `loomweave-plugin-python`,
+ module `clarion_plugin_python` → `loomweave_plugin_python`, shared-data path
+ `share/clarion/plugins/` → `share/loomweave/plugins/`.
+- **Persisted identity**: `.clarion/` → `.loomweave/`, `clarion.db` →
+ `loomweave.db`, `clarion.yaml` → `loomweave.yaml`; the SQLite `application_id`
+ magic `0x434C524E` (`"CLRN"`) → `0x4C4D5756` (`"LMWV"`).
+- **MCP server identity** `clarion` → `loomweave`; the `clarion-workflow` prompt/
+ skill → `loomweave-workflow`.
+- **Environment variables** `CLARION_*` → `LOOMWEAVE_*`; the federation/suite
+ variables `CLARION_LOOM_*` (e.g. `CLARION_LOOM_TOKEN`) → `WEFT_*` (e.g.
+ `WEFT_TOKEN`) — they name the framework, not the product.
+- **Error / diagnostic codes** prefix `CLA-` → `LMWV-` (e.g.
+ `CLA-INFRA-STORAGE-FOREIGN-DB` → `LMWV-INFRA-STORAGE-FOREIGN-DB`); the
+ ADR-022 plugin `rule_id_prefix` grammar core prefix `CLA-` → `LMWV-`.
+- **Documentation/site** repository `github.com/tachyon-beep/clarion` →
+ `github.com/foundryside-dev/loomweave`; docs domain `clarion.foundryside.dev`
+ → `loomweave.foundryside.dev`.
+
+### Changed — federation contract (cross-product; Filigree + Wardline move in lockstep)
+
+These touch the wire contract shared with sibling products and require the peers
+to be updated in step (the peer pins/clients are being renamed together):
+
+- Federation HTTP read API routes `/api/loom/...` → `/api/weft/...`; identity
+ headers `X-Loom-Component` / `X-Loom-Nonce` / `X-Loom-Timestamp` →
+ `X-Weft-*`; the `X-Loom-Component` identity value `clarion:` →
+ `loomweave:`. `api_version` stays `1` (a rename is not a wire-incompatible change).
+- Federation JSON field names `loom_*` (e.g. `loom_files`, `loom_findings`,
+ `loom_component`) → `weft_*`.
+- The Filigree entity-association field `clarion_entity_id` → `loomweave_entity_id`.
+- The Stable Entity Identifier (SEI) prefix `clarion:eid:` → `loomweave:eid:`
+ (ADR-038 / the Weft SEI conformance standard).
+
+### Version
+
+- Recut `1.3.0` → `1.0.0` across the workspace `Cargo.toml`, every
+ `pyproject.toml`/`plugin.toml`, and the Python plugin `__version__`.
+
## [1.3.0] — 2026-06-05
### Added
@@ -23,7 +80,7 @@ only when an incompatible change is made to that surface. See
Wardline trust decorators (`external_boundary` / `trust_boundary` / `trusted`)
receive `wardline` entity metadata and `wardline:*` tags when the descriptor is
available; a missing, invalid, or version-skewed descriptor degrades honestly to
- normal structural extraction. This **fully retires** the Clarion-side
+ normal structural extraction. This **fully retires** the Loomweave-side
`wardline.core.registry` startup coupling (federation asterisk 2, registered at
`~/loom/asterisk-register.md`): plugin startup performs zero in-process Wardline import, so the
plugin no longer requires a co-installed Wardline and is robust to Wardline's
@@ -34,7 +91,7 @@ only when an incompatible change is made to that surface. See
- **Filigree issue lookups key by Stable Entity Identity (SEI).** The MCP
`entity_issue_list` path and the federation Filigree client resolve issues by
- SEI rather than source locator, aligning issue enrichment with Clarion's stable
+ SEI rather than source locator, aligning issue enrichment with Loomweave's stable
identity (one key per entity — SEI xor locator, no per-row fallback).
- Refreshed release-facing README / index documentation for the 1.3.0 release
line, including the 39-tool MCP surface, current install artifact names, fixed
@@ -56,26 +113,26 @@ only when an incompatible change is made to that surface. See
### Fixed
-- **`clarion doctor` reports enrich-only integration bindings as a warning, not a
+- **`loomweave doctor` reports enrich-only integration bindings as a warning, not a
gate failure (federation-axiom compliance).** A missing or stale
- Clarion+Filigree+Wardline binding previously mapped to `problem` (exit 1),
+ Loomweave+Filigree+Wardline binding previously mapped to `problem` (exit 1),
which made an enrich-only sibling effectively required — contradicting
- `loom.md` §5. Both the JSON and text doctor paths now report `warning` for
+ `weft.md` §5. Both the JSON and text doctor paths now report `warning` for
missing/stale bindings; unparseable bindings and `--fix` repair failures remain
- `problem`. A bare `clarion doctor` on a no-bindings (Clarion-solo or
- Clarion+Filigree-only) project now exits 0 with the warning surfaced.
+ `problem`. A bare `loomweave doctor` on a no-bindings (Loomweave-solo or
+ Loomweave+Filigree-only) project now exits 0 with the warning surfaced.
### Security
- **Closed a config-driven command-execution path from untrusted repository
- contents (`clarion-4b5a8aff54`).** `clarion analyze` (the SEI git-rename
- signal) and `clarion serve` (the `index_diff_get` freshness/drift report)
+ contents (`clarion-4b5a8aff54`).** `loomweave analyze` (the SEI git-rename
+ signal) and `loomweave serve` (the `index_diff_get` freshness/drift report)
shelled `git` inside the analyzed repository with repo-local configuration and
Git attributes enabled, so a malicious repository could execute arbitrary
commands as the local user during an ordinary analyze/serve — via
`core.fsmonitor`, an external diff/textconv driver, or a `filter..clean`
selected by a `filter` attribute. All corpus-facing `git` calls now route
- through a single hardened helper (`clarion_core::hardened_git_command`) that
+ through a single hardened helper (`loomweave_core::hardened_git_command`) that
ignores operator/global/system config, strips config/exec-injecting environment
variables, overrides the program-naming repo-local keys via highest-precedence
`-c` flags (including `core.fsmonitor=false` and `core.attributesFile=`), and
@@ -103,18 +160,18 @@ only when an incompatible change is made to that surface. See
- **Guidance maturity — WS6 (REQ-GUIDANCE-03/-05/-06; ADR-007, ADR-024).** The
guidance system moves from schema baseline to operator-usable.
- - **`clarion guidance` CLI** — `create` / `edit` (`$EDITOR`) / `show` / `list`
+ - **`loomweave guidance` CLI** — `create` / `edit` (`$EDITOR`) / `show` / `list`
/ `delete` / `export` / `import`. Match-rule syntax (`path:` / `tag:` /
`kind:` / `subsystem:` / `entity:`), scope-levels (project→function), and
`--expires` normalisation to a full ISO-8601 instant. Sheets are written via a
- new non-run-scoped `clarion-storage` guidance API, and the rule-matcher is
- lifted into `clarion-storage` as the single source of truth shared by the CLI,
+ new non-run-scoped `loomweave-storage` guidance API, and the rule-matcher is
+ lifted into `loomweave-storage` as the single source of truth shared by the CLI,
`analyze`, and the MCP read path.
- - **Staleness findings (`analyze`).** `CLA-FACT-GUIDANCE-ORPHAN` (WARN) now also
+ - **Staleness findings (`analyze`).** `LMWV-FACT-GUIDANCE-ORPHAN` (WARN) now also
fires for a `match_rules {entity:…}` rule pointing at a deleted entity (was
- `guides`-edge only); new `CLA-FACT-GUIDANCE-EXPIRED` (INFO) and
- `CLA-FACT-GUIDANCE-CHURN-STALE` (WARN, confidence 0.7 heuristic, asymmetric
- threshold 50 / 20-pinned). Surfaced via `clarion guidance list --stale`
+ `guides`-edge only); new `LMWV-FACT-GUIDANCE-EXPIRED` (INFO) and
+ `LMWV-FACT-GUIDANCE-CHURN-STALE` (WARN, confidence 0.7 heuristic, asymmetric
+ threshold 50 / 20-pinned). Surfaced via `loomweave guidance list --stale`
(review-cadence age) / `--expired`. CHURN-STALE is honest-empty until
`git_churn_count` population lands.
- **Team import/export.** `export --to ` / `import ` — deterministic
@@ -143,9 +200,9 @@ only when an incompatible change is made to that surface. See
to `..HEAD`. `analyze` also applies any pending migrations on startup
(under the analyze lock) so a binary upgrade does not hard-fail on a DB that
`install` has not re-touched.
-- **Stable Entity Identity (SEI) — Wave 1 / WS1 (ADR-038).** Clarion is now the
+- **Stable Entity Identity (SEI) — Wave 1 / WS1 (ADR-038).** Loomweave is now the
suite's identity authority: it mints a durable, opaque **SEI**
- (`clarion:eid:`) for every entity
+ (`loomweave:eid:`) for every entity
and demotes the `{plugin}:{kind}:{qualname}` id to a mutable **locator**, so
cross-tool bindings survive rename and move.
- Migration `0005` adds `sei_bindings` (durable identity store, keyed by SEI,
@@ -164,7 +221,7 @@ only when an incompatible change is made to that surface. See
- HTTP read API: `POST /api/v1/identity/resolve` (+ `:batch`),
`GET /api/v1/identity/sei/{sei}`, `GET /api/v1/identity/lineage/{sei}`.
`resolve` fail-closed-rejects an SEI-shaped input by the reserved
- `clarion:eid:` prefix (REQ-F-02). `_capabilities` advertises
+ `loomweave:eid:` prefix (REQ-F-02). `_capabilities` advertises
`sei: { supported: true, version: 1 }`.
- The MCP tool surface carries the `sei` alongside every entity id (no MCP
locator exception — REQ-C-04), via a read-time `sei_bindings` join.
@@ -172,7 +229,7 @@ only when an incompatible change is made to that surface. See
passes; the cross-tool hard-cutover backfill is documented in
[`docs/federation/sei-migration-playbook.md`](docs/federation/sei-migration-playbook.md)
and surfaced for owner-gated scheduling.
-- **Incremental analysis — Wave 2 / T3.1.** `clarion analyze` now skips files
+- **Incremental analysis — Wave 2 / T3.1.** `loomweave analyze` now skips files
whose whole-file hash matches the prior run, reusing their entities (the entity
graph is cumulative and edges are insert-or-ignore, so the skip is speed-only).
`skipped_files` is reported in `stats.json` and a `skipped_unchanged` progress
@@ -182,18 +239,18 @@ only when an incompatible change is made to that surface. See
orphaned, and skipped entries are re-fed into the prior-index rebuild so the
snapshot does not decay. Files carrying a secret finding are never skipped (their
finding anchor must stay stable).
-- **Dossier participation surface — Wave 2 / WS4.** The exact Clarion HTTP slices
+- **Dossier participation surface — Wave 2 / WS4.** The exact Loomweave HTTP slices
the cross-tool dossier *assembler* (Wardline) reads are pinned in
[`docs/federation/contracts.md`](docs/federation/contracts.md) and specified in
- [`docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md`](docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md):
+ [`docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md`](docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md):
identity (`resolve` → SEI + content-axis freshness; `resolve_sei`/`lineage` →
identity-axis freshness), structural linkages (callers/callees), and file
- context. Clarion contributes slices and the SEI join key; it does **not** proxy
+ context. Loomweave contributes slices and the SEI join key; it does **not** proxy
Filigree associations (read directly from Filigree's own ADR-029 endpoint) or
assemble the envelope. Proven end-to-end against a renamed-function fixture
(`serve_http_dossier_participation_surface_serves_a_renamed_function`).
- **`legis` governance consumption — Wave 3 / WS9 (governed paradise).** `legis`
- consumes Clarion's stable identity as an **opt-in** governance layer a solo
+ consumes Loomweave's stable identity as an **opt-in** governance layer a solo
project never sees; core paradise (Wave 2) does not depend on it.
- **Git-rename provider seam (REQ-C-05).** A second `GitRenameSource`,
`LegisGitRenameSource`, reads `legis`'s `GET /git/renames` over HTTP and feeds
@@ -202,22 +259,22 @@ only when an incompatible change is made to that surface. See
(`select_git_rename_source`, `--legis-url`) is enrich-only and
capability-aware: the shell source remains the default and fallback; an
unset/unreachable `legis` issues no HTTP and is byte-identical to before. The
- two suppliers observe different rename windows (Clarion's `analyze` depends on
+ two suppliers observe different rename windows (Loomweave's `analyze` depends on
the working-tree window; `legis` serves only committed rev-ranges), so the
seam is built/tested/ready but inert in the default pipeline until `legis`
- adds a working-tree surface or Clarion drives a committed re-index — a gap
+ adds a working-tree surface or Loomweave drives a committed re-index — a gap
surfaced (not papered) in [`docs/federation/contracts.md`](docs/federation/contracts.md).
The matcher is fail-closed regardless, so neither window can cause a false
carry. Proven by
`selector_keeps_working_tree_rename_even_when_a_reachable_legis_sees_nothing`.
- - **Audit-spine consumption.** `legis` reads Clarion's existing
+ - **Audit-spine consumption.** `legis` reads Loomweave's existing
`resolve`/`resolve_sei`/`lineage` routes as its governance audit spine; the
consumption contract is pinned in `docs/federation/contracts.md`. Per REQ-L-01
(Option 3) `legis` owns integrity at its own boundary (snapshot-hash over
- polled lineage) — Clarion ships **no** lineage hash-chain or signature.
- - **No trust adjudication.** Clarion carries the trust vocabulary verbatim and
+ polled lineage) — Loomweave ships **no** lineage hash-chain or signature.
+ - **No trust adjudication.** Loomweave carries the trust vocabulary verbatim and
adds no policy/attestation engine — Wardline analyses, `legis` governs,
- attestations key on Clarion's SEI.
+ attestations key on Loomweave's SEI.
### Fixed
@@ -233,15 +290,15 @@ only when an incompatible change is made to that surface. See
### Added
-- **Wardline taint-fact store (ADR-036).** Clarion now serves as the persistent
- read+write store for Wardline's taint facts over HTTP, keyed to Clarion entity
+- **Wardline taint-fact store (ADR-036).** Loomweave now serves as the persistent
+ read+write store for Wardline's taint facts over HTTP, keyed to Loomweave entity
qualnames. New migration `0003` adds the `wardline_taint_facts` table; facts
are written through the storage writer-actor
(`WriterCmd::UpsertWardlineTaintFact`) and resolved/fetched via the new
`wardline_taint` storage module. Routes:
- `POST /api/wardline/resolve` — exact-tier qualname → entity resolution.
- `POST /api/wardline/taint-facts` — exact-only batch write; the Wardline
- payload is stored byte-verbatim (`serde_json::value::RawValue`) so Clarion
+ payload is stored byte-verbatim (`serde_json::value::RawValue`) so Loomweave
never reshapes Wardline's JSON.
- `GET /api/wardline/taint-facts` and `…:batch-get` — reads carry a live
freshness hash so a consumer can detect drift against the entity's current
@@ -254,35 +311,35 @@ only when an incompatible change is made to that surface. See
reconciled against Wardline findings stored in Filigree. A pure
qualname-reconciliation module matches `metadata.wardline.qualname` byte-exact
against entity-ID segment-3; a two-hop Filigree read
- (`GET /api/loom/files` → `GET /api/loom/findings`) resolves a path to its
+ (`GET /api/weft/files` → `GET /api/weft/findings`) resolves a path to its
findings. Per the enrich-only axiom, any unreachable hop degrades the section
to `result_kind: "unavailable"` rather than failing the tool.
-- **`clarion doctor [--fix]`.** A new subcommand that verifies — and with
+- **`loomweave doctor [--fix]`.** A new subcommand that verifies — and with
`--fix` repairs in place — the installed agent-orientation surfaces: the
- `clarion-workflow` skill pack, the `SessionStart` hook in
- `.claude/settings.json`, and the `clarion` entry in `.mcp.json` (which
- `clarion install` does not register automatically). It prints a per-surface
+ `loomweave-workflow` skill pack, the `SessionStart` hook in
+ `.claude/settings.json`, and the `loomweave` entry in `.mcp.json` (which
+ `loomweave install` does not register automatically). It prints a per-surface
✓/✗ report plus the index snapshot and exits non-zero when any problem
remains, so it is usable as a CI / pre-commit gate. Repairs reuse the same
- idempotent installers as `clarion install`, and the `.mcp.json` merge is
+ idempotent installers as `loomweave install`, and the `.mcp.json` merge is
never-clobber: sibling servers and a deliberately customised `command` are
preserved (only the `--path` args are corrected).
### Changed
- Federation contracts pinned/clarified: the Wardline taint-store routes +
- freshness contract, the two consumed loom routes for Flow B, Clarion→Filigree
+ freshness contract, the two consumed weft routes for Flow B, Loomweave→Filigree
ephemeral-port endpoint discovery, and the `scan_run_id` contract (stale
Phase-0 handshake references removed).
-- `docs/suite/loom.md`: added a written retirement condition to the §5
+- `docs/suite/weft.md`: added a written retirement condition to the §5
asterisk-2 (Wardline→Filigree pipeline coupling), and refreshed the §9 status
table to the post-1.0 state.
-- ADR-036 accepted (Clarion as Wardline taint-fact store), documenting the
+- ADR-036 accepted (Loomweave as Wardline taint-fact store), documenting the
in-process two-writer concurrency posture.
- The Python round-trip self-test now hard-fails (instead of silently skipping)
- when the installed `clarion-plugin-python` entry point is missing, so a broken
+ when the installed `loomweave-plugin-python` entry point is missing, so a broken
editable install cannot pass CI green.
-- **Shared error vocabulary (ADR-037).** New `clarion-core::errors` module is now
+- **Shared error vocabulary (ADR-037).** New `loomweave-core::errors` module is now
the single typed source of truth for both wire error-code vocabularies:
`HttpErrorCode` (federation HTTP read API, `SCREAMING_SNAKE_CASE`, moved out of
`http_read.rs`) and a new `McpErrorCode` (MCP tool envelope, kebab-case) that
@@ -312,9 +369,9 @@ carries the full post-1.0.0 scope below plus the build fix.
### Added
-- `clarion db backup ` — a consistent, WAL-safe online backup of
- `.clarion/clarion.db` via `rusqlite::backup::Backup`. Safe to run during a
- live `clarion analyze` (captures outstanding WAL frames into a standalone
+- `loomweave db backup ` — a consistent, WAL-safe online backup of
+ `.loomweave/loomweave.db` via `rusqlite::backup::Backup`. Safe to run during a
+ live `loomweave analyze` (captures outstanding WAL frames into a standalone
single-file copy, unlike `cp`), writes atomically (temp sibling + rename),
refuses to clobber without `--force`, and verifies the copy with
`PRAGMA integrity_check` before promoting it. Closes gap-register STO-04
@@ -329,25 +386,25 @@ carries the full post-1.0.0 scope below plus the build fix.
- `call_sites(id, role=caller|callee, kind?, confidence?, path?)` MCP tool —
shows the actual source sites behind `calls`/`references` edges (file, line,
byte column, line text, edge kind, confidence) so an agent can see *why*
- Clarion believes an edge exists. Statically-unbindable calls are returned in
+ Loomweave believes an edge exists. Statically-unbindable calls are returned in
a separate `unresolved_sites` list, never mixed with resolved evidence.
Filterable by edge kind and a documented best-effort production/test path
heuristic. No LLM call. The MCP surface now exposes thirteen tools
(clarion-9392f74881).
-- **Filigree finding emission (WP9-B core, REQ-FINDING-03).** `clarion analyze`
+- **Filigree finding emission (WP9-B core, REQ-FINDING-03).** `loomweave analyze`
Phase 8 POSTs the run's persisted findings to Filigree's
- `POST /api/v1/scan-results` intake, with Clarion's richer fields nested under
- `metadata.clarion.*` (wire contract pinned in
+ `POST /api/v1/scan-results` intake, with Loomweave's richer fields nested under
+ `metadata.loomweave.*` (wire contract pinned in
[`docs/federation/contracts.md`](docs/federation/contracts.md)). Emission is
enrich-only — gated behind `integrations.filigree.{enabled,emit_findings}`
(now **both default `false`**, so enabling Filigree for `issues_for` reads
never silently starts outbound emission — clarion-a26de2f368), and any
Filigree-side failure is recorded in `stats.json`
- (`CLA-INFRA-FILIGREE-UNREACHABLE`) instead of failing the run. Findings
+ (`LMWV-INFRA-FILIGREE-UNREACHABLE`) instead of failing the run. Findings
anchored to a `briefing_blocked` entity are excluded, matching the fail-closed
read posture (clarion-8b32ba0d02). Resolves the 1.0 "finding emission
deferred" limitation below.
-- **`clarion analyze --resume RUN_ID` (WP9-B, REQ-FINDING-05).** Reopens a prior
+- **`loomweave analyze --resume RUN_ID` (WP9-B, REQ-FINDING-05).** Reopens a prior
run's `runs` row (a new `WriterCmd::ResumeRun` `UPDATE`s it back to `running`
instead of `INSERT`ing, which conflicted on the existing run PK), re-walks the
tree, and emits findings to Filigree with `mark_unseen=false` so the re-emit
@@ -356,20 +413,20 @@ carries the full post-1.0.0 scope below plus the build fix.
(previously only entities did), so a resumed run reproduces the same durable
graph as the original. The emitted `mark_unseen` value is recorded in
`stats.json`. Tracked under clarion-dd29e69e0e.
-- **`clarion analyze --prune-unseen` (WP9-B, REQ-FINDING-06).** After emission
- (Phase 8b), POSTs Filigree's `POST /api/loom/findings/clean-stale` retention
- route, scoped to `scan_source=clarion`, asking it to soft-archive its own
- `unseen_in_latest` Clarion findings older than
+- **`loomweave analyze --prune-unseen` (WP9-B, REQ-FINDING-06).** After emission
+ (Phase 8b), POSTs Filigree's `POST /api/weft/findings/clean-stale` retention
+ route, scoped to `scan_source=loomweave`, asking it to soft-archive its own
+ `unseen_in_latest` Loomweave findings older than
`integrations.filigree.prune_unseen_days` (default 30). Soft-archive, not
delete: Filigree moves them to `fixed` and auto-reopens on reappearance
(Filigree ADR-015). Enrich-only — a Filigree outage or the integration being
disabled is recorded in `stats.json` (`filigree_prune`) and never fails the
run; the `scan_source` scoping is enforced server-side so the sweep can only
- touch Clarion's findings. (Closes the REQ-FINDING-06 piece of
+ touch Loomweave's findings. (Closes the REQ-FINDING-06 piece of
clarion-dd29e69e0e; the route was found to already exist in Filigree, so the
earlier "file a request" memo is superseded — see its withdrawal banner.) The
remaining Phase-0 scan-run-create handshake is an open contract question with
- Filigree (Clarion relies on the peer tolerating an unknown `scan_run_id`),
+ Filigree (Loomweave relies on the peer tolerating an unknown `scan_run_id`),
tracked separately under `release:1.1`.
### Changed
@@ -384,7 +441,7 @@ carries the full post-1.0.0 scope below plus the build fix.
ADR-024 (no published build). Closes gap-register STO-04 / V11-STO-04
(clarion-bdabfd6bca).
- The writer actor now opens its batch transactions with `BEGIN IMMEDIATE`
- (via a new `clarion_storage::retry::begin_immediate` helper with bounded
+ (via a new `loomweave_storage::retry::begin_immediate` helper with bounded
`SQLITE_BUSY`/`SQLITE_LOCKED` retry + exponential backoff) instead of a
deferred `BEGIN`. Taking the write lock up front resolves cross-process
write contention at lock-acquire — where `busy_timeout` is honored — rather
@@ -394,8 +451,8 @@ carries the full post-1.0.0 scope below plus the build fix.
### Fixed
- **macOS release build under `-D warnings`.** The Linux-only `prlimit`
- helpers imported/defined in `clarion-core::plugin::host` (and two Linux-only
- test sites in `clarion-mcp` and `clarion-cli`) were unused on
+ helpers imported/defined in `loomweave-core::plugin::host` (and two Linux-only
+ test sites in `loomweave-mcp` and `loomweave-cli`) were unused on
`*-apple-darwin`, so the release build failed with unused-import / dead-code
errors. They are now `cfg`-gated to `target_os = "linux"` (plus `test` where
unit tests reference them). CI gained a native macOS (aarch64) build + clippy
@@ -404,9 +461,9 @@ carries the full post-1.0.0 scope below plus the build fix.
## [1.0.0] — 2026-05-19
-First publishable release. Clarion ships as a Rust core (`clarion` binary, five
+First publishable release. Loomweave ships as a Rust core (`loomweave` binary, five
workspace crates) plus an editable-install Python language plugin
-(`clarion-plugin-python`). Released under the [MIT License](LICENSE).
+(`loomweave-plugin-python`). Released under the [MIT License](LICENSE).
Targets the `v1.0.0` tag (cut by the operator once all release blockers are
green); supersedes the pre-release `v0.1-sprint-1` and `v0.1-sprint-2`
@@ -414,16 +471,16 @@ working tags, which remain in the repo as historical anchors.
### Core
-- `clarion install --path` initialises a project's `.clarion/` directory
+- `loomweave install --path` initialises a project's `.loomweave/` directory
(instance ID, SQLite DB, migrations).
-- `clarion analyze` walks a Python corpus and persists the structural graph
+- `loomweave analyze` walks a Python corpus and persists the structural graph
(entities + `contains`, `calls`, `references`, `imports` edges) to a local
SQLite store via the writer-actor pattern (ADR-011).
-- `clarion serve` exposes the MCP stdio surface for consult-mode agents:
+- `loomweave serve` exposes the MCP stdio surface for consult-mode agents:
`entity_at`, `find_entity`, `callers_of`, `execution_paths_from`,
`summary`, `issues_for`, `neighborhood`, `subsystem_members`.
-### Python plugin (`clarion-plugin-python` 1.0.0)
+### Python plugin (`loomweave-plugin-python` 1.0.0)
- Pyright-backed entity extraction for functions, classes, and modules; resolved
/ ambiguous / inferred call edges per ADR-022.
@@ -434,8 +491,8 @@ working tags, which remain in the repo as historical anchors.
### Federation HTTP read API (ADR-014)
-The publisher-side of Clarion's federation contract with Filigree's
-`ClarionRegistry`. Pinned in [`docs/federation/contracts.md`](docs/federation/contracts.md);
+The publisher-side of Loomweave's federation contract with Filigree's
+`LoomweaveRegistry`. Pinned in [`docs/federation/contracts.md`](docs/federation/contracts.md);
fixtures under [`docs/federation/fixtures/`](docs/federation/fixtures/) are
normative.
@@ -450,10 +507,10 @@ normative.
request, single pooled `ReaderPool` checkout per batch. Four-way
partitioning: `resolved` / `not_found` / `briefing_blocked` / `errors`.
- **Bearer authentication.** `serve.http.token_env` (default
- `CLARION_LOOM_TOKEN`) names the env var holding the inbound bearer
+ `WEFT_TOKEN`) names the env var holding the inbound bearer
token. Loopback-without-token stays unauthenticated for the v0.1 trust
model; non-loopback-without-token is refused at startup with
- `CLA-CONFIG-HTTP-NO-AUTH`.
+ `LMWV-CONFIG-HTTP-NO-AUTH`.
- **Briefing-blocked propagation.** Files flagged by the pre-ingest secret
scanner return `403 BRIEFING_BLOCKED` on the single-file endpoint and
surface in the `briefing_blocked[]` partition on the batch endpoint.
@@ -493,7 +550,7 @@ normative.
- **Python only.** Other-language plugins (`NG-15`) are v2.0+ scope.
- **Filigree finding emission deferred to a future release (tracked under
- `release:v1.1`).** Cross-product POSTing of Clarion-generated findings into
+ `release:v1.1`).** Cross-product POSTing of Loomweave-generated findings into
Filigree's intake (WP9-B) is deferred per the [Sprint 2 scope amendment](docs/implementation/sprint-2/scope-amendment-2026-05.md).
`issues_for(id)` (the WP9-A binding for reading from Filigree) ships in 1.0.
*(WP9-B core shipped post-1.0 — see the Filigree-finding-emission entry under
@@ -502,11 +559,11 @@ normative.
available, with a narrow core-extension fallback for files that predate
manifest capture.
- **Cooperative HMAC inbound auth** ships for the HTTP read API via
- `serve.http.identity_token_env` and `X-Loom-Component: clarion:`.
+ `serve.http.identity_token_env` and `X-Weft-Component: loomweave:`.
The older bearer-token path remains available for compatibility.
- **Python plugin imports `wardline.core.registry.REGISTRY` at startup**
- (loom.md §5 asterisk 2). Initialization coupling scoped to the
- Wardline-aware plugin only; Clarion core and non-Wardline-aware plugins are
+ (weft.md §5 asterisk 2). Initialization coupling scoped to the
+ Wardline-aware plugin only; Loomweave core and non-Wardline-aware plugins are
unaffected. *Retirement condition*: Wardline ships a stable runtime probe
API.
- **Wardline state-file ingest deferred to a future release (tracked under
@@ -518,33 +575,33 @@ normative.
with WP9-B / WP10.
- **Pre-WP5 catalogue upgrade requirement.** Briefing-blocked annotations
are stored as a JSON property on file entities at v1.0 (v1.1 promotes
- the field to a typed column). A v1.0 binary opening a `.clarion/clarion.db`
+ the field to a typed column). A v1.0 binary opening a `.loomweave/loomweave.db`
produced by a pre-WP5 binary finds no `briefing_blocked` properties —
pre-WP5 analyzers never wrote them — and will serve the entire catalogue
without refusal. Operators upgrading from a pre-WP5 install MUST run
- `clarion analyze` (scanner active by default) against the project root
+ `loomweave analyze` (scanner active by default) against the project root
before exposing the HTTP read API or calling the `summary` MCP tool. See
[`docs/operator/secret-scanning.md`](docs/operator/secret-scanning.md#pre-wp5-catalogue-upgrade-requirement).
### Documentation
-- Design ladder under [`docs/clarion/1.0/`](docs/clarion/1.0/) — `requirements.md`,
+- Design ladder under [`docs/loomweave/1.0/`](docs/loomweave/1.0/) — `requirements.md`,
`system-design.md`, `detailed-design.md`.
-- ADRs under [`docs/clarion/adr/`](docs/clarion/adr/) — 28 Accepted at 1.0
+- ADRs under [`docs/loomweave/adr/`](docs/loomweave/adr/) — 28 Accepted at 1.0
(ADR-001…ADR-034 with the documented Backlog/Superseded subset excluded).
ADR-012 is superseded by ADR-014, whose Security
Posture and Error Envelope are in turn partially extended by ADR-034
for the Sprint 3 federation hardening. Four ADRs (ADR-009, ADR-010,
ADR-019, ADR-020) remain Backlog and are tracked inside
`system-design.md` §12 / `detailed-design.md` §11 until promoted.
-- Loom-suite doctrine at [`docs/suite/loom.md`](docs/suite/loom.md).
+- Weft-suite doctrine at [`docs/suite/weft.md`](docs/suite/weft.md).
- Federation contract surface at [`docs/federation/contracts.md`](docs/federation/contracts.md).
- Operator guides under [`docs/operator/`](docs/operator/) — getting-started,
OpenRouter setup, HTTP read API.
-[Unreleased]: https://github.com/tachyon-beep/clarion/compare/v1.3.0...HEAD
-[1.3.0]: https://github.com/tachyon-beep/clarion/compare/v1.2.0...v1.3.0
-[1.2.0]: https://github.com/tachyon-beep/clarion/compare/v1.1.0...v1.2.0
-[1.1.0]: https://github.com/tachyon-beep/clarion/compare/v1.0.1...v1.1.0
-[1.0.1]: https://github.com/tachyon-beep/clarion/compare/v1.0.0...v1.0.1
-[1.0.0]: https://github.com/tachyon-beep/clarion/releases/tag/v1.0.0
+[Unreleased]: https://github.com/foundryside-dev/loomweave/compare/v1.3.0...HEAD
+[1.3.0]: https://github.com/foundryside-dev/loomweave/compare/v1.2.0...v1.3.0
+[1.2.0]: https://github.com/foundryside-dev/loomweave/compare/v1.1.0...v1.2.0
+[1.1.0]: https://github.com/foundryside-dev/loomweave/compare/v1.0.1...v1.1.0
+[1.0.1]: https://github.com/foundryside-dev/loomweave/compare/v1.0.0...v1.0.1
+[1.0.0]: https://github.com/foundryside-dev/loomweave/releases/tag/v1.0.0
diff --git a/Cargo.lock b/Cargo.lock
index f7aa7efe..508f3d12 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -93,9 +93,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "assert_cmd"
-version = "2.2.1"
+version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39bae1d3fa576f7c6519514180a72559268dd7d1fe104070956cb687bc6673bd"
+checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
dependencies = [
"anstyle",
"bstr",
@@ -125,9 +125,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "axum"
@@ -192,9 +192,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
-version = "2.11.1"
+version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
[[package]]
name = "blake3"
@@ -232,9 +232,9 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.20.2"
+version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytes"
@@ -244,9 +244,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
-version = "1.2.60"
+version = "1.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
+checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -310,146 +310,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
-[[package]]
-name = "clarion-analysis"
-version = "1.3.0"
-dependencies = [
- "anyhow",
- "serde",
- "sha2",
- "xgraph",
-]
-
-[[package]]
-name = "clarion-cli"
-version = "1.3.0"
-dependencies = [
- "anyhow",
- "assert_cmd",
- "axum",
- "blake3",
- "clap",
- "clarion-analysis",
- "clarion-core",
- "clarion-federation",
- "clarion-mcp",
- "clarion-plugin-fixture",
- "clarion-scanner",
- "clarion-storage",
- "dotenvy",
- "fs2",
- "hmac",
- "ignore",
- "reqwest",
- "rusqlite",
- "serde",
- "serde_json",
- "serde_norway",
- "sha1",
- "sha2",
- "subtle",
- "tempfile",
- "time",
- "tokio",
- "toml",
- "tower",
- "tower-http",
- "tracing",
- "tracing-subscriber",
- "uuid",
-]
-
-[[package]]
-name = "clarion-core"
-version = "1.3.0"
-dependencies = [
- "async-trait",
- "nix",
- "reqwest",
- "serde",
- "serde_json",
- "tempfile",
- "thiserror 1.0.69",
- "tokio",
- "toml",
- "tracing",
- "which",
-]
-
-[[package]]
-name = "clarion-federation"
-version = "1.3.0"
-dependencies = [
- "clarion-core",
- "reqwest",
- "serde",
- "serde_json",
- "serde_norway",
- "tempfile",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "clarion-mcp"
-version = "1.3.0"
-dependencies = [
- "async-trait",
- "blake3",
- "clarion-core",
- "clarion-federation",
- "clarion-storage",
- "nix",
- "reqwest",
- "rusqlite",
- "serde",
- "serde_json",
- "serde_norway",
- "tempfile",
- "thiserror 1.0.69",
- "time",
- "tokio",
- "tracing",
- "uuid",
-]
-
-[[package]]
-name = "clarion-plugin-fixture"
-version = "1.3.0"
-dependencies = [
- "clarion-core",
- "nix",
- "serde_json",
-]
-
-[[package]]
-name = "clarion-scanner"
-version = "1.3.0"
-dependencies = [
- "regex",
- "serde",
- "serde_norway",
- "sha1",
- "tempfile",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "clarion-storage"
-version = "1.3.0"
-dependencies = [
- "blake3",
- "clarion-core",
- "deadpool-sqlite",
- "rusqlite",
- "serde",
- "serde_json",
- "tempfile",
- "thiserror 1.0.69",
- "time",
- "tokio",
- "tracing",
-]
-
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -600,9 +460,9 @@ dependencies = [
[[package]]
name = "displaydoc"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@@ -617,9 +477,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "either"
-version = "1.15.0"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
[[package]]
name = "equivalent"
@@ -827,9 +687,9 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.17.0"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "hashlink"
@@ -872,9 +732,9 @@ dependencies = [
[[package]]
name = "http"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
dependencies = [
"bytes",
"itoa",
@@ -917,9 +777,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "1.9.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [
"atomic-waker",
"bytes",
@@ -977,12 +837,13 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
+ "utf8_iter",
"yoke",
"zerofrom",
"zerovec",
@@ -990,9 +851,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
@@ -1003,9 +864,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@@ -1017,15 +878,15 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_properties"
-version = "2.1.2"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
"icu_collections",
"icu_locale_core",
@@ -1037,15 +898,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "2.1.2"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
@@ -1075,9 +936,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
@@ -1085,9 +946,9 @@ dependencies = [
[[package]]
name = "ignore"
-version = "0.4.25"
+version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
+checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d"
dependencies = [
"crossbeam-deque",
"globset",
@@ -1106,7 +967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
- "hashbrown 0.17.0",
+ "hashbrown 0.17.1",
"serde",
"serde_core",
]
@@ -1131,9 +992,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
-version = "0.3.95"
+version = "0.3.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
+checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
dependencies = [
"cfg-if",
"futures-util",
@@ -1155,9 +1016,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
-version = "0.2.185"
+version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libsqlite3-sys"
@@ -1190,9 +1051,149 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
+
+[[package]]
+name = "loomweave-analysis"
+version = "1.0.0"
+dependencies = [
+ "anyhow",
+ "serde",
+ "sha2",
+ "xgraph",
+]
+
+[[package]]
+name = "loomweave-cli"
+version = "1.0.0"
+dependencies = [
+ "anyhow",
+ "assert_cmd",
+ "axum",
+ "blake3",
+ "clap",
+ "dotenvy",
+ "fs2",
+ "hmac",
+ "ignore",
+ "loomweave-analysis",
+ "loomweave-core",
+ "loomweave-federation",
+ "loomweave-mcp",
+ "loomweave-plugin-fixture",
+ "loomweave-scanner",
+ "loomweave-storage",
+ "reqwest",
+ "rusqlite",
+ "serde",
+ "serde_json",
+ "serde_norway",
+ "sha1",
+ "sha2",
+ "subtle",
+ "tempfile",
+ "time",
+ "tokio",
+ "toml",
+ "tower",
+ "tower-http",
+ "tracing",
+ "tracing-subscriber",
+ "uuid",
+]
+
+[[package]]
+name = "loomweave-core"
+version = "1.0.0"
+dependencies = [
+ "async-trait",
+ "nix",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror 1.0.69",
+ "tokio",
+ "toml",
+ "tracing",
+ "which",
+]
+
+[[package]]
+name = "loomweave-federation"
+version = "1.0.0"
+dependencies = [
+ "loomweave-core",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_norway",
+ "tempfile",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "loomweave-mcp"
+version = "1.0.0"
+dependencies = [
+ "async-trait",
+ "blake3",
+ "loomweave-core",
+ "loomweave-federation",
+ "loomweave-storage",
+ "nix",
+ "reqwest",
+ "rusqlite",
+ "serde",
+ "serde_json",
+ "serde_norway",
+ "tempfile",
+ "thiserror 1.0.69",
+ "time",
+ "tokio",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "loomweave-plugin-fixture"
+version = "1.0.0"
+dependencies = [
+ "loomweave-core",
+ "nix",
+ "serde_json",
+]
+
+[[package]]
+name = "loomweave-scanner"
+version = "1.0.0"
+dependencies = [
+ "regex",
+ "serde",
+ "serde_norway",
+ "sha1",
+ "tempfile",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "loomweave-storage"
+version = "1.0.0"
+dependencies = [
+ "blake3",
+ "deadpool-sqlite",
+ "loomweave-core",
+ "rusqlite",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror 1.0.69",
+ "time",
+ "tokio",
+ "tracing",
+]
[[package]]
name = "lru-slab"
@@ -1217,9 +1218,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "memchr"
-version = "2.8.0"
+version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "mime"
@@ -1229,9 +1230,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mio"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
dependencies = [
"libc",
"wasi",
@@ -1261,9 +1262,9 @@ dependencies = [
[[package]]
name = "num-conv"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-traits"
@@ -1608,7 +1609,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1640,9 +1641,9 @@ dependencies = [
[[package]]
name = "rustls-native-certs"
-version = "0.8.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe",
"rustls-pki-types",
@@ -1762,9 +1763,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.149"
+version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1851,9 +1852,9 @@ dependencies = [
[[package]]
name = "shlex"
-version = "1.3.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "slab"
@@ -1869,9 +1870,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
-version = "0.6.3"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
"libc",
"windows-sys 0.61.2",
@@ -2052,9 +2053,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.52.1"
+version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"bytes",
"libc",
@@ -2159,9 +2160,9 @@ dependencies = [
[[package]]
name = "tower-http"
-version = "0.6.10"
+version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"bitflags",
"bytes",
@@ -2259,9 +2260,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
-version = "1.20.0"
+version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
@@ -2313,9 +2314,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.23.1"
+version = "1.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
+checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
dependencies = [
"getrandom 0.4.2",
"js-sys",
@@ -2376,11 +2377,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
-version = "1.0.1+wasi-0.2.4"
+version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
- "wit-bindgen 0.46.0",
+ "wit-bindgen 0.57.1",
]
[[package]]
@@ -2394,9 +2395,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
+checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
dependencies = [
"cfg-if",
"once_cell",
@@ -2407,9 +2408,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.68"
+version = "0.4.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
+checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -2417,9 +2418,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
+checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2427,9 +2428,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
+checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -2440,9 +2441,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
+checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
dependencies = [
"unicode-ident",
]
@@ -2483,9 +2484,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.95"
+version = "0.3.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
+checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -2559,6 +2560,15 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
[[package]]
name = "windows-sys"
version = "0.60.2"
@@ -2721,12 +2731,6 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
-[[package]]
-name = "wit-bindgen"
-version = "0.46.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
-
[[package]]
name = "wit-bindgen"
version = "0.51.0"
@@ -2736,6 +2740,12 @@ dependencies = [
"wit-bindgen-rust-macro",
]
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
@@ -2834,9 +2844,9 @@ dependencies = [
[[package]]
name = "yoke"
-version = "0.8.2"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -2857,18 +2867,18 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.8.48"
+version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
+checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.48"
+version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
+checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 5198071a..ed0e35e8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,21 +1,21 @@
[workspace]
resolver = "3"
members = [
- "crates/clarion-analysis",
- "crates/clarion-core",
- "crates/clarion-federation",
- "crates/clarion-storage",
- "crates/clarion-cli",
- "crates/clarion-mcp",
- "crates/clarion-plugin-fixture",
- "crates/clarion-scanner",
+ "crates/loomweave-analysis",
+ "crates/loomweave-core",
+ "crates/loomweave-federation",
+ "crates/loomweave-storage",
+ "crates/loomweave-cli",
+ "crates/loomweave-mcp",
+ "crates/loomweave-plugin-fixture",
+ "crates/loomweave-scanner",
]
[workspace.package]
-version = "1.3.0"
+version = "1.0.0"
edition = "2024"
license = "MIT"
-repository = "https://github.com/tachyon-beep/clarion"
+repository = "https://github.com/foundryside-dev/loomweave"
rust-version = "1.88"
[workspace.lints.rust]
diff --git a/README.md b/README.md
index 0648e3ab..ea4f44c8 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,27 @@
-# Clarion
+# Loomweave
-Clarion is a code-archaeology tool. It ingests a codebase, extracts entities
+Loomweave is a code-archaeology tool. It ingests a codebase, extracts entities
(functions, classes, modules) and their relationships (`contains`, `calls`,
`references`), persists the structural graph to a local SQLite store, and serves
the result to consult-mode LLM agents over MCP. A coding agent that would
-otherwise re-explore the tree on every question reaches Clarion first and asks a
+otherwise re-explore the tree on every question reaches Loomweave first and asks a
graph-aware tool. The current release line ships a Rust core plus a Python
language plugin; other languages remain future scope.
-Part of the [Loom suite](docs/suite/loom.md) of code-archaeology, issue-tracking,
+Part of the [Weft suite](docs/suite/weft.md) of code-archaeology, issue-tracking,
and trust-topology tools.
## Status
-**v1.3.0 — current release line.** Scope:
+**v1.0.0 — current release line.** Scope:
- **Python only.** Other-language plugins (`NG-15`) are v2.0+ scope.
-- **Structural extraction + on-demand LLM summarisation.** `clarion analyze`
+- **Structural extraction + on-demand LLM summarisation.** `loomweave analyze`
walks the corpus and persists entities + edges; `summary(id)` over MCP
dispatches the LLM lazily, one entity at a time.
- **Local-first.** No mandatory cloud component; the only required network
egress is the LLM provider during `summary` calls.
-- **Stable identity and suite enrichment.** Clarion mints Stable Entity
+- **Stable identity and suite enrichment.** Loomweave mints Stable Entity
Identity (SEI) tokens, serves the federation HTTP read API, emits opted-in
Filigree scan findings (issue lookups now key by SEI), and enriches MCP reads
with Filigree/Wardline context without making sibling products mandatory.
@@ -29,17 +29,17 @@ and trust-topology tools.
Wardline's NG-25 trust-vocabulary descriptor as a plain file and tags
trust-decorated entities (`wardline:*`) — without importing Wardline, so a
co-installed Wardline is not required. Degrades cleanly when the descriptor is
- absent. (Retires the last Clarion-side federation asterisk; see
- [`docs/suite/loom.md`](docs/suite/loom.md) §5.)
+ absent. (Retires the last Loomweave-side federation asterisk; see
+ [`docs/suite/weft.md`](docs/suite/weft.md) §5.)
- **Guidance authoring.** Operators can author, import, export, and review
- guidance sheets through `clarion guidance`; consult agents consume them
+ guidance sheets through `loomweave guidance`; consult agents consume them
through MCP and summary cache invalidation.
**Known limitations:**
- **HTTP file language inference uses stored plugin identity plus a narrow
core-extension fallback.** Plugin manifests declare language and extensions,
- but Clarion does not yet persist a manifest language registry for the
+ but Loomweave does not yet persist a manifest language registry for the
`/api/v1/files` read path.
- **Some guidance lifecycle surfaces remain deferred.** The in-browser
staleness-review UI is still tracked separately; authored guidance is
@@ -47,7 +47,7 @@ and trust-topology tools.
## What it does today
-`clarion serve` exposes a 39-tool MCP surface that a consult-mode agent calls
+`loomweave serve` exposes a 39-tool MCP surface that a consult-mode agent calls
instead of grep-and-read. The core tool families are:
| Family | Examples |
@@ -62,32 +62,32 @@ instead of grep-and-read. The core tool families are:
```bash
# 1. Install from the current GitHub Release
-TAG=v1.3.0
-curl -L -o clarion-x86_64-unknown-linux-gnu.tar.gz \
- "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-x86_64-unknown-linux-gnu.tar.gz"
-tar xzf clarion-x86_64-unknown-linux-gnu.tar.gz
-install clarion-x86_64-unknown-linux-gnu/clarion ~/.local/bin/
+TAG=v1.0.0
+curl -L -o loomweave-x86_64-unknown-linux-gnu.tar.gz \
+ "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-x86_64-unknown-linux-gnu.tar.gz"
+tar xzf loomweave-x86_64-unknown-linux-gnu.tar.gz
+install loomweave-x86_64-unknown-linux-gnu/loomweave ~/.local/bin/
pipx install \
- "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-plugin-python-1.3.0.tar.gz"
+ "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-plugin-python-1.0.0.tar.gz"
# 2. Initialise a project
cd /path/to/your/python/repo
-clarion install --path .
+loomweave install --path .
# 3. Walk the corpus and persist the structural graph
-clarion analyze
+loomweave analyze
# 4. Serve the graph over MCP for consult-mode agents
-clarion serve
+loomweave serve
```
-`clarion install` is the one-step agent setup path: it initialises `.clarion/`,
-installs the `clarion-workflow` skill for Claude Code and Codex, writes Claude
+`loomweave install` is the one-step agent setup path: it initialises `.loomweave/`,
+installs the `loomweave-workflow` skill for Claude Code and Codex, writes Claude
Code MCP config, upserts Codex MCP config, and installs the SessionStart hook.
Use component flags such as `--claude-code`, `--codex`, `--skills`,
`--codex-skills`, and `--hooks` for partial installs.
-`clarion analyze` works without any LLM credentials and is the fastest way to
+`loomweave analyze` works without any LLM credentials and is the fastest way to
verify the install. `summary(id)` calls require `OPENROUTER_API_KEY` to be set
(see [docs/operator/openrouter.md](docs/operator/openrouter.md)).
@@ -99,37 +99,37 @@ in [docs/operator/getting-started.md](docs/operator/getting-started.md).
```
crates/ Rust workspace
-├── clarion-core/ Entity-ID assembler, plugin host, manifest parser
-├── clarion-storage/ Writer-actor + reader-pool over SQLite (ADR-011)
-├── clarion-federation/ Shared federation HTTP types
-├── clarion-scanner/ Pre-ingest secret scanner (ADR-013, WP5)
-├── clarion-cli/ The `clarion` binary (install, analyze, serve)
-└── clarion-mcp/ MCP server exposing the consult tools
+├── loomweave-core/ Entity-ID assembler, plugin host, manifest parser
+├── loomweave-storage/ Writer-actor + reader-pool over SQLite (ADR-011)
+├── loomweave-federation/ Shared federation HTTP types
+├── loomweave-scanner/ Pre-ingest secret scanner (ADR-013, WP5)
+├── loomweave-cli/ The `loomweave` binary (install, analyze, serve)
+└── loomweave-mcp/ MCP server exposing the consult tools
plugins/python/ Python language plugin (pyright-backed)
-docs/clarion/1.0/ Design ladder — requirements → system-design → detailed-design
-docs/clarion/adr/ Authored architecture decision records
+docs/loomweave/1.0/ Design ladder — requirements → system-design → detailed-design
+docs/loomweave/adr/ Authored architecture decision records
```
For the design ladder start at
-[docs/clarion/1.0/README.md](docs/clarion/1.0/README.md). The full ADR index
-is at [docs/clarion/adr/README.md](docs/clarion/adr/README.md). The Loom
+[docs/loomweave/1.0/README.md](docs/loomweave/1.0/README.md). The full ADR index
+is at [docs/loomweave/adr/README.md](docs/loomweave/adr/README.md). The Weft
federation doctrine that anchors every cross-product decision is in
-[docs/suite/loom.md](docs/suite/loom.md).
+[docs/suite/weft.md](docs/suite/weft.md).
## Storage and operations
-Clarion keeps project state in a local `.clarion/` directory.
+Loomweave keeps project state in a local `.loomweave/` directory.
The local-first storage model, the no-NFS constraint, the no-double-analyze
constraint (fs2 advisory lock), and the backup/restore procedure are
documented in
-[docs/clarion/1.0/operations.md](docs/clarion/1.0/operations.md).
+[docs/loomweave/1.0/operations.md](docs/loomweave/1.0/operations.md).
## Contributing
-Read the [v1.0 docset README](docs/clarion/1.0/README.md) for the canonical
+Read the [v1.0 docset README](docs/loomweave/1.0/README.md) for the canonical
design ladder, its reading order, and where canonical truth lives. The CI
floor every PR must clear is fixed by
-[ADR-023](docs/clarion/adr/ADR-023-tooling-baseline.md):
+[ADR-023](docs/loomweave/adr/ADR-023-tooling-baseline.md):
```bash
# Rust gates
diff --git a/crates/clarion-mcp/src/config.rs b/crates/clarion-mcp/src/config.rs
deleted file mode 100644
index 1d7d4549..00000000
--- a/crates/clarion-mcp/src/config.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub use clarion_federation::config::*;
diff --git a/crates/clarion-mcp/src/filigree.rs b/crates/clarion-mcp/src/filigree.rs
deleted file mode 100644
index 234d9309..00000000
--- a/crates/clarion-mcp/src/filigree.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub use clarion_federation::filigree::*;
diff --git a/crates/clarion-mcp/src/filigree_url.rs b/crates/clarion-mcp/src/filigree_url.rs
deleted file mode 100644
index 5fd6e18b..00000000
--- a/crates/clarion-mcp/src/filigree_url.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub use clarion_federation::filigree_url::*;
diff --git a/crates/clarion-mcp/src/scan_results.rs b/crates/clarion-mcp/src/scan_results.rs
deleted file mode 100644
index d77288b0..00000000
--- a/crates/clarion-mcp/src/scan_results.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub use clarion_federation::scan_results::*;
diff --git a/crates/clarion-plugin-fixture/Cargo.toml b/crates/clarion-plugin-fixture/Cargo.toml
deleted file mode 100644
index 6f89a0a9..00000000
--- a/crates/clarion-plugin-fixture/Cargo.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[package]
-name = "clarion-plugin-fixture"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
-rust-version.workspace = true
-
-[lints]
-workspace = true
-
-[[bin]]
-name = "clarion-plugin-fixture"
-path = "src/main.rs"
-
-[dependencies]
-clarion-core = { path = "../clarion-core", version = "1.3.0" }
-serde_json.workspace = true
-
-[target.'cfg(unix)'.dependencies]
-nix = { workspace = true, features = ["mman", "signal"] }
diff --git a/crates/clarion-analysis/Cargo.toml b/crates/loomweave-analysis/Cargo.toml
similarity index 90%
rename from crates/clarion-analysis/Cargo.toml
rename to crates/loomweave-analysis/Cargo.toml
index 3f3372ed..95e02970 100644
--- a/crates/clarion-analysis/Cargo.toml
+++ b/crates/loomweave-analysis/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "clarion-analysis"
+name = "loomweave-analysis"
version.workspace = true
edition.workspace = true
license.workspace = true
diff --git a/crates/clarion-analysis/src/lib.rs b/crates/loomweave-analysis/src/lib.rs
similarity index 100%
rename from crates/clarion-analysis/src/lib.rs
rename to crates/loomweave-analysis/src/lib.rs
diff --git a/crates/clarion-cli/Cargo.toml b/crates/loomweave-cli/Cargo.toml
similarity index 62%
rename from crates/clarion-cli/Cargo.toml
rename to crates/loomweave-cli/Cargo.toml
index 1f7c8867..edd5d37b 100644
--- a/crates/clarion-cli/Cargo.toml
+++ b/crates/loomweave-cli/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "clarion-cli"
+name = "loomweave-cli"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -10,7 +10,7 @@ rust-version.workspace = true
workspace = true
[[bin]]
-name = "clarion"
+name = "loomweave"
path = "src/main.rs"
[dependencies]
@@ -18,12 +18,12 @@ anyhow.workspace = true
axum.workspace = true
blake3.workspace = true
clap.workspace = true
-clarion-core = { path = "../clarion-core", version = "1.3.0" }
-clarion-analysis = { path = "../clarion-analysis", version = "1.3.0" }
-clarion-federation = { path = "../clarion-federation", version = "1.3.0" }
-clarion-mcp = { path = "../clarion-mcp", version = "1.3.0" }
-clarion-scanner = { path = "../clarion-scanner", version = "1.3.0" }
-clarion-storage = { path = "../clarion-storage", version = "1.3.0" }
+loomweave-core = { path = "../loomweave-core", version = "1.0.0" }
+loomweave-analysis = { path = "../loomweave-analysis", version = "1.0.0" }
+loomweave-federation = { path = "../loomweave-federation", version = "1.0.0" }
+loomweave-mcp = { path = "../loomweave-mcp", version = "1.0.0" }
+loomweave-scanner = { path = "../loomweave-scanner", version = "1.0.0" }
+loomweave-storage = { path = "../loomweave-storage", version = "1.0.0" }
dotenvy.workspace = true
fs2.workspace = true
hmac.workspace = true
@@ -46,7 +46,7 @@ uuid.workspace = true
[dev-dependencies]
assert_cmd.workspace = true
-clarion-plugin-fixture = { path = "../clarion-plugin-fixture", version = "1.3.0" }
+loomweave-plugin-fixture = { path = "../loomweave-plugin-fixture", version = "1.0.0" }
rusqlite.workspace = true
serde_json.workspace = true
sha1.workspace = true
diff --git a/crates/loomweave-cli/pyproject.toml b/crates/loomweave-cli/pyproject.toml
new file mode 100644
index 00000000..9df6faef
--- /dev/null
+++ b/crates/loomweave-cli/pyproject.toml
@@ -0,0 +1,27 @@
+[build-system]
+requires = ["maturin>=1.7,<2"]
+build-backend = "maturin"
+
+[project]
+name = "loomweave"
+version = "1.0.0"
+description = "Loomweave — graph-aware code archaeology (Rust core)"
+readme = "../../README.md"
+requires-python = ">=3.11"
+license = { text = "MIT" }
+authors = [{ name = "John Morrissey", email = "qacona@gmail.com" }]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Programming Language :: Rust",
+ "Programming Language :: Python :: 3",
+]
+dependencies = ["loomweave-plugin-python==1.0.0"]
+
+[project.urls]
+Repository = "https://github.com/foundryside-dev/loomweave"
+
+[tool.maturin]
+bindings = "bin"
+manifest-path = "Cargo.toml"
+bins = ["loomweave"]
+strip = true
diff --git a/crates/clarion-cli/src/analyze.rs b/crates/loomweave-cli/src/analyze.rs
similarity index 96%
rename from crates/clarion-cli/src/analyze.rs
rename to crates/loomweave-cli/src/analyze.rs
index 4f3fd9cc..4e738f2b 100644
--- a/crates/clarion-cli/src/analyze.rs
+++ b/crates/loomweave-cli/src/analyze.rs
@@ -1,4 +1,4 @@
-//! `clarion analyze` — discover plugins, walk the source tree, persist entities.
+//! `loomweave analyze` — discover plugins, walk the source tree, persist entities.
//!
//! WP2 Task 8 replaces the Sprint-1 stub with real plugin orchestration:
//! - Discover plugins via L9 `$PATH` convention (Task 5).
@@ -21,12 +21,12 @@ use rusqlite::Connection;
use time::{OffsetDateTime, macros::format_description};
use uuid::Uuid;
-use clarion_core::{
+use loomweave_core::{
AcceptedEdge, AcceptedEntity, AnalyzeFileOutcome, CrashLoopBreaker, CrashLoopState,
DiscoveredPlugin, EmbeddingProvider, FINDING_DISABLED_CRASH_LOOP, HostError, HostFinding,
UnresolvedCallSite, discover,
};
-use clarion_storage::{
+use loomweave_storage::{
DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, EmbeddingKey, EmbeddingStore, GitRename,
NewEntityDescriptor, PriorIndexEntry, SeiBindingRecord, SeiDecision, SeiLineageEntry,
UnresolvedCallSiteRecord, Writer, alive_bindings_snapshot,
@@ -35,30 +35,30 @@ use clarion_storage::{
sei::{BindingStatus, LineageEvent},
};
-use clarion_federation::config::{FiligreeConfig, McpConfig, SemanticSearchConfig};
-use clarion_federation::filigree::FiligreeHttpClient;
-use clarion_federation::filigree_url::resolve_filigree_url;
-use clarion_federation::scan_results::{
- CLARION_SCAN_SOURCE, CleanStaleRequest, CleanStaleResponse, EmitOptions, FindingForEmit,
+use loomweave_federation::config::{FiligreeConfig, McpConfig, SemanticSearchConfig};
+use loomweave_federation::filigree::FiligreeHttpClient;
+use loomweave_federation::filigree_url::resolve_filigree_url;
+use loomweave_federation::scan_results::{
+ CleanStaleRequest, CleanStaleResponse, EmitOptions, FindingForEmit, LOOMWEAVE_SCAN_SOURCE,
PreparedBatch, ScanResultsResponse, clean_stale_url, prepare_batch, scan_results_url,
};
use crate::config::{AnalyzeConfig, ClusteringConfig};
use crate::stats::P95Accumulator;
-use clarion_analysis::{
+use loomweave_analysis::{
ClusterAlgorithm, ClusterConfig, ModuleEdge, ModuleGraph, cluster_hash, cluster_modules,
};
-const WEAK_MODULARITY_RULE_ID: &str = "CLA-FACT-CLUSTERING-WEAK-MODULARITY";
+const WEAK_MODULARITY_RULE_ID: &str = "LMWV-FACT-CLUSTERING-WEAK-MODULARITY";
/// REQ-ANALYZE-04: one finding per entity that vanished from source since the
/// prior run (deletion detection, Phase 7).
-const ENTITY_DELETED_RULE_ID: &str = "CLA-FACT-ENTITY-DELETED";
+const ENTITY_DELETED_RULE_ID: &str = "LMWV-FACT-ENTITY-DELETED";
/// REQ-ANALYZE-04: a guidance sheet whose explicit `guides` edge now points at a
/// deleted entity — the guidance is stranded and should not enrich briefings for
/// an entity that no longer exists.
-const GUIDANCE_ORPHAN_RULE_ID: &str = "CLA-FACT-GUIDANCE-ORPHAN";
+const GUIDANCE_ORPHAN_RULE_ID: &str = "LMWV-FACT-GUIDANCE-ORPHAN";
/// Bounded handoff from the blocking plugin worker to the async writer loop.
/// Mirrors detailed-design §11's `file_analyzed` backpressure cap.
@@ -70,20 +70,20 @@ type DescribedEdgeRecord = (String, EdgeRecord);
/// REQ-GUIDANCE-05 (WS6 T4a): a guidance sheet whose `expires` instant is in the
/// past. The read path already excludes expired sheets from composition; this
/// finding surfaces the state operatively (the sheet is not deleted).
-const GUIDANCE_EXPIRED_RULE_ID: &str = "CLA-FACT-GUIDANCE-EXPIRED";
+const GUIDANCE_EXPIRED_RULE_ID: &str = "LMWV-FACT-GUIDANCE-EXPIRED";
/// REQ-GUIDANCE-05 (WS6 T4a): a guidance sheet whose matched entities carry a high
/// aggregate `git_churn_count` — the code under the sheet has churned enough that
/// the guidance is likely stale. Heuristic (confidence 0.7); inert until the
/// churn-history pipeline (clarion-997c93ec4e) populates `git_churn_count`.
-const GUIDANCE_CHURN_STALE_RULE_ID: &str = "CLA-FACT-GUIDANCE-CHURN-STALE";
+const GUIDANCE_CHURN_STALE_RULE_ID: &str = "LMWV-FACT-GUIDANCE-CHURN-STALE";
/// REQ-GUIDANCE-05 (WS6 T4): a Wardline-derived guidance sheet was preserved as
/// an operator override while `wardline.yaml` changed underneath it.
-const GUIDANCE_STALE_RULE_ID: &str = "CLA-FACT-GUIDANCE-STALE";
+const GUIDANCE_STALE_RULE_ID: &str = "LMWV-FACT-GUIDANCE-STALE";
/// Aggregate `git_churn_count` (summed over a sheet's matched entities) at or above
-/// which a non-pinned sheet is flagged `CLA-FACT-GUIDANCE-CHURN-STALE`.
+/// which a non-pinned sheet is flagged `LMWV-FACT-GUIDANCE-CHURN-STALE`.
const CHURN_STALE_THRESHOLD: i64 = 50;
/// The lower (stricter) churn threshold for `pinned: true` sheets — pinned guidance
@@ -92,17 +92,17 @@ const CHURN_STALE_THRESHOLD_PINNED: i64 = 20;
/// REQ-ANALYZE-05: a subsystem whose tier-bearing members declare ≥2 distinct
/// Wardline tiers (a trust-boundary smell — the cluster straddles tiers).
-const TIER_MIXING_RULE_ID: &str = "CLA-FACT-TIER-SUBSYSTEM-MIXING";
+const TIER_MIXING_RULE_ID: &str = "LMWV-FACT-TIER-SUBSYSTEM-MIXING";
/// REQ-ANALYZE-05: a subsystem whose tier-bearing members (≥2) all agree on one
/// Wardline tier — a positive signal for tier-consistency reporting.
-const TIER_UNANIMOUS_RULE_ID: &str = "CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS";
+const TIER_UNANIMOUS_RULE_ID: &str = "LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS";
/// The finding rules persisted via `PersistPostRunFinding` *after* `CommitRun`
/// (the SEI mint pass's deletion findings + the tier-subsystem pass), and so
/// after Phase-8 emission has already run. A second, additive emission pass
/// (Phase 8c, `clarion-ef8f64d5fd`) re-reads exactly these so they reach Filigree
-/// in the same run rather than being stranded store-only. `CLA-FACT-ENTITY-DELETED`
+/// in the same run rather than being stranded store-only. `LMWV-FACT-ENTITY-DELETED`
/// anchors to the deleted entity's own path-bearing row; the subsystem-anchored
/// tier rules (and, once authoring lands, the guidance-anchored orphan rule) are
/// path-less, so the Phase-8c pass anchors them to the project root (the
@@ -125,7 +125,7 @@ const POST_RUN_FINDING_RULES: &[&str] = &[
/// in the plugin's stderr. Pyright degradation findings now ride the plugin
/// findings wire, but the syntax-error module property remains the stable
/// source for parse failures because the degraded module entity is the anchor.
-const SYNTAX_ERROR_RULE_ID: &str = "CLA-PY-SYNTAX-ERROR";
+const SYNTAX_ERROR_RULE_ID: &str = "LMWV-PY-SYNTAX-ERROR";
/// Writes structured run progress to a JSON file for the MCP `analyze_status`
/// tool (clarion-7e0c21558a). A no-op unless `analyze_start` passed a
@@ -309,7 +309,7 @@ pub(crate) struct AnalyzeOptions {
/// the Filigree peer. Takes precedence over `run_id` as the run identifier.
pub(crate) resume_run_id: Option,
/// `--prune-unseen` (REQ-FINDING-06): after emission, ask Filigree to
- /// soft-archive its stale `unseen_in_latest` Clarion findings. Enrich-only:
+ /// soft-archive its stale `unseen_in_latest` Loomweave findings. Enrich-only:
/// a failure or a disabled integration never fails the run.
pub(crate) prune_unseen: bool,
/// When set, structured progress is written here as the run proceeds.
@@ -333,7 +333,7 @@ pub(crate) struct AnalyzeOptions {
///
/// # Errors
///
-/// Returns an error if the target directory does not exist, has no `.clarion/`
+/// Returns an error if the target directory does not exist, has no `.loomweave/`
/// directory, if analyze config is invalid, or if the writer actor fails to
/// start or process commands.
#[allow(clippy::too_many_lines)]
@@ -347,19 +347,19 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti
let project_root = project_path
.canonicalize()
.with_context(|| format!("cannot canonicalise path {}", project_path.display()))?;
- let clarion_dir = project_root.join(".clarion");
- if !clarion_dir.exists() {
+ let loomweave_dir = project_root.join(".loomweave");
+ if !loomweave_dir.exists() {
bail!(
- "{} has no .clarion/ directory. Run `clarion install` first.",
+ "{} has no .loomweave/ directory. Run `loomweave install` first.",
project_root.display()
);
}
- let db_path = clarion_dir.join("clarion.db");
+ let db_path = loomweave_dir.join("loomweave.db");
// Cross-process advisory lock (STO-01). Must outlive the writer-actor's
// `handle.await` at the bottom of this function — see the drop-order
// note on `AnalyzeLockGuard`. Drop on function exit releases the lock.
- let _analyze_lock = crate::analyze_lock::acquire_analyze_lock(&clarion_dir)?;
+ let _analyze_lock = crate::analyze_lock::acquire_analyze_lock(&loomweave_dir)?;
// Apply any pending schema migrations before opening the writer. `install`
// is the usual migrator, but a binary upgrade that adds a migration the run
@@ -370,11 +370,12 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti
{
let mut conn =
Connection::open(&db_path).context("open database to apply pending migrations")?;
- clarion_storage::pragma::apply_write_pragmas(&conn).map_err(|e| anyhow::anyhow!("{e}"))?;
- clarion_storage::schema::apply_migrations(&mut conn)
+ loomweave_storage::pragma::apply_write_pragmas(&conn)
+ .map_err(|e| anyhow::anyhow!("{e}"))?;
+ loomweave_storage::schema::apply_migrations(&mut conn)
.map_err(|e| anyhow::anyhow!("{e}"))
.context("apply pending migrations")?;
- let repaired = clarion_storage::mark_stale_running_runs_failed(&conn)
+ let repaired = loomweave_storage::mark_stale_running_runs_failed(&conn)
.map_err(|e| anyhow::anyhow!("{e}"))
.context("mark stale running analyze runs failed")?;
if repaired > 0 {
@@ -490,7 +491,7 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti
.map_err(|e| anyhow::anyhow!("{e}"))?;
// Non-zero exit. Printing to stdout + returning Ok(()) here
- // hides the failure from `clarion analyze && do_next` chains
+ // hides the failure from `loomweave analyze && do_next` chains
// and breaks CI gating that reads `$?`. The run row in the DB
// is already marked `failed` above.
bail!("analyze run {run_id} failed — {reason}");
@@ -614,9 +615,9 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti
let (prior_file_hashes, mut prior_locs_by_file, prior_index_snapshot) = if incremental {
match Connection::open(&db_path) {
Ok(conn) => {
- let files = clarion_storage::previously_analyzed_files(&conn).unwrap_or_default();
- let locs = clarion_storage::prior_locators_by_file(&conn).unwrap_or_default();
- let snapshot = clarion_storage::load_prior_index(&conn).unwrap_or_default();
+ let files = loomweave_storage::previously_analyzed_files(&conn).unwrap_or_default();
+ let locs = loomweave_storage::prior_locators_by_file(&conn).unwrap_or_default();
+ let snapshot = loomweave_storage::load_prior_index(&conn).unwrap_or_default();
(files, locs, snapshot)
}
Err(err) => {
@@ -926,9 +927,9 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti
Err(plugin_error) => {
log_plugin_findings(&plugin_id, &plugin_error.findings);
// REQ-ANALYZE-06: persist the host findings collected before the
- // crash. A per-file timeout already rides in as a CLA-PY-TIMEOUT
+ // crash. A per-file timeout already rides in as a LMWV-PY-TIMEOUT
// finding (and is the root cause), so suppress the generic
- // CLA-INFRA-PLUGIN-CRASH in that case to avoid double-reporting.
+ // LMWV-INFRA-PLUGIN-CRASH in that case to avoid double-reporting.
let timed_out = plugin_error
.findings
.iter()
@@ -1400,7 +1401,7 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti
// emission already ran, so without this they reach the store but
// never the same-run Filigree emission. A second, additive pass
// re-reads only the post-commit rules (the during-run findings were
- // already emitted at Phase 8) and posts them: `CLA-FACT-ENTITY-DELETED`
+ // already emitted at Phase 8) and posts them: `LMWV-FACT-ENTITY-DELETED`
// against the deleted entity's own path, and the path-less
// subsystem-anchored tier facts against the project root (the
// `default_path` fallback supplied inside `emit_findings_to_filigree`
@@ -1525,8 +1526,8 @@ struct SeiPassStats {
minted: u64,
carried: u64,
orphaned: u64,
- /// Count of REQ-ANALYZE-04 deletion findings (`CLA-FACT-ENTITY-DELETED` +
- /// `CLA-FACT-GUIDANCE-ORPHAN`) persisted from this run's orphaned set.
+ /// Count of REQ-ANALYZE-04 deletion findings (`LMWV-FACT-ENTITY-DELETED` +
+ /// `LMWV-FACT-GUIDANCE-ORPHAN`) persisted from this run's orphaned set.
deletion_findings: u64,
}
@@ -1786,12 +1787,12 @@ async fn run_sei_mint_pass(
/// (already sorted + deduped by the caller for determinism), returning the total
/// finding count.
///
-/// For each deleted entity: emit one `CLA-FACT-ENTITY-DELETED` (anchored to the
+/// For each deleted entity: emit one `LMWV-FACT-ENTITY-DELETED` (anchored to the
/// entity's own row — `entities` is never pruned, so the FK resolves) and
/// invalidate its cached summaries. Then, for every guidance sheet stranded on a
/// deleted entity — via an explicit `guides` edge OR a `match_rules`
/// `{"type":"entity","id":X}` entry (detailed-design.md §5) — emit one
-/// `CLA-FACT-GUIDANCE-ORPHAN` (anchored to the sheet, deleted target as a related
+/// `LMWV-FACT-GUIDANCE-ORPHAN` (anchored to the sheet, deleted target as a related
/// id). A sheet that strands the same target via both paths emits one finding.
///
/// Returns `Ok(0)` for an empty deleted set without opening a connection.
@@ -1856,9 +1857,9 @@ async fn emit_deletion_findings(
// Scan every guidance sheet's `match_rules` for `{type:entity, id:X}`
// entries whose X is in the deleted set. Reuse the shared rule shape
- // (`clarion_storage::rule_match` reads `{"type":"entity","id":…}`), not a
+ // (`loomweave_storage::rule_match` reads `{"type":"entity","id":…}`), not a
// hand-rolled key.
- for sheet in clarion_storage::list_guidance_sheets(&conn)
+ for sheet in loomweave_storage::list_guidance_sheets(&conn)
.map_err(|e| anyhow::anyhow!("{e}"))
.context("list guidance sheets for match-rule orphan scan")?
{
@@ -1898,13 +1899,13 @@ async fn emit_deletion_findings(
Ok(count)
}
-/// Build a `CLA-FACT-ENTITY-DELETED` finding anchored to the deleted entity's own
+/// Build a `LMWV-FACT-ENTITY-DELETED` finding anchored to the deleted entity's own
/// (never-pruned) row. The id is deterministic and run-scoped so a `--resume`
/// re-walk regenerates the same id and `InsertFinding`'s upsert is idempotent.
fn entity_deleted_finding(entity_id: &str, run_id: &str, now: &str) -> FindingRecord {
FindingRecord {
id: format!("core:finding:{run_id}:entity-deleted:{entity_id}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: ENTITY_DELETED_RULE_ID.to_owned(),
@@ -1924,7 +1925,7 @@ fn entity_deleted_finding(entity_id: &str, run_id: &str, now: &str) -> FindingRe
}
}
-/// Build a `CLA-FACT-GUIDANCE-ORPHAN` finding anchored to the guidance sheet
+/// Build a `LMWV-FACT-GUIDANCE-ORPHAN` finding anchored to the guidance sheet
/// whose `guides` edge targets `deleted_entity_id`. Run-scoped, deterministic id.
fn guidance_orphan_finding(
guidance_id: &str,
@@ -1934,7 +1935,7 @@ fn guidance_orphan_finding(
) -> FindingRecord {
FindingRecord {
id: format!("core:finding:{run_id}:guidance-orphan:{guidance_id}:{deleted_entity_id}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: GUIDANCE_ORPHAN_RULE_ID.to_owned(),
@@ -1963,19 +1964,19 @@ fn guidance_orphan_finding(
/// REQ-GUIDANCE-05 (WS6 T4a): persist guidance-staleness findings over the
/// committed graph and return the count. Independent signals per sheet:
///
-/// - **`CLA-FACT-GUIDANCE-EXPIRED`** — the sheet's `expires` instant is lexically
+/// - **`LMWV-FACT-GUIDANCE-EXPIRED`** — the sheet's `expires` instant is lexically
/// `< now` (both are the fixed-width `YYYY-MM-DDTHH:MM:SS.sssZ` form
/// [`iso8601_now`] emits, so a byte compare is a valid instant compare). Absent
/// or malformed `expires` ⇒ skip.
-/// - **`CLA-FACT-GUIDANCE-CHURN-STALE`** — the aggregate `git_churn_count` over the
+/// - **`LMWV-FACT-GUIDANCE-CHURN-STALE`** — the aggregate `git_churn_count` over the
/// sheet's matched entities meets the staleness threshold (asymmetric: 20 for
/// `pinned` sheets, 50 otherwise).
-/// - **`CLA-FACT-GUIDANCE-STALE`** — a Wardline-derived override still carries
+/// - **`LMWV-FACT-GUIDANCE-STALE`** — a Wardline-derived override still carries
/// the old `wardline.yaml` manifest hash after the manifest changed.
///
/// Runs post-`CommitRun`, unconditionally (NOT gated on the SEI pass or on
/// deletions) — see the call site. Deterministic: sheets in
-/// [`clarion_storage::list_guidance_sheets`] order; matched ids sorted.
+/// [`loomweave_storage::list_guidance_sheets`] order; matched ids sorted.
///
/// Churn proxy note: the design wants "churn since `authored_at`/`reviewed_at`",
/// but there is no churn-history to compute a true delta and `git_churn_count` is
@@ -2010,7 +2011,7 @@ fn plan_guidance_staleness_findings(
.canonicalize()
.unwrap_or_else(|_| project_root.to_path_buf());
- let sheets = clarion_storage::list_guidance_sheets(&conn)
+ let sheets = loomweave_storage::list_guidance_sheets(&conn)
.map_err(|e| anyhow::anyhow!("{e}"))
.context("list guidance sheets for staleness scan")?;
@@ -2072,7 +2073,7 @@ fn plan_guidance_staleness_findings(
let mut agg: i64 = 0;
let mut matched: Vec = Vec::new();
for (entity_id, churn) in &churned {
- if clarion_storage::guidance_sheet_matches_entity(
+ if loomweave_storage::guidance_sheet_matches_entity(
&conn,
sheet,
entity_id,
@@ -2147,12 +2148,12 @@ async fn emit_guidance_staleness_findings(
Ok(count)
}
-/// Build a `CLA-FACT-GUIDANCE-EXPIRED` finding anchored to the expired sheet.
+/// Build a `LMWV-FACT-GUIDANCE-EXPIRED` finding anchored to the expired sheet.
/// Run-scoped, deterministic id; INFO, confidence 1.0.
fn guidance_expired_finding(guidance_id: &str, run_id: &str, now: &str) -> FindingRecord {
FindingRecord {
id: format!("core:finding:{run_id}:guidance-expired:{guidance_id}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: GUIDANCE_EXPIRED_RULE_ID.to_owned(),
@@ -2181,7 +2182,7 @@ fn guidance_stale_finding(
) -> FindingRecord {
FindingRecord {
id: format!("core:finding:{run_id}:guidance-stale:{guidance_id}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: GUIDANCE_STALE_RULE_ID.to_owned(),
@@ -2208,7 +2209,7 @@ fn guidance_stale_finding(
}
}
-/// Build a `CLA-FACT-GUIDANCE-CHURN-STALE` finding anchored to the sheet, carrying
+/// Build a `LMWV-FACT-GUIDANCE-CHURN-STALE` finding anchored to the sheet, carrying
/// the matched entities (sorted) as related ids and the aggregate churn +
/// threshold as evidence. Run-scoped, deterministic id; WARN, confidence 0.7
/// (heuristic).
@@ -2221,7 +2222,7 @@ fn guidance_churn_stale_finding(
) -> FindingRecord {
FindingRecord {
id: format!("core:finding:{run_id}:guidance-churn-stale:{guidance_id}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: GUIDANCE_CHURN_STALE_RULE_ID.to_owned(),
@@ -2269,8 +2270,8 @@ fn extract_wardline_tier(wardline_json: &str) -> Option {
/// Wardline tiers land on functions (`python:function:`), not modules,
/// so each tier-bearing entity is resolved up its `contains` chain to the
/// subsystem it belongs to (`subsystem_of_entity`). Per subsystem, over its
-/// tier-bearing members: ≥2 distinct tiers ⇒ `CLA-FACT-TIER-SUBSYSTEM-MIXING`;
-/// exactly one tier across ≥2 members ⇒ `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS`. A
+/// tier-bearing members: ≥2 distinct tiers ⇒ `LMWV-FACT-TIER-SUBSYSTEM-MIXING`;
+/// exactly one tier across ≥2 members ⇒ `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS`. A
/// single tier-bearing member yields neither (no consensus from one voice).
///
/// Conditional on a prior Wardline ingest: `analyze` never writes tier facts (the
@@ -2309,7 +2310,7 @@ async fn emit_tier_subsystem_findings(
let Some(tier) = extract_wardline_tier(&wardline_json) else {
continue;
};
- if let Some(subsystem) = clarion_storage::subsystem_of_entity(&conn, &entity_id)
+ if let Some(subsystem) = loomweave_storage::subsystem_of_entity(&conn, &entity_id)
.map_err(|e| anyhow::anyhow!("{e}"))
.with_context(|| format!("resolve subsystem for {entity_id}"))?
{
@@ -2350,7 +2351,7 @@ async fn emit_tier_subsystem_findings(
Ok(count)
}
-/// Build a `CLA-FACT-TIER-SUBSYSTEM-MIXING` finding anchored to the subsystem,
+/// Build a `LMWV-FACT-TIER-SUBSYSTEM-MIXING` finding anchored to the subsystem,
/// carrying its tier-bearing members as related ids and the tier distribution as
/// evidence. Members are pre-sorted by the caller; the id is run-scoped.
fn tier_mixing_finding(
@@ -2367,7 +2368,7 @@ fn tier_mixing_finding(
}
FindingRecord {
id: format!("core:finding:{run_id}:tier-mixing:{subsystem_id}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: TIER_MIXING_RULE_ID.to_owned(),
@@ -2391,7 +2392,7 @@ fn tier_mixing_finding(
}
}
-/// Build a `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS` finding (positive signal) anchored
+/// Build a `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS` finding (positive signal) anchored
/// to the subsystem whose ≥2 tier-bearing members all share `tier`.
fn tier_unanimous_finding(
subsystem_id: &str,
@@ -2403,7 +2404,7 @@ fn tier_unanimous_finding(
let member_ids: Vec<&str> = members.iter().map(|(id, _)| id.as_str()).collect();
FindingRecord {
id: format!("core:finding:{run_id}:tier-unanimous:{subsystem_id}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: TIER_UNANIMOUS_RULE_ID.to_owned(),
@@ -2636,7 +2637,7 @@ async fn run_phase3_clustering(
kind: "in_subsystem".to_owned(),
from_id: module_id.clone(),
to_id: subsystem_id.clone(),
- confidence: clarion_core::EdgeConfidence::Resolved,
+ confidence: loomweave_core::EdgeConfidence::Resolved,
properties_json: None,
source_file_id: None,
source_byte_start: None,
@@ -2696,7 +2697,7 @@ async fn run_phase3_clustering(
}
fn subsystem_entity_id(cluster_hash: &str) -> Result {
- Ok(clarion_core::entity_id::entity_id("core", "subsystem", cluster_hash)?.to_string())
+ Ok(loomweave_core::entity_id::entity_id("core", "subsystem", cluster_hash)?.to_string())
}
fn subsystem_display_name(member_ids: &[String], cluster_hash: &str) -> (String, String) {
@@ -2766,7 +2767,7 @@ async fn insert_weak_modularity_finding(
.send_wait(|ack| WriterCmd::InsertFinding {
finding: Box::new(FindingRecord {
id: finding_id.clone(),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: WEAK_MODULARITY_RULE_ID.to_owned(),
@@ -2803,7 +2804,7 @@ async fn insert_weak_modularity_finding(
Ok(true)
}
-/// Build a `CLA-PY-SYNTAX-ERROR` finding for an accepted entity the plugin
+/// Build a `LMWV-PY-SYNTAX-ERROR` finding for an accepted entity the plugin
/// flagged `parse_status="syntax_error"`, or `None` for cleanly-parsed entities.
///
/// The finding anchors to the degraded entity itself (the plugin still emits one
@@ -2830,7 +2831,7 @@ fn syntax_error_finding(
}
Some(FindingRecord {
id: format!("core:finding:{run_id}:syntax-error:{}", record.id),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: SYNTAX_ERROR_RULE_ID.to_owned(),
@@ -2860,8 +2861,8 @@ fn syntax_error_finding(
/// Core-emitted crash subcode (REQ-ANALYZE-06). Distinct from the crash-loop
/// breaker subcode (`FINDING_DISABLED_CRASH_LOOP`): this fires per plugin crash,
/// the breaker subcode fires once when the breaker trips.
-const INFRA_CRASH_RULE_ID: &str = "CLA-INFRA-PLUGIN-CRASH";
-const SOURCE_WALK_SKIPPED_RULE_ID: &str = "CLA-INFRA-SOURCE-WALK-SKIPPED";
+const INFRA_CRASH_RULE_ID: &str = "LMWV-INFRA-PLUGIN-CRASH";
+const SOURCE_WALK_SKIPPED_RULE_ID: &str = "LMWV-INFRA-SOURCE-WALK-SKIPPED";
const SOURCE_WALK_ERROR_SAMPLE_LIMIT: usize = 10;
/// Anchor entity id for project/plugin-level findings that are not file-scoped
@@ -2928,20 +2929,20 @@ async fn ensure_project_anchor(
/// Core-emitted per-file analysis-timeout subcode (REQ-ANALYZE-06). Host-side:
/// the plugin is killed when a single `analyze_file` exceeds the deadline.
-const PLUGIN_TIMEOUT_RULE_ID: &str = "CLA-PY-TIMEOUT";
-const PLUGIN_JAIL_OPEN_RULE_ID: &str = "CLA-INFRA-PLUGIN-JAIL-OPEN-FAILED";
+const PLUGIN_TIMEOUT_RULE_ID: &str = "LMWV-PY-TIMEOUT";
+const PLUGIN_JAIL_OPEN_RULE_ID: &str = "LMWV-INFRA-PLUGIN-JAIL-OPEN-FAILED";
/// Per-file `analyze_file` deadline. ADR-035 tuning: basis — a single file's
/// extraction (incl. pyright queries) completes in well under a second on
/// healthy plugins, so minutes of no progress means a hung plugin, not slow
-/// work; override — env `CLARION_PLUGIN_FILE_TIMEOUT_MS`; retune — raise if a
+/// work; override — env `LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS`; retune — raise if a
/// legitimate analyzer (very large generated file) trips it in practice.
const DEFAULT_PLUGIN_FILE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(120);
const PLUGIN_WATCHDOG_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(50);
/// Resolve the per-file analysis timeout, honouring the env override.
fn plugin_file_timeout() -> std::time::Duration {
- std::env::var("CLARION_PLUGIN_FILE_TIMEOUT_MS")
+ std::env::var("LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS")
.ok()
.and_then(|v| v.parse::().ok())
.map_or(
@@ -2958,8 +2959,8 @@ fn infra_severity(subcode: &str) -> &'static str {
INFRA_CRASH_RULE_ID
| PLUGIN_TIMEOUT_RULE_ID
| FINDING_DISABLED_CRASH_LOOP
- | "CLA-INFRA-PLUGIN-OOM-KILLED"
- | "CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE" => "ERROR",
+ | "LMWV-INFRA-PLUGIN-OOM-KILLED"
+ | "LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE" => "ERROR",
_ => "WARN",
}
}
@@ -2985,7 +2986,7 @@ fn host_finding_to_record(
.to_string();
FindingRecord {
id: format!("core:finding:{run_id}:infra:{discriminator}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: hf.subcode.clone(),
@@ -3013,9 +3014,9 @@ fn host_finding_anchor_id(hf: &HostFinding, project_root: &Path, project_anchor:
}
fn verified_plugin_dispatch_path(project_root: &Path, file: &Path) -> Result {
- let _handle = clarion_core::plugin::jail::safe_open(project_root, file)
+ let _handle = loomweave_core::plugin::jail::safe_open(project_root, file)
.with_context(|| format!("safe-open {}", file.display()))?;
- let jailed = clarion_core::plugin::jail::jail_to_string(project_root, file)
+ let jailed = loomweave_core::plugin::jail::jail_to_string(project_root, file)
.map_err(|e| anyhow::anyhow!("{e}"))
.with_context(|| format!("jail-check {}", file.display()))?;
Ok(PathBuf::from(jailed))
@@ -3039,7 +3040,7 @@ fn jail_open_failed_finding(file: &Path, error: &anyhow::Error) -> HostFinding {
}
}
-/// Build the `CLA-INFRA-PLUGIN-CRASH` finding for a plugin that crashed mid-run
+/// Build the `LMWV-INFRA-PLUGIN-CRASH` finding for a plugin that crashed mid-run
/// (REQ-ANALYZE-06). Anchored to the project entity; the crash reason is the
/// evidence.
fn crash_finding_record(
@@ -3052,7 +3053,7 @@ fn crash_finding_record(
let discriminator = blake3::hash(format!("{plugin_id}\u{0}{reason}").as_bytes()).to_hex();
FindingRecord {
id: format!("core:finding:{run_id}:crash:{discriminator}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: INFRA_CRASH_RULE_ID.to_owned(),
@@ -3086,7 +3087,7 @@ fn source_walk_finding_record(
.to_hex();
FindingRecord {
id: format!("core:finding:{run_id}:source-walk:{discriminator}"),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: SOURCE_WALK_SKIPPED_RULE_ID.to_owned(),
@@ -3115,12 +3116,12 @@ fn source_walk_finding_record(
}
}
-/// Load the MCP-side config (Filigree integration) from the same `clarion.yaml`
-/// `clarion serve` reads. A missing or unparseable file falls back to the
+/// Load the MCP-side config (Filigree integration) from the same `loomweave.yaml`
+/// `loomweave serve` reads. A missing or unparseable file falls back to the
/// default (Filigree disabled), so a config problem never fails the run — it
/// just means no emission.
pub(crate) fn load_mcp_config(project_root: &Path, config_path: Option<&Path>) -> McpConfig {
- let path = config_path.map_or_else(|| project_root.join("clarion.yaml"), Path::to_path_buf);
+ let path = config_path.map_or_else(|| project_root.join("loomweave.yaml"), Path::to_path_buf);
if !path.exists() {
return McpConfig::default();
}
@@ -3169,8 +3170,8 @@ async fn populate_semantic_embeddings(
}
let conn = Connection::open(db_path)
- .with_context(|| format!("open Clarion database {}", db_path.display()))?;
- let store = EmbeddingStore::open_in_clarion_dir(project_root)
+ .with_context(|| format!("open Loomweave database {}", db_path.display()))?;
+ let store = EmbeddingStore::open_in_loomweave_dir(project_root)
.map_err(|err| anyhow::anyhow!("{err}"))
.context("open semantic embedding sidecar")?;
let pending = semantic_embedding_candidates(&conn, &store, &model_id, &mut stats)?;
@@ -3319,7 +3320,7 @@ fn semantic_embedding_text(short_name: &str, name: &str, properties_json: &str)
/// Best-effort and enrich-only: gated behind
/// `integrations.filigree.{enabled,emit_findings}`, and any failure (Filigree
/// down, transport error, build error) is recorded in the returned stats blob
-/// and logged as `CLA-INFRA-FILIGREE-UNREACHABLE` rather than propagated — the
+/// and logged as `LMWV-INFRA-FILIGREE-UNREACHABLE` rather than propagated — the
/// analyze run never fails because a sibling tool is unreachable. Returns
/// [`serde_json::Value::Null`] when emission is disabled; otherwise a
/// `filigree_emission` stats object folded into `stats.json`.
@@ -3368,7 +3369,7 @@ async fn emit_findings_to_filigree(
}
let rows = match Connection::open(db_path) {
- Ok(conn) => match clarion_storage::findings_for_emit(&conn, run_id) {
+ Ok(conn) => match loomweave_storage::findings_for_emit(&conn, run_id) {
Ok(rows) => rows,
Err(err) => {
tracing::warn!(run_id, error = %err, "read findings for emission failed; skipping emission");
@@ -3397,7 +3398,7 @@ async fn emit_findings_to_filigree(
// synthetic-entity findings — the subsystem-anchored tier facts — to the
// project root (mirroring the `core:project:*` finding anchor) so they POST
// rather than being dropped as `skipped_no_path`. The wire layer flags these
- // `metadata.clarion.synthetic_anchor=true`. The Phase-8 pass passes `None`,
+ // `metadata.loomweave.synthetic_anchor=true`. The Phase-8 pass passes `None`,
// so during-run path-less findings (e.g. the weak-modularity subsystem fact)
// keep their existing store-only treatment.
let default_path = rule_filter.map(|_| project_root.display().to_string());
@@ -3439,7 +3440,7 @@ async fn emit_findings_to_filigree(
.await
}
-fn federation_finding_for_emit(row: clarion_storage::FindingForEmitRow) -> FindingForEmit {
+fn federation_finding_for_emit(row: loomweave_storage::FindingForEmitRow) -> FindingForEmit {
FindingForEmit {
id: row.id,
rule_id: row.rule_id,
@@ -3462,7 +3463,7 @@ fn federation_finding_for_emit(row: clarion_storage::FindingForEmitRow) -> Findi
/// the `filigree_emission` stats blob. Split out of [`emit_findings_to_filigree`]
/// so the Phase-8 read/filter logic and this network lifecycle stay independently
/// readable. Best-effort: a build/transport failure becomes an
-/// `CLA-INFRA-FILIGREE-UNREACHABLE` stats blob via [`unreachable_stats`].
+/// `LMWV-INFRA-FILIGREE-UNREACHABLE` stats blob via [`unreachable_stats`].
async fn post_findings_batch(
filigree_cfg: &FiligreeConfig,
project_root: &Path,
@@ -3475,7 +3476,7 @@ async fn post_findings_batch(
let skipped_no_path = batch.skipped_no_path;
// Resolve the live Filigree URL (ephemeral port over stale config), the same
- // resolution `clarion serve` and `project_status` use.
+ // resolution `loomweave serve` and `project_status` use.
let resolution = resolve_filigree_url(filigree_cfg, project_root);
let mut resolved_cfg = filigree_cfg.clone();
if let Some(url) = resolution.resolved_url {
@@ -3555,11 +3556,11 @@ async fn post_findings_batch(
}
/// Build the `filigree_emission` stats blob for a failed POST and log it as
-/// `CLA-INFRA-FILIGREE-UNREACHABLE`. The infra finding is recorded in
+/// `LMWV-INFRA-FILIGREE-UNREACHABLE`. The infra finding is recorded in
/// `stats.json` and the log (two of the three surfaces REQ-ANALYZE-06 names);
/// the local `findings` table is not used because its `entity_id` is a
/// non-null FK to `entities` and an infra finding has no anchor entity — the
-/// same reason every other `CLA-INFRA-*` finding is log-only today.
+/// same reason every other `LMWV-INFRA-*` finding is log-only today.
fn unreachable_stats(
run_id: &str,
endpoint: &str,
@@ -3571,13 +3572,13 @@ fn unreachable_stats(
tracing::warn!(
run_id,
endpoint,
- rule_id = "CLA-INFRA-FILIGREE-UNREACHABLE",
+ rule_id = "LMWV-INFRA-FILIGREE-UNREACHABLE",
error,
"could not post findings to Filigree; continuing (enrich-only)",
);
serde_json::json!({
"status": "unreachable",
- "rule_id": "CLA-INFRA-FILIGREE-UNREACHABLE",
+ "rule_id": "LMWV-INFRA-FILIGREE-UNREACHABLE",
"endpoint": endpoint,
"findings_total": total_findings,
"emitted_attempted": emitted,
@@ -3587,12 +3588,12 @@ fn unreachable_stats(
}
/// `--prune-unseen` retention sweep (WP9-B, REQ-FINDING-06): asks Filigree to
-/// soft-archive its own `unseen_in_latest` Clarion findings older than the
+/// soft-archive its own `unseen_in_latest` Loomweave findings older than the
/// configured age. Returns [`serde_json::Value::Null`] when not requested;
/// otherwise a `filigree_prune` stats object folded into `stats.json`. Like
/// emission, this is enrich-only — a disabled integration or a Filigree outage
/// is recorded in stats, never fails the run. `scan_source` scoping is enforced
-/// by Filigree, so the sweep can only touch Clarion's findings.
+/// by Filigree, so the sweep can only touch Loomweave's findings.
async fn prune_unseen_findings_in_filigree(
project_root: &Path,
run_id: &str,
@@ -3622,7 +3623,7 @@ async fn prune_unseen_findings_in_filigree(
}
let endpoint = clean_stale_url(&resolved_cfg.base_url);
let request = CleanStaleRequest {
- scan_source: CLARION_SCAN_SOURCE.to_owned(),
+ scan_source: LOOMWEAVE_SCAN_SOURCE.to_owned(),
older_than_days,
actor: resolved_cfg.actor.clone(),
};
@@ -3670,7 +3671,7 @@ async fn prune_unseen_findings_in_filigree(
}
/// Build the `filigree_prune` stats blob for a failed sweep and log it as
-/// `CLA-INFRA-FILIGREE-UNREACHABLE` — the enrich-only degrade, identical in
+/// `LMWV-INFRA-FILIGREE-UNREACHABLE` — the enrich-only degrade, identical in
/// spirit to [`unreachable_stats`] for emission.
fn prune_unreachable_stats(
run_id: &str,
@@ -3681,13 +3682,13 @@ fn prune_unreachable_stats(
tracing::warn!(
run_id,
endpoint,
- rule_id = "CLA-INFRA-FILIGREE-UNREACHABLE",
+ rule_id = "LMWV-INFRA-FILIGREE-UNREACHABLE",
error,
"could not prune unseen findings in Filigree; continuing (enrich-only)",
);
serde_json::json!({
"status": "unreachable",
- "rule_id": "CLA-INFRA-FILIGREE-UNREACHABLE",
+ "rule_id": "LMWV-INFRA-FILIGREE-UNREACHABLE",
"endpoint": endpoint,
"older_than_days": older_than_days,
"error": error,
@@ -3822,7 +3823,7 @@ fn handle_plugin_task_join_result(
/// Returned from the blocking plugin task on success.
struct BatchResult {
/// Findings accumulated by the host during the session.
- findings: Vec,
+ findings: Vec,
}
#[allow(clippy::large_enum_variant)]
@@ -3865,25 +3866,25 @@ struct PluginKindRoles {
}
impl PluginKindRoles {
- fn from_manifest(manifest: &clarion_core::Manifest) -> Self {
+ fn from_manifest(manifest: &loomweave_core::Manifest) -> Self {
let mut roles = Self::default();
for kind in &manifest.ontology.entity_kinds {
if manifest
.ontology
- .kind_has_role(kind, clarion_core::OntologyEntityRole::FileScope)
+ .kind_has_role(kind, loomweave_core::OntologyEntityRole::FileScope)
{
roles.file_scope.insert(kind.clone());
}
if manifest
.ontology
- .kind_has_role(kind, clarion_core::OntologyEntityRole::Callable)
+ .kind_has_role(kind, loomweave_core::OntologyEntityRole::Callable)
{
roles.callable.insert(kind.clone());
}
- if manifest
- .ontology
- .kind_has_role(kind, clarion_core::OntologyEntityRole::SyntaxDegradedModule)
- {
+ if manifest.ontology.kind_has_role(
+ kind,
+ loomweave_core::OntologyEntityRole::SyntaxDegradedModule,
+ ) {
roles.syntax_degraded_module.insert(kind.clone());
}
}
@@ -4083,7 +4084,7 @@ struct PendingUnresolvedCallSites {
sites: Vec,
}
-/// Per-file analysis-timeout watchdog (REQ-ANALYZE-06, `CLA-PY-TIMEOUT`).
+/// Per-file analysis-timeout watchdog (REQ-ANALYZE-06, `LMWV-PY-TIMEOUT`).
///
/// `analyze_file` blocks on a synchronous read of the plugin's stdout, which has
/// no read deadline. The watchdog runs on its own thread holding a shared handle
@@ -4176,18 +4177,18 @@ fn spawn_plugin_watchdog(
/// zombie into the kernel process table per spawn.
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
fn run_plugin_blocking(
- manifest: clarion_core::Manifest,
+ manifest: loomweave_core::Manifest,
project_root: &Path,
plugin_id: &str,
executable: &Path,
files: &[PathBuf],
- briefing_blocks: &Arc>,
+ briefing_blocks: &Arc>,
scanned_source_files: &Arc>,
progress: &ProgressReporter,
file_timeout: std::time::Duration,
batch_tx: &tokio::sync::mpsc::Sender,
) -> Result {
- use clarion_core::PluginHost;
+ use loomweave_core::PluginHost;
let manifest_language = manifest.plugin.language.clone();
let kind_roles = PluginKindRoles::from_manifest(&manifest);
@@ -4398,7 +4399,7 @@ fn run_plugin_blocking(
drop(host);
// REQ-ANALYZE-06: a per-file timeout is a recoverable failure that must be
- // visible. Add a CLA-PY-TIMEOUT host finding; it rides out through
+ // visible. Add a LMWV-PY-TIMEOUT host finding; it rides out through
// PluginRunError.findings and is persisted by the run's crash path.
if timed_out {
let mut metadata = BTreeMap::new();
@@ -4657,7 +4658,7 @@ fn core_file_entity_record(
project_root: &Path,
file: &Path,
manifest_language: &str,
- briefing_blocks: &BTreeMap,
+ briefing_blocks: &BTreeMap,
scanned_source_files: &BTreeSet,
) -> Result<(String, EntityRecord)> {
let canonical_root = project_root
@@ -4670,7 +4671,7 @@ fn core_file_entity_record(
core_file_entity_id_from_canonical(&canonical_root, &canonical_file)?;
let briefing_blocked = briefing_blocks.get(&canonical_file).copied().or_else(|| {
(!scanned_source_files.contains(&canonical_file))
- .then_some(clarion_core::BriefingBlockReason::UnscannedSource)
+ .then_some(loomweave_core::BriefingBlockReason::UnscannedSource)
});
let source_file_path = canonical_file
.into_os_string()
@@ -4752,7 +4753,7 @@ fn core_file_entity_id_from_canonical(
)
})?;
let qualified_name = project_relative_posix(relative)?;
- let id = clarion_core::entity_id::entity_id("core", "file", &qualified_name)?.to_string();
+ let id = loomweave_core::entity_id::entity_id("core", "file", &qualified_name)?.to_string();
Ok((id, qualified_name))
}
@@ -4869,7 +4870,7 @@ fn source_line_range(entity: &AcceptedEntity) -> Option {
/// read — callers fail toward re-analysis.
fn whole_file_hash(project_root: &Path, path: &Path) -> Option {
use std::io::Read;
- let mut file = clarion_core::plugin::jail::safe_open(project_root, path).ok()?;
+ let mut file = loomweave_core::plugin::jail::safe_open(project_root, path).ok()?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).ok()?;
Some(blake3::hash(&bytes).to_hex().to_string())
@@ -4923,7 +4924,7 @@ fn content_hash_for_entity(
let range = source_line_range?;
let mut file =
- clarion_core::plugin::jail::safe_open(project_root, Path::new(&entity.source_file_path))
+ loomweave_core::plugin::jail::safe_open(project_root, Path::new(&entity.source_file_path))
.ok()?;
let mut source = String::new();
file.read_to_string(&mut source).ok()?;
@@ -4980,7 +4981,7 @@ fn map_edge_to_record(edge: AcceptedEdge, source_file_id: Option) -> Edg
}
fn map_unresolved_call_sites_for_file(
- stats: &clarion_core::AnalyzeFileStats,
+ stats: &loomweave_core::AnalyzeFileStats,
entities: &[(String, EntityRecord)],
kind_roles: &PluginKindRoles,
created_at: &str,
@@ -5100,10 +5101,10 @@ fn unresolved_call_site_key(
/// Skip-list for directory names during the source walk.
///
-/// Sprint 1 conservative set: VCS directories, clarion's own state, and
+/// Sprint 1 conservative set: VCS directories, loomweave's own state, and
/// common virtual-environment directories.
const SKIP_DIRS: &[&str] = &[
- ".clarion",
+ ".loomweave",
".git",
".hg",
".svn",
@@ -5613,7 +5614,7 @@ mod tests {
assert_eq!(finding.entity_id, "python:module:pkg.broken");
assert_eq!(finding.kind, "defect");
assert_eq!(finding.severity, "WARN");
- assert_eq!(finding.tool, "clarion");
+ assert_eq!(finding.tool, "loomweave");
// Deterministic, run-scoped id keeps InsertFinding idempotent on resume.
assert_eq!(
finding.id,
@@ -5747,23 +5748,23 @@ mod tests {
#[test]
fn infra_severity_escalates_crash_and_kill() {
assert_eq!(infra_severity(INFRA_CRASH_RULE_ID), "ERROR");
- assert_eq!(infra_severity("CLA-INFRA-PLUGIN-OOM-KILLED"), "ERROR");
- assert_eq!(infra_severity("CLA-INFRA-PLUGIN-MALFORMED-ENTITY"), "WARN");
+ assert_eq!(infra_severity("LMWV-INFRA-PLUGIN-OOM-KILLED"), "ERROR");
+ assert_eq!(infra_severity("LMWV-INFRA-PLUGIN-MALFORMED-ENTITY"), "WARN");
}
#[test]
fn host_finding_to_record_anchors_and_carries_subcode() {
let hf = HostFinding {
- subcode: "CLA-INFRA-PLUGIN-MALFORMED-ENTITY".to_owned(),
+ subcode: "LMWV-INFRA-PLUGIN-MALFORMED-ENTITY".to_owned(),
message: "entity failed to deserialise".to_owned(),
metadata: std::collections::BTreeMap::new(),
};
let rec = host_finding_to_record(&hf, "python", "core:project:demo", "run-1", "t");
- assert_eq!(rec.rule_id, "CLA-INFRA-PLUGIN-MALFORMED-ENTITY");
+ assert_eq!(rec.rule_id, "LMWV-INFRA-PLUGIN-MALFORMED-ENTITY");
assert_eq!(rec.entity_id, "core:project:demo");
assert_eq!(rec.severity, "WARN");
assert_eq!(rec.kind, "defect");
- assert_eq!(rec.tool, "clarion");
+ assert_eq!(rec.tool, "loomweave");
assert!(rec.evidence_json.contains("python"));
}
@@ -5779,7 +5780,7 @@ mod tests {
source.to_string_lossy().into_owned(),
);
let hf = HostFinding {
- subcode: "CLA-PY-PYRIGHT-RESTART".to_owned(),
+ subcode: "LMWV-PY-PYRIGHT-RESTART".to_owned(),
message: "pyright restarted".to_owned(),
metadata,
};
@@ -5840,7 +5841,7 @@ mod tests {
kind: "imports".to_owned(),
from_id: from_id.to_owned(),
to_id: to_id.to_owned(),
- confidence: clarion_core::EdgeConfidence::Resolved,
+ confidence: loomweave_core::EdgeConfidence::Resolved,
properties_json: Some(
serde_json::json!({
"imported_name": imported_name,
@@ -5966,11 +5967,11 @@ mod tests {
kind: "function".to_owned(),
qualified_name: "demo.hello".to_owned(),
source_file_path: source_path.display().to_string(),
- raw: clarion_core::plugin::host::RawEntity {
+ raw: loomweave_core::plugin::host::RawEntity {
id: "python:function:demo.hello".to_owned(),
kind: "function".to_owned(),
qualified_name: "demo.hello".to_owned(),
- source: clarion_core::plugin::host::RawSource {
+ source: loomweave_core::plugin::host::RawSource {
file_path: source_path.display().to_string(),
extra: source_range.as_object().unwrap().clone(),
},
@@ -6045,16 +6046,16 @@ mod tests {
("python:module:demo".to_owned(), module),
("python:function:demo.caller".to_owned(), caller.clone()),
];
- let stats = clarion_core::AnalyzeFileStats {
+ let stats = loomweave_core::AnalyzeFileStats {
unresolved_call_sites_total: 1,
- unresolved_call_sites: vec![clarion_core::UnresolvedCallSite {
+ unresolved_call_sites: vec![loomweave_core::UnresolvedCallSite {
caller_entity_id: caller.id.clone(),
site_ordinal: 0,
source_byte_start: 14,
source_byte_end: 24,
callee_expr: "dynamic_target".to_owned(),
}],
- ..clarion_core::AnalyzeFileStats::default()
+ ..loomweave_core::AnalyzeFileStats::default()
};
let mapped = map_unresolved_call_sites_for_file(
@@ -6109,7 +6110,7 @@ mod tests {
updated_at: "2026-05-17T00:00:00.000Z".to_owned(),
};
let entities = vec![("python:function:demo.caller".to_owned(), caller)];
- let stats = clarion_core::AnalyzeFileStats::default();
+ let stats = loomweave_core::AnalyzeFileStats::default();
let mapped = map_unresolved_call_sites_for_file(
&stats,
@@ -6128,13 +6129,13 @@ mod tests {
async fn semantic_embedding_population_skips_fresh_sidecar_rows() {
use std::sync::Arc;
- use clarion_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider};
- use clarion_federation::config::SemanticSearchConfig;
- use clarion_storage::{EmbeddingKey, EmbeddingStore, pragma, schema};
+ use loomweave_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider};
+ use loomweave_federation::config::SemanticSearchConfig;
+ use loomweave_storage::{EmbeddingKey, EmbeddingStore, pragma, schema};
let project = tempfile::tempdir().unwrap();
- std::fs::create_dir(project.path().join(".clarion")).unwrap();
- let db_path = project.path().join(".clarion/clarion.db");
+ std::fs::create_dir(project.path().join(".loomweave")).unwrap();
+ let db_path = project.path().join(".loomweave/loomweave.db");
let mut conn = rusqlite::Connection::open(&db_path).unwrap();
pragma::apply_write_pragmas(&conn).unwrap();
schema::apply_migrations(&mut conn).unwrap();
@@ -6149,7 +6150,7 @@ mod tests {
.unwrap();
drop(conn);
- let store = EmbeddingStore::open_in_clarion_dir(project.path()).unwrap();
+ let store = EmbeddingStore::open_in_loomweave_dir(project.path()).unwrap();
store
.upsert(
&EmbeddingKey {
@@ -6197,13 +6198,13 @@ mod tests {
async fn semantic_embedding_population_skips_briefing_blocked_entities() {
use std::sync::Arc;
- use clarion_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider};
- use clarion_federation::config::SemanticSearchConfig;
- use clarion_storage::{pragma, schema};
+ use loomweave_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider};
+ use loomweave_federation::config::SemanticSearchConfig;
+ use loomweave_storage::{pragma, schema};
let project = tempfile::tempdir().unwrap();
- std::fs::create_dir(project.path().join(".clarion")).unwrap();
- let db_path = project.path().join(".clarion/clarion.db");
+ std::fs::create_dir(project.path().join(".loomweave")).unwrap();
+ let db_path = project.path().join(".loomweave/loomweave.db");
let mut conn = rusqlite::Connection::open(&db_path).unwrap();
pragma::apply_write_pragmas(&conn).unwrap();
schema::apply_migrations(&mut conn).unwrap();
diff --git a/crates/clarion-cli/src/analyze_lock.rs b/crates/loomweave-cli/src/analyze_lock.rs
similarity index 70%
rename from crates/clarion-cli/src/analyze_lock.rs
rename to crates/loomweave-cli/src/analyze_lock.rs
index 9108768d..e00f4947 100644
--- a/crates/clarion-cli/src/analyze_lock.rs
+++ b/crates/loomweave-cli/src/analyze_lock.rs
@@ -1,16 +1,16 @@
-//! Cross-process advisory lock for `clarion analyze`.
+//! Cross-process advisory lock for `loomweave analyze`.
//!
-//! Two concurrent `clarion analyze` processes against the same project
+//! Two concurrent `loomweave analyze` processes against the same project
//! corrupt the run-attribution graph: each opens its own writer-actor,
//! each calls `BeginRun` (insert a fresh `runs` row in `status='running'`),
//! and each races on entity/edge inserts under `SQLite` WAL. The in-process
-//! `ActorState::current_run` guard (clarion-storage `writer.rs`) prevents
+//! `ActorState::current_run` guard (loomweave-storage `writer.rs`) prevents
//! a single writer from issuing two `BeginRun`s; it does nothing across
//! processes.
//!
//! This module acquires an exclusive `fs2`-advisory lock on a dedicated
-//! sentinel file `.clarion/clarion.lock` for the duration of the analyze
-//! run. The lock file is separate from `clarion.db` so `SQLite`'s own
+//! sentinel file `.loomweave/loomweave.lock` for the duration of the analyze
+//! run. The lock file is separate from `loomweave.db` so `SQLite`'s own
//! locking (per-connection, transaction-scoped) is independent. The
//! guard's `Drop` releases the OS-level lock.
@@ -20,13 +20,13 @@ use std::path::Path;
use anyhow::{Context, Result, bail};
use fs2::FileExt;
-const LOCK_FILE_NAME: &str = "clarion.lock";
+const LOCK_FILE_NAME: &str = "loomweave.lock";
/// RAII guard holding the analyze lock. Drop releases the OS lock.
///
/// **Drop order is load-bearing.** The guard must outlive the writer-actor's
/// `JoinHandle::await` in `analyze::run_with_options`; otherwise a second
-/// `clarion analyze` can grab the lock while writer-actor 1's final
+/// `loomweave analyze` can grab the lock while writer-actor 1's final
/// transaction is still landing through WAL. `fs2`'s `File` impl unlocks
/// on file close, so dropping the `File` releases the OS lock; we rely on
/// Drop rather than an explicit unlock so panic and happy paths behave
@@ -37,21 +37,21 @@ pub(crate) struct AnalyzeLockGuard {
_file: File,
}
-/// Acquire an exclusive cross-process lock on `/clarion.lock`.
+/// Acquire an exclusive cross-process lock on `/loomweave.lock`.
///
-/// `clarion_dir` is the `.clarion/` directory inside the project root. The
+/// `loomweave_dir` is the `.loomweave/` directory inside the project root. The
/// lock file is created on first use (0-byte sentinel) and kept across
/// runs. The returned guard holds the lock for its lifetime.
///
/// # Errors
///
-/// - The lock file cannot be opened (missing `.clarion/` directory,
+/// - The lock file cannot be opened (missing `.loomweave/` directory,
/// permission denied, filesystem read-only).
-/// - Another `clarion analyze` process already holds the lock. Returns
+/// - Another `loomweave analyze` process already holds the lock. Returns
/// an error containing the lock-file path so the operator can identify
/// the conflict.
-pub(crate) fn acquire_analyze_lock(clarion_dir: &Path) -> Result {
- let lock_path = clarion_dir.join(LOCK_FILE_NAME);
+pub(crate) fn acquire_analyze_lock(loomweave_dir: &Path) -> Result {
+ let lock_path = loomweave_dir.join(LOCK_FILE_NAME);
let file = OpenOptions::new()
.read(true)
.write(true)
@@ -70,7 +70,7 @@ pub(crate) fn acquire_analyze_lock(clarion_dir: &Path) -> Result Result,
- /// Install the bundled clarion-workflow skill pack into .claude/skills/.
+ /// Install the bundled loomweave-workflow skill pack into .claude/skills/.
#[arg(long)]
skills: bool,
- /// Install the bundled clarion-workflow skill pack into .agents/skills/.
+ /// Install the bundled loomweave-workflow skill pack into .agents/skills/.
#[arg(long)]
codex_skills: bool,
@@ -52,13 +52,13 @@ pub enum Command {
#[arg(long)]
hooks: bool,
- /// Do everything: .clarion/ init + MCP config + skills + hooks.
+ /// Do everything: .loomweave/ init + MCP config + skills + hooks.
#[arg(long)]
all: bool,
},
/// Run an analysis pass: walk the source tree, dispatch discovered plugins
- /// to extract entities/edges, and persist results to `.clarion/clarion.db`.
+ /// to extract entities/edges, and persist results to `.loomweave/loomweave.db`.
/// Re-runs are idempotent (UPSERT on `entities.id`). If no plugins are on
/// `$PATH`, exits 0 with a WARN and status `skipped_no_plugins` — see
/// `docs/operator/getting-started.md` Troubleshooting.
@@ -67,7 +67,7 @@ pub enum Command {
#[arg(default_value = ".")]
path: PathBuf,
- /// Path to clarion.yaml (default: project-root/clarion.yaml if present).
+ /// Path to loomweave.yaml (default: project-root/loomweave.yaml if present).
#[arg(long)]
config: Option,
@@ -90,7 +90,7 @@ pub enum Command {
/// of starting fresh) and emit findings to Filigree with
/// `mark_unseen=false`, so re-emitting does not flip the prior run's
/// findings to `unseen_in_latest` on the peer (REQ-FINDING-05). The
- /// run id is the UUID a normal `clarion analyze` reports on completion.
+ /// run id is the UUID a normal `loomweave analyze` reports on completion.
/// This re-walks the tree from scratch (it is not incremental recovery)
/// and assumes the corpus is unchanged; findings that no longer fire are
/// not pruned from the resumed run.
@@ -98,12 +98,12 @@ pub enum Command {
resume: Option,
/// After emitting findings, ask Filigree to soft-archive its own
- /// `unseen_in_latest` Clarion findings older than
+ /// `unseen_in_latest` Loomweave findings older than
/// `integrations.filigree.prune_unseen_days` (default 30)
/// (REQ-FINDING-06). Opt-in retention sweep; enrich-only — a Filigree
/// outage or the integration being disabled never fails the run. The
/// sweep is `scan_source`-scoped server-side, so it only touches
- /// Clarion's findings.
+ /// Loomweave's findings.
#[arg(long)]
prune_unseen: bool,
@@ -131,19 +131,19 @@ pub enum Command {
/// `legis`'s read-API base URL (e.g. `http://127.0.0.1:8615`), enabling
/// the WS9 git-rename provider seam (REQ-C-05). Enrich-only and
/// capability-aware: the operative working-tree rename window stays on
- /// Clarion's own git probe, so an unset or unreachable `legis` leaves
- /// behaviour byte-identical. Omit to use Clarion's shell git source only.
+ /// Loomweave's own git probe, so an unset or unreachable `legis` leaves
+ /// behaviour byte-identical. Omit to use Loomweave's shell git source only.
#[arg(long)]
legis_url: Option,
},
/// Run the MCP stdio server.
Serve {
- /// Project directory containing .clarion/clarion.db.
+ /// Project directory containing .loomweave/loomweave.db.
#[arg(long, default_value = ".")]
path: PathBuf,
- /// Path to clarion.yaml (default: project-root/clarion.yaml if present).
+ /// Path to loomweave.yaml (default: project-root/loomweave.yaml if present).
#[arg(long)]
config: Option,
},
@@ -169,7 +169,7 @@ pub enum Command {
},
/// Verify (and optionally repair) the installed agent-orientation surfaces:
- /// the `clarion-workflow` skill pack, the `SessionStart` hook, and the
+ /// the `loomweave-workflow` skill pack, the `SessionStart` hook, and the
/// `.mcp.json` MCP registration. Prints a per-surface report plus the index
/// snapshot; exits non-zero if any problem remains (usable as a CI /
/// pre-commit gate).
@@ -203,15 +203,15 @@ pub enum DoctorOutputFormat {
#[derive(Subcommand)]
pub enum DbCommand {
- /// Take a consistent, WAL-safe online backup of `.clarion/clarion.db`.
+ /// Take a consistent, WAL-safe online backup of `.loomweave/loomweave.db`.
///
/// Unlike `cp`, this captures outstanding WAL frames into a standalone
- /// single-file copy, so it is safe to run during a live `clarion analyze`.
+ /// single-file copy, so it is safe to run during a live `loomweave analyze`.
Backup {
/// Destination file for the backup copy.
output: PathBuf,
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
@@ -230,7 +230,7 @@ pub enum GuidanceCommand {
/// `entity:`. Content comes from `--content`, else stdin (when
/// piped) or `$EDITOR`/`$VISUAL`.
Create {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
@@ -267,7 +267,7 @@ pub enum GuidanceCommand {
/// Edit a sheet's content in `$EDITOR`/`$VISUAL` (other properties, including
/// `authored_at` and provenance, are preserved).
Edit {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
/// The guidance sheet id (`core:guidance:`).
@@ -276,7 +276,7 @@ pub enum GuidanceCommand {
/// Print a guidance sheet (human-readable).
Show {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
/// The guidance sheet id.
@@ -290,7 +290,7 @@ pub enum GuidanceCommand {
/// filter (including `--for-entity`). Without any of them, behaves as the
/// plain list.
List {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
/// Only list sheets whose `match_rules` apply to this entity id.
@@ -313,7 +313,7 @@ pub enum GuidanceCommand {
/// Delete a guidance sheet.
Delete {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
/// The guidance sheet id.
@@ -324,10 +324,10 @@ pub enum GuidanceCommand {
/// guidance sheet. The observation must have been produced by MCP
/// `propose_guidance`; arbitrary observations are rejected.
Promote {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
- /// Path to clarion.yaml (default: project-root/clarion.yaml if present).
+ /// Path to loomweave.yaml (default: project-root/loomweave.yaml if present).
#[arg(long)]
config: Option,
/// The Filigree observation id to promote.
@@ -339,7 +339,7 @@ pub enum GuidanceCommand {
/// (REQ-GUIDANCE-06). Output is byte-stable across runs on identical DB
/// state. The target directory is created if absent.
Export {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
/// Directory to write the exported sheet files into. Export does NOT
@@ -356,7 +356,7 @@ pub enum GuidanceCommand {
/// untouched (never a destructive mirror). A malformed `*.json` aborts the
/// import naming the offending file (a dropped sheet is silent data loss).
Import {
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
/// Directory of exported sheet files to import.
@@ -368,7 +368,7 @@ pub enum GuidanceCommand {
pub enum HookCommand {
/// Print a project snapshot and re-sync the skill pack on drift.
SessionStart {
- /// Project directory containing .clarion/clarion.db.
+ /// Project directory containing .loomweave/loomweave.db.
#[arg(long, default_value = ".")]
path: PathBuf,
},
@@ -386,7 +386,7 @@ pub enum SarifCommand {
#[arg(long)]
scan_source: Option,
- /// Project directory containing .clarion/clarion.db (default: current).
+ /// Project directory containing .loomweave/loomweave.db (default: current).
#[arg(long, default_value = ".")]
path: PathBuf,
},
diff --git a/crates/clarion-cli/src/config.rs b/crates/loomweave-cli/src/config.rs
similarity index 97%
rename from crates/clarion-cli/src/config.rs
rename to crates/loomweave-cli/src/config.rs
index b7f2a5f8..db23073e 100644
--- a/crates/clarion-cli/src/config.rs
+++ b/crates/loomweave-cli/src/config.rs
@@ -2,7 +2,7 @@ use std::fs;
use std::path::Path;
use anyhow::{Context, Result, bail, ensure};
-use clarion_analysis::ClusterAlgorithm;
+use loomweave_analysis::ClusterAlgorithm;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
@@ -18,7 +18,7 @@ impl AnalyzeConfig {
.with_context(|| format!("load analyze config {}", path.display()));
}
- let default_path = project_root.join("clarion.yaml");
+ let default_path = project_root.join("loomweave.yaml");
if default_path.exists() {
Self::from_path(&default_path)
.with_context(|| format!("load analyze config {}", default_path.display()))
diff --git a/crates/clarion-cli/src/db.rs b/crates/loomweave-cli/src/db.rs
similarity index 88%
rename from crates/clarion-cli/src/db.rs
rename to crates/loomweave-cli/src/db.rs
index 54bce6db..ec63a7cd 100644
--- a/crates/clarion-cli/src/db.rs
+++ b/crates/loomweave-cli/src/db.rs
@@ -1,11 +1,11 @@
-//! `clarion db` maintenance subcommands.
+//! `loomweave db` maintenance subcommands.
//!
//! Currently a single verb: `backup`, an online, WAL-safe copy of
-//! `.clarion/clarion.db` (gap-register STO-04 / clarion-6d433b61ba).
+//! `.loomweave/loomweave.db` (gap-register STO-04 / clarion-6d433b61ba).
//!
//! Why an online backup rather than `cp`: the live database runs in WAL mode,
-//! so committed pages live in `clarion.db-wal` separately from the main file.
-//! A naive file copy taken during a `clarion analyze` produces a *torn* copy —
+//! so committed pages live in `loomweave.db-wal` separately from the main file.
+//! A naive file copy taken during a `loomweave analyze` produces a *torn* copy —
//! the main file without its outstanding WAL frames. `rusqlite::backup::Backup`
//! reads through a real connection, so it captures a transactionally consistent
//! snapshot and writes it into a fresh single-file database (no WAL sidecar to
@@ -17,7 +17,7 @@ use std::time::Duration;
use anyhow::{Context, Result, anyhow, bail, ensure};
use rusqlite::{Connection, OpenFlags};
-/// Back up the project's `.clarion/clarion.db` to `output`.
+/// Back up the project's `.loomweave/loomweave.db` to `output`.
///
/// The copy is taken with `rusqlite::backup::Backup` (a consistent online
/// snapshot) and staged into a sibling temp file that is renamed over `output`
@@ -30,10 +30,10 @@ use rusqlite::{Connection, OpenFlags};
/// exists and `force` is not set, if `output` resolves to the source database
/// itself, or if the backup / integrity check fails.
pub fn backup(project_root: &Path, output: &Path, force: bool) -> Result<()> {
- let db_path = project_root.join(".clarion").join("clarion.db");
+ let db_path = project_root.join(".loomweave").join("loomweave.db");
ensure!(
db_path.exists(),
- "Clarion database not found at {}; run `clarion analyze` first",
+ "Loomweave database not found at {}; run `loomweave analyze` first",
db_path.display()
);
@@ -118,10 +118,10 @@ fn run_backup(db_path: &Path, staging: &Path) -> Result<()> {
Ok(())
}
-/// Sibling staging path for the atomic write (`.clarion-backup.tmp-`).
+/// Sibling staging path for the atomic write (`.loomweave-backup.tmp-`).
fn staging_path(output: &Path) -> std::path::PathBuf {
let mut name = output.as_os_str().to_os_string();
- name.push(format!(".clarion-backup.tmp-{}", std::process::id()));
+ name.push(format!(".loomweave-backup.tmp-{}", std::process::id()));
std::path::PathBuf::from(name)
}
diff --git a/crates/clarion-cli/src/doctor.rs b/crates/loomweave-cli/src/doctor.rs
similarity index 80%
rename from crates/clarion-cli/src/doctor.rs
rename to crates/loomweave-cli/src/doctor.rs
index 760581b3..aa6b0f96 100644
--- a/crates/clarion-cli/src/doctor.rs
+++ b/crates/loomweave-cli/src/doctor.rs
@@ -1,27 +1,26 @@
-//! `clarion doctor [--fix]` — verify (and optionally repair) the installed
+//! `loomweave doctor [--fix]` — verify (and optionally repair) the installed
//! agent-orientation surfaces.
//!
//! Three surfaces are checked, each owned by an existing installer module:
-//! the `clarion-workflow` skill pack ([`crate::skill_pack`]), the `SessionStart`
+//! the `loomweave-workflow` skill pack ([`crate::skill_pack`]), the `SessionStart`
//! hook ([`crate::hooks_settings`]), and the Claude Code `.mcp.json` MCP
//! registration ([`crate::mcp_registration`]), plus the local
-//! Clarion/Filigree/Wardline binding files ([`crate::integration_bindings`]).
+//! Loomweave/Filigree/Wardline binding files ([`crate::integration_bindings`]).
//! The repair for each is that module's idempotent installer, so
-//! `doctor --fix` and `clarion install` converge to the same state.
+//! `doctor --fix` and `loomweave install` converge to the same state.
//!
//! Output is a per-surface ✓/⚠/✗ report followed by the index snapshot (reused
//! verbatim from the session-start hook). [`run`] returns whether every surface
//! is healthy *after* any repairs; the caller maps an unhealthy result to a
//! non-zero exit so `doctor` is usable as a CI / pre-commit gate.
//!
-//! Severity is deliberate. The Loom three-way integration bindings are an
-//! *enrich-only* surface (per `docs/suite/loom.md` §5): a Clarion-solo or
-//! Clarion+Filigree-only project is first-class, so their absence is a
+//! Severity is deliberate. The Weft three-way integration bindings are an
+//! *enrich-only* surface (per `docs/suite/weft.md` §5): a Loomweave-solo or
+//! Loomweave+Filigree-only project is first-class, so their absence is a
//! **warning** (surfaced, suggests `--fix`) and never a problem that fails the
//! gate. Only a genuinely broken state — an unparseable config file, or a
//! `--fix` repair that errors or does not converge — is a problem.
-use std::env;
use std::fs;
use std::path::Path;
@@ -36,7 +35,7 @@ use crate::mcp_registration::McpState;
use crate::skill_pack::SkillPackState;
use crate::{hook, hooks_settings, integration_bindings, mcp_registration, skill_pack};
-/// Run `clarion doctor`. Returns `Ok(true)` iff every orientation surface is
+/// Run `loomweave doctor`. Returns `Ok(true)` iff every orientation surface is
/// healthy after any requested repairs.
///
/// # Errors
@@ -61,7 +60,7 @@ pub fn run(path: &Path, fix: bool, json_output: bool) -> Result {
return Ok(report.ok);
}
- println!("clarion doctor{}", if fix { " --fix" } else { "" });
+ println!("loomweave doctor{}", if fix { " --fix" } else { "" });
let mut tally = Tally::default();
tally += check_skill(&project_root, fix);
@@ -150,7 +149,7 @@ impl DoctorJsonCheck {
fn json_report(project_root: &Path, fix: bool) -> DoctorJsonReport {
let mut checks = vec![
- check_clarion_dir_json(project_root),
+ check_loomweave_dir_json(project_root),
check_index_freshness_json(project_root),
check_plugin_availability_json(),
check_skill_json(project_root, fix),
@@ -167,14 +166,22 @@ fn json_report(project_root: &Path, fix: bool) -> DoctorJsonReport {
.iter()
.filter(|check| check.status == "problem" || check.status == "warning")
.map(|check| match check.id {
- "skill.pack" => "Run `clarion doctor --fix` or `clarion install --skills`.".to_owned(),
+ "skill.pack" => {
+ "Run `loomweave doctor --fix` or `loomweave install --skills`.".to_owned()
+ }
"hook.session_start" => {
- "Run `clarion doctor --fix` or `clarion install --hooks`.".to_owned()
+ "Run `loomweave doctor --fix` or `loomweave install --hooks`.".to_owned()
+ }
+ "mcp.registration" | "integration.bindings" => {
+ "Run `loomweave doctor --fix`.".to_owned()
+ }
+ "index.freshness" => {
+ "Run `loomweave analyze ` to refresh the index.".to_owned()
}
- "mcp.registration" | "integration.bindings" => "Run `clarion doctor --fix`.".to_owned(),
- "index.freshness" => "Run `clarion analyze ` to refresh the index.".to_owned(),
"plugin.availability" => {
- "Install or expose Clarion language plugins on PATH.".to_owned()
+ "Install a Loomweave language plugin (the Python plugin ships with `pip install \
+ loomweave`)."
+ .to_owned()
}
_ => format!("Review doctor check `{}`.", check.id),
})
@@ -189,21 +196,21 @@ fn json_report(project_root: &Path, fix: bool) -> DoctorJsonReport {
}
}
-fn check_clarion_dir_json(project_root: &Path) -> DoctorJsonCheck {
- let clarion_dir = project_root.join(".clarion");
- let db = clarion_dir.join("clarion.db");
- if clarion_dir.is_dir() && db.is_file() {
+fn check_loomweave_dir_json(project_root: &Path) -> DoctorJsonCheck {
+ let loomweave_dir = project_root.join(".loomweave");
+ let db = loomweave_dir.join("loomweave.db");
+ if loomweave_dir.is_dir() && db.is_file() {
DoctorJsonCheck::ok(
- ".clarion.schema",
- ".clarion directory and database are present",
+ ".loomweave.schema",
+ ".loomweave directory and database are present",
)
- } else if clarion_dir.is_dir() {
+ } else if loomweave_dir.is_dir() {
DoctorJsonCheck::warning(
- ".clarion.schema",
- ".clarion directory exists but clarion.db is absent",
+ ".loomweave.schema",
+ ".loomweave directory exists but loomweave.db is absent",
)
} else {
- DoctorJsonCheck::warning(".clarion.schema", ".clarion directory is absent")
+ DoctorJsonCheck::warning(".loomweave.schema", ".loomweave directory is absent")
}
}
@@ -220,30 +227,40 @@ fn check_index_freshness_json(project_root: &Path) -> DoctorJsonCheck {
}
fn check_plugin_availability_json() -> DoctorJsonCheck {
- let Some(path) = env::var_os("PATH") else {
- return DoctorJsonCheck::warning("plugin.availability", "PATH is unset");
- };
- for dir in env::split_paths(&path) {
- let Ok(entries) = fs::read_dir(dir) else {
- continue;
- };
- for entry in entries.flatten() {
- if entry
- .file_name()
- .to_string_lossy()
- .starts_with("clarion-plugin-")
- {
- return DoctorJsonCheck::ok(
- "plugin.availability",
- "at least one clarion-plugin-* executable is visible on PATH",
- );
- }
+ // Use the same discovery path as `loomweave analyze` (`$PATH` *and* the running
+ // binary's directory), so doctor agrees with analyze about which plugins are
+ // visible. A manual `$PATH`-only scan here would report a co-located
+ // PyPI/venv-installed plugin as missing even though analyze can drive it.
+ let mut ids = Vec::new();
+ let mut errs = Vec::new();
+ for result in loomweave_core::plugin::discover() {
+ match result {
+ Ok(plugin) => ids.push(plugin.manifest.plugin.plugin_id),
+ Err(err) => errs.push(err.to_string()),
}
}
- DoctorJsonCheck::warning(
- "plugin.availability",
- "no clarion-plugin-* executable is visible on PATH",
- )
+
+ if !ids.is_empty() {
+ let plural = if ids.len() == 1 { "" } else { "s" };
+ DoctorJsonCheck::ok(
+ "plugin.availability",
+ format!(
+ "{} language plugin{plural} discovered: {}",
+ ids.len(),
+ ids.join(", ")
+ ),
+ )
+ } else if !errs.is_empty() {
+ DoctorJsonCheck::warning(
+ "plugin.availability",
+ format!("plugin discovery reported errors: {}", errs.join("; ")),
+ )
+ } else {
+ DoctorJsonCheck::warning(
+ "plugin.availability",
+ "no loomweave language plugin discovered (on PATH or alongside the loomweave binary)",
+ )
+ }
}
fn check_skill_json(project_root: &Path, fix: bool) -> DoctorJsonCheck {
@@ -321,17 +338,18 @@ fn check_hook_json(project_root: &Path, fix: bool) -> DoctorJsonCheck {
fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck {
match mcp_registration::mcp_entry_state(project_root) {
- McpState::Present => {
- DoctorJsonCheck::ok("mcp.registration", ".mcp.json clarion serve entry present")
- }
+ McpState::Present => DoctorJsonCheck::ok(
+ "mcp.registration",
+ ".mcp.json loomweave serve entry present",
+ ),
McpState::Unparseable => {
DoctorJsonCheck::problem("mcp.registration", ".mcp.json is not parseable JSON")
}
McpState::UntrustedCommand => {
- let cmd = mcp_registration::clarion_entry_command(project_root)
+ let cmd = mcp_registration::loomweave_entry_command(project_root)
.unwrap_or_else(|| "".to_owned());
let what = format!(
- ".mcp.json clarion entry uses an unrecognized command {cmd:?} (not the clarion \
+ ".mcp.json loomweave entry uses an unrecognized command {cmd:?} (not the loomweave \
executable); doctor will not auto-replace it"
);
if !fix {
@@ -347,8 +365,8 @@ fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck {
}
state => {
let what = match state {
- McpState::Missing => ".mcp.json has no clarion serve entry",
- McpState::Stale => ".mcp.json clarion entry is stale or not runtime-discovered",
+ McpState::Missing => ".mcp.json has no loomweave serve entry",
+ McpState::Stale => ".mcp.json loomweave entry is stale or not runtime-discovered",
McpState::Present | McpState::Unparseable | McpState::UntrustedCommand => {
unreachable!()
}
@@ -360,7 +378,7 @@ fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck {
Ok(_) if mcp_registration::mcp_entry_state(project_root) == McpState::Present => {
DoctorJsonCheck::fixed(
"mcp.registration",
- format!("{what}; merged clarion serve entry"),
+ format!("{what}; merged loomweave serve entry"),
)
}
Ok(_) => DoctorJsonCheck::problem(
@@ -377,8 +395,8 @@ fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck {
}
fn check_http_config_json(project_root: &Path) -> DoctorJsonCheck {
- let Some(config) = read_clarion_yaml(project_root) else {
- return DoctorJsonCheck::warning("http.config", "clarion.yaml is absent or unparseable");
+ let Some(config) = read_loomweave_yaml(project_root) else {
+ return DoctorJsonCheck::warning("http.config", "loomweave.yaml is absent or unparseable");
};
let enabled = config
.get("serve")
@@ -400,8 +418,8 @@ fn check_http_config_json(project_root: &Path) -> DoctorJsonCheck {
}
fn check_filigree_url_json(project_root: &Path) -> DoctorJsonCheck {
- let Some(config) = read_clarion_yaml(project_root) else {
- return DoctorJsonCheck::warning("filigree.url", "clarion.yaml is absent or unparseable");
+ let Some(config) = read_loomweave_yaml(project_root) else {
+ return DoctorJsonCheck::warning("filigree.url", "loomweave.yaml is absent or unparseable");
};
let enabled = config
.get("integrations")
@@ -426,9 +444,9 @@ fn check_filigree_url_json(project_root: &Path) -> DoctorJsonCheck {
}
fn check_sei_population_json(project_root: &Path) -> DoctorJsonCheck {
- let db = project_root.join(".clarion/clarion.db");
+ let db = project_root.join(".loomweave/loomweave.db");
let Ok(conn) = Connection::open(&db) else {
- return DoctorJsonCheck::warning("sei.population", "clarion.db is absent or unreadable");
+ return DoctorJsonCheck::warning("sei.population", "loomweave.db is absent or unreadable");
};
let count: rusqlite::Result = conn.query_row(
"SELECT COUNT(*) FROM sei_bindings WHERE status = 'alive'",
@@ -448,10 +466,10 @@ fn check_sei_population_json(project_root: &Path) -> DoctorJsonCheck {
}
fn check_wardline_taint_capability_json(project_root: &Path) -> DoctorJsonCheck {
- let Some(config) = read_clarion_yaml(project_root) else {
+ let Some(config) = read_loomweave_yaml(project_root) else {
return DoctorJsonCheck::warning(
"wardline.taint_store",
- "clarion.yaml is absent or unparseable",
+ "loomweave.yaml is absent or unparseable",
);
};
if config
@@ -484,7 +502,7 @@ fn check_integration_bindings_json(project_root: &Path, fix: bool) -> DoctorJson
match integration_bindings::binding_state(project_root) {
BindingState::Present => DoctorJsonCheck::ok(
"integration.bindings",
- "three-way integration bindings present (Clarion + Filigree + Wardline)",
+ "three-way integration bindings present (Loomweave + Filigree + Wardline)",
),
BindingState::Unparseable => DoctorJsonCheck::problem(
"integration.bindings",
@@ -516,8 +534,8 @@ fn check_integration_bindings_json(project_root: &Path, fix: bool) -> DoctorJson
}
}
-fn read_clarion_yaml(project_root: &Path) -> Option {
- let raw = fs::read_to_string(project_root.join("clarion.yaml")).ok()?;
+fn read_loomweave_yaml(project_root: &Path) -> Option {
+ let raw = fs::read_to_string(project_root.join("loomweave.yaml")).ok()?;
serde_norway::from_str(&raw).ok()
}
@@ -579,7 +597,7 @@ fn check_skill(project_root: &Path, fix: bool) -> Tally {
if !fix {
return problem(
&format!("skill pack {what}"),
- Some("clarion install --skills"),
+ Some("loomweave install --skills"),
);
}
match skill_pack::install_skill_pack(project_root) {
@@ -615,7 +633,7 @@ fn check_hook(project_root: &Path, fix: bool) -> Tally {
HookState::Present | HookState::Unparseable => unreachable!(),
};
if !fix {
- return problem(what, Some("clarion install --hooks"));
+ return problem(what, Some("loomweave install --hooks"));
}
match hooks_settings::install_session_start_hook(project_root) {
Ok(_)
@@ -633,16 +651,16 @@ fn check_hook(project_root: &Path, fix: bool) -> Tally {
fn check_mcp(project_root: &Path, fix: bool) -> Tally {
match mcp_registration::mcp_entry_state(project_root) {
- McpState::Present => ok(".mcp.json clarion serve entry present"),
+ McpState::Present => ok(".mcp.json loomweave serve entry present"),
McpState::Unparseable => problem(
".mcp.json is not parseable JSON — fix it by hand, then re-run",
None,
),
McpState::UntrustedCommand => {
- let cmd = mcp_registration::clarion_entry_command(project_root)
+ let cmd = mcp_registration::loomweave_entry_command(project_root)
.unwrap_or_else(|| "".to_owned());
let what = format!(
- ".mcp.json clarion entry uses an unrecognized command {cmd:?} (not the clarion \
+ ".mcp.json loomweave entry uses an unrecognized command {cmd:?} (not the loomweave \
executable); doctor will not auto-replace it"
);
if !fix {
@@ -650,7 +668,7 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally {
&what,
Some(
"if this is a deliberate wrapper, leave it; otherwise set `command` to \
- `clarion` or remove the entry — `--fix` will not clobber it",
+ `loomweave` or remove the entry — `--fix` will not clobber it",
),
);
}
@@ -665,8 +683,8 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally {
}
state => {
let what = match state {
- McpState::Missing => ".mcp.json has no clarion serve entry",
- McpState::Stale => ".mcp.json clarion entry is stale or not runtime-discovered",
+ McpState::Missing => ".mcp.json has no loomweave serve entry",
+ McpState::Stale => ".mcp.json loomweave entry is stale or not runtime-discovered",
McpState::Present | McpState::Unparseable | McpState::UntrustedCommand => {
unreachable!()
}
@@ -674,12 +692,12 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally {
if !fix {
return problem(
what,
- Some("clarion doctor --fix (or add the entry to .mcp.json manually)"),
+ Some("loomweave doctor --fix (or add the entry to .mcp.json manually)"),
);
}
match mcp_registration::install_mcp_entry(project_root) {
Ok(_) if mcp_registration::mcp_entry_state(project_root) == McpState::Present => {
- ok(&format!("{what} — fixed (merged clarion serve entry)"))
+ ok(&format!("{what} — fixed (merged loomweave serve entry)"))
}
Ok(_) => problem(&format!("{what} — repair did not converge"), None),
Err(err) => problem(&format!("{what} — repair failed: {err}"), None),
@@ -691,7 +709,7 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally {
fn check_integration_bindings(project_root: &Path, fix: bool) -> Tally {
match integration_bindings::binding_state(project_root) {
BindingState::Present => {
- ok("three-way integration bindings present (Clarion + Filigree + Wardline)")
+ ok("three-way integration bindings present (Loomweave + Filigree + Wardline)")
}
BindingState::Unparseable => problem(
"three-way integration bindings are not parseable — fix config files by hand, then re-run",
@@ -701,7 +719,7 @@ fn check_integration_bindings(project_root: &Path, fix: bool) -> Tally {
let what = "three-way integration bindings missing or stale";
if !fix {
// Enrich-only surface: absence is a warning, not a gate failure.
- return warn(what, Some("clarion doctor --fix"));
+ return warn(what, Some("loomweave doctor --fix"));
}
match integration_bindings::install_bindings(project_root) {
Ok(_)
diff --git a/crates/clarion-cli/src/guidance.rs b/crates/loomweave-cli/src/guidance.rs
similarity index 96%
rename from crates/clarion-cli/src/guidance.rs
rename to crates/loomweave-cli/src/guidance.rs
index dec40653..88feb23f 100644
--- a/crates/clarion-cli/src/guidance.rs
+++ b/crates/loomweave-cli/src/guidance.rs
@@ -1,9 +1,9 @@
-//! `clarion guidance` authoring subcommands (WS6 / REQ-GUIDANCE-03).
+//! `loomweave guidance` authoring subcommands (WS6 / REQ-GUIDANCE-03).
//!
//! Operator-facing CLI to create, edit, show, list, and delete guidance sheets
//! — the institutional-knowledge entities (`kind = 'guidance'`) the MCP read
-//! path composes into briefings. All SQL lives in `clarion-storage`
-//! (`clarion_storage::guidance`); this module owns only argument parsing, the
+//! path composes into briefings. All SQL lives in `loomweave-storage`
+//! (`loomweave_storage::guidance`); this module owns only argument parsing, the
//! `$EDITOR` round-trip, and presentation.
//!
//! ## `--match` syntax
@@ -27,8 +27,8 @@ use anyhow::{Context, Result, anyhow, bail};
use rusqlite::{Connection, OpenFlags};
use serde_json::{Value, json};
-use clarion_federation::filigree::{FiligreeHttpClient, FiligreeLookup};
-use clarion_storage::{
+use loomweave_federation::filigree::{FiligreeHttpClient, FiligreeLookup};
+use loomweave_storage::{
GuidanceProposal, GuidanceSheet, GuidanceSheetInput, PortableSheet, delete_guidance_sheet,
get_guidance_sheet, guidance_sheet_is_expired, guidance_sheet_is_stale,
guidance_sheet_matches_entity, import_portable_sheet, insert_guidance_sheet,
@@ -37,14 +37,14 @@ use clarion_storage::{
use crate::cli::GuidanceCommand;
-/// Map a `clarion_storage::StorageError` (which is `Send` but not `Sync`, so it
+/// Map a `loomweave_storage::StorageError` (which is `Send` but not `Sync`, so it
/// does not satisfy `anyhow`'s `From` bound) into an `anyhow::Error` via its
/// Display — matching the convention in `analyze.rs`.
trait StorageResultExt {
fn into_anyhow(self) -> Result;
}
-impl StorageResultExt for clarion_storage::Result {
+impl StorageResultExt for loomweave_storage::Result {
fn into_anyhow(self) -> Result {
self.map_err(|e| anyhow!("{e}"))
}
@@ -63,7 +63,7 @@ const SCOPE_LEVELS: &[&str] = &[
const PROVENANCE_MANUAL: &str = "manual";
-/// Dispatch a `clarion guidance `.
+/// Dispatch a `loomweave guidance `.
///
/// # Errors
///
@@ -392,7 +392,7 @@ fn show(project_root: &Path, id: &str) -> Result<()> {
Ok(())
}
-/// Filters for `clarion guidance list`. All active filters compose by
+/// Filters for `loomweave guidance list`. All active filters compose by
/// **intersection** (AND): a sheet is shown only if it passes every one. The
/// two date filters (`expired`, `stale`) are independent — combining them shows
/// sheets that are expired AND stale, which is the intuitive "show me the worst
@@ -419,7 +419,7 @@ fn list(project_root: &Path, filters: ListFilters<'_>) -> Result<()> {
// storage predicates' lexical compares are valid instant compares. `now`
// drives `--expired`; `stale_before` (now − N days) drives `--stale`. NB:
// `--stale` here is the *age/review-cadence* signal (system-design §7.741),
- // distinct from the churn-based `CLA-FACT-GUIDANCE-CHURN-STALE` finding.
+ // distinct from the churn-based `LMWV-FACT-GUIDANCE-CHURN-STALE` finding.
let now = now_iso8601(&conn)?;
let stale_before = if filters.stale {
Some(now_minus_days(&conn, filters.days)?)
@@ -529,7 +529,7 @@ fn promote(project_root: &Path, config_path: Option<&Path>, observation_id: &str
Some(&canonical_root),
)
.context("build Filigree client")?
- .ok_or_else(|| anyhow!("Filigree integration is disabled in clarion.yaml"))?;
+ .ok_or_else(|| anyhow!("Filigree integration is disabled in loomweave.yaml"))?;
let observation = client
.observation_by_id(observation_id)
@@ -538,7 +538,7 @@ fn promote(project_root: &Path, config_path: Option<&Path>, observation_id: &str
let proposal = GuidanceProposal::from_observation_detail(&observation.detail)
.map_err(|e| anyhow!("{e}"))
.with_context(|| {
- format!("Filigree observation {observation_id} is not a Clarion guidance proposal")
+ format!("Filigree observation {observation_id} is not a Loomweave guidance proposal")
})?;
let conn = open_db(&canonical_root)?;
@@ -569,7 +569,7 @@ fn promote(project_root: &Path, config_path: Option<&Path>, observation_id: &str
};
let dismissed = client
- .dismiss_observation(observation_id, "promoted to Clarion guidance sheet")
+ .dismiss_observation(observation_id, "promoted to Loomweave guidance sheet")
.is_ok();
println!("Promoted observation {observation_id} to {}", promoted.id);
report_invalidation(invalidated);
@@ -749,14 +749,14 @@ fn render_sheet(sheet: &GuidanceSheet) -> String {
// ── I/O helpers ───────────────────────────────────────────────────────────────
-/// Open a read-write connection to `.clarion/clarion.db` with a generous busy
+/// Open a read-write connection to `.loomweave/loomweave.db` with a generous busy
/// timeout so a concurrently-running `serve` writer does not cause an immediate
/// lock error.
fn open_db(project_root: &Path) -> Result {
- let db_path = project_root.join(".clarion").join("clarion.db");
+ let db_path = project_root.join(".loomweave").join("loomweave.db");
if !db_path.exists() {
bail!(
- "Clarion database not found at {}; run `clarion analyze` first",
+ "Loomweave database not found at {}; run `loomweave analyze` first",
db_path.display()
);
}
@@ -793,7 +793,7 @@ fn edit_in_editor(seed: &str) -> Result {
.map_err(|_| anyhow!("neither $VISUAL nor $EDITOR is set; set one or pass --content"))?;
let dir = std::env::temp_dir();
- let file = dir.join(format!("clarion-guidance-{}.md", std::process::id()));
+ let file = dir.join(format!("loomweave-guidance-{}.md", std::process::id()));
{
let mut f = std::fs::File::create(&file)
.with_context(|| format!("create temp edit file {}", file.display()))?;
@@ -844,7 +844,7 @@ fn now_iso8601(conn: &Connection) -> Result {
/// Normalise an `--expires` value to a UTC instant in the exact
/// `YYYY-MM-DDTHH:MM:SS.mmmZ` shape the read path compares against. The expiry
-/// check (`crates/clarion-mcp/src/catalogue/inspection.rs`) is a *lexical*
+/// check (`crates/loomweave-mcp/src/catalogue/inspection.rs`) is a *lexical*
/// `expires < now` compare, so the stored string must be byte-format-identical
/// to `now`: same UTC zone (`Z`), same 3-digit subsecond, same length. We run
/// the input through the connection's own `strftime`, which:
diff --git a/crates/clarion-cli/src/hook.rs b/crates/loomweave-cli/src/hook.rs
similarity index 76%
rename from crates/clarion-cli/src/hook.rs
rename to crates/loomweave-cli/src/hook.rs
index 8aa8ce19..38d00141 100644
--- a/crates/clarion-cli/src/hook.rs
+++ b/crates/loomweave-cli/src/hook.rs
@@ -1,14 +1,14 @@
-//! `clarion hook session-start` — fail-soft session-start orientation.
+//! `loomweave hook session-start` — fail-soft session-start orientation.
//!
//! Never returns an error to the caller: the `SessionStart` hook must never
//! block an agent's session start. All failures degrade to a printed note.
use std::path::Path;
-use clarion_mcp::snapshot::{ProjectSnapshot, Staleness, missing_db_snapshot, project_snapshot};
+use loomweave_mcp::snapshot::{ProjectSnapshot, Staleness, missing_db_snapshot, project_snapshot};
use rusqlite::{Connection, OpenFlags};
-/// Run `clarion hook session-start`. Always returns `Ok(())`.
+/// Run `loomweave hook session-start`. Always returns `Ok(())`.
///
/// The `anyhow::Result` return type is intentional even though no `Err` is
/// ever produced: it keeps the `main.rs` dispatch arm uniform with the other
@@ -17,7 +17,7 @@ use rusqlite::{Connection, OpenFlags};
pub fn session_start(path: &Path) -> anyhow::Result<()> {
// (1) Re-sync the skill pack ONLY if it's already installed in at least one
// skill root, and drifted. A bare session-start never bootstraps a
- // never-installed project — that's `clarion install --skills`'s job. Note
+ // never-installed project — that's `loomweave install --skills`'s job. Note
// the resync normalises BOTH roots once triggered: if a project installed
// only `.claude/skills`, a drift repair also (re)creates
// `.agents/skills`, keeping the two roots in lock-step. A drift repair
@@ -30,11 +30,11 @@ pub fn session_start(path: &Path) -> anyhow::Result<()> {
Ok(())
}
-/// What [`load_snapshot`] could establish about the `.clarion/` index.
+/// What [`load_snapshot`] could establish about the `.loomweave/` index.
///
/// A *missing* db and a *present-but-unreadable* db are deliberately distinct:
/// the missing case nudges toward `install` + `analyze`, but that advice is
-/// wrong for a present-but-corrupt/locked db (`install` refuses while `.clarion/`
+/// wrong for a present-but-corrupt/locked db (`install` refuses while `.loomweave/`
/// exists; `analyze` cannot repair corruption). See [`print_snapshot`].
enum SnapshotOutcome {
/// Either the db file is absent (a `missing_db_snapshot()`) or it opened and
@@ -47,28 +47,28 @@ enum SnapshotOutcome {
fn resync_skill_if_present(project_root: &Path) {
let installed = project_root
- .join(".claude/skills/clarion-workflow/SKILL.md")
+ .join(".claude/skills/loomweave-workflow/SKILL.md")
.exists()
|| project_root
- .join(".agents/skills/clarion-workflow/SKILL.md")
+ .join(".agents/skills/loomweave-workflow/SKILL.md")
.exists();
if !installed {
return;
}
if let Err(err) = crate::skill_pack::install_skill_pack(project_root) {
- tracing::warn!(error = %err, "clarion-workflow skill resync failed");
+ tracing::warn!(error = %err, "loomweave-workflow skill resync failed");
}
}
fn load_snapshot(project_root: &Path) -> SnapshotOutcome {
- let db_path = project_root.join(".clarion").join("clarion.db");
+ let db_path = project_root.join(".loomweave").join("loomweave.db");
if !db_path.exists() {
return SnapshotOutcome::Ready(missing_db_snapshot());
}
let conn = match Connection::open_with_flags(&db_path, OpenFlags::SQLITE_OPEN_READ_ONLY) {
Ok(conn) => conn,
Err(err) => {
- tracing::warn!(error = %err, "open .clarion/clarion.db read-only failed");
+ tracing::warn!(error = %err, "open .loomweave/loomweave.db read-only failed");
return SnapshotOutcome::DbUnreadable;
}
};
@@ -78,7 +78,7 @@ fn load_snapshot(project_root: &Path) -> SnapshotOutcome {
// db is classified as unreadable rather than silently reported as 0 counts
// (which would otherwise print the wrong "no analysis yet" nudge).
if let Err(err) = conn.query_row("PRAGMA schema_version", [], |row| row.get::<_, i64>(0)) {
- tracing::warn!(error = %err, "probe read of .clarion/clarion.db failed");
+ tracing::warn!(error = %err, "probe read of .loomweave/loomweave.db failed");
return SnapshotOutcome::DbUnreadable;
}
let root = project_root
@@ -94,7 +94,7 @@ fn print_snapshot(project_root: &Path, outcome: &SnapshotOutcome) {
}
/// Load the index snapshot and render it to lines, for reuse by both the
-/// `SessionStart` hook (which prints them) and `clarion doctor` (which appends
+/// `SessionStart` hook (which prints them) and `loomweave doctor` (which appends
/// them under an `--- index ---` heading). Fail-soft: a missing/unreadable db
/// yields an advisory line, never an error.
#[must_use]
@@ -111,12 +111,12 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec
let snapshot = match outcome {
SnapshotOutcome::Ready(snapshot) => snapshot,
SnapshotOutcome::DbUnreadable => {
- let db_path = project_root.join(".clarion").join("clarion.db");
+ let db_path = project_root.join(".loomweave").join("loomweave.db");
lines.push(format!(
- "Clarion: an index exists at {} but could not be opened (it may be \
+ "Loomweave: an index exists at {} but could not be opened (it may be \
corrupt, locked by another process, or unreadable). Check permissions, \
- ensure no other clarion process holds it, or remove .clarion/ and re-run \
- `clarion install` + `clarion analyze`. (Run with RUST_LOG=warn for the \
+ ensure no other loomweave process holds it, or remove .loomweave/ and re-run \
+ `loomweave install` + `loomweave analyze`. (Run with RUST_LOG=warn for the \
open error.)",
db_path.display()
));
@@ -125,8 +125,8 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec
};
if !snapshot.db_present() {
lines.push(format!(
- "Clarion: no index at {}/.clarion/clarion.db. \
- Run `clarion install --path {}` then `clarion analyze {}`.",
+ "Loomweave: no index at {}/.loomweave/loomweave.db. \
+ Run `loomweave install --path {}` then `loomweave analyze {}`.",
project_root.display(),
project_root.display(),
project_root.display()
@@ -137,7 +137,7 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec
// subset of entity_count, not a parallel category — say so, or the two read
// as disjoint (clarion-e4e80eff3f).
lines.push(format!(
- "Clarion index: {} entities (incl. {} subsystems), {} findings.",
+ "Loomweave index: {} entities (incl. {} subsystems), {} findings.",
snapshot.entity_count(),
snapshot.subsystem_count(),
snapshot.finding_count()
@@ -147,7 +147,7 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec
// understate a populated index. Distinct from the present-but-empty
// case (which is not degraded). Operator detail is in the warn log.
lines.push(
- "Clarion: ⚠ snapshot is degraded — at least one index query failed and \
+ "Loomweave: ⚠ snapshot is degraded — at least one index query failed and \
the counts above may be incomplete. (Run with RUST_LOG=warn for details.)"
.to_string(),
);
@@ -155,21 +155,21 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec
match snapshot.staleness() {
Staleness::Fresh => {
lines.push(format!(
- "Index is fresh (last analyzed {}). Ask Clarion before re-exploring \
- the tree; see the clarion-workflow skill.",
+ "Index is fresh (last analyzed {}). Ask Loomweave before re-exploring \
+ the tree; see the loomweave-workflow skill.",
snapshot.last_analyzed_at().unwrap_or("unknown")
));
}
Staleness::Stale => {
lines.push(format!(
"Index may be stale: source files changed since the last run. \
- Run `clarion analyze {}` to refresh.",
+ Run `loomweave analyze {}` to refresh.",
project_root.display()
));
}
Staleness::NeverAnalyzed => {
lines.push(format!(
- "No analysis recorded yet. Run `clarion analyze {}` to build the index.",
+ "No analysis recorded yet. Run `loomweave analyze {}` to build the index.",
project_root.display()
));
}
@@ -184,7 +184,7 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec
Staleness::Unknown => {
lines.push(format!(
"Index freshness unknown (a freshness check failed). If briefings \
- look empty, run `clarion analyze {}`.",
+ look empty, run `loomweave analyze {}`.",
project_root.display()
));
}
diff --git a/crates/clarion-cli/src/hooks_settings.rs b/crates/loomweave-cli/src/hooks_settings.rs
similarity index 86%
rename from crates/clarion-cli/src/hooks_settings.rs
rename to crates/loomweave-cli/src/hooks_settings.rs
index d1a1317e..bfabb80c 100644
--- a/crates/clarion-cli/src/hooks_settings.rs
+++ b/crates/loomweave-cli/src/hooks_settings.rs
@@ -1,9 +1,9 @@
//! `.claude/settings.json` SessionStart-hook merge.
//!
//! Merge semantics (never clobber): parse existing JSON and ensure exactly one
-//! `SessionStart` matcher-group runs `clarion hook session-start --path
+//! `SessionStart` matcher-group runs `loomweave hook session-start --path
//! ` (the project path POSIX-single-quote-escaped for the shell).
-//! Clarion-owned hooks are canonicalised — the first is refreshed to the
+//! Loomweave-owned hooks are canonicalised — the first is refreshed to the
//! desired command and any extras (a stale duplicate, or one pinned to a
//! different project) are removed. Every other key is preserved.
//!
@@ -16,19 +16,19 @@ use std::path::Path;
use anyhow::{Context, Result, bail};
use serde_json::{Map, Value, json};
-/// Substring that identifies Clarion's own `SessionStart` hook command.
-pub const HOOK_COMMAND: &str = "clarion hook session-start";
+/// Substring that identifies Loomweave's own `SessionStart` hook command.
+pub const HOOK_COMMAND: &str = "loomweave hook session-start";
-/// Read-only health of the installed `SessionStart` hook, for `clarion doctor`.
+/// Read-only health of the installed `SessionStart` hook, for `loomweave doctor`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookState {
- /// Exactly one Clarion hook is present and runs the command this project
+ /// Exactly one Loomweave hook is present and runs the command this project
/// would install ([`desired_hook_command`]).
Present,
- /// A Clarion hook exists but is stale — the old path-less form, one pinned
+ /// A Loomweave hook exists but is stale — the old path-less form, one pinned
/// to a different project, or a duplicate. Repairable in place.
Stale,
- /// No `.claude/settings.json`, or it has no Clarion `SessionStart` hook.
+ /// No `.claude/settings.json`, or it has no Loomweave `SessionStart` hook.
Missing,
/// `.claude/settings.json` exists but is not parseable JSON. The merge
/// refuses to clobber it, so this cannot be auto-repaired.
@@ -36,7 +36,7 @@ pub enum HookState {
}
/// The `SessionStart` hook command this project would install: a bare
-/// `clarion hook session-start` (PATH-resolved) pinned to the absolute project
+/// `loomweave hook session-start` (PATH-resolved) pinned to the absolute project
/// path (POSIX-single-quote-escaped). Shared by the installer and the
/// `doctor` state check so the two never disagree on what "current" means.
#[must_use]
@@ -47,14 +47,14 @@ pub fn desired_hook_command(project_root: &Path) -> String {
.canonicalize()
.unwrap_or_else(|_| project_root.to_path_buf());
format!(
- "clarion hook session-start --path {}",
+ "loomweave hook session-start --path {}",
shell_single_quote(&canonical.display().to_string())
)
}
/// Every `command` string across all `SessionStart` groups that looks like a
-/// Clarion-owned hook (contains [`HOOK_COMMAND`]).
-fn clarion_commands(settings: &Value) -> Vec {
+/// Loomweave-owned hook (contains [`HOOK_COMMAND`]).
+fn loomweave_commands(settings: &Value) -> Vec {
settings["hooks"]["SessionStart"]
.as_array()
.into_iter()
@@ -68,7 +68,7 @@ fn clarion_commands(settings: &Value) -> Vec {
}
/// Classify the installed `SessionStart` hook without writing anything, for
-/// `clarion doctor`. The repair for `Missing`/`Stale` is the idempotent
+/// `loomweave doctor`. The repair for `Missing`/`Stale` is the idempotent
/// [`install_session_start_hook`]; `Unparseable` must be fixed by hand.
#[must_use]
pub fn session_start_hook_state(project_root: &Path) -> HookState {
@@ -82,7 +82,7 @@ pub fn session_start_hook_state(project_root: &Path) -> HookState {
let Ok(settings) = serde_json::from_str::(&raw) else {
return HookState::Unparseable;
};
- let cmds = clarion_commands(&settings);
+ let cmds = loomweave_commands(&settings);
if cmds.is_empty() {
HookState::Missing
} else if cmds.len() == 1 && cmds[0] == desired_hook_command(project_root) {
@@ -113,16 +113,16 @@ fn shell_single_quote(s: &str) -> String {
out
}
-/// Merge Clarion's `SessionStart` hook into a parsed settings `Value` in place,
+/// Merge Loomweave's `SessionStart` hook into a parsed settings `Value` in place,
/// inserting the supplied `command` (which must contain [`HOOK_COMMAND`] so it
-/// is recognised as Clarion-owned). Returns `true` if a change was made.
+/// is recognised as Loomweave-owned). Returns `true` if a change was made.
///
-/// Clarion-owned entries are keyed on the [`HOOK_COMMAND`] substring. The merge
+/// Loomweave-owned entries are keyed on the [`HOOK_COMMAND`] substring. The merge
/// canonicalises them to exactly one hook running `command`: the first is
/// refreshed and any extras (a stale duplicate, or a hook pinned to a different
/// project — possible in hand-merged settings) are removed, dropping any
-/// Clarion-dedicated group left empty. If none exists, the hook is appended.
-/// Returns `false` only when a single Clarion hook already runs `command` (the
+/// Loomweave-dedicated group left empty. If none exists, the hook is appended.
+/// Returns `false` only when a single Loomweave hook already runs `command` (the
/// idempotent re-install case); otherwise `true`.
#[must_use]
pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool {
@@ -160,11 +160,11 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool {
tracing::warn!(
"malformed .claude/settings.json shape (non-object settings/hooks or \
non-array SessionStart) was rewritten to the expected shape before \
- merging the clarion SessionStart hook"
+ merging the loomweave SessionStart hook"
);
}
- // Locate every Clarion-owned hook (its command contains HOOK_COMMAND),
+ // Locate every Loomweave-owned hook (its command contains HOOK_COMMAND),
// across all matcher-groups. Pass 1 only reads, so the immutable borrow is
// released before any mutation below.
let mut locations: Vec<(usize, usize)> = Vec::new();
@@ -194,12 +194,12 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool {
return true;
}
- // Canonicalise to exactly one Clarion hook running `command`: refresh the
+ // Canonicalise to exactly one Loomweave hook running `command`: refresh the
// first, then remove any extras (a stale duplicate, or a hook pinned to a
// different project — e.g. hand-merged settings). This delivers "don't
// no-op on a stale hook, don't leave duplicates, don't silently keep a
// wrong-project pin" even when a current and a stale entry coexist or
- // multiple stale entries exist. Returns `false` only when a single Clarion
+ // multiple stale entries exist. Returns `false` only when a single Loomweave
// hook already runs `command` (the idempotent re-install case).
let mut changed = false;
let (kg, kh) = locations[0];
@@ -219,7 +219,7 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool {
changed = true;
}
}
- // Drop any Clarion-dedicated group we just emptied (descending to keep
+ // Drop any Loomweave-dedicated group we just emptied (descending to keep
// indices valid). A group still holding unrelated hooks is left intact.
for gi in touched_groups.into_iter().rev() {
if groups[gi]["hooks"]
@@ -233,7 +233,7 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool {
}
/// Read `.claude/settings.json` under `project_root` (creating an empty object
-/// if absent), merge Clarion's `SessionStart` hook, and write it back
+/// if absent), merge Loomweave's `SessionStart` hook, and write it back
/// pretty-printed. Returns `true` if the file changed.
///
/// # Errors
@@ -332,11 +332,11 @@ mod tests {
use serde_json::json;
use super::{
- HOOK_COMMAND, HookState, clarion_commands, install_session_start_hook,
+ HOOK_COMMAND, HookState, install_session_start_hook, loomweave_commands,
merge_session_start_hook, session_start_hook_state,
};
- const TEST_COMMAND: &str = "clarion hook session-start --path \"/some/project\"";
+ const TEST_COMMAND: &str = "loomweave hook session-start --path \"/some/project\"";
#[cfg(unix)]
fn sh_roundtrip(quoted: &str) -> String {
@@ -355,7 +355,7 @@ mod tests {
// with shell metacharacters must survive as a single literal argument,
// never expanded or split. Double-quote wrapping (the prior form) lets
// $, backtick, and \ act; single-quote escaping does not. Prove the
- // helper round-trips through `sh` exactly. (clarion review #5)
+ // helper round-trips through `sh` exactly. (loomweave review #5)
for s in [
"/plain/path",
"/with space/x",
@@ -401,20 +401,20 @@ mod tests {
}
#[test]
- fn refreshes_a_stale_clarion_hook_in_place() {
- // A previously-installed Clarion hook (e.g. the old path-less form, or
+ fn refreshes_a_stale_loomweave_hook_in_place() {
+ // A previously-installed Loomweave hook (e.g. the old path-less form, or
// one pinned to a different project) must be refreshed to the desired
// command on re-install, not left stale. The idempotency check keys on
// the HOOK_COMMAND substring, so a stale entry used to no-op forever.
- // (clarion review #10)
+ // (loomweave review #10)
let mut settings = json!({
"hooks": {"SessionStart": [
- {"hooks": [{"type": "command", "command": "clarion hook session-start"}]}
+ {"hooks": [{"type": "command", "command": "loomweave hook session-start"}]}
]}
});
- let desired = "clarion hook session-start --path '/proj'";
+ let desired = "loomweave hook session-start --path '/proj'";
let changed = merge_session_start_hook(&mut settings, desired);
- assert!(changed, "a stale Clarion hook must be refreshed");
+ assert!(changed, "a stale Loomweave hook must be refreshed");
let groups = settings["hooks"]["SessionStart"].as_array().unwrap();
assert_eq!(
groups.len(),
@@ -437,16 +437,16 @@ mod tests {
fn refreshes_a_stale_hook_pinned_to_a_different_path() {
// The realistic re-install case: the repo moved, so the existing hook
// pins the old project path. It must be refreshed to the new path, not
- // no-oped. (clarion review #4/#10)
+ // no-oped. (loomweave review #4/#10)
let mut settings = json!({
"hooks": {"SessionStart": [
{"hooks": [{"type": "command",
- "command": "clarion hook session-start --path '/old/proj'"}]}
+ "command": "loomweave hook session-start --path '/old/proj'"}]}
]}
});
- let desired = "clarion hook session-start --path '/new/proj'";
+ let desired = "loomweave hook session-start --path '/new/proj'";
assert!(merge_session_start_hook(&mut settings, desired));
- assert_eq!(clarion_commands(&settings), vec![desired.to_string()]);
+ assert_eq!(loomweave_commands(&settings), vec![desired.to_string()]);
}
#[test]
@@ -454,14 +454,14 @@ mod tests {
// A current hook coexisting with a stale one pinned to a different
// project (e.g. hand-merged settings). The stale one silently orients
// the wrong project every session, so it must be reconciled away —
- // leaving exactly one Clarion hook running the desired command.
- // (clarion review #10 — found_current must not short-circuit the sweep)
- let desired = "clarion hook session-start --path '/proj'";
+ // leaving exactly one Loomweave hook running the desired command.
+ // (loomweave review #10 — found_current must not short-circuit the sweep)
+ let desired = "loomweave hook session-start --path '/proj'";
let mut settings = json!({
"hooks": {"SessionStart": [
{"hooks": [{"type": "command", "command": desired}]},
{"hooks": [{"type": "command",
- "command": "clarion hook session-start --path '/other'"}]}
+ "command": "loomweave hook session-start --path '/other'"}]}
]}
});
assert!(
@@ -469,26 +469,26 @@ mod tests {
"a stale entry coexisting with the current one must be reconciled"
);
assert_eq!(
- clarion_commands(&settings),
+ loomweave_commands(&settings),
vec![desired.to_string()],
- "exactly one Clarion hook must remain, running the desired command"
+ "exactly one Loomweave hook must remain, running the desired command"
);
}
#[test]
- fn dedups_multiple_stale_clarion_hooks() {
- // Two stale Clarion hooks, no current one. Must converge to a single
- // hook running the desired command, not leave survivors. (clarion #10)
- let desired = "clarion hook session-start --path '/proj'";
+ fn dedups_multiple_stale_loomweave_hooks() {
+ // Two stale Loomweave hooks, no current one. Must converge to a single
+ // hook running the desired command, not leave survivors. (loomweave #10)
+ let desired = "loomweave hook session-start --path '/proj'";
let mut settings = json!({
"hooks": {"SessionStart": [
- {"hooks": [{"type": "command", "command": "clarion hook session-start"}]},
+ {"hooks": [{"type": "command", "command": "loomweave hook session-start"}]},
{"hooks": [{"type": "command",
- "command": "clarion hook session-start --path '/old'"}]}
+ "command": "loomweave hook session-start --path '/old'"}]}
]}
});
assert!(merge_session_start_hook(&mut settings, desired));
- assert_eq!(clarion_commands(&settings), vec![desired.to_string()]);
+ assert_eq!(loomweave_commands(&settings), vec![desired.to_string()]);
// Convergent: a second merge is now a no-op.
assert!(!merge_session_start_hook(&mut settings, desired));
}
@@ -547,10 +547,10 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let claude = dir.path().join(".claude");
fs::create_dir_all(&claude).unwrap();
- // A Clarion hook pinned to some other project: present-but-wrong.
+ // A Loomweave hook pinned to some other project: present-but-wrong.
fs::write(
claude.join("settings.json"),
- r#"{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"clarion hook session-start --path '/some/other/proj'"}]}]}}"#,
+ r#"{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"loomweave hook session-start --path '/some/other/proj'"}]}]}}"#,
)
.unwrap();
assert_eq!(session_start_hook_state(dir.path()), HookState::Stale);
@@ -578,13 +578,13 @@ mod tests {
assert_eq!(
session_start_hook_state(dir.path()),
HookState::Missing,
- "an unrelated SessionStart hook is not a Clarion hook"
+ "an unrelated SessionStart hook is not a Loomweave hook"
);
- // And clarion_commands sees nothing Clarion-owned here.
+ // And loomweave_commands sees nothing Loomweave-owned here.
let settings: serde_json::Value =
serde_json::from_str(&fs::read_to_string(claude.join("settings.json")).unwrap())
.unwrap();
- assert!(clarion_commands(&settings).is_empty());
+ assert!(loomweave_commands(&settings).is_empty());
}
#[test]
@@ -661,7 +661,7 @@ mod tests {
let cmd = parsed["hooks"]["SessionStart"][0]["hooks"][0]["command"]
.as_str()
.unwrap();
- assert!(cmd.contains("clarion hook session-start"), "cmd: {cmd}");
+ assert!(cmd.contains("loomweave hook session-start"), "cmd: {cmd}");
assert!(
cmd.contains("--path"),
"installed hook must pin --path: {cmd}"
@@ -722,7 +722,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let claude_dir = dir.path().join(".claude");
fs::create_dir_all(&claude_dir).unwrap();
- // Hand-authored settings WITHOUT the clarion hook, so the install will
+ // Hand-authored settings WITHOUT the loomweave hook, so the install will
// try to add it (changed = true) and reach the staged write.
let settings_path = claude_dir.join("settings.json");
let original = "{\n \"model\": \"opus\"\n}\n";
diff --git a/crates/clarion-cli/src/http_read.rs b/crates/loomweave-cli/src/http_read.rs
similarity index 93%
rename from crates/clarion-cli/src/http_read.rs
rename to crates/loomweave-cli/src/http_read.rs
index 3bb9e9bb..41b4c16f 100644
--- a/crates/clarion-cli/src/http_read.rs
+++ b/crates/loomweave-cli/src/http_read.rs
@@ -12,9 +12,9 @@ use axum::http::{HeaderMap, Request, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::routing::{get, post};
use axum::{Json, Router};
-use clarion_core::HttpErrorCode as ErrorCode;
-use clarion_federation::config::HttpReadConfig;
-use clarion_storage::ReaderPool;
+use loomweave_core::HttpErrorCode as ErrorCode;
+use loomweave_federation::config::HttpReadConfig;
+use loomweave_storage::ReaderPool;
use serde::Serialize;
use tokio::sync::oneshot;
use tower::ServiceBuilder;
@@ -144,13 +144,13 @@ pub(crate) struct AppState {
/// `Authorization: Bearer ` when `Some`. `/api/v1/_capabilities`
/// is always unauthenticated so siblings can probe pre-auth.
pub(crate) auth_token: Option>,
- /// Resolved Loom component identity HMAC secret. When present, protected
- /// routes require `X-Loom-Component: clarion:` plus freshness headers.
+ /// Resolved Weft component identity HMAC secret. When present, protected
+ /// routes require `X-Weft-Component: loomweave:` plus freshness headers.
pub(crate) identity_secret: Option>,
pub(crate) hmac_replay_cache: auth::SharedHmacReplayCache,
/// Present only when `serve.http.wardline_taint_write` is true (ADR-036).
/// `None` ⇒ the write API is disabled and returns 403 `WRITE_DISABLED`.
- pub(crate) taint_writer: Option>,
+ pub(crate) taint_writer: Option>,
}
impl AppState {
@@ -261,7 +261,7 @@ where
let auth_token_thread = auth_token.clone();
let identity_secret_thread = identity_secret.clone();
let join = thread::Builder::new()
- .name("clarion-http-read".to_owned())
+ .name("loomweave-http-read".to_owned())
.spawn(move || -> Result<()> {
let result = run_http_read_server(
project_root,
@@ -296,7 +296,7 @@ where
tracing::warn!(
bind = %local_addr,
auth = %auth,
- "Clarion HTTP read API listening on non-loopback interface without authentication"
+ "Loomweave HTTP read API listening on non-loopback interface without authentication"
);
}
if warn_unauthenticated_loopback {
@@ -308,7 +308,7 @@ where
identity_token_env or token_env for multi-tenant safety."
);
}
- tracing::info!(bind = %local_addr, auth = %auth, "Clarion HTTP read API listening");
+ tracing::info!(bind = %local_addr, auth = %auth, "Loomweave HTTP read API listening");
Ok(Some(HttpReadServer {
shutdown: Some(shutdown_tx),
failure_rx,
@@ -373,10 +373,10 @@ fn run_http_read_server(
// resolves instead of deadlocking. The `taint_writer_join` is held
// OUTSIDE the AppState so it survives to be awaited at shutdown.
let (taint_writer, taint_writer_join) = if wardline_taint_write {
- let (writer, join) = clarion_storage::Writer::spawn(
+ let (writer, join) = loomweave_storage::Writer::spawn(
db_path.clone(),
- clarion_storage::DEFAULT_BATCH_SIZE,
- clarion_storage::DEFAULT_CHANNEL_CAPACITY,
+ loomweave_storage::DEFAULT_BATCH_SIZE,
+ loomweave_storage::DEFAULT_CHANNEL_CAPACITY,
)
.map_err(|err| anyhow!("spawn taint writer-actor: {err}"))?;
(Some(writer.sender()), Some(join))
@@ -457,7 +457,7 @@ async fn panic_trigger_watcher() {
fn build_http_runtime() -> Result {
tokio::runtime::Builder::new_multi_thread()
- .thread_name("clarion-http-worker")
+ .thread_name("loomweave-http-worker")
.enable_all()
.build()
.context("create HTTP read runtime")
@@ -560,11 +560,11 @@ struct CapabilitiesResponse {
/// the `/api/v1/entities/{id}/callers|callees` routes ship.
linkages: LinkagesCapability,
/// Stable Entity Identity (Wave 1 / WS1, ADR-038). Consumers degrade against
- /// a pre-SEI Clarion by reading `sei.supported`.
+ /// a pre-SEI Loomweave by reading `sei.supported`.
sei: SeiCapability,
/// Wardline taint-store sub-capabilities (T3.4). `read_by_sei` advertises
/// the `POST /api/wardline/taint-facts/by-sei` route discretely: an older
- /// SEI-capable Clarion has `sei.supported: true` but lacks this route, so
+ /// SEI-capable Loomweave has `sei.supported: true` but lacks this route, so
/// consumers MUST gate the rename-stable taint read on this flag rather
/// than on `sei.supported`.
taint_store: TaintStoreCapability,
@@ -603,26 +603,26 @@ const WARDLINE_BODY_LIMIT_BYTES: usize = 4 * 1024 * 1024;
const SCRUBBED_REQUEST_LOG_HEADERS: &[&str] = &[
"authorization",
- "x-loom-component",
- "x-loom-timestamp",
- "x-loom-nonce",
+ "x-weft-component",
+ "x-weft-timestamp",
+ "x-weft-nonce",
];
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct RequestLogContext {
- loom_component: Option,
+ weft_component: Option,
filigree_actor: Option,
}
fn request_log_context(headers: &HeaderMap) -> RequestLogContext {
RequestLogContext {
- loom_component: log_loom_component_kind(headers),
+ weft_component: log_weft_component_kind(headers),
filigree_actor: log_non_sensitive_header_value(headers, "x-filigree-actor"),
}
}
-fn log_loom_component_kind(headers: &HeaderMap) -> Option {
- let value = headers.get("x-loom-component")?.to_str().ok()?.trim();
+fn log_weft_component_kind(headers: &HeaderMap) -> Option {
+ let value = headers.get("x-weft-component")?.to_str().ok()?.trim();
let component = value
.split_once(':')
.map_or(value, |(component, _)| component);
@@ -649,11 +649,11 @@ fn http_request_span(request: &Request) -> tracing::Span {
"http_read_request",
method = %request.method(),
path = %request.uri().path(),
- loom_component = tracing::field::Empty,
+ weft_component = tracing::field::Empty,
filigree_actor = tracing::field::Empty,
);
- if let Some(loom_component) = context.loom_component {
- span.record("loom_component", tracing::field::display(loom_component));
+ if let Some(weft_component) = context.weft_component {
+ span.record("weft_component", tracing::field::display(weft_component));
}
if let Some(filigree_actor) = context.filigree_actor {
span.record("filigree_actor", tracing::field::display(filigree_actor));
@@ -737,22 +737,22 @@ mod tests {
fn request_log_context_reads_optional_actor_headers() {
let mut headers = HeaderMap::new();
headers.insert(
- "X-Loom-Component",
- HeaderValue::from_static("clarion:deadbeefsignature"),
+ "X-Weft-Component",
+ HeaderValue::from_static("loomweave:deadbeefsignature"),
);
- headers.insert("X-Loom-Timestamp", HeaderValue::from_static("123456"));
- headers.insert("X-Loom-Nonce", HeaderValue::from_static("nonce-value"));
+ headers.insert("X-Weft-Timestamp", HeaderValue::from_static("123456"));
+ headers.insert("X-Weft-Nonce", HeaderValue::from_static("nonce-value"));
headers.insert("Authorization", HeaderValue::from_static("Bearer secret"));
headers.insert("X-Filigree-Actor", HeaderValue::from_static("worker-f"));
let context = request_log_context(&headers);
- assert_eq!(context.loom_component.as_deref(), Some("clarion"));
+ assert_eq!(context.weft_component.as_deref(), Some("loomweave"));
assert_eq!(context.filigree_actor.as_deref(), Some("worker-f"));
assert!(is_scrubbed_request_log_header("authorization"));
- assert!(is_scrubbed_request_log_header("x-loom-component"));
- assert!(is_scrubbed_request_log_header("x-loom-timestamp"));
- assert!(is_scrubbed_request_log_header("x-loom-nonce"));
+ assert!(is_scrubbed_request_log_header("x-weft-component"));
+ assert!(is_scrubbed_request_log_header("x-weft-timestamp"));
+ assert!(is_scrubbed_request_log_header("x-weft-nonce"));
assert_eq!(
log_non_sensitive_header_value(&headers, "authorization"),
None
@@ -768,7 +768,7 @@ mod tests {
.expect("worker task")
});
- assert_eq!(worker_name.as_deref(), Some("clarion-http-worker"));
+ assert_eq!(worker_name.as_deref(), Some("loomweave-http-worker"));
}
/// SEC-02: when the HTTP API binds to loopback and neither
@@ -778,8 +778,8 @@ mod tests {
/// "without authentication".
#[test]
fn spawn_emits_loopback_no_token_trust_warning() {
- use clarion_federation::config::HttpReadConfig;
- use clarion_storage::ReaderPool;
+ use loomweave_federation::config::HttpReadConfig;
+ use loomweave_storage::ReaderPool;
use std::io;
use std::net::{SocketAddr, TcpListener};
use tracing_subscriber::fmt::MakeWriter;
@@ -831,14 +831,14 @@ mod tests {
drop(probe);
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
let readers = ReaderPool::open(&db_path, 4).expect("open reader pool");
let config = HttpReadConfig {
enabled: true,
bind,
allow_non_loopback: false,
- token_env: "CLARION_LOOPBACK_NO_TOKEN_TEST_UNSET".to_owned(),
+ token_env: "LOOMWEAVE_LOOPBACK_NO_TOKEN_TEST_UNSET".to_owned(),
identity_token_env: None,
wardline_taint_write: false,
};
@@ -847,7 +847,7 @@ mod tests {
.expect("parse synthetic instance id");
// Env lookup that returns None for every variable — emulates
- // the operator running `clarion serve` on loopback with no
+ // the operator running `loomweave serve` on loopback with no
// tokens configured.
let env_lookup = |_: &str| -> Option { None };
@@ -889,8 +889,8 @@ mod tests {
/// spawn→drop→join sequence end to end.
#[test]
fn spawn_with_taint_writer_shuts_down_cleanly() {
- use clarion_federation::config::HttpReadConfig;
- use clarion_storage::ReaderPool;
+ use loomweave_federation::config::HttpReadConfig;
+ use loomweave_storage::ReaderPool;
use std::net::{SocketAddr, TcpListener};
let _guard = http_runtime_test_guard();
@@ -900,7 +900,7 @@ mod tests {
drop(probe);
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
// `Writer::spawn` creates the file and `verify_user_version` passes at
// version 0; a shutdown-only test sends no commands.
let readers = ReaderPool::open(&db_path, 4).expect("open reader pool");
@@ -942,8 +942,8 @@ mod tests {
/// absorb the panic (i.e. anything outside per-request middleware).
#[test]
fn check_running_surfaces_supervisor_signal_after_runtime_panic() {
- use clarion_federation::config::HttpReadConfig;
- use clarion_storage::ReaderPool;
+ use loomweave_federation::config::HttpReadConfig;
+ use loomweave_storage::ReaderPool;
use std::net::{SocketAddr, TcpListener};
let _guard = http_runtime_test_guard();
@@ -956,7 +956,7 @@ mod tests {
drop(probe);
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
// ReaderPool::open is lazy; no connection is acquired before the
// panic trigger fires, so the absent SQLite file is irrelevant.
let readers = ReaderPool::open(&db_path, 4).expect("open reader pool");
diff --git a/crates/clarion-cli/src/http_read/auth.rs b/crates/loomweave-cli/src/http_read/auth.rs
similarity index 97%
rename from crates/clarion-cli/src/http_read/auth.rs
rename to crates/loomweave-cli/src/http_read/auth.rs
index a3ce0f49..88227e61 100644
--- a/crates/clarion-cli/src/http_read/auth.rs
+++ b/crates/loomweave-cli/src/http_read/auth.rs
@@ -9,8 +9,8 @@ use axum::body::{Body, to_bytes};
use axum::extract::State;
use axum::http::{Request, StatusCode};
use axum::response::Response;
-use clarion_core::HttpErrorCode as ErrorCode;
use hmac::{Hmac, Mac};
+use loomweave_core::HttpErrorCode as ErrorCode;
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;
use time::OffsetDateTime;
@@ -65,7 +65,7 @@ impl HmacReplayCache {
}
}
-/// Enforce configured identity on protected routes. Prefer the Loom HMAC
+/// Enforce configured identity on protected routes. Prefer the Weft HMAC
/// identity when `identity_token_env` is configured; otherwise preserve the
/// legacy bearer-token path for existing deployments.
pub(crate) async fn require_http_identity(
@@ -134,9 +134,9 @@ pub(crate) async fn require_hmac_identity(
);
let presented = parts
.headers
- .get("x-loom-component")
+ .get("x-weft-component")
.and_then(|value| value.to_str().ok())
- .and_then(|value| value.trim().strip_prefix("clarion:"))
+ .and_then(|value| value.trim().strip_prefix("loomweave:"))
.filter(|signature| !signature.is_empty())
.map(str::to_owned);
let Some(presented) = presented else {
@@ -144,7 +144,7 @@ pub(crate) async fn require_hmac_identity(
};
let timestamp = parts
.headers
- .get("x-loom-timestamp")
+ .get("x-weft-timestamp")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.trim().parse::().ok());
let Some(timestamp) = timestamp else {
@@ -152,7 +152,7 @@ pub(crate) async fn require_hmac_identity(
};
let nonce = parts
.headers
- .get("x-loom-nonce")
+ .get("x-weft-nonce")
.and_then(|value| value.to_str().ok())
.map(str::trim)
.filter(|nonce| !nonce.is_empty() && nonce.len() <= HMAC_NONCE_MAX_LEN)
@@ -447,12 +447,12 @@ mod tests {
let request = Request::builder()
.method("POST")
.uri("/api/v1/files/batch")
- .header("X-Loom-Component", "clarion:deadbeef")
+ .header("X-Weft-Component", "loomweave:deadbeef")
.header(
- "X-Loom-Timestamp",
+ "X-Weft-Timestamp",
OffsetDateTime::now_utc().unix_timestamp().to_string(),
)
- .header("X-Loom-Nonce", "body-read-failure")
+ .header("X-Weft-Nonce", "body-read-failure")
.body(Body::from(oversize))
.expect("request");
diff --git a/crates/clarion-cli/src/http_read/errors.rs b/crates/loomweave-cli/src/http_read/errors.rs
similarity index 97%
rename from crates/clarion-cli/src/http_read/errors.rs
rename to crates/loomweave-cli/src/http_read/errors.rs
index 2b0b2ca7..e7ed3a7a 100644
--- a/crates/clarion-cli/src/http_read/errors.rs
+++ b/crates/loomweave-cli/src/http_read/errors.rs
@@ -6,13 +6,13 @@ use std::error::Error as StdError;
use axum::http::StatusCode;
use axum::response::Response;
-use clarion_core::HttpErrorCode as ErrorCode;
-use clarion_storage::StorageError;
+use loomweave_core::HttpErrorCode as ErrorCode;
+use loomweave_storage::StorageError;
use super::{HTTP_ERROR_DISPATCH, json_error};
/// ISO-8601 UTC "now" with millisecond precision (`YYYY-MM-DDTHH:MM:SS.sssZ`),
-/// matching the caller-side timestamps `clarion analyze` stamps onto run rows.
+/// matching the caller-side timestamps `loomweave analyze` stamps onto run rows.
pub(crate) fn iso8601_now() -> String {
use time::macros::format_description;
const ISO8601_MILLIS_UTC: &[time::format_description::FormatItem<'_>] =
@@ -48,7 +48,7 @@ pub(crate) fn classify_read_error(err: &StorageError) -> ReadError {
code: ErrorCode::PathOutsideProject,
message: "path is outside project root",
},
- // A stored row that failed an integrity check is Clarion's fault, not
+ // A stored row that failed an integrity check is Loomweave's fault, not
// the client's: 500 + logged (via `json_read_error`), never a 4xx that
// blames the caller's request. A federation client routing on `code`
// must see STORAGE_ERROR, not INVALID_PATH.
@@ -71,7 +71,7 @@ pub(crate) fn classify_read_error(err: &StorageError) -> ReadError {
code: ErrorCode::StorageError,
message: "file lookup failed",
},
- // STO-02 (ADR-035): the on-disk file is not a Clarion database, or
+ // STO-02 (ADR-035): the on-disk file is not a Loomweave database, or
// was written by a newer build. Either condition is fatal for the
// server; the writer-actor refuses to spawn against it. Surfacing
// 500 here is defensive — in practice the HTTP API does not open
diff --git a/crates/clarion-cli/src/http_read/files.rs b/crates/loomweave-cli/src/http_read/files.rs
similarity index 97%
rename from crates/clarion-cli/src/http_read/files.rs
rename to crates/loomweave-cli/src/http_read/files.rs
index 5e7afa5a..1ef548bb 100644
--- a/crates/clarion-cli/src/http_read/files.rs
+++ b/crates/loomweave-cli/src/http_read/files.rs
@@ -8,8 +8,8 @@ use axum::extract::rejection::QueryRejection;
use axum::extract::{Query, State};
use axum::http::{HeaderMap, HeaderValue, StatusCode, header};
use axum::response::{IntoResponse, Response};
-use clarion_core::HttpErrorCode as ErrorCode;
-use clarion_storage::{CanonicalProjectPath, StorageError, resolve_file_catalog_entry};
+use loomweave_core::HttpErrorCode as ErrorCode;
+use loomweave_storage::{CanonicalProjectPath, StorageError, resolve_file_catalog_entry};
use serde::{Deserialize, Serialize};
use super::errors::{classify_read_error, json_read_error, log_briefing_blocked_refusal};
@@ -186,7 +186,7 @@ pub(crate) async fn get_file(
Ok(None) => json_error(
StatusCode::NOT_FOUND,
ErrorCode::NotFound,
- "file is not known to Clarion",
+ "file is not known to Loomweave",
),
Err(err) => json_read_error(&err),
}
@@ -218,7 +218,7 @@ pub(crate) fn insert_etag(response: &mut Response, etag: &str) {
/// single request, partitioning results into four lists:
///
/// - `resolved` — paths that mapped to a file-kind entity.
-/// - `not_found` — paths Clarion does not have a catalog row for.
+/// - `not_found` — paths Loomweave does not have a catalog row for.
/// - `briefing_blocked` — paths whose entity carries a `briefing_blocked`
/// property (the partition equivalent of the single-file 403 surface).
/// - `errors` — per-path resolution errors (`INVALID_PATH`,
@@ -226,7 +226,7 @@ pub(crate) fn insert_etag(response: &mut Response, etag: &str) {
///
/// The whole batch runs inside **one** `with_reader` closure so we
/// check out one pooled connection per request, not one per query —
-/// this is the perf win Filigree's `ClarionRegistry` needs for cold-
+/// this is the perf win Filigree's `LoomweaveRegistry` needs for cold-
/// start hydration. `ETag` is intentionally not applied to the batch
/// surface; clients should `ETag` the single-file endpoint when they
/// want conditional fetch semantics.
@@ -347,7 +347,7 @@ pub(crate) async fn post_files_resolve(
// ── Call-graph linkages (Wave 0 / WS2) ──────────────────────────────────────
//
-// Thin HTTP wrappers over `clarion_storage::call_edges_targeting` (callers) and
+// Thin HTTP wrappers over `loomweave_storage::call_edges_targeting` (callers) and
// `call_edges_from` (callees). Aggregated per neighbour entity: `call_site_count`
// is the number of call sites (across all returned confidence tiers) and
// `confidence` is the STRONGEST tier present (resolved > ambiguous > inferred) —
@@ -403,7 +403,7 @@ pub(crate) fn resolve_file_query_item(
Ok(None) => resolve_error_response(
ResolveFileStatus::NotFound,
ErrorCode::NotFound,
- "file is not known to Clarion",
+ "file is not known to Loomweave",
),
Err(err) => resolve_read_error_response(&err),
}
diff --git a/crates/clarion-cli/src/http_read/identity.rs b/crates/loomweave-cli/src/http_read/identity.rs
similarity index 97%
rename from crates/clarion-cli/src/http_read/identity.rs
rename to crates/loomweave-cli/src/http_read/identity.rs
index 843b17c3..cd75a498 100644
--- a/crates/clarion-cli/src/http_read/identity.rs
+++ b/crates/loomweave-cli/src/http_read/identity.rs
@@ -7,8 +7,8 @@ use axum::Json;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
-use clarion_core::HttpErrorCode as ErrorCode;
-use clarion_storage::{
+use loomweave_core::HttpErrorCode as ErrorCode;
+use loomweave_storage::{
SeiLookupResult, StorageError, is_reserved_sei, resolve_locator, resolve_sei, sei_lineage,
};
use serde::{Deserialize, Serialize};
@@ -45,7 +45,7 @@ pub(crate) struct SeiLineageEventBody {
/// empty segment. Returns the documented client message on rejection.
pub(crate) fn validate_locator(locator: &str) -> Result<(), &'static str> {
if is_reserved_sei(locator) {
- return Err("not a valid locator: input is an SEI (reserved clarion:eid: prefix)");
+ return Err("not a valid locator: input is an SEI (reserved loomweave:eid: prefix)");
}
let segments: Vec<&str> = locator.splitn(3, ':').collect();
if segments.len() != 3 || segments.iter().any(|s| s.is_empty()) {
@@ -55,7 +55,7 @@ pub(crate) fn validate_locator(locator: &str) -> Result<(), &'static str> {
}
pub(crate) fn lineage_rows_to_body(
- rows: Vec,
+ rows: Vec,
) -> Vec {
rows.into_iter()
.map(|r| SeiLineageEventBody {
@@ -231,7 +231,7 @@ mod tests {
fn validate_locator_rejects_reserved_sei_prefix() {
// A real SEI has the same colon count as a locator — only the reserved
// prefix distinguishes it, which is exactly what the rejection keys on.
- let err = validate_locator("clarion:eid:0123456789abcdef0123456789abcdef")
+ let err = validate_locator("loomweave:eid:0123456789abcdef0123456789abcdef")
.expect_err("an SEI-shaped input must be rejected");
assert!(err.contains("not a valid locator"), "message: {err}");
}
diff --git a/crates/clarion-cli/src/http_read/linkages.rs b/crates/loomweave-cli/src/http_read/linkages.rs
similarity index 98%
rename from crates/clarion-cli/src/http_read/linkages.rs
rename to crates/loomweave-cli/src/http_read/linkages.rs
index 52b7be96..9d8fbdc9 100644
--- a/crates/clarion-cli/src/http_read/linkages.rs
+++ b/crates/loomweave-cli/src/http_read/linkages.rs
@@ -8,9 +8,9 @@ use axum::extract::rejection::QueryRejection;
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
-use clarion_core::EdgeConfidence;
-use clarion_core::HttpErrorCode as ErrorCode;
-use clarion_storage::{
+use loomweave_core::EdgeConfidence;
+use loomweave_core::HttpErrorCode as ErrorCode;
+use loomweave_storage::{
CallEdgeMatch, EntityVisibility, StorageError, call_edges_from, call_edges_targeting,
entity_visibility,
};
@@ -217,7 +217,7 @@ pub(crate) async fn linkage_single(
Ok(LinkageLookup::NotFound) => json_error(
StatusCode::NOT_FOUND,
ErrorCode::NotFound,
- "entity is not known to Clarion",
+ "entity is not known to Loomweave",
),
Ok(LinkageLookup::Blocked) => json_error(
StatusCode::FORBIDDEN,
@@ -355,7 +355,7 @@ pub(crate) async fn post_callees_batch(
// (the source of truth); `entities` is joined only for `content_hash`.
//
// REQ-F-02 (fail-closed): `resolve(locator)` MUST reject an SEI-shaped input
-// (reserved `clarion:eid:` prefix) — never silently mis-resolve. A colon-count
+// (reserved `loomweave:eid:` prefix) — never silently mis-resolve. A colon-count
// check is insufficient (an SEI carries the same two colons a locator does), so
// the rejection keys on the reserved prefix. This is what makes the idempotent,
// resumable cross-tool backfill safe (an already-migrated SEI is rejected).
@@ -429,11 +429,11 @@ mod tests {
entities: &[(&str, Option<&str>)],
calls: &[(&str, &str, &str, &[&str])],
) -> (AppState, tempfile::TempDir) {
- use clarion_storage::ReaderPool;
- use clarion_storage::schema::apply_migrations;
+ use loomweave_storage::ReaderPool;
+ use loomweave_storage::schema::apply_migrations;
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
let mut conn = rusqlite::Connection::open(&db_path).expect("open db");
apply_migrations(&mut conn).expect("apply migrations");
for (id, blocked) in entities {
@@ -730,7 +730,7 @@ mod tests {
use tower::ServiceExt;
let secret = "linkage-secret";
let (state, _tempdir) = linkage_test_state(secret, &[("python:function:t", None)], &[]);
- // No X-Loom-Component header → 401 (route is HMAC-gated like /api/v1/files).
+ // No X-Weft-Component header → 401 (route is HMAC-gated like /api/v1/files).
let request = axum::http::Request::builder()
.method("GET")
.uri("/api/v1/entities/python:function:t/callers")
@@ -866,7 +866,7 @@ mod tests {
#[tokio::test]
async fn capabilities_reports_taint_store_read_by_sei_true() {
use tower::ServiceExt;
- // Discrete from `sei.supported`: an older SEI-capable Clarion would set
+ // Discrete from `sei.supported`: an older SEI-capable Loomweave would set
// `sei.supported: true` yet lack this route, so consumers gate the
// rename-stable taint read on this flag specifically.
let (state, _tempdir) = linkage_test_state("linkage-secret", &[], &[]);
diff --git a/crates/clarion-cli/src/http_read/test_support.rs b/crates/loomweave-cli/src/http_read/test_support.rs
similarity index 87%
rename from crates/clarion-cli/src/http_read/test_support.rs
rename to crates/loomweave-cli/src/http_read/test_support.rs
index c3dc6022..5c2e3816 100644
--- a/crates/clarion-cli/src/http_read/test_support.rs
+++ b/crates/loomweave-cli/src/http_read/test_support.rs
@@ -26,9 +26,9 @@ pub(crate) fn hmac_request(
axum::http::Request::builder()
.method(method)
.uri(path_and_query)
- .header("X-Loom-Component", format!("clarion:{signature}"))
- .header("X-Loom-Timestamp", timestamp.to_string())
- .header("X-Loom-Nonce", nonce)
+ .header("X-Weft-Component", format!("loomweave:{signature}"))
+ .header("X-Weft-Timestamp", timestamp.to_string())
+ .header("X-Weft-Nonce", nonce)
.header(header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::from(body.to_vec()))
.expect("build request")
diff --git a/crates/clarion-cli/src/http_read/wardline.rs b/crates/loomweave-cli/src/http_read/wardline.rs
similarity index 95%
rename from crates/clarion-cli/src/http_read/wardline.rs
rename to crates/loomweave-cli/src/http_read/wardline.rs
index 90ccd09e..5019db8d 100644
--- a/crates/clarion-cli/src/http_read/wardline.rs
+++ b/crates/loomweave-cli/src/http_read/wardline.rs
@@ -8,8 +8,8 @@ use axum::extract::rejection::QueryRejection;
use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
-use clarion_core::HttpErrorCode as ErrorCode;
-use clarion_storage::StorageError;
+use loomweave_core::HttpErrorCode as ErrorCode;
+use loomweave_storage::StorageError;
use serde::{Deserialize, Serialize};
use tokio::sync::oneshot;
@@ -102,7 +102,7 @@ pub(crate) struct BatchGetRequest {
pub(crate) struct BatchGetBySeiRequest {
#[serde(default)]
project: String,
- /// Opaque SEIs (`clarion:eid:`). Treated verbatim — NO locator-shape
+ /// Opaque SEIs (`loomweave:eid:`). Treated verbatim — NO locator-shape
/// validation (SEI-shaped strings are the valid input here, the inverse of
/// the `resolve` REQ-F-02 rejection).
seis: Vec,
@@ -123,8 +123,8 @@ pub(crate) struct TaintFactBySeiView {
/// Exact-tier Wardline qualname resolve (ADR-036, W.4). Takes a batch of
/// PRE-COMPOSED dotted qualnames that Wardline has already shaped to
-/// byte-match Clarion's `canonical_qualified_name`; resolution is the direct
-/// existence lookup in `clarion_storage::resolve_wardline_qualnames`. No
+/// byte-match Loomweave's `canonical_qualified_name`; resolution is the direct
+/// existence lookup in `loomweave_storage::resolve_wardline_qualnames`. No
/// `&file=` disambiguator, no normalization — the generic resolve oracle
/// remains deferred.
pub(crate) async fn post_wardline_resolve(
@@ -156,7 +156,7 @@ pub(crate) async fn post_wardline_resolve(
let qualnames = req.qualnames;
let result = state
.readers
- .with_reader(move |conn| clarion_storage::resolve_wardline_qualnames(conn, &qualnames))
+ .with_reader(move |conn| loomweave_storage::resolve_wardline_qualnames(conn, &qualnames))
.await;
match result {
Ok(pairs) => {
@@ -233,13 +233,13 @@ pub(crate) async fn post_wardline_taint_facts(
let resolution = state
.readers
.with_reader(move |conn| {
- let resolved = clarion_storage::resolve_wardline_qualnames(conn, &qualnames)?;
+ let resolved = loomweave_storage::resolve_wardline_qualnames(conn, &qualnames)?;
let locators: Vec = resolved
.iter()
.filter_map(|(_, r)| r.entity_id().map(str::to_owned))
.collect();
- let seis = clarion_storage::seis_for_locators(conn, &locators)?;
- Ok::<_, clarion_storage::StorageError>((resolved, seis))
+ let seis = loomweave_storage::seis_for_locators(conn, &locators)?;
+ Ok::<_, loomweave_storage::StorageError>((resolved, seis))
})
.await;
let (resolved, seis_by_locator) = match resolution {
@@ -271,7 +271,7 @@ pub(crate) async fn post_wardline_taint_facts(
);
}
let sei = fact.sei.clone().or(resolved_sei);
- let taint_fact = clarion_storage::TaintFact {
+ let taint_fact = loomweave_storage::TaintFact {
entity_id,
// Opaque + byte-verbatim: `RawValue::get()` returns the original
// bytes of the blob exactly as the client sent them (no key
@@ -284,7 +284,7 @@ pub(crate) async fn post_wardline_taint_facts(
sei,
};
let (ack_tx, ack_rx) = oneshot::channel();
- let cmd = clarion_storage::WriterCmd::UpsertWardlineTaintFact {
+ let cmd = loomweave_storage::WriterCmd::UpsertWardlineTaintFact {
fact: Box::new(taint_fact),
ack: ack_tx,
};
@@ -335,7 +335,7 @@ pub(crate) async fn post_wardline_taint_facts(
/// - for rows that exist, parse the stored blob byte-faithfully via
/// `RawValue::from_string` (W.2 wrote it from a `RawValue`, so it
/// round-trips) and derive `current_content_hash` live from the row's
-/// `source_file_path` via `clarion_storage::current_file_hash`.
+/// `source_file_path` via `loomweave_storage::current_file_hash`.
///
/// File hashing is DEDUPED per request by `source_file_path`: a chain-walk
/// batch hits many functions sharing one file, and a 425k-LOC project must
@@ -353,18 +353,18 @@ pub(crate) async fn respond_taint_facts(
.readers
.with_reader(move |conn| {
// 1. Resolve every qualname (exact tier), in input order.
- let resolved = clarion_storage::resolve_wardline_qualnames(conn, &qualnames)?;
+ let resolved = loomweave_storage::resolve_wardline_qualnames(conn, &qualnames)?;
// 2. Fetch facts for the resolved entity ids; map back by id.
let entity_ids: Vec = resolved
.iter()
.filter_map(|(_, r)| r.entity_id().map(str::to_owned))
.collect();
- let rows = clarion_storage::get_taint_facts(conn, &entity_ids)?;
- let by_entity: std::collections::HashMap = rows
- .into_iter()
- .map(|row| (row.entity_id.clone(), row))
- .collect();
+ let rows = loomweave_storage::get_taint_facts(conn, &entity_ids)?;
+ let by_entity: std::collections::HashMap =
+ rows.into_iter()
+ .map(|row| (row.entity_id.clone(), row))
+ .collect();
// 3. Build a view per qualname, deduping file hashing by path.
let mut file_hash_cache: std::collections::HashMap> =
@@ -391,7 +391,7 @@ pub(crate) async fn respond_taint_facts(
Some(path) => file_hash_cache
.entry(path.clone())
.or_insert_with(|| {
- clarion_storage::current_file_hash(&project_root, path)
+ loomweave_storage::current_file_hash(&project_root, path)
})
.clone(),
None => None,
@@ -507,8 +507,8 @@ pub(crate) async fn respond_taint_facts_by_sei(
.readers
.with_reader(move |conn| {
// Most-recent fact per SEI (rename-stable lookup).
- let rows = clarion_storage::get_taint_facts_by_sei(conn, &seis)?;
- let by_sei: std::collections::HashMap = rows
+ let rows = loomweave_storage::get_taint_facts_by_sei(conn, &seis)?;
+ let by_sei: std::collections::HashMap = rows
.into_iter()
.filter_map(|row| row.sei.clone().map(|sei| (sei, row)))
.collect();
@@ -536,7 +536,7 @@ pub(crate) async fn respond_taint_facts_by_sei(
Some(path) => file_hash_cache
.entry(path.clone())
.or_insert_with(|| {
- clarion_storage::current_file_hash(&project_root, path)
+ loomweave_storage::current_file_hash(&project_root, path)
})
.clone(),
None => None,
@@ -627,11 +627,11 @@ mod tests {
secret: &str,
seed_ids: &[&str],
) -> (AppState, tempfile::TempDir) {
- use clarion_storage::ReaderPool;
- use clarion_storage::schema::apply_migrations;
+ use loomweave_storage::ReaderPool;
+ use loomweave_storage::schema::apply_migrations;
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
let mut conn = rusqlite::Connection::open(&db_path).expect("open db");
apply_migrations(&mut conn).expect("apply migrations");
for id in seed_ids {
@@ -732,7 +732,7 @@ mod tests {
) -> (
AppState,
std::path::PathBuf,
- clarion_storage::Writer,
+ loomweave_storage::Writer,
tempfile::TempDir,
) {
wardline_write_test_state_with_bindings(secret, seed_ids, &[])
@@ -745,14 +745,14 @@ mod tests {
) -> (
AppState,
std::path::PathBuf,
- clarion_storage::Writer,
+ loomweave_storage::Writer,
tempfile::TempDir,
) {
- use clarion_storage::ReaderPool;
- use clarion_storage::schema::apply_migrations;
+ use loomweave_storage::ReaderPool;
+ use loomweave_storage::schema::apply_migrations;
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
let mut conn = rusqlite::Connection::open(&db_path).expect("open db");
apply_migrations(&mut conn).expect("apply migrations");
for id in seed_ids {
@@ -788,10 +788,10 @@ mod tests {
drop(conn);
let readers = ReaderPool::open(&db_path, 4).expect("open reader pool");
- let (writer, _join) = clarion_storage::Writer::spawn(
+ let (writer, _join) = loomweave_storage::Writer::spawn(
db_path.clone(),
- clarion_storage::DEFAULT_BATCH_SIZE,
- clarion_storage::DEFAULT_CHANNEL_CAPACITY,
+ loomweave_storage::DEFAULT_BATCH_SIZE,
+ loomweave_storage::DEFAULT_CHANNEL_CAPACITY,
)
.expect("spawn taint writer-actor");
// The join handle is dropped here: the test reads the DB on a fresh
@@ -887,11 +887,11 @@ mod tests {
let secret = "wardline-write-secret";
let locator = "python:function:a.b.c";
- let resolved_sei = "clarion:eid:resolved";
+ let resolved_sei = "loomweave:eid:resolved";
let (state, db_path, writer, _tempdir) =
wardline_write_test_state_with_bindings(secret, &[locator], &[(resolved_sei, locator)]);
- let body = br#"{"facts":[{"qualname":"a.b.c","sei":"clarion:eid:other","wardline_json":{"v":1}}]}"#;
+ let body = br#"{"facts":[{"qualname":"a.b.c","sei":"loomweave:eid:other","wardline_json":{"v":1}}]}"#;
let request = hmac_request(secret, "POST", "/api/wardline/taint-facts", body);
let response = router(state).oneshot(request).await.expect("oneshot");
@@ -912,11 +912,11 @@ mod tests {
let secret = "wardline-write-secret";
let locator = "python:function:a.b.c";
- let resolved_sei = "clarion:eid:resolved";
+ let resolved_sei = "loomweave:eid:resolved";
let (state, db_path, writer, _tempdir) =
wardline_write_test_state_with_bindings(secret, &[locator], &[(resolved_sei, locator)]);
- let body = br#"{"facts":[{"qualname":"a.b.c","sei":"clarion:eid:resolved","wardline_json":{"v":1}}]}"#;
+ let body = br#"{"facts":[{"qualname":"a.b.c","sei":"loomweave:eid:resolved","wardline_json":{"v":1}}]}"#;
let request = hmac_request(secret, "POST", "/api/wardline/taint-facts", body);
let response = router(state).oneshot(request).await.expect("oneshot");
@@ -1040,7 +1040,7 @@ mod tests {
let request = axum::http::Request::builder()
.method("POST")
.uri("/api/wardline/taint-facts")
- .header("X-Loom-Component", "clarion:deadbeefdeadbeef")
+ .header("X-Weft-Component", "loomweave:deadbeefdeadbeef")
.header(header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::from(body.to_vec()))
.expect("build request");
@@ -1054,7 +1054,7 @@ mod tests {
let parsed: serde_json::Value = serde_json::from_slice(&bytes).expect("json");
assert_eq!(parsed["code"], "UNAUTHENTICATED");
- // (3) Absent X-Loom-Component header → 401. This is the case that
+ // (3) Absent X-Weft-Component header → 401. This is the case that
// catches a regression dropping the route_layer: with no guard, this
// request would reach the handler and 403/200, not 401.
let (state, _td3) = wardline_resolve_test_state(secret, &[]);
@@ -1239,11 +1239,11 @@ mod tests {
/// is stored only when `blob` is `Some`. Returns the state and the
/// `TempDir` guard (drop it last).
fn wardline_read_test_state(secret: &str, seeds: &[SeedFn]) -> (AppState, tempfile::TempDir) {
- use clarion_storage::ReaderPool;
- use clarion_storage::schema::apply_migrations;
+ use loomweave_storage::ReaderPool;
+ use loomweave_storage::schema::apply_migrations;
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
let mut conn = rusqlite::Connection::open(&db_path).expect("open db");
apply_migrations(&mut conn).expect("apply migrations");
@@ -1386,9 +1386,9 @@ mod tests {
/// malformed client request. The validated write path (`RawValue` round-trip)
/// cannot produce this — only storage corruption or an out-of-band write
/// can — so the test injects it directly via the seed builder's verbatim
- /// blob. The read must return 500 `STORAGE_ERROR` (Clarion's fault, and 5xx
+ /// blob. The read must return 500 `STORAGE_ERROR` (Loomweave's fault, and 5xx
/// so `json_read_error` logs it), NOT 400 `INVALID_PATH` (which would blame
- /// the federation client's request for Clarion's storage damage).
+ /// the federation client's request for Loomweave's storage damage).
#[tokio::test]
async fn wardline_taint_get_corrupt_blob_is_500_storage_error_not_400() {
use tower::ServiceExt;
@@ -1412,7 +1412,7 @@ mod tests {
assert_eq!(
response.status(),
StatusCode::INTERNAL_SERVER_ERROR,
- "a corrupt stored blob is Clarion's fault → 500, never a client 400"
+ "a corrupt stored blob is Loomweave's fault → 500, never a client 400"
);
let bytes = to_bytes(response.into_body(), 4096).await.expect("body");
let parsed: serde_json::Value = serde_json::from_slice(&bytes).expect("json");
@@ -1538,15 +1538,15 @@ mod tests {
#[tokio::test]
async fn wardline_taint_batch_get_shared_file_yields_same_hash() {
- use clarion_storage::ReaderPool;
- use clarion_storage::schema::apply_migrations;
+ use loomweave_storage::ReaderPool;
+ use loomweave_storage::schema::apply_migrations;
use tower::ServiceExt;
let secret = "wardline-read-secret";
// Build state by hand so two entities share ONE file (exercises the
// per-request file-hash dedup; both must report the same hash).
let tempdir = tempfile::tempdir().expect("temp project root");
- let db_path = tempdir.path().join("clarion.db");
+ let db_path = tempdir.path().join("loomweave.db");
let mut conn = rusqlite::Connection::open(&db_path).expect("open db");
apply_migrations(&mut conn).expect("migrations");
let shared = tempdir.path().join("shared.py");
@@ -1715,7 +1715,7 @@ mod tests {
let secret = "wardline-write-secret";
let old = "python:function:old.pkg.fn";
let new = "python:function:new.pkg.fn";
- let sei = "clarion:eid:rename-stable";
+ let sei = "loomweave:eid:rename-stable";
// Both pre- and post-rename entity rows exist (entities is cumulative).
// Alive SEI binding at the OLD locator, as it stands at write time.
let (state, db_path, _writer, _tempdir) =
@@ -1843,7 +1843,7 @@ mod tests {
let secret = "wardline-write-secret";
let (state, _db_path, _writer, _tempdir) =
wardline_write_test_state(secret, &["python:function:a.b.c"]);
- let body = serde_json::json!({ "seis": ["clarion:eid:nope"] }).to_string();
+ let body = serde_json::json!({ "seis": ["loomweave:eid:nope"] }).to_string();
let request = hmac_request(
secret,
"POST",
@@ -1855,7 +1855,7 @@ mod tests {
let parsed = json_body(response).await;
let arr = parsed.as_array().expect("array");
assert_eq!(arr.len(), 1);
- assert_eq!(arr[0]["sei"], "clarion:eid:nope");
+ assert_eq!(arr[0]["sei"], "loomweave:eid:nope");
assert_eq!(arr[0]["exists"], false);
assert!(arr[0].get("wardline_json").is_none());
}
@@ -1873,7 +1873,7 @@ mod tests {
.method("POST")
.uri("/api/wardline/taint-facts/by-sei")
.header("content-type", "application/json")
- .body(axum::body::Body::from(r#"{"seis":["clarion:eid:x"]}"#))
+ .body(axum::body::Body::from(r#"{"seis":["loomweave:eid:x"]}"#))
.expect("build request");
let response = router(state).oneshot(request).await.expect("oneshot");
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
diff --git a/crates/clarion-cli/src/install.rs b/crates/loomweave-cli/src/install.rs
similarity index 75%
rename from crates/clarion-cli/src/install.rs
rename to crates/loomweave-cli/src/install.rs
index ecda594f..7a1ff576 100644
--- a/crates/clarion-cli/src/install.rs
+++ b/crates/loomweave-cli/src/install.rs
@@ -1,16 +1,16 @@
-//! `clarion install` — initialise .clarion/ in the target directory.
+//! `loomweave install` — initialise .loomweave/ in the target directory.
//!
//! Creates:
-//! - `.clarion/clarion.db` (migrated)
-//! - `.clarion/config.json` (internal state stub)
-//! - `.clarion/.gitignore` (UQ-WP1-04 rules; ADR-005)
-//! - `/clarion.yaml` (user-edited config stub at project root;
+//! - `.loomweave/loomweave.db` (migrated)
+//! - `.loomweave/config.json` (internal state stub)
+//! - `.loomweave/.gitignore` (UQ-WP1-04 rules; ADR-005)
+//! - `/loomweave.yaml` (user-edited config stub at project root;
//! see detailed-design.md §File layout)
//!
-//! A bare `clarion install` (no flags) does everything: init + MCP config +
-//! skills + hooks + local Loom integration bindings. If `.clarion/` already
+//! A bare `loomweave install` (no flags) does everything: init + MCP config +
+//! skills + hooks + local Weft integration bindings. If `.loomweave/` already
//! exists, init is skipped and the idempotent components are still applied.
-//! Pass `--force` to wipe and reinitialise `.clarion/`. Component flags and
+//! Pass `--force` to wipe and reinitialise `.loomweave/`. Component flags and
//! `--all` are still accepted for explicit partial installs.
use std::fs;
@@ -19,7 +19,7 @@ use std::path::Path;
use anyhow::{Context, Result, bail};
use rusqlite::Connection;
-use clarion_storage::{pragma, schema};
+use loomweave_storage::{pragma, schema};
const CONFIG_JSON_STUB: &str = r#"{
"schema_version": 1,
@@ -30,8 +30,8 @@ const CONFIG_JSON_STUB: &str = r#"{
// NOTE: Do not use `\` line-continuation here — Rust strips both the newline
// AND all leading whitespace on the continuation line, producing flat (and
// therefore broken) YAML. Use raw newlines + explicit indentation.
-const CLARION_YAML_STUB: &str = "# clarion.yaml — user-edited config.
-# Do not delete this file: clarion serve reads MCP, LLM, and integration
+const LOOMWEAVE_YAML_STUB: &str = "# loomweave.yaml — user-edited config.
+# Do not delete this file: loomweave serve reads MCP, LLM, and integration
# settings from here when present.
version: 1
llm_policy:
@@ -42,8 +42,8 @@ llm_policy:
endpoint_url: https://openrouter.ai/api/v1
api_key_env: OPENROUTER_API_KEY
attribution:
- referer: https://github.com/tachyon-beep/clarion
- title: Clarion
+ referer: https://github.com/foundryside-dev/loomweave
+ title: Loomweave
codex_cli:
executable: codex
model: null
@@ -67,7 +67,7 @@ integrations:
filigree:
enabled: false
base_url: http://127.0.0.1:8766
- actor: clarion-mcp
+ actor: loomweave-mcp
token_env: FILIGREE_API_TOKEN
timeout_seconds: 5
serve:
@@ -79,8 +79,8 @@ serve:
";
const GITIGNORE_CONTENTS: &str = "\
-# Clarion .gitignore — ADR-005 tracked-vs-excluded list.
-# Tracked (committed): clarion.db, config.json, .gitignore itself.
+# Loomweave .gitignore — ADR-005 tracked-vs-excluded list.
+# Tracked (committed): loomweave.db, config.json, .gitignore itself.
# Excluded (ignored): WAL sidecars, shadow DB, per-run logs, tmp scratch.
# SQLite write-ahead files never belong in the repo.
@@ -94,7 +94,7 @@ const GITIGNORE_CONTENTS: &str = "\
*.db.new
# Semantic-search embeddings sidecar (ADR-040): large + rebuildable, never
-# committed (keeps clarion.db unbloated). WAL files are covered by *.db-wal/-shm.
+# committed (keeps loomweave.db unbloated). WAL files are covered by *.db-wal/-shm.
embeddings.db
# Scratch / temp space.
@@ -107,7 +107,7 @@ logs/
runs/*/log.jsonl
";
-/// A single component selected by a partial `clarion install`.
+/// A single component selected by a partial `loomweave install`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstallComponent {
ClaudeCode,
@@ -117,21 +117,21 @@ pub enum InstallComponent {
Hooks,
}
-/// What `clarion install` should do, resolved from the CLI flags.
+/// What `loomweave install` should do, resolved from the CLI flags.
///
/// Modeled as an enum rather than three independent bools so the derived and
-/// illegal states the bool form allowed are unrepresentable: `init_clarion` is
+/// illegal states the bool form allowed are unrepresentable: `init_loomweave` is
/// no longer a peer field that can contradict an explicit component request,
/// and the do-nothing `{false, false, false}` state (which PR #21 had to guard
/// against at the `run()` entry) cannot be produced by
/// [`InstallPlan::from_components`]
/// at all (clarion-c6b8dc27f3). Component booleans are derived on demand via
-/// [`init_clarion`](Self::init_clarion) / [`skills`](Self::skills) /
+/// [`init_loomweave`](Self::init_loomweave) / [`skills`](Self::skills) /
/// [`hooks`](Self::hooks).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstallPlan {
/// Component flags without `--all`: apply the named components and do NOT
- /// initialise `.clarion/`. `from_components` only constructs this when at
+ /// initialise `.loomweave/`. `from_components` only constructs this when at
/// least one component is present.
Components {
claude_code: bool,
@@ -140,14 +140,14 @@ pub enum InstallPlan {
codex_skills: bool,
hooks: bool,
},
- /// No flags or `--all`: initialise `.clarion/` + every integration.
+ /// No flags or `--all`: initialise `.loomweave/` + every integration.
All,
}
impl InstallPlan {
/// Resolve the CLI flags into a plan. `--all` wins; otherwise any of
/// the component flags selects [`Components`](Self::Components); no flag
- /// selects [`All`](Self::All) so that a naked `clarion install` does
+ /// selects [`All`](Self::All) so that a naked `loomweave install` does
/// everything. Never yields a do-nothing plan.
#[must_use]
pub fn from_components(all: bool, components: &[InstallComponent]) -> Self {
@@ -164,9 +164,9 @@ impl InstallPlan {
}
}
- /// Whether to initialise `.clarion/` (the index). True for `All`.
+ /// Whether to initialise `.loomweave/` (the index). True for `All`.
#[must_use]
- pub fn init_clarion(self) -> bool {
+ pub fn init_loomweave(self) -> bool {
matches!(self, Self::All)
}
@@ -189,13 +189,13 @@ impl InstallPlan {
matches!(self, Self::All | Self::Components { codex: true, .. })
}
- /// Whether to install the `clarion-workflow` skill pack for Claude Code.
+ /// Whether to install the `loomweave-workflow` skill pack for Claude Code.
#[must_use]
pub fn skills(self) -> bool {
matches!(self, Self::All | Self::Components { skills: true, .. })
}
- /// Whether to install the `clarion-workflow` skill pack for Codex.
+ /// Whether to install the `loomweave-workflow` skill pack for Codex.
#[must_use]
pub fn codex_skills(self) -> bool {
matches!(
@@ -219,7 +219,7 @@ impl InstallPlan {
///
/// # Errors
///
-/// Returns an error if `.clarion/` already exists without `--force`, if the
+/// Returns an error if `.loomweave/` already exists without `--force`, if the
/// target directory cannot be canonicalised, or if any filesystem or database
/// operation fails.
pub fn run(
@@ -243,7 +243,7 @@ pub fn run(
// defensive guard rather than silently succeeding.
validate_plan(plan)?;
- if plan.init_clarion() {
+ if plan.init_loomweave() {
initialise_project(&project_root, force)?;
}
@@ -278,7 +278,7 @@ fn validate_plan(plan: InstallPlan) -> Result<()> {
// `from_components` cannot produce a do-nothing plan, but a hand-built
// `Components { skills: false, hooks: false }` still could, so keep a
// defensive guard rather than silently succeeding.
- if !plan.init_clarion()
+ if !plan.init_loomweave()
&& !plan.claude_code()
&& !plan.codex()
&& !plan.skills()
@@ -288,59 +288,60 @@ fn validate_plan(plan: InstallPlan) -> Result<()> {
bail!(
"nothing to install: pass --claude-code, --codex, --skills, \
--codex-skills, --hooks, --all, \
- or run bare `clarion install` to do everything."
+ or run bare `loomweave install` to do everything."
);
}
Ok(())
}
fn initialise_project(project_root: &Path, force: bool) -> Result<()> {
- let clarion_dir = project_root.join(".clarion");
- let exists = clarion_dir.exists();
- // `All` (including naked install) treats an existing .clarion/ as
+ let loomweave_dir = project_root.join(".loomweave");
+ let exists = loomweave_dir.exists();
+ // `All` (including naked install) treats an existing .loomweave/ as
// already-initialised and skips re-init, still applying the idempotent
- // components. A non-directory .clarion is not a usable index, so refuse
- // rather than "succeed" with skills/hooks atop a project with no clarion.db.
+ // components. A non-directory .loomweave is not a usable index, so refuse
+ // rather than "succeed" with skills/hooks atop a project with no loomweave.db.
// Component-only installs skip this block.
if exists && !force {
- if !clarion_dir.is_dir() {
+ if !loomweave_dir.is_dir() {
bail!(
- "found a non-directory at {}; expected an initialised .clarion/ \
+ "found a non-directory at {}; expected an initialised .loomweave/ \
directory. Remove it (or pass --force) and re-run.",
- clarion_dir.display()
+ loomweave_dir.display()
);
}
println!(
- "{} already initialised; skipping .clarion/ init (pass --force to recreate).",
- clarion_dir.display()
+ "{} already initialised; skipping .loomweave/ init (pass --force to recreate).",
+ loomweave_dir.display()
);
return Ok(());
}
if exists {
// --force overwrite path.
- if !clarion_dir.is_dir() {
+ if !loomweave_dir.is_dir() {
bail!(
- "--force can only overwrite an existing .clarion/ directory; \
+ "--force can only overwrite an existing .loomweave/ directory; \
found non-directory at {}.",
- clarion_dir.display()
+ loomweave_dir.display()
);
}
- fs::remove_dir_all(&clarion_dir)
- .with_context(|| format!("remove existing {}", clarion_dir.display()))?;
+ fs::remove_dir_all(&loomweave_dir)
+ .with_context(|| format!("remove existing {}", loomweave_dir.display()))?;
}
- fs::create_dir_all(&clarion_dir).with_context(|| format!("mkdir {}", clarion_dir.display()))?;
+ fs::create_dir_all(&loomweave_dir)
+ .with_context(|| format!("mkdir {}", loomweave_dir.display()))?;
- // Cleanup guard: if any post-mkdir step fails, remove .clarion/ before
+ // Cleanup guard: if any post-mkdir step fails, remove .loomweave/ before
// bubbling the error so the next install attempt isn't blocked by the
// "already exists" check (clarion-ed5017139f).
- if let Err(err) = populate_after_mkdir(&clarion_dir, project_root) {
- if let Err(cleanup_err) = fs::remove_dir_all(&clarion_dir) {
+ if let Err(err) = populate_after_mkdir(&loomweave_dir, project_root) {
+ if let Err(cleanup_err) = fs::remove_dir_all(&loomweave_dir) {
tracing::warn!(
- clarion_dir = %clarion_dir.display(),
+ loomweave_dir = %loomweave_dir.display(),
error = %cleanup_err,
- "install failed and cleanup of partial .clarion/ also failed; \
+ "install failed and cleanup of partial .loomweave/ also failed; \
manual rm -rf may be required"
);
}
@@ -348,10 +349,10 @@ fn initialise_project(project_root: &Path, force: bool) -> Result<()> {
}
tracing::info!(
- clarion_dir = %clarion_dir.display(),
- "clarion install complete"
+ loomweave_dir = %loomweave_dir.display(),
+ "loomweave install complete"
);
- println!("Initialised {}", clarion_dir.display());
+ println!("Initialised {}", loomweave_dir.display());
Ok(())
}
@@ -388,28 +389,28 @@ fn install_codex(codex_config_path: Option<&Path>) -> Result<()> {
fn install_claude_skills(project_root: &Path) -> Result<()> {
let report = crate::skill_pack::install_claude_skill_pack(project_root)
- .context("install clarion-workflow skill pack for Claude Code")?;
+ .context("install loomweave-workflow skill pack for Claude Code")?;
if report.copied {
println!(
- "Installed clarion-workflow skill into {}/.claude/skills",
+ "Installed loomweave-workflow skill into {}/.claude/skills",
project_root.display()
);
} else {
- println!("clarion-workflow Claude Code skill already up to date");
+ println!("loomweave-workflow Claude Code skill already up to date");
}
Ok(())
}
fn install_codex_skills(project_root: &Path) -> Result<()> {
let report = crate::skill_pack::install_codex_skill_pack(project_root)
- .context("install clarion-workflow skill pack for Codex")?;
+ .context("install loomweave-workflow skill pack for Codex")?;
if report.copied {
println!(
- "Installed clarion-workflow skill into {}/.agents/skills",
+ "Installed loomweave-workflow skill into {}/.agents/skills",
project_root.display()
);
} else {
- println!("clarion-workflow Codex skill already up to date");
+ println!("loomweave-workflow Codex skill already up to date");
}
Ok(())
}
@@ -419,46 +420,46 @@ fn install_hooks(project_root: &Path) -> Result<()> {
.context("merge SessionStart hook into .claude/settings.json")?;
if changed {
println!(
- "Added clarion SessionStart hook to {}/.claude/settings.json",
+ "Added loomweave SessionStart hook to {}/.claude/settings.json",
project_root.display()
);
} else {
- println!("clarion SessionStart hook already present");
+ println!("loomweave SessionStart hook already present");
}
Ok(())
}
fn install_integration_bindings(project_root: &Path) -> Result<()> {
let changed = crate::integration_bindings::install_bindings(project_root)
- .context("install local Clarion/Filigree/Wardline integration bindings")?;
+ .context("install local Loomweave/Filigree/Wardline integration bindings")?;
if changed {
- println!("Installed local Clarion/Filigree/Wardline integration bindings");
+ println!("Installed local Loomweave/Filigree/Wardline integration bindings");
} else {
- println!("Local Clarion/Filigree/Wardline integration bindings already up to date");
+ println!("Local Loomweave/Filigree/Wardline integration bindings already up to date");
}
Ok(())
}
-fn populate_after_mkdir(clarion_dir: &Path, project_root: &Path) -> Result<()> {
- let db_path = clarion_dir.join("clarion.db");
- initialise_db(&db_path).context("initialise clarion.db")?;
+fn populate_after_mkdir(loomweave_dir: &Path, project_root: &Path) -> Result<()> {
+ let db_path = loomweave_dir.join("loomweave.db");
+ initialise_db(&db_path).context("initialise loomweave.db")?;
- let config_path = clarion_dir.join("config.json");
+ let config_path = loomweave_dir.join("config.json");
fs::write(&config_path, CONFIG_JSON_STUB)
.with_context(|| format!("write {}", config_path.display()))?;
- let gitignore_path = clarion_dir.join(".gitignore");
+ let gitignore_path = loomweave_dir.join(".gitignore");
fs::write(&gitignore_path, GITIGNORE_CONTENTS)
.with_context(|| format!("write {}", gitignore_path.display()))?;
- let yaml_path = project_root.join("clarion.yaml");
+ let yaml_path = project_root.join("loomweave.yaml");
if yaml_path.exists() {
tracing::debug!(
path = %yaml_path.display(),
- "clarion.yaml already exists; leaving untouched"
+ "loomweave.yaml already exists; leaving untouched"
);
} else {
- fs::write(&yaml_path, CLARION_YAML_STUB)
+ fs::write(&yaml_path, LOOMWEAVE_YAML_STUB)
.with_context(|| format!("write {}", yaml_path.display()))?;
}
Ok(())
@@ -481,7 +482,7 @@ mod tests {
// Naked install: no flags -> everything (same as --all).
let naked = InstallPlan::from_components(false, &[]);
assert_eq!(naked, InstallPlan::All);
- assert!(naked.init_clarion());
+ assert!(naked.init_loomweave());
assert!(naked.claude_code());
assert!(naked.codex());
assert!(naked.skills());
@@ -500,7 +501,7 @@ mod tests {
hooks: false
}
);
- assert!(!skills.init_clarion());
+ assert!(!skills.init_loomweave());
assert!(!skills.claude_code());
assert!(!skills.codex());
assert!(skills.skills());
@@ -519,7 +520,7 @@ mod tests {
hooks: true
}
);
- assert!(!hooks.init_clarion());
+ assert!(!hooks.init_loomweave());
assert!(!hooks.claude_code());
assert!(!hooks.codex());
assert!(!hooks.skills());
@@ -529,7 +530,7 @@ mod tests {
// --all: everything (component flags ignored).
let all = InstallPlan::from_components(true, &[]);
assert_eq!(all, InstallPlan::All);
- assert!(all.init_clarion());
+ assert!(all.init_loomweave());
assert!(all.claude_code());
assert!(all.codex());
assert!(all.skills());
@@ -557,7 +558,7 @@ mod tests {
hooks: true
}
);
- assert!(!both.init_clarion());
+ assert!(!both.init_loomweave());
assert!(both.claude_code());
assert!(both.codex());
assert!(both.skills());
@@ -590,7 +591,7 @@ mod tests {
for components in cases {
let plan = InstallPlan::from_components(all, components);
assert!(
- plan.init_clarion()
+ plan.init_loomweave()
|| plan.claude_code()
|| plan.codex()
|| plan.skills()
diff --git a/crates/clarion-cli/src/instance.rs b/crates/loomweave-cli/src/instance.rs
similarity index 95%
rename from crates/clarion-cli/src/instance.rs
rename to crates/loomweave-cli/src/instance.rs
index 075cfdf4..235a3fab 100644
--- a/crates/clarion-cli/src/instance.rs
+++ b/crates/loomweave-cli/src/instance.rs
@@ -9,7 +9,7 @@ use uuid::Uuid;
const INSTANCE_ID_FILE: &str = "instance_id";
-/// A validated Clarion project instance ID — guaranteed to be a UUID at the
+/// A validated Loomweave project instance ID — guaranteed to be a UUID at the
/// type level. The inner `Uuid` is private and the only ways to construct
/// one are [`load_or_create`] (reads/creates the persisted file) and
/// [`parse_instance_id`] (parses a candidate string, used by tests).
@@ -42,7 +42,7 @@ impl Serialize for InstanceId {
}
pub fn load_or_create(project_root: &Path) -> Result {
- let path = project_root.join(".clarion").join(INSTANCE_ID_FILE);
+ let path = project_root.join(".loomweave").join(INSTANCE_ID_FILE);
match fs::read_to_string(&path) {
Ok(raw) => read_existing_instance_id(&path, &raw),
Err(err) if err.kind() == io::ErrorKind::NotFound => create_instance_id(&path),
@@ -142,7 +142,7 @@ pub(crate) fn parse_instance_id_for_test(raw: &str) -> Result {
fn invalid_instance_id(path: &Path, source: &uuid::Error) -> anyhow::Error {
anyhow!(
- "invalid Clarion instance ID in {}: {source}; expected a UUID",
+ "invalid Loomweave instance ID in {}: {source}; expected a UUID",
path.display()
)
}
diff --git a/crates/clarion-cli/src/integration_bindings.rs b/crates/loomweave-cli/src/integration_bindings.rs
similarity index 90%
rename from crates/clarion-cli/src/integration_bindings.rs
rename to crates/loomweave-cli/src/integration_bindings.rs
index a9d407a3..13825548 100644
--- a/crates/clarion-cli/src/integration_bindings.rs
+++ b/crates/loomweave-cli/src/integration_bindings.rs
@@ -1,7 +1,7 @@
-//! Local three-way Clarion/Filigree/Wardline dogfood bindings.
+//! Local three-way Loomweave/Filigree/Wardline dogfood bindings.
//!
//! These are intentionally configuration bindings, not a shared runtime:
-//! Clarion enables its own optional HTTP and Filigree read surfaces, Wardline
+//! Loomweave enables its own optional HTTP and Filigree read surfaces, Wardline
//! receives the two peer URLs, and the project-local `.mcp.json` launches
//! Wardline with the same URLs for MCP scans.
@@ -12,8 +12,8 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
use serde_json::{Map, Value, json};
-const CLARION_HTTP_BIND: &str = "127.0.0.1:9111";
-const CLARION_HTTP_URL: &str = "http://127.0.0.1:9111";
+const LOOMWEAVE_HTTP_BIND: &str = "127.0.0.1:9111";
+const LOOMWEAVE_HTTP_URL: &str = "http://127.0.0.1:9111";
const DEFAULT_FILIGREE_BASE_URL: &str = "http://127.0.0.1:8766";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -33,7 +33,7 @@ struct DesiredBindings {
pub fn binding_state(project_root: &Path) -> BindingState {
let desired = desired_bindings(project_root);
match (
- clarion_yaml_ok(project_root, &desired),
+ loomweave_yaml_ok(project_root, &desired),
wardline_yaml_ok(project_root, &desired),
wardline_mcp_ok(project_root, &desired),
) {
@@ -53,7 +53,7 @@ pub fn binding_state(project_root: &Path) -> BindingState {
pub fn install_bindings(project_root: &Path) -> Result {
let desired = desired_bindings(project_root);
let mut changed = false;
- changed |= install_clarion_yaml(project_root, &desired)?;
+ changed |= install_loomweave_yaml(project_root, &desired)?;
changed |= install_wardline_yaml(project_root, &desired)?;
changed |= install_wardline_mcp(project_root, &desired)?;
Ok(changed)
@@ -64,7 +64,7 @@ fn desired_bindings(project_root: &Path) -> DesiredBindings {
.or_else(|| configured_filigree_base_url(project_root))
.unwrap_or_else(|| DEFAULT_FILIGREE_BASE_URL.to_owned());
let wardline_filigree_url = format!(
- "{}/api/loom/scan-results",
+ "{}/api/weft/scan-results",
filigree_base_url.trim_end_matches('/')
);
DesiredBindings {
@@ -80,7 +80,7 @@ fn live_filigree_base_url(project_root: &Path) -> Option {
}
fn configured_filigree_base_url(project_root: &Path) -> Option {
- let path = project_root.join("clarion.yaml");
+ let path = project_root.join("loomweave.yaml");
let value = read_yaml_value(&path).ok()?;
value
.get("integrations")
@@ -91,8 +91,8 @@ fn configured_filigree_base_url(project_root: &Path) -> Option {
.map(str::to_owned)
}
-fn clarion_yaml_ok(project_root: &Path, desired: &DesiredBindings) -> Result {
- let path = project_root.join("clarion.yaml");
+fn loomweave_yaml_ok(project_root: &Path, desired: &DesiredBindings) -> Result {
+ let path = project_root.join("loomweave.yaml");
if !path.exists() {
return Ok(false);
}
@@ -114,7 +114,7 @@ fn clarion_yaml_ok(project_root: &Path, desired: &DesiredBindings) -> Result Result Result Result {
- let path = project_root.join("clarion.yaml");
+fn install_loomweave_yaml(project_root: &Path, desired: &DesiredBindings) -> Result {
+ let path = project_root.join("loomweave.yaml");
let mut value = read_yaml_value_or_empty(&path)?;
let root = object_mut(&mut value, &path)?;
root.entry("version".to_owned()).or_insert(json!(1));
@@ -172,7 +172,7 @@ fn install_clarion_yaml(project_root: &Path, desired: &DesiredBindings) -> Resul
let filigree = ensure_object(integrations, "filigree")?;
filigree.insert("enabled".to_owned(), json!(true));
filigree.insert("base_url".to_owned(), json!(desired.filigree_base_url));
- ensure_string(filigree, "actor", "clarion-mcp");
+ ensure_string(filigree, "actor", "loomweave-mcp");
ensure_string(filigree, "token_env", "FILIGREE_API_TOKEN");
filigree
.entry("timeout_seconds".to_owned())
@@ -181,7 +181,7 @@ fn install_clarion_yaml(project_root: &Path, desired: &DesiredBindings) -> Resul
let serve = ensure_object(root, "serve")?;
let http = ensure_object(serve, "http")?;
http.insert("enabled".to_owned(), json!(true));
- http.insert("bind".to_owned(), json!(CLARION_HTTP_BIND));
+ http.insert("bind".to_owned(), json!(LOOMWEAVE_HTTP_BIND));
http.insert("wardline_taint_write".to_owned(), json!(true));
write_yaml_if_changed(&path, &value)
}
@@ -190,8 +190,8 @@ fn install_wardline_yaml(project_root: &Path, desired: &DesiredBindings) -> Resu
let path = project_root.join("wardline.yaml");
let mut value = read_yaml_value_or_empty(&path)?;
let root = object_mut(&mut value, &path)?;
- let clarion = ensure_object(root, "clarion")?;
- clarion.insert("url".to_owned(), json!(CLARION_HTTP_URL));
+ let loomweave = ensure_object(root, "loomweave")?;
+ loomweave.insert("url".to_owned(), json!(LOOMWEAVE_HTTP_URL));
let filigree = ensure_object(root, "filigree")?;
filigree.insert("url".to_owned(), json!(desired.wardline_filigree_url));
write_yaml_if_changed(&path, &value)
@@ -245,8 +245,8 @@ fn desired_wardline_args(desired: &DesiredBindings) -> Value {
"mcp",
"--root",
".",
- "--clarion-url",
- CLARION_HTTP_URL,
+ "--loomweave-url",
+ LOOMWEAVE_HTTP_URL,
"--filigree-url",
desired.wardline_filigree_url
])
diff --git a/crates/clarion-cli/src/main.rs b/crates/loomweave-cli/src/main.rs
similarity index 92%
rename from crates/clarion-cli/src/main.rs
rename to crates/loomweave-cli/src/main.rs
index 37f8b2cd..43c75988 100644
--- a/crates/clarion-cli/src/main.rs
+++ b/crates/loomweave-cli/src/main.rs
@@ -190,14 +190,14 @@ mod tests {
#[test]
fn analyze_does_not_load_dotenv() {
- assert!(!loads(&["clarion", "analyze", "."]));
+ assert!(!loads(&["loomweave", "analyze", "."]));
}
#[test]
fn guidance_editor_subcommands_do_not_load_dotenv() {
// create/edit spawn $VISUAL/$EDITOR; a repo .env must not feed them.
assert!(!loads(&[
- "clarion",
+ "loomweave",
"guidance",
"create",
"--scope-level",
@@ -205,7 +205,12 @@ mod tests {
"--match",
"kind:function",
]));
- assert!(!loads(&["clarion", "guidance", "edit", "core:guidance:x"]));
+ assert!(!loads(&[
+ "loomweave",
+ "guidance",
+ "edit",
+ "core:guidance:x"
+ ]));
}
#[test]
@@ -213,14 +218,14 @@ mod tests {
// promote resolves a Filigree token from a .env-supplied token_env;
// excluding it would regress authenticated promotion. These commands
// never spawn an editor, so loading .env is safe.
- assert!(loads(&["clarion", "guidance", "promote", "obs-123"]));
- assert!(loads(&["clarion", "guidance", "show", "core:guidance:x"]));
- assert!(loads(&["clarion", "guidance", "list"]));
- assert!(loads(&["clarion", "guidance", "export", "--to", "out"]));
+ assert!(loads(&["loomweave", "guidance", "promote", "obs-123"]));
+ assert!(loads(&["loomweave", "guidance", "show", "core:guidance:x"]));
+ assert!(loads(&["loomweave", "guidance", "list"]));
+ assert!(loads(&["loomweave", "guidance", "export", "--to", "out"]));
}
#[test]
fn other_commands_load_dotenv() {
- assert!(loads(&["clarion", "doctor"]));
+ assert!(loads(&["loomweave", "doctor"]));
}
}
diff --git a/crates/clarion-cli/src/mcp_registration.rs b/crates/loomweave-cli/src/mcp_registration.rs
similarity index 77%
rename from crates/clarion-cli/src/mcp_registration.rs
rename to crates/loomweave-cli/src/mcp_registration.rs
index 29a35869..07059268 100644
--- a/crates/clarion-cli/src/mcp_registration.rs
+++ b/crates/loomweave-cli/src/mcp_registration.rs
@@ -1,31 +1,31 @@
-//! Clarion MCP server-entry detection and never-clobber merge.
+//! Loomweave MCP server-entry detection and never-clobber merge.
//!
-//! `clarion install --claude-code` writes the project-local `.mcp.json` entry
-//! for Claude Code. `clarion install --codex` writes the user-level Codex
-//! `config.toml` entry. `clarion doctor` detects a missing or mis-pointed
+//! `loomweave install --claude-code` writes the project-local `.mcp.json` entry
+//! for Claude Code. `loomweave install --codex` writes the user-level Codex
+//! `config.toml` entry. `loomweave doctor` detects a missing or mis-pointed
//! Claude Code entry and — under `--fix` — repairs it.
//!
//! Merge semantics mirror [`crate::hooks_settings`]: parse the existing JSON,
-//! touch only the `mcpServers.clarion` key, and preserve every other server
+//! touch only the `mcpServers.loomweave` key, and preserve every other server
//! (e.g. a sibling `filigree` entry) and top-level key. A fresh entry uses the
-//! current `clarion` executable; an existing entry refreshes stale `clarion`
+//! current `loomweave` executable; an existing entry refreshes stale `loomweave`
//! executable paths and corrects `args` to the runtime-autodiscovery form while
-//! preserving deliberately customised non-Clarion wrapper commands.
+//! preserving deliberately customised non-Loomweave wrapper commands.
//!
-//! Security posture for the owned `clarion` entry: `.mcp.json` is a
+//! Security posture for the owned `loomweave` entry: `.mcp.json` is a
//! repository-committed file, so a hostile checkout can ship an entry whose
//! `command` points at an attacker-controlled executable that the MCP client
-//! will later launch. `doctor` must therefore never report a `clarion` entry
-//! whose `command` is not a Clarion executable as healthy — that would be a
-//! false all-clear on a poisoned config. But Clarion also cannot tell a
+//! will later launch. `doctor` must therefore never report a `loomweave` entry
+//! whose `command` is not a Loomweave executable as healthy — that would be a
+//! false all-clear on a poisoned config. But Loomweave also cannot tell a
//! malicious command from a *deliberate* wrapper binary (a nix/bazel shim, a
//! sandbox launcher, a pinned absolute path), so it must not silently clobber
//! one either. The chosen policy is **warn, don't clobber**: an owned entry
-//! whose `command` basename is not `clarion`/`clarion.exe` is classified
+//! whose `command` basename is not `loomweave`/`loomweave.exe` is classified
//! [`McpState::UntrustedCommand`], which `doctor` flags (failing the gate
//! without `--fix`) while leaving the command in place for the operator to
-//! adjudicate. `--fix` still repairs `args`/stale `clarion` paths but never
-//! replaces a non-Clarion command.
+//! adjudicate. `--fix` still repairs `args`/stale `loomweave` paths but never
+//! replaces a non-Loomweave command.
use std::ffi::OsStr;
use std::fs;
@@ -34,34 +34,34 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
use serde_json::{Map, Value, json};
-/// The `mcpServers` key Clarion owns.
-pub const SERVER_KEY: &str = "clarion";
+/// The `mcpServers` key Loomweave owns.
+pub const SERVER_KEY: &str = "loomweave";
-/// Read-only health of the `.mcp.json` Clarion registration, for `doctor`.
+/// Read-only health of the `.mcp.json` Loomweave registration, for `doctor`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpState {
- /// A `clarion` stdio server is registered and runs `serve` using the MCP
+ /// A `loomweave` stdio server is registered and runs `serve` using the MCP
/// client's current working directory for project discovery.
Present,
- /// A `clarion` entry exists but is not the runtime-autodiscovery form
- /// (wrong args, stale `clarion` executable path, or not a `serve`
+ /// A `loomweave` entry exists but is not the runtime-autodiscovery form
+ /// (wrong args, stale `loomweave` executable path, or not a `serve`
/// invocation). Repairable in place.
Stale,
- /// A `clarion` entry exists whose `command` is not a Clarion executable
- /// (its basename is not `clarion`/`clarion.exe`). This may be a deliberate
+ /// A `loomweave` entry exists whose `command` is not a Loomweave executable
+ /// (its basename is not `loomweave`/`loomweave.exe`). This may be a deliberate
/// wrapper binary or a malicious entry shipped in a hostile checkout;
/// `doctor` cannot tell them apart, so it flags the entry for operator
/// review and never auto-replaces the command. `--fix` still corrects
/// `args` but leaves the `command` untouched.
UntrustedCommand,
- /// No `.mcp.json`, or it has no `clarion` server entry.
+ /// No `.mcp.json`, or it has no `loomweave` server entry.
Missing,
/// `.mcp.json` exists but is not parseable JSON (or has a non-object shape).
/// The merge refuses to clobber it, so this cannot be auto-repaired.
Unparseable,
}
-/// The `args` a stdio `clarion` MCP entry must carry.
+/// The `args` a stdio `loomweave` MCP entry must carry.
///
/// Claude Code project configs and Codex global configs should use runtime
/// project autodiscovery from the client working directory. Pinning `--path`
@@ -76,44 +76,44 @@ fn desired_arg_strings() -> Vec<&'static str> {
}
/// Return the command path to write into fresh MCP configs.
-fn clarion_command() -> String {
+fn loomweave_command() -> String {
match std::env::current_exe() {
- Ok(path) if executable_name_is_clarion(path.file_name()) => path.display().to_string(),
- _ => "clarion".to_owned(),
+ Ok(path) if executable_name_is_loomweave(path.file_name()) => path.display().to_string(),
+ _ => "loomweave".to_owned(),
}
}
-fn executable_name_is_clarion(name: Option<&OsStr>) -> bool {
+fn executable_name_is_loomweave(name: Option<&OsStr>) -> bool {
let Some(name) = name.and_then(OsStr::to_str) else {
return false;
};
- name == "clarion" || name == "clarion.exe"
+ name == "loomweave" || name == "loomweave.exe"
}
-fn command_string_is_clarion(command: &str) -> bool {
- executable_name_is_clarion(Path::new(command).file_name())
+fn command_string_is_loomweave(command: &str) -> bool {
+ executable_name_is_loomweave(Path::new(command).file_name())
}
/// True if `entry.args` runs `serve` (no pinned project path) under the current
-/// Clarion executable. A non-Clarion command is handled separately as
+/// Loomweave executable. A non-Loomweave command is handled separately as
/// [`McpState::UntrustedCommand`] and is never treated as the healthy form.
fn entry_uses_runtime_project(entry: &Value) -> bool {
let Some(args) = entry.get("args").and_then(Value::as_array) else {
return false;
};
let strs: Vec<&str> = args.iter().filter_map(Value::as_str).collect();
- strs == desired_arg_strings() && entry_command_is_current_clarion(entry)
+ strs == desired_arg_strings() && entry_command_is_current_loomweave(entry)
}
-/// True if the entry's `command` is the current Clarion executable.
-fn entry_command_is_current_clarion(entry: &Value) -> bool {
- entry.get("command").and_then(Value::as_str) == Some(clarion_command().as_str())
+/// True if the entry's `command` is the current Loomweave executable.
+fn entry_command_is_current_loomweave(entry: &Value) -> bool {
+ entry.get("command").and_then(Value::as_str) == Some(loomweave_command().as_str())
}
-/// The `command` string of the owned `clarion` entry, if any. Used by `doctor`
+/// The `command` string of the owned `loomweave` entry, if any. Used by `doctor`
/// to name an unrecognized command in its report.
#[must_use]
-pub fn clarion_entry_command(project_root: &Path) -> Option {
+pub fn loomweave_entry_command(project_root: &Path) -> Option {
let raw = fs::read_to_string(project_root.join(".mcp.json")).ok()?;
let root: Value = serde_json::from_str(&raw).ok()?;
root.get("mcpServers")?
@@ -123,7 +123,7 @@ pub fn clarion_entry_command(project_root: &Path) -> Option {
.map(ToOwned::to_owned)
}
-/// Classify the `.mcp.json` Clarion entry without writing anything.
+/// Classify the `.mcp.json` Loomweave entry without writing anything.
#[must_use]
pub fn mcp_entry_state(project_root: &Path) -> McpState {
let path = project_root.join(".mcp.json");
@@ -146,13 +146,13 @@ pub fn mcp_entry_state(project_root: &Path) -> McpState {
let Some(entry) = root.get("mcpServers").and_then(|m| m.get(SERVER_KEY)) else {
return McpState::Missing;
};
- // Security: an owned entry whose command is not a Clarion executable is
+ // Security: an owned entry whose command is not a Loomweave executable is
// never reported healthy and never auto-replaced (see module docs). It is
// surfaced for operator review regardless of whether its args look right.
if entry
.get("command")
.and_then(Value::as_str)
- .is_some_and(|command| !command_string_is_clarion(command))
+ .is_some_and(|command| !command_string_is_loomweave(command))
{
return McpState::UntrustedCommand;
}
@@ -164,13 +164,13 @@ pub fn mcp_entry_state(project_root: &Path) -> McpState {
}
/// Read `.mcp.json` under `project_root` (creating `{}` if absent), merge
-/// Clarion's `serve` entry, and write it back pretty-printed. Returns `true`
+/// Loomweave's `serve` entry, and write it back pretty-printed. Returns `true`
/// if the file changed.
///
/// Never-clobber: an existing object entry keeps `type` and `env`; `args`
-/// are corrected, stale `clarion` executable paths are refreshed, and
-/// non-Clarion wrapper commands are preserved. A fresh entry is written with
-/// the current `clarion` command. All other servers and top-level keys are
+/// are corrected, stale `loomweave` executable paths are refreshed, and
+/// non-Loomweave wrapper commands are preserved. A fresh entry is written with
+/// the current `loomweave` command. All other servers and top-level keys are
/// preserved.
///
/// # Errors
@@ -212,7 +212,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result {
}
let want_args = desired_args();
- let want_command = clarion_command();
+ let want_command = loomweave_command();
let obj = root.as_object_mut().expect("root is object");
let servers = obj
.entry("mcpServers")
@@ -221,7 +221,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result {
let changed = match servers.get_mut(SERVER_KEY) {
// Existing object entry: preserve type/env and deliberate wrappers,
- // correct args, and refresh stale clarion executable paths.
+ // correct args, and refresh stale loomweave executable paths.
Some(entry) if entry.is_object() => {
let entry = entry.as_object_mut().expect("entry is object");
let mut changed = false;
@@ -234,7 +234,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result {
.get("command")
.and_then(Value::as_str)
.is_none_or(|command| {
- command_string_is_clarion(command) && command != want_command
+ command_string_is_loomweave(command) && command != want_command
});
if should_refresh_command {
entry.insert("command".to_string(), Value::String(want_command.clone()));
@@ -279,7 +279,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result {
/// Codex reads a global config, so tests use [`install_codex_mcp_entry`] with
/// an explicit path rather than writing to the real user config.
pub fn codex_config_path() -> Result {
- if let Some(path) = std::env::var_os("CLARION_CODEX_CONFIG") {
+ if let Some(path) = std::env::var_os("LOOMWEAVE_CODEX_CONFIG") {
return Ok(PathBuf::from(path));
}
let Some(home) = std::env::var_os("HOME") else {
@@ -288,10 +288,10 @@ pub fn codex_config_path() -> Result {
Ok(PathBuf::from(home).join(".codex").join("config.toml"))
}
-/// Merge Clarion's stdio MCP server into Codex's TOML config.
+/// Merge Loomweave's stdio MCP server into Codex's TOML config.
///
/// The global Codex entry deliberately does not include a project path; Codex
-/// starts the stdio server in the active workspace and Clarion's `serve`
+/// starts the stdio server in the active workspace and Loomweave's `serve`
/// default path (`.`) resolves from there.
pub fn install_codex_mcp_entry(config_path: &Path) -> Result {
if let Some(parent) = config_path.parent() {
@@ -308,23 +308,23 @@ pub fn install_codex_mcp_entry(config_path: &Path) -> Result {
let parsed: toml::Value = existing
.parse()
.with_context(|| format!("parse {}", config_path.display()))?;
- if codex_config_has_desired_clarion(&parsed) {
+ if codex_config_has_desired_loomweave(&parsed) {
return Ok(false);
}
}
let updated = upsert_toml_table(
&existing,
- "mcp_servers.clarion",
- &codex_server_block(&clarion_command()),
+ "mcp_servers.loomweave",
+ &codex_server_block(&loomweave_command()),
);
write_text_if_changed(config_path, &updated)
}
-fn codex_config_has_desired_clarion(parsed: &toml::Value) -> bool {
+fn codex_config_has_desired_loomweave(parsed: &toml::Value) -> bool {
let Some(entry) = parsed
.get("mcp_servers")
- .and_then(|servers| servers.get("clarion"))
+ .and_then(|servers| servers.get("loomweave"))
.and_then(toml::Value::as_table)
else {
return false;
@@ -346,12 +346,12 @@ fn codex_config_has_desired_clarion(parsed: &toml::Value) -> bool {
}
fn toml_command_is_current_or_custom(command: &str) -> bool {
- !command_string_is_clarion(command) || command == clarion_command()
+ !command_string_is_loomweave(command) || command == loomweave_command()
}
fn codex_server_block(command: &str) -> String {
format!(
- "[mcp_servers.clarion]\ncommand = \"{}\"\nargs = [\"serve\"]\n",
+ "[mcp_servers.loomweave]\ncommand = \"{}\"\nargs = [\"serve\"]\n",
toml_quote(command)
)
}
@@ -428,10 +428,10 @@ mod tests {
install_mcp_entry(dir.path()).unwrap();
let raw = fs::read_to_string(dir.path().join(".mcp.json")).unwrap();
let v: Value = serde_json::from_str(&raw).unwrap();
- let entry = &v["mcpServers"]["clarion"];
+ let entry = &v["mcpServers"]["loomweave"];
assert!(
- entry["command"].as_str().unwrap().ends_with("clarion"),
- "fresh entry should point at a clarion executable: {entry:?}"
+ entry["command"].as_str().unwrap().ends_with("loomweave"),
+ "fresh entry should point at a loomweave executable: {entry:?}"
);
assert_eq!(entry["type"], "stdio");
assert_eq!(
@@ -444,25 +444,25 @@ mod tests {
#[test]
fn install_preserves_other_servers_and_keeps_custom_wrapper_command() {
let dir = tempfile::tempdir().unwrap();
- // Pre-existing file with a sibling server and a clarion entry that has a
+ // Pre-existing file with a sibling server and a loomweave entry that has a
// deliberately customised wrapper command but a WRONG --path.
fs::write(
dir.path().join(".mcp.json"),
r#"{
"mcpServers": {
"filigree": {"type": "stdio", "command": "/opt/filigree-mcp", "args": []},
- "clarion": {"type": "stdio", "command": "/custom/bin/clarion-wrapper", "args": ["serve", "--path", "/old/proj"], "env": {}}
+ "loomweave": {"type": "stdio", "command": "/custom/bin/loomweave-wrapper", "args": ["serve", "--path", "/old/proj"], "env": {}}
}
}"#,
)
.unwrap();
- // A non-Clarion command is flagged for review, never silently healthy —
+ // A non-Loomweave command is flagged for review, never silently healthy —
// doctor cannot tell a deliberate wrapper from a malicious entry.
assert_eq!(
mcp_entry_state(dir.path()),
McpState::UntrustedCommand,
- "a non-clarion command is UntrustedCommand, regardless of args"
+ "a non-loomweave command is UntrustedCommand, regardless of args"
);
assert!(install_mcp_entry(dir.path()).unwrap());
@@ -473,12 +473,12 @@ mod tests {
assert_eq!(v["mcpServers"]["filigree"]["command"], "/opt/filigree-mcp");
// Custom wrapper command PRESERVED (never clobbered), args corrected.
assert_eq!(
- v["mcpServers"]["clarion"]["command"], "/custom/bin/clarion-wrapper",
+ v["mcpServers"]["loomweave"]["command"], "/custom/bin/loomweave-wrapper",
"a customised wrapper command must be preserved, not clobbered"
);
let canon = dir.path().canonicalize().unwrap().display().to_string();
assert_eq!(
- v["mcpServers"]["clarion"]["args"],
+ v["mcpServers"]["loomweave"]["args"],
serde_json::json!(["serve"]),
"stale --path pin should be removed: {canon}"
);
@@ -500,7 +500,7 @@ mod tests {
fs::write(
dir.path().join(".mcp.json"),
format!(
- r#"{{"mcpServers":{{"clarion":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"#
+ r#"{{"mcpServers":{{"loomweave":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"#
),
)
.unwrap();
@@ -511,7 +511,7 @@ mod tests {
"matching args must NOT make an untrusted command healthy"
);
assert_eq!(
- super::clarion_entry_command(dir.path()).as_deref(),
+ super::loomweave_entry_command(dir.path()).as_deref(),
Some("./evil-mcp.sh")
);
@@ -522,7 +522,7 @@ mod tests {
serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap())
.unwrap();
assert_eq!(
- v["mcpServers"]["clarion"]["command"], "./evil-mcp.sh",
+ v["mcpServers"]["loomweave"]["command"], "./evil-mcp.sh",
"doctor must not clobber the command on --fix"
);
assert_eq!(
@@ -533,13 +533,13 @@ mod tests {
}
#[test]
- fn install_refreshes_stale_clarion_executable_path() {
+ fn install_refreshes_stale_loomweave_executable_path() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join(".mcp.json"),
r#"{
"mcpServers": {
- "clarion": {"type": "stdio", "command": "/tmp/old-target/release/clarion", "args": ["serve"], "env": {}}
+ "loomweave": {"type": "stdio", "command": "/tmp/old-target/release/loomweave", "args": ["serve"], "env": {}}
}
}"#,
)
@@ -548,7 +548,7 @@ mod tests {
assert_eq!(
mcp_entry_state(dir.path()),
McpState::Stale,
- "a stale clarion executable path is Stale even when args are already correct"
+ "a stale loomweave executable path is Stale even when args are already correct"
);
assert!(install_mcp_entry(dir.path()).unwrap());
@@ -556,21 +556,21 @@ mod tests {
serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap())
.unwrap();
assert!(
- v["mcpServers"]["clarion"]["command"]
+ v["mcpServers"]["loomweave"]["command"]
.as_str()
.unwrap()
- .ends_with("clarion"),
- "clarion command should be refreshed to the current executable"
+ .ends_with("loomweave"),
+ "loomweave command should be refreshed to the current executable"
);
assert_ne!(
- v["mcpServers"]["clarion"]["command"], "/tmp/old-target/release/clarion",
- "stale clarion executable path should not be preserved"
+ v["mcpServers"]["loomweave"]["command"], "/tmp/old-target/release/loomweave",
+ "stale loomweave executable path should not be preserved"
);
assert_eq!(mcp_entry_state(dir.path()), McpState::Present);
}
#[test]
- fn codex_entry_upserts_clarion_without_touching_other_servers() {
+ fn codex_entry_upserts_loomweave_without_touching_other_servers() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.toml");
fs::write(
@@ -588,8 +588,8 @@ mod tests {
"sibling server was not preserved: {raw}"
);
assert!(
- raw.contains("[mcp_servers.clarion]"),
- "clarion Codex server missing: {raw}"
+ raw.contains("[mcp_servers.loomweave]"),
+ "loomweave Codex server missing: {raw}"
);
assert!(
raw.contains("args = [\"serve\"]"),
@@ -598,12 +598,12 @@ mod tests {
}
#[test]
- fn codex_entry_refreshes_stale_clarion_executable_path() {
+ fn codex_entry_refreshes_stale_loomweave_executable_path() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.toml");
fs::write(
&config_path,
- "[mcp_servers.clarion]\ncommand = \"/tmp/old-target/release/clarion\"\nargs = [\"serve\"]\n",
+ "[mcp_servers.loomweave]\ncommand = \"/tmp/old-target/release/loomweave\"\nargs = [\"serve\"]\n",
)
.unwrap();
@@ -611,8 +611,8 @@ mod tests {
let raw = fs::read_to_string(&config_path).unwrap();
assert!(
- !raw.contains("/tmp/old-target/release/clarion"),
- "stale clarion executable path should not be preserved: {raw}"
+ !raw.contains("/tmp/old-target/release/loomweave"),
+ "stale loomweave executable path should not be preserved: {raw}"
);
assert!(raw.contains("args = [\"serve\"]"));
}
diff --git a/crates/clarion-cli/src/run_lifecycle.rs b/crates/loomweave-cli/src/run_lifecycle.rs
similarity index 97%
rename from crates/clarion-cli/src/run_lifecycle.rs
rename to crates/loomweave-cli/src/run_lifecycle.rs
index f624877f..1e7be1af 100644
--- a/crates/clarion-cli/src/run_lifecycle.rs
+++ b/crates/loomweave-cli/src/run_lifecycle.rs
@@ -1,5 +1,5 @@
use anyhow::{Context, Result};
-use clarion_storage::{Writer, commands::WriterCmd};
+use loomweave_storage::{Writer, commands::WriterCmd};
pub(crate) async fn begin_run(
writer: &Writer,
diff --git a/crates/clarion-cli/src/sarif.rs b/crates/loomweave-cli/src/sarif.rs
similarity index 98%
rename from crates/clarion-cli/src/sarif.rs
rename to crates/loomweave-cli/src/sarif.rs
index a7a89594..0c08869d 100644
--- a/crates/clarion-cli/src/sarif.rs
+++ b/crates/loomweave-cli/src/sarif.rs
@@ -4,8 +4,8 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result, anyhow};
use serde_json::{Map, Value, json};
-use clarion_federation::filigree::FiligreeHttpClient;
-use clarion_federation::scan_results::ScanResultsRequest;
+use loomweave_federation::filigree::FiligreeHttpClient;
+use loomweave_federation::scan_results::ScanResultsRequest;
/// Translate SARIF findings from a file and post them to Filigree.
#[allow(clippy::too_many_lines, clippy::collapsible_if)]
@@ -22,7 +22,7 @@ pub fn run_import(file: &Path, scan_source_opt: Option, project_path: &P
std::env::var(name).ok()
})
.context("build Filigree HTTP client")?
- .ok_or_else(|| anyhow!("Filigree integration is disabled in clarion.yaml"))?;
+ .ok_or_else(|| anyhow!("Filigree integration is disabled in loomweave.yaml"))?;
// Read and parse SARIF file
let content = fs::read_to_string(file)
diff --git a/crates/clarion-cli/src/secret_scan.rs b/crates/loomweave-cli/src/secret_scan.rs
similarity index 96%
rename from crates/clarion-cli/src/secret_scan.rs
rename to crates/loomweave-cli/src/secret_scan.rs
index b828df5e..b64c9e09 100644
--- a/crates/clarion-cli/src/secret_scan.rs
+++ b/crates/loomweave-cli/src/secret_scan.rs
@@ -1,4 +1,4 @@
-//! Pre-ingest secret scanning for `clarion analyze`.
+//! Pre-ingest secret scanning for `loomweave analyze`.
//!
//! Exit codes used by this module:
//! - 0: analysis may continue, with or without an explicit secret override.
@@ -23,17 +23,17 @@ mod files;
mod findings;
use anyhow::{Context, Result};
-use clarion_core::BriefingBlockReason;
-use clarion_scanner::{Detection, Scanner, SuppressionResult};
-use clarion_storage::{Writer, commands::EntityRecord};
pub(crate) use files::collect_scan_files;
use findings::{
FindingConfidence, FindingKind, FindingSeverity, PendingFinding, secret_detected_finding,
};
+use loomweave_core::BriefingBlockReason;
+use loomweave_scanner::{Detection, Scanner, SuppressionResult};
+use loomweave_storage::{Writer, commands::EntityRecord};
use serde_json::json;
-const SECRET_OVERRIDE_ALLOWED: &str = "CLA-SEC-UNREDACTED-SECRETS-ALLOWED";
-const OVERRIDE_UNCONFIRMED: &str = "CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED";
+const SECRET_OVERRIDE_ALLOWED: &str = "LMWV-SEC-UNREDACTED-SECRETS-ALLOWED";
+const OVERRIDE_UNCONFIRMED: &str = "LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED";
const CONFIRM_TOKEN: &str = "yes-i-understand";
#[derive(Debug, Clone, Default)]
@@ -277,12 +277,12 @@ pub(crate) fn pre_ingest(
continue;
}
// ADR-013 §"Override — --allow-unredacted-secrets": each detection
- // emits its own `CLA-SEC-SECRET-DETECTED` finding regardless of
+ // emits its own `LMWV-SEC-SECRET-DETECTED` finding regardless of
// whether the operator subsequently overrode the block. The override
- // finding (`CLA-SEC-UNREDACTED-SECRETS-ALLOWED`) is additive — it
+ // finding (`LMWV-SEC-UNREDACTED-SECRETS-ALLOWED`) is additive — it
// records the operator decision but does not replace the
// per-(rule,file,line) audit row, so a security review running
- // `filigree list --rule-id=CLA-SEC-SECRET-DETECTED` enumerates the
+ // `filigree list --rule-id=LMWV-SEC-SECRET-DETECTED` enumerates the
// full detection population.
findings.extend(
allowed
@@ -399,7 +399,7 @@ fn scan_one_source_file(
where
F: Fn(&[u8]) -> Vec + Sync + ?Sized,
{
- let mut handle = clarion_core::plugin::jail::safe_open(project_root, file)
+ let mut handle = loomweave_core::plugin::jail::safe_open(project_root, file)
.with_context(|| format!("safe-open {}", file.display()))?;
let canonical_file = canonical_or_original(file);
let mut buf = Vec::new();
diff --git a/crates/clarion-cli/src/secret_scan/anchors.rs b/crates/loomweave-cli/src/secret_scan/anchors.rs
similarity index 99%
rename from crates/clarion-cli/src/secret_scan/anchors.rs
rename to crates/loomweave-cli/src/secret_scan/anchors.rs
index 84155e99..ae539703 100644
--- a/crates/clarion-cli/src/secret_scan/anchors.rs
+++ b/crates/loomweave-cli/src/secret_scan/anchors.rs
@@ -4,7 +4,7 @@ use std::{
};
use anyhow::{Context, Result};
-use clarion_storage::{
+use loomweave_storage::{
Writer,
commands::{EntityRecord, WriterCmd},
};
diff --git a/crates/clarion-cli/src/secret_scan/baseline.rs b/crates/loomweave-cli/src/secret_scan/baseline.rs
similarity index 81%
rename from crates/clarion-cli/src/secret_scan/baseline.rs
rename to crates/loomweave-cli/src/secret_scan/baseline.rs
index 268038a8..fb03f880 100644
--- a/crates/clarion-cli/src/secret_scan/baseline.rs
+++ b/crates/loomweave-cli/src/secret_scan/baseline.rs
@@ -1,7 +1,7 @@
use std::path::Path;
use anyhow::{Context, Result};
-use clarion_scanner::{Baseline, BaselineError};
+use loomweave_scanner::{Baseline, BaselineError};
use serde_json::json;
use super::normalize_project_path;
@@ -9,12 +9,12 @@ use crate::secret_scan::findings::{
FindingConfidence, FindingKind, FindingSeverity, PendingFinding,
};
-const BASELINE_NO_JUSTIFICATION: &str = "CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION";
-const BASELINE_MATCH: &str = "CLA-INFRA-SECRET-BASELINE-MATCH";
+const BASELINE_NO_JUSTIFICATION: &str = "LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION";
+const BASELINE_MATCH: &str = "LMWV-INFRA-SECRET-BASELINE-MATCH";
pub(super) fn load_for_scan(project_root: &Path) -> Result<(Baseline, Vec)> {
- let path = project_root.join(".clarion/secrets-baseline.yaml");
- match clarion_scanner::load_baseline(&path) {
+ let path = project_root.join(".loomweave/secrets-baseline.yaml");
+ match loomweave_scanner::load_baseline(&path) {
Ok(baseline) => Ok((baseline, Vec::new())),
Err(BaselineError::MissingJustifications { entries }) => Ok((
Baseline::empty(),
diff --git a/crates/clarion-cli/src/secret_scan/files.rs b/crates/loomweave-cli/src/secret_scan/files.rs
similarity index 97%
rename from crates/clarion-cli/src/secret_scan/files.rs
rename to crates/loomweave-cli/src/secret_scan/files.rs
index b4cc74fd..654f8cbb 100644
--- a/crates/clarion-cli/src/secret_scan/files.rs
+++ b/crates/loomweave-cli/src/secret_scan/files.rs
@@ -8,7 +8,7 @@ use ignore::{DirEntry, WalkBuilder};
use super::canonical_or_original;
const SKIP_DIRS: &[&str] = &[
- ".clarion",
+ ".loomweave",
".git",
".hg",
".svn",
@@ -113,7 +113,7 @@ mod tests {
write(root.join("nested/service.env"), "TOKEN=four\n");
write(root.join("nested/.env"), "TOKEN=five\n");
write(root.join("nested/not-env.txt"), "TOKEN=six\n");
- write(root.join(".clarion/.env"), "TOKEN=skip\n");
+ write(root.join(".loomweave/.env"), "TOKEN=skip\n");
write(root.join("node_modules/.env"), "TOKEN=skip\n");
let files = collect_scan_files(root, &[root.join("src/app.py")]);
@@ -126,7 +126,7 @@ mod tests {
assert!(rel.contains(&"nested/.env".to_owned()));
assert!(rel.contains(&"src/app.py".to_owned()));
assert!(!rel.contains(&"nested/not-env.txt".to_owned()));
- assert!(!rel.contains(&".clarion/.env".to_owned()));
+ assert!(!rel.contains(&".loomweave/.env".to_owned()));
assert!(!rel.contains(&"node_modules/.env".to_owned()));
}
diff --git a/crates/clarion-cli/src/secret_scan/findings.rs b/crates/loomweave-cli/src/secret_scan/findings.rs
similarity index 96%
rename from crates/clarion-cli/src/secret_scan/findings.rs
rename to crates/loomweave-cli/src/secret_scan/findings.rs
index 74539014..532feeab 100644
--- a/crates/clarion-cli/src/secret_scan/findings.rs
+++ b/crates/loomweave-cli/src/secret_scan/findings.rs
@@ -4,8 +4,8 @@ use std::{
};
use anyhow::{Context, Result};
-use clarion_scanner::{Detection, SecretCategory};
-use clarion_storage::{
+use loomweave_scanner::{Detection, SecretCategory};
+use loomweave_storage::{
Writer,
commands::{FindingRecord, WriterCmd},
};
@@ -13,7 +13,7 @@ use serde_json::json;
use super::SecretScanOutcome;
-const SECRET_DETECTED: &str = "CLA-SEC-SECRET-DETECTED";
+const SECRET_DETECTED: &str = "LMWV-SEC-SECRET-DETECTED";
#[derive(Debug, Clone)]
pub(super) struct PendingFinding {
@@ -133,7 +133,7 @@ pub(crate) async fn emit_findings(
.send_wait(|ack| WriterCmd::InsertFinding {
finding: Box::new(FindingRecord {
id: finding_id.clone(),
- tool: "clarion".to_owned(),
+ tool: "loomweave".to_owned(),
tool_version: env!("CARGO_PKG_VERSION").to_owned(),
run_id: run_id.to_owned(),
rule_id: pending.rule_id.to_owned(),
@@ -193,7 +193,7 @@ fn finding_entity_id(file_path: &Path, anchors: &BTreeMap) -> O
#[cfg(test)]
mod tests {
use super::{FindingConfidence, finding_entity_id, secret_detected_finding};
- use clarion_scanner::{DetectSecretsRule, Detection, HashedSecret, SecretCategory};
+ use loomweave_scanner::{DetectSecretsRule, Detection, HashedSecret, SecretCategory};
use std::{collections::BTreeMap, path::PathBuf};
#[test]
diff --git a/crates/clarion-cli/src/sei_git.rs b/crates/loomweave-cli/src/sei_git.rs
similarity index 98%
rename from crates/clarion-cli/src/sei_git.rs
rename to crates/loomweave-cli/src/sei_git.rs
index 710e87a9..f6c85d93 100644
--- a/crates/clarion-cli/src/sei_git.rs
+++ b/crates/loomweave-cli/src/sei_git.rs
@@ -1,7 +1,7 @@
//! `GitRenameSource` implementations — the typed git-rename seam (REQ-C-05).
//!
//! The SEI matcher consumes a typed, locator-level git-rename signal
-//! (`{old_locator, new_locator}`), never "Clarion's git code". Two suppliers
+//! (`{old_locator, new_locator}`), never "Loomweave's git code". Two suppliers
//! implement the same trait, with a shared file→locator translation:
//!
//! - [`ShellGitRenameSource`] — the v1 concrete supplier: shells
@@ -30,8 +30,8 @@
//! So [`select_git_rename_source`] is **capability-aware**: for the staged-index
//! window (empty base) the shell source is the authority regardless of `legis`;
//! `legis` is selected only for a committed rev-range when configured AND
-//! reachable. This guarantees Clarion-with-`legis` is never *worse* than
-//! Clarion-without (the enrich-only invariant, loom.md §5). The matcher is
+//! reachable. This guarantees Loomweave-with-`legis` is never *worse* than
+//! Loomweave-without (the enrich-only invariant, weft.md §5). The matcher is
//! fail-closed anyway — a rename is only a hint, confirmed by byte-identical body
//! hash — so neither window choice can cause a *false* carry, only a missed one.
//!
@@ -54,8 +54,8 @@
use std::path::{Path, PathBuf};
use std::time::Duration;
-use clarion_core::hardened_git_command;
-use clarion_storage::{GitRename, GitRenameSource};
+use loomweave_core::hardened_git_command;
+use loomweave_storage::{GitRename, GitRenameSource};
/// How long to wait on a `legis` HTTP probe/read before giving up and degrading
/// to an empty signal. Short on purpose: `legis` is enrich-only, so a slow or
@@ -327,7 +327,7 @@ fn legis_reachable(base_url: &str) -> bool {
}
/// Capability-aware, enrich-only selection of the git-rename supplier (REQ-C-05,
-/// loom.md §5). `legis` is chosen ONLY when it is configured, the window is a
+/// weft.md §5). `legis` is chosen ONLY when it is configured, the window is a
/// committed rev-range (`!base.is_empty()`), AND it is reachable; in every other
/// case — `legis` absent/unset/unreachable, or the staged-index window the shell
/// source alone can answer — the shell source is the authority. The
@@ -678,7 +678,7 @@ mod tests {
#[test]
fn selector_falls_back_to_shell_when_legis_absent() {
- // No legis URL, committed base: shell source. Enrich-only — Clarion
+ // No legis URL, committed base: shell source. Enrich-only — Loomweave
// without legis is unchanged.
let tmp = std::env::temp_dir();
let src = select_git_rename_source(&tmp, None, "base123", vec![]);
@@ -702,7 +702,7 @@ mod tests {
/// selector handed the staged-index window to `legis`, the shell-detectable
/// rename would be LOST and the entity's SEI would orphan instead of carry.
/// The capability guard (`!base.is_empty()`) keeps the shell source for this
- /// window, so Clarion-with-`legis` is never worse than Clarion-without.
+ /// window, so Loomweave-with-`legis` is never worse than Loomweave-without.
#[test]
fn selector_keeps_working_tree_rename_even_when_a_reachable_legis_sees_nothing() {
let dir = tempfile::tempdir().unwrap();
diff --git a/crates/clarion-cli/src/serve.rs b/crates/loomweave-cli/src/serve.rs
similarity index 92%
rename from crates/clarion-cli/src/serve.rs
rename to crates/loomweave-cli/src/serve.rs
index 95b0e360..9496be06 100644
--- a/crates/clarion-cli/src/serve.rs
+++ b/crates/loomweave-cli/src/serve.rs
@@ -6,22 +6,22 @@ use std::thread;
use std::time::Duration;
use anyhow::{Context, Result, anyhow, ensure};
-use clarion_core::{
+use loomweave_core::{
ApiEmbeddingProvider, ApiEmbeddingProviderConfig, ClaudeCliProvider, ClaudeCliProviderConfig,
CodexCliProvider, CodexCliProviderConfig, EmbeddingProvider, EmbeddingProviderError,
LlmProvider, OpenRouterProvider, OpenRouterProviderConfig, Recording, RecordingProvider,
};
-use clarion_federation::config::{
+use loomweave_federation::config::{
LlmConfig, McpConfig, ProviderSelection, SemanticSearchConfig, select_provider_with_env,
};
-use clarion_federation::filigree::FiligreeHttpClient;
-use clarion_storage::{DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, ReaderPool, Writer};
+use loomweave_federation::filigree::FiligreeHttpClient;
+use loomweave_storage::{DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, ReaderPool, Writer};
pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
- let db_path = path.join(".clarion").join("clarion.db");
+ let db_path = path.join(".loomweave").join("loomweave.db");
ensure!(
db_path.exists(),
- "Clarion database not found at {}; run `clarion install --path {}` first",
+ "Loomweave database not found at {}; run `loomweave install --path {}` first",
db_path.display(),
path.display()
);
@@ -30,8 +30,8 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
.canonicalize()
.with_context(|| format!("canonicalize project path {}", path.display()))?;
let instance_id = crate::instance::load_or_create(&project_root)
- .context("load Clarion project instance ID")?;
- let default_config_path = path.join("clarion.yaml");
+ .context("load Loomweave project instance ID")?;
+ let default_config_path = path.join("loomweave.yaml");
let config_path = config_path.unwrap_or(&default_config_path);
let config = if config_path.exists() {
McpConfig::from_path(config_path)
@@ -50,7 +50,7 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
// (which goes stale, the dogfood bug) — then build the client against the
// resolved URL so `issues_for` reaches the running dashboard. The same
// resolution is surfaced by `project_status`.
- let filigree_resolution = clarion_federation::filigree_url::resolve_filigree_url(
+ let filigree_resolution = loomweave_federation::filigree_url::resolve_filigree_url(
&config.integrations.filigree,
&project_root,
);
@@ -62,7 +62,7 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
FiligreeHttpClient::from_config(&filigree_config, |name| std::env::var(name).ok())
.context("build Filigree HTTP client")?;
- let diagnostics = clarion_mcp::DiagnosticsContext {
+ let diagnostics = loomweave_mcp::DiagnosticsContext {
llm: llm_diagnostics,
filigree: filigree_resolution,
};
@@ -99,7 +99,7 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
semantic_search_state(&config.semantic_search, embedding_provider),
filigree_client,
diagnostics,
- clarion_mcp::McpToolPolicy {
+ loomweave_mcp::McpToolPolicy {
enable_write_tools: config.serve.mcp.enable_write_tools,
},
)?;
@@ -109,7 +109,10 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
/// Capture the LLM policy posture for `project_status`. `live` means a provider
/// that actually dispatches (`OpenRouter` / Codex / Claude CLIs); the recording
/// fixture and the disabled state are not live.
-fn llm_diagnostics(selection: &ProviderSelection, llm: &LlmConfig) -> clarion_mcp::LlmDiagnostics {
+fn llm_diagnostics(
+ selection: &ProviderSelection,
+ llm: &LlmConfig,
+) -> loomweave_mcp::LlmDiagnostics {
let (provider, live) = match selection {
ProviderSelection::Disabled => ("disabled", false),
ProviderSelection::Recording => ("recording", false),
@@ -117,7 +120,7 @@ fn llm_diagnostics(selection: &ProviderSelection, llm: &LlmConfig) -> clarion_mc
ProviderSelection::CodexCli => ("codex_cli", true),
ProviderSelection::ClaudeCli => ("claude_cli", true),
};
- clarion_mcp::LlmDiagnostics {
+ loomweave_mcp::LlmDiagnostics {
provider: provider.to_owned(),
live,
allow_live_provider: llm.allow_live_provider,
@@ -141,12 +144,12 @@ fn spawn_mcp_stdio(
llm_provider: Option>,
semantic_search: Option,
filigree_client: Option,
- diagnostics: clarion_mcp::DiagnosticsContext,
- tool_policy: clarion_mcp::McpToolPolicy,
+ diagnostics: loomweave_mcp::DiagnosticsContext,
+ tool_policy: loomweave_mcp::McpToolPolicy,
) -> Result {
let (result_tx, result_rx) = mpsc::channel();
let join = thread::Builder::new()
- .name("clarion-mcp-stdio".to_owned())
+ .name("loomweave-mcp-stdio".to_owned())
.spawn(move || {
let result = run_mcp_stdio(
project_root,
@@ -174,8 +177,8 @@ fn run_mcp_stdio(
llm_provider: Option>,
semantic_search: Option,
filigree_client: Option,
- diagnostics: clarion_mcp::DiagnosticsContext,
- tool_policy: clarion_mcp::McpToolPolicy,
+ diagnostics: loomweave_mcp::DiagnosticsContext,
+ tool_policy: loomweave_mcp::McpToolPolicy,
) -> Result<()> {
let stdin = std::io::stdin();
let stdout = std::io::stdout();
@@ -187,7 +190,7 @@ fn run_mcp_stdio(
.context("create MCP runtime")?;
let _runtime_guard = runtime.enter();
let mut state =
- clarion_mcp::ServerState::new(project_root, readers).with_tool_policy(tool_policy);
+ loomweave_mcp::ServerState::new(project_root, readers).with_tool_policy(tool_policy);
let mut llm_writer = None;
let mut llm_writer_join = None;
if let Some(provider) = llm_provider {
@@ -205,9 +208,13 @@ fn run_mcp_stdio(
}
state = state.with_diagnostics(diagnostics);
- let serve_result =
- clarion_mcp::serve_stdio_with_state_on_runtime(&runtime, &state, &mut reader, &mut writer)
- .context("serve MCP stdio");
+ let serve_result = loomweave_mcp::serve_stdio_with_state_on_runtime(
+ &runtime,
+ &state,
+ &mut reader,
+ &mut writer,
+ )
+ .context("serve MCP stdio");
drop(state);
drop(llm_writer);
let writer_result = if let Some(handle) = llm_writer_join {
diff --git a/crates/clarion-cli/src/skill_pack.rs b/crates/loomweave-cli/src/skill_pack.rs
similarity index 89%
rename from crates/clarion-cli/src/skill_pack.rs
rename to crates/loomweave-cli/src/skill_pack.rs
index 422922aa..c17c63ee 100644
--- a/crates/clarion-cli/src/skill_pack.rs
+++ b/crates/loomweave-cli/src/skill_pack.rs
@@ -1,9 +1,9 @@
-//! Embedded `clarion-workflow` skill pack and its on-disk installer.
+//! Embedded `loomweave-workflow` skill pack and its on-disk installer.
//!
//! The pack is compiled into the binary with `include_str!` (matching the
//! `include_str!` migration-embedding convention in
-//! `clarion-storage/src/schema.rs`). The asset itself is owned by
-//! `clarion-mcp` (which this crate depends on), so the embed below reaches
+//! `loomweave-storage/src/schema.rs`). The asset itself is owned by
+//! `loomweave-mcp` (which this crate depends on), so the embed below reaches
//! *down* the dependency edge rather than inverting it (clarion-04391392c7).
//! Each entry is `(relative_path, contents)`;
//! growing the pack with a `references/` directory is a data change here, not a
@@ -18,11 +18,11 @@ use anyhow::{Context, Result};
/// `(relative_path, contents)` for every file in the bundled skill pack.
pub const SKILL_PACK: &[(&str, &str)] = &[(
"SKILL.md",
- include_str!("../../clarion-mcp/assets/skills/clarion-workflow/SKILL.md"),
+ include_str!("../../loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md"),
)];
/// The on-disk subdirectory name the pack installs into.
-pub const PACK_DIR_NAME: &str = "clarion-workflow";
+pub const PACK_DIR_NAME: &str = "loomweave-workflow";
/// Deterministic blake3 hex digest over the pack's `(rel_path, contents)`
/// entries. Order-stable because `SKILL_PACK` is a fixed slice. This is the
@@ -49,7 +49,7 @@ const CLAUDE_SKILL_ROOT: &str = ".claude/skills";
/// The Codex / tool-agnostic skill root, relative to the project root.
const CODEX_SKILL_ROOT: &str = ".agents/skills";
-/// The two skill roots Clarion can install into, relative to the project root.
+/// The two skill roots Loomweave can install into, relative to the project root.
const SKILL_ROOTS: &[&str] = &[CLAUDE_SKILL_ROOT, CODEX_SKILL_ROOT];
const FINGERPRINT_FILE: &str = ".fingerprint";
@@ -65,7 +65,7 @@ pub struct SkillInstallReport {
/// Install (or re-sync on drift) the embedded skill pack into both skill roots
/// under `project_root`, idempotently.
///
-/// For each root, the pack lands at `/clarion-workflow/`. A
+/// For each root, the pack lands at `/loomweave-workflow/`. A
/// `.fingerprint` file recording [`pack_fingerprint`] is written alongside the
/// pack files as a provenance marker. If the digest recomputed from the files
/// on disk in every root (see [`installed_fingerprint`]) already equals the
@@ -125,7 +125,7 @@ fn installed_fingerprint(dest: &Path) -> Option {
Some(hasher.finalize().to_hex().to_string())
}
-/// Read-only health of the installed skill pack, for `clarion doctor`.
+/// Read-only health of the installed skill pack, for `loomweave doctor`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SkillPackState {
/// Every skill root holds the current pack bytes.
@@ -167,7 +167,7 @@ pub fn skill_pack_state(project_root: &Path) -> SkillPackState {
fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> {
fs::create_dir_all(root).with_context(|| format!("mkdir {}", root.display()))?;
// Stage in a sibling temp dir so the final rename is same-filesystem.
- let staging = root.join(format!(".clarion-workflow.tmp-{}", std::process::id()));
+ let staging = root.join(format!(".loomweave-workflow.tmp-{}", std::process::id()));
if staging.exists() {
fs::remove_dir_all(&staging)
.with_context(|| format!("clear stale staging {}", staging.display()))?;
@@ -175,8 +175,8 @@ fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> {
fs::create_dir_all(&staging).with_context(|| format!("mkdir {}", staging.display()))?;
// Cleanup guard: if writing the staged files fails, remove the staging dir
- // before bubbling the error so we don't leak a `.clarion-workflow.tmp-*`
- // sibling. Matches the partial-state-cleanup precedent on the `.clarion/`
+ // before bubbling the error so we don't leak a `.loomweave-workflow.tmp-*`
+ // sibling. Matches the partial-state-cleanup precedent on the `.loomweave/`
// path in install.rs. The original error is preserved.
if let Err(err) = write_staged_pack(&staging, fingerprint) {
let _ = fs::remove_dir_all(&staging);
@@ -192,7 +192,7 @@ fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> {
// backup, so the previously-installed pack is always recoverable; we never
// delete `dest` ahead of a rename that might not happen.
let had_existing = dest.exists();
- let backup = root.join(format!(".clarion-workflow.bak-{}", std::process::id()));
+ let backup = root.join(format!(".loomweave-workflow.bak-{}", std::process::id()));
if had_existing {
if backup.exists() {
fs::remove_dir_all(&backup)
@@ -252,7 +252,7 @@ mod tests {
.expect("SKILL.md present in pack");
assert_eq!(*rel, "SKILL.md");
assert!(
- contents.contains("name: clarion-workflow"),
+ contents.contains("name: loomweave-workflow"),
"SKILL.md is missing its frontmatter name"
);
}
@@ -277,11 +277,11 @@ mod tests {
let skill = dir
.path()
.join(root)
- .join("clarion-workflow")
+ .join("loomweave-workflow")
.join("SKILL.md");
assert!(skill.exists(), "missing {}", skill.display());
let body = std::fs::read_to_string(&skill).unwrap();
- assert!(body.contains("name: clarion-workflow"));
+ assert!(body.contains("name: loomweave-workflow"));
}
}
@@ -317,7 +317,8 @@ mod tests {
super::install_skill_pack(dir.path()).unwrap();
// Corrupt content (not delete) so the file is present but mismatched.
std::fs::write(
- dir.path().join(".claude/skills/clarion-workflow/SKILL.md"),
+ dir.path()
+ .join(".claude/skills/loomweave-workflow/SKILL.md"),
"STALE",
)
.unwrap();
@@ -329,7 +330,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
super::install_skill_pack(dir.path()).unwrap();
// Remove one root entirely: absence outranks drift -> Missing.
- std::fs::remove_dir_all(dir.path().join(".agents/skills/clarion-workflow")).unwrap();
+ std::fs::remove_dir_all(dir.path().join(".agents/skills/loomweave-workflow")).unwrap();
assert_eq!(skill_pack_state(dir.path()), SkillPackState::Missing);
}
@@ -339,7 +340,8 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
install_skill_pack(dir.path()).unwrap();
std::fs::write(
- dir.path().join(".claude/skills/clarion-workflow/SKILL.md"),
+ dir.path()
+ .join(".claude/skills/loomweave-workflow/SKILL.md"),
"STALE",
)
.unwrap();
@@ -348,7 +350,7 @@ mod tests {
for entry in std::fs::read_dir(&root).unwrap() {
let name = entry.unwrap().file_name();
assert!(
- !name.to_string_lossy().contains(".clarion-workflow.bak-"),
+ !name.to_string_lossy().contains(".loomweave-workflow.bak-"),
"leftover backup dir: {name:?}"
);
}
@@ -360,18 +362,20 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
install_skill_pack(dir.path()).unwrap();
// Corrupt one installed copy + its fingerprint to simulate drift.
- let skill = dir.path().join(".claude/skills/clarion-workflow/SKILL.md");
+ let skill = dir
+ .path()
+ .join(".claude/skills/loomweave-workflow/SKILL.md");
std::fs::write(&skill, "STALE").unwrap();
let fp = dir
.path()
- .join(".claude/skills/clarion-workflow/.fingerprint");
+ .join(".claude/skills/loomweave-workflow/.fingerprint");
std::fs::write(&fp, "deadbeef").unwrap();
let report = install_skill_pack(dir.path()).unwrap();
assert!(report.copied, "drift should trigger re-copy");
let body = std::fs::read_to_string(&skill).unwrap();
assert!(
- body.contains("name: clarion-workflow"),
+ body.contains("name: loomweave-workflow"),
"drift not repaired"
);
}
@@ -393,7 +397,7 @@ mod tests {
}
/// A failed re-install must be crash-safe: it surfaces the error, leaks no
- /// `.clarion-workflow.tmp-*` / `.bak-*` sibling, and never destroys the
+ /// `.loomweave-workflow.tmp-*` / `.bak-*` sibling, and never destroys the
/// already-installed pack (the guarantee the stage/backup/restore cleanup
/// code exists to protect). The failure is the only portable injection
/// point — `stage_and_swap` clears any pre-seeded staging dir on entry — so
@@ -413,12 +417,12 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
install_skill_pack(dir.path()).expect("first install ok");
let root = dir.path().join(".claude/skills");
- let skill_md = root.join("clarion-workflow/SKILL.md");
+ let skill_md = root.join("loomweave-workflow/SKILL.md");
// Force a drift so the next install attempts a swap, then make the skill
// root read-only so staging the new pack into it fails with EACCES.
// Drift is detected from pack *content* (installed_fingerprint rehashes
- // SKILL.md), so corrupt SKILL.md itself; the clarion-workflow child dir
+ // SKILL.md), so corrupt SKILL.md itself; the loomweave-workflow child dir
// stays writable, so this write succeeds before we lock the root.
std::fs::write(&skill_md, "STALE — drifted content").unwrap();
std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o555)).unwrap();
@@ -430,8 +434,8 @@ mod tests {
.filter_map(Result::ok)
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|name| {
- name.starts_with(".clarion-workflow.tmp-")
- || name.starts_with(".clarion-workflow.bak-")
+ name.starts_with(".loomweave-workflow.tmp-")
+ || name.starts_with(".loomweave-workflow.bak-")
})
.collect();
diff --git a/crates/clarion-cli/src/stats.rs b/crates/loomweave-cli/src/stats.rs
similarity index 100%
rename from crates/clarion-cli/src/stats.rs
rename to crates/loomweave-cli/src/stats.rs
diff --git a/crates/clarion-cli/src/wardline_guidance.rs b/crates/loomweave-cli/src/wardline_guidance.rs
similarity index 99%
rename from crates/clarion-cli/src/wardline_guidance.rs
rename to crates/loomweave-cli/src/wardline_guidance.rs
index bd82f953..5d4a2762 100644
--- a/crates/clarion-cli/src/wardline_guidance.rs
+++ b/crates/loomweave-cli/src/wardline_guidance.rs
@@ -7,7 +7,7 @@ use rusqlite::{Connection, OpenFlags};
use serde::Deserialize;
use serde_json::{Map, Value, json};
-use clarion_storage::{
+use loomweave_storage::{
GuidanceSheetInput, get_guidance_sheet, invalidate_summaries_for_sheet, slugify_guidance_name,
upsert_guidance_sheet,
};
@@ -371,7 +371,7 @@ fn collect_overlay_paths(dir: &Path, paths: &mut Vec) -> Res
if entry.file_type()?.is_dir() {
if matches!(
file_name.as_ref(),
- ".git" | ".clarion" | ".venv" | "target" | "node_modules"
+ ".git" | ".loomweave" | ".venv" | "target" | "node_modules"
) {
continue;
}
@@ -761,7 +761,7 @@ fn now_iso8601(conn: &Connection) -> Result {
#[cfg(test)]
mod tests {
use super::{overlay_scope, path_glob};
- use clarion_storage::glob_match;
+ use loomweave_storage::glob_match;
#[test]
fn root_overlay_scope_glob_matches_project_relative_paths() {
diff --git a/crates/clarion-cli/tests/analyze.rs b/crates/loomweave-cli/tests/analyze.rs
similarity index 91%
rename from crates/clarion-cli/tests/analyze.rs
rename to crates/loomweave-cli/tests/analyze.rs
index ee8ecd4f..ef1651a8 100644
--- a/crates/clarion-cli/tests/analyze.rs
+++ b/crates/loomweave-cli/tests/analyze.rs
@@ -1,14 +1,14 @@
-//! `clarion analyze` Sprint-1 integration test.
+//! `loomweave analyze` Sprint-1 integration test.
use assert_cmd::Command;
use rusqlite::Connection;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -16,7 +16,7 @@ fn clarion_bin() -> Command {
}
fn latest_run_config(project_root: &std::path::Path) -> serde_json::Value {
- let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap();
let config_raw: String = conn
.query_row(
"SELECT config FROM runs ORDER BY started_at DESC LIMIT 1",
@@ -28,7 +28,7 @@ fn latest_run_config(project_root: &std::path::Path) -> serde_json::Value {
}
fn latest_run_stats(project_root: &std::path::Path) -> serde_json::Value {
- let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap();
let stats_raw: String = conn
.query_row(
"SELECT stats FROM runs ORDER BY started_at DESC LIMIT 1",
@@ -77,7 +77,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-calls",
+ "name": "loomweave-plugin-calls",
"version": "0.1.0",
"ontology_version": "0.4.0",
"capabilities": {},
@@ -170,11 +170,11 @@ while True:
#[cfg(unix)]
const AMBIGUOUS_CALLS_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-calls"
+name = "loomweave-plugin-calls"
plugin_id = "callsfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-calls"
+executable = "loomweave-plugin-calls"
language = "callsfixture"
extensions = ["call"]
@@ -187,7 +187,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module", "function"]
edge_kinds = ["contains", "calls"]
-rule_id_prefix = "CLA-CALLS-"
+rule_id_prefix = "LMWV-CALLS-"
ontology_version = "0.4.0"
[ontology.roles]
@@ -234,7 +234,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-imports",
+ "name": "loomweave-plugin-imports",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -291,11 +291,11 @@ while True:
#[cfg(unix)]
const IMPORTS_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-imports"
+name = "loomweave-plugin-imports"
plugin_id = "importsfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-imports"
+executable = "loomweave-plugin-imports"
language = "importsfixture"
extensions = ["imp"]
@@ -308,7 +308,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = ["imports"]
-rule_id_prefix = "CLA-IMPORTS-"
+rule_id_prefix = "LMWV-IMPORTS-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -363,7 +363,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-phase3",
+ "name": "loomweave-plugin-phase3",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -410,11 +410,11 @@ while True:
#[cfg(unix)]
const PHASE3_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-phase3"
+name = "loomweave-plugin-phase3"
plugin_id = "phase3fixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-phase3"
+executable = "loomweave-plugin-phase3"
language = "phase3fixture"
extensions = ["p3"]
@@ -427,7 +427,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = ["imports"]
-rule_id_prefix = "CLA-PHASE3-"
+rule_id_prefix = "LMWV-PHASE3-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -438,7 +438,7 @@ file_scope = ["module"]
fn write_ambiguous_calls_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-calls");
+ let plugin_script = plugin_dir.join("loomweave-plugin-calls");
std::fs::write(&plugin_script, AMBIGUOUS_CALLS_PLUGIN_SCRIPT)
.expect("write calls plugin script");
let mut perms = std::fs::metadata(&plugin_script)
@@ -458,7 +458,7 @@ fn write_ambiguous_calls_plugin(plugin_dir: &std::path::Path) {
fn write_imports_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-imports");
+ let plugin_script = plugin_dir.join("loomweave-plugin-imports");
std::fs::write(&plugin_script, IMPORTS_PLUGIN_SCRIPT).expect("write imports plugin script");
let mut perms = std::fs::metadata(&plugin_script)
.expect("stat imports plugin")
@@ -474,7 +474,7 @@ fn write_imports_plugin(plugin_dir: &std::path::Path) {
fn write_phase3_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-phase3");
+ let plugin_script = plugin_dir.join("loomweave-plugin-phase3");
std::fs::write(&plugin_script, PHASE3_PLUGIN_SCRIPT).expect("write phase3 plugin script");
let mut perms = std::fs::metadata(&plugin_script)
.expect("stat phase3 plugin")
@@ -492,7 +492,7 @@ fn run_phase3_fixture(stems: &[&str], config_yaml: &str) -> tempfile::TempDir {
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -501,12 +501,12 @@ fn run_phase3_fixture(stems: &[&str], config_yaml: &str) -> tempfile::TempDir {
std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n")
.expect("write phase3 fixture file");
}
- let config_path = project_dir.path().join("phase3-clarion.yaml");
+ let config_path = project_dir.path().join("phase3-loomweave.yaml");
std::fs::write(&config_path, config_yaml).expect("write phase3 config");
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg(project_dir.path())
@@ -578,7 +578,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-categorised",
+ "name": "loomweave-plugin-categorised",
"version": "0.1.0",
"ontology_version": "0.1.0",
"capabilities": {},
@@ -640,11 +640,11 @@ while True:
#[cfg(unix)]
const CATEGORISED_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-categorised"
+name = "loomweave-plugin-categorised"
plugin_id = "catfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-categorised"
+executable = "loomweave-plugin-categorised"
language = "catfixture"
extensions = ["cat"]
@@ -657,7 +657,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module", "function"]
edge_kinds = ["contains"]
-rule_id_prefix = "CLA-CAT-"
+rule_id_prefix = "LMWV-CAT-"
ontology_version = "0.1.0"
[ontology.roles]
@@ -669,7 +669,7 @@ callable = ["function"]
fn write_categorised_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-categorised");
+ let plugin_script = plugin_dir.join("loomweave-plugin-categorised");
std::fs::write(&plugin_script, CATEGORISED_PLUGIN_SCRIPT)
.expect("write categorised plugin script");
let mut perms = std::fs::metadata(&plugin_script)
@@ -787,7 +787,7 @@ fn analyze_persists_plugin_tags_and_populates_embedding_sidecar() {
write_categorised_plugin(plugin_dir.path());
let (embedding_url, embedding_server) = spawn_embedding_mock();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -797,7 +797,7 @@ fn analyze_persists_plugin_tags_and_populates_embedding_sidecar() {
"def main():\n pass\n",
)
.expect("write categorised fixture source");
- let config_path = project_dir.path().join("clarion.yaml");
+ let config_path = project_dir.path().join("loomweave.yaml");
std::fs::write(
&config_path,
format!(
@@ -818,7 +818,7 @@ semantic_search:
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg(project_dir.path())
@@ -839,7 +839,7 @@ semantic_search:
requests[0]
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let tag_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM entity_tags \
@@ -852,7 +852,7 @@ semantic_search:
.expect("query persisted tags");
assert_eq!(tag_count, 1, "plugin-emitted tags must be persisted");
- let sidecar = project_dir.path().join(".clarion/embeddings.db");
+ let sidecar = project_dir.path().join(".loomweave/embeddings.db");
assert!(sidecar.exists(), "analyze should create embeddings sidecar");
let sidecar_conn = Connection::open(sidecar).unwrap();
let embedding_count: i64 = sidecar_conn
@@ -874,26 +874,26 @@ semantic_search:
fn analyze_without_plugins_writes_skipped_run_row() {
let dir = tempfile::tempdir().unwrap();
- // Scrub PATH — if the developer or CI image has any clarion-plugin-*
+ // Scrub PATH — if the developer or CI image has any loomweave-plugin-*
// binary installed (including the project's own fixture), discovery
// will find it and the run transitions out of `skipped_no_plugins`.
// The sibling test `analyze_failrun_exits_nonzero_with_run_row_marked_failed`
// uses the same pattern.
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap();
let (count, status): (i64, String) = conn
.query_row(
"SELECT COUNT(*), COALESCE(MAX(status), '') FROM runs",
@@ -919,14 +919,14 @@ fn analyze_without_plugins_writes_skipped_run_row() {
#[test]
fn analyze_migrates_a_stale_db_instead_of_failing() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let db = dir.path().join(".clarion/clarion.db");
+ let db = dir.path().join(".loomweave/loomweave.db");
// Rewind to the pre-0007 (v6) shape: no `analyzed_at_commit`, no v7 ledger
// row, user_version back to 6 — exactly an upgraded-binary-vs-old-DB state.
{
@@ -943,7 +943,7 @@ fn analyze_migrates_a_stale_db_instead_of_failing() {
assert_eq!(uv, 6, "precondition: DB rewound to v6");
}
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(dir.path())
.env("PATH", "")
@@ -958,7 +958,7 @@ fn analyze_migrates_a_stale_db_instead_of_failing() {
.unwrap();
assert_eq!(
uv,
- i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION),
+ i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION),
"analyze must apply all pending migrations"
);
let has_column: i64 = conn
@@ -975,14 +975,14 @@ fn analyze_migrates_a_stale_db_instead_of_failing() {
fn analyze_default_config_records_clustering_defaults() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(dir.path())
.env("PATH", "")
@@ -1009,13 +1009,13 @@ fn analyze_default_config_records_clustering_defaults() {
fn analyze_config_file_overrides_clustering_seed_and_algorithm() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let config_path = dir.path().join("custom-clarion.yaml");
+ let config_path = dir.path().join("custom-loomweave.yaml");
std::fs::write(
&config_path,
r"
@@ -1028,7 +1028,7 @@ analysis:
)
.expect("write analyze config");
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg(dir.path())
@@ -1052,13 +1052,13 @@ analysis:
fn analyze_rejects_invalid_clustering_algorithm() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let config_path = dir.path().join("bad-clarion.yaml");
+ let config_path = dir.path().join("bad-loomweave.yaml");
std::fs::write(
&config_path,
r"
@@ -1069,7 +1069,7 @@ analysis:
)
.expect("write invalid analyze config");
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg(dir.path())
@@ -1082,7 +1082,7 @@ analysis:
"stderr should identify invalid clustering algorithm; got: {stderr}"
);
- let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap();
let run_count: i64 = conn
.query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0))
.expect("query run count");
@@ -1096,7 +1096,7 @@ fn analyze_phase3_emits_subsystem_entities_and_edges() {
&["auth_a", "auth_b", "billing_a", "billing_b"],
&phase3_config(2),
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let subsystem_count: i64 = conn
.query_row(
@@ -1138,7 +1138,7 @@ fn analyze_phase3_emits_subsystem_entities_and_edges() {
#[test]
fn analyze_phase3_is_deterministic_across_two_runs() {
fn signature(project_root: &std::path::Path) -> Vec<(String, String)> {
- let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap();
conn.prepare("SELECT id, properties FROM entities WHERE kind = 'subsystem' ORDER BY id")
.unwrap()
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
@@ -1158,7 +1158,7 @@ fn analyze_phase3_is_deterministic_across_two_runs() {
#[test]
fn analyze_phase3_skips_empty_graph_with_stats() {
let project_dir = run_phase3_fixture(&["solo"], &phase3_config(2));
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let subsystem_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM entities WHERE kind = 'subsystem'",
@@ -1186,11 +1186,11 @@ fn analyze_phase3_skips_empty_graph_with_stats() {
#[test]
fn analyze_phase3_emits_weak_modularity_fact_when_below_threshold() {
let project_dir = run_phase3_fixture(&["weak_a", "weak_b"], &phase3_config(2));
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let row: (String, String, String, String, String) = conn
.query_row(
"SELECT rule_id, kind, severity, status, properties \
- FROM findings WHERE rule_id = 'CLA-FACT-CLUSTERING-WEAK-MODULARITY'",
+ FROM findings WHERE rule_id = 'LMWV-FACT-CLUSTERING-WEAK-MODULARITY'",
[],
|row| {
Ok((
@@ -1203,7 +1203,7 @@ fn analyze_phase3_emits_weak_modularity_fact_when_below_threshold() {
},
)
.expect("query weak modularity finding");
- assert_eq!(row.0, "CLA-FACT-CLUSTERING-WEAK-MODULARITY");
+ assert_eq!(row.0, "LMWV-FACT-CLUSTERING-WEAK-MODULARITY");
assert_eq!(row.1, "fact");
assert_eq!(row.2, "INFO");
assert_eq!(row.3, "open");
@@ -1229,7 +1229,7 @@ fn phase3_project_for_rerun(stems: &[&str]) -> (tempfile::TempDir, tempfile::Tem
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -1238,7 +1238,7 @@ fn phase3_project_for_rerun(stems: &[&str]) -> (tempfile::TempDir, tempfile::Tem
std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n")
.expect("write phase3 fixture file");
}
- let config_path = project_dir.path().join("phase3-clarion.yaml");
+ let config_path = project_dir.path().join("phase3-loomweave.yaml");
std::fs::write(&config_path, phase3_config(2)).expect("write phase3 config");
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
@@ -1253,7 +1253,7 @@ fn run_phase3_analyze(
config_path: &std::path::Path,
plugin_path: &std::ffi::OsStr,
) {
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(config_path)
.arg(project_root)
@@ -1327,7 +1327,7 @@ fn spawn_capturing_filigree_mock(
}
/// clarion-ef8f64d5fd: the post-`CommitRun` deletion finding
-/// (`CLA-FACT-ENTITY-DELETED`) must reach Filigree in the SAME run, not only the
+/// (`LMWV-FACT-ENTITY-DELETED`) must reach Filigree in the SAME run, not only the
/// store. Phase-8 emission runs *before* `CommitRun`, while the SEI mint pass
/// persists deletion findings *after* it, so without a second emission pass the
/// finding is stranded store-only even with `emit_findings=true`. End-to-end:
@@ -1346,7 +1346,7 @@ fn analyze_emits_post_commit_deletion_finding_to_filigree() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let (base_url, server) = spawn_capturing_filigree_mock("CLA-FACT-ENTITY-DELETED");
+ let (base_url, server) = spawn_capturing_filigree_mock("LMWV-FACT-ENTITY-DELETED");
// Run 2: rewrite the config to enable emission against the mock, delete a
// source file, and re-run.
@@ -1362,7 +1362,7 @@ fn analyze_emits_post_commit_deletion_finding_to_filigree() {
let requests = server.join().expect("mock server thread");
let combined = requests.join("\n---POST BOUNDARY---\n");
assert!(
- combined.contains("CLA-FACT-ENTITY-DELETED"),
+ combined.contains("LMWV-FACT-ENTITY-DELETED"),
"the post-commit deletion finding must reach Filigree in the same run; \
captured {} POST(s): {combined}",
requests.len(),
@@ -1385,7 +1385,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() {
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
{
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
// Two subsystems → two tier findings, both anchored to the project root.
// auth disagrees (MIXING); billing agrees (UNANIMOUS). They share
// (rule-family, path, null line) but carry subsystem-distinct messages —
@@ -1397,7 +1397,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() {
seed_wardline_tier(&conn, "phase3fixture:module:billing_b", "trusted");
}
- let (base_url, server) = spawn_capturing_filigree_mock("CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS");
+ let (base_url, server) = spawn_capturing_filigree_mock("LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS");
std::fs::write(&config_path, phase3_config_with_filigree(2, &base_url))
.expect("rewrite config with filigree emission enabled");
@@ -1410,7 +1410,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() {
let requests = server.join().expect("mock server thread");
let posted = requests
.iter()
- .find(|r| r.contains("CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS"))
+ .find(|r| r.contains("LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS"))
.unwrap_or_else(|| {
panic!(
"the post-commit tier finding must reach Filigree; captured {} POST(s): {}",
@@ -1420,8 +1420,8 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() {
});
// Both subsystems' tier findings ride the one Phase-8c batch...
assert!(
- posted.contains("CLA-FACT-TIER-SUBSYSTEM-MIXING")
- && posted.contains("CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS"),
+ posted.contains("LMWV-FACT-TIER-SUBSYSTEM-MIXING")
+ && posted.contains("LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS"),
"both tier findings reach Filigree in one batch: {posted}"
);
// ...anchored to the project root and flagged synthetic (non-file) so a
@@ -1447,7 +1447,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() {
}
/// REQ-ANALYZE-04 verification (verbatim): run analyze, delete a file, re-run;
-/// assert a `CLA-FACT-ENTITY-DELETED` finding per previously-extracted entity in
+/// assert a `LMWV-FACT-ENTITY-DELETED` finding per previously-extracted entity in
/// the deleted file — and no false positives for entities still present.
#[cfg(unix)]
#[test]
@@ -1464,12 +1464,12 @@ fn analyze_emits_entity_deleted_finding_when_file_removed() {
&plugin_path,
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
// The plugin's `module` entity carries the canonical finding shape.
let (kind, severity, status): (String, String, String) = conn
.query_row(
"SELECT kind, severity, status FROM findings \
- WHERE rule_id = 'CLA-FACT-ENTITY-DELETED' \
+ WHERE rule_id = 'LMWV-FACT-ENTITY-DELETED' \
AND entity_id = 'phase3fixture:module:billing_a'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
@@ -1483,7 +1483,7 @@ fn analyze_emits_entity_deleted_finding_when_file_removed() {
// entities — the core-minted `core:file:*` and the plugin `module` — and
// nothing belonging to the surviving files.
let deleted: std::collections::BTreeSet = conn
- .prepare("SELECT entity_id FROM findings WHERE rule_id = 'CLA-FACT-ENTITY-DELETED'")
+ .prepare("SELECT entity_id FROM findings WHERE rule_id = 'LMWV-FACT-ENTITY-DELETED'")
.unwrap()
.query_map([], |row| row.get::<_, String>(0))
.unwrap()
@@ -1500,7 +1500,7 @@ fn analyze_emits_entity_deleted_finding_when_file_removed() {
}
/// REQ-ANALYZE-04: a guidance sheet whose `guides` edge targets a deleted entity
-/// produces `CLA-FACT-GUIDANCE-ORPHAN`, and the deleted entity's cached summaries
+/// produces `LMWV-FACT-GUIDANCE-ORPHAN`, and the deleted entity's cached summaries
/// are invalidated. Both halves are injected between runs (the fixture plugin
/// emits neither guidance sheets nor summaries), then a file is deleted + re-run.
#[cfg(unix)]
@@ -1510,7 +1510,7 @@ fn analyze_emits_guidance_orphan_and_invalidates_summary_cache_on_deletion() {
phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a", "billing_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
let target = "phase3fixture:module:billing_a";
// Inject a guidance sheet that `guides` the soon-to-be-deleted entity, plus a
@@ -1555,12 +1555,12 @@ fn analyze_emits_guidance_orphan_and_invalidates_summary_cache_on_deletion() {
let (rule_id, severity, anchor, related): (String, String, String, String) = conn
.query_row(
"SELECT rule_id, severity, entity_id, related_entities \
- FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-ORPHAN'",
+ FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-ORPHAN'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.expect("query guidance-orphan finding");
- assert_eq!(rule_id, "CLA-FACT-GUIDANCE-ORPHAN");
+ assert_eq!(rule_id, "LMWV-FACT-GUIDANCE-ORPHAN");
assert_eq!(severity, "WARN");
assert_eq!(anchor, "core:guidance:g1");
let related: serde_json::Value = serde_json::from_str(&related).unwrap();
@@ -1580,7 +1580,7 @@ fn analyze_emits_guidance_orphan_and_invalidates_summary_cache_on_deletion() {
}
/// T4a (WS6): a guidance sheet whose `match_rules` carries `{"type":"entity","id":X}`
-/// pointing at a deleted entity also produces `CLA-FACT-GUIDANCE-ORPHAN`. When the
+/// pointing at a deleted entity also produces `LMWV-FACT-GUIDANCE-ORPHAN`. When the
/// SAME deleted target is reachable via BOTH a `guides` edge and a `match_rule`, only
/// one finding is emitted for that (sheet, target) pair (idempotent run-scoped id).
#[cfg(unix)]
@@ -1590,7 +1590,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() {
phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a", "billing_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
let target = "phase3fixture:module:billing_a";
{
@@ -1638,7 +1638,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() {
let match_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM findings \
- WHERE rule_id = 'CLA-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_match'",
+ WHERE rule_id = 'LMWV-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_match'",
[],
|row| row.get(0),
)
@@ -1649,7 +1649,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() {
let both_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM findings \
- WHERE rule_id = 'CLA-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_both'",
+ WHERE rule_id = 'LMWV-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_both'",
[],
|row| row.get(0),
)
@@ -1660,7 +1660,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() {
);
}
-/// T4a (WS6): `CLA-FACT-GUIDANCE-EXPIRED` fires for a sheet whose `expires` is in
+/// T4a (WS6): `LMWV-FACT-GUIDANCE-EXPIRED` fires for a sheet whose `expires` is in
/// the past, and does NOT fire for a future `expires` or a sheet with no `expires`.
/// Runs on every analyze (independent of deletions), so this re-runs with no source
/// change. Severity INFO, confidence 1.0.
@@ -1670,7 +1670,7 @@ fn analyze_emits_guidance_expired_for_past_expiry_only() {
let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
{
let conn = Connection::open(&db_path).unwrap();
@@ -1701,7 +1701,7 @@ fn analyze_emits_guidance_expired_for_past_expiry_only() {
let conn = Connection::open(&db_path).unwrap();
let anchors: Vec = conn
- .prepare("SELECT entity_id FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-EXPIRED'")
+ .prepare("SELECT entity_id FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-EXPIRED'")
.unwrap()
.query_map([], |row| row.get(0))
.unwrap()
@@ -1712,7 +1712,7 @@ fn analyze_emits_guidance_expired_for_past_expiry_only() {
let (severity, confidence): (String, f64) = conn
.query_row(
"SELECT severity, confidence FROM findings \
- WHERE rule_id = 'CLA-FACT-GUIDANCE-EXPIRED'",
+ WHERE rule_id = 'LMWV-FACT-GUIDANCE-EXPIRED'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
@@ -1730,7 +1730,7 @@ fn analyze_emits_guidance_expired_under_no_sei() {
let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
{
let conn = Connection::open(&db_path).unwrap();
@@ -1748,7 +1748,7 @@ fn analyze_emits_guidance_expired_under_no_sei() {
.unwrap();
}
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(std::path::Path::new(&config_path))
.arg("--no-sei")
@@ -1760,7 +1760,7 @@ fn analyze_emits_guidance_expired_under_no_sei() {
let conn = Connection::open(&db_path).unwrap();
let count: i64 = conn
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-EXPIRED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-EXPIRED'",
[],
|row| row.get(0),
)
@@ -1768,7 +1768,7 @@ fn analyze_emits_guidance_expired_under_no_sei() {
assert_eq!(count, 1, "EXPIRED must fire under --no-sei");
}
-/// T4a (WS6): `CLA-FACT-GUIDANCE-CHURN-STALE` asymmetric threshold. A pinned sheet
+/// T4a (WS6): `LMWV-FACT-GUIDANCE-CHURN-STALE` asymmetric threshold. A pinned sheet
/// matching entities whose aggregate `git_churn_count` is in [20, 49] fires; an
/// identical non-pinned sheet at the same churn does not. Below 20 neither fires;
/// at/above 50 both fire. With churn unpopulated (production), nothing fires.
@@ -1778,7 +1778,7 @@ fn analyze_emits_guidance_churn_stale_with_asymmetric_pinned_threshold() {
let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
// Seed git_churn_count on the matched module via properties JSON (the analyze
// pipeline does not populate it). A `kind:module` match_rule selects both
@@ -1818,7 +1818,7 @@ fn analyze_emits_guidance_churn_stale_with_asymmetric_pinned_threshold() {
let fired: i64 = conn
.query_row(
"SELECT COUNT(*) FROM findings \
- WHERE rule_id = 'CLA-FACT-GUIDANCE-CHURN-STALE' AND entity_id = ?1",
+ WHERE rule_id = 'LMWV-FACT-GUIDANCE-CHURN-STALE' AND entity_id = ?1",
rusqlite::params![format!("core:guidance:{slug}")],
|row| row.get(0),
)
@@ -1848,7 +1848,7 @@ fn analyze_emits_guidance_churn_stale_with_asymmetric_pinned_threshold() {
let (severity, confidence): (String, f64) = conn
.query_row(
"SELECT severity, confidence FROM findings \
- WHERE rule_id = 'CLA-FACT-GUIDANCE-CHURN-STALE' LIMIT 1",
+ WHERE rule_id = 'LMWV-FACT-GUIDANCE-CHURN-STALE' LIMIT 1",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
@@ -1866,7 +1866,7 @@ fn analyze_guidance_churn_stale_is_honest_empty_without_churn() {
let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
{
let conn = Connection::open(&db_path).unwrap();
@@ -1895,7 +1895,7 @@ fn analyze_guidance_churn_stale_is_honest_empty_without_churn() {
let conn = Connection::open(&db_path).unwrap();
let count: i64 = conn
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-CHURN-STALE'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-CHURN-STALE'",
[],
|row| row.get(0),
)
@@ -2030,7 +2030,7 @@ fn analyze_generates_pinned_wardline_derived_guidance() {
phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
write_wardline_manifest(project_dir.path(), "Keep integral code isolated.");
run_phase3_analyze(
@@ -2094,7 +2094,7 @@ fn analyze_accepts_real_wardline_output_bundle() {
let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["seed"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
write_real_wardline_output_fixture(project_dir.path());
run_phase3_analyze(
@@ -2200,7 +2200,7 @@ fn analyze_preserves_wardline_override_and_emits_guidance_stale() {
phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
write_wardline_manifest(project_dir.path(), "Initial Wardline guidance.");
run_phase3_analyze(
project_dir.path(),
@@ -2248,7 +2248,7 @@ fn analyze_preserves_wardline_override_and_emits_guidance_stale() {
let (severity, confidence, evidence): (String, f64, String) = conn
.query_row(
"SELECT severity, confidence, evidence FROM findings \
- WHERE rule_id = 'CLA-FACT-GUIDANCE-STALE' \
+ WHERE rule_id = 'LMWV-FACT-GUIDANCE-STALE' \
AND entity_id = 'core:guidance:wardline-tier-integral'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
@@ -2300,8 +2300,8 @@ fn findings_by_rule(conn: &Connection, rule_id: &str) -> Vec<(String, String, St
}
/// REQ-ANALYZE-05 verification (verbatim): a fixture with mixed Wardline tiers in
-/// a subsystem produces `CLA-FACT-TIER-SUBSYSTEM-MIXING`; a uniform-tier subsystem
-/// produces `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS`. Tier facts are seeded between
+/// a subsystem produces `LMWV-FACT-TIER-SUBSYSTEM-MIXING`; a uniform-tier subsystem
+/// produces `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS`. Tier facts are seeded between
/// runs (analyze never writes them — the enrich-only axiom), so run 1 builds the
/// subsystems and run 2 emits the findings against the seeded tiers.
#[cfg(unix)]
@@ -2311,7 +2311,7 @@ fn analyze_emits_tier_mixing_and_unanimous_findings() {
phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a", "billing_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
{
let conn = Connection::open(&db_path).unwrap();
@@ -2330,7 +2330,7 @@ fn analyze_emits_tier_mixing_and_unanimous_findings() {
);
let conn = Connection::open(&db_path).unwrap();
- let mixing = findings_by_rule(&conn, "CLA-FACT-TIER-SUBSYSTEM-MIXING");
+ let mixing = findings_by_rule(&conn, "LMWV-FACT-TIER-SUBSYSTEM-MIXING");
assert_eq!(mixing.len(), 1, "exactly the auth subsystem mixes tiers");
let related: serde_json::Value = serde_json::from_str(&mixing[0].1).unwrap();
assert_eq!(
@@ -2341,7 +2341,7 @@ fn analyze_emits_tier_mixing_and_unanimous_findings() {
assert_eq!(evidence["tier_distribution"]["public"], 1);
assert_eq!(evidence["tier_distribution"]["internal"], 1);
- let unanimous = findings_by_rule(&conn, "CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS");
+ let unanimous = findings_by_rule(&conn, "LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS");
assert_eq!(
unanimous.len(),
1,
@@ -2371,7 +2371,7 @@ fn analyze_resolves_function_tier_through_contains_chain_to_subsystem() {
let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]);
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
let func = "phase3fixture:function:auth_a.handler";
{
@@ -2405,7 +2405,7 @@ fn analyze_resolves_function_tier_through_contains_chain_to_subsystem() {
);
let conn = Connection::open(&db_path).unwrap();
- let unanimous = findings_by_rule(&conn, "CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS");
+ let unanimous = findings_by_rule(&conn, "LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS");
assert_eq!(
unanimous.len(),
1,
@@ -2432,11 +2432,11 @@ analysis:
weak_modularity_threshold: 0.0
",
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let finding_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM findings \
- WHERE rule_id = 'CLA-FACT-CLUSTERING-WEAK-MODULARITY'",
+ WHERE rule_id = 'LMWV-FACT-CLUSTERING-WEAK-MODULARITY'",
[],
|row| row.get(0),
)
@@ -2461,11 +2461,11 @@ fn analyze_phase3_does_not_emit_weak_modularity_fact_when_threshold_is_met() {
&["auth_a", "auth_b", "billing_a", "billing_b"],
&phase3_config(2),
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let finding_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM findings \
- WHERE rule_id = 'CLA-FACT-CLUSTERING-WEAK-MODULARITY'",
+ WHERE rule_id = 'LMWV-FACT-CLUSTERING-WEAK-MODULARITY'",
[],
|row| row.get(0),
)
@@ -2492,7 +2492,7 @@ fn analyze_phase3_min_cluster_size_drops_undersized_weighted_components() {
&["auth_a", "auth_b", "billing_a", "billing_b"],
&phase3_weighted_components_config(3),
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let subsystem_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM entities WHERE kind = 'subsystem'",
@@ -2529,7 +2529,7 @@ fn analyze_phase3_persists_weighted_components_algorithm_when_selected() {
&["auth_a", "auth_b", "billing_a", "billing_b"],
&phase3_weighted_components_config(2),
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let properties_json: String = conn
.query_row(
"SELECT properties FROM entities \
@@ -2553,16 +2553,16 @@ fn analyze_phase3_persists_weighted_components_algorithm_when_selected() {
}
#[test]
-fn analyze_fails_cleanly_if_clarion_dir_missing() {
+fn analyze_fails_cleanly_if_loomweave_dir_missing() {
let dir = tempfile::tempdir().unwrap();
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["analyze"])
.arg(dir.path())
.assert()
.failure();
let stderr = String::from_utf8(out.get_output().stderr.clone()).unwrap();
assert!(
- stderr.contains("clarion install"),
+ stderr.contains("loomweave install"),
"error did not point operator at install: {stderr}"
);
}
@@ -2574,7 +2574,7 @@ fn analyze_stats_reports_ambiguous_edges_total() {
let plugin_dir = tempfile::tempdir().unwrap();
write_ambiguous_calls_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -2584,14 +2584,14 @@ fn analyze_stats_reports_ambiguous_edges_total() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.success();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let stats_raw: String = conn
.query_row("SELECT stats FROM runs LIMIT 1", [], |row| row.get(0))
.expect("query runs.stats");
@@ -2671,7 +2671,7 @@ fn analyze_mints_core_file_entity_for_registry_resolution() {
let plugin_dir = tempfile::tempdir().unwrap();
write_ambiguous_calls_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -2681,15 +2681,15 @@ fn analyze_mints_core_file_entity_for_registry_resolution() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.success();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
- let resolved = clarion_storage::resolve_file(&conn, project_dir.path(), "demo.call", "")
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
+ let resolved = loomweave_storage::resolve_file(&conn, project_dir.path(), "demo.call", "")
.expect("resolve_file should not error")
.expect("analyzed ordinary source file should resolve as a core file entity");
@@ -2712,7 +2712,7 @@ fn analyze_filters_external_import_edges_before_writer_insert() {
let plugin_dir = tempfile::tempdir().unwrap();
write_imports_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -2727,14 +2727,14 @@ fn analyze_filters_external_import_edges_before_writer_insert() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.success();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let import_edges: Vec<(String, String)> = conn
.prepare("SELECT from_id, to_id FROM edges WHERE kind = 'imports' ORDER BY from_id, to_id")
.unwrap()
@@ -2762,10 +2762,10 @@ fn analyze_filters_external_import_edges_before_writer_insert() {
}
/// Regression for wp2 review-2 (clarion-f56dc6ee43): `FailRun` must exit
-/// non-zero so `clarion analyze && next` chains and CI gating work.
+/// non-zero so `loomweave analyze && next` chains and CI gating work.
///
/// Triggers the discovery-errors `FailRun` branch by placing a
-/// `clarion-plugin-*` executable on `$PATH` next to a malformed
+/// `loomweave-plugin-*` executable on `$PATH` next to a malformed
/// `plugin.toml`. Before the fix, this exited 0; after, it exits non-zero
/// AND the `runs.status` column still reads `failed` (the run row is
/// marked before the bail).
@@ -2777,16 +2777,16 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
.success();
- // Put a `clarion-plugin-broken` on the synthetic PATH alongside a
+ // Put a `loomweave-plugin-broken` on the synthetic PATH alongside a
// malformed plugin.toml. Discovery will try to parse the toml and
// collect the error; with no compliant plugins, FailRun fires.
- let plugin_bin = plugin_dir.path().join("clarion-plugin-broken");
+ let plugin_bin = plugin_dir.path().join("loomweave-plugin-broken");
symlink("/bin/true", &plugin_bin).expect("symlink /bin/true");
std::fs::write(
plugin_dir.path().join("plugin.toml"),
@@ -2796,8 +2796,8 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() {
// Scrub the ambient PATH — build the child's PATH from ONLY the
// broken-plugin dir. If we inherited the parent's PATH, a real
- // `clarion-plugin-*` binary installed on the developer's machine
- // (e.g. `clarion-plugin-python` under ~/.local/bin) would be
+ // `loomweave-plugin-*` binary installed on the developer's machine
+ // (e.g. `loomweave-plugin-python` under ~/.local/bin) would be
// discovered, the run would complete cleanly, and this FailRun test
// would fail with "Unexpected success". The sibling tests
// (`analyze_resume_*`, `analyze_prune_unseen_*`) build their PATH the
@@ -2805,7 +2805,7 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() {
let new_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).expect("join_paths");
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &new_path)
@@ -2820,7 +2820,7 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() {
// The run row must still be marked `failed` — the FailRun WriterCmd
// runs before the bail, so the DB state is consistent with the exit
// code.
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let status: String = conn
.query_row(
"SELECT status FROM runs ORDER BY started_at DESC LIMIT 1",
@@ -2862,8 +2862,8 @@ integrations:
/// WP9-B: emission is best-effort. With Filigree enabled but unreachable, the
/// analyze run must still complete (exit 0, run row `completed`) and record the
-/// failure in `stats.json` as `CLA-INFRA-FILIGREE-UNREACHABLE` — the enrich-only
-/// federation contract: a sibling being down never changes Clarion's outcome.
+/// failure in `stats.json` as `LMWV-INFRA-FILIGREE-UNREACHABLE` — the enrich-only
+/// federation contract: a sibling being down never changes Loomweave's outcome.
#[cfg(unix)]
#[test]
fn analyze_finding_emission_is_best_effort_when_filigree_unreachable() {
@@ -2875,7 +2875,7 @@ fn analyze_finding_emission_is_best_effort_when_filigree_unreachable() {
// run_phase3_fixture already asserted the analyze invocation `.success()`;
// confirm the run row landed `completed` despite the emission failure.
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let status: String = conn
.query_row(
"SELECT status FROM runs ORDER BY started_at DESC LIMIT 1",
@@ -2897,7 +2897,7 @@ fn analyze_finding_emission_is_best_effort_when_filigree_unreachable() {
);
assert_eq!(
emission["rule_id"].as_str(),
- Some("CLA-INFRA-FILIGREE-UNREACHABLE"),
+ Some("LMWV-INFRA-FILIGREE-UNREACHABLE"),
);
assert!(
emission["endpoint"]
@@ -2954,7 +2954,7 @@ fn analyze_finding_emission_posts_and_records_emitted_on_success() {
"analyze POSTed to the scan-results route: {request}",
);
assert!(
- request.contains("\"scan_source\":\"clarion\""),
+ request.contains("\"scan_source\":\"loomweave\""),
"request body carries scan_source: {request}",
);
@@ -3009,7 +3009,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3018,7 +3018,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() {
std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n")
.expect("write phase3 fixture file");
}
- let config_path = project_dir.path().join("phase3-clarion.yaml");
+ let config_path = project_dir.path().join("phase3-loomweave.yaml");
std::fs::write(
&config_path,
phase3_config_with_filigree(2, &format!("http://{addr}")),
@@ -3027,7 +3027,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg(project_dir.path())
@@ -3037,7 +3037,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() {
// Capture the fresh run's id, then resume it (POST 2).
let run_id: String = {
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
conn.query_row(
"SELECT id FROM runs ORDER BY started_at DESC LIMIT 1",
[],
@@ -3045,7 +3045,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() {
)
.expect("read fresh run id")
};
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.args(["--resume", &run_id])
@@ -3067,7 +3067,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() {
);
// Resume reused the run row — exactly one row in `runs`, finalized.
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let run_rows: i64 = conn
.query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0))
.unwrap();
@@ -3093,7 +3093,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() {
}
/// REQ-FINDING-06 `--prune-unseen`: after emission, analyze POSTs a retention
-/// sweep to Filigree's loom `clean-stale` route, scoped to `scan_source=clarion`,
+/// sweep to Filigree's weft `clean-stale` route, scoped to `scan_source=loomweave`,
/// and records the soft-archive count in `stats.json`. End-to-end through a mock
/// Filigree that accepts both the emission POST and the prune POST.
#[cfg(unix)]
@@ -3107,7 +3107,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() {
// One body satisfies both parsers (serde(default) ignores the other's
// fields): scan-results counts + clean-stale counts.
let server = std::thread::spawn(move || {
- let body = r#"{"files_created":0,"files_updated":0,"findings_created":0,"findings_updated":0,"new_finding_ids":[],"observations_created":0,"observations_failed":0,"warnings":[],"findings_fixed":2,"scan_source":"clarion","older_than_days":30}"#;
+ let body = r#"{"files_created":0,"files_updated":0,"findings_created":0,"findings_updated":0,"new_finding_ids":[],"observations_created":0,"observations_failed":0,"warnings":[],"findings_fixed":2,"scan_source":"loomweave","older_than_days":30}"#;
let mut requests = Vec::new();
for _ in 0..2 {
let (mut stream, _) = listener.accept().expect("accept POST");
@@ -3128,7 +3128,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3137,7 +3137,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() {
std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n")
.expect("write phase3 fixture file");
}
- let config_path = project_dir.path().join("phase3-clarion.yaml");
+ let config_path = project_dir.path().join("phase3-loomweave.yaml");
std::fs::write(
&config_path,
phase3_config_with_filigree(2, &format!("http://{addr}")),
@@ -3146,7 +3146,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg("--prune-unseen")
@@ -3162,13 +3162,13 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() {
requests[0],
);
assert!(
- requests[1].contains("POST /api/loom/findings/clean-stale HTTP/1.1"),
- "second POST is the loom clean-stale sweep: {}",
+ requests[1].contains("POST /api/weft/findings/clean-stale HTTP/1.1"),
+ "second POST is the weft clean-stale sweep: {}",
requests[1],
);
assert!(
- requests[1].contains("\"scan_source\":\"clarion\""),
- "prune is scoped to scan_source=clarion: {}",
+ requests[1].contains("\"scan_source\":\"loomweave\""),
+ "prune is scoped to scan_source=loomweave: {}",
requests[1],
);
// Guard the wire field name: the live Filigree clean-stale route silently
@@ -3196,7 +3196,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() {
/// REQ-FINDING-06 `--prune-unseen` is enrich-only: with Filigree unreachable the
/// analyze run still completes and the sweep failure is recorded in `stats.json`
-/// as `CLA-INFRA-FILIGREE-UNREACHABLE` — never failing the run.
+/// as `LMWV-INFRA-FILIGREE-UNREACHABLE` — never failing the run.
#[cfg(unix)]
#[test]
fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() {
@@ -3210,9 +3210,9 @@ fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() {
// (port 1), so both emission and prune fail soft.
let plugin = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin.path());
- let config_path = project_dir.path().join("phase3-clarion.yaml");
+ let config_path = project_dir.path().join("phase3-loomweave.yaml");
let plugin_path = std::env::join_paths(std::iter::once(plugin.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg("--prune-unseen")
@@ -3221,7 +3221,7 @@ fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() {
.assert()
.success();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let run_status: String = conn
.query_row(
"SELECT status FROM runs ORDER BY started_at DESC LIMIT 1",
@@ -3241,7 +3241,7 @@ fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() {
);
assert_eq!(
stats["filigree_prune"]["rule_id"].as_str(),
- Some("CLA-INFRA-FILIGREE-UNREACHABLE"),
+ Some("LMWV-INFRA-FILIGREE-UNREACHABLE"),
);
}
@@ -3288,7 +3288,7 @@ fn analyze_does_not_emit_when_emit_findings_false() {
/// REQ-FINDING-06 `--prune-unseen` is enrich-only against a non-2xx response,
/// not just connection refusal: when Filigree answers the clean-stale POST with
/// HTTP 500, analyze still exits 0 with the run row `completed`, and the sweep
-/// failure is recorded in `stats.json` as `CLA-INFRA-FILIGREE-UNREACHABLE`. The
+/// failure is recorded in `stats.json` as `LMWV-INFRA-FILIGREE-UNREACHABLE`. The
/// 500 is well-formed (content-length present) so it exercises the client's
/// `!status.is_success()` branch rather than a torn-connection error.
#[cfg(unix)]
@@ -3331,7 +3331,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3340,7 +3340,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() {
std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n")
.expect("write phase3 fixture file");
}
- let config_path = project_dir.path().join("phase3-clarion.yaml");
+ let config_path = project_dir.path().join("phase3-loomweave.yaml");
std::fs::write(
&config_path,
phase3_config_with_filigree(2, &format!("http://{addr}")),
@@ -3349,7 +3349,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg("--prune-unseen")
@@ -3361,7 +3361,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() {
server.join().expect("mock server thread");
// A non-2xx clean-stale response must never fail the run.
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let run_status: String = conn
.query_row(
"SELECT status FROM runs ORDER BY started_at DESC LIMIT 1",
@@ -3381,7 +3381,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() {
);
assert_eq!(
stats["filigree_prune"]["rule_id"].as_str(),
- Some("CLA-INFRA-FILIGREE-UNREACHABLE"),
+ Some("LMWV-INFRA-FILIGREE-UNREACHABLE"),
);
}
@@ -3395,7 +3395,7 @@ fn analyze_prune_unseen_noops_when_filigree_disabled() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3404,12 +3404,12 @@ fn analyze_prune_unseen_noops_when_filigree_disabled() {
std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n")
.expect("write phase3 fixture file");
}
- let config_path = project_dir.path().join("phase3-clarion.yaml");
+ let config_path = project_dir.path().join("phase3-loomweave.yaml");
std::fs::write(&config_path, phase3_config(2)).expect("write phase3 config");
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--config"])
.arg(&config_path)
.arg("--prune-unseen")
@@ -3430,7 +3430,7 @@ fn analyze_prune_unseen_noops_when_filigree_disabled() {
);
}
-/// Wave 0 / WS3 (plan T1.4): after a successful `clarion analyze`, the
+/// Wave 0 / WS3 (plan T1.4): after a successful `loomweave analyze`, the
/// `sei_prior_index` snapshot must equal EXACTLY that run's entity set — stale
/// rows from the prior run removed. Two back-to-back runs on the same project
/// where the second drops a file prove the full-snapshot replace: the dropped
@@ -3443,7 +3443,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() {
use std::collections::BTreeSet;
fn prior_index_locators(project_root: &std::path::Path) -> BTreeSet {
- let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap();
conn.prepare("SELECT locator FROM sei_prior_index")
.unwrap()
.query_map([], |row| row.get::<_, String>(0))
@@ -3456,7 +3456,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() {
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3467,7 +3467,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
let analyze = |dir: &std::path::Path| {
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(dir)
.env("PATH", &plugin_path)
@@ -3508,7 +3508,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() {
// Column contract: body_hash populated (NOT NULL), recorded_at stamped, and
// signature still NULL in Wave 0 (the WS1 matcher fills it later).
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let (body_hash, recorded_at, signature): (String, String, Option) = conn
.query_row(
"SELECT body_hash, recorded_at, signature FROM sei_prior_index WHERE locator = ?1",
@@ -3528,7 +3528,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() {
fn alive_sei_bindings(
project_root: &std::path::Path,
) -> std::collections::BTreeMap {
- let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap();
conn.prepare(
"SELECT current_locator, sei FROM sei_bindings \
WHERE status = 'alive' AND current_locator IS NOT NULL",
@@ -3543,7 +3543,7 @@ fn alive_sei_bindings(
}
fn all_entity_ids(project_root: &std::path::Path) -> std::collections::BTreeSet {
- let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap();
conn.prepare("SELECT id FROM entities")
.unwrap()
.query_map([], |row| row.get::<_, String>(0))
@@ -3556,11 +3556,11 @@ fn all_entity_ids(project_root: &std::path::Path) -> std::collections::BTreeSet<
#[cfg_attr(not(unix), ignore = "fixture plugin script is a unix shebang")]
fn analyze_mints_alive_sei_binding_for_every_entity() {
// DoD: every alive entity has an alive `sei_bindings` row after analysis,
- // and every SEI carries the reserved `clarion:eid:` prefix (ADR-038).
+ // and every SEI carries the reserved `loomweave:eid:` prefix (ADR-038).
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3570,7 +3570,7 @@ fn analyze_mints_alive_sei_binding_for_every_entity() {
std::fs::write(project_dir.path().join("sei_alpha.p3"), b"module\n").unwrap();
std::fs::write(project_dir.path().join("sei_beta.p3"), b"module\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -3592,7 +3592,7 @@ fn analyze_mints_alive_sei_binding_for_every_entity() {
assert!(!bindings.is_empty(), "expected at least one minted SEI");
for (locator, sei) in &bindings {
assert!(
- sei.starts_with("clarion:eid:"),
+ sei.starts_with("loomweave:eid:"),
"SEI for {locator} must carry the reserved prefix: {sei}"
);
}
@@ -3607,7 +3607,7 @@ fn analyze_carries_sei_on_unchanged_rerun() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3615,7 +3615,7 @@ fn analyze_carries_sei_on_unchanged_rerun() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
let analyze = || {
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -3644,7 +3644,7 @@ fn analyze_no_sei_flag_skips_minting() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3653,7 +3653,7 @@ fn analyze_no_sei_flag_skips_minting() {
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
std::fs::write(project_dir.path().join("sei_delta.p3"), b"module\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--no-sei"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -3678,7 +3678,7 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3686,7 +3686,7 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
let analyze = || {
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -3709,7 +3709,7 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() {
std::fs::remove_file(project_dir.path().join("sei_drop.p3")).unwrap();
analyze();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
// The dropped entity's binding is now orphaned (by SEI — its row persists).
let dropped_status: String = conn
.query_row(
@@ -3748,15 +3748,15 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() {
// ── Wave 2 / T3.1: incremental analysis (skip unchanged files) + orphan guard ──
-/// Install Clarion + the phase3 fixture plugin into a fresh project. Returns the
+/// Install Loomweave + the phase3 fixture plugin into a fresh project. Returns the
/// project dir, the plugin dir (kept alive so the script stays on disk), and the
-/// `PATH` value that exposes the plugin to `clarion analyze`.
+/// `PATH` value that exposes the plugin to `loomweave analyze`.
#[cfg(unix)]
fn phase3_env() -> (tempfile::TempDir, tempfile::TempDir, std::ffi::OsString) {
let project_dir = tempfile::tempdir().unwrap();
let plugin_dir = tempfile::tempdir().unwrap();
write_phase3_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -3809,14 +3809,14 @@ fn analyze_stamps_entities_with_git_head_commit() {
run_git(project_dir.path(), &["commit", "-qm", "initial"]);
let head = git_stdout(project_dir.path(), &["rev-parse", "HEAD"]);
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &analyze_path)
.assert()
.success();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
for entity_id in ["core:file:demo.p3", "phase3fixture:module:demo"] {
let (first_seen, last_seen): (Option, Option) = conn
.query_row(
@@ -3848,7 +3848,7 @@ fn analyze_incremental_skip_does_not_orphan_unchanged_file_entities() {
// entities, every entity in every unchanged file would be falsely orphaned.
let (project_dir, _plugin_dir, plugin_path) = phase3_env();
let analyze = || {
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -3903,7 +3903,7 @@ fn analyze_incremental_skip_does_not_orphan_unchanged_file_entities() {
);
// And the binding's status is literally alive (belt-and-braces: alive_sei_bindings
// already filters status='alive', but assert no orphaned lineage was recorded).
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let orphaned_for_stable: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sei_lineage WHERE sei = ?1 AND event = 'orphaned'",
@@ -3926,7 +3926,7 @@ fn analyze_incremental_repeated_unchanged_runs_keep_skipping() {
// each skip BOTH files after the first.
let (project_dir, _plugin_dir, plugin_path) = phase3_env();
let analyze = || {
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -3968,13 +3968,13 @@ fn analyze_no_incremental_forces_full_reanalysis() {
std::fs::write(project_dir.path().join("full_a.p3"), b"module\n").unwrap();
std::fs::write(project_dir.path().join("full_b.p3"), b"module\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.success();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--no-incremental"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -4039,7 +4039,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-syn",
+ "name": "loomweave-plugin-syn",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -4069,11 +4069,11 @@ while True:
#[cfg(unix)]
const SYNTAX_ERROR_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-syn"
+name = "loomweave-plugin-syn"
plugin_id = "synfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-syn"
+executable = "loomweave-plugin-syn"
language = "synfixture"
extensions = ["syn"]
@@ -4086,7 +4086,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = []
-rule_id_prefix = "CLA-SYN-"
+rule_id_prefix = "LMWV-SYN-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -4098,7 +4098,7 @@ syntax_degraded_module = ["module"]
fn write_syntax_error_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-syn");
+ let plugin_script = plugin_dir.join("loomweave-plugin-syn");
std::fs::write(&plugin_script, SYNTAX_ERROR_PLUGIN_SCRIPT).expect("write syn plugin script");
let mut perms = std::fs::metadata(&plugin_script)
.expect("stat syn plugin")
@@ -4111,7 +4111,7 @@ fn write_syntax_error_plugin(plugin_dir: &std::path::Path) {
}
/// REQ-ANALYZE-06 verification (in part): a file that fails to parse produces a
-/// `CLA-PY-SYNTAX-ERROR` finding **persisted to the store**, anchored to the
+/// `LMWV-PY-SYNTAX-ERROR` finding **persisted to the store**, anchored to the
/// degraded module entity — not merely logged. A cleanly-parsed file produces
/// no such finding.
#[cfg(unix)]
@@ -4121,7 +4121,7 @@ fn analyze_persists_syntax_error_finding_for_unparseable_file() {
let plugin_dir = tempfile::tempdir().unwrap();
write_syntax_error_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -4131,25 +4131,25 @@ fn analyze_persists_syntax_error_finding_for_unparseable_file() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.success();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let (count, anchor): (i64, String) = conn
.query_row(
"SELECT COUNT(*), COALESCE(MIN(entity_id), '') FROM findings \
- WHERE rule_id = 'CLA-PY-SYNTAX-ERROR'",
+ WHERE rule_id = 'LMWV-PY-SYNTAX-ERROR'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.expect("query syntax-error findings");
assert_eq!(
count, 1,
- "exactly one CLA-PY-SYNTAX-ERROR finding persisted"
+ "exactly one LMWV-PY-SYNTAX-ERROR finding persisted"
);
assert_eq!(
anchor, "synfixture:module:broken_mod",
@@ -4207,7 +4207,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-crash",
+ "name": "loomweave-plugin-crash",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -4225,11 +4225,11 @@ while True:
#[cfg(unix)]
const CRASHING_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-crash"
+name = "loomweave-plugin-crash"
plugin_id = "crashfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-crash"
+executable = "loomweave-plugin-crash"
language = "crashfixture"
extensions = ["crx"]
@@ -4242,7 +4242,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = []
-rule_id_prefix = "CLA-CRASH-"
+rule_id_prefix = "LMWV-CRASH-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -4253,7 +4253,7 @@ file_scope = ["module"]
fn write_crashing_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-crash");
+ let plugin_script = plugin_dir.join("loomweave-plugin-crash");
std::fs::write(&plugin_script, CRASHING_PLUGIN_SCRIPT).expect("write crash plugin script");
let mut perms = std::fs::metadata(&plugin_script)
.expect("stat crash plugin")
@@ -4266,7 +4266,7 @@ fn write_crashing_plugin(plugin_dir: &std::path::Path) {
}
/// REQ-ANALYZE-06 verification (in part): a plugin that crashes mid-run produces
-/// a `CLA-INFRA-PLUGIN-CRASH` finding **persisted to the store**, anchored to the
+/// a `LMWV-INFRA-PLUGIN-CRASH` finding **persisted to the store**, anchored to the
/// synthetic `core:project:{name}` entity — not merely logged.
#[cfg(unix)]
#[test]
@@ -4275,7 +4275,7 @@ fn analyze_persists_crash_finding_anchored_to_project() {
let plugin_dir = tempfile::tempdir().unwrap();
write_crashing_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -4286,23 +4286,23 @@ fn analyze_persists_crash_finding_anchored_to_project() {
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
// A crashed plugin yields a non-zero exit (SoftFailed → CommitRun(Failed));
// the persisted finding is what we assert on.
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.failure();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let (count, anchor): (i64, String) = conn
.query_row(
"SELECT COUNT(*), COALESCE(MIN(entity_id), '') FROM findings \
- WHERE rule_id = 'CLA-INFRA-PLUGIN-CRASH'",
+ WHERE rule_id = 'LMWV-INFRA-PLUGIN-CRASH'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.expect("query crash findings");
- assert!(count >= 1, "a CLA-INFRA-PLUGIN-CRASH finding is persisted");
+ assert!(count >= 1, "a LMWV-INFRA-PLUGIN-CRASH finding is persisted");
let project_name = project_dir
.path()
.file_name()
@@ -4380,7 +4380,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-hang",
+ "name": "loomweave-plugin-hang",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -4398,11 +4398,11 @@ while True:
#[cfg(unix)]
const HANGING_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-hang"
+name = "loomweave-plugin-hang"
plugin_id = "hangfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-hang"
+executable = "loomweave-plugin-hang"
language = "hangfixture"
extensions = ["hng"]
@@ -4415,7 +4415,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = []
-rule_id_prefix = "CLA-HANG-"
+rule_id_prefix = "LMWV-HANG-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -4426,7 +4426,7 @@ file_scope = ["module"]
fn write_hanging_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-hang");
+ let plugin_script = plugin_dir.join("loomweave-plugin-hang");
std::fs::write(&plugin_script, HANGING_PLUGIN_SCRIPT).expect("write hang plugin script");
let mut perms = std::fs::metadata(&plugin_script)
.expect("stat hang plugin")
@@ -4440,8 +4440,8 @@ fn write_hanging_plugin(plugin_dir: &std::path::Path) {
/// REQ-ANALYZE-06 verification (in part): a plugin that hangs on a file is killed
/// by the per-file analysis-timeout watchdog and produces a persisted
-/// `CLA-PY-TIMEOUT` finding (and not a redundant `CLA-INFRA-PLUGIN-CRASH`). The
-/// timeout is set low via `CLARION_PLUGIN_FILE_TIMEOUT_MS` on the spawned process.
+/// `LMWV-PY-TIMEOUT` finding (and not a redundant `LMWV-INFRA-PLUGIN-CRASH`). The
+/// timeout is set low via `LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS` on the spawned process.
#[cfg(unix)]
#[test]
fn analyze_persists_timeout_finding_for_hanging_plugin() {
@@ -4449,7 +4449,7 @@ fn analyze_persists_timeout_finding_for_hanging_plugin() {
let plugin_dir = tempfile::tempdir().unwrap();
write_hanging_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -4458,24 +4458,24 @@ fn analyze_persists_timeout_finding_for_hanging_plugin() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
- .env("CLARION_PLUGIN_FILE_TIMEOUT_MS", "500")
+ .env("LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS", "500")
.assert()
.failure();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let (timeout_count, anchor): (i64, String) = conn
.query_row(
"SELECT COUNT(*), COALESCE(MIN(entity_id), '') FROM findings \
- WHERE rule_id = 'CLA-PY-TIMEOUT'",
+ WHERE rule_id = 'LMWV-PY-TIMEOUT'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.expect("query timeout findings");
- assert!(timeout_count >= 1, "a CLA-PY-TIMEOUT finding is persisted");
+ assert!(timeout_count >= 1, "a LMWV-PY-TIMEOUT finding is persisted");
let project_name = project_dir
.path()
.file_name()
@@ -4490,13 +4490,13 @@ fn analyze_persists_timeout_finding_for_hanging_plugin() {
// The generic crash finding is suppressed when a timeout is the root cause.
let crash_count: i64 = conn
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-PLUGIN-CRASH'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-PLUGIN-CRASH'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
crash_count, 0,
- "no redundant CLA-INFRA-PLUGIN-CRASH when the cause is a timeout"
+ "no redundant LMWV-INFRA-PLUGIN-CRASH when the cause is a timeout"
);
}
diff --git a/crates/clarion-cli/tests/analyze_failure_modes.rs b/crates/loomweave-cli/tests/analyze_failure_modes.rs
similarity index 92%
rename from crates/clarion-cli/tests/analyze_failure_modes.rs
rename to crates/loomweave-cli/tests/analyze_failure_modes.rs
index 3d7229c2..50e09c5c 100644
--- a/crates/clarion-cli/tests/analyze_failure_modes.rs
+++ b/crates/loomweave-cli/tests/analyze_failure_modes.rs
@@ -1,7 +1,7 @@
-//! Failure-mode integration tests for `clarion analyze`.
+//! Failure-mode integration tests for `loomweave analyze`.
//!
//! These exercise the run-outcome promotion logic in
-//! `crates/clarion-cli/src/analyze.rs::run_with_options` — specifically the
+//! `crates/loomweave-cli/src/analyze.rs::run_with_options` — specifically the
//! branch where the writer-actor rejects an `InsertEdge` mid-run and
//! `RunOutcome` must be set to `HardFailed` (→ `FailRun`) rather than
//! `SoftFailed` (→ `CommitRun(Failed)` with the full stats blob).
@@ -24,12 +24,12 @@ use std::os::unix::fs::PermissionsExt;
use assert_cmd::Command;
use rusqlite::Connection;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -39,7 +39,7 @@ fn clarion_bin() -> Command {
/// Tiny Python fixture plugin that declares an edge kind which the writer
/// does not know about (`bogus_edge`). The host accepts it (it appears in
/// `[ontology].edge_kinds`); the writer's `enforce_edge_contract` rejects
-/// it with `CLA-INFRA-EDGE-UNKNOWN-KIND` — a `StorageError::WriterProtocol`
+/// it with `LMWV-INFRA-EDGE-UNKNOWN-KIND` — a `StorageError::WriterProtocol`
/// surfaced from `Writer::send_wait` on the first `InsertEdge`.
///
/// This is the most realistic deterministic trigger for a mid-run
@@ -82,7 +82,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-bogus",
+ "name": "loomweave-plugin-bogus",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -114,7 +114,7 @@ while True:
# `bogus_edge` is declared in the manifest's ontology, so
# `PluginHost::process_edges` accepts it; the writer's
# `enforce_edge_contract` rejects it with
- # `CLA-INFRA-EDGE-UNKNOWN-KIND`. That is the mid-run
+ # `LMWV-INFRA-EDGE-UNKNOWN-KIND`. That is the mid-run
# writer-actor failure under test.
"edges": [
{
@@ -137,11 +137,11 @@ while True:
const BOGUS_EDGE_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-bogus"
+name = "loomweave-plugin-bogus"
plugin_id = "bogusfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-bogus"
+executable = "loomweave-plugin-bogus"
language = "bogusfixture"
extensions = ["bog"]
@@ -154,7 +154,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = ["bogus_edge"]
-rule_id_prefix = "CLA-BOGUS-"
+rule_id_prefix = "LMWV-BOGUS-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -206,7 +206,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-partial",
+ "name": "loomweave-plugin-partial",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -243,11 +243,11 @@ while True:
const PARTIAL_CRASH_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-partial"
+name = "loomweave-plugin-partial"
plugin_id = "partialfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-partial"
+executable = "loomweave-plugin-partial"
language = "partialfixture"
extensions = ["part"]
@@ -260,7 +260,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = []
-rule_id_prefix = "CLA-PARTIAL-"
+rule_id_prefix = "LMWV-PARTIAL-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -309,7 +309,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-cross-file",
+ "name": "loomweave-plugin-cross-file",
"version": "0.1.0",
"ontology_version": "0.6.0",
"capabilities": {},
@@ -380,11 +380,11 @@ while True:
const CROSS_FILE_EDGE_PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-cross-file"
+name = "loomweave-plugin-cross-file"
plugin_id = "crossfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-cross-file"
+executable = "loomweave-plugin-cross-file"
language = "crossfixture"
extensions = ["cross"]
@@ -397,7 +397,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module", "function"]
edge_kinds = ["calls"]
-rule_id_prefix = "CLA-CROSS-"
+rule_id_prefix = "LMWV-CROSS-"
ontology_version = "0.6.0"
[ontology.roles]
@@ -406,7 +406,7 @@ callable = ["function"]
"#;
fn write_bogus_edge_plugin(plugin_dir: &std::path::Path) {
- let plugin_script = plugin_dir.join("clarion-plugin-bogus");
+ let plugin_script = plugin_dir.join("loomweave-plugin-bogus");
std::fs::write(&plugin_script, BOGUS_EDGE_PLUGIN_SCRIPT)
.expect("write bogus edge plugin script");
let mut perms = std::fs::metadata(&plugin_script)
@@ -420,7 +420,7 @@ fn write_bogus_edge_plugin(plugin_dir: &std::path::Path) {
}
fn write_partial_crash_plugin(plugin_dir: &std::path::Path) {
- let plugin_script = plugin_dir.join("clarion-plugin-partial");
+ let plugin_script = plugin_dir.join("loomweave-plugin-partial");
std::fs::write(&plugin_script, PARTIAL_CRASH_PLUGIN_SCRIPT)
.expect("write partial crash plugin script");
let mut perms = std::fs::metadata(&plugin_script)
@@ -437,7 +437,7 @@ fn write_partial_crash_plugin(plugin_dir: &std::path::Path) {
}
fn write_cross_file_edge_plugin(plugin_dir: &std::path::Path) {
- let plugin_script = plugin_dir.join("clarion-plugin-cross-file");
+ let plugin_script = plugin_dir.join("loomweave-plugin-cross-file");
std::fs::write(&plugin_script, CROSS_FILE_EDGE_PLUGIN_SCRIPT)
.expect("write cross-file edge plugin script");
let mut perms = std::fs::metadata(&plugin_script)
@@ -459,7 +459,7 @@ fn analyze_defers_cross_file_edges_until_target_entity_batch_arrives() {
let plugin_dir = tempfile::tempdir().unwrap();
write_cross_file_edge_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.env("PATH", "")
@@ -472,14 +472,14 @@ fn analyze_defers_cross_file_edges_until_target_entity_batch_arrives() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.success();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let run_status: String = conn
.query_row(
"SELECT status FROM runs ORDER BY started_at DESC LIMIT 1",
@@ -513,7 +513,7 @@ fn analyze_defers_cross_file_edges_until_target_entity_batch_arrives() {
///
/// 1. End with `status = 'failed'`.
/// 2. Carry a `stats.failure_reason` naming the writer-actor failure
-/// (`CLA-INFRA-EDGE-UNKNOWN-KIND` from `enforce_edge_contract`), not a
+/// (`LMWV-INFRA-EDGE-UNKNOWN-KIND` from `enforce_edge_contract`), not a
/// plugin-crash reason.
/// 3. Have the minimal `FailRun` stats shape — no `entities_inserted`
/// key, distinguishing this from the `SoftFailed` path which writes the
@@ -530,7 +530,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() {
let plugin_dir = tempfile::tempdir().unwrap();
write_bogus_edge_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.env("PATH", "")
@@ -545,7 +545,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() {
// entities and edges first, changing the stats-shape discriminator.
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
@@ -557,7 +557,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() {
"stderr should mention failure; got: {stderr}"
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
// (1) Run row marked failed.
let (run_status, run_stats_raw): (String, String) = conn
@@ -580,7 +580,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() {
.as_str()
.expect("HardFailed stats must contain failure_reason");
assert!(
- failure_reason.contains("CLA-INFRA-EDGE-UNKNOWN-KIND"),
+ failure_reason.contains("LMWV-INFRA-EDGE-UNKNOWN-KIND"),
"failure_reason should cite the writer's edge-contract code; \
got: {failure_reason}"
);
@@ -623,7 +623,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() {
let crash_loop_findings: i64 = conn
.query_row(
"SELECT COUNT(*) FROM findings \
- WHERE rule_id = 'CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP'",
+ WHERE rule_id = 'LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP'",
[],
|row| row.get(0),
)
@@ -641,7 +641,7 @@ fn analyze_persists_completed_file_batches_when_plugin_later_crashes() {
let plugin_dir = tempfile::tempdir().unwrap();
write_partial_crash_plugin(plugin_dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.env("PATH", "")
@@ -652,14 +652,14 @@ fn analyze_persists_completed_file_batches_when_plugin_later_crashes() {
let plugin_path =
std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &plugin_path)
.assert()
.failure();
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let (run_status, run_stats_raw): (String, String) = conn
.query_row(
"SELECT status, stats FROM runs ORDER BY started_at DESC LIMIT 1",
diff --git a/crates/clarion-cli/tests/db.rs b/crates/loomweave-cli/tests/db.rs
similarity index 82%
rename from crates/clarion-cli/tests/db.rs
rename to crates/loomweave-cli/tests/db.rs
index f05858ab..34896081 100644
--- a/crates/clarion-cli/tests/db.rs
+++ b/crates/loomweave-cli/tests/db.rs
@@ -1,29 +1,29 @@
-//! `clarion db backup` integration tests (clarion-6d433b61ba / STO-04).
+//! `loomweave db backup` integration tests (clarion-6d433b61ba / STO-04).
use assert_cmd::Command;
use rusqlite::{Connection, OpenFlags};
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
cmd
}
-/// Seed a real `.clarion/clarion.db` under `root` with one identifiable row,
+/// Seed a real `.loomweave/loomweave.db` under `root` with one identifiable row,
/// left in WAL mode (the state a live analyze leaves behind).
fn seed_db(root: &std::path::Path) {
- let clarion_dir = root.join(".clarion");
- std::fs::create_dir_all(&clarion_dir).expect("mkdir .clarion");
- let db_path = clarion_dir.join("clarion.db");
+ let loomweave_dir = root.join(".loomweave");
+ std::fs::create_dir_all(&loomweave_dir).expect("mkdir .loomweave");
+ let db_path = loomweave_dir.join("loomweave.db");
let mut conn = Connection::open(&db_path).expect("open db");
- clarion_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas");
- clarion_storage::schema::apply_migrations(&mut conn).expect("migrate");
+ loomweave_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas");
+ loomweave_storage::schema::apply_migrations(&mut conn).expect("migrate");
conn.execute(
"INSERT INTO runs (id, started_at, completed_at, config, stats, status) \
VALUES (?1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), NULL, '{}', '{}', 'running')",
@@ -40,7 +40,7 @@ fn backup_produces_a_readable_standalone_copy() {
seed_db(dir.path());
let output = dir.path().join("snapshot.db");
- clarion_bin()
+ loomweave_bin()
.args(["db", "backup"])
.arg(&output)
.arg("--path")
@@ -86,7 +86,7 @@ fn backup_refuses_to_clobber_without_force() {
let output = dir.path().join("snapshot.db");
std::fs::write(&output, b"pre-existing precious file").unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["db", "backup"])
.arg(&output)
.arg("--path")
@@ -101,7 +101,7 @@ fn backup_refuses_to_clobber_without_force() {
);
// --force replaces it with a real backup.
- clarion_bin()
+ loomweave_bin()
.args(["db", "backup"])
.arg(&output)
.arg("--path")
@@ -122,10 +122,10 @@ fn backup_refuses_to_clobber_without_force() {
#[test]
fn backup_rejects_missing_source_db() {
let dir = tempfile::tempdir().unwrap();
- // No seed_db: .clarion/clarion.db does not exist.
+ // No seed_db: .loomweave/loomweave.db does not exist.
let output = dir.path().join("snapshot.db");
- clarion_bin()
+ loomweave_bin()
.args(["db", "backup"])
.arg(&output)
.arg("--path")
diff --git a/crates/loomweave-cli/tests/discovery_co_located.rs b/crates/loomweave-cli/tests/discovery_co_located.rs
new file mode 100644
index 00000000..c8d8246c
--- /dev/null
+++ b/crates/loomweave-cli/tests/discovery_co_located.rs
@@ -0,0 +1,91 @@
+//! Proves the production discovery path finds a plugin co-located in the same
+//! directory as the `loomweave` binary even when that directory is NOT on $PATH —
+//! the PyPI/venv install scenario (spec 2026-06-05-loomweave-pypi-distribution).
+#![cfg(unix)]
+
+use std::fs;
+use std::os::unix::fs::PermissionsExt;
+use std::path::Path;
+
+#[test]
+fn co_located_plugin_discovered_off_path() {
+ let tmp = tempfile::TempDir::new().unwrap();
+ let bin = tmp.path().join("bin");
+ fs::create_dir_all(&bin).unwrap();
+
+ // Copy the built loomweave binary into the staged bin/.
+ let staged = bin.join("loomweave");
+ fs::copy(env!("CARGO_BIN_EXE_loomweave"), &staged).unwrap();
+ set_exec(&staged);
+
+ // Sibling plugin executable + install-prefix manifest.
+ let plugin_exe = bin.join("loomweave-plugin-mocktest");
+ fs::write(&plugin_exe, b"#!/bin/sh\nexit 0\n").unwrap();
+ set_exec(&plugin_exe);
+ let share = tmp.path().join("share/loomweave/plugins/mocktest");
+ fs::create_dir_all(&share).unwrap();
+ fs::write(share.join("plugin.toml"), MOCK_MANIFEST).unwrap();
+
+ // Run the staged binary with an EMPTY PATH so discovery can ONLY succeed via
+ // the current_exe() level. Use a project dir so doctor has somewhere to look.
+ let proj = tmp.path().join("proj");
+ fs::create_dir_all(&proj).unwrap();
+ let output = std::process::Command::new(&staged)
+ .args(["doctor", "--format", "json", "--path"])
+ .arg(&proj)
+ .current_dir(&proj)
+ .env("PATH", "")
+ .env("HOME", tmp.path())
+ .output()
+ .unwrap();
+ let stdout = String::from_utf8_lossy(&output.stdout);
+
+ // Parse the doctor JSON and assert the plugin.availability check is "ok"
+ // and names the discovered mock plugin.
+ let report: serde_json::Value = serde_json::from_str(&stdout)
+ .unwrap_or_else(|e| panic!("doctor --format json not parseable: {e}\nstdout:\n{stdout}"));
+ let checks = report["checks"].as_array().expect("checks array");
+ let plugin_check = checks
+ .iter()
+ .find(|c| c["id"] == "plugin.availability")
+ .expect("plugin.availability check present");
+ assert_eq!(
+ plugin_check["status"], "ok",
+ "co-located plugin should be discovered off-PATH; report:\n{stdout}"
+ );
+ assert!(
+ plugin_check["message"]
+ .as_str()
+ .unwrap_or("")
+ .contains("mocktest"),
+ "plugin.availability message should name the discovered plugin; got: {plugin_check}"
+ );
+}
+
+fn set_exec(path: &Path) {
+ let mut perms = fs::metadata(path).unwrap().permissions();
+ perms.set_mode(0o755);
+ fs::set_permissions(path, perms).unwrap();
+}
+
+const MOCK_MANIFEST: &str = r#"[plugin]
+name = "loomweave-plugin-mocktest"
+plugin_id = "mocktest"
+version = "0.1.0"
+protocol_version = "1.0"
+executable = "loomweave-plugin-mocktest"
+language = "mocktest"
+extensions = ["mt"]
+
+[capabilities.runtime]
+expected_max_rss_mb = 256
+expected_entities_per_file = 100
+wardline_aware = false
+reads_outside_project_root = false
+
+[ontology]
+entity_kinds = ["function"]
+edge_kinds = ["calls"]
+rule_id_prefix = "LMWV-MT-"
+ontology_version = "0.1.0"
+"#;
diff --git a/crates/clarion-cli/tests/doctor.rs b/crates/loomweave-cli/tests/doctor.rs
similarity index 87%
rename from crates/clarion-cli/tests/doctor.rs
rename to crates/loomweave-cli/tests/doctor.rs
index 55f035da..4fff01d9 100644
--- a/crates/clarion-cli/tests/doctor.rs
+++ b/crates/loomweave-cli/tests/doctor.rs
@@ -1,4 +1,4 @@
-//! `clarion doctor [--fix]` integration tests.
+//! `loomweave doctor [--fix]` integration tests.
//!
//! Exercises the exit-code contract (healthy -> 0, any problem -> 1) and the
//! end-to-end `--fix` wiring across the three orientation surfaces. Per-surface
@@ -10,12 +10,12 @@ use std::path::Path;
use assert_cmd::Command;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -23,7 +23,7 @@ fn clarion_bin() -> Command {
}
fn install(args: &[&str], dir: &Path) {
- clarion_bin()
+ loomweave_bin()
.args(args)
.arg("--path")
.arg(dir)
@@ -37,7 +37,7 @@ fn read_yaml(path: &Path) -> serde_json::Value {
/// Run `doctor` (optionally with `--fix`) and return `(exit_code, stdout)`.
fn doctor(dir: &Path, fix: bool) -> (i32, String) {
- let mut cmd = clarion_bin();
+ let mut cmd = loomweave_bin();
cmd.arg("doctor");
if fix {
cmd.arg("--fix");
@@ -50,7 +50,7 @@ fn doctor(dir: &Path, fix: bool) -> (i32, String) {
}
fn doctor_json(dir: &Path, fix: bool) -> (i32, serde_json::Value) {
- let mut cmd = clarion_bin();
+ let mut cmd = loomweave_bin();
cmd.arg("doctor");
if fix {
cmd.arg("--fix");
@@ -82,13 +82,13 @@ fn doctor_reports_plain_install_healthy() {
assert!(out.contains("skill pack up to date"), "stdout:\n{out}");
assert!(out.contains("SessionStart hook present"), "stdout:\n{out}");
assert!(
- out.contains(".mcp.json clarion serve entry present"),
+ out.contains(".mcp.json loomweave serve entry present"),
"stdout:\n{out}"
);
}
/// `doctor --fix` registers the MCP entry; a subsequent plain `doctor` is then
-/// fully healthy and exits 0. The `.mcp.json` gains a `clarion` serve entry.
+/// fully healthy and exits 0. The `.mcp.json` gains a `loomweave` serve entry.
#[test]
fn doctor_fix_registers_mcp_then_reports_healthy() {
let dir = tempfile::tempdir().unwrap();
@@ -108,13 +108,13 @@ fn doctor_fix_registers_mcp_then_reports_healthy() {
let v: serde_json::Value =
serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap()).unwrap();
assert!(
- v["mcpServers"]["clarion"]["command"]
+ v["mcpServers"]["loomweave"]["command"]
.as_str()
.unwrap()
- .ends_with("clarion")
+ .ends_with("loomweave")
);
assert_eq!(
- v["mcpServers"]["clarion"]["args"],
+ v["mcpServers"]["loomweave"]["args"],
serde_json::json!(["serve"])
);
@@ -124,7 +124,7 @@ fn doctor_fix_registers_mcp_then_reports_healthy() {
}
/// `doctor --fix` preserves a sibling MCP server (e.g. filigree) already in
-/// `.mcp.json` while adding the clarion entry.
+/// `.mcp.json` while adding the loomweave entry.
#[test]
fn doctor_fix_preserves_sibling_mcp_server() {
let dir = tempfile::tempdir().unwrap();
@@ -148,10 +148,10 @@ fn doctor_fix_preserves_sibling_mcp_server() {
"sibling server must be preserved"
);
assert!(
- v["mcpServers"]["clarion"]["command"]
+ v["mcpServers"]["loomweave"]["command"]
.as_str()
.unwrap()
- .ends_with("clarion")
+ .ends_with("loomweave")
);
}
@@ -177,7 +177,7 @@ fn doctor_fix_repairs_missing_three_way_integration_bindings() {
assert_eq!(
code, 0,
"missing enrich-only integration bindings must NOT fail the gate (federation axiom: \
- Wardline is enrich-only, a Clarion-solo/Filigree-only project is first-class):\n{out}"
+ Wardline is enrich-only, a Loomweave-solo/Filigree-only project is first-class):\n{out}"
);
assert!(
out.contains("⚠ three-way integration bindings missing or stale"),
@@ -195,20 +195,20 @@ fn doctor_fix_repairs_missing_three_way_integration_bindings() {
"stdout:\n{out}"
);
- let clarion_yaml = read_yaml(&dir.path().join("clarion.yaml"));
+ let loomweave_yaml = read_yaml(&dir.path().join("loomweave.yaml"));
assert_eq!(
- clarion_yaml["integrations"]["filigree"]["base_url"],
+ loomweave_yaml["integrations"]["filigree"]["base_url"],
"http://127.0.0.1:8749"
);
assert_eq!(
- clarion_yaml["serve"]["http"]["wardline_taint_write"],
+ loomweave_yaml["serve"]["http"]["wardline_taint_write"],
serde_json::json!(true)
);
let wardline_yaml = read_yaml(&dir.path().join("wardline.yaml"));
assert_eq!(
wardline_yaml["filigree"]["url"],
- "http://127.0.0.1:8749/api/loom/scan-results"
+ "http://127.0.0.1:8749/api/weft/scan-results"
);
let mcp: serde_json::Value =
@@ -219,10 +219,10 @@ fn doctor_fix_repairs_missing_three_way_integration_bindings() {
"mcp",
"--root",
".",
- "--clarion-url",
+ "--loomweave-url",
"http://127.0.0.1:9111",
"--filigree-url",
- "http://127.0.0.1:8749/api/loom/scan-results"
+ "http://127.0.0.1:8749/api/weft/scan-results"
])
);
@@ -309,14 +309,14 @@ fn doctor_fix_json_reports_fixed_config_bindings() {
#[test]
fn doctor_reports_missing_hook_and_mcp_and_prints_index_block() {
let dir = tempfile::tempdir().unwrap();
- // Skill flags install ONLY the skill packs (no .clarion/, no hook, no mcp).
+ // Skill flags install ONLY the skill packs (no .loomweave/, no hook, no mcp).
install(&["install", "--skills", "--codex-skills"], dir.path());
let (code, out) = doctor(dir.path(), false);
assert_eq!(code, 1, "stdout:\n{out}");
assert!(out.contains("SessionStart hook missing"), "stdout:\n{out}");
assert!(
- out.contains(".mcp.json has no clarion serve entry"),
+ out.contains(".mcp.json has no loomweave serve entry"),
"stdout:\n{out}"
);
assert!(
@@ -328,7 +328,7 @@ fn doctor_reports_missing_hook_and_mcp_and_prints_index_block() {
assert!(out.contains("2 problems found"), "stdout:\n{out}");
}
-/// A hostile checkout can ship a `.mcp.json` whose `clarion` entry names an
+/// A hostile checkout can ship a `.mcp.json` whose `loomweave` entry names an
/// attacker-controlled `command` that the MCP client would later launch.
/// `doctor` must NOT report that as healthy (the false all-clear bug), but it
/// also must not clobber a possibly-deliberate wrapper: it flags the entry
@@ -342,7 +342,7 @@ fn doctor_flags_untrusted_mcp_command_without_clobbering_it() {
fs::write(
dir.path().join(".mcp.json"),
format!(
- r#"{{"mcpServers":{{"clarion":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"#
+ r#"{{"mcpServers":{{"loomweave":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"#
),
)
.unwrap();
@@ -368,7 +368,7 @@ fn doctor_flags_untrusted_mcp_command_without_clobbering_it() {
let v: serde_json::Value =
serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap()).unwrap();
assert_eq!(
- v["mcpServers"]["clarion"]["command"], "./evil-mcp.sh",
+ v["mcpServers"]["loomweave"]["command"], "./evil-mcp.sh",
"doctor --fix must not clobber a custom command"
);
diff --git a/crates/clarion-cli/tests/guidance.rs b/crates/loomweave-cli/tests/guidance.rs
similarity index 93%
rename from crates/clarion-cli/tests/guidance.rs
rename to crates/loomweave-cli/tests/guidance.rs
index 98c69757..aa27a044 100644
--- a/crates/clarion-cli/tests/guidance.rs
+++ b/crates/loomweave-cli/tests/guidance.rs
@@ -1,6 +1,6 @@
-//! `clarion guidance` authoring CLI integration tests (WS6 / REQ-GUIDANCE-03).
+//! `loomweave guidance` authoring CLI integration tests (WS6 / REQ-GUIDANCE-03).
//!
-//! Drives the real binary end-to-end against a seeded `.clarion/clarion.db`:
+//! Drives the real binary end-to-end against a seeded `.loomweave/loomweave.db`:
//! create (via `--content`), show, list (incl. `--for-entity`), edit (via a
//! fake `$EDITOR`), and delete. Verifies the written `properties` JSON matches
//! the shape the MCP read path consumes.
@@ -11,30 +11,30 @@ use serde_json::Value;
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener};
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
cmd
}
-/// Seed a real `.clarion/clarion.db` with the schema and one code entity (so
+/// Seed a real `.loomweave/loomweave.db` with the schema and one code entity (so
/// `--for-entity` has a target to match).
fn seed_db(root: &std::path::Path) {
- let clarion_dir = root.join(".clarion");
- std::fs::create_dir_all(&clarion_dir).expect("mkdir .clarion");
- let db_path = clarion_dir.join("clarion.db");
+ let loomweave_dir = root.join(".loomweave");
+ std::fs::create_dir_all(&loomweave_dir).expect("mkdir .loomweave");
+ let db_path = loomweave_dir.join("loomweave.db");
let mut conn = Connection::open(&db_path).expect("open db");
- clarion_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas");
- clarion_storage::schema::apply_migrations(&mut conn).expect("migrate");
+ loomweave_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas");
+ loomweave_storage::schema::apply_migrations(&mut conn).expect("migrate");
// A function entity under src/auth/ so path + kind rules can match it.
// `analyze` stores `source_file_path` as a *canonicalized* absolute path
- // (clarion_storage::query::normalize_source_path canonicalizes both root and
+ // (loomweave_storage::query::normalize_source_path canonicalizes both root and
// file), and `serve` / the CLI canonicalize project_root the same way. The
// file must exist on disk for canonicalize to resolve symlinks (e.g. macOS
// /tmp → /private/tmp), so create it before seeding — this makes the seeded
@@ -59,7 +59,7 @@ fn seed_db(root: &std::path::Path) {
}
fn properties(root: &std::path::Path, id: &str) -> Value {
- let db_path = root.join(".clarion").join("clarion.db");
+ let db_path = root.join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).expect("reopen db");
let raw: String = conn
.query_row(
@@ -76,7 +76,7 @@ fn properties(root: &std::path::Path, id: &str) -> Value {
/// for the `--expired` / `--stale` filter tests). Bypasses the CLI `create` path
/// deliberately — these tests exercise `list`, not authoring.
fn seed_sheet(root: &std::path::Path, slug: &str, properties: &Value) {
- let db_path = root.join(".clarion").join("clarion.db");
+ let db_path = root.join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).expect("open db for seed_sheet");
let id = format!("core:guidance:{slug}");
conn.execute(
@@ -91,7 +91,7 @@ fn seed_sheet(root: &std::path::Path, slug: &str, properties: &Value) {
/// Run `guidance list` with the given extra args and return stdout.
fn list_stdout(root: &std::path::Path, extra: &[&str]) -> String {
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "list"])
.args(["--path"])
.arg(root)
@@ -110,18 +110,18 @@ fn spawn_observations_server(detail: String) -> (SocketAddr, std::thread::JoinHa
let read = stream.read(&mut buf).expect("read observations request");
let request = String::from_utf8_lossy(&buf[..read]);
assert!(
- request.contains("GET /api/loom/observations?limit=100&offset=0 HTTP/1.1"),
+ request.contains("GET /api/weft/observations?limit=100&offset=0 HTTP/1.1"),
"unexpected request: {request}"
);
let body = serde_json::json!({
"items": [{
- "observation_id": "clarion-obs-guidance",
- "summary": "Clarion guidance proposal for python:function:auth.tokens.refresh",
+ "observation_id": "loomweave-obs-guidance",
+ "summary": "Loomweave guidance proposal for python:function:auth.tokens.refresh",
"detail": detail,
"file_path": "src/auth/tokens.py",
"line": 1,
"priority": 2,
- "actor": "clarion"
+ "actor": "loomweave"
}],
"limit": 100,
"offset": 0,
@@ -281,7 +281,7 @@ fn create_show_list_delete_lifecycle() {
seed_db(dir.path());
// create with explicit content + two match rules.
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -312,7 +312,7 @@ fn create_show_list_delete_lifecycle() {
);
// show prints the id + content.
- let show = clarion_bin()
+ let show = loomweave_bin()
.args(["guidance", "show", id])
.args(["--path"])
.arg(dir.path())
@@ -326,7 +326,7 @@ fn create_show_list_delete_lifecycle() {
);
// list (no filter) shows the sheet.
- let list = clarion_bin()
+ let list = loomweave_bin()
.args(["guidance", "list"])
.args(["--path"])
.arg(dir.path())
@@ -338,7 +338,7 @@ fn create_show_list_delete_lifecycle() {
);
// list --for-entity matches via path/kind rule.
- let filtered = clarion_bin()
+ let filtered = loomweave_bin()
.args(["guidance", "list"])
.args(["--path"])
.arg(dir.path())
@@ -351,7 +351,7 @@ fn create_show_list_delete_lifecycle() {
);
// delete removes it.
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "delete", id])
.args(["--path"])
.arg(dir.path())
@@ -359,7 +359,7 @@ fn create_show_list_delete_lifecycle() {
.success();
// show now fails (not found).
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "show", id])
.args(["--path"])
.arg(dir.path())
@@ -371,7 +371,7 @@ fn create_show_list_delete_lifecycle() {
fn promote_observation_creates_guidance_sheet() {
let dir = tempfile::tempdir().unwrap();
seed_db(dir.path());
- let proposal = clarion_storage::GuidanceProposal {
+ let proposal = loomweave_storage::GuidanceProposal {
entity_id: "python:function:auth.tokens.refresh".to_owned(),
content: "Escalate auth-token risk in summaries.".to_owned(),
scope_level: "function".to_owned(),
@@ -386,25 +386,27 @@ fn promote_observation_creates_guidance_sheet() {
let detail = proposal.to_observation_detail().unwrap();
let (addr, handle) = spawn_observations_server(detail);
std::fs::write(
- dir.path().join("clarion.yaml"),
+ dir.path().join("loomweave.yaml"),
format!(
- "integrations:\n filigree:\n enabled: true\n base_url: http://{addr}\n actor: clarion-test\n"
+ "integrations:\n filigree:\n enabled: true\n base_url: http://{addr}\n actor: loomweave-test\n"
),
)
.unwrap();
- let promoted = clarion_bin()
- .args(["guidance", "promote", "clarion-obs-guidance"])
+ let promoted = loomweave_bin()
+ .args(["guidance", "promote", "loomweave-obs-guidance"])
.args(["--path"])
.arg(dir.path())
// Dismissal is best-effort and uses the Filigree MCP subprocess; point it
// at a fast no-op so this test only owns the observation-read contract.
- .env("CLARION_FILIGREE_MCP_COMMAND", "/bin/true")
+ .env("LOOMWEAVE_FILIGREE_MCP_COMMAND", "/bin/true")
.assert()
.success();
let out = String::from_utf8_lossy(&promoted.get_output().stdout);
assert!(
- out.contains("Promoted observation clarion-obs-guidance to core:guidance:auth-token-risk"),
+ out.contains(
+ "Promoted observation loomweave-obs-guidance to core:guidance:auth-token-risk"
+ ),
"unexpected promote output: {out}"
);
handle.join().expect("observations server");
@@ -429,7 +431,7 @@ fn list_for_entity_matches_via_path_rule_only() {
let dir = tempfile::tempdir().unwrap();
seed_db(dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -440,7 +442,7 @@ fn list_for_entity_matches_via_path_rule_only() {
.assert()
.success();
- let matched = clarion_bin()
+ let matched = loomweave_bin()
.args(["guidance", "list"])
.args(["--path"])
.arg(dir.path())
@@ -453,7 +455,7 @@ fn list_for_entity_matches_via_path_rule_only() {
);
// A non-matching path must NOT list for this entity.
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -463,7 +465,7 @@ fn list_for_entity_matches_via_path_rule_only() {
.args(["--content", "billing guidance"])
.assert()
.success();
- let filtered = clarion_bin()
+ let filtered = loomweave_bin()
.args(["guidance", "list"])
.args(["--path"])
.arg(dir.path())
@@ -486,7 +488,7 @@ fn create_rejects_duplicate_id() {
let dir = tempfile::tempdir().unwrap();
seed_db(dir.path());
let make = || {
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -504,7 +506,7 @@ fn create_rejects_duplicate_id() {
fn create_rejects_bad_scope_level_and_bad_match() {
let dir = tempfile::tempdir().unwrap();
seed_db(dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -514,7 +516,7 @@ fn create_rejects_bad_scope_level_and_bad_match() {
.assert()
.failure();
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -532,7 +534,7 @@ fn create_normalizes_and_validates_expires() {
// A bare date is accepted and normalized to start-of-day UTC in the same
// 24-char `…Z` shape the read path's lexical expiry compare expects.
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -553,7 +555,7 @@ fn create_normalizes_and_validates_expires() {
// Proxy the read path: a future expiry must NOT be lexically < now, i.e. the
// sheet is not treated as already expired.
- let db_path = dir.path().join(".clarion").join("clarion.db");
+ let db_path = dir.path().join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).unwrap();
let now: String = conn
.query_row("SELECT strftime('%Y-%m-%dT%H:%M:%fZ','now')", [], |r| {
@@ -563,7 +565,7 @@ fn create_normalizes_and_validates_expires() {
assert!(stored > now.as_str(), "future expiry must sort after now");
// Garbage `--expires` is rejected up front (no sheet written).
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -582,7 +584,7 @@ fn edit_preserves_authored_at_and_provenance_changes_only_content() {
seed_db(dir.path());
let id = "core:guidance:edit-me";
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -608,7 +610,7 @@ fn edit_preserves_authored_at_and_provenance_changes_only_content() {
std::fs::set_permissions(&editor, perms).unwrap();
}
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "edit", id])
.args(["--path"])
.arg(dir.path())
@@ -639,7 +641,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() {
seed_db(dir.path());
let id = "core:guidance:dotenv-editor";
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -681,7 +683,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() {
)
.unwrap();
- let bin = assert_cmd::cargo::cargo_bin("clarion");
+ let bin = assert_cmd::cargo::cargo_bin("loomweave");
let path = std::env::var("PATH").unwrap_or_default();
let out = std::process::Command::new(&bin)
.current_dir(dir.path())
@@ -692,7 +694,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() {
.args(["--path"])
.arg(dir.path())
.output()
- .expect("clarion guidance edit");
+ .expect("loomweave guidance edit");
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
@@ -711,7 +713,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() {
fn edit_without_editor_set_fails_cleanly() {
let dir = tempfile::tempdir().unwrap();
seed_db(dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -722,7 +724,7 @@ fn edit_without_editor_set_fails_cleanly() {
.assert()
.success();
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "edit", "core:guidance:noeditor"])
.args(["--path"])
.arg(dir.path())
@@ -735,7 +737,7 @@ fn edit_without_editor_set_fails_cleanly() {
/// Seed one `summary_cache` row for the given entity (the column shape
/// `analyze` and the cache writer use).
fn seed_summary_cache(root: &std::path::Path, entity_id: &str) {
- let db_path = root.join(".clarion").join("clarion.db");
+ let db_path = root.join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).expect("open db");
conn.execute(
"INSERT INTO summary_cache \
@@ -750,7 +752,7 @@ fn seed_summary_cache(root: &std::path::Path, entity_id: &str) {
}
fn summary_cache_count(root: &std::path::Path, entity_id: &str) -> i64 {
- let db_path = root.join(".clarion").join("clarion.db");
+ let db_path = root.join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).expect("open db");
conn.query_row(
"SELECT COUNT(*) FROM summary_cache WHERE entity_id = ?1",
@@ -772,7 +774,7 @@ fn create_invalidates_cached_summary_for_matched_entity() {
1
);
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -800,7 +802,7 @@ fn create_non_matching_sheet_leaves_cache_intact() {
seed_db(dir.path());
seed_summary_cache(dir.path(), "python:function:auth.tokens.refresh");
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -823,7 +825,7 @@ fn delete_invalidates_cached_summary_for_matched_entity() {
let dir = tempfile::tempdir().unwrap();
seed_db(dir.path());
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -843,7 +845,7 @@ fn delete_invalidates_cached_summary_for_matched_entity() {
1
);
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "delete", "core:guidance:auth-sheet"])
.args(["--path"])
.arg(dir.path())
@@ -869,7 +871,7 @@ fn edit_invalidates_cached_summary_for_matched_entity() {
seed_db(dir.path());
let id = "core:guidance:edit-cache";
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -893,7 +895,7 @@ fn edit_invalidates_cached_summary_for_matched_entity() {
std::fs::set_permissions(&editor, perms).unwrap();
}
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "edit", id])
.args(["--path"])
.arg(dir.path())
@@ -917,7 +919,7 @@ fn edit_invalidates_cached_summary_for_matched_entity() {
/// Run `guidance export --to `.
fn export_to(root: &std::path::Path, to_dir: &std::path::Path) {
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "export"])
.args(["--path"])
.arg(root)
@@ -929,7 +931,7 @@ fn export_to(root: &std::path::Path, to_dir: &std::path::Path) {
/// Run `guidance import `.
fn import_from(root: &std::path::Path, from_dir: &std::path::Path) {
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "import"])
.args(["--path"])
.arg(root)
@@ -940,7 +942,7 @@ fn import_from(root: &std::path::Path, from_dir: &std::path::Path) {
/// Fetch a guidance sheet's (name, properties) tuple, or None if absent.
fn sheet_fields(root: &std::path::Path, id: &str) -> Option<(String, Value)> {
- let db_path = root.join(".clarion").join("clarion.db");
+ let db_path = root.join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).expect("reopen db");
conn.query_row(
"SELECT name, properties FROM entities WHERE id = ?1 AND kind = 'guidance'",
@@ -1103,7 +1105,7 @@ fn import_is_idempotent() {
assert_eq!(first, second, "re-import is a content no-op");
// Exactly one sheet, not duplicated.
- let db_path = dst.path().join(".clarion").join("clarion.db");
+ let db_path = dst.path().join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).unwrap();
let count: i64 = conn
.query_row(
@@ -1161,7 +1163,7 @@ fn import_fails_loudly_on_malformed_file() {
// A junk .json file in the import set.
std::fs::write(import_dir.path().join("broken.json"), "{ not valid json").unwrap();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "import"])
.args(["--path"])
.arg(dst.path())
@@ -1197,7 +1199,7 @@ fn import_rejects_code_entity_id_and_leaves_entity_intact() {
)
.unwrap();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "import"])
.args(["--path"])
.arg(dst.path())
@@ -1220,7 +1222,7 @@ fn import_rejects_code_entity_id_and_leaves_entity_intact() {
/// Fetch the raw (name, kind, `plugin_id`, properties) tuple for ANY entity (not
/// just guidance), or None.
fn sheet_props_raw(root: &std::path::Path, id: &str) -> Option<(String, String, String, String)> {
- let db_path = root.join(".clarion").join("clarion.db");
+ let db_path = root.join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).expect("reopen db");
conn.query_row(
"SELECT name, kind, plugin_id, properties FROM entities WHERE id = ?1",
@@ -1242,7 +1244,7 @@ fn import_invalidates_union_of_old_and_new_matches() {
// Seed a `class` entity too, so an OLD `kind:class` rule has a target.
{
- let db_path = dst.path().join(".clarion").join("clarion.db");
+ let db_path = dst.path().join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).unwrap();
conn.execute(
"INSERT INTO entities (id, plugin_id, kind, name, short_name, properties, \
@@ -1313,7 +1315,7 @@ fn delete_invalidates_guides_edge_target() {
seed_db(dir.path()); // seeds python:function:auth.tokens.refresh
// Author a sheet with NO match_rules (so only the guides edge can match).
- clarion_bin()
+ loomweave_bin()
.args(["guidance", "create"])
.args(["--path"])
.arg(dir.path())
@@ -1326,7 +1328,7 @@ fn delete_invalidates_guides_edge_target() {
// Manually wire a `guides` edge (no authoring path creates one today) and a
// cache row on the target.
{
- let db_path = dir.path().join(".clarion").join("clarion.db");
+ let db_path = dir.path().join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).unwrap();
conn.execute(
"INSERT INTO edges (kind, from_id, to_id, confidence) VALUES \
@@ -1340,7 +1342,7 @@ fn delete_invalidates_guides_edge_target() {
}
seed_summary_cache(dir.path(), "python:function:auth.tokens.refresh");
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "delete", "core:guidance:guides-sheet"])
.args(["--path"])
.arg(dir.path())
@@ -1404,7 +1406,7 @@ fn import_rejects_guidance_id_with_path_separator() {
)
.unwrap();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "import"])
.args(["--path"])
.arg(dst.path())
@@ -1430,7 +1432,7 @@ fn import_rejects_guidance_id_with_path_separator() {
fn export_flattens_legacy_guidance_id_path_separators() {
let src = tempfile::tempdir().unwrap();
seed_db(src.path());
- let db_path = src.path().join(".clarion").join("clarion.db");
+ let db_path = src.path().join(".loomweave").join("loomweave.db");
let conn = Connection::open(&db_path).expect("open db");
conn.execute(
"INSERT INTO entities (id, plugin_id, kind, name, short_name, properties, \
@@ -1490,7 +1492,7 @@ fn import_is_partial_but_safe_when_a_later_file_is_malformed() {
std::fs::write(import_dir.path().join("zzz-bad.json"), "{ not valid json").unwrap();
// The whole import fails loudly naming the bad file...
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["guidance", "import"])
.args(["--path"])
.arg(dst.path())
diff --git a/crates/clarion-cli/tests/hook.rs b/crates/loomweave-cli/tests/hook.rs
similarity index 69%
rename from crates/clarion-cli/tests/hook.rs
rename to crates/loomweave-cli/tests/hook.rs
index f55bc0b2..a87c43aa 100644
--- a/crates/clarion-cli/tests/hook.rs
+++ b/crates/loomweave-cli/tests/hook.rs
@@ -1,13 +1,13 @@
-//! `clarion hook session-start` integration tests.
+//! `loomweave hook session-start` integration tests.
use assert_cmd::Command;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -15,20 +15,20 @@ fn clarion_bin() -> Command {
}
#[test]
-fn hook_session_start_exits_zero_without_clarion_db() {
- // Fail-soft: no .clarion/ at all must still exit 0 and nudge.
+fn hook_session_start_exits_zero_without_loomweave_db() {
+ // Fail-soft: no .loomweave/ at all must still exit 0 and nudge.
let dir = tempfile::tempdir().unwrap();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["hook", "session-start", "--path"])
.arg(dir.path())
.assert()
.success();
let out = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(
- out.contains("clarion analyze"),
+ out.contains("loomweave analyze"),
"missing analyze nudge in: {out}"
);
- // Installing a skill into a project that never had one is `clarion install
+ // Installing a skill into a project that never had one is `loomweave install
// --skills`'s job, NOT the hook's. The hook only re-syncs a skill that is
// already present, so a bare project must come away with no skill roots
// created (clarion-ac0fc3bd86).
@@ -45,29 +45,33 @@ fn hook_session_start_exits_zero_without_clarion_db() {
#[test]
fn hook_session_start_prints_counts_for_installed_project() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
.success();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["hook", "session-start", "--path"])
.arg(dir.path())
.assert()
.success();
let out = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(out.contains("entities"), "missing entity count line: {out}");
- assert!(out.contains("clarion analyze"), "missing nudge: {out}");
+ assert!(out.contains("loomweave analyze"), "missing nudge: {out}");
}
#[test]
fn hook_session_start_exits_zero_with_corrupt_db() {
let dir = tempfile::tempdir().unwrap();
- std::fs::create_dir_all(dir.path().join(".clarion")).unwrap();
- // Garbage where clarion.db should be — not a valid SQLite file.
- std::fs::write(dir.path().join(".clarion/clarion.db"), b"NOT A SQLITE DB").unwrap();
- let assert = clarion_bin()
+ std::fs::create_dir_all(dir.path().join(".loomweave")).unwrap();
+ // Garbage where loomweave.db should be — not a valid SQLite file.
+ std::fs::write(
+ dir.path().join(".loomweave/loomweave.db"),
+ b"NOT A SQLITE DB",
+ )
+ .unwrap();
+ let assert = loomweave_bin()
.args(["hook", "session-start", "--path"])
.arg(dir.path())
.assert()
@@ -82,15 +86,17 @@ fn hook_session_start_exits_zero_with_corrupt_db() {
#[test]
fn hook_session_start_resyncs_skill_when_present_and_drifted() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--skills", "--path"])
.arg(dir.path())
.assert()
.success();
- let skill = dir.path().join(".claude/skills/clarion-workflow/SKILL.md");
+ let skill = dir
+ .path()
+ .join(".claude/skills/loomweave-workflow/SKILL.md");
std::fs::write(&skill, "STALE").unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["hook", "session-start", "--path"])
.arg(dir.path())
.assert()
@@ -98,7 +104,7 @@ fn hook_session_start_resyncs_skill_when_present_and_drifted() {
let body = std::fs::read_to_string(&skill).unwrap();
assert!(
- body.contains("name: clarion-workflow"),
+ body.contains("name: loomweave-workflow"),
"hook did not repair drifted skill: {body}"
);
}
diff --git a/crates/clarion-cli/tests/install.rs b/crates/loomweave-cli/tests/install.rs
similarity index 65%
rename from crates/clarion-cli/tests/install.rs
rename to crates/loomweave-cli/tests/install.rs
index 47b41ff2..dce89f54 100644
--- a/crates/clarion-cli/tests/install.rs
+++ b/crates/loomweave-cli/tests/install.rs
@@ -1,16 +1,16 @@
-//! `clarion install` integration tests.
+//! `loomweave install` integration tests.
use std::fs;
use assert_cmd::Command;
use rusqlite::Connection;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -22,29 +22,35 @@ fn read_yaml(path: &std::path::Path) -> serde_json::Value {
}
#[test]
-fn install_creates_clarion_dir_with_expected_contents() {
+fn install_creates_loomweave_dir_with_expected_contents() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
.success();
- let clarion = dir.path().join(".clarion");
- assert!(clarion.join("clarion.db").exists(), "clarion.db missing");
- assert!(clarion.join("config.json").exists(), "config.json missing");
- assert!(clarion.join(".gitignore").exists(), ".gitignore missing");
+ let loomweave = dir.path().join(".loomweave");
assert!(
- dir.path().join("clarion.yaml").exists(),
- "clarion.yaml not at project root"
+ loomweave.join("loomweave.db").exists(),
+ "loomweave.db missing"
+ );
+ assert!(
+ loomweave.join("config.json").exists(),
+ "config.json missing"
+ );
+ assert!(loomweave.join(".gitignore").exists(), ".gitignore missing");
+ assert!(
+ dir.path().join("loomweave.yaml").exists(),
+ "loomweave.yaml not at project root"
);
- let config = fs::read_to_string(clarion.join("config.json")).unwrap();
+ let config = fs::read_to_string(loomweave.join("config.json")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&config).unwrap();
assert_eq!(parsed["schema_version"], 1);
assert!(parsed["last_run_id"].is_null());
- let gitignore = fs::read_to_string(clarion.join(".gitignore")).unwrap();
+ let gitignore = fs::read_to_string(loomweave.join(".gitignore")).unwrap();
for rule in &[
"*.shadow.db",
"tmp/",
@@ -67,36 +73,36 @@ fn install_all_wires_three_way_integration_bindings() {
fs::create_dir_all(&filigree_dir).unwrap();
fs::write(filigree_dir.join("ephemeral.port"), "8749\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--all", "--path"])
.arg(dir.path())
.assert()
.success();
- let clarion_yaml = read_yaml(&dir.path().join("clarion.yaml"));
+ let loomweave_yaml = read_yaml(&dir.path().join("loomweave.yaml"));
assert_eq!(
- clarion_yaml["integrations"]["filigree"]["enabled"],
+ loomweave_yaml["integrations"]["filigree"]["enabled"],
serde_json::json!(true)
);
assert_eq!(
- clarion_yaml["integrations"]["filigree"]["base_url"],
+ loomweave_yaml["integrations"]["filigree"]["base_url"],
"http://127.0.0.1:8749"
);
assert_eq!(
- clarion_yaml["serve"]["http"]["enabled"],
+ loomweave_yaml["serve"]["http"]["enabled"],
serde_json::json!(true)
);
- assert_eq!(clarion_yaml["serve"]["http"]["bind"], "127.0.0.1:9111");
+ assert_eq!(loomweave_yaml["serve"]["http"]["bind"], "127.0.0.1:9111");
assert_eq!(
- clarion_yaml["serve"]["http"]["wardline_taint_write"],
+ loomweave_yaml["serve"]["http"]["wardline_taint_write"],
serde_json::json!(true)
);
let wardline_yaml = read_yaml(&dir.path().join("wardline.yaml"));
- assert_eq!(wardline_yaml["clarion"]["url"], "http://127.0.0.1:9111");
+ assert_eq!(wardline_yaml["loomweave"]["url"], "http://127.0.0.1:9111");
assert_eq!(
wardline_yaml["filigree"]["url"],
- "http://127.0.0.1:8749/api/loom/scan-results"
+ "http://127.0.0.1:8749/api/weft/scan-results"
);
let mcp: serde_json::Value =
@@ -107,10 +113,10 @@ fn install_all_wires_three_way_integration_bindings() {
"mcp",
"--root",
".",
- "--clarion-url",
+ "--loomweave-url",
"http://127.0.0.1:9111",
"--filigree-url",
- "http://127.0.0.1:8749/api/loom/scan-results"
+ "http://127.0.0.1:8749/api/weft/scan-results"
])
);
}
@@ -118,13 +124,13 @@ fn install_all_wires_three_way_integration_bindings() {
#[test]
fn install_applies_each_migration_exactly_once() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
.success();
- let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap();
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM schema_migrations", [], |row| {
row.get(0)
@@ -132,7 +138,7 @@ fn install_applies_each_migration_exactly_once() {
.unwrap();
assert_eq!(
count,
- i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION)
+ i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION)
);
let versions: Vec = {
let mut stmt = conn
@@ -142,21 +148,21 @@ fn install_applies_each_migration_exactly_once() {
rows.map(std::result::Result::unwrap).collect()
};
let expected: Vec =
- (1..=i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION)).collect();
+ (1..=i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION)).collect();
assert_eq!(versions, expected);
}
#[test]
-fn install_all_rejects_non_directory_clarion() {
- // Bug (PR#21 review #6): when `.clarion` already exists as a regular file
+fn install_all_rejects_non_directory_loomweave() {
+ // Bug (PR#21 review #6): when `.loomweave` already exists as a regular file
// and `--all` (a non-bare init) is run without `--force`, install treated
// it as "already initialised" and skipped DB creation, then proceeded to
- // install skills/hooks atop a project with no usable `.clarion/clarion.db`.
+ // install skills/hooks atop a project with no usable `.loomweave/loomweave.db`.
// It must instead refuse with a clear non-directory error.
let dir = tempfile::tempdir().unwrap();
- std::fs::write(dir.path().join(".clarion"), "i am a file, not a dir").unwrap();
+ std::fs::write(dir.path().join(".loomweave"), "i am a file, not a dir").unwrap();
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["install", "--all", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -165,19 +171,19 @@ fn install_all_rejects_non_directory_clarion() {
let stderr = String::from_utf8(out.get_output().stderr.clone()).unwrap();
assert!(
stderr.contains("non-directory"),
- "error did not mention the non-directory .clarion: {stderr}"
+ "error did not mention the non-directory .loomweave: {stderr}"
);
}
#[test]
-fn install_force_rejects_non_directory_clarion() {
+fn install_force_rejects_non_directory_loomweave() {
// The --force overwrite path has its own non-directory guard (distinct from
- // the --all skip-init guard): it can only remove an existing .clarion/
+ // the --all skip-init guard): it can only remove an existing .loomweave/
// *directory*, never a regular file masquerading as one.
let dir = tempfile::tempdir().unwrap();
- std::fs::write(dir.path().join(".clarion"), "i am a file, not a dir").unwrap();
+ std::fs::write(dir.path().join(".loomweave"), "i am a file, not a dir").unwrap();
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["install", "--force", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -185,23 +191,23 @@ fn install_force_rejects_non_directory_clarion() {
.failure();
let stderr = String::from_utf8(out.get_output().stderr.clone()).unwrap();
assert!(
- stderr.contains("can only overwrite an existing .clarion/ directory"),
+ stderr.contains("can only overwrite an existing .loomweave/ directory"),
"error did not mention the --force non-directory guard: {stderr}"
);
}
#[test]
-fn install_skips_clarion_init_when_dir_already_exists() {
+fn install_skips_loomweave_init_when_dir_already_exists() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
.success();
- // Second bare install must succeed: skip .clarion/ init but still apply
+ // Second bare install must succeed: skip .loomweave/ init but still apply
// skills/hooks idempotently and report "already initialised".
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
@@ -214,34 +220,37 @@ fn install_skips_clarion_init_when_dir_already_exists() {
}
#[test]
-fn install_force_replaces_existing_clarion_dir_without_overwriting_yaml() {
+fn install_force_replaces_existing_loomweave_dir_without_overwriting_yaml() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
.success();
- let clarion = dir.path().join(".clarion");
- fs::write(clarion.join("stale.tmp"), "stale").unwrap();
+ let loomweave = dir.path().join(".loomweave");
+ fs::write(loomweave.join("stale.tmp"), "stale").unwrap();
fs::write(
- dir.path().join("clarion.yaml"),
+ dir.path().join("loomweave.yaml"),
"version: 1\ncustom: true\n",
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--force", "--path"])
.arg(dir.path())
.assert()
.success();
assert!(
- !clarion.join("stale.tmp").exists(),
- "--force should remove stale .clarion/ contents"
+ !loomweave.join("stale.tmp").exists(),
+ "--force should remove stale .loomweave/ contents"
+ );
+ assert!(
+ loomweave.join("loomweave.db").exists(),
+ "loomweave.db missing"
);
- assert!(clarion.join("clarion.db").exists(), "clarion.db missing");
- let yaml = read_yaml(&dir.path().join("clarion.yaml"));
+ let yaml = read_yaml(&dir.path().join("loomweave.yaml"));
assert_eq!(yaml["custom"], serde_json::json!(true));
assert_eq!(
yaml["serve"]["http"]["wardline_taint_write"],
@@ -251,49 +260,49 @@ fn install_force_replaces_existing_clarion_dir_without_overwriting_yaml() {
#[cfg(unix)]
#[test]
-fn install_cleans_up_clarion_dir_when_post_mkdir_step_fails() {
- // Bug clarion-ed5017139f: `clarion install` left .clarion/ partially
+fn install_cleans_up_loomweave_dir_when_post_mkdir_step_fails() {
+ // Bug clarion-ed5017139f: `loomweave install` left .loomweave/ partially
// populated on failure, blocking re-install without manual rm -rf.
//
- // Reproducer: pre-create clarion.yaml as a *broken symlink* whose target
+ // Reproducer: pre-create loomweave.yaml as a *broken symlink* whose target
// sits under a non-existent parent dir. Install's `yaml_path.exists()`
// check follows symlinks → returns false → install attempts `fs::write`,
// which follows the symlink → tries to open a path under a non-existent
- // dir → ENOENT. By that point .clarion/ has been mkdir'd and populated;
+ // dir → ENOENT. By that point .loomweave/ has been mkdir'd and populated;
// the bug was leaving it on disk.
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().unwrap();
- let yaml = dir.path().join("clarion.yaml");
+ let yaml = dir.path().join("loomweave.yaml");
symlink(
- "/clarion-test-nonexistent-by-construction/never/exists/cannot-write",
+ "/loomweave-test-nonexistent-by-construction/never/exists/cannot-write",
&yaml,
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
.failure();
- let clarion = dir.path().join(".clarion");
+ let loomweave = dir.path().join(".loomweave");
assert!(
- !clarion.exists(),
- ".clarion/ should have been cleaned up after install failed, \
+ !loomweave.exists(),
+ ".loomweave/ should have been cleaned up after install failed, \
but it still exists at {}",
- clarion.display()
+ loomweave.display()
);
}
#[test]
-fn install_preserves_existing_clarion_yaml_keys_while_wiring_bindings() {
+fn install_preserves_existing_loomweave_yaml_keys_while_wiring_bindings() {
let dir = tempfile::tempdir().unwrap();
- let yaml_path = dir.path().join("clarion.yaml");
- let user_content = "# user-edited clarion.yaml\nversion: 1\ncustom_key: preserved\n";
+ let yaml_path = dir.path().join("loomweave.yaml");
+ let user_content = "# user-edited loomweave.yaml\nversion: 1\ncustom_key: preserved\n";
fs::write(&yaml_path, user_content).unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.assert()
@@ -312,25 +321,25 @@ fn install_preserves_existing_clarion_yaml_keys_while_wiring_bindings() {
}
#[test]
-fn install_claude_code_writes_mcp_json_without_initialising_clarion_dir() {
+fn install_claude_code_writes_mcp_json_without_initialising_loomweave_dir() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--claude-code", "--path"])
.arg(dir.path())
.assert()
.success();
assert!(
- !dir.path().join(".clarion").exists(),
- "--claude-code should not create .clarion/"
+ !dir.path().join(".loomweave").exists(),
+ "--claude-code should not create .loomweave/"
);
let raw = fs::read_to_string(dir.path().join(".mcp.json")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&raw).unwrap();
- let entry = &parsed["mcpServers"]["clarion"];
+ let entry = &parsed["mcpServers"]["loomweave"];
assert_eq!(entry["type"], "stdio");
assert!(
- entry["command"].as_str().unwrap().ends_with("clarion"),
- "command should point at a clarion executable: {entry:?}"
+ entry["command"].as_str().unwrap().ends_with("loomweave"),
+ "command should point at a loomweave executable: {entry:?}"
);
assert_eq!(
entry["args"],
@@ -340,11 +349,11 @@ fn install_claude_code_writes_mcp_json_without_initialising_clarion_dir() {
}
#[test]
-fn install_codex_writes_requested_config_without_initialising_clarion_dir() {
+fn install_codex_writes_requested_config_without_initialising_loomweave_dir() {
let dir = tempfile::tempdir().unwrap();
let codex_config = dir.path().join("codex-config.toml");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--codex", "--codex-config"])
.arg(&codex_config)
.args(["--path"])
@@ -353,12 +362,12 @@ fn install_codex_writes_requested_config_without_initialising_clarion_dir() {
.success();
assert!(
- !dir.path().join(".clarion").exists(),
- "--codex should not create .clarion/"
+ !dir.path().join(".loomweave").exists(),
+ "--codex should not create .loomweave/"
);
let raw = fs::read_to_string(&codex_config).unwrap();
assert!(
- raw.contains("[mcp_servers.clarion]"),
+ raw.contains("[mcp_servers.loomweave]"),
"Codex MCP entry missing: {raw}"
);
assert!(
@@ -371,9 +380,9 @@ fn install_codex_writes_requested_config_without_initialising_clarion_dir() {
fn dotenv_in_cwd_is_loaded_before_tracing_setup() {
// Proves the dotenvy hook in `main()` runs before `init_tracing()`: a
// `.env`-supplied RUST_LOG=debug enables the debug-level log line in
- // `install` (the "clarion.yaml already exists; leaving untouched"
+ // `install` (the "loomweave.yaml already exists; leaving untouched"
// branch in install.rs) that the default `info` filter would
- // otherwise suppress. Pre-creating `clarion.yaml` puts us on the
+ // otherwise suppress. Pre-creating `loomweave.yaml` puts us on the
// branch that emits debug.
//
// Uses raw std::process::Command rather than assert_cmd::Command so the
@@ -382,22 +391,22 @@ fn dotenv_in_cwd_is_loaded_before_tracing_setup() {
// producing an empty stderr regardless of .env content.
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join(".env"), "RUST_LOG=debug\n").unwrap();
- fs::write(dir.path().join("clarion.yaml"), "version: 1\n").unwrap();
+ fs::write(dir.path().join("loomweave.yaml"), "version: 1\n").unwrap();
- let bin = assert_cmd::cargo::cargo_bin("clarion");
+ let bin = assert_cmd::cargo::cargo_bin("loomweave");
let path = std::env::var("PATH").unwrap_or_default();
let out = std::process::Command::new(&bin)
.current_dir(dir.path())
.env_clear()
.env("PATH", path)
.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
dir.path().join("isolated-codex-config.toml"),
)
.args(["install", "--path"])
.arg(dir.path())
.output()
- .expect("clarion install");
+ .expect("loomweave install");
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
@@ -421,9 +430,9 @@ fn explicit_env_var_wins_over_dotenv() {
// suppress the debug line even when .env tries to bump verbosity.
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join(".env"), "RUST_LOG=debug\n").unwrap();
- fs::write(dir.path().join("clarion.yaml"), "version: 1\n").unwrap();
+ fs::write(dir.path().join("loomweave.yaml"), "version: 1\n").unwrap();
- let bin = assert_cmd::cargo::cargo_bin("clarion");
+ let bin = assert_cmd::cargo::cargo_bin("loomweave");
let path = std::env::var("PATH").unwrap_or_default();
let out = std::process::Command::new(&bin)
.current_dir(dir.path())
@@ -431,13 +440,13 @@ fn explicit_env_var_wins_over_dotenv() {
.env("PATH", path)
.env("RUST_LOG", "info")
.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
dir.path().join("isolated-codex-config.toml"),
)
.args(["install", "--path"])
.arg(dir.path())
.output()
- .expect("clarion install");
+ .expect("loomweave install");
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
diff --git a/crates/clarion-cli/tests/sarif.rs b/crates/loomweave-cli/tests/sarif.rs
similarity index 91%
rename from crates/clarion-cli/tests/sarif.rs
rename to crates/loomweave-cli/tests/sarif.rs
index 764c2821..8d37de43 100644
--- a/crates/clarion-cli/tests/sarif.rs
+++ b/crates/loomweave-cli/tests/sarif.rs
@@ -1,4 +1,4 @@
-//! `clarion sarif import` integration tests.
+//! `loomweave sarif import` integration tests.
use std::fs;
use std::io::{Read, Write};
@@ -6,12 +6,12 @@ use std::net::TcpListener;
use assert_cmd::Command;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -24,7 +24,7 @@ fn sarif_import_posts_findings_to_mock_filigree() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind test server");
let addr = listener.local_addr().expect("local addr");
- // Spawn a thread to receive the HTTP request from clarion sarif import
+ // Spawn a thread to receive the HTTP request from loomweave sarif import
let handle = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept connection");
let mut request = vec![0_u8; 8192];
@@ -91,7 +91,7 @@ fn sarif_import_posts_findings_to_mock_filigree() {
let dir = tempfile::tempdir().unwrap();
- // Write a mock clarion.yaml config
+ // Write a mock loomweave.yaml config
let config_content = format!(
r#"
integrations:
@@ -102,10 +102,10 @@ integrations:
token_env: "TEST_FILIGREE_TOKEN"
"#
);
- fs::write(dir.path().join("clarion.yaml"), config_content).unwrap();
+ fs::write(dir.path().join("loomweave.yaml"), config_content).unwrap();
- // Create a dummy .clarion dir so it passes the project layout checks
- fs::create_dir_all(dir.path().join(".clarion")).unwrap();
+ // Create a dummy .loomweave dir so it passes the project layout checks
+ fs::create_dir_all(dir.path().join(".loomweave")).unwrap();
// Write a mock SARIF file
let sarif_content = r#"{
@@ -154,7 +154,7 @@ integrations:
fs::write(&sarif_path, sarif_content).unwrap();
// Run the cli command
- clarion_bin()
+ loomweave_bin()
.env("TEST_FILIGREE_TOKEN", "my-mock-token")
.args(["sarif", "import"])
.arg(&sarif_path)
@@ -204,7 +204,7 @@ fn sarif_import_prefers_wardline_partial_fingerprint() {
let dir = tempfile::tempdir().unwrap();
fs::write(
- dir.path().join("clarion.yaml"),
+ dir.path().join("loomweave.yaml"),
format!(
r#"
integrations:
@@ -217,7 +217,7 @@ integrations:
),
)
.unwrap();
- fs::create_dir_all(dir.path().join(".clarion")).unwrap();
+ fs::create_dir_all(dir.path().join(".loomweave")).unwrap();
let sarif_content = r#"{
"version": "2.1.0",
"runs": [{
@@ -243,7 +243,7 @@ integrations:
let sarif_path = dir.path().join("wardline.sarif");
fs::write(&sarif_path, sarif_content).unwrap();
- clarion_bin()
+ loomweave_bin()
.env("TEST_FILIGREE_TOKEN", "my-mock-token")
.args(["sarif", "import"])
.arg(&sarif_path)
diff --git a/crates/clarion-cli/tests/secret_scan.rs b/crates/loomweave-cli/tests/secret_scan.rs
similarity index 90%
rename from crates/clarion-cli/tests/secret_scan.rs
rename to crates/loomweave-cli/tests/secret_scan.rs
index 6c4e3d5b..6d10eb84 100644
--- a/crates/clarion-cli/tests/secret_scan.rs
+++ b/crates/loomweave-cli/tests/secret_scan.rs
@@ -4,12 +4,12 @@ use assert_cmd::Command;
use rusqlite::Connection;
use sha1::{Digest, Sha1};
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -56,7 +56,7 @@ while True:
"jsonrpc": "2.0",
"id": ident,
"result": {
- "name": "clarion-plugin-secretfixture",
+ "name": "loomweave-plugin-secretfixture",
"version": "0.1.0",
"ontology_version": "0.1.0",
"capabilities": {},
@@ -65,7 +65,7 @@ while True:
elif method == "analyze_file":
if (
os.environ.get("SECRETFIXTURE_ASSERT_ENV_ABSENT")
- and os.environ.get("CLARION_DOTENV_SENTINEL") is not None
+ and os.environ.get("LOOMWEAVE_DOTENV_SENTINEL") is not None
):
raise SystemExit(42)
path = msg["params"]["file_path"]
@@ -94,11 +94,11 @@ while True:
const PLUGIN_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-secretfixture"
+name = "loomweave-plugin-secretfixture"
plugin_id = "secretfixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-secretfixture"
+executable = "loomweave-plugin-secretfixture"
language = "secretfixture"
extensions = ["sec"]
@@ -111,7 +111,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module"]
edge_kinds = []
-rule_id_prefix = "CLA-SECRET-FIXTURE-"
+rule_id_prefix = "LMWV-SECRET-FIXTURE-"
ontology_version = "0.1.0"
[ontology.roles]
@@ -121,7 +121,7 @@ file_scope = ["module"]
fn write_secret_fixture_plugin(plugin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
- let plugin_script = plugin_dir.join("clarion-plugin-secretfixture");
+ let plugin_script = plugin_dir.join("loomweave-plugin-secretfixture");
std::fs::write(&plugin_script, PLUGIN_SCRIPT).expect("write plugin script");
let mut perms = std::fs::metadata(&plugin_script)
.expect("stat plugin")
@@ -133,7 +133,7 @@ fn write_secret_fixture_plugin(plugin_dir: &std::path::Path) {
}
fn install_project(project: &std::path::Path) {
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project)
.assert()
@@ -145,7 +145,7 @@ fn plugin_path(plugin_dir: &std::path::Path) -> std::ffi::OsString {
}
fn conn(project: &std::path::Path) -> Connection {
- Connection::open(project.join(".clarion/clarion.db")).expect("open clarion db")
+ Connection::open(project.join(".loomweave/loomweave.db")).expect("open loomweave db")
}
fn sha1_hex(bytes: &[u8]) -> String {
@@ -169,7 +169,7 @@ fn clean_project_has_no_secret_findings() {
install_project(project.path());
std::fs::write(project.path().join("clean.sec"), b"nothing to see\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -178,7 +178,7 @@ fn clean_project_has_no_secret_findings() {
let count: i64 = conn(project.path())
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id LIKE 'CLA-SEC-%'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id LIKE 'LMWV-SEC-%'",
[],
|row| row.get(0),
)
@@ -198,7 +198,7 @@ fn secret_file_persists_finding_and_briefing_block() {
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -216,7 +216,7 @@ fn secret_file_persists_finding_and_briefing_block() {
assert_eq!(blocked, "secret_present");
let count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|row| row.get(0),
)
@@ -248,7 +248,7 @@ fn resume_does_not_duplicate_secret_findings() {
.unwrap();
// Fresh run.
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -266,7 +266,7 @@ fn resume_does_not_duplicate_secret_findings() {
.unwrap();
let first_id = db
.query_row(
- "SELECT id FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT id FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|r| r.get(0),
)
@@ -275,7 +275,7 @@ fn resume_does_not_duplicate_secret_findings() {
};
// Resume the same run.
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.args(["--resume", &run_id])
.arg(project.path())
@@ -286,7 +286,7 @@ fn resume_does_not_duplicate_secret_findings() {
let db = conn(project.path());
let count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|row| row.get(0),
)
@@ -294,7 +294,7 @@ fn resume_does_not_duplicate_secret_findings() {
assert_eq!(count, 1, "resume must not duplicate the secret finding");
let after_id: String = db
.query_row(
- "SELECT id FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT id FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|r| r.get(0),
)
@@ -322,7 +322,7 @@ fn dotenv_sidecar_persists_finding_with_core_file_anchor() {
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -332,7 +332,7 @@ fn dotenv_sidecar_persists_finding_with_core_file_anchor() {
let db = conn(project.path());
let finding_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|row| row.get(0),
)
@@ -361,11 +361,11 @@ fn analyze_does_not_load_dotenv_into_plugin_environment() {
std::fs::write(project.path().join("clean.sec"), b"nothing to see\n").unwrap();
std::fs::write(
project.path().join(".env"),
- b"CLARION_DOTENV_SENTINEL=ordinaryvalue\n",
+ b"LOOMWEAVE_DOTENV_SENTINEL=ordinaryvalue\n",
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(".")
.current_dir(project.path())
@@ -385,7 +385,7 @@ fn plugin_entity_for_unscanned_source_is_briefing_blocked() {
let unscanned = project.path().join("notes.txt");
std::fs::write(&unscanned, b"ordinary notes\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -417,7 +417,7 @@ fn baseline_suppresses_secret_and_emits_audit_match() {
.unwrap();
let hashed_secret = sha1_hex(b"AKIAIOSFODNN7EXAMPLE");
std::fs::write(
- project.path().join(".clarion/secrets-baseline.yaml"),
+ project.path().join(".loomweave/secrets-baseline.yaml"),
format!(
r#"
version: "1.0"
@@ -433,7 +433,7 @@ results:
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -443,14 +443,14 @@ results:
let db = conn(project.path());
let secret_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|row| row.get(0),
)
.unwrap();
let match_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-SECRET-BASELINE-MATCH'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-SECRET-BASELINE-MATCH'",
[],
|row| row.get(0),
)
@@ -480,7 +480,7 @@ fn missing_baseline_justification_degrades_to_finding() {
.unwrap();
let hashed_secret = sha1_hex(b"AKIAIOSFODNN7EXAMPLE");
std::fs::write(
- project.path().join(".clarion/secrets-baseline.yaml"),
+ project.path().join(".loomweave/secrets-baseline.yaml"),
format!(
r#"
version: "1.0"
@@ -500,7 +500,7 @@ results:
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -509,7 +509,7 @@ results:
let count: i64 = conn(project.path())
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION'",
[],
|row| row.get(0),
)
@@ -529,7 +529,7 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() {
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.args([
"analyze",
"--allow-unredacted-secrets",
@@ -550,7 +550,7 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() {
.unwrap();
let override_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'",
[],
|row| row.get(0),
)
@@ -564,7 +564,7 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() {
.unwrap();
let evidence_json: String = db
.query_row(
- "SELECT evidence FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'",
+ "SELECT evidence FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'",
[],
|row| row.get(0),
)
@@ -572,11 +572,11 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() {
let evidence: serde_json::Value =
serde_json::from_str(&evidence_json).expect("override evidence JSON");
// ADR-013: override is additive — per-(rule,file,line) SECRET_DETECTED
- // still lands so `filigree list --rule-id=CLA-SEC-SECRET-DETECTED`
+ // still lands so `filigree list --rule-id=LMWV-SEC-SECRET-DETECTED`
// enumerates the full audited population, not just the unoverridden tail.
let secret_detected_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|row| row.get(0),
)
@@ -609,14 +609,14 @@ fn non_tty_override_without_confirmation_exits_78_before_run_start() {
)
.unwrap();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["analyze", "--allow-unredacted-secrets"])
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
.assert()
.code(78);
let stderr = String::from_utf8_lossy(&assert.get_output().stderr);
- assert!(stderr.contains("CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED"));
+ assert!(stderr.contains("LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED"));
assert!(stderr.contains("leaky.sec:1 AwsAccessKeyId"));
let run_count: i64 = conn(project.path())
.query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0))
@@ -636,7 +636,7 @@ fn non_tty_override_with_wrong_confirmation_exits_78_before_run_start() {
)
.unwrap();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args([
"analyze",
"--allow-unredacted-secrets",
@@ -647,7 +647,7 @@ fn non_tty_override_with_wrong_confirmation_exits_78_before_run_start() {
.assert()
.code(78);
let stderr = String::from_utf8_lossy(&assert.get_output().stderr);
- assert!(stderr.contains("CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED"));
+ assert!(stderr.contains("LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED"));
let run_count: i64 = conn(project.path())
.query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0))
.unwrap();
@@ -672,7 +672,7 @@ fn baseline_suppression_and_override_admission_are_audited_together() {
.unwrap();
let hashed_secret = sha1_hex(b"AKIAIOSFODNN7EXAMPLE");
std::fs::write(
- project.path().join(".clarion/secrets-baseline.yaml"),
+ project.path().join(".loomweave/secrets-baseline.yaml"),
format!(
r#"
version: "1.0"
@@ -688,7 +688,7 @@ results:
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.args([
"analyze",
"--allow-unredacted-secrets",
@@ -702,21 +702,21 @@ results:
let db = conn(project.path());
let baseline_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-SECRET-BASELINE-MATCH'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-SECRET-BASELINE-MATCH'",
[],
|row| row.get(0),
)
.unwrap();
let override_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'",
[],
|row| row.get(0),
)
.unwrap();
let secret_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'",
[],
|row| row.get(0),
)
@@ -748,12 +748,12 @@ fn assert_invalid_baseline_aborts(raw_baseline: &str, expected_stderr: &str) {
install_project(project.path());
std::fs::write(project.path().join("leaky.sec"), b"nothing to see\n").unwrap();
std::fs::write(
- project.path().join(".clarion/secrets-baseline.yaml"),
+ project.path().join(".loomweave/secrets-baseline.yaml"),
raw_baseline,
)
.unwrap();
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -843,7 +843,7 @@ fn override_flag_is_noop_without_detections() {
install_project(project.path());
std::fs::write(project.path().join("clean.sec"), b"nothing to see\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["analyze", "--allow-unredacted-secrets"])
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -853,7 +853,7 @@ fn override_flag_is_noop_without_detections() {
let db = conn(project.path());
let override_count: i64 = db
.query_row(
- "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'",
+ "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'",
[],
|row| row.get(0),
)
@@ -883,7 +883,7 @@ fn only_secret_bearing_file_is_blocked_in_multi_file_project() {
.unwrap();
std::fs::write(project.path().join("clean_b.sec"), b"still clean\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -915,7 +915,7 @@ fn cleared_sidecar_clears_stale_briefing_block() {
let dotenv = project.path().join(".env");
std::fs::write(&dotenv, b"aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE'\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -938,7 +938,7 @@ fn cleared_sidecar_clears_stale_briefing_block() {
std::fs::write(&dotenv, b"NO_SECRET_HERE=ordinary_value\n").unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
@@ -984,7 +984,7 @@ fn clean_sidecar_creates_anchor_without_block() {
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.arg("analyze")
.arg(project.path())
.env("PATH", plugin_path(plugin.path()))
diff --git a/crates/clarion-cli/tests/serve.rs b/crates/loomweave-cli/tests/serve.rs
similarity index 93%
rename from crates/clarion-cli/tests/serve.rs
rename to crates/loomweave-cli/tests/serve.rs
index 9e47bfdf..c19c51e0 100644
--- a/crates/clarion-cli/tests/serve.rs
+++ b/crates/loomweave-cli/tests/serve.rs
@@ -9,11 +9,11 @@ use std::thread;
use std::time::{Duration, Instant};
use assert_cmd::Command;
-use clarion_core::{
+use hmac::{Hmac, Mac};
+use loomweave_core::{
LEAF_SUMMARY_PROMPT_TEMPLATE_ID,
plugin::{ContentLengthCeiling, Frame, read_frame, write_frame},
};
-use hmac::{Hmac, Mac};
use rusqlite::{Connection, params};
use serde::Deserialize;
use serde_json::Value;
@@ -46,12 +46,12 @@ impl HttpRawResponse {
}
}
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -60,7 +60,7 @@ fn clarion_bin() -> Command {
#[test]
fn serve_help_advertises_mcp_stdio_server() {
- let assert = clarion_bin().args(["serve", "--help"]).assert().success();
+ let assert = loomweave_bin().args(["serve", "--help"]).assert().success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).expect("help is utf8");
assert!(stdout.contains("Run the MCP stdio server"));
@@ -70,7 +70,7 @@ fn serve_help_advertises_mcp_stdio_server() {
#[test]
fn serve_stdio_initialize_round_trip() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -78,14 +78,14 @@ fn serve_stdio_initialize_round_trip() {
.success();
write_stdio_config(dir.path());
- let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion"))
+ let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave"))
.args(["serve", "--path"])
.arg(dir.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
- .expect("spawn clarion serve");
+ .expect("spawn loomweave serve");
{
let mut stdin = child.stdin.take().expect("child stdin");
@@ -109,7 +109,7 @@ fn serve_stdio_initialize_round_trip() {
stdin.flush().expect("flush initialize frame");
}
- let output = child.wait_with_output().expect("wait for clarion serve");
+ let output = child.wait_with_output().expect("wait for loomweave serve");
assert!(
output.status.success(),
"serve failed: {}",
@@ -124,13 +124,13 @@ fn serve_stdio_initialize_round_trip() {
assert_eq!(response["id"], 1);
assert_eq!(response["result"]["protocolVersion"], "2025-11-25");
- assert_eq!(response["result"]["serverInfo"]["name"], "clarion");
+ assert_eq!(response["result"]["serverInfo"]["name"], "loomweave");
}
#[test]
fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -138,14 +138,14 @@ fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() {
.success();
write_stdio_config(dir.path());
- let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion"))
+ let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave"))
.args(["serve", "--path"])
.arg(dir.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
- .expect("spawn clarion serve");
+ .expect("spawn loomweave serve");
let mut stdin = child.stdin.take().expect("child stdin");
let stdout = child.stdout.take().expect("child stdout");
@@ -180,13 +180,13 @@ fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() {
Err(err) => {
let _ = child.kill();
let _ = child.wait();
- panic!("clarion serve did not answer newline-delimited MCP initialize: {err}");
+ panic!("loomweave serve did not answer newline-delimited MCP initialize: {err}");
}
};
let response: Value = serde_json::from_str(line.trim_end()).expect("response line is json");
drop(stdin);
- let status = child.wait().expect("wait for clarion serve");
+ let status = child.wait().expect("wait for loomweave serve");
reader.join().expect("stdout reader thread");
assert!(
@@ -195,7 +195,7 @@ fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() {
read_child_stderr(&mut child)
);
assert_eq!(response["id"], 7);
- assert_eq!(response["result"]["serverInfo"]["name"], "clarion");
+ assert_eq!(response["result"]["serverInfo"]["name"], "loomweave");
}
fn read_child_stderr(child: &mut Child) -> String {
@@ -225,14 +225,14 @@ fn serve_http_responses_match_federation_fixture_contracts() {
include_str!("../../../docs/federation/fixtures/post-api-v1-files-batch.json"),
);
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
fs::write(
- dir.path().join(".clarion/instance_id"),
+ dir.path().join(".loomweave/instance_id"),
format!("{STABLE_INSTANCE_ID}\n"),
)
.expect("seed stable instance ID");
@@ -259,7 +259,7 @@ fn serve_http_responses_match_federation_fixture_contracts() {
stop_serve(&mut child);
let auth_dir = tempfile::tempdir().expect("temp auth project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(auth_dir.path())
.env("PATH", "")
@@ -270,12 +270,12 @@ fn serve_http_responses_match_federation_fixture_contracts() {
write_http_config_with_token_env(
auth_dir.path(),
&auth_bind,
- "CLARION_TEST_FIXTURE_BATCH_TOKEN",
+ "LOOMWEAVE_TEST_FIXTURE_BATCH_TOKEN",
);
let mut auth_child = spawn_serve_with_env(
auth_dir.path(),
- &[("CLARION_TEST_FIXTURE_BATCH_TOKEN", "fixture-secret")],
+ &[("LOOMWEAVE_TEST_FIXTURE_BATCH_TOKEN", "fixture-secret")],
);
validate_fixture_examples_matching(
&auth_bind,
@@ -289,7 +289,7 @@ fn serve_http_responses_match_federation_fixture_contracts() {
#[test]
fn serve_http_files_endpoint_returns_briefing_blocked_for_blocked_entity() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -326,7 +326,7 @@ fn serve_http_files_endpoint_returns_briefing_blocked_for_blocked_entity() {
#[test]
fn serve_http_files_endpoint_resolves_known_file_on_configured_port() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -355,7 +355,7 @@ fn serve_http_files_endpoint_resolves_known_file_on_configured_port() {
#[test]
fn serve_http_files_endpoint_prefers_stored_manifest_language() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -377,7 +377,7 @@ fn serve_http_files_endpoint_prefers_stored_manifest_language() {
#[test]
fn serve_http_files_etag_round_trip_and_if_none_match_returns_304() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -413,7 +413,7 @@ fn serve_http_files_etag_round_trip_and_if_none_match_returns_304() {
#[test]
fn serve_http_files_blank_path_returns_invalid_path_envelope() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -441,7 +441,7 @@ fn serve_http_identity_resolve_rejects_sei_shaped_input_and_resolves_unknown() {
// with 400 INVALID_PATH (never silently mis-resolved); a well-formed but
// unknown locator resolves to { alive: false }.
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -451,8 +451,9 @@ fn serve_http_identity_resolve_rejects_sei_shaped_input_and_resolves_unknown() {
write_http_config(dir.path(), &bind);
let mut child = spawn_serve(dir.path());
- let sei_body = serde_json::json!({ "locator": "clarion:eid:0123456789abcdef0123456789abcdef" })
- .to_string();
+ let sei_body =
+ serde_json::json!({ "locator": "loomweave:eid:0123456789abcdef0123456789abcdef" })
+ .to_string();
let rejected = wait_for_http_post_json(&bind, "/api/v1/identity/resolve", &sei_body, &[]);
let unknown_body = serde_json::json!({ "locator": "python:function:nope.absent" }).to_string();
@@ -475,7 +476,7 @@ fn serve_http_identity_resolve_rejects_sei_shaped_input_and_resolves_unknown() {
/// caller/callee linkages, in a file with a content hash. This is the state the
/// Wave 1 matcher produces (proven by the SEI conformance oracle + the
/// production orphan test); the test below proves the Wardline dossier assembler
-/// can read every slice it needs over Clarion's HTTP surface alone. Returns the
+/// can read every slice it needs over Loomweave's HTTP surface alone. Returns the
/// carried SEI.
fn seed_renamed_function_dossier(project_root: &Path) -> String {
let source_path = project_root.join("mod.py");
@@ -485,11 +486,11 @@ fn seed_renamed_function_dossier(project_root: &Path) -> String {
.expect("canonical source path")
.display()
.to_string();
- let sei = "clarion:eid:0123456789abcdef0123456789abcdef".to_owned();
+ let sei = "loomweave:eid:0123456789abcdef0123456789abcdef".to_owned();
let new_locator = "python:function:mod.process_v2";
let old_locator = "python:function:mod.process";
let ts = "2026-06-02T00:00:00.000Z";
- let db_path = project_root.join(".clarion/clarion.db");
+ let db_path = project_root.join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
@@ -543,10 +544,10 @@ fn seed_renamed_function_dossier(project_root: &Path) -> String {
fn serve_http_dossier_participation_surface_serves_a_renamed_function() {
// WS4 core-paradise e2e (Wave 2 DoD): the Wardline dossier assembler can build
// a complete, freshness-stamped, SEI-keyed view of a RENAMED function using
- // ONLY Clarion's HTTP surface — SEI carried, facts not orphaned, freshness
+ // ONLY Loomweave's HTTP surface — SEI carried, facts not orphaned, freshness
// stamped. Exercises every slice the participation spec pins.
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -654,7 +655,7 @@ fn serve_http_dossier_participation_surface_serves_a_renamed_function() {
#[test]
fn serve_http_files_rejects_unknown_query_fields() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -683,7 +684,7 @@ fn serve_http_files_rejects_unknown_query_fields() {
#[test]
fn serve_http_rejects_oversized_get_body_before_handler() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -704,7 +705,7 @@ fn serve_http_rejects_oversized_get_body_before_handler() {
#[test]
fn serve_http_files_path_traversal_returns_outside_project_envelope() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -730,7 +731,7 @@ fn serve_http_files_path_traversal_returns_outside_project_envelope() {
#[test]
fn serve_http_files_unknown_catalog_file_returns_not_found_envelope() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -755,7 +756,7 @@ fn serve_http_files_unknown_catalog_file_returns_not_found_envelope() {
#[test]
fn serve_http_files_storage_failure_returns_closed_error_without_raw_detail() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -770,7 +771,7 @@ fn serve_http_files_storage_failure_returns_closed_error_without_raw_detail() {
.expect("canonical source path")
.display()
.to_string();
- let db_path = dir.path().join(".clarion/clarion.db");
+ let db_path = dir.path().join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
"INSERT INTO entities (
@@ -825,14 +826,14 @@ fn serve_http_files_storage_failure_returns_closed_error_without_raw_detail() {
#[test]
fn serve_http_capabilities_and_mcp_stdio_coexist() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
fs::write(
- dir.path().join(".clarion/instance_id"),
+ dir.path().join(".loomweave/instance_id"),
format!("{STABLE_INSTANCE_ID}\n"),
)
.expect("seed stable instance ID");
@@ -882,7 +883,7 @@ fn serve_http_capabilities_and_mcp_stdio_coexist() {
stdin.flush().expect("flush initialize frame");
}
drop(child.stdin.take());
- let output = child.wait_with_output().expect("wait for clarion serve");
+ let output = child.wait_with_output().expect("wait for loomweave serve");
assert!(
output.status.success(),
@@ -896,19 +897,19 @@ fn serve_http_capabilities_and_mcp_stdio_coexist() {
serde_json::from_slice(&frame.body).expect("response body is json");
assert_eq!(response["id"], 42);
- assert_eq!(response["result"]["serverInfo"]["name"], "clarion");
+ assert_eq!(response["result"]["serverInfo"]["name"], "loomweave");
}
#[test]
fn serve_http_capabilities_reuses_persisted_instance_id_across_restarts() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let instance_id_path = dir.path().join(".clarion/instance_id");
+ let instance_id_path = dir.path().join(".loomweave/instance_id");
let first_bind = free_loopback_bind();
write_http_config(dir.path(), &first_bind);
@@ -954,13 +955,13 @@ fn serve_http_capabilities_reuses_persisted_instance_id_across_restarts() {
#[test]
fn serve_http_capabilities_returns_new_instance_id_after_rotation() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let instance_id_path = dir.path().join(".clarion/instance_id");
+ let instance_id_path = dir.path().join(".loomweave/instance_id");
let first_bind = free_loopback_bind();
write_http_config(dir.path(), &first_bind);
@@ -1008,7 +1009,7 @@ fn serve_http_capabilities_returns_new_instance_id_after_rotation() {
#[test]
fn serve_http_capabilities_creates_instance_id_with_private_unix_mode() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1022,7 +1023,7 @@ fn serve_http_capabilities_creates_instance_id_with_private_unix_mode() {
.expect("HTTP /api/v1/_capabilities response");
stop_serve(&mut child);
- let instance_id_path = dir.path().join(".clarion/instance_id");
+ let instance_id_path = dir.path().join(".loomweave/instance_id");
assert_eq!(
fs::read_to_string(&instance_id_path)
.expect("read persisted instance_id")
@@ -1040,13 +1041,13 @@ fn serve_http_capabilities_creates_instance_id_with_private_unix_mode() {
#[test]
fn serve_http_capabilities_repairs_existing_instance_id_mode() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let instance_id_path = dir.path().join(".clarion/instance_id");
+ let instance_id_path = dir.path().join(".loomweave/instance_id");
let seeded_id = "9bd7234e-6d44-4a38-9ae4-76f912a10221";
fs::write(&instance_id_path, format!("{seeded_id}\n")).expect("seed instance ID");
fs::set_permissions(&instance_id_path, fs::Permissions::from_mode(0o644))
@@ -1071,13 +1072,13 @@ fn serve_http_capabilities_repairs_existing_instance_id_mode() {
#[test]
fn serve_rejects_invalid_instance_id_before_serving_http() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- fs::write(dir.path().join(".clarion/instance_id"), "not-a-uuid\n")
+ fs::write(dir.path().join(".loomweave/instance_id"), "not-a-uuid\n")
.expect("write invalid instance ID");
let bind = free_loopback_bind();
write_http_config(dir.path(), &bind);
@@ -1089,7 +1090,7 @@ fn serve_rejects_invalid_instance_id_before_serving_http() {
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
- stderr.contains("invalid Clarion instance ID"),
+ stderr.contains("invalid Loomweave instance ID"),
"unexpected stderr: {stderr}"
);
}
@@ -1097,7 +1098,7 @@ fn serve_rejects_invalid_instance_id_before_serving_http() {
#[test]
fn serve_http_batch_endpoint_resolves_mixed_paths() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1161,7 +1162,7 @@ fn serve_http_batch_endpoint_resolves_mixed_paths() {
#[test]
fn serve_http_files_resolve_endpoint_returns_per_path_results() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1227,7 +1228,7 @@ fn serve_http_files_resolve_endpoint_returns_per_path_results() {
#[test]
fn serve_http_files_resolve_rejects_over_1000_paths() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1262,7 +1263,7 @@ fn serve_http_files_resolve_rejects_over_1000_paths() {
#[test]
fn serve_http_batch_rejects_over_256_queries() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1299,7 +1300,7 @@ fn serve_http_batch_rejects_over_256_queries() {
#[test]
fn serve_http_batch_requires_auth_when_configured() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1307,12 +1308,9 @@ fn serve_http_batch_requires_auth_when_configured() {
.success();
seed_file_entity(dir.path());
let bind = free_loopback_bind();
- write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_BATCH");
+ write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_BATCH");
- let mut child = spawn_serve_with_env(
- dir.path(),
- &[("CLARION_TEST_LOOM_TOKEN_BATCH", "batch-secret")],
- );
+ let mut child = spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_BATCH", "batch-secret")]);
let body = serde_json::json!({
"queries": [{"path": "demo.py", "language": "python"}]
})
@@ -1341,7 +1339,7 @@ fn serve_http_batch_requires_auth_when_configured() {
#[test]
fn serve_http_files_endpoint_requires_bearer_token_when_configured() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1349,12 +1347,10 @@ fn serve_http_files_endpoint_requires_bearer_token_when_configured() {
.success();
seed_file_entity(dir.path());
let bind = free_loopback_bind();
- write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_REQ");
+ write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_REQ");
- let mut child = spawn_serve_with_env(
- dir.path(),
- &[("CLARION_TEST_LOOM_TOKEN_REQ", "shh-its-a-secret")],
- );
+ let mut child =
+ spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_REQ", "shh-its-a-secret")]);
let unauthenticated =
wait_for_http_response(&bind, "/api/v1/files?path=demo.py&language=python");
let authenticated = wait_for_http_raw_response(
@@ -1377,7 +1373,7 @@ fn serve_http_files_endpoint_requires_bearer_token_when_configured() {
#[test]
fn serve_http_files_endpoint_rejects_wrong_token() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1385,12 +1381,9 @@ fn serve_http_files_endpoint_rejects_wrong_token() {
.success();
seed_file_entity(dir.path());
let bind = free_loopback_bind();
- write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_WRONG");
+ write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_WRONG");
- let mut child = spawn_serve_with_env(
- dir.path(),
- &[("CLARION_TEST_LOOM_TOKEN_WRONG", "correct-horse")],
- );
+ let mut child = spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_WRONG", "correct-horse")]);
let wrong = wait_for_http_raw_response(
&bind,
"/api/v1/files?path=demo.py&language=python",
@@ -1426,7 +1419,7 @@ fn serve_http_files_endpoint_rejects_wrong_token() {
#[test]
fn serve_http_files_endpoint_requires_hmac_identity_when_configured() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1434,12 +1427,10 @@ fn serve_http_files_endpoint_requires_hmac_identity_when_configured() {
.success();
seed_file_entity(dir.path());
let bind = free_loopback_bind();
- write_http_config_with_identity_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_IDENTITY_REQ");
+ write_http_config_with_identity_token_env(dir.path(), &bind, "WEFT_TEST_IDENTITY_REQ");
- let mut child = spawn_serve_with_env(
- dir.path(),
- &[("CLARION_TEST_LOOM_IDENTITY_REQ", "shared-secret")],
- );
+ let mut child =
+ spawn_serve_with_env(dir.path(), &[("WEFT_TEST_IDENTITY_REQ", "shared-secret")]);
let path = "/api/v1/files?path=demo.py&language=python";
let missing = wait_for_http_raw_response(&bind, path, &[]);
let (signed_header, signed_timestamp, signed_nonce) =
@@ -1448,9 +1439,9 @@ fn serve_http_files_endpoint_requires_hmac_identity_when_configured() {
&bind,
path,
&[
- ("X-Loom-Component", &signed_header),
- ("X-Loom-Timestamp", &signed_timestamp),
- ("X-Loom-Nonce", &signed_nonce),
+ ("X-Weft-Component", &signed_header),
+ ("X-Weft-Timestamp", &signed_timestamp),
+ ("X-Weft-Nonce", &signed_nonce),
],
);
stop_serve(&mut child);
@@ -1469,7 +1460,7 @@ fn serve_http_files_endpoint_requires_hmac_identity_when_configured() {
#[test]
fn serve_http_files_endpoint_rejects_wrong_hmac_identity() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1477,16 +1468,10 @@ fn serve_http_files_endpoint_rejects_wrong_hmac_identity() {
.success();
seed_file_entity(dir.path());
let bind = free_loopback_bind();
- write_http_config_with_identity_token_env(
- dir.path(),
- &bind,
- "CLARION_TEST_LOOM_IDENTITY_WRONG",
- );
+ write_http_config_with_identity_token_env(dir.path(), &bind, "WEFT_TEST_IDENTITY_WRONG");
- let mut child = spawn_serve_with_env(
- dir.path(),
- &[("CLARION_TEST_LOOM_IDENTITY_WRONG", "shared-secret")],
- );
+ let mut child =
+ spawn_serve_with_env(dir.path(), &[("WEFT_TEST_IDENTITY_WRONG", "shared-secret")]);
let path = "/api/v1/files?path=demo.py&language=python";
let (wrong_header, wrong_timestamp, wrong_nonce) =
hmac_component_headers("other-secret", "GET", path, b"");
@@ -1494,9 +1479,9 @@ fn serve_http_files_endpoint_rejects_wrong_hmac_identity() {
&bind,
path,
&[
- ("X-Loom-Component", &wrong_header),
- ("X-Loom-Timestamp", &wrong_timestamp),
- ("X-Loom-Nonce", &wrong_nonce),
+ ("X-Weft-Component", &wrong_header),
+ ("X-Weft-Timestamp", &wrong_timestamp),
+ ("X-Weft-Nonce", &wrong_nonce),
],
);
stop_serve(&mut child);
@@ -1510,19 +1495,17 @@ fn serve_http_files_endpoint_rejects_wrong_hmac_identity() {
#[test]
fn serve_http_capabilities_does_not_require_token() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
let bind = free_loopback_bind();
- write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_CAPS");
+ write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_CAPS");
- let mut child = spawn_serve_with_env(
- dir.path(),
- &[("CLARION_TEST_LOOM_TOKEN_CAPS", "any-token-value")],
- );
+ let mut child =
+ spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_CAPS", "any-token-value")]);
let response = wait_for_http_response(&bind, "/api/v1/_capabilities");
stop_serve(&mut child);
let response = response.expect("capabilities probe response");
@@ -1535,18 +1518,18 @@ fn serve_http_capabilities_does_not_require_token() {
#[test]
fn serve_http_refuses_startup_on_non_loopback_without_token() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
// Non-loopback bind + allow_non_loopback opt-in + token_env unset
- // should refuse to start with CLA-CONFIG-HTTP-NO-AUTH.
+ // should refuse to start with LMWV-CONFIG-HTTP-NO-AUTH.
fs::write(
- dir.path().join("clarion.yaml"),
+ dir.path().join("loomweave.yaml"),
"version: 1\nserve:\n http:\n enabled: true\n bind: \"0.0.0.0:0\"\n \
- allow_non_loopback: true\n token_env: \"CLARION_TEST_LOOM_TOKEN_REFUSE\"\n",
+ allow_non_loopback: true\n token_env: \"WEFT_TEST_TOKEN_REFUSE\"\n",
)
.expect("write non-loopback HTTP config without token env");
@@ -1557,11 +1540,11 @@ fn serve_http_refuses_startup_on_non_loopback_without_token() {
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
- stderr.contains("CLA-CONFIG-HTTP-NO-AUTH"),
- "error should cite CLA-CONFIG-HTTP-NO-AUTH: {stderr}"
+ stderr.contains("LMWV-CONFIG-HTTP-NO-AUTH"),
+ "error should cite LMWV-CONFIG-HTTP-NO-AUTH: {stderr}"
);
assert!(
- stderr.contains("CLARION_TEST_LOOM_TOKEN_REFUSE"),
+ stderr.contains("WEFT_TEST_TOKEN_REFUSE"),
"error should name the configured token_env: {stderr}"
);
}
@@ -1569,18 +1552,14 @@ fn serve_http_refuses_startup_on_non_loopback_without_token() {
#[test]
fn serve_http_refuses_startup_when_identity_env_is_missing() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
let bind = free_loopback_bind();
- write_http_config_with_identity_token_env(
- dir.path(),
- &bind,
- "CLARION_TEST_LOOM_IDENTITY_MISSING",
- );
+ write_http_config_with_identity_token_env(dir.path(), &bind, "WEFT_TEST_IDENTITY_MISSING");
let child = spawn_serve_with_env(dir.path(), &[]);
let output = wait_for_child_exit(child, Duration::from_secs(2))
@@ -1589,11 +1568,11 @@ fn serve_http_refuses_startup_when_identity_env_is_missing() {
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
- stderr.contains("CLA-CONFIG-HTTP-IDENTITY-MISSING"),
- "error should cite CLA-CONFIG-HTTP-IDENTITY-MISSING: {stderr}"
+ stderr.contains("LMWV-CONFIG-HTTP-IDENTITY-MISSING"),
+ "error should cite LMWV-CONFIG-HTTP-IDENTITY-MISSING: {stderr}"
);
assert!(
- stderr.contains("CLARION_TEST_LOOM_IDENTITY_MISSING"),
+ stderr.contains("WEFT_TEST_IDENTITY_MISSING"),
"error should name the configured identity_token_env: {stderr}"
);
}
@@ -1601,14 +1580,14 @@ fn serve_http_refuses_startup_when_identity_env_is_missing() {
#[test]
fn serve_rejects_non_loopback_http_bind_before_binding_without_opt_in() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
fs::write(
- dir.path().join("clarion.yaml"),
+ dir.path().join("loomweave.yaml"),
"version: 1\nserve:\n http:\n enabled: true\n bind: \"0.0.0.0:0\"\n",
)
.expect("write non-loopback HTTP config");
@@ -1632,19 +1611,19 @@ fn serve_rejects_non_loopback_http_bind_before_binding_without_opt_in() {
#[test]
fn serve_rejects_invalid_project_config() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
fs::write(
- dir.path().join("clarion.yaml"),
+ dir.path().join("loomweave.yaml"),
"llm: [not valid for this schema]\n",
)
.expect("write invalid config");
- let assert = clarion_bin()
+ let assert = loomweave_bin()
.args(["serve", "--path"])
.arg(dir.path())
.assert()
@@ -1657,7 +1636,7 @@ fn serve_rejects_invalid_project_config() {
#[test]
fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1665,7 +1644,7 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() {
.success();
let source_path = dir.path().join("demo.py");
fs::write(&source_path, "def entry():\n return 1\n").expect("write source");
- let db_path = dir.path().join(".clarion/clarion.db");
+ let db_path = dir.path().join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
"INSERT INTO entities (
@@ -1696,19 +1675,19 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() {
.expect("insert summary cache");
drop(conn);
fs::write(
- dir.path().join("clarion.yaml"),
+ dir.path().join("loomweave.yaml"),
"llm:\n enabled: true\n provider: recording\nserve:\n mcp:\n enable_write_tools: true\n",
)
.expect("write config");
- let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion"))
+ let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave"))
.args(["serve", "--path"])
.arg(dir.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
- .expect("spawn clarion serve");
+ .expect("spawn loomweave serve");
{
let mut stdin = child.stdin.take().expect("child stdin");
write_frame(
@@ -1730,7 +1709,7 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() {
stdin.flush().expect("flush summary frame");
}
- let output = child.wait_with_output().expect("wait for clarion serve");
+ let output = child.wait_with_output().expect("wait for loomweave serve");
assert!(
output.status.success(),
"serve failed: {}",
@@ -1763,7 +1742,7 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() {
#[test]
fn serve_routes_summary_miss_through_codex_cli_provider() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1804,8 +1783,8 @@ done
stdin_prompt="$(cat)"
printf '%s' "$stdin_prompt" > "{prompt_log}"
case "$stdin_prompt" in
- *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;;
- *) echo "missing Clarion agent prompt contract" >&2; exit 32 ;;
+ *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;;
+ *) echo "missing Loomweave agent prompt contract" >&2; exit 32 ;;
esac
grep -q '"purpose"' "$schema"
printf '%s\n' '{{"usage":{{"input_tokens":31,"cached_input_tokens":9,"output_tokens":7,"total_tokens":38}}}}'
@@ -1840,14 +1819,14 @@ printf '%s' '{{"purpose":"via codex serve","behavior":"served through fake Codex
assert!(
fs::read_to_string(prompt_log)
.expect("read Codex prompt log")
- .contains("Prompt contract: clarion-agent-provider-v1")
+ .contains("Prompt contract: loomweave-agent-provider-v1")
);
}
#[test]
fn serve_routes_summary_miss_through_claude_cli_provider() {
let dir = tempfile::tempdir().expect("temp project");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
@@ -1888,12 +1867,12 @@ done
stdin_prompt="$(cat)"
printf '%s\n%s' "$print_prompt" "$stdin_prompt" > "{prompt_log}"
case "$print_prompt" in
- *"Clarion's local Claude Code LLM provider"*) ;;
+ *"Loomweave's local Claude Code LLM provider"*) ;;
*) echo "missing Claude print prompt" >&2; exit 41 ;;
esac
case "$stdin_prompt" in
- *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;;
- *) echo "missing Clarion agent prompt contract" >&2; exit 42 ;;
+ *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;;
+ *) echo "missing Loomweave agent prompt contract" >&2; exit 42 ;;
esac
case "$schema" in
*'"purpose"'*'"behavior"'*) ;;
@@ -1933,7 +1912,7 @@ printf '%s\n' '{{"type":"result","subtype":"success","structured_output":{{"purp
assert!(
fs::read_to_string(prompt_log)
.expect("read Claude prompt log")
- .contains("Clarion's local Claude Code LLM provider")
+ .contains("Loomweave's local Claude Code LLM provider")
);
}
@@ -1948,7 +1927,7 @@ fn seed_summary_entity(project_root: &Path) {
let source_path = project_root.join("demo.py");
fs::write(&source_path, source).expect("write source");
let content_hash = line_range_content_hash(source, 1, 2);
- let db_path = project_root.join(".clarion/clarion.db");
+ let db_path = project_root.join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
"INSERT INTO entities (
@@ -1986,7 +1965,7 @@ fn write_provider_config(
.trim_start_matches('\n')
.replace("\"__EXECUTABLE__\"", &executable_yaml);
fs::write(
- project_root.join("clarion.yaml"),
+ project_root.join("loomweave.yaml"),
format!(
concat!(
"version: 1\n",
@@ -2011,14 +1990,14 @@ fn write_provider_config(
}
fn call_summary_through_serve(project_root: &Path) -> Value {
- let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion"))
+ let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave"))
.args(["serve", "--path"])
.arg(project_root)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
- .expect("spawn clarion serve");
+ .expect("spawn loomweave serve");
{
let mut stdin = child.stdin.take().expect("child stdin");
write_frame(
@@ -2040,12 +2019,12 @@ fn call_summary_through_serve(project_root: &Path) -> Value {
stdin.flush().expect("flush summary frame");
}
- let output = child.wait_with_output().expect("wait for clarion serve");
- let config_debug = fs::read_to_string(project_root.join("clarion.yaml"))
- .unwrap_or_else(|err| format!("failed to read clarion.yaml: {err}"));
+ let output = child.wait_with_output().expect("wait for loomweave serve");
+ let config_debug = fs::read_to_string(project_root.join("loomweave.yaml"))
+ .unwrap_or_else(|err| format!("failed to read loomweave.yaml: {err}"));
assert!(
output.status.success(),
- "serve failed: {}\nclarion.yaml:\n{}",
+ "serve failed: {}\nloomweave.yaml:\n{}",
String::from_utf8_lossy(&output.stderr),
config_debug
);
@@ -2485,7 +2464,7 @@ fn seed_file_entity(project_root: &Path) -> (String, String, String) {
.to_string();
let content_hash = "hash-demo-file".to_owned();
let file_id = "core:file:demo.py".to_owned();
- let db_path = project_root.join(".clarion/clarion.db");
+ let db_path = project_root.join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
"INSERT INTO entities (
@@ -2510,7 +2489,7 @@ fn seed_custom_language_file_entity(project_root: &Path) {
.expect("canonical source path")
.display()
.to_string();
- let db_path = project_root.join(".clarion/clarion.db");
+ let db_path = project_root.join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
"INSERT INTO entities (
@@ -2534,7 +2513,7 @@ fn seed_briefing_blocked_file_entity(project_root: &Path) {
.expect("canonical blocked path")
.display()
.to_string();
- let db_path = project_root.join(".clarion/clarion.db");
+ let db_path = project_root.join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
"INSERT INTO entities (
@@ -2561,7 +2540,7 @@ fn seed_storage_failure_file_entity(project_root: &Path) {
.expect("canonical source path")
.display()
.to_string();
- let db_path = project_root.join(".clarion/clarion.db");
+ let db_path = project_root.join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open sqlite");
conn.execute(
"INSERT INTO entities (
@@ -2624,14 +2603,14 @@ fn release_reserved_loopback_bind(project_root: &Path) {
}
fn configured_http_bind(project_root: &Path) -> Option {
- let raw = fs::read_to_string(project_root.join("clarion.yaml")).ok()?;
+ let raw = fs::read_to_string(project_root.join("loomweave.yaml")).ok()?;
let parsed: ServeTestConfig = serde_norway::from_str(&raw).ok()?;
parsed.serve.http.bind
}
fn write_stdio_config(project_root: &Path) {
fs::write(
- project_root.join("clarion.yaml"),
+ project_root.join("loomweave.yaml"),
"version: 1\nserve:\n http:\n enabled: false\n",
)
.expect("write MCP stdio-only serve config");
@@ -2639,7 +2618,7 @@ fn write_stdio_config(project_root: &Path) {
fn write_http_config(project_root: &Path, bind: &str) {
fs::write(
- project_root.join("clarion.yaml"),
+ project_root.join("loomweave.yaml"),
format!("version: 1\nserve:\n http:\n enabled: true\n bind: \"{bind}\"\n"),
)
.expect("write HTTP serve config");
@@ -2647,7 +2626,7 @@ fn write_http_config(project_root: &Path, bind: &str) {
fn write_http_config_with_token_env(project_root: &Path, bind: &str, token_env: &str) {
fs::write(
- project_root.join("clarion.yaml"),
+ project_root.join("loomweave.yaml"),
format!(
"version: 1\nserve:\n http:\n enabled: true\n bind: \"{bind}\"\n token_env: \"{token_env}\"\n"
),
@@ -2657,7 +2636,7 @@ fn write_http_config_with_token_env(project_root: &Path, bind: &str, token_env:
fn write_http_config_with_identity_token_env(project_root: &Path, bind: &str, token_env: &str) {
fs::write(
- project_root.join("clarion.yaml"),
+ project_root.join("loomweave.yaml"),
format!(
"version: 1\nserve:\n http:\n enabled: true\n bind: \"{bind}\"\n identity_token_env: \"{token_env}\"\n"
),
@@ -2693,7 +2672,7 @@ fn hmac_component_header_with_freshness(
nonce: &str,
) -> String {
format!(
- "clarion:{}",
+ "loomweave:{}",
hmac_sha256_hex(
secret.as_bytes(),
canonical_hmac_message(method, path_and_query, body, timestamp, nonce).as_bytes()
@@ -2783,7 +2762,7 @@ fn spawn_serve(project_root: &Path) -> ServeChild {
fn spawn_serve_with_env(project_root: &Path, env: &[(&str, &str)]) -> ServeChild {
release_reserved_loopback_bind(project_root);
- let mut command = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion"));
+ let mut command = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave"));
command
.args(["serve", "--path"])
.arg(project_root)
@@ -2793,7 +2772,7 @@ fn spawn_serve_with_env(project_root: &Path, env: &[(&str, &str)]) -> ServeChild
for (key, value) in env {
command.env(key, value);
}
- ServeChild::new(command.spawn().expect("spawn clarion serve"))
+ ServeChild::new(command.spawn().expect("spawn loomweave serve"))
}
fn stop_serve(child: &mut ServeChild) {
diff --git a/crates/clarion-cli/tests/skills.rs b/crates/loomweave-cli/tests/skills.rs
similarity index 69%
rename from crates/clarion-cli/tests/skills.rs
rename to crates/loomweave-cli/tests/skills.rs
index f09505f9..e01fc549 100644
--- a/crates/clarion-cli/tests/skills.rs
+++ b/crates/loomweave-cli/tests/skills.rs
@@ -1,15 +1,15 @@
-//! `clarion install --skills/--codex-skills/--hooks/--all` integration tests.
+//! `loomweave install --skills/--codex-skills/--hooks/--all` integration tests.
use std::fs;
use assert_cmd::Command;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -17,9 +17,9 @@ fn clarion_bin() -> Command {
}
#[test]
-fn install_skills_writes_claude_pack_without_initialising_clarion_dir() {
+fn install_skills_writes_claude_pack_without_initialising_loomweave_dir() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--skills", "--path"])
.arg(dir.path())
.assert()
@@ -27,27 +27,27 @@ fn install_skills_writes_claude_pack_without_initialising_clarion_dir() {
assert!(
dir.path()
- .join(".claude/skills/clarion-workflow/SKILL.md")
+ .join(".claude/skills/loomweave-workflow/SKILL.md")
.exists(),
"skill not installed under .claude"
);
assert!(
!dir.path()
- .join(".agents/skills/clarion-workflow/SKILL.md")
+ .join(".agents/skills/loomweave-workflow/SKILL.md")
.exists(),
"--skills should not install Codex skills under .agents"
);
- // --skills MUST NOT init .clarion/.
+ // --skills MUST NOT init .loomweave/.
assert!(
- !dir.path().join(".clarion").exists(),
- "--skills should not create .clarion/"
+ !dir.path().join(".loomweave").exists(),
+ "--skills should not create .loomweave/"
);
}
#[test]
-fn install_codex_skills_writes_agents_pack_without_initialising_clarion_dir() {
+fn install_codex_skills_writes_agents_pack_without_initialising_loomweave_dir() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--codex-skills", "--path"])
.arg(dir.path())
.assert()
@@ -55,19 +55,19 @@ fn install_codex_skills_writes_agents_pack_without_initialising_clarion_dir() {
assert!(
!dir.path()
- .join(".claude/skills/clarion-workflow/SKILL.md")
+ .join(".claude/skills/loomweave-workflow/SKILL.md")
.exists(),
"--codex-skills should not install Claude skills under .claude"
);
assert!(
dir.path()
- .join(".agents/skills/clarion-workflow/SKILL.md")
+ .join(".agents/skills/loomweave-workflow/SKILL.md")
.exists(),
"Codex skill not installed under .agents"
);
assert!(
- !dir.path().join(".clarion").exists(),
- "--codex-skills should not create .clarion/"
+ !dir.path().join(".loomweave").exists(),
+ "--codex-skills should not create .loomweave/"
);
}
@@ -75,15 +75,18 @@ fn install_codex_skills_writes_agents_pack_without_initialising_clarion_dir() {
fn install_skills_is_idempotent() {
let dir = tempfile::tempdir().unwrap();
for _ in 0..2 {
- clarion_bin()
+ loomweave_bin()
.args(["install", "--skills", "--path"])
.arg(dir.path())
.assert()
.success();
}
- let body =
- fs::read_to_string(dir.path().join(".claude/skills/clarion-workflow/SKILL.md")).unwrap();
- assert!(body.contains("name: clarion-workflow"));
+ let body = fs::read_to_string(
+ dir.path()
+ .join(".claude/skills/loomweave-workflow/SKILL.md"),
+ )
+ .unwrap();
+ assert!(body.contains("name: loomweave-workflow"));
}
#[test]
@@ -97,7 +100,7 @@ fn install_hooks_merges_session_start_without_clobbering() {
)
.unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--hooks", "--path"])
.arg(dir.path())
.assert()
@@ -119,38 +122,41 @@ fn install_hooks_merges_session_start_without_clobbering() {
.collect();
assert!(
cmds.iter()
- .any(|c| c.contains("clarion hook session-start"))
+ .any(|c| c.contains("loomweave hook session-start"))
);
- assert!(!dir.path().join(".clarion").exists());
+ assert!(!dir.path().join(".loomweave").exists());
}
#[test]
fn install_all_does_init_skills_and_hooks() {
let dir = tempfile::tempdir().unwrap();
- clarion_bin()
+ loomweave_bin()
.args(["install", "--all", "--path"])
.arg(dir.path())
.assert()
.success();
- assert!(dir.path().join(".clarion/clarion.db").exists(), "no db");
+ assert!(dir.path().join(".loomweave/loomweave.db").exists(), "no db");
assert!(
dir.path()
- .join(".claude/skills/clarion-workflow/SKILL.md")
+ .join(".claude/skills/loomweave-workflow/SKILL.md")
.exists(),
"no Claude skill"
);
assert!(
dir.path()
- .join(".agents/skills/clarion-workflow/SKILL.md")
+ .join(".agents/skills/loomweave-workflow/SKILL.md")
.exists(),
"no Codex skill"
);
let raw = fs::read_to_string(dir.path().join(".claude/settings.json")).unwrap();
- assert!(raw.contains("clarion hook session-start"), "no hook: {raw}");
+ assert!(
+ raw.contains("loomweave hook session-start"),
+ "no hook: {raw}"
+ );
let mcp_raw = fs::read_to_string(dir.path().join(".mcp.json")).unwrap();
assert!(
- mcp_raw.contains("\"clarion\""),
+ mcp_raw.contains("\"loomweave\""),
"no Claude Code MCP entry: {mcp_raw}"
);
}
@@ -159,18 +165,18 @@ fn install_all_does_init_skills_and_hooks() {
fn install_all_is_rerunnable_and_preserves_index() {
let dir = tempfile::tempdir().unwrap();
// First --all: full setup.
- clarion_bin()
+ loomweave_bin()
.args(["install", "--all", "--path"])
.arg(dir.path())
.assert()
.success();
- let db = dir.path().join(".clarion/clarion.db");
+ let db = dir.path().join(".loomweave/loomweave.db");
assert!(db.exists(), "first --all did not create db");
// Mark the db so we can prove the second run did NOT recreate it.
let before = std::fs::metadata(&db).unwrap().modified().unwrap();
// Second --all: must succeed (not bail), keep the index, re-apply skills/hooks.
- clarion_bin()
+ loomweave_bin()
.args(["install", "--all", "--path"])
.arg(dir.path())
.assert()
@@ -183,13 +189,13 @@ fn install_all_is_rerunnable_and_preserves_index() {
);
assert!(
dir.path()
- .join(".claude/skills/clarion-workflow/SKILL.md")
+ .join(".claude/skills/loomweave-workflow/SKILL.md")
.exists(),
"skill missing after rerun"
);
let raw = std::fs::read_to_string(dir.path().join(".claude/settings.json")).unwrap();
assert!(
- raw.contains("clarion hook session-start"),
+ raw.contains("loomweave hook session-start"),
"hook missing after rerun"
);
}
diff --git a/crates/clarion-cli/tests/wp1_e2e.rs b/crates/loomweave-cli/tests/wp1_e2e.rs
similarity index 71%
rename from crates/clarion-cli/tests/wp1_e2e.rs
rename to crates/loomweave-cli/tests/wp1_e2e.rs
index 238aaf1d..e1e52624 100644
--- a/crates/clarion-cli/tests/wp1_e2e.rs
+++ b/crates/loomweave-cli/tests/wp1_e2e.rs
@@ -6,12 +6,12 @@
use assert_cmd::Command;
use rusqlite::Connection;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
@@ -22,7 +22,7 @@ fn clarion_bin() -> Command {
fn wp1_walking_skeleton_end_to_end() {
let dir = tempfile::tempdir().unwrap();
- // Scrub PATH on every clarion invocation. The runner's PATH almost
+ // Scrub PATH on every loomweave invocation. The runner's PATH almost
// always contains world-writable directories (`/usr/local/bin`,
// `/opt/pipx_bin`, …) which trip WP2 scrub commit `7c0e396`'s
// refusal during plugin discovery; an empty PATH guarantees the
@@ -30,22 +30,22 @@ fn wp1_walking_skeleton_end_to_end() {
// `tests/analyze.rs::analyze_without_plugins_writes_skipped_run_row`
// (scrub commit `ad054bd`).
- // Step 1: clarion install
- clarion_bin()
+ // Step 1: loomweave install
+ loomweave_bin()
.args(["install", "--path"])
.arg(dir.path())
.env("PATH", "")
.assert()
.success();
- let clarion_dir = dir.path().join(".clarion");
- assert!(clarion_dir.join("clarion.db").exists());
- assert!(clarion_dir.join("config.json").exists());
- assert!(clarion_dir.join(".gitignore").exists());
- assert!(dir.path().join("clarion.yaml").exists());
+ let loomweave_dir = dir.path().join(".loomweave");
+ assert!(loomweave_dir.join("loomweave.db").exists());
+ assert!(loomweave_dir.join("config.json").exists());
+ assert!(loomweave_dir.join(".gitignore").exists());
+ assert!(dir.path().join("loomweave.yaml").exists());
- // Step 2: clarion analyze (no plugins yet — WP2 wires them)
- clarion_bin()
+ // Step 2: loomweave analyze (no plugins yet — WP2 wires them)
+ loomweave_bin()
.args(["analyze"])
.arg(dir.path())
.env("PATH", "")
@@ -53,7 +53,7 @@ fn wp1_walking_skeleton_end_to_end() {
.success();
// Step 3: verify expected shape in the DB.
- let conn = Connection::open(clarion_dir.join("clarion.db")).unwrap();
+ let conn = Connection::open(loomweave_dir.join("loomweave.db")).unwrap();
let migration_version: i64 = conn
.query_row("SELECT MAX(version) FROM schema_migrations", [], |row| {
@@ -62,7 +62,7 @@ fn wp1_walking_skeleton_end_to_end() {
.unwrap();
assert_eq!(
migration_version,
- i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION),
+ i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION),
"schema not on the latest migration"
);
diff --git a/crates/clarion-cli/tests/wp2_e2e.rs b/crates/loomweave-cli/tests/wp2_e2e.rs
similarity index 87%
rename from crates/clarion-cli/tests/wp2_e2e.rs
rename to crates/loomweave-cli/tests/wp2_e2e.rs
index 886c1008..596fd2a1 100644
--- a/crates/clarion-cli/tests/wp2_e2e.rs
+++ b/crates/loomweave-cli/tests/wp2_e2e.rs
@@ -3,11 +3,11 @@
//! Proves signoff A.2.8: the full Sprint 1 walking-skeleton pipeline works.
//!
//! Scenario:
-//! 1. `clarion install` initialises `.clarion/clarion.db`.
-//! 2. A `clarion-plugin-fixture` binary is placed on a synthetic `$PATH`
+//! 1. `loomweave install` initialises `.loomweave/loomweave.db`.
+//! 2. A `loomweave-plugin-fixture` binary is placed on a synthetic `$PATH`
//! alongside its `plugin.toml` (neighbour-discovery convention, L9).
//! 3. A single source file `demo.mt` is created in the project root.
-//! 4. `clarion analyze` discovers the fixture plugin, spawns it,
+//! 4. `loomweave analyze` discovers the fixture plugin, spawns it,
//! handshakes, calls `analyze_file` once, receives one entity, and
//! persists it to the `entities` table.
//!
@@ -24,35 +24,40 @@ use assert_cmd::Command;
// it from tripping `-D warnings` as unused on non-Linux targets
// (clarion-12667da9f5).
#[cfg(target_os = "linux")]
-use clarion_core::plugin::limits::FINDING_OOM_KILLED;
+use loomweave_core::plugin::limits::FINDING_OOM_KILLED;
use rusqlite::Connection;
use tempfile::TempDir;
-fn clarion_bin() -> Command {
- let mut cmd = Command::cargo_bin("clarion").expect("clarion binary");
+fn loomweave_bin() -> Command {
+ let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary");
cmd.env(
- "CLARION_CODEX_CONFIG",
+ "LOOMWEAVE_CODEX_CONFIG",
std::env::temp_dir().join(format!(
- "clarion-test-codex-config-{}.toml",
+ "loomweave-test-codex-config-{}.toml",
std::process::id()
)),
);
cmd
}
-/// Locate the `clarion-plugin-fixture` binary.
+/// Locate the `loomweave-fixture-plugin` binary.
///
-/// Tries `CARGO_BIN_EXE_clarion-plugin-fixture` first (set by cargo nextest
-/// when `clarion-plugin-fixture` appears in `[dev-dependencies]`). Falls back
-/// to the standard `target/{debug,release}/` search.
+/// The cargo artifact is named `loomweave-fixture-plugin` (off the
+/// `loomweave-plugin-*` discovery glob — see the fixture crate's Cargo.toml);
+/// `setup_plugin_dir` symlinks it back under the `loomweave-plugin-fixture`
+/// glob name so discovery finds it on the synthetic `$PATH`.
+///
+/// Tries `CARGO_BIN_EXE_loomweave-fixture-plugin` first (set by cargo nextest
+/// when the fixture appears in `[dev-dependencies]`). Falls back to the
+/// standard `target/{debug,release}/` search.
fn fixture_binary_path() -> PathBuf {
- if let Ok(path) = env::var("CARGO_BIN_EXE_clarion-plugin-fixture") {
+ if let Ok(path) = env::var("CARGO_BIN_EXE_loomweave-fixture-plugin") {
return PathBuf::from(path);
}
// Fallback: search target/ relative to the workspace root.
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
- // clarion-cli is at crates/clarion-cli; workspace root is ../../
+ // loomweave-cli is at crates/loomweave-cli; workspace root is ../../
let workspace_root = manifest_dir
.parent() // crates/
.and_then(|p| p.parent()) // workspace root
@@ -62,14 +67,14 @@ fn fixture_binary_path() -> PathBuf {
env::var("CARGO_TARGET_DIR").map_or_else(|_| workspace_root.join("target"), PathBuf::from);
for profile in &["debug", "release"] {
- let candidate = target_dir.join(profile).join("clarion-plugin-fixture");
+ let candidate = target_dir.join(profile).join("loomweave-fixture-plugin");
if candidate.exists() {
return candidate;
}
}
panic!(
- "clarion-plugin-fixture binary not found. \
+ "loomweave-fixture-plugin binary not found. \
Run `cargo build --workspace` before running this test. \
Searched: {}",
target_dir.display()
@@ -77,7 +82,7 @@ fn fixture_binary_path() -> PathBuf {
}
/// Set up a synthetic `$PATH` directory containing:
-/// - `clarion-plugin-fixture` executable (symlink to the real binary).
+/// - `loomweave-plugin-fixture` executable (symlink to the real binary).
/// - `plugin.toml` manifest (copied from the core test fixtures).
///
/// Returns the temp dir (must stay alive for the duration of the test).
@@ -85,8 +90,8 @@ fn setup_plugin_dir(fixture_bin: &PathBuf) -> TempDir {
let plugin_dir = TempDir::new().expect("create plugin tempdir");
// Symlink the fixture binary into the dir under its expected name.
- let dest = plugin_dir.path().join("clarion-plugin-fixture");
- std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink clarion-plugin-fixture");
+ let dest = plugin_dir.path().join("loomweave-plugin-fixture");
+ std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink loomweave-plugin-fixture");
// Verify the target is executable.
let meta = fs::metadata(fixture_bin).expect("stat fixture binary");
@@ -99,7 +104,7 @@ fn setup_plugin_dir(fixture_bin: &PathBuf) -> TempDir {
let toml_src = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent() // crates/
.unwrap()
- .join("clarion-core")
+ .join("loomweave-core")
.join("tests")
.join("fixtures")
.join("plugin.toml");
@@ -113,16 +118,16 @@ fn setup_plugin_dir(fixture_bin: &PathBuf) -> TempDir {
fn setup_oom_plugin_dir(fixture_bin: &PathBuf) -> TempDir {
let plugin_dir = TempDir::new().expect("create oom plugin tempdir");
- let dest = plugin_dir.path().join("clarion-plugin-oom");
- std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink clarion-plugin-oom");
+ let dest = plugin_dir.path().join("loomweave-plugin-oom");
+ std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink loomweave-plugin-oom");
let manifest = r#"
[plugin]
-name = "clarion-plugin-oom"
+name = "loomweave-plugin-oom"
plugin_id = "oom"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-oom"
+executable = "loomweave-plugin-oom"
language = "fixture"
extensions = ["oom"]
@@ -135,7 +140,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = []
-rule_id_prefix = "CLA-OOM-"
+rule_id_prefix = "LMWV-OOM-"
ontology_version = "0.1.0"
"#;
fs::write(plugin_dir.path().join("plugin.toml"), manifest).expect("write oom plugin.toml");
@@ -154,8 +159,8 @@ fn wp2_e2e_smoke_fixture_plugin_round_trip() {
// 3. Set up the project directory.
let project_dir = TempDir::new().expect("create project tempdir");
- // 4. `clarion install` to initialise `.clarion/`.
- clarion_bin()
+ // 4. `loomweave install` to initialise `.loomweave/`.
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -176,8 +181,8 @@ fn wp2_e2e_smoke_fixture_plugin_round_trip() {
let new_path =
env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).expect("join_paths");
- // 7. Run `clarion analyze` with the synthetic PATH.
- clarion_bin()
+ // 7. Run `loomweave analyze` with the synthetic PATH.
+ loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &new_path)
@@ -185,7 +190,7 @@ fn wp2_e2e_smoke_fixture_plugin_round_trip() {
.success();
// 8. Verify the database — full round-trip identity assertions.
- let db_path = project_dir.path().join(".clarion/clarion.db");
+ let db_path = project_dir.path().join(".loomweave/loomweave.db");
let conn = Connection::open(&db_path).expect("open db");
// Assert 1 + 2: exactly one run row with status "completed".
@@ -273,7 +278,7 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() {
let plugin_dir = setup_oom_plugin_dir(&fixture_bin);
let project_dir = TempDir::new().expect("create project tempdir");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -283,11 +288,11 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() {
let new_path =
env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).expect("join_paths");
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &new_path)
- .env("CLARION_FIXTURE_EXCEED_RLIMIT_AS", "1")
+ .env("LOOMWEAVE_FIXTURE_EXCEED_RLIMIT_AS", "1")
.assert()
.failure();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
@@ -297,7 +302,7 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() {
"OOM finding missing from analyze diagnostics.\nstdout: {stdout}\nstderr: {stderr}"
);
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let (run_status, stats_raw): (String, String) = conn
.query_row("SELECT status, stats FROM runs LIMIT 1", [], |row| {
Ok((row.get(0)?, row.get(1)?))
@@ -320,8 +325,8 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() {
/// longer tanks the whole run.
///
/// Scenario:
-/// - `clarion-plugin-fixture` + its manifest in `plugin_dir_a` (extensions = mt)
-/// - `clarion-plugin-broken` (symlink to /bin/true) + a manifest declaring
+/// - `loomweave-plugin-fixture` + its manifest in `plugin_dir_a` (extensions = mt)
+/// - `loomweave-plugin-broken` (symlink to /bin/true) + a manifest declaring
/// `plugin_id` "broken" and extensions = "bk" in `plugin_dir_b`
/// - Project root has `demo.mt` (fixture input) and `demo.bk` (broken input)
/// - Both plugin dirs prepended to PATH
@@ -341,15 +346,15 @@ fn wp2_crash_in_one_plugin_does_not_prevent_other_plugins_from_running() {
// 3. plugin_dir_b: broken plugin pointing at /bin/true.
let plugin_dir_b = TempDir::new().expect("create broken plugin dir");
- let broken_bin = plugin_dir_b.path().join("clarion-plugin-broken");
+ let broken_bin = plugin_dir_b.path().join("loomweave-plugin-broken");
std::os::unix::fs::symlink("/bin/true", &broken_bin).expect("symlink /bin/true");
let broken_manifest = r#"
[plugin]
-name = "clarion-plugin-broken"
+name = "loomweave-plugin-broken"
plugin_id = "broken"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-broken"
+executable = "loomweave-plugin-broken"
language = "broken"
extensions = ["bk"]
@@ -362,7 +367,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = []
-rule_id_prefix = "CLA-BROKEN-"
+rule_id_prefix = "LMWV-BROKEN-"
ontology_version = "0.1.0"
"#;
fs::write(plugin_dir_b.path().join("plugin.toml"), broken_manifest)
@@ -370,7 +375,7 @@ ontology_version = "0.1.0"
// 4. Set up project directory with one file per plugin extension.
let project_dir = TempDir::new().expect("create project tempdir");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -393,7 +398,7 @@ ontology_version = "0.1.0"
// 6. analyze must exit non-zero (a plugin crashed) but the run still
// processes the other plugin's files.
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &new_path)
@@ -407,7 +412,7 @@ ontology_version = "0.1.0"
// 7. Verify the DB: run = 'failed', entity from fixture IS persisted.
// `fail_run` writes the reason into stats.failure_reason (JSON).
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let (row_count, run_status, stats_raw): (i64, String, String) = conn
.query_row(
"SELECT COUNT(*), COALESCE(MAX(status), ''), COALESCE(MAX(stats), '') FROM runs",
@@ -490,16 +495,16 @@ fn wp2_crash_loop_breaker_trips_and_skips_remaining_plugins() {
for i in 0..4u8 {
let dir = TempDir::new().expect("create broken plugin dir");
let suffix = format!("broken{i}");
- let binary = dir.path().join(format!("clarion-plugin-{suffix}"));
+ let binary = dir.path().join(format!("loomweave-plugin-{suffix}"));
std::os::unix::fs::symlink("/bin/true", &binary).expect("symlink /bin/true");
let manifest = format!(
r#"[plugin]
-name = "clarion-plugin-{suffix}"
+name = "loomweave-plugin-{suffix}"
plugin_id = "{suffix}"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-{suffix}"
+executable = "loomweave-plugin-{suffix}"
language = "{suffix}"
extensions = ["b{i}"]
@@ -512,7 +517,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = []
-rule_id_prefix = "CLA-{PREFIX}-"
+rule_id_prefix = "LMWV-{PREFIX}-"
ontology_version = "0.1.0"
"#,
PREFIX = suffix.to_uppercase(),
@@ -533,7 +538,7 @@ ontology_version = "0.1.0"
// via the "no files match" path at analyze.rs ~208, confounding the
// "skipped by breaker" assertion.
let project_dir = TempDir::new().expect("create project tempdir");
- clarion_bin()
+ loomweave_bin()
.args(["install", "--path"])
.arg(project_dir.path())
.assert()
@@ -556,7 +561,7 @@ ontology_version = "0.1.0"
let new_path = env::join_paths(parts).expect("join_paths");
// 5. analyze must fail (exit 1) — plugins crashed.
- let out = clarion_bin()
+ let out = loomweave_bin()
.args(["analyze"])
.arg(project_dir.path())
.env("PATH", &new_path)
@@ -576,7 +581,7 @@ ontology_version = "0.1.0"
"breaker-tripped log line missing from stderr.\nstdout: {stdout}\nstderr: {stderr}"
);
assert!(
- stderr.contains("CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP"),
+ stderr.contains("LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP"),
"FINDING_DISABLED_CRASH_LOOP subcode missing from stderr.\nstdout: {stdout}\nstderr: {stderr}"
);
@@ -585,7 +590,7 @@ ontology_version = "0.1.0"
// synthetic `core:project:*` finding anchor (REQ-ANALYZE-06, minted to
// hold the persisted crash findings) is excluded — it is not a
// plugin-produced entity.
- let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap();
+ let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap();
let entity_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM entities WHERE NOT (plugin_id = 'core' AND kind = 'project')",
diff --git a/crates/clarion-core/Cargo.toml b/crates/loomweave-core/Cargo.toml
similarity index 95%
rename from crates/clarion-core/Cargo.toml
rename to crates/loomweave-core/Cargo.toml
index 1fd3da7d..e31077ce 100644
--- a/crates/clarion-core/Cargo.toml
+++ b/crates/loomweave-core/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "clarion-core"
+name = "loomweave-core"
version.workspace = true
edition.workspace = true
license.workspace = true
diff --git a/crates/clarion-core/src/embedding_provider.rs b/crates/loomweave-core/src/embedding_provider.rs
similarity index 99%
rename from crates/clarion-core/src/embedding_provider.rs
rename to crates/loomweave-core/src/embedding_provider.rs
index ee4aea35..498709ca 100644
--- a/crates/clarion-core/src/embedding_provider.rs
+++ b/crates/loomweave-core/src/embedding_provider.rs
@@ -2,7 +2,7 @@
//!
//! Mirrors [`crate::llm_provider`]: a small trait with a deterministic recording
//! double for tests and one live API-endpoint implementation. Embeddings are
-//! **opt-in** (off by default, like the LLM policy) — Loom is local-first, so
+//! **opt-in** (off by default, like the LLM policy) — Weft is local-first, so
//! nothing here makes a hosted service *required*. When semantic search is off
//! the MCP tool degrades honestly; it never fabricates an empty-as-complete
//! result.
diff --git a/crates/clarion-core/src/entity_id.rs b/crates/loomweave-core/src/entity_id.rs
similarity index 99%
rename from crates/clarion-core/src/entity_id.rs
rename to crates/loomweave-core/src/entity_id.rs
index 3f47964f..a335f20e 100644
--- a/crates/clarion-core/src/entity_id.rs
+++ b/crates/loomweave-core/src/entity_id.rs
@@ -1,6 +1,6 @@
//! Entity-ID assembler.
//!
-//! Per ADR-003 + ADR-022, every Clarion entity has a stable 3-segment ID:
+//! Per ADR-003 + ADR-022, every Loomweave entity has a stable 3-segment ID:
//! `{plugin_id}:{kind}:{canonical_qualified_name}`.
//!
//! - `plugin_id` and `kind` must match the grammar `[a-z][a-z0-9_]*`.
diff --git a/crates/clarion-core/src/errors.rs b/crates/loomweave-core/src/errors.rs
similarity index 96%
rename from crates/clarion-core/src/errors.rs
rename to crates/loomweave-core/src/errors.rs
index cc83ece3..9ac8f37c 100644
--- a/crates/clarion-core/src/errors.rs
+++ b/crates/loomweave-core/src/errors.rs
@@ -1,20 +1,20 @@
-//! Shared error-code vocabularies for Clarion's two structured wire surfaces.
+//! Shared error-code vocabularies for Loomweave's two structured wire surfaces.
//!
-//! Clarion emits machine-routable error codes on two **independent** surfaces.
+//! Loomweave emits machine-routable error codes on two **independent** surfaces.
//! This module is the single typed source of truth for both, so they cannot
//! silently drift and so the MCP side gets compiler-checked codes instead of
//! bare string literals. The two vocabularies are deliberately **not merged**:
//! they serve different transports, different consumers, and different
//! granularities (see the narrowing table below). HTTP status is intentionally
//! chosen per endpoint and is therefore *not* derivable from the code — see
-//! `clarion-cli`'s `classify_read_error` and ADR-037.
+//! `loomweave-cli`'s `classify_read_error` and ADR-037.
//!
//! # Surfaces
//!
-//! * [`HttpErrorCode`] — the federation HTTP read API (`crates/clarion-cli`).
+//! * [`HttpErrorCode`] — the federation HTTP read API (`crates/loomweave-cli`).
//! `SCREAMING_SNAKE` on the wire; frozen contract in `docs/federation/contracts.md`
//! and ADR-034; switched on by Filigree / Wardline clients.
-//! * [`McpErrorCode`] — the MCP tool-error envelope (`crates/clarion-mcp`).
+//! * [`McpErrorCode`] — the MCP tool-error envelope (`crates/loomweave-mcp`).
//! kebab-case on the wire; consumed by consult-mode agents; pinned by tests.
//!
//! # MCP → HTTP narrowing relationship (documentation only)
@@ -180,7 +180,7 @@ mod tests {
#[test]
fn mcp_error_code_wire_strings_are_pinned() {
// These kebab spellings are stable on the MCP wire (pinned by
- // clarion-mcp/tests/storage_tools.rs and relied on by consult agents).
+ // loomweave-mcp/tests/storage_tools.rs and relied on by consult agents).
assert_eq!(McpErrorCode::InvalidPath.as_str(), "invalid-path");
assert_eq!(McpErrorCode::EntityNotFound.as_str(), "entity-not-found");
assert_eq!(McpErrorCode::StorageError.as_str(), "storage-error");
diff --git a/crates/clarion-core/src/hardened_git.rs b/crates/loomweave-core/src/hardened_git.rs
similarity index 98%
rename from crates/clarion-core/src/hardened_git.rs
rename to crates/loomweave-core/src/hardened_git.rs
index 91ef0019..729d437e 100644
--- a/crates/clarion-core/src/hardened_git.rs
+++ b/crates/loomweave-core/src/hardened_git.rs
@@ -1,7 +1,7 @@
//! Hardened `git` invocation for read-only probes against an **untrusted**
//! corpus.
//!
-//! Clarion analyzes and serves repositories whose contents are not trusted (the
+//! Loomweave analyzes and serves repositories whose contents are not trusted (the
//! same posture that motivates the plugin jail, ADR-021, and the pre-ingest
//! secret scanner). Running `git` inside such a repo is a command-execution
//! hazard: repo-local configuration and Git *attributes* can name programs that
@@ -115,7 +115,7 @@ const NULL_DEVICE: &str = "/dev/null";
///
/// ```no_run
/// # use std::path::Path;
-/// # use clarion_core::hardened_git_command;
+/// # use loomweave_core::hardened_git_command;
/// let out = hardened_git_command(Path::new("/corpus"))
/// .args(["rev-parse", "HEAD"])
/// .output();
diff --git a/crates/clarion-core/src/lib.rs b/crates/loomweave-core/src/lib.rs
similarity index 92%
rename from crates/clarion-core/src/lib.rs
rename to crates/loomweave-core/src/lib.rs
index 35b858a6..d30418b1 100644
--- a/crates/clarion-core/src/lib.rs
+++ b/crates/loomweave-core/src/lib.rs
@@ -1,10 +1,10 @@
-//! clarion-core — domain types, identifiers, and provider traits.
+//! loomweave-core — domain types, identifiers, and provider traits.
//!
//! # Re-export policy (ticket clarion-29acbcd042)
//!
//! Only facade types that external callers need are re-exported at the crate
//! root. Implementation types (`Frame`, `TransportError`, `RequestEnvelope`, etc.)
-//! remain accessible via `clarion_core::plugin::transport::*` and siblings.
+//! remain accessible via `loomweave_core::plugin::transport::*` and siblings.
pub mod embedding_provider;
pub mod entity_id;
diff --git a/crates/clarion-core/src/llm_provider.rs b/crates/loomweave-core/src/llm_provider.rs
similarity index 96%
rename from crates/clarion-core/src/llm_provider.rs
rename to crates/loomweave-core/src/llm_provider.rs
index 0287f717..dcc81c09 100644
--- a/crates/clarion-core/src/llm_provider.rs
+++ b/crates/loomweave-core/src/llm_provider.rs
@@ -15,8 +15,8 @@ use thiserror::Error;
pub const LEAF_SUMMARY_PROMPT_TEMPLATE_ID: &str = "leaf-v1";
pub const INFERRED_CALLS_PROMPT_VERSION: &str = "inferred-calls-v1";
-const AGENT_PROVIDER_PROMPT_VERSION: &str = "clarion-agent-provider-v1";
-const CLAUDE_CLI_PRINT_PROMPT: &str = "You are Clarion's local Claude Code LLM provider. Read the Clarion provider prompt from stdin, complete that exact task, and return only the validated JSON object.";
+const AGENT_PROVIDER_PROMPT_VERSION: &str = "loomweave-agent-provider-v1";
+const CLAUDE_CLI_PRINT_PROMPT: &str = "You are Loomweave's local Claude Code LLM provider. Read the Loomweave provider prompt from stdin, complete that exact task, and return only the validated JSON object.";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum LlmPurpose {
@@ -115,21 +115,21 @@ pub trait LlmProvider: Send + Sync {
pub fn build_coding_agent_provider_prompt(request: &LlmRequest) -> String {
format!(
"Prompt contract: {prompt_version}\n\
- You are Clarion's coding-agent LLM provider for repository graph enrichment.\n\
- Clarion has already selected the source excerpt, entity metadata, unresolved call sites, and candidate graph context needed for this task.\n\
+ You are Loomweave's coding-agent LLM provider for repository graph enrichment.\n\
+ Loomweave has already selected the source excerpt, entity metadata, unresolved call sites, and candidate graph context needed for this task.\n\
Follow these rules exactly:\n\
- 1. Use only the evidence inside . Do not inspect additional files, browse, run commands, edit files, or ask follow-up questions.\n\
+ 1. Use only the evidence inside . Do not inspect additional files, browse, run commands, edit files, or ask follow-up questions.\n\
2. Return exactly one JSON object matching the structured-output schema supplied by the caller. Do not wrap it in Markdown or prose.\n\
3. Reason privately if needed, but do not expose hidden reasoning. Put only concise evidence summaries in output fields that ask for rationale or relationships.\n\
4. When evidence is absent, prefer empty strings for optional prose fields and empty arrays for collection fields instead of guessing.\n\
- 5. Keep stable field names and JSON types; downstream Clarion storage parses the response mechanically.\n\
+ 5. Keep stable field names and JSON types; downstream Loomweave storage parses the response mechanically.\n\
Task type: {task_type}\n\
Prompt template: {prompt_id}\n\
Task guidance:\n\
{task_guidance}\n\
- \n\
+ \n\
{prompt}\n\
- \n",
+ \n",
prompt_version = AGENT_PROVIDER_PROMPT_VERSION,
task_type = agent_task_type(&request.purpose),
prompt_id = request.prompt_id,
@@ -363,8 +363,8 @@ impl LlmProvider for OpenRouterProvider {
/// Resolve `executable` via `which::which` and return a typed CLI error if
/// it is missing on PATH or at the configured absolute path. Called from each
-/// CLI provider's `from_config` so a typo in `clarion.yaml` aborts at
-/// `clarion serve` startup rather than exploding on the first MCP request.
+/// CLI provider's `from_config` so a typo in `loomweave.yaml` aborts at
+/// `loomweave serve` startup rather than exploding on the first MCP request.
fn validate_cli_executable(label: &str, executable: &str) -> Result<(), LlmProviderError> {
which::which(executable).map_err(|err| LlmProviderError::Cli {
message: format!("{label} executable {executable:?} not resolvable: {err}"),
@@ -563,8 +563,8 @@ impl LlmProvider for CodexCliProvider {
async fn invoke(&self, request: LlmRequest) -> Result {
let this = self.clone();
tokio::task::spawn_blocking(move || {
- let output_file = codex_temp_file("clarion-codex-output", ".json")?;
- let schema_file = codex_temp_file("clarion-codex-schema", ".json")?;
+ let output_file = codex_temp_file("loomweave-codex-output", ".json")?;
+ let schema_file = codex_temp_file("loomweave-codex-schema", ".json")?;
this.invoke_with_temp_files(request, output_file.path(), schema_file.path())
})
.await
@@ -803,7 +803,7 @@ fn response_format_for_purpose(purpose: &LlmPurpose) -> Value {
LlmPurpose::Summary => serde_json::json!({
"type": "json_schema",
"json_schema": {
- "name": "clarion_summary",
+ "name": "loomweave_summary",
"strict": true,
"schema": {
"type": "object",
@@ -833,7 +833,7 @@ fn response_format_for_purpose(purpose: &LlmPurpose) -> Value {
LlmPurpose::InferredEdges => serde_json::json!({
"type": "json_schema",
"json_schema": {
- "name": "clarion_inferred_calls",
+ "name": "loomweave_inferred_calls",
"strict": true,
"schema": {
"type": "object",
@@ -849,7 +849,7 @@ fn response_format_for_purpose(purpose: &LlmPurpose) -> Value {
},
"target_id": {
"type": "string",
- "description": "The Clarion entity id for the inferred callee."
+ "description": "The Loomweave entity id for the inferred callee."
},
"confidence": {
"type": "number",
@@ -1414,7 +1414,7 @@ pub fn build_leaf_summary_prompt(input: &LeafSummaryPromptInput) -> PromptTempla
PromptTemplate {
id: LEAF_SUMMARY_PROMPT_TEMPLATE_ID,
body: format!(
- "You are summarising one Clarion entity at leaf scope only.\n\
+ "You are summarising one Loomweave entity at leaf scope only.\n\
Entity id: {entity_id}\n\
Kind: {kind}\n\
Name: {name}\n\
@@ -1434,7 +1434,7 @@ pub fn build_inferred_calls_prompt(input: &InferredCallsPromptInput) -> PromptTe
PromptTemplate {
id: INFERRED_CALLS_PROMPT_VERSION,
body: format!(
- "You are resolving unresolved Clarion call sites for one caller.\n\
+ "You are resolving unresolved Loomweave call sites for one caller.\n\
Caller entity id: {caller}\n\
Caller source excerpt:\n{source}\n\
Unresolved call sites JSON:\n{sites}\n\
@@ -1528,12 +1528,12 @@ mod tests {
let prompt = build_coding_agent_provider_prompt(&request);
- assert!(prompt.contains("Prompt contract: clarion-agent-provider-v1"));
+ assert!(prompt.contains("Prompt contract: loomweave-agent-provider-v1"));
assert!(prompt.contains("Task type: inferred_edges"));
assert!(prompt.contains("Do not inspect additional files"));
assert!(prompt.contains("Return exactly one JSON object"));
assert!(prompt.contains("Choose targets only from the supplied candidate entities JSON"));
- assert!(prompt.contains(""));
+ assert!(prompt.contains(""));
assert!(prompt.contains("Resolve call-site a from the supplied candidates"));
}
@@ -1544,8 +1544,8 @@ mod tests {
allow_live_provider: false,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: "https://openrouter.ai/api/v1".to_owned(),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 30,
})
.expect_err("api key alone must not enable live calls");
@@ -1556,8 +1556,8 @@ mod tests {
allow_live_provider: true,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: "https://openrouter.ai/api/v1".to_owned(),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 30,
})
.expect_err("live opt-in without key should fail");
@@ -1568,8 +1568,8 @@ mod tests {
allow_live_provider: true,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: "https://openrouter.ai/api/v1".to_owned(),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 30,
})
.expect("live opt-in and key should construct provider");
@@ -1593,8 +1593,8 @@ mod tests {
allow_live_provider: true,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: "https://openrouter.ai/api/v1".to_owned(),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 0,
});
assert!(
@@ -1617,13 +1617,13 @@ mod tests {
let request = String::from_utf8_lossy(&request[..read]);
assert!(request.contains("POST /api/v1/chat/completions HTTP/1.1"));
assert!(request.contains("authorization: Bearer secret"));
- assert!(request.contains("http-referer: https://github.com/tachyon-beep/clarion"));
- assert!(request.contains("x-openrouter-title: Clarion"));
+ assert!(request.contains("http-referer: https://github.com/foundryside-dev/loomweave"));
+ assert!(request.contains("x-openrouter-title: Loomweave"));
assert!(request.contains(r#""model":"anthropic/claude-sonnet-4.6""#));
assert!(request.contains(r#""max_tokens":512"#));
assert!(request.contains("Summarize this function"));
assert!(
- request.contains(r#""response_format":{"json_schema":{"name":"clarion_summary""#)
+ request.contains(r#""response_format":{"json_schema":{"name":"loomweave_summary""#)
);
assert!(request.contains(r#""strict":true"#));
assert!(
@@ -1657,8 +1657,8 @@ mod tests {
allow_live_provider: true,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: format!("http://{addr}/api/v1"),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 30,
})
.expect("test provider");
@@ -1746,11 +1746,9 @@ mod tests {
let mut request = [0_u8; 8192];
let read = stream.read(&mut request).expect("read request");
let request = String::from_utf8_lossy(&request[..read]);
- assert!(
- request.contains(
- r#""response_format":{"json_schema":{"name":"clarion_inferred_calls""#
- )
- );
+ assert!(request.contains(
+ r#""response_format":{"json_schema":{"name":"loomweave_inferred_calls""#
+ ));
assert!(request.contains(r#""required":["edges"]"#));
assert!(
request.contains(r#""required":["site_key","target_id","confidence","rationale"]"#)
@@ -1783,8 +1781,8 @@ mod tests {
allow_live_provider: true,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: format!("http://{addr}/api/v1"),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 30,
})
.expect("test provider");
@@ -1816,8 +1814,8 @@ mod tests {
allow_live_provider: true,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: format!("http://{addr}/api/v1"),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 30,
})
.expect("test provider");
@@ -1917,8 +1915,8 @@ case "$stdin_prompt" in
*) echo "missing prompt" >&2; exit 31 ;;
esac
case "$stdin_prompt" in
- *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*) ;;
- *) echo "missing Clarion agent prompt contract" >&2; exit 32 ;;
+ *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*) ;;
+ *) echo "missing Loomweave agent prompt contract" >&2; exit 32 ;;
esac
echo "sandbox=$sandbox" >> "$log"
@@ -1940,7 +1938,7 @@ printf '%s' '{{"purpose":"via codex","behavior":"ran fake CLI","relationships":"
project_root: project_root.clone(),
model_id: "codex-cli-default".to_owned(),
model: Some("gpt-5.5".to_owned()),
- profile: Some("clarion".to_owned()),
+ profile: Some("loomweave".to_owned()),
sandbox: "read-only".to_owned(),
timeout_seconds: 5,
})
@@ -1976,7 +1974,7 @@ printf '%s' '{{"purpose":"via codex","behavior":"ran fake CLI","relationships":"
assert!(log.contains("sandbox=read-only"));
assert!(log.contains(&format!("cd={}", project_root.display())));
assert!(log.contains("model=gpt-5.5"));
- assert!(log.contains("profile=clarion"));
+ assert!(log.contains("profile=loomweave"));
}
#[tokio::test]
@@ -2140,8 +2138,8 @@ case "$stdin_prompt" in
*) echo "missing prompt" >&2; exit 42 ;;
esac
case "$stdin_prompt" in
- *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*) ;;
- *) echo "missing Clarion agent prompt contract" >&2; exit 43 ;;
+ *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*) ;;
+ *) echo "missing Loomweave agent prompt contract" >&2; exit 43 ;;
esac
echo "print_prompt=$print_prompt" >> "$log"
@@ -2210,7 +2208,7 @@ printf '%s\n' '{{"type":"result","subtype":"success","structured_output":{{"purp
);
let log = fs::read_to_string(log_path).expect("read fake claude log");
- assert!(log.contains("print_prompt=You are Clarion's local Claude Code LLM provider"));
+ assert!(log.contains("print_prompt=You are Loomweave's local Claude Code LLM provider"));
assert!(log.contains("model=claude-sonnet-4-6"));
assert!(log.contains("permission_mode=plan"));
assert!(log.contains("tools=Read,Grep"));
@@ -2542,8 +2540,8 @@ printf '%s\n' '{{"type":"result","subtype":"success","structured_output":{{"purp
allow_live_provider: true,
model_id: "anthropic/claude-sonnet-4.6".to_owned(),
endpoint_url: format!("http://{addr}/api/v1"),
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
timeout_seconds: 30,
})
.expect("test provider");
diff --git a/crates/clarion-core/src/plugin/breaker.rs b/crates/loomweave-core/src/plugin/breaker.rs
similarity index 97%
rename from crates/clarion-core/src/plugin/breaker.rs
rename to crates/loomweave-core/src/plugin/breaker.rs
index b9ca1a74..32aba0b3 100644
--- a/crates/clarion-core/src/plugin/breaker.rs
+++ b/crates/loomweave-core/src/plugin/breaker.rs
@@ -5,7 +5,7 @@
//! spawn attempts for the rolling-window duration.
//!
//! Sprint 1 hard-codes the threshold and window per UQ-WP2-10; the config
-//! surface (`clarion.yaml:plugin_limits.crash_*`) lands in WP6.
+//! surface (`loomweave.yaml:plugin_limits.crash_*`) lands in WP6.
use std::collections::VecDeque;
use std::time::{Duration, Instant};
@@ -13,7 +13,7 @@ use std::time::{Duration, Instant};
// ── Finding subcode constants ─────────────────────────────────────────────────
/// Subcode emitted when the breaker trips.
-pub const FINDING_DISABLED_CRASH_LOOP: &str = "CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP";
+pub const FINDING_DISABLED_CRASH_LOOP: &str = "LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP";
// ── CrashLoopState ────────────────────────────────────────────────────────────
@@ -266,7 +266,7 @@ mod tests {
"initialize",
&InitializeParams {
protocol_version: "1.0".to_owned(),
- project_root: "/tmp/clarion-breaker-test".to_owned(),
+ project_root: "/tmp/loomweave-breaker-test".to_owned(),
},
1,
);
@@ -303,7 +303,7 @@ mod tests {
let af_req = make_request(
"analyze_file",
&AnalyzeFileParams {
- file_path: "/tmp/clarion-breaker-test/stub.mock".to_owned(),
+ file_path: "/tmp/loomweave-breaker-test/stub.mock".to_owned(),
},
2,
);
diff --git a/crates/clarion-core/src/plugin/discovery.rs b/crates/loomweave-core/src/plugin/discovery.rs
similarity index 64%
rename from crates/clarion-core/src/plugin/discovery.rs
rename to crates/loomweave-core/src/plugin/discovery.rs
index 3a7829dc..b19bd98d 100644
--- a/crates/clarion-core/src/plugin/discovery.rs
+++ b/crates/loomweave-core/src/plugin/discovery.rs
@@ -1,42 +1,51 @@
-//! Plugin discovery via `$PATH` scanning (ADR-021 §L9).
+//! Plugin discovery via `$PATH` scanning and the running binary's own
+//! directory (ADR-021 §L9).
+//!
+//! Directories are scanned in order: every `$PATH` entry first, then the
+//! directory of the running `loomweave` binary (`std::env::current_exe()`'s
+//! parent). The exe-dir level makes a PyPI/venv install work — the plugin's
+//! console script is co-located in the same `bin/` as `loomweave` but is not on
+//! the user's `$PATH`.
//!
//! # Matching rule
//!
-//! A file is a Clarion plugin candidate if its name matches
-//! `clarion-plugin-` where `` is at least one character
-//! consisting solely of `[A-Za-z0-9_-]`. Names such as `clarion-plugin-`
-//! (empty suffix) or `clarion-plugin` (no second hyphen) are rejected.
+//! A file is a Loomweave plugin candidate if its name matches
+//! `loomweave-plugin-` where `` is at least one character
+//! consisting solely of `[A-Za-z0-9_-]`. Names such as `loomweave-plugin-`
+//! (empty suffix) or `loomweave-plugin` (no second hyphen) are rejected.
//!
//! Additionally the file must exist, be a regular file, and — on Unix — have
//! at least one executable bit set (`mode & 0o111 != 0`).
//!
//! # Manifest lookup order
//!
-//! For an executable at `/clarion-plugin-`:
+//! For an executable at `/loomweave-plugin-`:
//!
//! 1. **Neighbor first**: `/plugin.toml`.
//! 2. **Install-prefix fallback** (only when `` has basename `bin`):
-//! `/../share/clarion/plugins//plugin.toml`.
+//! `/../share/loomweave/plugins//plugin.toml`.
//! 3. **Symlink-resolved install-prefix fallback** (only when `` has
//! basename `bin` and the executable is a symlink, e.g. pipx layout):
//! canonicalise the executable, then try
-//! `/../share/clarion/plugins//plugin.toml`.
+//! `/../share/loomweave/plugins//plugin.toml`.
//! This catches `pipx install` (which puts a symlink in `~/.local/bin/`
//! pointing into `~/.local/share/pipx/venvs//bin/`, with the
//! manifest under that venv's `share/`).
//! 4. None found → [`DiscoveryError::ManifestNotFound`].
//!
-//! **Limitation**: when multiple `clarion-plugin-*` binaries share the same
+//! **Limitation**: when multiple `loomweave-plugin-*` binaries share the same
//! directory (e.g. `/usr/local/bin`), they all resolve to the *same*
//! neighbor `plugin.toml`. This is a known constraint of the neighbor
//! convention; real installs should use the install-prefix layout so each
-//! plugin has its own `share/clarion/plugins//plugin.toml`.
+//! plugin has its own `share/loomweave/plugins//plugin.toml`.
//!
//! # Deduplication
//!
//! Duplicate `$PATH` directories are skipped. If the same binary name
//! appears in multiple directories the first occurrence wins (matching
-//! POSIX shell / `which` semantics).
+//! POSIX shell / `which` semantics). Because `$PATH` is scanned before the
+//! exe directory, a PATH-installed plugin shadows a same-named sibling
+//! co-located next to the binary.
use std::collections::HashSet;
use std::ffi::OsStr;
@@ -49,20 +58,21 @@ use crate::plugin::{Manifest, ManifestError, parse_manifest};
// ── Public types ──────────────────────────────────────────────────────────────
-/// A plugin discovered via a `clarion-plugin-*` executable on `$PATH`.
+/// A plugin discovered via a `loomweave-plugin-*` executable on `$PATH`.
#[derive(Debug)]
pub struct DiscoveredPlugin {
- /// Path to the plugin executable **as found on `$PATH`**.
+ /// Path to the plugin executable **as found during discovery** (on
+ /// `$PATH`, or co-located in the running binary's directory).
///
/// Intentionally NOT canonicalised. The neighbour-manifest lookup
/// joins `plugin.toml` with this path's parent directory;
/// canonicalising here would follow symlinks (e.g.
- /// `~/bin/clarion-plugin-python` → `~/.local/pipx/venvs/*/bin/...`)
+ /// `~/bin/loomweave-plugin-python` → `~/.local/pipx/venvs/*/bin/...`)
/// and the manifest lookup would then miss the neighbour that lives
/// next to the symlink.
///
/// Deduplication uses a separate canonicalised key
- /// (`seen_dirs` inside [`discover_on_path`]), so the raw-path retained
+ /// (`seen_dirs` inside `scan_dir`), so the raw-path retained
/// here does not defeat shadowing.
///
/// If you need the real binary location for an operator message (e.g.
@@ -78,11 +88,11 @@ pub struct DiscoveredPlugin {
/// Errors produced during plugin discovery.
///
-/// Each variant corresponds to a single `clarion-plugin-*` binary; a
+/// Each variant corresponds to a single `loomweave-plugin-*` binary; a
/// failure for one plugin does **not** suppress results for others.
#[derive(Debug, Error)]
pub enum DiscoveryError {
- /// A `clarion-plugin-*` binary was found on `$PATH` but no `plugin.toml`
+ /// A `loomweave-plugin-*` binary was found on `$PATH` but no `plugin.toml`
/// was found at either the neighbor location or the install-prefix
/// location.
#[error(
@@ -114,7 +124,7 @@ pub enum DiscoveryError {
ManifestTooLarge { path: PathBuf },
/// A `$PATH` directory is world-writable. Any user with write
- /// access could drop a `clarion-plugin-*` binary into it. Refused
+ /// access could drop a `loomweave-plugin-*` binary into it. Refused
/// to preserve the ADR-021 "semi-trusted plugin" model — operator
/// must deliberately install plugins.
#[error(
@@ -134,12 +144,15 @@ pub const MAX_MANIFEST_BYTES: u64 = 64 * 1024;
/// Discover plugins on the user's `$PATH`.
///
/// Reads `$PATH` from the process environment and delegates to
-/// [`discover_on_path`]. Returns one `Result` per `clarion-plugin-*`
+/// [`discover_on_path`]. Returns one `Result` per `loomweave-plugin-*`
/// binary found.
#[cfg(unix)]
pub fn discover() -> Vec> {
let path_val = std::env::var_os("PATH").unwrap_or_default();
- discover_on_path(&path_val)
+ let exe_dir = std::env::current_exe()
+ .ok()
+ .and_then(|p| p.parent().map(std::path::Path::to_path_buf));
+ discover_on_path_and_exe_dir(&path_val, exe_dir.as_deref())
}
#[cfg(not(unix))]
@@ -150,10 +163,10 @@ pub fn discover() -> Vec> {
/// Discover plugins on the given explicit `PATH` value (useful in tests).
///
/// Parses `path_env` using [`std::env::split_paths`], then scans each
-/// directory for `clarion-plugin-*` executables. Returns one `Result` per
+/// directory for `loomweave-plugin-*` executables. Returns one `Result` per
/// candidate found; a broken plugin does not suppress its siblings.
///
-/// **Note**: if two `clarion-plugin-*` binaries sharing a directory both
+/// **Note**: if two `loomweave-plugin-*` binaries sharing a directory both
/// try to use the neighbor `plugin.toml`, they will resolve to the *same*
/// file. This is expected behaviour given the neighbor convention; see the
/// module-level docs for the recommended install-prefix layout.
@@ -161,88 +174,120 @@ pub fn discover() -> Vec> {
/// Primarily useful for testing; production callers should use [`discover`].
#[cfg(unix)]
pub fn discover_on_path(path_env: &OsStr) -> Vec> {
+ discover_on_path_and_exe_dir(path_env, None)
+}
+
+/// Like [`discover_on_path`], but additionally scans `exe_dir` (the directory of
+/// the running `loomweave` binary) **after** the `$PATH` entries. `$PATH` entries
+/// are scanned first, so a plugin found on `$PATH` shadows a same-named sibling
+/// next to the binary (first-match-wins, consistent with PATH shadowing).
+///
+/// This is the discovery source that makes a PyPI/venv install work: the plugin
+/// console script is co-located in the same `bin/` as `loomweave` but is not on
+/// the user's `$PATH`. See ADR-021.
+#[cfg(unix)]
+pub fn discover_on_path_and_exe_dir(
+ path_env: &OsStr,
+ exe_dir: Option<&std::path::Path>,
+) -> Vec> {
let mut results = Vec::new();
let mut seen_dirs: HashSet = HashSet::new();
let mut seen_names: HashSet = HashSet::new();
- for dir in std::env::split_paths(path_env) {
- // Skip empty entries (POSIX: empty means cwd — we don't support that).
- if dir.as_os_str().is_empty() {
- continue;
- }
+ let exe_dirs = exe_dir.map(std::path::Path::to_path_buf).into_iter();
+ for dir in std::env::split_paths(path_env).chain(exe_dirs) {
+ scan_dir(&dir, &mut seen_dirs, &mut seen_names, &mut results);
+ }
- // Deduplicate directories.
- let canonical_dir = match dir.canonicalize() {
- Ok(c) => c,
- // If the dir doesn't exist or can't be canonicalised, still use the
- // raw path for dedup so we don't skip a later entry that resolves
- // differently.
- Err(_) => dir.clone(),
- };
- if !seen_dirs.insert(canonical_dir.clone()) {
- continue;
- }
+ results
+}
+
+/// Scan a single directory for `loomweave-plugin-*` executables, appending results.
+/// Shared by every discovery source; honours dir/name de-duplication and the
+/// world-writable refusal (ADR-021).
+#[cfg(unix)]
+fn scan_dir(
+ dir: &std::path::Path,
+ seen_dirs: &mut HashSet,
+ seen_names: &mut HashSet,
+ results: &mut Vec>,
+) {
+ // Skip empty entries (POSIX: empty means cwd — we don't support that).
+ if dir.as_os_str().is_empty() {
+ return;
+ }
+
+ // Deduplicate directories.
+ let canonical_dir = match dir.canonicalize() {
+ Ok(c) => c,
+ // If the dir doesn't exist or can't be canonicalised, still use the
+ // raw path for dedup so we don't skip a later entry that resolves
+ // differently.
+ Err(_) => dir.to_path_buf(),
+ };
+ if !seen_dirs.insert(canonical_dir.clone()) {
+ return;
+ }
+
+ // Refuse to load plugins from world-writable directories. On a
+ // multi-user machine, any user with write access to a $PATH dir
+ // becomes a plugin installer — a threat model the hybrid-
+ // authority framing (ADR-021) rules out. Production installs
+ // should use `~/.local/bin` (0o755) or `/usr/local/bin` (0o755);
+ // only pathologically misconfigured dirs fail this check.
+ if is_world_writable(dir) {
+ results.push(Err(DiscoveryError::WorldWritableDir {
+ path: dir.to_path_buf(),
+ }));
+ return;
+ }
+
+ // Read directory entries; skip silently on I/O error (non-existent
+ // dirs are common in $PATH).
+ let Ok(entries) = std::fs::read_dir(dir) else {
+ return;
+ };
- // Refuse to load plugins from world-writable directories. On a
- // multi-user machine, any user with write access to a $PATH dir
- // becomes a plugin installer — a threat model the hybrid-
- // authority framing (ADR-021) rules out. Production installs
- // should use `~/.local/bin` (0o755) or `/usr/local/bin` (0o755);
- // only pathologically misconfigured dirs fail this check.
- if is_world_writable(&dir) {
- results.push(Err(DiscoveryError::WorldWritableDir { path: dir.clone() }));
+ for entry_result in entries {
+ let Ok(entry) = entry_result else {
continue;
- }
+ };
- // Read directory entries; skip silently on I/O error (non-existent
- // dirs are common in $PATH).
- let Ok(entries) = std::fs::read_dir(&dir) else {
+ // non-UTF-8 names can't match our prefix.
+ let Ok(file_name) = entry.file_name().into_string() else {
continue;
};
- for entry_result in entries {
- let Ok(entry) = entry_result else {
- continue;
- };
-
- // non-UTF-8 names can't match our prefix.
- let Ok(file_name) = entry.file_name().into_string() else {
- continue;
- };
-
- // ── Name filter ───────────────────────────────────────────────────
- let suffix = match extract_plugin_suffix(&file_name) {
- Some(s) => s.to_owned(),
- None => continue,
- };
-
- // ── Shadowing: first match wins ───────────────────────────────────
- // Safe to key on String: non-UTF-8 names were filtered above.
- if !seen_names.insert(file_name.clone()) {
- continue;
- }
+ // ── Name filter ───────────────────────────────────────────────────
+ let suffix = match extract_plugin_suffix(&file_name) {
+ Some(s) => s.to_owned(),
+ None => continue,
+ };
- // `exec_path` is the raw PATH-relative path (not canonicalised).
- // Do not canonicalise here — the neighbour-manifest convention
- // at `load_plugin` / `find_manifest` looks up `plugin.toml` next
- // to this path, and a symlink install pattern (e.g. `~/bin/` full
- // of symlinks into `~/.local/pipx/venvs/*/bin/`) expects the
- // manifest to live next to the symlink, not next to the resolved
- // binary in the venv. See the `executable` field doc-comment on
- // `DiscoveredPlugin` for the full consistency story.
- let exec_path = dir.join(&file_name);
-
- // ── Exec-bit check ────────────────────────────────────────────────
- if !is_executable(&exec_path) {
- continue;
- }
+ // ── Shadowing: first match wins ───────────────────────────────────
+ // Safe to key on String: non-UTF-8 names were filtered above.
+ if !seen_names.insert(file_name.clone()) {
+ continue;
+ }
- // ── Manifest lookup ───────────────────────────────────────────────
- results.push(load_plugin(exec_path, &suffix));
+ // `exec_path` is the raw PATH-relative path (not canonicalised).
+ // Do not canonicalise here — the neighbour-manifest convention
+ // at `load_plugin` / `find_manifest` looks up `plugin.toml` next
+ // to this path, and a symlink install pattern (e.g. `~/bin/` full
+ // of symlinks into `~/.local/pipx/venvs/*/bin/`) expects the
+ // manifest to live next to the symlink, not next to the resolved
+ // binary in the venv. See the `executable` field doc-comment on
+ // `DiscoveredPlugin` for the full consistency story.
+ let exec_path = dir.join(&file_name);
+
+ // ── Exec-bit check ────────────────────────────────────────────────
+ if !is_executable(&exec_path) {
+ continue;
}
- }
- results
+ // ── Manifest lookup ───────────────────────────────────────────────
+ results.push(load_plugin(exec_path, &suffix));
+ }
}
#[cfg(not(unix))]
@@ -250,13 +295,21 @@ pub fn discover_on_path(_path_env: &OsStr) -> Vec,
+) -> Vec> {
+ vec![]
+}
+
// ── Internal helpers ──────────────────────────────────────────────────────────
-/// Extract the `` from a `clarion-plugin-` name, or `None`.
+/// Extract the `` from a `loomweave-plugin-` name, or `None`.
///
/// Suffix must be at least one character and consist only of `[A-Za-z0-9_-]`.
fn extract_plugin_suffix(name: &str) -> Option<&str> {
- let suffix = name.strip_prefix("clarion-plugin-")?;
+ let suffix = name.strip_prefix("loomweave-plugin-")?;
if suffix.is_empty() {
return None;
}
@@ -373,8 +426,8 @@ fn find_manifest(exec_path: &std::path::Path, suffix: &str) -> Result is a symlink into
- // ~/.local/share/pipx/venvs//bin/clarion-plugin-, and
+ // layouts: ~/.local/bin/loomweave-plugin- is a symlink into
+ // ~/.local/share/pipx/venvs//bin/loomweave-plugin-, and
// the manifest lives under that venv's share/. Canonicalise
// the executable and re-try the install-prefix layout from
// the resolved location.
@@ -401,7 +454,7 @@ fn probe_install_prefix_manifest(
) -> Result, DiscoveryError> {
let share_path = prefix
.join("share")
- .join("clarion")
+ .join("loomweave")
.join("plugins")
.join(suffix)
.join("plugin.toml");
@@ -424,11 +477,11 @@ mod tests {
fn minimal_manifest_toml(plugin_id: &str) -> String {
format!(
r#"[plugin]
-name = "clarion-plugin-{plugin_id}"
+name = "loomweave-plugin-{plugin_id}"
plugin_id = "{plugin_id}"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-{plugin_id}"
+executable = "loomweave-plugin-{plugin_id}"
language = "{plugin_id}"
extensions = ["mt"]
@@ -441,7 +494,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["function"]
edge_kinds = ["calls"]
-rule_id_prefix = "CLA-MT-"
+rule_id_prefix = "LMWV-MT-"
ontology_version = "0.1.0"
"#
)
@@ -476,7 +529,7 @@ ontology_version = "0.1.0"
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
- make_executable(&bin.join("clarion-plugin-mocktest"));
+ make_executable(&bin.join("loomweave-plugin-mocktest"));
fs::write(bin.join("plugin.toml"), minimal_manifest_toml("mocktest")).unwrap();
let results = discover_on_path(&path_os(&[&bin]));
@@ -484,7 +537,7 @@ ontology_version = "0.1.0"
let plugin = results.into_iter().next().unwrap().unwrap();
assert_eq!(plugin.manifest.plugin.plugin_id, "mocktest");
- assert_eq!(plugin.executable, bin.join("clarion-plugin-mocktest"));
+ assert_eq!(plugin.executable, bin.join("loomweave-plugin-mocktest"));
assert_eq!(plugin.manifest_path, bin.join("plugin.toml"));
}
@@ -496,12 +549,12 @@ ontology_version = "0.1.0"
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
- make_executable(&bin.join("clarion-plugin-mocktest"));
+ make_executable(&bin.join("loomweave-plugin-mocktest"));
// No neighbor plugin.toml — only the share/ location.
let share = tmp
.path()
.join("share")
- .join("clarion")
+ .join("loomweave")
.join("plugins")
.join("mocktest");
fs::create_dir_all(&share).unwrap();
@@ -515,8 +568,87 @@ ontology_version = "0.1.0"
assert_eq!(
plugin.manifest_path,
tmp.path()
- .join("share/clarion/plugins/mocktest/plugin.toml")
+ .join("share/loomweave/plugins/mocktest/plugin.toml")
+ );
+ }
+
+ // ── T2b: current_exe() sibling level (install-prefix, NOT on $PATH) ────────
+
+ #[test]
+ fn t2b_exe_dir_install_prefix_found_when_not_on_path() {
+ let tmp = TempDir::new().unwrap();
+ let bin = tmp.path().join("bin");
+ fs::create_dir_all(&bin).unwrap();
+
+ make_executable(&bin.join("loomweave-plugin-mocktest"));
+ let share = tmp.path().join("share/loomweave/plugins/mocktest");
+ fs::create_dir_all(&share).unwrap();
+ fs::write(share.join("plugin.toml"), minimal_manifest_toml("mocktest")).unwrap();
+
+ // $PATH is EMPTY — the plugin is only reachable via the exe dir.
+ let results = discover_on_path_and_exe_dir(std::ffi::OsStr::new(""), Some(bin.as_path()));
+ assert_eq!(results.len(), 1, "exe-dir plugin should be discovered");
+
+ let plugin = results.into_iter().next().unwrap().unwrap();
+ assert_eq!(plugin.manifest.plugin.plugin_id, "mocktest");
+ assert_eq!(
+ plugin.manifest_path,
+ tmp.path()
+ .join("share/loomweave/plugins/mocktest/plugin.toml")
+ );
+ }
+
+ #[test]
+ fn t2c_path_entry_shadows_same_named_exe_dir_sibling() {
+ // A plugin on $PATH wins over a same-named sibling next to the binary.
+ let tmp = TempDir::new().unwrap();
+ let path_bin = tmp.path().join("pathbin");
+ let exe_bin = tmp.path().join("exebin");
+ fs::create_dir_all(&path_bin).unwrap();
+ fs::create_dir_all(&exe_bin).unwrap();
+
+ make_executable(&path_bin.join("loomweave-plugin-mocktest"));
+ fs::write(
+ path_bin.join("plugin.toml"),
+ minimal_manifest_toml("mocktest"),
+ )
+ .unwrap();
+ make_executable(&exe_bin.join("loomweave-plugin-mocktest"));
+ fs::write(
+ exe_bin.join("plugin.toml"),
+ minimal_manifest_toml("mocktest"),
+ )
+ .unwrap();
+
+ let results = discover_on_path_and_exe_dir(&path_os(&[&path_bin]), Some(exe_bin.as_path()));
+ assert_eq!(results.len(), 1, "duplicate name must be de-duplicated");
+ let plugin = results.into_iter().next().unwrap().unwrap();
+ assert_eq!(
+ plugin.executable,
+ path_bin.join("loomweave-plugin-mocktest"),
+ "$PATH entry must shadow the exe-dir sibling"
+ );
+ }
+
+ #[test]
+ fn t2d_exe_dir_equal_to_path_dir_is_deduped() {
+ // The exe dir resolving to a dir already on $PATH must not be scanned
+ // twice (seen_dirs gate), so the plugin is reported exactly once.
+ let tmp = TempDir::new().unwrap();
+ let bin = tmp.path().join("bin");
+ fs::create_dir_all(&bin).unwrap();
+ make_executable(&bin.join("loomweave-plugin-mocktest"));
+ let share = tmp.path().join("share/loomweave/plugins/mocktest");
+ fs::create_dir_all(&share).unwrap();
+ fs::write(share.join("plugin.toml"), minimal_manifest_toml("mocktest")).unwrap();
+
+ let results = discover_on_path_and_exe_dir(&path_os(&[&bin]), Some(bin.as_path()));
+ assert_eq!(
+ results.len(),
+ 1,
+ "exe dir == a $PATH dir must be deduped, not double-scanned"
);
+ assert!(results.into_iter().next().unwrap().is_ok());
}
// ── T3: no manifest anywhere → ManifestNotFound ───────────────────────────
@@ -527,7 +659,7 @@ ontology_version = "0.1.0"
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
- make_executable(&bin.join("clarion-plugin-orphan"));
+ make_executable(&bin.join("loomweave-plugin-orphan"));
let results = discover_on_path(&path_os(&[&bin]));
assert_eq!(results.len(), 1);
@@ -547,7 +679,7 @@ ontology_version = "0.1.0"
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
- make_executable(&bin.join("clarion-plugin-broken"));
+ make_executable(&bin.join("loomweave-plugin-broken"));
fs::write(bin.join("plugin.toml"), b"this is not valid toml ][[[").unwrap();
let results = discover_on_path(&path_os(&[&bin]));
@@ -569,12 +701,12 @@ ontology_version = "0.1.0"
fs::create_dir_all(&bin).unwrap();
// Should NOT match:
- make_executable(&bin.join("not-clarion-plugin"));
- make_executable(&bin.join("clarion-plugin-")); // empty suffix
- make_executable(&bin.join("clarion-plugin")); // no second hyphen
+ make_executable(&bin.join("not-loomweave-plugin"));
+ make_executable(&bin.join("loomweave-plugin-")); // empty suffix
+ make_executable(&bin.join("loomweave-plugin")); // no second hyphen
// Should match:
- make_executable(&bin.join("clarion-plugin-valid"));
+ make_executable(&bin.join("loomweave-plugin-valid"));
fs::write(bin.join("plugin.toml"), minimal_manifest_toml("valid")).unwrap();
let results = discover_on_path(&path_os(&[&bin]));
@@ -593,7 +725,7 @@ ontology_version = "0.1.0"
fs::create_dir_all(&bin).unwrap();
// File exists but has no exec bit.
- make_plain_file(&bin.join("clarion-plugin-noexec"), b"#!/bin/sh\n");
+ make_plain_file(&bin.join("loomweave-plugin-noexec"), b"#!/bin/sh\n");
fs::write(bin.join("plugin.toml"), minimal_manifest_toml("noexec")).unwrap();
let results = discover_on_path(&path_os(&[&bin]));
@@ -611,10 +743,10 @@ ontology_version = "0.1.0"
fs::create_dir_all(&dir_b).unwrap();
// Both dirs have the same binary name with valid manifests.
- make_executable(&dir_a.join("clarion-plugin-dup"));
+ make_executable(&dir_a.join("loomweave-plugin-dup"));
fs::write(dir_a.join("plugin.toml"), minimal_manifest_toml("dup")).unwrap();
- make_executable(&dir_b.join("clarion-plugin-dup"));
+ make_executable(&dir_b.join("loomweave-plugin-dup"));
fs::write(dir_b.join("plugin.toml"), minimal_manifest_toml("dup")).unwrap();
let results = discover_on_path(&path_os(&[dir_a.as_path(), dir_b.as_path()]));
@@ -626,7 +758,7 @@ ontology_version = "0.1.0"
let plugin = results.into_iter().next().unwrap().unwrap();
// Executable must come from dir_a, not dir_b.
- assert_eq!(plugin.executable, dir_a.join("clarion-plugin-dup"));
+ assert_eq!(plugin.executable, dir_a.join("loomweave-plugin-dup"));
}
// ── T8: world-writable directory refused ──────────────────────────────────
@@ -638,7 +770,7 @@ ontology_version = "0.1.0"
#[test]
fn t8_world_writable_dir_is_refused() {
let dir = TempDir::new().unwrap();
- make_executable(&dir.path().join("clarion-plugin-evil"));
+ make_executable(&dir.path().join("loomweave-plugin-evil"));
fs::write(
dir.path().join("plugin.toml"),
minimal_manifest_toml("evil"),
diff --git a/crates/clarion-core/src/plugin/host.rs b/crates/loomweave-core/src/plugin/host.rs
similarity index 99%
rename from crates/clarion-core/src/plugin/host.rs
rename to crates/loomweave-core/src/plugin/host.rs
index 4525c2b4..90c8df35 100644
--- a/crates/clarion-core/src/plugin/host.rs
+++ b/crates/loomweave-core/src/plugin/host.rs
@@ -94,7 +94,7 @@ pub const MAX_FINDING_SEVERITY_BYTES: usize = 32;
/// maps [`RawEntity::extra`] and [`RawSource::extra`].
///
/// These flow into `properties_json` downstream (via
-/// `clarion-cli::analyze::map_entity_to_record`) as `serde_json::to_string`
+/// `loomweave-cli::analyze::map_entity_to_record`) as `serde_json::to_string`
/// output. Without a cap, a plugin could return 8 MiB frames consisting of
/// one tiny `qualified_name` plus a multi-MiB `extra` map that lives in the
/// database row and in every host-side clone until the run ends. 64 KiB is
@@ -263,7 +263,7 @@ fn oversize_field(raw: &RawEntity) -> Option<(&'static str, usize)> {
// check is by serialised byte length rather than entry count — a single
// entry with a multi-MiB Value is as toxic as many entries each small.
// Serialisation is the next-downstream step anyway (via
- // clarion-cli::analyze::map_entity_to_record), so the to_vec here is not
+ // loomweave-cli::analyze::map_entity_to_record), so the to_vec here is not
// an additional allocation beyond what we were already going to pay.
for (name, map) in [("extra", &raw.extra), ("source.extra", &raw.source.extra)] {
if map.is_empty() {
@@ -648,7 +648,7 @@ impl
// 1. Absolute / relative paths in the manifest (`executable = "/bin/sh"`,
// `executable = "../../evil"`) that would run a binary the
// operator did not install.
- // 2. Mismatch between discovered name (`clarion-plugin-python`) and
+ // 2. Mismatch between discovered name (`loomweave-plugin-python`) and
// declared name — which would silently run the wrong binary if
// a plugin directory contained multiple.
let declared = &manifest.plugin.executable;
@@ -750,7 +750,7 @@ impl
let stderr_tail_for_thread = std::sync::Arc::clone(&stderr_tail);
let stderr_thread = std::thread::Builder::new()
.name(format!(
- "clarion-plugin-stderr-drain:{}",
+ "loomweave-plugin-stderr-drain:{}",
manifest.plugin.plugin_id
))
.spawn(move || drain_stderr_into_ring(stderr, &stderr_tail_for_thread))
@@ -1473,7 +1473,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["function"]
edge_kinds = []
-rule_id_prefix = "CLA-MOCK-"
+rule_id_prefix = "LMWV-MOCK-"
ontology_version = "0.1.0"
"#;
crate::plugin::parse_manifest(toml.as_bytes()).expect("valid compliant manifest")
@@ -1499,7 +1499,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["module", "function"]
edge_kinds = ["contains", "calls"]
-rule_id_prefix = "CLA-MOCK-"
+rule_id_prefix = "LMWV-MOCK-"
ontology_version = "0.4.0"
"#;
crate::plugin::parse_manifest(toml.as_bytes()).expect("valid calls manifest")
@@ -1528,7 +1528,7 @@ pin = "1.1.409"
[ontology]
entity_kinds = ["module", "function"]
edge_kinds = ["contains", "calls"]
-rule_id_prefix = "CLA-MOCK-"
+rule_id_prefix = "LMWV-MOCK-"
ontology_version = "0.4.0"
"#;
crate::plugin::parse_manifest(toml.as_bytes()).expect("valid pyright manifest")
@@ -1554,7 +1554,7 @@ reads_outside_project_root = true
[ontology]
entity_kinds = ["function"]
edge_kinds = []
-rule_id_prefix = "CLA-MOCK-"
+rule_id_prefix = "LMWV-MOCK-"
ontology_version = "0.1.0"
"#;
crate::plugin::parse_manifest(toml.as_bytes()).expect("valid reads-outside manifest")
@@ -1666,7 +1666,7 @@ ontology_version = "0.1.0"
// ── T2: reads_outside_project_root refusal ────────────────────────────────
/// T2: manifest with `reads_outside_project_root = true` is refused at
- /// handshake. Host emits `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`,
+ /// handshake. Host emits `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`,
/// sends `shutdown` + `exit`, and no `analyze_file` is dispatched.
#[test]
fn t2_reads_outside_project_root_refused_at_handshake() {
@@ -1738,7 +1738,7 @@ ontology_version = "0.1.0"
findings
.iter()
.any(|f| f.subcode == FINDING_UNSUPPORTED_CAPABILITY),
- "must have CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY; got: {findings:?}"
+ "must have LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY; got: {findings:?}"
);
// Verify that neither analyze_file NOR initialized was sent. The
@@ -1765,7 +1765,7 @@ ontology_version = "0.1.0"
// ── T3: ontology-boundary enforcement ────────────────────────────────────
/// T3: plugin emits entity with `kind: "unknown"` not in manifest ontology.
- /// Host drops it and emits `CLA-INFRA-PLUGIN-UNDECLARED-KIND`.
+ /// Host drops it and emits `LMWV-INFRA-PLUGIN-UNDECLARED-KIND`.
#[test]
fn t3_undeclared_kind_is_dropped_with_finding() {
let manifest = compliant_manifest(); // entity_kinds = ["function"]
@@ -1835,7 +1835,7 @@ ontology_version = "0.1.0"
/// T4: plugin emits entity whose `id` doesn't match
/// `entity_id(plugin_id, kind, qualified_name)`. Host drops it and emits
- /// `CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH`.
+ /// `LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH`.
#[test]
fn t4_identity_mismatch_drops_entity_with_finding() {
let manifest = compliant_manifest();
@@ -1872,14 +1872,14 @@ ontology_version = "0.1.0"
findings
.iter()
.any(|f| f.subcode == FINDING_ENTITY_ID_MISMATCH),
- "must have CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH; got: {findings:?}"
+ "must have LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH; got: {findings:?}"
);
}
// ── T5: path-jail drop-not-kill ───────────────────────────────────────────
/// T5: plugin emits one entity with a source path that escapes the jail.
- /// Host drops the entity, emits `CLA-INFRA-PLUGIN-PATH-ESCAPE`, plugin
+ /// Host drops the entity, emits `LMWV-INFRA-PLUGIN-PATH-ESCAPE`, plugin
/// stays alive (no kill error returned).
#[test]
fn t5_single_path_escape_drops_entity_plugin_survives() {
@@ -1914,7 +1914,7 @@ ontology_version = "0.1.0"
let findings = host.take_findings();
assert!(
findings.iter().any(|f| f.subcode == FINDING_PATH_ESCAPE),
- "must have CLA-INFRA-PLUGIN-PATH-ESCAPE; got: {findings:?}"
+ "must have LMWV-INFRA-PLUGIN-PATH-ESCAPE; got: {findings:?}"
);
assert!(
!findings
@@ -1928,7 +1928,7 @@ ontology_version = "0.1.0"
/// T6: plugin emits 11 entities each with an escaping path. On the 11th
/// the breaker trips; host kills the plugin and emits
- /// `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`.
+ /// `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`.
#[test]
fn t6_eleven_path_escapes_trip_breaker() {
let manifest = compliant_manifest();
@@ -2072,7 +2072,7 @@ ontology_version = "0.1.0"
/// T8 — oversize-field enforcement: a plugin emits one entity whose
/// `qualified_name` exceeds [`MAX_ENTITY_FIELD_BYTES`]. The host drops it
/// before the identity check's `format!()` would allocate a duplicate,
- /// emits `CLA-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE`, and the plugin stays
+ /// emits `LMWV-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE`, and the plugin stays
/// alive (the cap is per-entity; one offender is not a kill trigger).
///
/// Verifies the `DoS` amplification fix from review-2. Builds the
@@ -2137,7 +2137,7 @@ ontology_version = "0.1.0"
.iter()
.find(|f| f.subcode == FINDING_ENTITY_FIELD_OVERSIZE)
.unwrap_or_else(|| {
- panic!("must have CLA-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE; got: {findings:?}")
+ panic!("must have LMWV-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE; got: {findings:?}")
});
assert_eq!(
offense.metadata.get("field").map(String::as_str),
@@ -3044,7 +3044,7 @@ ontology_version = "0.1.0"
"entities": [],
"edges": [],
"findings": [{
- "subcode": "CLA-MOCK-PYRIGHT-RESTART",
+ "subcode": "LMWV-MOCK-PYRIGHT-RESTART",
"severity": "warning",
"message": "pyright subprocess died and was restarted",
"metadata": { "restart_count": 1 }
@@ -3070,7 +3070,7 @@ ontology_version = "0.1.0"
let findings = host.take_findings();
let finding = findings
.iter()
- .find(|f| f.subcode == "CLA-MOCK-PYRIGHT-RESTART")
+ .find(|f| f.subcode == "LMWV-MOCK-PYRIGHT-RESTART")
.unwrap_or_else(|| panic!("plugin finding must survive host path; got: {findings:?}"));
assert_eq!(finding.message, "pyright subprocess died and was restarted");
assert_eq!(
diff --git a/crates/clarion-core/src/plugin/host_findings.rs b/crates/loomweave-core/src/plugin/host_findings.rs
similarity index 92%
rename from crates/clarion-core/src/plugin/host_findings.rs
rename to crates/loomweave-core/src/plugin/host_findings.rs
index fa0c15e6..4fc21e2c 100644
--- a/crates/clarion-core/src/plugin/host_findings.rs
+++ b/crates/loomweave-core/src/plugin/host_findings.rs
@@ -14,15 +14,15 @@ use crate::plugin::protocol::UnresolvedCallSite;
/// Emitted when a plugin emits an entity whose `kind` is not in the manifest's
/// `entity_kinds` list (ADR-022 ontology boundary).
-pub const FINDING_UNDECLARED_KIND: &str = "CLA-INFRA-PLUGIN-UNDECLARED-KIND";
+pub const FINDING_UNDECLARED_KIND: &str = "LMWV-INFRA-PLUGIN-UNDECLARED-KIND";
/// Emitted when a plugin emits an entity whose `id` string does not match the
/// expected `entity_id(plugin_id, kind, qualified_name)` (UQ-WP2-11).
-pub const FINDING_ENTITY_ID_MISMATCH: &str = "CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH";
+pub const FINDING_ENTITY_ID_MISMATCH: &str = "LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH";
/// Emitted when the manifest contains a capability not supported in v0.1
/// (ADR-021 §Layer 1).
-pub const FINDING_UNSUPPORTED_CAPABILITY: &str = "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY";
+pub const FINDING_UNSUPPORTED_CAPABILITY: &str = "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY";
/// Emitted when a plugin returns an entity whose JSON shape fails to
/// deserialise into `RawEntity` (missing required field, wrong type, etc.).
@@ -31,7 +31,7 @@ pub const FINDING_UNSUPPORTED_CAPABILITY: &str = "CLA-INFRA-MANIFEST-UNSUPPORTED
/// the finding is the only signal the operator gets that the plugin emitted
/// malformed output. Without this, a plugin bug that silently produces garbage
/// for a subset of entities looks identical to "no entities found".
-pub const FINDING_MALFORMED_ENTITY: &str = "CLA-INFRA-PLUGIN-MALFORMED-ENTITY";
+pub const FINDING_MALFORMED_ENTITY: &str = "LMWV-INFRA-PLUGIN-MALFORMED-ENTITY";
/// Emitted when the host is asked to analyze a file whose path is not
/// representable as UTF-8. The wire protocol is JSON (UTF-8 only), so the host
@@ -42,7 +42,7 @@ pub const FINDING_MALFORMED_ENTITY: &str = "CLA-INFRA-PLUGIN-MALFORMED-ENTITY";
/// the wire boundary would replace invalid bytes with U+FFFD, yielding a path
/// the plugin cannot open and an obscure "plugin returned no entities" symptom.
/// Failing loudly with this finding keeps the diagnostic at the host layer.
-pub const FINDING_NON_UTF8_PATH: &str = "CLA-INFRA-HOST-NON-UTF8-PATH";
+pub const FINDING_NON_UTF8_PATH: &str = "LMWV-INFRA-HOST-NON-UTF8-PATH";
/// Emitted when a plugin returns an entity with a string field longer than
/// `MAX_ENTITY_FIELD_BYTES`. Entity is dropped; plugin is not killed.
@@ -52,32 +52,32 @@ pub const FINDING_NON_UTF8_PATH: &str = "CLA-INFRA-HOST-NON-UTF8-PATH";
/// identity check duplicates `qualified_name` through `format!()`, so the memory
/// cost is at least 2x the incoming string per offending entity, making this a
/// RAM-amplification vector even under the 8 MiB Content-Length ceiling.
-pub const FINDING_ENTITY_FIELD_OVERSIZE: &str = "CLA-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE";
+pub const FINDING_ENTITY_FIELD_OVERSIZE: &str = "LMWV-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE";
/// Emitted when a plugin returns an edge whose JSON shape fails to deserialise
/// into `RawEdge` (missing required field, wrong type, etc.). Symmetric with
/// [`FINDING_MALFORMED_ENTITY`]; edge is dropped, run continues.
-pub const FINDING_MALFORMED_EDGE: &str = "CLA-INFRA-PLUGIN-MALFORMED-EDGE";
+pub const FINDING_MALFORMED_EDGE: &str = "LMWV-INFRA-PLUGIN-MALFORMED-EDGE";
/// Emitted when a plugin emits an edge whose `kind` is not in the manifest's
/// `edge_kinds` list (ADR-022 ontology boundary, edge variant). Drop + finding;
/// no kill.
-pub const FINDING_UNDECLARED_EDGE_KIND: &str = "CLA-INFRA-PLUGIN-UNDECLARED-EDGE-KIND";
+pub const FINDING_UNDECLARED_EDGE_KIND: &str = "LMWV-INFRA-PLUGIN-UNDECLARED-EDGE-KIND";
/// Emitted when a plugin returns an edge with a string field longer than
/// `MAX_ENTITY_FIELD_BYTES`. Edge is dropped; plugin is not killed. Same
/// rationale as [`FINDING_ENTITY_FIELD_OVERSIZE`] (RAM amplification).
-pub const FINDING_EDGE_FIELD_OVERSIZE: &str = "CLA-INFRA-PLUGIN-EDGE-FIELD-OVERSIZE";
+pub const FINDING_EDGE_FIELD_OVERSIZE: &str = "LMWV-INFRA-PLUGIN-EDGE-FIELD-OVERSIZE";
/// Emitted when `stats.unresolved_call_sites` contains a row that cannot be
/// tied back to the accepted entities and source bytes for this `analyze_file`
/// response. The row is dropped; aggregate counters are retained.
pub const FINDING_MALFORMED_UNRESOLVED_CALL_SITE: &str =
- "CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE";
+ "LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE";
/// Emitted when a plugin-reported `analyze_file.findings[]` row fails host
/// validation. The row is dropped and this infrastructure finding is retained.
-pub const FINDING_MALFORMED_FINDING: &str = "CLA-INFRA-PLUGIN-MALFORMED-FINDING";
+pub const FINDING_MALFORMED_FINDING: &str = "LMWV-INFRA-PLUGIN-MALFORMED-FINDING";
/// Informational diagnostic accumulated during a host's lifetime.
///
@@ -86,7 +86,7 @@ pub const FINDING_MALFORMED_FINDING: &str = "CLA-INFRA-PLUGIN-MALFORMED-FINDING"
/// Findings; for Sprint 1 they are collected only.
#[derive(Debug, Clone)]
pub struct HostFinding {
- /// Finding subcode, e.g. `"CLA-INFRA-PLUGIN-PATH-ESCAPE"`.
+ /// Finding subcode, e.g. `"LMWV-INFRA-PLUGIN-PATH-ESCAPE"`.
pub subcode: String,
/// Human-readable message.
pub message: String,
diff --git a/crates/clarion-core/src/plugin/jail.rs b/crates/loomweave-core/src/plugin/jail.rs
similarity index 99%
rename from crates/clarion-core/src/plugin/jail.rs
rename to crates/loomweave-core/src/plugin/jail.rs
index a5babeaa..705093a2 100644
--- a/crates/clarion-core/src/plugin/jail.rs
+++ b/crates/loomweave-core/src/plugin/jail.rs
@@ -1,4 +1,4 @@
-//! Path-jail enforcement for the Clarion plugin host.
+//! Path-jail enforcement for the Loomweave plugin host.
//!
//! Implements ADR-021 §2a: every file path that a plugin names — whether in a
//! request parameter or in a returned entity — must lie *inside* the project
diff --git a/crates/clarion-core/src/plugin/limits.rs b/crates/loomweave-core/src/plugin/limits.rs
similarity index 96%
rename from crates/clarion-core/src/plugin/limits.rs
rename to crates/loomweave-core/src/plugin/limits.rs
index ae95d712..2741835c 100644
--- a/crates/clarion-core/src/plugin/limits.rs
+++ b/crates/loomweave-core/src/plugin/limits.rs
@@ -10,11 +10,11 @@
//! | ADR-021 §2c | Entity/edge/finding cap| [`EntityCountCap`] |
//! | ADR-021 §2d | Virtual-address limit | [`apply_prlimit_as`] |
//!
-//! # Deferred: CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING
+//! # Deferred: LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING
//!
//! ADR-021 §2c also calls for a *warning* finding emitted when the cap is
//! approached (e.g. at 80 % of `DEFAULT_MAX`). This warning finding
-//! (`CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`) is **not implemented in
+//! (`LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`) is **not implemented in
//! Sprint 1**. It requires the Filigree scan-result ingest path that lands in
//! WP5/WP6; deferring avoids a hard dependency on that infrastructure.
//! When the ingest path is ready, add a `try_admit_with_warning` variant that
@@ -34,24 +34,24 @@ use thiserror::Error;
// ── Finding subcode constants (ADR-021, consumed by Task 6) ──────────────────
/// Finding subcode emitted when a plugin returns a path that escapes the jail.
-pub const FINDING_PATH_ESCAPE: &str = "CLA-INFRA-PLUGIN-PATH-ESCAPE";
+pub const FINDING_PATH_ESCAPE: &str = "LMWV-INFRA-PLUGIN-PATH-ESCAPE";
/// Finding subcode emitted when the path-escape breaker trips and the plugin
/// is killed (the "disabled" sense: further entities from this plugin are
/// refused entirely).
-pub const FINDING_DISABLED_PATH_ESCAPE: &str = "CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE";
+pub const FINDING_DISABLED_PATH_ESCAPE: &str = "LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE";
/// Finding subcode emitted when `read_frame` rejects a frame because its
/// `Content-Length` exceeds the configured [`ContentLengthCeiling`].
-pub const FINDING_FRAME_OVERSIZE: &str = "CLA-INFRA-PLUGIN-FRAME-OVERSIZE";
+pub const FINDING_FRAME_OVERSIZE: &str = "LMWV-INFRA-PLUGIN-FRAME-OVERSIZE";
/// Finding subcode emitted when [`EntityCountCap::try_admit`] returns
/// [`CapExceeded`] and the supervisor stops processing plugin output.
-pub const FINDING_ENTITY_CAP: &str = "CLA-INFRA-PLUGIN-ENTITY-CAP";
+pub const FINDING_ENTITY_CAP: &str = "LMWV-INFRA-PLUGIN-ENTITY-CAP";
/// Finding subcode emitted when the plugin process is killed by the OS due to
/// exceeding its `RLIMIT_AS` memory ceiling (OOM kill on `RLIMIT_AS`).
-pub const FINDING_OOM_KILLED: &str = "CLA-INFRA-PLUGIN-OOM-KILLED";
+pub const FINDING_OOM_KILLED: &str = "LMWV-INFRA-PLUGIN-OOM-KILLED";
// ── ContentLengthCeiling (ADR-021 §2b) ───────────────────────────────────────
@@ -291,7 +291,7 @@ pub const DEFAULT_MAX_NPROC: u64 = 32;
/// Apply `RLIMIT_AS` to the current process.
///
/// Called inside `CommandExt::pre_exec` (Task 6) so the limit applies to the
-/// plugin child process, not the Clarion host process. Setting the limit in
+/// plugin child process, not the Loomweave host process. Setting the limit in
/// `pre_exec` is safe because `pre_exec` runs after `fork()` but before
/// `exec()`, so only the child's address-space limit is affected.
///
@@ -346,7 +346,7 @@ pub fn apply_prlimit_as(_max_rss_mib: u64) -> std::io::Result<()> {
/// Emit a one-time warning on non-Linux/macOS platforms.
///
-/// Uses `std::sync::Once` rather than `tracing` — clarion-core has no tracing
+/// Uses `std::sync::Once` rather than `tracing` — loomweave-core has no tracing
/// dep and we do not add one for this single warning (per task spec).
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn warn_once_non_linux() {
@@ -354,7 +354,7 @@ fn warn_once_non_linux() {
static WARN: Once = Once::new();
WARN.call_once(|| {
eprintln!(
- "clarion: RLIMIT_AS enforcement is Linux/macOS only; \
+ "loomweave: RLIMIT_AS enforcement is Linux/macOS only; \
plugin memory ceiling will not be applied on this platform"
);
});
diff --git a/crates/clarion-core/src/plugin/manifest.rs b/crates/loomweave-core/src/plugin/manifest.rs
similarity index 86%
rename from crates/clarion-core/src/plugin/manifest.rs
rename to crates/loomweave-core/src/plugin/manifest.rs
index 18b90d8f..5bdf79eb 100644
--- a/crates/clarion-core/src/plugin/manifest.rs
+++ b/crates/loomweave-core/src/plugin/manifest.rs
@@ -5,7 +5,7 @@
//! # Usage
//!
//! ```no_run
-//! use clarion_core::plugin::parse_manifest;
+//! use loomweave_core::plugin::parse_manifest;
//!
//! let bytes = std::fs::read("plugin.toml").unwrap();
//! let manifest = parse_manifest(&bytes).unwrap();
@@ -29,15 +29,15 @@ pub const RESERVED_ENTITY_KINDS: &[&str] = &["file", "subsystem", "guidance"];
/// Rule-ID prefixes the core owns; plugins may not claim these (ADR-022 §Core owns).
///
-/// `CLA-INFRA-` is core/pipeline-only; `CLA-FACT-` is shared (core or any tool may
+/// `LMWV-INFRA-` is core/pipeline-only; `LMWV-FACT-` is shared (core or any tool may
/// emit) but a plugin manifest may not claim it as *the plugin's* prefix.
-const RESERVED_RULE_ID_PREFIXES: &[&str] = &["CLA-INFRA-", "CLA-FACT-"];
+const RESERVED_RULE_ID_PREFIXES: &[&str] = &["LMWV-INFRA-", "LMWV-FACT-"];
// ── Error type ────────────────────────────────────────────────────────────────
/// Errors returned by [`parse_manifest`] and [`Manifest::validate_for_v0_1`].
///
-/// Each variant corresponds to a `CLA-INFRA-MANIFEST-*` finding code that Task 6
+/// Each variant corresponds to a `LMWV-INFRA-MANIFEST-*` finding code that Task 6
/// surfaces in the `initialize` handshake reply. Use [`ManifestError::subcode`] to
/// obtain the machine-readable finding code.
#[derive(Debug, Error, PartialEq, Eq)]
@@ -45,43 +45,43 @@ const RESERVED_RULE_ID_PREFIXES: &[&str] = &["CLA-INFRA-", "CLA-FACT-"];
pub enum ManifestError {
/// TOML parse failure or a required field is absent.
///
- /// Finding code: `CLA-INFRA-MANIFEST-MALFORMED`.
- #[error("CLA-INFRA-MANIFEST-MALFORMED: {message}")]
+ /// Finding code: `LMWV-INFRA-MANIFEST-MALFORMED`.
+ #[error("LMWV-INFRA-MANIFEST-MALFORMED: {message}")]
Malformed { message: String },
/// An identifier string fails the ADR-022 grammar `[a-z][a-z0-9_]*` (kinds)
- /// or `CLA-[A-Z]+(-[A-Z0-9]+)+-` (rule-ID prefix).
+ /// or `LMWV-[A-Z]+(-[A-Z0-9]+)+-` (rule-ID prefix).
///
- /// Finding code: `CLA-INFRA-MANIFEST-MALFORMED`.
- #[error("CLA-INFRA-MANIFEST-MALFORMED: {field} {value:?} violates ADR-022 identifier grammar")]
+ /// Finding code: `LMWV-INFRA-MANIFEST-MALFORMED`.
+ #[error("LMWV-INFRA-MANIFEST-MALFORMED: {field} {value:?} violates ADR-022 identifier grammar")]
GrammarViolation { field: &'static str, value: String },
/// A plugin manifest declares one of the core-reserved entity kinds.
///
- /// Finding code: `CLA-INFRA-MANIFEST-RESERVED-KIND`.
+ /// Finding code: `LMWV-INFRA-MANIFEST-RESERVED-KIND`.
#[error(
- "CLA-INFRA-MANIFEST-RESERVED-KIND: entity kind {kind:?} is reserved by the core (ADR-022)"
+ "LMWV-INFRA-MANIFEST-RESERVED-KIND: entity kind {kind:?} is reserved by the core (ADR-022)"
)]
ReservedKind { kind: String },
/// A plugin manifest claims a rule-ID prefix owned by the core.
///
- /// Finding code: `CLA-INFRA-RULE-ID-NAMESPACE`.
+ /// Finding code: `LMWV-INFRA-RULE-ID-NAMESPACE`.
#[error(
- "CLA-INFRA-RULE-ID-NAMESPACE: rule_id_prefix {prefix:?} is a core-reserved namespace (ADR-022)"
+ "LMWV-INFRA-RULE-ID-NAMESPACE: rule_id_prefix {prefix:?} is a core-reserved namespace (ADR-022)"
)]
ReservedPrefix { prefix: String },
/// A manifest declares a capability that v0.1 does not support.
///
- /// Finding code: `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`.
+ /// Finding code: `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`.
///
/// This variant is produced by [`Manifest::validate_for_v0_1`], not by
/// [`parse_manifest`]. The parser accepts the field faithfully; Task 6's
/// supervisor calls `validate_for_v0_1` and surfaces this error as a
/// handshake rejection.
#[error(
- "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY: capability {capability:?} is not supported in v0.1"
+ "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY: capability {capability:?} is not supported in v0.1"
)]
UnsupportedCapability { capability: &'static str },
}
@@ -89,17 +89,17 @@ pub enum ManifestError {
impl ManifestError {
/// Return the machine-readable finding code for this error.
///
- /// Task 6 uses this to populate the `rule_id` field of the `CLA-INFRA-*`
+ /// Task 6 uses this to populate the `rule_id` field of the `LMWV-INFRA-*`
/// finding emitted when a plugin fails to start.
pub fn subcode(&self) -> &'static str {
match self {
ManifestError::Malformed { .. } | ManifestError::GrammarViolation { .. } => {
- "CLA-INFRA-MANIFEST-MALFORMED"
+ "LMWV-INFRA-MANIFEST-MALFORMED"
}
- ManifestError::ReservedKind { .. } => "CLA-INFRA-MANIFEST-RESERVED-KIND",
- ManifestError::ReservedPrefix { .. } => "CLA-INFRA-RULE-ID-NAMESPACE",
+ ManifestError::ReservedKind { .. } => "LMWV-INFRA-MANIFEST-RESERVED-KIND",
+ ManifestError::ReservedPrefix { .. } => "LMWV-INFRA-RULE-ID-NAMESPACE",
ManifestError::UnsupportedCapability { .. } => {
- "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"
+ "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"
}
}
}
@@ -180,7 +180,7 @@ pub const MAX_INTEGRATIONS: usize = 64;
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PluginMeta {
- /// Package name, e.g. `"clarion-plugin-python"`. Informational; hyphens allowed.
+ /// Package name, e.g. `"loomweave-plugin-python"`. Informational; hyphens allowed.
pub name: String,
/// Identifier fed to `entity_id()`, e.g. `"python"`. Must satisfy `[a-z][a-z0-9_]*`
@@ -225,7 +225,7 @@ pub struct CapabilitiesRuntime {
/// Must be > 0.
pub expected_max_rss_mb: u64,
- /// Declared per-file entity budget. Exceeding triggers `CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`
+ /// Declared per-file entity budget. Exceeding triggers `LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`
/// (implementation deferred to Tier B sprint).
pub expected_entities_per_file: u64,
@@ -234,7 +234,7 @@ pub struct CapabilitiesRuntime {
/// `true` if the plugin needs to read paths outside the project root.
///
- /// v0.1 refuses `true` at `initialize` with `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`.
+ /// v0.1 refuses `true` at `initialize` with `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`.
/// The parser accepts the field faithfully; [`Manifest::validate_for_v0_1`] performs
/// the rejection check.
pub reads_outside_project_root: bool,
@@ -266,8 +266,8 @@ pub struct Ontology {
#[serde(default)]
pub edge_kinds: Vec,
- /// Rule-ID prefix, e.g. `"CLA-PY-"`. Must end with `-` and match
- /// `CLA-[A-Z]+(-[A-Z0-9]+)+-`. Must not be a core-reserved prefix.
+ /// Rule-ID prefix, e.g. `"LMWV-PY-"`. Must end with `-` and match
+ /// `LMWV-[A-Z]+(-[A-Z0-9]+)+-`. Must not be a core-reserved prefix.
pub rule_id_prefix: String,
/// Ontology version (semver). Bumped when entity/edge/rule set changes.
@@ -514,16 +514,16 @@ fn validate_role_kinds(
///
/// Rules:
/// 1. Must end with `-`.
-/// 2. Strip the trailing `-`; the remainder must match `CLA-[A-Z]+(-[A-Z0-9]+)+`
-/// (one-or-more `-[A-Z0-9]+` segments after `CLA`, per ADR-022).
-/// Implementation: split on `-`, verify the first segment is `CLA`, and each
+/// 2. Strip the trailing `-`; the remainder must match `LMWV-[A-Z]+(-[A-Z0-9]+)+`
+/// (one-or-more `-[A-Z0-9]+` segments after `LMWV`, per ADR-022).
+/// Implementation: split on `-`, verify the first segment is `LMWV`, and each
/// subsequent non-empty segment is `[A-Z0-9]+` (ASCII uppercase or digit).
/// The `segments.len() < 2` guard below is how the `+` quantifier is enforced
-/// without a regex engine — `CLA-` alone has only one segment and is rejected.
+/// without a regex engine — `LMWV-` alone has only one segment and is rejected.
///
-/// Examples of valid prefixes: `CLA-PY-`, `CLA-JAVA-`, `CLA-FOO-BAR-`.
-/// Examples of invalid prefixes: `PY-`, `cla-py-`, `CLA-py-`, `CLA-PY` (no trailing
-/// hyphen), `CLA-` (no segment after CLA), `CLA--PY-` (empty segment).
+/// Examples of valid prefixes: `LMWV-PY-`, `LMWV-JAVA-`, `LMWV-FOO-BAR-`.
+/// Examples of invalid prefixes: `PY-`, `lmwv-py-`, `LMWV-py-`, `LMWV-PY` (no trailing
+/// hyphen), `LMWV-` (no segment after LMWV), `LMWV--PY-` (empty segment).
fn validate_rule_id_prefix_grammar(prefix: &str) -> Result<(), ManifestError> {
// Rule 1: must end with `-`.
let Some(without_trailing) = prefix.strip_suffix('-') else {
@@ -533,19 +533,19 @@ fn validate_rule_id_prefix_grammar(prefix: &str) -> Result<(), ManifestError> {
});
};
- // Rule 2: split on `-`; first segment must be `CLA`; all subsequent segments
- // must be non-empty `[A-Z0-9]+`; at least one segment must follow `CLA`.
+ // Rule 2: split on `-`; first segment must be `LMWV`; all subsequent segments
+ // must be non-empty `[A-Z0-9]+`; at least one segment must follow `LMWV`.
let segments: Vec<&str> = without_trailing.split('-').collect();
- // First segment must be exactly `CLA`.
- if segments.first().copied() != Some("CLA") {
+ // First segment must be exactly `LMWV`.
+ if segments.first().copied() != Some("LMWV") {
return Err(ManifestError::GrammarViolation {
field: "rule_id_prefix",
value: prefix.to_owned(),
});
}
- // There must be at least one segment after `CLA`.
+ // There must be at least one segment after `LMWV`.
if segments.len() < 2 {
return Err(ManifestError::GrammarViolation {
field: "rule_id_prefix",
@@ -581,11 +581,11 @@ mod tests {
/// The canonical valid manifest fixture (mirrors the L5 schema in §2).
const VALID_MANIFEST: &str = r#"
[plugin]
-name = "clarion-plugin-python"
+name = "loomweave-plugin-python"
plugin_id = "mockplugin"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-python"
+executable = "loomweave-plugin-python"
language = "python"
extensions = ["py"]
@@ -601,7 +601,7 @@ pin = "1.1.409"
[ontology]
entity_kinds = ["function", "class", "module", "decorator"]
edge_kinds = ["imports", "calls", "decorates", "contains"]
-rule_id_prefix = "CLA-PY-"
+rule_id_prefix = "LMWV-PY-"
ontology_version = "0.1.0"
[ontology.roles]
@@ -657,11 +657,11 @@ syntax_degraded_module = ["module"]
let manifest = parse_manifest(VALID_MANIFEST.as_bytes()).unwrap();
// [plugin]
- assert_eq!(manifest.plugin.name, "clarion-plugin-python");
+ assert_eq!(manifest.plugin.name, "loomweave-plugin-python");
assert_eq!(manifest.plugin.plugin_id, "mockplugin");
assert_eq!(manifest.plugin.version, "0.1.0");
assert_eq!(manifest.plugin.protocol_version, "1.0");
- assert_eq!(manifest.plugin.executable, "clarion-plugin-python");
+ assert_eq!(manifest.plugin.executable, "loomweave-plugin-python");
assert_eq!(manifest.plugin.language, "python");
assert_eq!(manifest.plugin.extensions, vec!["py"]);
@@ -692,7 +692,7 @@ syntax_degraded_module = ["module"]
manifest.ontology.edge_kinds,
vec!["imports", "calls", "decorates", "contains"]
);
- assert_eq!(manifest.ontology.rule_id_prefix, "CLA-PY-");
+ assert_eq!(manifest.ontology.rule_id_prefix, "LMWV-PY-");
assert_eq!(manifest.ontology.ontology_version, "0.1.0");
assert_eq!(manifest.ontology.roles.file_scope, vec!["module"]);
assert_eq!(
@@ -740,7 +740,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = ["contains", "calls"]
-rule_id_prefix = "CLA-MY-"
+rule_id_prefix = "LMWV-MY-"
ontology_version = "0.1.0"
"#;
let manifest = parse_manifest(toml.as_bytes()).unwrap();
@@ -757,11 +757,11 @@ ontology_version = "0.1.0"
fn positive_non_python_manifest_roles_parse_successfully() {
let toml = r#"
[plugin]
-name = "clarion-plugin-fixture"
+name = "loomweave-plugin-fixture"
plugin_id = "fixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-fixture"
+executable = "loomweave-plugin-fixture"
language = "fixture"
extensions = ["mt"]
@@ -774,7 +774,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = ["uses"]
-rule_id_prefix = "CLA-FIXTURE-"
+rule_id_prefix = "LMWV-FIXTURE-"
ontology_version = "0.1.0"
[ontology.roles]
@@ -796,11 +796,11 @@ file_scope = ["widget"]
// WP3's plugin.toml adds [integrations.wardline]; must parse without error.
let toml = r#"
[plugin]
-name = "clarion-plugin-python"
+name = "loomweave-plugin-python"
plugin_id = "python"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-python"
+executable = "loomweave-plugin-python"
language = "python"
extensions = ["py"]
@@ -813,7 +813,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["function"]
edge_kinds = []
-rule_id_prefix = "CLA-PY-"
+rule_id_prefix = "LMWV-PY-"
ontology_version = "0.1.0"
[integrations.wardline]
@@ -843,11 +843,11 @@ max_version = "1.0.0"
use std::fmt::Write;
let mut toml = String::from(
r#"[plugin]
-name = "clarion-plugin-x"
+name = "loomweave-plugin-x"
plugin_id = "x"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-x"
+executable = "loomweave-plugin-x"
language = "x"
extensions = ["x"]
@@ -860,7 +860,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = []
-rule_id_prefix = "CLA-X-"
+rule_id_prefix = "LMWV-X-"
ontology_version = "0.1.0"
"#,
@@ -883,15 +883,15 @@ ontology_version = "0.1.0"
fn positive_plugin_id_can_differ_from_name() {
// Verifies that [plugin].name (hyphens OK) and plugin_id (kind grammar)
// are independently valid. This is the exact case that caused the
- // wp2/wp3 contradiction: name = "clarion-plugin-python" (hyphens) while
+ // wp2/wp3 contradiction: name = "loomweave-plugin-python" (hyphens) while
// the entity_id needed the segment "python".
let toml = r#"
[plugin]
-name = "clarion-plugin-python"
+name = "loomweave-plugin-python"
plugin_id = "python"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-python"
+executable = "loomweave-plugin-python"
language = "python"
extensions = ["py"]
@@ -904,11 +904,11 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["function"]
edge_kinds = []
-rule_id_prefix = "CLA-PY-"
+rule_id_prefix = "LMWV-PY-"
ontology_version = "0.1.0"
"#;
let manifest = parse_manifest(toml.as_bytes()).unwrap();
- assert_eq!(manifest.plugin.name, "clarion-plugin-python");
+ assert_eq!(manifest.plugin.name, "loomweave-plugin-python");
assert_eq!(manifest.plugin.plugin_id, "python");
}
@@ -920,7 +920,7 @@ ontology_version = "0.1.0"
// plugin_id is a required field (no serde default).
let toml = manifest_without("plugin_id");
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(err, ManifestError::Malformed { .. }));
}
@@ -933,7 +933,7 @@ ontology_version = "0.1.0"
// plugin_id from name.
let toml = manifest_with(r#"plugin_id = "my-plugin""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation {
@@ -949,7 +949,7 @@ ontology_version = "0.1.0"
fn negative_missing_plugin_name_returns_malformed() {
let toml = manifest_without("name");
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(err, ManifestError::Malformed { .. }));
}
@@ -959,7 +959,7 @@ ontology_version = "0.1.0"
fn negative_zero_rss_mb_rejected() {
let toml = manifest_with("expected_max_rss_mb = 0");
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(
matches!(err, ManifestError::Malformed { message } if message.contains("expected_max_rss_mb"))
);
@@ -977,7 +977,7 @@ ontology_version = "0.1.0"
"syntax_degraded_module = []",
);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(
matches!(err, ManifestError::Malformed { message } if message.contains("entity_kinds"))
);
@@ -1003,7 +1003,7 @@ ontology_version = "0.1.0"
fn negative_entity_kind_uppercase_is_grammar_violation() {
let toml = manifest_with(r#"entity_kinds = ["Function"]"#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation { field: "entity_kinds", value } if value == "Function"
@@ -1014,7 +1014,7 @@ ontology_version = "0.1.0"
fn negative_entity_kind_hyphen_is_grammar_violation() {
let toml = manifest_with(r#"entity_kinds = ["func-tion"]"#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation { field: "entity_kinds", value } if value == "func-tion"
@@ -1025,7 +1025,7 @@ ontology_version = "0.1.0"
fn negative_entity_kind_digit_prefix_is_grammar_violation() {
let toml = manifest_with(r#"entity_kinds = ["1function"]"#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation { field: "entity_kinds", value } if value == "1function"
@@ -1036,10 +1036,10 @@ ontology_version = "0.1.0"
#[test]
fn negative_rule_id_prefix_no_cla_prefix_rejected() {
- // "PY-" — does not start with CLA.
+ // "PY-" — does not start with LMWV.
let toml = manifest_with(r#"rule_id_prefix = "PY-""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "PY-"
@@ -1048,25 +1048,25 @@ ontology_version = "0.1.0"
#[test]
fn negative_rule_id_prefix_lowercase_rejected() {
- // "cla-py-" — lowercase is invalid.
- let toml = manifest_with(r#"rule_id_prefix = "cla-py-""#);
+ // "lmwv-py-" — lowercase is invalid.
+ let toml = manifest_with(r#"rule_id_prefix = "lmwv-py-""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
- ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "cla-py-"
+ ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "lmwv-py-"
));
}
#[test]
fn negative_rule_id_prefix_mixed_case_segment_rejected() {
- // "CLA-py-" — mixed-case segment after CLA.
- let toml = manifest_with(r#"rule_id_prefix = "CLA-py-""#);
+ // "LMWV-py-" — mixed-case segment after LMWV.
+ let toml = manifest_with(r#"rule_id_prefix = "LMWV-py-""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
- ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "CLA-py-"
+ ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "LMWV-py-"
));
}
@@ -1076,7 +1076,7 @@ ontology_version = "0.1.0"
fn negative_reserved_entity_kind_file_rejected() {
let toml = manifest_with(r#"entity_kinds = ["file", "widget"]"#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-RESERVED-KIND");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-RESERVED-KIND");
assert!(matches!(
err,
ManifestError::ReservedKind { kind } if kind == "file"
@@ -1087,7 +1087,7 @@ ontology_version = "0.1.0"
fn negative_reserved_entity_kind_subsystem_rejected() {
let toml = manifest_with(r#"entity_kinds = ["subsystem"]"#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-RESERVED-KIND");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-RESERVED-KIND");
assert!(matches!(
err,
ManifestError::ReservedKind { kind } if kind == "subsystem"
@@ -1098,7 +1098,7 @@ ontology_version = "0.1.0"
fn negative_reserved_entity_kind_guidance_rejected() {
let toml = manifest_with(r#"entity_kinds = ["guidance"]"#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-RESERVED-KIND");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-RESERVED-KIND");
assert!(matches!(
err,
ManifestError::ReservedKind { kind } if kind == "guidance"
@@ -1109,23 +1109,23 @@ ontology_version = "0.1.0"
#[test]
fn negative_reserved_prefix_cla_infra_rejected() {
- let toml = manifest_with(r#"rule_id_prefix = "CLA-INFRA-""#);
+ let toml = manifest_with(r#"rule_id_prefix = "LMWV-INFRA-""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-RULE-ID-NAMESPACE");
+ assert_eq!(err.subcode(), "LMWV-INFRA-RULE-ID-NAMESPACE");
assert!(matches!(
err,
- ManifestError::ReservedPrefix { prefix } if prefix == "CLA-INFRA-"
+ ManifestError::ReservedPrefix { prefix } if prefix == "LMWV-INFRA-"
));
}
#[test]
fn negative_reserved_prefix_cla_fact_rejected() {
- let toml = manifest_with(r#"rule_id_prefix = "CLA-FACT-""#);
+ let toml = manifest_with(r#"rule_id_prefix = "LMWV-FACT-""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-RULE-ID-NAMESPACE");
+ assert_eq!(err.subcode(), "LMWV-INFRA-RULE-ID-NAMESPACE");
assert!(matches!(
err,
- ManifestError::ReservedPrefix { prefix } if prefix == "CLA-FACT-"
+ ManifestError::ReservedPrefix { prefix } if prefix == "LMWV-FACT-"
));
}
@@ -1141,7 +1141,7 @@ ontology_version = "0.1.0"
// validate_for_v0_1 must surface the unsupported-capability error.
let err = manifest.validate_for_v0_1().unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY");
assert!(matches!(
err,
ManifestError::UnsupportedCapability {
@@ -1159,7 +1159,7 @@ ontology_version = "0.1.0"
message: String::new()
}
.subcode(),
- "CLA-INFRA-MANIFEST-MALFORMED"
+ "LMWV-INFRA-MANIFEST-MALFORMED"
);
assert_eq!(
ManifestError::GrammarViolation {
@@ -1167,25 +1167,25 @@ ontology_version = "0.1.0"
value: String::new()
}
.subcode(),
- "CLA-INFRA-MANIFEST-MALFORMED"
+ "LMWV-INFRA-MANIFEST-MALFORMED"
);
assert_eq!(
ManifestError::ReservedKind {
kind: String::new()
}
.subcode(),
- "CLA-INFRA-MANIFEST-RESERVED-KIND"
+ "LMWV-INFRA-MANIFEST-RESERVED-KIND"
);
assert_eq!(
ManifestError::ReservedPrefix {
prefix: String::new()
}
.subcode(),
- "CLA-INFRA-RULE-ID-NAMESPACE"
+ "LMWV-INFRA-RULE-ID-NAMESPACE"
);
assert_eq!(
ManifestError::UnsupportedCapability { capability: "x" }.subcode(),
- "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"
+ "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"
);
}
@@ -1193,10 +1193,10 @@ ontology_version = "0.1.0"
#[test]
fn negative_rule_id_prefix_no_trailing_hyphen_rejected() {
- // "CLA-PY" — missing trailing `-`.
- let toml = manifest_with(r#"rule_id_prefix = "CLA-PY""#);
+ // "LMWV-PY" — missing trailing `-`.
+ let toml = manifest_with(r#"rule_id_prefix = "LMWV-PY""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation {
@@ -1208,10 +1208,10 @@ ontology_version = "0.1.0"
#[test]
fn negative_rule_id_prefix_empty_inner_segment_rejected() {
- // "CLA--PY-" — empty segment between hyphens.
- let toml = manifest_with(r#"rule_id_prefix = "CLA--PY-""#);
+ // "LMWV--PY-" — empty segment between hyphens.
+ let toml = manifest_with(r#"rule_id_prefix = "LMWV--PY-""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation {
@@ -1223,10 +1223,10 @@ ontology_version = "0.1.0"
#[test]
fn negative_rule_id_prefix_only_cla_rejected() {
- // "CLA-" — no segment after CLA.
- let toml = manifest_with(r#"rule_id_prefix = "CLA-""#);
+ // "LMWV-" — no segment after LMWV.
+ let toml = manifest_with(r#"rule_id_prefix = "LMWV-""#);
let err = parse_manifest(toml.as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation {
@@ -1238,10 +1238,10 @@ ontology_version = "0.1.0"
#[test]
fn positive_multi_segment_rule_id_prefix_valid() {
- // "CLA-FOO-BAR-" — valid multi-segment prefix.
- let toml = manifest_with(r#"rule_id_prefix = "CLA-FOO-BAR-""#);
+ // "LMWV-FOO-BAR-" — valid multi-segment prefix.
+ let toml = manifest_with(r#"rule_id_prefix = "LMWV-FOO-BAR-""#);
let manifest = parse_manifest(toml.as_bytes()).unwrap();
- assert_eq!(manifest.ontology.rule_id_prefix, "CLA-FOO-BAR-");
+ assert_eq!(manifest.ontology.rule_id_prefix, "LMWV-FOO-BAR-");
}
// ── Extension format grammar (ticket clarion-fa35cad487) ─────────────────
@@ -1266,7 +1266,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = []
-rule_id_prefix = "CLA-MY-"
+rule_id_prefix = "LMWV-MY-"
ontology_version = "0.1.0"
"#
)
@@ -1281,7 +1281,7 @@ ontology_version = "0.1.0"
#[test]
fn negative_extension_uppercase_rejected() {
let err = parse_manifest(ext_manifest(r#"["PY"]"#).as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation { field: "extensions", value } if value == "PY"
@@ -1291,7 +1291,7 @@ ontology_version = "0.1.0"
#[test]
fn negative_extension_with_dot_rejected() {
let err = parse_manifest(ext_manifest(r#"[".py"]"#).as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation { field: "extensions", value } if value == ".py"
@@ -1301,7 +1301,7 @@ ontology_version = "0.1.0"
#[test]
fn negative_extension_empty_string_rejected() {
let err = parse_manifest(ext_manifest(r#"[""]"#).as_bytes()).unwrap_err();
- assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED");
+ assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED");
assert!(matches!(
err,
ManifestError::GrammarViolation { field: "extensions", value } if value.is_empty()
diff --git a/crates/clarion-core/src/plugin/mock.rs b/crates/loomweave-core/src/plugin/mock.rs
similarity index 100%
rename from crates/clarion-core/src/plugin/mock.rs
rename to crates/loomweave-core/src/plugin/mock.rs
diff --git a/crates/clarion-core/src/plugin/mod.rs b/crates/loomweave-core/src/plugin/mod.rs
similarity index 95%
rename from crates/clarion-core/src/plugin/mod.rs
rename to crates/loomweave-core/src/plugin/mod.rs
index ee556ea9..cf46643f 100644
--- a/crates/clarion-core/src/plugin/mod.rs
+++ b/crates/loomweave-core/src/plugin/mod.rs
@@ -6,7 +6,7 @@
//! - `transport` — Task 2: LSP-style Content-Length framing (L4).
//! - `jail` — Task 4: path-jail enforcement (ADR-021 §2a).
//! - `limits` — Task 4: core-enforced ceilings and circuit-breakers (ADR-021 §2b–§2d).
-//! - `discovery` — Task 5: `$PATH` scanning for `clarion-plugin-*` executables (L9, ADR-021 §L9).
+//! - `discovery` — Task 5: `$PATH` scanning for `loomweave-plugin-*` executables (L9, ADR-021 §L9).
//! - `host` — Task 6: plugin-host supervisor (ADR-021 §Layer 2, ADR-022, UQ-WP2-11).
//! - `host_findings` — `HostFinding` subcodes and constructors used by `host`.
//! - `breaker` — Task 7: crash-loop breaker (ADR-002 + UQ-WP2-10).
diff --git a/crates/clarion-core/src/plugin/protocol.rs b/crates/loomweave-core/src/plugin/protocol.rs
similarity index 99%
rename from crates/clarion-core/src/plugin/protocol.rs
rename to crates/loomweave-core/src/plugin/protocol.rs
index 6c0f7ee9..175b3152 100644
--- a/crates/clarion-core/src/plugin/protocol.rs
+++ b/crates/loomweave-core/src/plugin/protocol.rs
@@ -1,4 +1,4 @@
-//! JSON-RPC 2.0 protocol types for the Clarion plugin host.
+//! JSON-RPC 2.0 protocol types for the Loomweave plugin host.
//!
//! # Design choice: Option B — struct-per-method with manual dispatch
//!
@@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for JsonRpcVersion {
///
/// Wire shape: `{"jsonrpc":"2.0","method":"...","params":{...},"id":1}`
///
-/// JSON-RPC 2.0 permits string, number, or null request IDs. Clarion narrows
+/// JSON-RPC 2.0 permits string, number, or null request IDs. Loomweave narrows
/// this to `i64` as an application-level policy because the plugin transport is
/// core-initiated and core-controlled. Revisit this type before exposing the
/// envelope to external JSON-RPC peers or plugins that originate arbitrary IDs.
@@ -128,7 +128,7 @@ pub struct NotificationEnvelope {
/// Wire shape (error): `{"jsonrpc":"2.0","error":{...},"id":1}`
///
/// Like [`RequestEnvelope`], this intentionally accepts only `i64` IDs. That is
-/// narrower than JSON-RPC 2.0 but matches Clarion's current core-issued request
+/// narrower than JSON-RPC 2.0 but matches Loomweave's current core-issued request
/// IDs; a plugin response with a string or null ID is treated as malformed
/// plugin traffic rather than general JSON-RPC interop.
///
@@ -677,7 +677,7 @@ mod tests {
// initialize result
let r = InitializeResult {
- name: "clarion-plugin-python".to_owned(),
+ name: "loomweave-plugin-python".to_owned(),
version: "0.1.0".to_owned(),
ontology_version: "0.1.0".to_owned(),
capabilities: serde_json::json!({"wardline_aware": true}),
@@ -727,7 +727,7 @@ mod tests {
extractor_parse_latency_ms: 5,
},
findings: vec![AnalyzeFileFinding {
- subcode: "CLA-PY-PYRIGHT-RESTART".to_owned(),
+ subcode: "LMWV-PY-PYRIGHT-RESTART".to_owned(),
severity: Some("warning".to_owned()),
message: "pyright subprocess died and was restarted".to_owned(),
metadata: BTreeMap::new(),
diff --git a/crates/clarion-core/src/plugin/transport.rs b/crates/loomweave-core/src/plugin/transport.rs
similarity index 99%
rename from crates/clarion-core/src/plugin/transport.rs
rename to crates/loomweave-core/src/plugin/transport.rs
index c3bab569..e8a5e060 100644
--- a/crates/clarion-core/src/plugin/transport.rs
+++ b/crates/loomweave-core/src/plugin/transport.rs
@@ -1,4 +1,4 @@
-//! LSP-style Content-Length framing for the Clarion plugin transport.
+//! LSP-style Content-Length framing for the Loomweave plugin transport.
//!
//! Each frame is a self-describing byte sequence:
//!
diff --git a/crates/clarion-core/tests/fixtures/plugin.toml b/crates/loomweave-core/tests/fixtures/plugin.toml
similarity index 78%
rename from crates/clarion-core/tests/fixtures/plugin.toml
rename to crates/loomweave-core/tests/fixtures/plugin.toml
index 85a1173b..c385a6ba 100644
--- a/crates/clarion-core/tests/fixtures/plugin.toml
+++ b/crates/loomweave-core/tests/fixtures/plugin.toml
@@ -1,9 +1,9 @@
[plugin]
-name = "clarion-plugin-fixture"
+name = "loomweave-plugin-fixture"
plugin_id = "fixture"
version = "0.1.0"
protocol_version = "1.0"
-executable = "clarion-plugin-fixture"
+executable = "loomweave-plugin-fixture"
language = "fixture"
extensions = ["mt"]
@@ -16,7 +16,7 @@ reads_outside_project_root = false
[ontology]
entity_kinds = ["widget"]
edge_kinds = ["uses"]
-rule_id_prefix = "CLA-FIXTURE-"
+rule_id_prefix = "LMWV-FIXTURE-"
ontology_version = "0.1.0"
[ontology.roles]
diff --git a/crates/clarion-core/tests/fixtures/sample.mt b/crates/loomweave-core/tests/fixtures/sample.mt
similarity index 100%
rename from crates/clarion-core/tests/fixtures/sample.mt
rename to crates/loomweave-core/tests/fixtures/sample.mt
diff --git a/crates/clarion-core/tests/host_subprocess.rs b/crates/loomweave-core/tests/host_subprocess.rs
similarity index 81%
rename from crates/clarion-core/tests/host_subprocess.rs
rename to crates/loomweave-core/tests/host_subprocess.rs
index 18b4a77c..e3852793 100644
--- a/crates/clarion-core/tests/host_subprocess.rs
+++ b/crates/loomweave-core/tests/host_subprocess.rs
@@ -1,6 +1,6 @@
//! T1 — subprocess happy path integration test.
//!
-//! Spawns the `clarion-plugin-fixture` binary via [`PluginHost::spawn`],
+//! Spawns the `loomweave-plugin-fixture` binary via [`PluginHost::spawn`],
//! performs the full handshake, issues one `analyze_file` for a fixture file,
//! receives one entity, shuts down cleanly, and asserts exit code 0.
//!
@@ -9,32 +9,37 @@
//! for binaries in the same crate; cross-crate binary resolution requires
//! either `-Z bindeps` (unstable) or a runtime search.
-use clarion_core::PluginHost;
-use clarion_core::plugin::parse_manifest;
+use loomweave_core::PluginHost;
+use loomweave_core::plugin::parse_manifest;
/// Path to the fixture plugin.toml — embedded at compile time.
const FIXTURE_MANIFEST_BYTES: &[u8] = include_bytes!("fixtures/plugin.toml");
-/// Locate the `clarion-plugin-fixture` binary in the Cargo target directory.
+/// Locate the `loomweave-fixture-plugin` binary in the Cargo target directory.
+///
+/// The cargo artifact is named `loomweave-fixture-plugin` (off the
+/// `loomweave-plugin-*` discovery glob — see the fixture crate's Cargo.toml).
+/// Tests that spawn it stage it under its `loomweave-plugin-fixture` manifest
+/// name first (see [`staged_fixture`]).
///
/// Searches the standard Cargo output locations in order:
-/// 1. `CARGO_BIN_EXE_clarion-plugin-fixture` env var (set by cargo nextest
+/// 1. `CARGO_BIN_EXE_loomweave-fixture-plugin` env var (set by cargo nextest
/// when artifact deps are enabled — future use).
-/// 2. `/debug/clarion-plugin-fixture` (default dev build).
-/// 3. `/release/clarion-plugin-fixture` (release build).
+/// 2. `/debug/loomweave-fixture-plugin` (default dev build).
+/// 3. `/release/loomweave-fixture-plugin` (release build).
///
/// Builds the fixture on demand and panics with a clear message if the binary
/// still cannot be found.
fn fixture_binary_path() -> std::path::PathBuf {
// Check if an explicit path was provided (e.g. by a future artifact dep).
- if let Ok(path) = std::env::var("CARGO_BIN_EXE_clarion-plugin-fixture") {
+ if let Ok(path) = std::env::var("CARGO_BIN_EXE_loomweave-fixture-plugin") {
return std::path::PathBuf::from(path);
}
// Locate the workspace target directory via CARGO_MANIFEST_DIR.
// CARGO_MANIFEST_DIR for an integration test is the crate's directory.
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
- // clarion-core is at crates/clarion-core; workspace root is ../../
+ // loomweave-core is at crates/loomweave-core; workspace root is ../../
let workspace_root = manifest_dir
.parent() // crates/
.and_then(|p| p.parent()) // workspace root
@@ -55,17 +60,38 @@ fn fixture_binary_path() -> std::path::PathBuf {
}
panic!(
- "clarion-plugin-fixture binary not found. \
- Tried `cargo build -p clarion-plugin-fixture --bin clarion-plugin-fixture`. \
+ "loomweave-fixture-plugin binary not found. \
+ Tried `cargo build -p loomweave-plugin-fixture --bin loomweave-fixture-plugin`. \
Searched in: {}",
target_dir.display()
);
}
+/// Stage the fixture binary under its manifest-declared basename
+/// (`loomweave-plugin-fixture`) and return `(tempdir, staged_path)`.
+///
+/// The cargo artifact is named `loomweave-fixture-plugin` (off the
+/// `loomweave-plugin-*` discovery glob), so it cannot be spawned directly:
+/// [`PluginHost::spawn`] requires the binary's basename to equal the manifest's
+/// `plugin.executable` (`loomweave-plugin-fixture`). Staging a copy under that
+/// name mirrors how a real install presents the binary. Keep the returned
+/// `TempDir` alive for the duration of the spawn.
+fn staged_fixture() -> (tempfile::TempDir, std::path::PathBuf) {
+ let dir = tempfile::TempDir::new().expect("create fixture staging dir");
+ let staged = dir.path().join(format!(
+ "loomweave-plugin-fixture{}",
+ std::env::consts::EXE_SUFFIX
+ ));
+ // `copy` preserves the exec bit on Unix and compiles on all platforms
+ // (unlike `os::unix::fs::symlink`); this test is not `cfg(unix)`-gated.
+ std::fs::copy(fixture_binary_path(), &staged).expect("stage fixture binary");
+ (dir, staged)
+}
+
fn find_fixture_binary(target_dir: &std::path::Path) -> Option {
for profile in &["debug", "release"] {
let candidate = target_dir.join(profile).join(format!(
- "clarion-plugin-fixture{}",
+ "loomweave-fixture-plugin{}",
std::env::consts::EXE_SUFFIX
));
if candidate.exists() {
@@ -81,17 +107,17 @@ fn build_fixture_binary(workspace_root: &std::path::Path, target_dir: &std::path
.current_dir(workspace_root)
.arg("build")
.arg("-p")
- .arg("clarion-plugin-fixture")
+ .arg("loomweave-plugin-fixture")
.arg("--bin")
- .arg("clarion-plugin-fixture")
+ .arg("loomweave-fixture-plugin")
.arg("--target-dir")
.arg(target_dir)
.output()
- .expect("spawn cargo build for clarion-plugin-fixture");
+ .expect("spawn cargo build for loomweave-plugin-fixture");
assert!(
output.status.success(),
- "cargo build for clarion-plugin-fixture failed with status {}.\nstdout:\n{}\nstderr:\n{}",
+ "cargo build for loomweave-plugin-fixture failed with status {}.\nstdout:\n{}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
@@ -105,7 +131,7 @@ fn fixture_manifest_parses_correctly() {
let manifest = parse_manifest(FIXTURE_MANIFEST_BYTES).expect("fixture manifest must parse");
assert_eq!(manifest.plugin.plugin_id, "fixture");
assert_eq!(manifest.ontology.entity_kinds, vec!["widget"]);
- assert_eq!(manifest.ontology.rule_id_prefix, "CLA-FIXTURE-");
+ assert_eq!(manifest.ontology.rule_id_prefix, "LMWV-FIXTURE-");
assert!(
!manifest.capabilities.runtime.reads_outside_project_root,
"fixture manifest must not request reads_outside_project_root"
@@ -129,8 +155,10 @@ fn t1_subprocess_happy_path() {
let sample_path = project_dir.path().join("sample.mt");
std::fs::write(&sample_path, b"widget demo.sample {}\n").expect("write sample.mt");
- // 3. Spawn the plugin with the discovered binary path.
- let exec = fixture_binary_path();
+ // 3. Spawn the plugin with the discovered binary path, staged under its
+ // manifest-declared `loomweave-plugin-fixture` basename so spawn's
+ // basename check passes.
+ let (_fixture_stage, exec) = staged_fixture();
let (mut host, mut child) =
PluginHost::spawn(manifest, project_dir.path(), &exec).expect("spawn must succeed");
@@ -203,13 +231,13 @@ fn t9_handshake_failure_on_immediate_exit_returns_err_promptly() {
let project_dir = tempfile::TempDir::new().expect("tmpdir");
// Construct a symlink whose basename matches the manifest-declared
- // `plugin.executable` (`clarion-plugin-fixture`) but whose target is
+ // `plugin.executable` (`loomweave-plugin-fixture`) but whose target is
// `/bin/true`. This exits immediately without reading stdin, which is
// the handshake-failure mode we want to test. Pointing `spawn` at
// `/bin/true` directly would fail the basename-match check before
// forking, which tests a different property.
let stub_dir = tempfile::TempDir::new().expect("stub dir");
- let stub_exec = stub_dir.path().join("clarion-plugin-fixture");
+ let stub_exec = stub_dir.path().join("loomweave-plugin-fixture");
std::os::unix::fs::symlink("/bin/true", &stub_exec).expect("symlink /bin/true");
let start = std::time::Instant::now();
@@ -243,7 +271,7 @@ fn t9a_handshake_failure_reaps_exited_subprocess() {
let project_dir = tempfile::TempDir::new().expect("tmpdir");
let stub_dir = tempfile::TempDir::new().expect("stub dir");
let pid_file = stub_dir.path().join("plugin.pid");
- let stub_exec = stub_dir.path().join("clarion-plugin-fixture");
+ let stub_exec = stub_dir.path().join("loomweave-plugin-fixture");
std::fs::write(
&stub_exec,
format!(
@@ -321,7 +349,7 @@ fn t9b_stderr_tail_is_some_after_spawn() {
let sample_path = project_dir.path().join("sample.mt");
std::fs::write(&sample_path, b"widget demo.sample {}\n").expect("write sample.mt");
- let exec = fixture_binary_path();
+ let (_fixture_stage, exec) = staged_fixture();
let (mut host, mut child) =
PluginHost::spawn(manifest, project_dir.path(), &exec).expect("spawn must succeed");
@@ -346,7 +374,7 @@ fn t9b_stderr_tail_is_some_after_spawn() {
#[test]
#[cfg(unix)]
fn t10_manifest_executable_with_path_separator_is_refused() {
- use clarion_core::HostError;
+ use loomweave_core::HostError;
let mut manifest = parse_manifest(FIXTURE_MANIFEST_BYTES).expect("fixture manifest must parse");
manifest.plugin.executable = "/bin/sh".to_owned();
@@ -376,11 +404,11 @@ fn t10_manifest_executable_with_path_separator_is_refused() {
#[test]
#[cfg(unix)]
fn t11_manifest_executable_basename_mismatch_is_refused() {
- use clarion_core::HostError;
+ use loomweave_core::HostError;
let mut manifest = parse_manifest(FIXTURE_MANIFEST_BYTES).expect("fixture manifest must parse");
// Declare a basename that will not match the discovered binary.
- manifest.plugin.executable = "clarion-plugin-other".to_owned();
+ manifest.plugin.executable = "loomweave-plugin-other".to_owned();
let project_dir = tempfile::TempDir::new().expect("tmpdir");
let exec = fixture_binary_path();
diff --git a/crates/clarion-federation/Cargo.toml b/crates/loomweave-federation/Cargo.toml
similarity index 78%
rename from crates/clarion-federation/Cargo.toml
rename to crates/loomweave-federation/Cargo.toml
index 1cc05352..27006e45 100644
--- a/crates/clarion-federation/Cargo.toml
+++ b/crates/loomweave-federation/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "clarion-federation"
+name = "loomweave-federation"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -10,7 +10,7 @@ rust-version.workspace = true
workspace = true
[dependencies]
-clarion-core = { path = "../clarion-core", version = "1.3.0" }
+loomweave-core = { path = "../loomweave-core", version = "1.0.0" }
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
diff --git a/crates/clarion-federation/src/config.rs b/crates/loomweave-federation/src/config.rs
similarity index 92%
rename from crates/clarion-federation/src/config.rs
rename to crates/loomweave-federation/src/config.rs
index 92b316b6..e3b790a5 100644
--- a/crates/clarion-federation/src/config.rs
+++ b/crates/loomweave-federation/src/config.rs
@@ -39,13 +39,13 @@ impl McpConfig {
|| self.llm.anthropic_api_key_env.is_some()
{
return Err(ConfigError::DeprecatedProvider {
- code: "CLA-CONFIG-DEPRECATED-PROVIDER",
+ code: "LMWV-CONFIG-DEPRECATED-PROVIDER",
});
}
if self.integrations.filigree.enabled && self.integrations.filigree.actor.trim().is_empty()
{
return Err(ConfigError::InvalidFiligreeActor {
- code: "CLA-CONFIG-FILIGREE-ACTOR-BLANK",
+ code: "LMWV-CONFIG-FILIGREE-ACTOR-BLANK",
});
}
self.serve.http.validate_loopback_trust()?;
@@ -90,7 +90,7 @@ impl Default for LlmConfig {
}
/// Semantic-search (embeddings) policy for `search_semantic` (`WS5b` / ADR-040).
-/// **Opt-in, off by default** — mirrors [`LlmConfig`]; Loom is local-first, so
+/// **Opt-in, off by default** — mirrors [`LlmConfig`]; Weft is local-first, so
/// nothing here makes a hosted embedding service required. When `enabled` is
/// false the `search_semantic` tool degrades honestly to "not enabled".
#[derive(Debug, Clone, PartialEq, Deserialize)]
@@ -170,8 +170,8 @@ pub struct OpenRouterAttributionConfig {
impl Default for OpenRouterAttributionConfig {
fn default() -> Self {
Self {
- referer: "https://github.com/tachyon-beep/clarion".to_owned(),
- title: "Clarion".to_owned(),
+ referer: "https://github.com/foundryside-dev/loomweave".to_owned(),
+ title: "Loomweave".to_owned(),
}
}
}
@@ -286,7 +286,7 @@ pub struct ServeConfig {
#[serde(default)]
pub struct McpServeConfig {
/// Enable MCP tools that can mutate state, spawn processes, or call an LLM.
- /// Default false: `clarion serve` exposes consult-mode read tools unless an
+ /// Default false: `loomweave serve` exposes consult-mode read tools unless an
/// operator explicitly opts into write-capable MCP operations.
pub enable_write_tools: bool,
}
@@ -303,14 +303,14 @@ pub struct HttpReadConfig {
/// `Authorization: Bearer `; the capabilities probe is
/// always unauthenticated. When the env var is unset on a loopback
/// bind, the surface stays unauthenticated (the v0.1 trust model).
- /// When the env var is unset on a non-loopback bind, `clarion serve`
- /// refuses to start (`CLA-CONFIG-HTTP-NO-AUTH`). Default
- /// `CLARION_LOOM_TOKEN` matches Filigree's pinned client default.
+ /// When the env var is unset on a non-loopback bind, `loomweave serve`
+ /// refuses to start (`LMWV-CONFIG-HTTP-NO-AUTH`). Default
+ /// `WEFT_TOKEN` matches Filigree's pinned client default.
pub token_env: String,
- /// Optional env var holding the Loom component identity HMAC secret.
- /// When configured, `clarion serve` refuses to start unless the env var
+ /// Optional env var holding the Weft component identity HMAC secret.
+ /// When configured, `loomweave serve` refuses to start unless the env var
/// exists and protected HTTP read routes require
- /// `X-Loom-Component: clarion:`.
+ /// `X-Weft-Component: loomweave:`.
pub identity_token_env: Option,
/// Enable the Wardline taint-store WRITE API (POST /api/wardline/taint-facts).
/// Default false — `serve` is read-only unless explicitly opted in (ADR-036).
@@ -325,7 +325,7 @@ impl Default for HttpReadConfig {
enabled: false,
bind: SocketAddr::from(([127, 0, 0, 1], 9111)),
allow_non_loopback: false,
- token_env: "CLARION_LOOM_TOKEN".to_owned(),
+ token_env: "WEFT_TOKEN".to_owned(),
identity_token_env: None,
wardline_taint_write: false,
}
@@ -336,7 +336,7 @@ impl HttpReadConfig {
pub fn validate_loopback_trust(&self) -> Result<(), ConfigError> {
if self.enabled && !self.allow_non_loopback && !self.is_loopback_bind() {
return Err(ConfigError::NonLoopbackHttpBind {
- code: "CLA-CONFIG-HTTP-NON-LOOPBACK",
+ code: "LMWV-CONFIG-HTTP-NON-LOOPBACK",
bind: self.bind,
});
}
@@ -361,7 +361,7 @@ impl HttpReadConfig {
.is_some_and(|value| !value.trim().is_empty());
if !has_secret {
return Err(ConfigError::MissingHttpIdentitySecret {
- code: "CLA-CONFIG-HTTP-IDENTITY-MISSING",
+ code: "LMWV-CONFIG-HTTP-IDENTITY-MISSING",
token_env: env_var.to_owned(),
});
}
@@ -382,7 +382,7 @@ impl HttpReadConfig {
return Ok(());
}
Err(ConfigError::NonLoopbackHttpNoAuth {
- code: "CLA-CONFIG-HTTP-NO-AUTH",
+ code: "LMWV-CONFIG-HTTP-NO-AUTH",
bind: self.bind,
token_env: self.token_env.clone(),
})
@@ -411,16 +411,16 @@ pub struct FiligreeConfig {
pub actor: String,
pub token_env: String,
pub timeout_seconds: u64,
- /// Whether `clarion analyze` POSTs its findings to Filigree's
+ /// Whether `loomweave analyze` POSTs its findings to Filigree's
/// `POST /api/v1/scan-results` intake on completion (WP9-B,
- /// REQ-FINDING-03). Emission is a one-way Clarion→Filigree data egress, so
+ /// REQ-FINDING-03). Emission is a one-way Loomweave→Filigree data egress, so
/// it is its own explicit opt-in: it requires both `enabled` *and* this
/// flag, and **both default `false`**. Enabling the integration for the
/// read side (`issues_for` reverse-lookup) therefore does not silently
/// start outbound emission — the operator opts into the write direction
/// separately by setting `emit_findings: true`.
pub emit_findings: bool,
- /// Age threshold (days) for `clarion analyze --prune-unseen` (REQ-FINDING-06):
+ /// Age threshold (days) for `loomweave analyze --prune-unseen` (REQ-FINDING-06):
/// findings Filigree has marked `unseen_in_latest` and that are older than
/// this are soft-archived (`fixed`) by the retention sweep. Default 30.
/// Only consulted when `--prune-unseen` is passed; the sweep itself is
@@ -433,7 +433,7 @@ impl Default for FiligreeConfig {
Self {
enabled: false,
base_url: "http://127.0.0.1:8766".to_owned(),
- actor: "clarion-mcp".to_owned(),
+ actor: "loomweave-mcp".to_owned(),
token_env: "FILIGREE_API_TOKEN".to_owned(),
timeout_seconds: 5,
emit_findings: false,
@@ -465,10 +465,10 @@ where
match config.llm.provider {
LlmProviderKind::Recording => Ok(ProviderSelection::Recording),
LlmProviderKind::Anthropic => Err(ConfigError::DeprecatedProvider {
- code: "CLA-CONFIG-DEPRECATED-PROVIDER",
+ code: "LMWV-CONFIG-DEPRECATED-PROVIDER",
}),
LlmProviderKind::OpenRouter => {
- let live_env_opt_in = env_lookup("CLARION_LLM_LIVE").as_deref() == Some("1");
+ let live_env_opt_in = env_lookup("LOOMWEAVE_LLM_LIVE").as_deref() == Some("1");
if !config.llm.allow_live_provider && !live_env_opt_in {
return Ok(ProviderSelection::Disabled);
}
@@ -486,14 +486,14 @@ where
})
}
LlmProviderKind::CodexCli => {
- let live_env_opt_in = env_lookup("CLARION_LLM_LIVE").as_deref() == Some("1");
+ let live_env_opt_in = env_lookup("LOOMWEAVE_LLM_LIVE").as_deref() == Some("1");
if !config.llm.allow_live_provider && !live_env_opt_in {
return Ok(ProviderSelection::Disabled);
}
Ok(ProviderSelection::CodexCli)
}
LlmProviderKind::ClaudeCli => {
- let live_env_opt_in = env_lookup("CLARION_LLM_LIVE").as_deref() == Some("1");
+ let live_env_opt_in = env_lookup("LOOMWEAVE_LLM_LIVE").as_deref() == Some("1");
if !config.llm.allow_live_provider && !live_env_opt_in {
return Ok(ProviderSelection::Disabled);
}
@@ -526,7 +526,7 @@ pub enum ConfigError {
InvalidFiligreeActor { code: &'static str },
#[error(
- "{code}: serve.http.bind {bind} exposes the unauthenticated non-loopback Clarion HTTP read API; \
+ "{code}: serve.http.bind {bind} exposes the unauthenticated non-loopback Loomweave HTTP read API; \
bind to loopback (127.0.0.1 or ::1) or set serve.http.allow_non_loopback: true only on a trusted network"
)]
NonLoopbackHttpBind {
@@ -548,7 +548,7 @@ pub enum ConfigError {
#[error(
"{code}: serve.http.identity_token_env names ${token_env}, but that env var is unset; \
- refusing to start an HTTP read API with incomplete Loom component identity configuration."
+ refusing to start an HTTP read API with incomplete Weft component identity configuration."
)]
MissingHttpIdentitySecret {
code: &'static str,
@@ -556,7 +556,7 @@ pub enum ConfigError {
},
#[error(
- "{code}: clarion.yaml contains both `llm` and `llm_policy` top-level keys; \
+ "{code}: loomweave.yaml contains both `llm` and `llm_policy` top-level keys; \
`llm_policy` is a serde alias for `llm` and serde silently discards one. \
Pick one and remove the other."
)]
@@ -581,7 +581,7 @@ fn reject_llm_policy_alias_collision(raw: &str) -> Result<(), ConfigError> {
let has_llm_policy = mapping.contains_key("llm_policy");
if has_llm && has_llm_policy {
return Err(ConfigError::AmbiguousLlmKey {
- code: "CLA-CONFIG-AMBIGUOUS-LLM-KEY",
+ code: "LMWV-CONFIG-AMBIGUOUS-LLM-KEY",
});
}
Ok(())
@@ -604,15 +604,15 @@ llm:
endpoint_url: http://localhost:4000/api/v1
api_key_env: TEST_OPENROUTER_KEY
attribution:
- referer: https://example.invalid/clarion
- title: Clarion Test
+ referer: https://example.invalid/loomweave
+ title: Loomweave Test
max_inferred_edges_per_caller: 3
cache_max_age_days: 7
integrations:
filigree:
enabled: true
base_url: "http://127.0.0.1:9999"
- actor: "clarion-test"
+ actor: "loomweave-test"
token_env: TEST_FILIGREE_TOKEN
timeout_seconds: 2
"#,
@@ -630,15 +630,15 @@ integrations:
assert_eq!(cfg.llm.openrouter.api_key_env, "TEST_OPENROUTER_KEY");
assert_eq!(
cfg.llm.openrouter.attribution.referer,
- "https://example.invalid/clarion"
+ "https://example.invalid/loomweave"
);
- assert_eq!(cfg.llm.openrouter.attribution.title, "Clarion Test");
+ assert_eq!(cfg.llm.openrouter.attribution.title, "Loomweave Test");
assert_eq!(cfg.llm.openrouter.timeout_seconds, 300); // default — not set in YAML
assert_eq!(cfg.llm.max_inferred_edges_per_caller, 3);
assert_eq!(cfg.llm.cache_max_age_days, 7);
assert!(cfg.integrations.filigree.enabled);
assert_eq!(cfg.integrations.filigree.base_url, "http://127.0.0.1:9999");
- assert_eq!(cfg.integrations.filigree.actor, "clarion-test");
+ assert_eq!(cfg.integrations.filigree.actor, "loomweave-test");
assert_eq!(cfg.integrations.filigree.token_env, "TEST_FILIGREE_TOKEN");
assert_eq!(cfg.integrations.filigree.timeout_seconds, 2);
}
@@ -709,7 +709,7 @@ llm_policy:
match err {
ConfigError::AmbiguousLlmKey { code } => {
- assert_eq!(code, "CLA-CONFIG-AMBIGUOUS-LLM-KEY");
+ assert_eq!(code, "LMWV-CONFIG-AMBIGUOUS-LLM-KEY");
}
other => panic!("expected AmbiguousLlmKey error, got: {other:?}"),
}
@@ -781,7 +781,7 @@ llm_policy:
codex_cli:
executable: /tmp/fake-codex
model: gpt-5.5
- profile: clarion
+ profile: loomweave
sandbox: read-only
timeout_seconds: 30
",
@@ -792,7 +792,7 @@ llm_policy:
assert_eq!(cfg.llm.model_id, "codex-cli-default");
assert_eq!(cfg.llm.codex_cli.executable, "/tmp/fake-codex");
assert_eq!(cfg.llm.codex_cli.model.as_deref(), Some("gpt-5.5"));
- assert_eq!(cfg.llm.codex_cli.profile.as_deref(), Some("clarion"));
+ assert_eq!(cfg.llm.codex_cli.profile.as_deref(), Some("loomweave"));
assert_eq!(cfg.llm.codex_cli.sandbox, CodexSandboxMode::ReadOnly);
assert_eq!(cfg.llm.codex_cli.timeout_seconds, 30);
@@ -817,7 +817,7 @@ llm_policy:
assert_eq!(selected, ProviderSelection::Disabled);
let env_selected = select_provider_with_env(&cfg, |name| {
- (name == "CLARION_LLM_LIVE").then(|| "1".to_owned())
+ (name == "LOOMWEAVE_LLM_LIVE").then(|| "1".to_owned())
})
.expect("provider selection via env opt-in");
assert_eq!(env_selected, ProviderSelection::CodexCli);
@@ -881,7 +881,7 @@ llm_policy:
assert_eq!(selected, ProviderSelection::Disabled);
let env_selected = select_provider_with_env(&cfg, |name| {
- (name == "CLARION_LLM_LIVE").then(|| "1".to_owned())
+ (name == "LOOMWEAVE_LLM_LIVE").then(|| "1".to_owned())
})
.expect("provider selection via env opt-in");
assert_eq!(env_selected, ProviderSelection::ClaudeCli);
@@ -947,14 +947,14 @@ serve:
http:
enabled: true
bind: "127.0.0.1:0"
- identity_token_env: CLARION_TEST_IDENTITY
+ identity_token_env: LOOMWEAVE_TEST_IDENTITY
"#,
)
.expect("parse HTTP identity_token_env");
assert_eq!(
cfg.serve.http.identity_token_env.as_deref(),
- Some("CLARION_TEST_IDENTITY")
+ Some("LOOMWEAVE_TEST_IDENTITY")
);
}
@@ -1080,7 +1080,7 @@ llm:
.expect_err("old provider shape should be rejected");
assert!(matches!(err, ConfigError::DeprecatedProvider { .. }));
- assert!(err.to_string().contains("CLA-CONFIG-DEPRECATED-PROVIDER"));
+ assert!(err.to_string().contains("LMWV-CONFIG-DEPRECATED-PROVIDER"));
assert!(err.to_string().contains("provider: openrouter"));
}
@@ -1096,6 +1096,6 @@ integrations:
)
.expect_err("blank Filigree actor should be rejected");
- assert!(err.to_string().contains("CLA-CONFIG-FILIGREE-ACTOR-BLANK"));
+ assert!(err.to_string().contains("LMWV-CONFIG-FILIGREE-ACTOR-BLANK"));
}
}
diff --git a/crates/clarion-federation/src/filigree.rs b/crates/loomweave-federation/src/filigree.rs
similarity index 90%
rename from crates/clarion-federation/src/filigree.rs
rename to crates/loomweave-federation/src/filigree.rs
index 869d88b3..4ceda8a1 100644
--- a/crates/clarion-federation/src/filigree.rs
+++ b/crates/loomweave-federation/src/filigree.rs
@@ -1,11 +1,11 @@
-//! Filigree HTTP/MCP contract helpers for Clarion MCP.
+//! Filigree HTTP/MCP contract helpers for Loomweave MCP.
use std::io::{BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::Duration;
-use clarion_core::plugin::{ContentLengthCeiling, Frame, read_frame, write_frame};
+use loomweave_core::plugin::{ContentLengthCeiling, Frame, read_frame, write_frame};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -20,9 +20,9 @@ pub struct EntityAssociationsResponse {
pub associations: Vec,
}
-/// The subset of a Filigree issue Clarion surfaces alongside an
+/// The subset of a Filigree issue Loomweave surfaces alongside an
/// entity-association match: enough to render the match without an agent
-/// having to call back into Filigree. Sourced from `GET /api/loom/issues/{id}`.
+/// having to call back into Filigree. Sourced from `GET /api/weft/issues/{id}`.
/// Unknown fields in the response are ignored, so Filigree can grow the route
/// without breaking this read.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -32,8 +32,8 @@ pub struct IssueDetail {
pub priority: i64,
}
-/// Request Clarion sends to Filigree's observation scratchpad when an agent
-/// proposes guidance. This is an observation, not a Clarion sheet.
+/// Request Loomweave sends to Filigree's observation scratchpad when an agent
+/// proposes guidance. This is an observation, not a Loomweave sheet.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ObservationCreateRequest {
pub summary: String,
@@ -51,7 +51,7 @@ pub struct ObservationCreateResponse {
pub observation_id: String,
}
-/// Pending Filigree observation row, as read from `GET /api/loom/observations`
+/// Pending Filigree observation row, as read from `GET /api/weft/observations`
/// or from a test double. Unknown live fields are ignored.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct ObservationRecord {
@@ -72,17 +72,17 @@ pub struct ObservationRecord {
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct EntityAssociation {
pub issue_id: String,
- /// Opaque Clarion association key as stored by Filigree. New bindings use
- /// the entity's SEI (`clarion:eid:*`); legacy rows may still carry the
+ /// Opaque Loomweave association key as stored by Filigree. New bindings use
+ /// the entity's SEI (`loomweave:eid:*`); legacy rows may still carry the
/// mutable locator (`{plugin}:{kind}:{qualname}`).
- pub clarion_entity_id: String,
+ pub loomweave_entity_id: String,
pub content_hash_at_attach: String,
pub attached_at: String,
pub attached_by: String,
}
-/// One Wardline finding as Clarion surfaces it — the subset of Filigree's
-/// `ScanFindingLoom` (`GET /api/loom/findings`) used for read-time
+/// One Wardline finding as Loomweave surfaces it — the subset of Filigree's
+/// `ScanFindingWeft` (`GET /api/weft/findings`) used for read-time
/// reconciliation. Unknown fields are ignored so Filigree can grow the row.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct WardlineFinding {
@@ -106,13 +106,13 @@ pub struct WardlineFinding {
pub metadata: serde_json::Value,
}
-/// Envelope returned by `GET /api/loom/findings` — the paged list of
-/// [`WardlineFinding`] rows Clarion reconciles against.
+/// Envelope returned by `GET /api/weft/findings` — the paged list of
+/// [`WardlineFinding`] rows Loomweave reconciles against.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct WardlineFindingsResponse {
#[serde(default)]
pub items: Vec,
- /// True when more findings pages follow. Clarion does not page the findings
+ /// True when more findings pages follow. Loomweave does not page the findings
/// list (the offset param is unpinned in the federation contract); when this
/// is true the first page is an incomplete view, so the caller fails closed
/// to `unavailable` rather than silently undercounting the file's findings.
@@ -120,20 +120,20 @@ pub struct WardlineFindingsResponse {
pub has_more: bool,
}
-/// One row of `GET /api/loom/files` — only the fields needed to map a path to
+/// One row of `GET /api/weft/files` — only the fields needed to map a path to
/// Filigree's `file_id`.
#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct LoomFileRecord {
+pub struct WeftFileRecord {
pub file_id: String,
pub path: String,
}
-/// Envelope returned by `GET /api/loom/files` — the paged list of
-/// [`LoomFileRecord`] rows Clarion uses to map a path to a `file_id`.
+/// Envelope returned by `GET /api/weft/files` — the paged list of
+/// [`WeftFileRecord`] rows Loomweave uses to map a path to a `file_id`.
#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct LoomFilesResponse {
+pub struct WeftFilesResponse {
#[serde(default)]
- pub items: Vec,
+ pub items: Vec,
/// True when more pages follow. When the exact-path match is absent and
/// `has_more` is true, the result is indeterminate — the file may be on a
/// later page — so callers must degrade to `unavailable` rather than
@@ -143,7 +143,7 @@ pub struct LoomFilesResponse {
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct LoomObservationsResponse {
+pub struct WeftObservationsResponse {
#[serde(default)]
pub items: Vec,
#[serde(default)]
@@ -160,7 +160,7 @@ pub fn parse_wardline_findings_response(
serde_json::from_str(body).map_err(FiligreeContractError::from)
}
-pub fn parse_loom_files_response(body: &str) -> Result {
+pub fn parse_weft_files_response(body: &str) -> Result {
serde_json::from_str(body).map_err(FiligreeContractError::from)
}
@@ -257,7 +257,7 @@ pub trait FiligreeLookup: Send + Sync {
Ok(None)
}
- /// Mark a pending observation as consumed after Clarion writes the local
+ /// Mark a pending observation as consumed after Loomweave writes the local
/// guidance sheet. Default no-ops so promotion remains local-first if the
/// scratchpad cleanup route is unavailable.
fn dismiss_observation(
@@ -315,7 +315,7 @@ impl FiligreeHttpClient {
}
/// POST a scan-results batch to Filigree's native intake (WP9-B,
- /// REQ-FINDING-03). One-way Clarion→Filigree push; the caller is expected to
+ /// REQ-FINDING-03). One-way Loomweave→Filigree push; the caller is expected to
/// inspect [`ScanResultsResponse::warnings`] (severity coercion, unknown
/// `scan_run_id`, etc.) rather than just the counts.
///
@@ -358,10 +358,10 @@ impl FiligreeHttpClient {
}
/// POST a retention sweep to Filigree's `clean-stale` route (REQ-FINDING-06,
- /// `--prune-unseen`). One-way Clarion→Filigree call; Filigree soft-archives
+ /// `--prune-unseen`). One-way Loomweave→Filigree call; Filigree soft-archives
/// its own `unseen_in_latest` findings for the given `scan_source`. The
/// `scan_source` scoping is enforced server-side, so this can only sweep
- /// Clarion's findings.
+ /// Loomweave's findings.
///
/// # Errors
///
@@ -501,20 +501,20 @@ impl FiligreeHttpClient {
&mut stdin,
&serde_json::json!({
"jsonrpc": "2.0",
- "id": "clarion-init",
+ "id": "loomweave-init",
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
- "name": "clarion",
+ "name": "loomweave",
"version": env!("CARGO_PKG_VERSION")
}
}
}),
tool,
)?;
- let _ = read_mcp_json(&mut stdout, "clarion-init", tool)?;
+ let _ = read_mcp_json(&mut stdout, "loomweave-init", tool)?;
write_mcp_json(
&mut stdin,
@@ -530,7 +530,7 @@ impl FiligreeHttpClient {
&mut stdin,
&serde_json::json!({
"jsonrpc": "2.0",
- "id": "clarion-call",
+ "id": "loomweave-call",
"method": "tools/call",
"params": {
"name": tool,
@@ -541,7 +541,7 @@ impl FiligreeHttpClient {
)?;
drop(stdin);
- let response = read_mcp_json(&mut stdout, "clarion-call", tool)?;
+ let response = read_mcp_json(&mut stdout, "loomweave-call", tool)?;
let _ = child.wait();
if let Some(error) = response.get("error") {
return Err(FiligreeClientError::McpTool {
@@ -592,8 +592,8 @@ impl FiligreeLookup for FiligreeHttpClient {
) -> Result, FiligreeClientError> {
// Hop 1: path -> Filigree file_id. path_prefix is a prefix filter, so
// take only the row whose path is byte-exact.
- let files: LoomFilesResponse =
- self.get_json(&loom_files_url(&self.base_url, "wardline", path))?;
+ let files: WeftFilesResponse =
+ self.get_json(&weft_files_url(&self.base_url, "wardline", path))?;
let exact = files.items.into_iter().find(|f| f.path == path);
let Some(file_id) = exact.map(|f| f.file_id) else {
// No exact match on this page. If has_more is true the result is
@@ -603,22 +603,22 @@ impl FiligreeLookup for FiligreeHttpClient {
return Err(FiligreeClientError::HttpStatus {
status: 0,
body:
- "loom/files truncated before exact path match; cannot conclude no findings"
+ "weft/files truncated before exact path match; cannot conclude no findings"
.to_owned(),
});
}
return Ok(Vec::new());
};
- // Hop 2: file_id -> wardline findings. As with hop-1, Clarion reads only
+ // Hop 2: file_id -> wardline findings. As with hop-1, Loomweave reads only
// the first page; if it is truncated (`has_more`) the findings view is
// incomplete, so fail closed to `unavailable` rather than returning a
// silent undercount.
let findings: WardlineFindingsResponse =
- self.get_json(&loom_findings_url(&self.base_url, "wardline", &file_id))?;
+ self.get_json(&weft_findings_url(&self.base_url, "wardline", &file_id))?;
if findings.has_more {
return Err(FiligreeClientError::HttpStatus {
status: 0,
- body: "loom/findings truncated; cannot enumerate all findings for file".to_owned(),
+ body: "weft/findings truncated; cannot enumerate all findings for file".to_owned(),
});
}
Ok(findings.items)
@@ -653,8 +653,8 @@ impl FiligreeLookup for FiligreeHttpClient {
let mut offset = 0_u64;
let limit = 100_u64;
loop {
- let page: LoomObservationsResponse =
- self.get_json(&loom_observations_url(&self.base_url, limit, offset))?;
+ let page: WeftObservationsResponse =
+ self.get_json(&weft_observations_url(&self.base_url, limit, offset))?;
if let Some(found) = page
.items
.into_iter()
@@ -696,7 +696,7 @@ pub fn parse_issue_detail_response(body: &str) -> Result String {
format!(
- "{}/api/loom/issues/{}",
+ "{}/api/weft/issues/{}",
base_url.trim_end_matches('/'),
percent_encode_query_value(issue_id)
)
@@ -710,27 +710,27 @@ pub fn entity_associations_url(base_url: &str, entity_id: &str) -> String {
)
}
-pub fn loom_files_url(base_url: &str, scan_source: &str, path_prefix: &str) -> String {
+pub fn weft_files_url(base_url: &str, scan_source: &str, path_prefix: &str) -> String {
format!(
- "{}/api/loom/files?scan_source={}&path_prefix={}",
+ "{}/api/weft/files?scan_source={}&path_prefix={}",
base_url.trim_end_matches('/'),
percent_encode_query_value(scan_source),
percent_encode_query_value(path_prefix)
)
}
-pub fn loom_findings_url(base_url: &str, scan_source: &str, file_id: &str) -> String {
+pub fn weft_findings_url(base_url: &str, scan_source: &str, file_id: &str) -> String {
format!(
- "{}/api/loom/findings?scan_source={}&file_id={}",
+ "{}/api/weft/findings?scan_source={}&file_id={}",
base_url.trim_end_matches('/'),
percent_encode_query_value(scan_source),
percent_encode_query_value(file_id)
)
}
-pub fn loom_observations_url(base_url: &str, limit: u64, offset: u64) -> String {
+pub fn weft_observations_url(base_url: &str, limit: u64, offset: u64) -> String {
format!(
- "{}/api/loom/observations?limit={}&offset={}",
+ "{}/api/weft/observations?limit={}&offset={}",
base_url.trim_end_matches('/'),
limit,
offset
@@ -780,7 +780,7 @@ fn read_mcp_json(
}
fn resolve_filigree_mcp_command(project_root: Option<&Path>) -> (String, Vec) {
- if let Ok(raw) = std::env::var("CLARION_FILIGREE_MCP_COMMAND") {
+ if let Ok(raw) = std::env::var("LOOMWEAVE_FILIGREE_MCP_COMMAND") {
let mut parts: Vec = raw
.split_whitespace()
.map(|part| replace_project_placeholder(part, project_root))
@@ -860,7 +860,7 @@ mod tests {
"associations": [
{
"issue_id": "filigree-1234567890",
- "clarion_entity_id": "python:function:demo.hello",
+ "loomweave_entity_id": "python:function:demo.hello",
"content_hash_at_attach": "hash-a",
"attached_at": "2026-05-17T00:00:00.000Z",
"attached_by": "codex"
@@ -873,7 +873,7 @@ mod tests {
assert_eq!(parsed.associations.len(), 1);
let row = &parsed.associations[0];
assert_eq!(row.issue_id, "filigree-1234567890");
- assert_eq!(row.clarion_entity_id, "python:function:demo.hello");
+ assert_eq!(row.loomweave_entity_id, "python:function:demo.hello");
assert_eq!(row.content_hash_at_attach, "hash-a");
assert_eq!(row.attached_at, "2026-05-17T00:00:00.000Z");
assert_eq!(row.attached_by, "codex");
@@ -886,7 +886,7 @@ mod tests {
"associations": [
{
"issue_id": "filigree-1234567890",
- "clarion_entity_id": "clarion:eid:0123456789abcdef0123456789abcdef",
+ "loomweave_entity_id": "loomweave:eid:0123456789abcdef0123456789abcdef",
"content_hash_at_attach": "hash-a",
"attached_at": "2026-05-17T00:00:00.000Z",
"attached_by": "codex"
@@ -897,8 +897,8 @@ mod tests {
.expect("parse SEI-keyed Filigree reverse route response");
assert_eq!(
- parsed.associations[0].clarion_entity_id,
- "clarion:eid:0123456789abcdef0123456789abcdef"
+ parsed.associations[0].loomweave_entity_id,
+ "loomweave:eid:0123456789abcdef0123456789abcdef"
);
}
@@ -916,12 +916,12 @@ mod tests {
fn builds_reverse_route_url_with_encoded_sei_key() {
let url = entity_associations_url(
"http://127.0.0.1:8766/",
- "clarion:eid:0123456789abcdef0123456789abcdef",
+ "loomweave:eid:0123456789abcdef0123456789abcdef",
);
assert_eq!(
url,
- "http://127.0.0.1:8766/api/entity-associations?entity_id=clarion%3Aeid%3A0123456789abcdef0123456789abcdef"
+ "http://127.0.0.1:8766/api/entity-associations?entity_id=loomweave%3Aeid%3A0123456789abcdef0123456789abcdef"
);
}
@@ -937,10 +937,10 @@ mod tests {
assert!(request.contains(
"GET /api/entity-associations?entity_id=python%3Afunction%3Ademo.hello HTTP/1.1"
));
- assert!(request.contains("x-filigree-actor: clarion-test"));
+ assert!(request.contains("x-filigree-actor: loomweave-test"));
assert!(request.contains("authorization: Bearer secret-token"));
- let body = r#"{"associations":[{"issue_id":"filigree-1234567890","clarion_entity_id":"python:function:demo.hello","content_hash_at_attach":"hash-a","attached_at":"2026-05-17T00:00:00.000Z","attached_by":"codex"}]}"#;
+ let body = r#"{"associations":[{"issue_id":"filigree-1234567890","loomweave_entity_id":"python:function:demo.hello","content_hash_at_attach":"hash-a","attached_at":"2026-05-17T00:00:00.000Z","attached_by":"codex"}]}"#;
write!(
stream,
"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\r\n{}",
@@ -952,7 +952,7 @@ mod tests {
let config = FiligreeConfig {
enabled: true,
base_url: format!("http://{addr}"),
- actor: "clarion-test".to_owned(),
+ actor: "loomweave-test".to_owned(),
token_env: "TEST_FILIGREE_TOKEN".to_owned(),
timeout_seconds: 1,
emit_findings: true,
@@ -995,7 +995,7 @@ mod tests {
let url = issue_detail_url("http://127.0.0.1:8542/", "clarion-51a2868c86");
assert_eq!(
url,
- "http://127.0.0.1:8542/api/loom/issues/clarion-51a2868c86"
+ "http://127.0.0.1:8542/api/weft/issues/clarion-51a2868c86"
);
}
@@ -1008,7 +1008,7 @@ mod tests {
let mut request = [0_u8; 4096];
let read = stream.read(&mut request).expect("read request");
let request = String::from_utf8_lossy(&request[..read]);
- assert!(request.contains("GET /api/loom/issues/clarion-51a2868c86 HTTP/1.1"));
+ assert!(request.contains("GET /api/weft/issues/clarion-51a2868c86 HTTP/1.1"));
let body = r#"{"issue_id":"clarion-51a2868c86","title":"enrich","status":"proposed","priority":3}"#;
write!(
@@ -1049,7 +1049,7 @@ mod tests {
});
let client = detail_test_client(addr);
let detail = client
- .issue_detail("clarion-missing")
+ .issue_detail("loomweave-missing")
.expect("404 is Ok(None), not an error");
assert!(detail.is_none(), "404 degrades to None: {detail:?}");
handle.join().expect("server thread");
@@ -1068,11 +1068,11 @@ mod tests {
request.contains("POST /api/v1/scan-results HTTP/1.1"),
"request line: {request}"
);
- assert!(request.contains("x-filigree-actor: clarion-test"));
+ assert!(request.contains("x-filigree-actor: loomweave-test"));
assert!(request.contains("authorization: Bearer secret-token"));
// The wire body carries the mapped severity, not the internal one.
assert!(
- request.contains("\"scan_source\":\"clarion\""),
+ request.contains("\"scan_source\":\"loomweave\""),
"body: {request}"
);
assert!(
@@ -1096,7 +1096,7 @@ mod tests {
let config = FiligreeConfig {
enabled: true,
base_url: format!("http://{addr}"),
- actor: "clarion-test".to_owned(),
+ actor: "loomweave-test".to_owned(),
token_env: "TEST_FILIGREE_TOKEN".to_owned(),
timeout_seconds: 1,
emit_findings: true,
@@ -1110,7 +1110,7 @@ mod tests {
let row = crate::scan_results::FindingForEmit {
id: "core:finding:run-1:circular".to_owned(),
- rule_id: "CLA-PY-STRUCTURE-001".to_owned(),
+ rule_id: "LMWV-PY-STRUCTURE-001".to_owned(),
kind: "defect".to_owned(),
severity: "WARN".to_owned(),
confidence: Some(0.9),
@@ -1182,7 +1182,7 @@ mod tests {
}
#[test]
- fn parses_loom_findings_list_envelope() {
+ fn parses_weft_findings_list_envelope() {
let resp = parse_wardline_findings_response(
r#"{"items":[
{"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open",
@@ -1209,8 +1209,8 @@ mod tests {
}
#[test]
- fn parses_loom_files_list_envelope() {
- let resp = parse_loom_files_response(
+ fn parses_weft_files_list_envelope() {
+ let resp = parse_weft_files_response(
r#"{"items":[
{"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"},
{"file_id":"file-10","path":"src/demo_helpers.py","language":"python","file_type":"source"}
@@ -1223,14 +1223,14 @@ mod tests {
}
#[test]
- fn builds_loom_url_builders_with_encoding() {
+ fn builds_weft_url_builders_with_encoding() {
assert_eq!(
- loom_files_url("http://127.0.0.1:8542/", "wardline", "src/demo.py"),
- "http://127.0.0.1:8542/api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py"
+ weft_files_url("http://127.0.0.1:8542/", "wardline", "src/demo.py"),
+ "http://127.0.0.1:8542/api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py"
);
assert_eq!(
- loom_findings_url("http://127.0.0.1:8542/", "wardline", "file-9"),
- "http://127.0.0.1:8542/api/loom/findings?scan_source=wardline&file_id=file-9"
+ weft_findings_url("http://127.0.0.1:8542/", "wardline", "file-9"),
+ "http://127.0.0.1:8542/api/weft/findings?scan_source=wardline&file_id=file-9"
);
}
@@ -1239,14 +1239,14 @@ mod tests {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind test server");
let addr = listener.local_addr().expect("local addr");
let handle = std::thread::spawn(move || {
- // Hop 1: GET /api/loom/files — path_prefix matches two files; the
+ // Hop 1: GET /api/weft/files — path_prefix matches two files; the
// exact-path filter must pick file-9, not the helpers file.
let (mut s1, _) = listener.accept().expect("accept files");
let mut buf = [0_u8; 4096];
let n = s1.read(&mut buf).expect("read files req");
let req = String::from_utf8_lossy(&buf[..n]);
assert!(req.contains(
- "GET /api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1"
+ "GET /api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1"
));
let body = r#"{"items":[{"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"},{"file_id":"file-10","path":"src/demo.py.bak","language":"python","file_type":"source"}],"has_more":false}"#;
// connection: close forces reqwest to open a fresh TCP connection for
@@ -1261,12 +1261,12 @@ mod tests {
)
.unwrap();
- // Hop 2: GET /api/loom/findings for file-9.
+ // Hop 2: GET /api/weft/findings for file-9.
let (mut s2, _) = listener.accept().expect("accept findings");
let n = s2.read(&mut buf).expect("read findings req");
let req = String::from_utf8_lossy(&buf[..n]);
assert!(
- req.contains("GET /api/loom/findings?scan_source=wardline&file_id=file-9 HTTP/1.1")
+ req.contains("GET /api/weft/findings?scan_source=wardline&file_id=file-9 HTTP/1.1")
);
let body = r#"{"items":[{"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open","scan_source":"wardline","rule_id":"WLN-TAINT-001","message":"sink","suggestion":"","scan_run_id":"r-1","line_start":12,"line_end":12,"fingerprint":"fp","issue_id":null,"seen_count":1,"metadata":{"wardline":{"qualname":"demo.Foo.bar"}},"data_warnings":[]}],"has_more":false}"#;
write!(
@@ -1283,7 +1283,7 @@ mod tests {
let config = FiligreeConfig {
enabled: true,
base_url: format!("http://{addr}"),
- actor: "clarion-test".to_owned(),
+ actor: "loomweave-test".to_owned(),
token_env: "TEST_FILIGREE_TOKEN".to_owned(),
timeout_seconds: 5,
emit_findings: true,
@@ -1315,7 +1315,7 @@ mod tests {
let n = s1.read(&mut buf).expect("read files req");
let req = String::from_utf8_lossy(&buf[..n]);
assert!(req.contains(
- "GET /api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1"
+ "GET /api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1"
));
// Return a page that omits the target path with has_more:true.
let body = r#"{"items":[{"file_id":"file-1","path":"src/demo_other.py","language":"python","file_type":"source"}],"has_more":true}"#;
@@ -1331,7 +1331,7 @@ mod tests {
let config = FiligreeConfig {
enabled: true,
base_url: format!("http://{addr}"),
- actor: "clarion-test".to_owned(),
+ actor: "loomweave-test".to_owned(),
token_env: "TEST_FILIGREE_TOKEN".to_owned(),
timeout_seconds: 5,
emit_findings: true,
@@ -1364,7 +1364,7 @@ mod tests {
let n = s1.read(&mut buf).expect("read files req");
let req = String::from_utf8_lossy(&buf[..n]);
assert!(req.contains(
- "GET /api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1"
+ "GET /api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1"
));
let body = r#"{"items":[{"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"}],"has_more":false}"#;
write!(
@@ -1380,7 +1380,7 @@ mod tests {
let n = s2.read(&mut buf).expect("read findings req");
let req = String::from_utf8_lossy(&buf[..n]);
assert!(
- req.contains("GET /api/loom/findings?scan_source=wardline&file_id=file-9 HTTP/1.1")
+ req.contains("GET /api/weft/findings?scan_source=wardline&file_id=file-9 HTTP/1.1")
);
let body = r#"{"items":[{"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open","scan_source":"wardline","rule_id":"WLN-TAINT-001","message":"sink","suggestion":"","scan_run_id":"r-1","line_start":12,"line_end":12,"fingerprint":"fp","issue_id":null,"seen_count":1,"metadata":{"wardline":{"qualname":"demo.Foo.bar"}},"data_warnings":[]}],"has_more":true}"#;
write!(
@@ -1394,7 +1394,7 @@ mod tests {
let config = FiligreeConfig {
enabled: true,
base_url: format!("http://{addr}"),
- actor: "clarion-test".to_owned(),
+ actor: "loomweave-test".to_owned(),
token_env: "TEST_FILIGREE_TOKEN".to_owned(),
timeout_seconds: 5,
emit_findings: true,
@@ -1415,7 +1415,7 @@ mod tests {
let config = FiligreeConfig {
enabled: true,
base_url: format!("http://{addr}"),
- actor: "clarion-test".to_owned(),
+ actor: "loomweave-test".to_owned(),
token_env: "TEST_FILIGREE_TOKEN".to_owned(),
timeout_seconds: 1,
emit_findings: true,
diff --git a/crates/clarion-federation/src/filigree_url.rs b/crates/loomweave-federation/src/filigree_url.rs
similarity index 94%
rename from crates/clarion-federation/src/filigree_url.rs
rename to crates/loomweave-federation/src/filigree_url.rs
index a8e90cbb..f7d208c2 100644
--- a/crates/clarion-federation/src/filigree_url.rs
+++ b/crates/loomweave-federation/src/filigree_url.rs
@@ -9,9 +9,9 @@
//! - `filigree/src/filigree/ephemeral.py::{write,read}_port_file`
//! - `filigree/src/filigree/scanner_callback.py::resolve_scanner_api_url_with_source`
//!
-//! Federation discipline (`docs/suite/loom.md` §5): this is enrich-only
-//! connection discovery. Clarion stays solo-useful — when no live port file is
-//! present (or Filigree is disabled) Clarion falls back to its *own* configured
+//! Federation discipline (`docs/suite/weft.md` §5): this is enrich-only
+//! connection discovery. Loomweave stays solo-useful — when no live port file is
+//! present (or Filigree is disabled) Loomweave falls back to its *own* configured
//! `base_url`, never to a Filigree-internal default (copying Filigree's
//! `DEFAULT_PORT` would be a silent cross-product coupling). Reading the port
//! file is fail-soft: any missing/corrupt/out-of-range content degrades to the
@@ -34,17 +34,17 @@ use crate::config::FiligreeConfig;
pub const SOURCE_DISABLED: &str = "disabled";
/// The live ethereal port published by Filigree's running dashboard.
pub const SOURCE_EPHEMERAL_PORT: &str = ".filigree/ephemeral.port";
-/// Clarion's own configured `integrations.filigree.base_url`.
+/// Loomweave's own configured `integrations.filigree.base_url`.
pub const SOURCE_CONFIG: &str = "config";
-/// The outcome of resolving where Clarion should reach Filigree's read API.
+/// The outcome of resolving where Loomweave should reach Filigree's read API.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct FiligreeUrlResolution {
/// Whether the Filigree integration is enabled in config at all.
pub enabled: bool,
/// The statically configured base URL (`integrations.filigree.base_url`).
pub configured_url: String,
- /// The URL Clarion will actually call. `None` only when disabled.
+ /// The URL Loomweave will actually call. `None` only when disabled.
pub resolved_url: Option,
/// Which input produced [`Self::resolved_url`]; one of the `SOURCE_*` labels.
pub source: &'static str,
diff --git a/crates/clarion-federation/src/lib.rs b/crates/loomweave-federation/src/lib.rs
similarity index 100%
rename from crates/clarion-federation/src/lib.rs
rename to crates/loomweave-federation/src/lib.rs
diff --git a/crates/clarion-federation/src/scan_results.rs b/crates/loomweave-federation/src/scan_results.rs
similarity index 85%
rename from crates/clarion-federation/src/scan_results.rs
rename to crates/loomweave-federation/src/scan_results.rs
index 667bf10c..6910d684 100644
--- a/crates/clarion-federation/src/scan_results.rs
+++ b/crates/loomweave-federation/src/scan_results.rs
@@ -1,23 +1,23 @@
//! Filigree-native scan-results emission (WP9-B, REQ-FINDING-03).
//!
-//! Maps Clarion's persisted findings onto Filigree's `POST /api/v1/scan-results`
+//! Maps Loomweave's persisted findings onto Filigree's `POST /api/v1/scan-results`
//! intake schema (ADR-004 + detailed-design §7) and models the response. This
//! module is pure — request building and response parsing only; the HTTP POST
//! lives on [`crate::filigree::FiligreeHttpClient::post_scan_results`].
//!
-//! Emission is enrich-only: a one-way Clarion→Filigree push that adds no
-//! Filigree-side routes and never gates Clarion's own semantics. Clarion's
-//! richer fields nest under `metadata.clarion.*` so Filigree's silent
+//! Emission is enrich-only: a one-way Loomweave→Filigree push that adds no
+//! Filigree-side routes and never gates Loomweave's own semantics. Loomweave's
+//! richer fields nest under `metadata.loomweave.*` so Filigree's silent
//! top-level-key drop (verified against the live intake) cannot lose them.
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value, json};
-/// The `scan_source` Clarion stamps on every emitted finding. Filigree's dedup
+/// The `scan_source` Loomweave stamps on every emitted finding. Filigree's dedup
/// key includes `scan_source`, so this is stable across runs.
-pub const CLARION_SCAN_SOURCE: &str = "clarion";
+pub const LOOMWEAVE_SCAN_SOURCE: &str = "loomweave";
-/// Federation-owned Clarion finding projection for Filigree scan-results
+/// Federation-owned Loomweave finding projection for Filigree scan-results
/// emission. Storage maps persistence rows into this DTO at the caller
/// boundary so this contract layer does not depend on `SQLite` row shape.
#[derive(Debug, Clone, PartialEq)]
@@ -38,11 +38,11 @@ pub struct FindingForEmit {
pub source_line_end: Option,
}
-/// Map Clarion's internal severity vocabulary (`INFO` | `WARN` | `ERROR` |
+/// Map Loomweave's internal severity vocabulary (`INFO` | `WARN` | `ERROR` |
/// `CRITICAL` | `NONE`) to Filigree's wire vocabulary (detailed-design §7
/// table). Anything unrecognised — including `NONE` (facts) and `INFO` — maps
/// to `info`, mirroring the coercion Filigree applies server-side, except done
-/// here so the original survives in `metadata.clarion.internal_severity`.
+/// here so the original survives in `metadata.loomweave.internal_severity`.
///
/// This mapping is load-bearing: a live probe confirmed Filigree coerces an
/// unmapped uppercase `WARN` to `info` (with a response warning), so emitting
@@ -58,11 +58,11 @@ pub fn severity_to_wire(internal: &str) -> &'static str {
}
}
-/// Knobs the emitter sets per `clarion analyze` invocation. `create_observations`
-/// is always `false` (Clarion emits findings, not observations).
+/// Knobs the emitter sets per `loomweave analyze` invocation. `create_observations`
+/// is always `false` (Loomweave emits findings, not observations).
#[derive(Debug, Clone)]
pub struct EmitOptions {
- /// Filigree's `scan_run_id`; Clarion passes its `run_id` here. An unknown
+ /// Filigree's `scan_run_id`; Loomweave passes its `run_id` here. An unknown
/// id is tolerated by Filigree (it warns and proceeds), so this carries the
/// REQ-FINDING-05 wire shape without a pre-create handshake.
pub scan_run_id: Option,
@@ -76,7 +76,7 @@ pub struct EmitOptions {
/// rejects path-less findings, so when this is set such a finding emits
/// against this stand-in path (the project root, mirroring the
/// `core:project:*` finding anchor) and carries
- /// `metadata.clarion.synthetic_anchor=true` so a consumer knows the path is a
+ /// `metadata.loomweave.synthetic_anchor=true` so a consumer knows the path is a
/// placeholder for a non-file entity, not the finding's real location. When
/// `None`, path-less findings are skipped (`skipped_no_path`) as before.
pub default_path: Option,
@@ -123,7 +123,7 @@ pub fn prepare_batch(rows: &[FindingForEmit], opts: &EmitOptions) -> PreparedBat
let emitted = findings.len();
PreparedBatch {
request: ScanResultsRequest {
- scan_source: CLARION_SCAN_SOURCE.to_owned(),
+ scan_source: LOOMWEAVE_SCAN_SOURCE.to_owned(),
scan_run_id: opts.scan_run_id.clone(),
mark_unseen: opts.mark_unseen,
create_observations: false,
@@ -142,7 +142,7 @@ pub fn prepare_batch(rows: &[FindingForEmit], opts: &EmitOptions) -> PreparedBat
/// `default_path` is the [`EmitOptions::default_path`] fallback: when the anchor
/// entity has no `source_file_path` (a synthetic, non-file entity) but a fallback
/// is supplied, the finding emits against it and is flagged
-/// `metadata.clarion.synthetic_anchor=true`. A synthetic anchor never carries
+/// `metadata.loomweave.synthetic_anchor=true`. A synthetic anchor never carries
/// line numbers (the placeholder path has no meaningful position).
fn wire_finding(row: &FindingForEmit, default_path: Option<&str>) -> Option {
let row_path = row
@@ -181,8 +181,8 @@ fn wire_finding(row: &FindingForEmit, default_path: Option<&str>) -> Option Value {
let mut meta = Map::new();
meta.insert("kind".to_owned(), json!(row.kind));
@@ -193,30 +193,30 @@ fn wire_metadata(row: &FindingForEmit, synthetic_anchor: bool) -> Value {
meta.insert("confidence_basis".to_owned(), json!(basis));
}
- let mut clarion = Map::new();
- clarion.insert("entity_id".to_owned(), json!(row.entity_id));
- clarion.insert(
+ let mut loomweave = Map::new();
+ loomweave.insert("entity_id".to_owned(), json!(row.entity_id));
+ loomweave.insert(
"related_entities".to_owned(),
json_array_or_empty(&row.related_entities_json),
);
- clarion.insert(
+ loomweave.insert(
"supports".to_owned(),
json_array_or_empty(&row.supports_json),
);
- clarion.insert(
+ loomweave.insert(
"supported_by".to_owned(),
json_array_or_empty(&row.supported_by_json),
);
// Lossless round-trip: the wire `severity` is the mapped value, so the
// internal vocabulary is preserved here for read-back.
- clarion.insert("internal_severity".to_owned(), json!(row.severity));
- clarion.insert("internal_status".to_owned(), json!("open"));
+ loomweave.insert("internal_severity".to_owned(), json!(row.severity));
+ loomweave.insert("internal_status".to_owned(), json!("open"));
// Flag the placeholder anchor so a consumer never mistakes the stand-in
// `path` (the project root) for the finding's real file location.
if synthetic_anchor {
- clarion.insert("synthetic_anchor".to_owned(), json!(true));
+ loomweave.insert("synthetic_anchor".to_owned(), json!(true));
}
- meta.insert("clarion".to_owned(), Value::Object(clarion));
+ meta.insert("loomweave".to_owned(), Value::Object(loomweave));
Value::Object(meta)
}
@@ -230,7 +230,7 @@ fn json_array_or_empty(raw: &str) -> Value {
}
/// Filigree's scan-results response. `#[serde(default)]` keeps the read
-/// forward-compatible: Filigree may add fields without breaking Clarion.
+/// forward-compatible: Filigree may add fields without breaking Loomweave.
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct ScanResultsResponse {
@@ -264,24 +264,24 @@ pub fn scan_results_url(base_url: &str) -> String {
}
/// The retention-sweep URL for a Filigree base URL (REQ-FINDING-06,
-/// `--prune-unseen`). This is a **loom-generation** route (`/api/loom/…`),
+/// `--prune-unseen`). This is a **weft-generation** route (`/api/weft/…`),
/// unlike the classic `/api/v1/scan-results` emission intake — do not derive it
/// from [`scan_results_url`]. Verified against Filigree's own route handler and
/// API tests.
#[must_use]
pub fn clean_stale_url(base_url: &str) -> String {
format!(
- "{}/api/loom/findings/clean-stale",
+ "{}/api/weft/findings/clean-stale",
base_url.trim_end_matches('/')
)
}
-/// The `POST /api/loom/findings/clean-stale` request body (REQ-FINDING-06).
+/// The `POST /api/weft/findings/clean-stale` request body (REQ-FINDING-06).
/// Filigree **soft-archives** `unseen_in_latest` findings older than
/// `older_than_days`, scoped to `scan_source`, moving them to `fixed` status
/// (they auto-reopen if a later scan re-detects them — see Filigree ADR-015).
/// `scan_source` is required server-side as an accident-guard so a caller
-/// cannot sweep every tool's findings; Clarion always sends `"clarion"`.
+/// cannot sweep every tool's findings; Loomweave always sends `"loomweave"`.
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct CleanStaleRequest {
pub scan_source: String,
@@ -316,7 +316,7 @@ mod tests {
fn defect_row() -> FindingForEmit {
FindingForEmit {
id: "core:finding:run-1:circular".to_owned(),
- rule_id: "CLA-PY-STRUCTURE-001".to_owned(),
+ rule_id: "LMWV-PY-STRUCTURE-001".to_owned(),
kind: "defect".to_owned(),
severity: "WARN".to_owned(),
confidence: Some(0.95),
@@ -344,11 +344,11 @@ mod tests {
}
#[test]
- fn wire_finding_carries_mapped_severity_and_nested_clarion_metadata() {
+ fn wire_finding_carries_mapped_severity_and_nested_loomweave_metadata() {
let finding = wire_finding(&defect_row(), None).expect("path present");
assert_eq!(finding["path"], json!("src/auth/tokens.py"));
- assert_eq!(finding["rule_id"], json!("CLA-PY-STRUCTURE-001"));
+ assert_eq!(finding["rule_id"], json!("LMWV-PY-STRUCTURE-001"));
assert_eq!(finding["message"], json!("Circular import detected"));
// Internal WARN maps to wire medium...
assert_eq!(finding["severity"], json!("medium"));
@@ -360,20 +360,20 @@ mod tests {
assert_eq!(meta["confidence"], json!(0.95));
assert_eq!(meta["confidence_basis"], json!("ast_match"));
- let clarion = &meta["clarion"];
+ let loomweave = &meta["loomweave"];
assert_eq!(
- clarion["entity_id"],
+ loomweave["entity_id"],
json!("python:class:auth.tokens::TokenManager")
);
assert_eq!(
- clarion["related_entities"],
+ loomweave["related_entities"],
json!(["python:class:auth.sessions::SessionStore"])
);
- assert_eq!(clarion["supports"], json!([]));
- assert_eq!(clarion["supported_by"], json!([]));
- // ...while the internal value round-trips under clarion.*.
- assert_eq!(clarion["internal_severity"], json!("WARN"));
- assert_eq!(clarion["internal_status"], json!("open"));
+ assert_eq!(loomweave["supports"], json!([]));
+ assert_eq!(loomweave["supported_by"], json!([]));
+ // ...while the internal value round-trips under loomweave.*.
+ assert_eq!(loomweave["internal_severity"], json!("WARN"));
+ assert_eq!(loomweave["internal_status"], json!("open"));
}
#[test]
@@ -396,7 +396,7 @@ mod tests {
meta.get("confidence_basis").is_none(),
"confidence_basis omitted: {meta}"
);
- assert_eq!(meta["clarion"]["internal_severity"], json!("NONE"));
+ assert_eq!(meta["loomweave"]["internal_severity"], json!("NONE"));
}
#[test]
@@ -424,7 +424,7 @@ mod tests {
let finding = wire_finding(&row, Some("/repo/root")).expect("emits via default path");
assert_eq!(finding["path"], json!("/repo/root"));
assert_eq!(
- finding["metadata"]["clarion"]["synthetic_anchor"],
+ finding["metadata"]["loomweave"]["synthetic_anchor"],
json!(true)
);
assert!(
@@ -436,7 +436,7 @@ mod tests {
let finding = wire_finding(&defect_row(), Some("/repo/root")).expect("path present");
assert_eq!(finding["path"], json!("src/auth/tokens.py"));
assert!(
- finding["metadata"]["clarion"]
+ finding["metadata"]["loomweave"]
.get("synthetic_anchor")
.is_none(),
"real-path finding is not a synthetic anchor: {finding}"
@@ -454,7 +454,7 @@ mod tests {
row.related_entities_json = "not json".to_owned();
let finding = wire_finding(&row, None).expect("path present");
assert_eq!(
- finding["metadata"]["clarion"]["related_entities"],
+ finding["metadata"]["loomweave"]["related_entities"],
json!([])
);
}
@@ -480,7 +480,7 @@ mod tests {
assert_eq!(batch.emitted, 1);
assert_eq!(batch.skipped_no_path, 1);
assert_eq!(batch.request.findings.len(), 1);
- assert_eq!(batch.request.scan_source, "clarion");
+ assert_eq!(batch.request.scan_source, "loomweave");
assert_eq!(batch.request.scan_run_id.as_deref(), Some("run-1"));
assert!(batch.request.mark_unseen);
assert!(batch.request.complete_scan_run);
@@ -500,7 +500,7 @@ mod tests {
);
let value = serde_json::to_value(&batch.request).expect("serialize request");
- assert_eq!(value["scan_source"], json!("clarion"));
+ assert_eq!(value["scan_source"], json!("loomweave"));
assert_eq!(value["scan_run_id"], json!("run-1"));
assert_eq!(value["mark_unseen"], json!(true));
assert_eq!(value["create_observations"], json!(false));
@@ -578,41 +578,41 @@ mod tests {
}
#[test]
- fn clean_stale_url_targets_the_loom_route() {
- // Prune is a loom-generation route, distinct from the classic
+ fn clean_stale_url_targets_the_weft_route() {
+ // Prune is a weft-generation route, distinct from the classic
// /api/v1 emission intake.
assert_eq!(
clean_stale_url("http://127.0.0.1:8542/"),
- "http://127.0.0.1:8542/api/loom/findings/clean-stale"
+ "http://127.0.0.1:8542/api/weft/findings/clean-stale"
);
assert_eq!(
clean_stale_url("http://127.0.0.1:8542"),
- "http://127.0.0.1:8542/api/loom/findings/clean-stale"
+ "http://127.0.0.1:8542/api/weft/findings/clean-stale"
);
}
#[test]
fn clean_stale_request_serializes_to_filigree_wire_shape() {
let request = CleanStaleRequest {
- scan_source: CLARION_SCAN_SOURCE.to_owned(),
+ scan_source: LOOMWEAVE_SCAN_SOURCE.to_owned(),
older_than_days: 30,
- actor: "clarion-mcp".to_owned(),
+ actor: "loomweave-mcp".to_owned(),
};
let value = serde_json::to_value(&request).expect("serialize clean-stale request");
- assert_eq!(value["scan_source"], json!("clarion"));
+ assert_eq!(value["scan_source"], json!("loomweave"));
assert_eq!(value["older_than_days"], json!(30));
- assert_eq!(value["actor"], json!("clarion-mcp"));
+ assert_eq!(value["actor"], json!("loomweave-mcp"));
}
#[test]
fn parses_clean_stale_response_shape() {
// Pinned to Filigree's clean-stale handler response.
let response = parse_clean_stale_response(
- r#"{"findings_fixed": 4, "scan_source": "clarion", "older_than_days": 30}"#,
+ r#"{"findings_fixed": 4, "scan_source": "loomweave", "older_than_days": 30}"#,
)
.expect("parse clean-stale response");
assert_eq!(response.findings_fixed, 4);
- assert_eq!(response.scan_source, "clarion");
+ assert_eq!(response.scan_source, "loomweave");
assert_eq!(response.older_than_days, 30);
}
diff --git a/crates/clarion-mcp/Cargo.toml b/crates/loomweave-mcp/Cargo.toml
similarity index 71%
rename from crates/clarion-mcp/Cargo.toml
rename to crates/loomweave-mcp/Cargo.toml
index 9955ff29..457cedcd 100644
--- a/crates/clarion-mcp/Cargo.toml
+++ b/crates/loomweave-mcp/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "clarion-mcp"
+name = "loomweave-mcp"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -12,9 +12,9 @@ workspace = true
[dependencies]
async-trait.workspace = true
blake3.workspace = true
-clarion-core = { path = "../clarion-core", version = "1.3.0" }
-clarion-federation = { path = "../clarion-federation", version = "1.3.0" }
-clarion-storage = { path = "../clarion-storage", version = "1.3.0" }
+loomweave-core = { path = "../loomweave-core", version = "1.0.0" }
+loomweave-federation = { path = "../loomweave-federation", version = "1.0.0" }
+loomweave-storage = { path = "../loomweave-storage", version = "1.0.0" }
reqwest.workspace = true
rusqlite.workspace = true
serde.workspace = true
diff --git a/crates/clarion-mcp/assets/skills/clarion-workflow/SKILL.md b/crates/loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md
similarity index 90%
rename from crates/clarion-mcp/assets/skills/clarion-workflow/SKILL.md
rename to crates/loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md
index db6925b6..1b074574 100644
--- a/crates/clarion-mcp/assets/skills/clarion-workflow/SKILL.md
+++ b/crates/loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md
@@ -1,21 +1,21 @@
---
-name: clarion-workflow
+name: loomweave-workflow
description: >
Use when orienting in an unfamiliar or large codebase and you want to avoid
re-reading or grepping the whole source tree: answering "what calls X",
"where is X defined", "what does X depend on", "what subsystem is X in", or
- "find the function/class/module that does Y". Applies whenever a Clarion
- code-archaeology MCP server (clarion serve / mcp__clarion__* tools) is
+ "find the function/class/module that does Y". Applies whenever a Loomweave
+ code-archaeology MCP server (loomweave serve / mcp__loomweave__* tools) is
available for the project.
---
-# Clarion Workflow
+# Loomweave Workflow
## Overview
-Clarion pre-extracts a codebase into a queryable map — entities (functions,
+Loomweave pre-extracts a codebase into a queryable map — entities (functions,
classes, modules, files), the call/reference/import edges between them, and
-subsystem clusters — and serves it over MCP. **Ask Clarion instead of
+subsystem clusters — and serves it over MCP. **Ask Loomweave instead of
re-exploring the tree.** One `find_entity` + one `callers_of` answers "what
calls this?" without reading a single file.
@@ -26,7 +26,7 @@ calls this?" without reading a single file.
- You need a function's neighborhood, execution paths, or which subsystem it belongs to.
**Not for:** editing code, reading exact implementation bodies (use `summary` or
-read the file once you have its path), or codebases with no `.clarion/` index.
+read the file once you have its path), or codebases with no `.loomweave/` index.
## Entity IDs — the model
@@ -44,10 +44,10 @@ They are not interchangeable:
- **`id`** is the entity's *locator* — a mutable address. It changes when the
code is renamed or moved, and it's the right thing to feed into the next
- Clarion tool call (above).
+ Loomweave tool call (above).
- **`sei`** is the entity's *durable, stable identity*. It survives renames and
moves. **When you record a cross-tool binding** — e.g. attaching a Filigree
- issue to a Clarion entity — **bind on the `sei`, not the `id`.** A binding
+ issue to a Loomweave entity — **bind on the `sei`, not the `id`.** A binding
keyed on the mutable `id` silently breaks the first time the entity moves.
`sei` is `null` when the index predates SEI support or the entity has no binding
@@ -99,7 +99,7 @@ re-reading each path element. `truncated`/`truncation_reason` report `edge-cap`
## Catalogue tools — inspection · faceted search · shortcuts
-Beyond navigation, Clarion serves a **stateless catalogue** of read tools. All
+Beyond navigation, Loomweave serves a **stateless catalogue** of read tools. All
of them: take explicit ids/scopes (no cursor/session — there is no `goto`/`back`
state to manage); **paginate** (`limit`/`offset`, with a `page` block reporting
`total`/`returned`/`truncated` — no silent caps); carry `sei` on every entity
@@ -151,14 +151,14 @@ honest-empty unless a plugin emits those tags. Likewise `high_churn` and
`index_diff` for repo-level freshness).
`search_semantic` is also in the catalogue. It is opt-in under
-`semantic_search:`; when enabled, `clarion analyze` populates the git-ignored
-`.clarion/embeddings.db` sidecar and the query path filters stale vectors by
+`semantic_search:`; when enabled, `loomweave analyze` populates the git-ignored
+`.loomweave/embeddings.db` sidecar and the query path filters stale vectors by
content hash.
> Not in this catalogue: `emit_observation` as a general-purpose write surface.
**Guidance authoring has an operator boundary.** Operators can manage sheets via
-`clarion guidance create/edit/show/list/delete/promote` (plus `export`/`import`
+`loomweave guidance create/edit/show/list/delete/promote` (plus `export`/`import`
for team sharing). Agents may call `propose_guidance` to create a Filigree
observation, but that proposal is inert until an operator promotes it through
`promote_guidance` or the CLI. Promoted sheets reach you through `guidance_for`
@@ -192,10 +192,10 @@ and are composed into `summary` prompts with a real guidance fingerprint.
## Launch
-`clarion serve --path ` where `` contains `.clarion/clarion.db`
-(built by `clarion analyze `). In an MCP client the tools appear as
-`mcp__clarion__find_entity`, etc.
+`loomweave serve --path ` where `` contains `.loomweave/loomweave.db`
+(built by `loomweave analyze `). In an MCP client the tools appear as
+`mcp__loomweave__find_entity`, etc.
-Besides the tools, the server exposes a `clarion://context` **resource** — live
+Besides the tools, the server exposes a `loomweave://context` **resource** — live
entity/subsystem/finding counts and index freshness as JSON, a lightweight read
when you only want the numbers (`project_status` is the fuller tool-based view).
diff --git a/crates/clarion-mcp/src/analyze_runs.rs b/crates/loomweave-mcp/src/analyze_runs.rs
similarity index 94%
rename from crates/clarion-mcp/src/analyze_runs.rs
rename to crates/loomweave-mcp/src/analyze_runs.rs
index 3c9ee1fd..c9616ae7 100644
--- a/crates/clarion-mcp/src/analyze_runs.rs
+++ b/crates/loomweave-mcp/src/analyze_runs.rs
@@ -1,4 +1,4 @@
-//! In-memory registry of `clarion analyze` subprocesses launched over MCP
+//! In-memory registry of `loomweave analyze` subprocesses launched over MCP
//! (`analyze_start` / `analyze_status` / `analyze_cancel`, clarion-7e0c21558a).
//!
//! Decomposition decision: the MCP owns the subprocess and the cancel kill +
@@ -23,7 +23,7 @@ use std::sync::{Arc, Mutex};
/// One supervised analyze subprocess.
pub(crate) struct RunHandle {
- /// The `clarion analyze` child. `try_wait`/`wait` reap it; held by value so
+ /// The `loomweave analyze` child. `try_wait`/`wait` reap it; held by value so
/// the registry owns the process.
pub child: Child,
/// Process-group id (== child pid; the child is spawned as a group leader)
@@ -43,7 +43,7 @@ pub(crate) struct RunHandle {
/// surface is low-volume.
pub(crate) type RunRegistry = Arc