0xray 4.0.31 → 4.0.32
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/skills/orchestrator/SKILL.md +2 -2
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +8 -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/integrations/grok/grok-cli.d.ts +3 -0
- package/dist/integrations/grok/grok-cli.js +41 -1
- package/dist/integrations/hooks/goggles-mcp.mjs +371 -0
- package/dist/integrations/hooks/goggles-pipeline.mjs +131 -67
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/skills/orchestrator/SKILL.md +2 -2
- package/package.json +2 -1
- package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
- package/scripts/mjs/run-goggles-mcp.mjs +41 -0
- package/scripts/node/bridge-mcp-wiring.cjs +61 -21
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/orchestrator/SKILL.md +2 -2
- package/xray/features.json +1 -1
package/.grok-plugin/plugin.json
CHANGED
package/.mcp.json
CHANGED
|
@@ -34,6 +34,14 @@
|
|
|
34
34
|
"repertoire": {
|
|
35
35
|
"command": "node",
|
|
36
36
|
"args": ["scripts/mjs/run-repertoire-mcp.mjs"]
|
|
37
|
+
},
|
|
38
|
+
"goggles": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["scripts/mjs/run-goggles-mcp.mjs"],
|
|
41
|
+
"env": {
|
|
42
|
+
"GOGGLES_ROOT": ".",
|
|
43
|
+
"GOGGLES_PLANES_PATH": ""
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
47
|
}
|
|
@@ -55,11 +55,11 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
|
|
|
55
55
|
|
|
56
56
|
## Goggles
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
|
|
59
59
|
|
|
60
60
|
Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet. It does not read the suit that is running. It is not a plate type.
|
|
61
61
|
|
|
62
|
-
Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
|
|
62
|
+
Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
|
|
63
63
|
|
|
64
64
|
`digest` and `triage` return the card already built by `assemblePlane`. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Empty stays empty. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. `digest` fills the card. Entry and exit are the plate's input and output box titles when that plate has them. Setup and teardown stay empty unless the row names them. The mill path is `scripts/foundry`. `triage` adds the empty fields and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact. One file is one artifact. Several files are not narrowed to a guessed file.
|
|
65
65
|
|
package/.xray/features.json
CHANGED
package/dist/CHANGELOG.md
CHANGED
|
@@ -42,6 +42,14 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
42
42
|
- Archive root `integrations/` costume SKILL dump → `docs/archive/integrations-costume/`. Live wear is `src/integrations`.
|
|
43
43
|
- Archive root `commands/` stale copy → `docs/archive/commands-copy/`. Live wear is `src/opencode/commands`.
|
|
44
44
|
|
|
45
|
+
## [4.0.32] - 2026-10-01
|
|
46
|
+
|
|
47
|
+
Next patch after npm `4.0.31`. Not published from this commit.
|
|
48
|
+
|
|
49
|
+
Organ bolster for goggles-pipeline after #167. `0xray look` (and `goggles`) opens one plane through the organ. Digest and triage return the card pane. Status and health show the organ worn and Kind 0 quiet/match. Bot Plugin MCP `goggles` exposes lean tools `look` and `status_lens` over stdio on the same organ. Kind 0 name-checks the six outers and stays quiet on match; reading stays a dichotomy; digest/triage stay the card. Calling and confer stay off. Companion `@0xray/grok-bot` is unchanged at 0.1.8.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
45
53
|
## [4.0.31] - 2026-10-01
|
|
46
54
|
|
|
47
55
|
Next patch after npm `4.0.30`. Not published from this commit.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Usage: npx 0xray status
|
|
12
12
|
*/
|
|
13
|
+
import type { GogglesStatus } from "../goggles-status.js";
|
|
13
14
|
interface StatusReport {
|
|
14
15
|
opencode: {
|
|
15
16
|
installed: boolean;
|
|
@@ -34,8 +35,9 @@ interface StatusReport {
|
|
|
34
35
|
outcomesCount: number;
|
|
35
36
|
patternsCount: number;
|
|
36
37
|
};
|
|
38
|
+
goggles: GogglesStatus;
|
|
37
39
|
}
|
|
38
|
-
export declare function getStatusReport(cwd?: string): StatusReport;
|
|
40
|
+
export declare function getStatusReport(cwd?: string, packageRoot?: string): StatusReport;
|
|
39
41
|
export declare function printStatus(report: StatusReport): void;
|
|
40
|
-
export declare function statusCommand(): Promise<void>;
|
|
42
|
+
export declare function statusCommand(packageRoot?: string): Promise<void>;
|
|
41
43
|
export default statusCommand;
|
|
@@ -14,6 +14,7 @@ import { readdirSync, existsSync, readFileSync } from "fs";
|
|
|
14
14
|
import { join } from "path";
|
|
15
15
|
import { getConfigDir } from "../../core/config-paths.js";
|
|
16
16
|
import { featuresConfigLoader } from "../../core/features-config.js";
|
|
17
|
+
import { formatGogglesStatus, probeGoggles } from "../goggles-status.js";
|
|
17
18
|
function getSkillsList(cwd) {
|
|
18
19
|
const skills = [];
|
|
19
20
|
const configDir = getConfigDir(cwd);
|
|
@@ -119,7 +120,7 @@ function getInferenceStatus(cwd) {
|
|
|
119
120
|
catch { /* ignore */ }
|
|
120
121
|
return { active, lastTuning, outcomesCount, patternsCount };
|
|
121
122
|
}
|
|
122
|
-
export function getStatusReport(cwd = process.cwd()) {
|
|
123
|
+
export function getStatusReport(cwd = process.cwd(), packageRoot = cwd) {
|
|
123
124
|
const opencodeConfigPath = join(cwd, "opencode.json");
|
|
124
125
|
const cwdSkills = getSkillsList(cwd);
|
|
125
126
|
const agents = getAgentsList(cwd);
|
|
@@ -134,6 +135,7 @@ export function getStatusReport(cwd = process.cwd()) {
|
|
|
134
135
|
agents,
|
|
135
136
|
health,
|
|
136
137
|
inference,
|
|
138
|
+
goggles: probeGoggles(packageRoot, cwd),
|
|
137
139
|
};
|
|
138
140
|
}
|
|
139
141
|
export function printStatus(report) {
|
|
@@ -166,6 +168,7 @@ export function printStatus(report) {
|
|
|
166
168
|
console.log(`⚙️ Inference: ${inferenceStatus} (${report.inference.outcomesCount} outcomes, ${report.inference.patternsCount} patterns)`);
|
|
167
169
|
console.log(`📊 Health: ${healthStatus}`);
|
|
168
170
|
console.log("");
|
|
171
|
+
process.stdout.write(`${formatGogglesStatus(report.goggles).join("\n")}\n`);
|
|
169
172
|
if (report.skills.names.length > 0) {
|
|
170
173
|
console.log("Installed Skills:");
|
|
171
174
|
const cols = 4;
|
|
@@ -191,9 +194,9 @@ export function printStatus(report) {
|
|
|
191
194
|
console.log("");
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
|
-
export async function statusCommand() {
|
|
197
|
+
export async function statusCommand(packageRoot = process.cwd()) {
|
|
195
198
|
const cwd = process.cwd();
|
|
196
|
-
const report = getStatusReport(cwd);
|
|
199
|
+
const report = getStatusReport(cwd, packageRoot);
|
|
197
200
|
printStatus(report);
|
|
198
201
|
}
|
|
199
202
|
export default statusCommand;
|
|
@@ -23,6 +23,10 @@ export const VALIDATE_PACK_PATHS = [
|
|
|
23
23
|
"dist/integrations/cursor/hooks/pre-compact.sh",
|
|
24
24
|
"dist/integrations/cursor/hooks/after-file-edit.sh",
|
|
25
25
|
"dist/integrations/hooks/plates.cjs",
|
|
26
|
+
"dist/integrations/hooks/goggles-pipeline.mjs",
|
|
27
|
+
"dist/integrations/hooks/goggles-planes.json",
|
|
28
|
+
"dist/integrations/hooks/goggles-mcp.mjs",
|
|
29
|
+
"scripts/mjs/run-goggles-mcp.mjs",
|
|
26
30
|
"docs-site/docs/plates/memory-recall.md",
|
|
27
31
|
"docs-site/docs/plates/processor.md",
|
|
28
32
|
"docs-site/docs/plates/goggles.md",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goggles on status and health.
|
|
3
|
+
* Organ present is not Kind 0. Quiet is a match. No phone-home.
|
|
4
|
+
*/
|
|
5
|
+
export type GogglesStatus = {
|
|
6
|
+
organPresent: boolean;
|
|
7
|
+
kind0Text: string;
|
|
8
|
+
kind0Quiet: boolean;
|
|
9
|
+
kind0Label: string;
|
|
10
|
+
organLine: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function probeGoggles(packageRoot: string, cwd: string): GogglesStatus;
|
|
13
|
+
export declare function formatGogglesStatus(probe: GogglesStatus): string[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goggles on status and health.
|
|
3
|
+
* Organ present is not Kind 0. Quiet is a match. No phone-home.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
const WORN_PLANES = ["dichotomy", "syncopate", "synthesis", "digest", "triage", "loop"];
|
|
8
|
+
const ORGAN_PAIRS = [
|
|
9
|
+
["dist/integrations/hooks/goggles-pipeline.mjs", "dist/integrations/hooks/goggles-planes.json"],
|
|
10
|
+
["src/integrations/hooks/goggles-pipeline.mjs", "src/integrations/hooks/goggles-planes.json"],
|
|
11
|
+
];
|
|
12
|
+
function organPair(packageRoot) {
|
|
13
|
+
for (const pair of ORGAN_PAIRS) {
|
|
14
|
+
if (existsSync(join(packageRoot, pair[0])) && existsSync(join(packageRoot, pair[1]))) {
|
|
15
|
+
return pair;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function lensText(cwd) {
|
|
21
|
+
const file = join(cwd, ".xray", "state", "LENS.md");
|
|
22
|
+
if (!existsSync(file))
|
|
23
|
+
return { found: false, text: "" };
|
|
24
|
+
try {
|
|
25
|
+
return { found: true, text: readFileSync(file, "utf8").trim() };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { found: false, text: "" };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function mapDrift(packageRoot, planesRel) {
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(readFileSync(join(packageRoot, planesRel), "utf8"));
|
|
34
|
+
const map = Array.isArray(data.planes) ? data.planes.map((name) => String(name)) : [];
|
|
35
|
+
const same = map.length === WORN_PLANES.length && map.every((name, index) => name === WORN_PLANES[index]);
|
|
36
|
+
return same ? "" : "Actuality. Drift: worn is not the map.";
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function probeGoggles(packageRoot, cwd) {
|
|
43
|
+
const pair = organPair(packageRoot);
|
|
44
|
+
const organPresent = pair !== null;
|
|
45
|
+
const lens = lensText(cwd);
|
|
46
|
+
let kind0Text = "";
|
|
47
|
+
let kind0Label = "(no LENS yet)";
|
|
48
|
+
let kind0Quiet = false;
|
|
49
|
+
if (lens.found) {
|
|
50
|
+
kind0Text = lens.text;
|
|
51
|
+
if (!lens.text) {
|
|
52
|
+
kind0Quiet = true;
|
|
53
|
+
kind0Label = "quiet (match)";
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
kind0Label = lens.text;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (pair) {
|
|
60
|
+
const drift = mapDrift(packageRoot, pair[1]);
|
|
61
|
+
if (drift) {
|
|
62
|
+
kind0Text = drift;
|
|
63
|
+
kind0Label = drift;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
organPresent,
|
|
68
|
+
kind0Text,
|
|
69
|
+
kind0Quiet,
|
|
70
|
+
kind0Label,
|
|
71
|
+
organLine: organPresent ? "Goggles: worn" : "Goggles: not found",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function formatGogglesStatus(probe) {
|
|
75
|
+
return [probe.organLine, `Kind 0: ${probe.kind0Label}`];
|
|
76
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -76,20 +76,42 @@ program
|
|
|
76
76
|
process.exit(1);
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.argument("[args...]", "plane, then examine, triage, cascade, or teardown")
|
|
82
|
-
.description("Glimpse every plane, or one plane's way in. A hit does not open the plane.")
|
|
83
|
-
.action((args) => {
|
|
79
|
+
const LOOK_HELP = "Kind 0 is quiet on a match. One outer is a reading. digest and triage return a card.";
|
|
80
|
+
function gogglesScript() {
|
|
84
81
|
const built = join(packageRoot, "dist", "integrations", "hooks", "goggles-pipeline.mjs");
|
|
85
82
|
const source = join(packageRoot, "src", "integrations", "hooks", "goggles-pipeline.mjs");
|
|
86
|
-
|
|
83
|
+
return existsSync(built) ? built : source;
|
|
84
|
+
}
|
|
85
|
+
function runGoggles(args) {
|
|
86
|
+
const script = gogglesScript();
|
|
87
|
+
if (!existsSync(script)) {
|
|
88
|
+
process.stderr.write("Goggles: not found\n");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
if (process.stdout.isTTY) {
|
|
92
|
+
const result = spawnSync(process.execPath, [script, ...(args ?? [])], { stdio: "inherit" });
|
|
93
|
+
process.exit(result.status ?? 1);
|
|
94
|
+
}
|
|
87
95
|
const result = spawnSync(process.execPath, [script, ...(args ?? [])], { encoding: "utf8" });
|
|
88
96
|
if (result.stdout)
|
|
89
97
|
process.stdout.write(result.stdout);
|
|
90
98
|
if (result.stderr)
|
|
91
99
|
process.stderr.write(result.stderr);
|
|
92
100
|
process.exit(result.status ?? 1);
|
|
101
|
+
}
|
|
102
|
+
program
|
|
103
|
+
.command("look")
|
|
104
|
+
.argument("[args...]", "kind 0, one outer, or digest/triage and one card plane")
|
|
105
|
+
.description(LOOK_HELP)
|
|
106
|
+
.action((args) => {
|
|
107
|
+
runGoggles(args);
|
|
108
|
+
});
|
|
109
|
+
program
|
|
110
|
+
.command("goggles")
|
|
111
|
+
.argument("[args...]", "same words as look")
|
|
112
|
+
.description(`Same organ as look. ${LOOK_HELP}`)
|
|
113
|
+
.action((args) => {
|
|
114
|
+
runGoggles(args);
|
|
93
115
|
});
|
|
94
116
|
program
|
|
95
117
|
.command("wear")
|
|
@@ -130,7 +152,7 @@ program
|
|
|
130
152
|
.description("Show comprehensive xray framework status")
|
|
131
153
|
.action(async () => {
|
|
132
154
|
const { statusCommand } = await import("./commands/status.js");
|
|
133
|
-
await statusCommand();
|
|
155
|
+
await statusCommand(packageRoot);
|
|
134
156
|
});
|
|
135
157
|
program
|
|
136
158
|
.command("validate")
|
|
@@ -267,6 +289,8 @@ program
|
|
|
267
289
|
}
|
|
268
290
|
}
|
|
269
291
|
console.log("");
|
|
292
|
+
const { probeGoggles, formatGogglesStatus } = await import("./goggles-status.js");
|
|
293
|
+
process.stdout.write(`${formatGogglesStatus(probeGoggles(packageRoot, process.cwd())).join("\n")}\n\n`);
|
|
270
294
|
if (allHealthy) {
|
|
271
295
|
console.log("🎉 Framework is healthy and ready to use!");
|
|
272
296
|
console.log("");
|
|
@@ -24,8 +24,11 @@ export interface GrokInstallOptions {
|
|
|
24
24
|
export declare function installForGrokCLI(options?: GrokInstallOptions): Promise<void>;
|
|
25
25
|
/** Grok TUI reads <project>/.grok/config.toml, not the package copy. */
|
|
26
26
|
export declare function writeProjectRepertoireMcp(projectRoot: string): string | null;
|
|
27
|
+
/** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
|
|
28
|
+
export declare function writeProjectGogglesMcp(projectRoot: string): string | null;
|
|
27
29
|
declare const _default: {
|
|
28
30
|
installForGrokCLI: typeof installForGrokCLI;
|
|
29
31
|
writeProjectRepertoireMcp: typeof writeProjectRepertoireMcp;
|
|
32
|
+
writeProjectGogglesMcp: typeof writeProjectGogglesMcp;
|
|
30
33
|
};
|
|
31
34
|
export default _default;
|
|
@@ -27,7 +27,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
27
27
|
const packageRoot = path.resolve(__dirname, '..', '..', '..');
|
|
28
28
|
const requireCjs = createRequire(import.meta.url);
|
|
29
29
|
const { resolveConsumerTargetDir, patchGrokHooks } = requireCjs(path.join(packageRoot, 'scripts/node/install-bridges.cjs'));
|
|
30
|
-
const { XRAY_MCP_SERVERS, resolveRepertoireMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
|
|
30
|
+
const { XRAY_MCP_SERVERS, resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
|
|
31
31
|
function registerGrokMcpServers(targetDir) {
|
|
32
32
|
try {
|
|
33
33
|
execSync('which grok', { stdio: 'ignore' });
|
|
@@ -113,6 +113,7 @@ export async function installForGrokCLI(options = {}) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
writeProjectRepertoireMcp(targetDir);
|
|
116
|
+
writeProjectGogglesMcp(targetDir);
|
|
116
117
|
mintAfterWear(targetDir);
|
|
117
118
|
const primary = dests[0];
|
|
118
119
|
try {
|
|
@@ -177,6 +178,7 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
const repertoireMcp = resolveRepertoireMcp(targetDir);
|
|
181
|
+
const gogglesMcp = resolveGogglesMcp(targetDir);
|
|
180
182
|
if (mcp.mcpServers) {
|
|
181
183
|
if (repertoireMcp) {
|
|
182
184
|
mcp.mcpServers.repertoire = {
|
|
@@ -187,6 +189,16 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
|
|
|
187
189
|
else {
|
|
188
190
|
delete mcp.mcpServers.repertoire;
|
|
189
191
|
}
|
|
192
|
+
if (gogglesMcp) {
|
|
193
|
+
mcp.mcpServers.goggles = {
|
|
194
|
+
command: 'node',
|
|
195
|
+
args: [gogglesMcp],
|
|
196
|
+
env: { GOGGLES_ROOT: targetDir, GOGGLES_PLANES_PATH: '' },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
delete mcp.mcpServers.goggles;
|
|
201
|
+
}
|
|
190
202
|
}
|
|
191
203
|
fs.writeFileSync(mcpPath, `${JSON.stringify(mcp, null, 2)}\n`);
|
|
192
204
|
}
|
|
@@ -217,7 +229,35 @@ enabled = true
|
|
|
217
229
|
}
|
|
218
230
|
return tomlPath;
|
|
219
231
|
}
|
|
232
|
+
/** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
|
|
233
|
+
export function writeProjectGogglesMcp(projectRoot) {
|
|
234
|
+
const gogglesMcp = resolveGogglesMcp(projectRoot);
|
|
235
|
+
if (!gogglesMcp)
|
|
236
|
+
return null;
|
|
237
|
+
const grokDir = path.join(projectRoot, '.grok');
|
|
238
|
+
fs.mkdirSync(grokDir, { recursive: true });
|
|
239
|
+
const tomlPath = path.join(grokDir, 'config.toml');
|
|
240
|
+
const block = `[mcp_servers.goggles]
|
|
241
|
+
command = "node"
|
|
242
|
+
args = [${JSON.stringify(gogglesMcp)}]
|
|
243
|
+
enabled = true
|
|
244
|
+
`;
|
|
245
|
+
let existing = '';
|
|
246
|
+
if (fs.existsSync(tomlPath)) {
|
|
247
|
+
existing = fs.readFileSync(tomlPath, 'utf8');
|
|
248
|
+
}
|
|
249
|
+
if (/\[mcp_servers\.goggles\]/.test(existing)) {
|
|
250
|
+
existing = existing.replace(/\[mcp_servers\.goggles\][\s\S]*?(?=\n\[|$)/, block.trim());
|
|
251
|
+
fs.writeFileSync(tomlPath, existing.endsWith('\n') ? existing : `${existing}\n`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const prefix = existing.trim() ? `${existing.trim()}\n\n` : '';
|
|
255
|
+
fs.writeFileSync(tomlPath, `${prefix}${block}`);
|
|
256
|
+
}
|
|
257
|
+
return tomlPath;
|
|
258
|
+
}
|
|
220
259
|
export default {
|
|
221
260
|
installForGrokCLI,
|
|
222
261
|
writeProjectRepertoireMcp,
|
|
262
|
+
writeProjectGogglesMcp,
|
|
223
263
|
};
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goggles MCP. Stdio. Server name `goggles`.
|
|
3
|
+
* Tools: look, status_lens. Same organ as the CLI.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { basename, join, resolve } from 'node:path';
|
|
7
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
8
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
import * as bundled from './goggles-pipeline.mjs';
|
|
12
|
+
|
|
13
|
+
const HERE = fileURLToPath(new URL('.', import.meta.url));
|
|
14
|
+
const SERVER_NAME = 'goggles';
|
|
15
|
+
const DRAWING = /docs-site\/docs\/plates\/[a-z0-9-]+\.md/i;
|
|
16
|
+
const CARD_PLANES = [
|
|
17
|
+
'ground',
|
|
18
|
+
'routing',
|
|
19
|
+
'house',
|
|
20
|
+
'boot',
|
|
21
|
+
'governance',
|
|
22
|
+
'memory-recall',
|
|
23
|
+
'orchestration',
|
|
24
|
+
'processor',
|
|
25
|
+
'reporting',
|
|
26
|
+
];
|
|
27
|
+
const SCOPES = ['ecosystem', 'part', 'one flow', 'one artifact'];
|
|
28
|
+
|
|
29
|
+
const LOOK_SCHEMA = {
|
|
30
|
+
type: 'object',
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
properties: {
|
|
33
|
+
kind: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Optional. Use "0" or "actuality" for the lens. Omit when naming an outer.',
|
|
36
|
+
enum: ['0', 'actuality'],
|
|
37
|
+
},
|
|
38
|
+
outer: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'One outer name. digest and triage return a card. The others return a reading.',
|
|
41
|
+
enum: ['dichotomy', 'syncopate', 'synthesis', 'digest', 'triage', 'loop'],
|
|
42
|
+
},
|
|
43
|
+
scope: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Zoom only. Not a kind. One level.',
|
|
46
|
+
enum: SCOPES,
|
|
47
|
+
},
|
|
48
|
+
plane: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Card plane for digest or triage only. Not the lens.',
|
|
51
|
+
enum: CARD_PLANES,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const TOOLS = [
|
|
57
|
+
{
|
|
58
|
+
name: 'look',
|
|
59
|
+
description: 'Kind 0 is quiet on a match. It checks the six outer names. One outer is a reading. digest and triage return a card.',
|
|
60
|
+
inputSchema: LOOK_SCHEMA,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'status_lens',
|
|
64
|
+
description: 'Whether the organ is worn, and the last Kind 0. Quiet is a match. A missing organ is not drift.',
|
|
65
|
+
inputSchema: { type: 'object', additionalProperties: false, properties: {} },
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
function serverVersion() {
|
|
70
|
+
try {
|
|
71
|
+
const pkg = JSON.parse(readFileSync(join(HERE, '../../../package.json'), 'utf8'));
|
|
72
|
+
return typeof pkg.version === 'string' && pkg.version ? pkg.version : 'local';
|
|
73
|
+
} catch {
|
|
74
|
+
return 'local';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function deny(code, reason) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
quiet: false,
|
|
82
|
+
mode: 'deny',
|
|
83
|
+
content: '',
|
|
84
|
+
error: { gate: 'goggles', decision: 'deny', code, reason },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function quiet(mode) {
|
|
89
|
+
return { ok: true, quiet: true, mode, content: '' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function organApi() {
|
|
93
|
+
const override = typeof process.env.GOGGLES_ORGAN_PATH === 'string'
|
|
94
|
+
? process.env.GOGGLES_ORGAN_PATH.trim()
|
|
95
|
+
: '';
|
|
96
|
+
if (!override) return bundled;
|
|
97
|
+
if (!existsSync(override)) return null;
|
|
98
|
+
return import(pathToFileURL(override).href);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function projectRoot(root) {
|
|
102
|
+
if (typeof root === 'string' && root.trim()) return root;
|
|
103
|
+
const env = process.env.GOGGLES_ROOT;
|
|
104
|
+
if (typeof env === 'string' && env.trim() && env.trim() !== '.') return env.trim();
|
|
105
|
+
return process.cwd();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function heldReading(root) {
|
|
109
|
+
try {
|
|
110
|
+
const data = JSON.parse(readFileSync(join(root, '.xray', 'state', 'goggles-reading.json'), 'utf8'));
|
|
111
|
+
if (!data || typeof data !== 'object') return null;
|
|
112
|
+
if (typeof data.drift === 'string' && data.drift) return { drift: data.drift };
|
|
113
|
+
if (typeof data.plane !== 'string' || !data.plane) return null;
|
|
114
|
+
return { plane: data.plane, scope: typeof data.scope === 'string' ? data.scope : '' };
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function denyCode(reason) {
|
|
121
|
+
if (/wider|crosses parts|not one flow/i.test(reason)) return 'scope_wider';
|
|
122
|
+
if (/Name one plane/i.test(reason)) return 'name_one';
|
|
123
|
+
return 'leave_plane';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function drawingPaths(args) {
|
|
127
|
+
const hits = [];
|
|
128
|
+
for (const value of Object.values(args)) {
|
|
129
|
+
if (typeof value === 'string' && DRAWING.test(value)) hits.push(value);
|
|
130
|
+
}
|
|
131
|
+
return hits;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function argvFrom(args) {
|
|
135
|
+
const words = [];
|
|
136
|
+
if (args.kind === '0') words.push('kind', '0');
|
|
137
|
+
else if (args.kind === 'actuality') words.push('actuality');
|
|
138
|
+
else if (args.kind === '1' || args.kind === '2') words.push('kind', String(args.kind));
|
|
139
|
+
else if (typeof args.kind === 'string') words.push(args.kind);
|
|
140
|
+
if (args.outer === 'outer loop') words.push('outer', 'loop');
|
|
141
|
+
else if (typeof args.outer === 'string') words.push(args.outer);
|
|
142
|
+
if (typeof args.plane === 'string' && !DRAWING.test(args.plane)) words.push(args.plane);
|
|
143
|
+
if (args.scope === 'one flow') words.push('one', 'flow');
|
|
144
|
+
else if (args.scope === 'one artifact') words.push('one', 'artifact');
|
|
145
|
+
else if (typeof args.scope === 'string') words.push(args.scope);
|
|
146
|
+
return words;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function pureKind0(args) {
|
|
150
|
+
return (args.kind === '0' || args.kind === 'actuality')
|
|
151
|
+
&& args.outer == null
|
|
152
|
+
&& args.plane == null
|
|
153
|
+
&& args.scope == null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function checkArgs(args) {
|
|
157
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
158
|
+
return deny('invalid_args', 'look takes an object.');
|
|
159
|
+
}
|
|
160
|
+
const allowed = new Set(['kind', 'outer', 'scope', 'plane']);
|
|
161
|
+
for (const key of Object.keys(args)) {
|
|
162
|
+
if (!allowed.has(key)) return deny('invalid_args', 'Unknown argument.');
|
|
163
|
+
}
|
|
164
|
+
for (const key of ['kind', 'outer', 'scope', 'plane']) {
|
|
165
|
+
if (args[key] != null && typeof args[key] !== 'string') {
|
|
166
|
+
return deny('invalid_args', `${key} must be a string.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (args.kind != null && !['0', 'actuality', '1', '2'].includes(args.kind)) {
|
|
170
|
+
return deny('invalid_args', 'kind is 0 or actuality.');
|
|
171
|
+
}
|
|
172
|
+
if (args.outer != null && !LOOK_SCHEMA.properties.outer.enum.includes(args.outer)) {
|
|
173
|
+
const setName = args.outer === 'outer' || args.outer === 'outer-loop' || args.outer === 'outer loop';
|
|
174
|
+
if (!setName) return deny('invalid_args', 'Name one outer.');
|
|
175
|
+
}
|
|
176
|
+
if (args.scope != null && !SCOPES.includes(args.scope)) {
|
|
177
|
+
return deny('invalid_args', 'scope is one zoom level.');
|
|
178
|
+
}
|
|
179
|
+
if (args.plane != null && !CARD_PLANES.includes(args.plane) && !DRAWING.test(args.plane)) {
|
|
180
|
+
return deny('invalid_args', 'plane is a card plane.');
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function publicCard(view) {
|
|
186
|
+
const notes = [];
|
|
187
|
+
if (view.flavor === 'triage') {
|
|
188
|
+
if (Array.isArray(view.empty) && view.empty.length) notes.push(`Empty: ${view.empty.join(', ')}`);
|
|
189
|
+
if (view.exam) notes.push(view.exam);
|
|
190
|
+
}
|
|
191
|
+
const skills = view.skills ? basename(String(view.skills)) : '';
|
|
192
|
+
if (view.narrow) {
|
|
193
|
+
return {
|
|
194
|
+
plane: view.plane,
|
|
195
|
+
flavor: view.flavor,
|
|
196
|
+
digest: view.digest || '',
|
|
197
|
+
files: Array.isArray(view.files) ? view.files : [],
|
|
198
|
+
notes,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
plane: view.plane,
|
|
203
|
+
flavor: view.flavor,
|
|
204
|
+
from: view.from || '',
|
|
205
|
+
digest: view.digest || '',
|
|
206
|
+
plate: view.plate || '',
|
|
207
|
+
entry: view.entry || '',
|
|
208
|
+
exit: view.exit || '',
|
|
209
|
+
files: Array.isArray(view.files) ? view.files : [],
|
|
210
|
+
skills,
|
|
211
|
+
setup: view.setup || '',
|
|
212
|
+
teardown: view.teardown || '',
|
|
213
|
+
worn: view.worn || '',
|
|
214
|
+
notes,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function answerLook(raw, root) {
|
|
219
|
+
if (raw != null && (typeof raw !== 'object' || Array.isArray(raw))) {
|
|
220
|
+
return deny('invalid_args', 'look takes an object.');
|
|
221
|
+
}
|
|
222
|
+
const args = raw && typeof raw === 'object' ? raw : {};
|
|
223
|
+
const bad = checkArgs(args);
|
|
224
|
+
if (bad) return bad;
|
|
225
|
+
const api = await organApi();
|
|
226
|
+
if (!api) return deny('invalid_args', 'Goggles organ not found.');
|
|
227
|
+
const here = projectRoot(root);
|
|
228
|
+
const drawings = drawingPaths(args);
|
|
229
|
+
if (drawings.length) {
|
|
230
|
+
const stop = api.organStop(heldReading(here), { text: drawings.join('\n'), paths: drawings, tool: 'look' });
|
|
231
|
+
if (stop && stop.reason) return deny(denyCode(stop.reason), stop.reason);
|
|
232
|
+
return quiet('reading');
|
|
233
|
+
}
|
|
234
|
+
if (pureKind0(args)) {
|
|
235
|
+
api.maintainLens(here);
|
|
236
|
+
const line = api.actualityLine();
|
|
237
|
+
if (!line) return quiet('kind0');
|
|
238
|
+
return { ok: true, quiet: false, mode: 'kind0', content: line };
|
|
239
|
+
}
|
|
240
|
+
const argv = argvFrom(args);
|
|
241
|
+
const text = api.look(argv).text;
|
|
242
|
+
if (text === 'Name one plane.') return deny('name_one', 'Name one plane.');
|
|
243
|
+
const cards = api.lookCards(argv);
|
|
244
|
+
if (Array.isArray(cards) && cards.length) {
|
|
245
|
+
const shown = cards.map(publicCard);
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
quiet: false,
|
|
249
|
+
mode: 'card',
|
|
250
|
+
content: shown.length === 1 ? shown[0] : shown,
|
|
251
|
+
outer: args.outer,
|
|
252
|
+
scope: args.scope || null,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (!text) {
|
|
256
|
+
if (args.kind === '0' || args.kind === 'actuality') return quiet('kind0');
|
|
257
|
+
if (args.outer === 'digest' || args.outer === 'triage') return quiet('card');
|
|
258
|
+
return quiet('reading');
|
|
259
|
+
}
|
|
260
|
+
if (text.startsWith('Actuality. Drift:')) {
|
|
261
|
+
return { ok: true, quiet: false, mode: 'kind0', content: text };
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
ok: true,
|
|
265
|
+
quiet: false,
|
|
266
|
+
mode: 'reading',
|
|
267
|
+
content: text,
|
|
268
|
+
outer: args.outer ?? null,
|
|
269
|
+
scope: args.scope || null,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function lensBody(root) {
|
|
274
|
+
const file = join(root, '.xray', 'state', 'LENS.md');
|
|
275
|
+
if (!existsSync(file)) return { found: false, text: '' };
|
|
276
|
+
try {
|
|
277
|
+
return { found: true, text: readFileSync(file, 'utf8').trim() };
|
|
278
|
+
} catch {
|
|
279
|
+
return { found: false, text: '' };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function answerStatus(root) {
|
|
284
|
+
const api = await organApi();
|
|
285
|
+
const override = typeof process.env.GOGGLES_ORGAN_PATH === 'string'
|
|
286
|
+
? process.env.GOGGLES_ORGAN_PATH.trim()
|
|
287
|
+
: '';
|
|
288
|
+
const organPath = override || fileURLToPath(new URL('./goggles-pipeline.mjs', import.meta.url));
|
|
289
|
+
const planesPath = api ? api.planesFile() : '';
|
|
290
|
+
const organOk = existsSync(organPath);
|
|
291
|
+
const planesOk = Boolean(planesPath) && existsSync(planesPath);
|
|
292
|
+
const worn = Boolean(api) && organOk && planesOk;
|
|
293
|
+
const here = projectRoot(root);
|
|
294
|
+
const lens = lensBody(here);
|
|
295
|
+
let kind0 = 'none';
|
|
296
|
+
let kind0Text = '';
|
|
297
|
+
if (lens.found && !lens.text) kind0 = 'quiet';
|
|
298
|
+
else if (lens.found) {
|
|
299
|
+
kind0 = 'drift';
|
|
300
|
+
kind0Text = lens.text;
|
|
301
|
+
} else if (worn) {
|
|
302
|
+
const line = api.actualityLine();
|
|
303
|
+
if (line) {
|
|
304
|
+
kind0 = 'drift';
|
|
305
|
+
kind0Text = line;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
ok: true,
|
|
310
|
+
organ: worn ? 'worn' : 'not_found',
|
|
311
|
+
organPath: organOk ? organPath : '',
|
|
312
|
+
planesPath: planesOk ? planesPath : '',
|
|
313
|
+
kind0,
|
|
314
|
+
kind0Text,
|
|
315
|
+
lensPath: '.xray/state/LENS.md',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export async function dispatchTool(name, args, root) {
|
|
320
|
+
const bare = String(name || '').replace(/^goggles__/, '');
|
|
321
|
+
if (bare === 'status_lens') {
|
|
322
|
+
const input = args && typeof args === 'object' ? args : {};
|
|
323
|
+
if (Object.keys(input).length) {
|
|
324
|
+
return { payload: deny('invalid_args', 'status_lens takes no arguments.'), isError: true };
|
|
325
|
+
}
|
|
326
|
+
return { payload: await answerStatus(root), isError: false };
|
|
327
|
+
}
|
|
328
|
+
if (bare === 'look') {
|
|
329
|
+
const payload = await answerLook(args, root);
|
|
330
|
+
return { payload, isError: payload.mode === 'deny' };
|
|
331
|
+
}
|
|
332
|
+
return { payload: deny('invalid_args', 'Unknown tool.'), isError: true };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function createGogglesServer() {
|
|
336
|
+
const server = new Server(
|
|
337
|
+
{ name: SERVER_NAME, version: serverVersion() },
|
|
338
|
+
{ capabilities: { tools: {} } },
|
|
339
|
+
);
|
|
340
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
341
|
+
tools: TOOLS.map((tool) => ({
|
|
342
|
+
name: tool.name,
|
|
343
|
+
description: tool.description,
|
|
344
|
+
inputSchema: tool.inputSchema,
|
|
345
|
+
})),
|
|
346
|
+
}));
|
|
347
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
348
|
+
const args = request.params.arguments ?? {};
|
|
349
|
+
const { payload, isError } = await dispatchTool(request.params.name, args, projectRoot());
|
|
350
|
+
return {
|
|
351
|
+
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
352
|
+
isError,
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
return server;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function main() {
|
|
359
|
+
const server = createGogglesServer();
|
|
360
|
+
const transport = new StdioServerTransport();
|
|
361
|
+
await server.connect(transport);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
365
|
+
const invoked = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
366
|
+
if (invoked && resolve(thisFile) === invoked) {
|
|
367
|
+
main().catch((err) => {
|
|
368
|
+
process.stderr.write(`goggles MCP failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
@@ -14,9 +14,16 @@ const CARD_PLANES = ['ground', 'routing', 'house', 'boot', 'governance', 'memory
|
|
|
14
14
|
const CARD_FLAVORS = ['digest', 'triage'];
|
|
15
15
|
const SCOPE_ZOOM = ['ecosystem', 'part', 'one flow', 'one artifact'];
|
|
16
16
|
|
|
17
|
+
/** Empty GOGGLES_PLANES_PATH keeps the file beside the organ. */
|
|
18
|
+
export function planesFile() {
|
|
19
|
+
const override = process.env.GOGGLES_PLANES_PATH;
|
|
20
|
+
if (typeof override === 'string' && override.trim()) return override.trim();
|
|
21
|
+
return join(HERE, 'goggles-planes.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
function drawnPlanes() {
|
|
18
25
|
try {
|
|
19
|
-
const data = JSON.parse(readFileSync(
|
|
26
|
+
const data = JSON.parse(readFileSync(planesFile(), 'utf8'));
|
|
20
27
|
return Array.isArray(data.planes) ? data.planes.map((name) => String(name)) : [];
|
|
21
28
|
} catch {
|
|
22
29
|
return [];
|
|
@@ -169,7 +176,7 @@ function namesTheSet(lower) {
|
|
|
169
176
|
return planes.length === 1 && planes[0] === 'loop' && findScopes(lower).length === 0;
|
|
170
177
|
}
|
|
171
178
|
|
|
172
|
-
|
|
179
|
+
function gogglesAnswer(argv) {
|
|
173
180
|
const words = [];
|
|
174
181
|
for (const raw of argv || []) {
|
|
175
182
|
const arg = String(raw || '').trim();
|
|
@@ -177,102 +184,157 @@ export function readGoggles(argv) {
|
|
|
177
184
|
words.push(arg);
|
|
178
185
|
}
|
|
179
186
|
const lower = words.map((word) => word.toLowerCase());
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
if (words
|
|
183
|
-
if (
|
|
187
|
+
const none = (text) => ({ text, cards: null });
|
|
188
|
+
if (lower.includes('calling')) return none('');
|
|
189
|
+
if (kindPastOne(words)) return none('');
|
|
190
|
+
if (words.length === 0) return none('');
|
|
191
|
+
if (namesTheSet(lower)) return none('Name one plane.');
|
|
184
192
|
if (isKindZero(lower)) {
|
|
185
|
-
if (findScopes(words).length) return
|
|
186
|
-
if (acceptedPlanes().some((name) => lower.includes(name))) return
|
|
187
|
-
return
|
|
193
|
+
if (findScopes(words).length) return none('');
|
|
194
|
+
if (acceptedPlanes().some((name) => lower.includes(name))) return none('');
|
|
195
|
+
return none(actualityLine());
|
|
188
196
|
}
|
|
189
197
|
const flavors = CARD_FLAVORS.filter((name) => lower.includes(name));
|
|
190
|
-
if (flavors.length > 1) return
|
|
198
|
+
if (flavors.length > 1) return none('');
|
|
191
199
|
if (flavors.length === 1) {
|
|
192
200
|
const others = acceptedPlanes().filter((name) => name !== flavors[0] && lower.includes(name));
|
|
193
|
-
if (others.length) return
|
|
194
|
-
|
|
201
|
+
if (others.length) return none('');
|
|
202
|
+
const cards = collectCards(flavors[0], lower);
|
|
203
|
+
if (!cards) return none('');
|
|
204
|
+
return { text: cards.map((card) => formatCardText(card)).join('\n\n'), cards };
|
|
195
205
|
}
|
|
196
206
|
const planes = acceptedPlanes().filter((name) => lower.includes(name));
|
|
197
|
-
if (planes.length !== 1) return
|
|
207
|
+
if (planes.length !== 1) return none('');
|
|
198
208
|
const scopes = findScopes(words.filter((word) => word.toLowerCase() !== planes[0]));
|
|
199
|
-
if (scopes.length > 1) return
|
|
200
|
-
return
|
|
209
|
+
if (scopes.length > 1) return none('');
|
|
210
|
+
return none(readingLine(planes[0], scopes[0] || ''));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function readGoggles(argv) {
|
|
214
|
+
return ok(gogglesAnswer(argv).text);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function lookCards(argv) {
|
|
218
|
+
return gogglesAnswer(argv).cards;
|
|
201
219
|
}
|
|
202
220
|
|
|
203
221
|
function cardPlanesNamed(lower) {
|
|
204
222
|
return CARD_PLANES.filter((name) => lower.includes(name));
|
|
205
223
|
}
|
|
206
224
|
|
|
207
|
-
function
|
|
208
|
-
const files = Array.isArray(plane.files) ? plane.files : [];
|
|
225
|
+
function listedFiles(plane, scope) {
|
|
226
|
+
const files = Array.isArray(plane.files) ? plane.files.map(String) : [];
|
|
227
|
+
if (scope === 'one artifact') return files.length === 1 ? [files[0]] : null;
|
|
209
228
|
const unpathed = Array.isArray(plane.unpathed) ? plane.unpathed : [];
|
|
210
|
-
const parts =
|
|
211
|
-
for (const name of unpathed)
|
|
212
|
-
|
|
229
|
+
const parts = files.slice();
|
|
230
|
+
for (const name of unpathed) parts.push(name === 'mill' ? 'mill has no path' : String(name));
|
|
231
|
+
return parts;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function cardView(plane, scope, flavor, root) {
|
|
235
|
+
const listed = listedFiles(plane, scope);
|
|
236
|
+
if (!listed) return null;
|
|
237
|
+
const view = {
|
|
238
|
+
plane: plane.id,
|
|
239
|
+
flavor,
|
|
240
|
+
scope: scope || '',
|
|
241
|
+
from: plane.id === 'ground' ? '' : 'ground',
|
|
242
|
+
digest: plane.digest || '',
|
|
243
|
+
plate: plane.plate || '',
|
|
244
|
+
entry: plane.entry || '',
|
|
245
|
+
exit: plane.exit || '',
|
|
246
|
+
files: listed,
|
|
247
|
+
skills: plane.skills || '',
|
|
248
|
+
setup: plane.setup || '',
|
|
249
|
+
teardown: plane.teardown || '',
|
|
250
|
+
worn: plane.worn || '',
|
|
251
|
+
narrow: scope === 'one flow' || scope === 'one artifact',
|
|
252
|
+
};
|
|
253
|
+
if (flavor === 'triage') {
|
|
254
|
+
const filled = new Set(filledOf(plane));
|
|
255
|
+
view.empty = FIELD_ORDER.filter((name) => !filled.has(name));
|
|
256
|
+
view.exam = examinePlane(plane, root).text;
|
|
213
257
|
}
|
|
214
|
-
return
|
|
258
|
+
return view;
|
|
215
259
|
}
|
|
216
260
|
|
|
217
|
-
function
|
|
218
|
-
const files = Array.isArray(plane.files) ? plane.files : [];
|
|
219
|
-
if (scope === 'one artifact' && files.length !== 1) return null;
|
|
261
|
+
function formatCardText(view) {
|
|
220
262
|
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',
|
|
263
|
+
['Plane', view.plane],
|
|
264
|
+
['From', view.from],
|
|
265
|
+
['Digest', view.digest],
|
|
266
|
+
['Plate', view.plate],
|
|
267
|
+
['Entry', view.entry],
|
|
268
|
+
['Exit', view.exit],
|
|
269
|
+
['Files', view.files.join(', ')],
|
|
270
|
+
['Skills', view.skills],
|
|
271
|
+
['Setup', view.setup],
|
|
272
|
+
['Teardown', view.teardown],
|
|
273
|
+
['Worn', view.worn],
|
|
232
274
|
];
|
|
233
|
-
|
|
234
|
-
|
|
275
|
+
const shown = view.narrow
|
|
276
|
+
? rows.filter(([key]) => key === 'Plane' || key === 'Digest' || key === 'Files')
|
|
277
|
+
: rows;
|
|
278
|
+
let text = shown.map(([key, value]) => `${key}: ${value}`.trimEnd()).join('\n');
|
|
279
|
+
if (view.flavor === 'triage') {
|
|
280
|
+
const lines = [];
|
|
281
|
+
if (view.empty.length) lines.push(`Empty: ${view.empty.join(', ')}`);
|
|
282
|
+
lines.push(view.exam);
|
|
283
|
+
text = `${text}\n${lines.join('\n')}`;
|
|
235
284
|
}
|
|
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');
|
|
285
|
+
return text;
|
|
247
286
|
}
|
|
248
287
|
|
|
249
|
-
function
|
|
250
|
-
const rows =
|
|
251
|
-
if (!
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
288
|
+
export function formatCardPane(view) {
|
|
289
|
+
const rows = [['Plane', view.plane]];
|
|
290
|
+
if (!view.narrow) rows.push(['From', view.from]);
|
|
291
|
+
rows.push(['Digest', view.digest]);
|
|
292
|
+
if (!view.narrow) {
|
|
293
|
+
rows.push(
|
|
294
|
+
['Plate', view.plate],
|
|
295
|
+
['Entry', view.entry],
|
|
296
|
+
['Exit', view.exit],
|
|
297
|
+
['Files', view.files.join(', ')],
|
|
298
|
+
['Skills', view.skills],
|
|
299
|
+
['Setup', view.setup],
|
|
300
|
+
['Teardown', view.teardown],
|
|
301
|
+
['Worn', view.worn],
|
|
302
|
+
);
|
|
303
|
+
} else {
|
|
304
|
+
rows.push(['Files', view.files.join(', ')]);
|
|
305
|
+
}
|
|
306
|
+
if (view.flavor === 'triage') {
|
|
307
|
+
if (Array.isArray(view.empty) && view.empty.length) rows.push(['Empty', view.empty.join(', ')]);
|
|
308
|
+
if (view.exam) rows.push(['', view.exam]);
|
|
309
|
+
}
|
|
310
|
+
const body = rows.map(([label, value]) => (label ? `${label.padEnd(8)} ${value}` : value).trimEnd());
|
|
311
|
+
const title = `${view.flavor} · ${view.plane}`;
|
|
312
|
+
const width = Math.max(title.length + 2, ...body.map((line) => line.length));
|
|
313
|
+
const top = `┌ ${title} ${'─'.repeat(Math.max(0, width - title.length - 1))}┐`;
|
|
314
|
+
const mid = body.map((line) => `│ ${line.padEnd(width)} │`);
|
|
315
|
+
const bot = `└${'─'.repeat(width + 2)}┘`;
|
|
316
|
+
return [top, ...mid, bot].join('\n');
|
|
255
317
|
}
|
|
256
318
|
|
|
257
|
-
function
|
|
319
|
+
function collectCards(flavor, lower) {
|
|
258
320
|
const named = cardPlanesNamed(lower);
|
|
259
321
|
const scopes = findScopes(lower.filter((word) => word !== flavor && !named.includes(word)));
|
|
260
|
-
if (named.length > 1 || scopes.length > 1) return
|
|
322
|
+
if (named.length > 1 || scopes.length > 1) return null;
|
|
261
323
|
const scope = scopes[0] || '';
|
|
262
|
-
if (!named.length && (scope === 'part' || scope === 'one flow' || scope === 'one artifact')) return
|
|
324
|
+
if (!named.length && (scope === 'part' || scope === 'one flow' || scope === 'one artifact')) return null;
|
|
263
325
|
const platesDir = findPlatesDir(HERE);
|
|
264
|
-
if (!platesDir) return
|
|
326
|
+
if (!platesDir) return null;
|
|
265
327
|
const ids = named.length ? named : CARD_PLANES;
|
|
266
328
|
const root = repoRootFrom(platesDir);
|
|
267
|
-
const
|
|
329
|
+
const cards = [];
|
|
268
330
|
for (const id of ids) {
|
|
269
331
|
const plane = assemblePlane(id, platesDir);
|
|
270
332
|
if (!plane) continue;
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
|
|
333
|
+
const view = cardView(plane, scope, flavor, root);
|
|
334
|
+
if (!view) return null;
|
|
335
|
+
cards.push(view);
|
|
274
336
|
}
|
|
275
|
-
return
|
|
337
|
+
return cards;
|
|
276
338
|
}
|
|
277
339
|
|
|
278
340
|
export function findPlatesDir(start) {
|
|
@@ -347,7 +409,7 @@ function takeOf(body) {
|
|
|
347
409
|
|
|
348
410
|
|
|
349
411
|
function overlayTable() {
|
|
350
|
-
const file =
|
|
412
|
+
const file = planesFile();
|
|
351
413
|
if (!existsSync(file)) return {};
|
|
352
414
|
return JSON.parse(readFileSync(file, 'utf8'));
|
|
353
415
|
}
|
|
@@ -562,7 +624,9 @@ export function maintainLens(root) {
|
|
|
562
624
|
|
|
563
625
|
const isMain = Boolean(process.argv[1]) && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
564
626
|
if (isMain) {
|
|
565
|
-
const
|
|
566
|
-
process.stdout.
|
|
567
|
-
|
|
627
|
+
const answered = gogglesAnswer(process.argv.slice(2));
|
|
628
|
+
const pane = process.stdout.isTTY && Array.isArray(answered.cards) && answered.cards.length > 0;
|
|
629
|
+
const text = pane ? answered.cards.map((card) => formatCardPane(card)).join('\n\n') : answered.text;
|
|
630
|
+
process.stdout.write(`${text}\n`);
|
|
631
|
+
process.exit(0);
|
|
568
632
|
}
|
|
@@ -55,11 +55,11 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
|
|
|
55
55
|
|
|
56
56
|
## Goggles
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
|
|
59
59
|
|
|
60
60
|
Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet. It does not read the suit that is running. It is not a plate type.
|
|
61
61
|
|
|
62
|
-
Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
|
|
62
|
+
Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
|
|
63
63
|
|
|
64
64
|
`digest` and `triage` return the card already built by `assemblePlane`. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Empty stays empty. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. `digest` fills the card. Entry and exit are the plate's input and output box titles when that plate has them. Setup and teardown stay empty unless the row names them. The mill path is `scripts/foundry`. `triage` adds the empty fields and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact. One file is one artifact. Several files are not narrowed to a guessed file.
|
|
65
65
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0xray",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.32",
|
|
4
4
|
"description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"license": "MIT",
|
|
@@ -170,6 +170,7 @@
|
|
|
170
170
|
"scripts/mjs/verify-temperament-live.mjs",
|
|
171
171
|
"scripts/mjs/verify-four-floor-compact.mjs",
|
|
172
172
|
"scripts/mjs/run-repertoire-mcp.mjs",
|
|
173
|
+
"scripts/mjs/run-goggles-mcp.mjs",
|
|
173
174
|
"scripts/mjs/verify-delegation-gate-core.mjs",
|
|
174
175
|
"scripts/mjs/delegation-gate-fixture.mjs",
|
|
175
176
|
"src/opencode/",
|
|
@@ -29,6 +29,10 @@ export const REQUIRED_PACK_PATHS = [
|
|
|
29
29
|
"dist/integrations/cursor/hooks/pre-compact.sh",
|
|
30
30
|
"dist/integrations/cursor/hooks/after-file-edit.sh",
|
|
31
31
|
"dist/integrations/hooks/plates.cjs",
|
|
32
|
+
"dist/integrations/hooks/goggles-pipeline.mjs",
|
|
33
|
+
"dist/integrations/hooks/goggles-planes.json",
|
|
34
|
+
"dist/integrations/hooks/goggles-mcp.mjs",
|
|
35
|
+
"scripts/mjs/run-goggles-mcp.mjs",
|
|
32
36
|
"docs-site/docs/plates/memory-recall.md",
|
|
33
37
|
"docs-site/docs/plates/processor.md",
|
|
34
38
|
"docs-site/docs/plates/goggles.md",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CWD-proof Goggles MCP launcher.
|
|
4
|
+
* Resolves the organ server from this file, not from the caller's cwd.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { dirname, join, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const xrayRoot = join(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
12
|
+
const candidates = [
|
|
13
|
+
join(xrayRoot, 'dist', 'integrations', 'hooks', 'goggles-mcp.mjs'),
|
|
14
|
+
join(xrayRoot, 'src', 'integrations', 'hooks', 'goggles-mcp.mjs'),
|
|
15
|
+
];
|
|
16
|
+
const server = candidates.find((file) => existsSync(file));
|
|
17
|
+
if (!server) {
|
|
18
|
+
process.stderr.write('goggles MCP missing\n');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const env = { ...process.env };
|
|
23
|
+
if (!env.GOGGLES_ROOT || env.GOGGLES_ROOT === '.') {
|
|
24
|
+
env.GOGGLES_ROOT = resolve(process.cwd());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const child = spawn(process.execPath, [server], {
|
|
28
|
+
stdio: 'inherit',
|
|
29
|
+
env,
|
|
30
|
+
});
|
|
31
|
+
child.on('error', (err) => {
|
|
32
|
+
process.stderr.write(`goggles MCP spawn failed: ${err.message}\n`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
child.on('exit', (code, signal) => {
|
|
36
|
+
if (signal) {
|
|
37
|
+
process.kill(process.pid, signal);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
process.exit(code ?? 1);
|
|
41
|
+
});
|
|
@@ -139,31 +139,70 @@ function enableMemoryRoutingIfResolves(features, targetDir) {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function resolveGogglesMcp(targetDir) {
|
|
143
|
+
const candidates = [
|
|
144
|
+
path.join(targetDir, "node_modules", "0xray", "scripts", "mjs", "run-goggles-mcp.mjs"),
|
|
145
|
+
path.join(targetDir, "scripts", "mjs", "run-goggles-mcp.mjs"),
|
|
146
|
+
];
|
|
147
|
+
return candidates.find((file) => fs.existsSync(file)) || null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function gogglesScopedLaunch(targetDir, launcher) {
|
|
151
|
+
return wrapScopedLaunch(
|
|
152
|
+
{ command: "node", args: [launcher] },
|
|
153
|
+
mergeScopedEnv([
|
|
154
|
+
baseScopedEnv(targetDir),
|
|
155
|
+
{ GOGGLES_ROOT: targetDir, GOGGLES_PLANES_PATH: "" },
|
|
156
|
+
]),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
142
160
|
function detectConsumerExtraMcpServers(targetDir) {
|
|
143
161
|
const extras = { hermes: {}, opencode: {}, openclaw: {} };
|
|
144
162
|
try {
|
|
145
163
|
const repertoireMcp = resolveRepertoireMcp(targetDir);
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
if (repertoireMcp) {
|
|
165
|
+
const launch = wrapScopedLaunch(
|
|
166
|
+
{ command: "node", args: [repertoireMcp] },
|
|
167
|
+
baseScopedEnv(targetDir),
|
|
168
|
+
);
|
|
169
|
+
extras.hermes.repertoire = {
|
|
170
|
+
command: launch.command,
|
|
171
|
+
args: launch.args,
|
|
172
|
+
env: launch.env,
|
|
173
|
+
};
|
|
174
|
+
extras.opencode.repertoire = {
|
|
175
|
+
type: "local",
|
|
176
|
+
command: launch.commandList,
|
|
177
|
+
enabled: true,
|
|
178
|
+
environment: launch.env,
|
|
179
|
+
};
|
|
180
|
+
extras.openclaw.repertoire = {
|
|
181
|
+
command: launch.command,
|
|
182
|
+
args: launch.args,
|
|
183
|
+
env: launch.env,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const gogglesMcp = resolveGogglesMcp(targetDir);
|
|
187
|
+
if (gogglesMcp) {
|
|
188
|
+
const launch = gogglesScopedLaunch(targetDir, gogglesMcp);
|
|
189
|
+
extras.hermes.goggles = {
|
|
190
|
+
command: launch.command,
|
|
191
|
+
args: launch.args,
|
|
192
|
+
env: launch.env,
|
|
193
|
+
};
|
|
194
|
+
extras.opencode.goggles = {
|
|
195
|
+
type: "local",
|
|
196
|
+
command: launch.commandList,
|
|
197
|
+
enabled: true,
|
|
198
|
+
environment: launch.env,
|
|
199
|
+
};
|
|
200
|
+
extras.openclaw.goggles = {
|
|
201
|
+
command: launch.command,
|
|
202
|
+
args: launch.args,
|
|
203
|
+
env: launch.env,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
167
206
|
} catch {
|
|
168
207
|
// best-effort
|
|
169
208
|
}
|
|
@@ -880,6 +919,7 @@ module.exports = {
|
|
|
880
919
|
detectConsumerExtraMcpServers,
|
|
881
920
|
isRepertoirePackageRoot,
|
|
882
921
|
resolveRepertoireMcp,
|
|
922
|
+
resolveGogglesMcp,
|
|
883
923
|
resolveRepertoireProvider,
|
|
884
924
|
isDefaultMemoryRoutingOff,
|
|
885
925
|
enableMemoryRoutingIfResolves,
|
|
@@ -55,11 +55,11 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
|
|
|
55
55
|
|
|
56
56
|
## Goggles
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
|
|
59
59
|
|
|
60
60
|
Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet. It does not read the suit that is running. It is not a plate type.
|
|
61
61
|
|
|
62
|
-
Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
|
|
62
|
+
Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
|
|
63
63
|
|
|
64
64
|
`digest` and `triage` return the card already built by `assemblePlane`. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Empty stays empty. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. `digest` fills the card. Entry and exit are the plate's input and output box titles when that plate has them. Setup and teardown stay empty unless the row names them. The mill path is `scripts/foundry`. `triage` adds the empty fields and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact. One file is one artifact. Several files are not narrowed to a guessed file.
|
|
65
65
|
|