@aiwg/cli 2026.10.3 → 2026.10.5

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 (279) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/model-capabilities.v1.json +96 -4
  3. package/bin/aiwg.mjs +50 -27
  4. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  5. package/dist/src/artifacts/browser-export.js +6 -0
  6. package/dist/src/artifacts/checksum-manifest.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +173 -92
  8. package/dist/src/artifacts/citation-table-schema.js +59 -0
  9. package/dist/src/artifacts/cli.js +69 -18
  10. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  11. package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
  12. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  13. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  14. package/dist/src/artifacts/discovery-eval.js +6 -4
  15. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  16. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  17. package/dist/src/artifacts/framework-capabilities.js +171 -0
  18. package/dist/src/artifacts/framework-index-paths.js +73 -0
  19. package/dist/src/artifacts/framework-source-digest.js +75 -0
  20. package/dist/src/artifacts/index-builder.js +286 -131
  21. package/dist/src/artifacts/index-files.js +132 -18
  22. package/dist/src/artifacts/index-preparation.js +130 -0
  23. package/dist/src/artifacts/index-refresh.js +591 -0
  24. package/dist/src/artifacts/index-worker.mjs +11 -0
  25. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  26. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  27. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  28. package/dist/src/artifacts/query-engine.js +410 -88
  29. package/dist/src/artifacts/relations-builder.js +168 -0
  30. package/dist/src/artifacts/relations.js +148 -0
  31. package/dist/src/artifacts/stats.js +5 -2
  32. package/dist/src/artifacts/types.js +30 -10
  33. package/dist/src/artifacts/watcher.js +1 -1
  34. package/dist/src/auth/credential-store.js +2 -1
  35. package/dist/src/catalog/cli.d.mts +5 -0
  36. package/dist/src/channel/manager.d.mts +20 -0
  37. package/dist/src/channel/manager.mjs +19 -2
  38. package/dist/src/cli/command-log.js +2 -3
  39. package/dist/src/cli/handlers/artifacts.js +3 -0
  40. package/dist/src/cli/handlers/auth.js +3 -0
  41. package/dist/src/cli/handlers/decision.js +19 -12
  42. package/dist/src/cli/handlers/diagnose.js +4 -3
  43. package/dist/src/cli/handlers/effect.js +4 -2
  44. package/dist/src/cli/handlers/evidence.js +3 -0
  45. package/dist/src/cli/handlers/help.js +1 -0
  46. package/dist/src/cli/handlers/index.js +3 -1
  47. package/dist/src/cli/handlers/init.js +4 -1
  48. package/dist/src/cli/handlers/lint.js +5 -0
  49. package/dist/src/cli/handlers/models.js +104 -8
  50. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  51. package/dist/src/cli/handlers/refresh.js +12 -0
  52. package/dist/src/cli/handlers/resource-versions.js +3 -0
  53. package/dist/src/cli/handlers/rules.js +195 -0
  54. package/dist/src/cli/handlers/run.js +4 -0
  55. package/dist/src/cli/handlers/runtime-info.js +1 -2
  56. package/dist/src/cli/handlers/script-runner.js +3 -2
  57. package/dist/src/cli/handlers/session.js +19 -9
  58. package/dist/src/cli/handlers/sessions.js +137 -43
  59. package/dist/src/cli/handlers/setup-manifest.js +39 -44
  60. package/dist/src/cli/handlers/setup.js +41 -0
  61. package/dist/src/cli/handlers/steward.js +11 -0
  62. package/dist/src/cli/handlers/subcommands.js +57 -2
  63. package/dist/src/cli/handlers/uhp.js +3 -0
  64. package/dist/src/cli/handlers/use.js +294 -55
  65. package/dist/src/cli/handlers/utilities.js +26 -10
  66. package/dist/src/cli/handlers/version.js +8 -0
  67. package/dist/src/cli/router.js +15 -1
  68. package/dist/src/cli/services/deployment-verification.js +19 -9
  69. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  70. package/dist/src/cli/skill-usage.js +2 -3
  71. package/dist/src/config/aiwg-config.js +87 -37
  72. package/dist/src/config/cli.js +111 -5
  73. package/dist/src/config/decision-config.js +106 -0
  74. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  75. package/dist/src/config/gitignore.js +50 -0
  76. package/dist/src/config/index-graph-overrides.js +36 -0
  77. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  78. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  79. package/dist/src/config/user-config-dir.d.mts +9 -0
  80. package/dist/src/config/user-config.js +2 -1
  81. package/dist/src/config/user-registry.js +23 -15
  82. package/dist/src/config/user-state.js +7 -0
  83. package/dist/src/decision/ask.js +203 -43
  84. package/dist/src/decision/context-live-qualification.js +2 -2
  85. package/dist/src/decision/driver.js +13 -8
  86. package/dist/src/decision/ensemble/aggregate.js +23 -10
  87. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  88. package/dist/src/decision/entry.js +2 -15
  89. package/dist/src/decision/graph-live-qualification.js +1 -1
  90. package/dist/src/decision/graph-run.js +9 -0
  91. package/dist/src/decision/heldout/collector.js +5 -13
  92. package/dist/src/decision/heldout/contract.js +30 -7
  93. package/dist/src/decision/jev-credentials.js +15 -1
  94. package/dist/src/decision/ledger.js +131 -28
  95. package/dist/src/decision/offload-activation.js +137 -0
  96. package/dist/src/decision/offload.js +550 -0
  97. package/dist/src/decision/qualification/load.js +4 -6
  98. package/dist/src/decision/savings.js +28 -0
  99. package/dist/src/effects/types.js +5 -1
  100. package/dist/src/effects/verifiers/git.js +2 -1
  101. package/dist/src/effects/verifiers/tracker.js +26 -14
  102. package/dist/src/extensions/commands/definitions.js +19 -0
  103. package/dist/src/extensions/project-local-remove.js +36 -1
  104. package/dist/src/gates/providers/loader.js +61 -17
  105. package/dist/src/installation/manager-command.d.mts +18 -0
  106. package/dist/src/installation/manager-command.mjs +14 -2
  107. package/dist/src/installation/manager.d.mts +49 -0
  108. package/dist/src/installation/manager.mjs +78 -3
  109. package/dist/src/issues/cli.js +4 -4
  110. package/dist/src/issues/local.js +24 -5
  111. package/dist/src/jobs/runner.js +1 -1
  112. package/dist/src/lint/cli.js +27 -1
  113. package/dist/src/lint/loader.js +2 -1
  114. package/dist/src/lint/reporters.js +25 -1
  115. package/dist/src/lint/rule-activation.js +43 -0
  116. package/dist/src/lint/runner.js +137 -62
  117. package/dist/src/mcp/adapters/codex-runtime.js +7 -1
  118. package/dist/src/mcp/claude-install.d.mts +4 -0
  119. package/dist/src/mcp/claude-install.mjs +41 -0
  120. package/dist/src/mcp/cli.d.mts +1 -0
  121. package/dist/src/mcp/cli.mjs +36 -11
  122. package/dist/src/mcp/config-file.d.mts +28 -0
  123. package/dist/src/mcp/config-layers.d.mts +36 -0
  124. package/dist/src/mcp/credentials.d.mts +51 -0
  125. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  126. package/dist/src/mcp/grok-build-config.mjs +1 -1
  127. package/dist/src/mcp/helpers.mjs +3 -2
  128. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  129. package/dist/src/mcp/muse-native-config.mjs +1 -0
  130. package/dist/src/mcp/omp-config.d.mts +4 -0
  131. package/dist/src/mcp/registry.js +3 -93
  132. package/dist/src/mcp/registry.mjs +3 -87
  133. package/dist/src/mcp/server-config.d.mts +3 -0
  134. package/dist/src/mcp/server-config.mjs +89 -0
  135. package/dist/src/mcp/server.mjs +37 -375
  136. package/dist/src/mcp/test-simple.mjs +2 -1
  137. package/dist/src/mcp/toml-editor.d.mts +4 -0
  138. package/dist/src/mcp/toml-string.d.mts +1 -0
  139. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  140. package/dist/src/mcp/tool-filters.d.mts +46 -0
  141. package/dist/src/mcp/tools/decision.mjs +12 -11
  142. package/dist/src/mcp/tools/discovery.mjs +62 -55
  143. package/dist/src/mcp/tools/orchestration.mjs +12 -2
  144. package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
  145. package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
  146. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
  147. package/dist/src/mission-protocol/codecs.js +8 -0
  148. package/dist/src/models/config-loader.js +7 -2
  149. package/dist/src/models/model-capabilities.v1.json +96 -4
  150. package/dist/src/models/model-discovery.js +76 -11
  151. package/dist/src/models/provider-policy.js +101 -7
  152. package/dist/src/models/qualification-digest.d.mts +2 -0
  153. package/dist/src/models/resolver.js +12 -10
  154. package/dist/src/models/worker-direct-readiness.js +65 -44
  155. package/dist/src/models/worker-dispatch-offer.js +3 -3
  156. package/dist/src/models/worker-health-readiness.js +16 -3
  157. package/dist/src/models/worker-installation.d.mts +39 -0
  158. package/dist/src/models/worker-installation.mjs +135 -28
  159. package/dist/src/process/bounded-child-process.d.mts +84 -0
  160. package/dist/src/process/bounded-child-process.mjs +43 -0
  161. package/dist/src/process/cli-command.d.mts +16 -0
  162. package/dist/src/process/signal-child.d.mts +6 -0
  163. package/dist/src/process/signal-child.mjs +7 -0
  164. package/dist/src/providers/deployed-skill-roots.js +20 -0
  165. package/dist/src/providers/grok-build-acp.js +3 -2
  166. package/dist/src/providers/grok-build-headless.js +12 -4
  167. package/dist/src/providers/grok-build-inspect.js +1 -0
  168. package/dist/src/providers/omp-agent.d.mts +8 -0
  169. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  170. package/dist/src/providers/omp-paths.d.mts +13 -0
  171. package/dist/src/providers/skill-ownership.d.mts +9 -0
  172. package/dist/src/providers/skill-ownership.mjs +88 -0
  173. package/dist/src/rules/activation.d.mts +21 -0
  174. package/dist/src/rules/activation.mjs +62 -0
  175. package/dist/src/rules/inline-budget.d.mts +26 -0
  176. package/dist/src/rules/inline-budget.mjs +66 -0
  177. package/dist/src/rules/inventory.js +129 -0
  178. package/dist/src/rules/pin-state.js +73 -0
  179. package/dist/src/rules/pins.d.mts +14 -0
  180. package/dist/src/rules/pins.mjs +112 -0
  181. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  182. package/dist/src/serve/mission-conductor.js +38 -12
  183. package/dist/src/serve/worker-invocation.js +30 -10
  184. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  185. package/dist/src/sessions/adapter-registry.js +23 -19
  186. package/dist/src/sessions/adapters/claude.js +39 -54
  187. package/dist/src/sessions/adapters/codex.js +95 -67
  188. package/dist/src/sessions/adapters/copilot.js +4 -4
  189. package/dist/src/sessions/adapters/cursor.js +40 -57
  190. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  191. package/dist/src/sessions/adapters/factory.js +31 -27
  192. package/dist/src/sessions/adapters/generic.js +10 -10
  193. package/dist/src/sessions/adapters/grok-build.js +11 -11
  194. package/dist/src/sessions/adapters/grokbot.js +24 -13
  195. package/dist/src/sessions/adapters/hermes.js +27 -29
  196. package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
  197. package/dist/src/sessions/adapters/muse.js +150 -93
  198. package/dist/src/sessions/adapters/omp.js +28 -14
  199. package/dist/src/sessions/adapters/openclaw.js +89 -79
  200. package/dist/src/sessions/adapters/opencode.js +27 -24
  201. package/dist/src/sessions/adapters/openhuman.js +18 -60
  202. package/dist/src/sessions/adapters/pi.js +29 -14
  203. package/dist/src/sessions/adapters/warp.js +3 -3
  204. package/dist/src/sessions/adapters/windsurf.js +19 -48
  205. package/dist/src/sessions/batch-contracts.js +16 -8
  206. package/dist/src/sessions/batch-import.js +252 -12
  207. package/dist/src/sessions/contracts.js +50 -3
  208. package/dist/src/sessions/coverage-preflight.js +27 -2
  209. package/dist/src/sessions/export-plan.js +4 -3
  210. package/dist/src/sessions/import-exclusions.js +72 -0
  211. package/dist/src/sessions/import-lease.js +34 -18
  212. package/dist/src/sessions/import-limits.js +37 -0
  213. package/dist/src/sessions/importer.js +60 -38
  214. package/dist/src/sessions/lifecycle.js +45 -0
  215. package/dist/src/sessions/policy.js +145 -26
  216. package/dist/src/sessions/readers.js +106 -53
  217. package/dist/src/sessions/refresh.js +105 -0
  218. package/dist/src/sessions/repository.js +151 -54
  219. package/dist/src/sessions/workspace-discovery.js +292 -60
  220. package/dist/src/sessions/workspace-matching.js +107 -0
  221. package/dist/src/skills/importer.js +1 -0
  222. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  223. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  224. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  225. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  226. package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
  227. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  228. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  229. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
  230. package/dist/src/storage/backends/fs.js +18 -76
  231. package/dist/src/storage/backends/obsidian.js +1 -0
  232. package/dist/src/tracker/forge-http.js +2 -1
  233. package/dist/src/update/checker.d.mts +2 -0
  234. package/dist/src/update/service.d.mts +22 -0
  235. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  236. package/dist/src/utils/contained-filesystem.mjs +683 -0
  237. package/dist/src/utils/temporary-directory.js +36 -0
  238. package/dist/src/writing/example-generator.js +1 -1
  239. package/package.json +15 -2
  240. package/schemas/models/model-catalog.v1.schema.json +1 -1
  241. package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
  242. package/tools/_resolve-impl.mjs +13 -15
  243. package/tools/agents/deploy-agents.mjs +49 -21
  244. package/tools/agents/providers/base.mjs +104 -134
  245. package/tools/agents/providers/claude.mjs +8 -24
  246. package/tools/agents/providers/codex.mjs +18 -11
  247. package/tools/agents/providers/cursor.mjs +19 -18
  248. package/tools/agents/providers/grok-build.mjs +3 -1
  249. package/tools/agents/providers/hermes.mjs +57 -47
  250. package/tools/agents/providers/openclaw.mjs +9 -4
  251. package/tools/agents/providers/pi.mjs +5 -0
  252. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  253. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  254. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  255. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  256. package/tools/agents/providers/warp.mjs +20 -26
  257. package/tools/cli/decision-offload-report.mjs +40 -0
  258. package/tools/cli/workspace-status.mjs +862 -0
  259. package/tools/plugin/package-plugins.mjs +186 -8
  260. package/tools/plugin/plugin-links.mjs +114 -0
  261. package/tools/providers/antigravity-transport.mjs +3 -1
  262. package/tools/providers/deepseek-harness-transport.mjs +6 -4
  263. package/tools/providers/omp-transport.mjs +4 -2
  264. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  265. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  266. package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
  267. package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
  268. package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
  269. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  270. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  271. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  272. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  273. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  274. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  275. package/tools/ralph-external/process-monitor.mjs +1017 -0
  276. package/tools/ralph-external/session-launcher.mjs +5057 -0
  277. package/tools/scripts/resource-preflight.mjs +541 -0
  278. package/tools/skills/deploy-skills-codex.mjs +2 -1
  279. package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
