@gcunharodrigues/wrxn 0.4.0 → 0.6.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/manifest.json CHANGED
@@ -23,6 +23,11 @@
23
23
  "class": "managed",
24
24
  "profile": "project"
25
25
  },
26
+ {
27
+ "path": ".claude/hooks/drift-detect.cjs",
28
+ "class": "managed",
29
+ "profile": "project"
30
+ },
26
31
  {
27
32
  "path": ".claude/hooks/enforce-managed-guard.cjs",
28
33
  "class": "managed",
@@ -98,6 +103,11 @@
98
103
  "class": "managed",
99
104
  "profile": "project"
100
105
  },
106
+ {
107
+ "path": ".claude/skills/dream/SKILL.md",
108
+ "class": "managed",
109
+ "profile": "project"
110
+ },
101
111
  {
102
112
  "path": ".claude/skills/grill-me/SKILL.md",
103
113
  "class": "managed",
@@ -288,6 +298,11 @@
288
298
  "class": "managed",
289
299
  "profile": "project"
290
300
  },
301
+ {
302
+ "path": ".claude/skills/sync/SKILL.md",
303
+ "class": "managed",
304
+ "profile": "project"
305
+ },
291
306
  {
292
307
  "path": ".claude/skills/tdd/SKILL.md",
293
308
  "class": "managed",
@@ -408,6 +423,21 @@
408
423
  "class": "state",
409
424
  "profile": "project"
410
425
  },
426
+ {
427
+ "path": ".wrxn/dream.cjs",
428
+ "class": "managed",
429
+ "profile": "project"
430
+ },
431
+ {
432
+ "path": ".wrxn/dream/.gitkeep",
433
+ "class": "state",
434
+ "profile": "project"
435
+ },
436
+ {
437
+ "path": ".wrxn/sync.cjs",
438
+ "class": "managed",
439
+ "profile": "project"
440
+ },
411
441
  {
412
442
  "path": ".wrxn/wiki.cjs",
413
443
  "class": "managed",
@@ -438,6 +468,16 @@
438
468
  "class": "state",
439
469
  "profile": "project"
440
470
  },
