@aiwg/cli 2026.10.5 → 2026.10.7

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 (263) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +7 -3
  6. package/dist/src/a2a/agent-card.js +11 -0
  7. package/dist/src/api/index.d.ts +1 -0
  8. package/dist/src/api/index.js +1 -0
  9. package/dist/src/artifacts/browser-export.js +1 -0
  10. package/dist/src/artifacts/citation-parser.js +8 -1
  11. package/dist/src/artifacts/cli.js +57 -6
  12. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  13. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  14. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
  15. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
  16. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
  17. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
  18. package/dist/src/artifacts/fortemi-shard-export.js +284 -0
  19. package/dist/src/artifacts/graph-query.js +18 -8
  20. package/dist/src/artifacts/index-adapters.js +339 -0
  21. package/dist/src/artifacts/index-builder.js +143 -14
  22. package/dist/src/artifacts/index-files.js +15 -4
  23. package/dist/src/artifacts/index-preparation.js +2 -0
  24. package/dist/src/artifacts/index-reader.js +44 -0
  25. package/dist/src/artifacts/index-refresh.js +114 -55
  26. package/dist/src/artifacts/index-status.js +20 -5
  27. package/dist/src/artifacts/memory-maintenance.js +503 -0
  28. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  29. package/dist/src/artifacts/query-engine.js +263 -108
  30. package/dist/src/artifacts/relations-builder.js +2 -2
  31. package/dist/src/artifacts/source-graph.js +2 -2
  32. package/dist/src/artifacts/types.js +9 -1
  33. package/dist/src/auth/client.js +5 -1
  34. package/dist/src/auth/credential-store.js +5 -1
  35. package/dist/src/auth/sensitive-input.js +151 -0
  36. package/dist/src/channel/manager.mjs +3 -0
  37. package/dist/src/cli/handlers/agentcard.js +47 -25
  38. package/dist/src/cli/handlers/auth.js +22 -2
  39. package/dist/src/cli/handlers/decision.js +22 -3
  40. package/dist/src/cli/handlers/effect.js +4 -84
  41. package/dist/src/cli/handlers/guided-setup.js +470 -0
  42. package/dist/src/cli/handlers/help.js +2 -2
  43. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  44. package/dist/src/cli/handlers/index.js +4 -2
  45. package/dist/src/cli/handlers/init.js +41 -6
  46. package/dist/src/cli/handlers/install.js +6 -0
  47. package/dist/src/cli/handlers/local-executor.js +5 -3
  48. package/dist/src/cli/handlers/marketplace.js +15 -4
  49. package/dist/src/cli/handlers/mc.js +509 -53
  50. package/dist/src/cli/handlers/models.js +32 -2
  51. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  52. package/dist/src/cli/handlers/rules.js +1 -1
  53. package/dist/src/cli/handlers/script-runner.js +73 -2
  54. package/dist/src/cli/handlers/serve.js +5 -3
  55. package/dist/src/cli/handlers/sessions.js +1 -0
  56. package/dist/src/cli/handlers/setup.js +432 -1
  57. package/dist/src/cli/handlers/skill-lint.js +1 -1
  58. package/dist/src/cli/handlers/subcommands.js +118 -8
  59. package/dist/src/cli/handlers/use.js +335 -107
  60. package/dist/src/cli/handlers/workspace.js +3 -1
  61. package/dist/src/cli/prompt-utils.js +81 -0
  62. package/dist/src/cli/router.js +16 -1
  63. package/dist/src/config/aiwg-config.js +42 -0
  64. package/dist/src/config/cli.js +7 -3
  65. package/dist/src/config/decision-config.js +14 -1
  66. package/dist/src/config/gitignore.js +4 -0
  67. package/dist/src/config/native-sandbox-warnings.js +25 -0
  68. package/dist/src/config/workspace.js +2 -1
  69. package/dist/src/contributors/validation.js +6 -5
  70. package/dist/src/decision/adapters/jev.js +20 -31
  71. package/dist/src/decision/admission.js +7 -3
  72. package/dist/src/decision/ask.js +6 -1
  73. package/dist/src/decision/benchmark.js +482 -0
  74. package/dist/src/decision/ledger.js +22 -3
  75. package/dist/src/decision/pricing.js +147 -0
  76. package/dist/src/decision/qualification/gate-evidence.js +23 -18
  77. package/dist/src/decision/qualification/load-staged.js +576 -0
  78. package/dist/src/decision/qualification/load.js +109 -33
  79. package/dist/src/decision/qualification/runner.js +12 -5
  80. package/dist/src/decision/review/service.js +6 -4
  81. package/dist/src/decision/review/validate.js +23 -2
  82. package/dist/src/decision/savings.js +8 -3
  83. package/dist/src/discovery/index.js +492 -0
  84. package/dist/src/discovery/interview.js +238 -0
  85. package/dist/src/discovery/plan-apply.js +567 -0
  86. package/dist/src/discovery/plan-store.js +171 -0
  87. package/dist/src/discovery/plan-types.js +2 -0
  88. package/dist/src/discovery/plan-validation.js +139 -0
  89. package/dist/src/discovery/types.js +2 -0
  90. package/dist/src/effects/host.js +91 -0
  91. package/dist/src/extensions/commands/definitions.js +49 -9
  92. package/dist/src/extensions/manifest.js +71 -44
  93. package/dist/src/extensions/project-local-discovery.js +1 -1
  94. package/dist/src/extensions/project-local-gitignore.js +2 -0
  95. package/dist/src/extensions/project-quickref.js +18 -17
  96. package/dist/src/extensions/validation.js +32 -29
  97. package/dist/src/housekeeper/daemon-config.js +148 -0
  98. package/dist/src/housekeeper/ipc.js +84 -0
  99. package/dist/src/housekeeper/lifecycle.js +148 -0
  100. package/dist/src/housekeeper/platform.js +35 -0
  101. package/dist/src/housekeeper/status.js +70 -0
  102. package/dist/src/housekeeper/units.js +114 -0
  103. package/dist/src/issues/cli.js +16 -5
  104. package/dist/src/issues/live.js +44 -12
  105. package/dist/src/jobs/flow.js +22 -21
  106. package/dist/src/lint/rule-activation.js +20 -5
  107. package/dist/src/lint/runner.js +1 -1
  108. package/dist/src/marketplace/exchange.js +18 -6
  109. package/dist/src/marketplace/provenance.js +3 -2
  110. package/dist/src/mcp/adapters/codex-runtime.js +46 -76
  111. package/dist/src/mcp/cli.mjs +22 -96
  112. package/dist/src/mcp/config-file.mjs +6 -2
  113. package/dist/src/mcp/config-layers.mjs +23 -7
  114. package/dist/src/mcp/grok-build-config.mjs +2 -1
  115. package/dist/src/mcp/muse-native-config.mjs +2 -1
  116. package/dist/src/mcp/profiles.js +18 -31
  117. package/dist/src/mcp/profiles.mjs +18 -32
  118. package/dist/src/mcp/registry.js +7 -27
  119. package/dist/src/mcp/registry.mjs +7 -28
  120. package/dist/src/mcp/server.mjs +34 -1
  121. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  122. package/dist/src/mcp/tools/command-run.mjs +3 -2
  123. package/dist/src/mcp/tools/decision.mjs +49 -48
  124. package/dist/src/mcp/tools/discovery.mjs +46 -21
  125. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  126. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  127. package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
  128. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  129. package/dist/src/memory/audit.js +719 -0
  130. package/dist/src/memory/canonical-context.js +392 -13
  131. package/dist/src/memory/canonical-maintenance.js +95 -0
  132. package/dist/src/memory/cli-result.js +22 -0
  133. package/dist/src/memory/cli.js +176 -0
  134. package/dist/src/memory/context-pack.js +57 -6
  135. package/dist/src/memory/cost.js +512 -0
  136. package/dist/src/memory/firewall-classification.d.mts +7 -0
  137. package/dist/src/memory/firewall-classification.mjs +32 -0
  138. package/dist/src/memory/index.js +8 -0
  139. package/dist/src/memory/line-maintenance.js +68 -0
  140. package/dist/src/memory/maintenance-cli.js +286 -0
  141. package/dist/src/memory/maintenance-contract.js +136 -0
  142. package/dist/src/memory/maintenance-index.js +18 -0
  143. package/dist/src/memory/maintenance-owner.js +2 -0
  144. package/dist/src/memory/maintenance-record.js +138 -0
  145. package/dist/src/memory/maintenance.js +254 -0
  146. package/dist/src/memory/project-registry.js +2 -1
  147. package/dist/src/memory/provider-memory-adapter.js +154 -0
  148. package/dist/src/memory/recommend-cli.js +83 -0
  149. package/dist/src/memory/recommend.js +269 -0
  150. package/dist/src/memory/scheduled.js +81 -0
  151. package/dist/src/memory/taxonomy.js +44 -0
  152. package/dist/src/mission-protocol/codecs.js +19 -9
  153. package/dist/src/mission-protocol/worker-status.js +91 -1
  154. package/dist/src/models/config-loader.js +11 -1
  155. package/dist/src/models/model-capabilities.v1.json +74 -50
  156. package/dist/src/models/provider-policy.js +125 -62
  157. package/dist/src/models/resolver.js +45 -8
  158. package/dist/src/models/worker-installation.d.mts +8 -1
  159. package/dist/src/models/worker-installation.mjs +231 -38
  160. package/dist/src/models/wrapper-route.js +1 -1
  161. package/dist/src/output-modes/registry.js +8 -7
  162. package/dist/src/packages/adapters/git.js +99 -23
  163. package/dist/src/packages/adapters/gitea.js +8 -27
  164. package/dist/src/packages/adapters/github.js +7 -25
  165. package/dist/src/packages/registry.js +4 -4
  166. package/dist/src/plugin/metadata-validator.js +1 -1
  167. package/dist/src/process/cli-command.d.mts +5 -0
  168. package/dist/src/process/cli-command.mjs +42 -0
  169. package/dist/src/providers/deploy-version.d.mts +1 -0
  170. package/dist/src/providers/deploy-version.mjs +17 -0
  171. package/dist/src/providers/provider-definitions.js +19 -16
  172. package/dist/src/providers/skill-ownership.d.mts +11 -2
  173. package/dist/src/providers/skill-ownership.mjs +35 -17
  174. package/dist/src/rules/activation.d.mts +11 -2
  175. package/dist/src/rules/activation.mjs +82 -6
  176. package/dist/src/rules/inventory.js +1 -1
  177. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  178. package/dist/src/schema/zod-validation.d.mts +6 -0
  179. package/dist/src/schema/zod-validation.mjs +50 -0
  180. package/dist/src/serve/executor-registry.js +297 -20
  181. package/dist/src/serve/executor-token.d.mts +1 -0
  182. package/dist/src/serve/executor-token.mjs +8 -0
  183. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  184. package/dist/src/serve/worker-invocation.js +124 -15
  185. package/dist/src/sessions/adapters/claude.js +19 -18
  186. package/dist/src/sessions/adapters/codex.js +10 -9
  187. package/dist/src/sessions/adapters/copilot.js +14 -13
  188. package/dist/src/sessions/adapters/cursor.js +19 -18
  189. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  190. package/dist/src/sessions/adapters/factory.js +11 -10
  191. package/dist/src/sessions/adapters/generic.js +32 -23
  192. package/dist/src/sessions/adapters/grokbot.js +35 -35
  193. package/dist/src/sessions/adapters/hermes.js +20 -19
  194. package/dist/src/sessions/adapters/muse.js +50 -49
  195. package/dist/src/sessions/adapters/omp.js +5 -4
  196. package/dist/src/sessions/adapters/openclaw.js +30 -29
  197. package/dist/src/sessions/adapters/opencode.js +26 -26
  198. package/dist/src/sessions/adapters/openhuman.js +24 -23
  199. package/dist/src/sessions/adapters/pi.js +5 -4
  200. package/dist/src/sessions/adapters/windsurf.js +8 -8
  201. package/dist/src/sessions/analytics.js +10 -9
  202. package/dist/src/sessions/candidates.js +7 -6
  203. package/dist/src/sessions/contracts.js +63 -53
  204. package/dist/src/sessions/output-registration.js +11 -10
  205. package/dist/src/sessions/workspace-discovery.js +3 -4
  206. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  207. package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
  208. package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
  209. package/dist/src/smiths/context-pipeline/finalization.js +3 -2
  210. package/dist/src/smiths/context-pipeline/generator.js +7 -19
  211. package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
  212. package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
  213. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  214. package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
  215. package/dist/src/tracker/project-inputs.js +2 -0
  216. package/dist/src/update/checker.d.mts +4 -0
  217. package/dist/src/update/checker.mjs +14 -0
  218. package/dist/src/update/notifier.mjs +2 -4
  219. package/dist/src/writing/exemplar-selection.js +6 -6
  220. package/dist/src/writing/voice-evaluation.js +24 -23
  221. package/dist/src/writing/writer-migration.js +14 -13
  222. package/dist/src/writing/writer-profile.js +19 -18
  223. package/dist/src/writing/writing-brief.js +18 -17
  224. package/dist/src/writing/writing-receipt.js +47 -46
  225. package/package.json +5 -2
  226. package/schemas/decision/DecisionReview.schema.json +78 -1
  227. package/schemas/mission-v1.schema.json +3 -0
  228. package/tools/agents/deploy-agents.mjs +67 -39
  229. package/tools/agents/providers/antigravity.mjs +6 -1
  230. package/tools/agents/providers/base.mjs +24 -14
  231. package/tools/agents/providers/claude.mjs +6 -3
  232. package/tools/agents/providers/codex.mjs +54 -57
  233. package/tools/agents/providers/context-write.mjs +28 -0
  234. package/tools/agents/providers/copilot.mjs +13 -4
  235. package/tools/agents/providers/cursor.mjs +1 -1
  236. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  237. package/tools/agents/providers/factory.mjs +7 -1
  238. package/tools/agents/providers/grok-build.mjs +18 -2
  239. package/tools/agents/providers/grokbot.mjs +1 -1
  240. package/tools/agents/providers/hermes.mjs +12 -15
  241. package/tools/agents/providers/muse-hooks.mjs +21 -14
  242. package/tools/agents/providers/muse.mjs +7 -2
  243. package/tools/agents/providers/omp.mjs +31 -5
  244. package/tools/agents/providers/opencode.mjs +14 -5
  245. package/tools/agents/providers/openhuman.mjs +1 -1
  246. package/tools/agents/providers/pi.mjs +1 -1
  247. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  248. package/tools/agents/providers/rule-delivery.mjs +289 -80
  249. package/tools/agents/providers/warp.mjs +4 -1
  250. package/tools/agents/providers/windsurf.mjs +13 -3
  251. package/tools/cli/workspace-status.mjs +5 -0
  252. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  253. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  254. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
  255. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
  256. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  257. package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
  258. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  259. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  260. package/tools/ralph-external/process-monitor.mjs +49 -8
  261. package/tools/ralph-external/session-launcher.mjs +405 -35
  262. package/tools/skills/deploy-skills-codex.mjs +11 -7
  263. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -1,4 +1,4 @@
