Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cf5144d
feat(locate): ownership sidecar and archify locate <base>..<head>
tt-a1i Sep 8, 2026
ba230ab
feat(bundle): identity-based drilldown bundles and descend-in-place v…
tt-a1i Sep 8, 2026
6c81394
docs(identity-map): decision record, references, self-map bundle, exp…
tt-a1i Sep 8, 2026
be5fd7c
chore: rebuild generated artifacts for the identity-map template change
tt-a1i Sep 8, 2026
c881670
fix(viewer): keep the parent SVG in layout while descended
tt-a1i Sep 8, 2026
1b3a916
fix(locate): preserve ownership boundaries and fail closed on incompl…
tt-a1i Sep 12, 2026
2179745
merge: integrate identity-map recovery with modular Viewer main
tt-a1i Sep 12, 2026
365fec9
chore(package): rebuild recovery archive with official Node 22
tt-a1i Sep 12, 2026
4e82271
fix(locate): deliver manifest-bound bundles and preserve renamed map …
tt-a1i Sep 12, 2026
678910f
chore(package): refresh Locate acceptance fixes archive
tt-a1i Sep 12, 2026
78df1ec
test(experiments): preserve pinned legacy counterexample replay
tt-a1i Sep 12, 2026
66576ed
fix(viewer): honor Backspace inside nested diagrams
tt-a1i Sep 12, 2026
506d87e
test: recover bounded legacy regressions and reproducible studies
tt-a1i Sep 12, 2026
dc2153b
test: capture browser storage loss and record legacy acceptance
tt-a1i Sep 12, 2026
0e38aa9
fix(locate): close handoff contracts and scope bundle listeners
tt-a1i Sep 12, 2026
d83f5ce
test(viewer): distinguish file reload from same-origin navigation
tt-a1i Sep 12, 2026
cc1b33a
test(viewer): remove storage probe that perturbs file reloads
tt-a1i Sep 12, 2026
15e9313
feat(bundle): recursive (N-depth) bundle manifests and tree validation
Cagatay342 Sep 13, 2026
a6499aa
feat(viewer): recursive descend, breadcrumb and Escape ladder
Cagatay342 Sep 13, 2026
a3dd2ce
chore(viewer): compress nested-child chrome padding (CSS only)
Cagatay342 Sep 13, 2026
5a85a36
chore: rebuild generated artifacts for PR1 (recursive bundles + desce…
Cagatay342 Sep 13, 2026
8399234
feat(viewer): cursor/pinch-anchored camera gestures and onChange
Cagatay342 Sep 13, 2026
dc0350f
feat(viewer): opt-in zoom-to-descend (dive)
Cagatay342 Sep 13, 2026
d829cab
chore: rebuild generated artifacts for PR2 (camera + dive)
Cagatay342 Sep 13, 2026
7b8a096
docs(plans): nested drilldown plan, briefs, reports, reviews and upst…
Cagatay342 Sep 13, 2026
dfb9f55
feat(dive): allow zoom-dive in Presentation; block only while a story…
Cagatay342 Sep 13, 2026
5664694
chore: rebuild generated artifacts after the dive Presentation change
Cagatay342 Sep 13, 2026
6220654
docs(skill): default whole-repository maps to recursive drilldown bun…
Cagatay342 Sep 13, 2026
3a0196a
docs(readme): fork install one-liner and default recursive usage
Cagatay342 Sep 13, 2026
f85c953
docs(readme): describe nested drilldown as the default repository map…
Cagatay342 Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ jobs:
env:
ARCHIFY_CHROME: ${{ steps.setup-chrome.outputs.chrome-path }}
ARCHIFY_CHROME_NO_SANDBOX: '1'
- name: Verify bundle navigation, message boundaries, portable Locate output and export
run: node --test --test-concurrency=2 test/drilldown-browser.test.mjs test/bundle-message-origin-browser.test.mjs test/locate-bundle-delivery.test.mjs test/locate-export-browser.test.mjs
working-directory: archify
env:
ARCHIFY_CHROME: ${{ steps.setup-chrome.outputs.chrome-path }}
ARCHIFY_CHROME_NO_SANDBOX: '1'

zip-freshness:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ node_modules/
.claude/
.lore/
/.impeccable/

# Internal planning logs (multi-MB test transcripts)
docs/plans/**/*.log
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@ All notable changes are documented here. Format loosely follows [Keep a Changelo

> Development identity: `v2.17.0-dev.1`. Not a stable release.

### Added
- **Component ownership sidecar and `archify locate`.** A `<map-stem>.ownership.json` sidecar maps every component id in a diagram to repository path globs, with an `excluded` list applied first and an optional `parent` / `child_map` link between a drilldown parent and its child. `archify locate <base>..<head> --map <map.json> --out <dir>` classifies every path in a Git range as `touched`, `uncovered`, `ambiguous`, or `excluded`, and every component as `touched`, `untouched`, or `stale`, then writes a deterministic receipt plus an annotated HTML view. It is pure computation over Git output and two validated JSON files: no model call, no inference, no map edits. Receipts carry no timestamps and no absolute paths, sort every array by code point, and pin the map and sidecar by SHA-256, so two runs over the same inputs are byte-identical. Ambiguity is reported rather than resolved — there is no implicit glob precedence — and `archify locate --lint [<rev>]` turns ambiguity into an authoring-time failure while reporting uncovered paths as an advisory. `--facts` optionally attaches import-edge observations, which never change a state and are never reconciled against authored connections. When the map itself changed inside the range, the receipt gains `mapDelta` and, for architecture maps, an attached `archify compare` artifact under `<out>/compare/`; locate's own exit code is independent of that comparison. Three-dot `A...B` is `locate/range-invalid`. The receipt is schema-validated before write; control-character paths fail as `locate/path-invalid`. Documented in `archify/references/locate.md` and `archify/schemas/locate-receipt.schema.json`; not part of the SKILL.md fast path.
- **Identity-based drilldown bundles (`archify bundle`).** One directory holds an entry diagram, up to twelve same-directory children of any of the five diagram types, and a `manifest.json` that binds them by diagram id and two SHA-256 digests: `spec_sha256` over the input JSON for the runtime handshake, `artifact_sha256` over the HTML bytes for offline validation. Depth is fixed at two levels and each diagram keeps the existing 12-node cap. `archify bundle <dir>` writes and refreshes the manifest; `--check` validates only. Ten checks cover manifest schema, levels, id and file uniqueness, embedded-manifest byte equality, drilldown resolution, node caps, second-layer marks, and ownership subset rules. Reading is descend-in-place: the parent shrinks to a breadcrumb plus a static silhouette, the child opens in a same-directory iframe on the same canvas, and `Esc` or the breadcrumb returns to the parent with its geometry and scroll position intact. While a drilldown is active, `Backspace` follows the same dismissal order as `Esc`. A child whose id or spec digest does not match, or whose handshake does not complete within 1200 ms, is never rendered: the viewer shows an explicit stale card with the expected and actual values and the repair command. `archify locate --bundle <dir>` embeds a change projection that dims untouched nodes and shows a "N FILES TOUCHED INSIDE" chip only when the count is non-zero; there are no check marks, no success colour, and no risk or merge claims. Documented in `archify/references/drilldown-bundles.md` and `archify/schemas/bundle.schema.json`.
- **N-depth drilldown bundles.** `archify bundle` no longer stops at one level of children: any diagram in the bundle — not only the entry — may declare `components[].drilldown` on one of its own components, and the manifest's `max_depth` (now `2`–`8`, one more than the deepest `diagrams[].level` present) is computed from that tree instead of always being `2`. A bundle with only direct children produces the exact same manifest as before this change — the format is additive and existing two-level bundles are unaffected byte-for-byte. The bundle is a tree, not a DAG: a diagram reachable through more than one drilldown row now fails validation (`bundle/drilldown-shared`), same as a cycle (`bundle/drilldown-cycle`) or an unreachable diagram (`bundle/orphan`); depth above 8 levels fails with `bundle/depth-exceeded`. The former two-level-only rules `bundle/drilldown-nested` and `bundle/child-mark` are replaced by the tree checks above and by `bundle/leaf-mark`, which forbids a drilldown mark only on diagrams that declare no drilldown of their own (a leaf), not on every non-entry diagram. `archify locate --bundle` and the ownership-subset check follow the same generalization: a child's ownership sidecar binds to whichever diagram its drilldown row names as `parent`, not always the entry, and inherited `excluded` globs accumulate down the full chain from the entry through every intermediate parent. A sidecar's identity is now taken from the file actually read (`x.ownership.json` → `x.json`, matched against `manifest.diagrams[]`), never from its own `map` declaration, and a walked child's `parent.map` must name the exact sidecar file it was reached from rather than any manifest-recognized parent for that component id; a malformed sidecar (a bad schema shape, or a literal JSON `null`) now fails validation with a controlled `bundle/ownership-*` code instead of throwing. The Viewer's own recursive descend follows in the next entry below.
- **Recursive Viewer descend.** The Viewer now reads a bundle's full tree, not only its first level: a child that is itself not a leaf offers the same Descend control and drilldown mark for its own components, so a diagram opens a second and a third time inside an already-descended viewer (iframe inside iframe). A parent hands its child a `subtree` of the manifest (scoped to that child's own descendants) inside the existing handshake hello, since a `file://` child cannot fetch a sibling manifest of its own; a leaf's subtree still carries the leaf's own row in `diagrams`, only `drilldowns` is empty, so it never offers a further descend. The breadcrumb is drawn once, at the root, as the full chain down to whichever level is currently deepest, aggregated one hop at a time as each level reports its own contribution to its own direct parent; only the root shows a silhouette. `Esc`/`Backspace` still close exactly one level per keystroke, from whichever level currently has focus — including the root while a grandchild is the deepest open level — and a breadcrumb rung's click ascends directly to that depth. Readiness (`data-drilldown-state="open"`, replacing the former `"level1"`) is now gated only on a successful handshake ack, never on the ~170ms transition timer, with or without reduced motion; every descend carries a session number so a delayed or superseded ack, escape, or breadcrumb update is ignored rather than corrupting the current descent. Zoom in/out/reset and the Descend control keep working at any nested depth; Route Probe, Semantic Radar, Semantic Lens, Node Finder and the Diagram Guide stay hidden while nested, as before. Documented in `viewer/README.md` "Drilldown contract" and `archify/references/drilldown-bundles.md` "Reader behavior".
- **Delta `navigation` field group.** Architecture comparison classifies a changed `drilldown` target as the new `navigation-changed` status with its own change-row marker, legend chip, review-strip entry, and colour, instead of overstating it as a semantic `changed` or misreporting it as `moved`. Component summaries gain `navigationChanged`. Existing statuses and counts are unaffected.
- **Wheel and pinch camera zoom.** The Viewer now zooms continuously between 1x and 3x around the pointer (mouse wheel, `Ctrl`+wheel trackpad pinch) or the two-finger touch pinch midpoint, in addition to the existing `+`/`-`/`0` controls and keyboard shortcuts. `viewer-camera.js` adds `zoomAt(nextScale, clientX, clientY, { snap })` — the same fixed-point math as the existing center-anchored zoom, parameterized by screen point — and keeps `zoomIn`/`zoomOut`/`+`/`-` on their existing quarter-step snap. A wheel gesture or a pinch that cannot change scale (already at the 1x floor) leaves page scrolling alone and emits `minZoomOut` (with `source`/`gestureId`) instead. Both gestures no-op in the mobile-contained wide-diagram mode; pinch pointer capture is held for the gesture's lifetime instead of released early, a drag is scoped to the pointer that started it, and a third touch during an active pinch cannot start a new drag. Every wheel tick and pinch update also emits `{phase,source,id}` through `on('gesture', cb)` so a continuing physical gesture can be told apart from a fresh one. `onChange`/`offChange` subscribe to every camera update, now with a `transitioning` flag that flips to `false` once the change has visually settled (`transitionend` or a 200ms fallback); `on`/`off` cover `minZoomOut`, `gesture` and future events. Reading Depth and the 1–3 clamp are unchanged.
- **Recursive repository maps by default.** `SKILL.md` gains a "Repository maps: recursive drilldown bundles" section: when asked to visualize a whole repository or system and its parts, the skill authors the outside view first, then recursively inspects each component and gives it its own child diagram only where real internal structure warrants one, authoring independent children in parallel and finishing with `archify bundle <dir>`; single flat maps remain the choice when the whole system honestly fits twelve nodes.
- **Opt-in zoom-triggered drilldown descend ("dive").** A new `Archify.dive` (`viewer/dive.js`) lets a reader turn manual zoom itself into drilldown navigation. Off by default (`localStorage['archify-dive']`, toggled by the `Z` shortcut or a `.diagram-nav` button shown only on a diagram that actually has a drilldown row); zooming a drilldown-capable node past scale 2.5 under manual camera control starts a visible 250ms dwell (a ring on the node plus a status line) before calling the existing `Archify.drilldown.descend`, cancelled by any camera movement or lost eligibility in between. It never fires during a semantic-camera reveal, under `prefers-reduced-motion`, in the mobile-contained mode, while another exploration surface (Route Probe, Semantic Lens, a playing guided story — Presentation itself is not blocking, it only hides chrome; Intent Trace is deliberately not blocking, since its own "active" node is just an ordinary 90ms hover preview that is true almost the entire time a real mouse hovers the node being wheel-zoomed) is active, or while a descent is already open or mid-handshake. Inside a nested child, two distinct zoom-out gestures at the 1x floor within 600ms ascend one level the same way a breadcrumb click does; after an ascend, a redive lock holds until either a fresh pointerdown/keydown or ≥400ms of gesture silence followed by a new gesture start, and separately requires the camera to have been seen back below scale 2.5 at least once since the ascend (`rearmed`) — both conditions must clear before a new dwell can start. The always-on drilldown-mark/Descend-control highlight at full camera detail (scale ≥ 1.75) is independent of the toggle and needs no new code — it is existing CSS keyed off `viewer-camera.js`'s own `data-detail-level` attribute. `viewer-camera.js` is unchanged by this feature; export, print and SVG bytes are unaffected. Documented in `viewer/README.md` "Drilldown dive contract" and `archify/references/drilldown-bundles.md` "Reader behavior".

**Pending maintainer sign-off.** Two of the above touch public contracts and are recorded as unresolved in `docs/decisions/identity-map-2026-09-09.md` until the maintainer signs them off: the optional `drilldown` field on `architecture.schema.json` components, and the Delta `navigation` field group that classifies it. Existing schema-v1 documents remain valid because the field is optional. The HTML wrapper includes the drilldown runtime even when the field is absent, so byte-identical HTML is not promised.

### Fixed
- **Locate glob subset, `ls-tree`, and drilldown stale.** `subsumes` no longer treats `**` as covering `*` / `*/*` empty segments. `listTree` uses `ls-tree -z` so unicode paths are not C-quoted. `descend()` shows the stale card when the manifest lacks the child id instead of returning false. Locate HTML honours `?theme=`; the header map path stays repo-relative. Nested bundle children hide their own toolbar and PATH/MAP/LENS chrome. Parent nodes with `files_touched_inside > 0` show a count badge on the box.
- **DSH plugin refresh.** Adapter 0.2.0 pins the current Archify development snapshot, includes the newer runtime and CLI fixes, and targets DSH 0.1.2-rc.1. Release metadata replaces the frozen 0.1.0 packaging source; the tarball uses the canonical clean-Skill stager and documents independent plugin upgrades.
- **Machine-readable CLI argument failures (#330).** `validate --json` and `deliver --json` now keep invalid or missing option values, unknown options and diagram types, unsupported option combinations, and usage errors inside one versioned failure receipt on stdout. These failures use the `arguments` stage, stable diagnostic codes, and exit status 2, while human-mode stderr behavior remains unchanged.
- **Complete artifact-check receipts (#311).** The checker now lets stdout drain before exiting, so large JSON receipts remain complete through pipes. Validation, delivery, and architecture comparison retain their original success/failure status without truncated-JSON errors.
Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Archify is a Node.js rendering and validation system for Cursor, Claude Code, Co

- **Open it and present** — five diagram types, four presets, dark/light themes, built-in brand marks, and finite motion
- **Review architecture changes before merge** — compare two validated snapshots as Before / Delta / After, with exact added, removed, changed, moved, and rerouted facts
- **Locate changes and open component details** — [locate](archify/references/locate.md) classifies Git paths against an authored ownership sidecar; [drilldown bundles](archify/references/drilldown-bundles.md) connect an overview to same-directory child diagrams
- **Every interaction stays grounded** — search nodes, optionally open revision-verified source, trace upstream/downstream authored reach and exact routes, compare roles, and play guided stories without inventing topology
- **One file, ready to trust and share** — typed JSON IR and deterministic checks produce self-contained HTML plus PNG, SVG, WebM, and 1200×630 share cards

Expand All @@ -28,7 +29,7 @@ Archify is a Node.js rendering and validation system for Cursor, Claude Code, Co
**[Project page](https://tt-a1i.github.io/archify/)** · **[Scenario guide](https://tt-a1i.github.io/archify/guide.html)** · **[Proof Lab](https://tt-a1i.github.io/archify/gallery.html)**

```bash
npx skills add tt-a1i/archify -g
npx skills add Cagatay342/archify -g
```

Using Cursor? Open the [agent-aware quick start](https://tt-a1i.github.io/archify/start.html?agent=cursor&type=architecture) for exact global and project commands.
Expand Down Expand Up @@ -98,19 +99,19 @@ Open [`examples/web-app.html`](examples/web-app.html) locally to try the complet
### 1. Install

```bash
npx skills add tt-a1i/archify -g
npx skills add Cagatay342/archify -g
```

For an explicit, non-interactive Cursor install:

```bash
npx -y skills add tt-a1i/archify --skill archify --agent cursor --global --copy --yes
npx -y skills add Cagatay342/archify --skill archify --agent cursor --global --copy --yes
```

To try without installing:

```bash
npx skills use tt-a1i/archify@archify --agent codex
npx skills use Cagatay342/archify@archify --agent codex
```

[DSH community opt-in](integrations/deepseek-harness/README.md): `dsh plugin --profile web add @tt-a1i/archify-dsh@0.1.0`
Expand All @@ -128,11 +129,11 @@ Use Archify to draw: Browser -> API -> Redis cache -> PostgreSQL fallback.
For source evidence, open a repository and ask:

```text
Analyze this repository, then use archify to create a high-level runtime architecture diagram.
Show 8–12 core components, one primary path, external dependencies, and trust boundaries.
Put supporting detail in cards instead of adding more edges.
Use archify to map this repository's architecture as a nested drilldown bundle under docs/diagrams/.
```

Archify maps the outside view first and adds children only where a component has real internal structure.

### 3. Refine in chat

Continue with focused requests such as `add Redis`, `move auth to the left`, or `highlight the rollback path`. Archify keeps the typed source available for targeted iteration.
Expand Down Expand Up @@ -248,7 +249,7 @@ Settings:
| Play a guided story / change chapter | <kbd>P</kbd> / <kbd>[</kbd> <kbd>]</kbd> |
| Enter Presentation Stage | <kbd>F</kbd> |
| Choose visual style (`S` cycles) / toggle theme / open Export | <kbd>S</kbd> / <kbd>T</kbd> / <kbd>E</kbd> |
| Zoom or reset | <kbd>+</kbd> / <kbd>-</kbd> / <kbd>0</kbd> |
| Zoom or reset | <kbd>+</kbd> / <kbd>-</kbd> / <kbd>0</kbd>/wheel/pinch/<kbd>Z</kbd>-dive |

Stable links can restore `#focus=<id>`, `#focus=<id>&reach=upstream|downstream`, `#relation=<id>`, `#route=<source>~<target>`, `#lens=<kind>~<kind>`, and `#view=<view-id>`. Reader-driven motion is finite, respects `prefers-reduced-motion`, and never enters canonical exports.

Expand Down
Loading