@akira-tl/forgerelay 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +14 -0
  3. package/dist/activity/runtime/mcp-query-tools.js +15 -5
  4. package/dist/cli/config/inspect.js +331 -0
  5. package/dist/cli/config/migrate.js +351 -0
  6. package/dist/cli/init/setup-config.js +41 -0
  7. package/dist/cli/init.js +128 -130
  8. package/dist/cli/maintenance.js +15 -5
  9. package/dist/cli/mcp/external-mcp.js +14 -10
  10. package/dist/cli/mcp/status.js +11 -6
  11. package/dist/cli.js +40 -7
  12. package/dist/lsp/language-server-config.js +66 -144
  13. package/dist/lsp/runtime/manager.js +35 -4
  14. package/dist/mcp/hooks/config.js +141 -0
  15. package/dist/mcp/hooks/external-mcp-transform.js +22 -13
  16. package/dist/mcp/hooks/hook-cli.js +62 -52
  17. package/dist/mcp/hooks/hooks.js +111 -85
  18. package/dist/mcp/operations/external-mcp/external-mcp-runtime.js +8 -5
  19. package/dist/mcp/operations/external-mcp/external-mcp.js +9 -2
  20. package/dist/mcp/server/core/capability-support.js +7 -0
  21. package/dist/runtime/config/config.js +142 -64
  22. package/dist/runtime/config/definition/catalog.js +12 -0
  23. package/dist/runtime/config/definition/definition.js +115 -0
  24. package/dist/runtime/config/definition/external-mcp.js +58 -0
  25. package/dist/runtime/config/definition/general-config.js +258 -0
  26. package/dist/runtime/config/definition/language-servers.js +116 -0
  27. package/dist/runtime/config/definition/schema.js +113 -0
  28. package/dist/runtime/config/definition/types.js +2 -0
  29. package/dist/runtime/config/external-mcp-auth-store.js +105 -29
  30. package/dist/runtime/config/external-mcp-config.js +39 -0
  31. package/dist/runtime/config/external-mcp-registry.js +284 -106
  32. package/dist/runtime/config/inspection/live.js +77 -0
  33. package/dist/runtime/config/resolution/general.js +93 -0
  34. package/dist/runtime/config/resolution/hooks.js +315 -0
  35. package/dist/runtime/config/resolution/language-servers.js +237 -0
  36. package/dist/runtime/config/resolution/project-sources.js +159 -0
  37. package/dist/runtime/config/resolution/resolver.js +485 -0
  38. package/dist/runtime/config/resolution/types.js +1 -0
  39. package/dist/runtime/config/runtime/config-runtime.js +74 -0
  40. package/dist/runtime/config/runtime/source-refresh.js +263 -0
  41. package/dist/runtime/config/user-config.js +38 -31
  42. package/dist/runtime/security/project-execution-trust.js +34 -0
  43. package/dist/runtime/testing/server-fixture.js +9 -8
  44. package/dist/server.js +10 -6
  45. package/dist/subagents/profiles.js +336 -53
  46. package/dist/subagents/sessions/execution.js +6 -35
  47. package/dist/subagents/sessions/manager.js +14 -2
  48. package/dist/ui/.vite/manifest.json +275 -275
  49. package/dist/ui/activity-panel-app.html +1 -1
  50. package/dist/ui/assets/{activity-panel-app-Bw7ZFX2g.js → activity-panel-app-BeDiwd7D.js} +3 -3
  51. package/dist/ui/assets/{angular-html-DeUNP12X.js → angular-html-DuoCE7nn.js} +1 -1
  52. package/dist/ui/assets/{angular-ts-B3v8FKi_.js → angular-ts-D5tGk0hL.js} +1 -1
  53. package/dist/ui/assets/{apl-Bs3iQm4P.js → apl-lciOTPbw.js} +1 -1
  54. package/dist/ui/assets/{astro-D6GHecBY.js → astro-vJbqfNnJ.js} +1 -1
  55. package/dist/ui/assets/{blade-BMNbVOHv.js → blade-C7655Q4K.js} +1 -1
  56. package/dist/ui/assets/{c-BquuA6bv.js → c-DdHp1Jrw.js} +1 -1
  57. package/dist/ui/assets/{cobol-BuZ6kfps.js → cobol-DgvzQxZP.js} +1 -1
  58. package/dist/ui/assets/{coffee-DgC5uBzL.js → coffee-BIjVUOUd.js} +1 -1
  59. package/dist/ui/assets/{cpp-BdvWqCaB.js → cpp-CU8tGzvI.js} +1 -1
  60. package/dist/ui/assets/{crystal-0Q5qBvNI.js → crystal-CUQAwsg9.js} +1 -1
  61. package/dist/ui/assets/{css-Be_T8idh.js → css-DtMTqD3F.js} +1 -1
  62. package/dist/ui/assets/{edge-DaGLdgh_.js → edge-DoCX9RnL.js} +1 -1
  63. package/dist/ui/assets/{elixir-CMgiFIzB.js → elixir-DJxV9_U6.js} +1 -1
  64. package/dist/ui/assets/{elm-B-ufMlMC.js → elm-DIgWqK4r.js} +1 -1
  65. package/dist/ui/assets/{erb-CaGiOZM-.js → erb-BHxP0c_e.js} +1 -1
  66. package/dist/ui/assets/{git-rebase-CpwUWCzK.js → git-rebase-HbI_Vo8d.js} +1 -1
  67. package/dist/ui/assets/{glimmer-js-BRxccvnE.js → glimmer-js-CthY4Ty_.js} +1 -1
  68. package/dist/ui/assets/{glimmer-ts-CgQbPzlT.js → glimmer-ts-B2-_A1Xz.js} +1 -1
  69. package/dist/ui/assets/{glsl-D7JmHFr0.js → glsl-D9V9gq98.js} +1 -1
  70. package/dist/ui/assets/{graphql-C8n0eUBK.js → graphql-CddeAaS3.js} +1 -1
  71. package/dist/ui/assets/{hack-lVN7KVQv.js → hack-D_loGkOT.js} +1 -1
  72. package/dist/ui/assets/{haml-h18lG0pS.js → haml-BoWDczd0.js} +1 -1
  73. package/dist/ui/assets/{handlebars-lRFjRPfj.js → handlebars-ZoSOvw9y.js} +1 -1
  74. package/dist/ui/assets/{heavy-payload-Cqx9MOkH.js → heavy-payload-DO34LwoZ.js} +3 -3
  75. package/dist/ui/assets/{html-DtILqTE1.js → html-CE-obXVo.js} +1 -1
  76. package/dist/ui/assets/{html-derivative-CSTSmPup.js → html-derivative-Cd-smAbW.js} +1 -1
  77. package/dist/ui/assets/{http-DNI8fsbY.js → http-CZrkbrSl.js} +1 -1
  78. package/dist/ui/assets/{hurl-DgRi60JG.js → hurl-CxTuxBt1.js} +1 -1
  79. package/dist/ui/assets/{java-CW9pZ5bh.js → java-BshLjCat.js} +1 -1
  80. package/dist/ui/assets/{javascript-Cvg2UrWL.js → javascript-D2pxQTiX.js} +1 -1
  81. package/dist/ui/assets/{jinja-CZw_S1FB.js → jinja-kbfPP7-F.js} +1 -1
  82. package/dist/ui/assets/{jison-C56u_FZv.js → jison-C2mt9l1M.js} +1 -1
  83. package/dist/ui/assets/{json-DW2UTv1q.js → json-DU_rg_AU.js} +1 -1
  84. package/dist/ui/assets/{jsx-Zj5gobdD.js → jsx-D_8aQMlx.js} +1 -1
  85. package/dist/ui/assets/{julia-Cvcrre-L.js → julia-BmefTaVV.js} +1 -1
  86. package/dist/ui/assets/{just-8CZ2u6Tj.js → just-DXV8vk1S.js} +1 -1
  87. package/dist/ui/assets/{latex-D289AXWG.js → latex-Cjm2MvGW.js} +1 -1
  88. package/dist/ui/assets/{liquid-CFEX3SkZ.js → liquid-DyQDlo0u.js} +1 -1
  89. package/dist/ui/assets/{lua-BKpJ0ybO.js → lua-B2A1vzzy.js} +1 -1
  90. package/dist/ui/assets/{marko-D9maZ0a6.js → marko-B9vOkXOU.js} +1 -1
  91. package/dist/ui/assets/{mdc-rM9l0qWR.js → mdc-D42m6spU.js} +1 -1
  92. package/dist/ui/assets/{nginx-C9w6p2C9.js → nginx-TpfVxSUY.js} +1 -1
  93. package/dist/ui/assets/{nim-BRePHk0J.js → nim-CJr5T1FK.js} +1 -1
  94. package/dist/ui/assets/{perl-CskTmiPQ.js → perl-cJZcsdm9.js} +1 -1
  95. package/dist/ui/assets/{php-DCU_FvNI.js → php-CSZL5KRT.js} +1 -1
  96. package/dist/ui/assets/{pug-XLaVxTvv.js → pug-B1eMKzuA.js} +1 -1
  97. package/dist/ui/assets/{qml-CaONKSl-.js → qml-Dl2_7zc9.js} +1 -1
  98. package/dist/ui/assets/{r-BCVR0ZE3.js → r-F0aaO9gG.js} +1 -1
  99. package/dist/ui/assets/{razor-DDnGXxnU.js → razor-BPnkSMCw.js} +1 -1
  100. package/dist/ui/assets/{regexp-DWGTYYVx.js → regexp-BnGHMFbv.js} +1 -1
  101. package/dist/ui/assets/{rst-Dz3GWvQm.js → rst-B3CO7WYO.js} +1 -1
  102. package/dist/ui/assets/{ruby-DC3jGtMY.js → ruby-B6VMzT1T.js} +1 -1
  103. package/dist/ui/assets/{sas-7mNpvsjZ.js → sas-Df2lHVuU.js} +1 -1
  104. package/dist/ui/assets/{scss-uBsoCJVy.js → scss-4XxI6sgu.js} +1 -1
  105. package/dist/ui/assets/{shellscript-BCN9N_Jf.js → shellscript-DYASCN4r.js} +1 -1
  106. package/dist/ui/assets/{shellsession-Hsw46VXV.js → shellsession-Br6AGJO_.js} +1 -1
  107. package/dist/ui/assets/{soy-D6KV69HU.js → soy-p3ZNq1We.js} +1 -1
  108. package/dist/ui/assets/{sql-CVzVQkJh.js → sql-CAdv2OXL.js} +1 -1
  109. package/dist/ui/assets/{stata-BwUjXNhI.js → stata-Dx7yUbG_.js} +1 -1
  110. package/dist/ui/assets/{surrealql-CgloWqRa.js → surrealql-6YsE8HQa.js} +1 -1
  111. package/dist/ui/assets/{svelte-DcC3n6IC.js → svelte-YgzedwpD.js} +1 -1
  112. package/dist/ui/assets/{templ-DOFfnWTJ.js → templ-Bahqx3uD.js} +1 -1
  113. package/dist/ui/assets/{tex-ln89VPaX.js → tex-o6fTQjkH.js} +1 -1
  114. package/dist/ui/assets/{ts-tags-CWMzCstH.js → ts-tags-BO-Co0Ju.js} +1 -1
  115. package/dist/ui/assets/{tsx-DgzfiGeK.js → tsx-DtQe5AoH.js} +1 -1
  116. package/dist/ui/assets/{twig-Bi2itZL9.js → twig-BcUs6Kqc.js} +1 -1
  117. package/dist/ui/assets/{typescript-CQ24f4zW.js → typescript-BPtM8h4u.js} +1 -1
  118. package/dist/ui/assets/{vue-BS8jE5u6.js → vue-5KkGUzJJ.js} +1 -1
  119. package/dist/ui/assets/{vue-html-o38xqhyB.js → vue-html-Df_Xfsz9.js} +1 -1
  120. package/dist/ui/assets/{vue-vine-D2NvKv50.js → vue-vine-hUIe_u_Y.js} +1 -1
  121. package/dist/ui/assets/{xml-_44rx9_2.js → xml-C3Jy7as5.js} +1 -1
  122. package/dist/ui/assets/{xsl-B5QWQPoi.js → xsl-BnF9OuFo.js} +1 -1
  123. package/dist/ui/assets/{yaml-CKL2pEWe.js → yaml-DyrFgCou.js} +1 -1
  124. package/dist/workspaces/conversation-test-support.js +3 -11
  125. package/dist/workspaces/sessions.js +2 -0
  126. package/dist/workspaces/state/project-context.js +221 -0
  127. package/dist/workspaces.js +3 -1
  128. package/docs/chatgpt-coding-workflow.md +8 -3
  129. package/docs/configuration.md +99 -41
  130. package/docs/gotchas.md +24 -5
  131. package/docs/security.md +14 -3
  132. package/docs/setup.md +12 -7
  133. package/package.json +12 -3
  134. package/schemas/v1/config.project-local.schema.json +14 -0
  135. package/schemas/v1/config.project.schema.json +14 -0
  136. package/schemas/v1/config.user.schema.json +538 -0
  137. package/schemas/v1/hooks.project-local.schema.json +118 -0
  138. package/schemas/v1/hooks.project.schema.json +118 -0
  139. package/schemas/v1/hooks.user.schema.json +118 -0
  140. package/schemas/v1/language-servers.project-local.schema.json +230 -0
  141. package/schemas/v1/language-servers.project.schema.json +230 -0
  142. package/schemas/v1/language-servers.user.schema.json +230 -0
  143. package/schemas/v1/mcp.project-local.schema.json +143 -0
  144. package/schemas/v1/mcp.project.schema.json +143 -0
  145. package/schemas/v1/mcp.user.schema.json +143 -0
  146. package/scripts/ci/architecture.mjs +54 -4
  147. package/scripts/ci/cmd-acceptance.mjs +9 -9
  148. package/scripts/ci/config-v2-product-acceptance.mjs +639 -0
  149. package/scripts/ci/gates/acceptance-artifact.mjs +66 -0
  150. package/scripts/ci/gates/core-contract.mjs +27 -0
  151. package/scripts/ci/gates/platform-tests.mjs +38 -0
  152. package/scripts/ci/gates/prepare-windows-product.mjs +57 -0
  153. package/scripts/ci/gates/test-shard.mjs +71 -0
  154. package/scripts/ci/powershell51-acceptance.mjs +9 -9
  155. package/scripts/ci/pwsh-acceptance.mjs +9 -9
  156. package/scripts/ci/verify.mjs +2 -0
  157. package/scripts/ci/windows-product-acceptance.mjs +9 -10
  158. package/scripts/config/schema-text.mjs +4 -0
  159. package/scripts/config/schema-text.test.mjs +18 -0
  160. package/scripts/config/schema.mjs +74 -0
  161. package/scripts/lsp/interop.mjs +4 -1
  162. package/scripts/release/pack.mjs +64 -4
  163. package/scripts/release/release-gate.test.mjs +93 -27
  164. package/scripts/release-parity.mjs +19 -2
  165. package/templates/instructions/cmd.md +12 -0
  166. package/templates/instructions/fish.md +11 -0
  167. package/templates/instructions/powershell.md +14 -0
  168. package/templates/instructions/pwsh.md +14 -0
  169. package/templates/instructions/zsh.md +11 -0
  170. package/dist/runtime/instructions/powershell-skill.js +0 -65
