@akira-tl/forgerelay 0.2.5 → 0.3.0

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.
@@ -9,14 +9,14 @@ checkout.
9
9
  `open_workspace` returns a `workspaceId`. Continue using that ID for later tools
10
10
  in the same directory.
11
11
 
12
- Workspace identity follows the canonical opened directory rather than the
13
- conversation/request identity. Reopening the same checkout reuses the same
14
- active workspace even from another conversation. Conversation metadata is used
15
- only to decide whether bootstrap context such as project instructions should be
16
- repeated to that conversation.
12
+ `workspaceId` is a logical conversation handle, not the physical-directory
13
+ identity. Reopening the same checkout in the same conversation keeps that
14
+ logical ID stable. A different conversation normally receives a different
15
+ `workspaceId` even when it points at the same checkout or worktree; pass an
16
+ existing ID explicitly when the user wants to resume that logical workspace.
17
17
 
18
- A Git worktree directory is a separate workspace identity from its source
19
- checkout.
18
+ A Git worktree directory is a separate physical workspace target from its source
19
+ checkout, and each conversation can still have its own logical handle for it.
20
20
 
21
21
  ## Checkout-first behavior
22
22
 
@@ -108,6 +108,37 @@ being injected eagerly. Read the relevant nested file before working under that
108
108
  `FORGERELAY_AGENT_DIR` is not an instruction source; it remains only a compatibility
109
109
  skill-discovery path.
110
110
 
111
+ ## MCP capability loading
112
+
113
+ ForgeRelay keeps callable MCP tools and explanatory capability documentation
114
+ separate. `tools/list` remains the source of truth for what the current server
115
+ actually exposes; 0.3 does not hide callable tools behind documentation.
116
+
117
+ `open_workspace` adds two lightweight discovery surfaces:
118
+
119
+ - `capabilityFingerprint` is returned on every open/resume and includes the
120
+ ForgeRelay version, active tool mode, and stable semantic capability names;
121
+ - `capabilityGuides` is returned with bootstrap context and contains compact
122
+ descriptors for ForgeRelay-owned, versioned guides that can be loaded with
123
+ the normal `read` tool.
124
+
125
+ Do not preload every capability guide. Read a guide only when the current task
126
+ needs that domain. Built-in guides cover lifecycle Hooks, advanced managed
127
+ worktrees, subagents, artifact/change-review workflows, Host/OAuth/MCP App
128
+ integration, and long-running shell/PTY/process behavior. Optional guides are
129
+ advertised only when their feature is enabled; for example, disabled subagents
130
+ and artifact/change-review features do not add those descriptors to bootstrap
131
+ context. Reopening a workspace in the same Host context does not repeat the
132
+ descriptors, but the previously advertised guides remain valid.
133
+
134
+ The fingerprint is also a stale-Host-schema diagnostic. If `open_workspace`
135
+ reports a capability such as `filesystem.rename-move` but the Host's current
136
+ MCP tool snapshot does not expose `rename`, the server and Host metadata are out
137
+ of sync. Refresh/reconnect the MCP integration or start a Host context that
138
+ reloads `tools/list`; do not conclude that the running ForgeRelay server lacks
139
+ that capability. ForgeRelay can report its own version/capabilities but cannot
140
+ force the Host to discard a cached tool schema.
141
+
111
142
  ## Agent Skills
112
143
 
113
144
  ForgeRelay discovers standard Agent Skills from:
