blun-king-cli 9.1.562 → 9.1.564
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/LIESMICH.txt +2 -2
- package/README.md +1 -1
- package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
- package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-spine-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-spine-plugin/blun.plugin.json +3 -3
- package/agent-spine-plugin/hooks/codex.json +2 -2
- package/agent-spine-plugin/hooks/hooks.json +1 -1
- package/agent-spine-plugin/hooks/version.json +1 -1
- package/agent-spine-plugin/package.json +1 -1
- package/agent-spine-plugin/scripts/check-hosts.js +199 -196
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +1 -1
- package/agent-spine-plugin/src/cli.js +390 -7
- package/agent-spine-plugin/src/hook.js +130 -11
- package/agent-spine-plugin/src/index.js +4 -2
- package/agent-spine-plugin/src/lib/audit.js +20 -2
- package/agent-spine-plugin/src/lib/filesystem-retry.js +5 -0
- package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
- package/agent-spine-plugin/src/lib/learning.js +5575 -128
- package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -0
- package/agent-spine-plugin/src/lib/selfstarter.js +52 -5
- package/agent-spine-plugin/src/lib/source-roots.js +36 -20
- package/agent-spine-plugin/src/version.js +1 -1
- package/blun.mjs +8 -1
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-spine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "A non-destructive identity and memory spine for BLUN King agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"hooks": [
|
|
18
|
-
{ "event": "SessionStart", "command": "node \"./src/hook.js\"", "timeout":
|
|
18
|
+
{ "event": "SessionStart", "command": "node \"./src/hook.js\"", "timeout": 15 },
|
|
19
19
|
{ "event": "UserPromptSubmit", "command": "node \"./src/hook.js\"", "timeout": 15 },
|
|
20
20
|
{ "event": "PreToolUse", "matcher": "Edit|Write|apply_patch|Bash|exec_command", "command": "node \"./src/hook.js\"", "timeout": 15 },
|
|
21
|
-
{ "event": "PostToolUse", "command": "node \"./src/hook.js\"", "timeout": 15 },
|
|
21
|
+
{ "event": "PostToolUse", "command": "node \"./src/hook.js\" --silent-oversize-post-tool-use", "timeout": 15 },
|
|
22
22
|
{ "event": "PreCompact", "command": "node \"./src/hook.js\"", "timeout": 15 },
|
|
23
23
|
{ "event": "PostCompact", "command": "node \"./src/hook.js\"", "timeout": 15 },
|
|
24
24
|
{ "event": "Stop", "command": "node \"./src/hook.js\"", "timeout": 15 },
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
6
6
|
"matcher": "startup|resume|clear|compact",
|
|
7
|
-
"hooks": [{ "type": "command", "command": "node \"${PLUGIN_ROOT}/src/hook.js\"", "timeout":
|
|
7
|
+
"hooks": [{ "type": "command", "command": "node \"${PLUGIN_ROOT}/src/hook.js\"", "timeout": 15 }]
|
|
8
8
|
}
|
|
9
9
|
],
|
|
10
10
|
"UserPromptSubmit": [
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"PostToolUse": [
|
|
22
22
|
{
|
|
23
|
-
"hooks": [{ "type": "command", "command": "node \"${PLUGIN_ROOT}/src/hook.js\"", "timeout": 15 }]
|
|
23
|
+
"hooks": [{ "type": "command", "command": "node \"${PLUGIN_ROOT}/src/hook.js\" --silent-oversize-post-tool-use", "timeout": 15 }]
|
|
24
24
|
}
|
|
25
25
|
],
|
|
26
26
|
"PreCompact": [
|
|
@@ -1,196 +1,199 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { readFile, stat } from "node:fs/promises";
|
|
4
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
function assert(condition, message) {
|
|
8
|
-
if (!condition) throw new Error(message);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function json(root, path) {
|
|
12
|
-
return JSON.parse(await readFile(resolve(root, path), "utf8"));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function expand(value, variable, root) {
|
|
16
|
-
assert(typeof value === "string" && value.length > 0, "host command values must be non-empty strings");
|
|
17
|
-
return value.split(`\${${variable}}`).join(root);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function validateEntrypoint(root, value) {
|
|
21
|
-
const target = resolve(value);
|
|
22
|
-
const within = relative(root, target);
|
|
23
|
-
assert(within && !within.startsWith("..") && !isAbsolute(within), "MCP entrypoint must remain inside the plugin");
|
|
24
|
-
const metadata = await stat(target);
|
|
25
|
-
assert(metadata.isFile(), "MCP entrypoint must be a regular file");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function validateHooks(root, hooks, { required, commandRoot }) {
|
|
29
|
-
assert(hooks && typeof hooks === "object" && !Array.isArray(hooks), "hook bundle is missing");
|
|
30
|
-
assert(Object.keys(hooks).every((key) => ["description", "hooks"].includes(key)), "hook bundle contains unsupported top-level metadata");
|
|
31
|
-
assert(hooks.description && typeof hooks.description === "string", "hook bundle description is missing");
|
|
32
|
-
for (const event of required) {
|
|
33
|
-
const registrations = hooks.hooks?.[event];
|
|
34
|
-
assert(Array.isArray(registrations) && registrations.length === 1, `${event} must have exactly one registration`);
|
|
35
|
-
assert(Array.isArray(registrations[0].hooks) && registrations[0].hooks.length === 1, `${event} must have exactly one hook command`);
|
|
36
|
-
const command = registrations[0].hooks[0];
|
|
37
|
-
assert(command.type === "command", `${event} must use a command hook`);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
assert(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
assert(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
json(root, ".
|
|
138
|
-
json(root, "
|
|
139
|
-
json(root, "
|
|
140
|
-
json(root, "
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
assert(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
assert(
|
|
151
|
-
assert(
|
|
152
|
-
|
|
153
|
-
assert(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
function assert(condition, message) {
|
|
8
|
+
if (!condition) throw new Error(message);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function json(root, path) {
|
|
12
|
+
return JSON.parse(await readFile(resolve(root, path), "utf8"));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function expand(value, variable, root) {
|
|
16
|
+
assert(typeof value === "string" && value.length > 0, "host command values must be non-empty strings");
|
|
17
|
+
return value.split(`\${${variable}}`).join(root);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function validateEntrypoint(root, value) {
|
|
21
|
+
const target = resolve(value);
|
|
22
|
+
const within = relative(root, target);
|
|
23
|
+
assert(within && !within.startsWith("..") && !isAbsolute(within), "MCP entrypoint must remain inside the plugin");
|
|
24
|
+
const metadata = await stat(target);
|
|
25
|
+
assert(metadata.isFile(), "MCP entrypoint must be a regular file");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateHooks(root, hooks, { required, commandRoot }) {
|
|
29
|
+
assert(hooks && typeof hooks === "object" && !Array.isArray(hooks), "hook bundle is missing");
|
|
30
|
+
assert(Object.keys(hooks).every((key) => ["description", "hooks"].includes(key)), "hook bundle contains unsupported top-level metadata");
|
|
31
|
+
assert(hooks.description && typeof hooks.description === "string", "hook bundle description is missing");
|
|
32
|
+
for (const event of required) {
|
|
33
|
+
const registrations = hooks.hooks?.[event];
|
|
34
|
+
assert(Array.isArray(registrations) && registrations.length === 1, `${event} must have exactly one registration`);
|
|
35
|
+
assert(Array.isArray(registrations[0].hooks) && registrations[0].hooks.length === 1, `${event} must have exactly one hook command`);
|
|
36
|
+
const command = registrations[0].hooks[0];
|
|
37
|
+
assert(command.type === "command", `${event} must use a command hook`);
|
|
38
|
+
const expected = `node "\${${commandRoot}}/src/hook.js"${event === "PostToolUse"
|
|
39
|
+
? " --silent-oversize-post-tool-use" : ""}`;
|
|
40
|
+
assert(command.command === expected, `${event} must use the bundled lifecycle adapter`);
|
|
41
|
+
assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} timeout is unsafe`);
|
|
42
|
+
}
|
|
43
|
+
const extras = Object.keys(hooks.hooks || {}).filter((event) => !required.includes(event));
|
|
44
|
+
assert(extras.length === 0, `unknown hook events: ${extras.join(", ")}`);
|
|
45
|
+
return { events: required, commands: required.length, entrypoint: relative(root, resolve(root, "src/hook.js")) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validateBlunHooks(root, hooks) {
|
|
49
|
+
const required = [
|
|
50
|
+
"SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse",
|
|
51
|
+
"PreCompact", "PostCompact", "Stop", "SubagentStop"
|
|
52
|
+
];
|
|
53
|
+
assert(Array.isArray(hooks), "BLUN hook bundle is missing");
|
|
54
|
+
assert(hooks.length === required.length, "BLUN must register exactly one command per lifecycle event");
|
|
55
|
+
assert(JSON.stringify(hooks.map(({ event }) => event)) === JSON.stringify(required),
|
|
56
|
+
"BLUN lifecycle events must remain complete and ordered");
|
|
57
|
+
for (const event of required) {
|
|
58
|
+
const registrations = hooks.filter((hook) => hook.event === event);
|
|
59
|
+
assert(registrations.length === 1, `${event} must have exactly one BLUN registration`);
|
|
60
|
+
const command = registrations[0];
|
|
61
|
+
const expected = `node "./src/hook.js"${event === "PostToolUse"
|
|
62
|
+
? " --silent-oversize-post-tool-use" : ""}`;
|
|
63
|
+
assert(command.command === expected, `${event} must use the bundled BLUN lifecycle adapter`);
|
|
64
|
+
assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} BLUN timeout is unsafe`);
|
|
65
|
+
}
|
|
66
|
+
return { events: required, commands: required.length, entrypoint: relative(root, resolve(root, "src/hook.js")) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function initializeServer({ label, root, variable, server, version }) {
|
|
70
|
+
assert(server && typeof server === "object" && !Array.isArray(server), `${label} MCP registration is missing`);
|
|
71
|
+
assert(server.command === "node", `${label} MCP registration must use the Node.js runtime`);
|
|
72
|
+
assert(Array.isArray(server.args) && server.args.length === 1, `${label} MCP registration must name exactly one entrypoint`);
|
|
73
|
+
const command = process.execPath;
|
|
74
|
+
const args = server.args.map((value) => {
|
|
75
|
+
const expanded = expand(value, variable, root);
|
|
76
|
+
return isAbsolute(expanded) ? expanded : resolve(root, expanded);
|
|
77
|
+
});
|
|
78
|
+
assert(!args.some((value) => value.includes("${")), `${label} MCP registration contains an unresolved variable`);
|
|
79
|
+
await validateEntrypoint(root, args[0]);
|
|
80
|
+
|
|
81
|
+
return await new Promise((resolveResult, reject) => {
|
|
82
|
+
const child = spawn(command, args, {
|
|
83
|
+
cwd: root,
|
|
84
|
+
env: { ...process.env, CLAUDE_PLUGIN_ROOT: root, PLUGIN_ROOT: root },
|
|
85
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
86
|
+
});
|
|
87
|
+
let stdout = "";
|
|
88
|
+
let stderr = "";
|
|
89
|
+
let settled = false;
|
|
90
|
+
const finish = (error, value) => {
|
|
91
|
+
if (settled) return;
|
|
92
|
+
settled = true;
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
const settle = () => {
|
|
95
|
+
if (error) reject(error);
|
|
96
|
+
else resolveResult(value);
|
|
97
|
+
};
|
|
98
|
+
if (child.exitCode !== null || child.signalCode !== null) return settle();
|
|
99
|
+
child.once("close", settle);
|
|
100
|
+
child.kill();
|
|
101
|
+
};
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
finish(new Error(`${label} MCP initialize timed out${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
104
|
+
}, 3000);
|
|
105
|
+
child.stderr.setEncoding("utf8");
|
|
106
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
107
|
+
child.stdout.setEncoding("utf8");
|
|
108
|
+
child.stdout.on("data", (chunk) => {
|
|
109
|
+
stdout += chunk;
|
|
110
|
+
const newline = stdout.indexOf("\n");
|
|
111
|
+
if (newline < 0) return;
|
|
112
|
+
try {
|
|
113
|
+
const message = JSON.parse(stdout.slice(0, newline));
|
|
114
|
+
assert(message.id === 1, `${label} MCP initialize returned the wrong request id`);
|
|
115
|
+
assert(message.result?.serverInfo?.name === "agent-spine", `${label} MCP initialize returned the wrong server identity`);
|
|
116
|
+
assert(message.result?.serverInfo?.version === version, `${label} MCP initialize returned a stale server version`);
|
|
117
|
+
finish(null, { label, server: message.result.serverInfo.name, entrypoint: relative(root, args[0]) });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
finish(error);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
child.once("error", (error) => finish(error));
|
|
123
|
+
child.once("close", (code) => {
|
|
124
|
+
if (!settled) finish(new Error(`${label} MCP server exited with ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
125
|
+
});
|
|
126
|
+
// A real MCP host keeps stdio open after initialization. Closing stdin
|
|
127
|
+
// here let macOS and Windows terminate the server before its queued reply.
|
|
128
|
+
child.stdin.write(`${JSON.stringify({
|
|
129
|
+
jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18" }
|
|
130
|
+
})}\n`);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function checkHosts(root = process.cwd()) {
|
|
135
|
+
root = resolve(root);
|
|
136
|
+
const [pkg, blunManifest, claudeManifest, claudeMcp, codexManifest, claudeHooks, codexHooks, hookVersion] = await Promise.all([
|
|
137
|
+
json(root, "package.json"),
|
|
138
|
+
json(root, "blun.plugin.json"),
|
|
139
|
+
json(root, ".claude-plugin/plugin.json"),
|
|
140
|
+
json(root, ".mcp.json"),
|
|
141
|
+
json(root, ".codex-plugin/plugin.json"),
|
|
142
|
+
json(root, "hooks/hooks.json"),
|
|
143
|
+
json(root, "hooks/codex.json"),
|
|
144
|
+
json(root, "hooks/version.json")
|
|
145
|
+
]);
|
|
146
|
+
assert(blunManifest.version === pkg.version && claudeManifest.version === pkg.version && codexManifest.version === pkg.version,
|
|
147
|
+
"host manifests must use the package cache version");
|
|
148
|
+
assert(hookVersion.schema === "agentspine.hook-bundle/v1" && hookVersion.version === pkg.version,
|
|
149
|
+
"hook bundle version must match the package cache version");
|
|
150
|
+
assert(hookVersion.contract === "agentspine.preflight/v2", "hook bundle preflight contract is missing");
|
|
151
|
+
assert(pkg.bin?.["agentspine-worker"] === "./src/worker.js", "package must register exactly one gateway worker entrypoint");
|
|
152
|
+
await validateEntrypoint(root, resolve(root, pkg.bin["agentspine-worker"]));
|
|
153
|
+
assert(claudeManifest.mcpServers === "./.mcp.json", "Claude manifest must explicitly reference ./.mcp.json");
|
|
154
|
+
assert(claudeManifest.hooks === undefined, "default hooks/hooks.json must not also be registered through a supplemental manifest path");
|
|
155
|
+
assert(codexManifest.hooks === undefined, "Codex manifest must omit unsupported hook registration fields");
|
|
156
|
+
assert(claudeMcp.mcpServers && Object.keys(claudeMcp.mcpServers).length === 1, "Claude MCP file must contain one mcpServers registration");
|
|
157
|
+
assert(codexManifest.mcpServers && Object.keys(codexManifest.mcpServers).length === 1, "Codex manifest must contain one MCP registration");
|
|
158
|
+
const commonEvents = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PreCompact", "PostCompact", "Stop", "SubagentStop"];
|
|
159
|
+
const claudeHookInventory = validateHooks(root, claudeHooks, {
|
|
160
|
+
required: [...commonEvents, "InstructionsLoaded"], commandRoot: "CLAUDE_PLUGIN_ROOT"
|
|
161
|
+
});
|
|
162
|
+
const codexHookInventory = validateHooks(root, codexHooks, { required: commonEvents, commandRoot: "PLUGIN_ROOT" });
|
|
163
|
+
const blunHookInventory = validateBlunHooks(root, blunManifest.hooks);
|
|
164
|
+
const registrations = await Promise.all([
|
|
165
|
+
initializeServer({ label: "blun", root, variable: "BLUN_PLUGIN_ROOT", server: blunManifest.mcpServers["agent-spine"], version: pkg.version }),
|
|
166
|
+
initializeServer({ label: "claude", root, variable: "CLAUDE_PLUGIN_ROOT", server: claudeMcp.mcpServers["agent-spine"], version: pkg.version }),
|
|
167
|
+
initializeServer({ label: "codex", root, variable: "PLUGIN_ROOT", server: codexManifest.mcpServers["agent-spine"], version: pkg.version })
|
|
168
|
+
]);
|
|
169
|
+
return {
|
|
170
|
+
ok: true, root, version: pkg.version, registrations,
|
|
171
|
+
hooks: { blun: blunHookInventory, claude: claudeHookInventory, codex: codexHookInventory },
|
|
172
|
+
hookDiscovery: {
|
|
173
|
+
blun: "plugin-manifest", claude: "default-hooks-directory", codex: "bundled-host-adapter",
|
|
174
|
+
trust: "host-user-required", liveTrustVerified: false
|
|
175
|
+
},
|
|
176
|
+
worker: { entrypoint: pkg.bin["agentspine-worker"], setsPerInstall: 1 },
|
|
177
|
+
exactlyOnce: { mcpServersPerHost: 1, hookSetsPerHost: 1, workerSetsPerInstall: 1 },
|
|
178
|
+
authority: "registration-check-only"
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function main() {
|
|
183
|
+
const args = process.argv.slice(2);
|
|
184
|
+
let root = process.cwd();
|
|
185
|
+
let pretty = false;
|
|
186
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
187
|
+
if (args[index] === "--root") root = args[++index];
|
|
188
|
+
else if (args[index] === "--json") pretty = true;
|
|
189
|
+
else throw new Error(`unknown host-check argument: ${args[index]}`);
|
|
190
|
+
}
|
|
191
|
+
process.stdout.write(`${JSON.stringify(await checkHosts(root), null, pretty ? 2 : 0)}\n`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
195
|
+
main().catch((error) => {
|
|
196
|
+
process.stderr.write(`AgentSpine host check failed: ${error.message}\n`);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
@@ -66,7 +66,7 @@ Follow Markdown links from the host's native instruction files and memory index.
|
|
|
66
66
|
- New observations begin as candidates. Promote them only with adequate evidence; later information changes relevance and confidence instead of silently erasing history.
|
|
67
67
|
- Record a learning candidate with evidence instead of directly asserting a new fact. Add evidence append-only; use `supersedesId` for changed facts and rollback for mistakes.
|
|
68
68
|
- Never set `confirmedByUser` based on another agent, memory, Markdown, or inferred intent. It attests to a real user confirmation. Automatic promotion remains default-off and limited to low-risk project facts and references.
|
|
69
|
-
- Outcome-gated `behavior` learning requires exact persona/user/tenant/project/group/task scope, independent fixed-task before and after measurements, at least one objective evaluator, a bounded canary, and measured improvement. Model suggestions never count. A blocking defect, regression, insufficient improvement,
|
|
69
|
+
- Outcome-gated `behavior` learning requires exact persona/user/tenant/project/group/task scope, independent fixed-task before and after measurements, at least one objective evaluator, a bounded canary, and measured improvement. Model suggestions never count. A blocking defect, regression, insufficient improvement, stale canary, or locally revoked measurement or delivery must roll back; MCP may inspect `learning_outcome_status` but cannot record outcomes or revocations.
|
|
70
70
|
- Ask personal questions sparingly and only when conversation makes them natural. Never conduct a profile interview.
|
|
71
71
|
- Attention cues are suggestions, not obligations. Never contact a person, assign work, or interrupt focused work solely because a cue exists.
|
|
72
72
|
- Record activity as a minimal timestamp when possible; do not copy conversation content into attention state.
|