@@ -40,33 +40,52 @@ test("optional release:verify records proof only after the cloud-equivalent pari
40
40
  assert.equal(pkg.scripts["release:push-ready"], "node scripts/release/push-ready.mjs");
41
41
  });
42
42
 
43
- test("cross-platform cloud CI delegates to one shell-free verification entrypoint", async () => {
43
+ test("Config schemas and ForgeRelay-owned runtime resources are gated in packaged verification", async () => {
44
+ const pkg = await readJson("package.json");
45
+ assert.ok(pkg.files.includes("schemas"));
46
+ assert.ok(pkg.files.includes("templates"));
47
+ assert.ok(pkg.files.includes("capabilities"));
48
+ assert.match(pkg.scripts.build, /config:schema:check/);
49
+ assert.equal(pkg.scripts["config:schema:generate"], "node --import tsx scripts/config/schema.mjs write");
50
+ assert.equal(pkg.scripts["config:schema:check"], "node --import tsx scripts/config/schema.mjs check");
51
+ assert.equal(pkg.scripts["config:product-accept"], "node scripts/ci/config-v2-product-acceptance.mjs");
52
+
53
+ const verify = await readFile(resolve(repoRoot, "scripts/ci/verify.mjs"), "utf8");
54
+ assert.match(verify, /\["run", "config:schema:check"\]/);
55
+ assert.match(verify, /\["run", "config:product-accept"\]/);
56
+
57
+ const pack = await readFile(resolve(repoRoot, "scripts/release/pack.mjs"), "utf8");
58
+ assert.match(pack, /schemas", "v1"/);
59
+ assert.match(pack, /release:pack omitted generated Config schemas/);
60
+ assert.match(pack, /templates/);
61
+ assert.match(pack, /capabilities/);
62
+ assert.match(pack, /release:pack omitted ForgeRelay-owned runtime resources/);
63
+ });
64
+
65
+ test("cross-platform cloud CI splits shared tests from bounded platform acceptance", async () => {
44
66
  const workflow = await readFile(resolve(repoRoot, ".github/workflows/ci.yml"), "utf8");
45
- assert.match(workflow, /node-version-file:\s*\.nvmrc/);
46
- assert.match(workflow, /run:\s*npm ci/);
47
- assert.match(workflow, /run:\s*npm run ci:verify/);
67
+ assert.match(workflow, /core-contract:/);
68
+ assert.match(workflow, /core-tests:/);
69
+ for (const shard of ["runtime-config", "workspace-mcp", "lsp", "subagent-ui-cli"]) {
70
+ assert.match(workflow, new RegExp(`\\b${shard}\\b`));
71
+ }
72
+ assert.match(workflow, /run:\s*npm run ci:contract/);
73
+ assert.match(workflow, /run:\s*npm run ci:test-shard -- \$\{\{ matrix\.shard \}\}/);
74
+ assert.match(workflow, /macos-platform:[\s\S]*run:\s*npm run ci:platform-tests/);
75
+ assert.match(workflow, /windows-platform:[\s\S]*run:\s*npm run ci:platform-tests/);
76
+ assert.match(workflow, /windows-platform:[\s\S]*timeout-minutes:\s*25/);
77
+ assert.doesNotMatch(workflow, /run:\s*npm run ci:verify/);
78
+ assert.doesNotMatch(workflow, /run:\s*npm test/);
48
79
  assert.doesNotMatch(workflow, /shell:/);
49
80
  assert.doesNotMatch(workflow, /run:\s*\|/);
50
- for (const duplicatedCommand of [
51
- "npm run release:check",
52
- "npm run typecheck",
53
- "npm test",
54
- "npm run build",
55
- "npm run lsp:interop",
56
- "node dist/cli.js doctor",
57
- ]) {
58
- assert.equal(
59
- workflow.includes(`run: ${duplicatedCommand}`),
60
- false,
61
- `cloud CI must delegate ${duplicatedCommand} through ci:verify`,
62
- );
63
- }
64
81
  });
65
82
 
66
- test("architecture gate treats the append-only release-note archive as an explicit flat-path exception", async () => {
83
+ test("architecture gate treats versioned public contract directories as explicit flat-path exceptions", async () => {
67
84
  const architecture = await readFile(resolve(repoRoot, "scripts/ci/architecture.mjs"), "utf8");
68
85
  assert.match(architecture, /DIRECT_FILE_LIMIT_EXEMPT_DIRS/);
69
86
  assert.match(architecture, /"docs\/releases"/);
87
+ assert.match(architecture, /"schemas\/v1"/);
88
+ assert.match(architecture, /directory !== "\." && dirs > MAX_DIRECT_DIRS/);
70
89
  });
71
90
 
72
91
  test("release parity sandbox gives architecture an isolated non-empty Git index", async () => {
@@ -111,9 +130,6 @@ test("release runtime and local parity share the checked-in Node contract", asyn
111
130
  const nodeVersion = (await readFile(resolve(repoRoot, ".nvmrc"), "utf8")).trim();
112
131
  assert.equal(nodeVersion, "22.19.0");
113
132
 
114
- const pkg = await readJson("package.json");
115
- assert.equal(pkg.scripts["ci:verify"], "node scripts/ci/verify.mjs");
116
-
117
133
  const source = await readFile(resolve(repoRoot, "scripts/release-parity.mjs"), "utf8");
118
134
  assert.match(source, /readFileSync\(join\(repoRoot, "\.nvmrc"\), "utf8"\)/);
119
135
  assert.match(source, /const NPM_VERSION = "11\.19\.1"/);
@@ -121,21 +137,67 @@ test("release runtime and local parity share the checked-in Node contract", asyn
121
137
  const ciWorkflow = await readFile(resolve(repoRoot, ".github/workflows/ci.yml"), "utf8");
122
138
  assert.match(ciWorkflow, /npm install --global npm@11\.19\.1/);
123
139
  assert.ok(source.includes('["npm", "ci", "--no-audit", "--no-fund"]'));
124
- assert.ok(source.includes('["npm", "run", "ci:verify"]'));
140
+ assert.ok(source.includes('["npm", "run", "ci:contract"]'));
141
+ for (const shard of ["runtime-config", "workspace-mcp", "lsp", "subagent-ui-cli"]) {
142
+ assert.match(source, new RegExp(`\\"${shard}\\"`));
143
+ }
144
+ assert.ok(source.includes('["npm", "run", "build"]'));
145
+ assert.ok(source.includes('["npm", "run", "traffic:audit"]'));
146
+ assert.ok(source.includes('["npm", "run", "lsp:interop"]'));
125
147
  assert.ok(source.includes('["npm", "run", "release:pack"]'));
126
- assert.doesNotMatch(source, /\["npm", "run", "typecheck"\]/);
148
+ assert.ok(source.includes('["npm", "run", "config:product-accept"]'));
149
+ assert.match(source, /FORGERELAY_ACCEPTANCE_ARTIFACT_DIR:\s*"\.release-artifacts"/);
150
+ assert.doesNotMatch(source, /\["npm", "run", "ci:verify"\]/);
127
151
  assert.doesNotMatch(source, /\["npm", "test"\]/);
128
152
  });
129
153
 
130
- test("cloud verification produces one reusable npm package on Linux", async () => {
154
+ test("cloud verification builds one npm artifact and reuses it across platform acceptance", async () => {
131
155
  const workflow = await readFile(resolve(repoRoot, ".github/workflows/ci.yml"), "utf8");
132
- assert.match(workflow, /if:\s*runner\.os == 'Linux'[\s\S]*run:\s*npm run release:pack/);
133
- assert.match(workflow, /uses:\s*actions\/upload-artifact@v7/);
156
+ assert.match(workflow, /package:[\s\S]*needs:\s*core-contract[\s\S]*run:\s*npm run build[\s\S]*run:\s*npm run traffic:audit[\s\S]*run:\s*npm run lsp:interop[\s\S]*run:\s*npm run release:pack/);
157
+ assert.equal((workflow.match(/run:\s*npm run release:pack/g) ?? []).length, 1);
158
+ assert.equal((workflow.match(/uses:\s*actions\/upload-artifact@v7/g) ?? []).length, 1);
159
+ assert.equal((workflow.match(/uses:\s*actions\/download-artifact@v7/g) ?? []).length, 3);
160
+ assert.match(workflow, /FORGERELAY_ACCEPTANCE_ARTIFACT_DIR:\s*\.release-package/);
161
+ assert.match(workflow, /windows-platform:[\s\S]*FORGERELAY_ACCEPTANCE_PREFIX:\s*\.release-installed/);
162
+ assert.equal((workflow.match(/run:\s*npm run ci:prepare-windows-product/g) ?? []).length, 1);
163
+ assert.equal((workflow.match(/run:\s*npm run traffic:audit/g) ?? []).length, 1);
164
+ assert.equal((workflow.match(/run:\s*npm run lsp:interop/g) ?? []).length, 1);
134
165
  assert.match(workflow, /name:\s*npm-package/);
135
166
  assert.match(workflow, /include-hidden-files:\s*true/);
136
167
  assert.match(workflow, /overwrite:\s*true/);
137
168
  });
138
169
 
170
+ test("packaged acceptance scripts can consume the one downloaded release artifact", async () => {
171
+ const helper = await readFile(resolve(repoRoot, "scripts/ci/gates/acceptance-artifact.mjs"), "utf8");
172
+ assert.match(helper, /FORGERELAY_ACCEPTANCE_ARTIFACT_DIR/);
173
+ assert.match(helper, /FORGERELAY_ACCEPTANCE_PREFIX/);
174
+ assert.match(helper, /exactly one \.tgz package/);
175
+
176
+ const prepareWindows = await readFile(
177
+ resolve(repoRoot, "scripts/ci/gates/prepare-windows-product.mjs"),
178
+ "utf8",
179
+ );
180
+ assert.match(prepareWindows, /resolveAcceptancePrefix/);
181
+ assert.match(prepareWindows, /"install", "--global", "--prefix"/);
182
+
183
+ for (const relativePath of [
184
+ "scripts/ci/config-v2-product-acceptance.mjs",
185
+ "scripts/ci/windows-product-acceptance.mjs",
186
+ "scripts/ci/pwsh-acceptance.mjs",
187
+ "scripts/ci/powershell51-acceptance.mjs",
188
+ "scripts/ci/cmd-acceptance.mjs",
189
+ ]) {
190
+ const source = await readFile(resolve(repoRoot, relativePath), "utf8");
191
+ assert.match(source, /resolveAcceptanceTarball/);
192
+ }
193
+ });
194
+
195
+ test("Windows product acceptance uses the supported Skills environment seam rather than a removed config key", async () => {
196
+ const source = await readFile(resolve(repoRoot, "scripts/ci/windows-product-acceptance.mjs"), "utf8");
197
+ assert.match(source, /FORGERELAY_SKILLS:\s*"0"/);
198
+ assert.doesNotMatch(source, /skillsEnabled\s*:/);
199
+ });
200
+
139
201
  test("release workflow is tag-only and promotes the verified npm artifact without rebuilding", async () => {
140
202
  const workflow = await readFile(resolve(repoRoot, ".github/workflows/release.yml"), "utf8");
141
203
  assert.doesNotMatch(workflow, /workflow_dispatch:/);
@@ -157,6 +219,10 @@ test("manual Windows shell acceptance can never publish a release", async () =>
157
219
  );
158
220
  assert.match(workflow, /workflow_dispatch:/);
159
221
  assert.match(workflow, /runs-on:\s*windows-2022/);
222
+ assert.match(workflow, /FORGERELAY_ACCEPTANCE_ARTIFACT_DIR:\s*\.release-artifacts/);
223
+ assert.match(workflow, /FORGERELAY_ACCEPTANCE_PREFIX:\s*\.release-installed/);
224
+ assert.equal((workflow.match(/run:\s*npm run release:pack/g) ?? []).length, 1);
225
+ assert.equal((workflow.match(/run:\s*npm run ci:prepare-windows-product/g) ?? []).length, 1);
160
226
  assert.match(workflow, /run:\s*npm run pwsh:accept/);
161
227
  assert.doesNotMatch(workflow, /release:publish/);
162
228
  assert.doesNotMatch(workflow, /npm publish/);
@@ -28,11 +28,28 @@ try {
28
28
  ["npm", "ci", "--no-audit", "--no-fund"],
29
29
  `Node ${NODE_VERSION} / npm ${NPM_VERSION} install`,
30
30
  );
31
- runNodeNpm(sandbox, env, ["npm", "run", "ci:verify"], "Cloud verification entrypoint");
31
+ runNodeNpm(sandbox, env, ["npm", "run", "ci:contract"], "Cloud core contract");
32
+ for (const shard of ["runtime-config", "workspace-mcp", "lsp", "subagent-ui-cli"]) {
33
+ runNodeNpm(
34
+ sandbox,
35
+ env,
36
+ ["npm", "run", "ci:test-shard", "--", shard],
37
+ `Cloud core test shard: ${shard}`,
38
+ );
39
+ }
40
+ runNodeNpm(sandbox, env, ["npm", "run", "build"], "Cloud package build");
41
+ runNodeNpm(sandbox, env, ["npm", "run", "traffic:audit"], "Cloud traffic audit");
42
+ runNodeNpm(sandbox, env, ["npm", "run", "lsp:interop"], "Cloud LSP interoperability");
32
43
  runNodeNpm(sandbox, env, ["npm", "run", "release:pack"], "Cloud release packaging");
44
+ runNodeNpm(
45
+ sandbox,
46
+ { ...env, FORGERELAY_ACCEPTANCE_ARTIFACT_DIR: ".release-artifacts" },
47
+ ["npm", "run", "config:product-accept"],
48
+ "Cloud Linux packaged product acceptance",
49
+ );
33
50
 
34
51
  console.log(
35
- `Release parity passed through ci:verify and release:pack on Node ${NODE_VERSION} / npm ${NPM_VERSION}.`,
52
+ `Release parity passed through the split Linux cloud gates and release artifact acceptance on Node ${NODE_VERSION} / npm ${NPM_VERSION}.`,
36
53
  );
37
54
  } finally {
38
55
  rmSync(sandbox, {
@@ -0,0 +1,12 @@
1
+ # ForgeRelay cmd.exe Instructions
2
+
3
+ ForgeRelay executes Agent commands and Hooks with `cmd.exe` for this runtime. Write cmd syntax, not PowerShell or Bash syntax.
4
+
5
+ - Environment variables use `%NAME%`; mutate them with cmd syntax such as `set NAME=value`.
6
+ - Preserve and inspect native process status with `%ERRORLEVEL%` when correctness depends on success.
7
+ - Command chaining, redirection, `%` expansion, `^` escaping, and parentheses are cmd-specific. Do not reuse Bash or PowerShell escaping rules.
8
+ - In compound or parenthesized command blocks, `%NAME%` may be expanded before later commands run. Use delayed expansion (`setlocal EnableDelayedExpansion` with `!NAME!`) only when needed, and remember that enabling it changes how literal `!` characters are handled.
9
+ - Quote executable paths and arguments using cmd semantics, especially when paths contain spaces or metacharacters.
10
+ - Do not emit PowerShell cmdlets such as `Get-ChildItem`, `$env:NAME`, or `&` unless explicitly launching PowerShell as a child process.
11
+ - Do not assume Unix aliases or utilities exist. Cross-platform executables such as `git`, `node`, `npm`, or `rg` may be invoked directly when installed.
12
+ - Do not silently switch to another shell merely because a command is easier there. The effective ForgeRelay command language remains cmd unless the owner changes the runtime configuration.
@@ -0,0 +1,11 @@
1
+ # ForgeRelay fish Instructions
2
+
3
+ ForgeRelay executes Agent commands and Hooks with fish for this runtime. Write fish syntax, not Bash or zsh syntax.
4
+
5
+ - Set shell variables with `set NAME value`; export environment variables with `set -gx NAME value` or another scope appropriate to the command.
6
+ - Inspect the previous command status with `$status`. Save it before another command runs if later logic depends on it.
7
+ - fish does not use Bash-style `VAR=value command`, `export NAME=value`, `$(...)`, or `[[ ... ]]` syntax. Use fish-native variable assignment, command substitution `(command)`, and conditionals.
8
+ - Use `and` / `or` or fish control flow deliberately; do not emit Bash-only chaining syntax when compatibility with the selected fish runtime matters.
9
+ - fish list variables and expansions differ from Bash arrays. Do not copy Bash array syntax.
10
+ - Quote paths and expansions when whitespace or wildcard interpretation would change the intended argument boundaries.
11
+ - ForgeRelay already inherits the server process environment. Do not source the user's interactive fish configuration, prompt, or plugins merely to recover PATH, aliases, or abbreviations.
@@ -0,0 +1,14 @@
1
+ # ForgeRelay Windows PowerShell 5.1 Instructions
2
+
3
+ ForgeRelay executes Agent commands and Hooks with Windows PowerShell 5.1 (`powershell.exe`) for this runtime. Use Windows PowerShell syntax, not Bash syntax and not PowerShell 7-only syntax.
4
+
5
+ - Environment variables use `$env:NAME`; do not use `export NAME=...`.
6
+ - Prefer explicit PowerShell cmdlets when semantics matter. Do not pass Unix flags to aliases such as `rm`, `ls`, or `cat`.
7
+ - Use `Get-Command` for command discovery and an explicit `.exe` name when native executable semantics matter.
8
+ - Use the call operator `&` for an executable stored in a variable or a quoted executable path.
9
+ - Windows PowerShell 5.1 does **not** support PowerShell 7 pipeline-chain operators `&&` and `||`. Use explicit control flow and inspect status instead.
10
+ - Native process success/failure is represented by `$LASTEXITCODE`; cmdlet errors use PowerShell error semantics. Do not conflate them.
11
+ - When cmdlet failure must stop execution, use terminating-error behavior deliberately rather than assuming every error terminates.
12
+ - Windows PowerShell 5.1 native argument parsing differs from modern PowerShell. Quote Windows paths and native arguments deliberately; do not copy Bash escaping rules.
13
+ - PowerShell pipelines pass objects between cmdlets. Be explicit when crossing into native executables.
14
+ - ForgeRelay inherits the server environment. Do not source the user's PowerShell profile merely to recover PATH, aliases, modules, or prompt configuration.
@@ -0,0 +1,14 @@
1
+ # ForgeRelay PowerShell 7 Instructions
2
+
3
+ ForgeRelay executes Agent commands and Hooks with PowerShell 7 (`pwsh`) for this runtime. Use PowerShell syntax, not Bash syntax.
4
+
5
+ - Environment variables use `$env:NAME`; do not use `export NAME=...`.
6
+ - Prefer explicit PowerShell cmdlets when semantics matter. Do not pass Unix flags to aliases such as `rm`, `ls`, or `cat`.
7
+ - Use `Get-Command` to discover commands. Use an explicit `.exe` name when native executable semantics matter.
8
+ - Use the call operator `&` for an executable stored in a variable or for a quoted executable path.
9
+ - Quote Windows paths deliberately. Do not copy Bash escaping rules into PowerShell native-command arguments.
10
+ - Native program success/failure is represented by `$LASTEXITCODE`; PowerShell cmdlet errors use PowerShell error semantics. Do not conflate the two.
11
+ - When cmdlet failure must stop the command, use terminating-error behavior deliberately rather than assuming every error terminates execution.
12
+ - PowerShell pipelines pass objects between cmdlets. Be explicit when crossing between object pipelines and native executables.
13
+ - PowerShell 7 supports pipeline-chain operators such as `&&` and `||`, but use them only when the reported runtime version supports them.
14
+ - ForgeRelay already inherits the server process environment. Do not source the user's PowerShell profile merely to recover PATH, aliases, modules, or prompt configuration.
@@ -0,0 +1,11 @@
1
+ # ForgeRelay zsh Instructions
2
+
3
+ ForgeRelay executes Agent commands and Hooks with zsh for this runtime. Use zsh syntax deliberately rather than assuming every Bash-specific construct behaves identically.
4
+
5
+ - Environment variables are exported with zsh/POSIX syntax such as `export NAME=value`; shell-local variables do not need `export`.
6
+ - Inspect command success with `$?` when it is needed, and preserve the relevant status before running another command.
7
+ - Quote parameter expansions unless zsh-specific splitting or globbing is intentionally required. Do not depend on Bash-only word-splitting behavior.
8
+ - zsh arrays are 1-indexed by default and have zsh-specific expansion semantics. Do not copy Bash array expressions without checking them.
9
+ - Use zsh conditionals, glob qualifiers, and parameter-expansion features only when they make the command clearer; prefer portable command-line tools when shell-specific syntax is unnecessary.
10
+ - Do not assume Bash-only builtins or options such as `shopt` exist.
11
+ - ForgeRelay already inherits the server process environment. Do not source the user's `.zshrc`, Oh My Zsh, prompt theme, plugins, or interactive profile merely to recover PATH or aliases.
@@ -1,65 +0,0 @@
1
- import { constants as fsConstants } from "node:fs";
2
- import { access, mkdir, writeFile } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
4
- const TEMPLATE_REPOSITORY_RAW = "https://raw.githubusercontent.com/Akira-TL/forgerelay";
5
- const POWERSHELL_SKILL_PATH = "templates/skills/powershell/SKILL.md";
6
- export function powerShellSkillPath(configDir) {
7
- return join(resolve(configDir), "skills", "powershell", "SKILL.md");
8
- }
9
- export function powerShellSkillTemplateUrl(version) {
10
- const normalizedVersion = version.trim().replace(/^v/, "");
11
- if (!normalizedVersion)
12
- throw new Error("ForgeRelay version is required to fetch the PowerShell Skill.");
13
- return `${TEMPLATE_REPOSITORY_RAW}/v${encodeURIComponent(normalizedVersion)}/${POWERSHELL_SKILL_PATH}`;
14
- }
15
- export async function seedPowerShellSkill(options) {
16
- const path = powerShellSkillPath(options.configDir);
17
- const sourceUrl = powerShellSkillTemplateUrl(options.version);
18
- if (!options.reset && await fileExists(path)) {
19
- return { path, status: "preserved", sourceUrl };
20
- }
21
- await mkdir(join(resolve(options.configDir), "skills", "powershell"), {
22
- recursive: true,
23
- mode: 0o700,
24
- });
25
- const fetchText = options.fetchText ?? fetchSkillText;
26
- try {
27
- const content = await fetchText(sourceUrl);
28
- if (!content.trim())
29
- throw new Error("template response was empty");
30
- await writeFile(path, content.endsWith("\n") ? content : `${content}\n`, {
31
- encoding: "utf8",
32
- mode: 0o600,
33
- flag: options.reset ? "w" : "wx",
34
- });
35
- return { path, status: "created", sourceUrl };
36
- }
37
- catch (error) {
38
- if (!options.reset && await fileExists(path)) {
39
- return { path, status: "preserved", sourceUrl };
40
- }
41
- return {
42
- path,
43
- status: "failed",
44
- sourceUrl,
45
- error: error instanceof Error ? error.message : String(error),
46
- };
47
- }
48
- }
49
- async function fetchSkillText(url) {
50
- const response = await fetch(url, {
51
- headers: { "user-agent": "forgerelay-powershell-skill" },
52
- });
53
- if (!response.ok)
54
- throw new Error(`HTTP ${response.status} while fetching ${url}`);
55
- return response.text();
56
- }
57
- async function fileExists(path) {
58
- try {
59
- await access(path, fsConstants.F_OK);
60
- return true;
61
- }
62
- catch {
63
- return false;
64
- }
65
- }