@@ -0,0 +1,89 @@
1
+ import { renderCredentialMaps } from './credentials.mjs';
2
+ import { getMcpInjectionDefinition, normalizeRuntimeProviderId } from '../providers/provider-definitions.mjs';
3
+
4
+ export function buildServerConfig(server, provider) {
5
+ const mcpDefinition = getMcpInjectionDefinition(provider);
6
+ // TOML providers render references through their own named keys in
7
+ // buildServerToml; unconditional interpolation would refuse them here even
8
+ // though the TOML writer handles them.
9
+ const { env, headers } = mcpDefinition?.serverConfigFormat === 'toml'
10
+ ? {}
11
+ : renderCredentialMaps(server, normalizeRuntimeProviderId(provider) || provider);
12
+
13
+ switch (mcpDefinition?.serverConfigFormat) {
14
+ case 'claude-code': {
15
+ if (server.type === 'stdio') {
16
+ return {
17
+ command: server.command,
18
+ args: server.args || [],
19
+ ...(env ? { env } : {}),
20
+ };
21
+ }
22
+ // Claude Code skips a url entry that has no type.
23
+ return {
24
+ type: server.type,
25
+ url: server.url,
26
+ ...(headers ? { headers } : {}),
27
+ };
28
+ }
29
+ case 'antigravity': {
30
+ if (server.type === 'stdio') {
31
+ return { command: server.command, args: server.args || [], ...(env ? { env } : {}) };
32
+ }
33
+ return { serverUrl: server.url, ...(headers ? { headers } : {}) };
34
+ }
35
+ case 'standard': {
36
+ if (server.type === 'stdio') {
37
+ return {
38
+ command: server.command,
39
+ args: server.args || [],
40
+ ...(env ? { env } : {}),
41
+ };
42
+ }
43
+ return {
44
+ url: server.url,
45
+ ...(headers ? { headers } : {}),
46
+ };
47
+ }
48
+
49
+ case 'factory': {
50
+ if (server.type === 'stdio') {
51
+ return {
52
+ type: 'stdio',
53
+ command: server.command,
54
+ args: server.args || [],
55
+ disabled: false,
56
+ ...(env ? { env } : {}),
57
+ };
58
+ }
59
+ return {
60
+ type: server.type,
61
+ url: server.url,
62
+ disabled: false,
63
+ ...(headers ? { headers } : {}),
64
+ };
65
+ }
66
+
67
+ case 'opencode': {
68
+ if (server.type === 'stdio') {
69
+ // opencode names a local server's variables `environment`.
70
+ return {
71
+ type: 'local',
72
+ command: [server.command, ...(server.args || [])],
73
+ ...(env ? { environment: env } : {}),
74
+ };
75
+ }
76
+ return {
77
+ type: 'remote',
78
+ url: server.url,
79
+ ...(headers ? { headers } : {}),
80
+ };
81
+ }
82
+
83
+ case 'toml':
84
+ return {};
85
+
86
+ default:
87
+ return {};
88
+ }
89
+ }
@@ -16,8 +16,6 @@ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mc
16
16
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
17
17
  import { z } from 'zod';
