arkaik 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/skill/references/schema.md +368 -0
- package/dist/assets/skill/references/values.md +47 -0
- package/dist/assets/skill/scripts/validate-bundle.js +18 -0
- package/dist/assets/skill/skill.md +344 -0
- package/dist/index.js +17693 -0
- package/dist/io.js +15653 -0
- package/package.json +38 -0
- package/src/io.ts +23 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arkaik
|
|
3
|
+
version: 3.0.0
|
|
4
|
+
description: >
|
|
5
|
+
Maintain the Arkaik product graph map for {{PRODUCT_NAME}} — add, update, or
|
|
6
|
+
remove nodes and edges in the ProjectBundle JSON that describes its screens,
|
|
7
|
+
flows, data models, and API endpoints, and record every change as a journal
|
|
8
|
+
event. Use this skill whenever you create, rename, move, or delete a
|
|
9
|
+
view/screen, route, data model, or API endpoint in the codebase. Also use it
|
|
10
|
+
when changing a feature's status (idea → development → live), adding a new user
|
|
11
|
+
journey, or restructuring navigation. If your code change touches product
|
|
12
|
+
architecture, this skill applies — even if no one explicitly asked you to
|
|
13
|
+
update the map.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Arkaik Map Maintenance
|
|
17
|
+
|
|
18
|
+
You are maintaining an **Arkaik ProjectBundle** — a JSON file that describes the
|
|
19
|
+
product architecture of {{PRODUCT_NAME}} as a graph of nodes (screens, flows,
|
|
20
|
+
data models, API endpoints) and edges (relationships between them) — plus a
|
|
21
|
+
**journal**, an append-only log of typed events recording how that graph changed
|
|
22
|
+
over time.
|
|
23
|
+
|
|
24
|
+
The map lives at `{{BUNDLE_PATH}}` in the repository. If it doesn't exist at
|
|
25
|
+
that path yet, check the project root and `docs/` for any `*arkaik*.json` file.
|
|
26
|
+
If you find one elsewhere, use it where it is — don't move files without the
|
|
27
|
+
user's approval.
|
|
28
|
+
|
|
29
|
+
The journal is a sidecar file next to the snapshot at `{{JOURNAL_PATH}}` —
|
|
30
|
+
append-only, one JSON event per line (JSONL), **never inside the snapshot**. If
|
|
31
|
+
the journal doesn't exist yet, the map is a plain snapshot with no history; start
|
|
32
|
+
one the first time you make a change (see [Dual-write](#dual-write-snapshot--journal)).
|
|
33
|
+
|
|
34
|
+
> **Template parameters.** This skill is *rendered*, not copied — `arkaik init`
|
|
35
|
+
> (and `arkaik init --update`, which reads the `version` stamp in the frontmatter
|
|
36
|
+
> above to upgrade cleanly instead of blind-overwriting local edits) substitutes
|
|
37
|
+
> these per project. If you are reading a raw, unrendered copy, treat the defaults
|
|
38
|
+
> in parentheses as the values:
|
|
39
|
+
>
|
|
40
|
+
> | Parameter | Meaning | Default |
|
|
41
|
+
> |---|---|---|
|
|
42
|
+
> | `{{PRODUCT_NAME}}` | The product this map describes | the current product |
|
|
43
|
+
> | `{{PROJECT_ID}}` | Kebab-case `project.id` for this map (distinct from the display name above) | a kebab-case slug of the product name |
|
|
44
|
+
> | `{{BUNDLE_PATH}}` | Path to the snapshot | `docs/arkaik/bundle.json` |
|
|
45
|
+
> | `{{JOURNAL_PATH}}` | Path to the journal sidecar | `docs/arkaik/journal.jsonl` |
|
|
46
|
+
|
|
47
|
+
## When to Update the Map
|
|
48
|
+
|
|
49
|
+
Update the map as a side-effect of your main work whenever you:
|
|
50
|
+
|
|
51
|
+
- **Add** a new screen, page, route, component, model, or endpoint
|
|
52
|
+
- **Remove** or deprecate a feature, screen, or endpoint
|
|
53
|
+
- **Rename** a view, model, or route
|
|
54
|
+
- **Change status** of a feature (e.g., moving from `idea` to `development`)
|
|
55
|
+
- **Restructure** navigation or user flows
|
|
56
|
+
- **Add or change** API contracts
|
|
57
|
+
|
|
58
|
+
Do NOT regenerate the entire map. Make **surgical patches** — touch only the
|
|
59
|
+
nodes and edges affected by your change. This keeps diffs reviewable and avoids
|
|
60
|
+
accidental regressions.
|
|
61
|
+
|
|
62
|
+
## Dual-write: snapshot + journal
|
|
63
|
+
|
|
64
|
+
The snapshot is authoritative for **current state**; the journal is authoritative
|
|
65
|
+
for **history**. Every change is a **dual-write**: in the *same* change you
|
|
66
|
+
|
|
67
|
+
1. patch the snapshot (`{{BUNDLE_PATH}}`) surgically, **and**
|
|
68
|
+
2. append the matching event(s) to the journal (`{{JOURNAL_PATH}}`).
|
|
69
|
+
|
|
70
|
+
Appending is a one-line addition to the JSONL sidecar — structurally incapable of
|
|
71
|
+
corrupting existing history or the snapshot, and safe under concurrent edits
|
|
72
|
+
(git's `merge=union` reorders lines; consumers order by `ts`, tiebreaking by `id`).
|
|
73
|
+
|
|
74
|
+
**Never re-project the snapshot from the journal, or vice versa.** If the two ever
|
|
75
|
+
disagree, that divergence is a signal to surface, not to launder — the validator
|
|
76
|
+
below is what catches it.
|
|
77
|
+
|
|
78
|
+
### Which events to append
|
|
79
|
+
|
|
80
|
+
Each graph operation has a matching event. Append one line per operation:
|
|
81
|
+
|
|
82
|
+
| Graph operation | Journal event |
|
|
83
|
+
|---|---|
|
|
84
|
+
| Add a node | `node.created` (`node_id`, `species`, `title`) |
|
|
85
|
+
| Add an edge | `edge.added` (`edge_id`, `source_id`, `target_id`, `edge_type`) |
|
|
86
|
+
| Change a node's `status` | `node.status_changed` (`node_id`, `from`, `to`; add `platform` when a per-platform node status — an acceptance, or a not-yet-covered view — moved) |
|
|
87
|
+
| Change any other node field (rename, description, playlist…) | `node.updated` (`node_id`, `fields[]`; `from`/`to` for short scalars like `title`) |
|
|
88
|
+
| Remove a node | `node.deleted` (`node_id`) — **implies** cascade removal of its edges; do NOT also emit `edge.removed` for those |
|
|
89
|
+
| Remove an edge on its own (node stays) | `edge.removed` (`edge_id`) |
|
|
90
|
+
| Attach / detach an external reference | `ref.added` / `ref.removed` |
|
|
91
|
+
|
|
92
|
+
Other event types you may append when the change warrants it: `release.tagged`
|
|
93
|
+
(a version shipped), `idea.proposed` (an idea, before or linked to a node),
|
|
94
|
+
`request.filed` (an external ask), `ref.status_changed` (a mirrored external
|
|
95
|
+
status moved). See the [event vocabulary](#event-vocabulary) for full payloads.
|
|
96
|
+
|
|
97
|
+
### Event envelope
|
|
98
|
+
|
|
99
|
+
Every event is one JSON object on its own line with these envelope fields, plus
|
|
100
|
+
the type-specific payload flat on the object:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{ "id": "<ULID>", "ts": "<ISO 8601>", "actor": "claude-code", "type": "node.status_changed", "node_id": "V-home", "from": "development", "to": "live" }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `id` — a ULID (sortable, collision-free). If you cannot generate a real ULID,
|
|
107
|
+
any strictly increasing, unique, 26-char Crockford-base32 string works; order
|
|
108
|
+
is recovered from `ts` first, `id` only as a tiebreak.
|
|
109
|
+
- `ts` — an ISO 8601 timestamp (the moment of the change).
|
|
110
|
+
- `actor` — who wrote it; use `"claude-code"` for your own writes.
|
|
111
|
+
- Events carry **no** `project_id`; scope is the file they live in.
|
|
112
|
+
|
|
113
|
+
## How to Update
|
|
114
|
+
|
|
115
|
+
### 1. Read the current map
|
|
116
|
+
|
|
117
|
+
Always read the snapshot first. Parse it and identify the relevant nodes/edges
|
|
118
|
+
before making changes. If a journal sidecar exists, you don't need to read all of
|
|
119
|
+
it — you only ever **append**.
|
|
120
|
+
|
|
121
|
+
### 2. Decide what to change
|
|
122
|
+
|
|
123
|
+
Map your code change to graph operations (and the events they pair with):
|
|
124
|
+
|
|
125
|
+
| Code change | Graph operation | Journal event(s) |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| New screen/page | Add a `V-` view node + `displays` edges to its data models + `calls` edges to its APIs | `node.created` + `edge.added` per edge |
|
|
128
|
+
| New route/endpoint | Add an `API-` node + `queries` edges to the data models it reads/writes | `node.created` + `edge.added` per edge |
|
|
129
|
+
| New model/table | Add a `DM-` node | `node.created` |
|
|
130
|
+
| New user journey | Add a `F-` flow node with a playlist + `composes` edges to all views/sub-flows in the playlist | `node.created` + `edge.added` per edge |
|
|
131
|
+
| Screen added to a flow | Add entry to the flow's playlist + a `composes` edge | `node.updated` (playlist) + `edge.added` |
|
|
132
|
+
| Feature removed | Remove the node + all edges referencing it + remove from any playlists | `node.deleted` (edges cascade — do not emit `edge.removed`) |
|
|
133
|
+
| Status change | Update the node's `status` field | `node.status_changed` |
|
|
134
|
+
| Rename (label only) | Update the node's `title`; keep the `id` stable so edges stay intact | `node.updated` (`fields: ["title"]`, with `from`/`to`) |
|
|
135
|
+
| Rename (id must change) | Update the `id`, then repoint every edge's `source_id`/`target_id` **and** the edge `id` (`e-{source}-{target}`), plus any playlist `view_id`/`flow_id` and `root_node_id` | `node.updated` + `edge.removed`/`edge.added` for each repointed edge |
|
|
136
|
+
| Ship/add user-visible behavior on a platform | Find or create the covering `AC-` acceptance + `covers` edge to the view/flow; set `metadata.platformStatuses.<platform>` on the acceptance | `node.created` + `edge.added` (new acceptance) + `node.status_changed` (`platform`) |
|
|
137
|
+
|
|
138
|
+
### 3. Apply the change
|
|
139
|
+
|
|
140
|
+
Edit the snapshot JSON using the Edit tool for surgical changes, or Write for
|
|
141
|
+
larger restructuring. Follow these rules strictly:
|
|
142
|
+
|
|
143
|
+
**Node rules:**
|
|
144
|
+
- IDs are prefixed by species: `F-` (flow), `V-` (view), `DM-` (data-model), `API-` (api-endpoint)
|
|
145
|
+
- IDs use lowercase kebab-case after the prefix (e.g., `V-user-profile`)
|
|
146
|
+
- **IDs must be globally unique.** Derive each ID deterministically from the title,
|
|
147
|
+
then check it against every existing node ID before adding — a duplicate ID
|
|
148
|
+
breaks the entire graph render (elkjs/React Flow key nodes by ID).
|
|
149
|
+
- **Data-model IDs come in two flavors that must not collide:** conceptual models
|
|
150
|
+
use a singular `DM-<concept>` (title "Bounce" → `DM-bounce`); physical tables/views
|
|
151
|
+
use the exact identifier `DM-<table_name>` (title `bounces` → `DM-bounces`,
|
|
152
|
+
`karma_events` → `DM-karma-events`). See the schema reference for the full table.
|
|
153
|
+
- **Every node must have a non-empty `title`.** Concepts/views/flows/APIs use 2–5
|
|
154
|
+
capitalized words; physical tables/views use the exact DB identifier verbatim.
|
|
155
|
+
- Every `node.project_id` must match `project.id`
|
|
156
|
+
- `platforms` must contain at least one of: `"web"`, `"ios"`, `"android"`
|
|
157
|
+
- Flow nodes must have `metadata.playlist` with at least one entry
|
|
158
|
+
|
|
159
|
+
**Edge rules:**
|
|
160
|
+
- Edge IDs follow the pattern `e-{source_id}-{target_id}`
|
|
161
|
+
- Every `source_id` and `target_id` must reference existing node IDs
|
|
162
|
+
- Edge type semantics:
|
|
163
|
+
- `composes`: flow -> view, flow -> flow (sub-flow), view -> flow (triggers)
|
|
164
|
+
- `calls`: view -> api-endpoint, flow -> api-endpoint, api-endpoint -> api-endpoint (endpoint fan-out to internal/external APIs)
|
|
165
|
+
- `displays`: view -> data-model
|
|
166
|
+
- `queries`: api-endpoint -> data-model
|
|
167
|
+
- `covers`: acceptance -> view, acceptance -> flow
|
|
168
|
+
- Every view/flow referenced in a playlist MUST also have a `composes` edge
|
|
169
|
+
|
|
170
|
+
**Playlist rules:**
|
|
171
|
+
- Entry types: `view` (with `view_id`), `flow` (with `flow_id`), `condition` (with `label`, `if_true`, `if_false`), `junction` (with `label`, `cases`)
|
|
172
|
+
- All `view_id` / `flow_id` values must reference existing nodes
|
|
173
|
+
- No cycles — a flow cannot contain itself directly or indirectly
|
|
174
|
+
|
|
175
|
+
### 4. Append the matching journal event
|
|
176
|
+
|
|
177
|
+
In the **same change**, append one line per graph operation to `{{JOURNAL_PATH}}`
|
|
178
|
+
(see [Dual-write](#dual-write-snapshot--journal)). Create the file if it doesn't
|
|
179
|
+
exist yet. This is not optional bookkeeping — the validator in the next step
|
|
180
|
+
cross-checks the two by value and **rejects a snapshot that its journal
|
|
181
|
+
contradicts** (e.g. a status the last `node.status_changed` never reached, or a
|
|
182
|
+
node with no `node.created`).
|
|
183
|
+
|
|
184
|
+
### 5. Validate — hard gate, not optional
|
|
185
|
+
|
|
186
|
+
After **every** change (surgical patch or full generation), run the validator:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
node <skill-path>/scripts/validate-bundle.js <path-to-bundle.json>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The validator auto-discovers the `journal.jsonl` sidecar next to the bundle and
|
|
193
|
+
folds it into the check, so pointing it at the snapshot gates **both** the patch
|
|
194
|
+
and the appended event in one run.
|
|
195
|
+
|
|
196
|
+
**A non-zero exit code is a hard stop.** Do not commit, hand off, or declare the
|
|
197
|
+
task done until it exits 0 — and that includes fixing the journal, not just the
|
|
198
|
+
snapshot. The map that ships is the one the app imports, so run the validator
|
|
199
|
+
against *that* file (e.g. the seed the app loads), not just a local working copy.
|
|
200
|
+
Where possible, wire this script into the consuming repo's CI or a pre-commit hook
|
|
201
|
+
so a broken bundle cannot land regardless of who edits it.
|
|
202
|
+
|
|
203
|
+
The validator enforces the full [Validation Checklist](references/schema.md#validation-checklist).
|
|
204
|
+
Common issues it catches:
|
|
205
|
+
- Duplicate node IDs (e.g. a concept and its backing table both kebab-cased to the same `DM-` id)
|
|
206
|
+
- A node with a missing or empty `title`
|
|
207
|
+
- Forgetting to add a `composes` edge when adding a view to a playlist
|
|
208
|
+
- Stale edge IDs/references after renaming or removing a node
|
|
209
|
+
- Missing `project_id` on new nodes
|
|
210
|
+
- An invalid `view_card_variant` (the app's import throws on it)
|
|
211
|
+
|
|
212
|
+
**Snapshot ↔ journal cross-checks** (the dual-write gate). The validator compares
|
|
213
|
+
the two **by value, never by timestamp** (per-node timestamps don't exist and
|
|
214
|
+
clocks lie) and errors on any mismatch, naming both sides:
|
|
215
|
+
- The last project-level `node.status_changed.to` for a node must equal its
|
|
216
|
+
current `status`. (Platform-scoped transitions — those carrying `platform` —
|
|
217
|
+
move a per-platform node status — an acceptance, or a not-yet-covered view —
|
|
218
|
+
not `node.status`, and are excluded.)
|
|
219
|
+
- Every node in the snapshot must have a `node.created` event.
|
|
220
|
+
- No event may reference a node or edge that never existed. The `node.deleted`
|
|
221
|
+
edge cascade is applied, so you never emit the cascaded `edge.removed` events.
|
|
222
|
+
- Each JSONL line must be one valid event object; a malformed line is reported by
|
|
223
|
+
its line number and fails the run.
|
|
224
|
+
|
|
225
|
+
### 6. Update timestamps
|
|
226
|
+
|
|
227
|
+
Set `project.updated_at` to the current ISO 8601 timestamp.
|
|
228
|
+
|
|
229
|
+
## Acceptances — the parity layer
|
|
230
|
+
|
|
231
|
+
An **acceptance** is a testable promise: a short `title` (the What), exactly one
|
|
232
|
+
Given/When/Then scenario in `metadata.gherkin` (the How), 1–3 value elements in
|
|
233
|
+
`metadata.values` (the Why), and a status per applicable platform. Acceptances
|
|
234
|
+
are where per-platform truth lives; views and flows are computed aggregates.
|
|
235
|
+
|
|
236
|
+
**Discipline — when you ship user-visible behavior on a platform:**
|
|
237
|
+
|
|
238
|
+
1. Find the acceptance covering that behavior (`covers` edge into the view or
|
|
239
|
+
flow). If none exists, create one (`species: "acceptance"`, id prefix `AC-`).
|
|
240
|
+
2. Set `metadata.platformStatuses.<platform>` on the **acceptance** — never on
|
|
241
|
+
the view. View `platformStatuses` is legacy fallback for views no acceptance
|
|
242
|
+
covers yet; do not write it on covered views.
|
|
243
|
+
3. Append the matching `node.status_changed` event with the `platform` field.
|
|
244
|
+
|
|
245
|
+
Creating a new acceptance + covers edge is itself a dual-write: append
|
|
246
|
+
`node.created` and `edge.added` alongside the `node.status_changed`.
|
|
247
|
+
|
|
248
|
+
**Rules:**
|
|
249
|
+
|
|
250
|
+
- One Given/When/Then per acceptance — a second scenario is a second acceptance.
|
|
251
|
+
- `Given` encodes render variants ("Given the pebble has a picture attached…").
|
|
252
|
+
- `platforms` lists only the platforms where the behavior is *expected* — a
|
|
253
|
+
mobile-only behavior is `["ios", "android"]`, not backlog-on-web.
|
|
254
|
+
- `covers` edges: acceptance → view or acceptance → flow. Zero edges = a
|
|
255
|
+
product-level acceptance (legal). Several = the behavior spans surfaces.
|
|
256
|
+
- Statuses reuse the standard lifecycle; "shipped" = `live`.
|
|
257
|
+
|
|
258
|
+
**Example** — iOS ships the draw-in animation:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"id": "AC-pebble-draw-in-animation",
|
|
263
|
+
"project_id": "{{PROJECT_ID}}",
|
|
264
|
+
"species": "acceptance",
|
|
265
|
+
"title": "Pebble draw-in animation",
|
|
266
|
+
"status": "backlog",
|
|
267
|
+
"platforms": ["web", "ios", "android"],
|
|
268
|
+
"metadata": {
|
|
269
|
+
"gherkin": "When I'm on the Pebble Detail, Then I see the Pebble appearing in a drawing animation.",
|
|
270
|
+
"values": ["fun-entertainment", "design-aesthetics"],
|
|
271
|
+
"platformStatuses": { "ios": "live" }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
plus `{"type": "node.status_changed", "node_id": "AC-pebble-draw-in-animation", "from": "backlog", "to": "live", "platform": "ios"}` in the journal.
|
|
277
|
+
|
|
278
|
+
<!-- values:start -->
|
|
279
|
+
### Value mapping
|
|
280
|
+
|
|
281
|
+
Assign 1–3 `metadata.values` from the 30-element Bain pyramid when creating an
|
|
282
|
+
acceptance. **If unsure, omit them** — enrichment passes exist; a wrong value is
|
|
283
|
+
worse than a missing one. Consult `references/values.md` (one-line definitions
|
|
284
|
+
per element) only when actually mapping — do not load it otherwise.
|
|
285
|
+
<!-- values:end -->
|
|
286
|
+
|
|
287
|
+
## Full Schema Reference
|
|
288
|
+
|
|
289
|
+
For the complete TypeScript types, allowed values for `status`, `species`,
|
|
290
|
+
`edge_type`, `platform`, the journal event payloads, and detailed playlist
|
|
291
|
+
structures, read:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
<skill-path>/references/schema.md
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Consult this reference whenever you're unsure about a field's type or allowed
|
|
298
|
+
values. It is generated from `@arkaik/schema` and is the source of truth.
|
|
299
|
+
|
|
300
|
+
## Event Vocabulary
|
|
301
|
+
|
|
302
|
+
The v1 event types and their payloads (envelope fields `id`, `ts`, `type`,
|
|
303
|
+
optional `actor` are implied on every one). The vocabulary grows without version
|
|
304
|
+
bumps: an unknown `type` is preserved on rewrite and ignored on read, so append a
|
|
305
|
+
known type whenever one fits.
|
|
306
|
+
|
|
307
|
+
| Type | Payload | Meaning |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| `node.created` | `node_id`, `species`, `title` | Node added to the graph |
|
|
310
|
+
| `node.updated` | `node_id`, `fields[]`, optional `from`/`to` for scalars | Non-status fields changed |
|
|
311
|
+
| `node.status_changed` | `node_id`, `from`, `to`, `platform?` | Lifecycle transition; `platform` present when a per-platform node status — an acceptance, or a not-yet-covered view — moved |
|
|
312
|
+
| `node.deleted` | `node_id` | Node removed. **Implies** cascade removal of every edge referencing it — do not emit the cascaded `edge.removed` events |
|
|
313
|
+
| `edge.added` | `edge_id`, `source_id`, `target_id`, `edge_type` | Relationship created |
|
|
314
|
+
| `edge.removed` | `edge_id` | Relationship removed (non-cascade) |
|
|
315
|
+
| `release.tagged` | `version`, `notes?`, `platform?` | A version shipped. `platform` absent = project-wide; present = that platform's rhythm |
|
|
316
|
+
| `idea.proposed` | `title`, `description?`, `node_id?` | An idea, before (or linked to) any node |
|
|
317
|
+
| `request.filed` | `title`, `description?`, `source?`, `node_id?` | An external ask (user feedback, stakeholder request) |
|
|
318
|
+
| `ref.added` | `node_id`, `ref_id`, `ref_type`, `url` | External reference attached |
|
|
319
|
+
| `ref.removed` | `node_id`, `ref_id` | External reference detached |
|
|
320
|
+
| `ref.status_changed` | `node_id`, `ref_id`, `from?`, `to`, `synced_at` | Mirrored external status moved (issue closed, PR merged) |
|
|
321
|
+
|
|
322
|
+
## Bootstrap: Generating a Map from Scratch
|
|
323
|
+
|
|
324
|
+
If the project doesn't have a map yet and the user asks you to create one:
|
|
325
|
+
|
|
326
|
+
1. Scan the codebase for routes/pages, models, and API endpoints
|
|
327
|
+
2. Read any product specs or PRDs in the repo
|
|
328
|
+
3. Generate the full ProjectBundle following the schema reference. Keep a running
|
|
329
|
+
registry of assigned IDs and assert each new ID is unique **as you emit it** —
|
|
330
|
+
don't rely on the final validator to catch a collision after the fact. Watch
|
|
331
|
+
especially for concept-vs-table `DM-` pairs (see Node rules) and give every
|
|
332
|
+
node a non-empty title.
|
|
333
|
+
4. Seed the journal to match: emit a `node.created` for every node and an
|
|
334
|
+
`edge.added` for every edge (and a `node.status_changed` for any node that
|
|
335
|
+
isn't at its starting status), so the snapshot↔journal cross-check passes from
|
|
336
|
+
the very first validation.
|
|
337
|
+
5. Validate with the bundled script and fix everything until it exits 0 (a large
|
|
338
|
+
from-scratch bundle is exactly where duplicate IDs, missing titles, and
|
|
339
|
+
snapshot↔journal gaps slip in)
|
|
340
|
+
6. Save the snapshot to `{{BUNDLE_PATH}}` and the journal to `{{JOURNAL_PATH}}`
|
|
341
|
+
(or ask the user where they want them)
|
|
342
|
+
|
|
343
|
+
Full generation is the **only** sanctioned non-surgical case. For every
|
|
344
|
+
subsequent change, use a surgical patch paired with an appended event.
|