@akira-tl/forgerelay 0.8.9 → 0.9.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +13 -10
  3. package/capabilities/code-intelligence/GUIDE.md +7 -3
  4. package/capabilities/subagents/GUIDE.md +9 -0
  5. package/capabilities/workspace/workspace-tasks/GUIDE.md +6 -0
  6. package/dist/cli/init.js +180 -0
  7. package/dist/cli/setup-support.js +65 -0
  8. package/dist/cli.js +9 -94
  9. package/dist/lsp/code-intelligence.js +17 -3
  10. package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
  11. package/dist/lsp/runtime/managed-language-servers.js +172 -0
  12. package/dist/lsp/runtime/manager.js +37 -2
  13. package/dist/lsp/runtime/process-launch.js +3 -0
  14. package/dist/lsp/test-support/server-fixture.js +5 -2
  15. package/dist/mcp/process/process-platform.js +21 -9
  16. package/dist/mcp/process/process-sessions.js +3 -3
  17. package/dist/mcp/process/tools.js +6 -6
  18. package/dist/mcp/server/core/capability-registry.js +7 -2
  19. package/dist/mcp/server/core/schemas.js +23 -0
  20. package/dist/mcp/server/core/tool-support.js +8 -5
  21. package/dist/mcp/server/operations/runtime/filesystem-tools.js +74 -19
  22. package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
  23. package/dist/mcp/server/operations/runtime/operation-runtime.js +8 -7
  24. package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +28 -8
  25. package/dist/mcp/server/workspace/runtime/workspace-open.js +4 -1
  26. package/dist/mcp/server/workspace/runtime/workspace-tools.js +3 -3
  27. package/dist/mcp/server-instructions.js +1 -1
  28. package/dist/runtime/config/config.js +1 -0
  29. package/dist/runtime/config/user-config.js +1 -10
  30. package/dist/server.js +36 -6
  31. package/dist/workspaces/context.js +35 -0
  32. package/dist/workspaces/git/worktree-recovery.js +189 -0
  33. package/dist/workspaces/inventory.js +4 -1
  34. package/dist/workspaces/relay/result-support.js +36 -0
  35. package/dist/workspaces/relay/transport/remote-transport.js +30 -3
  36. package/dist/workspaces/relay/workspace-relay.js +4 -1
  37. package/dist/workspaces/resources/resource-monitor.js +377 -0
  38. package/dist/workspaces/resources/skills.js +13 -15
  39. package/dist/workspaces/sessions.js +3 -0
  40. package/dist/workspaces.js +11 -0
  41. package/docs/chatgpt-coding-workflow.md +4 -3
  42. package/docs/configuration.md +33 -21
  43. package/docs/roadmap.md +45 -35
  44. package/package.json +2 -3
  45. package/scripts/release/publish.mjs +12 -9
  46. package/scripts/release/release-gate.test.mjs +3 -1
  47. package/scripts/release/release-version.test.mjs +13 -1
  48. package/scripts/release-parity.mjs +1 -1
  49. package/scripts/release-proof.mjs +15 -0
  50. package/scripts/release-proof.test.mjs +27 -1
  51. package/scripts/release-version.mjs +16 -1
  52. package/skills/subagent-delegation/SKILL.md +0 -132
package/docs/roadmap.md CHANGED
@@ -38,7 +38,7 @@ ForgeRelay does not plan to add:
38
38
  - a plugin marketplace/runtime;
39
39
  - a second conversation/session runtime;
40
40
  - host-owned commands such as plan mode, context inspection, or model selection;
41
- - automatic installation and management of language servers.
41
+ - automatic or unapproved language-server installation. Agent-managed TypeScript/JavaScript and Pyright installs require explicit user opt-in and remain disabled by default; Rust Analyzer, `gopls`, and `clangd` stay external toolchain/system dependencies.
42
42
 
43
43
  Shell execution remains a trusted local-user capability. Workspace filesystem
44
44
  containment must not be described as a shell sandbox.
@@ -197,11 +197,12 @@ capability
197
197
 
198
198
  ## 0.4 — LSP code intelligence v1
199
199
 
