@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
@@ -104,7 +104,7 @@ export function listMdFiles(dir, excludePatterns = []) {
104
104
  const excluded = [...defaultExcluded, ...excludePatterns];
105
105
  return fs
106
106
  .readdirSync(dir, { withFileTypes: true })
107
- .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md') && !e.name.toLowerCase().endsWith('.soul.md') && !excluded.includes(e.name))
107
+ .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md') && !e.name.toLowerCase().endsWith('.soul.md') && !e.name.toLowerCase().endsWith('.core.md') && !excluded.includes(e.name))
108
108
  .map((e) => path.join(dir, e.name));
109
109
  }
110
110
 
@@ -2474,7 +2474,7 @@ export function applyInlineRuleBudget(ruleFiles) {
2474
2474
  /**
2475
2475
  * Tier gate (#1673, enforcement-tiered deployment ADR). Only CRITICAL and HIGH
2476
2476
  * rules are inlined into a provider's always-on rule directory; MEDIUM/LOW are
2477
- * left on-demand (reachable via `aiwg show rule <name>` and the assembled
2477
+ * left on-demand (reachable via `aiwg show rule <name> --body` and the assembled
2478
2478
  * RULES-INDEX pointer index). Index files and rules with no enforcement marker
2479
2479
  * default to always-on, so an un-triaged or structural file is never dropped.
2480
2480
  */
@@ -2558,17 +2558,17 @@ export function renderOnDemandRuleSection(onDemandFiles, opts = {}) {
2558
2558
  'on demand:',
2559
2559
  '',
2560
2560
  '```bash',
2561
- 'aiwg show rule <name>',
2561
+ 'aiwg show rule <name> --body',
2562
2562
  '```',
2563
2563
  '',
2564
- ...names.map((n) => `- \`${n}\` — \`aiwg show rule ${n}\``),
2564
+ ...names.map((n) => `- \`${n}\` — \`aiwg show rule ${n} --body\``),
2565
2565
  ].join('\n');
2566
2566
  }
2567
2567
 
2568
2568
  /**
2569
2569
  * Write a compact on-demand rule index (#1673) into a provider's rule dir.
2570
2570
  * Lists the MEDIUM/LOW rules that are NOT inlined at startup, with the
2571
- * `aiwg show rule <name>` fetch hint. Removes a stale index when empty.
2571
+ * `aiwg show rule <name> --body` fetch hint. Removes a stale index when empty.
2572
2572
  */