18
18
  import fs from 'fs/promises';
19
- import { constants } from 'node:fs';
20
- import { randomUUID } from 'node:crypto';
21
19
  import path from 'path';
22
20
  import { fileURLToPath } from 'url';
23
21
  import { registerWriterProfileResources } from './tools/writer-profiles.mjs';
@@ -26,6 +24,7 @@ import { registerCommandRunTool } from './tools/command-run.mjs';
26
24
  import { registerInteractionTools } from './tools/interaction.mjs';
27
25
  import { parseToolsets, registerOptInToolsets, KNOWN_TOOLSETS } from './tools/subsystems.mjs';
28
26
  import { AIWG_ROOT, findProjectRoot, resolveProjectAiwgDir } from './helpers.mjs';
27
+ import { pinContainedRoot as pinArtifactRoot, openContainedFile as openArtifact, readContainedFile, listContainedFiles, validateContainedPath } from '../utils/contained-filesystem.mjs';
29
28
 
30
29
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
31
30
 
@@ -35,317 +34,6 @@ const FRAMEWORKS = {
35
34
  'media-marketing-kit': path.join(AIWG_ROOT, 'agentic/code/frameworks/media-marketing-kit')
36
35
  };
37
36
 
38
- function assertArtifactContained(root, target) {
39
- const relative = path.relative(root, target);
40
- if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
41
- throw new Error('Artifact path must stay beneath the artifact root');
42
- }
43
- }
44
-
45
- function rootedArtifactAccess() {
46
- return process.platform === 'linux' && constants.O_NOFOLLOW && constants.O_DIRECTORY;
47
- }
48
-
49
- /** A root pinned as missing must be created by us, never adopted later. */
50
- async function createArtifactRoot(store) {
51
- let parent;
52
- let directory;
53
- let verified = false;
54
- try {
55
- let entry = store.configured;
56
- if (rootedArtifactAccess()) {
57
- parent = await fs.open(store.projectRoot, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
58
- if (await fs.realpath(`/proc/self/fd/${parent.fd}`) !== store.projectRoot) {
59
- throw new Error('Project root changed during artifact creation');
60
- }
61
- entry = `/proc/self/fd/${parent.fd}/.aiwg`;
62
- }
63
- // EEXIST is a refusal, including a symlink or directory installed since pinning.
64
- await fs.mkdir(entry);
65
- const created = await fs.lstat(entry);
66
- if (created.isSymbolicLink() || !created.isDirectory()) throw new Error('Artifact root changed during creation');
67
- // Windows cannot open directory handles through fs.open; its fallback
68
- // uses static identity checks, as do the subsequent artifact operations.
69
- if (process.platform !== 'win32') {
70
- directory = await fs.open(entry, constants.O_RDONLY | (constants.O_DIRECTORY || 0) | (constants.O_NOFOLLOW || 0));
71
- }
72
- const opened = directory ? await directory.stat() : await fs.stat(entry);
73
- const current = await fs.lstat(entry);
74
- if (current.isSymbolicLink() || !opened.isDirectory()
75
- || opened.dev !== created.dev || opened.ino !== created.ino
76
- || opened.dev !== current.dev || opened.ino !== current.ino
77
- || await fs.realpath(store.configured) !== store.root) {
78
- throw new Error('Artifact root changed during creation');
79
- }
80
- store.identity = opened;
81
- verified = true;
82
- } finally {
83
- const closed = await Promise.allSettled([...(directory ? [directory.close()] : []), ...(parent ? [parent.close()] : [])]);
84
- const failure = closed.find(result => result.status === 'rejected');
85
- if (verified && failure) throw failure.reason;
86
- }
87
- }
88
-
89
- /** Pin an existing store once; only a missing local default may be bootstrapped. */
90
- async function pinArtifactRoot(configured, projectRoot) {
91
- try {
92
- const root = await fs.realpath(configured);
93
- const identity = await fs.stat(root);
94
- const store = { configured, projectRoot, root, identity };
95
- await verifyArtifactRoot(store);
96
- return store;
97
- } catch (error) {
98
- if (error.code === 'ENOENT' && configured === path.join(projectRoot, '.aiwg')) {
99
- const existing = await fs.lstat(configured).catch(missing => {
100
- if (missing.code !== 'ENOENT') throw missing;
101
- });
102
- if (!existing) return { configured, projectRoot, root: configured, identity: null };
103
- // A symlink (or other unexpected node) present where the default root
104
- // should be created must not become the containment root later.
105
- return {
106
- configured,
107
- projectRoot,
108
- root: configured,
109
- error: new Error('Default artifact root is not a creatable directory'),
110
- };
111
- }
112
- // Keep discovery available, but never adopt a store that was unavailable
113
- // at startup (including a dangling alias) on a later request.
114
- return { configured, projectRoot, root: configured, error };
115
- }
116
- }
117
-
118
- async function verifyArtifactRoot(store) {
119
- if (store.error) throw store.error;
120
- const canonical = await fs.realpath(store.configured);
121
- const current = await fs.stat(store.configured);
122
- if (canonical !== store.root || !current.isDirectory()
123
- || current.dev !== store.identity.dev || current.ino !== store.identity.ino) {
124
- throw new Error('Artifact root changed since it was pinned');
125
- }
126
- }
127
-
128
- async function pinnedArtifactRoot(store, write) {
129
- if (store.error) throw store.error;
130
- if (!store.identity) {
131
- if (!write) throw new Error('Default artifact root has not been created');
132
- // Serialize first-write initialization so concurrent requests share one
133
- // identity. Creation still rejects symlinks and uses a project descriptor.
134
- store.initialization ||= createArtifactRoot(store);
135
- await store.initialization;
136
- }
137
- await verifyArtifactRoot(store);
138
- return store.root;
139
- }
140
-
141
- /** Resolve existing aliases before creating anything; dangling symlinks fail closed. */
142
- async function resolveArtifactPath(store, artifactPath, { write = false } = {}) {
143
- if (!artifactPath || path.isAbsolute(artifactPath) || path.win32.isAbsolute(artifactPath)
144
- || /^[A-Za-z]:/.test(artifactPath)
145
- || (process.platform === 'win32' && artifactPath.split(/[\\/]/).some(part => part !== '.'
146
- && (/[<>:"|?*\x00-\x1f]/.test(part) || /[. ]$/.test(part)
147
- || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part))))
148
- || artifactPath.split(/[\\/]/).includes('..')) {
149
- throw new Error('Artifact path must be relative and contain no .. segments');
150
- }
151
- if (write && artifactPath.split(/[\\/]/).some(part => /^\.aiwg-location[. ]*(?::.*)?$/i.test(part))) {
152
- throw new Error('Refusing to write an artifact location pointer');
153
- }
154
- const root = await pinnedArtifactRoot(store, write);
155
- const target = path.resolve(root, artifactPath);
156
- assertArtifactContained(root, target);
157
- if (!rootedArtifactAccess()) {
158
- let componentPath = root;
159
- for (const component of path.relative(root, target).split(path.sep)) {
160
- componentPath = path.join(componentPath, component);
161
- const info = await fs.lstat(componentPath).catch(error => {
162
- if (error.code !== 'ENOENT') throw error;
163
- });
164
- if (info?.isSymbolicLink()) throw new Error('Refusing artifact symlink component');
165
- }
166
- }
167
- let ancestor = path.dirname(target);
168
- const missing = [];
169
- while (true) {
170
- try {
171
- // lstat distinguishes a missing directory from a dangling symlink.
172
- await fs.lstat(ancestor);
173
- break;
174
- } catch (error) {
175
- if (error.code !== 'ENOENT') throw error;
176
- missing.unshift(path.basename(ancestor));
177
- ancestor = path.dirname(ancestor);
178
- }
179
- }
180
- const realAncestor = await fs.realpath(ancestor);
181
- if (realAncestor !== root) assertArtifactContained(root, realAncestor);
182
- const fullPath = path.resolve(realAncestor, ...missing, path.basename(target));
183
- assertArtifactContained(root, fullPath);
184
- let info;
185
- try {
186
- info = await fs.lstat(fullPath);
187
- } catch (error) {
188
- if (error.code !== 'ENOENT') throw error;
189
- }
190
- if (info) {
191
- if (write && info.isSymbolicLink()) throw new Error('Refusing to write through an artifact symlink');
192
- const realTarget = await fs.realpath(fullPath);
193
- assertArtifactContained(root, realTarget);
194
- if (!write) return { root, fullPath: realTarget };
195
- }
196
- return { root, fullPath };
197
- }
198
-
199
- /**
200
- * Linux procfs descriptor paths anchor each lookup to an opened directory.
201
- * Other platforms use static containment checks. Writes use a fresh inode;
202
- * failed verification closes handles without unsafe pathname cleanup.
203
- * Concurrent same-user relocation after verification is not a sandbox boundary.
204
- */
205
- async function openArtifact(store, artifactPath, { write = false } = {}) {
206
- const { root, fullPath } = await resolveArtifactPath(store, artifactPath, { write });
207
- if (!rootedArtifactAccess()) return openStaticArtifact(store, root, fullPath, { write });
208
- const directories = [];
209
- let file;
210
- let transferred = false;
211
- const descriptorPath = handle => `/proc/self/fd/${handle.fd}`;
212
- async function verify(handle, { rootHandle = false, directory = false, entry } = {}) {
213
- const actual = await fs.realpath(descriptorPath(handle));
214
- if (rootHandle) {
215
- if (actual !== root) throw new Error('Artifact root changed during access');
216
- } else {
217
- assertArtifactContained(root, actual);
218
- }
219
- const [opened, current] = await Promise.all([handle.stat(), fs.lstat(entry || actual)]);
220
- if (rootHandle) {
221
- await verifyArtifactRoot(store);
222
- if (opened.dev !== store.identity.dev || opened.ino !== store.identity.ino) {
223
- throw new Error('Artifact root changed since it was pinned');
224
- }
225
- }
226
- if (current.isSymbolicLink() || opened.dev !== current.dev || opened.ino !== current.ino
227
- || (directory ? !opened.isDirectory() : !opened.isFile())) {
228
- throw new Error('Artifact changed during access or is not a regular file/directory');
229
- }
230
- if (!directory && (opened.nlink > 1 || current.nlink > 1)) {
231
- throw new Error('Refusing multiply-linked artifact file');
232
- }
233
- return actual;
234
- }
235
- async function close() {
236
- const closed = await Promise.allSettled([...(file ? [file.close()] : []), ...directories.map(handle => handle.close())]);
237
- const failure = closed.find(result => result.status === 'rejected');
238
- if (failure) throw failure.reason;
239
- }
240
- try {
241
- let parent = await fs.open(root, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
242
- directories.push(parent);
243
- await verify(parent, { rootHandle: true, directory: true });
244
- const components = path.relative(root, fullPath).split(path.sep);
245
- const name = components.pop();
246
- for (const component of components) {
247
- const entry = path.join(descriptorPath(parent), component);
248
- let child;
249
- try {
250
- child = await fs.open(entry, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
251
- } catch (error) {
252
- if (!write || error.code !== 'ENOENT') throw error;
253
- await verify(parent, { rootHandle: directories.length === 1, directory: true });
254
- try {
255
- await fs.mkdir(entry);
256
- } catch (mkdirError) {
257
- if (mkdirError.code !== 'EEXIST') throw mkdirError;
258
- }
259
- child = await fs.open(entry, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
260
- }
261
- directories.push(child);
262
- await verify(child, { directory: true });
263
- parent = child;
264
- }
265
- const parentOptions = { rootHandle: directories.length === 1, directory: true };
266
- await verify(parent, parentOptions);
267
- const destination = path.join(descriptorPath(parent), name);
268
- async function verifyDestination() {
269
- const info = await fs.lstat(destination).catch(error => {
270
- if (error.code !== 'ENOENT') throw error;
271
- });
272
- if (info && (info.isSymbolicLink() || !info.isFile())) {
273
- throw new Error('Refusing to replace an artifact symlink or nonregular file');
274
- }
275
- return info;
276
- }
277
- const targetInfo = write ? await verifyDestination() : undefined;
278
- const finalMode = targetInfo ? targetInfo.mode & 0o7777 : 0o666 & ~process.umask();
279
- const entry = write ? path.join(descriptorPath(parent), `.aiwg-write-${randomUUID()}.tmp`) : destination;
280
- file = await fs.open(entry,
281
- (write ? constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL : constants.O_RDONLY)
282
- | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
283
- await verify(file, { entry });
284
- transferred = true;
285
- return {
286
- handle: file,
287
- fullPath,
288
- close,
289
- async commit() {
290
- await verify(directories[0], { rootHandle: true, directory: true });
291
- await verify(file, { entry });
292
- await verify(parent, parentOptions);
293
- await verifyDestination();
294
- await file.chmod(finalMode);
295
- await fs.rename(entry, destination);
296
- },
297
- };
298
- } finally {
299
- if (!transferred) await Promise.allSettled([close()]);
300
- }
301
- }
302
-
303
- /** Static checks for platforms without Linux descriptor paths. */
304
- async function openStaticArtifact(store, root, fullPath, { write }) {
305
- // Inspect the lexical path as well as the resolved path: contained aliases
306
- // must not hide symlink components on this fallback.
307
- async function check(target, { allowMissing = false } = {}) {
308
- const relative = path.relative(root, target);
309
- assertArtifactContained(root, target);
310
- let current = root;
311
- for (const component of relative.split(path.sep)) {
312
- current = path.join(current, component);
313
- const info = await fs.lstat(current).catch(error => {
314
- if (!allowMissing || error.code !== 'ENOENT') throw error;
315
- });
316
- if (!info) continue;
317
- if (info.isSymbolicLink()) throw new Error('Refusing artifact symlink component');
318
- assertArtifactContained(root, await fs.realpath(current));
319
- }
320
- }
321
- await check(fullPath, { allowMissing: write });
322
- if (write) await fs.mkdir(path.dirname(fullPath), { recursive: true });
323
- const target = await fs.lstat(fullPath).catch(error => {
324
- if (!write || error.code !== 'ENOENT') throw error;
325
- });
326
- if (target && (!target.isFile() || target.isSymbolicLink())) throw new Error('Artifact must be a regular file');
327
- if (!write && target.nlink > 1) throw new Error('Refusing multiply-linked artifact file');
328
- const entry = write ? path.join(path.dirname(fullPath), `.aiwg-write-${randomUUID()}.tmp`) : fullPath;
329
- const finalMode = target ? target.mode & 0o7777 : 0o666 & ~process.umask();
330
- const handle = await fs.open(entry, write ? constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL : constants.O_RDONLY, 0o600);
331
- try {
332
- const opened = await handle.stat();
333
- if (!opened.isFile() || opened.nlink > 1) throw new Error('Refusing nonregular or multiply-linked artifact file');
334
- return {
335
- handle, fullPath, close: () => handle.close(),
336
- async commit() {
337
- await verifyArtifactRoot(store);
338
- await check(fullPath, { allowMissing: true });
339
- await handle.chmod(finalMode);
340
- await fs.rename(entry, fullPath);
341
- },
342
- };
343
- } catch (error) {
344
- await handle.close().catch(() => {});
345
- throw error;
346
- }
347
- }
348
-
349
37
  /**
350
38
  * Create and configure the AIWG MCP Server
351
39
  */
@@ -364,7 +52,28 @@ export async function createServer() {
364
52
  // discover, skill-list, skill-show, command-list, command-show,
365
53
  // rule-list, rule-show, agent-show, template-list, template-show
366
54
  // ============================================
367
- registerDiscoveryTools(server);
55
+ const corpusStore = await pinArtifactRoot(path.resolve(AIWG_ROOT));
56
+ registerDiscoveryTools(server, { corpusStore });
57
+ async function readCorpus(relative) {
58
+ const content = await readContainedFile(corpusStore, relative);
59
+ if (content === null) throw new Error('Corpus file not found');
60
+ return content;
61
+ }
62
+ function frameworkName(framework) {
63
+ validateContainedPath(framework);
64
+ return Object.hasOwn(FRAMEWORKS, framework) ? framework : 'sdlc-complete';
65
+ }
66
+ async function scanCorpusDirectory(relative, type) {
67
+ const entries = await listContainedFiles(corpusStore, `${relative}/`);
68
+ return entries.filter(entry => entry.path.endsWith('.md')).map(entry => {
69
+ const local = path.relative(relative, entry.path);
70
+ const name = path.basename(local, '.md');
71
+ return {
72
+ name, path: path.join(corpusStore.root, entry.path), uri: `aiwg://${type}/${name}`,
73
+ ...(path.dirname(local) !== '.' ? { category: local.split(path.sep)[0] } : {}),
74
+ };
75
+ });
76
+ }
368
77
  registerWriterProfileResources(server);
369
78
 
370
79
  // ============================================
@@ -544,12 +253,10 @@ export async function createServer() {
544
253
  }, async ({ template, variables = {} }) => {
545
254
  try {
546
255
  // Determine framework and template path
256
+ validateContainedPath(template);
547
257
  const [framework, ...pathParts] = template.split('/');
548
- const frameworkPath = FRAMEWORKS[framework] || FRAMEWORKS['sdlc-complete'];
549
- const templatePath = path.join(frameworkPath, 'templates', ...pathParts);
550
-
551
- // Read template
552
- let content = await fs.readFile(templatePath, 'utf-8');
258
+ const relative = `agentic/code/frameworks/${frameworkName(framework)}/templates/${pathParts.join('/')}`;
259
+ let content = await readCorpus(relative);
553
260
 
554
261
  // Simple variable substitution
555
262
  for (const [key, value] of Object.entries(variables)) {
@@ -646,8 +353,7 @@ export async function createServer() {
646
353
  mimeType: 'application/json'
647
354
  },
648
355
  async () => {
649
- const promptsDir = path.join(AIWG_ROOT, 'agentic/code/addons/aiwg-utils/prompts');
650
- const catalog = await scanDirectory(promptsDir, 'prompts');
356
+ const catalog = await scanCorpusDirectory('agentic/code/addons/aiwg-utils/prompts', 'prompts');
651
357
  return {
652
358
  contents: [{
653
359
  uri: 'aiwg://prompts/catalog',
@@ -668,10 +374,9 @@ export async function createServer() {
668
374
  },
669
375
  async () => {
670
376
  const catalogs = {};
671
- for (const [fw, fwPath] of Object.entries(FRAMEWORKS)) {
672
- const templatesDir = path.join(fwPath, 'templates');
377
+ for (const fw of Object.keys(FRAMEWORKS)) {
673
378
  try {
674
- catalogs[fw] = await scanDirectory(templatesDir, 'templates');
379
+ catalogs[fw] = await scanCorpusDirectory(`agentic/code/frameworks/${fw}/templates`, 'templates');
675
380
  } catch {
676
381
  catalogs[fw] = [];
677
382
  }
@@ -696,10 +401,9 @@ export async function createServer() {
696
401
  },
697
402
  async () => {
698
403
  const catalogs = {};
699
- for (const [fw, fwPath] of Object.entries(FRAMEWORKS)) {
700
- const agentsDir = path.join(fwPath, 'agents');
404
+ for (const fw of Object.keys(FRAMEWORKS)) {
701
405
  try {
702
- catalogs[fw] = await scanDirectory(agentsDir, 'agents');
406
+ catalogs[fw] = await scanCorpusDirectory(`agentic/code/frameworks/${fw}/agents`, 'agents');
703
407
  } catch {
704
408
  catalogs[fw] = [];
705
409
  }
@@ -722,14 +426,9 @@ export async function createServer() {
722
426
  description: 'Retrieve specific AIWG prompt template'
723
427
  },
724
428
  async (uri, { category, name }) => {
725
- const promptPath = path.join(
726
- AIWG_ROOT,
727
- 'agentic/code/addons/aiwg-utils/prompts',
728
- category,
729
- `${name}.md`
730
- );
731
429
  try {
732
- const content = await fs.readFile(promptPath, 'utf-8');
430
+ validateContainedPath(category); validateContainedPath(name);
431
+ const content = await readCorpus(`agentic/code/addons/aiwg-utils/prompts/${category}/${name}.md`);
733
432
  return {
734
433
  contents: [{
735
434
  uri: uri.href,
@@ -756,14 +455,9 @@ export async function createServer() {
756
455
  description: 'Retrieve specific AIWG template'
757
456
  },
758
457
  async (uri, { framework, category, name }) => {
759
- const templatePath = path.join(
760
- FRAMEWORKS[framework] || FRAMEWORKS['sdlc-complete'],
761
- 'templates',
762
- category,
763
- `${name}.md`
764
- );
765
458
  try {
766
- const content = await fs.readFile(templatePath, 'utf-8');
459
+ validateContainedPath(category); validateContainedPath(name);
460
+ const content = await readCorpus(`agentic/code/frameworks/${frameworkName(framework)}/templates/${category}/${name}.md`);
767
461
  return {
768
462
  contents: [{
769
463
  uri: uri.href,
@@ -790,13 +484,9 @@ export async function createServer() {
790
484
  description: 'Retrieve specific AIWG agent definition'
791
485
  },
792
486
  async (uri, { framework, name }) => {
793
- const agentPath = path.join(
794
- FRAMEWORKS[framework] || FRAMEWORKS['sdlc-complete'],
795
- 'agents',
796
- `${name}.md`
797
- );
798
487
  try {
799
- const content = await fs.readFile(agentPath, 'utf-8');
488
+ validateContainedPath(name);
489
+ const content = await readCorpus(`agentic/code/frameworks/${frameworkName(framework)}/agents/${name}.md`);
800
490
  return {
801
491
  contents: [{
802
492
  uri: uri.href,
@@ -962,34 +652,6 @@ If retry fails, prepare structured escalation:
962
652
  // HELPER FUNCTIONS
963
653
  // ============================================
964
654
 
965
- /**
966
- * Scan directory for files
967
- */
968
- async function scanDirectory(dir, type) {
969
- const results = [];
970
- try {
971
- const entries = await fs.readdir(dir, { withFileTypes: true });
972
- for (const entry of entries) {
973
- if (entry.isDirectory()) {
974
- const subResults = await scanDirectory(path.join(dir, entry.name), type);
975
- results.push(...subResults.map(r => ({
976
- ...r,
977
- category: entry.name
978
- })));
979
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
980
- results.push({
981
- name: entry.name.replace('.md', ''),
982
- path: path.join(dir, entry.name),
983
- uri: `aiwg://${type}/${entry.name.replace('.md', '')}`
984
- });
985
- }
986
- }
987
- } catch {
988
- // Directory doesn't exist
989
- }
990
- return results;
991
- }
992
-
993
655
  /**
994
656
  * Start the MCP server with stdio transport
995
657
  */
@@ -6,6 +6,7 @@
6
6
  * Tests the server by spawning it and sending JSON-RPC messages via stdio.
7
7
  */
8
8
 
9
+ import { signalChild } from '../process/signal-child.mjs';
9
10
  import { spawn } from 'child_process';
10
11
  import { fileURLToPath } from 'url';
11
12
  import path from 'path';
@@ -254,7 +255,7 @@ async function main() {
254
255
  });
255
256
 
256
257
  // Cleanup
257
- server.kill();
258
+ signalChild(server);
258
259
 
259
260
  // Summary
260
261
  console.log('');
@@ -0,0 +1,4 @@
1
+ export function replaceServer(text: string, name: string, section: string): {
2
+ text: string;
3
+ alreadyPresent: boolean;
4
+ };
@@ -0,0 +1 @@
1
+ export function tomlString(value: unknown): string;
@@ -0,0 +1 @@
1
+ export function stripMcpServers(text: string): string;
@@ -0,0 +1,46 @@
1
+ import type { ConfigWrite } from './config-file.mjs';
2
+
3
+ export interface ToolFilters {
4
+ deny: string[];
5
+ allow: string[];
6
+ }
7
+
8
+ export interface ToolFilterPlan {
9
+ serverFields: Record<string, Record<string, unknown>>;
10
+ tomlLines: Record<string, string[]>;
11
+ topLevel: { tools?: Record<string, boolean> };
12
+ claudePermissions: { deny: string[]; allow: string[] } | null;
13
+ warnings: string[];
14
+ }
15
+
16
+ export function resolveToolFilters(
17
+ profile: { providerOverrides?: Record<string, { toolDeny?: string[]; toolAllow?: string[] }> } | undefined,
18
+ provider: string,
19
+ ): ToolFilters;
20
+ export function hasToolFilters(filters: ToolFilters | undefined | null): boolean;
21
+ export function parseToolPattern(pattern: string): { server: string; tool: string } | null;
22
+ export function planToolFilters(provider: string, serverNames: string[], filters: ToolFilters): ToolFilterPlan;
23
+ export function applyJsonToolFilterPlan(
24
+ config: Record<string, unknown>,
25
+ serversKey: string,
26
+ plan: ToolFilterPlan,
27
+ ): Record<string, unknown>;
28
+ export function claudeSettingsPath(projectDir?: string, scope?: 'user' | 'project'): string;
29
+ export interface ClaudePermissionOptions {
30
+ dryRun?: boolean;
31
+ userScope?: boolean;
32
+ projectRoot?: string;
33
+ managedDir?: string;
34
+ sidecar?: boolean;
35
+ mcpPath?: string;
36
+ }
37
+ export function prepareClaudePermissions(
38
+ settingsPath: string,
39
+ permissions: { deny: string[]; allow: string[] },
40
+ options?: ClaudePermissionOptions,
41
+ ): Promise<{ merged: Record<string, unknown>; active: boolean; warnings: string[]; writes: ConfigWrite[]; commit(): Promise<void> }>;
42
+ export function mergeClaudePermissions(
43
+ settingsPath: string,
44
+ permissions: { deny: string[]; allow: string[] },
45
+ options?: ClaudePermissionOptions,
46
+ ): Promise<Record<string, unknown>>;