1
- /** Activation-selected deployment, shared by the six provider projections (#2939). */
1
+ /** Activation-selected deployment shared by provider projections. */
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
@@ -6,17 +6,20 @@ import { stringify } from 'yaml';
6
6
  import { createHash, randomUUID } from 'node:crypto';
7
7
  import { importImpl } from '../../_resolve-impl.mjs';
8
8
  import {
9
- listMdFiles, discoverAddons, getFrameworksForMode, collectBehaviorDirs,
9
+ listMdFiles, listSkillDirs, getAddonSkillDirs, deploySkillsWithKernelRouting, isKernelSkill,
10
+ discoverAddons, getFrameworksForMode, collectBehaviorDirs,
10
11
  resolveAiwgRoot, ruleEnforcementLevel, stripTriggersFromContent,
11
12
  addManagedMarker, contentHash, replaceBoundRulesBlock, deployEmulatedBehaviors, filterAgentFiles,
12
13
  } from './base.mjs';
13
14
 
14
- const { parseRuleFrontmatter, inferRuleActivation, RULE_BINDING_TYPES } = await importImpl(import.meta.url, 'rules/activation.mjs');
15
+ const { parseRuleFrontmatter, inferRuleActivation, RULE_BINDING_TYPES, readCoreBody, validateCoreSet, CORE_RULES_START, CORE_RULES_END } = await importImpl(import.meta.url, 'rules/activation.mjs');
16
+ const { resolveDeployVersion } = await importImpl(import.meta.url, 'providers/deploy-version.mjs');
15
17
  const { trackTemporaryDirectory } = await importImpl(import.meta.url, 'utils/temporary-directory.js');
16
18
  const { loadRulePinConfigs, installedRuleCapabilities, resolveRulePins } = await importImpl(import.meta.url, 'rules/pins.mjs');
17
19
  const { guardRulesInlineBudget } = await importImpl(import.meta.url, 'rules/inline-budget.mjs');
18
20
  const { userConfigFile } = await importImpl(import.meta.url, 'config/user-config-dir.mjs');
19
21
  const { resolveProjectAiwgDir } = await importImpl(import.meta.url, 'config/project-artifacts-runtime.mjs');
22
+ import { writeContextFileSync, CONTEXT_STAGE_ENV } from './context-write.mjs';
20
23
  const PREPARATION_HOOK = Symbol.for('aiwg.provider-preparation-hook.v1');
21
24
 
