@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.
- package/CHANGELOG.md +20 -0
- package/capabilities/code-intelligence/GUIDE.md +7 -3
- package/capabilities/subagents/GUIDE.md +9 -0
- package/capabilities/workspace/workspace-tasks/GUIDE.md +6 -0
- package/dist/cli/init.js +180 -0
- package/dist/cli/setup-support.js +65 -0
- package/dist/cli.js +9 -94
- package/dist/lsp/code-intelligence.js +17 -3
- package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
- package/dist/lsp/runtime/managed-language-servers.js +172 -0
- package/dist/lsp/runtime/manager.js +37 -2
- package/dist/lsp/runtime/process-launch.js +3 -0
- package/dist/lsp/test-support/server-fixture.js +5 -2
- package/dist/mcp/process/process-platform.js +21 -9
- package/dist/mcp/process/process-sessions.js +3 -3
- package/dist/mcp/process/tools.js +6 -6
- package/dist/mcp/server/core/capability-registry.js +7 -2
- package/dist/mcp/server/core/tool-support.js +8 -5
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +74 -19
- package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +8 -7
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +28 -8
- package/dist/mcp/server/workspace/runtime/workspace-open.js +4 -1
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +3 -3
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/runtime/config/config.js +1 -0
- package/dist/runtime/config/user-config.js +1 -10
- package/dist/server.js +36 -6
- package/dist/workspaces/context.js +35 -0
- package/dist/workspaces/relay/transport/remote-transport.js +30 -3
- package/dist/workspaces/resources/resource-monitor.js +377 -0
- package/dist/workspaces/resources/skills.js +13 -15
- package/dist/workspaces/sessions.js +3 -0
- package/dist/workspaces.js +11 -0
- package/docs/chatgpt-coding-workflow.md +4 -3
- package/docs/configuration.md +14 -9
- package/package.json +2 -3
- package/scripts/release/publish.mjs +12 -9
- package/scripts/release/release-gate.test.mjs +3 -1
- package/scripts/release/release-version.test.mjs +13 -1
- package/scripts/release-parity.mjs +1 -1
- package/scripts/release-proof.mjs +15 -0
- package/scripts/release-proof.test.mjs +27 -1
- package/scripts/release-version.mjs +16 -1
- package/skills/subagent-delegation/SKILL.md +0 -132
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
251
|
-
ForgeRelay built-in
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
with that name
|
|
611
|
-
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 = "
|
|
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
|
|
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 = "
|
|
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
|
-
|
|
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.
|