@aiwg/cli 2026.10.4 → 2026.10.6

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 (220) 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 +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +72 -9
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +146 -16
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-preparation.js +91 -26
  16. package/dist/src/artifacts/index-reader.js +44 -0
  17. package/dist/src/artifacts/index-refresh.js +130 -56
  18. package/dist/src/artifacts/index-status.js +20 -5
  19. package/dist/src/artifacts/index-worker.mjs +9 -7
  20. package/dist/src/artifacts/memory-maintenance.js +503 -0
  21. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  22. package/dist/src/artifacts/query-engine.js +263 -108
  23. package/dist/src/artifacts/relations-builder.js +2 -2
  24. package/dist/src/artifacts/source-graph.js +2 -2
  25. package/dist/src/artifacts/types.js +9 -1
  26. package/dist/src/cli/handlers/decision.js +19 -1
  27. package/dist/src/cli/handlers/guided-setup.js +470 -0
  28. package/dist/src/cli/handlers/help.js +2 -2
  29. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  30. package/dist/src/cli/handlers/index.js +4 -2
  31. package/dist/src/cli/handlers/init.js +41 -6
  32. package/dist/src/cli/handlers/local-executor.js +5 -3
  33. package/dist/src/cli/handlers/mc.js +509 -53
  34. package/dist/src/cli/handlers/models.js +32 -2
  35. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  36. package/dist/src/cli/handlers/rules.js +1 -1
  37. package/dist/src/cli/handlers/script-runner.js +73 -2
  38. package/dist/src/cli/handlers/serve.js +5 -3
  39. package/dist/src/cli/handlers/sessions.js +40 -6
  40. package/dist/src/cli/handlers/setup.js +428 -1
  41. package/dist/src/cli/handlers/skill-lint.js +1 -1
  42. package/dist/src/cli/handlers/subcommands.js +118 -8
  43. package/dist/src/cli/handlers/use.js +279 -93
  44. package/dist/src/cli/handlers/workspace.js +3 -1
  45. package/dist/src/cli/prompt-utils.js +8 -0
  46. package/dist/src/cli/router.js +16 -1
  47. package/dist/src/config/aiwg-config.js +42 -0
  48. package/dist/src/config/cli.js +7 -3
  49. package/dist/src/config/decision-config.js +14 -1
  50. package/dist/src/config/native-sandbox-warnings.js +25 -0
  51. package/dist/src/contributors/validation.js +6 -5
  52. package/dist/src/decision/admission.js +7 -3
  53. package/dist/src/decision/benchmark.js +469 -0
  54. package/dist/src/decision/ledger.js +22 -3
  55. package/dist/src/decision/pricing.js +147 -0
  56. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  57. package/dist/src/decision/qualification/load-staged.js +539 -0
  58. package/dist/src/decision/qualification/load.js +109 -33
  59. package/dist/src/decision/qualification/runner.js +5 -2
  60. package/dist/src/decision/review/service.js +6 -4
  61. package/dist/src/decision/review/validate.js +23 -2
  62. package/dist/src/decision/savings.js +8 -3
  63. package/dist/src/discovery/index.js +492 -0
  64. package/dist/src/discovery/interview.js +233 -0
  65. package/dist/src/discovery/plan-apply.js +571 -0
  66. package/dist/src/discovery/plan-store.js +171 -0
  67. package/dist/src/discovery/plan-types.js +2 -0
  68. package/dist/src/discovery/plan-validation.js +139 -0
  69. package/dist/src/discovery/types.js +2 -0
  70. package/dist/src/extensions/commands/definitions.js +49 -9
  71. package/dist/src/extensions/manifest.js +71 -44
  72. package/dist/src/extensions/project-local-discovery.js +1 -1
  73. package/dist/src/extensions/project-quickref.js +18 -17
  74. package/dist/src/extensions/validation.js +32 -29
  75. package/dist/src/housekeeper/daemon-config.js +148 -0
  76. package/dist/src/housekeeper/ipc.js +84 -0
  77. package/dist/src/housekeeper/lifecycle.js +148 -0
  78. package/dist/src/housekeeper/platform.js +35 -0
  79. package/dist/src/housekeeper/status.js +70 -0
  80. package/dist/src/housekeeper/units.js +114 -0
  81. package/dist/src/jobs/flow.js +22 -21
  82. package/dist/src/lint/rule-activation.js +20 -5
  83. package/dist/src/lint/runner.js +1 -1
  84. package/dist/src/mcp/cli.mjs +5 -6
  85. package/dist/src/mcp/config-file.mjs +6 -2
  86. package/dist/src/mcp/config-layers.mjs +23 -7
  87. package/dist/src/mcp/profiles.js +18 -31
  88. package/dist/src/mcp/profiles.mjs +18 -32
  89. package/dist/src/mcp/registry.js +7 -27
  90. package/dist/src/mcp/registry.mjs +7 -28
  91. package/dist/src/mcp/server.mjs +34 -1
  92. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  93. package/dist/src/mcp/tools/command-run.mjs +3 -2
  94. package/dist/src/mcp/tools/decision.mjs +49 -48
  95. package/dist/src/mcp/tools/discovery.mjs +46 -21
  96. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  97. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  98. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  99. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  100. package/dist/src/memory/audit.js +719 -0
  101. package/dist/src/memory/canonical-context.js +392 -13
  102. package/dist/src/memory/canonical-maintenance.js +95 -0
  103. package/dist/src/memory/cli-result.js +22 -0
  104. package/dist/src/memory/cli.js +176 -0
  105. package/dist/src/memory/context-pack.js +57 -6
  106. package/dist/src/memory/cost.js +512 -0
  107. package/dist/src/memory/firewall-classification.d.mts +7 -0
  108. package/dist/src/memory/firewall-classification.mjs +32 -0
  109. package/dist/src/memory/index.js +8 -0
  110. package/dist/src/memory/line-maintenance.js +68 -0
  111. package/dist/src/memory/maintenance-cli.js +286 -0
  112. package/dist/src/memory/maintenance-contract.js +136 -0
  113. package/dist/src/memory/maintenance-index.js +18 -0
  114. package/dist/src/memory/maintenance-owner.js +2 -0
  115. package/dist/src/memory/maintenance-record.js +138 -0
  116. package/dist/src/memory/maintenance.js +254 -0
  117. package/dist/src/memory/provider-memory-adapter.js +154 -0
  118. package/dist/src/memory/recommend-cli.js +83 -0
  119. package/dist/src/memory/recommend.js +269 -0
  120. package/dist/src/memory/scheduled.js +81 -0
  121. package/dist/src/memory/taxonomy.js +44 -0
  122. package/dist/src/mission-protocol/codecs.js +19 -9
  123. package/dist/src/models/config-loader.js +11 -1
  124. package/dist/src/models/model-capabilities.v1.json +74 -50
  125. package/dist/src/models/provider-policy.js +125 -62
  126. package/dist/src/models/resolver.js +45 -8
  127. package/dist/src/models/worker-installation.d.mts +7 -0
  128. package/dist/src/models/worker-installation.mjs +169 -37
  129. package/dist/src/models/wrapper-route.js +1 -1
  130. package/dist/src/output-modes/registry.js +8 -7
  131. package/dist/src/plugin/metadata-validator.js +1 -1
  132. package/dist/src/providers/deploy-version.d.mts +1 -0
  133. package/dist/src/providers/deploy-version.mjs +17 -0
  134. package/dist/src/providers/provider-definitions.js +19 -16
  135. package/dist/src/providers/skill-ownership.d.mts +11 -2
  136. package/dist/src/providers/skill-ownership.mjs +35 -17
  137. package/dist/src/rules/activation.d.mts +11 -2
  138. package/dist/src/rules/activation.mjs +82 -6
  139. package/dist/src/rules/inventory.js +1 -1
  140. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  141. package/dist/src/schema/zod-validation.d.mts +6 -0
  142. package/dist/src/schema/zod-validation.mjs +50 -0
  143. package/dist/src/serve/executor-registry.js +297 -20
  144. package/dist/src/serve/executor-token.d.mts +1 -0
  145. package/dist/src/serve/executor-token.mjs +8 -0
  146. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  147. package/dist/src/serve/worker-invocation.js +114 -15
  148. package/dist/src/sessions/adapters/claude.js +19 -18
  149. package/dist/src/sessions/adapters/codex.js +33 -19
  150. package/dist/src/sessions/adapters/copilot.js +14 -13
  151. package/dist/src/sessions/adapters/cursor.js +19 -18
  152. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  153. package/dist/src/sessions/adapters/factory.js +11 -10
  154. package/dist/src/sessions/adapters/generic.js +32 -23
  155. package/dist/src/sessions/adapters/grokbot.js +35 -35
  156. package/dist/src/sessions/adapters/hermes.js +20 -19
  157. package/dist/src/sessions/adapters/muse.js +50 -49
  158. package/dist/src/sessions/adapters/omp.js +5 -4
  159. package/dist/src/sessions/adapters/openclaw.js +30 -29
  160. package/dist/src/sessions/adapters/opencode.js +26 -26
  161. package/dist/src/sessions/adapters/openhuman.js +24 -23
  162. package/dist/src/sessions/adapters/pi.js +5 -4
  163. package/dist/src/sessions/adapters/windsurf.js +8 -8
  164. package/dist/src/sessions/analytics.js +10 -9
  165. package/dist/src/sessions/batch-contracts.js +1 -1
  166. package/dist/src/sessions/batch-import.js +202 -2
  167. package/dist/src/sessions/candidates.js +7 -6
  168. package/dist/src/sessions/contracts.js +73 -54
  169. package/dist/src/sessions/coverage-preflight.js +26 -1
  170. package/dist/src/sessions/output-registration.js +11 -10
  171. package/dist/src/sessions/policy.js +18 -7
  172. package/dist/src/sessions/refresh.js +2 -3
  173. package/dist/src/sessions/repository.js +4 -2
  174. package/dist/src/sessions/workspace-discovery.js +92 -26
  175. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  176. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  177. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  178. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  179. package/dist/src/update/checker.d.mts +4 -0
  180. package/dist/src/update/checker.mjs +14 -0
  181. package/dist/src/writing/exemplar-selection.js +6 -6
  182. package/dist/src/writing/voice-evaluation.js +24 -23
  183. package/dist/src/writing/writer-migration.js +14 -13
  184. package/dist/src/writing/writer-profile.js +19 -18
  185. package/dist/src/writing/writing-brief.js +18 -17
  186. package/dist/src/writing/writing-receipt.js +47 -46
  187. package/package.json +5 -2
  188. package/schemas/decision/DecisionReview.schema.json +78 -1
  189. package/schemas/mission-v1.schema.json +3 -0
  190. package/tools/agents/deploy-agents.mjs +67 -39
  191. package/tools/agents/providers/antigravity.mjs +6 -1
  192. package/tools/agents/providers/base.mjs +19 -13
  193. package/tools/agents/providers/claude.mjs +1 -1
  194. package/tools/agents/providers/codex.mjs +50 -56
  195. package/tools/agents/providers/copilot.mjs +8 -2
  196. package/tools/agents/providers/cursor.mjs +1 -1
  197. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  198. package/tools/agents/providers/factory.mjs +7 -1
  199. package/tools/agents/providers/grok-build.mjs +18 -2
  200. package/tools/agents/providers/grokbot.mjs +1 -1
  201. package/tools/agents/providers/hermes.mjs +7 -13
  202. package/tools/agents/providers/muse-hooks.mjs +21 -14
  203. package/tools/agents/providers/muse.mjs +7 -2
  204. package/tools/agents/providers/omp.mjs +28 -4
  205. package/tools/agents/providers/opencode.mjs +14 -5
  206. package/tools/agents/providers/openhuman.mjs +1 -1
  207. package/tools/agents/providers/pi.mjs +1 -1
  208. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  209. package/tools/agents/providers/rule-delivery.mjs +263 -71
  210. package/tools/agents/providers/windsurf.mjs +7 -0
  211. package/tools/cli/workspace-status.mjs +5 -0
  212. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  213. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  214. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  215. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  216. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  217. package/tools/ralph-external/process-monitor.mjs +7 -4
  218. package/tools/ralph-external/session-launcher.mjs +280 -32
  219. package/tools/skills/deploy-skills-codex.mjs +11 -7
  220. 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,12 +6,14 @@ 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');
