@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
@@ -0,0 +1,77 @@
1
+ import { liveGeneralConfigState } from "../config.js";
2
+ import { ExternalMcpConfigRegistry } from "../external-mcp-registry.js";
3
+ import { resolveHooksConfig } from "../resolution/hooks.js";
4
+ import { resolveLanguageServersConfig } from "../resolution/language-servers.js";
5
+ import { resolveSubagentProfilesConfigSources } from "../../../subagents/profiles.js";
6
+ import { resolveProjectContext } from "../../../workspaces/state/project-context.js";
7
+ export async function liveWorkspaceConfigPanelState(config, workspaceRoot) {
8
+ const general = liveGeneralConfigState(config);
9
+ const restartRequired = Object.values(general.applied.fields)
10
+ .filter((field) => field.restartRequired)
11
+ .map((field) => ({
12
+ logicalPath: field.logicalPath,
13
+ configuredValue: field.configuredValue,
14
+ appliedValue: field.appliedValue,
15
+ }));
16
+ const project = await resolveProjectContext(config.configDir, workspaceRoot);
17
+ const mcp = new ExternalMcpConfigRegistry({
18
+ configDir: config.configDir,
19
+ environment: process.env,
20
+ sourceRuntime: config.configRuntime.sources,
21
+ }).resolveConfiguration({
22
+ projectSharedConfigDir: project.sharedConfigDir,
23
+ projectLocalConfigDir: project.localConfigDir,
24
+ });
25
+ const languageServers = await resolveLanguageServersConfig({
26
+ configDir: config.configDir,
27
+ project,
28
+ environment: process.env,
29
+ sourceRuntime: config.configRuntime.sources,
30
+ });
31
+ const hooks = await resolveHooksConfig({
32
+ configDir: config.configDir,
33
+ project,
34
+ sourceRuntime: config.configRuntime.sources,
35
+ });
36
+ const subagents = resolveSubagentProfilesConfigSources({
37
+ configDir: config.configDir,
38
+ sourceRuntime: config.configRuntime.sources,
39
+ projectSharedConfigDir: project.sharedConfigDir,
40
+ projectLocalConfigDir: project.localConfigDir,
41
+ });
42
+ const issues = uniqueLiveIssues([mcp, languageServers, hooks, subagents]);
43
+ if (restartRequired.length === 0 && !general.source && issues.length === 0)
44
+ return undefined;
45
+ return {
46
+ ...(restartRequired.length > 0 ? { restartRequired } : {}),
47
+ ...(general.source ? { source: general.source } : {}),
48
+ ...(issues.length > 0 ? { issues } : {}),
49
+ };
50
+ }
51
+ function uniqueLiveIssues(domains) {
52
+ const seen = new Set();
53
+ const issues = [];
54
+ for (const domain of domains) {
55
+ for (const diagnostic of domain.diagnostics) {
56
+ if (diagnostic.severity !== "error")
57
+ continue;
58
+ const issue = liveIssue(domain.domain, diagnostic);
59
+ const key = `${issue.domain}\0${issue.code}\0${issue.source}\0${issue.message}\0${issue.usingLastKnownGood}`;
60
+ if (seen.has(key))
61
+ continue;
62
+ seen.add(key);
63
+ issues.push(issue);
64
+ }
65
+ }
66
+ return issues.sort((left, right) => left.domain.localeCompare(right.domain) || left.source.localeCompare(right.source) || left.code.localeCompare(right.code));
67
+ }
68
+ function liveIssue(domain, diagnostic) {
69
+ return {
70
+ domain,
71
+ severity: "error",
72
+ code: diagnostic.code,
73
+ source: diagnostic.source.location ?? diagnostic.source.id,
74
+ usingLastKnownGood: diagnostic.usingLastKnownGood === true,
75
+ message: diagnostic.message,
76
+ };
77
+ }
@@ -0,0 +1,93 @@
1
+ import { generalConfigDefinition } from "../definition/general-config.js";
2
+ import { resolveConfigDomain } from "./resolver.js";
3
+ export function resolveGeneralConfig(input = {}) {
4
+ const env = input.env ?? process.env;
5
+ const sources = [];
6
+ appendFileSource(sources, {
7
+ expectedScope: "user",
8
+ prepared: input.userSource,
9
+ value: input.user,
10
+ sourcePath: input.userSourcePath,
11
+ });
12
+ appendFileSource(sources, {
13
+ expectedScope: "project",
14
+ prepared: input.projectSource,
15
+ value: input.project,
16
+ sourcePath: input.projectSourcePath,
17
+ });
18
+ appendFileSource(sources, {
19
+ expectedScope: "project-local",
20
+ prepared: input.projectLocalSource,
21
+ value: input.projectLocal,
22
+ sourcePath: input.projectLocalSourcePath,
23
+ });
24
+ sources.push(environmentSource(env));
25
+ if (input.cli !== undefined) {
26
+ sources.push({
27
+ id: "runtime:cli",
28
+ scope: "runtime",
29
+ kind: "cli",
30
+ priority: 20,
31
+ value: input.cli,
32
+ });
33
+ }
34
+ return resolveConfigDomain({
35
+ definition: generalConfigDefinition,
36
+ sources,
37
+ environment: env,
38
+ });
39
+ }
40
+ function appendFileSource(sources, input) {
41
+ if (input.prepared !== undefined && input.value !== undefined) {
42
+ throw new Error(`General configuration received duplicate ${input.expectedScope} sources.`);
43
+ }
44
+ if (input.prepared !== undefined) {
45
+ if (input.prepared.scope !== input.expectedScope || input.prepared.kind !== "file") {
46
+ throw new Error(`General configuration ${input.expectedScope} source must be a file source at the matching scope.`);
47
+ }
48
+ sources.push(input.prepared);
49
+ return;
50
+ }
51
+ if (input.value === undefined)
52
+ return;
53
+ sources.push({
54
+ id: `${input.expectedScope}:config`,
55
+ scope: input.expectedScope,
56
+ kind: "file",
57
+ location: input.sourcePath,
58
+ priority: 0,
59
+ value: input.value,
60
+ });
61
+ }
62
+ function environmentSource(env) {
63
+ const values = {};
64
+ try {
65
+ for (const [name, field] of Object.entries(generalConfigDefinition.fields)) {
66
+ const readEnv = field.runtimeOverride?.readEnv;
67
+ if (!readEnv)
68
+ continue;
69
+ const value = readEnv(env);
70
+ if (value !== undefined)
71
+ values[name] = value;
72
+ }
73
+ return {
74
+ id: "runtime:environment",
75
+ scope: "runtime",
76
+ kind: "environment",
77
+ priority: 10,
78
+ value: values,
79
+ };
80
+ }
81
+ catch (error) {
82
+ return {
83
+ id: "runtime:environment",
84
+ scope: "runtime",
85
+ kind: "environment",
86
+ priority: 10,
87
+ error: {
88
+ code: "invalid_source",
89
+ message: error instanceof Error ? error.message : "Runtime environment configuration is invalid.",
90
+ },
91
+ };
92
+ }
93
+ }
@@ -0,0 +1,315 @@
1
+ import { join } from "node:path";
2
+ import { hooksConfigDefinition, normalizeLegacyHookEntries, } from "../../../mcp/hooks/config.js";
3
+ import { ConfigSourceRuntime } from "../runtime/source-refresh.js";
4
+ import { refreshLegacyUserConfigField } from "./project-sources.js";
5
+ import { resolveConfigDomain } from "./resolver.js";
6
+ const CANONICAL_PRIORITY = 100;
7
+ const LEGACY_PRIORITY = 0;
8
+ const LEGACY_DEPRECATION = {
9
+ since: "1.2.0",
10
+ removeIn: "1.4.0",
11
+ replacement: "hooks/",
12
+ };
13
+ export async function resolveHooksConfig(input) {
14
+ const sources = [];
15
+ const refreshDiagnostics = [];
16
+ const sourceRuntime = input.sourceRuntime ?? new ConfigSourceRuntime();
17
+ if (input.legacyUser && isRecord(input.legacyUser) && Object.keys(input.legacyUser).length > 0) {
18
+ sources.push({
19
+ id: "legacy:user:hooks",
20
+ scope: "user",
21
+ kind: "file",
22
+ priority: LEGACY_PRIORITY,
23
+ ...(input.configDir ? { location: join(input.configDir, "config.json/hooks") } : {}),
24
+ normalized: true,
25
+ deprecation: LEGACY_DEPRECATION,
26
+ value: { hooks: normalizeLegacyHookEntries(input.legacyUser) },
27
+ });
28
+ }
29
+ if (input.configDir) {
30
+ const legacyInline = readLegacyInlineHooks(sourceRuntime, input.configDir);
31
+ if (legacyInline.source)
32
+ sources.push(legacyInline.source);
33
+ if (legacyInline.diagnostic)
34
+ refreshDiagnostics.push(legacyInline.diagnostic);
35
+ const legacyUserFile = readLegacyHookAggregate(sourceRuntime, "legacy:user:hooks.json", "user", join(input.configDir, "hooks.json"));
36
+ if (legacyUserFile.source)
37
+ sources.push(legacyUserFile.source);
38
+ if (legacyUserFile.diagnostic)
39
+ refreshDiagnostics.push(legacyUserFile.diagnostic);
40
+ appendHookDirectory(sources, refreshDiagnostics, sourceRuntime, "user", join(input.configDir, "hooks"));
41
+ }
42
+ const sharedConfigDir = input.project?.sharedConfigDir ?? input.projectSharedConfigDir;
43
+ if (sharedConfigDir) {
44
+ const legacyProject = readLegacyHookAggregate(sourceRuntime, "legacy:project:hooks.json", "project", join(sharedConfigDir, "hooks.json"));
45
+ if (legacyProject.source)
46
+ sources.push(legacyProject.source);
47
+ if (legacyProject.diagnostic)
48
+ refreshDiagnostics.push(legacyProject.diagnostic);
49
+ appendHookDirectory(sources, refreshDiagnostics, sourceRuntime, "project", join(sharedConfigDir, "hooks"));
50
+ }
51
+ if (input.project) {
52
+ appendHookDirectory(sources, refreshDiagnostics, sourceRuntime, "project-local", join(input.project.localConfigDir, "hooks"));
53
+ }
54
+ const resolution = resolveConfigDomain({
55
+ definition: hooksConfigDefinition,
56
+ sources,
57
+ });
58
+ resolution.diagnostics = mergeDiagnostics(resolution.diagnostics, refreshDiagnostics);
59
+ return resolution;
60
+ }
61
+ export function effectiveHookConfigEntries(resolution) {
62
+ const hooks = isRecord(resolution.values.hooks) ? resolution.values.hooks : {};
63
+ const entries = Object.entries(hooks).flatMap(([name, value]) => {
64
+ const provenance = resolution.entries[`hooks.${name}`];
65
+ if (!provenance || !Array.isArray(value))
66
+ return [];
67
+ const scope = provenance.effective.source.scope;
68
+ if (scope !== "user" && scope !== "project" && scope !== "project-local")
69
+ return [];
70
+ return [{
71
+ name,
72
+ scope,
73
+ sourcePriority: provenance.effective.source.priority,
74
+ ...(provenance.effective.source.location ? { sourceLocation: provenance.effective.source.location } : {}),
75
+ entries: value,
76
+ }];
77
+ });
78
+ return entries.sort((left, right) => {
79
+ const scope = hookScopeRank(left.scope) - hookScopeRank(right.scope);
80
+ if (scope !== 0)
81
+ return scope;
82
+ const priority = left.sourcePriority - right.sourcePriority;
83
+ if (priority !== 0)
84
+ return priority;
85
+ const leftOrder = left.entries[0]?.order ?? Number.MAX_SAFE_INTEGER;
86
+ const rightOrder = right.entries[0]?.order ?? Number.MAX_SAFE_INTEGER;
87
+ if (leftOrder !== rightOrder)
88
+ return leftOrder - rightOrder;
89
+ const location = (left.sourceLocation ?? "").localeCompare(right.sourceLocation ?? "");
90
+ return location !== 0 ? location : left.name.localeCompare(right.name);
91
+ });
92
+ }
93
+ function hookScopeRank(scope) {
94
+ return scope === "user" ? 0 : scope === "project" ? 1 : 2;
95
+ }
96
+ function appendHookDirectory(sources, diagnostics, sourceRuntime, scope, directory) {
97
+ const refreshed = sourceRuntime.refreshDirectory({
98
+ key: `hooks:${scope}:${directory}`,
99
+ directory,
100
+ include: (name) => name.endsWith(".json"),
101
+ parse: (_name, content) => JSON.parse(content),
102
+ parseIssue: {
103
+ code: "invalid_source",
104
+ message: "Hook configuration file is not valid JSON.",
105
+ },
106
+ readIssue: {
107
+ code: "invalid_source",
108
+ message: "Hook configuration file could not be read.",
109
+ },
110
+ validate: (value, name, location) => {
111
+ const entryKey = name.slice(0, -5);
112
+ const validation = resolveConfigDomain({
113
+ definition: hooksConfigDefinition,
114
+ sources: [canonicalHookSource(scope, location, entryKey, value)],
115
+ });
116
+ const error = validation.diagnostics.find((diagnostic) => diagnostic.severity === "error");
117
+ if (!error)
118
+ return undefined;
119
+ return {
120
+ code: error.code === "missing_environment" ? "missing_environment" : "invalid_source",
121
+ message: error.message,
122
+ };
123
+ },
124
+ });
125
+ if (refreshed.state === "invalid" && refreshed.issue) {
126
+ diagnostics.push({
127
+ severity: "error",
128
+ code: refreshed.issue.code,
129
+ source: {
130
+ id: `canonical:${scope}:hooks-directory`,
131
+ scope,
132
+ kind: "file",
133
+ location: refreshed.directory,
134
+ priority: CANONICAL_PRIORITY,
135
+ },
136
+ message: refreshed.issue.message,
137
+ diagnosticChanged: refreshed.diagnosticChanged,
138
+ });
139
+ }
140
+ for (const unit of refreshed.units) {
141
+ const location = unit.status.path;
142
+ const entryKey = unit.name.slice(0, -5);
143
+ if (unit.value !== undefined) {
144
+ const source = canonicalHookSource(scope, location, entryKey, unit.value);
145
+ sources.push(source);
146
+ if (unit.status.state === "invalid" && unit.issue) {
147
+ diagnostics.push({
148
+ severity: "error",
149
+ code: unit.issue.code,
150
+ source: sourceReference(source),
151
+ message: unit.issue.message,
152
+ usingLastKnownGood: true,
153
+ diagnosticChanged: unit.status.diagnosticChanged,
154
+ });
155
+ }
156
+ continue;
157
+ }
158
+ if (unit.status.state === "invalid") {
159
+ sources.push(invalidHookSource(scope, location, entryKey, unit.issue?.message ?? "Hook configuration file is invalid.", unit.issue?.code));
160
+ }
161
+ }
162
+ }
163
+ function canonicalHookSource(scope, location, entryKey, value) {
164
+ return {
165
+ id: `canonical:${scope}:hook:${entryKey}`,
166
+ scope,
167
+ kind: "file",
168
+ location,
169
+ priority: CANONICAL_PRIORITY,
170
+ entryKey,
171
+ shadowsLowerPriorityKeys: [`hooks.${entryKey}`],
172
+ value,
173
+ };
174
+ }
175
+ function invalidHookSource(scope, location, entryKey, message, code = "invalid_source") {
176
+ return {
177
+ id: `canonical:${scope}:hook:${entryKey}`,
178
+ scope,
179
+ kind: "file",
180
+ location,
181
+ priority: CANONICAL_PRIORITY,
182
+ entryKey,
183
+ shadowsLowerPriorityKeys: [`hooks.${entryKey}`],
184
+ error: { code, message },
185
+ };
186
+ }
187
+ function readLegacyInlineHooks(sourceRuntime, configDir) {
188
+ const location = join(configDir, "config.json");
189
+ const refreshed = refreshLegacyUserConfigField({
190
+ sourceRuntime,
191
+ configDir,
192
+ field: "hooks",
193
+ parse: (value) => value === undefined ? undefined : normalizeLegacyHookEntries(value),
194
+ invalidMessage: "Legacy inline Hook configuration is invalid.",
195
+ });
196
+ if (refreshed.status.state === "missing")
197
+ return {};
198
+ const source = refreshed.value === undefined
199
+ ? undefined
200
+ : legacyHookSource("legacy:user:config-hooks", "user", location, refreshed.value);
201
+ if (refreshed.status.state !== "invalid" || !refreshed.issue)
202
+ return source ? { source } : {};
203
+ if (refreshed.issueScope === "container")
204
+ return source ? { source } : {};
205
+ if (source) {
206
+ return {
207
+ source,
208
+ diagnostic: {
209
+ severity: "error",
210
+ code: refreshed.issue.code,
211
+ source: sourceReference(source),
212
+ message: refreshed.issue.message,
213
+ usingLastKnownGood: refreshed.status.usingLastKnownGood,
214
+ diagnosticChanged: refreshed.status.diagnosticChanged,
215
+ },
216
+ };
217
+ }
218
+ return {
219
+ source: {
220
+ id: "legacy:user:config-hooks",
221
+ scope: "user",
222
+ kind: "file",
223
+ location,
224
+ priority: LEGACY_PRIORITY,
225
+ deprecation: LEGACY_DEPRECATION,
226
+ error: {
227
+ code: refreshed.issue.code,
228
+ message: refreshed.issue.message,
229
+ },
230
+ },
231
+ };
232
+ }
233
+ function readLegacyHookAggregate(sourceRuntime, id, scope, location) {
234
+ const refreshed = sourceRuntime.refreshFile({
235
+ key: `hooks:${id}:${location}`,
236
+ path: location,
237
+ parse: (raw) => normalizeLegacyHookEntries(JSON.parse(raw)),
238
+ parseIssue: { code: "invalid_source", message: "Legacy Hook configuration is invalid." },
239
+ readIssue: { code: "invalid_source", message: "Legacy Hook configuration could not be read." },
240
+ validate: (value) => {
241
+ const validation = resolveConfigDomain({
242
+ definition: hooksConfigDefinition,
243
+ sources: [legacyHookSource(id, scope, location, value)],
244
+ });
245
+ const error = validation.diagnostics.find((diagnostic) => diagnostic.severity === "error");
246
+ return error ? { code: "invalid_source", message: error.message } : undefined;
247
+ },
248
+ });
249
+ if (refreshed.status.state === "missing")
250
+ return {};
251
+ if (refreshed.value !== undefined) {
252
+ const source = legacyHookSource(id, scope, location, refreshed.value);
253
+ if (refreshed.status.state !== "invalid" || !refreshed.issue)
254
+ return { source };
255
+ return {
256
+ source,
257
+ diagnostic: {
258
+ severity: "error",
259
+ code: refreshed.issue.code,
260
+ source: sourceReference(source),
261
+ message: refreshed.issue.message,
262
+ usingLastKnownGood: true,
263
+ diagnosticChanged: refreshed.status.diagnosticChanged,
264
+ },
265
+ };
266
+ }
267
+ return {
268
+ source: {
269
+ id,
270
+ scope,
271
+ kind: "file",
272
+ location,
273
+ priority: LEGACY_PRIORITY,
274
+ deprecation: LEGACY_DEPRECATION,
275
+ error: {
276
+ code: refreshed.issue?.code ?? "invalid_source",
277
+ message: refreshed.issue?.message ?? "Legacy Hook configuration is invalid.",
278
+ },
279
+ },
280
+ };
281
+ }
282
+ function legacyHookSource(id, scope, location, value) {
283
+ return {
284
+ id,
285
+ scope,
286
+ kind: "file",
287
+ location,
288
+ priority: LEGACY_PRIORITY,
289
+ normalized: true,
290
+ deprecation: LEGACY_DEPRECATION,
291
+ value: { hooks: value },
292
+ };
293
+ }
294
+ function sourceReference(source) {
295
+ return {
296
+ id: source.id,
297
+ scope: source.scope,
298
+ kind: source.kind,
299
+ ...(source.location ? { location: source.location } : {}),
300
+ priority: source.priority,
301
+ };
302
+ }
303
+ function mergeDiagnostics(left, right) {
304
+ const seen = new Set();
305
+ return [...left, ...right].filter((diagnostic) => {
306
+ const key = `${diagnostic.source.id}\0${diagnostic.code}\0${diagnostic.message}\0${diagnostic.usingLastKnownGood === true}`;
307
+ if (seen.has(key))
308
+ return false;
309
+ seen.add(key);
310
+ return true;
311
+ });
312
+ }
313
+ function isRecord(value) {
314
+ return typeof value === "object" && value !== null && !Array.isArray(value);
315
+ }