@akira-tl/forgerelay 0.6.0 → 0.6.2

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 (48) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +47 -12
  3. package/dist/activity/mcp-query-tools.js +48 -2
  4. package/dist/activity/query-service.js +1 -0
  5. package/dist/cli.js +181 -4
  6. package/dist/composite-activity.js +155 -0
  7. package/dist/composite-workspaces.js +197 -0
  8. package/dist/config.js +4 -1
  9. package/dist/oauth/router.js +21 -1
  10. package/dist/oauth-provider.js +32 -4
  11. package/dist/oauth-store.js +9 -0
  12. package/dist/remote-auth.js +110 -0
  13. package/dist/remote-transport.js +196 -0
  14. package/dist/remote-workspace-relay.js +473 -0
  15. package/dist/server.js +824 -121
  16. package/dist/ui/.vite/manifest.json +33 -33
  17. package/dist/ui/activity-panel-app.html +3 -3
  18. package/dist/ui/assets/{activity-panel-app-CjZVvVNc.js → activity-panel-app-E1ju2dqI.js} +1 -1
  19. package/dist/ui/assets/{heavy-payload-vGgBRvNX.js → heavy-payload-CeW-n9w5.js} +1 -1
  20. package/dist/ui/assets/{review-payload-4erWKckt.js → review-payload-B9CO298v.js} +1 -1
  21. package/dist/ui/assets/{scrollbar-CaOPzUJd.js → scrollbar-C2twAENW.js} +1 -1
  22. package/dist/ui/assets/workspace-app-BztEvZIC.js +5 -0
  23. package/dist/ui/assets/{workspace-app-DkAiSl_0.js → workspace-app-CwbJnb_w.js} +1 -1
  24. package/dist/ui/assets/workspace-app-YnUST8IP.css +1 -0
  25. package/dist/ui/assets/workspace-app-rKuhdae8.js +1 -0
  26. package/dist/ui/assets/workspace-lifecycle-app-CEfMdudP.js +1 -0
  27. package/dist/ui/workspace-app.html +4 -4
  28. package/dist/ui/workspace-lifecycle-app.html +4 -4
  29. package/dist/user-config.js +131 -5
  30. package/docs/configuration.md +26 -3
  31. package/docs/debugging.md +7 -0
  32. package/docs/versioning.md +11 -19
  33. package/package.json +5 -2
  34. package/scripts/ci/verify.mjs +38 -0
  35. package/scripts/debug/runtime.mjs +23 -1
  36. package/scripts/debug/runtime.test.mjs +14 -2
  37. package/scripts/debug/serve.mjs +4 -4
  38. package/scripts/release/pack.mjs +36 -0
  39. package/scripts/release/publish.mjs +157 -0
  40. package/scripts/release/release-gate.test.mjs +73 -16
  41. package/scripts/release-parity.mjs +5 -13
  42. package/scripts/release-proof.mjs +28 -19
  43. package/scripts/release-proof.test.mjs +32 -11
  44. package/scripts/release-version.mjs +1 -1
  45. package/dist/ui/assets/workspace-app-CcrHAUIn.css +0 -1
  46. package/dist/ui/assets/workspace-app-DJmkPYJC.js +0 -1
  47. package/dist/ui/assets/workspace-app-QyauBrJX.js +0 -5
  48. package/dist/ui/assets/workspace-lifecycle-app-BIXEo53I.js +0 -1
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync, spawnSync } from "node:child_process";
4
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
5
+ import { basename, dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
9
+ const repository = "Akira-TL/forgerelay";
10
+ const releaseTag = requiredEnv("RELEASE_TAG");
11
+ const packageDir = resolve(repoRoot, process.env.RELEASE_PACKAGE_DIR ?? ".release-package");
12
+ const npmCli = process.env.npm_execpath;
13
+ if (!npmCli) {
14
+ throw new Error("release:publish must be launched through npm so npm_execpath is available");
15
+ }
16
+
17
+ const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
18
+ const expectedTag = `v${pkg.version}`;
19
+ if (releaseTag !== expectedTag) {
20
+ throw new Error(`release tag ${releaseTag} does not match package version ${pkg.version}; expected ${expectedTag}`);
21
+ }
22
+
23
+ const head = git(["rev-parse", "HEAD"]);
24
+ const tagHead = git(["rev-parse", `${releaseTag}^{commit}`]);
25
+ if (head !== tagHead) {
26
+ throw new Error(`checked-out HEAD ${head.slice(0, 12)} does not match ${releaseTag} at ${tagHead.slice(0, 12)}`);
27
+ }
28
+ if (!gitSucceeds(["merge-base", "--is-ancestor", tagHead, "origin/main"])) {
29
+ throw new Error(`${releaseTag} does not point to a commit contained in origin/main`);
30
+ }
31
+
32
+ const packages = readdirSync(packageDir).filter((name) => name.endsWith(".tgz"));
33
+ if (packages.length !== 1) {
34
+ throw new Error(`release package directory must contain exactly one .tgz artifact, found ${packages.length}`);
35
+ }
36
+ const packagePath = join(packageDir, packages[0]);
37
+ const expectedPackageName = `${pkg.name.replace(/^@/, "").replaceAll("/", "-")}-${pkg.version}.tgz`;
38
+ if (basename(packagePath) !== expectedPackageName) {
39
+ throw new Error(`unexpected release artifact ${basename(packagePath)}; expected ${expectedPackageName}`);
40
+ }
41
+
42
+ const npmTag = pkg.version.includes("-rc.") ? "next" : "latest";
43
+ const packageSpec = `${pkg.name}@${pkg.version}`;
44
+ if (npmPackageExists(packageSpec)) {
45
+ console.log(`${packageSpec} is already published; leaving npm unchanged.`);
46
+ } else {
47
+ console.log(`Publishing verified artifact ${basename(packagePath)} with npm tag ${npmTag}.`);
48
+ const publishEnv = { ...process.env };
49
+ if (!publishEnv.NODE_AUTH_TOKEN) delete publishEnv.NODE_AUTH_TOKEN;
50
+ runNpm(["publish", packagePath, "--access", "public", "--tag", npmTag], "npm publish", publishEnv);
51
+ }
52
+
53
+ if (ghReleaseExists(releaseTag)) {
54
+ console.log(`GitHub Release ${releaseTag} already exists; leaving it unchanged.`);
55
+ } else {
56
+ const notesPath = prepareReleaseNotes(releaseTag);
57
+ const args = [
58
+ "release",
59
+ "create",
60
+ releaseTag,
61
+ "--repo",
62
+ repository,
63
+ "--verify-tag",
64
+ "--title",
65
+ releaseTag,
66
+ "--notes-file",
67
+ notesPath,
68
+ ];
69
+ if (npmTag === "next") args.push("--prerelease");
70
+ run("gh", args, "GitHub Release");
71
+ }
72
+
73
+ function requiredEnv(name) {
74
+ const value = process.env[name]?.trim();
75
+ if (!value) throw new Error(`${name} is required`);
76
+ return value;
77
+ }
78
+
79
+ function git(args) {
80
+ return execFileSync("git", args, {
81
+ cwd: repoRoot,
82
+ encoding: "utf8",
83
+ stdio: ["ignore", "pipe", "pipe"],
84
+ }).trim();
85
+ }
86
+
87
+ function gitSucceeds(args) {
88
+ const result = spawnSync("git", args, {
89
+ cwd: repoRoot,
90
+ stdio: "ignore",
91
+ windowsHide: true,
92
+ shell: false,
93
+ });
94
+ if (result.error) throw result.error;
95
+ return result.status === 0;
96
+ }
97
+
98
+ function npmPackageExists(spec) {
99
+ const result = spawnSync(process.execPath, [npmCli, "view", spec, "version", "--json"], {
100
+ cwd: repoRoot,
101
+ env: process.env,
102
+ encoding: "utf8",
103
+ windowsHide: true,
104
+ shell: false,
105
+ });
106
+ if (result.error) throw result.error;
107
+ if (result.status === 0) return true;
108
+ const detail = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
109
+ if (/E404|404 Not Found/i.test(detail)) return false;
110
+ throw new Error(`npm view failed while checking ${spec}: ${detail.trim() || `exit ${result.status ?? "unknown"}`}`);
111
+ }
112
+
113
+ function runNpm(args, label, env = process.env) {
114
+ run(process.execPath, [npmCli, ...args], label, env);
115
+ }
116
+
117
+ function ghReleaseExists(tag) {
118
+ const result = spawnSync("gh", ["release", "view", tag, "--repo", repository], {
119
+ cwd: repoRoot,
120
+ env: process.env,
121
+ stdio: "ignore",
122
+ windowsHide: true,
123
+ shell: false,
124
+ });
125
+ if (result.error) throw result.error;
126
+ return result.status === 0;
127
+ }
128
+
129
+ function prepareReleaseNotes(tag) {
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
+ });
138
+ const tempRoot = resolve(process.env.RUNNER_TEMP ?? join(repoRoot, ".forgerelay-debug"));
139
+ mkdirSync(tempRoot, { recursive: true });
140
+ const notesPath = join(tempRoot, `release-notes-${tag}.md`);
141
+ writeFileSync(notesPath, notes.endsWith("\n") ? notes : `${notes}\n`);
142
+ return notesPath;
143
+ }
144
+
145
+ function run(command, args, label, env = process.env) {
146
+ const result = spawnSync(command, args, {
147
+ cwd: repoRoot,
148
+ env,
149
+ stdio: "inherit",
150
+ windowsHide: true,
151
+ shell: false,
152
+ });
153
+ if (result.error) throw result.error;
154
+ if (result.status !== 0) {
155
+ throw new Error(`${label} failed with exit ${result.status ?? "unknown"}`);
156
+ }
157
+ }
@@ -10,14 +10,25 @@ async function readJson(relativePath) {
10
10
  return JSON.parse(await readFile(resolve(repoRoot, relativePath), "utf8"));
11
11
  }
12
12
 