@@ -65,15 +67,62 @@ function promoteDirectories(source, destination, skip = () => false, prefix = ''
65
67
  }
66
68
  const read = file => fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
67
69
  const structural = file => /^(RULES-INDEX|RULES-ONDEMAND)\.(md|mdc)$/.test(path.basename(file));
70
+ // Verified startup owners from provider-definitions.ts. These providers do not
71
+ // need a prose fetch of AIWG.md to activate their compact core contracts.
72
+ const inlineStartup = {
73
+ copilot: '.github/copilot-instructions.md', factory: 'AGENTS.md', opencode: 'AGENTS.md',
74
+ windsurf: 'AGENTS.md', antigravity: 'AGENTS.md', muse: 'AGENTS.md',
75
+ omp: '.omp/AGENTS.md', 'grok-build': 'AGENTS.md',
76
+ };
68
77
 
69
78
  /** Collect selected source contracts, before any tier filter or projection. */
70
79
  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'));
80
+ if (opts.ruleSources) {
81
+ // Pin-only callers enumerate rule files separately from behavior contracts.
82
+ // Retain earlier behaviors only while their owning bundle is still selected.
83
+ const selected = new Set(collectDeploymentRuleSources({ ...opts, ruleSources: undefined }));
84
+ const behaviors = (opts.previousRuleSources || []).filter(file => path.basename(file) === 'BEHAVIOR.md' && selected.has(file));
85
+ return [...new Set([...opts.ruleSources, ...behaviors])];
86
+ }
87
+ const corpus = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
88
+ const bundles = [];
89
+ const frameworks = getFrameworksForMode(corpus, 'all');
90
+ for (const kind of ['frameworks', 'addons', 'extensions', 'plugins']) {
91
+ const dir = path.join(corpus, 'agentic', 'code', kind);
92
+ if (!fs.existsSync(dir)) continue;
93
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
94
+ if (!entry.isDirectory()) continue;
95
+ const root = path.join(dir, entry.name);
96
+ const manifest = JSON.parse(read(path.join(root, 'manifest.json')) || '{}');
97
+ bundles.push({ root, manifest, id: manifest.id || manifest.framework || entry.name,
98
+ aliases: [entry.name, ...(manifest.modeAliases || []), ...(manifest.aliases || []),
99
+ ...(kind === 'frameworks' ? frameworks.find(framework => framework.path === root)?.aliases || [] : [])] });
100
+ }
101
+ }
102
+ const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
103
+ // Project installation is authoritative. User-wide discoverability is not a
104
+ // project install, and a previous projection must never reselect retired rules.
105
+ const installed = configs.project.installed || {};
106
+ const names = Array.isArray(installed) ? installed : Object.keys(installed);
107
+ const selected = new Set();
108
+ const select = name => {
109
+ const id = name.replace(/^(framework|addon|extension|plugin):/, '');
110
+ const bundle = bundles.find(b => b.id === id || b.aliases.includes(id));
111
+ if (!bundle || selected.has(bundle)) return;
112
+ selected.add(bundle);
113
+ const required = Array.isArray(bundle.manifest.dependencies) ? bundle.manifest.dependencies : bundle.manifest.dependencies?.required || [];
114
+ for (const dep of Array.isArray(required) ? required : Object.keys(required)) select(dep);
115
+ };
116
+ for (const bundle of bundles.filter(b => b.manifest.core && b.manifest.autoInstall)) select(bundle.id);
117
+ for (const name of names.filter(name => name !== 'all')) select(name);
118
+ const invoked = bundles.find(bundle => path.resolve(bundle.root) === path.resolve(opts.srcRoot));
119
+ if (invoked && !opts.kernelOnly) select(invoked.id);
120
+ // Include the invocation being installed before its config entry is saved.
121
+ if (!opts.kernelOnly) for (const framework of getFrameworksForMode(opts.srcRoot, opts.mode === 'all' && names.length ? 'general' : opts.mode)) select(framework.id);
122
+ const roots = [...(!invoked || !opts.kernelOnly ? [opts.srcRoot] : []), ...(opts.additionalSources || []), ...[...selected].map(bundle => bundle.root)];
123
+ for (const entry of Object.values(installed)) if (entry?.localPath) roots.push(path.resolve(opts.target, entry.localPath));
124
+ const files = roots.flatMap(root => listMdFiles(path.join(root, 'rules'))).filter(file => !structural(file));
125
+ const behaviors = roots.flatMap(root => walk(path.join(root, 'behaviors')).filter(file => path.basename(file) === 'BEHAVIOR.md'));
77
126
  return [...new Set([...files, ...behaviors])];