471
+ {
472
+ "path": ".wrxn/wiki/_rules/.gitkeep",
473
+ "class": "state",
474
+ "profile": "project"
475
+ },
476
+ {
477
+ "path": ".wrxn/wiki/_slots/.gitkeep",
478
+ "class": "state",
479
+ "profile": "project"
480
+ },
441
481
  {
442
482
  "path": "docs/agents/domain.md",
443
483
  "class": "seeded",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcunharodrigues/wrxn",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "WRXN Kernel — installable AI operating system. Two profiles (project | workspace), pull-based updates, managed/seeded/state file classes.",
5
5
  "bin": {
6
6
  "wrxn": "bin/wrxn.cjs"
@@ -13,7 +13,7 @@
13
13
  "manifest.json"
14
14
  ],
15
15
  "scripts": {
16
- "test": "node --test"
16
+ "test": "node --test --require ./test/setup.cjs"
17
17
  },
18
18
  "dependencies": {
19
19
  "recon-wrxn": "6.0.0-wrxn.3"
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN drift-detect hook — reactive provenance-drift nudge (sync-07).
5
+ // PostToolUse (Edit|Write). When an edit touches a SOURCE file that downstream wiki docs declare
6
+ // `derived_from:`, it injects a <drift> nudge naming the affected doc(s) — so drift surfaces the moment
7
+ // the source moves, not only at the next batch `wrxn sync`.
8
+ //
9
+ // Self-contained: ships into installs, MUST NOT import the kernel lib OR recon (node stdlib only).
10
+ // Mechanical: a pure fs + string frontmatter scan, NO LLM call. Independent of sync-01 — it never reads
11
+ // a `synced_to:` watermark and never writes; detection NUDGE only.
12
+ //
13
+ // Fail-open: any fault (no install root, unreadable wiki, a corrupt page, a missing dir) emits {} and
14
+ // NEVER blocks the edit.
15
+ //
16
+ // Contract: PostToolUse event JSON on stdin → envelope JSON on stdout (exit 0).
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ function emit(envelope) {
22
+ process.stdout.write(JSON.stringify(envelope));
23
+ process.exit(0);
24
+ }
25
+
26
+ function findInstallRoot(startDir) {
27
+ let dir = startDir || process.env.CLAUDE_PROJECT_DIR || process.cwd();
28
+ for (let i = 0; i < 12; i++) {
29
+ if (fs.existsSync(path.join(dir, 'wrxn.install.json'))) return dir;
30
+ const up = path.dirname(dir);
31
+ if (up === dir) break;
32
+ dir = up;
33
+ }
34
+ return null;
35
+ }
36
+
37
+ // Normalize a path-ish value to an install-root-relative POSIX path. Drops a `#symbol` anchor
38
+ // (sync's `derived_from: path#symbol` form), then resolves relative/absolute/`./`-prefixed forms to
39
+ // the same canonical key so same-path and relative-path declarations both match. Returns '' on empty.
40
+ function relTo(root, p) {
41
+ const s = String(p == null ? '' : p).split('#')[0].trim();
42
+ if (!s) return '';
43
+ const abs = path.isAbsolute(s) ? s : path.resolve(root, s);
44
+ return path.relative(root, abs).split(path.sep).join('/');
45
+ }
46
+
47
+ function unquote(s) {
48
+ return String(s).trim().replace(/^["']|["']$/g, '').trim();
49
+ }
50
+
51
+ // Extract the frontmatter block (between the leading `---` fence and the next `---`). A page without a
52
+ // closed fence yields '' — its provenance is unreadable, so it simply contributes no declaration.
53
+ function frontmatter(content) {
54
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
55
+ return m ? m[1] : '';
56
+ }
57
+
58
+ // Parse the `derived_from:` declaration(s) from a page's frontmatter. Handles a scalar, an inline list
59
+ // (`[a, b]`), and a block list (`- item` lines). Returns the raw value strings (anchors intact).
60
+ function parseDerivedFrom(content) {
61
+ const fm = frontmatter(content);
62
+ if (!fm) return [];
63
+ const lines = fm.split(/\r?\n/);
64
+ const out = [];
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const m = /^derived_from:\s*(.*)$/.exec(lines[i]);
67
+ if (!m) continue;
68
+ const val = m[1].trim();
69
+ if (val.startsWith('[')) {
70
+ // inline list: [a, b, c]
71
+ for (const part of val.replace(/^\[|\]$/g, '').split(',')) {
72
+ const v = unquote(part);
73
+ if (v) out.push(v);
74
+ }
75
+ } else if (val) {
76
+ out.push(unquote(val));
77
+ } else {
78
+ // block list: subsequent ` - item` lines until the first non-item line
79
+ for (let j = i + 1; j < lines.length; j++) {
80
+ const li = /^\s*-\s+(.*)$/.exec(lines[j]);
81
+ if (!li) break;
82
+ const v = unquote(li[1]);
83
+ if (v) out.push(v);
84
+ }
85
+ }
86
+ }
87
+ return out;
88
+ }
89
+
90
+ // Collect every .md page under <root>/.wrxn/wiki/ (recursively). A missing/unreadable dir yields [].
91
+ function collectDocs(wikiDir, acc) {
92
+ let entries;
93
+ try {
94
+ entries = fs.readdirSync(wikiDir, { withFileTypes: true });
95
+ } catch {
96
+ return acc; // missing/unreadable tree → no docs
97
+ }
98
+ for (const e of entries) {
99
+ const full = path.join(wikiDir, e.name);
100
+ if (e.isDirectory()) collectDocs(full, acc);
101
+ else if (e.isFile() && e.name.endsWith('.md')) acc.push(full);
102
+ }
103
+ return acc;
104
+ }
105
+
106
+ // The set of doc relpaths whose frontmatter declares `derived_from:` the edited path.
107
+ function affectedDocs(root, editedRel) {
108
+ const wikiDir = path.join(root, '.wrxn', 'wiki');
109
+ const hits = new Set();
110
+ for (const file of collectDocs(wikiDir, [])) {
111
+ let content;
112
+ try {
113
+ content = fs.readFileSync(file, 'utf8');
114
+ } catch {
115
+ continue; // skip an unreadable page (fail-open per-file)
116
+ }
117
+ const docRel = path.relative(root, file).split(path.sep).join('/');
118
+ if (docRel === editedRel) continue; // never flag the edited file against itself
119
+ for (const raw of parseDerivedFrom(content)) {
120
+ if (relTo(root, raw) === editedRel) {
121
+ hits.add(docRel);
122
+ break;
123
+ }
124
+ }
125
+ }
126
+ return [...hits].sort();
127
+ }
128
+
129
+ function main() {
130
+ let event = {};
131
+ try {
132
+ const stdin = fs.readFileSync(0, 'utf8');
133
+ if (stdin.trim()) event = JSON.parse(stdin);
134
+ } catch {
135
+ emit({});
136
+ }
137
+
138
+ const root = findInstallRoot();
139
+ if (!root) emit({});
140
+
141
+ const filePath = event.tool_input && event.tool_input.file_path;
142
+ if (!filePath || typeof filePath !== 'string') emit({}); // not a file-touching tool
143
+
144
+ const editedRel = relTo(root, filePath);
145
+ if (!editedRel) emit({});
146
+
147
+ const docs = affectedDocs(root, editedRel);
148
+ if (docs.length === 0) emit({}); // no downstream provenance → silent
149
+
150
+ const ctx = [
151
+ '<drift>',
152
+ `Edited ${editedRel} — ${docs.length} downstream doc(s) declare derived_from it and may now be stale:`,
153
+ ...docs.map((d) => ` - ${d}`),
154
+ 'Re-derive the affected doc(s), or run `wrxn sync` to confirm the drift set.',
155
+ '</drift>',
156
+ ].join('\n');
157
+
158
+ emit({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: ctx } });
159
+ }
160
+
161
+ try {
162
+ main();
163
+ } catch {
164
+ emit({});
165
+ }
@@ -257,6 +257,7 @@ function handoffDirective(consumed, pct) {
257
257
  ' 1. Finish the current request.',
258
258
  ' 2. Run the handoff skill to write the baton (a compact handoff document).',
259
259
  ' 3. Tell the operator to /clear and open a fresh session, where the baton injects on resume.',
260
+ ' Suggestion (optional, before step 2): run the dream skill to consolidate this session\'s durable learnings into wiki memory — a suggestion only; dream never auto-runs, it acts only when you invoke it.',
260
261
  ].join('\n');
261
262
  }
262
263
 
@@ -15,6 +15,9 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
+ // The human-prose tiers only. The `_`-prefixed tiers (`_rules` and `_slots`) are machine-written by the
19
+ // dream adapter through wiki.cjs — they are deliberately OUTSIDE this human-prose frontmatter lint, not
20
+ // an omission (dream-03: no silent divergence).
18
21
  const TIERS = ['concepts', 'decisions', 'gotchas', 'sessions'];
19
22
  const REQUIRED_KEYS = ['name', 'description', 'tier'];
20
23
  const MAX_FLAGGED = 20;
@@ -45,7 +45,8 @@
45
45
  {
46
46
  "matcher": "Edit|Write",
47
47
  "hooks": [
48
- { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/code-intel-push.cjs\"" }
48
+ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/code-intel-push.cjs\"" },
49
+ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/drift-detect.cjs\"" }
49
50
  ]
