@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 +40 -0
- package/package.json +2 -2
- package/payload/.claude/hooks/drift-detect.cjs +165 -0
- package/payload/.claude/hooks/synapse-engine.cjs +1 -0
- package/payload/.claude/hooks/wiki-lint.cjs +3 -0
- package/payload/.claude/settings.json +2 -1
- package/payload/.claude/skills/dream/SKILL.md +210 -0
- package/payload/.claude/skills/handoff/SKILL.md +2 -0
- package/payload/.claude/skills/sync/SKILL.md +106 -0
- package/payload/.wrxn/dream/.gitkeep +0 -0
- package/payload/.wrxn/dream.cjs +468 -0
- package/payload/.wrxn/sync.cjs +508 -0
- package/payload/.wrxn/wiki/_rules/.gitkeep +0 -0
- package/payload/.wrxn/wiki/_slots/.gitkeep +0 -0
- package/payload/.wrxn/wiki.cjs +31 -10
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.
|
|
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
|