@@ -134,7 +165,11 @@ config directory plus:
134
165
  ```
135
166
 
136
167
  The workspace result exposes only compact profile metadata so the host can
137
- choose a provider/profile without loading full provider launch details.
168
+ choose a provider/profile without loading full provider launch details. Read the
169
+ ForgeRelay-owned `subagents` capability guide when delegation is actually needed;
170
+ 0.3 no longer auto-loads the historical bundled `subagent-delegation` Skill for
171
+ new setups. Existing user-authored or previously seeded Skills remain normal
172
+ user configuration and are not deleted.
138
173
 
139
174
  The current model-facing delegation workflow is:
140
175
 
@@ -168,16 +203,18 @@ The exact lifecycle tools available depend on the active server configuration.
168
203
  In minimal mode, normal shell inspection commands such as `rg`, `find`, and `ls`
169
204
  can be used rather than dedicated MCP search tools. `bash` waits in the foreground
170
205
  for at most 300 seconds. If the command is still running, ForgeRelay returns a
171
- process `sessionId` without killing it. The Agent can use `write_stdin` to poll,
206
+ canonical `processId` without killing it. The Agent can use `write_stdin` to poll,
172
207
  wait again, interact, or explicitly send Ctrl-C, or continue other work; once the
173
208
  command finishes, its completion is attached to a later tool result using the
174
- same workspace ID.
209
+ same workspace ID. The former process `sessionId` remains a deprecated alias in
210
+ 0.2.x for compatibility with existing clients.
175
211
 
176
212
  `FORGERELAY_TOOL_MODE=full` adds dedicated search/directory tools.
177
213
 
178
214
  Experimental `FORGERELAY_TOOL_MODE=codex` provides a smaller Codex-shaped
179
215
  surface including direct `rename`/`delete` path mutations alongside `apply_patch`,
180
- `exec_command`, and `write_stdin`.
216
+ `exec_command`, and `write_stdin`. `rename` is the unified move/rename primitive
217
+ for both files and directories; ForgeRelay does not expose a separate `move` tool.
181
218
 
182
219
  Workspace IDs are logical conversation handles rather than physical-directory
183
220
  identities. The same conversation keeps a stable ID for a project, while another
@@ -192,10 +229,16 @@ be released that way and must be finalized with `close_worktree`.
192
229
  Shell commands are allowed to modify ordinary project files when that is a
193
230
  natural part of the user's requested development task; ForgeRelay does not apply
194
231
  a blanket ban to package managers, generators, formatters, or similar commands
195
- that write files. The Agent contract still prohibits shell mutation of
196
- security- or privilege-sensitive operating-system files and credential material,
197
- and requires an explicit user request before changing configuration files
198
- through `bash` or `exec_command`.
232
+ that write files. They may also perform external device or hardware mutations
233
+ when the user's current request explicitly asks for the actual device-changing
234
+ operation. A check, audit, probe, backup, verification, dry-run, or build-only
235
+ request does not implicitly authorize a later persistent device write, and
236
+ ForgeRelay does not assume a particular flashing protocol or transport.
237
+
238
+ The Agent contract still prohibits shell mutation of security- or
239
+ privilege-sensitive operating-system files and credential material, and requires
240
+ an explicit user request before changing configuration files through `bash` or
241
+ `exec_command`.
199
242
 
200
243
  ## Change review UI
201
244
 
@@ -123,6 +123,35 @@ MCP clients discover metadata from:
123
123
  explicit tool mode is unset. The corresponding legacy `DEVSPACE_*` names are
124
124
  also accepted.
125
125
 
126
+ The selected mode controls the real `tools/list` surface. ForgeRelay does not
127
+ hide callable tools behind capability documentation. In every mode,
128
+ `open_workspace` returns a `capabilityFingerprint` containing the package
129
+ version, tool mode, and stable semantic capability names. The fingerprint also
130
+ reports enabled optional domains such as subagent profile discovery, native
131
+ artifact download, MCP App UI, or aggregate `show_changes` review when those
132
+ features are actually available; it remains a semantic summary rather than a
133
+ copy of `tools/list`.
134
+
135
+ Bootstrap responses also return `capabilityGuides`, which are compact descriptors
136
+ for built-in ForgeRelay documentation that the Agent can explicitly load with
137
+ `read` when a task needs that domain. Current built-in domains cover lifecycle
138
+ Hooks, managed worktrees, subagents, artifact/change-review workflows, Host/OAuth/
139
+ MCP App integration, and long-running shell/PTY/process behavior. Optional-domain
140
+ descriptors such as `subagents` and `artifacts-review` are advertised only when
141
+ the corresponding feature is enabled, so disabled features do not add bootstrap
142
+ context.
143
+
144
+ There is no separate progressive-disclosure configuration switch. Capability
145
+ Guide discovery is built in, while actual tool exposure continues to be
146
+ controlled by `FORGERELAY_TOOL_MODE` and feature-specific settings. If the
147
+ fingerprint reports a capability that is missing from the Host's current tool
148
+ snapshot, treat that as stale Host MCP metadata: reconnect/refresh the integration
149
+ or use a Host context that reloads `tools/list`. The ForgeRelay process cannot
150
+ force a Host to invalidate its cached schema.
151
+
152
+ `rename` is the canonical move/rename primitive for files and directories; there
153
+ is no separate `move` MCP tool.
154
+
126
155
  Codex-mode commands run without a PTY by default. `tty: true` enables interactive
127
156
  programs when the optional `node-pty` dependency is available.
128
157
 
@@ -132,16 +161,17 @@ receives a different ID for the same physical checkout/worktree. Pass
132
161
  `workspaceId` to `open_workspace` to explicitly resume an existing handle in the
133
162
  current conversation. `newWorkspace: true` allocates a new logical handle without
134
163
  creating another checkout or Git worktree and should be used only on explicit user
135
- request. Sessions idle for more than two days are returned in `staleWorkspaces` so
164
+ request. Logical workspaces idle for more than two days are returned in `staleWorkspaces` so
136
165
  the user can choose whether to resume or release them. `close_workspace` removes a
137
166
  logical handle without deleting checkout files; it refuses to remove the last
138
167
  handle anchoring a physical worktree.
139
168
 
140
169
  `bash` has no execution-timeout input. It waits in the foreground for at most 300
141
170
  seconds; if the process is still alive, the result contains `running: true` and a
142
- `sessionId`. `write_stdin` can poll or interact with that session for up to another
143
- 300 seconds per call. ForgeRelay does not kill a process merely because a wait
144
- window expires. Completed background processes are delivered once with a later
171
+ canonical `processId`. `write_stdin` can poll or interact with that process for up
172
+ to another 300 seconds per call. The former `sessionId` field remains a deprecated
173
+ alias during the 0.2.x compatibility window. ForgeRelay does not kill a process
174
+ merely because a wait window expires. Completed background processes are delivered once with a later
145
175
  tool result for the same logical workspace ID.
146
176
 
147
177
  ## Widgets
@@ -315,7 +345,8 @@ When subagents are enabled, profiles are discovered from:
315
345
  - active legacy config directory `~/.devspace/agents/*.md` when reused;
316
346
  - project `.devspace/agents/*.md` for migration compatibility.
317
347
 
318
- The bundled `subagent-delegation` skill teaches the current CLI workflow:
348
+ The ForgeRelay-owned `subagents` capability guide teaches the current CLI
349
+ workflow on demand:
319
350
 
320
351
  ```bash
321
352
  forgerelay agents ls
@@ -323,6 +354,11 @@ forgerelay agents run <profile-or-provider-or-id> "<prompt>"
323
354
  forgerelay agents show <id>
324
355
  ```
325
356
 
357
+ 0.3 no longer auto-discovers or seeds the package's historical bundled
358
+ `subagent-delegation` Skill for new setups. An existing or user-authored Skill
359
+ with that name remains an ordinary Skill and is still discovered from the normal
360
+ Skill paths when subagents are enabled; ForgeRelay does not delete or rewrite it.
361
+
326
362
  ## Logging
327
363
 
328
364
  | Variable | Default |
@@ -333,14 +369,15 @@ forgerelay agents show <id>
333
369
  | `FORGERELAY_LOG_ASSETS` | `0` |
334
370
  | `FORGERELAY_LOG_TOOL_CALLS` | `1` |
335
371
  | `FORGERELAY_LOG_SHELL_COMMANDS` | `1` in `pretty`, `0` in `json` |
336
- | `FORGERELAY_TRUST_PROXY` | `0` |
372
+ | `FORGERELAY_TRUST_PROXY` | auto: `1` only for loopback bind + non-loopback public URL; otherwise `0` |
337
373
 
338
374
  `pretty` is the human-facing local console format. It uses terminal-aware color,
339
375
  short timestamps, workspace-first context, and compact operation results while
340
376
  keeping HTTP request records off by default. Project names receive stable
341
377
  per-project colors and logical `ws_...` identifiers remain visible; transient MCP
342
- transport session IDs and normal session lifecycle events are shown only at
343
- `debug` level. Shell command previews are enabled
378
+ transport session IDs and normal transport lifecycle events are shown only at
379
+ `debug` level, where they are labeled as `transport` rather than workspace/process
380
+ identity. Shell command previews are enabled
344
381
  in this mode and truncated to 120 characters; set
345
382
  `FORGERELAY_LOG_SHELL_COMMANDS=0` when command arguments may contain secrets.
346
383
 
@@ -349,6 +386,14 @@ overridden, JSON mode preserves request logging and omits shell command previews
349
386
  `FORGERELAY_LOG_REQUESTS` and `FORGERELAY_LOG_SHELL_COMMANDS` always override
350
387
  these format-specific defaults when set.
351
388
 
389
+ When ForgeRelay binds to loopback (`127.0.0.1`, `::1`, or `localhost`) but is
390
+ configured with a non-loopback public URL, it automatically trusts exactly one
391
+ upstream proxy hop. This matches the normal tunnel/reverse-proxy topology and
392
+ keeps OAuth rate limiting aligned with Express client-IP resolution. Set
393
+ `FORGERELAY_TRUST_PROXY=0` to disable this inference, or `=1` to enable one-hop
394
+ trust explicitly. ForgeRelay never auto-enables proxy trust when binding to
395
+ `0.0.0.0` or another directly reachable interface.
396
+
352
397
  ## Environment-only example
353
398
 
354
399
  ```bash
package/docs/debugging.md CHANGED
@@ -59,10 +59,10 @@ The acceptance checks:
59
59
  3. unauthenticated `/mcp` rejection;
60
60
  4. dynamic OAuth client registration, PKCE Owner-password approval, and access-token exchange;
61
61
  5. MCP `initialize`, including package/server version consistency and the shell mutation safety contract;
62
- 6. `tools/list` for the full debug tool surface, including `close_workspace`, `write_stdin`, the non-blanket `bash` mutation policy, no kill-timeout input, the 300-second foreground-wait contract, workspace resume/stale-session schema, and MCP App tool metadata;
62
+ 6. `tools/list` for the full debug tool surface, including `close_workspace`, `write_stdin`, canonical `processId` plus the deprecated `sessionId` compatibility alias, the non-blanket `bash` mutation policy, no kill-timeout input, the 300-second foreground-wait contract, workspace resume/stale-workspace schema, and MCP App tool metadata;
63
63
  7. the full MCP App template chain: `resources/list`, `resources/templates/list`, current content-hashed `resources/read`, legacy/historical template compatibility reads, `text/html;profile=mcp-app`, CSP resource domains, and an HTTP fetch of the JavaScript asset referenced by the template;
64
- 8. a real checkout workspace with `write`, `read`, `rename`, `delete`, foreground `bash` through `ProcessSessionManager`, and a deliberate failed `edit`;
65
- 9. OS temp-directory `write` → `read` → `edit` → `rename` → `delete` over the same real MCP session, plus rejection of an arbitrary path outside the workspace/temp roots;
64
+ 8. a real checkout workspace with `write`, `read`, `rename`, `delete`, foreground `bash` through `ProcessManager`, and a deliberate failed `edit`;
65
+ 9. OS temp-directory `write` → `read` → `edit` → `rename` → `delete` over the same real MCP transport session, plus rejection of an arbitrary path outside the workspace/temp roots;
66
66
  10. a temporary Git repository with managed worktree creation, file modification, and `close_worktree`;
67
67
  11. 本地 bare remote 上的 release-tag-push Hook:成功 Hook 必须先运行再允许 `v0.2.0` push,失败 Hook 必须在 remote mutation 前阻断 `v0.2.1`;
68
68
  12. deterministic local subagent error path,不联系任何模型 provider;
package/docs/roadmap.md CHANGED
@@ -99,10 +99,36 @@ Hooks v1 的目标是给用户和 Agent 一个很小、自动、可组合的生
99
99
  - `workspaceId` 是唯一持久的逻辑工作身份,跨请求和 transport 重连保持连续;
100
100
  - `requestId` 只追踪单次 HTTP/JSON-RPC 请求,不持久化;
101
101
  - MCP 协议层 session 在内部和 debug 输出中明确称为 `transportSessionId`,业务状态不得依赖它;
102
- - 后台命令句柄逐步迁移为 `processId` / process handle;若公开 schema 改名需要兼容窗口,则在明确的版本边界完成;
102
+ - 后台命令句柄以 `processId` / process handle canonical 名称,旧 `sessionId` 在 0.2.x 保留 deprecated alias 兼容窗口;
103
103
  - 为 stateless MCP transport 做准备,同时保留旧协议兼容 adapter,避免把 transport 生命周期重新提升成 ForgeRelay 会话模型。
104
104
 
105
- ## 0.3 — LSP code intelligence v1
105
+ ## 0.3 — MCP loading 与渐进式能力披露
106
+
107
+ 0.3 的目标是缩小 MCP 首次加载时注入给 Agent 的上下文,同时保持工具可调用性、安全边界和 Host 编排权不变。ForgeRelay 不再把所有低频能力说明都塞进 server instructions 或工具 description,而是把模型接口拆成三层:
108
+
109
+ - `tools/list` 继续暴露真实可调用 primitive,并只携带调用该工具所必需的简洁语义;
110
+ - `open_workspace` 返回紧凑的 server/capability 摘要与版本指纹,帮助 Agent 发现能力,并识别 Host 持有旧 tool schema snapshot 的情况;
111
+ - ForgeRelay-owned capability guide 提供低频能力的完整说明,由 Agent 在任务相关时显式 `read`,而不是首次连接时自动注入。
112
+
113
+ 首版优先复用现有 Skill-style 的 advertised path / `read` 授权机制,而不是新增第二套文档加载协议。Capability guide 与 Skill 的语义所有权保持区分:Skill 描述用户、项目或生态工作流;capability guide 描述 ForgeRelay 自身、与版本绑定的产品能力。
114
+
115
+ Core Capability Contract 必须始终内联保留至少这些信息:
116
+
117
+ - `workspaceId` 生命周期与 workspace 复用规则;
118
+ - 常用文件读写改、`rename` 同时承担 move/rename、删除的核心语义;
119
+ - shell 以本地用户权限执行且不是 OS sandbox;
120
+ - `processId` / `write_stdin` 的基本长进程语义;
121
+ - Hook 阻断结果必须对 Agent 可见;
122
+ - 关键 mutation/safety invariant;
123
+ - `close_workspace` 与 `close_worktree` 的区别。
124
+
125
+ 适合按需读取的首批领域包括:生命周期 Hooks、managed worktree 高级流程、subagents、artifact/review 工作流、debug/MCP App、OAuth/deployment,以及 shell/PTTY/process 的低频边界情况。首个实现切片迁移 Hooks 与 managed worktree 高级说明;第二切片继续覆盖 subagents、artifact/review、Host/OAuth/MCP App integration 与 shell/PTTY/process,并把历史 bundled `subagent-delegation` Skill 的默认自动发现迁回 ForgeRelay-owned capability guide。必要安全语义始终保留在 core contract 或真实 tool schema/description 中。
126
+
127
+ Capability/version fingerprint 必须是轻量、语义化、稳定的摘要,不复制完整 `tools/list`。当 server 报告的能力与 Host 当前暴露的 tool snapshot 明显不一致时,Agent 应能判断为 Host metadata stale,并建议刷新 MCP 或开启新会话,而不是错误断言 ForgeRelay 缺少能力。
128
+
129
+ 0.3 不隐藏 callable tool,不增加隐式 autonomous workflow,也不把 Host Refresh/session 行为归到 ForgeRelay。`rename` 继续作为文件和目录 move/rename 的统一 primitive。
130
+
131
+ ## 0.4 — LSP code intelligence v1
106
132
 
107
133
  LSP is moderate implementation complexity if ForgeRelay does not become a
108
134
  language-server installer.
@@ -110,7 +136,7 @@ language-server installer.
110
136
  The first version should launch only language servers already available on the
111
137
  user's machine or explicitly configured by the user/project.
112
138
 
113
- During 0.3 development, MCP App UI hardening can land alongside the LSP work when
139
+ During 0.4 development, MCP App UI hardening can land alongside the LSP work when
114
140
  it does not distort the code-intelligence scope. In particular, evaluate a more
115
141
  self-contained template/bootstrap bundle, reduce avoidable external chunk fetches,
116
142
  and keep the current content-hash/compatibility-resource contract intact. This is
@@ -144,7 +170,7 @@ Candidate servers include `typescript-language-server`/tsserver, Pyright,
144
170
  `rust-analyzer`, `gopls`, and `clangd`, but ForgeRelay should treat server
145
171
  commands/configuration as external dependencies.
146
172
 
147
- ## 0.4 — First-class subagent MCP
173
+ ## 0.5 — First-class subagent MCP
148
174
 
149
175
  ForgeRelay already owns provider adapters and resumable local agent sessions.
150
176
  The next step is to remove the current `bash -> forgerelay agents ...` indirection
package/docs/security.md CHANGED
@@ -28,6 +28,17 @@ Filesystem-oriented tools canonicalize existing path segments before access so
28
28
  symlinks inside either the workspace or OS temp directory cannot escape to
29
29
  arbitrary filesystem locations.
30
30
 
31
+ `read` has one narrow additional path class for explicitly advertised documents.
32
+ `open_workspace` may advertise a Skill entry file or a ForgeRelay capability
33
+ Guide outside the normal workspace/temp roots. Only that advertised entry file is
34
+ readable initially; after it is read, files inside that advertised document's
35
+ own directory may be read as supporting material. This does not expand write,
36
+ edit, rename, delete, shell working-directory, or workspace-open roots.
37
+
38
+ Capability Guides are ForgeRelay-owned, versioned package documentation. Their
39
+ paths are surfaced by the running server rather than guessed by the Agent. This
40
+ keeps progressive disclosure from becoming a general arbitrary-file read escape.
41
+
31
42
  ## Owner-password OAuth
32
43
 
33
44
  New installations store local configuration in:
@@ -100,13 +111,19 @@ for dynamically crossing such a boundary.
100
111
  The Agent-facing shell contract therefore does not ban every command that can
101
112
  change files. Commands may modify ordinary project files when that is a natural
102
113
  part of the user's requested development task, including package managers,
103
- generators, formatters, and similar tooling. The contract does prohibit shell
104
- mutation of security- or privilege-sensitive operating-system files and
105
- credential material such as `/etc/sudoers`, `/etc/passwd`, `/etc/shadow`, PAM or
106
- authentication policy, SSH private keys, and equivalent privileged targets.
107
- Configuration files may be changed through shell only when the user's request
108
- explicitly calls for that configuration change rather than merely making it a
109
- convenient implementation detail.
114
+ generators, formatters, and similar tooling. Shell commands may also perform
115
+ external device or hardware mutations when the user's current request explicitly
116
+ asks for the actual device-changing operation. ForgeRelay does not assume a
117
+ particular flashing protocol, transport, device path, or firmware workflow. A
118
+ check, audit, probe, backup, verification, dry-run, or build-only request must not
119
+ be treated as authorization for a later persistent hardware write.
120
+
121
+ The contract does prohibit shell mutation of security- or privilege-sensitive
122
+ operating-system files and credential material such as `/etc/sudoers`,
123
+ `/etc/passwd`, `/etc/shadow`, PAM or authentication policy, SSH private keys, and
124
+ equivalent privileged targets. Configuration files may be changed through shell
125
+ only when the user's request explicitly calls for that configuration change
126
+ rather than merely making it a convenient implementation detail.
110
127
 
111
128
  This is an Agent execution policy, not an OS-level sandbox or command parser.
112
129
  Operators that need a stronger project-specific runtime gate can use blocking
@@ -124,8 +141,9 @@ Do not describe ForgeRelay as a sandboxed coding environment.
124
141
 
125
142
  Shell execution has a 300-second foreground wait ceiling, not a 300-second
126
143
  process lifetime. When `bash` is still running after that window, ForgeRelay
127
- returns a process `sessionId` and leaves the process alive. `write_stdin` can poll,
128
- wait, interact, or explicitly interrupt it. An asynchronously completed process
144
+ returns a canonical `processId` and leaves the process alive. `write_stdin` can
145
+ poll, wait, interact, or explicitly interrupt it. The former process `sessionId`
146
+ remains a deprecated compatibility alias during 0.2.x. An asynchronously completed process
129
147
  is reported on a later tool result for the same logical workspace ID, including
130
148
  error-result paths, and is never broadcast to another workspace ID. Explicitly
131
149
  resuming the same workspace ID in another conversation intentionally transfers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
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",
@@ -24,6 +24,7 @@
24
24
  "examples",
25
25
  "scripts",
26
26
  "skills",
27
+ "capabilities",
27
28
  "README.md",
28
29
  "CHANGELOG.md",
29
30
  "NOTICE.md"
@@ -34,14 +35,14 @@
34
35
  },
35
36
  "scripts": {
36
37
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
37
- "build": "npm run clean && npm run build:app && tsc -p tsconfig.build.json",
38
+ "build": "npm run clean && npm run build:app && tsc -p tsconfig.build.json && node scripts/ensure-cli-executable.mjs",
38
39
  "build:app": "vite build",
39
40
  "dev": "node scripts/debug/serve.mjs",
40
41
  "debug:serve": "node scripts/debug/serve.mjs",
41
42
  "debug:accept": "node scripts/debug/accept.mjs",
42
43
  "postinstall": "node scripts/fix-node-pty-permissions.mjs",
43
44
  "start": "node dist/cli.js serve",
44
- "test": "tsx src/config.test.ts && tsx src/logger.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.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/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
45
+ "test": "tsx src/config.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/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/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
45
46
  "typecheck": "tsc -p tsconfig.json --noEmit",
46
47
  "release:check": "node scripts/release-version.mjs check",
47
48
  "release:tag-check": "node scripts/release-version.mjs tag",
@@ -93,6 +93,9 @@ try {
93
93
  assert.match(serverInstructions, /Shell commands may modify ordinary project files/);
94
94
  assert.match(serverInstructions, /\/etc\/sudoers/);
95
95
  assert.match(serverInstructions, /configuration files through shell only when the user's request explicitly calls for that configuration change/);
96
+ assert.match(serverInstructions, /managed-worktrees capability guide/);
97
+ assert.ok(serverInstructions.length < 3_000, `server instructions should stay compact, got ${serverInstructions.length} characters`);
98
+ assert.doesNotMatch(serverInstructions, /fast-forwards the original target branch/);
96
99
  assert.doesNotMatch(serverInstructions, /Do not create or modify files with bash/);
97
100
  pass(
98
101
  "MCP initialize",
@@ -118,18 +121,30 @@ try {
118
121
  assert.ok(toolNames.includes(expected), `missing debug tool ${expected}`);
119
122
  }
120
123
  const bashTool = tools.find((tool) => tool.name === "bash");
121
- assert.match(bashTool?.description ?? "", /may modify ordinary project files/);
122
- assert.match(bashTool?.description ?? "", /\/etc\/sudoers/);
124
+ assert.match(bashTool?.description ?? "", /local user's authority/);
125
+ assert.doesNotMatch(bashTool?.description ?? "", /may modify ordinary project files/);
126
+ assert.doesNotMatch(bashTool?.description ?? "", /\/etc\/sudoers/);
127
+ assert.doesNotMatch(bashTool?.description ?? "", /external device or hardware mutations/);
123
128
  assert.doesNotMatch(bashTool?.description ?? "", /Do not use bash to create, move, rename, or delete project files/);
124
129
  assert.equal(bashTool?.inputSchema?.properties?.timeout, undefined);
125
130
  assert.match(bashTool?.description ?? "", /waits up to 300 seconds/);
126
131
  assert.match(bashTool?.description ?? "", /write_stdin/);
127
132
  const writeStdinTool = tools.find((tool) => tool.name === "write_stdin");
128
133
  assert.equal(writeStdinTool?.inputSchema?.properties?.yieldTimeMs?.maximum, 300000);
134
+ assert.match(
135
+ writeStdinTool?.inputSchema?.properties?.processId?.description ?? "",
136
+ /Canonical process identifier/,
137
+ );
138
+ assert.match(
139
+ writeStdinTool?.inputSchema?.properties?.sessionId?.description ?? "",
140
+ /Deprecated alias for processId/,
141
+ );
129
142
  const openWorkspaceTool = tools.find((tool) => tool.name === "open_workspace");
130
143
  assert.ok(openWorkspaceTool?.inputSchema?.properties?.workspaceId);
131
144
  assert.ok(openWorkspaceTool?.inputSchema?.properties?.newWorkspace);
132
145
  assert.ok(openWorkspaceTool?.outputSchema?.properties?.staleWorkspaces);
146
+ assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityFingerprint);
147
+ assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityGuides);
133
148
  const templateUri = bashTool?._meta?.ui?.resourceUri;
134
149
  assert.match(
135
150
  templateUri ?? "",
@@ -195,7 +210,35 @@ try {
195
210
  assert.match(workspaceId, /^ws_/);
196
211
  assert.equal(opened.structuredContent.root, checkoutWorkspace);
197
212
  assert.equal(opened.structuredContent.mode, "checkout");
198
- pass("open_workspace", `${workspaceId} -> ${opened.structuredContent.root}`);
213
+ assert.deepEqual(opened.structuredContent.capabilityFingerprint, {
214
+ version: packageJson.version,
215
+ toolMode: "full",
216
+ capabilities: [
217
+ "workspace.close",
218
+ "worktree.managed",
219
+ "filesystem.rename-move",
220
+ "filesystem.delete",
221
+ "process.write-stdin",
222
+ "hooks.lifecycle",
223
+ "capability-guides.read",
224
+ "inspection.search-tools",
225
+ "ui.mcp-app",
226
+ ],
227
+ });
228
+ const capabilityGuides = opened.structuredContent.capabilityGuides;
229
+ assert.deepEqual(capabilityGuides.map((guide) => guide.name), [
230
+ "lifecycle-hooks",
231
+ "managed-worktrees",
232
+ "host-integration",
233
+ "shell-processes",
234
+ ]);
235
+ const hooksGuide = callTool(oauth.accessToken, sessionId, 78, "read", {
236
+ workspaceId,
237
+ path: capabilityGuides[0].path,
238
+ });
239
+ assert.match(hooksGuide.structuredContent.result, /BeforeTool/);
240
+ assert.match(hooksGuide.structuredContent.result, /BeforeWorktreeClose/);
241
+ pass("open_workspace", `${workspaceId} -> fingerprint + ${capabilityGuides.length} capability guides`);
199
242
 
200
243
  const written = callTool(oauth.accessToken, sessionId, 4, "write", {
201
244
  workspaceId,
@@ -217,7 +260,7 @@ try {
217
260
  });
218
261
  assert.match(shell.structuredContent.result, /debug-bash-ok/);
219
262
  assert.equal(shell.structuredContent.running, false);
220
- pass("bash", "foreground command completed through ProcessSessionManager");
263
+ pass("bash", "foreground command completed through ProcessManager");
221
264
 
222
265
  const failedEdit = callTool(oauth.accessToken, sessionId, 7, "edit", {
223
266
  workspaceId,
@@ -0,0 +1,12 @@
1
+ import { chmodSync, statSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ if (process.platform !== "win32") {
5
+ const cliPath = fileURLToPath(new URL("../dist/cli.js", import.meta.url));
6
+ const currentMode = statSync(cliPath).mode;
7
+ chmodSync(cliPath, currentMode | 0o111);
8
+
9
+ if ((statSync(cliPath).mode & 0o111) === 0) {
10
+ throw new Error(`ForgeRelay CLI is not executable after build: ${cliPath}`);
11
+ }
12
+ }