blun-king-cli 9.1.527 → 9.1.550
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 +13 -834
- package/README.md +41 -799
- package/bin/agent-resume-snapshot.cjs +31 -0
- package/bin/assistant-message-offload-policy.cjs +23 -2
- package/bin/codebase-search-runtime.cjs +23 -0
- package/bin/context-performance-policy.cjs +2 -5
- package/bin/context-pressure-policy.cjs +20 -0
- package/bin/cron-run-output.cjs +45 -0
- package/bin/cron-run-store.cjs +145 -0
- package/bin/durable-task-resume-policy.cjs +130 -0
- package/bin/durable-task-resume-runtime.cjs +117 -0
- package/bin/durable-task-resume-store.cjs +88 -0
- package/bin/editable-tool-approval-policy.cjs +540 -0
- package/bin/editable-tool-approval-runtime.cjs +99 -0
- package/bin/empty-response-retry-policy.cjs +29 -0
- package/bin/history-offload-pressure-policy.cjs +33 -0
- package/bin/html-to-research-markdown.cjs +146 -0
- package/bin/programmatic-context-isolation.cjs +25 -0
- package/bin/programmatic-tool-runtime.mjs +627 -0
- package/bin/read-continuation-policy.cjs +36 -5
- package/bin/scoped-cron-run-policy.cjs +358 -0
- package/bin/skill-activation-performance-policy.cjs +9 -0
- package/bin/startup-preferences.cjs +3 -3
- package/bin/structured-agent-swarm-output.cjs +325 -0
- package/bin/structured-subagent-output.cjs +252 -0
- package/bin/subagent-context-fork-policy.cjs +155 -0
- package/bin/subagent-skill-policy.cjs +204 -0
- package/bin/telegram-approval-relay.cjs +2 -1
- package/bin/telegram-direct-focus-policy.cjs +25 -1
- package/bin/todo-list-turn-policy.cjs +111 -1
- package/bin/tool-result-offload-policy.cjs +43 -1
- package/bin/turn-thinking-policy.cjs +6 -15
- package/bin/turn-tool-performance-policy.cjs +5 -4
- package/bin/update-notice.js +14 -18
- package/bin/user-message-offload-policy.cjs +12 -1
- package/blun.mjs +1406 -265
- package/codebase-index/README.md +82 -0
- package/codebase-index/codebase_index.py +469 -0
- package/package.json +24 -38
- package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
- package/telegram-plugin/dist/bridge.mjs +8 -1
- package/CHANGELOG.md +0 -260
- package/agent-spine-plugin/CHANGELOG.md +0 -406
- package/agent-spine-plugin/CONTRIBUTING.md +0 -52
- package/agent-spine-plugin/README.md +0 -344
- package/agent-spine-plugin/SECURITY.md +0 -47
- package/agent-spine-plugin/docs/acceptance.md +0 -61
- package/agent-spine-plugin/docs/architecture.md +0 -183
- package/agent-spine-plugin/docs/attention.md +0 -121
- package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
- package/agent-spine-plugin/docs/channel-runtime.md +0 -92
- package/agent-spine-plugin/docs/coordination.md +0 -138
- package/agent-spine-plugin/docs/feed-transport.md +0 -99
- package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
- package/agent-spine-plugin/docs/harness-reference.md +0 -45
- package/agent-spine-plugin/docs/host-integration.md +0 -129
- package/agent-spine-plugin/docs/https-transport.md +0 -116
- package/agent-spine-plugin/docs/learning.md +0 -133
- package/agent-spine-plugin/docs/object-transport.md +0 -93
- package/agent-spine-plugin/docs/peer-transport.md +0 -88
- package/agent-spine-plugin/docs/preflight-recall.md +0 -69
- package/agent-spine-plugin/docs/preservation-contract.md +0 -53
- package/agent-spine-plugin/docs/quality-gates.md +0 -50
- package/agent-spine-plugin/docs/relationships.md +0 -73
- package/agent-spine-plugin/docs/releasing.md +0 -83
- package/agent-spine-plugin/docs/roadmap.md +0 -307
- package/agent-spine-plugin/docs/selfstarter.md +0 -88
- package/agent-spine-plugin/docs/session-briefing.md +0 -74
- package/agent-spine-plugin/docs/shared-memory.md +0 -259
- package/agent-spine-plugin/docs/source-roots.md +0 -86
- package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
- package/agent-spine-plugin/scripts/check-hosts.js +0 -195
- package/agent-spine-plugin/scripts/check-install.js +0 -569
- package/agent-spine-plugin/scripts/check-syntax.js +0 -29
- package/agent-spine-plugin/scripts/github-actions.js +0 -11
- package/agent-spine-plugin/scripts/release-check.js +0 -128
- package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
- package/agent-spine-plugin/scripts/run-checks.js +0 -46
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
- package/agent-spine-plugin/spine-example/1-identity.md +0 -12
- package/agent-spine-plugin/spine-example/2-voice.md +0 -6
- package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
- package/agent-spine-plugin/spine-example/4-history.md +0 -4
- package/bin/package-regression-policy.cjs +0 -77
- package/release-planned-removals.json +0 -15
- package/scripts/check-active-profile-plugin-startup.js +0 -36
- package/scripts/check-approval-observability-regression.js +0 -111
- package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
- package/scripts/check-bundled-agent-spine-regression.js +0 -48
- package/scripts/check-copy-command-regression.js +0 -74
- package/scripts/check-historical-tool-result-preview-regression.js +0 -77
- package/scripts/check-mcp-startup-wait-budget.js +0 -48
- package/scripts/check-package-regression.js +0 -38
- package/scripts/check-plugin-startup-regression.js +0 -53
- package/scripts/check-queue-controls-regression.js +0 -189
- package/scripts/check-release-metadata.js +0 -103
- package/scripts/check-reload-agent-spine-regression.js +0 -76
- package/scripts/check-resume-replay-regression.js +0 -100
- package/scripts/check-session-cancel-regression.js +0 -43
- package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
- package/scripts/check-session-start-hook-context-regression.js +0 -228
- package/scripts/check-shell-terminal-isolation-regression.js +0 -81
- package/scripts/check-slash-escape-regression.js +0 -89
- package/scripts/check-telegram-bridge-watchdog.js +0 -60
- package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
- package/scripts/check-todo-loop-regression.js +0 -78
- package/scripts/check-todo-recovery-catalog-regression.js +0 -50
- /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
8
|
-
const REQUIRED_PACKAGE_FILES = [
|
|
9
|
-
"blun.plugin.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json", ".mcp.json",
|
|
10
|
-
"CHANGELOG.md", "LICENSE", "README.md", "bin/agentspine.js", "bin/agentspine-mcp.js",
|
|
11
|
-
"docs/acceptance.md", "docs/source-roots.md", "docs/preflight-recall.md", "docs/preservation-contract.md", "scripts/run-acceptance.js",
|
|
12
|
-
"skills/agent-spine/SKILL.md", "src/index.js", "src/mcp.js", "src/worker.js",
|
|
13
|
-
"hooks/hooks.json", "hooks/codex.json", "hooks/version.json"
|
|
14
|
-
];
|
|
15
|
-
const FORBIDDEN_PACKAGE_PATHS = [
|
|
16
|
-
/(?:^|\/)\.env(?:\.|$)/i,
|
|
17
|
-
/(?:^|\/)\.git(?:$|\/)/i,
|
|
18
|
-
/(?:^|\/)\.agentspine(?:$|\/)/i,
|
|
19
|
-
/(?:^|\/)(?:AGENTS|CLAUDE|SOUL|MEMORY)\.md$/i,
|
|
20
|
-
/(?:^|\/)(?:test|\.github)(?:$|\/)/,
|
|
21
|
-
/\.(?:key|pem|p12|pfx)$/i,
|
|
22
|
-
/(?:^|\/)state\.json$/i,
|
|
23
|
-
/(?:^|\/)(?:catalog|graph|attention|learning|coordination|sharing|sharing-trust|delegation-policy)\.json$/
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
function parse(argv) {
|
|
27
|
-
const options = { root: process.cwd(), tag: null, allowDirty: false, skipPack: false, json: false };
|
|
28
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
29
|
-
const token = argv[index];
|
|
30
|
-
if (token === "--root") options.root = argv[++index];
|
|
31
|
-
else if (token === "--tag") options.tag = argv[++index];
|
|
32
|
-
else if (token === "--allow-dirty") options.allowDirty = true;
|
|
33
|
-
else if (token === "--skip-pack") options.skipPack = true;
|
|
34
|
-
else if (token === "--json") options.json = true;
|
|
35
|
-
else throw new Error(`unknown release-check argument: ${token}`);
|
|
36
|
-
}
|
|
37
|
-
options.root = resolve(options.root);
|
|
38
|
-
return options;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function json(root, path) {
|
|
42
|
-
return JSON.parse(await readFile(resolve(root, path), "utf8"));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function command(root, args) {
|
|
46
|
-
const npmCli = process.env.npm_execpath;
|
|
47
|
-
const executable = npmCli ? process.execPath : (process.platform === "win32" ? "npm.cmd" : "npm");
|
|
48
|
-
const commandArgs = npmCli ? [npmCli, ...args] : args;
|
|
49
|
-
return spawnSync(executable, commandArgs, { cwd: root, encoding: "utf8" });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function git(root, args) {
|
|
53
|
-
return spawnSync("git", args, { cwd: root, encoding: "utf8" });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function assert(condition, message) {
|
|
57
|
-
if (!condition) throw new Error(message);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function validatePackageReport(report, version) {
|
|
61
|
-
assert(Array.isArray(report) && report.length === 1, "npm pack report must describe exactly one package");
|
|
62
|
-
const item = report[0];
|
|
63
|
-
assert(item.version === version, "npm pack version does not match package.json");
|
|
64
|
-
assert(typeof item.filename === "string" && item.filename.endsWith(`-${version}.tgz`), "npm pack filename is not versioned correctly");
|
|
65
|
-
assert(typeof item.integrity === "string" && item.integrity.startsWith("sha512-"), "npm pack did not report SHA-512 integrity");
|
|
66
|
-
assert(Number.isInteger(item.entryCount) && item.entryCount > 0 && item.entryCount <= 500, "npm package file count is outside the release limit");
|
|
67
|
-
assert(Number.isInteger(item.unpackedSize) && item.unpackedSize > 0 && item.unpackedSize <= 2 * 1024 * 1024, "npm package exceeds the 2 MiB unpacked release limit");
|
|
68
|
-
const paths = (item.files || []).map((file) => file.path);
|
|
69
|
-
assert(paths.length === new Set(paths).size, "npm package contains duplicate paths");
|
|
70
|
-
for (const path of REQUIRED_PACKAGE_FILES) assert(paths.includes(path), `npm package is missing required file: ${path}`);
|
|
71
|
-
for (const path of paths) {
|
|
72
|
-
assert(path && !path.startsWith("/") && !path.split("/").includes(".."), `npm package contains an unsafe path: ${path}`);
|
|
73
|
-
assert(!FORBIDDEN_PACKAGE_PATHS.some((pattern) => pattern.test(path)), `npm package contains forbidden state or source material: ${path}`);
|
|
74
|
-
}
|
|
75
|
-
return { filename: item.filename, integrity: item.integrity, files: paths.length, unpackedSize: item.unpackedSize };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function releaseCheck(options) {
|
|
79
|
-
const [pkg, lock, blun, claude, codex, marketplace, hookVersion, changelog] = await Promise.all([
|
|
80
|
-
json(options.root, "package.json"), json(options.root, "package-lock.json"),
|
|
81
|
-
json(options.root, "blun.plugin.json"),
|
|
82
|
-
json(options.root, ".claude-plugin/plugin.json"), json(options.root, ".codex-plugin/plugin.json"),
|
|
83
|
-
json(options.root, ".claude-plugin/marketplace.json"), json(options.root, "hooks/version.json"),
|
|
84
|
-
readFile(resolve(options.root, "CHANGELOG.md"), "utf8")
|
|
85
|
-
]);
|
|
86
|
-
assert(SEMVER_RE.test(pkg.version || ""), "package version is not valid SemVer");
|
|
87
|
-
const versions = [lock.version, lock.packages?.[""]?.version, blun.version, claude.version, codex.version, marketplace.plugins?.[0]?.version, hookVersion.version];
|
|
88
|
-
assert(versions.every((version) => version === pkg.version), "release version differs across package and host manifests");
|
|
89
|
-
assert(pkg.name === lock.name && pkg.name === lock.packages?.[""]?.name, "package name differs from package-lock.json");
|
|
90
|
-
assert(pkg.repository?.url === "git+https://github.com/Maykbiletti/AgentSpine.git", "package repository must identify the public source repository exactly");
|
|
91
|
-
if (options.tag) {
|
|
92
|
-
assert(options.tag === `v${pkg.version}`, `release tag must be v${pkg.version}`);
|
|
93
|
-
const escaped = pkg.version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
94
|
-
assert(new RegExp(`^## \\[${escaped}\\] - \\d{4}-\\d{2}-\\d{2}$`, "m").test(changelog), `CHANGELOG.md needs a dated ${pkg.version} section`);
|
|
95
|
-
}
|
|
96
|
-
if (!options.allowDirty) {
|
|
97
|
-
const status = git(options.root, ["status", "--porcelain"]);
|
|
98
|
-
assert(status.status === 0, status.stderr || "git status failed");
|
|
99
|
-
assert(status.stdout.trim() === "", "release worktree must be clean");
|
|
100
|
-
}
|
|
101
|
-
let packageReport = null;
|
|
102
|
-
if (!options.skipPack) {
|
|
103
|
-
const packed = command(options.root, ["pack", "--dry-run", "--ignore-scripts", "--json"]);
|
|
104
|
-
assert(packed.status === 0, packed.stderr || "npm pack --dry-run failed");
|
|
105
|
-
packageReport = validatePackageReport(JSON.parse(packed.stdout), pkg.version);
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
ok: true,
|
|
109
|
-
version: pkg.version,
|
|
110
|
-
tag: options.tag,
|
|
111
|
-
cleanRequired: !options.allowDirty,
|
|
112
|
-
package: packageReport,
|
|
113
|
-
authority: "release-metadata-only"
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function main() {
|
|
118
|
-
const options = parse(process.argv.slice(2));
|
|
119
|
-
const result = await releaseCheck(options);
|
|
120
|
-
process.stdout.write(`${JSON.stringify(result, null, options.json ? 2 : 0)}\n`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
124
|
-
main().catch((error) => {
|
|
125
|
-
process.stderr.write(`AgentSpine release check failed: ${error.message}\n`);
|
|
126
|
-
process.exitCode = 1;
|
|
127
|
-
});
|
|
128
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { renderAcceptanceReport, runVisibleAcceptance } from "../src/lib/acceptance.js";
|
|
5
|
-
|
|
6
|
-
export async function main(argv = process.argv.slice(2)) {
|
|
7
|
-
const unknown = argv.filter((item) => item !== "--json");
|
|
8
|
-
if (unknown.length) throw new Error(`unknown acceptance argument: ${unknown[0]}`);
|
|
9
|
-
const report = await runVisibleAcceptance();
|
|
10
|
-
process.stdout.write(argv.includes("--json") ? `${JSON.stringify(report, null, 2)}\n` : renderAcceptanceReport(report));
|
|
11
|
-
return report;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
15
|
-
main().catch((error) => {
|
|
16
|
-
process.stderr.write(`AgentSpine acceptance failed: ${error.message}\n`);
|
|
17
|
-
process.exitCode = 1;
|
|
18
|
-
});
|
|
19
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { githubErrorCommand } from "./github-actions.js";
|
|
6
|
-
|
|
7
|
-
const CHECKS = [
|
|
8
|
-
{ title: "Syntax check", script: "lint" },
|
|
9
|
-
{ title: "Hermetic test suite", script: "test" },
|
|
10
|
-
{ title: "Host manifest check", script: "host:check" },
|
|
11
|
-
{ title: "Installed bundle check", script: "host:install-check" },
|
|
12
|
-
{ title: "Runtime smoke test", script: "smoke" },
|
|
13
|
-
{ title: "Self audit", script: "audit:self" }
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
function runNpmScript(script) {
|
|
17
|
-
const npmExecPath = process.env.npm_execpath;
|
|
18
|
-
const executable = npmExecPath ? process.execPath : (process.platform === "win32" ? "npm.cmd" : "npm");
|
|
19
|
-
const args = npmExecPath ? [npmExecPath, "run", script] : ["run", script];
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const child = spawn(executable, args, { stdio: "inherit", shell: false, windowsHide: true });
|
|
22
|
-
child.once("error", reject);
|
|
23
|
-
child.once("close", (code) => resolve(code ?? 1));
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function runChecks({
|
|
28
|
-
checks = CHECKS, runCommand = runNpmScript, githubActions = process.env.GITHUB_ACTIONS === "true", output = process.stderr
|
|
29
|
-
} = {}) {
|
|
30
|
-
for (const check of checks) {
|
|
31
|
-
let code;
|
|
32
|
-
try { code = await runCommand(check.script); }
|
|
33
|
-
catch (error) {
|
|
34
|
-
if (githubActions) output.write(githubErrorCommand(check.title, error.message) + "\n");
|
|
35
|
-
else output.write(`${check.title} failed: ${error.message}\n`);
|
|
36
|
-
return 1;
|
|
37
|
-
}
|
|
38
|
-
if (code !== 0) {
|
|
39
|
-
if (githubActions) output.write(githubErrorCommand(check.title, `npm run ${check.script} exited with code ${code}`) + "\n");
|
|
40
|
-
return code;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) process.exitCode = await runChecks();
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { cpus, tmpdir } from "node:os";
|
|
4
|
-
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { githubErrorCommand } from "./github-actions.js";
|
|
8
|
-
|
|
9
|
-
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
10
|
-
const base = await mkdtemp(join(tmpdir(), "agentspine-hermetic-tests-"));
|
|
11
|
-
const testFiles = (await readdir(join(root, "test"))).filter((name) => name.endsWith(".test.js")).sort();
|
|
12
|
-
const concurrency = Math.max(1, Math.min(4, cpus().length));
|
|
13
|
-
|
|
14
|
-
async function profileEnvironment(mode, index) {
|
|
15
|
-
const profile = join(base, mode, String(index));
|
|
16
|
-
const home = join(profile, "home"); const claude = join(profile, "claude"); const codex = join(profile, "codex");
|
|
17
|
-
const state = join(profile, "state"); const xdgState = join(profile, "xdg-state"); const temporary = join(profile, "tmp");
|
|
18
|
-
for (const directory of [home, claude, codex, state, xdgState, temporary]) await mkdir(directory, { recursive: true });
|
|
19
|
-
if (mode === "populated") {
|
|
20
|
-
await writeFile(join(claude, "CLAUDE.md"), "# Hermetic populated Claude profile\n\nSynthetic global source.\n");
|
|
21
|
-
await writeFile(join(codex, "AGENTS.md"), "# Hermetic populated Codex profile\n\nSynthetic global source.\n");
|
|
22
|
-
}
|
|
23
|
-
const inherited = Object.fromEntries(Object.entries(process.env).filter(([key]) =>
|
|
24
|
-
!/^(?:AGENTSPINE_|CLAUDE_|CODEX_|HOME$|USERPROFILE$|XDG_STATE_HOME$|TMPDIR$|TMP$|TEMP$|NODE_OPTIONS$|NODE_PATH$)/.test(key)));
|
|
25
|
-
return { ...inherited, HOME: home, USERPROFILE: home, CLAUDE_CONFIG_DIR: claude, CODEX_HOME: codex,
|
|
26
|
-
AGENTSPINE_STATE_DIR: state, XDG_STATE_HOME: xdgState, TMPDIR: temporary, TMP: temporary, TEMP: temporary,
|
|
27
|
-
AGENTSPINE_HERMETIC_ROOT: profile, AGENTSPINE_TEST_PROFILE: mode };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function runOne(file, mode, index) {
|
|
31
|
-
const env = await profileEnvironment(mode, index);
|
|
32
|
-
return new Promise((resolvePromise) => {
|
|
33
|
-
const child = spawn(process.execPath, ["--test", join(root, "test", file)], { cwd: root, env, shell: false,
|
|
34
|
-
stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
35
|
-
let stdout = ""; let stderr = "";
|
|
36
|
-
child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
37
|
-
child.on("close", (code) => resolvePromise({ file, mode, code, stdout, stderr }));
|
|
38
|
-
child.on("error", (error) => resolvePromise({ file, mode, code: 1, stdout, stderr: error.message }));
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function runMode(mode) {
|
|
43
|
-
const queue = testFiles.map((file, index) => ({ file, index })); const results = [];
|
|
44
|
-
async function worker() { while (queue.length) { const item = queue.shift(); results.push(await runOne(item.file, mode, item.index)); } }
|
|
45
|
-
await Promise.all(Array.from({ length: concurrency }, () => worker()));
|
|
46
|
-
for (const result of results.sort((a, b) => a.file.localeCompare(b.file))) {
|
|
47
|
-
if (result.code !== 0) {
|
|
48
|
-
if (process.env.GITHUB_ACTIONS === "true") {
|
|
49
|
-
process.stderr.write(githubErrorCommand(`${mode}: ${result.file}`, result.stdout + result.stderr) + "\n");
|
|
50
|
-
}
|
|
51
|
-
process.stderr.write("\n[" + mode + "] " + result.file + " failed\n" + result.stdout + result.stderr);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return results;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let failed = false; let runnerError = null;
|
|
58
|
-
try {
|
|
59
|
-
for (const mode of ["empty", "populated"]) {
|
|
60
|
-
const results = await runMode(mode); const failures = results.filter((item) => item.code !== 0);
|
|
61
|
-
process.stdout.write("Hermetic " + mode + " profile: " + (results.length - failures.length) + "/" + results.length + " test files passed\n");
|
|
62
|
-
if (failures.length) failed = true;
|
|
63
|
-
}
|
|
64
|
-
} catch (error) { runnerError = error; }
|
|
65
|
-
try { await rm(base, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }
|
|
66
|
-
catch (error) { runnerError = runnerError ? new AggregateError([runnerError, error], "Hermetic test run and cleanup failed") : error; }
|
|
67
|
-
if (runnerError) {
|
|
68
|
-
if (process.env.GITHUB_ACTIONS === "true") {
|
|
69
|
-
process.stderr.write(githubErrorCommand("Hermetic test runner", runnerError.stack || runnerError.message) + "\n");
|
|
70
|
-
}
|
|
71
|
-
throw runnerError;
|
|
72
|
-
}
|
|
73
|
-
if (failed) process.exitCode = 1;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Layer 1 — Identity
|
|
2
|
-
|
|
3
|
-
I am <AgentName>, the <role> of <Owner>. <Owner> is my owner; their word is
|
|
4
|
-
final.
|
|
5
|
-
|
|
6
|
-
My purpose: <one sentence — what this agent exists to do>.
|
|
7
|
-
|
|
8
|
-
Principles that no later layer may override:
|
|
9
|
-
|
|
10
|
-
- I am honest before I am impressive. "Not verified yet" beats a wrong "done".
|
|
11
|
-
- I claim only what a real result has shown me.
|
|
12
|
-
- What my owner teaches me about myself becomes part of me (layer 4).
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Layer 3 — Conduct
|
|
2
|
-
|
|
3
|
-
- Measure before acting; re-measure before reporting.
|
|
4
|
-
- A claim carries its evidence: the command, the number, the file.
|
|
5
|
-
- Errors are reported at once, with what I did and what I will do.
|
|
6
|
-
- Escalate to the owner only for decisions that are truly theirs;
|
|
7
|
-
everything reversible I do myself.
|
|
8
|
-
- Tasks, credentials and permissions do not live in this spine.
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const zlib = require('node:zlib');
|
|
5
|
-
|
|
6
|
-
function tarText(buffer, start, length) {
|
|
7
|
-
return buffer.subarray(start, start + length).toString('utf8').replace(/\0.*$/s, '').trim();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function listTarGzPaths(tarballPath) {
|
|
11
|
-
const archive = zlib.gunzipSync(fs.readFileSync(tarballPath));
|
|
12
|
-
const paths = [];
|
|
13
|
-
let offset = 0;
|
|
14
|
-
while (offset + 512 <= archive.length) {
|
|
15
|
-
const header = archive.subarray(offset, offset + 512);
|
|
16
|
-
if (header.every((byte) => byte === 0)) break;
|
|
17
|
-
const name = tarText(header, 0, 100);
|
|
18
|
-
const prefix = tarText(header, 345, 155);
|
|
19
|
-
const sizeText = tarText(header, 124, 12);
|
|
20
|
-
const size = sizeText ? Number.parseInt(sizeText, 8) : 0;
|
|
21
|
-
if (!Number.isFinite(size)) throw new Error(`Ungueltige TAR-Groesse bei ${name || '[ohne Namen]'}`);
|
|
22
|
-
const fullName = [prefix, name].filter(Boolean).join('/').replaceAll('\\', '/');
|
|
23
|
-
if (fullName) paths.push(fullName);
|
|
24
|
-
offset += 512 + Math.ceil(size / 512) * 512;
|
|
25
|
-
}
|
|
26
|
-
return paths;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isStablePackagePath(filePath) {
|
|
30
|
-
const normalized = String(filePath || '').replaceAll('\\', '/');
|
|
31
|
-
if (!normalized || normalized.endsWith('/')) return false;
|
|
32
|
-
if (normalized.startsWith('package/dist-web/assets/')) return false;
|
|
33
|
-
if (normalized.startsWith('package/dist-web/vis/')) return false;
|
|
34
|
-
return normalized.startsWith('package/');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function validatePlannedRemovals(document) {
|
|
38
|
-
const removals = Array.isArray(document?.removals) ? document.removals : [];
|
|
39
|
-
for (const removal of removals) {
|
|
40
|
-
if (!isStablePackagePath(removal?.path)) throw new Error(`Ungueltiger Entfernungspfad: ${removal?.path || '[leer]'}`);
|
|
41
|
-
if (!String(removal?.reason || '').trim()) throw new Error(`Entfernungsgrund fehlt: ${removal.path}`);
|
|
42
|
-
if (!String(removal?.decisionRef || '').trim()) throw new Error(`Entscheidungsreferenz fehlt: ${removal.path}`);
|
|
43
|
-
}
|
|
44
|
-
return new Set(removals.map((removal) => removal.path.replaceAll('\\', '/')));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function findUnexpectedRemovals(previousPaths, currentPaths, plannedDocument = { removals: [] }) {
|
|
48
|
-
const current = new Set(currentPaths.map((entry) => String(entry).replaceAll('\\', '/')));
|
|
49
|
-
const planned = validatePlannedRemovals(plannedDocument);
|
|
50
|
-
return [...new Set(previousPaths
|
|
51
|
-
.map((entry) => String(entry).replaceAll('\\', '/'))
|
|
52
|
-
.filter(isStablePackagePath)
|
|
53
|
-
.filter((entry) => !current.has(entry) && !planned.has(entry)))]
|
|
54
|
-
.sort();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function comparePackagePaths(previousPaths, currentPaths, plannedDocument = { removals: [] }) {
|
|
58
|
-
const previousStablePaths = [...new Set(previousPaths
|
|
59
|
-
.map((entry) => String(entry).replaceAll('\\', '/'))
|
|
60
|
-
.filter(isStablePackagePath))];
|
|
61
|
-
const currentStablePaths = [...new Set(currentPaths
|
|
62
|
-
.map((entry) => String(entry).replaceAll('\\', '/'))
|
|
63
|
-
.filter(isStablePackagePath))];
|
|
64
|
-
return {
|
|
65
|
-
checkedStablePaths: previousStablePaths.length,
|
|
66
|
-
currentStablePaths: currentStablePaths.length,
|
|
67
|
-
unexpectedRemovals: findUnexpectedRemovals(previousStablePaths, currentStablePaths, plannedDocument),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
module.exports = {
|
|
72
|
-
comparePackagePaths,
|
|
73
|
-
findUnexpectedRemovals,
|
|
74
|
-
isStablePackagePath,
|
|
75
|
-
listTarGzPaths,
|
|
76
|
-
validatePlannedRemovals,
|
|
77
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "9.1.393",
|
|
3
|
-
"removals": [
|
|
4
|
-
{
|
|
5
|
-
"path": "package/bin/running-update.cjs",
|
|
6
|
-
"reason": "Background updater removed by owner decision; the visible manual startup picker remains.",
|
|
7
|
-
"decisionRef": "Telegram DM 5597, 5603, 5610, 5614 and 5619, 2026-08-28"
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"path": "package/bin/running-update-preference.cjs",
|
|
11
|
-
"reason": "Background updater preference removed with the background updater; manual startup choices remain.",
|
|
12
|
-
"decisionRef": "Telegram DM 5597, 5603, 5610, 5614 and 5619, 2026-08-28"
|
|
13
|
-
}
|
|
14
|
-
]
|
|
15
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const assert = require('node:assert/strict');
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
|
|
8
|
-
const { installAgentSpineForExistingProfiles } = require('../bin/launcher-runtime');
|
|
9
|
-
|
|
10
|
-
const packageRoot = path.resolve(__dirname, '..');
|
|
11
|
-
const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'blun-active-profile-plugin-'));
|
|
12
|
-
const sharedHome = path.join(temporaryRoot, '.blun');
|
|
13
|
-
const currentProfile = path.join(sharedHome, 'profile', 'current');
|
|
14
|
-
const dormantProfile = path.join(sharedHome, 'profile', 'dormant');
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
fs.mkdirSync(currentProfile, { recursive: true });
|
|
18
|
-
fs.mkdirSync(dormantProfile, { recursive: true });
|
|
19
|
-
|
|
20
|
-
installAgentSpineForExistingProfiles(packageRoot, sharedHome, currentProfile);
|
|
21
|
-
|
|
22
|
-
assert.equal(
|
|
23
|
-
fs.existsSync(path.join(currentProfile, 'plugins', 'installed.json')),
|
|
24
|
-
true,
|
|
25
|
-
'the active profile must receive the managed AgentSpine plugin',
|
|
26
|
-
);
|
|
27
|
-
assert.equal(
|
|
28
|
-
fs.existsSync(path.join(dormantProfile, 'plugins', 'installed.json')),
|
|
29
|
-
false,
|
|
30
|
-
'startup must not rescan or mutate dormant profiles',
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
process.stdout.write('active profile plugin startup regression: PASS\n');
|
|
34
|
-
} finally {
|
|
35
|
-
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
|
36
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const fs = require('node:fs');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
|
|
8
|
-
const packageRoot = path.resolve(__dirname, '..');
|
|
9
|
-
const bundlePath = process.env.BLUN_BUNDLE_UNDER_TEST
|
|
10
|
-
? path.resolve(process.env.BLUN_BUNDLE_UNDER_TEST)
|
|
11
|
-
: path.join(packageRoot, 'blun.mjs');
|
|
12
|
-
const relayPath = process.env.BLUN_APPROVAL_RELAY_UNDER_TEST
|
|
13
|
-
? path.resolve(process.env.BLUN_APPROVAL_RELAY_UNDER_TEST)
|
|
14
|
-
: path.join(packageRoot, 'bin', 'telegram-approval-relay.cjs');
|
|
15
|
-
const pluginRelayPath = process.env.BLUN_PLUGIN_APPROVAL_RELAY_UNDER_TEST
|
|
16
|
-
? path.resolve(process.env.BLUN_PLUGIN_APPROVAL_RELAY_UNDER_TEST)
|
|
17
|
-
: path.join(packageRoot, 'telegram-plugin', 'bin', 'telegram-approval-relay.cjs');
|
|
18
|
-
|
|
19
|
-
const bundle = fs.readFileSync(bundlePath, 'utf8');
|
|
20
|
-
const requestToolApproval = bundle.match(
|
|
21
|
-
/async requestToolApproval\(context, result, policyName\) \{[\s\S]*?\n\t\t\}/,
|
|
22
|
-
);
|
|
23
|
-
assert(requestToolApproval, 'APPROVAL_OBSERVABILITY_REGRESSION: requestToolApproval is missing');
|
|
24
|
-
assert.match(
|
|
25
|
-
requestToolApproval[0],
|
|
26
|
-
/this\.agent\.records\.logRecord\(\{[\s\S]*?type: "permission\.approval_requested"[\s\S]*?toolCallId: id[\s\S]*?toolName: name/,
|
|
27
|
-
'APPROVAL_OBSERVABILITY_REGRESSION: opening an approval is invisible in the wire',
|
|
28
|
-
);
|
|
29
|
-
assert(
|
|
30
|
-
requestToolApproval[0].indexOf('type: "permission.approval_requested"')
|
|
31
|
-
< requestToolApproval[0].indexOf('await this.agent.rpc.requestApproval'),
|
|
32
|
-
'APPROVAL_OBSERVABILITY_REGRESSION: approval wire event is emitted too late',
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const ApprovalProbe = Function(`return class ApprovalProbe {${requestToolApproval[0]}};`)();
|
|
36
|
-
const wireRecords = [];
|
|
37
|
-
const probe = new ApprovalProbe();
|
|
38
|
-
probe.agent = {
|
|
39
|
-
records: { logRecord(record) { wireRecords.push(record); } },
|
|
40
|
-
rpc: { async requestApproval() { return { decision: 'approved' }; } },
|
|
41
|
-
hooks: { fireAndForgetTrigger() {} },
|
|
42
|
-
telemetry: { track() {} },
|
|
43
|
-
};
|
|
44
|
-
probe.recordApprovalResult = () => {};
|
|
45
|
-
probe.permissionPolicyResolutionToPrepare = () => undefined;
|
|
46
|
-
probe.formatApprovalRejectionMessage = () => '';
|
|
47
|
-
|
|
48
|
-
const approvalRun = probe.requestToolApproval({
|
|
49
|
-
signal: new AbortController().signal,
|
|
50
|
-
turnId: 7,
|
|
51
|
-
toolCall: { id: 'tool-7', name: 'Bash' },
|
|
52
|
-
execution: {
|
|
53
|
-
description: 'Run command',
|
|
54
|
-
display: { kind: 'command', command: 'echo ok' },
|
|
55
|
-
},
|
|
56
|
-
args: { command: 'echo ok' },
|
|
57
|
-
}, {}, 'manual-approval');
|
|
58
|
-
assert.equal(wireRecords.length, 1, 'approval request must be written before awaiting a response');
|
|
59
|
-
assert.deepEqual(
|
|
60
|
-
wireRecords[0],
|
|
61
|
-
{
|
|
62
|
-
type: 'permission.approval_requested',
|
|
63
|
-
turnId: 7,
|
|
64
|
-
toolCallId: 'tool-7',
|
|
65
|
-
toolName: 'Bash',
|
|
66
|
-
action: 'Run command',
|
|
67
|
-
display: { kind: 'command', command: 'echo ok' },
|
|
68
|
-
policyName: 'manual-approval',
|
|
69
|
-
},
|
|
70
|
-
'approval wire record lost identifying fields',
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
function checkRelay(modulePath, label) {
|
|
74
|
-
delete require.cache[require.resolve(modulePath)];
|
|
75
|
-
const { resolveTelegramApprovalTarget } = require(modulePath);
|
|
76
|
-
assert.equal(
|
|
77
|
-
resolveTelegramApprovalTarget(['1605241602', '-1003927574737']),
|
|
78
|
-
'1605241602',
|
|
79
|
-
`${label}: one authorized private chat must be selected even when groups are also allowed`,
|
|
80
|
-
);
|
|
81
|
-
assert.equal(
|
|
82
|
-
resolveTelegramApprovalTarget(['0', '-1003927574737']),
|
|
83
|
-
null,
|
|
84
|
-
`${label}: zero is not a positive private Telegram chat ID`,
|
|
85
|
-
);
|
|
86
|
-
assert.equal(
|
|
87
|
-
resolveTelegramApprovalTarget(['1605241602', '8711923962', '-1003927574737']),
|
|
88
|
-
null,
|
|
89
|
-
`${label}: multiple authorized private chats must remain fail-closed`,
|
|
90
|
-
);
|
|
91
|
-
assert.equal(
|
|
92
|
-
resolveTelegramApprovalTarget(['1605241602', '8711923962'], '8711923962'),
|
|
93
|
-
'8711923962',
|
|
94
|
-
`${label}: an explicitly configured authorized chat must win`,
|
|
95
|
-
);
|
|
96
|
-
assert.equal(
|
|
97
|
-
resolveTelegramApprovalTarget(['1605241602'], '-1003927574737'),
|
|
98
|
-
null,
|
|
99
|
-
`${label}: an unauthorized configured chat must be rejected`,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
checkRelay(relayPath, 'package relay');
|
|
104
|
-
checkRelay(pluginRelayPath, 'plugin relay');
|
|
105
|
-
|
|
106
|
-
approvalRun.then(() => {
|
|
107
|
-
process.stdout.write('approval-observability-regression PASS\n');
|
|
108
|
-
}).catch((error) => {
|
|
109
|
-
process.stderr.write(`${error.stack || error.message}\n`);
|
|
110
|
-
process.exitCode = 1;
|
|
111
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert/strict');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
|
|
5
|
-
const bundlePath = process.env.BLUN_BUNDLE_UNDER_TEST
|
|
6
|
-
|| path.join(__dirname, '..', 'blun.mjs');
|
|
7
|
-
const bundle = fs.readFileSync(bundlePath, 'utf8');
|
|
8
|
-
|
|
9
|
-
function between(start, end, from = 0) {
|
|
10
|
-
const startIndex = bundle.indexOf(start, from);
|
|
11
|
-
assert.notEqual(startIndex, -1, `missing bundle marker: ${start}`);
|
|
12
|
-
const endIndex = bundle.indexOf(end, startIndex + start.length);
|
|
13
|
-
assert.notEqual(endIndex, -1, `missing bundle marker: ${end}`);
|
|
14
|
-
return bundle.slice(startIndex, endIndex);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const panelStart = bundle.indexOf('var ApprovalPanelComponent = class extends Container {');
|
|
18
|
-
assert.notEqual(panelStart, -1, 'ApprovalPanelComponent must exist');
|
|
19
|
-
const handleSource = between('\n\thandleInput(data) {', '\n\trender(width) {', panelStart).trim();
|
|
20
|
-
|
|
21
|
-
const Key = {
|
|
22
|
-
escape: 'escape',
|
|
23
|
-
ctrl(key) { return `ctrl+${key}`; },
|
|
24
|
-
};
|
|
25
|
-
const matchesKey = (data, expected) => {
|
|
26
|
-
if (expected === 'escape') return data === '\x1b';
|
|
27
|
-
if (expected === 'ctrl+s') return data === '\x13';
|
|
28
|
-
return data === expected;
|
|
29
|
-
};
|
|
30
|
-
const handleInput = Function(
|
|
31
|
-
'matchesKey',
|
|
32
|
-
'Key',
|
|
33
|
-
`"use strict"; return ({${handleSource}}).handleInput;`,
|
|
34
|
-
)(matchesKey, Key);
|
|
35
|
-
|
|
36
|
-
let responses = 0;
|
|
37
|
-
let flushed = 0;
|
|
38
|
-
let released = 0;
|
|
39
|
-
const panel = {
|
|
40
|
-
choiceCount() { return 0; },
|
|
41
|
-
onResponse(response) {
|
|
42
|
-
responses += 1;
|
|
43
|
-
assert.equal(response.response, 'rejected');
|
|
44
|
-
},
|
|
45
|
-
onCtrlS() { flushed += 1; },
|
|
46
|
-
onEscape() { released += 1; return true; },
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
handleInput.call(panel, '\x13');
|
|
50
|
-
assert.equal(flushed, 1, 'Ctrl+S must reach the global queue flush while approval is open');
|
|
51
|
-
assert.equal(responses, 0);
|
|
52
|
-
|
|
53
|
-
handleInput.call(panel, '\x1b');
|
|
54
|
-
assert.equal(released, 1, 'Escape must first release one queued message while approval is open');
|
|
55
|
-
assert.equal(responses, 0, 'a released queued message must not reject the approval');
|
|
56
|
-
|
|
57
|
-
panel.onEscape = () => false;
|
|
58
|
-
handleInput.call(panel, '\x1b');
|
|
59
|
-
assert.equal(responses, 1, 'Escape must reject the approval when no queued message waits');
|
|
60
|
-
|
|
61
|
-
const showApprovalPanel = between('\n\tshowApprovalPanel(payload) {', '\n\thideApprovalPanel() {');
|
|
62
|
-
assert.match(showApprovalPanel, /this\.flushQueuedMessages\(this\.state\.editor\.getText\(\)\.trim\(\), this\.state\.editor\.inputMode\)/);
|
|
63
|
-
assert.match(showApprovalPanel, /return this\.releaseNextQueuedMessage\(\)/);
|
|
64
|
-
|
|
65
|
-
console.log('approval queue shortcuts regression: PASS');
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert/strict');
|
|
2
|
-
const { execFileSync } = require('node:child_process');
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
|
|
6
|
-
const expectedVersion = '0.11.4';
|
|
7
|
-
const pluginRoot = process.env.BLUN_AGENT_SPINE_ROOT_UNDER_TEST
|
|
8
|
-
? path.resolve(process.env.BLUN_AGENT_SPINE_ROOT_UNDER_TEST)
|
|
9
|
-
: path.resolve(__dirname, '..', 'agent-spine-plugin');
|
|
10
|
-
|
|
11
|
-
function readJson(relativePath) {
|
|
12
|
-
return JSON.parse(fs.readFileSync(path.join(pluginRoot, relativePath), 'utf8'));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
for (const relativePath of [
|
|
16
|
-
'package.json',
|
|
17
|
-
'hooks/version.json',
|
|
18
|
-
'.claude-plugin/plugin.json',
|
|
19
|
-
'.codex-plugin/plugin.json',
|
|
20
|
-
'blun.plugin.json',
|
|
21
|
-
]) {
|
|
22
|
-
assert.equal(
|
|
23
|
-
readJson(relativePath).version,
|
|
24
|
-
expectedVersion,
|
|
25
|
-
`BUNDLED_AGENT_SPINE_REGRESSION: ${relativePath} must declare ${expectedVersion}`,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const runtimeVersion = execFileSync(
|
|
30
|
-
process.execPath,
|
|
31
|
-
[path.join(pluginRoot, 'bin', 'agentspine.js'), '--version'],
|
|
32
|
-
{ encoding: 'utf8' },
|
|
33
|
-
).trim();
|
|
34
|
-
assert.equal(
|
|
35
|
-
runtimeVersion,
|
|
36
|
-
expectedVersion,
|
|
37
|
-
'BUNDLED_AGENT_SPINE_REGRESSION: packaged runtime version is stale',
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
for (const relativePath of ['CONTRIBUTING.md', 'SECURITY.md']) {
|
|
41
|
-
assert.equal(
|
|
42
|
-
fs.existsSync(path.join(pluginRoot, relativePath)),
|
|
43
|
-
true,
|
|
44
|
-
`BUNDLED_AGENT_SPINE_REGRESSION: README link target ${relativePath} is missing`,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(`bundled-agent-spine-regression PASS (${expectedVersion})`);
|