Skip to content

Commit 33bba20

Browse files
authored
Merge pull request #270 from neuroglyph/docs/adr-0004-content-attachments
Resolved 11 review issues across 3 rounds; 1 false positive dismissed with evidence. 537 tests passing, all CI green, CodeRabbit final review clean.
2 parents 85bf145 + a41637f commit 33bba20

21 files changed

+569
-35
lines changed

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
### Relevant ADR(s)
2020

2121
<!-- List all applicable ADR IDs. If none, explicitly state "None". -->
22-
- [ ] ADR-00XX (Worktree Independence and Materialization Architecture)
23-
- [ ] ADR-00XY (Graph-Native Content, Deterministic Materialization, and Workspace Bridge)
22+
- [ ] ADR-0002 (Worktree Independence and Materialization Architecture)
23+
- [ ] ADR-0003 (Graph-Native Content, Deterministic Materialization, and Workspace Bridge)
2424
- [ ] None
2525

2626
### Compliance Declaration

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- **ADR-0004: Content Attachments Belong in git-warp** — Decision record establishing that CAS-backed content-on-node is a git-warp substrate responsibility, not a git-mind domain concern. Aligns with Paper I's `Atom(p)` attachment formalism (#252)
13+
- **Chalk formatting for `extension list`**`formatExtensionList()` renders extension names in cyan bold, versions dimmed, `[builtin]` in yellow / `[custom]` in magenta, consistent with all other CLI commands (#265)
14+
- **Prefix collision detection**`registerExtension()` now checks incoming domain prefixes against all registered extensions and throws a descriptive error on overlap. Idempotent re-registration of the same extension name is still allowed (#264)
15+
- **Imperative views declared in extension manifests**`milestone` and `progress` views added to the roadmap manifest; `traceability`, `coverage`, and `onboarding` views added to the architecture manifest. Purely declarative — makes `extension list` show the full picture (#268)
16+
- **`git mind extension remove <name>` subcommand** — Removes a custom extension from the registry. Throws on built-in or non-existent extensions. `--json` output supported. `removeExtension()` exported from public API (#263)
17+
- **JSON Schema contracts for extension CLI output** — 4 new schemas in `docs/contracts/cli/`: `extension-list`, `extension-validate`, `extension-add`, `extension-remove`. Valid samples added to the contract test harness (#262)
18+
- **Deferred items documented in ROADMAP**#261 (ephemeral registration) and #269 (`--extension` flag) documented with rationale and recommended H2 slot
19+
20+
### Changed
21+
22+
- **Upgraded `@git-stunts/git-warp`** from v11.3.3 to v11.5.0
23+
- **`registerBuiltinExtensions()` memoized** — Module-level `builtInsLoaded` flag prevents redundant YAML file reads on repeated invocations within the same process (#266)
24+
- **Test count** — 537 tests across 28 files (was 527)
25+
826
## [3.2.0] - 2026-02-17
927

1028
### Changed

ROADMAP.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,6 +2175,20 @@ NEXUS ←── ORACLE │
21752175

21762176
These items are not assigned to a milestone yet. They'll be scheduled based on user feedback and priorities.
21772177

2178+
### Extension persistence & ephemeral loading (deferred from M12 polish)
2179+
2180+
Two issues were filed during the M12 extension polish pass and intentionally deferred:
2181+
2182+
- **#261 — Ephemeral registration: extension add doesn't persist across invocations.**
2183+
`git mind extension add <path>` registers for the current process only. The fix requires a persistence mechanism — a lockfile (`.git/git-mind/extensions.yaml`), graph-stored config, or git-config entries. Each option has different tradeoffs for portability, discoverability, and merge semantics. This also changes the CLI boot sequence for ALL commands (startup must load user extensions after built-ins), so it needs careful design.
2184+
2185+
- **#269`--extension <path>` flag for single-invocation loading.**
2186+
A workaround for #261: load an extension for one command only (`git mind view my-view --extension ./ext.yaml`). Useful for CI/CD pipelines that inject custom domain logic. Deferred because this is cleaner to design after #261's persistence exists — the flag would be "like `extension add` but ephemeral", which is only meaningful once `add` is actually persistent.
2187+
2188+
**Recommended slot:** H2 (CONTENT + MATERIALIZATION) planning. Both issues naturally fall into the extension lifecycle story — persistence is a prerequisite for the extension marketplace vision (H4). Design the persistence mechanism during H2 kickoff, implement as the first H2 deliverable so that all subsequent extension work (content system extensions, materializer extensions) benefits from proper registration.
2189+
2190+
### Other backlog items
2191+
21782192
- `git mind onboarding` as a guided walkthrough (not just a view)
21792193
- Confidence decay over time (edges rot if not refreshed)
21802194
- View composition (combine multiple views)

bin/git-mind.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Usage: git mind <command> [options]
66
*/
77

8-
import { init, link, view, list, remove, nodes, status, at, importCmd, importMarkdownCmd, exportCmd, mergeCmd, installHooks, processCommitCmd, doctor, suggest, review, diff, set, unsetCmd, extensionList, extensionValidate, extensionAdd } from '../src/cli/commands.js';
8+
import { init, link, view, list, remove, nodes, status, at, importCmd, importMarkdownCmd, exportCmd, mergeCmd, installHooks, processCommitCmd, doctor, suggest, review, diff, set, unsetCmd, extensionList, extensionValidate, extensionAdd, extensionRemove } from '../src/cli/commands.js';
99
import { parseDiffRefs, collectDiffPositionals } from '../src/diff.js';
1010
import { createContext } from '../src/context-envelope.js';
1111
import { registerBuiltinExtensions } from '../src/extension.js';
@@ -94,6 +94,8 @@ Commands:
9494
--json Output as JSON
9595
add <manifest> Load and register an extension
9696
--json Output as JSON
97+
remove <name> Unregister an extension by name
98+
--json Output as JSON
9799
98100
Edge types: implements, augments, relates-to, blocks, belongs-to,
99101
consumed-by, depends-on, documents`);
@@ -377,7 +379,7 @@ switch (command) {
377379
const extFlags = parseFlags(args.slice(2));
378380
switch (subCmd) {
379381
case 'list':
380-
await extensionList(cwd, { json: extFlags.json ?? false });
382+
extensionList(cwd, { json: extFlags.json ?? false });
381383
break;
382384
case 'validate': {
383385
const validatePath = args.slice(2).find(a => !a.startsWith('--'));
@@ -389,9 +391,14 @@ switch (command) {
389391
await extensionAdd(cwd, addPath, { json: extFlags.json ?? false });
390392
break;
391393
}
394+
case 'remove': {
395+
const removeName = args.slice(2).find(a => !a.startsWith('--'));
396+
extensionRemove(cwd, removeName, { json: extFlags.json ?? false });
397+
break;
398+
}
392399
default:
393400
console.error(`Unknown extension subcommand: ${subCmd ?? '(none)'}`);
394-
console.error('Usage: git mind extension <list|validate|add>');
401+
console.error('Usage: git mind extension <list|validate|add|remove>');
395402
process.exitCode = 1;
396403
}
397404
break;

docs/adr/ADR-0004.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# ADR-0004: Content Attachments Belong in git-warp
2+
3+
- **Status:** Accepted
4+
- **Date:** 2026-02-20
5+
- **Deciders:** Git Mind maintainers
6+
- **Related:** ADR-0002, ADR-0003, WARP Paper I (aion-paper-01), M13A VESSEL-CORE roadmap milestone
7+
- **Upstream impact:** git-warp, git-cas
8+
9+
---
10+
11+
## 1. Context
12+
13+
The M13A (VESSEL-CORE) roadmap milestone calls for a content-on-node system: the ability to attach rich content (documents, specs, narratives) to graph nodes, stored as content-addressed blobs, with full CRDT versioning and time-travel.
14+
15+
The original plan placed this system entirely in git-mind: a `ContentStore` abstraction, a `_content` property convention, `git mind content set/show` commands, storage policy engine, and provenance receipts.
16+
17+
On review, the storage primitive — attaching a content-addressed blob to a graph node — is not a domain concern. It is a graph-level operation that belongs in git-warp.
18+
19+
### 1.1 The Paper Says So
20+
21+
WARP Paper I defines a WARP graph as `(S, α, β)` where:
22+
23+
- `S` is a finite directed multigraph (the skeleton)
24+
- `α : V_S → WARP` assigns an **attachment** to every vertex
25+
- `β : E_S → WARP` assigns an **attachment** to every edge
26+
27+
Attachments are full WARP graphs or **atoms**: `Atom(p)` for some payload `p ∈ P`, where P is "the stuff we are not going to model internally (bytestrings, floats, external object IDs, ...)".
28+
29+
Currently, git-warp models nodes and edges with flat key-value properties. There is no first-class concept of an attachment payload. Content-addressed blob attachment is the concrete realization of `Atom(p)` — it gives nodes (and potentially edges) the ability to carry opaque payload data, exactly as the paper's formalism requires.
30+
31+
### 1.2 The Layering Argument
32+
33+
If content attachment lives in git-warp:
34+
35+
- **Time-travel works for free.** Content SHAs are properties; `materialize({ ceiling })` already handles property-level time-travel.
36+
- **Multi-writer merge works for free.** CRDT conflict resolution on the SHA property follows existing semantics.
37+
- **Observer scoping works for free.** Content visibility follows node visibility.
38+
- **Provenance is already tracked.** Every property mutation carries tick/writer metadata.
39+
- **Any tool on git-warp gets it.** Not just git-mind — any future consumer of the WARP graph API can attach and retrieve content.
40+
41+
If content attachment lives in git-mind, all of the above must be re-implemented or worked around at the application layer, duplicating guarantees that already exist in the substrate.
42+
43+
---
44+
45+
## 2. Decision
46+
47+
**Content attachment is a git-warp responsibility, not a git-mind responsibility.**
48+
49+
git-warp should:
50+
51+
1. **Install `git-cas` as a dependency** — providing content-addressed blob storage backed by the git object store.
52+
2. **Expose an API for attaching content to nodes** — this could be as simple as a property convention (e.g., a CAS key stored as a node property), or a dedicated method on the patch/node API. The exact API design is git-warp's decision.
53+
3. **Expose content read/retrieval** — given a node, retrieve its attached content blob.
54+
55+
git-mind's role becomes a thin CLI/UX layer:
56+
57+
- `git mind content set <node> <file>` — CLI command that calls the git-warp attachment API
58+
- `git mind content show <node>` — CLI command that reads and displays attached content
59+
- `git mind content edit <node>` — editor integration, crash recovery, conflict handling (M13B)
60+
- Storage policy (MIME thresholds, size limits) — domain-level configuration
61+
- Materialization templates that consume attached content (M14)
62+
63+
---
64+
65+
## 3. Alternatives Considered
66+
67+
### 3A: Content system entirely in git-mind (original M13A plan)
68+
69+
git-mind implements `ContentStore` over git plumbing, manages `_content` property convention, handles CAS operations directly.
70+
71+
**Rejected because:**
72+
- Duplicates CRDT/time-travel/observer guarantees already in git-warp
73+
- Other git-warp consumers cannot benefit
74+
- Thickens the git-mind layer with storage plumbing that isn't domain logic
75+
- Diverges from the paper's formalism, which places attachments at the graph level
76+
77+
### 3B: Content as plain git-warp properties (no CAS, inline values)
78+
79+
Store content directly as property values on nodes.
80+
81+
**Rejected because:**
82+
- Property values are not designed for large payloads (documents, images)
83+
- No deduplication across nodes with identical content
84+
- No streaming/chunking for large files
85+
- Misses the `Atom(p)` semantics — atoms are opaque external references, not inline data
86+
87+
---
88+
89+
## 4. Consequences
90+
91+
### For git-warp
92+
93+
- New dependency: `git-cas`
94+
- New API surface: content attachment on nodes (and potentially edges)
95+
- Closer alignment with Paper I's `(S, α, β)` formalism
96+
- git-warp's README/docs should reference the attachment concept
97+
98+
### For git-mind
99+
100+
- M13A scope shrinks significantly — git-mind provides CLI/UX, not storage
101+
- M13B (content editing UX) is unaffected — editor integration remains a git-mind concern
102+
- Existing property-based workflows are unaffected
103+
- The `_content` property convention (if that's the API shape) is documented as a git-warp concern, not a git-mind convention
104+
105+
### For the roadmap
106+
107+
- M13A splits: upstream work in git-warp (attachment API), then thin git-mind CLI layer
108+
- M14 (FORGE/materialization) benefits — the materialization pipeline reads content via the same git-warp API, no git-mind-specific content abstraction needed
109+
- M16 (CITADEL/trust) benefits — trust-scoped content visibility is just trust-scoped property visibility
110+
111+
---
112+
113+
## 5. Relationship to the Paper
114+
115+
| Paper concept | git-warp realization |
116+
|---|---|
117+
| `Atom(p)` — opaque payload | CAS blob referenced by SHA |
118+
| `α(v)` — vertex attachment | Content property on node |
119+
| `β(e)` — edge attachment | Content property on edge (future) |
120+
| `P` — set of atomic payloads | Git object store (content-addressed) |
121+
| Depth-0 attachment | A node whose attachment is a single blob |
122+
| Deeper attachments | Future: nested WARP graphs as attachments (not in scope for this decision) |
123+
124+
This ADR addresses the depth-0 case: nodes carry `Atom(p)` payloads via CAS. The full recursive attachment model (`α(v)` mapping to arbitrary WARP graphs) is a future concern — but this decision establishes the correct layering so that deeper attachments can be added later without architectural rework.
125+
126+
---
127+
128+
## 6. Open Questions (for git-warp)
129+
130+
These are implementation details for the git-warp maintainers:
131+
132+
1. **API shape:** Dedicated `node.attach(blob)` / `node.content()` methods, or a property convention like `_content: <cas-sha>`? Either works; the former is more discoverable.
133+
2. **Edge attachments:** Should edges also support content attachment from day one, or is node-only sufficient for the first iteration?
134+
3. **Integrity verification:** Should `getContent()` verify the SHA on read, or trust the object store?
135+
4. **MIME / metadata:** Should git-warp store content metadata (MIME type, size, encoding) alongside the CAS reference, or leave that to consumers like git-mind?

docs/adr/README.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Use ADRs for decisions that are hard to reverse, cross-cut multiple subsystems,
88

99
## ADR Index
1010

11-
## ADR-00XX — Worktree Independence and Materialization Architecture
12-
**Status:** Accepted
11+
## [ADR-0002](./ADR-0002.md) — Worktree Independence and Materialization Architecture
12+
**Status:** Accepted
1313
**Date:** 2026-02-15
1414

1515
### What it establishes
@@ -24,8 +24,8 @@ Defines the core separation model: **worktree-aware, never worktree-bound**.
2424

2525
---
2626

27-
## ADR-00XY — Graph-Native Content, Deterministic Materialization, and Workspace Bridge
28-
**Status:** Accepted
27+
## [ADR-0003](./ADR-0003.md) — Graph-Native Content, Deterministic Materialization, and Workspace Bridge
28+
**Status:** Accepted
2929
**Date:** 2026-02-15
3030

3131
### What it adds
@@ -41,10 +41,10 @@ Turns the separation model into an adoption-ready product path without breaking
4141

4242
---
4343

44-
## What changed from ADR-00XX to ADR-00XY
44+
## What changed from ADR-0002 to ADR-0003
4545

4646
1. **From principle to execution:**
47-
ADR-00XX defined boundaries; ADR-00XY defines how users actually work within them.
47+
ADR-0002 defined boundaries; ADR-0003 defines how users actually work within them.
4848

4949
2. **Editing UX became first-class:**
5050
The project now explicitly treats editing ergonomics as a top adoption risk.
@@ -88,6 +88,21 @@ Recommended sections:
8888

8989
---
9090

91+
## [ADR-0004](./ADR-0004.md) — Content Attachments Belong in git-warp
92+
**Status:** Accepted
93+
**Date:** 2026-02-20
94+
95+
### What it establishes
96+
- Content-on-node (CAS-backed blob attachment) is a **git-warp** responsibility, not git-mind.
97+
- git-warp should install `git-cas` and expose an API for attaching content-addressed blobs to nodes.
98+
- git-mind provides the CLI/UX layer (`content set/show/edit`) on top.
99+
- This aligns git-warp with Paper I's `(S, α, β)` formalism — nodes carrying `Atom(p)` payloads.
100+
101+
### Why it matters
102+
Prevents git-mind from duplicating CRDT, time-travel, observer, and provenance guarantees that already exist in the substrate. Makes the attachment primitive available to any git-warp consumer, not just git-mind.
103+
104+
---
105+
91106
## Quick Contribution Rules
92107

93108
- Keep ADRs concise but specific.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/extension-add.schema.json",
4+
"title": "git-mind extension add --json",
5+
"description": "Extension registration result from `git mind extension add --json`",
6+
"type": "object",
7+
"required": ["schemaVersion", "command", "name", "version", "views", "lenses"],
8+
"additionalProperties": false,
9+
"properties": {
10+
"schemaVersion": { "type": "integer", "const": 1 },
11+
"command": { "type": "string", "const": "extension-add" },
12+
"name": { "type": "string", "minLength": 1 },
13+
"version": { "type": "string", "minLength": 1 },
14+
"views": {
15+
"type": "array",
16+
"items": { "type": "string" }
17+
},
18+
"lenses": {
19+
"type": "array",
20+
"items": { "type": "string" }
21+
}
22+
}
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/extension-list.schema.json",
4+
"title": "git-mind extension list --json",
5+
"description": "Registered extensions from `git mind extension list --json`",
6+
"type": "object",
7+
"required": ["schemaVersion", "command", "extensions"],
8+
"additionalProperties": false,
9+
"properties": {
10+
"schemaVersion": { "type": "integer", "const": 1 },
11+
"command": { "type": "string", "const": "extension-list" },
12+
"extensions": {
13+
"type": "array",
14+
"items": {
15+
"type": "object",
16+
"required": ["name", "version", "builtin", "views", "lenses"],
17+
"additionalProperties": false,
18+
"properties": {
19+
"name": { "type": "string", "minLength": 1 },
20+
"version": { "type": "string", "minLength": 1 },
21+
"description": { "type": "string" },
22+
"builtin": { "type": "boolean" },
23+
"views": {
24+
"type": "array",
25+
"items": {
26+
"type": "object",
27+
"required": ["name"],
28+
"additionalProperties": false,
29+
"properties": {
30+
"name": { "type": "string", "minLength": 1 },
31+
"description": { "type": "string" },
32+
"prefixes": { "type": "array", "items": { "type": "string" } },
33+
"edgeTypes": { "type": "array", "items": { "type": "string" } },
34+
"requireBothEndpoints": { "type": "boolean" }
35+
}
36+
}
37+
},
38+
"lenses": {
39+
"type": "array",
40+
"items": { "type": "string" }
41+
}
42+
}
43+
}
44+
}
45+
}
46+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/extension-remove.schema.json",
4+
"title": "git-mind extension remove --json",
5+
"description": "Extension removal result from `git mind extension remove --json`",
6+
"type": "object",
7+
"required": ["schemaVersion", "command", "name", "version"],
8+
"additionalProperties": false,
9+
"properties": {
10+
"schemaVersion": { "type": "integer", "const": 1 },
11+
"command": { "type": "string", "const": "extension-remove" },
12+
"name": { "type": "string", "minLength": 1 },
13+
"version": { "type": "string", "minLength": 1 }
14+
}
15+
}

0 commit comments

Comments
 (0)