50
51
  }
51
52
  ],
@@ -0,0 +1,210 @@
1
+ ---
2
+ name: dream
3
+ description: Consolidate the live session into durable wiki memory — reflect on this conversation, draft evidence-backed Proposals (concept/decision/gotcha/rule), gate each through the dream adapter, and on your confirmation write net-new pages the Brain will recall next time. Use when someone says "dream", "consolidate this session", "save what we learned", or wants to capture durable memory before a handoff.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # dream — session consolidation
8
+
9
+ dream turns what you learned in **this session** into durable wiki pages the **Brain** will recall in
10
+ future sessions. You **propose**; the deterministic adapter **judges**. Every page must quote the
11
+ session that justifies it, and **nothing is written without the operator's confirmation** — so a bad
12
+ proposal can never poison recall. "Bad memory is worse than no memory."
13
+
14
+ ## Indirection contract (MUST)
15
+
16
+ > Drive the adapters. NEVER write wiki files directly and NEVER re-implement the gate.
17
+
18
+ - Validate / stage / commit go through **`.wrxn/dream.cjs`** (the Validation gate + audit + writer).
19
+ - Wiki reads (to check what already exists, or to confirm recall) go through **`.wrxn/wiki.cjs`**.
20
+ - The skill is the **semantic** filter (you don't even draft junk); the adapter is the **mechanical**
21
+ backstop (it rejects what slips through). Run a proposal the gate rejected? It is never written.
22
+
23
+ ## The loop
24
+
25
+ 1. **Reflect** on the live conversation already in your context. Do NOT read transcripts or stored
26
+ session pages — reflect on what is in front of you, this session only.
27
+ 2. **Draft** candidate Proposals (see schema + rubric below), each grounded in a **verbatim quote**
28
+ from THIS session. If the session yields no durable insight, **abstain** — propose nothing.
29
+ 3. **check** the batch through the adapter; drop or fix anything it rejects (never carry a reject
30
+ forward).
31
+ 4. **stage** the validated batch — records it to the audit trail, outside the recalled wiki.
32
+ 5. **Present** the staged batch to the operator and wait for confirmation.
33
+ 6. **commit** only the operator-approved subset — net-new pages, additively, into their tiers.
34
+
35
+ If reflection surfaces nothing durable, or the gate rejects every proposal, **stop**: say so, stage
36
+ nothing, commit nothing. Restraint is a success, not a failure.
37
+
38
+ ## FAITHFULNESS — the most important rule
39
+
40
+ The wiki records *what happened in this project, this session* — not what you know about the topic in
41
+ general. You are not writing tutorials, documentation, or reference material. Every claim in every
42
+ page MUST trace to the session in front of you.
43
+
44
+ Do NOT:
45
+ - Invent dates, version numbers, commit hashes, author names, file paths, function names, line
46
+ numbers, or error codes that did not appear in the session.
47
+ - Add "When to use" / "Best practices" / "Alternatives" / "See also" sections that weren't grounded in
48
+ the session — those are reference-material patterns, not memory.
49
+ - Enumerate options that weren't actually considered, or expand a terse operator comment into an essay.
50
+ - Fabricate code or speculate about consequences the session itself didn't raise.
51
+ - **Write a session secret into a page.** Redact any credential (API key, token, private key) that
52
+ surfaced in the session — a durable page is recalled forever. The gate also rejects `contains_secret`,
53
+ but you are the first filter.
54
+
55
+ Do:
56
+ - Compress the session into well-titled pages with the right `kind`.
57
+ - **Preserve the operator's actual phrasing** for decisions and rules — it is load-bearing.
58
+ - Write each page at the length the session actually warrants — dense fact, no padding, no truncation.
59
+ - If the session yields no durable insight, **abstain**. Resist the urge to manufacture content.
60
+
61
+ ## What to propose — the `kind` rubric
62
+
63
+ Exactly one kind per Proposal; `tier` must agree with `kind`.
64
+
65
+ | kind | tier | propose when the session produced… |
66
+ |------------|--------------|-------------------------------------------------------------------------------|
67
+ | `decision` | `decisions` | a choice of X over Y, with its rationale and consequences (why the project is the way it is) |
68
+ | `gotcha` | `gotchas` | a reproducible pitfall / failure mode, its root cause, and the mitigation |
69
+ | `concept` | `concepts` | stable architecture or domain knowledge (synthesis, not a task chronology) |
70
+ | `rule` | `_rules` | an always/never project convention the session established — a standing rule, recalled like a concept (NOT a SYNAPSE always-on rule; see Boundaries) |
71
+
72
+ Two unrelated insights stay **two** pages — never merge them into one. Small pages, stable
73
+ kebab-case names (Karpathy LLM-wiki style). Cap a run at **≤ 5** proposals.
74
+
75
+ ## What NOT to propose — anti-superstition
76
+
77
+ Do not even draft these. A transient or false "memory", once recalled, hardens into a permanent false
78
+ constraint on every future session. (The adapter rejects them too — but you are the first filter.)
79
+
80
+ | Reject | Why |
81
+ |---------------------------------|----------------------------------------------------------------------|
82
+ | "tool X is broken" | A broad negative tool claim hardens into a permanent false refusal after the tool is fixed. |
83
+ | Transient env / setup failures | ENOENT, connection refused, timeouts, flaky/intermittent, rate-limits, a missing binary — stale false constraints, not durable truth. |
84
+ | Smoke / sanity / happy-path checks | Operational evidence, not reusable knowledge. |
85
+ | Release / version markers | A one-time event (a version bump, a changelog, an npm publish), not a lesson. |
86
+ | One-off task narratives | "Renamed a file", "fixed a typo", a trivial chore — episodic, already captured. |
87
+ | **wrxn itself** | Never memorialize wrxn's own routing / skills / synapse / hooks / constitution / adapters — the memory system must not pollute itself. |
88
+
89
+ ## Proposal schema
90
+
91
+ A Proposal is one JSON object. A run is a JSON **array** of them (the batch).
92
+
93
+ ```jsonc
94
+ {
95
+ "kind": "concept" | "decision" | "gotcha" | "rule", // pick one
96
+ "tier": "concepts" | "decisions" | "gotchas" | "_rules", // = f(kind); MUST agree
97
+ "slug": "kebab-case-page-name", // stable name
98
+ "title": "One-line page title",
99
+ "body": "# Title\n\n…markdown… ", // MUST start with '# '
100
+ "confidence": 0.0, // honest 0–1; the gate floor is 0.75
101
+ "rationale": "Why this is durable.",
102
+ "evidence": [ // >= 1, each a VERBATIM quote from THIS session
103
+ { "quote": "exact words from the session", "source": "file:line | commit | turn-N" } // source optional
104
+ ]
105
+ }
106
+ ```
107
+
108
+ ## Driving the adapter
109
+
110
+ Run from inside the install (the adapter walks up to `wrxn.install.json` to find the root — no
111
+ `--root` needed). Write each batch to a **throwaway temp file** (it is scratch input, not a wiki page;
112
+ only the adapter's own `.wrxn/dream/*.jsonl` audit files persist).
113
+
114
+ **1 — check** (the gate; PROPOSE, then let it JUDGE):
115
+
116
+ ```bash
117
+ node .wrxn/dream.cjs check /tmp/dream-batch.json
118
+ ```
119
+
120
+ A batch returns `{ abstained, accepted[], rejected[ {index, slug, reason} ] }`. Each `reason` is a
121
+ machine code — `confidence_below_threshold`, `missing_evidence`, `missing_rationale`,
122
+ `body_missing_h1`, `body_too_large`, `invalid_slug`, `missing_title`, `invalid_title`,
123
+ `unsupported_tier`, `kind_tier_mismatch`, `contains_secret`, `duplicate_existing_path`,
124
+ `duplicate_existing_title`, `max_proposals_exceeded`, or a `negative_filter_*`. Fix or drop every
125
+ rejected proposal; re-check until the batch is clean. If it returns `{ abstained: true }` (or every
126
+ proposal is rejected), **stop** — write nothing.
127
+
128
+ If the gate rejects a genuinely durable insight on a `negative_filter_*` **false positive** (e.g. a real
129
+ decision that merely mentions "transient", "synapse", or "release"), **rephrase** the page to drop the
130
+ transient/operational wording — state the durable decision, not the episodic event — then re-check.
131
+ Never write around the gate.
132
+
133
+ **2 — stage** (record the validated batch to the audit trail; nothing reaches the wiki yet):
134
+
135
+ ```bash
136
+ node .wrxn/dream.cjs stage /tmp/dream-batch.json
137
+ ```
138
+
139
+ **3 — present, then confirm.** Show the operator each staged proposal — its **tier/slug**, **title**,
140
+ **confidence**, the **verbatim evidence quote**, and the one-line rationale — and ask which to approve.
141
+ Never skip this step. If the operator approves none, you are done: commit nothing.
142
+
143
+ **4 — commit** (write ONLY the operator-approved subset, **by reference**). Build a JSON array of the
144
+ approved **slugs** — NOT a rebuilt Proposal array — and commit it:
145
+
146
+ ```bash
147
+ node .wrxn/dream.cjs commit /tmp/dream-approved.json # ["slug-a","slug-b"] (or {"approved":["slug-a",…]})
148
+ ```
149
+
150
+ `commit` reads `.wrxn/dream/staged.jsonl`, finds each approved slug's **staged** proposal, **re-runs the
151
+ gate** on it (confidence, evidence, body H1, kind↔tier, secret-scan, negative filters, identity, dedup —
152
+ everything `check` ran), and writes ONLY the ones that still pass — net-new pages, additively, via
153
+ `wiki.cjs`. It **dedup-skips** any whose path already exists (never clobbers a curated page); a slug not
154
+ staged (`not_staged`) or one that fails re-validation is recorded skipped with the reason. Returns
155
+ `{ written[], skipped[] }`. This binds *committed == staged == presented*: a proposal the gate would
156
+ reject can never be written, even if its slug is force-approved.
157
+
158
+ **5 — confirm recall (optional).** The committed pages are plain `.md` in the wiki, so the Brain
159
+ recalls them automatically next session. Spot-check with a wiki query:
160
+
161
+ ```bash
162
+ node .wrxn/wiki.cjs query "<a phrase from a page you just wrote>"
163
+ ```
164
+
165
+ ## Refreshing the focus slot
166
+
167
+ `_slots/current-focus.md` is the project's **durable standing focus** — a short statement of what the
168
+ project is centered on right now, recall-surfaced like any other page. It is the **lone updatable wiki
169
+ page**: every knowledge page is additive + dedup-skip, but the focus slot may be **overwritten in
170
+ place**.
171
+
172
+ This is **not** the knowledge-proposal loop — do not run a focus update through `check` / `stage` /
173
+ `commit` (those are for evidence-backed concept/decision/gotcha/rule pages). The slot has its **own op**:
174
+
175
+ 1. Draft a short standing-focus statement (a few lines of markdown, body starting with `# `).
176
+ 2. **Present it to the operator and wait for confirmation** — like every dream write.
177
+ 3. On approval, write it via the dedicated op — it overwrites the slot in place:
178
+
179
+ ```bash
180
+ node .wrxn/dream.cjs set-focus /tmp/dream-focus.json # { "title": "Current focus", "body": "# Current focus\n\n…" }
181
+ ```
182
+
183
+ The focus slot is **gated** too: `set-focus` runs the anti-superstition negative filters and the
184
+ credential secret-scan over the focus body and **refuses** (writing nothing) if either fires. Redact
185
+ secrets and pin durable standing context, not a transient note.
186
+
187
+ **Continuity doctrine — do not cross these wires.** The focus slot is **disjoint** from the handoff
188
+ **baton** (`.wrxn/continuity/latest.md`): different path, different writer. `set-focus` NEVER reads or
189
+ writes the baton, and the **handoff** skill remains its sole writer. The baton is ephemeral cross-session
190
+ resume; the focus slot is durable standing context. Keeping their paths and writers separate is the
191
+ structural fix that stops a deliberate handoff from being clobbered.
192
+
193
+ ## Boundaries
194
+
195
+ - **Current session only.** No transcript mining, no cross-session backlog.
196
+ - **Additive only, save one slot.** dream creates net-new knowledge pages; merging or refreshing an
197
+ existing page is out of scope (that is harvest, a later phase). The **lone exception** is the focus
198
+ slot `_slots/current-focus.md`, which `set-focus` overwrites in place (see *Refreshing the focus slot*).
199
+ - **Never autonomous.** dream is a deliberate, attended, operator-confirmed skill — never a background
200
+ run, never a write without confirmation.
201
+ - **`_rules` ≠ SYNAPSE.** A `rule` page is *recalled knowledge* — the Brain surfaces it like a concept
202
+ or gotcha. It is NOT a SYNAPSE always-on rule. Promoting a `_rules` page into SYNAPSE's curated
203
+ always-injected set (`.synapse/`) is a separate, deliberate act — **dream NEVER edits `.synapse/`**.
204
+
205
+ ## Source
206
+
207
+ WRXN Kernel issue dream-02. Adapter: `.wrxn/dream.cjs` (dream-01). Prompts adapted from
208
+ `akitaonrails/ai-memory` (`auto_improve` system prompt, the `batch_consolidate` FAITHFULNESS block,
209
+ the `kind` rubric, the `docs/auto-improvement-loop.md` negative-filter list). ADR 0003; PRD
210
+ `dream-prd`.
@@ -6,6 +6,8 @@ argument-hint: "What will the next session be used for?"
6
6
 
7
7
  Write a handoff document summarising the current conversation so a fresh agent can continue the work.
8
8
 
9
+ Before writing the baton, OFFER to run the `dream` skill to consolidate this session's durable learnings into wiki memory (concept/decision/gotcha/rule pages the Brain recalls next session). This is an offer only — run `dream` solely if the operator agrees; never auto-run it. `dream` writes additive wiki pages (and may refresh its own `_slots/current-focus.md`), while this skill remains the SOLE writer of `.wrxn/continuity/latest.md` — the two are disjoint, so neither clobbers the other.
10
+
9
11
  Save it to the install's continuity slot: `.wrxn/continuity/latest.md` (resolve the install root by walking up to the `wrxn.install.json` receipt; create the `.wrxn/continuity/` directory if absent). This slot is the deliberate, intent-carrying baton — the NEXT session's `session-start` hook injects its contents as the resume surface, taking precedence over the automatic episodic session page.
10
12
 
11
13
  CONTINUITY DOCTRINE: this skill is the SINGLE writer of `.wrxn/continuity/latest.md`. The automatic `session-end` hook writes ONLY dated session pages under `.wrxn/wiki/sessions/` and NEVER touches the baton — so a deliberate handoff is never clobbered by the automatic episodic record. Overwrite the previous baton (the latest deliberate handoff is the live one).
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: sync
3
+ description: Report which derived docs have drifted from the source code they describe — query recon's computable drift set over the warm serve door and list each stale page, the source symbol that moved, and the watermark it was last reconciled at. Use when someone says "sync", "check for stale docs", "what docs have drifted", or wants to know if the prose is still reconciled with the code before a handoff or release.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # sync — drift report
8
+
9
+ `sync` tells you which **derived prose** has fallen out of step with the **source code** it documents.
10
+ A page that declares `derived_from: path#symbol` carries a `synced_to:` watermark — the source version
11
+ it was last reconciled against. recon-wrxn computes, purely from its index, the set of docs whose source
12
+ symbol has since changed (an AST fingerprint, so reformatting and comment edits do **not** trip drift).
13
+ This skill **queries that set and reports it**. It is the third maintenance loop alongside `dream`
14
+ (memory) — `sync` keeps derived prose reconciled with source.
15
+
16
+ > The **report** is read-only — it never edits a doc or advances a watermark. For **prose** drift you can
17
+ > then go one step further: `sync` can DRAFT a reconciling edit and, on your explicit confirm, write it in
18
+ > place and advance the watermark (the `propose → confirm` loop below). Auto-regen of *mechanical* derived
19
+ > files is still a separate, later step.
20
+
21
+ ## Indirection contract (MUST)
22
+
23
+ > Drive the adapter. NEVER hand-compute drift, re-read a doc's frontmatter, or write any file.
24
+
25
+ - The drift query goes through **`.wrxn/sync.cjs report`** (the install-local door client + report gate).
26
+ - The adapter consumes the `synced_to` watermark **from the recon_drift door response** — recon parsed
27
+ it out of the doc's frontmatter. Do not open wiki files to re-derive it; `sync` is strictly read-only.
28
+
29
+ ## The loop
30
+
31
+ 1. **Run the report** from inside the install (the adapter walks up to `wrxn.install.json` — no `--root`
32
+ needed):
33
+
34
+ ```bash
35
+ node .wrxn/sync.cjs report
36
+ ```
37
+
38
+ 2. **Read the JSON** it prints — `{ status, stale[], unwatermarked[] }`:
39
+
40
+ - **`status: "synced"`** — the stale set is empty. **Say so briefly ("all synced") and stop.** Do not
41
+ manufacture findings. A clean tree is a successful no-op.
42
+ - **`status: "drift"`** — present each `stale[]` entry to the operator: the **doc** page, the **symbol**
43
+ that moved, and **`synced_to` → `current`** (the watermark vs the source's current fingerprint). If
44
+ `unwatermarked[]` is non-empty, note those separately — docs that declare `derived_from` but were
45
+ never watermarked (so drift can't yet be computed for them).
46
+ - **`status: "unavailable"`** — recon's serve door is not warm (no `recon-wrxn serve` running, or it was
47
+ unreachable). Report "drift unavailable — start `recon-wrxn serve` and retry." Never treat this as
48
+ "all synced": unknown is not clean.
49
+
50
+ 3. **Decide per stale doc.** Hand the stale list to the operator. For a **prose** page, you may reconcile
51
+ it with the `propose → confirm` loop below. Regenerating a *mechanical* derived file is still out of
52
+ scope here.
53
+
54
+ ## Propose → confirm (prose re-stamp, sync-06)
55
+
56
+ For a stale PROSE doc, reconcile it WITHOUT ever auto-rewriting words: you draft the edit, the operator
57
+ confirms, then the watermark advances. The watermark means **"verified fresh"**, never "stamped without
58
+ checking". Same split as `dream`: **you (the skill) draft the prose; `.wrxn/sync.cjs` gates and writes.**
59
+
60
+ 1. **Draft + propose (stage).** From a `stale[]` entry, write the reconciling markdown body and stage it
61
+ by-reference — secret-scanned, recorded under `.wrxn/sync/staged.jsonl`, the live doc untouched:
62
+
63
+ ```bash
64
+ node .wrxn/sync.cjs propose proposal.json
65
+ ```
66
+
67
+ `proposal.json` carries the drift record's own fields (do NOT re-derive them) plus your drafted body:
68
+
69
+ ```json
70
+ { "doc": ".wrxn/wiki/concepts/auth-flow.md", "symbol": "src/auth.ts#login",
71
+ "synced_to": "<old watermark from the report>", "current": "<current fingerprint from the report>",
72
+ "body": "# Auth flow\n\n…the reconciled prose…" }
73
+ ```
74
+
75
+ 2. **Present it to the operator and wait.** Show the drafted edit. Nothing is written and the watermark is
76
+ NOT advanced until the operator confirms — staging alone never re-stamps.
77
+
78
+ 3. **Confirm (commit) or decline.** On approval, confirm BY REFERENCE (the doc path). The adapter re-reads
79
+ the staged edit, re-runs the secret-scan + an integrity check (a tampered or altered proposal cannot
80
+ write), edits the doc in place, and advances `synced_to:` to `current`:
81
+
82
+ ```bash
83
+ node .wrxn/sync.cjs confirm approved.json
84
+ ```
85
+
86
+ where `approved.json` is the operator-approved doc list — `[".wrxn/wiki/concepts/auth-flow.md"]` or
87
+ `{ "approved": [".wrxn/wiki/concepts/auth-flow.md"] }`. **Decline** = confirm an empty approval
88
+ (`{ "approved": [] }`) — the file AND the watermark stay exactly as they were.
89
+
90
+ ## Boundaries
91
+
92
+ - **Report is read-only.** Prose `propose → confirm` is the ONLY write path, and only on explicit operator
93
+ confirm. Regen of mechanical derived files is a later sync slice.
94
+ - **Never auto-rewrite words.** The reconciling edit is staged and presented; the in-place write + watermark
95
+ advance happen only on confirm, re-validated at the write boundary.
96
+ - **Declared provenance only.** Only docs carrying a `derived_from:` anchor participate; an undocumented
97
+ file is never "drifted". This is opt-in by provenance, by design.
98
+ - **Fail-soft, never alarmist.** If recon is unreachable the answer is "unavailable", not "stale" and not
99
+ "synced". The adapter never throws; neither should your report.
100
+
101
+ ## Source
102
+
103
+ WRXN Kernel issues sync-04 (report) + sync-06 (prose propose → confirm → re-stamp). Adapter: `.wrxn/sync.cjs`.
104
+ Drift signal: recon-wrxn `recon_drift` (sync-03), watermark storage (sync-01) + AST fingerprint (sync-02).
105
+ Door discovery mirrors `recall-surface.cjs`; skill+adapter shape (stage → commit-by-reference, secret-scan)
106
+ mirrors `dream`. PRD `sync-prd`; ADR 0004.
File without changes