22
25
  export function installRuleDeploymentPreparationHook(hook) {
@@ -65,15 +68,62 @@ function promoteDirectories(source, destination, skip = () => false, prefix = ''
65
68
  }
66
69
  const read = file => fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
67
70
  const structural = file => /^(RULES-INDEX|RULES-ONDEMAND)\.(md|mdc)$/.test(path.basename(file));
71
+ // Verified startup owners from provider-definitions.ts. These providers do not
72
+ // need a prose fetch of AIWG.md to activate their compact core contracts.
73
+ const inlineStartup = {
74
+ copilot: '.github/copilot-instructions.md', factory: 'AGENTS.md', opencode: 'AGENTS.md',
75
+ windsurf: 'AGENTS.md', antigravity: 'AGENTS.md', muse: 'AGENTS.md',
76
+ omp: '.omp/AGENTS.md', 'grok-build': 'AGENTS.md',
77
+ };
68
78
 
69
79
  /** Collect selected source contracts, before any tier filter or projection. */
70
80
  export function collectDeploymentRuleSources(opts) {
71
- if (opts.ruleSources) return [...new Set([...opts.ruleSources, ...(opts.previousRuleSources || []).filter(file => fs.existsSync(file))])];
72
- const roots = [opts.srcRoot];
73
- for (const framework of getFrameworksForMode(opts.srcRoot, opts.mode)) roots.push(framework.path);
74
- for (const addon of discoverAddons(opts.srcRoot)) roots.push(addon.path);
75
- const files = [...(opts.previousRuleSources || []).filter(file => fs.existsSync(file)), ...roots.flatMap(root => listMdFiles(path.join(root, 'rules')))].filter(file => !structural(file));
76
- const behaviors = collectBehaviorDirs(opts.srcRoot).map(dir => path.join(dir, 'BEHAVIOR.md'));
81
+ if (opts.ruleSources) {
82
+ // Pin-only callers enumerate rule files separately from behavior contracts.
83
+ // Retain earlier behaviors only while their owning bundle is still selected.
84
+ const selected = new Set(collectDeploymentRuleSources({ ...opts, ruleSources: undefined }));
85
+ const behaviors = (opts.previousRuleSources || []).filter(file => path.basename(file) === 'BEHAVIOR.md' && selected.has(file));
86
+ return [...new Set([...opts.ruleSources, ...behaviors])];
87
+ }
88
+ const corpus = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
89
+ const bundles = [];
90
+ const frameworks = getFrameworksForMode(corpus, 'all');
91
+ for (const kind of ['frameworks', 'addons', 'extensions', 'plugins']) {
92
+ const dir = path.join(corpus, 'agentic', 'code', kind);
93
+ if (!fs.existsSync(dir)) continue;
94
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
95
+ if (!entry.isDirectory()) continue;
96
+ const root = path.join(dir, entry.name);
97
+ const manifest = JSON.parse(read(path.join(root, 'manifest.json')) || '{}');
98
+ bundles.push({ root, manifest, id: manifest.id || manifest.framework || entry.name,
99
+ aliases: [entry.name, ...(manifest.modeAliases || []), ...(manifest.aliases || []),
100
+ ...(kind === 'frameworks' ? frameworks.find(framework => framework.path === root)?.aliases || [] : [])] });
101
+ }
102
+ }
103
+ const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
104
+ // Project installation is authoritative. User-wide discoverability is not a
105
+ // project install, and a previous projection must never reselect retired rules.
106
+ const installed = configs.project.installed || {};
107
+ const names = Array.isArray(installed) ? installed : Object.keys(installed);
108
+ const selected = new Set();
109
+ const select = name => {
110
+ const id = name.replace(/^(framework|addon|extension|plugin):/, '');
111
+ const bundle = bundles.find(b => b.id === id || b.aliases.includes(id));
112
+ if (!bundle || selected.has(bundle)) return;
113
+ selected.add(bundle);
114
+ const required = Array.isArray(bundle.manifest.dependencies) ? bundle.manifest.dependencies : bundle.manifest.dependencies?.required || [];
115
+ for (const dep of Array.isArray(required) ? required : Object.keys(required)) select(dep);
116
+ };
117
+ for (const bundle of bundles.filter(b => b.manifest.core && b.manifest.autoInstall)) select(bundle.id);
118
+ for (const name of names.filter(name => name !== 'all')) select(name);
119
+ const invoked = bundles.find(bundle => path.resolve(bundle.root) === path.resolve(opts.srcRoot));
120
+ if (invoked && !opts.kernelOnly) select(invoked.id);
121
+ // Include the invocation being installed before its config entry is saved.
122
+ if (!opts.kernelOnly) for (const framework of getFrameworksForMode(opts.srcRoot, opts.mode === 'all' && names.length ? 'general' : opts.mode)) select(framework.id);
123
+ const roots = [...(!invoked || !opts.kernelOnly ? [opts.srcRoot] : []), ...(opts.additionalSources || []), ...[...selected].map(bundle => bundle.root)];
124
+ for (const entry of Object.values(installed)) if (entry?.localPath) roots.push(path.resolve(opts.target, entry.localPath));
125
+ const files = roots.flatMap(root => listMdFiles(path.join(root, 'rules'))).filter(file => !structural(file));
126
+ const behaviors = roots.flatMap(root => walk(path.join(root, 'behaviors')).filter(file => path.basename(file) === 'BEHAVIOR.md'));
77
127
  return [...new Set([...files, ...behaviors])];
78
128
  }
79
129
 
@@ -85,12 +135,16 @@ export async function prepareRuleDelivery(opts) {
85
135
  const behavior = path.basename(file) === 'BEHAVIOR.md';
86
136
  const rule = { ...parsed.rule, file, content, body: parsed.body, behavior,
87
137
  name: behavior ? `behavior/${path.basename(path.dirname(file))}` : parsed.data.name || path.basename(file, '.md') };
88
- if (!rule.tagged) rule.activation = behavior ? 'kernel' : inferRuleActivation(ruleEnforcementLevel(content));
138
+ if (!rule.tagged) rule.activation = inferRuleActivation();
139
+ // Core rules (#3450) carry a compact body beside the full rule; the full text is never inlined.
140
+ if (rule.activation === 'core') rule.coreBody = readCoreBody(file);
89
141
  for (const diagnostic of parsed.diagnostics) {
90
- console.warn(`${opts.logicalInputPath ? opts.logicalInputPath(file) : file}: ${diagnostic.code}: ${diagnostic.code === 'missing-activation' ? `Missing activation; inferred ${rule.activation} delivery (migration fallback).` : diagnostic.message}`);
142
+ console.warn(`${opts.logicalInputPath ? opts.logicalInputPath(file) : file}: ${diagnostic.code}: ${diagnostic.code === 'missing-activation' ? `Missing activation; inferred ${rule.activation} delivery (migration fallback); add explicit activation frontmatter.` : diagnostic.message}`);
91
143
  }
92
144
  return rule;
93
145
  });
146
+ const coreErrors = validateCoreSet([...new Map(rules.filter(rule => rule.coreBody !== undefined).map(rule => [rule.name, { name: rule.name, text: rule.coreBody }])).values()]);
147
+ if (coreErrors.length) throw new Error(coreErrors.join('; '));
94
148
  const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
95
149
  const installed = installedRuleCapabilities(configs);
96
150
  const root = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
