0xray 4.0.20 → 4.0.22
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/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/hermes-agent/SKILL.md +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +23 -1
- package/.xray/features.json +1 -1
- package/AGENTS-consumer.md +15 -15
- package/AGENTS.md +12 -12
- package/README.md +40 -40
- package/SKILLS.md +4 -4
- package/dist/AGENTS.md +12 -12
- package/dist/CHANGELOG.md +91 -8
- package/dist/README.md +40 -40
- package/dist/SKILLS.md +4 -4
- package/dist/cli/commands/validate.d.ts +1 -1
- package/dist/cli/commands/validate.js +7 -1
- package/dist/governance/governance-core.d.ts +2 -1
- package/dist/governance/governance-core.js +20 -11
- package/dist/governance/governance-service.js +9 -7
- package/dist/inference/inference-cycle.js +22 -5
- package/dist/integrations/cursor/README.md +7 -6
- package/dist/integrations/cursor/hooks/after-file-edit.sh +3 -0
- package/dist/integrations/cursor/hooks/before-read-file.sh +3 -0
- package/dist/integrations/cursor/hooks/before-shell-execution.sh +3 -0
- package/dist/integrations/cursor/hooks/cursor-hook-utils.js +112 -12
- package/dist/integrations/cursor/hooks/cursor-usage-receipt.js +3 -0
- package/dist/integrations/cursor/hooks/hooks.json +13 -3
- package/dist/integrations/cursor/hooks/pre-compact.js +9 -0
- package/dist/integrations/cursor/hooks/pre-compact.sh +3 -0
- package/dist/integrations/cursor/hooks/pre-tool-use.js +17 -12
- package/dist/integrations/cursor/hooks/pre-tool-use.sh +3 -0
- package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +63 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +3 -2
- package/dist/integrations/hooks/station-hook-runtime.cjs +310 -98
- package/dist/integrations/hooks/station-hook-runtime.mjs +10 -0
- package/dist/integrations/hooks/station-memory-ingest.mjs +4 -3
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/memory-routing/provider-loader.d.ts +3 -1
- package/dist/memory-routing/provider-loader.js +3 -1
- package/dist/nucleus/suit-temperament.d.ts +0 -5
- package/dist/nucleus/suit-temperament.js +13 -1
- package/dist/nucleus/thin-dispatch.js +11 -2
- package/dist/processors/implementations/agents-md-validation-processor.js +1 -1
- package/dist/skills/hermes-agent/SKILL.md +1 -1
- package/dist/skills/orchestrator/SKILL.md +23 -1
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/scripts/foundry/CHANGELOG.md +2 -0
- package/scripts/foundry/README.md +4 -4
- package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
- package/scripts/foundry/cli.mjs +1 -1
- package/scripts/foundry/inspect.mjs +160 -5
- package/scripts/foundry/plant/skills/inspect/SKILL.md +2 -0
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/foundry/validate-release-docs.mjs +48 -0
- package/scripts/foundry/version-manager.mjs +127 -7
- package/scripts/mjs/test-consumer-readiness.mjs +10 -3
- package/scripts/node/install-bridges.cjs +256 -10
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/hermes-agent/SKILL.md +1 -1
- package/src/skills/orchestrator/SKILL.md +23 -1
- package/vendor/@0xray/repertoire/data/stack-overlay.json +154 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.js +7 -6
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js +10 -11
- package/vendor/@0xray/repertoire/dist/paths.js +66 -4
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +54 -14
- package/xray/agents_template.md +10 -5
- package/xray/features.json +1 -1
- package/dist/mcps/shared/prompt-security-validator.d.ts +0 -44
- package/dist/mcps/shared/prompt-security-validator.js +0 -148
- package/dist/mcps/shared/security-scanner.d.ts +0 -119
- package/dist/mcps/shared/security-scanner.js +0 -429
- package/dist/scripts/activate-kernel-pipeline.d.ts +0 -7
- package/dist/scripts/activate-kernel-pipeline.js +0 -101
- package/src/mcps/shared/prompt-security-validator.ts +0 -199
- package/src/mcps/shared/security-scanner.ts +0 -599
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Station heat — compaction / host-swap card.
|
|
3
|
-
*
|
|
3
|
+
* STATION.md is the pickup ticket. session-boot.json is the snapshot.
|
|
4
|
+
* Heat must not paint leftover boot extras onto the card.
|
|
4
5
|
* Grok ignores SessionStart/UserPromptSubmit stdout; disk + AGENTS.md is the contract.
|
|
5
6
|
* Heat writers merge stock fields; unknown keys and ## Durable / ## Seed survive.
|
|
6
7
|
*/
|
|
@@ -104,6 +105,63 @@ function readPlanLine(root) {
|
|
|
104
105
|
return readGitSubject(root);
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
function isStockTicket(value) {
|
|
109
|
+
const text = String(value || "").trim().toLowerCase();
|
|
110
|
+
return !text || text === "(none)" || text === "(none yet)";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readStationTicketField(root, field) {
|
|
114
|
+
const md = readExistingStationMarkdown(root);
|
|
115
|
+
const re = field === "Plan" ? /^Plan:\s*(.*)$/im : /^Intent:\s*(.*)$/im;
|
|
116
|
+
const match = md.match(re);
|
|
117
|
+
if (!match) return null;
|
|
118
|
+
const value = clipIntent(match[1]);
|
|
119
|
+
if (!value || isStockTicket(value)) return null;
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function bootHeadMoved(root, existing) {
|
|
124
|
+
const live = readGitBrief(root);
|
|
125
|
+
const bootHead = existing && existing.git && existing.git.head ? String(existing.git.head) : "";
|
|
126
|
+
return Boolean(live && live.head && bootHead && bootHead !== live.head);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Live ticket beats leftover boot. Extra spoken intent still wins. */
|
|
130
|
+
function resolveHeatIntent(root, extra, existing) {
|
|
131
|
+
const incoming = clipIntent(
|
|
132
|
+
extra.intent || extra.prompt || extra.userMessage || extra.user_prompt,
|
|
133
|
+
);
|
|
134
|
+
if (incoming) return { intent: incoming, rematch: true };
|
|
135
|
+
const card = readStationTicketField(root, "Intent");
|
|
136
|
+
const pickup = clipIntent(readNotesPickup(root));
|
|
137
|
+
const bootIntent = typeof existing.intent === "string" ? clipIntent(existing.intent) : null;
|
|
138
|
+
const cardIsBootEcho = Boolean(card && bootIntent && card === bootIntent);
|
|
139
|
+
if (pickup && cardIsBootEcho && pickup !== bootIntent) {
|
|
140
|
+
return { intent: pickup, rematch: true };
|
|
141
|
+
}
|
|
142
|
+
if (card && !cardIsBootEcho) return { intent: card, rematch: card !== bootIntent };
|
|
143
|
+
if (pickup && pickup !== bootIntent) return { intent: pickup, rematch: true };
|
|
144
|
+
if (card) return { intent: card, rematch: false };
|
|
145
|
+
if (bootIntent) return { intent: bootIntent, rematch: false };
|
|
146
|
+
const git = readGitSubject(root);
|
|
147
|
+
return { intent: git, rematch: Boolean(git) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function resolveHeatPlan(root, extra, existing) {
|
|
151
|
+
const incoming = clipIntent(extra.plan || extra.planLine);
|
|
152
|
+
if (incoming) return incoming;
|
|
153
|
+
const card = readStationTicketField(root, "Plan");
|
|
154
|
+
const bootPlan = typeof existing.planLine === "string" ? clipIntent(existing.planLine) : null;
|
|
155
|
+
const live = readPlanLine(root);
|
|
156
|
+
const cardIsBootEcho = Boolean(card && bootPlan && card === bootPlan);
|
|
157
|
+
if (card && !cardIsBootEcho) return card;
|
|
158
|
+
if (bootHeadMoved(root, existing) && bootPlan && live === bootPlan) {
|
|
159
|
+
return readGitSubject(root) || card || live;
|
|
160
|
+
}
|
|
161
|
+
if (card) return card;
|
|
162
|
+
return live;
|
|
163
|
+
}
|
|
164
|
+
|
|
107
165
|
function resolveRepertoireProviderModule(root) {
|
|
108
166
|
const mr = readMemoryRoutingConfig(root);
|
|
109
167
|
if (mr && typeof mr.module_path === "string" && mr.module_path) {
|
|
@@ -200,32 +258,44 @@ function readRepertoireWorking(root) {
|
|
|
200
258
|
}
|
|
201
259
|
}
|
|
202
260
|
|
|
261
|
+
function isKeywordDestName(name) {
|
|
262
|
+
const n = String(name || "").trim().toLowerCase();
|
|
263
|
+
if (!n) return true;
|
|
264
|
+
if (n.startsWith("repo-") || n.startsWith("bedrock-")) return true;
|
|
265
|
+
if (n === "test-expansion" || n === "dead-code-removal" || n === "station-heat") return true;
|
|
266
|
+
if (/^release-v?\d/.test(n) || /^stamp-/.test(n) || /^drop-stale-/.test(n)) return true;
|
|
267
|
+
if (/\b4-0-\d{2}\b/.test(n) && /npm|package-lock|release|stamp/.test(n)) return true;
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function isGenericFieldObservedSignal(signal) {
|
|
272
|
+
const definition = String((signal && signal.definition) || "");
|
|
273
|
+
return /field-observed domain primitive/i.test(definition);
|
|
274
|
+
}
|
|
275
|
+
|
|
203
276
|
function stationSafeSignals(names) {
|
|
204
277
|
if (!Array.isArray(names)) return [];
|
|
205
278
|
const out = [];
|
|
206
279
|
for (const raw of names) {
|
|
207
280
|
const name = String(raw || "").trim();
|
|
208
|
-
if (!name) continue;
|
|
209
|
-
if (name.toLowerCase().startsWith("bedrock-")) continue;
|
|
281
|
+
if (!name || isKeywordDestName(name)) continue;
|
|
210
282
|
out.push(name);
|
|
211
283
|
if (out.length >= 4) break;
|
|
212
284
|
}
|
|
213
285
|
return out;
|
|
214
286
|
}
|
|
215
287
|
|
|
216
|
-
/**
|
|
217
|
-
function
|
|
288
|
+
/** Laws only. Hangar repo-* and git-slug keywords stay off the card. */
|
|
289
|
+
function preferLawHits(names, cap = 8) {
|
|
218
290
|
if (!Array.isArray(names)) return [];
|
|
219
291
|
const unique = [];
|
|
220
292
|
for (const raw of names) {
|
|
221
293
|
const name = String(raw || "").trim();
|
|
222
|
-
if (!name || name
|
|
294
|
+
if (!name || isKeywordDestName(name)) continue;
|
|
223
295
|
if (unique.includes(name)) continue;
|
|
224
296
|
unique.push(name);
|
|
225
297
|
}
|
|
226
|
-
|
|
227
|
-
const rest = unique.filter((name) => !name.startsWith("repo-"));
|
|
228
|
-
return [...subject, ...rest].slice(0, cap);
|
|
298
|
+
return unique.slice(0, cap);
|
|
229
299
|
}
|
|
230
300
|
|
|
231
301
|
function destSignalsPath(root) {
|
|
@@ -279,6 +349,76 @@ function signalNameSet(filePath) {
|
|
|
279
349
|
return new Set(readSignalRecords(filePath).map((signal) => String(signal.name).trim()));
|
|
280
350
|
}
|
|
281
351
|
|
|
352
|
+
function destLawNameSet(root) {
|
|
353
|
+
const names = new Set();
|
|
354
|
+
for (const fileName of ["curated_signals.json", "stack-overlay.json"]) {
|
|
355
|
+
for (const name of signalNameSet(repertoireDataFile(root, fileName))) {
|
|
356
|
+
if (!isKeywordDestName(name)) names.add(name);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const name of signalNameSet(destSignalsPath(root))) {
|
|
360
|
+
if (!isKeywordDestName(name)) names.add(name);
|
|
361
|
+
}
|
|
362
|
+
return names;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Drop hangar repo-* and git-slug keywords. Keep factory + stack laws. */
|
|
366
|
+
function pruneKeywordDest(root) {
|
|
367
|
+
const dest = destSignalsPath(root);
|
|
368
|
+
if (!dest || !existsSync(dest)) return { removed: 0, kept: 0 };
|
|
369
|
+
let data;
|
|
370
|
+
try {
|
|
371
|
+
data = JSON.parse(readFileSync(dest, "utf8"));
|
|
372
|
+
} catch {
|
|
373
|
+
return { removed: 0, kept: 0 };
|
|
374
|
+
}
|
|
375
|
+
if (!Array.isArray(data.signals)) return { removed: 0, kept: 0 };
|
|
376
|
+
const factory = signalNameSet(repertoireDataFile(root, "curated_signals.json"));
|
|
377
|
+
const stack = signalNameSet(repertoireDataFile(root, "stack-overlay.json"));
|
|
378
|
+
const kept = [];
|
|
379
|
+
let removed = 0;
|
|
380
|
+
for (const signal of data.signals) {
|
|
381
|
+
const name = String((signal && signal.name) || "").trim();
|
|
382
|
+
if (!name) continue;
|
|
383
|
+
const protectedLaw = factory.has(name) || stack.has(name);
|
|
384
|
+
if (!protectedLaw && (isKeywordDestName(name) || isGenericFieldObservedSignal(signal))) {
|
|
385
|
+
removed += 1;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
kept.push(signal);
|
|
389
|
+
}
|
|
390
|
+
if (removed) {
|
|
391
|
+
data.signals = kept;
|
|
392
|
+
data.last_updated = new Date().toISOString();
|
|
393
|
+
writeFileSync(dest, `${JSON.stringify(data, null, 2)}\n`);
|
|
394
|
+
}
|
|
395
|
+
return { removed, kept: kept.length };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const STACK_LAW_FIELDS = [
|
|
399
|
+
"definition",
|
|
400
|
+
"tags",
|
|
401
|
+
"priority",
|
|
402
|
+
"evaluation_criteria",
|
|
403
|
+
"validation_experiment",
|
|
404
|
+
"example_inference_snippet",
|
|
405
|
+
"implementation_notes",
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
/** Stack text cadences onto an existing project law. Observation stats stay. */
|
|
409
|
+
function refreshStackLaw(existing, signal) {
|
|
410
|
+
let dirty = false;
|
|
411
|
+
for (const field of STACK_LAW_FIELDS) {
|
|
412
|
+
if (signal[field] == null) continue;
|
|
413
|
+
const next = signal[field];
|
|
414
|
+
const prev = existing[field];
|
|
415
|
+
if (JSON.stringify(prev) === JSON.stringify(next)) continue;
|
|
416
|
+
existing[field] = next;
|
|
417
|
+
dirty = true;
|
|
418
|
+
}
|
|
419
|
+
return dirty;
|
|
420
|
+
}
|
|
421
|
+
|
|
282
422
|
function mergeMissingSignals(destPath, incoming) {
|
|
283
423
|
if (!incoming.length || !existsSync(destPath)) return 0;
|
|
284
424
|
let data;
|
|
@@ -288,27 +428,38 @@ function mergeMissingSignals(destPath, incoming) {
|
|
|
288
428
|
return 0;
|
|
289
429
|
}
|
|
290
430
|
if (!Array.isArray(data.signals)) return 0;
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
431
|
+
const byName = new Map();
|
|
432
|
+
for (const signal of data.signals) {
|
|
433
|
+
const name = String((signal && signal.name) || "").trim();
|
|
434
|
+
if (name) byName.set(name, signal);
|
|
435
|
+
}
|
|
436
|
+
let changed = 0;
|
|
295
437
|
for (const signal of incoming) {
|
|
296
438
|
const name = String(signal.name).trim();
|
|
297
|
-
if (!name
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
439
|
+
if (!name) continue;
|
|
440
|
+
const existing = byName.get(name);
|
|
441
|
+
if (!existing) {
|
|
442
|
+
const created = {
|
|
443
|
+
...signal,
|
|
444
|
+
name,
|
|
445
|
+
tags: Array.isArray(signal.tags) ? signal.tags : [],
|
|
446
|
+
definition: typeof signal.definition === "string" ? signal.definition : name,
|
|
447
|
+
};
|
|
448
|
+
data.signals.push(created);
|
|
449
|
+
byName.set(name, created);
|
|
450
|
+
changed += 1;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (refreshStackLaw(existing, signal)) changed += 1;
|
|
306
454
|
}
|
|
307
|
-
if (
|
|
308
|
-
|
|
455
|
+
if (changed) {
|
|
456
|
+
data.last_updated = new Date().toISOString();
|
|
457
|
+
writeFileSync(destPath, `${JSON.stringify(data, null, 2)}\n`);
|
|
458
|
+
}
|
|
459
|
+
return changed;
|
|
309
460
|
}
|
|
310
461
|
|
|
311
|
-
/** Hydrate project dest from seed + stack
|
|
462
|
+
/** Hydrate project dest from seed + stack. Subject repo-* stay off dest. */
|
|
312
463
|
function hydrateDestOnWake(root) {
|
|
313
464
|
const dest = destSignalsPath(root);
|
|
314
465
|
const seed = repertoireDataFile(root, "curated_signals.json");
|
|
@@ -317,10 +468,12 @@ function hydrateDestOnWake(root) {
|
|
|
317
468
|
copyFileSync(seed, dest);
|
|
318
469
|
}
|
|
319
470
|
if (!existsSync(dest)) return { dest: null, added: 0, destCount: 0 };
|
|
320
|
-
const added =
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
471
|
+
const added = mergeMissingSignals(
|
|
472
|
+
dest,
|
|
473
|
+
readSignalRecords(repertoireDataFile(root, "stack-overlay.json")),
|
|
474
|
+
);
|
|
475
|
+
const pruned = pruneKeywordDest(root);
|
|
476
|
+
return { dest, added, destCount: countCuratedSignals(dest) || 0, pruned };
|
|
324
477
|
}
|
|
325
478
|
|
|
326
479
|
function readNotesPickup(root) {
|
|
@@ -405,7 +558,12 @@ function sleepMs(ms) {
|
|
|
405
558
|
/** Parallel live contexts share one project copy. Exclusive create; stale after 60s. */
|
|
406
559
|
function withDestLock(root, fn) {
|
|
407
560
|
const lockPath = destLockPath(root);
|
|
408
|
-
|
|
561
|
+
try {
|
|
562
|
+
mkdirSync(join(root, ".xray", "state", "repertoire"), { recursive: true });
|
|
563
|
+
} catch (err) {
|
|
564
|
+
if (err && (err.code === "EACCES" || err.code === "EPERM")) return null;
|
|
565
|
+
throw err;
|
|
566
|
+
}
|
|
409
567
|
const started = Date.now();
|
|
410
568
|
while (Date.now() - started < 50000) {
|
|
411
569
|
try {
|
|
@@ -466,42 +624,18 @@ function stripConventionalSubject(message) {
|
|
|
466
624
|
.trim();
|
|
467
625
|
}
|
|
468
626
|
|
|
469
|
-
/**
|
|
470
|
-
function patternsFromGit(root, commits
|
|
627
|
+
/** Observe existing laws mentioned in git. Do not mint commit slugs. */
|
|
628
|
+
function patternsFromGit(root, commits) {
|
|
629
|
+
const text = (Array.isArray(commits) ? commits : [])
|
|
630
|
+
.map((row) => String((row && row.message) || ""))
|
|
631
|
+
.join("\n")
|
|
632
|
+
.toLowerCase();
|
|
471
633
|
const patterns = [];
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
seen.add(slug);
|
|
477
|
-
patterns.push({ name: slug, confidence, description: description || String(raw) });
|
|
478
|
-
};
|
|
479
|
-
for (const row of commits) {
|
|
480
|
-
add(stripConventionalSubject(row.message), 0.55, row.message);
|
|
481
|
-
}
|
|
482
|
-
try {
|
|
483
|
-
const from = span && span.from ? String(span.from) : "";
|
|
484
|
-
const to = span && span.to ? String(span.to) : "HEAD";
|
|
485
|
-
if (from && !from.startsWith("HEAD~")) {
|
|
486
|
-
const out = execFileSync("git", ["diff", "--name-status", `${from}..${to}`], {
|
|
487
|
-
cwd: root,
|
|
488
|
-
encoding: "utf8",
|
|
489
|
-
timeout: 4000,
|
|
490
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
491
|
-
});
|
|
492
|
-
const lines = String(out).split("\n").filter(Boolean);
|
|
493
|
-
if (lines.some((line) => /\.(test|spec)\.[jt]sx?|__tests__|\/tests\//.test(line))) {
|
|
494
|
-
add("test-expansion", 0.55, "git test files");
|
|
495
|
-
}
|
|
496
|
-
if (lines.some((line) => /^D[\t ]/.test(line))) {
|
|
497
|
-
add("dead-code-removal", 0.55, "git deletions");
|
|
498
|
-
}
|
|
499
|
-
if (lines.some((line) => /hooks\/station|session-capture|semantic-patterns/.test(line))) {
|
|
500
|
-
add("station-heat", 0.55, "heat path");
|
|
501
|
-
}
|
|
634
|
+
if (!text.trim()) return patterns;
|
|
635
|
+
for (const name of destLawNameSet(root)) {
|
|
636
|
+
if (text.includes(name) || text.includes(name.replace(/-/g, " "))) {
|
|
637
|
+
patterns.push({ name, confidence: 0.7, description: "observe existing law from git" });
|
|
502
638
|
}
|
|
503
|
-
} catch {
|
|
504
|
-
/* structural names are extra — commit slugs still grow dest */
|
|
505
639
|
}
|
|
506
640
|
return patterns;
|
|
507
641
|
}
|
|
@@ -521,16 +655,22 @@ function latestSessionNewerThanGrow(root) {
|
|
|
521
655
|
|
|
522
656
|
/** Capture then grow. Skip-path for floors that already have a live card. */
|
|
523
657
|
function heatLiveMemory(root) {
|
|
524
|
-
|
|
525
|
-
|
|
658
|
+
try {
|
|
659
|
+
const mr = readMemoryRoutingConfig(root);
|
|
660
|
+
if (isExplicitMemoryRoutingOptOut(mr)) {
|
|
661
|
+
return { captured: null, grow: null };
|
|
662
|
+
}
|
|
663
|
+
const result = withDestLock(root, () => {
|
|
664
|
+
const captured = maybeCaptureSessionOnHeadMove(root);
|
|
665
|
+
const grow = captured || latestSessionNewerThanGrow(root) ? growDestOnWake(root) : null;
|
|
666
|
+
if (grow) persistRepertoireWorking(root, { grow });
|
|
667
|
+
return { captured, grow };
|
|
668
|
+
});
|
|
669
|
+
return result || { captured: null, grow: null };
|
|
670
|
+
} catch {
|
|
671
|
+
/* dest EACCES / unwritable heat root must not deny the host tool */
|
|
526
672
|
return { captured: null, grow: null };
|
|
527
673
|
}
|
|
528
|
-
return withDestLock(root, () => {
|
|
529
|
-
const captured = maybeCaptureSessionOnHeadMove(root);
|
|
530
|
-
const grow = captured || latestSessionNewerThanGrow(root) ? growDestOnWake(root) : null;
|
|
531
|
-
if (grow) persistRepertoireWorking(root, { grow });
|
|
532
|
-
return { captured, grow };
|
|
533
|
-
});
|
|
534
674
|
}
|
|
535
675
|
|
|
536
676
|
/** Every floor that heats. Dedup per HEAD. Patterns come from git, not a leftover catalog. */
|
|
@@ -556,6 +696,7 @@ function maybeCaptureSessionOnHeadMove(root) {
|
|
|
556
696
|
from: stamp && stamp.head ? stamp.head : commits[commits.length - 1] ? commits[commits.length - 1].hash : git.head,
|
|
557
697
|
to: git.head,
|
|
558
698
|
};
|
|
699
|
+
const patterns = patternsFromGit(root, commits, span);
|
|
559
700
|
const session = {
|
|
560
701
|
sessionId,
|
|
561
702
|
timestamp: new Date().toISOString(),
|
|
@@ -564,8 +705,8 @@ function maybeCaptureSessionOnHeadMove(root) {
|
|
|
564
705
|
approaches,
|
|
565
706
|
wrongTurns: [],
|
|
566
707
|
solutions: [],
|
|
567
|
-
patterns
|
|
568
|
-
matched_primitives:
|
|
708
|
+
patterns,
|
|
709
|
+
matched_primitives: preferLawHits(patterns.map((row) => row && row.name)),
|
|
569
710
|
metrics: { commits: commits.length },
|
|
570
711
|
};
|
|
571
712
|
const outDir = join(root, "docs", "inference");
|
|
@@ -648,7 +789,17 @@ function growDestOnWake(root) {
|
|
|
648
789
|
});
|
|
649
790
|
const line = String(out).trim().split("\n").filter(Boolean).at(-1) || "{}";
|
|
650
791
|
const parsed = JSON.parse(line);
|
|
651
|
-
|
|
792
|
+
const pruned = pruneKeywordDest(root);
|
|
793
|
+
const destCount = countCuratedSignals(destSignalsPath(root)) || 0;
|
|
794
|
+
if (!parsed || typeof parsed !== "object") return { pruned: pruned.removed, destCount };
|
|
795
|
+
const heated = Array.isArray(parsed.heated) ? parsed.heated.length : 0;
|
|
796
|
+
return {
|
|
797
|
+
...parsed,
|
|
798
|
+
observed: heated,
|
|
799
|
+
pruned: pruned.removed,
|
|
800
|
+
destCount,
|
|
801
|
+
after: destCount,
|
|
802
|
+
};
|
|
652
803
|
} catch {
|
|
653
804
|
return null;
|
|
654
805
|
}
|
|
@@ -679,6 +830,53 @@ function isCompactHook(extra) {
|
|
|
679
830
|
return /compact/i.test(hook);
|
|
680
831
|
}
|
|
681
832
|
|
|
833
|
+
function isCompactEventName(value) {
|
|
834
|
+
return /compact/i.test(String(value || ""));
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** Keep Compact boot fields after later preToolUse / HEAD rewrite. */
|
|
838
|
+
function retainCompactFields(existing, extra = {}) {
|
|
839
|
+
const incomingHook = extra && (extra.hookEvent || extra.source || extra.hook);
|
|
840
|
+
if (isCompactEventName(incomingHook) || (extra && extra.event_class)) {
|
|
841
|
+
const kept = {};
|
|
842
|
+
if (extra.hookEvent) kept.hookEvent = extra.hookEvent;
|
|
843
|
+
if (extra.conversation_id) kept.conversation_id = extra.conversation_id;
|
|
844
|
+
if (extra.generation_id) kept.generation_id = extra.generation_id;
|
|
845
|
+
if (extra.event_class) kept.event_class = extra.event_class;
|
|
846
|
+
return kept;
|
|
847
|
+
}
|
|
848
|
+
const prior = existing && typeof existing === "object" ? existing : {};
|
|
849
|
+
if (!isCompactEventName(prior.hookEvent) && !prior.event_class) return {};
|
|
850
|
+
const kept = {};
|
|
851
|
+
if (prior.hookEvent) kept.hookEvent = prior.hookEvent;
|
|
852
|
+
if (prior.conversation_id) kept.conversation_id = prior.conversation_id;
|
|
853
|
+
if (prior.generation_id) kept.generation_id = prior.generation_id;
|
|
854
|
+
if (prior.event_class) kept.event_class = prior.event_class;
|
|
855
|
+
return kept;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** Persist heated length. A number stays a number. An array is a count. Never drop a number to 0. */
|
|
859
|
+
function workingGrowSnapshot(grow) {
|
|
860
|
+
if (!grow || typeof grow !== "object") return null;
|
|
861
|
+
if (typeof grow.after !== "number") {
|
|
862
|
+
return typeof grow.skipped === "string" ? grow.skipped : null;
|
|
863
|
+
}
|
|
864
|
+
const observed =
|
|
865
|
+
typeof grow.observed === "number"
|
|
866
|
+
? grow.observed
|
|
867
|
+
: Array.isArray(grow.observed)
|
|
868
|
+
? grow.observed.length
|
|
869
|
+
: Array.isArray(grow.heated)
|
|
870
|
+
? grow.heated.length
|
|
871
|
+
: 0;
|
|
872
|
+
return {
|
|
873
|
+
before: grow.before,
|
|
874
|
+
after: grow.after,
|
|
875
|
+
imported: grow.imported,
|
|
876
|
+
observed,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
682
880
|
function formatWorkingLine(working) {
|
|
683
881
|
if (!working || typeof working !== "object") return null;
|
|
684
882
|
const matched = stationSafeSignals(working.matchedSignals);
|
|
@@ -694,7 +892,7 @@ function buildRepertoireResume(root) {
|
|
|
694
892
|
const modulePath = resolveRepertoireProviderModule(root);
|
|
695
893
|
const mr = readMemoryRoutingConfig(root);
|
|
696
894
|
if (!modulePath) {
|
|
697
|
-
return "Repertoire:
|
|
895
|
+
return "Repertoire: module unresolved";
|
|
698
896
|
}
|
|
699
897
|
const destPath = destSignalsPath(root);
|
|
700
898
|
let signalsPath = existsSync(destPath) ? destPath : mr.config && mr.config.signalsPath;
|
|
@@ -744,14 +942,12 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
744
942
|
? existing.hotSwap
|
|
745
943
|
: null;
|
|
746
944
|
const hotSwap = nextSwap || keptSwap;
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const intent = incomingIntent || (typeof existing.intent === "string" ? existing.intent : null);
|
|
945
|
+
const resolved = resolveHeatIntent(root, extra, existing);
|
|
946
|
+
const intent = resolved.intent;
|
|
947
|
+
const rematch = resolved.rematch;
|
|
751
948
|
const matchText = clipIntent([intent, pickup, approaches].filter(Boolean).join(" "));
|
|
752
|
-
const rematch = Boolean(incomingIntent);
|
|
753
949
|
const git = readGitBrief(root);
|
|
754
|
-
const planLine =
|
|
950
|
+
const planLine = resolveHeatPlan(root, extra, existing);
|
|
755
951
|
const repertoireResume =
|
|
756
952
|
typeof extra.repertoireResume === "string" ? extra.repertoireResume : buildRepertoireResume(root);
|
|
757
953
|
const swapBit = hotSwap ? `hot-swap ${hotSwap.from} → ${hotSwap.to}` : `host ${host}`;
|
|
@@ -760,7 +956,7 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
760
956
|
const planBit = planLine ? `plan: ${planLine}` : "plan: (none)";
|
|
761
957
|
let matchedSignals = [];
|
|
762
958
|
if (Array.isArray(extra.matchedSignals) && extra.matchedSignals.length) {
|
|
763
|
-
matchedSignals =
|
|
959
|
+
matchedSignals = preferLawHits(extra.matchedSignals, 8);
|
|
764
960
|
}
|
|
765
961
|
const priorWorking = readRepertoireWorking(root);
|
|
766
962
|
const destCount =
|
|
@@ -776,10 +972,10 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
776
972
|
Array.isArray(priorWorking.matchedSignals) &&
|
|
777
973
|
priorWorking.matchedSignals.length
|
|
778
974
|
) {
|
|
779
|
-
matchedSignals =
|
|
975
|
+
matchedSignals = preferLawHits(priorWorking.matchedSignals, 8);
|
|
780
976
|
} else {
|
|
781
|
-
matchedSignals =
|
|
782
|
-
matchStationSignalsSync(root, rematch &&
|
|
977
|
+
matchedSignals = preferLawHits(
|
|
978
|
+
matchStationSignalsSync(root, rematch && intent ? intent : matchText),
|
|
783
979
|
8,
|
|
784
980
|
);
|
|
785
981
|
}
|
|
@@ -787,32 +983,26 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
787
983
|
if (!matchedSignals.length && priorWorking) {
|
|
788
984
|
matchedSignals = stationSafeSignals(priorWorking.matchedSignals);
|
|
789
985
|
}
|
|
986
|
+
const compactHold = retainCompactFields(existing, extra);
|
|
987
|
+
const nextHook = compactHold.hookEvent || extra.hookEvent || extra.source || null;
|
|
790
988
|
const workingSnapshot = {
|
|
791
989
|
host,
|
|
792
990
|
intent,
|
|
793
991
|
git,
|
|
794
992
|
hotSwap,
|
|
795
|
-
hookEvent: extra.hookEvent || extra.source || null,
|
|
796
993
|
memoryRouting: repertoireResume.startsWith("Repertoire: on") ? "on" : "off",
|
|
797
994
|
repertoireResume,
|
|
798
995
|
destCount,
|
|
799
996
|
};
|
|
997
|
+
if (nextHook) workingSnapshot.hookEvent = nextHook;
|
|
800
998
|
if (pickup) workingSnapshot.pickup = pickup;
|
|
801
999
|
if (matchText) workingSnapshot.matchText = matchText;
|
|
802
1000
|
if (captured) workingSnapshot.sessionCapture = captured;
|
|
803
|
-
|
|
804
|
-
if (
|
|
805
|
-
workingSnapshot.grow = {
|
|
806
|
-
before: grow.before,
|
|
807
|
-
after: grow.after,
|
|
808
|
-
imported: grow.imported,
|
|
809
|
-
observed: Array.isArray(grow.observed) ? grow.observed.length : 0,
|
|
810
|
-
};
|
|
811
|
-
}
|
|
1001
|
+
const growReceipt = workingGrowSnapshot(grow);
|
|
1002
|
+
if (growReceipt) workingSnapshot.grow = growReceipt;
|
|
812
1003
|
if (matchedSignals.length) workingSnapshot.matchedSignals = matchedSignals.slice(0, 8);
|
|
813
1004
|
const opProcNames = readOpProcNames(root);
|
|
814
1005
|
if (opProcNames.length) workingSnapshot.opProcNames = opProcNames;
|
|
815
|
-
const working = persistRepertoireWorking(root, workingSnapshot);
|
|
816
1006
|
const pickupChanged = Boolean(pickup && (!priorWorking || priorWorking.pickup !== pickup));
|
|
817
1007
|
if (
|
|
818
1008
|
!memoryOff &&
|
|
@@ -826,6 +1016,11 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
826
1016
|
matchedSignals,
|
|
827
1017
|
);
|
|
828
1018
|
}
|
|
1019
|
+
if (!memoryOff) {
|
|
1020
|
+
const latePrune = pruneKeywordDest(root);
|
|
1021
|
+
if (latePrune.kept) workingSnapshot.destCount = latePrune.kept;
|
|
1022
|
+
}
|
|
1023
|
+
const working = persistRepertoireWorking(root, workingSnapshot);
|
|
829
1024
|
const workingLine = formatWorkingLine(working);
|
|
830
1025
|
const workingBit = workingLine ? workingLine : "working: (none)";
|
|
831
1026
|
const stationLine = `${swapBit}. ${intentBit}. ${planBit}. ${gitBit}. ${repertoireResume}. ${workingBit}`;
|
|
@@ -838,6 +1033,7 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
838
1033
|
repertoireResume,
|
|
839
1034
|
workingLine,
|
|
840
1035
|
stationLine,
|
|
1036
|
+
...compactHold,
|
|
841
1037
|
};
|
|
842
1038
|
}
|
|
843
1039
|
|
|
@@ -884,6 +1080,12 @@ function stationBootNeedsRefresh(existing, root, host) {
|
|
|
884
1080
|
const liveResume = buildRepertoireResume(root);
|
|
885
1081
|
if (liveResume && existing.repertoireResume && liveResume !== existing.repertoireResume) return true;
|
|
886
1082
|
if (stationDurableHoldsNpm(root)) return true;
|
|
1083
|
+
const cardIntent = readStationTicketField(root, "Intent");
|
|
1084
|
+
const bootIntent = typeof existing.intent === "string" ? clipIntent(existing.intent) : null;
|
|
1085
|
+
if (cardIntent && bootIntent && cardIntent !== bootIntent) return true;
|
|
1086
|
+
const cardPlan = readStationTicketField(root, "Plan");
|
|
1087
|
+
const bootPlan = typeof existing.planLine === "string" ? clipIntent(existing.planLine) : null;
|
|
1088
|
+
if (cardPlan && bootPlan && cardPlan !== bootPlan) return true;
|
|
887
1089
|
return false;
|
|
888
1090
|
}
|
|
889
1091
|
|
|
@@ -965,7 +1167,7 @@ function formatStationMarkdown(fields) {
|
|
|
965
1167
|
} else {
|
|
966
1168
|
lines.push("Git: n/a");
|
|
967
1169
|
}
|
|
968
|
-
lines.push(fields.repertoireResume || "Repertoire:
|
|
1170
|
+
lines.push(fields.repertoireResume || "Repertoire: module unresolved");
|
|
969
1171
|
if (fields.workingLine) {
|
|
970
1172
|
lines.push(fields.workingLine);
|
|
971
1173
|
}
|
|
@@ -1022,7 +1224,12 @@ module.exports = {
|
|
|
1022
1224
|
readNotesPickup,
|
|
1023
1225
|
maybeCaptureSessionOnHeadMove,
|
|
1024
1226
|
formatWorkingLine,
|
|
1227
|
+
workingGrowSnapshot,
|
|
1025
1228
|
applyStationHeat,
|
|
1229
|
+
isStockTicket,
|
|
1230
|
+
readStationTicketField,
|
|
1231
|
+
resolveHeatIntent,
|
|
1232
|
+
resolveHeatPlan,
|
|
1026
1233
|
extractPreservedStationLines,
|
|
1027
1234
|
mergeStationMarkdown,
|
|
1028
1235
|
formatStationMarkdown,
|
|
@@ -1030,4 +1237,9 @@ module.exports = {
|
|
|
1030
1237
|
isHoldNpmLine,
|
|
1031
1238
|
stationDurableHoldsNpm,
|
|
1032
1239
|
stationBootNeedsRefresh,
|
|
1240
|
+
isKeywordDestName,
|
|
1241
|
+
pruneKeywordDest,
|
|
1242
|
+
preferLawHits,
|
|
1243
|
+
retainCompactFields,
|
|
1244
|
+
destLawNameSet,
|
|
1033
1245
|
};
|
|
@@ -28,7 +28,12 @@ export const patternsFromGit = impl.patternsFromGit;
|
|
|
28
28
|
export const readNotesPickup = impl.readNotesPickup;
|
|
29
29
|
export const maybeCaptureSessionOnHeadMove = impl.maybeCaptureSessionOnHeadMove;
|
|
30
30
|
export const formatWorkingLine = impl.formatWorkingLine;
|
|
31
|
+
export const workingGrowSnapshot = impl.workingGrowSnapshot;
|
|
31
32
|
export const applyStationHeat = impl.applyStationHeat;
|
|
33
|
+
export const isStockTicket = impl.isStockTicket;
|
|
34
|
+
export const readStationTicketField = impl.readStationTicketField;
|
|
35
|
+
export const resolveHeatIntent = impl.resolveHeatIntent;
|
|
36
|
+
export const resolveHeatPlan = impl.resolveHeatPlan;
|
|
32
37
|
export const extractPreservedStationLines = impl.extractPreservedStationLines;
|
|
33
38
|
export const mergeStationMarkdown = impl.mergeStationMarkdown;
|
|
34
39
|
export const formatStationMarkdown = impl.formatStationMarkdown;
|
|
@@ -36,3 +41,8 @@ export const writeStationMarkdown = impl.writeStationMarkdown;
|
|
|
36
41
|
export const isHoldNpmLine = impl.isHoldNpmLine;
|
|
37
42
|
export const stationDurableHoldsNpm = impl.stationDurableHoldsNpm;
|
|
38
43
|
export const stationBootNeedsRefresh = impl.stationBootNeedsRefresh;
|
|
44
|
+
export const isKeywordDestName = impl.isKeywordDestName;
|
|
45
|
+
export const pruneKeywordDest = impl.pruneKeywordDest;
|
|
46
|
+
export const preferLawHits = impl.preferLawHits;
|
|
47
|
+
export const retainCompactFields = impl.retainCompactFields;
|
|
48
|
+
export const destLawNameSet = impl.destLawNameSet;
|
|
@@ -118,7 +118,6 @@ async function growDest(projectRoot) {
|
|
|
118
118
|
ensureDestShape(destSignals);
|
|
119
119
|
const before = service.signalsManager.load().signals.length;
|
|
120
120
|
const sessions = service.syncXrayMemory();
|
|
121
|
-
const siblings = service.syncWorkspaceRepos();
|
|
122
121
|
const kernel =
|
|
123
122
|
typeof pathsMod.collectKernelDiaryText === 'function'
|
|
124
123
|
? pathsMod.collectKernelDiaryText(projectRoot)
|
|
@@ -129,13 +128,15 @@ async function growDest(projectRoot) {
|
|
|
129
128
|
sources: [...(kernel.sources || []), ...cleanup.sources],
|
|
130
129
|
});
|
|
131
130
|
const after = service.signalsManager.load().signals.length;
|
|
131
|
+
const heatedNames = Array.isArray(diary.heated) ? diary.heated : [];
|
|
132
132
|
process.stdout.write(
|
|
133
133
|
`${JSON.stringify({
|
|
134
134
|
before,
|
|
135
135
|
after,
|
|
136
136
|
imported: sessions.imported,
|
|
137
|
-
observed:
|
|
138
|
-
heated:
|
|
137
|
+
observed: heatedNames.length,
|
|
138
|
+
heated: heatedNames,
|
|
139
|
+
destCount: after,
|
|
139
140
|
})}\n`,
|
|
140
141
|
);
|
|
141
142
|
return;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { MemoryRoutingConfig, MemoryRoutingProvider } from './types.js';
|
|
2
2
|
export declare function isExplicitMemoryRoutingOptOut(config: MemoryRoutingConfig): boolean;
|
|
3
|
-
/** Leftover shipped default: enabled false + provider null. Not an explicit opt-out.
|
|
3
|
+
/** Leftover shipped default: enabled false + provider null. Not an explicit opt-out.
|
|
4
|
+
* resolveLeftoverEnabledConfig migrates old tarballs when the organ is on disk.
|
|
5
|
+
* Explicit opt-out is enabled: false + provider: repertoire. */
|
|
4
6
|
export declare function isLeftoverMemoryRoutingOff(config: MemoryRoutingConfig): boolean;
|
|
5
7
|
export declare function resolveLeftoverEnabledConfig(config: MemoryRoutingConfig, cwd: string): MemoryRoutingConfig | null;
|
|
6
8
|
export declare function loadMemoryRoutingProvider(config: MemoryRoutingConfig, cwd?: string): Promise<MemoryRoutingProvider>;
|