0xray 4.0.4 → 4.0.5
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/.xray/features.json +1 -1
- package/AGENTS-consumer.md +2 -1
- package/dist/CHANGELOG.md +7 -0
- package/dist/integrations/grok/grok-cli.js +10 -0
- package/package.json +1 -1
- package/scripts/foundry/CHANGELOG.md +4 -0
- package/scripts/foundry/README.md +6 -5
- package/scripts/foundry/ci-monitor.mjs +7 -3
- package/scripts/foundry/cli.mjs +4 -2
- package/scripts/foundry/inspect.mjs +284 -0
- package/scripts/foundry/mint-suit.cjs +127 -6
- package/scripts/foundry/mint.mjs +9 -3
- package/scripts/foundry/package.json +2 -1
- package/scripts/foundry/plant/agents/inspect.yml +1 -1
- package/scripts/foundry/plant/agents/mill.yml +2 -2
- package/scripts/foundry/plant/skills/inspect/SKILL.md +9 -8
- package/scripts/node/bridge-mcp-wiring.cjs +8 -0
- package/scripts/node/install-bridges.cjs +16 -8
- package/xray/features.json +1 -1
package/.xray/features.json
CHANGED
package/AGENTS-consumer.md
CHANGED
|
@@ -19,7 +19,7 @@ Before other work, Read `.xray/state/STATION.md`. Compaction and host change are
|
|
|
19
19
|
`npm install 0xray` runs `install-bridges.cjs` and:
|
|
20
20
|
|
|
21
21
|
- Copies this file → **`AGENTS.md`** in your project root
|
|
22
|
-
- Fastens mill plant (`mill` + `inspect`) then overlays **your** `src/skills` (same name wins).
|
|
22
|
+
- Fastens mill plant (`mill` + `inspect`) then overlays **your** `src/skills` (same name wins). `npx @0xray/foundry inspect` runs the mill checks (chat is not a receipt). Does **not** dump 45/42 costume unless `foundry.json` `"costume": true`. Does **not** write root **`SKILLS.md`**
|
|
23
23
|
- Seeds **`.gitignore`** (from template, if absent)
|
|
24
24
|
- Deploys **`.xray/`** (`codex.json`, `features.json`, `config.json`) then overlays **your** mill SSOT (`xray/codex.json`, `xray/features.json`, `src/skills`, `src/opencode/agents`; remap with `foundry.json`)
|
|
25
25
|
- Writes **`.mcp.json`** with 7 MCPs servers (`npx -y 0xray mcp …`)
|
|
@@ -58,6 +58,7 @@ Governance deliberation uses **code-review**, **security-audit** (via enforcer/s
|
|
|
58
58
|
| `npx 0xray codex check` | Check codex rules |
|
|
59
59
|
| `npx 0xray health` | Framework health check |
|
|
60
60
|
| `npx 0xray mcp <server>` | Start an MCP server (stdio) |
|
|
61
|
+
| `npx @0xray/foundry inspect` | Mill inspect (nested mill). Chat is not a receipt. |
|
|
61
62
|
|
|
62
63
|
## Governance
|
|
63
64
|
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,13 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
28
28
|
- Nested mill in the 0xray tarball (`scripts/foundry/`). Wear no longer needs a second `npm i @0xray/foundry` for mint-on-install. 4.0.2.
|
|
29
29
|
- Empty garment: mill plant (`mill` skill) is the hanger. Not 45/42 costume unless `foundry.json` `"costume": true`. Mill 0.1.5.
|
|
30
30
|
- Mill plant fastens **inspect** (inspect AI work) plus `mill`. Not empty. Mill 0.1.6.
|
|
31
|
+
- Nested mill **0.1.7**: inspect organ, four floors, passwd HOME, mill CI receipt. 4.0.5.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## [4.0.5] - 2026-09-04
|
|
36
|
+
|
|
37
|
+
Nested mill **0.1.7**. Inspect is a mill command (`npx @0xray/foundry inspect`): costume dump fails mint, live PUT GETs the `.tgz`, four project floors, mill CI receipt, passwd HOME isolation. Mill plant stays `mill` + `inspect`. Not 45/42 costume. Not 5.0.0.
|
|
31
38
|
|
|
32
39
|
---
|
|
33
40
|
|
|
@@ -50,8 +50,18 @@ function registerGrokMcpServers(targetDir) {
|
|
|
50
50
|
}
|
|
51
51
|
export async function installForGrokCLI(options = {}) {
|
|
52
52
|
frameworkLogger.log('grok-integration', 'install-start', 'info', { options });
|
|
53
|
+
const millSuit = requireCjs(path.join(packageRoot, 'scripts/foundry/mint-suit.cjs'));
|
|
53
54
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
55
|
+
const machine = millSuit.machineHome();
|
|
54
56
|
const targetPluginDir = path.join(home, '.grok/plugins/0xray');
|
|
57
|
+
if (millSuit.wouldClobberMachineGrok(targetPluginDir, process.env, machine)) {
|
|
58
|
+
frameworkLogger.log('grok-integration', 'refuse-machine-grok-clobber', 'error', {
|
|
59
|
+
home,
|
|
60
|
+
machineHome: machine,
|
|
61
|
+
targetPluginDir,
|
|
62
|
+
});
|
|
63
|
+
throw new Error('foundry-inspect: isolated HOME must not clobber ~/.grok/plugins/0xray');
|
|
64
|
+
}
|
|
55
65
|
// Try to find the plugin source from the installed package
|
|
56
66
|
const possibleSources = [
|
|
57
67
|
path.join(__dirname, '..', '..', '..', 'src/integrations/grok/plugin/0xray'), // dev
|
package/package.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.7] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
Inspect is mill flesh: `npx @0xray/foundry inspect` runs diff, plant vs worn (mint fails costume dump), mill receipt, CI, GET the `.tgz` (HTTP 200), isolated HOME vs passwd home. Four project skill floors created on fasten (OpenCode, Grok, Hermes, OpenClaw). CI report is `.xray/foundry-ci-report.json`. Not an 8th MCP. Not 5.0.
|
|
6
|
+
|
|
3
7
|
## [0.1.6] - 2026-09-04
|
|
4
8
|
|
|
5
9
|
Mill plant fastens an **inspect** suit: inspect AI work (diffs, traces, mill receipts, CI). Not empty. `mill` + `inspect` skills. Costume dump still opt-in.
|
|
@@ -23,6 +23,7 @@ npx @0xray/foundry release [patch|minor|major] --i-mean-it
|
|
|
23
23
|
npx @0xray/foundry release --publish-only --dry-run
|
|
24
24
|
npx @0xray/foundry release --publish-only --i-mean-it
|
|
25
25
|
npx @0xray/foundry mint
|
|
26
|
+
npx @0xray/foundry inspect [--skip-live]
|
|
26
27
|
npx @0xray/foundry ci [--commit SHA] [--report]
|
|
27
28
|
npx @0xray/foundry hooks
|
|
28
29
|
```
|
|
@@ -31,7 +32,7 @@ npx @0xray/foundry hooks
|
|
|
31
32
|
|
|
32
33
|
`FOUNDRY_ROOT` overrides cwd (the repo being milled).
|
|
33
34
|
|
|
34
|
-
`gate` is build + test + docs-check. `docs-build` runs Docusaurus on the 0xray exo (`docs-site/`); stranger mills skip. `ci` reports GitHub Actions (no auto-push). `hooks` installs git pre/post hooks. GitHub `0xRay CI/CD` is the mill gate on `main`. `Deploy Docs` is the mill Pages put.
|
|
35
|
+
`gate` is build + test + docs-check. `docs-build` runs Docusaurus on the 0xray exo (`docs-site/`); stranger mills skip. `inspect` runs the six mill checks (diff, plant vs worn, receipt, CI, live tarball GET, isolated HOME). Mint fails on a costume dump. Isolated HOME skips machine `~/.grok`. `ci` reports GitHub Actions (no auto-push). `hooks` installs git pre/post hooks. GitHub `0xRay CI/CD` is the mill gate on `main`. `Deploy Docs` is the mill Pages put.
|
|
35
36
|
|
|
36
37
|
`docs-check` and `gate` run **full** 0xRay corpus only when the milled `package.json` name is `0xray` and `docs-site/` exists. Otherwise **light**: `package.json` + CHANGELOG.
|
|
37
38
|
|
|
@@ -41,16 +42,16 @@ npx @0xray/foundry hooks
|
|
|
41
42
|
|
|
42
43
|
After mill plant is fastened, overlay their plant (defaults; remap in `foundry.json` or `.xray/foundry.json`):
|
|
43
44
|
|
|
44
|
-
| Facet | Default plant |
|
|
45
|
+
| Facet | Default plant | Worn |
|
|
45
46
|
|---|---|---|
|
|
46
47
|
| Constitution | `xray/codex.json` | `.xray/codex.json` |
|
|
47
48
|
| Features / temperament | `xray/features.json` | `.xray/features.json` |
|
|
48
49
|
| Config | `xray/config.json` | `.xray/config.json` |
|
|
49
|
-
| Skills | `src/skills/<name>/SKILL.md` | `.opencode/skills
|
|
50
|
-
| Agents | `src/opencode/agents/*.yml` | `.opencode/agents/` (OpenCode
|
|
50
|
+
| Skills | `src/skills/<name>/SKILL.md` | project `.opencode/skills`, `.grok/plugins/0xray/skills`, `.hermes/plugins/xray-hermes/skills`, `.openclaw/skills` (created on fasten; never machine home) |
|
|
51
|
+
| Agents | `src/opencode/agents/*.yml` | `.opencode/agents/` (OpenCode wear dir; other floors do not read mill YML agents) |
|
|
51
52
|
| Agents card | `xray/AGENTS.md` | `AGENTS.md` if mill-managed |
|
|
52
53
|
|
|
53
|
-
**Mill-fill is law:** their keys/files win; mill names they did not plant stay unless they overlay the same name. JSON facets always merge. Skill overlay is project dirs
|
|
54
|
+
**Mill-fill is law:** their keys/files win; mill names they did not plant stay unless they overlay the same name. JSON facets always merge. Skill overlay is project dirs on every TUI floor (OpenCode, Grok, Hermes, OpenClaw). YML agents stay OpenCode. CLI re-wear calls mint **once** after mill copies. Isolated HOME is passwd home, not `os.homedir()`. CI report is `.xray/foundry-ci-report.json` (`.opencode/logs` shim).
|
|
54
55
|
|
|
55
56
|
**Default mill plant:** `mill` + `inspect` (inspect AI work). Fastens a suit, not an empty one. Not 45/42 costume. `foundry.json` `"costume": true` copies that dump. Factory Repertoire still enable-when-resolves. PPE stays worn.
|
|
56
57
|
|
|
@@ -16,11 +16,15 @@ const commitIdx = args.indexOf("--commit");
|
|
|
16
16
|
const shaArg = commitIdx >= 0 ? args[commitIdx + 1] : "";
|
|
17
17
|
const sha = (shaArg && !shaArg.startsWith("-") ? shaArg : process.env.GITHUB_SHA || "").trim();
|
|
18
18
|
const root = resolveMillRoot();
|
|
19
|
-
const
|
|
19
|
+
const millReportPath = path.join(root, ".xray", "foundry-ci-report.json");
|
|
20
|
+
const shimReportPath = path.join(root, ".opencode", "logs", "ci-cd-monitor-report.json");
|
|
20
21
|
|
|
21
22
|
function writeReport(report) {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const body = `${JSON.stringify(report, null, 2)}\n`;
|
|
24
|
+
for (const reportPath of [millReportPath, shimReportPath]) {
|
|
25
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
26
|
+
fs.writeFileSync(reportPath, body);
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
function emptyReport(status, reason) {
|
package/scripts/foundry/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @0xray/foundry CLI — mill, not exo. No fifth MCP.
|
|
4
4
|
*
|
|
5
|
-
* npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|ci|hooks> [...args]
|
|
5
|
+
* npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|inspect|ci|hooks> [...args]
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
@@ -16,16 +16,18 @@ const COMMANDS = {
|
|
|
16
16
|
"docs-check": { script: "validate-release-docs.mjs", preset: [] },
|
|
17
17
|
"docs-build": { script: "docs-build.mjs", preset: [] },
|
|
18
18
|
mint: { script: "mint.mjs", preset: [] },
|
|
19
|
+
inspect: { script: "inspect.mjs", preset: [] },
|
|
19
20
|
ci: { script: "ci-monitor.mjs", preset: [] },
|
|
20
21
|
hooks: { script: "hooks.mjs", preset: [] },
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const HELP =
|
|
24
|
-
"Usage: npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|ci|hooks> [...args]\n" +
|
|
25
|
+
"Usage: npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|inspect|ci|hooks> [...args]\n" +
|
|
25
26
|
"\n" +
|
|
26
27
|
"Mill, not exo. FOUNDRY_ROOT overrides cwd (the repo being milled).\n" +
|
|
27
28
|
"release bumps/commits/pushes/publishes the milled cwd — pass --dry-run or --i-mean-it (or FOUNDRY_RELEASE=1).\n" +
|
|
28
29
|
"Mint fastens mill plant then overlays their plant (mill-fill JSON). PPE stays worn.\n" +
|
|
30
|
+
"inspect runs the six mill checks (diff, plant vs worn, receipt, CI, live tarball GET, isolated HOME). Not an 8th MCP.\n" +
|
|
29
31
|
"gate is build+test. docs-build is Docusaurus on the 0xray exo. ci reports GitHub Actions (no auto-push). hooks installs git pre/post hooks.\n";
|
|
30
32
|
|
|
31
33
|
const cmd = process.argv[2];
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Mill inspect organ. Runs the six checks. Not an 8th MCP. Not PPE.
|
|
4
|
+
*
|
|
5
|
+
* npx @0xray/foundry inspect [--skip-live]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { millPackageDir, resolveMillRoot } from "./mill-root.mjs";
|
|
14
|
+
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const mint = require("./mint-suit.cjs");
|
|
17
|
+
|
|
18
|
+
export function npmTarballUrl(name, version) {
|
|
19
|
+
if (typeof name !== "string" || !name || typeof version !== "string" || !version) return null;
|
|
20
|
+
if (name.startsWith("@")) {
|
|
21
|
+
const slash = name.indexOf("/");
|
|
22
|
+
if (slash < 1) return null;
|
|
23
|
+
const pkg = name.slice(slash + 1);
|
|
24
|
+
return `https://registry.npmjs.org/${name}/-/${pkg}-${version}.tgz`;
|
|
25
|
+
}
|
|
26
|
+
return `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isIsolatedHome(env = process.env, machine = mint.machineHome()) {
|
|
30
|
+
return mint.isIsolatedHome(env, machine);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function machineGrokPluginDir(machine = mint.machineHome()) {
|
|
34
|
+
return mint.machineGrokPluginDir(machine);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function wouldClobberMachineGrok(
|
|
38
|
+
dest,
|
|
39
|
+
env = process.env,
|
|
40
|
+
machine = mint.machineHome(),
|
|
41
|
+
) {
|
|
42
|
+
return mint.wouldClobberMachineGrok(dest, env, machine);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readJson(file) {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function checkDiff(root) {
|
|
54
|
+
const status = spawnSync("git", ["status", "-sb"], { cwd: root, encoding: "utf8" });
|
|
55
|
+
if (status.error || status.status === 128) {
|
|
56
|
+
return {
|
|
57
|
+
id: "diff",
|
|
58
|
+
ok: true,
|
|
59
|
+
skipped: true,
|
|
60
|
+
detail: status.stderr?.trim() || "not a git repo",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const diff = spawnSync("git", ["diff", "--stat"], { cwd: root, encoding: "utf8" });
|
|
64
|
+
return {
|
|
65
|
+
id: "diff",
|
|
66
|
+
ok: true,
|
|
67
|
+
status: (status.stdout || "").trim(),
|
|
68
|
+
diffStat: (diff.stdout || "").trim(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function checkPlantVsWorn(root, millRoot) {
|
|
73
|
+
const plantDir = mint.millPlantDir(millRoot);
|
|
74
|
+
const millPlant = {
|
|
75
|
+
skills: plantDir ? mint.listSkillNamesAt(path.join(plantDir, "skills")) : [],
|
|
76
|
+
agents: plantDir ? mint.listAgentFilesAt(path.join(plantDir, "agents")) : [],
|
|
77
|
+
};
|
|
78
|
+
const params = mint.loadFoundryParams(root);
|
|
79
|
+
const tree = {
|
|
80
|
+
skills: mint.listConsumerSkillNames(root, params.skills),
|
|
81
|
+
agents: mint.listConsumerAgentFiles(root, params.agents),
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
mint.assertNoCostumeDump(root, millPlant, tree);
|
|
85
|
+
return {
|
|
86
|
+
id: "plant-vs-worn",
|
|
87
|
+
ok: true,
|
|
88
|
+
millPlant: millPlant.skills,
|
|
89
|
+
tree: tree.skills,
|
|
90
|
+
worn: mint.wornSkillNames(root),
|
|
91
|
+
};
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return {
|
|
94
|
+
id: "plant-vs-worn",
|
|
95
|
+
ok: false,
|
|
96
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
97
|
+
extraSkills: err.extraSkills || [],
|
|
98
|
+
extraAgents: err.extraAgents || [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function checkReceipt(root) {
|
|
104
|
+
const file = path.join(root, ".xray", "foundry-inventory.json");
|
|
105
|
+
if (mint.isDogfood(millPackageDir(), root)) {
|
|
106
|
+
return { id: "receipt", ok: true, skipped: true, detail: "dogfood / exo — no consumer inventory" };
|
|
107
|
+
}
|
|
108
|
+
const inventory = readJson(file);
|
|
109
|
+
if (!inventory) {
|
|
110
|
+
return { id: "receipt", ok: false, detail: "missing .xray/foundry-inventory.json" };
|
|
111
|
+
}
|
|
112
|
+
const millPlantSkills = inventory.millPlant?.skills || [];
|
|
113
|
+
const hasMill = millPlantSkills.includes("mill");
|
|
114
|
+
const hasInspect = millPlantSkills.includes("inspect");
|
|
115
|
+
const ok = hasMill && hasInspect;
|
|
116
|
+
return {
|
|
117
|
+
id: "receipt",
|
|
118
|
+
ok,
|
|
119
|
+
suit: inventory.suit,
|
|
120
|
+
mill: inventory.mill,
|
|
121
|
+
millPlant: millPlantSkills,
|
|
122
|
+
detail: ok ? null : "millPlant.skills must include mill and inspect",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function checkCi(root) {
|
|
127
|
+
const millPath = path.join(root, ".xray", "foundry-ci-report.json");
|
|
128
|
+
const shimPath = path.join(root, ".opencode", "logs", "ci-cd-monitor-report.json");
|
|
129
|
+
const report = readJson(millPath) || readJson(shimPath);
|
|
130
|
+
if (!report) {
|
|
131
|
+
return {
|
|
132
|
+
id: "ci",
|
|
133
|
+
ok: true,
|
|
134
|
+
skipped: true,
|
|
135
|
+
detail: "no mill ci report — run npx @0xray/foundry ci --report",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const status = String(report.ci_status || "");
|
|
139
|
+
if (status === "failure" || status === "unhealthy") {
|
|
140
|
+
return { id: "ci", ok: false, status, reason: report.reason || null };
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
id: "ci",
|
|
144
|
+
ok: true,
|
|
145
|
+
skipped: status === "unknown",
|
|
146
|
+
status,
|
|
147
|
+
reason: report.reason || null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function checkLivePut(packages, fetchFn = fetch) {
|
|
152
|
+
const results = [];
|
|
153
|
+
for (const pkg of packages) {
|
|
154
|
+
const url = npmTarballUrl(pkg.name, pkg.version);
|
|
155
|
+
if (!url) {
|
|
156
|
+
results.push({ name: pkg.name, version: pkg.version, ok: false, detail: "missing name/version" });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
let status = 0;
|
|
160
|
+
try {
|
|
161
|
+
const head = await fetchFn(url, { method: "HEAD" });
|
|
162
|
+
status = head.status;
|
|
163
|
+
if (status === 405 || status === 501) {
|
|
164
|
+
const get = await fetchFn(url, { method: "GET" });
|
|
165
|
+
status = get.status;
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
results.push({
|
|
169
|
+
name: pkg.name,
|
|
170
|
+
version: pkg.version,
|
|
171
|
+
url,
|
|
172
|
+
ok: false,
|
|
173
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
174
|
+
});
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
results.push({ name: pkg.name, version: pkg.version, url, status, ok: status === 200 });
|
|
178
|
+
}
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function packagesToProbe(root) {
|
|
183
|
+
const pkgs = [];
|
|
184
|
+
const inventory = readJson(path.join(root, ".xray", "foundry-inventory.json"));
|
|
185
|
+
if (inventory?.mill?.name && inventory?.mill?.version) {
|
|
186
|
+
pkgs.push({ name: inventory.mill.name, version: inventory.mill.version });
|
|
187
|
+
}
|
|
188
|
+
const worn = readJson(path.join(root, "node_modules", "0xray", "package.json"));
|
|
189
|
+
if (worn?.name && worn?.version) {
|
|
190
|
+
pkgs.push({ name: worn.name, version: worn.version });
|
|
191
|
+
}
|
|
192
|
+
const self = readJson(path.join(root, "package.json"));
|
|
193
|
+
if (self?.name === "0xray" && self?.version) {
|
|
194
|
+
pkgs.push({ name: "0xray", version: self.version });
|
|
195
|
+
}
|
|
196
|
+
const seen = new Set();
|
|
197
|
+
return pkgs.filter((p) => {
|
|
198
|
+
const key = `${p.name}@${p.version}`;
|
|
199
|
+
if (seen.has(key)) return false;
|
|
200
|
+
seen.add(key);
|
|
201
|
+
return true;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function checkIsolatedHome(env = process.env, machine = mint.machineHome()) {
|
|
206
|
+
const isolated = isIsolatedHome(env, machine);
|
|
207
|
+
const machinePlugin = machineGrokPluginDir(machine);
|
|
208
|
+
const home = env.HOME || env.USERPROFILE || "";
|
|
209
|
+
const dest = home ? path.join(home, ".grok", "plugins", "0xray") : "";
|
|
210
|
+
const clobber = wouldClobberMachineGrok(dest, env, machine);
|
|
211
|
+
if (clobber) {
|
|
212
|
+
return {
|
|
213
|
+
id: "isolated-home",
|
|
214
|
+
ok: false,
|
|
215
|
+
isolated,
|
|
216
|
+
detail: `isolated HOME must not write ${machinePlugin}`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
id: "isolated-home",
|
|
221
|
+
ok: true,
|
|
222
|
+
isolated,
|
|
223
|
+
machinePlugin,
|
|
224
|
+
dest: dest || null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function inspectSuit(root, opts = {}) {
|
|
229
|
+
const millRoot = opts.millRoot || millPackageDir();
|
|
230
|
+
const fetchFn = opts.fetch || fetch;
|
|
231
|
+
const skipLive = Boolean(opts.skipLive);
|
|
232
|
+
const env = opts.env || process.env;
|
|
233
|
+
const machineHome = opts.machineHome || mint.machineHome();
|
|
234
|
+
|
|
235
|
+
const checks = [];
|
|
236
|
+
checks.push(checkDiff(root));
|
|
237
|
+
checks.push(checkPlantVsWorn(root, millRoot));
|
|
238
|
+
checks.push(checkReceipt(root));
|
|
239
|
+
checks.push(checkCi(root));
|
|
240
|
+
|
|
241
|
+
if (skipLive) {
|
|
242
|
+
checks.push({ id: "live-put", ok: true, skipped: true, detail: "--skip-live" });
|
|
243
|
+
} else {
|
|
244
|
+
const pkgs = packagesToProbe(root);
|
|
245
|
+
if (pkgs.length === 0) {
|
|
246
|
+
checks.push({
|
|
247
|
+
id: "live-put",
|
|
248
|
+
ok: true,
|
|
249
|
+
skipped: true,
|
|
250
|
+
detail: "no mill/0xray package identity to GET",
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
const puts = await checkLivePut(pkgs, fetchFn);
|
|
254
|
+
checks.push({
|
|
255
|
+
id: "live-put",
|
|
256
|
+
ok: puts.every((p) => p.ok),
|
|
257
|
+
packages: puts,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
checks.push(checkIsolatedHome(env, machineHome));
|
|
263
|
+
|
|
264
|
+
const failed = checks.filter((c) => c.ok === false);
|
|
265
|
+
return {
|
|
266
|
+
ok: failed.length === 0,
|
|
267
|
+
failed: failed.map((c) => c.id),
|
|
268
|
+
checks,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function main() {
|
|
273
|
+
const skipLive = process.argv.includes("--skip-live");
|
|
274
|
+
const root = resolveMillRoot();
|
|
275
|
+
const report = await inspectSuit(root, { skipLive });
|
|
276
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
277
|
+
process.exit(report.ok ? 0 : 1);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const isMain =
|
|
281
|
+
process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
282
|
+
if (isMain) {
|
|
283
|
+
main();
|
|
284
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const fs = require("fs");
|
|
10
|
+
const os = require("os");
|
|
10
11
|
const path = require("path");
|
|
11
12
|
|
|
12
13
|
const DEFAULT_PARAMS = {
|
|
@@ -135,7 +136,7 @@ function listAgentFilesAt(agentsSrc) {
|
|
|
135
136
|
.map((entry) => entry.name);
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
/**
|
|
139
|
+
/** Fasten mill plant first. Mill names they did not plant stay; same-name overlay wins. */
|
|
139
140
|
function fastenMillPlant(millPackageRoot, targetDir, log) {
|
|
140
141
|
const plant = millPlantDir(millPackageRoot);
|
|
141
142
|
if (!plant) return { skills: [], agents: [] };
|
|
@@ -180,12 +181,57 @@ function isDirectory(p) {
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
/**
|
|
184
|
+
/** Passwd home. `os.homedir()` follows $HOME and cannot detect last-mile isolation. */
|
|
185
|
+
function machineHome() {
|
|
186
|
+
try {
|
|
187
|
+
const passwd = os.userInfo().homedir;
|
|
188
|
+
if (typeof passwd === "string" && passwd) return passwd;
|
|
189
|
+
} catch {
|
|
190
|
+
/* no passwd */
|
|
191
|
+
}
|
|
192
|
+
return os.homedir();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function processHome(env) {
|
|
196
|
+
const e = env || process.env;
|
|
197
|
+
return e.HOME || e.USERPROFILE || "";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isIsolatedHome(env, machine) {
|
|
201
|
+
const home = processHome(env);
|
|
202
|
+
if (!home) return false;
|
|
203
|
+
const real = machine || machineHome();
|
|
204
|
+
try {
|
|
205
|
+
return path.resolve(home) !== path.resolve(real);
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function machineGrokPluginDir(machine) {
|
|
212
|
+
return path.join(machine || machineHome(), ".grok", "plugins", "0xray");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function wouldClobberMachineGrok(dest, env, machine) {
|
|
216
|
+
if (!dest) return false;
|
|
217
|
+
const realMachine = machine || machineHome();
|
|
218
|
+
if (!isIsolatedHome(env, realMachine)) return false;
|
|
219
|
+
const real = path.resolve(machineGrokPluginDir(realMachine));
|
|
220
|
+
const target = path.resolve(dest);
|
|
221
|
+
return target === real || target.startsWith(`${real}${path.sep}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Project skill dirs on every TUI floor. Create on fasten. Never machine home.
|
|
226
|
+
* YML agents stay OpenCode (`.opencode/agents`).
|
|
227
|
+
*/
|
|
184
228
|
function listProjectSkillDirs(targetDir) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
229
|
+
return [
|
|
230
|
+
path.join(targetDir, ".opencode", "skills"),
|
|
231
|
+
path.join(targetDir, ".grok", "plugins", "0xray", "skills"),
|
|
232
|
+
path.join(targetDir, ".hermes", "plugins", "xray-hermes", "skills"),
|
|
233
|
+
path.join(targetDir, ".openclaw", "skills"),
|
|
234
|
+
];
|
|
189
235
|
}
|
|
190
236
|
|
|
191
237
|
function overlayJsonFacet(src, dest) {
|
|
@@ -238,6 +284,69 @@ function overlayAgentsCard(targetDir, params) {
|
|
|
238
284
|
return true;
|
|
239
285
|
}
|
|
240
286
|
|
|
287
|
+
function wornSkillNames(targetDir) {
|
|
288
|
+
const names = new Set();
|
|
289
|
+
for (const dir of listProjectSkillDirs(targetDir)) {
|
|
290
|
+
for (const name of listSkillNamesAt(dir)) names.add(name);
|
|
291
|
+
}
|
|
292
|
+
return [...names];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function wornAgentFiles(targetDir) {
|
|
296
|
+
return listAgentFilesAt(path.join(targetDir, ".opencode", "agents"));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function previousTreeAllowlist(targetDir) {
|
|
300
|
+
const file = path.join(targetDir, ".xray", "foundry-inventory.json");
|
|
301
|
+
if (!fs.existsSync(file)) return { skills: [], agents: [] };
|
|
302
|
+
try {
|
|
303
|
+
const inventory = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
304
|
+
return {
|
|
305
|
+
skills: Array.isArray(inventory?.tree?.skills) ? inventory.tree.skills : [],
|
|
306
|
+
agents: Array.isArray(inventory?.tree?.agents) ? inventory.tree.agents : [],
|
|
307
|
+
};
|
|
308
|
+
} catch {
|
|
309
|
+
return { skills: [], agents: [] };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Extra worn names that are neither mill plant, their plant, nor a prior overlay. */
|
|
314
|
+
function costumeDumpExtras(targetDir, millPlant, tree) {
|
|
315
|
+
const prior = previousTreeAllowlist(targetDir);
|
|
316
|
+
const allowedSkills = new Set([
|
|
317
|
+
...(Array.isArray(millPlant?.skills) ? millPlant.skills : []),
|
|
318
|
+
...(Array.isArray(tree?.skills) ? tree.skills : []),
|
|
319
|
+
...prior.skills,
|
|
320
|
+
]);
|
|
321
|
+
const allowedAgents = new Set([
|
|
322
|
+
...(Array.isArray(millPlant?.agents) ? millPlant.agents : []),
|
|
323
|
+
...(Array.isArray(tree?.agents) ? tree.agents : []),
|
|
324
|
+
...prior.agents,
|
|
325
|
+
]);
|
|
326
|
+
return {
|
|
327
|
+
extraSkills: wornSkillNames(targetDir).filter((name) => !allowedSkills.has(name)),
|
|
328
|
+
extraAgents: wornAgentFiles(targetDir).filter((name) => !allowedAgents.has(name)),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function assertNoCostumeDump(targetDir, millPlant, tree) {
|
|
333
|
+
if (wantsCostume(targetDir)) {
|
|
334
|
+
return { ok: true, extraSkills: [], extraAgents: [] };
|
|
335
|
+
}
|
|
336
|
+
const extras = costumeDumpExtras(targetDir, millPlant, tree);
|
|
337
|
+
if (extras.extraSkills.length > 0 || extras.extraAgents.length > 0) {
|
|
338
|
+
const listed = [...extras.extraSkills, ...extras.extraAgents].join(", ");
|
|
339
|
+
const err = new Error(
|
|
340
|
+
`foundry-inspect: costume dump without foundry.json "costume": true (${listed})`,
|
|
341
|
+
);
|
|
342
|
+
err.code = "FOUNDRY_COSTUME_DUMP";
|
|
343
|
+
err.extraSkills = extras.extraSkills;
|
|
344
|
+
err.extraAgents = extras.extraAgents;
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
return { ok: true, extraSkills: [], extraAgents: [] };
|
|
348
|
+
}
|
|
349
|
+
|
|
241
350
|
function overlayConsumerTree(targetDir, log, params) {
|
|
242
351
|
const resolved = params || loadFoundryParams(targetDir);
|
|
243
352
|
const skillsRel = resolved.skills;
|
|
@@ -365,6 +474,7 @@ function mintConsumerSuit(millPackageRoot, targetDir, log) {
|
|
|
365
474
|
path.join(targetDir, ".xray", "config.json"),
|
|
366
475
|
);
|
|
367
476
|
tree.agentsCard = overlayAgentsCard(targetDir, params);
|
|
477
|
+
assertNoCostumeDump(targetDir, millPlant, tree);
|
|
368
478
|
if (log && (tree.codex || tree.features || tree.config)) {
|
|
369
479
|
log("foundry-mint", "Overlaid consumer suit facets onto .xray", "info", {
|
|
370
480
|
constitution: tree.codex,
|
|
@@ -382,6 +492,8 @@ module.exports = {
|
|
|
382
492
|
loadFoundryExtra,
|
|
383
493
|
wantsCostume,
|
|
384
494
|
millPlantDir,
|
|
495
|
+
listSkillNamesAt,
|
|
496
|
+
listAgentFilesAt,
|
|
385
497
|
fastenMillPlant,
|
|
386
498
|
resolveInside,
|
|
387
499
|
overlayJsonFacet,
|
|
@@ -394,4 +506,13 @@ module.exports = {
|
|
|
394
506
|
mintConsumerSuit,
|
|
395
507
|
isDogfood,
|
|
396
508
|
readPackageIdentity,
|
|
509
|
+
wornSkillNames,
|
|
510
|
+
wornAgentFiles,
|
|
511
|
+
costumeDumpExtras,
|
|
512
|
+
assertNoCostumeDump,
|
|
513
|
+
machineHome,
|
|
514
|
+
processHome,
|
|
515
|
+
isIsolatedHome,
|
|
516
|
+
machineGrokPluginDir,
|
|
517
|
+
wouldClobberMachineGrok,
|
|
397
518
|
};
|
package/scripts/foundry/mint.mjs
CHANGED
|
@@ -15,9 +15,15 @@ const { mintConsumerSuit } = require("./mint-suit.cjs");
|
|
|
15
15
|
function main() {
|
|
16
16
|
const target = resolveMillRoot();
|
|
17
17
|
const millRoot = millPackageDir();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
let inventory;
|
|
19
|
+
try {
|
|
20
|
+
inventory = mintConsumerSuit(millRoot, target, (component, action, status) => {
|
|
21
|
+
process.stdout.write(`${component} ${action} ${status}\n`);
|
|
22
|
+
});
|
|
23
|
+
} catch (err) {
|
|
24
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
21
27
|
if (inventory.skipped) {
|
|
22
28
|
process.stderr.write("mint: skipped dogfood / 0xray exo plant (not a consumer project)\n");
|
|
23
29
|
process.exit(0);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xray/foundry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "0xRay mill: mint their suit params, stamp, gate, publish, CI report, git hooks. Not the worn exo.",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"./release-gate": "./release-gate.mjs",
|
|
38
38
|
"./mill-root": "./mill-root.mjs",
|
|
39
39
|
"./mint-suit": "./mint-suit.cjs",
|
|
40
|
+
"./inspect": "./inspect.mjs",
|
|
40
41
|
"./ci-monitor": "./ci-monitor.mjs",
|
|
41
42
|
"./hooks": "./hooks.mjs",
|
|
42
43
|
"./docs-build": "./docs-build.mjs"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: mill
|
|
2
2
|
skill: mill
|
|
3
|
-
description: "
|
|
4
|
-
version: "0.1.
|
|
3
|
+
description: "Fasten their plant as the suit. Factory Repertoire. Inspect is the mill discipline."
|
|
4
|
+
version: "0.1.7"
|
|
5
5
|
mode: subagent
|
|
@@ -7,16 +7,16 @@ description: Inspect AI work — diffs, tool traces, mill receipts, CI. Factory
|
|
|
7
7
|
|
|
8
8
|
This mill **fastens** a suit for **inspecting AI work**. Not an empty suit. Not a copy of 0xRay's 45/42 costume.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Mill **does** these checks (`npx @0xray/foundry inspect`). Mint fails on a costume dump. Live PUT GETs the `.tgz`. Isolated HOME refuses `~/.grok/plugins/0xray`. Not an 8th MCP.
|
|
11
11
|
|
|
12
|
-
## What
|
|
12
|
+
## What mill inspects
|
|
13
13
|
|
|
14
|
-
1. **Diff.**
|
|
15
|
-
2. **Plant vs worn.** `src/skills`, `src/opencode/agents
|
|
16
|
-
3. **Mill receipt.** `.xray/foundry-inventory.json` — mill name/version, `suit` (`fastened` / `overlay` / `costume`), `millPlant.skills`
|
|
17
|
-
4. **CI.** GitHub mill CI
|
|
18
|
-
5. **Live PUT.**
|
|
19
|
-
6. **
|
|
14
|
+
1. **Diff.** `git status` / `git diff --stat` of the milled cwd. Ignore the agent's summary if the diff disagrees.
|
|
15
|
+
2. **Plant vs worn.** `src/skills`, `src/opencode/agents` vs `.opencode/skills`, `.opencode/agents`. Same name: plant wins. Costume dump (worn names they did not plant and mill did not fasten) fails mint unless `foundry.json` has `"costume": true`.
|
|
16
|
+
3. **Mill receipt.** `.xray/foundry-inventory.json` — mill name/version, `suit` (`fastened` / `overlay` / `costume`), `millPlant.skills` must include `mill` and `inspect`.
|
|
17
|
+
4. **CI.** GitHub mill CI report (`.opencode/logs/ci-cd-monitor-report.json`), not a green check in chat. No token is a skip, not a pass invented.
|
|
18
|
+
5. **Live PUT.** HTTP 200 on the npm `.tgz`. Metadata without a tarball is not shipped.
|
|
19
|
+
6. **Isolated HOME.** Last-mile must not clobber `~/.grok/plugins/0xray`. Mill compares `$HOME` to the passwd home (`os.userInfo().homedir`), not `os.homedir()` (which follows `$HOME`).
|
|
20
20
|
|
|
21
21
|
## How to work
|
|
22
22
|
|
|
@@ -27,5 +27,6 @@ Use this when an agent (or a mill) just wrote, minted, or shipped something. Rea
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
npx @0xray/foundry mint
|
|
30
|
+
npx @0xray/foundry inspect
|
|
30
31
|
npx @0xray/foundry ci --report
|
|
31
32
|
```
|
|
@@ -222,6 +222,13 @@ function isEphemeralInstallRoot(targetDir) {
|
|
|
222
222
|
return /\/T\/|\/tmp\/|\/var\/folders\/|\/Temp\//i.test(normalized);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
const millSuit = require("../foundry/mint-suit.cjs");
|
|
226
|
+
|
|
227
|
+
/** Last-mile isolated HOME vs passwd home. `os.homedir()` follows $HOME. */
|
|
228
|
+
function isIsolatedHome(env = process.env, machine = millSuit.machineHome()) {
|
|
229
|
+
return millSuit.isIsolatedHome(env, machine);
|
|
230
|
+
}
|
|
231
|
+
|
|
225
232
|
function copyHermesHookRuntimes(packageRoot) {
|
|
226
233
|
const hooksSrc = [
|
|
227
234
|
path.join(packageRoot, "dist", "integrations", "hooks"),
|
|
@@ -560,6 +567,7 @@ module.exports = {
|
|
|
560
567
|
isDefaultMemoryRoutingOff,
|
|
561
568
|
enableMemoryRoutingIfResolves,
|
|
562
569
|
isEphemeralInstallRoot,
|
|
570
|
+
isIsolatedHome,
|
|
563
571
|
copyHermesHookRuntimes,
|
|
564
572
|
resolveOpenClawPreToolHookSource,
|
|
565
573
|
resolveOpenClawPluginDir,
|
|
@@ -8,7 +8,7 @@ const fs = require("fs");
|
|
|
8
8
|
const path = require("path");
|
|
9
9
|
const os = require("os");
|
|
10
10
|
const { execSync } = require("child_process");
|
|
11
|
-
const { wantsCostume } = require("../foundry/mint-suit.cjs");
|
|
11
|
+
const { wantsCostume, isIsolatedHome, machineHome } = require("../foundry/mint-suit.cjs");
|
|
12
12
|
const {
|
|
13
13
|
wireHermesBridge,
|
|
14
14
|
wireOpencodeBridge,
|
|
@@ -375,18 +375,25 @@ function installGrokBridge(targetDir, packageRoot, log) {
|
|
|
375
375
|
return;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
-
const
|
|
378
|
+
const machine = machineHome();
|
|
379
379
|
const ephemeral = isEphemeralInstallRoot(targetDir);
|
|
380
|
+
const isolated = isIsolatedHome();
|
|
380
381
|
const targets = [path.join(targetDir, ".grok", "plugins", "0xray")];
|
|
381
|
-
if (!ephemeral) {
|
|
382
|
-
targets.push(path.join(
|
|
382
|
+
if (!ephemeral && !isolated) {
|
|
383
|
+
targets.push(path.join(machine, ".grok", "plugins", "0xray"));
|
|
383
384
|
} else {
|
|
384
|
-
log(
|
|
385
|
+
log(
|
|
386
|
+
"grok-bridge",
|
|
387
|
+
isolated
|
|
388
|
+
? "skip machine ~/.grok plugin — isolated HOME"
|
|
389
|
+
: "skip machine ~/.grok plugin — ephemeral consumer",
|
|
390
|
+
"info",
|
|
391
|
+
);
|
|
385
392
|
}
|
|
386
393
|
|
|
387
394
|
for (const dest of targets) {
|
|
388
395
|
if (copyPluginDir(sourceDir, dest)) {
|
|
389
|
-
const rel = dest.startsWith(
|
|
396
|
+
const rel = dest.startsWith(machine) ? dest.replace(machine, "~") : path.relative(targetDir, dest);
|
|
390
397
|
log("grok-bridge", "plugin copied", "info", { path: rel || dest });
|
|
391
398
|
writePluginMcpJson(dest, targetDir, log, "grok-bridge");
|
|
392
399
|
patchGrokHooks(dest, packageRoot, targetDir, log, "grok-bridge");
|
|
@@ -395,8 +402,8 @@ function installGrokBridge(targetDir, packageRoot, log) {
|
|
|
395
402
|
}
|
|
396
403
|
}
|
|
397
404
|
|
|
398
|
-
if (!ephemeral) {
|
|
399
|
-
const grokGlobalSkills = path.join(
|
|
405
|
+
if (!ephemeral && !isolated) {
|
|
406
|
+
const grokGlobalSkills = path.join(machine, ".grok", "skills");
|
|
400
407
|
const globalCopied = syncCostumeSkills(grokGlobalSkills, packageRoot, targetDir);
|
|
401
408
|
if (globalCopied > 0) {
|
|
402
409
|
log("grok-bridge", `global skills synced (${globalCopied})`, "info", { path: "~/.grok/skills/" });
|
|
@@ -706,5 +713,6 @@ module.exports = {
|
|
|
706
713
|
grokHookShellCommand,
|
|
707
714
|
writeGrokDiscoveredHooks,
|
|
708
715
|
isEphemeralInstallRoot,
|
|
716
|
+
isIsolatedHome,
|
|
709
717
|
installFrameworkDogfoodWear,
|
|
710
718
|
};
|