@gcunharodrigues/wrxn 0.7.0 → 0.7.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gcunharodrigues/wrxn",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
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"
|
|
@@ -16,11 +16,19 @@
|
|
|
16
16
|
"test": "node --test --require ./test/setup.cjs"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"recon-wrxn": "6.0.0-wrxn.
|
|
19
|
+
"recon-wrxn": "6.0.0-wrxn.6"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/gcunharodrigues/wrxn-kernel.git"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"provenance": true,
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
24
32
|
"license": "MIT",
|
|
25
33
|
"private": false
|
|
26
34
|
}
|
|
@@ -35,14 +35,17 @@ This skill **queries that set and reports it**. It is the third maintenance loop
|
|
|
35
35
|
node .wrxn/sync.cjs report
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
2. **Read the JSON** it prints — `{ status, stale[], unwatermarked[] }`:
|
|
38
|
+
2. **Read the JSON** it prints — `{ status, stale[], unwatermarked[], orphaned[] }`:
|
|
39
39
|
|
|
40
|
-
- **`status: "synced"`** — the stale
|
|
41
|
-
manufacture findings. A clean tree is a successful no-op.
|
|
40
|
+
- **`status: "synced"`** — both the stale AND orphaned sets are empty. **Say so briefly ("all synced") and
|
|
41
|
+
stop.** Do not manufacture findings. A clean tree is a successful no-op.
|
|
42
42
|
- **`status: "drift"`** — present each `stale[]` entry to the operator: the **doc** page, the **symbol**
|
|
43
43
|
that moved, and **`synced_to` → `current`** (the watermark vs the source's current fingerprint). If
|
|
44
|
-
`
|
|
45
|
-
|
|
44
|
+
`orphaned[]` is non-empty, flag those DISTINCTLY — each is a **dangling** doc (`doc` + `synced_to`) whose
|
|
45
|
+
`derived_from:` source symbol was **renamed or deleted**, so its provenance is gone and drift can no
|
|
46
|
+
longer be computed. The reconcile loop below cannot fix an orphan (there is no live source to re-stamp
|
|
47
|
+
against); surface it so the operator can re-anchor or retire the page. If `unwatermarked[]` is non-empty,
|
|
48
|
+
note those separately — docs that declare `derived_from` but were never watermarked.
|
|
46
49
|
- **`status: "unavailable"`** — recon's serve door is not warm (no `recon-wrxn serve` running, or it was
|
|
47
50
|
unreachable). Report "drift unavailable — start `recon-wrxn serve` and retry." Never treat this as
|
|
48
51
|
"all synced": unknown is not clean.
|
package/payload/.mcp.json
CHANGED
|
@@ -50,6 +50,14 @@ const HARVEST_DIR = ['.wrxn', 'harvest'];
|
|
|
50
50
|
const STAGED_FILE = 'staged.jsonl'; // the proposed-but-unconfirmed merges (survivor body + absorbed, by-reference).
|
|
51
51
|
const AUDIT_FILE = 'audit.jsonl'; // append-only outcome log (stage + commit + decay events).
|
|
52
52
|
const DECAY_STAGED_FILE = 'decay-staged.jsonl'; // harvest-04: proposed-but-unconfirmed decay annotations (by-reference). Distinct fixed name from merge's staged.jsonl; both non-.md so recon never recalls a staged-but-unconfirmed op.
|
|
53
|
+
// Bounded retention for the timestamped <ts>.jsonl check reports (phase-4.5-04). `check` writes a fresh
|
|
54
|
+
// report every run; without a cap the dir grows without bound on a long-lived install. Keep the N most-recent
|
|
55
|
+
// (ISO timestamps sort lexically = chronologically — no clock read). 20 is a generous trailing window of
|
|
56
|
+
// recent checks for trend/diff while strictly bounding growth; env override (clamped >= 1 so the just-written
|
|
57
|
+
// report is never pruned) mirrors synapse's WRXN_HANDOFF_PCT precedent. REPORT_RE matches ONLY a timestamped
|
|
58
|
+
// report — the fixed-name state files (staged/audit/decay-staged.jsonl) + .gitkeep never match, never prune.
|
|
59
|
+
const REPORT_RETAIN_DEFAULT = 20;
|
|
60
|
+
const REPORT_RE = /^\d{4}-\d{2}-\d{2}T.*\.jsonl$/;
|
|
53
61
|
const BODY_MAX = 32000; // survivor body cap (chars) — a durable merged page, not a dump (dream/sync parity).
|
|
54
62
|
const WIKI_REL = ['.wrxn', 'wiki']; // all merge targets confine under <root>/.wrxn/wiki/<knowledge-tier>/.
|
|
55
63
|
const WIKI_PREFIX = '.wrxn/wiki/'; // stripped to form the reinforce.json wiki-rel join key (recall-surface parity).
|
|
@@ -296,6 +304,22 @@ function nearDupQualifies(hit) {
|
|
|
296
304
|
return Number.isFinite(sem) && hasSemantic && sem >= NEAR_DUP_THRESHOLD;
|
|
297
305
|
}
|
|
298
306
|
|
|
307
|
+
// Order clusters by a STABLE total order so an unchanged tree yields a byte-identical report every run
|
|
308
|
+
// (phase-4.5-04). Primary key = the lexically-first member. The old `(a,b) => a.members[0] < b.members[0]
|
|
309
|
+
// ? -1 : 1` returned 1 for BOTH compare(a,b) AND compare(b,a) on an equal leading member — non-antisymmetric,
|
|
310
|
+
// so V8's sort could resolve tied clusters by input permutation → non-reproducible reports. Clusters are
|
|
311
|
+
// disjoint connected components, so leading members never collide TODAY; the secondary keys make this a
|
|
312
|
+
// proper total order regardless (defense in depth): larger cluster first, then the stronger edge score, then
|
|
313
|
+
// the full member list — and two truly-identical clusters compare EQUAL (return 0).
|
|
314
|
+
function compareClusters(a, b) {
|
|
315
|
+
if (a.members[0] !== b.members[0]) return a.members[0] < b.members[0] ? -1 : 1;
|
|
316
|
+
if (a.members.length !== b.members.length) return b.members.length - a.members.length; // larger cluster first
|
|
317
|
+
if (a.score !== b.score) return b.score - a.score; // stronger dup signal first
|
|
318
|
+
const ja = a.members.join('');
|
|
319
|
+
const jb = b.members.join('');
|
|
320
|
+
return ja < jb ? -1 : ja > jb ? 1 : 0; // full member list; identical clusters → 0
|
|
321
|
+
}
|
|
322
|
+
|
|
299
323
|
// Collapse pairwise near-dup edges into connected-component CLUSTERS (union-find), so a symmetric A↔B
|
|
300
324
|
// match is reported once and a transitive A-B-C chain is one cluster of three. Each cluster carries its
|
|
301
325
|
// sorted members + the STRONGEST edge similarity (the clearest dup signal). Singletons are not clusters.
|
|
@@ -333,7 +357,7 @@ function clusterNearDups(edges) {
|
|
|
333
357
|
if (members.size < 2) continue;
|
|
334
358
|
clusters.push({ members: [...members].sort(), score: Math.round((groupScore.get(r) || 0) * 1e4) / 1e4 });
|
|
335
359
|
}
|
|
336
|
-
return clusters.sort(
|
|
360
|
+
return clusters.sort(compareClusters);
|
|
337
361
|
}
|
|
338
362
|
|
|
339
363
|
// ── the door (IO shell, injectable transport) — the recall-surface.cjs contract ──
|
|
@@ -504,6 +528,41 @@ function reportPath(dir, ts) {
|
|
|
504
528
|
return file;
|
|
505
529
|
}
|
|
506
530
|
|
|
531
|
+
// The effective report-retention bound: the WRXN_HARVEST_RETAIN env override (clamped to a whole number >= 1
|
|
532
|
+
// so a bogus/zero value can never prune away the just-written report), else the sane default (phase-4.5-04).
|
|
533
|
+
function reportRetention() {
|
|
534
|
+
const env = Number(process.env.WRXN_HARVEST_RETAIN);
|
|
535
|
+
return Number.isFinite(env) && env >= 1 ? Math.floor(env) : REPORT_RETAIN_DEFAULT;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Prune the timestamped check reports under .wrxn/harvest/ to the retention bound, keeping the `keep` most-
|
|
539
|
+
// recent (phase-4.5-04). ISO timestamps sort lexically = chronologically, so the oldest are the lexical
|
|
540
|
+
// prefix — no clock is read. ONLY <ts>.jsonl reports are eligible (REPORT_RE); the fixed-name state files
|
|
541
|
+
// (staged/audit/decay-staged.jsonl) + .gitkeep never match, so curation/merge/decay trails are never touched.
|
|
542
|
+
// Fail-soft: an unreadable dir or a failed unlink is swallowed — retention is hygiene, never the point of check.
|
|
543
|
+
function pruneReports(dir, keep, protect) {
|
|
544
|
+
let names;
|
|
545
|
+
try {
|
|
546
|
+
names = fs.readdirSync(dir);
|
|
547
|
+
} catch {
|
|
548
|
+
return; // no report dir yet → nothing to prune
|
|
549
|
+
}
|
|
550
|
+
const reports = names.filter((n) => REPORT_RE.test(n)).sort(); // lexical = chronological (oldest first)
|
|
551
|
+
// Delete the oldest down to the `keep` bound, but NEVER the just-written report (`protect`): a same-
|
|
552
|
+
// millisecond collision names it `<base>-N.jsonl`, which collates BEFORE its `<base>.jsonl` sibling, so a
|
|
553
|
+
// blind oldest-prefix prune could delete the fresh report `check` is about to return (phase-4.5-04 review).
|
|
554
|
+
let toDelete = reports.length - keep;
|
|
555
|
+
for (let i = 0; i < reports.length && toDelete > 0; i++) {
|
|
556
|
+
if (reports[i] === protect) continue; // the fresh report is retained and counts toward `keep`
|
|
557
|
+
try {
|
|
558
|
+
fs.unlinkSync(path.join(dir, reports[i]));
|
|
559
|
+
} catch {
|
|
560
|
+
/* fail-soft — a vanished/locked report never breaks the check run */
|
|
561
|
+
}
|
|
562
|
+
toDelete--;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
507
566
|
// ── check: the IO orchestrator ───────────────────────────────────────────────────
|
|
508
567
|
// scanLocal (always) + nearDupFromDoor (degrades to unavailable when cold) → assemble → write the jsonl.
|
|
509
568
|
// REPORT-ONLY: the ONLY write is the report under .wrxn/harvest/. `transport` is injected in tests.
|
|
@@ -520,6 +579,7 @@ async function check(root, { transport, timeoutMs } = {}) {
|
|
|
520
579
|
const dir = harvestDir(root);
|
|
521
580
|
const file = reportPath(dir, ts);
|
|
522
581
|
fs.writeFileSync(file, records.length ? records.map((r) => JSON.stringify(r)).join('\n') + '\n' : '');
|
|
582
|
+
pruneReports(dir, reportRetention(), path.basename(file)); // phase-4.5-04: bound the report dir, never pruning the fresh report
|
|
523
583
|
const nearDupCount = near.status === 'unavailable' ? 0 : near.clusters.length;
|
|
524
584
|
return {
|
|
525
585
|
report: path.relative(root, file),
|
|
@@ -1154,7 +1214,10 @@ module.exports = {
|
|
|
1154
1214
|
scanLocal,
|
|
1155
1215
|
isProse,
|
|
1156
1216
|
nearDupQualifies,
|
|
1217
|
+
compareClusters,
|
|
1157
1218
|
clusterNearDups,
|
|
1219
|
+
pruneReports,
|
|
1220
|
+
reportRetention,
|
|
1158
1221
|
assembleRecords,
|
|
1159
1222
|
tierOfPath,
|
|
1160
1223
|
isHarvestPath,
|
|
@@ -1187,4 +1250,5 @@ module.exports = {
|
|
|
1187
1250
|
NEAR_DUP_THRESHOLD,
|
|
1188
1251
|
FIND_PATH,
|
|
1189
1252
|
REINFORCE_WINDOW_DAYS,
|
|
1253
|
+
REPORT_RETAIN_DEFAULT,
|
|
1190
1254
|
};
|
package/payload/.wrxn/sync.cjs
CHANGED
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Subcommand:
|
|
17
17
|
// report query recon_drift over the serve door and print the drift summary JSON:
|
|
18
|
-
// { status, stale[], unwatermarked[] }
|
|
19
|
-
// · status "drift" — at least one doc is stale
|
|
20
|
-
//
|
|
21
|
-
//
|
|
18
|
+
// { status, stale[], unwatermarked[], orphaned[] }
|
|
19
|
+
// · status "drift" — at least one doc is stale OR orphaned. stale[] names doc/symbol/
|
|
20
|
+
// synced_to/current; orphaned[] (phase-4.5-02) names a doc whose
|
|
21
|
+
// derived_from source symbol is GONE (doc/synced_to, no symbol/current).
|
|
22
|
+
// · status "synced" — the warm door computed an EMPTY stale AND orphaned set ("all synced";
|
|
23
|
+
// AC3 no-op, never manufactures rows).
|
|
22
24
|
// · status "unavailable" — recon is unreachable OR answered without the structured drift sidecar
|
|
23
25
|
// (no warm door, a timeout, a non-200, a malformed body, or a 200 whose
|
|
24
26
|
// body lacks an affirmative `drift.stale` array — unknown is not clean,
|
|
@@ -117,11 +119,18 @@ function summarizeDrift(parsed) {
|
|
|
117
119
|
if (!isEntry(d) || !Array.isArray(d.stale)) return unavailable();
|
|
118
120
|
const stale = d.stale.filter(isEntry).map(normEntry);
|
|
119
121
|
const unwatermarked = (Array.isArray(d.unwatermarked) ? d.unwatermarked : []).filter(isEntry).map(normEntry);
|
|
120
|
-
|
|
122
|
+
// phase-4.5-02: the third drift class — a watermarked page whose derived_from source symbol is GONE from
|
|
123
|
+
// recon's graph (renamed/deleted). Each entry normalizes to { doc, synced_to } (no symbol/current — the
|
|
124
|
+
// source is absent). It is DISTINCT from stale (source moved) and unwatermarked (never reconciled), but
|
|
125
|
+
// like stale it MUST elevate the status off a clean "synced": a dangling page is the exact case sync used
|
|
126
|
+
// to hide (the operator never learned the page was un-reconcilable). An older recon with no drift.orphaned
|
|
127
|
+
// degrades to [] (back-compatible, never throws).
|
|
128
|
+
const orphaned = (Array.isArray(d.orphaned) ? d.orphaned : []).filter(isEntry).map(normEntry);
|
|
129
|
+
return { status: stale.length || orphaned.length ? 'drift' : 'synced', stale, unwatermarked, orphaned };
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
function unavailable() {
|
|
124
|
-
return { status: 'unavailable', stale: [], unwatermarked: [] };
|
|
133
|
+
return { status: 'unavailable', stale: [], unwatermarked: [], orphaned: [] };
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
// ── the door (IO shell, injectable transport) — the recall-surface.cjs contract ─
|