@akira-tl/forgerelay 0.5.5 → 0.5.6

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/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable ForgeRelay changes are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.6] - 2026-08-17
8
+
9
+ ### Fixed
10
+
11
+ - Repaired historical `bash_output_streams` SQLite schemas that had already recorded the Bash audit migration before later audit columns were added, preventing Bash completion from crashing ForgeRelay with `no such column: error` and avoiding ambiguous Host timeouts after commands had already executed.
12
+ - Made the background-completion retention regression wait for the process to actually finish before crossing its retention window, removing a Windows-only timing race from the release suite.
13
+
14
+ ### Changed
15
+
16
+ - The `release-tag-local-ci` Hook now runs the full cloud-equivalent verification surface on an isolated Node 22.19 / npm 10.9.3 checkout before allowing a stable tag push, instead of only checking a previously recorded proof whose parity suite could omit server-level tests.
17
+
7
18
  ## [0.5.5] - 2026-08-15
8
19
 
9
20
  ### Added
@@ -54,6 +54,11 @@ const migrations = [
54
54
  name: "activity-parent-child",
55
55
  up: migrateActivityParentChild,
56
56
  },
57
+ {
58
+ version: 12,
59
+ name: "bash-output-audit-columns",
60
+ up: migrateBashOutputAuditColumns,
61
+ },
57
62
  ];
