@akira-tl/forgerelay 0.8.9 → 0.8.10

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 (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/capabilities/code-intelligence/GUIDE.md +7 -3
  3. package/capabilities/subagents/GUIDE.md +9 -0
  4. package/capabilities/workspace/workspace-tasks/GUIDE.md +6 -0
  5. package/dist/cli/init.js +180 -0
  6. package/dist/cli/setup-support.js +65 -0
  7. package/dist/cli.js +9 -94
  8. package/dist/lsp/code-intelligence.js +17 -3
  9. package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
  10. package/dist/lsp/runtime/managed-language-servers.js +172 -0
  11. package/dist/lsp/runtime/manager.js +37 -2
  12. package/dist/lsp/runtime/process-launch.js +3 -0
  13. package/dist/lsp/test-support/server-fixture.js +5 -2
  14. package/dist/mcp/process/process-platform.js +21 -9
  15. package/dist/mcp/process/process-sessions.js +3 -3
  16. package/dist/mcp/process/tools.js +6 -6
  17. package/dist/mcp/server/core/capability-registry.js +7 -2
  18. package/dist/mcp/server/core/tool-support.js +8 -5
  19. package/dist/mcp/server/operations/runtime/filesystem-tools.js +74 -19
  20. package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
  21. package/dist/mcp/server/operations/runtime/operation-runtime.js +8 -7
  22. package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +28 -8
  23. package/dist/mcp/server/workspace/runtime/workspace-open.js +4 -1
  24. package/dist/mcp/server/workspace/runtime/workspace-tools.js +3 -3
  25. package/dist/mcp/server-instructions.js +1 -1
  26. package/dist/runtime/config/config.js +1 -0
  27. package/dist/runtime/config/user-config.js +1 -10
  28. package/dist/server.js +36 -6
  29. package/dist/workspaces/context.js +35 -0
  30. package/dist/workspaces/relay/transport/remote-transport.js +30 -3
  31. package/dist/workspaces/resources/resource-monitor.js +377 -0
  32. package/dist/workspaces/resources/skills.js +13 -15
  33. package/dist/workspaces/sessions.js +3 -0
  34. package/dist/workspaces.js +11 -0
  35. package/docs/chatgpt-coding-workflow.md +4 -3
  36. package/docs/configuration.md +14 -9
  37. package/package.json +2 -3
  38. package/scripts/release/publish.mjs +12 -9
  39. package/scripts/release/release-gate.test.mjs +3 -1
  40. package/scripts/release/release-version.test.mjs +13 -1
  41. package/scripts/release-parity.mjs +1 -1
  42. package/scripts/release-proof.mjs +15 -0
  43. package/scripts/release-proof.test.mjs +27 -1
  44. package/scripts/release-version.mjs +16 -1
  45. package/skills/subagent-delegation/SKILL.md +0 -132
@@ -224,7 +224,7 @@ use structured process launch and never go through a shell:
224
224
  }
225
225
  ```
226
226
 
227
- Global configuration uses the same definition shape under `languageServers`:
227
+ Global configuration uses the same definition shape under `languageServers`. ForgeRelay can also keep optional npm-managed Language Servers under its private config directory. Agent-triggered installation is disabled by default and must be explicitly enabled:
228
228
 
229
229
  ```json
230
230
  {
@@ -233,10 +233,13 @@ Global configuration uses the same definition shape under `languageServers`:
233
233
  "command": "/absolute/path/to/typescript-language-server",
234
234
  "args": ["--stdio"]
235
235
  }
236
- }
236
+ },
237
+ "allowAgentLanguageServerInstall": false
237
238
  }
238
239
  ```
239
240
 
241
+ `forgerelay init` can manage TypeScript/JavaScript and Pyright installations without touching global npm. When `allowAgentLanguageServerInstall` is `true`, an Agent may use `code.intelligence` operations `managed.status` and `managed.install`; successful installs become discoverable by the same running ForgeRelay process on the next semantic request, with no restart required. Rust Analyzer, `gopls`, and `clangd` remain external toolchain/system installations.
242
+
240
243
  Explicit configuration can set `"enabled": false` to suppress the matching
241
244
  built-in definition. Project values override global values, and both override
242
245
  built-in defaults. ForgeRelay resolves a Language project by walking ancestors of
@@ -246,10 +249,12 @@ not recursively scan the Workspace.
246
249
  Code-intelligence input positions are 1-based line and 1-based Unicode code-point
247
250
  column values. The Workspace filesystem is the only v1 document source of truth.
248
251
 
252
+ Successful `write`, `edit`, `rename`, and Codex `apply_patch` mutations automatically request Language Server diagnostics for affected code files. Non-empty diagnostics are appended to the same mutation response; missing Language Servers are skipped without turning a successful file mutation into a failure.
253
+
249
254
  For contributor/release interoperability checks, run `npm run lsp:interop`. The
250
- command tests each supported real Language server that is already on `PATH` through
251
- ForgeRelay built-in discovery and stdio LSP, reports a clear skip when an executable
252
- is absent, and never installs external dependencies.
255
+ command tests each supported real Language server that is already discoverable through
256
+ ForgeRelay built-in resolution and stdio LSP, reports a clear skip when an executable
257
+ is absent, and never installs external dependencies itself.
253
258
  Definition results may point outside the Workspace and are then marked
254
259
  `external: true`; this is informational only and does not expand allowed roots or
255
260
  file-tool authority.
@@ -605,10 +610,10 @@ forgerelay agents run <profile-or-provider-or-id> "<prompt>"
605
610
  forgerelay agents show <id>
606
611
  ```
607
612
 
608
- 0.3 no longer auto-discovers or seeds the package's historical bundled
609
- `subagent-delegation` Skill for new setups. An existing or user-authored Skill
610
- with that name remains an ordinary Skill and is still discovered from the normal
611
- Skill paths when subagents are enabled; ForgeRelay does not delete or rewrite it.
613
+ ForgeRelay no longer ships or seeds the historical bundled `subagent-delegation` Skill.
614
+ The official delegation rules live in the `subagents` capability guide and MCP
615
+ contract. A user-authored Skill with that name is an ordinary Skill discovered
616
+ from the normal Skill paths; ForgeRelay does not reserve, delete, or rewrite it.
612
617
 
613
618
  ## Logging
614
619
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
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/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.