@@ -110,7 +164,8 @@ export async function prepareRuleDelivery(opts) {
110
164
  const pinsByFile = new Map(pinned.rules.map(rule => [rule.file, rule]));
111
165
  for (const rule of rules) {
112
166
  const pin = pinsByFile.get(rule.file);
113
- if (!pin) continue;
167
+ // A core rule is already delivered in full; a pin must not re-promote it to a kernel body.
168
+ if (!pin || rule.activation === 'core') continue;
114
169
  rule.canonicalActivation = rule.activation;
115
170
  rule.pinMatches = pin.matches;
116
171
  rule.pinned = true;
@@ -136,34 +191,35 @@ export async function prepareRuleDelivery(opts) {
136
191
  const key = `${anchor}\0${entry.path}`;
137
192
  if (!entryPaths.has(key)) entryPaths.set(key, path.resolve(anchor, entry.path));
138
193
  const resolvedPath = entryPaths.get(key);
139
- return { ...entry, path: opts.mapInputPath ? opts.mapInputPath(resolvedPath) : resolvedPath };
194
+ // Canonical IDs hash the indexed package-relative path. Keep it through
195
+ // resolution; the selected target is anchored to root below.
196
+ return { ...entry, path: entry.path.startsWith('agentic/code/') ? entry.path
197
+ : opts.mapInputPath ? opts.mapInputPath(resolvedPath) : resolvedPath };
140
198
  });
141
199
  let rebuilt = false;
142
200
  const cacheDir = process.env.AIWG_RULE_BINDING_INDEX_CACHE;
143
201
  const cacheFile = cacheDir && path.join(cacheDir, `${contentHash(opts.logicalInputPath ? opts.logicalInputPath(root) : root)}.json`);
144
202
  const buildSourceEntries = async (refresh = false) => {
145
203
  if (!refresh && cacheFile) {
146
- try { return JSON.parse(fs.readFileSync(cacheFile, 'utf8')).map(entry => ({ ...entry, path: opts.mapInputPath ? opts.mapInputPath(entry.path) : entry.path })); }
204
+ try { return JSON.parse(fs.readFileSync(cacheFile, 'utf8')).map(entry => ({ ...entry, path: path.isAbsolute(entry.path) && opts.mapInputPath ? opts.mapInputPath(entry.path) : entry.path })); }
147
205
  catch (error) {
148
206
  if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error;
149
207
  }
150
208
  }
151
- const indexTemp = fs.mkdtempSync(path.join(os.tmpdir(), 'aiwg-rule-index-'));
152
- try {
153
- const { buildIndex } = await importImpl(import.meta.url, 'artifacts/index-builder.js');
154
- await buildIndex(root, { scope: root === opts.srcRoot && !fs.existsSync(path.join(root, 'agentic')) ? '.' : 'agentic/code', outputDir: indexTemp, force: true, quiet: true });
155
- rebuilt = true;
156
- const index = JSON.parse(read(path.join(indexTemp, '.aiwg', '.index', 'metadata.json')));
157
- const entries = Object.values(index.entries).map(entry => ({ ...entry, path: path.resolve(root, entry.path) }));
158
- if (cacheFile) {
159
- fs.mkdirSync(cacheDir, { recursive: true });
160
- // Workers may publish the same immutable source snapshot concurrently.
161
- const temporary = `${cacheFile}.${process.pid}.tmp`;
162
- fs.writeFileSync(temporary, JSON.stringify(entries.map(entry => ({ ...entry, path: opts.logicalInputPath ? opts.logicalInputPath(entry.path) : entry.path }))));
163
- fs.renameSync(temporary, cacheFile);
164
- }
165
- return entries;
166
- } finally { fs.rmSync(indexTemp, { recursive: true, force: true }); }
209
+ const { collectRuleBindingEntries } = await importImpl(import.meta.url, 'artifacts/index-builder.js');
210
+ const sourceEntries = await collectRuleBindingEntries(root,
211
+ root === opts.srcRoot && !fs.existsSync(path.join(root, 'agentic')) ? '.' : 'agentic/code');
212
+ rebuilt = true;
213
+ const entries = sourceEntries.map(entry => ({ ...entry, path: entry.path.startsWith('agentic/code/')
214
+ ? entry.path : path.resolve(root, entry.path) }));
215
+ if (cacheFile) {
216
+ fs.mkdirSync(cacheDir, { recursive: true });
217
+ // Workers may publish the same immutable source snapshot concurrently.
218
+ const temporary = `${cacheFile}.${process.pid}.tmp`;
219
+ fs.writeFileSync(temporary, JSON.stringify(entries.map(entry => ({ ...entry, path: opts.logicalInputPath ? opts.logicalInputPath(entry.path) : entry.path }))));
220
+ fs.renameSync(temporary, cacheFile);
221
+ }
222
+ return entries;
167
223
  };
168
224
  const localEntries = opts.bindingEntries || loadEntries(opts.target, Boolean(packageRelations));
169
225
  let entries = [...localEntries];
@@ -244,20 +300,22 @@ export async function prepareRuleDelivery(opts) {
244
300
 
245
301
  export function renderRuleKernelIndex(rules, nativePaths = false) {
246
302
  const summary = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'summary');
247
- const paths = nativePaths ? [] : rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
248
- if (!summary.length && !paths.length) return '';
303
+ const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
304
+ const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
305
+ if (!summary.length && !paths.length && !kernels.length) return '';
249
306
  const lines = ['# Rule kernel index', ''];
250
- for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name}\`.`, '');
307
+ for (const rule of kernels) lines.push(`- ${rule.name} — ${rule.summary || 'Fetch and follow the full rule before governed work.'} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
308
+ for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
251
309
  if (paths.length) {
252
- lines.push('## Path rules (prompt emulation)', '',
253
- 'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
254
- for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name}\``, '');
310
+ lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
311
+ nativePaths ? 'The provider loads these rules for matching paths; full contracts remain available through the commands below.' : 'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
312
+ for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
255
313
  }
256
314
  return lines.join('\n');
257
315
  }
258
316
 
259
317
  function managed(content, opts) {
260
- return addManagedMarker(content, opts.deployVersion || 'unknown', opts.deploySource || 'bundled');
318
+ return addManagedMarker(content, opts.deployVersion, opts.deploySource || 'bundled');
261
319
  }