200
- LSP is moderate implementation complexity if ForgeRelay does not become a
201
- language-server installer.
202
-
203
- The first version should launch only language servers already available on the
204
- user's machine or explicitly configured by the user/project.
200
+ LSP v1 was designed around language servers already available on the user's machine
201
+ or explicitly configured by the user/project. ForgeRelay later added a narrow managed
202
+ installation path for TypeScript/JavaScript and Pyright: it is disabled by default,
203
+ requires explicit user authorization during setup, installs only into ForgeRelay's
204
+ private config directory, and refreshes live without a server restart. This does not
205
+ turn arbitrary language-server installation into an implicit Agent capability.
205
206
 
206
207
  During 0.4 development, MCP App UI hardening can land alongside the LSP work when
207
208
  it does not distort the code-intelligence scope. In particular, evaluate a more
@@ -245,9 +246,10 @@ and verify publication before work begins on the next boundary:
245
246
  The shipping 0.4 LSP v1 contract keeps the canonical nine Core MCP tools unchanged
246
247
  and exposes semantic operations only through `code.intelligence`. Deterministic
247
248
  fake-LSP coverage remains the primary cross-platform protocol/lifecycle gate, while
248
- `npm run lsp:interop` exercises `typescript-language-server`, Pyright,
249
- `rust-analyzer`, `gopls`, and `clangd` only when those external executables are
250
- already present and otherwise reports explicit skips without installing them.
249
+ `npm run lsp:interop` exercises detected servers only when executables are already
250
+ present and never downloads them. The later managed TypeScript/Pyright installation
251
+ path is a separate explicitly authorized runtime capability and does not change that
252
+ interop-test contract.
251
253
 
252
254
  ## 0.5 — Durable Activity and batch execution
253
255
 
@@ -315,32 +317,40 @@ milestone: the next stage remains blocked until the previous version's tag-trigg
315
317
  release workflow has completed successfully. Runtime acceptance uses only the
316
318
  reserved 7677/7678 debug instances and never touches the normal 7676 installation.
317
319
 
318
- ## Later: first-class subagent MCP
319
-
320
- ForgeRelay already owns provider adapters and resumable local agent sessions. A
321
- future release may remove the current `bash -> forgerelay agents ...` indirection
322
- for MCP hosts.
323
-
324
- First-class subagent operations should reuse the Capability Gateway rather than
325
- add another top-level MCP tool. The parent agent should continue choosing from
326
- available provider/profile metadata while ForgeRelay owns provider-backed worker
327
- lifecycle state. This remains provider-backed delegation, not an attempt to
328
- emulate a Host-native subagent implementation.
329
-
330
- ## Worktree and history refinements
331
-
332
- These can land alongside the main release lines when the underlying interfaces
333
- are ready:
334
-
335
- - `.worktreeinclude`-style explicit copying of selected gitignored files;
336
- - hook-backed worktree close verification;
337
- - stale managed-worktree recovery and cleanup;
338
- - checkpoint/list/restore primitives based on the existing Git snapshot engine;
339
- - retention/GC for stale workspace sessions, conversation bindings, and review
340
- refs.
341
-
342
- Checkpoint restore must protect concurrent/external user edits rather than
343
- blindly overwriting a working tree.
320
+ ## 0.9 Workspace Recovery & History
321
+
322
+ 0.9 builds on persistent Workspace identity, first-class `subagent.session`, and the
323
+ existing Hook-backed managed-worktree finalize lifecycle. It does not add another Core
324
+ MCP tool. Each stage is release-gated: the following stage begins only after the prior
325
+ version's tag-triggered Linux/macOS/Windows verification and publication succeed.
326
+
327
+ - **0.9.0** add bounded read-only managed-worktree recovery observations to
328
+ `open_workspace(action="list"|"inspect")`, distinguishing healthy, recoverable,
329
+ and manual-intervention states without repairing or pruning Git state; align current
330
+ Roadmap/product documentation with already-shipped Subagent, worktree verification,
331
+ and explicitly authorized managed-LSP behavior;
332
+ - **0.9.1** — add Workspace-scoped safe managed-worktree repair and cleanup through
333
+ the Capability Gateway. Recovery must preserve surviving managed-branch work and
334
+ refuse ambiguous ownership rather than guessing;
335
+ - **0.9.2** — add persistent Workspace checkpoints with explicit create/list/inspect/
336
+ delete lifecycle, stored outside normal project history and surviving Workspace
337
+ close/reopen;
338
+ - **0.9.3** — add checkpoint restore with optimistic-concurrency preflight so external
339
+ or user edits made after inspection cannot be silently overwritten. Restore changes
340
+ working-tree content without moving branch HEAD or using `git reset --hard`;
341
+ - **0.9.4** add owner-facing retention inspection and explicitly authorized prune
342
+ through the CLI. Audit retention remains unlimited by default, persistent Workspace
343
+ identity/Tasks are not ordinary GC targets, and destructive global maintenance is
344
+ not exposed as an ordinary Agent MCP Capability.
345
+
346
+ Workspace Relay continues to treat the Execution ForgeRelay as the owner of Git,
347
+ recovery, checkpoint, and retention facts; the Gateway only routes and presents bounded
348
+ results. Development acceptance remains on isolated 7677/7678 instances and never uses
349
+ the normal 7676 installation.
350
+
351
+ Later refinements may add `.worktreeinclude`-style explicit copying of selected
352
+ Git-ignored files. Native PowerShell/`cmd.exe` shell execution is also a separate
353
+ compatibility decision rather than part of the 0.9 recovery/history line.
344
354
 