13
- test("release tag Hook is a fast proof gate, not a multi-minute CI runner", async () => {
14
- const hook = await readJson(".forgerelay/hooks/release-tag-local-ci.json");
13
+ test("release tag Hook is a fast repository-state gate for common origin tag push forms", async () => {
14
+ const hook = await readJson(".forgerelay/hooks/release-tag-gate.json");
15
15
  assert.equal(hook.event, "BeforeTool");
16
16
  assert.equal(hook.command, "node scripts/release-proof.mjs check-hook");
17
17
  assert.ok(hook.timeoutSeconds <= 30);
18
+
19
+ const matcher = new RegExp(hook.matcher.commandRegex);
20
+ for (const command of [
21
+ "git push origin v1.2.3",
22
+ "git push --atomic origin v1.2.3",
23
+ "git push origin refs/tags/v1.2.3",
24
+ "git status && git push origin tag v1.2.3 && echo done",
25
+ ]) {
26
+ assert.match(command, matcher);
27
+ }
28
+ assert.doesNotMatch("git push origin main", matcher);
18
29
  });
19
30
 
20
- test("release:verify records proof only after the cloud-equivalent parity gate", async () => {
31
+ test("optional release:verify records proof only after the cloud-equivalent parity gate", async () => {
21
32
  const pkg = await readJson("package.json");
22
33
  assert.equal(
23
34
  pkg.scripts["release:verify"],
@@ -25,19 +36,65 @@ test("release:verify records proof only after the cloud-equivalent parity gate",
25
36
  );
26
37
  });
27
38
 
28
- test("release parity mirrors the cloud CI command surface on Node 22.19 and npm 10.9", async () => {
29
- const source = await readFile(resolve(repoRoot, "scripts/release-parity.mjs"), "utf8");
30
- assert.match(source, /const NODE_VERSION = "22\.19\.0"/);
31
- assert.match(source, /const NPM_VERSION = "10\.9\.3"/);
32
- for (const command of [
33
- '["npm", "ci", "--no-audit", "--no-fund"]',
34
- '["npm", "run", "release:check"]',
35
- '["npm", "run", "typecheck"]',
36
- '["npm", "test"]',
37
- '["npm", "run", "build"]',
38
- '["npm", "run", "lsp:interop"]',
39
- '["dist/cli.js", "doctor"]',
39
+ test("cross-platform cloud CI delegates to one shell-free verification entrypoint", async () => {
40
+ const workflow = await readFile(resolve(repoRoot, ".github/workflows/ci.yml"), "utf8");
41
+ assert.match(workflow, /node-version-file:\s*\.nvmrc/);
42
+ assert.match(workflow, /run:\s*npm ci/);
43
+ assert.match(workflow, /run:\s*npm run ci:verify/);
44
+ assert.doesNotMatch(workflow, /shell:/);
45
+ assert.doesNotMatch(workflow, /run:\s*\|/);
46
+ for (const duplicatedCommand of [
47
+ "npm run release:check",
48
+ "npm run typecheck",
49
+ "npm test",
50
+ "npm run build",
51
+ "npm run lsp:interop",
52
+ "node dist/cli.js doctor",
40
53
  ]) {
41
- assert.ok(source.includes(command), `release parity is missing ${command}`);
54
+ assert.equal(
55
+ workflow.includes(`run: ${duplicatedCommand}`),
56
+ false,
57
+ `cloud CI must delegate ${duplicatedCommand} through ci:verify`,
58
+ );
42
59
  }
43
60
  });
61
+
62
+ test("release runtime and local parity share the checked-in Node contract", async () => {
63
+ const nodeVersion = (await readFile(resolve(repoRoot, ".nvmrc"), "utf8")).trim();
64
+ assert.equal(nodeVersion, "22.19.0");
65
+
66
+ const pkg = await readJson("package.json");
67
+ assert.equal(pkg.scripts["ci:verify"], "node scripts/ci/verify.mjs");
68
+
69
+ const source = await readFile(resolve(repoRoot, "scripts/release-parity.mjs"), "utf8");
70
+ assert.match(source, /readFileSync\(join\(repoRoot, "\.nvmrc"\), "utf8"\)/);
71
+ assert.match(source, /const NPM_VERSION = "10\.9\.3"/);
72
+ assert.ok(source.includes('["npm", "ci", "--no-audit", "--no-fund"]'));
73
+ assert.ok(source.includes('["npm", "run", "ci:verify"]'));
74
+ assert.ok(source.includes('["npm", "run", "release:pack"]'));
75
+ assert.doesNotMatch(source, /\["npm", "run", "typecheck"\]/);
76
+ assert.doesNotMatch(source, /\["npm", "test"\]/);
77
+ });
78
+
79
+ test("cloud verification produces one reusable npm package on Linux", async () => {
80
+ const workflow = await readFile(resolve(repoRoot, ".github/workflows/ci.yml"), "utf8");
81
+ assert.match(workflow, /if:\s*runner\.os == 'Linux'[\s\S]*run:\s*npm run release:pack/);
82
+ assert.match(workflow, /uses:\s*actions\/upload-artifact@v4/);
83
+ assert.match(workflow, /name:\s*npm-package/);
84
+ assert.match(workflow, /include-hidden-files:\s*true/);
85
+ assert.match(workflow, /overwrite:\s*true/);
86
+ });
87
+
88
+ test("release workflow is tag-only and promotes the verified npm artifact without rebuilding", async () => {
89
+ const workflow = await readFile(resolve(repoRoot, ".github/workflows/release.yml"), "utf8");
90
+ assert.doesNotMatch(workflow, /workflow_dispatch:/);
91
+ assert.match(workflow, /needs:\s*verify/);
92
+ assert.match(workflow, /uses:\s*actions\/download-artifact@v5/);
93
+ assert.match(workflow, /name:\s*npm-package/);
94
+ assert.match(workflow, /run:\s*npm run release:publish/);
95
+ assert.match(workflow, /npm install --global npm@11\.19\.1/);
96
+ assert.doesNotMatch(workflow, /run:\s*npm ci/);
97
+ assert.doesNotMatch(workflow, /run:\s*npm run build/);
98
+ assert.doesNotMatch(workflow, /run:\s*\|/);
99
+ assert.doesNotMatch(workflow, /shell:\s*bash/);
100
+ });
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execFileSync, spawnSync } from "node:child_process";
4
- import { cpSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
4
+ 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 NODE_VERSION = "22.19.0";
9
8
  const NPM_VERSION = "10.9.3";
10
9
  const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
+ const NODE_VERSION = readFileSync(join(repoRoot, ".nvmrc"), "utf8").trim();
11
11
  const debugRoot = join(repoRoot, ".forgerelay-debug");
12
12
  const sandbox = mkdtempSync(join(ensureDirectory(debugRoot), "release-parity-node22-"));
13
13
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
@@ -29,15 +29,11 @@ try {
29
29
  ["npm", "ci", "--no-audit", "--no-fund"],
30
30
  `Node ${NODE_VERSION} / npm ${NPM_VERSION} install`,
31
31
  );
32
- runNodeNpm(sandbox, env, ["npm", "run", "release:check"], "Release metadata");
33
- runNodeNpm(sandbox, env, ["npm", "run", "typecheck"], "Typecheck");
34
- runNodeNpm(sandbox, env, ["npm", "test"], "Full test suite");
35
- runNodeNpm(sandbox, env, ["npm", "run", "build"], "Build");
36
- runNodeNpm(sandbox, env, ["npm", "run", "lsp:interop"], "Optional LSP interoperability");
37
- runNode(sandbox, env, ["dist/cli.js", "doctor"], "Doctor");
32
+ runNodeNpm(sandbox, env, ["npm", "run", "ci:verify"], "Cloud verification entrypoint");
33
+ runNodeNpm(sandbox, env, ["npm", "run", "release:pack"], "Cloud release packaging");
38
34
 
39
35
  console.log(
40
- `Release parity passed with the cloud CI command surface on Node ${NODE_VERSION} / npm ${NPM_VERSION}.`,
36
+ `Release parity passed through ci:verify and release:pack on Node ${NODE_VERSION} / npm ${NPM_VERSION}.`,
41
37
  );
42
38
  } finally {
43
39
  rmSync(sandbox, {
@@ -92,10 +88,6 @@ function runNodeNpm(cwd, env, command, label) {
92
88
  );
93
89
  }
94
90
 
95
- function runNode(cwd, env, args, label) {
96
- run(cwd, env, ["--yes", `node@${NODE_VERSION}`, ...args], label);
97
- }
98
-
99
91
  function run(cwd, env, args, label) {
100
92
  console.log(`\n== ${label} ==`);
101
93
  const result = spawnSync(npx, args, {
@@ -42,15 +42,15 @@ function currentHead() {
42
42
  return git(["rev-parse", "HEAD"]);
43
43
  }
44
44
 
45
- function assertReleaseTreeClean() {
45
+ function assertReleaseTreeClean(context) {
46
46
  const status = git(["status", "--porcelain", "--untracked-files=all"]);
47
47
  if (status) {
48
- throw new Error("working tree differs from HEAD or contains untracked files; commit or remove every release input before running release:verify");
48
+ throw new Error(`working tree differs from HEAD or contains untracked files; commit or remove every release input before ${context}`);
49
49
  }
50
50
  }
51
51
 
52
52
  function writeProof() {
53
- assertReleaseTreeClean();
53
+ assertReleaseTreeClean("running release:verify");
54
54
  const proof = {
55
55
  proofVersion: PROOF_VERSION,
56
56
  head: currentHead(),
@@ -90,28 +90,37 @@ function hookTag() {
90
90
  } catch {
91
91
  throw new Error("FORGERELAY_HOOK_PAYLOAD is not valid JSON");
92
92
  }
93
- const command = typeof payload.command === "string" ? payload.command : "";
94
- const match = /git\s+push\s+origin\s+(v\d+\.\d+\.\d+(?:-rc\.\d+)?)/.exec(command);
95
- if (!match?.[1]) {
93
+ const command = typeof payload.originalCommand === "string"
94
+ ? payload.originalCommand
95
+ : typeof payload.command === "string"
96
+ ? payload.command
97
+ : "";
98
+ const pushMatch = /git\s+push\b([^;&|\n]*)/.exec(command);
99
+ if (!pushMatch?.[0] || !/\borigin\b/.test(pushMatch[0])) {
100
+ throw new Error("release Hook payload does not contain an origin release tag push");
101
+ }
102
+ const pushCommand = pushMatch[0];
103
+ if (/(?:^|\s)(?:-f|--force(?:-with-lease)?)(?:=\S*)?(?=$|\s)/.test(pushCommand)
104
+ || /(?:^|\s)\+(?:refs\/tags\/)?v\d+\.\d+\.\d+(?:-rc\.\d+)?(?=$|\s)/.test(pushCommand)) {
105
+ throw new Error("force push is not allowed for release tags");
106
+ }
107
+ if (/(?:^|\s)(?:-d|--delete)(?=$|\s)/.test(pushCommand)) {
108
+ throw new Error("deleting a release tag is not allowed");
109
+ }
110
+ const tagMatch = /(?:^|\s)(?:tag\s+)?(?:refs\/tags\/)?(v\d+\.\d+\.\d+(?:-rc\.\d+)?)(?=$|\s)/.exec(pushCommand);
111
+ if (!tagMatch?.[1]) {
96
112
  throw new Error("release Hook payload does not contain a release tag push");
97
113
  }
98
- return match[1];
114
+ return tagMatch[1];
99
115
  }
100
116
 
101
- function checkHookProof() {
102
- assertReleaseTreeClean();
103
- const proof = readProof();
117
+ function checkHookTag() {
118
+ assertReleaseTreeClean("pushing a release tag");
104
119
  const head = currentHead();
105
120
  const version = packageVersion();
106
121
  const tag = hookTag();
107
122
  const expectedTag = `v${version}`;
108
123
 
109
- if (proof.head !== head) {
110
- throw new Error(`release proof is for ${proof.head.slice(0, 12)}, but current HEAD is ${head.slice(0, 12)}; rerun npm run release:verify`);
111
- }
112
- if (proof.packageVersion !== version) {
113
- throw new Error(`release proof is for package ${proof.packageVersion}, but package.json is ${version}; rerun npm run release:verify`);
114
- }
115
124
  if (tag !== expectedTag) {
116
125
  throw new Error(`tag ${tag} does not match package version ${version}; expected ${expectedTag}`);
117
126
  }
@@ -123,16 +132,16 @@ function checkHookProof() {
123
132
  throw new Error(`local tag ${tag} does not exist or does not resolve to a commit`);
124
133
  }
125
134
  if (tagHead !== head) {
126
- throw new Error(`tag ${tag} points to ${tagHead.slice(0, 12)}, but verified HEAD is ${head.slice(0, 12)}`);
135
+ throw new Error(`tag ${tag} points to ${tagHead.slice(0, 12)}, but current HEAD is ${head.slice(0, 12)}`);
127
136
  }
128
137
 
129
- console.log(`Release proof OK: ${tag} -> ${head.slice(0, 12)} (${proof.verifiedAt}).`);
138
+ console.log(`Release tag gate OK: ${tag} -> ${head.slice(0, 12)}; cloud CI will perform release verification.`);
130
139
  }
131
140
 
132
141
  const action = process.argv[2];
133
142
  try {
134
143
  if (action === "write") writeProof();
135
- else if (action === "check-hook") checkHookProof();
144
+ else if (action === "check-hook") checkHookTag();
136
145
  else throw new Error("usage: node scripts/release-proof.mjs <write|check-hook>");
137
146
  } catch (error) {
138
147
  fail(error instanceof Error ? error.message : String(error));
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { execFile } from "node:child_process";
3
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { 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";
@@ -21,7 +21,7 @@ async function runProof(cwd, action, env = {}) {
21
21
  });
22
22
  }
23
23
 
24
- test("release proof binds a successful local verification to the exact tag HEAD", async (t) => {
24
+ test("release tag hook gate uses repository facts instead of requiring a local proof", async (t) => {
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");
@@ -34,12 +34,37 @@ test("release proof binds a successful local verification to the exact tag HEAD"
34
34
 
35
35
  const written = await runProof(root, "write");
36
36
  assert.match(written.stdout, /Release verification proof recorded/);
37
+ await rm(join(root, ".git", "forgerelay", "release-proof.json"));
37
38
  await git(root, ["tag", "v1.2.3"]);
38
39
 
39
40
  const checked = await runProof(root, "check-hook", {
40
41
  FORGERELAY_HOOK_PAYLOAD: JSON.stringify({ command: "git push origin v1.2.3" }),
41
42
  });
42
- assert.match(checked.stdout, /Release proof OK: v1\.2\.3/);
43
+ assert.match(checked.stdout, /Release tag gate OK: v1\.2\.3/);
44
+
45
+ for (const command of [
46
+ "git push --atomic origin v1.2.3",
47
+ "git push origin refs/tags/v1.2.3",
48
+ "git status && git push origin tag v1.2.3 && echo done",
49
+ ]) {
50
+ const alternative = await runProof(root, "check-hook", {
51
+ FORGERELAY_HOOK_PAYLOAD: JSON.stringify({ command, originalCommand: command }),
52
+ });
53
+ assert.match(alternative.stdout, /Release tag gate OK: v1\.2\.3/);
54
+ }
55
+
56
+ await assert.rejects(
57
+ () => runProof(root, "check-hook", {
58
+ FORGERELAY_HOOK_PAYLOAD: JSON.stringify({
59
+ command: "git push origin v1.2.3",
60
+ originalCommand: "git push --force origin v1.2.3",
61
+ }),
62
+ }),
63
+ (error) => {
64
+ assert.match(String(error.stderr ?? error), /force push is not allowed for release tags/);
65
+ return true;
66
+ },
67
+ );
43
68
 
44
69
  await writeFile(join(root, "tracked.txt"), "changed after verification\n");
45
70
  await assert.rejects(
@@ -71,13 +96,13 @@ test("release proof binds a successful local verification to the exact tag HEAD"
71
96
  FORGERELAY_HOOK_PAYLOAD: JSON.stringify({ command: "git push origin v1.2.3" }),
72
97
  }),
73
98
  (error) => {
74
- assert.match(String(error.stderr ?? error), /release proof is for .* current HEAD is/);
99
+ assert.match(String(error.stderr ?? error), /tag v1\.2\.3 points to .* current HEAD is/);
75
100
  return true;
76
101
  },
77
102
  );
78
103
  });
79
104
 
80
- test("release proof accepts an rc tag for the verified package version", async (t) => {
105
+ test("release tag hook gate accepts an rc tag for the package version", async (t) => {
81
106
  const root = await mkdtemp(join(tmpdir(), "forgerelay-release-proof-"));
82
107
  t.after(() => rm(root, { recursive: true, force: true }));
83
108
  await writeFile(join(root, "package.json"), JSON.stringify({ version: "0.6.0-rc.1" }) + "\n");
@@ -86,16 +111,15 @@ test("release proof accepts an rc tag for the verified package version", async (
86
111
  await git(root, ["config", "user.name", "Release Proof Test"]);
87
112
  await git(root, ["add", "."]);
88
113
  await git(root, ["commit", "-m", "release 0.6.0-rc.1"]);
89
- await runProof(root, "write");
90
114
  await git(root, ["tag", "v0.6.0-rc.1"]);
91
115
 
92
116
  const checked = await runProof(root, "check-hook", {
93
117
  FORGERELAY_HOOK_PAYLOAD: JSON.stringify({ command: "git push origin v0.6.0-rc.1" }),
94
118
  });
95
- assert.match(checked.stdout, /Release proof OK: v0\.6\.0-rc\.1/);
119
+ assert.match(checked.stdout, /Release tag gate OK: v0\.6\.0-rc\.1/);
96
120
  });
97
121
 
98
- test("release proof rejects a mismatched tag without invoking a remote", async (t) => {
122
+ test("release tag hook gate rejects a mismatched tag without invoking a remote", async (t) => {
99
123
  const root = await mkdtemp(join(tmpdir(), "forgerelay-release-proof-"));
100
124
  t.after(() => rm(root, { recursive: true, force: true }));
101
125
  await writeFile(join(root, "package.json"), JSON.stringify({ version: "2.0.0" }) + "\n");
@@ -104,7 +128,6 @@ test("release proof rejects a mismatched tag without invoking a remote", async (
104
128
  await git(root, ["config", "user.name", "Release Proof Test"]);
105
129
  await git(root, ["add", "."]);
106
130
  await git(root, ["commit", "-m", "release 2.0.0"]);
107
- await runProof(root, "write");
108
131
  await git(root, ["tag", "v2.0.0"]);
109
132
 
110
133
  await assert.rejects(
@@ -117,6 +140,4 @@ test("release proof rejects a mismatched tag without invoking a remote", async (
117
140
  },
118
141
  );
119
142
 
120
- const proof = JSON.parse(await readFile(join(root, ".git", "forgerelay", "release-proof.json"), "utf8"));
121
- assert.equal(proof.packageVersion, "2.0.0");
122
143
  });
@@ -171,7 +171,7 @@ async function prepareRelease(state, nextVersion, dryRun) {
171
171
 
172
172
  console.log(`${state.pkg.version} -> ${nextVersion}`);
173
173
  console.log("updated package.json, package-lock.json, and CHANGELOG.md");
174
- console.log("next: review the diff, commit the release-ready tree, run npm run release:verify on that clean HEAD, then push the matching release tag");
174
+ console.log("next: review the diff, commit and push the release-ready tree, then push the matching release tag; cloud CI is the authoritative release verification");
175
175
  }
176
176
 
177
177
  function promoteUnreleased(changelog, nextVersion) {
@@ -1 +0,0 @@
1
- .forgerelay-panel{border:1px solid var(--tool-card-border);background:var(--tool-card-header-bg);width:100%;color:var(--color-text-primary,#f5f5f6);border-radius:12px;overflow:hidden}.workspace-panel{background:var(--tool-card-header-bg);width:100%}.workspace-panel-header{grid-template-columns:22px minmax(0,1fr) auto;align-items:center;gap:10px;min-height:58px;padding:9px 12px;display:grid}.workspace-panel-icon{background:color-mix(in srgb, var(--tool-accent) 9%, transparent);width:22px;height:22px;color:color-mix(in srgb, var(--tool-accent) 72%, var(--color-text-tertiary,#a3a3aa));border-radius:6px;place-items:center;display:grid}.workspace-panel-icon-svg{stroke-width:1.8px;width:14px;height:14px}.workspace-panel-title-group{gap:2px;min-width:0;display:grid}.workspace-panel-title{font-size:var(--font-text-sm-size,14px);font-weight:600;line-height:1.3}.workspace-panel-subtitle,.workspace-panel-mode{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,11px);line-height:1.35}.workspace-panel-subtitle{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.workspace-panel-mode{border:1px solid color-mix(in srgb, var(--tool-card-divider) 76%, transparent);white-space:nowrap;border-radius:999px;padding:2px 7px}.workspace-panel .workspace-details{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg);max-height:none}.workspace-panel-pending-dot{background:var(--color-text-info,#38bdf8);width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in srgb, var(--color-text-info,#38bdf8) 12%, transparent);border-radius:999px;justify-self:center}.activity-panel{border:1px solid var(--tool-card-border);background:var(--tool-card-header-bg);width:100%;color:var(--color-text-primary,#f5f5f6);border-radius:12px;overflow:hidden}.forgerelay-panel .activity-panel{border:0;border-top:1px solid var(--tool-card-divider);border-radius:0}.activity-panel-header{width:100%;min-height:58px;color:inherit;cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:14px minmax(0,1fr) auto 20px;align-items:center;gap:10px;padding:9px 12px;display:grid}.activity-panel-header:hover{background:var(--tool-card-hover-bg)}.activity-panel-header:focus-visible{outline:2px solid color-mix(in srgb, var(--color-text-info,#38bdf8) 72%, transparent);outline-offset:-2px}.activity-panel-header-pending{cursor:default}.activity-panel-header-pending:hover{background:0 0}.activity-panel-pending-spacer{width:20px}.activity-panel-status{background:var(--color-text-info,#38bdf8);width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in srgb, var(--color-text-info,#38bdf8) 12%, transparent);border-radius:9999px;justify-self:center}.activity-panel-status.state-done{background:var(--color-success-text,#6fda83);box-shadow:0 0 0 3px color-mix(in srgb, var(--color-success-text,#6fda83) 12%, transparent)}.activity-panel-status.state-error{background:var(--color-danger-text,#ee7676);box-shadow:0 0 0 3px color-mix(in srgb, var(--color-danger-text,#ee7676) 12%, transparent)}.activity-panel-title-group{gap:2px;min-width:0;display:grid}.activity-panel-title{font-size:var(--font-text-sm-size,14px);font-weight:600;line-height:1.3}.activity-panel-subtitle,.activity-panel-count{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,11px);line-height:1.35}.activity-panel-count{white-space:nowrap}.activity-panel-body{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg);display:grid}.activity-viewport{overscroll-behavior:contain;max-height:420px;overflow:hidden auto}.activity-list{display:grid}.activity-group+.activity-group{border-top:1px solid var(--tool-card-divider)}.activity-group.grouped>.activity-row.parent{background:color-mix(in srgb, var(--color-background-secondary,#272727) 54%, transparent)}.activity-children{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 72%, transparent);display:grid}.activity-row{--activity-accent:var(--color-text-secondary,#b6b6bd);--activity-phase:var(--color-text-tertiary,#a3a3aa);width:100%;min-height:46px;color:inherit;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:28px minmax(0,1fr) auto 16px;align-items:center;gap:10px;padding:7px 12px;display:grid}.activity-row.interactive{cursor:pointer}.activity-row.interactive:hover{background:var(--tool-card-hover-bg)}.activity-row.interactive:focus-visible{outline:2px solid color-mix(in srgb, var(--activity-accent) 68%, transparent);outline-offset:-2px}.activity-row.child{padding-left:34px;position:relative}.activity-row.child:before{background:color-mix(in srgb, var(--activity-accent) 26%, var(--tool-card-divider));content:"";width:1px;position:absolute;top:0;bottom:0;left:20px}.activity-row.child+.activity-row.child{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 52%, transparent)}.activity-row.kind-read{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 32%, #06b6d4 68%)}.activity-row.kind-write{--activity-accent:var(--color-success-text,#6fda83)}.activity-row.kind-edit,.activity-row.kind-rename{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 28%, #d99742 72%)}.activity-row.kind-delete{--activity-accent:var(--color-danger-text,#ee7676)}.activity-row.kind-shell{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 42%, #64748b 58%)}.activity-row.kind-shell-result{--activity-accent:color-mix(in srgb, var(--color-success-text,#6fda83) 72%, var(--color-text-secondary,#b6b6bd))}.activity-row.kind-capability{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 34%, #3b82f6 66%)}.activity-row.kind-batch{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 34%, #8b5cf6 66%)}.activity-row.phase-executing{--activity-phase:var(--color-text-info,#38bdf8)}.activity-row.phase-returned{--activity-phase:var(--color-warning-text,#e6b566)}.activity-row.phase-done{--activity-phase:var(--color-success-text,#6fda83)}.activity-row.phase-error{--activity-phase:var(--color-danger-text,#ee7676)}.activity-row.shell-result{box-shadow:inset 2px 0 0 color-mix(in srgb, var(--activity-accent) 68%, transparent)}.activity-icon{border:1px solid color-mix(in srgb, var(--activity-accent) 18%, transparent);background:color-mix(in srgb, var(--activity-accent) 9%, transparent);width:28px;height:28px;color:var(--activity-accent);border-radius:7px;place-items:center;display:grid}.activity-icon-svg{stroke-width:1.8px;width:15px;height:15px}.activity-main{gap:4px;min-width:0;display:grid}.activity-title-line{align-items:baseline;gap:8px;min-width:0;display:flex}.activity-title{color:var(--color-text-primary,#f5f5f6);font-size:var(--font-text-sm-size,12px);flex:none;font-weight:600;line-height:1.35}.activity-target{min-width:0;color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,11px);text-overflow:ellipsis;white-space:nowrap;line-height:1.4;overflow:hidden}.activity-meta{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);font-variant-numeric:tabular-nums;white-space:nowrap;justify-content:flex-end;align-items:center;gap:8px;display:inline-flex}.activity-phase{color:var(--activity-phase);align-items:center;gap:5px;display:inline-flex}.activity-phase:before{content:"";background:currentColor;border-radius:9999px;width:6px;height:6px}.activity-progress-wrap{grid-template-columns:auto minmax(48px,110px);align-items:center;gap:8px;max-width:220px;display:grid}.activity-progress-counts{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);font-variant-numeric:tabular-nums;white-space:nowrap}.activity-progress-track{background:color-mix(in srgb, var(--activity-accent) 14%, var(--tool-card-divider));border-radius:9999px;height:3px;display:block;overflow:hidden}.activity-progress-fill{border-radius:inherit;background:var(--activity-accent);height:100%;display:block}.activity-empty,.activity-refresh-error{color:var(--color-text-secondary,#b7b7bf);font-size:var(--font-text-sm-size,12px);padding:12px}.activity-refresh-error{border-top:1px solid var(--tool-card-divider);color:var(--color-danger-text,#ee7676)}.activity-detail-chevron,.activity-detail-spacer,.activity-detail-chevron.chevron{width:16px;height:16px}.activity-detail-chevron .icon-svg{width:13px;height:13px}.activity-entry.expanded>.activity-row{background:color-mix(in srgb, var(--activity-accent) 6%, transparent)}.activity-detail{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 72%, transparent);background:color-mix(in srgb, var(--color-background-primary,#101114) 90%, transparent);display:grid}.activity-detail-section+.activity-detail-section{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 62%, transparent)}.activity-detail-label{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);letter-spacing:.02em;padding:8px 12px 0;font-weight:600}.activity-detail-section.error .activity-detail-label,.activity-detail-section.error .activity-detail-value{color:var(--color-danger-text,#ee7676)}.activity-detail-value{max-height:260px;color:var(--color-text-secondary,#c7c7ce);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,11px);white-space:pre-wrap;overflow-wrap:break-word;margin:0;padding:6px 12px 10px;line-height:1.5;overflow:auto}.activity-detail-status{color:var(--color-text-secondary,#b7b7bf);font-size:var(--font-text-sm-size,12px);padding:10px 12px}.activity-detail-status.error{color:var(--color-danger-text,#ee7676)}.activity-terminal{background:var(--color-background-primary,#101114)}.activity-terminal-command,.activity-terminal-output{color:var(--color-text-primary,#f5f5f6);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,11px);white-space:pre-wrap;overflow-wrap:break-word;background:0 0;border:0;border-radius:0;margin:0;line-height:1.55}.activity-terminal-command{border-bottom:1px solid color-mix(in srgb, var(--tool-card-divider) 74%, transparent);color:var(--color-text-secondary,#c7c7ce);padding:9px 12px}.activity-terminal-output{max-height:320px;padding:10px 12px;overflow:auto}.activity-terminal-meta{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 74%, transparent);color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);font-variant-numeric:tabular-nums;padding:7px 12px}.activity-terminal-meta.status-running{color:var(--color-text-info,#38bdf8)}.activity-terminal-meta.status-done{color:var(--color-success-text,#6fda83)}.activity-terminal-meta.status-failed{color:var(--color-danger-text,#ee7676)}@media (width<=520px){.activity-panel-header{grid-template-columns:12px minmax(0,1fr) auto 18px;gap:8px;min-height:54px;padding:8px 10px}.activity-panel-subtitle,.activity-panel-count{font-size:10px}.activity-row{grid-template-columns:26px minmax(0,1fr) 18px;gap:6px 8px;min-height:48px;padding:8px 10px}.activity-row.child{padding-left:28px}.activity-row.child:before{left:16px}.activity-icon{align-self:start;width:26px;height:26px}.activity-title-line{gap:2px;display:grid}.activity-meta{grid-column:2;justify-content:flex-start}.activity-detail-chevron,.activity-detail-spacer{grid-area:1/3/span 2}.activity-progress-wrap{grid-template-columns:auto minmax(40px,1fr);max-width:none}}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;font-family:var(--font-sans,ui-sans-serif, system-ui, sans-serif);color:var(--color-text-primary,#f5f5f6);--tool-card-border:color-mix(in srgb, var(--color-border-primary,#414141) 74%, transparent);--tool-card-header-bg:color-mix(in srgb, var(--color-background-secondary,#272727) 88%, transparent);--tool-card-body-bg:color-mix(in srgb, var(--color-background-primary,#181818) 94%, transparent);--tool-card-hover-bg:color-mix(in srgb, var(--color-background-tertiary,#343434) 46%, transparent);--tool-card-divider:color-mix(in srgb, var(--color-border-primary,#414141) 66%, transparent);--tool-accent:var(--color-text-secondary,#b6b6bd);--scrollbar-thumb:color-mix(in srgb, var(--color-text-tertiary,#8a8a8a) 56%, transparent);--scrollbar-thumb-hover:color-mix(in srgb, var(--color-text-secondary,#a8a8a8) 82%, transparent);background:0 0}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}*{box-sizing:border-box}html,body{background:0 0;margin:0;overflow:hidden}.shell{width:100%;padding:0;overflow:hidden}.empty,.tool-card{--tool-accent-soft:color-mix(in srgb, var(--tool-accent) 12%, transparent);border:1px solid var(--tool-card-border);background:var(--tool-card-header-bg);width:100%;box-shadow:none;color:var(--color-text-primary,#f5f5f6);border-radius:12px;overflow:hidden}.tool-card.workspace,.tool-card.directory{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 34%, #3b82f6 66%)}.tool-card.read,.tool-card.search{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 32%, #06b6d4 68%)}.tool-card.write{--tool-accent:var(--color-success-text,#6fda83)}.tool-card.edit,.tool-card.review{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 28%, #d99742 72%)}.tool-card.delete{--tool-accent:var(--color-danger-text,#ee7676)}.tool-card.shell{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 42%, #64748b 58%)}.tool-card.state-success{--tool-accent:var(--color-success-text,#6fda83)}.tool-card.state-error{--tool-accent:var(--color-danger-text,#ee7676)}.tool-card.state-running{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 30%, #38bdf8 70%)}@supports selector(::-webkit-scrollbar){.pretty-scrollbar::-webkit-scrollbar{width:12px;height:12px}.pretty-scrollbar::-webkit-scrollbar-button{width:0;height:0;display:none}.pretty-scrollbar::-webkit-scrollbar-track{background:0 0}.pretty-scrollbar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);background-clip:content-box;border:4px solid #0000;border-radius:9999px}.pretty-scrollbar::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover)}.pretty-scrollbar::-webkit-scrollbar-thumb:active{background-color:var(--scrollbar-thumb-hover)}.pretty-scrollbar::-webkit-scrollbar-corner{background:0 0}}.empty{color:var(--color-text-secondary,#b6b6bd);font-size:var(--font-text-sm-size,13px);padding:14px 16px}.tool-header{width:100%;min-height:64px;color:inherit;cursor:pointer;text-align:left;background:0 0;border:0;border-radius:11px;grid-template-columns:40px minmax(0,1fr) auto 20px;align-items:center;gap:12px;padding:10px 12px;display:grid}.tool-header:focus-visible,.review-diff-file-header:focus-visible,.review-more:focus-visible{outline:2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);outline-offset:-2px}.tool-header:hover:not(:disabled){background:var(--tool-card-hover-bg)}.tool-header:disabled{cursor:default}.tool-icon{border:1px solid color-mix(in srgb, var(--tool-accent) 18%, transparent);background:var(--tool-accent-soft);width:40px;height:40px;color:var(--tool-accent);border-radius:10px;place-items:center;display:grid}.icon-svg{stroke-width:1.8px;width:20px;height:20px;display:block}.tool-main{gap:2px;min-width:0;display:grid}.tool-title{color:var(--color-text-primary,#f5f5f6);font-size:var(--font-text-sm-size,14px);font-weight:550;line-height:1.3}.tool-label{color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,12px);text-overflow:ellipsis;white-space:nowrap;line-height:1.4;overflow:hidden}.stats{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,12px);font-variant-numeric:tabular-nums;white-space:nowrap;align-items:center;gap:5px;display:inline-flex}.header-meta{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-sm-size,12px);text-align:right;white-space:nowrap;line-height:1.35}.header-meta.empty{width:0}.add{color:var(--color-success-text,#6fda83)}.remove{color:var(--color-danger-text,#ee7676)}.chevron{width:20px;height:20px;color:var(--color-text-tertiary,#a3a3aa);border-radius:7px;place-items:center;transition:background .14s,color .14s,transform .14s;display:grid}.tool-header:hover:not(:disabled) .chevron{color:var(--color-text-primary,#f5f5f6)}.chevron .icon-svg{width:15px;height:15px}.chevron.expanded{transform:rotate(180deg)}.chevron.loading{transform:none}.chevron.loading .icon-svg{fill:none;stroke-linecap:round;stroke-dasharray:38 14;animation:.7s linear infinite payload-spinner}@keyframes payload-spinner{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.chevron.loading .icon-svg{animation:none}}.tool-body{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg)}.workspace-details{max-height:420px;display:grid;overflow:auto}.workspace-rows{padding:4px 0;display:grid}.workspace-row{grid-template-columns:22px minmax(116px,.24fr) minmax(0,1fr);align-items:center;gap:10px;min-height:40px;padding:7px 12px;display:grid}.workspace-row-icon{background:color-mix(in srgb, var(--tool-accent) 9%, transparent);width:22px;height:22px;color:color-mix(in srgb, var(--tool-accent) 72%, var(--color-text-tertiary,#a3a3aa));border-radius:6px;place-items:center;display:grid}.workspace-row-icon-svg{stroke-width:1.8px;width:14px;height:14px}.workspace-key{min-height:22px;color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-sm-size,12px);align-items:center;font-weight:500;display:flex}.workspace-value{min-width:0;color:var(--color-text-secondary,#c7c7ce);font-size:var(--font-text-sm-size,12px);text-overflow:ellipsis;white-space:nowrap;line-height:1.45;overflow:hidden}.workspace-value.mono{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace)}.workspace-base-value{align-items:center;gap:7px;min-width:0;display:flex}.workspace-base-value .workspace-value{flex:0 auto}.workspace-base-warning{width:18px;height:18px;color:var(--color-warning-text,#e6b566);cursor:help;flex:none;place-items:center;display:grid}.workspace-base-warning-svg{stroke-width:2px;width:14px;height:14px}.workspace-chip-list{flex-wrap:nowrap;align-items:center;gap:6px;min-width:0;display:flex;overflow:hidden}.workspace-chip{border:1px solid color-mix(in srgb, var(--tool-accent) 16%, var(--tool-card-divider));background:color-mix(in srgb, var(--tool-accent) 7%, transparent);max-width:100%;min-height:24px;color:var(--color-text-secondary,#c7c7ce);text-overflow:ellipsis;white-space:nowrap;border-radius:9999px;flex:none;align-items:center;gap:5px;padding:3px 8px;font-size:11px;line-height:1.25;display:inline-flex;overflow:hidden}.workspace-chip-logo{object-fit:contain;flex:none;width:13px;height:13px;display:block}.workspace-chip-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.workspace-agent-profile{border:0;border-bottom:1px solid color-mix(in srgb, var(--tool-accent) 34%, var(--tool-card-divider));max-width:100%;min-height:24px;color:var(--color-text-secondary,#c7c7ce);white-space:nowrap;background:0 0;border-radius:0;align-items:center;gap:5px;padding:3px 2px 4px;font-size:11px;line-height:1.25;display:inline-flex;overflow:hidden}.workspace-agent-profile-logo{object-fit:contain;flex:none;width:14px;height:14px;display:block}.workspace-agent-profile:hover{border-bottom-color:var(--tool-accent);color:var(--color-text-primary,#f5f5f6)}.workspace-agent-profile.muted{color:var(--color-text-tertiary,#a3a3aa);opacity:.72;border-bottom-style:dashed}.workspace-provider-logo{cursor:help;flex:none;place-items:center;width:20px;height:24px;display:inline-grid}.workspace-provider-logo-image{object-fit:contain;width:16px;height:16px;display:block}.workspace-provider-logo.muted{opacity:.62}.workspace-chip.muted{color:var(--color-text-tertiary,#a3a3aa);opacity:.72;border-style:dashed}.workspace-skills-row,.workspace-instructions-row,.workspace-agents-row{align-items:start}.workspace-skills-list,.workspace-agents-list{flex-wrap:wrap;overflow:visible}.workspace-instruction-status{border-radius:5px;flex:none;place-items:center;width:18px;height:18px;display:grid}.workspace-instruction-status.loaded{background:color-mix(in srgb, var(--color-success-text,#6fda83) 12%, transparent);color:var(--color-success-text,#6fda83)}.workspace-instruction-status.available{background:color-mix(in srgb, var(--color-text-tertiary,#a3a3aa) 9%, transparent);color:var(--color-text-tertiary,#a3a3aa)}.workspace-instruction-status-svg{stroke-width:1.9px;width:12px;height:12px}.workspace-instruction-list{border:1px solid var(--tool-card-divider);background:color-mix(in srgb, var(--tool-card-body-bg) 88%, transparent);border-radius:9px;min-width:0;display:grid;overflow:hidden}.workspace-instructions-content{min-width:0;display:block}.workspace-instructions-toggle{border:0;border-top:1px solid var(--tool-card-divider);background:color-mix(in srgb, var(--tool-card-body-bg) 72%, transparent);width:100%;min-height:32px;color:var(--tool-accent);cursor:pointer;font:inherit;font-size:var(--font-text-sm-size,11px);white-space:nowrap;border-radius:0 0 9px 9px;justify-content:center;align-items:center;padding:6px 10px;font-weight:550;line-height:1.25;display:flex}.workspace-instructions-toggle:hover{background:var(--tool-card-hover-bg)}.workspace-instructions-toggle:focus-visible{outline:2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);outline-offset:2px}.workspace-instruction-item+.workspace-instruction-item{border-top:1px solid var(--tool-card-divider)}.workspace-instruction-header{width:100%;min-width:0;color:inherit;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:22px minmax(0,1fr) 18px;align-items:center;gap:9px;padding:8px 10px;display:grid}.workspace-instruction-header.interactive{cursor:pointer}.workspace-instruction-header.interactive:hover{background:var(--tool-card-hover-bg)}.workspace-instruction-header.interactive:focus-visible{outline:2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);outline-offset:-2px}.workspace-instruction-text{gap:2px;min-width:0;display:grid}.workspace-instruction-name{min-width:0;color:var(--color-text-primary,#f5f5f6);font-size:var(--font-text-sm-size,12px);text-overflow:ellipsis;white-space:nowrap;font-weight:550;overflow:hidden}.workspace-instruction-path{min-width:0;color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);text-overflow:ellipsis;white-space:nowrap;font-size:10px;line-height:1.35;overflow:hidden}.workspace-instruction-chevron{width:18px;height:18px;color:var(--color-text-tertiary,#a3a3aa);place-items:center;transition:transform .14s;display:grid}.workspace-instruction-item.expanded .workspace-instruction-chevron{transform:rotate(180deg)}.workspace-instruction-chevron-svg{width:14px;height:14px}.workspace-instruction-preview{border-top:1px solid var(--tool-card-divider);background:color-mix(in srgb, var(--color-background-primary,#101114) 92%, transparent);max-height:300px;color:var(--color-text-secondary,#c7c7ce);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);white-space:pre-wrap;overflow-wrap:break-word;margin:0;padding:10px 12px;font-size:11px;line-height:1.55;overflow:auto}.workspace-instruction-preview[hidden]{display:none}.review-header{grid-template-columns:40px minmax(0,1fr) auto 20px}.review-title-group{gap:3px;min-width:0;display:grid}.review-summary{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg);display:grid}.review-diff-file-stats{align-items:center;gap:8px;display:flex}.review-empty{color:var(--color-text-secondary,#b7b7bf);font-size:var(--font-text-sm-size,13px)}.review-more{border:0;border-top:1px solid var(--tool-card-divider);width:100%;min-height:40px;color:var(--color-text-tertiary,#a3a3aa);cursor:pointer;font:inherit;font-size:var(--font-text-sm-size,12px);text-align:left;background:0 0;padding:0 12px}.review-more:hover{background:var(--tool-card-hover-bg);color:var(--color-text-primary,#f5f5f6)}.review-diff{max-height:520px;display:grid;overflow:hidden auto}.review-diff-files{gap:0;padding:0;display:grid}.review-diff-file{border:0;border-radius:0;overflow:hidden}.review-diff-file+.review-diff-file{border-top:1px solid var(--tool-card-divider)}.review-diff-file-header{width:100%;min-height:42px;color:var(--color-text-primary,#f5f5f6);cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:22px minmax(0,1fr) auto;align-items:center;gap:10px;padding:0 12px;display:grid}.review-file-kind{background:color-mix(in srgb, var(--color-text-tertiary,#a3a3aa) 10%, transparent);width:20px;height:20px;color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);border-radius:6px;align-self:center;place-items:center;font-size:10px;font-weight:700;display:grid}.review-file-kind.added{background:color-mix(in srgb, var(--color-success-text,#6fda83) 12%, transparent);color:var(--color-success-text,#6fda83)}.review-file-kind.edited,.review-file-kind.renamed,.review-file-kind.renamed-edited{background:color-mix(in srgb, var(--color-warning-text,#e6b566) 12%, transparent);color:var(--color-warning-text,#e6b566)}.review-file-kind.deleted{background:color-mix(in srgb, var(--color-danger-text,#ee7676) 12%, transparent);color:var(--color-danger-text,#ee7676)}.review-single-file{overflow:hidden}.review-diff-file-header:hover{background:var(--tool-card-hover-bg)}.review-diff-file-name,.review-diff-file-stats{text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:20px;overflow:hidden}.review-diff-file-name{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace)}.review-diff-file-name.renamed{text-overflow:clip;align-items:center;gap:6px;min-width:0;display:flex}.review-diff-file-path{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:calc(50% - 10px);overflow:hidden}.review-diff-file-path.previous{color:var(--color-text-tertiary,#a3a3aa)}.review-diff-file-path.current{color:var(--color-text-primary,#f5f5f6)}.review-diff-file-arrow{color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-sans,ui-sans-serif, system-ui, sans-serif);flex:none}.review-diff-file-stats{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-sm-size,12px);font-variant-numeric:tabular-nums;justify-content:flex-end;overflow:visible}.status{font-size:var(--font-text-sm-size,12px);padding:10px 12px}.status.muted{color:var(--color-text-secondary,#b7b7bf)}.status.error{color:var(--color-danger-text,#ee7676)}.pierre-diff,.pierre-file{--diffs-bg:var(--tool-payload-bg,var(--color-background-primary,#101114));--diffs-light-bg:var(--color-background-primary,#fff);--diffs-dark-bg:var(--tool-payload-bg,var(--color-background-primary,#101114));--diffs-font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);--diffs-header-font-family:var(--font-sans,ui-sans-serif, system-ui, sans-serif);--diffs-font-size:var(--font-text-sm-size,12px);--diffs-line-height:20px;border-bottom-right-radius:8px;border-bottom-left-radius:8px;max-height:420px;display:block;overflow:auto}.text-payload{max-height:420px;color:var(--color-text-secondary,#c7c7ce);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-sm-size,12px);white-space:pre-wrap;overflow-wrap:break-word;margin:0;padding:10px 12px;line-height:1.55;overflow:auto}.text-payload.bash{color:var(--color-text-primary,#f5f5f6);background:var(--color-background-primary,#101114)}@media (width<=520px){.tool-header{grid-template-columns:36px minmax(0,1fr) auto 18px;gap:9px;min-height:58px;padding:9px 10px}.tool-icon{border-radius:9px;width:36px;height:36px}.chevron{width:18px;height:18px}.review-header{grid-template-columns:36px minmax(0,1fr) auto 18px}.workspace-row{grid-template-columns:22px minmax(0,1fr);gap:2px 8px;padding-block:8px}.workspace-row-icon{grid-row:1/span 2;align-self:start}.workspace-row>.workspace-key,.workspace-row>.workspace-value,.workspace-row>.workspace-chip-list,.workspace-row>.workspace-instructions-content{grid-column:2}}
@@ -1 +0,0 @@
1
- import"./workspace-app-DkAiSl_0.js";import"./workspace-app-QyauBrJX.js";document.documentElement.dataset.forgerelayApp=`historical-tool-card`;
@@ -1,5 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./heavy-payload-vGgBRvNX.js","./scrollbar-CaOPzUJd.js","./chunk-EyZ2wyi3.js","./workspace-app-DkAiSl_0.js","./workspace-app-CcrHAUIn.css","./review-payload-4erWKckt.js"])))=>i.map(i=>d[i]);
2
- import{a as e,c as t,d as n,i as r,l as i,n as a,o,s,t as c,u as l}from"./workspace-app-DkAiSl_0.js";function u(e){return e===`open_workspace`||e===`close_workspace`||e===`capability`||e===`apply_patch`||e===`exec_command`||e===`write_stdin`||e===`read`||e===`write`||e===`edit`||e===`rename`||e===`delete`||e===`grep`||e===`glob`||e===`ls`||e===`bash`}function d(e){return e===`read`}function f(e){return e===`write`}function p(e){return e===`edit`}function m(e){return e===`apply_patch`}function ee(e){return e===`bash`||e===`exec_command`||e===`write_stdin`}function h(e){return e.tool===`capability`&&e.capabilityName===`review.changes`}function te(e){return!!(e&&typeof e==`object`)}function ne(e){return e?.content?.map(e=>e.type===`text`?e.text??``:`[${e.mimeType??`image`} image payload]`).filter(Boolean).join(`
3
-
4
- `)??``}function g(e,t){let n=e?.[t];return typeof n==`number`&&Number.isFinite(n)?n:void 0}function _(e){return e.tool===`open_workspace`?Number(e.summary?.agentsFiles??0)>0||Number(e.summary?.skills??0)>0||Number(e.summary?.agentProviders??0)>0||Number(e.summary?.agents??0)>0||!!e.agentsFiles?.length||!!e.availableAgentsFiles?.length||!!e.skills?.length||!!e.agentProviders?.length||!!e.agents?.length||!!e.worktree||!!e.instruction:h(e)?!!(e.files?.length||e.payload?.patch):m(e.tool)?!!e.payload?.patch:!!e.payload}function re(e){return e.tool===`open_workspace`||h(e)?_(e):m(e.tool)?e.files?.length===1&&_(e):!1}var ie={added:`Added`,edited:`Edited`,deleted:`Deleted`,renamed:`Renamed`,"renamed-edited":`Renamed and edited`};function ae(e,t={}){let n=e.files??[],r=le(n);if(r===0)return{title:t.emptyTitle??`Applied patch`,tone:`edit`};let i=new Set(n.map(v)),a=i.size===1?[...i][0]:void 0,o={title:ue(a,r),tone:de(a)};return a&&a!==`unknown`&&(o.iconKind=a),o}function v(e){switch(e.operation){case`add`:return`added`;case`update`:return`edited`;case`delete`:return`deleted`;case`move`:return`renamed`}switch(e.type){case`new`:return`added`;case`change`:return`edited`;case`deleted`:return`deleted`;case`rename-pure`:return`renamed`;case`rename-changed`:return`renamed-edited`;default:return`unknown`}}function oe(e,t,n){let r=v(t);if(r!==`edited`&&r!==`unknown`)return r;let i=e[n];return i?.operation===`move`&&(!t.path||i.path===t.path)||e.find(e=>e.operation===`move`&&e.path===t.path&&(!t.previousPath||e.previousPath===t.previousPath))?`renamed`:r===`edited`?`edited`:i?v(i):`unknown`}function y(e){let t=e.path??e.previousPath;if(!t)return;let n=e.previousPath;if(!n||n===t)return{current:t,title:t};let r=x(n)===x(t);return{current:r?S(t):t,previous:r?S(n):n,title:`${n} → ${t}`}}function se(e,t,n){let r=e[n],i=(r?.path===t.path?r:e.find(e=>e.path===t.path&&(!t.previousPath||!e.previousPath||e.previousPath===t.previousPath)))??r;return y({path:t.path??i?.path,previousPath:t.previousPath??i?.previousPath})}function ce(e){return e===`unknown`?`Changed`:ie[e]}function le(e){let t=new Set,n=0;for(let r of e){let e=r.path??r.previousPath;e?t.add(e):n+=1}return t.size+n}function ue(e,t){return e&&e!==`unknown`?`${ie[e]} ${t} ${b(t)}`:`Changed ${t} ${b(t)}`}function de(e){return e===`added`?`write`:e===`deleted`?`delete`:`edit`}function b(e){return e===1?`file`:`files`}function x(e){let t=Math.max(e.lastIndexOf(`/`),e.lastIndexOf(`\\`));return t===-1?``:e.slice(0,t)}function S(e){let t=Math.max(e.lastIndexOf(`/`),e.lastIndexOf(`\\`));return t===-1?e:e.slice(t+1)}function fe(e){switch(e.tool){case`open_workspace`:return{icon:e.mode===`worktree`?o.gitBranch:o.folderOpen,title:he(e),label:e.root??e.path,tone:`workspace`};case`close_workspace`:return{icon:e.mode===`worktree`?o.gitBranch:o.folderOpen,title:`Closed workspace`,label:e.sourceRoot??e.root??e.path??e.workspaceId,tone:`workspace`};case`read`:return{icon:o.readFile,title:`Read file`,label:e.path,tone:`read`};case`write`:return{icon:o.writeFile,title:`Wrote file`,label:e.path,tone:`write`};case`edit`:return{icon:o.editFile,title:`Edited file`,label:e.path,tone:`edit`};case`rename`:return{icon:o.editFile,title:`Renamed path`,label:e.path,tone:`edit`};case`delete`:return{icon:o.deleteFile,title:`Deleted path`,label:e.path,tone:`delete`};case`apply_patch`:{let t=ae(e);return{icon:me(t.iconKind),title:t.title,label:C(e),tone:t.tone}}case`grep`:return{icon:o.search,title:`Searched files`,label:ge(e),tone:`search`};case`glob`:return{icon:o.files,title:`Found files`,label:ge(e),tone:`search`};case`ls`:return{icon:o.folderTree,title:`Listed directory`,label:e.path,tone:`directory`};case`bash`:case`exec_command`:return{icon:o.terminalSquare,title:_e(e,`command`),label:w(e),tone:`shell`,state:ve(e)};case`write_stdin`:return{icon:o.terminal,title:_e(e,`process`),label:w(e),tone:`shell`,state:ve(e)};case`capability`:if(h(e)){let t=ae(e,{emptyTitle:`Changes ready`}),n=e.files?.length??0;return{icon:o.diff,title:n>0||e.payload?.patch?t.title:`No changes`,label:C(e),tone:`review`}}return{icon:o.skills,title:e.capabilityName?`Capability: ${e.capabilityName}`:`Capability completed`,tone:`workspace`}}}function pe(e){let t=e.summary??{};if(h(e)||m(e.tool)||p(e.tool)||f(e.tool))return{kind:`diff`,additions:g(t,`additions`)??0,removals:g(t,`removals`)??0};if(e.tool===`open_workspace`){let e=[T(g(t,`agentsFiles`),`instruction`),T(g(t,`skills`),`skill`)].filter(e=>!!e);return e.length>0?{kind:`text`,text:e.join(` · `)}:{kind:`empty`}}if(ee(e.tool)){let e=[T(g(t,`lines`),`line`),ye(g(t,`wallTimeMs`))].filter(e=>!!e);return e.length>0?{kind:`text`,text:e.join(` · `)}:{kind:`empty`}}if(e.tool===`grep`||e.tool===`read`||e.tool===`ls`){let e=T(g(t,`lines`),`line`);return e?{kind:`text`,text:e}:{kind:`empty`}}return{kind:`empty`}}function me(e){return e===`added`?o.writeFile:e===`deleted`?o.deleteFile:e===`renamed`||e===`renamed-edited`?o.files:o.editFile}function he(e){return`${e.workspaceReused?`Reused`:`Opened`} workspace`}function C(e){if(e.files?.length===1)return y(e.files[0])?.title??e.path}function ge(e){let t=e.summary?.pattern,n=e.summary?.scope;return typeof t==`string`?typeof n==`string`&&n!==`.`?`${t} in ${n}`:t:e.path}function _e(e,t){if(e.summary?.running===!0)return t===`command`?`Command running`:`Process running`;let n=g(e.summary,`exitCode`);return n!==void 0&&n!==0?t===`command`?`Command failed`:`Process failed`:t===`command`?`Ran command`:`Process finished`}function ve(e){if(e.summary?.running===!0)return`running`;let t=g(e.summary,`exitCode`);return t!==void 0&&t!==0?`error`:t===0?`success`:void 0}function w(e){let t=e.summary?.command;if(typeof t==`string`)return t;let n=e.summary?.sessionId;return typeof n==`number`||typeof n==`string`?`Session ${String(n)}`:e.path}function T(e,t){if(e!==void 0)return`${e} ${t}${e===1?``:`s`}`}function ye(e){if(e!==void 0)return e<1e3?`${Math.round(e)}ms`:`${(e/1e3).toFixed(+(e<1e4))}s`}var E=null,D=!1,O=null,k,A=null,j=!1,M=!1,N=null,P=null,F=null,I=null,L=!1,R=document.querySelector(`#app`);if(!R)throw Error(`Missing #app root element.`);var z=R,B=new c(z);be();async function be(){H(),E=new i({name:`forgerelay-tool-cards`,version:`0.1.0`},{}),E.ontoolresult=e=>{let t=a(B.active,e.structuredContent);if(t===`activity`&&B.accept(e)){A=null,j=!1,M=!1,I=null,L=!1,N=null,H();return}if(t===`preserve-panel`)return;let n=Re(e),r=Le(e),i=r?{...n,...r}:n,o=Ie(e);if(!o||!te(i)){A=null,j=!1,M=!1,I=null,L=!1,N=`No result card is available for this tool result.`,H();return}let s={...i,tool:o};A=s,j=re(s),M=!1,I=null,L=!1,N=null,H()},E.onhostcontextchanged=e=>{k={...k,...e},V(),B.active?B.render():A?.tool!==`open_workspace`&&W()},E.onteardown=async()=>(D=!1,B.detach(),G(),{});try{await E.connect();let e=E.getHostContext();e&&(k=e),V(),D=!0,B.attach(E)}catch(e){O=e instanceof Error?e.message:String(e)}H()}function V(){k?.theme&&l(k.theme),k?.styles?.variables&&t(k.styles.variables),k?.styles?.css?.fonts&&s(k.styles.css.fonts);let e=k?.safeAreaInsets;e&&(document.body.style.padding=`${e.top}px ${e.right}px ${e.bottom}px ${e.left}px`)}function H(){if(G(),O){U(O,`error`);return}if(!D){U(`Connecting to host...`);return}if(B.render())return;if(!A){U(N??`Waiting for a tool result.`,N?`error`:`muted`);return}let t=fe(A);if(h(A)){Ce(A,t);return}let n=_(A),r=$(`main`,{className:`shell`}),i=$(`section`,{className:Te(t)}),a=$(`button`,{className:`tool-header`,type:`button`,ariaExpanded:String(j),disabled:!n});n&&a.addEventListener(`click`,()=>{j=!j,H()});let o=$(`span`,{className:`tool-icon`,ariaHidden:`true`});o.append(e(t.icon));let s=$(`span`,{className:`tool-main`}),c=$(`span`,{className:`tool-title`,text:t.title});if(s.append(c),t.label&&s.append($(`span`,{className:`tool-label`,text:t.label,title:t.label})),a.append(o,s,J(A),we(j,n)),i.append(a),j){let e=$(`div`,{className:`tool-body`});F=e,i.append(e)}r.append(i),z.replaceChildren(r),W()}function U(e,t=`muted`){let n=$(`main`,{className:`shell`});n.append($(`section`,{className:`empty ${t}`,text:e})),z.replaceChildren(n)}async function W(){if(!A||!F||!j)return;let e=F;if(N){q(e,N,`error`);return}if(A.tool===`open_workspace`){Ee(e,A);return}if(xe(A)){if(P){P.update({card:A,hostContext:k,errorMessage:N});return}Y(e,!0);try{let{mountHeavyPayload:t}=await n(async()=>{let{mountHeavyPayload:e}=await import(`./heavy-payload-vGgBRvNX.js`);return{mountHeavyPayload:e}},__vite__mapDeps([0,1,2,3,4]),import.meta.url);if(e!==F||!j||!A)return;Y(e,!1),P=t(e,{card:A,hostContext:k,errorMessage:N})}catch(t){if(e!==F||!j)return;Y(e,!1),q(e,t instanceof Error?t.message:`Unable to load details.`,`error`)}return}if(h(A)||m(A.tool)){let t=h(A)&&!M?Math.max(3,(A.files??[]).slice(0,3).length):void 0;if(P){P.update({card:A,hostContext:k,errorMessage:N,visibleFileCount:t});return}q(e,h(A)?`Loading review...`:`Loading diff...`);let{mountReviewPayload:r}=await n(async()=>{let{mountReviewPayload:e}=await import(`./review-payload-4erWKckt.js`);return{mountReviewPayload:e}},__vite__mapDeps([5,1,2,3,4]),import.meta.url);if(e!==F||!A)return;P=r(e,{card:A,hostContext:k,errorMessage:N,visibleFileCount:t});return}let t=ne(A.payload);if(!t){q(e,`No details available.`);return}Se(e,t,A.tool)}function xe(e){return d(e.tool)||p(e.tool)||f(e.tool)}function G(){K(),P=null,F=null}function K(){P?.unmount(),P=null}function q(e,t,n=`muted`){K(),e.replaceChildren($(`div`,{className:`status ${n}`,text:t}))}function Se(e,t,n){K(),e.replaceChildren($(`pre`,{className:`text-payload pretty-scrollbar ${n}`,text:t}))}function J(e){let t=pe(e);if(t.kind===`diff`){let e=$(`span`,{className:`stats`});return e.setAttribute(`aria-label`,`Diff statistics`),e.append($(`span`,{className:`add`,text:`+${String(t.additions)}`}),$(`span`,{className:`remove`,text:`-${String(t.removals)}`})),e}let n=$(`span`,{className:`header-meta ${t.kind===`empty`?`empty`:``}`,text:t.kind===`text`?t.text:``});return t.kind===`empty`&&n.setAttribute(`aria-hidden`,`true`),n}function Ce(t,n){G();let r=t.files??[],i=M?r:r.slice(0,3),a=Math.max(0,r.length-i.length),o=_(t),s=$(`main`,{className:`shell`}),c=$(`section`,{className:Te(n)}),l=$(`button`,{className:`tool-header review-header`,type:`button`,ariaExpanded:String(j),disabled:!o});o&&l.addEventListener(`click`,()=>{j=!j,H()});let u=$(`span`,{className:`tool-icon`,ariaHidden:`true`});u.append(e(n.icon));let d=$(`span`,{className:`tool-main review-title-group`});if(d.append($(`span`,{className:`tool-title`,text:n.title})),n.label&&d.append($(`span`,{className:`tool-label`,text:n.label,title:n.label})),l.append(u,d,J(t),we(j,o)),c.append(l),j){let e=$(`div`,{className:`review-summary`}),t=$(`div`,{className:`review-payload`});if(F=t,e.append(t),a>0){let t=$(`button`,{className:`review-more`,type:`button`,text:`Show ${a} more ${a===1?`file`:`files`}`});t.addEventListener(`click`,()=>{M=!0,H()}),e.append(t)}c.append(e)}s.append(c),z.replaceChildren(s),W()}function we(t,n){let r=$(`span`,{className:n?`chevron ${t?`expanded`:``}`:`chevron`,ariaHidden:`true`});return n&&r.append(e(o.chevronDown)),r}function Te(e){return[`tool-card`,e.tone,e.state?`state-${e.state}`:void 0].filter(Boolean).join(` `)}function Y(t,n){let r=t.previousElementSibling,i=r?.querySelector(`.chevron`);if(!i)return;i.classList.toggle(`loading`,n),i.replaceChildren(e(n?o.loading:o.chevronDown));let a=r instanceof HTMLButtonElement?r:null;a&&a.setAttribute(`aria-busy`,String(n))}function Ee(t,n){K();let i=$(`div`,{className:`workspace-details pretty-scrollbar`}),a=$(`div`,{className:`workspace-rows`}),s=n.worktree;if(s){let t=[s.baseRef,s.baseSha?.slice(0,8)].filter(e=>!!e).join(` · `)||`Worktree`,n=$(`span`,{className:`workspace-base-value`});if(n.append($(`span`,{className:`workspace-value`,text:t,title:t})),s.dirtySource){let t=$(`span`,{className:`workspace-base-warning`,title:`The source checkout had uncommitted changes when this worktree was created. Those changes are not included here.`,ariaLabel:`Source checkout changes are not included in this worktree`});t.append(e(o.warning,`workspace-base-warning-svg`)),n.append(t)}Z(a,`Base`,n,o.base),s.branch&&X(a,`Worktree branch`,s.branch,o.gitBranch,!1),s.targetBranch&&X(a,`Merge target`,s.targetBranch,o.gitBranch,!1)}n.sourceRoot&&n.sourceRoot!==n.root&&X(a,`Source checkout`,n.sourceRoot,o.sourceCheckout,!0),De(a,n.agentsFiles??[],n.availableAgentsFiles??[]);let c=n.skills??[];c.length>0&&Pe(a,c);let l=n.agentProviders??[],u=(n.agents??[]).map(e=>{let t=e.name??`Unnamed agent`,n=e.provider?.trim(),i=e.providerAvailable===!1,a=[e.description,n?`Provider: ${n}`:void 0,e.model?`Model: ${e.model}`:void 0,e.thinking?`Thinking: ${e.thinking}`:void 0,i?e.providerUnavailableReason??`Provider unavailable`:void 0].filter(e=>!!e).join(`
5
- `);return{label:t,logo:n?r(n):void 0,profile:!0,tone:i?`muted`:void 0,title:a||void 0}}),d=l.map(e=>{let t=e.name?.trim()||`Unknown provider`,n=e.available===!1,i=r(t);return{label:t,logo:i,bareLogo:!!i,ariaLabel:t,tone:n?`muted`:void 0,title:n?e.reason??`Provider unavailable`:t}});if(u.length>0){let e=Q([...u,...d]);e.classList.add(`workspace-agents-list`),Z(a,`Agents`,e,o.agents,`workspace-agents-row`)}else d.length>0&&Ne(a,`Providers`,d,o.providers);a.childElementCount>0&&i.append(a),i.childElementCount===0&&i.append($(`div`,{className:`status muted`,text:`No workspace details available.`})),t.replaceChildren(i)}function De(e,t,n){let r=[],i=new Set;for(let[e,n]of t.entries())r.push({key:`loaded:${e}`,path:n.path,label:n.path??`Loaded instructions`,content:n.content,status:`loaded`}),n.path&&i.add(n.path);let a=[];for(let[e,t]of n.entries())t.path&&i.has(t.path)||a.push({key:`available:${e}`,path:t.path,label:t.path??`Nested instructions`,status:`available`});if(r.length===0&&a.length===0)return;let s=Oe(L?[...r,...a]:r);if(a.length>0){let e=L,t=$(`button`,{className:`workspace-instructions-toggle`,type:`button`,text:e?`Show less`:`View all`,ariaLabel:e?`Show only loaded instruction files`:`View all ${a.length} available instruction files`,ariaExpanded:String(e)});t.addEventListener(`click`,()=>{L=!L,L||(I=null),H()}),s.append(t)}let c=$(`div`,{className:`workspace-instructions-content`});c.append(s),Z(e,`Instructions`,c,o.instructions,`workspace-instructions-row`)}function Oe(t){let n=$(`span`,{className:`workspace-instruction-list`});for(let r of t){let t=$(`span`,{className:`workspace-instruction-item`});t.dataset.instructionKey=r.key;let i=r.status===`loaded`&&r.content!==void 0,a=$(i?`button`:`span`,{className:`workspace-instruction-header${i?` interactive`:``}`,type:i?`button`:void 0,ariaLabel:i?`View ${r.label}`:void 0,ariaExpanded:i?`false`:void 0}),s=$(`span`,{className:`workspace-instruction-text`}),c=Me(r.label);if(s.append($(`span`,{className:`workspace-instruction-name`,text:c})),r.path&&r.path!==c&&s.append($(`span`,{className:`workspace-instruction-path`,text:r.path,title:r.path})),a.append(Ae(r.status),s),i){let i=$(`span`,{className:`workspace-instruction-chevron`,ariaHidden:`true`});i.append(e(o.chevronDown,`workspace-instruction-chevron-svg`)),a.append(i),a.addEventListener(`click`,()=>{I=I===r.key?null:r.key,ke(n)});let s=$(`pre`,{className:`workspace-instruction-preview pretty-scrollbar`,text:r.content});s.hidden=!0,t.append(a,s)}else t.append(a);n.append(t)}return ke(n),n}function ke(e){for(let t of e.querySelectorAll(`.workspace-instruction-item`)){let e=t.dataset.instructionKey===I;t.classList.toggle(`expanded`,e),t.querySelector(`.workspace-instruction-header.interactive`)?.setAttribute(`aria-expanded`,String(e));let n=t.querySelector(`.workspace-instruction-preview`);n&&(n.hidden=!e)}}function Ae(t){let n=je(t),r=$(`span`,{className:`workspace-instruction-status ${t}`,title:n,ariaLabel:n});return r.setAttribute(`role`,`img`),r.append(e(t===`loaded`?o.instructionLoaded:o.instructionAvailable,`workspace-instruction-status-svg`)),r}function je(e){return e===`loaded`?`Loaded into the current workspace context`:`Available for a nested directory`}function Me(e){return e.replaceAll(`\\`,`/`).split(`/`).filter(Boolean).at(-1)??e}function X(e,t,n,r,i=!1){Z(e,t,$(`span`,{className:`workspace-value${i?` mono`:``}`,text:n,title:n}),r)}function Ne(e,t,n,r){Z(e,t,Q(n),r)}function Z(e,t,n,r,i){let a=$(`div`,{className:[`workspace-row`,i].filter(Boolean).join(` `)});a.append(Fe(r),$(`span`,{className:`workspace-key`,text:t}),n),e.append(a)}function Pe(e,t){let n=Q(t.map(e=>({label:e.name??`Unnamed skill`,title:e.description||void 0})));n.classList.add(`workspace-skills-list`),Z(e,`Skills`,n,o.skills,`workspace-skills-row`)}function Fe(t){let n=$(`span`,{className:`workspace-row-icon`,ariaHidden:`true`});return n.append(e(t,`workspace-row-icon-svg`)),n}function Q(e){let t=$(`span`,{className:`workspace-chip-list`});for(let n of e){let e=!!(n.bareLogo&&n.logo),r=$(`span`,{className:[e?`workspace-provider-logo`:n.profile?`workspace-agent-profile`:`workspace-chip`,n.tone].filter(Boolean).join(` `),title:n.title});if(e&&(r.setAttribute(`role`,`img`),r.setAttribute(`aria-label`,n.ariaLabel??n.label)),n.logo){let t=document.createElement(`img`);t.className=e?`workspace-provider-logo-image`:n.profile?`workspace-agent-profile-logo`:`workspace-chip-logo`,t.src=n.logo,t.alt=``,t.setAttribute(`aria-hidden`,`true`),r.append(t)}e||r.append($(`span`,{className:`workspace-chip-label`,text:n.label})),t.append(r)}return t}function Ie(e){let t=e._meta?.tool;return u(t)?t:void 0}function Le(e){let t=e._meta?.card;return t&&typeof t==`object`?t:void 0}function Re(e){return e.structuredContent}function $(e,t={}){let n=document.createElement(e);return t.className&&(n.className=t.className),t.text!==void 0&&(n.textContent=t.text),t.type!==void 0&&`type`in n&&n.setAttribute(`type`,t.type),t.title!==void 0&&(n.title=t.title),t.ariaHidden!==void 0&&n.setAttribute(`aria-hidden`,t.ariaHidden),t.ariaLabel!==void 0&&n.setAttribute(`aria-label`,t.ariaLabel),t.ariaExpanded!==void 0&&n.setAttribute(`aria-expanded`,t.ariaExpanded),t.disabled!==void 0&&`disabled`in n&&(n.disabled=t.disabled),n}export{d as a,g as c,p as i,oe as n,f as o,se as r,ne as s,ce as t};
@@ -1 +0,0 @@
1
- import"./workspace-app-DkAiSl_0.js";import"./workspace-app-QyauBrJX.js";document.documentElement.dataset.forgerelayApp=`workspace-lifecycle-compatibility`;