262
320
  function put(file, content) {
263
321
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -271,12 +329,21 @@ function block(content, body) {
271
329
 
272
330
  function putStartup(file, body) {
273
331
  const content = fs.existsSync(file) ? read(file) : '<!-- aiwg-managed -->\n';
274
- put(file, block(content, body));
332
+ writeContextFileSync(file, block(content, body));
275
333
  }
276
334
 
277
335
  /** Tagged rules bypass legacy aggregate writers. Untagged projections retain their tier policy. */
278
336
  function projectRules(opts, provider) {
279
337
  const { rules } = opts.ruleDelivery;
338
+ if (opts.contextFiles !== false && provider.name === 'hermes') {
339
+ // The old context generator carries hardcoded corpus directives. Only the
340
+ // selected contracts below may activate policy; preserve operator prose.
341
+ const file = path.join(opts.target, 'AGENTS.md');
342
+ const content = read(file);
343
+ const clean = content.replace(/(<!-- AIWG:hermes-context:start -->)([\s\S]*?)(<!-- AIWG:hermes-context:end -->)/g,
344
+ (_, start, body, end) => start + body.replace(/^### Rule: [\s\S]*?(?=^#{1,3} |(?![\s\S]))/gm, '') + end);
345
+ if (clean !== content) writeContextFileSync(file, clean);
346
+ }
280
347
  const nativePaths = ['claude', 'cursor'].includes(provider.name);
281
348
  const rulesDir = path.isAbsolute(provider.paths.rules || '') ? provider.paths.rules : path.join(opts.target, provider.paths.rules || `.${provider.name}/rules`);
282
349
  const tagged = rules.filter(rule => rule.tagged);
@@ -285,9 +352,14 @@ function projectRules(opts, provider) {
285
352
  const name = rule.behavior ? `behaviors/${path.basename(path.dirname(rule.file))}` : path.basename(rule.file, '.md');
286
353
  put(path.join(rulesDir, `${name}${extension}`), managed(content, opts));
287
354
  };
355
+ // Legacy projectors may have enumerated the whole corpus. Rebuild managed
356
+ // rule output from the canonical selection; preserve operator-owned files.
357
+ for (const file of walk(rulesDir)) {
358
+ if (/\.(md|mdc)$/.test(file) && /aiwg:managed/.test(read(file))) fs.rmSync(file);
359
+ }
288
360
  const priorManifestPath = path.join(rulesDir, '.aiwg-manifest.json');
289
361
  const priorSources = fs.existsSync(priorManifestPath) ? JSON.parse(read(priorManifestPath)).ruleSources || [] : [];
290
- for (const source of priorSources.filter(file => !fs.existsSync(file))) {
362
+ for (const source of priorSources.filter(file => !rules.some(rule => rule.file === file))) {
291
363
  const name = path.basename(source) === 'BEHAVIOR.md' ? `behaviors/${path.basename(path.dirname(source))}` : path.basename(source, '.md');
292
364
  for (const ext of ['.md', '.mdc']) {
293
365
  const stale = path.join(rulesDir, name + ext);
@@ -302,6 +374,26 @@ function projectRules(opts, provider) {
302
374
  if (/aiwg:managed/.test(read(dest))) fs.rmSync(dest);
303
375
  }
304
376
  }
377
+ // Core rules (#3450): compact bodies in the provider's always-loaded surface,
378
+ // independent of deploy mode. Native providers get one core-<name> file each;
379
+ // the others get a marked section first in their managed startup block.
380
+ const cores = [...new Map(tagged.filter(rule => rule.activation === 'core').map(rule => [rule.name, rule])).values()];
381
+ if (fs.existsSync(rulesDir)) {
382
+ for (const entry of fs.readdirSync(rulesDir).filter(name => /^core-.+\.(md|mdc)$/.test(name))) {
383
+ if (/aiwg:managed/.test(read(path.join(rulesDir, entry)))) fs.rmSync(path.join(rulesDir, entry));
384
+ }
385
+ }
386
+ if (nativePaths) {
387
+ for (const rule of cores) {
388
+ const body = provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${rule.coreBody}\n` : `${rule.coreBody}\n`;
389
+ const dest = path.join(rulesDir, `core-${path.basename(rule.file, '.md')}${extension}`);
390
+ if (fs.existsSync(dest) && !/aiwg:managed/.test(read(dest)) && !opts.force) {
391
+ console.warn(`Unmanaged core rule left in place: ${dest}. Re-run with --force to replace it.`);
392
+ continue;
393
+ }
394
+ put(dest, managed(body, opts));
395
+ }
396
+ }
305
397
  for (const rule of tagged) {
306
398
  if (rule.activation === 'kernel' || (nativePaths && rule.activation === 'paths')) {
307
399
  let content = stripTriggersFromContent(rule.content);
@@ -316,6 +408,16 @@ function projectRules(opts, provider) {
316
408
  writeRule(rule, content);
317
409
  }
318
410
  }
411
+ // Explicit local/external bundle deployments predate activation metadata.
412
+ // Keep their native rule files reachable while they migrate; missing metadata
413
+ // in the shipped corpus still uses the on-demand fallback (#3463).
414
+ for (const rule of rules.filter(rule => !rule.tagged && !rule.behavior)) {
415
+ const source = opts.logicalInputPath ? opts.logicalInputPath(rule.file) : rule.file;
416
+ if (/(?:^|\/)agentic\/code\//.test(source.replaceAll('\\', '/'))) continue;
417
+ let content = stripTriggersFromContent(rule.content);
418
+ if (provider.name === 'cursor') content = provider.transformRule(rule.file, content, opts);
419
+ writeRule(rule, content);
420
+ }
319
421
  // A legacy consolidated index can mention now-bound rules. Replace it with an
320
422
  // activation-selected index when migration starts; pure legacy deployments keep it.
321
423
  // Hermes's 20K context limit requires a fixed provider projection: only
@@ -325,8 +427,7 @@ function projectRules(opts, provider) {
325
427
  const isCritical = rule => String(rule.enforcement || '').trim().toLowerCase() === 'critical';
326
428
  const deferredKernels = provider.name === 'hermes'
327
429
  ? rules.filter(rule => rule.tagged && rule.activation === 'kernel' && !rule.pinned && !isCritical(rule)) : [];
328
- const indexRules = deferredKernels.length ? rules.map(rule => deferredKernels.includes(rule)
329
- ? { ...rule, activation: 'summary', summary: rule.summary || 'Fetch and follow the full rule before governed work.' } : rule) : rules;
430
+ const indexRules = rules;
330
431
  const index = renderRuleKernelIndex(indexRules, nativePaths);
331
432
  if (tagged.length) {
332
433
  for (const ext of ['.md', '.mdc']) {
@@ -335,6 +436,11 @@ function projectRules(opts, provider) {
335
436
  }
336
437
  if (index) put(path.join(rulesDir, `RULES-INDEX${extension}`), managed(provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${index}` : index, opts));
337
438
  }
439
+ const legacyDemand = rules.filter(rule => !rule.tagged && !rule.behavior && rule.activation === 'on-demand');
440
+ if (legacyDemand.length) put(path.join(rulesDir, `RULES-ONDEMAND${extension}`), managed([
441
+ '# On-demand rules', '', 'These rules require explicit activation metadata. Fetch the applicable full rule before governed work.', '',
442
+ ...legacyDemand.map(rule => `- \`${rule.name}\` — \`aiwg show rule ${rule.name} --body\``), '',
443
+ ].join('\n'), opts));
338
444
  fs.rmSync(path.join(rulesDir, '.aiwg-rules-budget.json'), { force: true });
339
445
  const manifestPath = path.join(rulesDir, '.aiwg-manifest.json');
340
446
  const manifest = fs.existsSync(manifestPath) ? JSON.parse(read(manifestPath)) : { managed: {} };
@@ -345,7 +451,7 @@ function projectRules(opts, provider) {
345
451
  }
346
452
  for (const name of Object.keys(manifest.managed)) if (!fs.existsSync(path.join(rulesDir, name))) delete manifest.managed[name];
347
453
  put(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
348
- if (!nativePaths) {
454
+ if (!nativePaths && opts.contextFiles !== false) {
349
455
  // The conventional directory is not a loader: startup reaches the complete
350
456
  // selected bodies through the provider's existing context owner.
351
457
  const kernels = rules.filter(rule => rule.activation === 'kernel' &&
@@ -354,8 +460,9 @@ function projectRules(opts, provider) {
354
460
  const routing = routes.length ? `## Invoked artifact definitions\n\nRead the complete definition before invoking its artifact:\n${routes.map(route => `- ${route}`).join('\n')}` : '';
355
461
  let startupIndex = index;
356
462
  let startupRouting = routing;
357
- const fetchKernels = deferredKernels.length ? 'Before every task and subagent task, fetch and follow the kernel rules listed below with `aiwg show rule <name>` before governed work. Their full rules are binding.' : '';
358
- const renderBody = () => [...kernels.map(rule => `## Rule: ${rule.name}\n\n${rule.body.trim()}`), fetchKernels, startupIndex, startupRouting].filter(Boolean).join('\n\n');
463
+ const fetchKernels = deferredKernels.length ? 'Before every task and subagent task, fetch and follow the kernel rules listed below with `aiwg show rule <name> --body` before governed work. Their full rules are binding.' : '';
464
+ const coreSection = cores.length ? `${CORE_RULES_START}\n## Core rules\n\n${cores.map(rule => rule.coreBody).join('\n\n')}\n${CORE_RULES_END}` : '';
465
+ const renderBody = () => [coreSection, ...kernels.map(rule => `## Rule: ${rule.name}\n\n${rule.body.trim()}`), fetchKernels, startupIndex, startupRouting].filter(Boolean).join('\n\n');
359
466
  const dest = path.join(opts.target, 'AGENTS.md');
360
467
  if (provider.name === 'hermes' && block(read(dest), renderBody()).length > 19_000) {
361
468
  // The complete summary/path index and definition routes can themselves
@@ -379,17 +486,22 @@ function projectRules(opts, provider) {
379
486
  if (!body) {
380
487
  // No activation-selected startup contract: retain legacy context owners
381
488
  // without inventing an empty policy file or a dangling bootstrap read.
382
- const contexts = ['AIWG.md', 'AGENTS.md', 'WARP.md', '.hermes.md'].map(name => path.join(opts.target, name));
489
+ const contexts = ['AIWG.md', 'AGENTS.md', 'WARP.md', '.hermes.md', inlineStartup[provider.name]].filter(Boolean).map(name => path.join(opts.target, name));
383
490
  if (opts.activeWorkspace) contexts.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
384
491
  for (const file of contexts) {
385
492
  if (!fs.existsSync(file)) continue;
386
493
  const content = read(file);
387
494
  const clean = block(content, '');
388
- if (clean !== content) put(file, clean);
495
+ if (clean !== content) writeContextFileSync(file, clean);
389
496
  }
390
497
  return;
391
498
  }
392
- if (provider.name === 'hermes') {
499
+ if (inlineStartup[provider.name]) {
500
+ // Retire the B1 indirect startup block when migrating to the actual loader.
501
+ const former = path.join(opts.target, 'AIWG.md');
502
+ if (fs.existsSync(former)) writeContextFileSync(former, block(read(former), ''));
503
+ putStartup(path.join(opts.target, inlineStartup[provider.name]), body);
504
+ } else if (provider.name === 'hermes') {
393
505
  const dest = path.join(opts.target, 'AGENTS.md');
394
506
  putStartup(dest, body);
395
507
  putStartup(path.join(opts.target, '.hermes.md'), 'Read and follow `AGENTS.md` in this directory before every task and subagent task.');
@@ -407,14 +519,19 @@ function projectRules(opts, provider) {
407
519
 
408
520
  /** External helpers also project skills/flows. Inject into their body, never descriptions. */
409
521
  function finishBindings(opts, provider) {
410
- const roots = [opts.target, opts.deploymentHome, ...Object.values(provider.paths).filter(p => path.isAbsolute(p))];
522
+ const roots = [opts.target, opts.deploymentHome,
523
+ ...[opts.kernelSkillsPath, opts.activeWorkspace, ...Object.values(provider.paths)].filter(p => p && path.isAbsolute(p))];
411
524
  const files = [...new Set(roots.flatMap(walk))];
525
+ const indexRel = provider.name === 'copilot' ? '.github/RULES-BOUND.md' : `.${provider.name}/RULES-BOUND.md`;
526
+ const indexFile = path.join(opts.target, indexRel);
527
+ const anchor = target => `${target.type}-${target.name}`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
528
+ const sections = new Map();
412
529
  const targetsBySource = new Map();
413
530
  for (const target of opts.ruleDelivery.targets) {
414
531
  targetsBySource.set(target.source, [...(targetsBySource.get(target.source) || []), target]);
415
532
  }
416
533
  for (const file of files.filter(file => /\.(md|toml)$/.test(file))) {
417
- if (!/(?:[/\\](?:skills|agents|commands)[/\\])/.test(file)) continue;
534
+ if (path.basename(file) !== 'SKILL.md' && !/(?:[/\\](?:skills?|agents?|droids|commands?|prompts|modes?)[/\\])/.test(file)) continue;
418
535
  if (/[/\\]rules[/\\]/.test(file) || /[/\\]\.aiwg[/\\](?!skills[/\\])/.test(file.replace(opts.target, ''))) continue;
419
536
  let content = read(file);
420
537
  if (!/aiwg:managed/.test(content) && !fs.existsSync(path.join(path.dirname(file), '.aiwg-managed')) && !fs.existsSync(path.join(path.dirname(file), '.aiwg-manifest.json'))) continue;
@@ -426,12 +543,30 @@ function finishBindings(opts, provider) {
426
543
  const flowSource = opts.flowSkillSources?.get(file);
427
544
  const targets = [...(flowSource ? targetsBySource.get(flowSource) || [] : []),
428
545
  ...(source ? targetsBySource.get(path.resolve(source)) || [] : opts.ruleDelivery.targets.filter(target =>
429
- (skill ? target.type !== 'agent' : file.includes('/agents/') ? target.type === 'agent' : target.type === 'flow') &&
546
+ (skill ? target.type !== 'agent' : /[/\\](?:agents?|droids|modes?)[/\\]/.test(file) ? target.type === 'agent' : target.type === 'flow') &&
430
547
  [target.name, path.basename(target.source, '.md'), path.basename(path.dirname(target.source))].includes(name)))];
431
548
  const rules = targets.flatMap(target => opts.ruleDelivery.bindings.get(target.source) || []);
432
549
  if (!rules.length && !content.includes('<!-- AIWG:bound-rules:start -->')) continue;
433
- if (file.endsWith('.toml')) content = content.replace(/^developer_instructions = (".*")$/m, (_, value) => `developer_instructions = ${JSON.stringify(replaceBoundRulesBlock(JSON.parse(value), rules))}`);
434
- else content = replaceBoundRulesBlock(content, rules);
550
+ const pointer = body => {
551
+ const clean = replaceBoundRulesBlock(body, []);
552
+ if (!rules.length) return clean;
553
+ const identities = [...new Map(targets.map(target => [`${target.type} ${target.name}`, target])).values()];
554
+ for (const target of identities) {
555
+ const bound = opts.ruleDelivery.bindings.get(target.source) || [];
556
+ const unique = [...new Map(bound.map(rule => [rule.name, rule])).values()].sort((a, b) => a.name.localeCompare(b.name));
557
+ if (unique.length) sections.set(anchor(target), `## ${anchor(target)}\n\n` + unique.map(rule =>
558
+ `- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`).join('\n'));
559
+ }
560
+ // Skills must carry exact fetch commands themselves: a section pointer
561
+ // alone loses the invocation contract in clean installs (#3473).
562
+ const fetch = skill ? [...new Map(rules.map(rule => [rule.name, rule])).values()]
563
+ .sort((a, b) => a.name.localeCompare(b.name))
564
+ .map(rule => `${rule.summary}\nFetch and follow before governed work: \`aiwg show rule ${rule.name} --body\`.`).join('\n') : '';
565
+ return `${clean}${clean.endsWith('\n') ? '' : '\n'}\n<!-- AIWG:bound-rules:start -->\nRead and follow before governed work: ${identities.map(target =>
566
+ `\`${indexRel}#${anchor(target)}\``).join('; ')}.\n${fetch ? fetch + '\n' : ''}<!-- AIWG:bound-rules:end -->\n`;
567
+ };
568
+ if (file.endsWith('.toml')) content = content.replace(/^developer_instructions = (".*")$/m, (_, value) => `developer_instructions = ${JSON.stringify(pointer(JSON.parse(value)))}`);
569
+ else content = pointer(content);
435
570
  put(file, content);
436
571
  // Keep deployment hashes honest after projection by external helpers.
437
572
  if (sidecar?.managed?.[path.basename(file)]) {
@@ -439,6 +574,10 @@ function finishBindings(opts, provider) {
439
574
  put(sidecarPath, `${JSON.stringify(sidecar, null, 2)}\n`);
440
575
  }
441
576
  }
577
+ if (sections.size) put(indexFile, managed('# Bound rules\n\nFetch and follow every full rule in the invoked artifact’s section before governed work.\n\n' +
578
+ [...sections.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, section]) => section).join('\n\n') + '\n', opts));
579
+ else if (/aiwg:managed/.test(read(indexFile))) fs.rmSync(indexFile);
580
+
442
581
  }
443
582
 
444
583
  /** Bindings annotate the selected deployment; they do not select standard skills
@@ -490,7 +629,8 @@ function projectBoundFlowSkills(opts, provider, selected) {
490
629
  if (!fs.existsSync(file) || receipt?.flowSource === entry.source) {
491
630
  const body = read(entry.source);
492
631
  const markdown = entry.source.endsWith('.md') ? body : `---\nname: ${name}\nnamespace: aiwg\ndescription: Invoke ${name}.\n---\n\n\`\`\`yaml\n${body}\n\`\`\`\n`;
493
- const content = managed(replaceBoundRulesBlock(markdown, opts.ruleDelivery.bindings.get(entry.source)), opts);
632
+ const projected = provider.transformSkillMd ? provider.transformSkillMd(markdown, opts) : markdown;
633
+ const content = managed(replaceBoundRulesBlock(projected, opts.ruleDelivery.bindings.get(entry.source)), opts);
494
634
  put(file, content);
495
635
  put(path.join(dir, '.aiwg-managed'), 'true\n');
496
636
  put(receiptFile, `${JSON.stringify({ flowSource: entry.source,
@@ -512,7 +652,7 @@ export function warpRuleContent(content) {
512
652
 
513
653
  function startupFiles(opts, provider) {
514
654
  const bridgeNames = { claude: ['CLAUDE.md'], cursor: ['AGENTS.md'], codex: ['AGENTS.md'], hermes: ['AGENTS.md', '.hermes.md'], pi: ['AGENTS.md'], warp: ['WARP.md'], openclaw: [] };
515
- const paths = ['AIWG.md', 'WORKSPACE.md', '.aiwg/AIWG.md', ...bridgeNames[provider.name]].map(name => path.join(opts.target, name));
655
+ const paths = ['AIWG.md', 'WORKSPACE.md', '.aiwg/AIWG.md', ...(bridgeNames[provider.name] || ['AGENTS.md']), inlineStartup[provider.name]].filter(Boolean).map(name => path.join(opts.target, name));
516
656
  if (opts.activeWorkspace) paths.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
517
657
  if (['claude', 'cursor', 'openclaw'].includes(provider.name)) {
518
658
  const dir = path.isAbsolute(provider.paths.rules) ? provider.paths.rules : path.join(opts.target, provider.paths.rules);
@@ -524,10 +664,11 @@ function startupFiles(opts, provider) {
524
664
  const parsed = parseRuleFrontmatter(content);
525
665
  if (parsed.data?.paths || parsed.data?.globs) content = content.slice(0, content.length - parsed.body.length);
526
666
  }
527
- // WARP.md aggregates agents and commands as well; only its rule content
528
- // (managed startup blocks, legacy inline rules, and on-demand lists) counts
529
- // toward the rules startup budget.
530
- if (provider.name === 'warp' && path.basename(file) === 'WARP.md') content = warpRuleContent(content);
667
+ // WARP.md and Windsurf's AGENTS.md aggregate agents (and Warp's commands)
668
+ // as well; only their rule content (managed startup blocks, legacy inline
669
+ // rules, and on-demand lists) counts toward the rules startup budget.
670
+ if ((provider.name === 'warp' && path.basename(file) === 'WARP.md')
671
+ || (provider.name === 'windsurf' && file === path.join(opts.target, 'AGENTS.md'))) content = warpRuleContent(content);
531
672
  return { path: file, content };
532
673
  });
533
674
  }
@@ -562,17 +703,23 @@ function capturePreimage(file, ancestors = new Set()) {
562
703
  }
563
704
  // cpSync materializes links; compare its output with the recursively resolved
564
705
  // content while retaining the original link-shaped preimage for commit drift.
565
- function materializeInput(from, to, ancestors = new Set()) {
566
- if (!fs.existsSync(from)) return;
567
- const real = fs.realpathSync(from);
568
- if (ancestors.has(real)) throw new Error(`Cyclic deployment input: ${from}`);
569
- const stat = fs.statSync(from);
706
+ function materializeInput(from, to, ancestors = new Set(), parentReady = false) {
707
+ let stat;
708
+ try { stat = fs.statSync(from); } catch (error) {
709
+ if (error.code === 'ENOENT') return;
710
+ throw error;
711
+ }
570
712
  if (stat.isDirectory()) {
713
+ // Only directories can lead back to an ancestor. Resolve them once and
714
+ // share the immutable ancestry set with their children, including aliases.
715
+ const real = fs.realpathSync(from);
716
+ if (ancestors.has(real)) throw new Error(`Cyclic deployment input: ${from}`);
717
+ const children = new Set([...ancestors, real]);
571
718
  fs.mkdirSync(to, { recursive: true });
572
- for (const name of fs.readdirSync(from)) materializeInput(path.join(from, name), path.join(to, name), new Set([...ancestors, real]));
719
+ for (const name of fs.readdirSync(from)) materializeInput(path.join(from, name), path.join(to, name), children, true);
573
720
  fs.chmodSync(to, stat.mode & 0o7777);
574
721
  } else if (stat.isFile()) {
575
- fs.mkdirSync(path.dirname(to), { recursive: true });
722
+ if (!parentReady) fs.mkdirSync(path.dirname(to), { recursive: true });
576
723
  fs.copyFileSync(from, to); fs.chmodSync(to, stat.mode & 0o7777);
577
724
  } else throw new Error(`Unsupported deployment input: ${from}`);
578
725
  }
@@ -774,12 +921,12 @@ function expectedAnchorPaths(paths) {
774
921
  /** Adopt a detached worker plan into the coordinator's process-local lease. */
775
922
  export function adoptRuleDeploymentPlan(detached, { scratch, target, inputPool, cleanup }) {
776
923
  const invalid = () => { throw new Error('Invalid transferred rule deployment plan'); };
777
- if (!exactObjectKeys(detached, ['version', 'lease', 'temp', 'target', 'stage', 'rootNames', 'workspaceDirs', 'preimages', 'poolSeal', 'anchors', 'snapshotRoot', 'corpusRoot', 'sourceMappings', 'staged', 'report', 'mappings']) ||
924
+ if (!exactObjectKeys(detached, ['version', 'lease', 'temp', 'target', 'stage', 'rootNames', 'workspaceDirs', 'preimages', 'poolSeal', 'anchors', 'snapshotRoot', 'corpusRoot', 'sourceMappings', 'staged', 'report', 'mappings', 'contextWrites']) ||
778
925
  detached.version !== 1 || typeof detached.lease !== 'string' || !(detached.poolSeal === null || typeof detached.poolSeal === 'string') ||
779
926
  !detached.report || typeof detached.report !== 'object' || Array.isArray(detached.report) || typeof scratch !== 'string' || typeof target !== 'string' || typeof cleanup !== 'function') invalid();
780
927
  const root = path.resolve(scratch), expectedTarget = path.resolve(target);
781
928
  if (path.resolve(detached.temp || '') !== root || path.resolve(detached.stage || '') !== path.join(root, 'project') || path.resolve(detached.target || '') !== expectedTarget) invalid();
782
- if (!Array.isArray(detached.staged) || !Array.isArray(detached.mappings) || !Array.isArray(detached.rootNames) || !Array.isArray(detached.preimages) ||
929
+ if (!Array.isArray(detached.contextWrites) || detached.contextWrites.some(file => typeof file !== 'string' || !within(root, file)) || !Array.isArray(detached.staged) || !Array.isArray(detached.mappings) || !Array.isArray(detached.rootNames) || !Array.isArray(detached.preimages) ||
783
930
  !Array.isArray(detached.workspaceDirs) || !Array.isArray(detached.anchors) || !Array.isArray(detached.sourceMappings) ||
784
931
  typeof detached.snapshotRoot !== 'string' || !path.isAbsolute(detached.snapshotRoot) || typeof detached.corpusRoot !== 'string' || !path.isAbsolute(detached.corpusRoot)) invalid();
785
932
  if (detached.rootNames.some(name => !safeRelative(expectedTarget, name))) invalid();
@@ -828,6 +975,9 @@ export async function prepareRuleDeployment(original, provider, deploy) {
828
975
  const previousText = fs.existsSync(manifestFile) ? read(manifestFile) : '';
829
976
  const previous = previousText ? JSON.parse(previousText) : {};
830
977
  const corpusRoot = resolveAiwgRoot(original.srcRoot) || original.srcRoot;
978
+ if (!original.deployVersion || /unknown/i.test(original.deployVersion)) {
979
+ original = { ...original, deployVersion: resolveDeployVersion(corpusRoot, original.deployVersion) };
980
+ }
831
981
  const sourceTree = fs.existsSync(path.join(corpusRoot, 'agentic', 'code'))
832
982
  ? path.join(corpusRoot, 'agentic', 'code') : original.srcRoot;
833
983
  const pool = original.inputPool && inputPools.get(original.inputPool);
@@ -846,7 +996,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
846
996
  const selectedRules = collectDeploymentRuleSources({ ...original, previousRuleSources: previous.ruleSources || [] });
847
997
  const initialPreimages = [...(pool ? pool.preimages : snapshotPreimages([sourceTree, path.join(corpusRoot, 'prebuilt', 'framework-relations'), path.join(corpusRoot, 'package.json'), path.join(corpusRoot, 'schemas')])), ...snapshotPreimages([path.join(corpusRoot, '.aiwg', '.index'),
848
998
  manifestFile, path.join(original.target, '.aiwg', 'aiwg.config'), path.join(original.target, '.aiwg', 'AIWG.md'), path.join(original.target, '.aiwg', 'skills'), path.join(original.target, '.aiwg-location'), ...projectGraphFiles, ...userGraphFiles, ...localSources,
849
- ...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...selectedRules, ...selectedRules.map(file => path.dirname(file)),
999
+ ...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...selectedRules, ...selectedRules.map(file => path.dirname(file)),
850
1000
  userConfigFile('aiwg.config'), process.env.AIWG_USER_REGISTRY_PATH || userConfigFile('installed.json'),
851
1001
  ...(original.ruleSources || []), ...(previous.ruleSources || []), ...(original.bindingEntries || []).map(entry => entry.path)])];
852
1002
  let manifestState = initialPreimages.find(item => item.path === path.resolve(manifestFile)).state;
@@ -866,6 +1016,10 @@ export async function prepareRuleDeployment(original, provider, deploy) {
866
1016
  const removeTemp = () => { removeStage(); if (pool) releaseInputPool(pool); };
867
1017
  const lease = randomUUID();
868
1018
  stagedCleanups.set(lease, { temp, cleanup: removeTemp, detach: () => { if (pool) releaseInputPool(pool); }, pool, seal: null, transferable });
1019
+ const priorContextStage = process.env[CONTEXT_STAGE_ENV];
1020
+ const contextStageLog = path.join(temp, 'context-writes.jsonl');
1021
+ fs.writeFileSync(contextStageLog, '');
1022
+ process.env[CONTEXT_STAGE_ENV] = contextStageLog;
869
1023
  const mappings = [];
870
1024
  const preimages = [...initialPreimages];
871
1025
  let retained = false;
@@ -928,7 +1082,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
928
1082
  if (JSON.stringify(capturePreimage(to)) !== JSON.stringify(materializedPreimage(expected || { kind: 'missing' }))) throw new Error(`Project-local input changed while preparing snapshot: ${from}`);
929
1083
  sourceMappings.push({ from, to });
930
1084
  }
931
- const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.ruleSources || []), ...(previous.ruleSources || []),
1085
+ const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...(original.ruleSources || []), ...(previous.ruleSources || []),
932
1086
  ...(original.bindingEntries || []).map(entry => entry.path)].map(file => path.resolve(file)))];
933
1087
  for (const [index, from] of externalInputs.entries()) {
934
1088
  if (sourceMappings.some(item => from === item.from || from.startsWith(`${item.from}${path.sep}`))) continue;
@@ -963,6 +1117,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
963
1117
  return result.split(snapshotRoot).join(corpusRoot);
964
1118
  };
965
1119
  original = { ...original, config: effectiveConfig, srcRoot: mapSource(original.srcRoot), pinConfigs, mapInputPath: mapSource, logicalInputPath,
1120
+ ...(original.additionalSources ? { additionalSources: original.additionalSources.map(mapSource) } : {}),
966
1121
  ...(original.ruleSources ? { ruleSources: original.ruleSources.map(mapSource) } : {}),
967
1122
  ...(original.bindingEntries ? { bindingEntries: original.bindingEntries.map(entry => ({ ...entry, path: mapSource(entry.path) })) } : {}) };
968
1123
  const target = path.resolve(original.target);
@@ -979,10 +1134,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
979
1134
  mappings.push({ from, to, before });
980
1135
  };
981
1136
  // Mount directories independently so only provider-owned surfaces are promoted.
982
- for (const dir of [`.${provider.name}`, '.agents', '.aiwg/skills']) mount(path.join(target, dir), path.join(stage, dir));
1137
+ const relativeRoots = Object.values(provider.paths).filter(dir => dir && !path.isAbsolute(dir))
1138
+ .map(dir => dir.split(/[\\/]/)[0]).filter(dir => dir && dir !== '.' && !path.extname(dir));
1139
+ for (const dir of new Set([`.${provider.name}`, '.agents', '.aiwg/skills', ...relativeRoots])) mount(path.join(target, dir), path.join(stage, dir));
983
1140
  fs.mkdirSync(path.join(stage, '.aiwg'), { recursive: true });
984
1141
  if (fs.existsSync(projectSnapshot)) materializeInput(projectSnapshot, path.join(stage, '.aiwg', 'aiwg.config'));
985
1142
  const rootNames = ['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md', '.aiwg/frameworks/registry.json'];
1143
+ if (provider.name === 'windsurf') rootNames.push('.windsurfrules');
986
1144
  for (const framework of getFrameworksForMode(original.srcRoot, original.mode)) {
987
1145
  for (const entry of framework.manifest?.memory?.creates || []) {
988
1146
  if (typeof entry?.path !== 'string' || !entry.path.startsWith('.aiwg/') || !path.extname(entry.path)) continue;
@@ -1092,11 +1250,11 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1092
1250
  preimages.push(...snapshotPreimages([...delivery.rules.map(rule => rule.file), ...delivery.targets.map(entry => entry.source), path.join(target, '.aiwg', 'aiwg.config')]));
1093
1251
  const opts = { ...original, target: stage, dryRun: false, ruleDelivery: delivery,
1094
1252
  kernelSkillsPath: kernel, deploymentHome: home, activeWorkspace, hermesLegacySkills, legacyCodexSkills, staging: true };
1095
- await deploy(opts);
1253
+ await deploySelectedSurface(opts, provider, deploy);
1096
1254
  if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly && opts.deployBehaviors !== false) {
1097
- deployEmulatedBehaviors(collectBehaviorDirs(opts.srcRoot), provider.name, stage, opts);
1255
+ deployEmulatedBehaviors(delivery.rules.filter(rule => rule.behavior).map(rule => path.dirname(rule.file)), provider.name, stage, opts);
1098
1256
  }
1099
- const selectedRoots = [original.srcRoot, ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
1257
+ const selectedRoots = [original.srcRoot, ...(original.additionalSources || []), ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
1100
1258
  const selected = entry => selectedRoots.some(root => /^(agents|skills|commands|flows|workflows)[/\\]/.test(path.relative(root, entry.source)));
1101
1259
  opts.definitionRoutes = [];
1102
1260
  if (provider.name === 'hermes' && !opts.kernelOnly && !opts.skillsOnly && !opts.commandsOnly && !opts.rulesOnly) {
@@ -1104,7 +1262,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1104
1262
  const selectedAgents = new Set(filterAgentFiles(agents.map(target => target.source), opts));
1105
1263
  for (const target of agents.filter(target => selectedAgents.has(target.source))) {
1106
1264
  const source = target.source;
1107
- put(path.join(stage, '.hermes', 'agents', path.basename(source)), replaceBoundRulesBlock(read(source), delivery.bindings.get(source)));
1265
+ put(path.join(stage, '.hermes', 'agents', path.basename(source)), managed(replaceBoundRulesBlock(read(source), delivery.bindings.get(source)), opts));
1108
1266
  opts.definitionRoutes.push(`${target.name}: .hermes/agents/${path.basename(source)}`);
1109
1267
  }
1110
1268
  }
@@ -1115,8 +1273,10 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1115
1273
  if (fs.existsSync(dest)) opts.definitionRoutes.push(`${entry.name}: .warp/agents/${path.basename(entry.source)}`);
1116
1274
  }
1117
1275
  }
1118
- if (opts.deployRules || opts.rulesOnly || (!opts.skillsOnly && !opts.commandsOnly && ['hermes', 'warp'].includes(provider.name))) projectRules(opts, provider);
1119
- finishBindings(opts, provider);
1276
+ if (!opts.deferRuleProjection) {
1277
+ if (opts.kernelOnly || opts.deployRules || opts.rulesOnly || (!opts.skillsOnly && !opts.commandsOnly && (inlineStartup[provider.name] || ['hermes', 'warp'].includes(provider.name)))) projectRules(opts, provider);
1278
+ finishBindings(opts, provider);
1279
+ }
1120
1280
  // Normalize all generated path wrappers before counting or promoting bytes.
1121
1281
  const normalize = content => {
1122
1282
  for (const mapping of [...mappings].sort((a, b) => b.to.length - a.to.length)) content = content.split(mapping.to).join(mapping.from);
@@ -1150,6 +1310,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1150
1310
  const delegated = (from, rel) => mappings.some(mapping => mapping.from !== from
1151
1311
  && mapping.from.startsWith(`${from}${path.sep}`)
1152
1312
  && (path.join(from, rel) === mapping.from || path.join(from, rel).startsWith(`${mapping.from}${path.sep}`)));
1313
+ if (original.contextFiles === false) {
1314
+ // Keep context inputs in the snapshot for validation, but exclude them
1315
+ // from both dry-run output and promotion (including unchanged rewrites).
1316
+ for (let i = rootNames.length - 1; i >= 0; i--) {
1317
+ if (['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md'].includes(rootNames[i])) rootNames.splice(i, 1);
1318
+ }
1319
+ }
1153
1320
  if (pool) verifyInputPool(pool);
1154
1321
  if (original.previewReport) return { report: guarded.report, noop: true };
1155
1322
  if (original.dryRun) {
@@ -1180,7 +1347,8 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1180
1347
  }
1181
1348
  };
1182
1349
  collectDirs(path.join(stage, '.aiwg'), '.aiwg');
1183
- const plan = { version: 1, lease, temp, target, stage, rootNames, workspaceDirs, preimages, poolSeal: pool?.inventorySeal || null,
1350
+ const contextWrites = [...new Set(fs.readFileSync(contextStageLog, 'utf8').split('\n').filter(Boolean).map(line => JSON.parse(line)))];
1351
+ const plan = { version: 1, lease, temp, target, stage, contextWrites, rootNames, workspaceDirs, preimages, poolSeal: pool?.inventorySeal || null,
1184
1352
  anchors: captureAnchors([target, ...mappings.map(mapping => mapping.from), ...workspaceDirs.map(dir => path.join(target, dir))]),
1185
1353
  snapshotRoot, corpusRoot, sourceMappings, staged: snapshotPreimages([stage, ...mappings.map(mapping => mapping.to)]), report: guarded.report,
1186
1354
  mappings: mappings.map(({ from, to, before }) => ({ from, to, before: [...before] })) };
@@ -1188,6 +1356,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1188
1356
  retained = true;
1189
1357
  return plan;
1190
1358
  } finally {
1359
+ if (priorContextStage === undefined) delete process.env[CONTEXT_STAGE_ENV]; else process.env[CONTEXT_STAGE_ENV] = priorContextStage;
1191
1360
  Object.assign(provider.paths, originals);
1192
1361
  if (priorAiwgRoot === undefined) delete process.env.AIWG_ROOT; else process.env.AIWG_ROOT = priorAiwgRoot;
1193
1362
  if (priorRegistry === undefined) delete process.env.AIWG_USER_REGISTRY_PATH; else process.env.AIWG_USER_REGISTRY_PATH = priorRegistry;
@@ -1244,12 +1413,32 @@ export function commitRuleDeploymentPlan(plan) {
1244
1413
  if (before.get(rel) === fileDigest(file)) continue;
1245
1414
  const buffer = fs.readFileSync(file);
1246
1415
  // Provider output includes binary skill resources; normalize text only.
1247
- const output = buffer.includes(0) ? buffer : Buffer.from(normalize(buffer.toString('utf8')));
1416
+ let output = buffer.includes(0) ? buffer : Buffer.from(normalize(buffer.toString('utf8')));
1417
+ if (path.basename(file) === '.aiwg-manifest.json') {
1418
+ const receipt = JSON.parse(output.toString('utf8'));
1419
+ for (const [name, record] of Object.entries(receipt.managed || {})) {
1420
+ const recordedFile = path.resolve(path.dirname(file), name);
1421
+ if (!within(path.dirname(file), recordedFile) || !fs.existsSync(recordedFile)) continue;
1422
+ const recorded = fs.readFileSync(recordedFile);
1423
+ // Do not bless operator edits whose bytes already differ from the
1424
+ // ownership receipt. Only normalize hashes of pristine staged files.
1425
+ if (record.hash !== `sha256:${contentHash(recorded)}`) continue;
1426
+ const promoted = recorded.includes(0) ? recorded : Buffer.from(normalize(recorded.toString('utf8')));
1427
+ record.hash = `sha256:${contentHash(promoted)}`;
1428
+ }
1429
+ output = Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`);
1430
+ }
1248
1431
  fs.mkdirSync(path.dirname(path.join(from, rel)), { recursive: true });
1249
- fs.writeFileSync(path.join(from, rel), output);
1432
+ if (plan.contextWrites.includes(file)) writeContextFileSync(path.join(from, rel), output);
1433
+ else fs.writeFileSync(path.join(from, rel), output);
1250
1434
  }
1251
1435
  }
1252
- for (const name of rootNames) if (fs.existsSync(path.join(stage, name))) put(path.join(target, name), normalize(read(path.join(stage, name))));
1436
+ for (const name of rootNames) if (fs.existsSync(path.join(stage, name))) {
1437
+ const output = normalize(read(path.join(stage, name)));
1438
+ if (output === read(path.join(target, name))) continue;
1439
+ const write = plan.contextWrites.includes(path.join(stage, name)) ? writeContextFileSync : put;
1440
+ write(path.join(target, name), output);
1441
+ }
1253
1442
  return plan.report;
1254
1443
  } finally {
1255
1444
  disposeRuleDeploymentPlan(plan);
@@ -1258,6 +1447,13 @@ export function commitRuleDeploymentPlan(plan) {
1258
1447
 
1259
1448
  /** Stage and commit serially for existing provider callers. */
1260
1449
  export async function deployWithRuleDelivery(original, provider, deploy) {
1450
+ // Kernel source passes share the same selected rule set. Deploy their
1451
+ // skills directly; the final source projects rules and bindings across the
1452
+ // complete destination. Full passes retain staging for bound flow skills
1453
+ // and provider definitions, but defer rule presentation above.
1454
+ if (original.kernelOnly && original.deferRuleProjection && !globalThis[PREPARATION_HOOK]) {
1455
+ return deploySelectedSurface(original, provider, deploy);
1456
+ }
1261
1457
  if (original.ruleDelivery) return deploy(original);
1262
1458
  const hook = globalThis[PREPARATION_HOOK];
1263
1459
  if (hook) {
@@ -1268,3 +1464,16 @@ export async function deployWithRuleDelivery(original, provider, deploy) {
1268
1464
  }
1269
1465
  return commitRuleDeploymentPlan(await prepareRuleDeployment(original, provider, deploy));
1270
1466
  }
1467
+
1468
+ async function deploySelectedSurface(opts, provider, deploy) {
1469
+ if (!opts.kernelOnly || provider.name !== 'warp') return deploy(opts);
1470
+ // Warp's aggregate writer emits bulk definitions even in skills-only mode.
1471
+ // Both staged and deferred kernel passes use only its shared skill router.
1472
+ const skills = [...listSkillDirs(path.join(opts.srcRoot, 'skills')),
1473
+ ...getFrameworksForMode(opts.srcRoot, opts.mode).flatMap(bundle => listSkillDirs(path.join(bundle.path, 'skills'))),
1474
+ ...getAddonSkillDirs(opts.srcRoot)].filter(isKernelSkill);
1475
+ const standard = path.join(opts.target, provider.paths.skills);
1476
+ for (const dir of new Set([opts.kernelSkillsPath || provider.kernelSkillsPath, provider.paths.crossAgentSkills])) {
1477
+ if (dir) deploySkillsWithKernelRouting(skills, standard, path.isAbsolute(dir) ? dir : path.join(opts.target, dir), opts);
1478
+ }
1479
+ }