345
355
  ## Workspace Task Lists
346
356
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.8.9",
3
+ "version": "0.9.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",
@@ -23,7 +23,6 @@
23
23
  "docs/*.md",
24
24
  "examples",
25
25
  "scripts",
26
- "skills",
27
26
  "capabilities",
28
27
  "README.md",
29
28
  "CHANGELOG.md",
@@ -53,7 +52,7 @@
53
52
  "release:push-ready": "node scripts/release/push-ready.mjs",
54
53
  "postinstall": "node scripts/fix-node-pty-permissions.mjs",
55
54
  "start": "node dist/cli.js serve",
56
- "test": "node --test scripts/debug/runtime.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/push-ready.test.mjs scripts/release/release-version.test.mjs && tsx src/mcp/oauth/router.test.ts && tsx src/workspaces/relay/auth/remote-auth-cli.test.ts && tsx src/workspaces/relay/auth/remote-ssh-auth-cli.test.ts && tsx src/workspaces/relay/tests/lifecycle.test.ts && tsx src/workspaces/relay/tests/routing.test.ts && tsx src/workspaces/relay/tests/ssh.test.ts && tsx src/workspaces/relay/tests/process.test.ts && tsx src/runtime/config/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/runtime/logging/logger.test.ts && tsx src/runtime/logging/proxy-trust.test.ts && tsx src/runtime/state/lock/file-lock.test.ts && tsx src/mcp/panel/mcp-app-template.test.ts && tsx src/mcp/hooks/hooks.test.ts && tsx src/mcp/server/core/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/mcp/request-meta.test.ts && tsx src/mcp/artifacts/incoming-artifacts.test.ts && tsx src/mcp/artifacts/artifact-download.test.ts && tsx src/ui/core/card-types.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/activity/detail-card.test.ts && tsx src/ui/review/patch-display.test.ts && tsx src/ui/core/tool-display.test.ts && tsx src/mcp/filesystem/apply-patch.test.ts && tsx src/mcp/process/process-platform.test.ts && tsx src/mcp/process/process-sessions.test.ts && tsx src/mcp/server/transport/mcp-sessions.test.ts && tsx src/mcp/server/transport/server-shutdown.test.ts && tsx src/subagents/providers/adapters/codex.test.ts && tsx src/subagents/providers/adapters/pi.test.ts && tsx src/subagents/providers/registry.test.ts && tsx src/subagents/providers/availability.test.ts && tsx src/subagents/profiles.test.ts && tsx src/subagents/cli-target.test.ts && tsx src/subagents/sessions/store.test.ts && tsx src/subagents/sessions/manager.test.ts && tsx src/subagents/sessions/mcp/capability.server.test.ts && tsx src/subagents/sessions/mcp/continuation.server.test.ts && tsx src/subagents/sessions/mcp/lifecycle.server.test.ts && tsx src/subagents/sessions/mcp/reconciliation.server.test.ts && tsx src/subagents/sessions/mcp/routing.server.test.ts && tsx src/mcp/filesystem/roots.test.ts && tsx src/mcp/filesystem/file-mutations.test.ts && tsx src/mcp/operations/edit-preflight.test.ts && tsx src/workspaces/resources/skills.test.ts && tsx src/runtime/state/db/migrations.test.ts && tsx src/workspaces/state/workspace-store.test.ts && tsx src/workspaces/tasks/workspace-tasks.test.ts && tsx src/workspaces/tasks/workspace-task-reminders.test.ts && tsx src/activity/history/audit-store.test.ts && tsx src/activity/history/bash-output-store.test.ts && tsx src/activity/runtime/lifecycle.test.ts && tsx src/activity/history/query-service.test.ts && tsx src/mcp/operations/core-operation-executor.test.ts && tsx src/mcp/operations/bulk-mutation.test.ts && tsx src/mcp/operations/batch/scheduler.test.ts && tsx src/mcp/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspaces/conversation-checkout.test.ts && tsx src/workspaces/conversation-worktree.test.ts && tsx src/workspaces/review/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/mcp/process/server.test.ts && tsx src/mcp/panel/server.test.ts && tsx src/mcp/server/server.test.ts && tsx src/mcp/oauth/oauth-store.test.ts && tsx src/cli/cli.test.ts",
55
+ "test": "node --test scripts/debug/runtime.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/push-ready.test.mjs scripts/release/release-version.test.mjs && tsx src/mcp/oauth/router.test.ts && tsx src/workspaces/relay/auth/remote-auth-cli.test.ts && tsx src/workspaces/relay/auth/remote-ssh-auth-cli.test.ts && tsx src/workspaces/relay/tests/lifecycle.test.ts && tsx src/workspaces/relay/tests/routing.test.ts && tsx src/workspaces/relay/tests/ssh.test.ts && tsx src/workspaces/relay/tests/process.test.ts && tsx src/runtime/config/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/runtime/managed-language-servers.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/runtime/logging/logger.test.ts && tsx src/runtime/logging/proxy-trust.test.ts && tsx src/runtime/state/lock/file-lock.test.ts && tsx src/mcp/panel/mcp-app-template.test.ts && tsx src/mcp/hooks/hooks.test.ts && tsx src/mcp/server/core/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/mcp/request-meta.test.ts && tsx src/mcp/artifacts/incoming-artifacts.test.ts && tsx src/mcp/artifacts/artifact-download.test.ts && tsx src/ui/core/card-types.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/activity/detail-card.test.ts && tsx src/ui/review/patch-display.test.ts && tsx src/ui/core/tool-display.test.ts && tsx src/mcp/filesystem/apply-patch.test.ts && tsx src/mcp/process/process-platform.test.ts && tsx src/mcp/process/process-sessions.test.ts && tsx src/mcp/server/transport/mcp-sessions.test.ts && tsx src/mcp/server/transport/server-shutdown.test.ts && tsx src/mcp/server/operations/mutation-diagnostics.test.ts && tsx src/subagents/providers/adapters/codex.test.ts && tsx src/subagents/providers/adapters/pi.test.ts && tsx src/subagents/providers/registry.test.ts && tsx src/subagents/providers/availability.test.ts && tsx src/subagents/profiles.test.ts && tsx src/subagents/cli-target.test.ts && tsx src/subagents/sessions/store.test.ts && tsx src/subagents/sessions/manager.test.ts && tsx src/subagents/sessions/mcp/capability.server.test.ts && tsx src/subagents/sessions/mcp/continuation.server.test.ts && tsx src/subagents/sessions/mcp/lifecycle.server.test.ts && tsx src/subagents/sessions/mcp/reconciliation.server.test.ts && tsx src/subagents/sessions/mcp/routing.server.test.ts && tsx src/mcp/filesystem/roots.test.ts && tsx src/mcp/filesystem/file-mutations.test.ts && tsx src/mcp/operations/edit-preflight.test.ts && tsx src/workspaces/resources/skills.test.ts && tsx src/runtime/state/db/migrations.test.ts && tsx src/workspaces/state/workspace-store.test.ts && tsx src/workspaces/tasks/workspace-tasks.test.ts && tsx src/workspaces/tasks/workspace-task-reminders.test.ts && tsx src/activity/history/audit-store.test.ts && tsx src/activity/history/bash-output-store.test.ts && tsx src/activity/runtime/lifecycle.test.ts && tsx src/activity/history/query-service.test.ts && tsx src/mcp/operations/core-operation-executor.test.ts && tsx src/mcp/operations/bulk-mutation.test.ts && tsx src/mcp/operations/batch/scheduler.test.ts && tsx src/mcp/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspaces/conversation-checkout.test.ts && tsx src/workspaces/conversation-worktree.test.ts && tsx src/workspaces/git/worktree-recovery.test.ts && tsx src/mcp/server/workspace/workspace-inventory.test.ts && tsx src/workspaces/review/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/mcp/process/server.test.ts && tsx src/mcp/panel/server.test.ts && tsx src/mcp/server/server.test.ts && tsx src/mcp/oauth/oauth-store.test.ts && tsx src/cli/cli.test.ts",
57
56
  "typecheck": "tsc -p tsconfig.json --noEmit",