78
127
  }
79
128
 
@@ -85,12 +134,16 @@ export async function prepareRuleDelivery(opts) {
85
134
  const behavior = path.basename(file) === 'BEHAVIOR.md';
86
135
  const rule = { ...parsed.rule, file, content, body: parsed.body, behavior,
87
136
  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));
137
+ if (!rule.tagged) rule.activation = inferRuleActivation();
138
+ // Core rules (#3450) carry a compact body beside the full rule; the full text is never inlined.
139
+ if (rule.activation === 'core') rule.coreBody = readCoreBody(file);
89
140
  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}`);
141
+ 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
142
  }
92
143
  return rule;
93
144
  });
145
+ const coreErrors = validateCoreSet([...new Map(rules.filter(rule => rule.coreBody !== undefined).map(rule => [rule.name, { name: rule.name, text: rule.coreBody }])).values()]);
146
+ if (coreErrors.length) throw new Error(coreErrors.join('; '));
94
147
  const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
95
148
  const installed = installedRuleCapabilities(configs);
96
149
  const root = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
@@ -110,7 +163,8 @@ export async function prepareRuleDelivery(opts) {
110
163
  const pinsByFile = new Map(pinned.rules.map(rule => [rule.file, rule]));
111
164
  for (const rule of rules) {
112
165
  const pin = pinsByFile.get(rule.file);
113
- if (!pin) continue;
166
+ // A core rule is already delivered in full; a pin must not re-promote it to a kernel body.
167
+ if (!pin || rule.activation === 'core') continue;
114
168
  rule.canonicalActivation = rule.activation;
115
169
  rule.pinMatches = pin.matches;
116
170
  rule.pinned = true;
@@ -148,22 +202,19 @@ export async function prepareRuleDelivery(opts) {
148
202
  if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error;
149
203
  }
150
204
  }
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 }); }
205
+ const { collectRuleBindingEntries } = await importImpl(import.meta.url, 'artifacts/index-builder.js');
206
+ const sourceEntries = await collectRuleBindingEntries(root,
207
+ root === opts.srcRoot && !fs.existsSync(path.join(root, 'agentic')) ? '.' : 'agentic/code');
208
+ rebuilt = true;
209
+ const entries = sourceEntries.map(entry => ({ ...entry, path: path.resolve(root, entry.path) }));
210
+ if (cacheFile) {
211
+ fs.mkdirSync(cacheDir, { recursive: true });
212
+ // Workers may publish the same immutable source snapshot concurrently.
213
+ const temporary = `${cacheFile}.${process.pid}.tmp`;
214
+ fs.writeFileSync(temporary, JSON.stringify(entries.map(entry => ({ ...entry, path: opts.logicalInputPath ? opts.logicalInputPath(entry.path) : entry.path }))));
215
+ fs.renameSync(temporary, cacheFile);
216
+ }
217
+ return entries;
167
218
  };
168
219
  const localEntries = opts.bindingEntries || loadEntries(opts.target, Boolean(packageRelations));
169
220
  let entries = [...localEntries];
@@ -244,20 +295,22 @@ export async function prepareRuleDelivery(opts) {
244
295
 
245
296
  export function renderRuleKernelIndex(rules, nativePaths = false) {
246
297
  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 '';
298
+ const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
299
+ const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
300
+ if (!summary.length && !paths.length && !kernels.length) return '';
249
301
  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}\`.`, '');