58
63
  export function migrateDatabase(sqlite) {
59
64
  const migrate = sqlite.transaction(() => {
@@ -329,6 +334,11 @@ function migrateActivityParentChild(sqlite) {
329
334
  on activity_audit_events(parent_activity_id, created_at);
330
335
  `);
331
336
  }
337
+ function migrateBashOutputAuditColumns(sqlite) {
338
+ addColumnIfMissing(sqlite, "bash_output_streams", "error", "text");
339
+ addColumnIfMissing(sqlite, "bash_output_streams", "returned", "integer not null default 0");
340
+ addColumnIfMissing(sqlite, "bash_output_streams", "completion_claimed_at", "text");
341
+ }
332
342
  function addColumnIfMissing(sqlite, table, column, definition) {
333
343
  const columns = sqlite.prepare(`pragma table_info(${table})`).all();
334
344
  if (columns.some((existingColumn) => existingColumn.name === column))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Local development control plane for MCP coding agents.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Akira-TL/forgerelay#readme",
@@ -44,7 +44,7 @@
44
44
  "release:parity": "node scripts/release-parity.mjs",
45
45
  "postinstall": "node scripts/fix-node-pty-permissions.mjs",
46
46
  "start": "node dist/cli.js serve",
47
- "test": "node --test scripts/release-proof.test.mjs && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/operations/edit-preflight.test.ts && tsx src/skills.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/bash-output-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/activity/query-service.test.ts && tsx src/operations/core-operation-executor.test.ts && tsx src/operations/bulk-mutation.test.ts && tsx src/operations/batch/scheduler.test.ts && tsx src/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
47
+ "test": "node --test scripts/release-proof.test.mjs && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/operations/edit-preflight.test.ts && tsx src/skills.test.ts && tsx src/db/migrations.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/bash-output-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/activity/query-service.test.ts && tsx src/operations/core-operation-executor.test.ts && tsx src/operations/bulk-mutation.test.ts && tsx src/operations/batch/scheduler.test.ts && tsx src/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
48
48
  "typecheck": "tsc -p tsconfig.json --noEmit",
49
49
  "release:check": "node scripts/release-version.mjs check",
50
50
  "release:tag-check": "node scripts/release-version.mjs tag",
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync, spawnSync } from "node:child_process";
4
+ import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { dirname, join, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const NODE_VERSION = "22.19.0";
10
+ const NPM_VERSION = "10.9.3";
11
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
12
+ const npx = process.platform === "win32" ? "npx.cmd" : "npx";
13
+
14
+ main();
15
+
16
+ function main() {
17
+ assertCleanReleaseHead();
18
+ const tag = releaseTagFromHookPayload();
19
+ assertTagMatchesReleaseHead(tag);
20
+
21
+ const sandbox = mkdtempSync(join(tmpdir(), "forgerelay-release-hook-ci-"));
22
+ try {
23
+ copyTrackedTree(sandbox);
24
+ const env = {
25
+ ...process.env,
26
+ FORGERELAY_ALLOWED_ROOTS: sandbox,
27
+ FORGERELAY_OAUTH_OWNER_TOKEN: "ci-owner-token-that-is-long-enough",
28
+ FORGERELAY_PUBLIC_BASE_URL: "http://127.0.0.1:7676",
29
+ };
30
+
31
+ runNodeNpm(
32
+ sandbox,
33
+ env,
34
+ ["npm", "ci"],
35
+ `Node ${NODE_VERSION} / npm ${NPM_VERSION} install`,
36
+ );
37
+ runNodeNpm(sandbox, env, ["npm", "run", "release:check"], "Release metadata");
38
+ runNodeNpm(sandbox, env, ["npm", "run", "typecheck"], "Typecheck");
39
+ runNodeNpm(sandbox, env, ["npm", "test"], "Full test suite");
40
+ runNodeNpm(sandbox, env, ["npm", "run", "build"], "Build");
41
+ runNodeNpm(sandbox, env, ["npm", "run", "lsp:interop"], "Optional LSP interoperability");
42
+ runNode(
43
+ sandbox,
44
+ env,
45
+ ["dist/cli.js", "doctor"],
46
+ "Doctor",
47
+ );
48
+
49
+ execFileSync(process.execPath, [join(repoRoot, "scripts", "release-proof.mjs"), "write"], {
50
+ cwd: repoRoot,
51
+ env: process.env,
52
+ stdio: "inherit",
53
+ });
54
+ execFileSync(process.execPath, [join(repoRoot, "scripts", "release-proof.mjs"), "check-hook"], {
55
+ cwd: repoRoot,
56
+ env: process.env,
57
+ stdio: "inherit",
58
+ });
59
+
60
+ console.log(`Release tag local CI passed for ${tag} with cloud-equivalent Node ${NODE_VERSION} checks.`);
61
+ } finally {
62
+ rmSync(sandbox, { recursive: true, force: true, maxRetries: 8, retryDelay: 100 });
63
+ }
64
+ }
65
+
66
+ function git(args) {
67
+ return execFileSync("git", args, {
68
+ cwd: repoRoot,
69
+ encoding: "utf8",
70
+ stdio: ["ignore", "pipe", "pipe"],
71
+ }).trim();
72
+ }
73
+
74
+ function assertCleanReleaseHead() {
75
+ const status = git(["status", "--porcelain", "--untracked-files=all"]);
76
+ if (status) throw new Error("release tag local CI requires a clean working tree");
77
+ }
78
+
79
+ function releaseTagFromHookPayload() {
80
+ let payload;
81
+ try {
82
+ payload = JSON.parse(process.env.FORGERELAY_HOOK_PAYLOAD ?? "{}");
83
+ } catch {
84
+ throw new Error("FORGERELAY_HOOK_PAYLOAD is not valid JSON");
85
+ }
86
+ const command = typeof payload.command === "string" ? payload.command : "";
87
+ const match = /git\s+push\s+origin\s+(v\d+\.\d+\.\d+)/.exec(command);
88
+ if (!match?.[1]) throw new Error("release Hook payload does not contain a stable tag push");
89
+ return match[1];
90
+ }
91
+
92
+ function assertTagMatchesReleaseHead(tag) {
93
+ const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
94
+ const expectedTag = `v${pkg.version}`;
95
+ if (tag !== expectedTag) throw new Error(`tag ${tag} does not match package version ${pkg.version}`);
96
+ const head = git(["rev-parse", "HEAD"]);
97
+ let tagHead;
98
+ try {
99
+ tagHead = git(["rev-parse", `${tag}^{commit}`]);
100
+ } catch {
101
+ throw new Error(`local tag ${tag} does not exist or does not resolve to a commit`);
102
+ }
103
+ if (tagHead !== head) {
104
+ throw new Error(`tag ${tag} points to ${tagHead.slice(0, 12)}, but current HEAD is ${head.slice(0, 12)}`);
105
+ }
106
+ }
107
+
108
+ function copyTrackedTree(sandbox) {
109
+ const files = execFileSync("git", ["ls-files", "-z"], {
110
+ cwd: repoRoot,
111
+ encoding: "utf8",
112
+ }).split("\0").filter(Boolean);
113
+ for (const relativePath of files) {
114
+ const source = join(repoRoot, relativePath);
115
+ const destination = join(sandbox, relativePath);
116
+ mkdirSync(dirname(destination), { recursive: true });
117
+ cpSync(source, destination, { recursive: true, force: true });
118
+ }
119
+ }
120
+
121
+ function runNodeNpm(cwd, env, command, label) {
122
+ run(
123
+ cwd,
124
+ env,
125
+ ["--yes", "-p", `node@${NODE_VERSION}`, "-p", `npm@${NPM_VERSION}`, "--", ...command],
126
+ label,
127
+ );
128
+ }
129
+
130
+ function runNode(cwd, env, args, label) {
131
+ run(cwd, env, ["--yes", `node@${NODE_VERSION}`, ...args], label);
132
+ }
133
+
134
+ function run(cwd, env, args, label) {
135
+ console.log(`\n== ${label} ==`);
136
+ const result = spawnSync(npx, args, {
137
+ cwd,
138
+ env,
139
+ stdio: "inherit",
140
+ windowsHide: true,
141
+ });
142
+ if (result.error) throw result.error;
143
+ if (result.status !== 0) throw new Error(`${label} failed with exit ${result.status ?? "unknown"}`);
144
+ }