58
57
  "release:check": "node scripts/release-version.mjs check",
59
58
  "release:tag-check": "node scripts/release-version.mjs tag",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execFileSync, spawnSync } from "node:child_process";
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
4
+ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
@@ -28,6 +28,7 @@ if (head !== tagHead) {
28
28
  if (!gitSucceeds(["merge-base", "--is-ancestor", tagHead, "origin/main"])) {
29
29
  throw new Error(`${releaseTag} does not point to a commit contained in origin/main`);
30
30
  }
31
+ const notesPath = prepareReleaseNotes(releaseTag);
31
32
 
32
33
  const packages = readdirSync(packageDir).filter((name) => name.endsWith(".tgz"));
33
34
  if (packages.length !== 1) {
@@ -53,7 +54,6 @@ if (npmPackageExists(packageSpec)) {
53
54
  if (ghReleaseExists(releaseTag)) {
54
55
  console.log(`GitHub Release ${releaseTag} already exists; leaving it unchanged.`);
55
56
  } else {
56
- const notesPath = prepareReleaseNotes(releaseTag);
57
57
  const args = [
58
58
  "release",
59
59
  "create",
@@ -128,13 +128,16 @@ function ghReleaseExists(tag) {
128
128
 
129
129
  function prepareReleaseNotes(tag) {
130
130
  const manualNotes = join(repoRoot, "docs", "releases", `${tag}.md`);
131
- const notes = existsSync(manualNotes)
132
- ? readFileSync(manualNotes, "utf8")
133
- : execFileSync(process.execPath, ["scripts/release-version.mjs", "notes", tag], {
134
- cwd: repoRoot,
135
- encoding: "utf8",
136
- stdio: ["ignore", "pipe", "inherit"],
137
- });
131
+ let notes;
132
+ try {
133
+ notes = readFileSync(manualNotes, "utf8");
134
+ } catch (error) {
135
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
136
+ throw new Error(`Missing dedicated release notes: docs/releases/${tag}.md`);
137
+ }
138
+ throw error;
139
+ }
140
+ if (!notes.trim()) throw new Error(`Dedicated release notes are empty: docs/releases/${tag}.md`);
138
141
  const tempRoot = resolve(process.env.RUNNER_TEMP ?? join(repoRoot, ".forgerelay-debug"));
139
142
  mkdirSync(tempRoot, { recursive: true });
140
143
  const notesPath = join(tempRoot, `release-notes-${tag}.md`);
@@ -69,7 +69,9 @@ test("release runtime and local parity share the checked-in Node contract", asyn
69
69
 
70
70
  const source = await readFile(resolve(repoRoot, "scripts/release-parity.mjs"), "utf8");
71
71
  assert.match(source, /readFileSync\(join\(repoRoot, "\.nvmrc"\), "utf8"\)/);
72
- assert.match(source, /const NPM_VERSION = "10\.9\.3"/);
72
+ assert.match(source, /const NPM_VERSION = "11\.19\.1"/);
73
+ const ciWorkflow = await readFile(resolve(repoRoot, ".github/workflows/ci.yml"), "utf8");
74
+ assert.match(ciWorkflow, /npm install --global npm@11\.19\.1/);
73
75
  assert.ok(source.includes('["npm", "ci", "--no-audit", "--no-fund"]'));
74
76
  assert.ok(source.includes('["npm", "run", "ci:verify"]'));
75
77
  assert.ok(source.includes('["npm", "run", "release:pack"]'));
@@ -14,6 +14,7 @@ async function createFixture(t) {
14
14
  const root = await mkdtemp(join(tmpdir(), "forgerelay-release-version-"));
15
15
  t.after(() => rm(root, { recursive: true, force: true }));
16
16
  await mkdir(join(root, "scripts"), { recursive: true });
17
+ await mkdir(join(root, "docs", "releases"), { recursive: true });
17
18
  await cp(sourceScript, join(root, "scripts", "release-version.mjs"));
18
19
 
19
20
  const pkg = {
@@ -88,8 +89,19 @@ test("release version tooling prepares and validates an rc release", async (t) =
88
89
  const checked = await runRelease(root, "check");
89
90
  assert.match(checked.stdout, /release metadata is consistent at 0\.6\.0-rc\.1/);
90
91
 
92
+ await assert.rejects(
93
+ () => runRelease(root, "tag", "v0.6.0-rc.1"),
94
+ (error) => {
95
+ assert.match(String(error.stderr ?? error), /missing dedicated release notes: docs\/releases\/v0\.6\.0-rc\.1\.md/);
96
+ return true;
97
+ },
98
+ );
99
+ await writeFile(
100
+ join(root, "docs", "releases", "v0.6.0-rc.1.md"),
101
+ "# ForgeRelay v0.6.0-rc.1\n\nRelease candidate notes.\n",
102
+ );
91
103
  const tagged = await runRelease(root, "tag", "v0.6.0-rc.1");
92
- assert.match(tagged.stdout, /release tag v0\.6\.0-rc\.1 matches package version and changelog/);
104
+ assert.match(tagged.stdout, /release tag v0\.6\.0-rc\.1 matches package version, changelog, and dedicated release notes/);
93
105
 
94
106
  const notes = await runRelease(root, "notes", "v0.6.0-rc.1");
95
107
  assert.match(notes.stdout, /Release candidate behavior/);
@@ -5,7 +5,7 @@ import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
- const NPM_VERSION = "10.9.3";
8
+ const NPM_VERSION = "11.19.1";
9
9
  const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
10
  const NODE_VERSION = readFileSync(join(repoRoot, ".nvmrc"), "utf8").trim();
11
11
  const debugRoot = join(repoRoot, ".forgerelay-debug");
@@ -29,6 +29,20 @@ function packageVersion() {
29
29
  return pkg.version;
30
30
  }
31
31
 
32
+ function requireDedicatedReleaseNotes(version) {
33
+ const path = join(process.cwd(), "docs", "releases", `v${version}.md`);
34
+ let notes;
35
+ try {
36
+ notes = readFileSync(path, "utf8");
37
+ } catch (error) {
38
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
39
+ throw new Error(`missing dedicated release notes: docs/releases/v${version}.md`);
40
+ }
41
+ throw error;
42
+ }
43
+ if (!notes.trim()) throw new Error(`dedicated release notes are empty: docs/releases/v${version}.md`);
44
+ }
45
+
32
46
  function gitDir() {
33
47
  const path = git(["rev-parse", "--git-dir"]);
34
48
  return path.startsWith("/") ? path : join(process.cwd(), path);
@@ -138,6 +152,7 @@ function checkHookTag() {
138
152
  if (tag !== expectedTag) {
139
153
  throw new Error(`tag ${tag} does not match package version ${version}; expected ${expectedTag}`);
140
154
  }
155
+ requireDedicatedReleaseNotes(version);
141
156
 
142
157
  let tagHead;
143
158
  try {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { execFile } from "node:child_process";
3
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import test from "node:test";
@@ -25,6 +25,8 @@ test("release tag hook gate uses repository facts instead of requiring a local p
25
25
  const root = await mkdtemp(join(tmpdir(), "forgerelay-release-proof-"));
26
26
  t.after(() => rm(root, { recursive: true, force: true }));
27
27
  await writeFile(join(root, "package.json"), JSON.stringify({ version: "1.2.3" }) + "\n");
28
+ await mkdir(join(root, "docs", "releases"), { recursive: true });
29
+ await writeFile(join(root, "docs", "releases", "v1.2.3.md"), "# v1.2.3\n\nDedicated notes.\n");
28
30
  await writeFile(join(root, "tracked.txt"), "verified\n");
29
31
  await git(root, ["init"]);
30
32
  await git(root, ["config", "user.email", "proof@example.com"]);
@@ -102,10 +104,34 @@ test("release tag hook gate uses repository facts instead of requiring a local p
102
104
  );
103
105
  });
104
106
 
107
+ test("release tag hook gate rejects a matching tag when dedicated notes are missing", async (t) => {
108
+ const root = await mkdtemp(join(tmpdir(), "forgerelay-release-proof-"));
109
+ t.after(() => rm(root, { recursive: true, force: true }));
110
+ await writeFile(join(root, "package.json"), JSON.stringify({ version: "1.4.0" }) + "\n");
111
+ await git(root, ["init"]);
112
+ await git(root, ["config", "user.email", "proof@example.com"]);
113
+ await git(root, ["config", "user.name", "Release Proof Test"]);
114
+ await git(root, ["add", "."]);
115
+ await git(root, ["commit", "-m", "release 1.4.0"]);
116
+ await git(root, ["tag", "v1.4.0"]);
117
+
118
+ await assert.rejects(
119
+ () => runProof(root, "check-hook", {
120
+ FORGERELAY_HOOK_PAYLOAD: JSON.stringify({ command: "git push origin v1.4.0" }),
121
+ }),
122
+ (error) => {
123
+ assert.match(String(error.stderr ?? error), /missing dedicated release notes: docs\/releases\/v1\.4\.0\.md/);
124
+ return true;
125
+ },
126
+ );
127
+ });
128
+
105
129
  test("release tag hook gate accepts an rc tag for the package version", async (t) => {
106
130
  const root = await mkdtemp(join(tmpdir(), "forgerelay-release-proof-"));
107
131
  t.after(() => rm(root, { recursive: true, force: true }));
108
132
  await writeFile(join(root, "package.json"), JSON.stringify({ version: "0.6.0-rc.1" }) + "\n");
133
+ await mkdir(join(root, "docs", "releases"), { recursive: true });
134
+ await writeFile(join(root, "docs", "releases", "v0.6.0-rc.1.md"), "# v0.6.0-rc.1\n\nDedicated RC notes.\n");
109
135
  await git(root, ["init"]);
110
136
  await git(root, ["config", "user.email", "proof@example.com"]);
111
137
  await git(root, ["config", "user.name", "Release Proof Test"]);
@@ -35,7 +35,8 @@ switch (command) {
35
35
  if (value !== expectedTag) {
36
36
  fail(`tag ${JSON.stringify(value)} does not match package version; expected ${expectedTag}`);
37
37
  }
38
- console.log(`release tag ${value} matches package version and changelog`);
38
+ await requireReleaseNotes(state.pkg.version);
39
+ console.log(`release tag ${value} matches package version, changelog, and dedicated release notes`);
39
40
  break;
40
41
  }
41
42
  case "prepare": {
@@ -203,6 +204,20 @@ function getUnreleasedBody(changelog) {
203
204
  return changelog.slice(bodyStart, bodyEnd).trim();
204
205
  }
205
206
 
207
+ async function requireReleaseNotes(version) {
208
+ const path = resolve(repoRoot, "docs", "releases", `v${version}.md`);
209
+ let content;
210
+ try {
211
+ content = await readFile(path, "utf8");
212
+ } catch (error) {
213
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
214
+ fail(`missing dedicated release notes: docs/releases/v${version}.md`);
215
+ }
216
+ throw error;
217
+ }
218
+ if (!content.trim()) fail(`dedicated release notes are empty: docs/releases/v${version}.md`);
219
+ }
220
+
206
221
  function getReleaseBody(changelog, version) {
207
222
  const heading = `## [${version}]`;
208
223
  const headingIndex = changelog.indexOf(heading);
@@ -1,132 +0,0 @@
1
- ---
2
- name: subagent-delegation
3
- description: Delegate coding tasks to user-configured ForgeRelay subagents.
4
- ---
5
-
6
- # Subagent Delegation
7
-
8
- Use this skill when the user explicitly asks to delegate work to another coding
9
- agent, use a named subagent, get a second opinion, compare approaches, or run
10
- a subagent-like workflow.
11
-
12
- Do not use subagents silently. Tell the user when another subagent is
13
- being used.
14
-
15
- ## Core commands
16
-
17
- Use only these commands for normal delegation:
18
-
19
- ```bash
20
- forgerelay agents ls
21
- forgerelay agents run <profile-or-provider-or-id> "<prompt>"
22
- forgerelay agents show <id>
23
- ```
24
-
25
- `ls` shows existing subagent sessions for the current workspace. ForgeRelay scopes
26
- it automatically from the shell environment injected by the workspace tool.
27
-
28
- `run <profile> "<prompt>"` starts a new configured profile and prints a
29
- ForgeRelay agent id.
30
-
31
- `run <provider> "<prompt>"` starts a raw built-in provider when no configured
32
- profile is needed. Built-in providers are listed by `open_workspace`.
33
-
34
- `run <id> "<prompt>"` sends a follow-up to an existing agent.
35
-
36
- `show <id>` prints status and the latest response. If the agent is still
37
- running, `show` waits briefly. If there is still no final response, call `show`
38
- again later.
39
-
40
- Do not run provider CLIs such as `codex`, `claude`, `opencode`, `pi`,
41
- `cursor-agent`, or `copilot` directly unless you are explicitly debugging
42
- ForgeRelay agent integration.
43
-
44
- ## Choosing a profile
45
-
46
- Choose profiles from the compact subagent profile catalog returned by
47
- `open_workspace`. Use the profile name with `forgerelay agents run`. If no
48
- profile fits and delegation is still appropriate, use a built-in provider name
49
- from `open_workspace`.
50
-
51
- Profiles may declare a model and optional thinking level. To override the
52
- configured/default provider model or thinking level for a run, pass `--model`
53
- or `--thinking`:
54
-
55
- ```bash
56
- forgerelay agents run <profile-or-provider> --model <model> "<prompt>"
57
- forgerelay agents run <profile-or-provider> --thinking <level> "<prompt>"
58
- ```
59
-
60
- Use `--thinking` only when the user asks for a specific reasoning depth or when
61
- the task clearly needs a different effort than the configured profile default.
62
- Thinking values are provider-specific passthrough values. Use names supported by
63
- the selected local agent harness; ForgeRelay does not translate values between
64
- providers.
65
-
66
- Good delegation targets:
67
-
68
- - `reviewer`: second opinion, bug risk, security risk, test gaps.
69
- - `explorer`: read-only codebase investigation.
70
- - `implementer`: focused implementation when the user asked for delegation.
71
-
72
- Do not delegate ordinary coding work just because a profile exists. Use normal
73
- ForgeRelay tools unless the user asked for delegation, another agent's opinion,
74
- parallel work, or a named subagent.
75
-
76
- ## Worker prompts
77
-
78
- Agents start with only the prompt you send plus their configured profile
79
- instructions. Make prompts self-contained.
80
-
81
- Implementation prompt shape:
82
-
83
- ```text
84
- Goal:
85
- <clear goal>
86
-
87
- Context:
88
- <repo/module/user constraints>
89
-
90
- Relevant files:
91
- <paths and why they matter>
92
-
93
- Acceptance criteria:
94
- - <criterion>
95
-
96
- Rules:
97
- - Keep changes focused.
98
- - Do not perform unrelated refactors.
99
- - Report blockers clearly.
100
- ```
101
-
102
- Read-only investigation prompt shape:
103
-
104
- ```text
105
- Question:
106
- <specific question>
107
-
108
- Scope:
109
- <files/directories/modules to inspect>
110
-
111
- Rules:
112
- - Do not modify files.
113
- - Cite relevant file paths and symbols.
114
- - Separate facts from guesses.
115
- ```
116
-
117
- ## After the worker responds
118
-
119
- Always review the result before presenting it as verified.
120
-
121
- For write-capable tasks, inspect changed files and run or explain relevant
122
- tests. For read-only tasks, verify that important claims are supported by repo
123
- evidence.
124
-
125
- Be transparent in the final response:
126
-
127
- ```text
128
- I used <profile>. It reported <summary>. I verified <checks>. Remaining risk:
129
- <risk or none>.
130
- ```
131
-
132
- Never hide that a subagent was used.