302
+ 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\`.`, '');
303
+ for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
251
304
  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}\``, '');
305
+ lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
306
+ 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.', '');
307
+ for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
255
308
  }
256
309
  return lines.join('\n');
257
310
  }
258
311
 
259
312
  function managed(content, opts) {
260
- return addManagedMarker(content, opts.deployVersion || 'unknown', opts.deploySource || 'bundled');
313
+ return addManagedMarker(content, opts.deployVersion, opts.deploySource || 'bundled');
261
314
  }
262
315
  function put(file, content) {
263
316
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -277,6 +330,15 @@ function putStartup(file, body) {
277
330
  /** Tagged rules bypass legacy aggregate writers. Untagged projections retain their tier policy. */
278
331
  function projectRules(opts, provider) {
279
332
  const { rules } = opts.ruleDelivery;
333
+ if (opts.contextFiles !== false && provider.name === 'hermes') {
334
+ // The old context generator carries hardcoded corpus directives. Only the
335
+ // selected contracts below may activate policy; preserve operator prose.
336
+ const file = path.join(opts.target, 'AGENTS.md');
337
+ const content = read(file);
338
+ const clean = content.replace(/(<!-- AIWG:hermes-context:start -->)([\s\S]*?)(<!-- AIWG:hermes-context:end -->)/g,
339
+ (_, start, body, end) => start + body.replace(/^### Rule: [\s\S]*?(?=^#{1,3} |(?![\s\S]))/gm, '') + end);
340
+ if (clean !== content) put(file, clean);
341
+ }
280
342
  const nativePaths = ['claude', 'cursor'].includes(provider.name);
281
343
  const rulesDir = path.isAbsolute(provider.paths.rules || '') ? provider.paths.rules : path.join(opts.target, provider.paths.rules || `.${provider.name}/rules`);
282
344
  const tagged = rules.filter(rule => rule.tagged);
@@ -285,9 +347,14 @@ function projectRules(opts, provider) {
285
347
  const name = rule.behavior ? `behaviors/${path.basename(path.dirname(rule.file))}` : path.basename(rule.file, '.md');
286
348
  put(path.join(rulesDir, `${name}${extension}`), managed(content, opts));
287
349
  };
350
+ // Legacy projectors may have enumerated the whole corpus. Rebuild managed
351
+ // rule output from the canonical selection; preserve operator-owned files.
352
+ for (const file of walk(rulesDir)) {
353
+ if (/\.(md|mdc)$/.test(file) && /aiwg:managed/.test(read(file))) fs.rmSync(file);
354
+ }
288
355
  const priorManifestPath = path.join(rulesDir, '.aiwg-manifest.json');
289
356
  const priorSources = fs.existsSync(priorManifestPath) ? JSON.parse(read(priorManifestPath)).ruleSources || [] : [];
290
- for (const source of priorSources.filter(file => !fs.existsSync(file))) {
357
+ for (const source of priorSources.filter(file => !rules.some(rule => rule.file === file))) {
291
358
  const name = path.basename(source) === 'BEHAVIOR.md' ? `behaviors/${path.basename(path.dirname(source))}` : path.basename(source, '.md');
292
359
  for (const ext of ['.md', '.mdc']) {
293
360
  const stale = path.join(rulesDir, name + ext);
@@ -302,6 +369,26 @@ function projectRules(opts, provider) {
302
369
  if (/aiwg:managed/.test(read(dest))) fs.rmSync(dest);
303
370
  }
304
371
  }
372
+ // Core rules (#3450): compact bodies in the provider's always-loaded surface,
373
+ // independent of deploy mode. Native providers get one core-<name> file each;
374
+ // the others get a marked section first in their managed startup block.
375
+ const cores = [...new Map(tagged.filter(rule => rule.activation === 'core').map(rule => [rule.name, rule])).values()];
376
+ if (fs.existsSync(rulesDir)) {
377
+ for (const entry of fs.readdirSync(rulesDir).filter(name => /^core-.+\.(md|mdc)$/.test(name))) {
378
+ if (/aiwg:managed/.test(read(path.join(rulesDir, entry)))) fs.rmSync(path.join(rulesDir, entry));
379
+ }
380
+ }
381
+ if (nativePaths) {
382
+ for (const rule of cores) {
383
+ const body = provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${rule.coreBody}\n` : `${rule.coreBody}\n`;
384
+ const dest = path.join(rulesDir, `core-${path.basename(rule.file, '.md')}${extension}`);
385
+ if (fs.existsSync(dest) && !/aiwg:managed/.test(read(dest)) && !opts.force) {
386
+ console.warn(`Unmanaged core rule left in place: ${dest}. Re-run with --force to replace it.`);
387
+ continue;
388
+ }
389
+ put(dest, managed(body, opts));
390
+ }
391
+ }
305
392
  for (const rule of tagged) {
306
393
  if (rule.activation === 'kernel' || (nativePaths && rule.activation === 'paths')) {
307
394
  let content = stripTriggersFromContent(rule.content);
@@ -316,6 +403,16 @@ function projectRules(opts, provider) {
316
403
  writeRule(rule, content);
317
404
  }
318
405
  }
406
+ // Explicit local/external bundle deployments predate activation metadata.
407
+ // Keep their native rule files reachable while they migrate; missing metadata
408
+ // in the shipped corpus still uses the on-demand fallback (#3463).
409
+ for (const rule of rules.filter(rule => !rule.tagged && !rule.behavior)) {
410
+ const source = opts.logicalInputPath ? opts.logicalInputPath(rule.file) : rule.file;
411
+ if (/(?:^|\/)agentic\/code\//.test(source.replaceAll('\\', '/'))) continue;
412
+ let content = stripTriggersFromContent(rule.content);
413
+ if (provider.name === 'cursor') content = provider.transformRule(rule.file, content, opts);
414
+ writeRule(rule, content);
415
+ }
319
416
  // A legacy consolidated index can mention now-bound rules. Replace it with an
320
417
  // activation-selected index when migration starts; pure legacy deployments keep it.
321
418
  // Hermes's 20K context limit requires a fixed provider projection: only
@@ -325,8 +422,7 @@ function projectRules(opts, provider) {
325
422
  const isCritical = rule => String(rule.enforcement || '').trim().toLowerCase() === 'critical';
326
423
  const deferredKernels = provider.name === 'hermes'
327
424
  ? 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;
425
+ const indexRules = rules;
330
426
  const index = renderRuleKernelIndex(indexRules, nativePaths);
331
427
  if (tagged.length) {
332
428
  for (const ext of ['.md', '.mdc']) {
@@ -335,6 +431,11 @@ function projectRules(opts, provider) {
335
431
  }
336
432
  if (index) put(path.join(rulesDir, `RULES-INDEX${extension}`), managed(provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${index}` : index, opts));
337
433
  }
434
+ const legacyDemand = rules.filter(rule => !rule.tagged && !rule.behavior && rule.activation === 'on-demand');
435
+ if (legacyDemand.length) put(path.join(rulesDir, `RULES-ONDEMAND${extension}`), managed([
436
+ '# On-demand rules', '', 'These rules require explicit activation metadata. Fetch the applicable full rule before governed work.', '',
437
+ ...legacyDemand.map(rule => `- \`${rule.name}\` — \`aiwg show rule ${rule.name} --body\``), '',
438
+ ].join('\n'), opts));
338
439
  fs.rmSync(path.join(rulesDir, '.aiwg-rules-budget.json'), { force: true });
339
440
  const manifestPath = path.join(rulesDir, '.aiwg-manifest.json');
340
441
  const manifest = fs.existsSync(manifestPath) ? JSON.parse(read(manifestPath)) : { managed: {} };
@@ -345,7 +446,7 @@ function projectRules(opts, provider) {
345
446
  }
346
447
  for (const name of Object.keys(manifest.managed)) if (!fs.existsSync(path.join(rulesDir, name))) delete manifest.managed[name];
347
448
  put(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
348
- if (!nativePaths) {
449
+ if (!nativePaths && opts.contextFiles !== false) {
349
450
  // The conventional directory is not a loader: startup reaches the complete
350
451
  // selected bodies through the provider's existing context owner.
351
452
  const kernels = rules.filter(rule => rule.activation === 'kernel' &&
@@ -354,8 +455,9 @@ function projectRules(opts, provider) {
354
455
  const routing = routes.length ? `## Invoked artifact definitions\n\nRead the complete definition before invoking its artifact:\n${routes.map(route => `- ${route}`).join('\n')}` : '';
355
456
  let startupIndex = index;
356
457
  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');
458
+ 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.' : '';
459
+ const coreSection = cores.length ? `${CORE_RULES_START}\n## Core rules\n\n${cores.map(rule => rule.coreBody).join('\n\n')}\n${CORE_RULES_END}` : '';
460
+ const renderBody = () => [coreSection, ...kernels.map(rule => `## Rule: ${rule.name}\n\n${rule.body.trim()}`), fetchKernels, startupIndex, startupRouting].filter(Boolean).join('\n\n');
359
461
  const dest = path.join(opts.target, 'AGENTS.md');
360
462
  if (provider.name === 'hermes' && block(read(dest), renderBody()).length > 19_000) {
361
463
  // The complete summary/path index and definition routes can themselves
@@ -379,7 +481,7 @@ function projectRules(opts, provider) {
379
481
  if (!body) {
380
482
  // No activation-selected startup contract: retain legacy context owners
381
483
  // 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));
484
+ const contexts = ['AIWG.md', 'AGENTS.md', 'WARP.md', '.hermes.md', inlineStartup[provider.name]].filter(Boolean).map(name => path.join(opts.target, name));
383
485
  if (opts.activeWorkspace) contexts.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
384
486
  for (const file of contexts) {
385
487
  if (!fs.existsSync(file)) continue;
@@ -389,7 +491,12 @@ function projectRules(opts, provider) {
389
491
  }
390
492
  return;
391
493
  }
392
- if (provider.name === 'hermes') {
494
+ if (inlineStartup[provider.name]) {
495
+ // Retire the B1 indirect startup block when migrating to the actual loader.
496
+ const former = path.join(opts.target, 'AIWG.md');
497
+ if (fs.existsSync(former)) put(former, block(read(former), ''));
498
+ putStartup(path.join(opts.target, inlineStartup[provider.name]), body);
499
+ } else if (provider.name === 'hermes') {
393
500
  const dest = path.join(opts.target, 'AGENTS.md');
394
501
  putStartup(dest, body);
395
502
  putStartup(path.join(opts.target, '.hermes.md'), 'Read and follow `AGENTS.md` in this directory before every task and subagent task.');
@@ -407,14 +514,19 @@ function projectRules(opts, provider) {
407
514
 
408
515
  /** External helpers also project skills/flows. Inject into their body, never descriptions. */
409
516
  function finishBindings(opts, provider) {
410
- const roots = [opts.target, opts.deploymentHome, ...Object.values(provider.paths).filter(p => path.isAbsolute(p))];
517
+ const roots = [opts.target, opts.deploymentHome,
518
+ ...[opts.kernelSkillsPath, opts.activeWorkspace, ...Object.values(provider.paths)].filter(p => p && path.isAbsolute(p))];
411
519
  const files = [...new Set(roots.flatMap(walk))];
520
+ const indexRel = provider.name === 'copilot' ? '.github/RULES-BOUND.md' : `.${provider.name}/RULES-BOUND.md`;
521
+ const indexFile = path.join(opts.target, indexRel);
522
+ const anchor = target => `${target.type}-${target.name}`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
523
+ const sections = new Map();
412
524
  const targetsBySource = new Map();
413
525
  for (const target of opts.ruleDelivery.targets) {
414
526
  targetsBySource.set(target.source, [...(targetsBySource.get(target.source) || []), target]);
415
527
  }
416
528
  for (const file of files.filter(file => /\.(md|toml)$/.test(file))) {
417
- if (!/(?:[/\\](?:skills|agents|commands)[/\\])/.test(file)) continue;
529
+ if (path.basename(file) !== 'SKILL.md' && !/(?:[/\\](?:skills?|agents?|droids|commands?|prompts|modes?)[/\\])/.test(file)) continue;
418
530
  if (/[/\\]rules[/\\]/.test(file) || /[/\\]\.aiwg[/\\](?!skills[/\\])/.test(file.replace(opts.target, ''))) continue;
419
531
  let content = read(file);
420
532
  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 +538,30 @@ function finishBindings(opts, provider) {
426
538
  const flowSource = opts.flowSkillSources?.get(file);
427
539
  const targets = [...(flowSource ? targetsBySource.get(flowSource) || [] : []),
428
540
  ...(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') &&
541
+ (skill ? target.type !== 'agent' : /[/\\](?:agents?|droids|modes?)[/\\]/.test(file) ? target.type === 'agent' : target.type === 'flow') &&
430
542
  [target.name, path.basename(target.source, '.md'), path.basename(path.dirname(target.source))].includes(name)))];
431
543
  const rules = targets.flatMap(target => opts.ruleDelivery.bindings.get(target.source) || []);
432
544
  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);
545
+ const pointer = body => {
546
+ const clean = replaceBoundRulesBlock(body, []);
547
+ if (!rules.length) return clean;
548
+ const identities = [...new Map(targets.map(target => [`${target.type} ${target.name}`, target])).values()];
549
+ for (const target of identities) {
550
+ const bound = opts.ruleDelivery.bindings.get(target.source) || [];
551
+ const unique = [...new Map(bound.map(rule => [rule.name, rule])).values()].sort((a, b) => a.name.localeCompare(b.name));
552
+ if (unique.length) sections.set(anchor(target), `## ${anchor(target)}\n\n` + unique.map(rule =>
553
+ `- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`).join('\n'));
554
+ }
555
+ // Skills must carry exact fetch commands themselves: a section pointer
556
+ // alone loses the invocation contract in clean installs (#3473).
557
+ const fetch = skill ? [...new Map(rules.map(rule => [rule.name, rule])).values()]
558
+ .sort((a, b) => a.name.localeCompare(b.name))
559
+ .map(rule => `${rule.summary}\nFetch and follow before governed work: \`aiwg show rule ${rule.name} --body\`.`).join('\n') : '';
560
+ return `${clean}${clean.endsWith('\n') ? '' : '\n'}\n<!-- AIWG:bound-rules:start -->\nRead and follow before governed work: ${identities.map(target =>
561
+ `\`${indexRel}#${anchor(target)}\``).join('; ')}.\n${fetch ? fetch + '\n' : ''}<!-- AIWG:bound-rules:end -->\n`;
562
+ };
563
+ if (file.endsWith('.toml')) content = content.replace(/^developer_instructions = (".*")$/m, (_, value) => `developer_instructions = ${JSON.stringify(pointer(JSON.parse(value)))}`);
564
+ else content = pointer(content);
435
565
  put(file, content);
436
566
  // Keep deployment hashes honest after projection by external helpers.
437
567
  if (sidecar?.managed?.[path.basename(file)]) {
@@ -439,6 +569,10 @@ function finishBindings(opts, provider) {
439
569
  put(sidecarPath, `${JSON.stringify(sidecar, null, 2)}\n`);
440
570
  }
441
571
  }
572
+ 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' +
573
+ [...sections.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, section]) => section).join('\n\n') + '\n', opts));
574
+ else if (/aiwg:managed/.test(read(indexFile))) fs.rmSync(indexFile);
575
+
442
576
  }
443
577
 
444
578
  /** Bindings annotate the selected deployment; they do not select standard skills
@@ -490,7 +624,8 @@ function projectBoundFlowSkills(opts, provider, selected) {
490
624
  if (!fs.existsSync(file) || receipt?.flowSource === entry.source) {
491
625
  const body = read(entry.source);
492
626
  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);
627
+ const projected = provider.transformSkillMd ? provider.transformSkillMd(markdown, opts) : markdown;
628
+ const content = managed(replaceBoundRulesBlock(projected, opts.ruleDelivery.bindings.get(entry.source)), opts);
494
629
  put(file, content);
495
630
  put(path.join(dir, '.aiwg-managed'), 'true\n');
496
631
  put(receiptFile, `${JSON.stringify({ flowSource: entry.source,
@@ -512,7 +647,7 @@ export function warpRuleContent(content) {
512
647
 
513
648
  function startupFiles(opts, provider) {
514
649
  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));
650
+ 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
651
  if (opts.activeWorkspace) paths.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
517
652
  if (['claude', 'cursor', 'openclaw'].includes(provider.name)) {
518
653
  const dir = path.isAbsolute(provider.paths.rules) ? provider.paths.rules : path.join(opts.target, provider.paths.rules);
@@ -524,10 +659,11 @@ function startupFiles(opts, provider) {
524
659
  const parsed = parseRuleFrontmatter(content);
525
660
  if (parsed.data?.paths || parsed.data?.globs) content = content.slice(0, content.length - parsed.body.length);
526
661
  }
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);
662
+ // WARP.md and Windsurf's AGENTS.md aggregate agents (and Warp's commands)
663
+ // as well; only their rule content (managed startup blocks, legacy inline
664
+ // rules, and on-demand lists) counts toward the rules startup budget.
665
+ if ((provider.name === 'warp' && path.basename(file) === 'WARP.md')
666
+ || (provider.name === 'windsurf' && file === path.join(opts.target, 'AGENTS.md'))) content = warpRuleContent(content);
531
667
  return { path: file, content };
532
668
  });
533
669
  }
@@ -562,17 +698,23 @@ function capturePreimage(file, ancestors = new Set()) {
562
698
  }
563
699
  // cpSync materializes links; compare its output with the recursively resolved
564
700
  // 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);
701
+ function materializeInput(from, to, ancestors = new Set(), parentReady = false) {
702
+ let stat;
703
+ try { stat = fs.statSync(from); } catch (error) {
704
+ if (error.code === 'ENOENT') return;
705
+ throw error;
706
+ }
570
707
  if (stat.isDirectory()) {
708
+ // Only directories can lead back to an ancestor. Resolve them once and
709
+ // share the immutable ancestry set with their children, including aliases.
710
+ const real = fs.realpathSync(from);
711
+ if (ancestors.has(real)) throw new Error(`Cyclic deployment input: ${from}`);
712
+ const children = new Set([...ancestors, real]);
571
713
  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]));
714
+ for (const name of fs.readdirSync(from)) materializeInput(path.join(from, name), path.join(to, name), children, true);
573
715
  fs.chmodSync(to, stat.mode & 0o7777);
574
716
  } else if (stat.isFile()) {
575
- fs.mkdirSync(path.dirname(to), { recursive: true });
717
+ if (!parentReady) fs.mkdirSync(path.dirname(to), { recursive: true });
576
718
  fs.copyFileSync(from, to); fs.chmodSync(to, stat.mode & 0o7777);
577
719
  } else throw new Error(`Unsupported deployment input: ${from}`);
578
720
  }
@@ -828,6 +970,9 @@ export async function prepareRuleDeployment(original, provider, deploy) {
828
970
  const previousText = fs.existsSync(manifestFile) ? read(manifestFile) : '';
829
971
  const previous = previousText ? JSON.parse(previousText) : {};
830
972
  const corpusRoot = resolveAiwgRoot(original.srcRoot) || original.srcRoot;
973
+ if (!original.deployVersion || /unknown/i.test(original.deployVersion)) {
974
+ original = { ...original, deployVersion: resolveDeployVersion(corpusRoot, original.deployVersion) };
975
+ }
831
976
  const sourceTree = fs.existsSync(path.join(corpusRoot, 'agentic', 'code'))
832
977
  ? path.join(corpusRoot, 'agentic', 'code') : original.srcRoot;
833
978
  const pool = original.inputPool && inputPools.get(original.inputPool);
@@ -846,7 +991,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
846
991
  const selectedRules = collectDeploymentRuleSources({ ...original, previousRuleSources: previous.ruleSources || [] });
847
992
  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
993
  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)),
994
+ ...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...selectedRules, ...selectedRules.map(file => path.dirname(file)),
850
995
  userConfigFile('aiwg.config'), process.env.AIWG_USER_REGISTRY_PATH || userConfigFile('installed.json'),
851
996
  ...(original.ruleSources || []), ...(previous.ruleSources || []), ...(original.bindingEntries || []).map(entry => entry.path)])];
852
997
  let manifestState = initialPreimages.find(item => item.path === path.resolve(manifestFile)).state;
@@ -928,7 +1073,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
928
1073
  if (JSON.stringify(capturePreimage(to)) !== JSON.stringify(materializedPreimage(expected || { kind: 'missing' }))) throw new Error(`Project-local input changed while preparing snapshot: ${from}`);
929
1074
  sourceMappings.push({ from, to });
930
1075
  }
931
- const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.ruleSources || []), ...(previous.ruleSources || []),
1076
+ const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...(original.ruleSources || []), ...(previous.ruleSources || []),
932
1077
  ...(original.bindingEntries || []).map(entry => entry.path)].map(file => path.resolve(file)))];
933
1078
  for (const [index, from] of externalInputs.entries()) {
934
1079
  if (sourceMappings.some(item => from === item.from || from.startsWith(`${item.from}${path.sep}`))) continue;
@@ -963,6 +1108,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
963
1108
  return result.split(snapshotRoot).join(corpusRoot);
964
1109
  };
965
1110
  original = { ...original, config: effectiveConfig, srcRoot: mapSource(original.srcRoot), pinConfigs, mapInputPath: mapSource, logicalInputPath,
1111
+ ...(original.additionalSources ? { additionalSources: original.additionalSources.map(mapSource) } : {}),
966
1112
  ...(original.ruleSources ? { ruleSources: original.ruleSources.map(mapSource) } : {}),
967
1113
  ...(original.bindingEntries ? { bindingEntries: original.bindingEntries.map(entry => ({ ...entry, path: mapSource(entry.path) })) } : {}) };
968
1114
  const target = path.resolve(original.target);
@@ -979,10 +1125,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
979
1125
  mappings.push({ from, to, before });
980
1126
  };
981
1127
  // 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));
1128
+ const relativeRoots = Object.values(provider.paths).filter(dir => dir && !path.isAbsolute(dir))
1129
+ .map(dir => dir.split(/[\\/]/)[0]).filter(dir => dir && dir !== '.' && !path.extname(dir));
1130
+ for (const dir of new Set([`.${provider.name}`, '.agents', '.aiwg/skills', ...relativeRoots])) mount(path.join(target, dir), path.join(stage, dir));
983
1131
  fs.mkdirSync(path.join(stage, '.aiwg'), { recursive: true });
984
1132
  if (fs.existsSync(projectSnapshot)) materializeInput(projectSnapshot, path.join(stage, '.aiwg', 'aiwg.config'));
985
1133
  const rootNames = ['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md', '.aiwg/frameworks/registry.json'];
1134
+ if (provider.name === 'windsurf') rootNames.push('.windsurfrules');
986
1135
  for (const framework of getFrameworksForMode(original.srcRoot, original.mode)) {
987
1136
  for (const entry of framework.manifest?.memory?.creates || []) {
988
1137
  if (typeof entry?.path !== 'string' || !entry.path.startsWith('.aiwg/') || !path.extname(entry.path)) continue;
@@ -1092,11 +1241,11 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1092
1241
  preimages.push(...snapshotPreimages([...delivery.rules.map(rule => rule.file), ...delivery.targets.map(entry => entry.source), path.join(target, '.aiwg', 'aiwg.config')]));
1093
1242
  const opts = { ...original, target: stage, dryRun: false, ruleDelivery: delivery,
1094
1243
  kernelSkillsPath: kernel, deploymentHome: home, activeWorkspace, hermesLegacySkills, legacyCodexSkills, staging: true };
1095
- await deploy(opts);
1244
+ await deploySelectedSurface(opts, provider, deploy);
1096
1245
  if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly && opts.deployBehaviors !== false) {
1097
- deployEmulatedBehaviors(collectBehaviorDirs(opts.srcRoot), provider.name, stage, opts);
1246
+ deployEmulatedBehaviors(delivery.rules.filter(rule => rule.behavior).map(rule => path.dirname(rule.file)), provider.name, stage, opts);
1098
1247
  }
1099
- const selectedRoots = [original.srcRoot, ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
1248
+ const selectedRoots = [original.srcRoot, ...(original.additionalSources || []), ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
1100
1249
  const selected = entry => selectedRoots.some(root => /^(agents|skills|commands|flows|workflows)[/\\]/.test(path.relative(root, entry.source)));
1101
1250
  opts.definitionRoutes = [];
1102
1251
  if (provider.name === 'hermes' && !opts.kernelOnly && !opts.skillsOnly && !opts.commandsOnly && !opts.rulesOnly) {
@@ -1104,7 +1253,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1104
1253
  const selectedAgents = new Set(filterAgentFiles(agents.map(target => target.source), opts));
1105
1254
  for (const target of agents.filter(target => selectedAgents.has(target.source))) {
1106
1255
  const source = target.source;
1107
- put(path.join(stage, '.hermes', 'agents', path.basename(source)), replaceBoundRulesBlock(read(source), delivery.bindings.get(source)));
1256
+ put(path.join(stage, '.hermes', 'agents', path.basename(source)), managed(replaceBoundRulesBlock(read(source), delivery.bindings.get(source)), opts));
1108
1257
  opts.definitionRoutes.push(`${target.name}: .hermes/agents/${path.basename(source)}`);
1109
1258
  }
1110
1259
  }
@@ -1115,8 +1264,10 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1115
1264
  if (fs.existsSync(dest)) opts.definitionRoutes.push(`${entry.name}: .warp/agents/${path.basename(entry.source)}`);
1116
1265
  }
1117
1266
  }
1118
- if (opts.deployRules || opts.rulesOnly || (!opts.skillsOnly && !opts.commandsOnly && ['hermes', 'warp'].includes(provider.name))) projectRules(opts, provider);
1119
- finishBindings(opts, provider);
1267
+ if (!opts.deferRuleProjection) {
1268
+ if (opts.kernelOnly || opts.deployRules || opts.rulesOnly || (!opts.skillsOnly && !opts.commandsOnly && (inlineStartup[provider.name] || ['hermes', 'warp'].includes(provider.name)))) projectRules(opts, provider);
1269
+ finishBindings(opts, provider);
1270
+ }
1120
1271
  // Normalize all generated path wrappers before counting or promoting bytes.
1121
1272
  const normalize = content => {
1122
1273
  for (const mapping of [...mappings].sort((a, b) => b.to.length - a.to.length)) content = content.split(mapping.to).join(mapping.from);
@@ -1150,6 +1301,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
1150
1301
  const delegated = (from, rel) => mappings.some(mapping => mapping.from !== from
1151
1302
  && mapping.from.startsWith(`${from}${path.sep}`)
1152
1303
  && (path.join(from, rel) === mapping.from || path.join(from, rel).startsWith(`${mapping.from}${path.sep}`)));
1304
+ if (original.contextFiles === false) {
1305
+ // Keep context inputs in the snapshot for validation, but exclude them
1306
+ // from both dry-run output and promotion (including unchanged rewrites).
1307
+ for (let i = rootNames.length - 1; i >= 0; i--) {
1308
+ if (['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md'].includes(rootNames[i])) rootNames.splice(i, 1);
1309
+ }
1310
+ }
1153
1311
  if (pool) verifyInputPool(pool);
1154
1312
  if (original.previewReport) return { report: guarded.report, noop: true };
1155
1313
  if (original.dryRun) {
@@ -1244,7 +1402,21 @@ export function commitRuleDeploymentPlan(plan) {
1244
1402
  if (before.get(rel) === fileDigest(file)) continue;
1245
1403
  const buffer = fs.readFileSync(file);
1246
1404
  // Provider output includes binary skill resources; normalize text only.
1247
- const output = buffer.includes(0) ? buffer : Buffer.from(normalize(buffer.toString('utf8')));
1405
+ let output = buffer.includes(0) ? buffer : Buffer.from(normalize(buffer.toString('utf8')));
1406
+ if (path.basename(file) === '.aiwg-manifest.json') {
1407
+ const receipt = JSON.parse(output.toString('utf8'));
1408
+ for (const [name, record] of Object.entries(receipt.managed || {})) {
1409
+ const recordedFile = path.resolve(path.dirname(file), name);
1410
+ if (!within(path.dirname(file), recordedFile) || !fs.existsSync(recordedFile)) continue;
1411
+ const recorded = fs.readFileSync(recordedFile);
1412
+ // Do not bless operator edits whose bytes already differ from the
1413
+ // ownership receipt. Only normalize hashes of pristine staged files.
1414
+ if (record.hash !== `sha256:${contentHash(recorded)}`) continue;
1415
+ const promoted = recorded.includes(0) ? recorded : Buffer.from(normalize(recorded.toString('utf8')));
1416
+ record.hash = `sha256:${contentHash(promoted)}`;
1417
+ }
1418
+ output = Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`);
1419
+ }
1248
1420
  fs.mkdirSync(path.dirname(path.join(from, rel)), { recursive: true });
1249
1421
  fs.writeFileSync(path.join(from, rel), output);
1250
1422
  }
@@ -1258,6 +1430,13 @@ export function commitRuleDeploymentPlan(plan) {
1258
1430
 
1259
1431
  /** Stage and commit serially for existing provider callers. */
1260
1432
  export async function deployWithRuleDelivery(original, provider, deploy) {
1433
+ // Kernel source passes share the same selected rule set. Deploy their
1434
+ // skills directly; the final source projects rules and bindings across the
1435
+ // complete destination. Full passes retain staging for bound flow skills
1436
+ // and provider definitions, but defer rule presentation above.
1437
+ if (original.kernelOnly && original.deferRuleProjection && !globalThis[PREPARATION_HOOK]) {
1438
+ return deploySelectedSurface(original, provider, deploy);
1439
+ }
1261
1440
  if (original.ruleDelivery) return deploy(original);
1262
1441
  const hook = globalThis[PREPARATION_HOOK];
1263
1442
  if (hook) {
@@ -1268,3 +1447,16 @@ export async function deployWithRuleDelivery(original, provider, deploy) {
1268
1447
  }
1269
1448
  return commitRuleDeploymentPlan(await prepareRuleDeployment(original, provider, deploy));
1270
1449
  }
1450
+
1451
+ async function deploySelectedSurface(opts, provider, deploy) {
1452
+ if (!opts.kernelOnly || provider.name !== 'warp') return deploy(opts);
1453
+ // Warp's aggregate writer emits bulk definitions even in skills-only mode.
1454
+ // Both staged and deferred kernel passes use only its shared skill router.
1455
+ const skills = [...listSkillDirs(path.join(opts.srcRoot, 'skills')),
1456
+ ...getFrameworksForMode(opts.srcRoot, opts.mode).flatMap(bundle => listSkillDirs(path.join(bundle.path, 'skills'))),
1457
+ ...getAddonSkillDirs(opts.srcRoot)].filter(isKernelSkill);
1458
+ const standard = path.join(opts.target, provider.paths.skills);
1459
+ for (const dir of new Set([opts.kernelSkillsPath || provider.kernelSkillsPath, provider.paths.crossAgentSkills])) {
1460
+ if (dir) deploySkillsWithKernelRouting(skills, standard, path.isAbsolute(dir) ? dir : path.join(opts.target, dir), opts);
1461
+ }
1462
+ }