2573
2573
  export function writeOnDemandRuleIndex(destDir, onDemandFiles, opts = {}) {
2574
2574
  const indexPath = path.join(destDir, 'RULES-ONDEMAND.md');
@@ -2588,10 +2588,10 @@ export function writeOnDemandRuleIndex(destDir, onDemandFiles, opts = {}) {
2588
2588
  'fetch any rule body on demand:',
2589
2589
  '',
2590
2590
  '```bash',
2591
- 'aiwg show rule <name>',
2591
+ 'aiwg show rule <name> --body',
2592
2592
  '```',
2593
2593
  '',
2594
- ...names.map((n) => `- \`${n}\` — \`aiwg show rule ${n}\``),
2594
+ ...names.map((n) => `- \`${n}\` — \`aiwg show rule ${n} --body\``),
2595
2595
  '',
2596
2596
  ];
2597
2597
  if (demotedNames.length > 0) {
@@ -2605,7 +2605,7 @@ export function writeOnDemandRuleIndex(destDir, onDemandFiles, opts = {}) {
2605
2605
  '(AIWG_RULES_INLINE_BUDGET_TOKENS) and break subagent dispatch with "Prompt is too long".',
2606
2606
  'Fetch any of them before work they govern:',
2607
2607
  '',
2608
- ...demotedNames.map((n) => `- \`${n}\` — \`aiwg show rule ${n}\``),
2608
+ ...demotedNames.map((n) => `- \`${n}\` — \`aiwg show rule ${n} --body\``),
2609
2609
  '',
2610
2610
  );
2611
2611
  }
@@ -2803,7 +2803,9 @@ export function getAddonFiles(srcRoot, options = {}) {
2803
2803
  // ============================================================================
2804
2804
 
2805
2805
  /**
2806
- * Load rules manifest from source directory
2806
+ * Load rules manifest from source directory.
2807
+ * Inventory metadata only: delivery is decided by rule `activation` (rule-delivery.mjs);
2808
+ * the former manifest `tiers` deploy policy was never consulted and was removed (#3450).
2807
2809
  * @param {string} srcRoot - Source root directory
2808
2810
  * @returns {object|null} - Parsed manifest or null if not found
2809
2811
  */
@@ -3168,8 +3170,9 @@ export function migrateCommandsDirectory(commandsDir, opts = {}) {
3168
3170
  const rel = path.relative(process.cwd(), commandsDir);
3169
3171
  // The condition is a property of the directory, not of each deployed unit;
3170
3172
  // emit it once per run no matter how many units pass through.
3171
- if (warnedCommandsDirs.has(commandsDir)) return false;
3172
- warnedCommandsDirs.add(commandsDir);
3173
+ const warned = opts.warnedCommandsDirs || warnedCommandsDirs;
3174
+ if (warned.has(commandsDir)) return false;
3175
+ warned.add(commandsDir);
3173
3176
 
3174
3177
  const stale = listManagedCommandFiles(commandsDir);
3175
3178
  if (stale.length === 0) return false;
@@ -3338,7 +3341,7 @@ export function replaceBoundRulesBlock(content, rules = []) {
3338
3341
  const unique = [...new Map(rules.map(rule => [rule.name, rule])).values()]
3339
3342
  .sort((a, b) => a.name.localeCompare(b.name));
3340
3343
  return `${clean}${clean.endsWith('\n') ? '' : '\n'}\n<!-- AIWG:bound-rules:start -->\n## Bound rules\n\nRequired reading on every invocation: fetch and follow every bound rule below before starting governed work. A summary or discovery pointer does not discharge this obligation.\n\n${unique.map(rule =>
3341
- `- ${rule.name} — ${rule.summary}\n Fetch and follow before governed work: \`aiwg show rule ${rule.name}\`.`
3344
+ `- ${rule.name} — ${rule.summary}\n Fetch and follow before governed work: \`aiwg show rule ${rule.name} --body\`.`
3342
3345
  ).join('\n')}\n<!-- AIWG:bound-rules:end -->\n`;
3343
3346
  }
3344
3347
 
@@ -3347,8 +3350,11 @@ export function injectBoundRules(content, source, opts = {}) {
3347
3350
  return replaceBoundRulesBlock(content, opts.ruleDelivery.bindings.get(path.resolve(source)) || []);
3348
3351
  }
3349
3352
 
3350
- /** All provider output is staged; the complete startup guard runs before promotion. */
3353
+ /** Rule delivery stages output and guards startup before promotion. Deferred
3354
+ * kernel passes leave rule delivery to the final source in the invocation. */
3351
3355
  export async function withRuleDeployment(opts, provider, deploy) {
3356
+ if (opts.kernelOnly && opts.deferRuleProjection && provider.name !== 'warp'
3357
+ && !globalThis[Symbol.for('aiwg.provider-preparation-hook.v1')]) return deploy(opts);
3352
3358
  const { deployWithRuleDelivery } = await import('./rule-delivery.mjs');
3353
3359
  return deployWithRuleDelivery(opts, provider, deploy);
3354
3360
  }
@@ -416,7 +416,7 @@ export async function postDeploy(targetDir, opts) {
416
416
  }
417
417
 
418
418
  // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
419
- if (opts.srcRoot) {
419
+ if (opts.srcRoot && opts.contextFiles !== false) {
420
420
  deployHookFile(targetDir, opts);
421
421
  }
422
422
  }
@@ -35,6 +35,7 @@ const staticModelCatalog = _require('../../../agentic/code/providers/model-catal
35
35
  import path from 'path';
36
36
  import os from 'os';
37
37
  import { spawn } from 'child_process';
38
+ import { pathToFileURL } from 'node:url';
38
39
  import { load as loadYaml } from 'js-yaml';
39
40
  import { classifyModelRole, modelForRole } from './model-role.mjs';
40
41
  import {
@@ -66,7 +67,6 @@ import {
66
67
  parseFrontmatter,
67
68
  resolveAiwgRoot
68
69
  } from './base.mjs';
69
- const modelCatalog = loadRuntimeModelCatalog(staticModelCatalog);
70
70
 
71
71
  function resolveCodexHelperRoot(srcRoot) {
72
72
  const resolved = resolveAiwgRoot(srcRoot);
@@ -101,7 +101,6 @@ export const kernelSkillsPath = '.agents/skills/';
101
101
  // Legacy home-dir skills location written by AIWG versions prior to the #766
102
102
  // regression fix. Pruned on every codex skill deploy so codex-rs stops listing
103
103
  // each AIWG skill twice. Never touches non-AIWG (unmarked) skills.
104
- const legacyHomeSkillsDir = path.join(os.homedir(), '.codex', 'skills');
105
104
 
106
105
  export const support = {
107
106
  agents: 'native',
@@ -125,6 +124,7 @@ export const capabilities = {
125
124
  * Map model shorthand to OpenAI/GPT format
126
125
  */
127
126
  export function mapModel(originalModel, modelCfg, modelsConfig) {
127
+ const modelCatalog = modelCfg.modelCatalog || loadRuntimeModelCatalog(staticModelCatalog);
128
128
  const gptModels = {
129
129
  'opus': modelCatalog.providers.codex.roles.reasoning.id,
130
130
  'sonnet': modelCatalog.providers.codex.roles.coding.id,
@@ -204,7 +204,7 @@ export function renderAgentToml(srcPath, content, models) {
204
204
  */
205
205
  export function transformAgent(srcPath, content, opts) {
206
206
  const { reasoningModel, codingModel, efficiencyModel } = opts;
207
- const catalogModels = modelCatalog.providers.codex.roles;
207
+ const catalogModels = (opts.modelCatalog || loadRuntimeModelCatalog(staticModelCatalog)).providers.codex.roles;
208
208
 
209
209
  const models = {
210
210
  reasoning: reasoningModel || catalogModels.reasoning.id,
@@ -239,6 +239,27 @@ export function deployAgents(agentFiles, targetDir, opts) {
239
239
  }, transformAgent);
240
240
  }
241
241
 
242
+ /** Run the helper in this pass; retain process isolation for troubleshooting. */
243
+ async function runCodexDeployer(scriptPath, args, helperRoot, opts) {
244
+ if (process.env.AIWG_DEPLOY_SUBPROCESS !== '1') {
245
+ const deployer = await import(pathToFileURL(scriptPath).href);
246
+ await deployer.main(args, { homeDir: opts.deploymentHome || os.homedir() });
247
+ return;
248
+ }
249
+ await new Promise((resolve, reject) => {
250
+ const child = spawn('node', [scriptPath, ...args], {
251
+ stdio: 'inherit',
252
+ cwd: helperRoot,
253
+ env: opts.deploymentHome ? { ...process.env, HOME: opts.deploymentHome } : process.env,
254
+ });
255
+ child.on('close', code => {
256
+ if (code === 0) resolve();
257
+ else reject(new Error(`${path.basename(scriptPath)} exited with code ${code}`));
258
+ });
259
+ child.on('error', reject);
260
+ });
261
+ }
262
+
242
263
  /**
243
264
  * Deploy commands via external script
244
265
  *
@@ -257,30 +278,16 @@ export async function deployCommands(targetDir, srcRoot, opts) {
257
278
 
258
279
  console.log('Delegating command deployment to deploy-prompts-codex.mjs (~/.codex/prompts/)...');
259
280
 
260
- return new Promise((resolve, reject) => {
261
- // NOTE: Do NOT pass --target - Codex prompts belong in ~/.codex/prompts/ (home)
262
- const args = ['--source', srcRoot];
263
- if (opts.dryRun) args.push('--dry-run');
264
- if (opts.force) args.push('--force');
265
- if (opts.mode) args.push('--mode', opts.mode);
266
- if (opts.copyStandardSkills === true) args.push('--copy-all');
267
- // A copy-all the deployer forced on the caller's behalf (project-local
268
- // bundles) still honors the startup listing cap (#2561).
269
- if (opts.listingBudget === true) args.push('--listing-budget');
270
-
271
- const child = spawn('node', [scriptPath, ...args], {
272
- stdio: 'inherit',
273
- cwd: helperRoot,
274
- env: opts.deploymentHome ? { ...process.env, HOME: opts.deploymentHome } : process.env
275
- });
276
-
277
- child.on('close', (code) => {
278
- if (code === 0) resolve();
279
- else reject(new Error(`deploy-prompts-codex.mjs exited with code ${code}`));
280
- });
281
-
282
- child.on('error', reject);
283
- });
281
+ // NOTE: Do NOT pass --target - Codex prompts belong in ~/.codex/prompts/ (home)
282
+ const args = ['--source', srcRoot];
283
+ if (opts.dryRun) args.push('--dry-run');
284
+ if (opts.force) args.push('--force');
285
+ if (opts.mode) args.push('--mode', opts.mode);
286
+ if (opts.copyStandardSkills === true) args.push('--copy-all');
287
+ // A copy-all the deployer forced on the caller's behalf (project-local
288
+ // bundles) still honors the startup listing cap (#2561).
289
+ if (opts.listingBudget === true) args.push('--listing-budget');
290
+ await runCodexDeployer(scriptPath, args, helperRoot, opts);
284
291
  }
285
292
 
286
293
  /**
@@ -305,37 +312,23 @@ export async function deploySkills(targetDir, srcRoot, opts) {
305
312
  const crossAgentSkillsDir = path.join(targetDir, '.agents', 'skills');
306
313
  console.log(`Deploying skills to ${crossAgentSkillsDir} (.agents/skills — codex-scanned path)...`);
307
314
 
308
- await new Promise((resolve, reject) => {
309
- const args = ['--source', srcRoot, '--target', crossAgentSkillsDir];
310
- // Overflow past Codex's startup listing cap lands here instead of being
311
- // deployed over the cap (#2561); the index still reaches these skills.
312
- args.push('--standard-target', path.join(targetDir, ...paths.skills.split('/').filter(Boolean)));
313
- if (opts.dryRun) args.push('--dry-run');
314
- if (opts.force) args.push('--force');
315
- if (opts.mode) args.push('--mode', opts.mode);
316
- if (opts.copyStandardSkills === true) args.push('--copy-all');
317
- // A copy-all the deployer forced on the caller's behalf (project-local
318
- // bundles) still honors the startup listing cap (#2561).
319
- if (opts.listingBudget === true) args.push('--listing-budget');
320
-
321
- const child = spawn('node', [scriptPath, ...args], {
322
- stdio: 'inherit',
323
- cwd: helperRoot,
324
- env: opts.deploymentHome ? { ...process.env, HOME: opts.deploymentHome } : process.env
325
- });
326
-
327
- child.on('close', (code) => {
328
- if (code === 0) resolve();
329
- else reject(new Error(`deploy-skills-codex.mjs exited with code ${code}`));
330
- });
331
-
332
- child.on('error', reject);
333
- });
315
+ const args = ['--source', srcRoot, '--target', crossAgentSkillsDir];
316
+ // Overflow past Codex's startup listing cap lands here instead of being
317
+ // deployed over the cap (#2561); the index still reaches these skills.
318
+ args.push('--standard-target', path.join(targetDir, ...paths.skills.split('/').filter(Boolean)));
319
+ if (opts.dryRun) args.push('--dry-run');
320
+ if (opts.force) args.push('--force');
321
+ if (opts.mode) args.push('--mode', opts.mode);
322
+ if (opts.copyStandardSkills === true) args.push('--copy-all');
323
+ // A copy-all the deployer forced on the caller's behalf (project-local
324
+ // bundles) still honors the startup listing cap (#2561).
325
+ if (opts.listingBudget === true) args.push('--listing-budget');
326
+ await runCodexDeployer(scriptPath, args, helperRoot, opts);
334
327
 
335
328
  // Self-heal: prune AIWG-managed skill dirs left behind in the legacy
336
329
  // ~/.codex/skills/ home location by pre-fix versions, so codex-rs stops
337
330
  // listing each skill twice.
338
- pruneLegacyCodexSkills(opts, opts.legacyCodexSkills || legacyHomeSkillsDir);
331
+ pruneLegacyCodexSkills(opts, opts.legacyCodexSkills || path.join(os.homedir(), '.codex', 'skills'));
339
332
  }
340
333
 
341
334
  /**
@@ -346,7 +339,7 @@ export async function deploySkills(targetDir, srcRoot, opts) {
346
339
  * the `.aiwg-managed` marker are removed — user-authored skills are never
347
340
  * touched. The now-empty legacy dir is removed if AIWG owned everything in it.
348
341
  */
349
- export function pruneLegacyCodexSkills(opts = {}, legacyDir = legacyHomeSkillsDir) {
342
+ export function pruneLegacyCodexSkills(opts = {}, legacyDir = path.join(os.homedir(), '.codex', 'skills')) {
350
343
  let entries;
351
344
  try {
352
345
  entries = fs.readdirSync(legacyDir, { withFileTypes: true });
@@ -401,6 +394,7 @@ export function deployRules(ruleFiles, targetDir, opts) {
401
394
  * Aggregate agents to single AGENTS.md file
402
395
  */
403
396
  export function aggregateToAgentsMd(agentFiles, destPath, opts) {
397
+ if (opts.contextFiles === false) return 0;
404
398
  const lines = ['# AIWG agent definitions', '', 'Read the selected definition before invoking an agent.', ''];
405
399
  for (const file of agentFiles) {
406
400
  const { metadata } = parseFrontmatter(fs.readFileSync(file, 'utf8'));
@@ -518,7 +512,7 @@ export async function postDeploy(targetDir, opts) {
518
512
  initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
519
513
 
520
514
  if (opts.createAgentsMd) {
521
- createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
515
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
522
516
  }
523
517
  }
524
518
 
@@ -27,6 +27,7 @@ let fs;
27
27
  try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
28
28
  import path from 'path';
29
29
  import {
30
+ withRuleDeployment,
30
31
  ensureDir,
31
32
  listMdFiles,
32
33
  listMdFilesRecursive,
@@ -429,6 +430,7 @@ export function deploySkills(skillDirs, targetDir, opts) {
429
430
  * Deploy rules to .github/instructions/ as .instructions.md files
430
431
  */
431
432
  export function deployRules(ruleFiles, targetDir, opts) {
433
+ if (opts.ruleDelivery) return [];
432
434
  const destDir = path.join(targetDir, paths.rules);
433
435
  ensureDir(destDir, opts.dryRun);
434
436
  cleanupOldRuleFiles(destDir, opts);
@@ -495,8 +497,8 @@ export function createCopilotInstructions(target, srcRoot, dryRun) {
495
497
  export async function postDeploy(targetDir, opts) {
496
498
  initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
497
499
 
498
- // Create copilot-instructions.md
499
- createCopilotInstructions(targetDir, opts.srcRoot, opts.dryRun);
500
+ // copilot-instructions.md is Copilot's context file: --no-context-files leaves it alone.
501
+ if (opts.contextFiles !== false) createCopilotInstructions(targetDir, opts.srcRoot, opts.dryRun);
500
502
  }
501
503
 
502
504
  // ============================================================================
@@ -517,6 +519,10 @@ export function getFileExtension(type) {
517
519
  // ============================================================================
518
520
 
519
521
  export async function deploy(opts) {
522
+ return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
523
+ }
524
+
525
+ async function deploySelected(opts) {
520
526
  const {
521
527
  srcRoot,
522
528
  target,
@@ -414,7 +414,7 @@ export async function postDeploy(targetDir, opts) {
414
414
  initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
415
415
 
416
416
  if (opts.createAgentsMd) {
417
- createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
417
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
418
418
  }
419
419
 
420
420
  // PUW-019 (#1120): wire environment.json + worktrees.json templates.
@@ -46,7 +46,7 @@ export function deployCordisPatch(target, opts) {
46
46
  }
47
47
 
48
48
  export async function postDeploy(target, opts) {
49
- if (opts.createAgentsMd || (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)) createAgentsMd(target, opts.srcRoot, opts.dryRun);
49
+ if (opts.contextFiles !== false && (opts.createAgentsMd || (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly))) createAgentsMd(target, opts.srcRoot, opts.dryRun);
50
50
  if (!opts.quiet) console.log('DeepSeek Harness: runtime credentials remain environment-only; use a reviewed ephemeral route patch.');
51
51
  }
52
52
 
@@ -25,6 +25,7 @@ let fs;
25
25
  try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
26
26
  import path from 'path';
27
27
  import {
28
+ withRuleDeployment,
28
29
  ensureDir,
29
30
  listMdFiles,
30
31
  listMdFilesRecursive,
@@ -576,6 +577,7 @@ export function deploySkills(skillDirs, targetDir, opts) {
576
577
  * Deploy rules to .factory/rules/
577
578
  */
578
579
  export function deployRules(ruleFiles, targetDir, opts) {
580
+ if (opts.ruleDelivery) return [];
579
581
  const destDir = path.join(targetDir, paths.rules);
580
582
  ensureDir(destDir, opts.dryRun);
581
583
  cleanupOldRuleFiles(destDir, opts);
@@ -816,7 +818,7 @@ export async function postDeploy(targetDir, opts) {
816
818
 
817
819
  // Create/update AGENTS.md if requested
818
820
  if (opts.createAgentsMd) {
819
- createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
821
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
820
822
  }
821
823
  }
822
824
 
@@ -945,6 +947,10 @@ export function getFileExtension(type) {
945
947
  * Main deployment function for Factory provider
946
948
  */
947
949
  export async function deploy(opts) {
950
+ return withRuleDeployment(opts, { name, paths, kernelSkillsPath, transformSkillMd: transformSkillFrontmatter }, deploySelected);
951
+ }
952
+
953
+ async function deploySelected(opts) {
948
954
  const {
949
955
  srcRoot,
950
956
  target,
@@ -16,6 +16,8 @@ import fs from 'node:fs';
16
16
  import { createHash, randomUUID } from 'node:crypto';
17
17
  import yaml from 'js-yaml';
18
18
  import {
19
+ withRuleDeployment,
20
+ replaceBoundRulesBlock,
19
21
  createAgentsMdFromTemplate,
20
22
  deploySkillsWithKernelRouting,
21
23
  collectFrameworkArtifacts,
@@ -224,7 +226,17 @@ function matchesSkillSource(source, target) {
224
226
  if (relative.endsWith(path.sep)) return true;
225
227
  let content = fs.readFileSync(path.join(source, relative), 'utf8');
226
228
  if (relative === 'SKILL.md') content = injectPlatformInContent(content, 'grok-build');
227
- return content === fs.readFileSync(path.join(target, relative), 'utf8');
229
+ const deployed = fs.readFileSync(path.join(target, relative), 'utf8');
230
+ if (content === deployed) return true;
231
+ if (relative !== 'SKILL.md' || !deployed.includes('<!-- AIWG:bound-rules:start -->')) return false;
232
+ // Activation projection appends generated bindings. Verify the complete
233
+ // deployed bytes against the receipt before comparing the authored body;
234
+ // an operator edit (including inside the binding block) must survive.
235
+ const receipt = JSON.parse(fs.readFileSync(path.join(target, '.aiwg-manifest.json'), 'utf8'));
236
+ const digest = `sha256:${createHash('sha256').update(deployed).digest('hex')}`;
237
+ return receipt.skillSource === path.resolve(source)
238
+ && receipt.managed?.[relative]?.hash === digest
239
+ && replaceBoundRulesBlock(content, []).trimEnd() === replaceBoundRulesBlock(deployed, []).trimEnd();
228
240
  });
229
241
  } catch { return false; }
230
242
  }
@@ -326,7 +338,7 @@ export function uninstall(target, opts = {}) {
326
338
 
327
339
  export async function postDeploy(target, opts = {}) {
328
340
  if (opts.createAgentsMd || (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)) {
329
- createAgentsMd(target, opts.srcRoot, opts.dryRun);
341
+ if (opts.contextFiles !== false) createAgentsMd(target, opts.srcRoot, opts.dryRun);
330
342
  }
331
343
  if (!opts.quiet) {
332
344
  const home = resolveGrokHome(opts.env || process.env);
@@ -344,6 +356,10 @@ export function getFileExtension() {
344
356
  }
345
357
 
346
358
  export async function deploy(opts) {
359
+ return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
360
+ }
361
+
362
+ async function deploySelected(opts) {
347
363
  const mode = normalizeDeploymentMode(opts.mode);
348
364
  if (opts.createAgentsMd || (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)) {
349
365
  assertSafeAgentsMd(opts.target);
@@ -171,7 +171,7 @@ export async function postDeploy(targetDir, opts = {}) {
171
171
  opts.createAgentsMd ||
172
172
  (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)
173
173
  ) {
174
- createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
174
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
175
175
  }
176
176
  if (!opts.quiet) {
177
177
  const root = resolveGrokbotSkillsDir(opts.env || process.env);
@@ -224,9 +224,9 @@ export function assertContextBudget(content, name = 'AGENTS.md', rules = []) {
224
224
  * priming on every Hermes turn (#1318 / S7; skill-discovery added #1347).
225
225
  *
226
226
  * The full rule bodies are reachable without MCP via `aiwg discover` and
227
- * `aiwg show rule <name>`; MCP exposes the same path through `rule-list` and
227
+ * `aiwg show rule <name> --body`; MCP exposes the same path through `rule-list` and
228
228
  * `rule-show` when configured (#1320). The bodies exceed Hermes's 20K context
229
- * cap if inlined verbatim (anti-laziness alone is 32K chars), so we inline
229
+ * cap if inlined verbatim (anti-laziness alone was 32K chars; it now ships as a compact core body, #3450), so we inline
230
230
  * compressed directives only and point to CLI/MCP for the full text.
231
231
  *
232
232
  * Selection criteria: rules tagged CRITICAL/HIGH enforcement level whose
@@ -240,7 +240,7 @@ const CRITICAL_RULE_DIRECTIVES = `## CRITICAL Rules (always apply)
240
240
  These are the highest-enforcement AIWG rules. Full bodies are reachable without
241
241
  MCP:
242
242
  - Find rules: \`aiwg discover "rule <topic>" --type rule\`
243
- - Fetch a rule: \`aiwg show rule <name>\`
243
+ - Fetch a rule: \`aiwg show rule <name> --body\`
244
244
 
245
245
  If the optional MCP sidecar is configured, the same surface is available through
246
246
  \`mcp_aiwg_rule_list\` and \`mcp_aiwg_rule_show\`.
@@ -260,13 +260,6 @@ Never add AI-tool attribution to commits, PRs, code, or docs. No \`Co-Authored-B
260
260
  no "Generated with", no "Written by [AI tool]". The AI is a tool; tools don't sign
261
261
  their output. Applies to ALL platforms (Claude, Codex, Copilot, Cursor, etc.).
262
262
 
263
- ### Rule: anti-laziness
264
- Never delete tests to make them pass. Never skip/disable tests. Never remove
265
- features instead of fixing them. Never weaken assertions to be meaningless.
266
- Never suppress CI/pipeline signals (\`continue-on-error\`, \`|| true\`, \`set +e\`).
267
- If stuck after 3 honest attempts: escalate with full context, don't shortcut.
268
- Within scope: leave nothing half-done — code + tests + docs + verification.
269
-
270
263
  ### Rule: citation-policy
271
264
  Never fabricate citations, DOIs, URLs, or page numbers. Only cite sources that
272
265
  exist in the research corpus (.aiwg/research/sources/). Match claim strength
@@ -331,6 +324,7 @@ function mergeHermesContext(file, generated, kind) {
331
324
  }
332
325
 
333
326
  export function generateAgentsMd(agentCount, skillCount, targetDir, opts) {
327
+ if (opts.contextFiles === false) return;
334
328
  const { dryRun } = opts;
335
329
 
336
330
  // Startup is reserved for rule contracts. Workflow routing, artifact catalogs,
@@ -339,7 +333,7 @@ export function generateAgentsMd(agentCount, skillCount, targetDir, opts) {
339
333
  const header = `# AIWG Integration
340
334
 
341
335
  Skills: \`$HERMES_HOME/skills/\` (kernel), \`$HERMES_HOME/skills/.aiwg/\` (standard).
342
- Find: \`aiwg discover "rule <topic>" --type rule\`. Fetch: \`aiwg show rule <name>\`.
336
+ Find: \`aiwg discover "rule <topic>" --type rule\`. Fetch: \`aiwg show rule <name> --body\`.
343
337
  MCP is optional; \`mcp_aiwg_rule_show\` exposes the same rule catalog.
344
338
  `;
345
339
 
@@ -533,7 +527,7 @@ async function deploySelected(opts) {
533
527
  // Generate AGENTS.md (with inlined CRITICAL rule priming, #1318/#1532)
534
528
  // AND .hermes.md thin pointer (resolves #1319 doc-debt, was claimed in
535
529
  // CHANGELOG but not previously implemented).
536
- if (!skillsOnly && !opts.commandsOnly && !opts.rulesOnly) {
530
+ if (opts.contextFiles !== false && !skillsOnly && !opts.commandsOnly && !opts.rulesOnly) {
537
531
  const artifacts = collectFrameworkArtifacts(srcRoot, normalizedMode, {
538
532
  includeAgents: true,
539
533
  includeCommands: false,
@@ -560,7 +554,7 @@ async function deploySelected(opts) {
560
554
  if (!opts.quiet) {
561
555
  console.log('');
562
556
  console.log(`Skills root: ${kernelSkillsPath}`);
563
- console.log('Rules are in AGENTS.md as compressed directives; full bodies via `aiwg show rule <name>`.');
557
+ console.log('Rules are in AGENTS.md as compressed directives; full bodies via `aiwg show rule <name> --body`.');
564
558
  console.log('Optional: configure config.yaml to connect AIWG MCP server.');
565
559
  console.log('See: docs/integrations/hermes-quickstart.md (optional MCP setup)');
566
560
  }
@@ -43,9 +43,7 @@
43
43
  * Servers inherit the operator's process environment; pass `MUSE_SESSION_ID`
44
44
  * through at runtime instead of hardcoding a session id into `env`.
45
45
  *
46
- * This module is intentionally dependency-free (node builtins +
47
- * muse-paths.mjs only) so provider-policy-style import graphs never pull it
48
- * in transitively.
46
+ * MCP writes use the shared config writer, resolved for source and packaged installs.
49
47
  *
50
48
  * @issue #228
51
49
  */
@@ -53,9 +51,13 @@
53
51
  import fs from 'node:fs';
54
52
  import os from 'node:os';
55
53
  import path from 'node:path';
54
+ import { randomUUID } from 'node:crypto';
55
+ import { importImpl } from '../../_resolve-impl.mjs';
56
56
  import { stripJsonComments } from './base.mjs';
57
57
  import { resolveMuseXdgSkillsDir, museXdgSkillsDirRemediation } from './muse-paths.mjs';
58
58
 
59
+ const { assertConfigDestination, writeConfigAtomic, writeConfigTransaction } = await importImpl(import.meta.url, 'mcp/config-file.mjs');
60
+
59
61
  // ---------------------------------------------------------------------------
60
62
  // Shared JSON helpers
61
63
  // ---------------------------------------------------------------------------
@@ -631,7 +633,7 @@ export function mergeMcpProfile(existingDoc, opts = {}) {
631
633
  * preserving unknown keys, timestamped backup before writing, atomic write,
632
634
  * dry-run diff with zero writes, fail closed on unparseable settings.
633
635
  */
634
- export function deployMuseMcp(opts = {}) {
636
+ export async function deployMuseMcp(opts = {}) {
635
637
  const { dryRun = false, quiet = false, mcp = false, env = process.env, userHome = os.homedir() } = opts;
636
638
  const log = quiet ? () => {} : (msg) => console.log(msg);
637
639
 
@@ -642,6 +644,8 @@ export function deployMuseMcp(opts = {}) {
642
644
 
643
645
  const settingsPath = assertMuseUserSettingsPath(env, userHome);
644
646
  const configDir = path.dirname(settingsPath);
647
+ await assertConfigDestination(settingsPath);
648
+ await assertConfigDestination(path.join(configDir, MUSE_MCP_SIDECAR_NAME));
645
649
  const { doc: existingDoc, raw: existingRaw, existed } = readSettingsDoc(settingsPath);
646
650
  const sidecar = readMcpSidecar(configDir);
647
651
  const profile = aiwgMcpServerProfile();
@@ -680,14 +684,15 @@ export function deployMuseMcp(opts = {}) {
680
684
  return { settingsPath, changed: true, plan, wrote: false, warnings };
681
685
  }
682
686
 
683
- fs.mkdirSync(configDir, { recursive: true });
684
687
  let backupPath = null;
685
688
  if (existed) {
686
- backupPath = `${settingsPath}.aiwg-backup-${new Date().toISOString().replace(/[:.]/g, '-')}`;
687
- fs.copyFileSync(settingsPath, backupPath);
689
+ backupPath = `${settingsPath}.aiwg-backup-${new Date().toISOString().replace(/[:.]/g, '-')}-${randomUUID()}`;
690
+ await writeConfigAtomic(backupPath, existingRaw, { userScope: true });
688
691
  }
689
- atomicWrite(settingsPath, afterText);
690
- atomicWrite(sidecar.path, canonicalJson({ version: 1, servers: ['aiwg'] }));
692
+ await writeConfigTransaction([
693
+ { file: settingsPath, content: afterText, options: { userScope: true } },
694
+ { file: sidecar.path, content: canonicalJson({ version: 1, servers: ['aiwg'] }), options: { userScope: true } },
695
+ ]);
691
696
 
692
697
  log(`Muse MCP: merged AIWG stdio profile into ${settingsPath}`);
693
698
  for (const step of plan) log(` ${step}`);
@@ -700,11 +705,13 @@ export function deployMuseMcp(opts = {}) {
700
705
  * Removal path: drop the AIWG-managed `aiwg` server key (only when recorded
701
706
  * in the sidecar) and delete the sidecar. Operator servers are never touched.
702
707
  */
703
- export function pruneMuseMcp(opts = {}) {
708
+ export async function pruneMuseMcp(opts = {}) {
704
709
  const { dryRun = false, quiet = false, env = process.env, userHome = os.homedir() } = opts;
705
710
  const log = quiet ? () => {} : (msg) => console.log(msg);
706
711
  const settingsPath = assertMuseUserSettingsPath(env, userHome);
707
712
  const configDir = path.dirname(settingsPath);
713
+ await assertConfigDestination(settingsPath);
714
+ await assertConfigDestination(path.join(configDir, MUSE_MCP_SIDECAR_NAME));
708
715
 
709
716
  if (!fs.existsSync(settingsPath)) {
710
717
  log(`Muse MCP: no ${settingsPath}; nothing to prune.`);
@@ -731,10 +738,10 @@ export function pruneMuseMcp(opts = {}) {
731
738
  return { settingsPath, pruned: true, wrote: false };
732
739
  }
733
740
 
734
- const backupPath = `${settingsPath}.aiwg-backup-${new Date().toISOString().replace(/[:.]/g, '-')}`;
735
- fs.copyFileSync(settingsPath, backupPath);
736
- atomicWrite(settingsPath, canonicalJson(merged));
737
- if (fs.existsSync(sidecar.path)) fs.unlinkSync(sidecar.path);
741
+ const backupPath = `${settingsPath}.aiwg-backup-${new Date().toISOString().replace(/[:.]/g, '-')}-${randomUUID()}`;
742
+ await writeConfigAtomic(backupPath, existingRaw, { userScope: true });
743
+ await writeConfigAtomic(settingsPath, canonicalJson(merged), { userScope: true });
744
+ if (await assertConfigDestination(sidecar.path)) fs.unlinkSync(sidecar.path);
738
745
  log(`Muse MCP: removed mcp_servers.aiwg from ${settingsPath} (backup: ${backupPath}).`);
739
746
  return { settingsPath, pruned: true, wrote: true, backupPath };
740
747
  }
@@ -35,6 +35,7 @@ import fs from 'node:fs';
35
35
  import os from 'node:os';
36
36
  import path from 'node:path';
37
37
  import {
38
+ withRuleDeployment,
38
39
  collectFrameworkArtifacts,
39
40
  createAgentsMdFromTemplate,
40
41
  deploySkillsWithKernelRouting,
@@ -226,7 +227,7 @@ export async function postDeploy(targetDir, opts = {}) {
226
227
  // bridge, managed hooks, and an opted-in MCP profile must not be skipped.
227
228
  const fullDeploy = !opts.commandsOnly && !opts.rulesOnly && (!opts.skillsOnly || opts.kernelOnly === true);
228
229
  if (opts.createAgentsMd || fullDeploy) {
229
- createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
230
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
230
231
  }
231
232
 
232
233
  // #228 — managed project hooks (.muse/hooks.json). Default on, mirroring
@@ -240,7 +241,7 @@ export async function postDeploy(targetDir, opts = {}) {
240
241
  // #228 — optional MCP settings profile. Explicit opt-in only (--mcp);
241
242
  // default `aiwg use --provider muse` never touches user settings.
242
243
  if (fullDeploy && opts.mcp === true) {
243
- deployMuseMcp(opts);
244
+ await deployMuseMcp(opts);
244
245
  }
245
246
 
246
247
  if (!opts.quiet) {
@@ -265,6 +266,10 @@ export async function postDeploy(targetDir, opts = {}) {
265
266
  }
266
267
 
267
268
  export async function deploy(opts = {}) {
269
+ return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
270
+ }
271
+
272
+ async function deploySelected(opts) {
268
273
  const {
269
274
  srcRoot,
270
275
  target,