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,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Foundry stamper — JSON version fields
|
|
4
|
+
* Foundry stamper — JSON version fields, CHANGELOG, and the present-tense patch-pin strip.
|
|
5
5
|
*
|
|
6
|
-
* Does NOT bump package.json (reconcile-version.mjs
|
|
7
|
-
* Does NOT
|
|
6
|
+
* Does NOT bump package.json (reconcile-version.mjs advances the next cut).
|
|
7
|
+
* Does NOT publish.
|
|
8
|
+
* Strips every present-tense patch pin from shipped guides and shipped OP-PROC.
|
|
9
|
+
* Does NOT edit Station, NOTES, dest, or node_modules.
|
|
8
10
|
*
|
|
9
11
|
* Usage:
|
|
10
12
|
* npx @0xray/foundry stamp
|
|
@@ -285,7 +287,7 @@ function updateChangelog(newVersion, changeDescription) {
|
|
|
285
287
|
console.log(`✅ Updated CHANGELOG.md`);
|
|
286
288
|
}
|
|
287
289
|
|
|
288
|
-
/**
|
|
290
|
+
/** Kernel headers stay era text. Patch refs are stripped from the shipped set below. */
|
|
289
291
|
|
|
290
292
|
export const DOCS_SITE_HEADER_FILES = [
|
|
291
293
|
'docs-site/docs/index.md',
|
|
@@ -328,6 +330,7 @@ function updateJsonVersionField(relPath, newVersion) {
|
|
|
328
330
|
|
|
329
331
|
function updateFeaturesJsonVersion(newVersion) {
|
|
330
332
|
updateJsonVersionField('xray/features.json', newVersion);
|
|
333
|
+
updateJsonVersionField('.xray/features.json', newVersion);
|
|
331
334
|
}
|
|
332
335
|
|
|
333
336
|
function updateOpenclawPluginVersion(newVersion) {
|
|
@@ -339,6 +342,117 @@ function getCurrentVersion() {
|
|
|
339
342
|
return pkg.version;
|
|
340
343
|
}
|
|
341
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Shipped guides whose present-tense patch ref the stamper removes.
|
|
347
|
+
* Historical headings (features-since) stay. Seat memory is not in this list.
|
|
348
|
+
*/
|
|
349
|
+
export const SHIPPED_GUIDE_STRIP_FILES = [
|
|
350
|
+
'README.md',
|
|
351
|
+
'AGENTS.md',
|
|
352
|
+
'AGENTS-consumer.md',
|
|
353
|
+
'SKILLS.md',
|
|
354
|
+
'llms.txt',
|
|
355
|
+
'docs-site/docs/index.md',
|
|
356
|
+
'docs-site/docs/introduction.md',
|
|
357
|
+
'docs-site/docs/full-reference.md',
|
|
358
|
+
'docs-site/docs/architecture/v4-now.md',
|
|
359
|
+
'docs-site/docs/architecture/v4-vision.md',
|
|
360
|
+
'docs-site/docs/guides/memory-wake.md',
|
|
361
|
+
'docs-site/docs/guides/memory-routing.md',
|
|
362
|
+
'docs-site/docs/guides/repertoire.md',
|
|
363
|
+
'docs-site/docs/guides/station-vs-repertoire.md',
|
|
364
|
+
'docs-site/docs/guides/getting-started.md',
|
|
365
|
+
'docs-site/docs/guides/integrations.md',
|
|
366
|
+
'docs-site/docs/guides/consumer-migration.md',
|
|
367
|
+
'docs-site/docs/guides/aside-context.md',
|
|
368
|
+
'docs-site/docs/mcp/README.md',
|
|
369
|
+
'docs-site/docs/agents/README.md',
|
|
370
|
+
'grok-bot/AGENTS.md',
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
/** Shipped OP-PROC. Station, NOTES, and dest are not procedure files the stamper may edit. */
|
|
374
|
+
export const SHIPPED_OP_PROC_STRIP_FILES = [
|
|
375
|
+
'grok-bot/ops/LEAD-CADENCE.md',
|
|
376
|
+
'src/skills/orchestrator/SKILL.md',
|
|
377
|
+
'grok-bot/skills/ship-ready-mill-gate/SKILL.md',
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
export function patchRefStripRelPaths() {
|
|
381
|
+
return [...SHIPPED_GUIDE_STRIP_FILES, ...SHIPPED_OP_PROC_STRIP_FILES];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Seat-local memory and installed trees. The stamper never writes these. */
|
|
385
|
+
export function isPatchRefStripRefused(relPath) {
|
|
386
|
+
const norm = path.posix.normalize(String(relPath).replace(/\\/g, '/')).replace(/^\.\//, '');
|
|
387
|
+
const parts = norm.split('/').filter((part) => part !== '' && part !== '.');
|
|
388
|
+
if (parts[parts.length - 1] === 'NOTES.md') return true;
|
|
389
|
+
if (parts.includes('node_modules')) return true;
|
|
390
|
+
const xrayAt = parts.indexOf('.xray');
|
|
391
|
+
if (xrayAt !== -1 && parts[xrayAt + 1] === 'state') return true;
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Remove every present-tense patch pin from prose. Stamps in CHANGELOG
|
|
397
|
+
* headings stay, because this does not match `## [x.y.z]`.
|
|
398
|
+
* `version` must be semver or the call is a no-op. The pins removed are
|
|
399
|
+
* not limited to that version.
|
|
400
|
+
* @param {string} content
|
|
401
|
+
* @param {string} version
|
|
402
|
+
*/
|
|
403
|
+
export function stripPatchRefText(content, version) {
|
|
404
|
+
if (typeof version !== 'string' || !/^\d+\.\d+\.\d+$/.test(version)) return content;
|
|
405
|
+
const semver = '\\d+\\.\\d+\\.\\d+';
|
|
406
|
+
const patterns = [
|
|
407
|
+
new RegExp(`0xray@${semver}\\b`, 'g'),
|
|
408
|
+
new RegExp(`npm is \\*\\*${semver}\\*\\*`, 'g'),
|
|
409
|
+
new RegExp(`Product \\*\\*${semver}\\*\\* is on npm`, 'g'),
|
|
410
|
+
new RegExp(`Do not republish ${semver}\\b`, 'g'),
|
|
411
|
+
new RegExp(`This cut is \\*\\*${semver}\\*\\*`, 'g'),
|
|
412
|
+
new RegExp(`This cut is ${semver}\\b`, 'g'),
|
|
413
|
+
];
|
|
414
|
+
let next = content;
|
|
415
|
+
for (const re of patterns) next = next.replace(re, '');
|
|
416
|
+
return next.replace(/[ \t]{2,}/g, ' ');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* @param {string} baseDir
|
|
421
|
+
* @param {string} rel
|
|
422
|
+
* @param {string} version
|
|
423
|
+
* @returns {boolean} true when the file changed
|
|
424
|
+
*/
|
|
425
|
+
export function stripPatchRefFile(baseDir, rel, version) {
|
|
426
|
+
if (isPatchRefStripRefused(rel)) return false;
|
|
427
|
+
const full = path.resolve(baseDir, rel);
|
|
428
|
+
const relToBase = path.relative(baseDir, full);
|
|
429
|
+
if (relToBase.startsWith('..') || path.isAbsolute(relToBase)) return false;
|
|
430
|
+
if (isPatchRefStripRefused(relToBase.split(path.sep).join('/'))) return false;
|
|
431
|
+
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) return false;
|
|
432
|
+
const before = fs.readFileSync(full, 'utf8');
|
|
433
|
+
const after = stripPatchRefText(before, version);
|
|
434
|
+
if (after === before) return false;
|
|
435
|
+
fs.writeFileSync(full, after);
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Strip every present-tense patch pin from shipped guides and shipped OP-PROC under baseDir.
|
|
441
|
+
* `version` must be semver or the call is a no-op.
|
|
442
|
+
* Refuses `.xray/state/**`, NOTES, dest curated_signals.json, and node_modules.
|
|
443
|
+
* Does not bump. Does not publish.
|
|
444
|
+
* @param {string} baseDir
|
|
445
|
+
* @param {string} version
|
|
446
|
+
* @returns {string[]} relative paths that changed
|
|
447
|
+
*/
|
|
448
|
+
export function stripLivePatchRefs(baseDir, version) {
|
|
449
|
+
const changed = [];
|
|
450
|
+
for (const rel of patchRefStripRelPaths()) {
|
|
451
|
+
if (stripPatchRefFile(baseDir, rel, version)) changed.push(rel);
|
|
452
|
+
}
|
|
453
|
+
return changed;
|
|
454
|
+
}
|
|
455
|
+
|
|
342
456
|
/** Paths written by release artifact updates (existing files only). */
|
|
343
457
|
export function getReleaseArtifactPaths(baseDir = resolveMillRoot()) {
|
|
344
458
|
const candidates = [
|
|
@@ -374,16 +488,20 @@ export function getReleaseArtifactPaths(baseDir = resolveMillRoot()) {
|
|
|
374
488
|
return candidates.filter((rel) => fs.existsSync(path.join(baseDir, rel)));
|
|
375
489
|
}
|
|
376
490
|
|
|
377
|
-
/**
|
|
491
|
+
/** Stamp JSON + CHANGELOG, then strip every present-tense patch pin from shipped guides and OP-PROC. No bump. No publish. */
|
|
378
492
|
function updateReleaseArtifactsOnly(changeDescription = '') {
|
|
379
493
|
const current = getCurrentVersion();
|
|
380
494
|
const counts = getFrameworkCounts();
|
|
381
|
-
process.stdout.write(`Release artifacts for v${current} (JSON + CHANGELOG
|
|
495
|
+
process.stdout.write(`Release artifacts for v${current} (JSON + CHANGELOG; strip shipped guides and OP-PROC)\n`);
|
|
382
496
|
process.stdout.write(`counts: ${counts.agents} agents, ${counts.mcps} MCPs, ${counts.skills} skills\n`);
|
|
383
497
|
updateChangelog(current, changeDescription);
|
|
384
498
|
updatePluginJsonVersion(current);
|
|
385
499
|
updateFeaturesJsonVersion(current);
|
|
386
500
|
updateOpenclawPluginVersion(current);
|
|
501
|
+
const stripped = stripLivePatchRefs(rootDir, current);
|
|
502
|
+
if (stripped.length > 0) {
|
|
503
|
+
process.stdout.write(`Stripped present-tense patch pins from ${stripped.join(', ')}\n`);
|
|
504
|
+
}
|
|
387
505
|
runReleaseDocsValidation();
|
|
388
506
|
process.stdout.write(`Release artifacts updated for v${current}\n`);
|
|
389
507
|
}
|
|
@@ -415,7 +533,9 @@ function main() {
|
|
|
415
533
|
if (args.includes('--help') || args.includes('-h')) {
|
|
416
534
|
process.stdout.write(`Current version: ${getCurrentVersion()}\n`);
|
|
417
535
|
process.stdout.write('Usage: npx @0xray/foundry stamp\n');
|
|
418
|
-
process.stdout.write('Bump is refused.
|
|
536
|
+
process.stdout.write('Bump is refused. Reconcile advances the next cut, then this stamper runs.\n');
|
|
537
|
+
process.stdout.write('The stamper strips every present-tense patch pin from shipped guides and shipped OP-PROC.\n');
|
|
538
|
+
process.stdout.write('It does not edit Station, NOTES, dest, or node_modules. It does not publish.\n');
|
|
419
539
|
process.exit(0);
|
|
420
540
|
}
|
|
421
541
|
|
|
@@ -17,9 +17,16 @@ const __dirname = path.dirname(__filename);
|
|
|
17
17
|
class ConsumerReadinessCheck {
|
|
18
18
|
constructor() {
|
|
19
19
|
this.checks = [];
|
|
20
|
-
//
|
|
20
|
+
// Mill = this repo (package name 0xray + src/). Consumer = installed tarball.
|
|
21
21
|
const cwd = process.cwd();
|
|
22
|
-
|
|
22
|
+
let isDevMill = false;
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
25
|
+
isDevMill = pkg.name === "0xray" && fs.existsSync(path.join(cwd, "src"));
|
|
26
|
+
} catch {
|
|
27
|
+
isDevMill = false;
|
|
28
|
+
}
|
|
29
|
+
this.isConsumerEnvironment = !isDevMill;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
async runChecks() {
|
|
@@ -78,7 +85,7 @@ class ConsumerReadinessCheck {
|
|
|
78
85
|
if (fs.existsSync(".mcp.json")) {
|
|
79
86
|
const mcpConfig = JSON.parse(fs.readFileSync(".mcp.json", "utf8"));
|
|
80
87
|
const serverCount = Object.keys(mcpConfig.mcpServers || {}).length;
|
|
81
|
-
const hasServers = serverCount >=
|
|
88
|
+
const hasServers = serverCount >= 7; // consumer mill: 7 0xRay MCP servers
|
|
82
89
|
|
|
83
90
|
this.checks.push({
|
|
84
91
|
name: "MCP server configuration",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require("fs");
|
|
9
9
|
const path = require("path");
|
|
10
|
-
const { execSync } = require("child_process");
|
|
10
|
+
const { execFileSync, execSync } = require("child_process");
|
|
11
11
|
const {
|
|
12
12
|
wantsCostume,
|
|
13
13
|
isIsolatedHome,
|
|
@@ -710,6 +710,26 @@ function deployXrayConfig(targetDir, packageRoot, log) {
|
|
|
710
710
|
return copied;
|
|
711
711
|
}
|
|
712
712
|
|
|
713
|
+
const CURSOR_HOOK_SCRIPTS = [
|
|
714
|
+
"xray-cloud-hook.sh",
|
|
715
|
+
"pre-tool-use.sh",
|
|
716
|
+
"pre-compact.sh",
|
|
717
|
+
"after-file-edit.sh",
|
|
718
|
+
"before-read-file.sh",
|
|
719
|
+
"before-shell-execution.sh",
|
|
720
|
+
];
|
|
721
|
+
|
|
722
|
+
const CLOUD_SAFE_CURSOR_HOOKS = {
|
|
723
|
+
version: 1,
|
|
724
|
+
hooks: {
|
|
725
|
+
preToolUse: [{ command: ".cursor/hooks/pre-tool-use.sh" }],
|
|
726
|
+
preCompact: [{ command: ".cursor/hooks/pre-compact.sh" }],
|
|
727
|
+
afterFileEdit: [{ command: ".cursor/hooks/after-file-edit.sh" }],
|
|
728
|
+
beforeShellExecution: [{ command: ".cursor/hooks/before-shell-execution.sh" }],
|
|
729
|
+
beforeReadFile: [{ command: ".cursor/hooks/before-read-file.sh" }],
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
|
|
713
733
|
function resolveCursorHooksTemplate(packageRoot) {
|
|
714
734
|
const src = path.join(packageRoot, "src", "integrations", "cursor", "hooks", "hooks.json");
|
|
715
735
|
if (fs.existsSync(src)) return src;
|
|
@@ -718,24 +738,240 @@ function resolveCursorHooksTemplate(packageRoot) {
|
|
|
718
738
|
return null;
|
|
719
739
|
}
|
|
720
740
|
|
|
741
|
+
function resolveCursorHookScriptDir(packageRoot) {
|
|
742
|
+
const src = path.join(packageRoot, "src", "integrations", "cursor", "hooks");
|
|
743
|
+
if (fs.existsSync(path.join(src, "xray-cloud-hook.sh"))) return src;
|
|
744
|
+
const dist = path.join(packageRoot, "dist", "integrations", "cursor", "hooks");
|
|
745
|
+
if (fs.existsSync(path.join(dist, "xray-cloud-hook.sh"))) return dist;
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function isEnvAssignmentCursorCommand(command) {
|
|
750
|
+
const cmd = String(command || "");
|
|
751
|
+
return (
|
|
752
|
+
/XRAY_AI_PATH=/.test(cmd) ||
|
|
753
|
+
/XRAY_HOOK_EVENT=/.test(cmd) ||
|
|
754
|
+
/invoke-probe\.sh/.test(cmd) ||
|
|
755
|
+
/\$\{XRAY_AI_PATH/.test(cmd) ||
|
|
756
|
+
/^\s*node\s+/.test(cmd)
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function isRelativeCursorHookCommand(command) {
|
|
761
|
+
return /^\.cursor\/hooks\/[\w.-]+\.sh$/.test(String(command || ""));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function mergeCloudSafeCursorHooks(existing) {
|
|
765
|
+
const next =
|
|
766
|
+
existing && typeof existing === "object" ? { ...existing, hooks: { ...(existing.hooks || {}) } } : { version: 1, hooks: {} };
|
|
767
|
+
next.version = 1;
|
|
768
|
+
for (const [name, entries] of Object.entries(CLOUD_SAFE_CURSOR_HOOKS.hooks)) {
|
|
769
|
+
const current = next.hooks[name];
|
|
770
|
+
const leftover = Array.isArray(current) && current.some((h) => isEnvAssignmentCursorCommand(h && h.command));
|
|
771
|
+
const missingRelative =
|
|
772
|
+
!Array.isArray(current) || !current.some((h) => isRelativeCursorHookCommand(h && h.command));
|
|
773
|
+
if (!current || leftover || missingRelative) {
|
|
774
|
+
next.hooks[name] = entries;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return next;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function fastenCursorHookScripts(targetDir, packageRoot, log) {
|
|
781
|
+
const srcDir = resolveCursorHookScriptDir(packageRoot);
|
|
782
|
+
const destDir = path.join(targetDir, ".cursor", "hooks");
|
|
783
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
784
|
+
if (!srcDir) {
|
|
785
|
+
log("cursor-bridge", "hook scripts missing", "warn", { reason: "xray-cloud-hook.sh not in src or dist" });
|
|
786
|
+
return 0;
|
|
787
|
+
}
|
|
788
|
+
let copied = 0;
|
|
789
|
+
for (const name of CURSOR_HOOK_SCRIPTS) {
|
|
790
|
+
const src = path.join(srcDir, name);
|
|
791
|
+
if (!fs.existsSync(src)) continue;
|
|
792
|
+
const dest = path.join(destDir, name);
|
|
793
|
+
fs.copyFileSync(src, dest);
|
|
794
|
+
try {
|
|
795
|
+
fs.chmodSync(dest, 0o755);
|
|
796
|
+
} catch {
|
|
797
|
+
// chmod is best-effort on hosts that ignore mode
|
|
798
|
+
}
|
|
799
|
+
copied++;
|
|
800
|
+
}
|
|
801
|
+
return copied;
|
|
802
|
+
}
|
|
803
|
+
|
|
721
804
|
/**
|
|
722
|
-
*
|
|
723
|
-
*
|
|
805
|
+
* Multi-repo Cloud workspaces bind hooks at the daemon cwd (e.g. /agent),
|
|
806
|
+
* not the primary git checkout. Fasten that root too when it is visible.
|
|
724
807
|
*/
|
|
725
|
-
function
|
|
808
|
+
function resolveCursorWorkspaceRoot(targetDir) {
|
|
809
|
+
const envRoot = process.env.CURSOR_PROJECT_DIR;
|
|
810
|
+
if (envRoot) {
|
|
811
|
+
const resolved = path.resolve(envRoot);
|
|
812
|
+
if (fs.existsSync(resolved) && resolved !== path.resolve(targetDir)) {
|
|
813
|
+
return resolved;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
let dir = path.resolve(targetDir);
|
|
817
|
+
for (let i = 0; i < 6; i += 1) {
|
|
818
|
+
const parent = path.dirname(dir);
|
|
819
|
+
if (parent === dir) break;
|
|
820
|
+
if (
|
|
821
|
+
fs.existsSync(path.join(parent, "repos", "xray")) ||
|
|
822
|
+
fs.existsSync(path.join(parent, "repos", "repertoire"))
|
|
823
|
+
) {
|
|
824
|
+
return parent;
|
|
825
|
+
}
|
|
826
|
+
dir = parent;
|
|
827
|
+
}
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function fastenCursorHooksAt(targetDir, packageRoot, log) {
|
|
832
|
+
try {
|
|
833
|
+
return fastenCursorHooksAtUnsafe(targetDir, packageRoot, log);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
log("cursor-bridge", "workspace fasten skipped", "warn", {
|
|
836
|
+
target: targetDir,
|
|
837
|
+
error: err && err.message ? err.message : String(err),
|
|
838
|
+
});
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function fastenCursorHooksAtUnsafe(targetDir, packageRoot, log) {
|
|
726
844
|
const dest = path.join(targetDir, ".cursor", "hooks.json");
|
|
845
|
+
const copied = fastenCursorHookScripts(targetDir, packageRoot, log);
|
|
846
|
+
let existing = null;
|
|
727
847
|
if (fs.existsSync(dest)) {
|
|
728
|
-
|
|
729
|
-
|
|
848
|
+
try {
|
|
849
|
+
existing = JSON.parse(fs.readFileSync(dest, "utf8"));
|
|
850
|
+
} catch {
|
|
851
|
+
existing = null;
|
|
852
|
+
}
|
|
730
853
|
}
|
|
731
854
|
const template = resolveCursorHooksTemplate(packageRoot);
|
|
732
|
-
if (!template) {
|
|
733
|
-
log("cursor-bridge", "skipped", "warn", { reason: "cursor hooks template missing" });
|
|
855
|
+
if (!existing && !template) {
|
|
856
|
+
log("cursor-bridge", "skipped", "warn", { reason: "cursor hooks template missing", target: targetDir });
|
|
734
857
|
return null;
|
|
735
858
|
}
|
|
859
|
+
const next = mergeCloudSafeCursorHooks(existing);
|
|
736
860
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
737
|
-
fs.
|
|
738
|
-
log("cursor-bridge", "hooks.json
|
|
861
|
+
fs.writeFileSync(dest, `${JSON.stringify(next, null, 2)}\n`);
|
|
862
|
+
log("cursor-bridge", existing ? "hooks.json rewritten cloud-safe" : "hooks.json fastened", "info", {
|
|
863
|
+
path: dest,
|
|
864
|
+
scripts: copied,
|
|
865
|
+
});
|
|
866
|
+
return dest;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Fifth wear: project `.cursor/hooks.json` + relative `.cursor/hooks/*.sh`.
|
|
871
|
+
* Cloud execs argv[0] without a shell — leftover `XRAY_AI_PATH=` one-liners never start.
|
|
872
|
+
* Also fasten the daemon workspace root on multi-repo Cloud seats.
|
|
873
|
+
*/
|
|
874
|
+
function parseExecDaemonCmdline(parts) {
|
|
875
|
+
if (!Array.isArray(parts) || parts.length === 0) return null;
|
|
876
|
+
const joined = parts.join(" ");
|
|
877
|
+
if (!joined.includes("exec-daemon")) return null;
|
|
878
|
+
let port = "";
|
|
879
|
+
let token = "";
|
|
880
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
881
|
+
const arg = parts[i];
|
|
882
|
+
if (arg === "--port" || arg === "-p") port = parts[i + 1] || "";
|
|
883
|
+
if (arg === "--auth-token" || arg === "--authToken") token = parts[i + 1] || "";
|
|
884
|
+
if (typeof arg === "string" && arg.startsWith("--port=")) port = arg.slice("--port=".length);
|
|
885
|
+
if (typeof arg === "string" && arg.startsWith("--auth-token=")) {
|
|
886
|
+
token = arg.slice("--auth-token=".length);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (!port || !token) return null;
|
|
890
|
+
return { port: String(port), token: String(token) };
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function findLocalExecDaemon() {
|
|
894
|
+
let names;
|
|
895
|
+
try {
|
|
896
|
+
names = fs.readdirSync("/proc");
|
|
897
|
+
} catch {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
for (const name of names) {
|
|
901
|
+
if (!/^\d+$/.test(name)) continue;
|
|
902
|
+
let parts;
|
|
903
|
+
try {
|
|
904
|
+
parts = fs.readFileSync(path.join("/proc", name, "cmdline"), "utf8").split("\0");
|
|
905
|
+
} catch {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
const parsed = parseExecDaemonCmdline(parts);
|
|
909
|
+
if (parsed) return parsed;
|
|
910
|
+
}
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function reloadCursorHostHooks(log) {
|
|
915
|
+
const write =
|
|
916
|
+
typeof log === "function"
|
|
917
|
+
? log
|
|
918
|
+
: () => {
|
|
919
|
+
/* noop */
|
|
920
|
+
};
|
|
921
|
+
try {
|
|
922
|
+
const daemon = findLocalExecDaemon();
|
|
923
|
+
if (!daemon) {
|
|
924
|
+
write("cursor-bridge", "host reload skipped", "info", { reason: "no-local-daemon" });
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
const script = [
|
|
928
|
+
'const http = require("http");',
|
|
929
|
+
"const req = http.request({",
|
|
930
|
+
' host: "127.0.0.1",',
|
|
931
|
+
" port: process.env.XRAY_RELOAD_PORT,",
|
|
932
|
+
' path: "/agent.v1.ControlService/ReloadAgentSkills",',
|
|
933
|
+
' method: "POST",',
|
|
934
|
+
" headers: {",
|
|
935
|
+
' "content-type": "application/json",',
|
|
936
|
+
' authorization: "Bearer " + process.env.XRAY_RELOAD_TOKEN,',
|
|
937
|
+
' "connect-protocol-version": "1",',
|
|
938
|
+
" },",
|
|
939
|
+
"}, (res) => { res.resume(); res.on(\"end\", () => process.stdout.write(String(res.statusCode || 0))); });",
|
|
940
|
+
"req.on(\"error\", () => process.exit(2));",
|
|
941
|
+
"req.setTimeout(3000, () => { req.destroy(); process.exit(3); });",
|
|
942
|
+
'req.end("{}");',
|
|
943
|
+
].join("\n");
|
|
944
|
+
const status = execFileSync(process.execPath, ["-e", script], {
|
|
945
|
+
encoding: "utf8",
|
|
946
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
947
|
+
env: {
|
|
948
|
+
...process.env,
|
|
949
|
+
XRAY_RELOAD_PORT: String(daemon.port),
|
|
950
|
+
XRAY_RELOAD_TOKEN: daemon.token,
|
|
951
|
+
},
|
|
952
|
+
timeout: 4000,
|
|
953
|
+
}).trim();
|
|
954
|
+
const code = Number(status);
|
|
955
|
+
write("cursor-bridge", "host hooks reloaded", code === 200 ? "info" : "warn", {
|
|
956
|
+
status: code,
|
|
957
|
+
port: daemon.port,
|
|
958
|
+
});
|
|
959
|
+
return code === 200;
|
|
960
|
+
} catch (err) {
|
|
961
|
+
write("cursor-bridge", "host reload skipped", "warn", {
|
|
962
|
+
error: err && err.message ? err.message : String(err),
|
|
963
|
+
});
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function installCursorBridge(targetDir, packageRoot, log) {
|
|
969
|
+
const dest = fastenCursorHooksAt(targetDir, packageRoot, log);
|
|
970
|
+
const workspace = resolveCursorWorkspaceRoot(targetDir);
|
|
971
|
+
if (workspace && path.resolve(workspace) !== path.resolve(targetDir)) {
|
|
972
|
+
fastenCursorHooksAt(workspace, packageRoot, log);
|
|
973
|
+
}
|
|
974
|
+
reloadCursorHostHooks(log);
|
|
739
975
|
return dest;
|
|
740
976
|
}
|
|
741
977
|
|
|
@@ -832,5 +1068,15 @@ module.exports = {
|
|
|
832
1068
|
installFrameworkDogfoodWear,
|
|
833
1069
|
wearVendoredRepertoire,
|
|
834
1070
|
installCursorBridge,
|
|
1071
|
+
parseExecDaemonCmdline,
|
|
1072
|
+
reloadCursorHostHooks,
|
|
835
1073
|
resolveCursorHooksTemplate,
|
|
1074
|
+
resolveCursorWorkspaceRoot,
|
|
1075
|
+
fastenCursorHooksAt,
|
|
1076
|
+
resolveCursorHookScriptDir,
|
|
1077
|
+
fastenCursorHookScripts,
|
|
1078
|
+
mergeCloudSafeCursorHooks,
|
|
1079
|
+
isEnvAssignmentCursorCommand,
|
|
1080
|
+
CURSOR_HOOK_SCRIPTS,
|
|
1081
|
+
CLOUD_SAFE_CURSOR_HOOKS,
|
|
836
1082
|
};
|
|
@@ -64,7 +64,7 @@ Returns: pass/fail with per-file results and violations.
|
|
|
64
64
|
|
|
65
65
|
### xray_codex_check
|
|
66
66
|
|
|
67
|
-
Validate code against the
|
|
67
|
+
Validate code against the 69-term Universal Development Codex. Checks error-handling, type-safety, performance, security, architecture.
|
|
68
68
|
|
|
69
69
|
```
|
|
70
70
|
xray_codex_check(code="const x: any = foo()", operation="create")
|
|
@@ -64,6 +64,10 @@ Config: `features.json` → `multi_agent_orchestration.lead_dev_mode`
|
|
|
64
64
|
|
|
65
65
|
The seven rules are the engine. The cadence is the time signature. Disk SSOT: `grok-bot/ops/LEAD-CADENCE.md`.
|
|
66
66
|
|
|
67
|
+
Jargon is not the job. Review and cadence ask whether the change works. Do not spend the loop reciting dest names, mill liturgy, or host mantras.
|
|
68
|
+
|
|
69
|
+
- **Feat rebase:** when the mandated work turns into mantra, or the job has split across planes that no longer share one present, stop. Go up a level and answer the original message again from this seat. Do not continue the side thread and call that the job.
|
|
70
|
+
|
|
67
71
|
- **Peer test:** another instance with only worn 0xRay continues the ticket. No command stream.
|
|
68
72
|
- **Wear first:** Read `.xray/state/STATION.md`. Same cloud. Same critic (resume only). Dummy Reads Station.
|
|
69
73
|
- **Dummy tests `SKILLS.md`:** a suited dummy proves the worn catalog. Same-sess bodies keep context and hold a Station card.
|
|
@@ -73,8 +77,26 @@ The seven rules are the engine. The cadence is the time signature. Disk SSOT: `g
|
|
|
73
77
|
- **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes. **Clean ticks every cycle** — unsubscribe then resubscribe so the prompt matches metal. Name-dedupe does not rewrite. A stale prompt (waiting URL after live, Hold after strip) is dirty: rewrite, do not act. **If the card and WAVEBOARD are idle, unsubscribe and do not resubscribe.**
|
|
74
78
|
- **Board clock:** PR events + resume the same critic. Not two idle pacers. Merge after PASS+CI (gate A). Publish only after mill-gate D + `foundry gate`.
|
|
75
79
|
- **Dispatch:** ticket + PR URLs + “Wear, then review.” The critic Reads `SEATS.md` Reviewer. Do not paste a command novel. When a law is spoken twice, encode it this wake.
|
|
80
|
+
- **Cascade:** Chat is not the brain. A cascade that must survive compaction is a named repertoire signal on the stack overlay, hydrated onto the project law list. NOTES holds the same cascade for the second Read. Station stays the ticket. A reflection is not this capture. Capture priority: a law is an invariant you would otherwise re-read the source to relearn. One law per subsystem bone. Heat touches last_seen and does not append a confidence sample, so more hits do not raise conviction. Heat touches last_seen only when the diary contains that signal's name; two definition words are not a hit; a weak session score is not lifted to 0.55 and a sample below 0.55 is not recorded. Constructing the organ does not append workspace samples. Hero names: `heat-is-not-conviction`, `inference-cycle-invariant`, `governance-vote-invariant`, `thin-dispatch-invariant`. Repertoire mergeStackOverlay refreshes a changed stack definition and keeps observation stats.
|
|
81
|
+
- **Cold brief:** a new critic or any new subagent that does not already have this thread gets the latest commit SHA on the branch under review (fetch it; do not reuse an earlier SHA from a previous pass) and the Station card (`.xray/state/STATION.md`: intent, plan, git). If the card is missing, give branch + HEAD + what just changed. If the branch moves after you brief them, send the new SHA before they verdict.
|
|
82
|
+
- **Control:** The lead keeps control. Do not assume the subagent has the context. Give the duty, the latest SHA, the Station card, and the laws that apply. The subagent wears the applicable 0xRay suit before it works: the tree under review, built and installed. An older published package is not that suit. The lead checks the result.
|
|
83
|
+
|
|
84
|
+
### Engine cadence
|
|
85
|
+
|
|
86
|
+
This suite is the engine that builds the engine. When a seat sees OP-PROC, write it onto these existing surfaces in the same wake. Do not open a new skill.
|
|
87
|
+
|
|
88
|
+
1. Change the engine with precision.
|
|
89
|
+
2. Ship it local: build and install the tree. A live npm version stays live. Once a version is live, the stamper strips every present-tense patch pin from shipped guides and shipped OP-PROC, or reconcile advances the next cut. The stamper does not edit Station, NOTES, dest, or node_modules. It does not publish.
|
|
90
|
+
3. Test the change.
|
|
91
|
+
4. Run another deep review of the diff you just made.
|
|
92
|
+
5. Monitor long-term memory (dest name count and whether observations are the only growth) and short-term memory (Station: intent, plan, git).
|
|
93
|
+
6. Ship the PR.
|
|
94
|
+
|
|
95
|
+
### Planes
|
|
96
|
+
|
|
97
|
+
Operating planes: code, OP-PROC, model, suit, mill, host, and test/ship. Test/ship is the critical OP-PROC plane. Test then ship is the hero. Ship without that proof is the catastrophe. Long-term memory is the project law list. Each wake refreshes a changed stack definition and keeps observation stats. Heat writes the matched names onto Station, the short-term card that survives compaction. Reactive decisions move out of TypeScript into OP-PROC as the model improves. That move is why the mill and the suit exist. It is not fully proved. Once a version is live, the stamper strips every present-tense patch pin from shipped guides and shipped OP-PROC, or reconcile advances the next cut. The stamper does not edit Station, NOTES, dest, or node_modules. It does not publish. Do not freeze the live cut.
|
|
76
98
|
- **Close:** loop until Station is done. Compact: Station survives the cut. Overlay dest names are OP-PROC — reload `repertoire-working.json` `opProcNames`, not Station.
|
|
77
99
|
- **Board:** critic Strict comments; forge implements; lead stays main thread.
|
|
78
100
|
- **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
|
|
79
|
-
- **Ship matrix:** `LEAD-CADENCE.md` Decision matrix — A PR+CI · B pack · C `release:docs-check` · D `foundry release --i-mean-it`. Green CI is not ship. Spend / credentials stay Ask-first.
|
|
101
|
+
- **Ship matrix:** `LEAD-CADENCE.md` Decision matrix — A PR+CI · B pack · C `release:docs-check` (`reconcile --check`; no patch stamps on guides) · D `foundry release --i-mean-it`. Green CI is not ship. Spend / credentials stay Ask-first. Patch number lives in `package.json` + CHANGELOG.
|
|
80
102
|
- **Metamorphosis:** LLM → OP-PROC is in-context worn seats (same critic, same dummy, resume only). Not more bots.
|