0xray 4.0.31 → 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/.mcp.json +8 -0
- 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 +5 -5
- package/.opencode/workflows/post-deployment-audit.yml +123 -0
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +16 -0
- package/dist/cli/commands/status.d.ts +4 -2
- package/dist/cli/commands/status.js +6 -3
- package/dist/cli/commands/validate.js +4 -0
- package/dist/cli/goggles-status.d.ts +13 -0
- package/dist/cli/goggles-status.js +76 -0
- package/dist/cli/index.js +31 -7
- package/dist/governance/governance-service.js +11 -1
- package/dist/integrations/grok/grok-cli.d.ts +3 -0
- package/dist/integrations/grok/grok-cli.js +41 -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 +338 -0
- package/dist/integrations/hooks/goggles-pipeline.d.mts +2 -0
- package/dist/integrations/hooks/goggles-pipeline.mjs +485 -100
- 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 +5 -5
- package/package.json +2 -1
- package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
- package/scripts/foundry/mint-suit.cjs +6 -1
- package/scripts/mjs/run-goggles-mcp.mjs +41 -0
- package/scripts/node/bridge-mcp-wiring.cjs +61 -21
- 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 +5 -5
- 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
|
|
|
@@ -14,9 +16,16 @@ const CARD_PLANES = ['ground', 'routing', 'house', 'boot', 'governance', 'memory
|
|
|
14
16
|
const CARD_FLAVORS = ['digest', 'triage'];
|
|
15
17
|
const SCOPE_ZOOM = ['ecosystem', 'part', 'one flow', 'one artifact'];
|
|
16
18
|
|
|
19
|
+
/** Empty GOGGLES_PLANES_PATH keeps the file beside the organ. */
|
|
20
|
+
export function planesFile() {
|
|
21
|
+
const override = process.env.GOGGLES_PLANES_PATH;
|
|
22
|
+
if (typeof override === 'string' && override.trim()) return override.trim();
|
|
23
|
+
return join(HERE, 'goggles-planes.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
function drawnPlanes() {
|
|
18
27
|
try {
|
|
19
|
-
const data = JSON.parse(readFileSync(
|
|
28
|
+
const data = JSON.parse(readFileSync(planesFile(), 'utf8'));
|
|
20
29
|
return Array.isArray(data.planes) ? data.planes.map((name) => String(name)) : [];
|
|
21
30
|
} catch {
|
|
22
31
|
return [];
|
|
@@ -169,7 +178,7 @@ function namesTheSet(lower) {
|
|
|
169
178
|
return planes.length === 1 && planes[0] === 'loop' && findScopes(lower).length === 0;
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
|
|
181
|
+
function gogglesAnswer(argv) {
|
|
173
182
|
const words = [];
|
|
174
183
|
for (const raw of argv || []) {
|
|
175
184
|
const arg = String(raw || '').trim();
|
|
@@ -177,102 +186,148 @@ export function readGoggles(argv) {
|
|
|
177
186
|
words.push(arg);
|
|
178
187
|
}
|
|
179
188
|
const lower = words.map((word) => word.toLowerCase());
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
if (words
|
|
183
|
-
if (
|
|
189
|
+
const none = (text) => ({ text, cards: null });
|
|
190
|
+
if (lower.includes('calling')) return none('');
|
|
191
|
+
if (kindPastOne(words)) return none('');
|
|
192
|
+
if (words.length === 0) return none('');
|
|
193
|
+
if (namesTheSet(lower)) return none('Name one plane.');
|
|
184
194
|
if (isKindZero(lower)) {
|
|
185
|
-
if (findScopes(words).length) return
|
|
186
|
-
if (acceptedPlanes().some((name) => lower.includes(name))) return
|
|
187
|
-
return
|
|
195
|
+
if (findScopes(words).length) return none('');
|
|
196
|
+
if (acceptedPlanes().some((name) => lower.includes(name))) return none('');
|
|
197
|
+
return none(actualityLine());
|
|
188
198
|
}
|
|
189
199
|
const flavors = CARD_FLAVORS.filter((name) => lower.includes(name));
|
|
190
|
-
if (flavors.length > 1) return
|
|
200
|
+
if (flavors.length > 1) return none('');
|
|
191
201
|
if (flavors.length === 1) {
|
|
192
202
|
const others = acceptedPlanes().filter((name) => name !== flavors[0] && lower.includes(name));
|
|
193
|
-
if (others.length) return
|
|
194
|
-
|
|
203
|
+
if (others.length) return none('');
|
|
204
|
+
const cards = collectCards(flavors[0], lower);
|
|
205
|
+
if (!cards) return none('');
|
|
206
|
+
return { text: cards.map((card) => formatCardText(card)).join('\n\n'), cards };
|
|
195
207
|
}
|
|
196
208
|
const planes = acceptedPlanes().filter((name) => lower.includes(name));
|
|
197
|
-
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
|
+
}
|
|
198
218
|
const scopes = findScopes(words.filter((word) => word.toLowerCase() !== planes[0]));
|
|
199
|
-
if (scopes.length > 1) return
|
|
200
|
-
|
|
219
|
+
if (scopes.length > 1) return none('');
|
|
220
|
+
const line = readingLine(planes[0], scopes[0] || '');
|
|
221
|
+
const file = moduleFile(planes[0]);
|
|
222
|
+
return none(file ? `${line} File: ${file}` : line);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function readGoggles(argv) {
|
|
226
|
+
return ok(gogglesAnswer(argv).text);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function lookCards(argv) {
|
|
230
|
+
return gogglesAnswer(argv).cards;
|
|
201
231
|
}
|
|
202
232
|
|
|
203
233
|
function cardPlanesNamed(lower) {
|
|
204
234
|
return CARD_PLANES.filter((name) => lower.includes(name));
|
|
205
235
|
}
|
|
206
236
|
|
|
207
|
-
function
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
237
|
+
function listedFiles(plane) {
|
|
238
|
+
return Array.isArray(plane.files) ? plane.files.map(String) : [];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function cardView(plane, scope, flavor, root) {
|
|
242
|
+
const listed = listedFiles(plane);
|
|
243
|
+
const view = {
|
|
244
|
+
plane: plane.id,
|
|
245
|
+
flavor,
|
|
246
|
+
scope: scope || '',
|
|
247
|
+
from: plane.id === 'ground' ? '' : (plane.from || 'ground'),
|
|
248
|
+
digest: plane.digest || '',
|
|
249
|
+
plate: plane.plate || '',
|
|
250
|
+
entry: plane.entry || '',
|
|
251
|
+
exit: plane.exit || '',
|
|
252
|
+
files: listed,
|
|
253
|
+
skills: plane.skills || '',
|
|
254
|
+
setup: plane.setup || '',
|
|
255
|
+
teardown: plane.teardown || '',
|
|
256
|
+
worn: plane.worn || '',
|
|
257
|
+
};
|
|
258
|
+
if (flavor === 'triage') {
|
|
259
|
+
const filled = new Set(filledOf(plane));
|
|
260
|
+
view.empty = FIELD_ORDER.filter((name) => !filled.has(name));
|
|
261
|
+
view.exam = examinePlane(plane, root).text;
|
|
213
262
|
}
|
|
214
|
-
return
|
|
263
|
+
return view;
|
|
215
264
|
}
|
|
216
265
|
|
|
217
|
-
function
|
|
218
|
-
const files = Array.isArray(plane.files) ? plane.files : [];
|
|
219
|
-
if (scope === 'one artifact' && files.length !== 1) return null;
|
|
266
|
+
function formatCardText(view) {
|
|
220
267
|
const rows = [
|
|
221
|
-
['Plane', plane
|
|
222
|
-
['From',
|
|
223
|
-
['Digest',
|
|
224
|
-
['Plate',
|
|
225
|
-
['Entry',
|
|
226
|
-
['Exit',
|
|
227
|
-
['Files',
|
|
228
|
-
['Skills',
|
|
229
|
-
['Setup',
|
|
230
|
-
['Teardown',
|
|
231
|
-
['Worn',
|
|
268
|
+
['Plane', view.plane],
|
|
269
|
+
['From', view.from],
|
|
270
|
+
['Digest', view.digest],
|
|
271
|
+
['Plate', view.plate],
|
|
272
|
+
['Entry', view.entry],
|
|
273
|
+
['Exit', view.exit],
|
|
274
|
+
['Files', view.files.join(', ')],
|
|
275
|
+
['Skills', view.skills],
|
|
276
|
+
['Setup', view.setup],
|
|
277
|
+
['Teardown', view.teardown],
|
|
278
|
+
['Worn', view.worn],
|
|
232
279
|
];
|
|
233
|
-
|
|
234
|
-
|
|
280
|
+
let text = rows.map(([key, value]) => `${key}: ${value}`.trimEnd()).join('\n');
|
|
281
|
+
if (view.flavor === 'triage') {
|
|
282
|
+
const lines = [];
|
|
283
|
+
if (view.empty.length) lines.push(`Empty: ${view.empty.join(', ')}`);
|
|
284
|
+
lines.push(view.exam);
|
|
285
|
+
text = `${text}\n${lines.join('\n')}`;
|
|
235
286
|
}
|
|
236
|
-
return
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function triageNote(plane, root) {
|
|
240
|
-
const filled = new Set(filledOf(plane));
|
|
241
|
-
const empty = FIELD_ORDER.filter((name) => !filled.has(name));
|
|
242
|
-
const exam = examinePlane(plane, root);
|
|
243
|
-
const lines = [];
|
|
244
|
-
if (empty.length) lines.push(`Empty: ${empty.join(', ')}`);
|
|
245
|
-
lines.push(exam.text);
|
|
246
|
-
return lines.join('\n');
|
|
287
|
+
return text;
|
|
247
288
|
}
|
|
248
289
|
|
|
249
|
-
function
|
|
250
|
-
const rows =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
290
|
+
export function formatCardPane(view) {
|
|
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
|
+
];
|
|
304
|
+
if (view.flavor === 'triage') {
|
|
305
|
+
if (Array.isArray(view.empty) && view.empty.length) rows.push(['Empty', view.empty.join(', ')]);
|
|
306
|
+
if (view.exam) rows.push(['', view.exam]);
|
|
307
|
+
}
|
|
308
|
+
const body = rows.map(([label, value]) => (label ? `${label.padEnd(8)} ${value}` : value).trimEnd());
|
|
309
|
+
const title = `${view.flavor} · ${view.plane}`;
|
|
310
|
+
const width = Math.max(title.length + 2, ...body.map((line) => line.length));
|
|
311
|
+
const top = `┌ ${title} ${'─'.repeat(Math.max(0, width - title.length - 1))}┐`;
|
|
312
|
+
const mid = body.map((line) => `│ ${line.padEnd(width)} │`);
|
|
313
|
+
const bot = `└${'─'.repeat(width + 2)}┘`;
|
|
314
|
+
return [top, ...mid, bot].join('\n');
|
|
255
315
|
}
|
|
256
316
|
|
|
257
|
-
function
|
|
317
|
+
function collectCards(flavor, lower) {
|
|
258
318
|
const named = cardPlanesNamed(lower);
|
|
259
319
|
const scopes = findScopes(lower.filter((word) => word !== flavor && !named.includes(word)));
|
|
260
|
-
if (
|
|
320
|
+
if (scopes.length > 1) return null;
|
|
261
321
|
const scope = scopes[0] || '';
|
|
262
|
-
if (!named.length && (scope === 'part' || scope === 'one flow' || scope === 'one artifact')) return '';
|
|
263
322
|
const platesDir = findPlatesDir(HERE);
|
|
264
|
-
if (!platesDir) return '';
|
|
265
323
|
const ids = named.length ? named : CARD_PLANES;
|
|
266
|
-
const root =
|
|
267
|
-
const
|
|
324
|
+
const root = sourceRoot(platesDir);
|
|
325
|
+
const cards = [];
|
|
268
326
|
for (const id of ids) {
|
|
269
|
-
const plane =
|
|
270
|
-
|
|
271
|
-
const text = formatPlaneCard(plane, scope, flavor, root);
|
|
272
|
-
if (text === null) return '';
|
|
273
|
-
blocks.push(text);
|
|
327
|
+
const plane = growPlane(id, platesDir, stateRoot());
|
|
328
|
+
cards.push(cardView(plane, scope, flavor, root));
|
|
274
329
|
}
|
|
275
|
-
return
|
|
330
|
+
return cards;
|
|
276
331
|
}
|
|
277
332
|
|
|
278
333
|
export function findPlatesDir(start) {
|
|
@@ -304,6 +359,45 @@ function boxTitles(line) {
|
|
|
304
359
|
return titles;
|
|
305
360
|
}
|
|
306
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
|
+
|
|
307
401
|
function doorsOf(body) {
|
|
308
402
|
let layer = '';
|
|
309
403
|
let want = false;
|
|
@@ -347,7 +441,7 @@ function takeOf(body) {
|
|
|
347
441
|
|
|
348
442
|
|
|
349
443
|
function overlayTable() {
|
|
350
|
-
const file =
|
|
444
|
+
const file = planesFile();
|
|
351
445
|
if (!existsSync(file)) return {};
|
|
352
446
|
return JSON.parse(readFileSync(file, 'utf8'));
|
|
353
447
|
}
|
|
@@ -371,40 +465,322 @@ export function filledOf(plane) {
|
|
|
371
465
|
return FIELD_ORDER.filter((name) => flags[name]);
|
|
372
466
|
}
|
|
373
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
|
+
|
|
374
500
|
export function assemblePlane(id, platesDir) {
|
|
375
501
|
const extra = overlayTable()[id] || {};
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (!existsSync(abs)) return null;
|
|
382
|
-
const raw = readFileSync(abs, 'utf8');
|
|
383
|
-
plate = join('docs-site', 'docs', 'plates', `${id}.md`);
|
|
384
|
-
if (!digest) digest = takeOf(stripFrontmatter(raw));
|
|
385
|
-
doors = doorsOf(raw);
|
|
386
|
-
}
|
|
387
|
-
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) : [];
|
|
388
507
|
return {
|
|
389
508
|
id,
|
|
390
509
|
digest,
|
|
391
|
-
plate,
|
|
392
|
-
entry: extra.entry || doors.entry ||
|
|
393
|
-
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) || '',
|
|
394
513
|
entryIsMark: Boolean(extra.entry),
|
|
395
514
|
exitIsMark: Boolean(extra.exit),
|
|
396
|
-
files
|
|
515
|
+
files,
|
|
397
516
|
unpathed: extra.unpathed || null,
|
|
398
|
-
skills: extra.skills ||
|
|
399
|
-
setup: extra.setup ||
|
|
400
|
-
teardown: extra.teardown ||
|
|
401
|
-
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),
|
|
402
522
|
mark: extra.mark || null,
|
|
403
523
|
wornMark: extra.wornMark || null,
|
|
404
524
|
pick: extra.pick || null,
|
|
405
525
|
};
|
|
406
526
|
}
|
|
407
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
|
+
|
|
408
784
|
function repoRootFrom(platesDir) {
|
|
409
785
|
return dirname(dirname(dirname(platesDir)));
|
|
410
786
|
}
|
|
@@ -473,8 +849,8 @@ function isGogglesWork(text) {
|
|
|
473
849
|
|
|
474
850
|
|
|
475
851
|
export function cardStop(root, toolName, text, paths = []) {
|
|
476
|
-
const
|
|
477
|
-
if (isGogglesWork(
|
|
852
|
+
const spoken = String(text || '');
|
|
853
|
+
if (spoken.length <= 200 && isGogglesWork(spoken) && !(paths || []).some((item) => /goggles/i.test(String(item)))) {
|
|
478
854
|
clearReading(root);
|
|
479
855
|
clearCardLine(root);
|
|
480
856
|
return null;
|
|
@@ -512,15 +888,21 @@ function intentWords(text) {
|
|
|
512
888
|
return String(text || '').toLowerCase().split(/[^a-z0-9-]+/).filter(Boolean);
|
|
513
889
|
}
|
|
514
890
|
|
|
891
|
+
function lookVerb(words) {
|
|
892
|
+
return words.includes('look') || words.includes('name');
|
|
893
|
+
}
|
|
894
|
+
|
|
515
895
|
export function handCard(root, intent) {
|
|
516
896
|
try {
|
|
517
|
-
clearCardLine(root);
|
|
518
897
|
const text = String(intent || '');
|
|
519
|
-
|
|
898
|
+
const words = intentWords(text);
|
|
899
|
+
if (isGogglesWork(text) && text.length <= 200) {
|
|
520
900
|
clearReading(root);
|
|
901
|
+
clearCardLine(root);
|
|
521
902
|
return null;
|
|
522
903
|
}
|
|
523
|
-
|
|
904
|
+
if (!lookVerb(words)) return null;
|
|
905
|
+
clearCardLine(root);
|
|
524
906
|
const looked = readGoggles(words);
|
|
525
907
|
if (looked.text.startsWith('Actuality. Drift')) {
|
|
526
908
|
saveReading(root, { drift: looked.text });
|
|
@@ -553,16 +935,19 @@ export function lensPath(root) {
|
|
|
553
935
|
|
|
554
936
|
|
|
555
937
|
export function maintainLens(root) {
|
|
556
|
-
const
|
|
938
|
+
const drift = actualityLine();
|
|
939
|
+
const text = drift || 'quiet (match)';
|
|
557
940
|
mkdirSync(dirname(lensPath(root)), { recursive: true });
|
|
558
941
|
writeFileSync(lensPath(root), `${text}\n`);
|
|
559
|
-
return ok(
|
|
942
|
+
return ok(drift);
|
|
560
943
|
}
|
|
561
944
|
|
|
562
945
|
|
|
563
946
|
const isMain = Boolean(process.argv[1]) && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
564
947
|
if (isMain) {
|
|
565
|
-
const
|
|
566
|
-
process.stdout.
|
|
567
|
-
|
|
948
|
+
const answered = gogglesAnswer(process.argv.slice(2));
|
|
949
|
+
const pane = process.stdout.isTTY && Array.isArray(answered.cards) && answered.cards.length > 0;
|
|
950
|
+
const text = pane ? answered.cards.map((card) => formatCardPane(card)).join('\n\n') : answered.text;
|
|
951
|
+
process.stdout.write(`${text}\n`);
|
|
952
|
+
process.exit(0);
|
|
568
953
|
}
|