0xray 4.0.32 → 4.0.33
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/agents/api-design.yml +31 -0
- package/.opencode/agents/architect.yml +15 -0
- package/.opencode/agents/architecture-patterns.yml +14 -0
- package/.opencode/agents/auto-format.yml +14 -0
- package/.opencode/agents/backend-engineer.yml +12 -0
- package/.opencode/agents/boot-orchestrator.yml +14 -0
- package/.opencode/agents/bug-triage-specialist.yml +15 -0
- package/.opencode/agents/code-analyzer.yml +12 -0
- package/.opencode/agents/code-reviewer.yml +14 -0
- package/.opencode/agents/content-creator.yml +12 -0
- package/.opencode/agents/database-engineer.yml +12 -0
- package/.opencode/agents/devops-engineer.yml +12 -0
- package/.opencode/agents/framework-compliance-audit.yml +14 -0
- package/.opencode/agents/frontend-engineer.yml +12 -0
- package/.opencode/agents/frontend-ui-ux-engineer.yml +12 -0
- package/.opencode/agents/git-workflow.yml +15 -0
- package/.opencode/agents/growth-strategist.yml +12 -0
- package/.opencode/agents/hermes-agent.yml +12 -0
- package/.opencode/agents/inference-improve.yml +12 -0
- package/.opencode/agents/lint.yml +14 -0
- package/.opencode/agents/log-monitor.yml +12 -0
- package/.opencode/agents/mobile-developer.yml +12 -0
- package/.opencode/agents/model-health-check.yml +12 -0
- package/.opencode/agents/multimodal-looker.yml +12 -0
- package/.opencode/agents/performance-analysis.yml +12 -0
- package/.opencode/agents/performance-engineer.yml +12 -0
- package/.opencode/agents/performance-optimization.yml +12 -0
- package/.opencode/agents/processor-pipeline.yml +14 -0
- package/.opencode/agents/project-analysis.yml +12 -0
- package/.opencode/agents/refactorer.yml +12 -0
- package/.opencode/agents/researcher.yml +12 -0
- package/.opencode/agents/security-auditor.yml +12 -0
- package/.opencode/agents/security-scan.yml +12 -0
- package/.opencode/agents/seo-consultant.yml +12 -0
- package/.opencode/agents/session-management.yml +12 -0
- package/.opencode/agents/state-manager.yml +12 -0
- package/.opencode/agents/storyteller.yml +12 -0
- package/.opencode/agents/strategist.yml +12 -0
- package/.opencode/agents/tech-writer.yml +12 -0
- package/.opencode/agents/testing-best-practices.yml +12 -0
- package/.opencode/agents/testing-lead.yml +12 -0
- package/.opencode/agents/ui-ux-design.yml +12 -0
- package/.opencode/codex.codex +8 -0
- package/.opencode/commands/auto-format.md +99 -0
- package/.opencode/commands/auto-summary-capture.md +90 -0
- package/.opencode/commands/dependency-audit.md +184 -0
- package/.opencode/commands/enforcer-daily-scan.md +137 -0
- package/.opencode/commands/framework-compliance-audit.md +205 -0
- package/.opencode/commands/interactive-validator.md +75 -0
- package/.opencode/commands/job-summary-logger.md +68 -0
- package/.opencode/commands/lint.md +11 -0
- package/.opencode/commands/mode-switch.md +95 -0
- package/.opencode/commands/model-health-check.md +186 -0
- package/.opencode/commands/performance-analysis.md +144 -0
- package/.opencode/commands/pre-commit-introspection.md +185 -0
- package/.opencode/commands/pre-commit-introspection.sh +133 -0
- package/.opencode/commands/security-scan.md +157 -0
- package/.opencode/commands/sisyphus-validation.md +128 -0
- package/.opencode/commands/summary-logger.md +83 -0
- package/.opencode/enforcer-config.json +282 -0
- package/.opencode/skills/orchestrator/SKILL.md +3 -3
- package/.opencode/workflows/post-deployment-audit.yml +123 -0
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +8 -0
- package/dist/cli/goggles-status.js +1 -1
- package/dist/governance/governance-service.js +11 -1
- package/dist/integrations/grok/hooks/grok-hook-utils.js +7 -4
- package/dist/integrations/grok/hooks/pre-tool-use.js +7 -1
- package/dist/integrations/grok/hooks/session-start.js +5 -2
- package/dist/integrations/hooks/goggles-mcp.mjs +3 -36
- package/dist/integrations/hooks/goggles-pipeline.d.mts +2 -0
- package/dist/integrations/hooks/goggles-pipeline.mjs +393 -72
- package/dist/integrations/hooks/plates.cjs +15 -1
- package/dist/integrations/hooks/station-hook-runtime.cjs +53 -5
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/orchestrator/config/agent-capabilities.js +5 -0
- package/dist/nucleus/thin-dispatch.d.ts +4 -0
- package/dist/nucleus/thin-dispatch.js +9 -1
- package/dist/skills/orchestrator/SKILL.md +3 -3
- package/package.json +1 -1
- package/scripts/foundry/mint-suit.cjs +6 -1
- package/scripts/node/install-bridges.cjs +24 -7
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/orchestrator/config/agent-capabilities.ts +4 -0
- package/src/skills/orchestrator/SKILL.md +3 -3
- package/xray/features.json +1 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Goggles. Kind 0 name-checks the six outer names.
|
|
3
|
-
*
|
|
3
|
+
* A named plane returns its view. Field pipes fill what they can see.
|
|
4
|
+
* The view keeps every field for the next look.
|
|
4
5
|
* A held plane stays quiet. A leave is denied.
|
|
5
6
|
*/
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
7
9
|
import { dirname, join, resolve } from 'node:path';
|
|
8
10
|
import { fileURLToPath } from 'node:url';
|
|
9
11
|
|
|
@@ -204,10 +206,20 @@ function gogglesAnswer(argv) {
|
|
|
204
206
|
return { text: cards.map((card) => formatCardText(card)).join('\n\n'), cards };
|
|
205
207
|
}
|
|
206
208
|
const planes = acceptedPlanes().filter((name) => lower.includes(name));
|
|
207
|
-
if (planes.length !== 1)
|
|
209
|
+
if (planes.length !== 1) {
|
|
210
|
+
if (!planes.length && cardPlanesNamed(lower).length) {
|
|
211
|
+
const cards = collectCards('digest', lower);
|
|
212
|
+
if (cards && cards.length) {
|
|
213
|
+
return { text: cards.map((card) => formatCardText(card)).join('\n\n'), cards };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return none('');
|
|
217
|
+
}
|
|
208
218
|
const scopes = findScopes(words.filter((word) => word.toLowerCase() !== planes[0]));
|
|
209
219
|
if (scopes.length > 1) return none('');
|
|
210
|
-
|
|
220
|
+
const line = readingLine(planes[0], scopes[0] || '');
|
|
221
|
+
const file = moduleFile(planes[0]);
|
|
222
|
+
return none(file ? `${line} File: ${file}` : line);
|
|
211
223
|
}
|
|
212
224
|
|
|
213
225
|
export function readGoggles(argv) {
|
|
@@ -222,23 +234,17 @@ function cardPlanesNamed(lower) {
|
|
|
222
234
|
return CARD_PLANES.filter((name) => lower.includes(name));
|
|
223
235
|
}
|
|
224
236
|
|
|
225
|
-
function listedFiles(plane
|
|
226
|
-
|
|
227
|
-
if (scope === 'one artifact') return files.length === 1 ? [files[0]] : null;
|
|
228
|
-
const unpathed = Array.isArray(plane.unpathed) ? plane.unpathed : [];
|
|
229
|
-
const parts = files.slice();
|
|
230
|
-
for (const name of unpathed) parts.push(name === 'mill' ? 'mill has no path' : String(name));
|
|
231
|
-
return parts;
|
|
237
|
+
function listedFiles(plane) {
|
|
238
|
+
return Array.isArray(plane.files) ? plane.files.map(String) : [];
|
|
232
239
|
}
|
|
233
240
|
|
|
234
241
|
function cardView(plane, scope, flavor, root) {
|
|
235
|
-
const listed = listedFiles(plane
|
|
236
|
-
if (!listed) return null;
|
|
242
|
+
const listed = listedFiles(plane);
|
|
237
243
|
const view = {
|
|
238
244
|
plane: plane.id,
|
|
239
245
|
flavor,
|
|
240
246
|
scope: scope || '',
|
|
241
|
-
from: plane.id === 'ground' ? '' : 'ground',
|
|
247
|
+
from: plane.id === 'ground' ? '' : (plane.from || 'ground'),
|
|
242
248
|
digest: plane.digest || '',
|
|
243
249
|
plate: plane.plate || '',
|
|
244
250
|
entry: plane.entry || '',
|
|
@@ -248,7 +254,6 @@ function cardView(plane, scope, flavor, root) {
|
|
|
248
254
|
setup: plane.setup || '',
|
|
249
255
|
teardown: plane.teardown || '',
|
|
250
256
|
worn: plane.worn || '',
|
|
251
|
-
narrow: scope === 'one flow' || scope === 'one artifact',
|
|
252
257
|
};
|
|
253
258
|
if (flavor === 'triage') {
|
|
254
259
|
const filled = new Set(filledOf(plane));
|
|
@@ -272,10 +277,7 @@ function formatCardText(view) {
|
|
|
272
277
|
['Teardown', view.teardown],
|
|
273
278
|
['Worn', view.worn],
|
|
274
279
|
];
|
|
275
|
-
|
|
276
|
-
? rows.filter(([key]) => key === 'Plane' || key === 'Digest' || key === 'Files')
|
|
277
|
-
: rows;
|
|
278
|
-
let text = shown.map(([key, value]) => `${key}: ${value}`.trimEnd()).join('\n');
|
|
280
|
+
let text = rows.map(([key, value]) => `${key}: ${value}`.trimEnd()).join('\n');
|
|
279
281
|
if (view.flavor === 'triage') {
|
|
280
282
|
const lines = [];
|
|
281
283
|
if (view.empty.length) lines.push(`Empty: ${view.empty.join(', ')}`);
|
|
@@ -286,23 +288,19 @@ function formatCardText(view) {
|
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
export function formatCardPane(view) {
|
|
289
|
-
const rows = [
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
);
|
|
303
|
-
} else {
|
|
304
|
-
rows.push(['Files', view.files.join(', ')]);
|
|
305
|
-
}
|
|
291
|
+
const rows = [
|
|
292
|
+
['Plane', view.plane],
|
|
293
|
+
['From', view.from],
|
|
294
|
+
['Digest', view.digest],
|
|
295
|
+
['Plate', view.plate],
|
|
296
|
+
['Entry', view.entry],
|
|
297
|
+
['Exit', view.exit],
|
|
298
|
+
['Files', view.files.join(', ')],
|
|
299
|
+
['Skills', view.skills],
|
|
300
|
+
['Setup', view.setup],
|
|
301
|
+
['Teardown', view.teardown],
|
|
302
|
+
['Worn', view.worn],
|
|
303
|
+
];
|
|
306
304
|
if (view.flavor === 'triage') {
|
|
307
305
|
if (Array.isArray(view.empty) && view.empty.length) rows.push(['Empty', view.empty.join(', ')]);
|
|
308
306
|
if (view.exam) rows.push(['', view.exam]);
|
|
@@ -319,20 +317,15 @@ export function formatCardPane(view) {
|
|
|
319
317
|
function collectCards(flavor, lower) {
|
|
320
318
|
const named = cardPlanesNamed(lower);
|
|
321
319
|
const scopes = findScopes(lower.filter((word) => word !== flavor && !named.includes(word)));
|
|
322
|
-
if (
|
|
320
|
+
if (scopes.length > 1) return null;
|
|
323
321
|
const scope = scopes[0] || '';
|
|
324
|
-
if (!named.length && (scope === 'part' || scope === 'one flow' || scope === 'one artifact')) return null;
|
|
325
322
|
const platesDir = findPlatesDir(HERE);
|
|
326
|
-
if (!platesDir) return null;
|
|
327
323
|
const ids = named.length ? named : CARD_PLANES;
|
|
328
|
-
const root =
|
|
324
|
+
const root = sourceRoot(platesDir);
|
|
329
325
|
const cards = [];
|
|
330
326
|
for (const id of ids) {
|
|
331
|
-
const plane =
|
|
332
|
-
|
|
333
|
-
const view = cardView(plane, scope, flavor, root);
|
|
334
|
-
if (!view) return null;
|
|
335
|
-
cards.push(view);
|
|
327
|
+
const plane = growPlane(id, platesDir, stateRoot());
|
|
328
|
+
cards.push(cardView(plane, scope, flavor, root));
|
|
336
329
|
}
|
|
337
330
|
return cards;
|
|
338
331
|
}
|
|
@@ -366,6 +359,45 @@ function boxTitles(line) {
|
|
|
366
359
|
return titles;
|
|
367
360
|
}
|
|
368
361
|
|
|
362
|
+
function flowEnds(body) {
|
|
363
|
+
if (!body || body.includes('INPUT LAYER')) return null;
|
|
364
|
+
const stages = [];
|
|
365
|
+
let title = '';
|
|
366
|
+
let notes = [];
|
|
367
|
+
let open = false;
|
|
368
|
+
for (const line of String(body).split(/\r?\n/)) {
|
|
369
|
+
if (line.includes('┌')) {
|
|
370
|
+
open = true;
|
|
371
|
+
title = '';
|
|
372
|
+
notes = [];
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (!open) continue;
|
|
376
|
+
if (line.includes('└')) {
|
|
377
|
+
if (title) stages.push({ title, notes: notes.slice() });
|
|
378
|
+
open = false;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (!/[A-Za-z]/.test(line)) continue;
|
|
382
|
+
const titles = boxTitles(line);
|
|
383
|
+
if (titles.length && !title) {
|
|
384
|
+
title = titles.join(' ');
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const note = line.replace(/[│|]/g, ' ').replace(/^[\s·.-]+/, '').trim();
|
|
388
|
+
if (note) notes.push(note);
|
|
389
|
+
}
|
|
390
|
+
if (stages.length < 2) return null;
|
|
391
|
+
const first = stages[0];
|
|
392
|
+
const last = stages[stages.length - 1];
|
|
393
|
+
return {
|
|
394
|
+
entry: first.title,
|
|
395
|
+
exit: last.title,
|
|
396
|
+
setup: first.notes.join(' '),
|
|
397
|
+
teardown: last.notes.join(' '),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
369
401
|
function doorsOf(body) {
|
|
370
402
|
let layer = '';
|
|
371
403
|
let want = false;
|
|
@@ -433,40 +465,322 @@ export function filledOf(plane) {
|
|
|
433
465
|
return FIELD_ORDER.filter((name) => flags[name]);
|
|
434
466
|
}
|
|
435
467
|
|
|
468
|
+
function plateSource(id, platesDir) {
|
|
469
|
+
if (!platesDir || id === 'ground') return { body: '', path: '' };
|
|
470
|
+
const abs = join(platesDir, `${id}.md`);
|
|
471
|
+
if (!existsSync(abs)) return { body: '', path: '' };
|
|
472
|
+
return {
|
|
473
|
+
body: readFileSync(abs, 'utf8'),
|
|
474
|
+
path: join('docs-site', 'docs', 'plates', `${id}.md`),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function sectionOf(body, title) {
|
|
479
|
+
if (!body) return '';
|
|
480
|
+
const lines = stripFrontmatter(body).split(/\r?\n/);
|
|
481
|
+
const heading = new RegExp(`^#{1,3}\\s+${title}\\s*$`, 'i');
|
|
482
|
+
let on = false;
|
|
483
|
+
const prose = [];
|
|
484
|
+
for (const line of lines) {
|
|
485
|
+
if (heading.test(line)) {
|
|
486
|
+
on = true;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (!on) continue;
|
|
490
|
+
if (/^#{1,3}\s+/.test(line) || line.startsWith('```')) break;
|
|
491
|
+
if (!line.trim()) {
|
|
492
|
+
if (prose.length) break;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
prose.push(line.trim());
|
|
496
|
+
}
|
|
497
|
+
return prose.join(' ');
|
|
498
|
+
}
|
|
499
|
+
|
|
436
500
|
export function assemblePlane(id, platesDir) {
|
|
437
501
|
const extra = overlayTable()[id] || {};
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (!existsSync(abs)) return null;
|
|
444
|
-
const raw = readFileSync(abs, 'utf8');
|
|
445
|
-
plate = join('docs-site', 'docs', 'plates', `${id}.md`);
|
|
446
|
-
if (!digest) digest = takeOf(stripFrontmatter(raw));
|
|
447
|
-
doors = doorsOf(raw);
|
|
448
|
-
}
|
|
449
|
-
if (!digest) return null;
|
|
502
|
+
const src = plateSource(id, platesDir);
|
|
503
|
+
const doors = src.body ? doorsOf(src.body) : { entry: null, exit: null };
|
|
504
|
+
const flow = src.body ? flowEnds(src.body) : null;
|
|
505
|
+
const digest = extra.digest || (src.body ? takeOf(stripFrontmatter(src.body)) : '');
|
|
506
|
+
const files = Array.isArray(extra.files) ? extra.files.map(String) : [];
|
|
450
507
|
return {
|
|
451
508
|
id,
|
|
452
509
|
digest,
|
|
453
|
-
plate,
|
|
454
|
-
entry: extra.entry || doors.entry ||
|
|
455
|
-
exit: extra.exit || doors.exit ||
|
|
510
|
+
plate: src.path,
|
|
511
|
+
entry: extra.entry || doors.entry || (flow && flow.entry) || '',
|
|
512
|
+
exit: extra.exit || doors.exit || (flow && flow.exit) || '',
|
|
456
513
|
entryIsMark: Boolean(extra.entry),
|
|
457
514
|
exitIsMark: Boolean(extra.exit),
|
|
458
|
-
files
|
|
515
|
+
files,
|
|
459
516
|
unpathed: extra.unpathed || null,
|
|
460
|
-
skills: extra.skills ||
|
|
461
|
-
setup: extra.setup ||
|
|
462
|
-
teardown: extra.teardown ||
|
|
463
|
-
worn: extra.worn ||
|
|
517
|
+
skills: extra.skills || '',
|
|
518
|
+
setup: extra.setup || sectionOf(src.body, 'Setup') || (flow && flow.setup) || '',
|
|
519
|
+
teardown: extra.teardown || sectionOf(src.body, 'Teardown') || (flow && flow.teardown) || '',
|
|
520
|
+
worn: extra.worn || '',
|
|
521
|
+
clues: cluesOf(src.body),
|
|
464
522
|
mark: extra.mark || null,
|
|
465
523
|
wornMark: extra.wornMark || null,
|
|
466
524
|
pick: extra.pick || null,
|
|
467
525
|
};
|
|
468
526
|
}
|
|
469
527
|
|
|
528
|
+
const VIEW_KEYS = ['from', 'digest', 'plate', 'entry', 'exit', 'files', 'skills', 'setup', 'teardown', 'worn'];
|
|
529
|
+
|
|
530
|
+
function stateRoot() {
|
|
531
|
+
const env = process.env.GOGGLES_ROOT;
|
|
532
|
+
if (typeof env === 'string' && env.trim() && env.trim() !== '.') return resolve(env.trim());
|
|
533
|
+
return process.cwd();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function sourceRoot(platesDir) {
|
|
537
|
+
return platesDir ? repoRootFrom(platesDir) : stateRoot();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function viewsPath(root) {
|
|
541
|
+
return join(root, '.xray', 'state', 'goggles-views.json');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function loadViews(root) {
|
|
545
|
+
try {
|
|
546
|
+
const data = JSON.parse(readFileSync(viewsPath(root), 'utf8'));
|
|
547
|
+
return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
|
|
548
|
+
} catch {
|
|
549
|
+
return {};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function saveViews(root, views) {
|
|
554
|
+
const file = viewsPath(root);
|
|
555
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
556
|
+
writeFileSync(file, `${JSON.stringify(views, null, 2)}\n`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function cluesOf(body) {
|
|
560
|
+
if (!body) return [];
|
|
561
|
+
const clues = [];
|
|
562
|
+
for (const match of String(body).matchAll(/`([^`]{4,80})`/g)) clues.push(match[1]);
|
|
563
|
+
for (const line of String(body).split(/\r?\n/)) {
|
|
564
|
+
if (!line.includes('│')) continue;
|
|
565
|
+
for (const title of boxTitles(line)) {
|
|
566
|
+
if (title.length >= 8 && title.length <= 60) clues.push(title);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return clues;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const sourceCache = new Map();
|
|
573
|
+
|
|
574
|
+
function sourceTexts(root) {
|
|
575
|
+
if (sourceCache.has(root)) return sourceCache.get(root);
|
|
576
|
+
const out = [];
|
|
577
|
+
const walk = (dir, depth) => {
|
|
578
|
+
if (depth > 8) return;
|
|
579
|
+
let entries = [];
|
|
580
|
+
try {
|
|
581
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
582
|
+
} catch {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
for (const entry of entries) {
|
|
586
|
+
if (entry.name === 'node_modules' || entry.name === '__tests__' || entry.name === 'dist') continue;
|
|
587
|
+
const abs = join(dir, entry.name);
|
|
588
|
+
if (entry.isDirectory()) {
|
|
589
|
+
walk(abs, depth + 1);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (!/\.(ts|mjs|cjs)$/.test(entry.name) || entry.name.endsWith('.test.ts')) continue;
|
|
593
|
+
try {
|
|
594
|
+
out.push({ rel: abs.slice(root.length + 1), text: readFileSync(abs, 'utf8') });
|
|
595
|
+
} catch {
|
|
596
|
+
/* unreadable source is not a plane file */
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
walk(join(root, 'src'), 0);
|
|
601
|
+
sourceCache.set(root, out);
|
|
602
|
+
return out;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function fileFromClues(clues, root, planeId) {
|
|
606
|
+
const texts = sourceTexts(root);
|
|
607
|
+
const votes = new Map();
|
|
608
|
+
for (const clue of clues) {
|
|
609
|
+
const hits = [];
|
|
610
|
+
for (const file of texts) {
|
|
611
|
+
if (!file.text.includes(clue)) continue;
|
|
612
|
+
hits.push(file.rel);
|
|
613
|
+
if (hits.length > 8) break;
|
|
614
|
+
}
|
|
615
|
+
if (!hits.length || hits.length > 8) continue;
|
|
616
|
+
const weight = 9 - hits.length;
|
|
617
|
+
for (const rel of hits) votes.set(rel, (votes.get(rel) || 0) + weight);
|
|
618
|
+
}
|
|
619
|
+
const token = String(planeId || '').split('-')[0].toLowerCase();
|
|
620
|
+
const named = token ? [...votes.keys()].filter((rel) => rel.toLowerCase().includes(token)) : [];
|
|
621
|
+
const pool = new Set(named.length ? named : [...votes.keys()]);
|
|
622
|
+
let best = '';
|
|
623
|
+
let bestRank = -1;
|
|
624
|
+
for (const [rel, score] of votes) {
|
|
625
|
+
if (!pool.has(rel)) continue;
|
|
626
|
+
let rank = score * 10;
|
|
627
|
+
if (token && rel.toLowerCase().includes(token)) rank += 20;
|
|
628
|
+
if (rel.startsWith('src/cli/')) rank -= 3;
|
|
629
|
+
if (rank > bestRank || (rank === bestRank && rel.length < best.length)) {
|
|
630
|
+
best = rel;
|
|
631
|
+
bestRank = rank;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return best;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function skillFor(id, root) {
|
|
638
|
+
const dir = join(root, 'src', 'skills');
|
|
639
|
+
if (!existsSync(dir)) return '';
|
|
640
|
+
let names = [];
|
|
641
|
+
try {
|
|
642
|
+
names = readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
643
|
+
} catch {
|
|
644
|
+
return '';
|
|
645
|
+
}
|
|
646
|
+
const hit = names.filter((name) => name === id || name.startsWith(`${id}-`));
|
|
647
|
+
if (hit.length !== 1) return '';
|
|
648
|
+
const rel = join('src', 'skills', hit[0], 'SKILL.md');
|
|
649
|
+
return existsSync(join(root, rel)) ? rel : '';
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function moduleFile(name) {
|
|
653
|
+
const platesDir = findPlatesDir(HERE);
|
|
654
|
+
const root = sourceRoot(platesDir);
|
|
655
|
+
const hit = sourceTexts(root).find((file) => file.rel.endsWith(`/${name}.ts`) || file.rel.endsWith(`/${name}.mjs`));
|
|
656
|
+
return hit ? hit.rel : '';
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function hintLine(name) {
|
|
660
|
+
const cards = lookCards(['digest', name]);
|
|
661
|
+
const files = cards && cards[0] && Array.isArray(cards[0].files) ? cards[0].files : [];
|
|
662
|
+
return files.length ? `${name}: ${files.join(', ')}` : '';
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export function suitHint(text) {
|
|
666
|
+
const words = intentWords(text);
|
|
667
|
+
const named = CARD_PLANES.filter((name) => words.includes(name));
|
|
668
|
+
if (!named.length) return lookVerb(words) ? 'Name one plane.' : '';
|
|
669
|
+
return named.map(hintLine).filter(Boolean).join('\n');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const DEEP_SEARCH = /search_codebase|find_implementation|get_documentation/i;
|
|
673
|
+
|
|
674
|
+
function researchCall(toolName, text) {
|
|
675
|
+
const tool = String(toolName || '');
|
|
676
|
+
const spoken = String(text || '');
|
|
677
|
+
if (/researcher|explorer|deep[- ]?research/i.test(tool) || DEEP_SEARCH.test(tool)) return true;
|
|
678
|
+
return /bash|shell/i.test(tool) && (/researcher|explorer|deep[- ]?research/i.test(spoken) || DEEP_SEARCH.test(spoken));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** One plane: the files the suit continues with. A look with no plane stops. Two planes stay quiet. */
|
|
682
|
+
export function lensPlane(text) {
|
|
683
|
+
const hint = suitHint(String(text || ''));
|
|
684
|
+
if (hint === 'Name one plane.') return { stop: true, files: [] };
|
|
685
|
+
if (!hint || hint.includes('\n')) return { stop: false, files: [] };
|
|
686
|
+
const mark = hint.indexOf(': ');
|
|
687
|
+
if (mark < 0) return { stop: false, files: [] };
|
|
688
|
+
const files = hint.slice(mark + 2).split(',').map((part) => part.trim()).filter(Boolean);
|
|
689
|
+
return { stop: false, files };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function lensPage(root, names) {
|
|
693
|
+
if (!names.length) return 'Name one plane.';
|
|
694
|
+
const platesDir = findPlatesDir(HERE);
|
|
695
|
+
const parts = [];
|
|
696
|
+
for (const id of names) {
|
|
697
|
+
const plane = growPlane(id, platesDir, root);
|
|
698
|
+
parts.push(formatCardText(cardView(plane, '', 'digest', sourceRoot(platesDir))));
|
|
699
|
+
}
|
|
700
|
+
return parts.join('\n\n');
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function stampPlane(root, id) {
|
|
704
|
+
try {
|
|
705
|
+
const plates = createRequire(import.meta.url)('./plates.cjs');
|
|
706
|
+
if (!plates.PLATE_IDS.includes(id)) return;
|
|
707
|
+
plates.stampPlateIfMissing(root, id);
|
|
708
|
+
} catch {
|
|
709
|
+
/* a missed stamp must not fail the stop */
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** A search stays stopped. The next move is the file on the lens, not the same search again. */
|
|
714
|
+
export function lensBeforeResearch(root, toolName, text) {
|
|
715
|
+
const spoken = String(text || '');
|
|
716
|
+
if (!researchCall(toolName, spoken)) return null;
|
|
717
|
+
const names = CARD_PLANES.filter((name) => intentWords(spoken).includes(name));
|
|
718
|
+
if (names.length === 1) stampPlane(root, names[0]);
|
|
719
|
+
return { gate: 'lens', decision: 'deny', reason: lensPage(root, names) };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function seenFields(plane, root) {
|
|
723
|
+
const files = Array.isArray(plane.files) ? plane.files.slice() : [];
|
|
724
|
+
let worn = plane.worn || '';
|
|
725
|
+
if (!files.length && Array.isArray(plane.unpathed) && plane.unpathed.includes('mill')) {
|
|
726
|
+
if (existsSync(join(root, 'scripts', 'foundry'))) files.push('scripts/foundry');
|
|
727
|
+
}
|
|
728
|
+
if (!files.length && Array.isArray(plane.clues) && plane.clues.length) {
|
|
729
|
+
const found = fileFromClues(plane.clues, root, plane.id);
|
|
730
|
+
if (found) files.push(found);
|
|
731
|
+
}
|
|
732
|
+
let skills = plane.skills || '';
|
|
733
|
+
if (!skills) skills = skillFor(plane.id, root);
|
|
734
|
+
if (!worn) {
|
|
735
|
+
for (const file of files) {
|
|
736
|
+
if (!file.startsWith('src/')) continue;
|
|
737
|
+
let rel = file.slice(4);
|
|
738
|
+
if (rel.endsWith('.ts')) rel = rel.replace(/\.ts$/, '.js');
|
|
739
|
+
else if (!/\.(cjs|mjs|js)$/.test(rel)) continue;
|
|
740
|
+
const next = `dist/${rel}`;
|
|
741
|
+
if (existsSync(join(root, next))) {
|
|
742
|
+
worn = next;
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
from: plane.id === 'ground' ? '' : 'ground',
|
|
749
|
+
digest: plane.digest || '',
|
|
750
|
+
plate: plane.plate || '',
|
|
751
|
+
entry: plane.entry || '',
|
|
752
|
+
exit: plane.exit || '',
|
|
753
|
+
files,
|
|
754
|
+
skills,
|
|
755
|
+
setup: plane.setup || '',
|
|
756
|
+
teardown: plane.teardown || '',
|
|
757
|
+
worn,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function kept(stored, seen, key) {
|
|
762
|
+
if (key === 'files') {
|
|
763
|
+
if (Array.isArray(seen.files) && seen.files.length) return seen.files.map(String);
|
|
764
|
+
return Array.isArray(stored.files) ? stored.files.map(String) : [];
|
|
765
|
+
}
|
|
766
|
+
if (seen[key]) return seen[key];
|
|
767
|
+
return stored[key] || '';
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export function growPlane(id, platesDir, root) {
|
|
771
|
+
const seen = seenFields(assemblePlane(id, platesDir), sourceRoot(platesDir));
|
|
772
|
+
const views = loadViews(root);
|
|
773
|
+
const stored = views[id] && typeof views[id] === 'object' ? views[id] : {};
|
|
774
|
+
const grown = { id };
|
|
775
|
+
for (const key of VIEW_KEYS) grown[key] = kept(stored, seen, key);
|
|
776
|
+
if (id === 'ground') grown.from = stored.from || '';
|
|
777
|
+
views[id] = {};
|
|
778
|
+
for (const key of VIEW_KEYS) views[id][key] = grown[key];
|
|
779
|
+
saveViews(root, views);
|
|
780
|
+
const base = assemblePlane(id, platesDir);
|
|
781
|
+
return { ...base, ...grown };
|
|
782
|
+
}
|
|
783
|
+
|
|
470
784
|
function repoRootFrom(platesDir) {
|
|
471
785
|
return dirname(dirname(dirname(platesDir)));
|
|
472
786
|
}
|
|
@@ -535,8 +849,8 @@ function isGogglesWork(text) {
|
|
|
535
849
|
|
|
536
850
|
|
|
537
851
|
export function cardStop(root, toolName, text, paths = []) {
|
|
538
|
-
const
|
|
539
|
-
if (isGogglesWork(
|
|
852
|
+
const spoken = String(text || '');
|
|
853
|
+
if (spoken.length <= 200 && isGogglesWork(spoken) && !(paths || []).some((item) => /goggles/i.test(String(item)))) {
|
|
540
854
|
clearReading(root);
|
|
541
855
|
clearCardLine(root);
|
|
542
856
|
return null;
|
|
@@ -574,15 +888,21 @@ function intentWords(text) {
|
|
|
574
888
|
return String(text || '').toLowerCase().split(/[^a-z0-9-]+/).filter(Boolean);
|
|
575
889
|
}
|
|
576
890
|
|
|
891
|
+
function lookVerb(words) {
|
|
892
|
+
return words.includes('look') || words.includes('name');
|
|
893
|
+
}
|
|
894
|
+
|
|
577
895
|
export function handCard(root, intent) {
|
|
578
896
|
try {
|
|
579
|
-
clearCardLine(root);
|
|
580
897
|
const text = String(intent || '');
|
|
581
|
-
|
|
898
|
+
const words = intentWords(text);
|
|
899
|
+
if (isGogglesWork(text) && text.length <= 200) {
|
|
582
900
|
clearReading(root);
|
|
901
|
+
clearCardLine(root);
|
|
583
902
|
return null;
|
|
584
903
|
}
|
|
585
|
-
|
|
904
|
+
if (!lookVerb(words)) return null;
|
|
905
|
+
clearCardLine(root);
|
|
586
906
|
const looked = readGoggles(words);
|
|
587
907
|
if (looked.text.startsWith('Actuality. Drift')) {
|
|
588
908
|
saveReading(root, { drift: looked.text });
|
|
@@ -615,10 +935,11 @@ export function lensPath(root) {
|
|
|
615
935
|
|
|
616
936
|
|
|
617
937
|
export function maintainLens(root) {
|
|
618
|
-
const
|
|
938
|
+
const drift = actualityLine();
|
|
939
|
+
const text = drift || 'quiet (match)';
|
|
619
940
|
mkdirSync(dirname(lensPath(root)), { recursive: true });
|
|
620
941
|
writeFileSync(lensPath(root), `${text}\n`);
|
|
621
|
-
return ok(
|
|
942
|
+
return ok(drift);
|
|
622
943
|
}
|
|
623
944
|
|
|
624
945
|
|
|
@@ -179,10 +179,24 @@ function stampPlateIfMissing(projectRoot, id) {
|
|
|
179
179
|
return { id, path, written: true };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
function organFile(intent) {
|
|
183
|
+
try {
|
|
184
|
+
const { suitHint } = require("./goggles-pipeline.mjs");
|
|
185
|
+
const hint = suitHint(String(intent || ""));
|
|
186
|
+
if (!hint || hint === "Name one plane." || hint.includes("\n")) return "";
|
|
187
|
+
const file = hint.split(": ").slice(1).join(": ").trim();
|
|
188
|
+
return file;
|
|
189
|
+
} catch {
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
182
194
|
function plateStockLine(intent) {
|
|
183
195
|
const plate = recallPlate(intent);
|
|
184
196
|
if (!plate) return null;
|
|
185
|
-
|
|
197
|
+
const line = `Plate: ${plate.id} — .xray/state/plates/${plate.id}.md`;
|
|
198
|
+
const file = organFile(intent);
|
|
199
|
+
return file ? `${line} File: ${file}` : line;
|
|
186
200
|
}
|
|
187
201
|
|
|
188
202
|
module.exports = {
|