Skip to content

Commit b68ccba

Browse files
montfortclaude
andauthored
feat(loom): loom-0.1.0 / cli-3.24.0 — Loom M1: walking skeleton (server + UI + CLI launcher) (#240)
CHARTER-01-loom-server M1 (T1.1–T1.12). All 6 acceptance criteria verified (AILOG-2026-06-12-002 §Verification): live force graph, thread highlight, 255ms save→browser, /api/graph ≡ straymark audit, loopback-only + Host rejection, orphans/dangling in /api/stats. - straymark-loom crate (axum 0.8 + tokio + notify-debouncer-full): Spec 001 §4 API over 127.0.0.1 only; 250ms-debounced watcher → WS rebuild events; unparseable mid-save docs skipped, never fatal - core 0.2.0: Graph::thread(id, depth) — directional ancestors∪descendants (S2 semantics; deliberately NOT the connected component — siblings dim) - Web UI (Vite/TS/graphology/Sigma, 46KB gzip, rust-embed): type colors, degree sizing, FA2 (strongGravityMode keeps orphans in frame), accent thread highlight, short labels w/ full title on hover/selection, dark hover panel, live reconnecting WS, --assets-dir dev override - CLI 3.24.0: straymark loom serve — download-on-demand launcher (loom-* releases, ~/.straymark/bin cache, EXPERIMENTAL banner, offline fallback) - release-loom.yml: npm build in CI, 4-target matrix, --latest=false - Smoke-tested on the reference adopter corpus (Sentinel, 130 docs/389 edges): follow-ups R1 (filename/path reference normalization), R2 (Charters as graph nodes), R3 (visual density) recorded in the AILOG Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0f0b075 commit b68ccba

36 files changed

Lines changed: 3594 additions & 33 deletions

.github/workflows/release-loom.yml

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
name: Release Loom
2+
3+
# Loom is EXPERIMENTAL: GitHub-release-only (no crates.io publish) while the
4+
# `loom-0.x` series lasts — see experimento/README.md and plan.md §6.
5+
6+
on:
7+
push:
8+
tags:
9+
- 'loom-*'
10+
workflow_dispatch:
11+
inputs:
12+
tag:
13+
description: 'Release tag (e.g., loom-0.1.0)'
14+
required: true
15+
16+
permissions:
17+
contents: write
18+
19+
env:
20+
CARGO_TERM_COLOR: always
21+
22+
jobs:
23+
resolve-version:
24+
name: Resolve version
25+
runs-on: ubuntu-latest
26+
outputs:
27+
tag: ${{ steps.resolve.outputs.tag }}
28+
version: ${{ steps.resolve.outputs.version }}
29+
steps:
30+
- name: Resolve tag and version
31+
id: resolve
32+
shell: bash
33+
run: |
34+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
35+
TAG="${{ github.event.inputs.tag }}"
36+
else
37+
TAG="${{ github.ref_name }}"
38+
fi
39+
40+
if [[ "$TAG" != loom-* ]]; then
41+
echo "ERROR: tag '$TAG' is not a Loom release (must start with loom-)"
42+
exit 1
43+
fi
44+
45+
VERSION="${TAG#loom-}"
46+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
47+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
48+
echo "Building Loom $VERSION from tag $TAG"
49+
50+
build-loom:
51+
name: Build Loom (${{ matrix.target }})
52+
needs: [resolve-version]
53+
runs-on: ${{ matrix.os }}
54+
strategy:
55+
matrix:
56+
include:
57+
- target: x86_64-unknown-linux-gnu
58+
os: ubuntu-latest
59+
archive: tar.gz
60+
- target: x86_64-apple-darwin
61+
os: macos-latest
62+
archive: tar.gz
63+
- target: aarch64-apple-darwin
64+
os: macos-latest
65+
archive: tar.gz
66+
- target: x86_64-pc-windows-msvc
67+
os: windows-latest
68+
archive: zip
69+
70+
steps:
71+
- uses: actions/checkout@v6
72+
with:
73+
ref: ${{ needs.resolve-version.outputs.tag }}
74+
75+
- name: Install Rust toolchain
76+
uses: dtolnay/rust-toolchain@stable
77+
with:
78+
targets: ${{ matrix.target }}
79+
80+
- name: Install Node
81+
uses: actions/setup-node@v4
82+
with:
83+
node-version: 22
84+
cache: npm
85+
cache-dependency-path: experimento/web/package-lock.json
86+
87+
- name: Verify Cargo.toml version matches tag
88+
shell: bash
89+
run: |
90+
CARGO_VERSION=$(grep '^version' experimento/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
91+
TAG_VERSION="${{ needs.resolve-version.outputs.version }}"
92+
if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then
93+
echo "ERROR: experimento/Cargo.toml version ($CARGO_VERSION) does not match tag version ($TAG_VERSION)"
94+
exit 1
95+
fi
96+
97+
- name: Build frontend (embedded via rust-embed)
98+
shell: bash
99+
working-directory: experimento/web
100+
run: |
101+
npm ci
102+
npm run build
103+
test -f dist/index.html
104+
105+
- name: Build release binary
106+
run: cargo build --release --manifest-path experimento/Cargo.toml --target ${{ matrix.target }}
107+
108+
- name: Package (Unix)
109+
if: matrix.archive == 'tar.gz'
110+
shell: bash
111+
run: |
112+
BINARY=target/${{ matrix.target }}/release/straymark-loom
113+
ARCHIVE=straymark-loom-v${{ needs.resolve-version.outputs.version }}-${{ matrix.target }}.tar.gz
114+
tar czf "$ARCHIVE" -C "$(dirname "$BINARY")" "$(basename "$BINARY")"
115+
echo "ARCHIVE=$ARCHIVE" >> "$GITHUB_ENV"
116+
117+
- name: Package (Windows)
118+
if: matrix.archive == 'zip'
119+
shell: bash
120+
run: |
121+
BINARY=target/${{ matrix.target }}/release/straymark-loom.exe
122+
ARCHIVE=straymark-loom-v${{ needs.resolve-version.outputs.version }}-${{ matrix.target }}.zip
123+
cp "$BINARY" straymark-loom.exe
124+
7z a "$ARCHIVE" straymark-loom.exe
125+
echo "ARCHIVE=$ARCHIVE" >> "$GITHUB_ENV"
126+
127+
- name: Upload binary artifact
128+
uses: actions/upload-artifact@v7
129+
with:
130+
name: loom-${{ matrix.target }}
131+
path: ${{ env.ARCHIVE }}
132+
133+
upload-to-release:
134+
name: Upload binaries to release
135+
needs: [resolve-version, build-loom]
136+
runs-on: ubuntu-latest
137+
steps:
138+
- uses: actions/checkout@v6
139+
140+
- name: Download all artifacts
141+
uses: actions/download-artifact@v8
142+
with:
143+
path: release-artifacts
144+
145+
- name: Collect release files
146+
run: |
147+
mkdir -p release
148+
find release-artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) -exec cp {} release/ \;
149+
ls -la release/
150+
151+
- name: Create or update GitHub Release
152+
env:
153+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
154+
shell: bash
155+
run: |
156+
TAG="${{ needs.resolve-version.outputs.tag }}"
157+
VERSION="${{ needs.resolve-version.outputs.version }}"
158+
159+
if gh release view "$TAG" > /dev/null 2>&1; then
160+
echo "Release $TAG exists, uploading binaries..."
161+
gh release upload "$TAG" release/* --clobber
162+
else
163+
echo "Creating release $TAG..."
164+
# NOT --latest: Loom is experimental; the CLI release stays the
165+
# repo's "latest" so update flows are unaffected.
166+
gh release create "$TAG" \
167+
--title "StrayMark Loom $VERSION (EXPERIMENTAL)" \
168+
--generate-notes \
169+
--latest=false \
170+
release/*
171+
fi
172+
173+
- name: Delete previous Loom releases
174+
env:
175+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
176+
shell: bash
177+
run: |
178+
CURRENT_TAG="${{ needs.resolve-version.outputs.tag }}"
179+
gh release list --json tagName --jq '.[].tagName' | while read -r tag; do
180+
if [[ "$tag" == loom-* && "$tag" != "$CURRENT_TAG" ]]; then
181+
echo "Deleting old release $tag..."
182+
gh release delete "$tag" --yes --cleanup-tag
183+
fi
184+
done

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ yarn-error.log*
7171
package-lock.json
7272
yarn.lock
7373

74-
# Website lockfile must be committed — CI uses `npm ci` for reproducible builds.
74+
# Website and Loom lockfiles must be committed — CI uses `npm ci` for reproducible builds.
7575
!website/package-lock.json
76+
!experimento/web/package-lock.json
7677

7778
# =============================================================================
7879
# Temporary Files
@@ -122,3 +123,10 @@ secrets.json
122123
api_keys.txt
123124
api_keys.json
124125
nul
126+
127+
# =============================================================================
128+
# Loom (experimento/) — JS confined to web/, built only in CI
129+
# =============================================================================
130+
131+
experimento/web/node_modules/
132+
experimento/web/dist/

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project uses [independent versioning](README.md#versioning) for Framewo
77

88
---
99

10+
## Loom 0.1.0 / CLI 3.24.0 — Loom M1: the walking skeleton ships
11+
12+
First release of **Loom**, StrayMark's EXPERIMENTAL third component (`CHARTER-01-loom-server` M1): a loopback-only, read-only web dashboard that renders the project's document graph live in the browser.
13+
14+
### Added (Loom)
15+
16+
- **`straymark-loom 0.1.0`** (`loom-0.1.0`, GitHub-release-only): axum + tokio server that discovers and parses StrayMark documents via the shared `straymark-core` crate, builds the typed knowledge graph, and serves `GET /api/graph`, `/api/node/:id`, `/api/node/:id/thread?depth=N`, `/api/stats`, `/healthz`, and `WS /api/stream` over `127.0.0.1` only (Spec 001 §4).
17+
- **Live rebuilds**: a `notify` watcher (250ms debounce) re-parses on settled `.md` changes and pushes a `rebuild` event over the WebSocket — an open browser reflects an edit in well under 1 second (measured ~255ms).
18+
- **Web UI** (Sigma.js + graphology, embedded via rust-embed — adopters never run npm): force-directed graph colored by document type and sized by degree; selecting a node lights up its full thread (transitive in/out relationships) and dims the rest; node detail panel with metadata and body excerpt; live type legend and corpus counters.
19+
- **Security posture** (Spec 001 FR7/NFR4): binds `127.0.0.1` exclusively, rejects non-loopback `Host` headers (anti DNS-rebinding), read-only by construction.
20+
- **`release-loom.yml`**: 4-platform build matrix with the frontend compiled in CI and embedded; releases marked `--latest=false`.
21+
22+
### Added (CLI)
23+
24+
- **`straymark loom serve [path] [--port] [--no-open]`**: download-on-demand launcher — fetches the latest `loom-*` release binary for the host platform on first use (the download gate *is* the experimental opt-in boundary), caches it under `~/.straymark/bin/`, prints a loud EXPERIMENTAL banner, and launches it pointed at the project. The CLI gains no axum/tokio dependency. Falls back to the cached binary when offline.
25+
26+
### Added (core)
27+
28+
- `straymark-core 0.2.0`: `Graph::thread(id, depth)` — the connected neighborhood of a node (Spec 001 §3.3), powering `/api/node/:id/thread` and the UI's thread highlighting.
29+
30+
---
31+
1032
## CLI 3.23.1 — `straymark-core` extraction (Loom M0)
1133

1234
First milestone of the experimental **Loom** component (`experimento/`, `CHARTER-01-loom-server`): the document model and traceability graph move into a shared crate so the CLI and the upcoming Loom visualization server parse StrayMark documents with exactly the same code (`ADR-2026-06-02-001`). **No user-facing behavior changes** — the full test suite and the `straymark audit` output are byte-for-byte identical pre/post refactor.

CLAUDE.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@ straymark/
3333
│ │ └── utils.rs # Output helpers, file hashing
3434
│ ├── tests/ # Integration tests
3535
│ └── Cargo.toml
36+
├── experimento/ # Loom (EXPERIMENTAL): straymark-loom server crate
37+
│ ├── src/ # axum server + notify watcher
38+
│ ├── web/ # Sigma.js + graphology frontend (Vite/TS, built in CI only)
39+
│ ├── specs/ # SpecKit sets (001 knowledge graph, 002 architecture plan)
40+
│ └── CHARTER-01-loom-server.md
3641
├── dist/ # Framework distribution files
3742
│ ├── .straymark/ # Templates, governance, config
3843
│ ├── STRAYMARK.md # Unified governance rules
3944
│ └── dist-manifest.yml # What gets installed
4045
├── docs/ # Project documentation (EN + ES + zh-CN)
4146
├── .github/workflows/ # CI/CD
42-
│ ├── release-cli.yml # Build + release CLI binaries
47+
│ ├── release-cli.yml # Build + release CLI binaries (publishes straymark-core first)
48+
│ ├── release-loom.yml # Build + release Loom binaries (loom-* tags; npm build step; no crates.io)
4349
│ └── release-framework.yml
4450
└── README.md
4551
```
@@ -186,6 +192,15 @@ gh release view fw-X.Y.Z --json assets --jq '.assets[].name'
186192

187193
Users can now run `straymark update-framework` to get the new version.
188194

195+
## Release Workflow — Loom (EXPERIMENTAL)
196+
197+
Loom releases are GitHub-release-only (no crates.io while experimental). Tag format: `loom-X.Y.Z`.
198+
199+
1. Bump `version` in `experimento/Cargo.toml` and update `experimento/CHANGELOG.md`.
200+
2. Commit via PR (same rules as any change).
201+
3. `git tag loom-X.Y.Z && git push origin loom-X.Y.Z` — `release-loom.yml` verifies the version matches the tag, builds the frontend (`npm ci && npm run build` in `experimento/web/`, embedded via rust-embed), compiles the same 4-platform matrix as the CLI, and uploads `straymark-loom-vX.Y.Z-<target>.{tar.gz,zip}` assets. The release is marked `--latest=false` so CLI update flows are unaffected.
202+
4. Verify: `gh release view loom-X.Y.Z --json assets --jq '.assets[].name'` (4 binaries). `straymark loom serve` picks the new version automatically (version marker in `~/.straymark/bin/`).
203+
189204
## Git Workflow
190205

191206
### Rules
@@ -251,6 +266,7 @@ Users can now run `straymark update-framework` to get the new version.
251266
| `straymark followups promote FU-NNN` | Elevate an entry to a TDE document with `promoted_from_followup` traceability |
252267
| `straymark audit [path]` | Generate audit trail reports with timeline and traceability |
253268
| `straymark explore [path]` | Interactive TUI documentation browser |
269+
| `straymark loom serve [path] [--port] [--no-open]` | Launch Loom, the EXPERIMENTAL knowledge-graph visualization server (binary downloaded on demand from `loom-*` releases, cached in `~/.straymark/bin/`) |
254270
| `straymark about` | Show version and license info |
255271

256272
## Development

0 commit comments

Comments
 (0)