@aiwg/cli 2026.10.0 → 2026.10.3

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 (168) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +270 -0
  2. package/agentic/code/providers/model-capabilities.v1.json +3091 -24
  3. package/bin/aiwg.mjs +86 -21
  4. package/dist/src/a2a/codecs.js +9 -0
  5. package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
  6. package/dist/src/artifacts/repair.js +9 -4
  7. package/dist/src/channel/manager.mjs +59 -10
  8. package/dist/src/cli/child-process-result.js +40 -0
  9. package/dist/src/cli/env.js +38 -16
  10. package/dist/src/cli/flag-parsing.js +236 -0
  11. package/dist/src/cli/handlers/decision.js +297 -2
  12. package/dist/src/cli/handlers/diagnose.js +9 -2
  13. package/dist/src/cli/handlers/feedback.js +14 -5
  14. package/dist/src/cli/handlers/init.js +245 -30
  15. package/dist/src/cli/handlers/mc.js +23 -0
  16. package/dist/src/cli/handlers/models.js +69 -6
  17. package/dist/src/cli/handlers/ralph.js +3 -0
  18. package/dist/src/cli/handlers/refresh.js +16 -0
  19. package/dist/src/cli/handlers/regenerate.js +14 -0
  20. package/dist/src/cli/handlers/runtime-info.js +24 -9
  21. package/dist/src/cli/handlers/script-runner.js +16 -6
  22. package/dist/src/cli/handlers/session.js +396 -62
  23. package/dist/src/cli/handlers/sessions.js +146 -40
  24. package/dist/src/cli/handlers/setup-manifest.js +439 -39
  25. package/dist/src/cli/handlers/setup.js +139 -39
  26. package/dist/src/cli/handlers/subcommands.js +3 -1
  27. package/dist/src/cli/handlers/team.js +18 -8
  28. package/dist/src/cli/handlers/use.js +13 -0
  29. package/dist/src/cli/handlers/utilities.js +106 -23
  30. package/dist/src/cli/prompt-utils.js +72 -0
  31. package/dist/src/cli/provider-resolution.js +11 -0
  32. package/dist/src/config/aiwg-config.js +268 -4
  33. package/dist/src/config/cli.js +146 -19
  34. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  35. package/dist/src/config/project-artifacts-health.mjs +14 -3
  36. package/dist/src/config/user-config.js +36 -10
  37. package/dist/src/decision/adapters/jev.js +80 -28
  38. package/dist/src/decision/ask.js +352 -0
  39. package/dist/src/decision/driver.js +4 -0
  40. package/dist/src/decision/entry.js +38 -6
  41. package/dist/src/decision/heldout/collector.js +10 -1
  42. package/dist/src/decision/index.js +3 -0
  43. package/dist/src/decision/interop.js +60 -8
  44. package/dist/src/decision/jev-credentials.js +121 -0
  45. package/dist/src/decision/ledger.js +839 -0
  46. package/dist/src/decision/validate.js +6 -3
  47. package/dist/src/effects/dispatch-verifier.js +252 -0
  48. package/dist/src/effects/index.js +1 -0
  49. package/dist/src/features/installer.js +2 -2
  50. package/dist/src/flow/graph-metadata.js +5 -0
  51. package/dist/src/gates/providers/loader.js +3 -1
  52. package/dist/src/installation/manager-command.mjs +1 -1
  53. package/dist/src/installation/manager.mjs +17 -2
  54. package/dist/src/lint/runner.js +2 -2
  55. package/dist/src/mcp/adapters/codex-runtime.js +360 -56
  56. package/dist/src/mcp/cli.mjs +587 -77
  57. package/dist/src/mcp/config-file.mjs +279 -0
  58. package/dist/src/mcp/config-layers.mjs +348 -0
  59. package/dist/src/mcp/credentials.mjs +484 -0
  60. package/dist/src/mcp/grok-build-config.mjs +16 -26
  61. package/dist/src/mcp/helpers.mjs +106 -2
  62. package/dist/src/mcp/omp-config.mjs +50 -29
  63. package/dist/src/mcp/profiles.js +97 -23
  64. package/dist/src/mcp/profiles.mjs +99 -27
  65. package/dist/src/mcp/registry.js +264 -57
  66. package/dist/src/mcp/registry.mjs +198 -45
  67. package/dist/src/mcp/server.mjs +403 -20
  68. package/dist/src/mcp/toml-string.mjs +9 -0
  69. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  70. package/dist/src/mcp/tool-filters.mjs +357 -0
  71. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  72. package/dist/src/mcp/tools/decision.mjs +58 -0
  73. package/dist/src/mcp/tools/orchestration.mjs +38 -0
  74. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  75. package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
  76. package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
  77. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  78. package/dist/src/memory/project-registry.js +2 -2
  79. package/dist/src/mission-protocol/codecs.js +639 -1
  80. package/dist/src/mission-protocol/types.js +110 -0
  81. package/dist/src/mission-protocol/worker-status.js +2 -0
  82. package/dist/src/models/config-loader.js +70 -13
  83. package/dist/src/models/grok-build-models.js +93 -41
  84. package/dist/src/models/model-capabilities.v1.json +3091 -24
  85. package/dist/src/models/model-discovery.js +862 -63
  86. package/dist/src/models/provider-policy.js +625 -11
  87. package/dist/src/models/qualification-digest.mjs +17 -0
  88. package/dist/src/models/resolver.js +259 -0
  89. package/dist/src/models/worker-direct-readiness.js +529 -0
  90. package/dist/src/models/worker-dispatch-offer.js +169 -0
  91. package/dist/src/models/worker-health-readiness.js +131 -0
  92. package/dist/src/models/worker-installation.mjs +879 -0
  93. package/dist/src/models/wrapper-route.js +18 -0
  94. package/dist/src/ops/registry.js +9 -14
  95. package/dist/src/plugins/standalone-packager.js +4 -3
  96. package/dist/src/process/bounded-child-process.mjs +387 -0
  97. package/dist/src/process/cli-command.mjs +75 -0
  98. package/dist/src/providers/capability-matrix.js +8 -0
  99. package/dist/src/providers/capability-matrix.yaml +270 -0
  100. package/dist/src/providers/grok-build-headless.js +43 -21
  101. package/dist/src/providers/provider-definitions.js +9 -1
  102. package/dist/src/providers/provider-definitions.mjs +9 -2
  103. package/dist/src/serve/executor-registry.js +16 -24
  104. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  105. package/dist/src/serve/mission-conductor.js +9 -0
  106. package/dist/src/serve/shared-host-scheduler.js +2 -0
  107. package/dist/src/serve/worker-invocation.js +538 -0
  108. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  109. package/dist/src/sessions/adapter-registry.js +45 -0
  110. package/dist/src/sessions/adapters/generic.js +37 -3
  111. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  112. package/dist/src/sessions/adapters/grokbot.js +423 -24
  113. package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
  114. package/dist/src/sessions/adapters/muse.js +68 -29
  115. package/dist/src/sessions/adapters/pi.js +1 -0
  116. package/dist/src/sessions/batch-contracts.js +13 -1
  117. package/dist/src/sessions/batch-import.js +23 -42
  118. package/dist/src/sessions/candidates.js +47 -11
  119. package/dist/src/sessions/contracts.js +35 -0
  120. package/dist/src/sessions/coverage-preflight.js +134 -0
  121. package/dist/src/sessions/discovery.js +3 -0
  122. package/dist/src/sessions/grok-build-cli.js +67 -5
  123. package/dist/src/sessions/importer.js +24 -8
  124. package/dist/src/sessions/index.js +2 -0
  125. package/dist/src/sessions/repository.js +54 -13
  126. package/dist/src/sessions/workspace-discovery.js +99 -37
  127. package/dist/src/skills/adapters/clawhub.js +10 -8
  128. package/dist/src/skills/adapters/openclaw.js +10 -7
  129. package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
  130. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  131. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  132. package/dist/src/smiths/mcpsmith/example.js +1 -1
  133. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
  134. package/dist/src/storage/backends/logseq.js +68 -7
  135. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  136. package/dist/src/testing/generators/test-code-generator.js +57 -30
  137. package/dist/src/uhp/client.js +11 -1
  138. package/dist/src/uhp/mission.js +5 -0
  139. package/dist/src/update/checker.mjs +5 -5
  140. package/dist/src/update/notifier.mjs +54 -2
  141. package/dist/src/writing/fidelity.js +18 -1
  142. package/package.json +18 -1
  143. package/schemas/mission-v1.schema.json +2429 -0
  144. package/schemas/models/model-catalog.v1.schema.json +301 -0
  145. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  146. package/schemas/models/model-policy.v1.schema.json +14 -0
  147. package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
  148. package/tools/_resolve-impl.mjs +39 -0
  149. package/tools/agents/deploy-agents.mjs +15 -2
  150. package/tools/agents/providers/base.mjs +63 -43
  151. package/tools/agents/providers/grokbot.mjs +11 -12
  152. package/tools/agents/providers/hermes.mjs +6 -3
  153. package/tools/agents/providers/omp.mjs +5 -2
  154. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  155. package/tools/providers/deepseek-harness-transport.mjs +358 -0
  156. package/tools/providers/omp-transport.mjs +192 -0
  157. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  158. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  159. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  160. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  161. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  162. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  163. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  164. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  165. package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
  166. package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
  167. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  168. package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
@@ -46,6 +46,45 @@ function resolveCandidate(root, relativeFromSrc, base) {
46
46
  *
47
47
  * Prefers dist/src/ when present. Set AIWG_RESOLVE_IMPL_FROM=dist or src to
48
48
  * force a side during CI/package-layout checks.
49
+ *
50
+ * ## Why a tools/ script that imports src/ needs this (#3062)
51
+ *
52
+ * The npm package ships the `tools/` tree (with maintainer-only exceptions
53
+ * below) and `dist/src/`; it does not ship `src/` itself (see `files` in
54
+ * package.json). A script under `tools/` that imports `../../src/<module>`
55
+ * directly by relative path works from a git checkout (`src/` exists
56
+ * there) but throws `ERR_MODULE_NOT_FOUND` once installed from npm, where
57
+ * only `dist/src/<module>` exists. Any shipped `.mjs` file under `tools/`
58
+ * or `bin/` that needs something under `src/` must resolve it through
59
+ * `importImpl(import.meta.url, '<module-path-relative-to-src>')` instead of
60
+ * a literal relative import, so it finds `dist/src/` when installed and
61
+ * `src/` in a checkout. `test/integration/tools-packed-relative-imports.test.ts`
62
+ * guards this for every relative import already known to resolve.
63
+ *
64
+ * Directories `package.json`'s `files` excludes from the npm package
65
+ * (`tools/benchmarks/`, `tools/ci/`, `tools/contrib/`, `tools/docker/`,
66
+ * `tools/eval/`, `tools/lint/` other than its listed exceptions,
67
+ * `tools/manifest/` other than its listed exception, `tools/qualification/`,
68
+ * and `tools/release/`) are maintainer/CI-only: they never run from an
69
+ * installed package, so a literal `../../src/<module>` import there is
70
+ * fine and does not need `importImpl`. Keep that distinction explicit at
71
+ * each such import site rather than silently mixing styles within one
72
+ * shipped-vs-not-shipped directory. `tools/benchmarks/` and
73
+ * `tools/qualification/` were excluded specifically because nothing
74
+ * shipped ever referenced them (#3062 B/C finding 2) — removing them
75
+ * also trims install size (#3075).
76
+ *
77
+ * `.ts` files directly under `tools/**` that remain shipped (e.g. under
78
+ * `tools/decision/`, `tools/experiments/`) are a separate, still-open
79
+ * case: they are only ever run via `tsx` through an `npm run` script in a
80
+ * checkout (there is no compile step that makes them directly
81
+ * `node`-executable), so they also keep literal `src/**` imports — there
82
+ * is no installed-package code path that reaches them to break. This is
83
+ * pre-existing, documented debt (see the baseline in
84
+ * `test/integration/tools-packed-relative-imports.test.ts`), not a
85
+ * recommendation to add more of it: a new shipped `.ts` tool should
86
+ * resolve through `importImpl` like everything else unless it has the
87
+ * same checkout-only justification.
49
88
  */
50
89
  export function resolveImpl(callerUrl, relativeFromSrc) {
51
90
  const root = findPackageRoot(callerUrl);
@@ -11,6 +11,8 @@
11
11
  * Options:
12
12
  * --source <path> Source directory (defaults to repo root)
13
13
  * --target <path> Target directory (defaults to cwd)
14
+ * --scope <user|project> Deployment scope (default: project)
15
+ * --user Shorthand for --scope user
14
16
  * --mode <type> Deployment mode: general, sdlc, marketing (alias: mmk), media-curator, research, both, or all (default)
15
17
  * --deploy-commands Deploy commands in addition to agents
16
18
  * --deploy-skills Deploy skills in addition to agents
@@ -472,10 +474,10 @@ function pruneStaleAiwgArtifacts(provider, target, srcRoot, opts, explicitSource
472
474
  // Argument Parsing
473
475
  // ============================================================================
474
476
 
475
- function parseArgs() {
476
- const args = process.argv.slice(2);
477
+ export function parseArgs(args = process.argv.slice(2)) {
477
478
  const cfg = {
478
479
  source: null,
480
+ userScope: false,
479
481
  target: process.cwd(),
480
482
  mode: 'all', // 'general', 'sdlc', 'marketing', 'media-curator', 'research', 'both' (legacy), or 'all'
481
483
  dryRun: false,
@@ -521,6 +523,14 @@ function parseArgs() {
521
523
  if (a === '--source' && args[i + 1]) cfg.source = path.resolve(args[++i]);
522
524
  else if (a === '--target' && args[i + 1]) cfg.target = path.resolve(args[++i]);
523
525
  else if (a === '--mode' && args[i + 1]) cfg.mode = String(args[++i]).toLowerCase();
526
+ else if (a === '--scope') {
527
+ const scope = args[++i];
528
+ if (scope !== 'user' && scope !== 'project') {
529
+ throw new Error(`--scope expected 'user' or 'project', got '${scope ?? '(missing)'}'`);
530
+ }
531
+ cfg.userScope = scope === 'user';
532
+ }
533
+ else if (a === '--user') cfg.userScope = true;
524
534
  else if (a === '--dry-run') cfg.dryRun = true;
525
535
  else if (a === '--force') cfg.force = true;
526
536
  else if ((a === '--provider' || a === '--platform') && args[i + 1]) cfg.provider = String(args[++i]).toLowerCase();
@@ -577,6 +587,8 @@ Usage:
577
587
  Options:
578
588
  --source <path> Source directory (defaults to repo root)
579
589
  --target <path> Target directory (defaults to cwd)
590
+ --scope <user|project> Deployment scope (default: project)
591
+ --user Shorthand for --scope user
580
592
  --mode <type> Deployment mode: general, sdlc, marketing (alias: mmk), media-curator, research, both, or all (default)
581
593
  --deploy-commands Deploy commands in addition to agents
582
594
  --deploy-skills Deploy skills in addition to agents
@@ -1000,6 +1012,7 @@ export async function main() {
1000
1012
  const opts = {
1001
1013
  srcRoot,
1002
1014
  target: cfg.target,
1015
+ ...(cfg.userScope ? { userScope: true } : {}),
1003
1016
  mode: cfg.mode,
1004
1017
  // Provider aliases are selectors, not artifact identities. Passing the
1005
1018
  // canonical id keeps generated frontmatter byte-identical for devin,
@@ -11,10 +11,13 @@
11
11
  import realFs from 'fs';
12
12
  import path from 'path';
13
13
  import os from 'os';
14
+ import yaml from 'yaml';
14
15
  import { classifyModelRole } from './model-role.mjs';
15
16
  import { createHash } from 'crypto';
16
17
  import { createRequire } from 'module';
17
- import { execSync as nodeExecSync } from 'child_process';
18
+ import { importImpl } from '../../_resolve-impl.mjs';
19
+
20
+ const { boundedExecFileSync } = await importImpl(import.meta.url, 'process/bounded-child-process.mjs');
18
21
 
19
22
  // Use graceful-fs to prevent EMFILE crashes on systems with low ulimit.
20
23
  // graceful-fs queues open() calls when FD pressure is detected and retries
@@ -376,41 +379,38 @@ const PROVIDER_TO_PLATFORM = {
376
379
 
377
380
  /**
378
381
  * Parse the platforms: field from a SKILL.md frontmatter block.
379
- * Handles both inline array (platforms: [a, b]) and multi-line list formats.
380
- * Returns null if no platforms field is present (= deploy to all providers).
381
- * Returns an empty array only if the field is explicitly empty.
382
+ * Accepts a string or a list of strings; all alone means unrestricted.
383
+ * Returns null if platforms is missing, or no frontmatter is present.
384
+ * Returns an empty array for malformed frontmatter or invalid platforms.
382
385
  */
383
386
  export function parseSkillPlatforms(content) {
384
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
385
- if (!fmMatch) return null;
386
-
387
- const fm = fmMatch[1];
388
-
389
- // Inline array: platforms: [claude-code, codex] or platforms: [all]
390
- const inlineMatch = fm.match(/^platforms:\s*\[([^\]]*)\]/m);
391
- if (inlineMatch) {
392
- const items = inlineMatch[1].split(',').map(s => s.trim()).filter(Boolean);
393
- if (items.length === 0 || (items.length === 1 && items[0] === 'all')) return null;
394
- return items;
387
+ // A leading UTF-8 BOM must not hide the frontmatter (and its restrictions).
388
+ if (content.charCodeAt(0) === 0xfeff) content = content.slice(1);
389
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)^---[ \t]*(?:\r?\n|$)/m);
390
+ if (!fmMatch || fmMatch.index !== 0) {
391
+ return /^---[ \t]*(?:\r?\n|$)/.test(content) ? [] : null;
395
392
  }
396
393
 
397
- // Multi-line list:
398
- // platforms:
399
- // - claude-code
400
- // - hermes
401
- const multiMatch = fm.match(/^platforms:\s*\n((?:[ \t]+-[ \t]+\S[^\n]*\n?)+)/m);
402
- if (multiMatch) {
403
- const items = multiMatch[1]
404
- .split('\n')
405
- .map(line => line.match(/^[ \t]+-[ \t]+(\S+)/)?.[1])
406
- .filter(Boolean);
407
- return items.length > 0 ? items : null;
394
+ let metadata;
395
+ try {
396
+ const document = yaml.parseDocument(fmMatch[1]);
397
+ if (document.errors.length > 0) return [];
398
+ if (!document.contents) return null; // Empty frontmatter has no restrictions.
399
+ metadata = document.toJS();
400
+ } catch {
401
+ return [];
408
402
  }
403
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) return [];
404
+ // The yaml package leaves merge keys unresolved, so platforms could arrive only
405
+ // through `<<`; without an explicit key that is ambiguous, so fail closed.
406
+ if (!Object.hasOwn(metadata, 'platforms')) return Object.hasOwn(metadata, '<<') ? [] : null;
409
407
 
410
- // platforms: key present but empty
411
- if (/^platforms:\s*$/m.test(fm)) return null;
412
-
413
- return null; // Field absent = deploy to all
408
+ const platforms = metadata.platforms;
409
+ const items = typeof platforms === 'string' ? [platforms] : platforms;
410
+ if (!Array.isArray(items) || !items.every(item => typeof item === 'string')) return [];
411
+ const normalized = items.map(item => item.trim());
412
+ if (normalized.some(item => !item)) return [];
413
+ return normalized.length === 1 && normalized[0] === 'all' ? null : normalized;
414
414
  }
415
415
 
416
416
  /**
@@ -1363,13 +1363,13 @@ export function computeAllArtifactBasenames(srcRoot, type) {
1363
1363
  // Soul companions live alongside agents and are deployed with them —
1364
1364
  // keep their stems in the desired set so the prune never removes them.
1365
1365
  add(frameworkArtifacts.souls || []);
1366
- add(getAddonAgentFiles(aiwgRoot));
1366
+ add(getAddonAgentFiles(aiwgRoot, [], { includeExplicit: true }));
1367
1367
  } else if (type === 'commands') {
1368
1368
  add(frameworkArtifacts.commands);
1369
- add(getAddonCommandFiles(aiwgRoot));
1369
+ add(getAddonCommandFiles(aiwgRoot, [], { includeExplicit: true }));
1370
1370
  } else if (type === 'rules') {
1371
1371
  add(frameworkArtifacts.rules);
1372
- add(getAddonRuleFiles(aiwgRoot));
1372
+ add(getAddonRuleFiles(aiwgRoot, [], { includeExplicit: true }));
1373
1373
  }
1374
1374
 
1375
1375
  return stems;
@@ -1811,7 +1811,7 @@ export function buildRemotesTopologyBlock(targetDir) {
1811
1811
 
1812
1812
  function getUrl(remote) {
1813
1813
  try {
1814
- return nodeExecSync(`git -C ${JSON.stringify(targetDir)} remote get-url ${JSON.stringify(remote)}`, {
1814
+ return boundedExecFileSync('git', ['-C', targetDir, 'remote', 'get-url', '--', remote], 'quick', {
1815
1815
  stdio: ['ignore', 'pipe', 'ignore'],
1816
1816
  encoding: 'utf8',
1817
1817
  }).trim();
@@ -2264,9 +2264,10 @@ export function collectFrameworkArtifacts(srcRoot, mode, options = {}) {
2264
2264
  /**
2265
2265
  * Discover all addons in the agentic/code/addons directory
2266
2266
  * @param {string} srcRoot - Source root directory
2267
+ * @param {{includeExplicit?: boolean}} [options] - includeExplicit also returns explicitInstall addons
2267
2268
  * @returns {Array<{name: string, path: string, manifest: object}>} - Array of addon info
2268
2269
  */
2269
- export function discoverAddons(srcRoot) {
2270
+ export function discoverAddons(srcRoot, { includeExplicit = false } = {}) {
2270
2271
  const addonsDir = path.join(srcRoot, 'agentic', 'code', 'addons');
2271
2272
  if (!fs.existsSync(addonsDir)) return [];
2272
2273
 
@@ -2290,7 +2291,9 @@ export function discoverAddons(srcRoot) {
2290
2291
  if (manifest.devOnly === true) continue;
2291
2292
  // explicitInstall addons deploy only when named (`aiwg use <addon>`), never
2292
2293
  // as part of a framework or `all` bulk deploy (#2641).
2293
- if (manifest.explicitInstall === true) continue;
2294
+ // The stale-artifact prune passes includeExplicit so a named install's own
2295
+ // files stay in its desired set and are not deleted right after deployment.
2296
+ if (manifest.explicitInstall === true && !includeExplicit) continue;
2294
2297
 
2295
2298
  addons.push({
2296
2299
  name: entry.name,
@@ -2308,8 +2311,8 @@ export function discoverAddons(srcRoot) {
2308
2311
  * @param {string[]} excludeAddons - Addon names to exclude (default: none)
2309
2312
  * @returns {string[]} - Array of agent file paths
2310
2313
  */
2311
- export function getAddonAgentFiles(srcRoot, excludeAddons = []) {
2312
- const addons = discoverAddons(srcRoot);
2314
+ export function getAddonAgentFiles(srcRoot, excludeAddons = [], options = {}) {
2315
+ const addons = discoverAddons(srcRoot, options);
2313
2316
  const files = [];
2314
2317
 
2315
2318
  for (const addon of addons) {
@@ -2330,8 +2333,8 @@ export function getAddonAgentFiles(srcRoot, excludeAddons = []) {
2330
2333
  * @param {string[]} excludeAddons - Addon names to exclude (default: none)
2331
2334
  * @returns {string[]} - Array of command file paths
2332
2335
  */
2333
- export function getAddonCommandFiles(srcRoot, excludeAddons = []) {
2334
- const addons = discoverAddons(srcRoot);
2336
+ export function getAddonCommandFiles(srcRoot, excludeAddons = [], options = {}) {
2337
+ const addons = discoverAddons(srcRoot, options);
2335
2338
  const files = [];
2336
2339
 
2337
2340
  for (const addon of addons) {
@@ -2347,7 +2350,7 @@ export function getAddonCommandFiles(srcRoot, excludeAddons = []) {
2347
2350
  }
2348
2351
 
2349
2352
  /**
2350
- * Get all skill directories from all addons
2353
+ * Get skill directories from a direct addon/framework source or all addons
2351
2354
  * @param {string} srcRoot - Source root directory
2352
2355
  * @param {string[]} excludeAddons - Addon names to exclude (default: none)
2353
2356
  * @returns {string[]} - Array of skill directory paths
@@ -2355,6 +2358,8 @@ export function getAddonCommandFiles(srcRoot, excludeAddons = []) {
2355
2358
  export function getAddonSkillDirs(srcRoot, excludeAddons = []) {
2356
2359
  const addons = discoverAddons(srcRoot);
2357
2360
  const dirs = [];
2361
+ const directDir = getDirectSourceDir(srcRoot, excludeAddons);
2362
+ if (directDir) dirs.push(...listSkillDirs(directDir));
2358
2363
 
2359
2364
  for (const addon of addons) {
2360
2365
  if (excludeAddons.includes(addon.name)) continue;
@@ -2368,6 +2373,21 @@ export function getAddonSkillDirs(srcRoot, excludeAddons = []) {
2368
2373
  return dirs;
2369
2374
  }
2370
2375
 
2376
+ // `aiwg use <addon>` passes the component itself as --source. Keep bulk
2377
+ // repository discovery unchanged, even if the repo has a top-level skills/.
2378
+ function getDirectSourceDir(srcRoot, excludeAddons) {
2379
+ if (fs.existsSync(path.join(srcRoot, 'agentic', 'code')) ||
2380
+ excludeAddons.includes(path.basename(path.resolve(srcRoot)))) return null;
2381
+ const dir = path.join(srcRoot, 'skills');
2382
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return null;
2383
+ const relative = path.relative(fs.realpathSync(srcRoot), fs.realpathSync(dir));
2384
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
2385
+ console.warn(`Warning: Skipping direct skills directory ${dir}: resolves outside source root ${srcRoot}`);
2386
+ return null;
2387
+ }
2388
+ return dir;
2389
+ }
2390
+
2371
2391
  /**
2372
2392
  * Get all rule files from all addons
2373
2393
  * @param {string} srcRoot - Source root directory
@@ -2651,8 +2671,8 @@ export function writeOnDemandRuleIndex(destDir, onDemandFiles, opts = {}) {
2651
2671
  return names.length + demotedNames.length;
2652
2672
  }
2653
2673
 
2654
- export function getAddonRuleFiles(srcRoot, excludeAddons = []) {
2655
- const addons = discoverAddons(srcRoot);
2674
+ export function getAddonRuleFiles(srcRoot, excludeAddons = [], options = {}) {
2675
+ const addons = discoverAddons(srcRoot, options);
2656
2676
  const files = [];
2657
2677
 
2658
2678
  for (const addon of addons) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Grok Bot provider (experimental).
2
+ * Grok Bot provider (stable).
3
3
  *
4
4
  * Discover-first project bridge + fail-closed user skill root.
5
5
  * Never targets `.cursor/**` or invents `~/.grokbot`.
@@ -127,21 +127,18 @@ export function deployRules() {
127
127
  }
128
128
 
129
129
  /**
130
- * Deploy skills only when AIWG_GROKBOT_SKILLS_DIR is configured.
130
+ * Deploy skills only at user scope with AIWG_GROKBOT_SKILLS_DIR configured.
131
131
  * Kernel → configured root; standard → index/discovery unless --copy-all
132
132
  * (then `<root>/.aiwg/skills`).
133
133
  */
134
134
  export function deploySkills(skillDirs, targetDir, opts = {}) {
135
- const root = resolveGrokbotSkillsDir(opts.env || process.env);
136
- if (!root) {
135
+ if (opts.userScope !== true && opts.scope !== 'user') {
137
136
  if (!opts.quiet) {
138
- console.log(
139
- `\n Grok Bot: skipping native skill copy (${SKILLS_DIR_ENV} unset). ` +
140
- 'Skills remain available via aiwg discover / aiwg show.',
141
- );
137
+ console.log('\n Grok Bot: project bridge only; native skills require --scope user.');
142
138
  }
143
139
  return 0;
144
140
  }
141
+ const root = assertUserScopeConfigured(opts);
145
142
 
146
143
  if (root.includes(`${path.sep}.cursor${path.sep}`) || root.endsWith(`${path.sep}.cursor`) || root.includes('/.cursor/') || root.endsWith('/.cursor')) {
147
144
  throw new Error(
@@ -178,14 +175,14 @@ export async function postDeploy(targetDir, opts = {}) {
178
175
  }
179
176
  if (!opts.quiet) {
180
177
  const root = resolveGrokbotSkillsDir(opts.env || process.env);
181
- if (root) {
178
+ if (root && (opts.userScope === true || opts.scope === 'user')) {
182
179
  console.log(
183
180
  'Grok Bot: after deploy, start a new agent chat or re-read skills. ' +
184
181
  'Live refresh is not claimed until product behavior is verified.',
185
182
  );
186
183
  } else {
187
184
  console.log(
188
- `Grok Bot: project bridge only. Set ${SKILLS_DIR_ENV} for user-scope skill deploy.`,
185
+ `Grok Bot: project bridge only. Use --scope user with ${SKILLS_DIR_ENV} for native skills.`,
189
186
  );
190
187
  }
191
188
  }
@@ -197,7 +194,7 @@ export function getFileExtension() {
197
194
 
198
195
  /**
199
196
  * Assert user-scope is allowed. Called when the orchestrator is mirroring
200
- * to user scope or when opts.scope === 'user'.
197
+ * to user scope or when opts.userScope === true / opts.scope === 'user'.
201
198
  */
202
199
  export function assertUserScopeConfigured(opts = {}) {
203
200
  const root = resolveGrokbotSkillsDir(opts.env || process.env);
@@ -227,7 +224,9 @@ export async function deploy(opts = {}) {
227
224
  console.log(`Mode: ${mode}`);
228
225
  const root = resolveGrokbotSkillsDir(opts.env || process.env);
229
226
  console.log(
230
- `Skills root: ${root || `(unset — set ${SKILLS_DIR_ENV} for user-scope writes)`}`,
227
+ (opts.userScope === true || scope === 'user')
228
+ ? `User skills root: ${root || `(unset — set ${SKILLS_DIR_ENV})`}`
229
+ : 'Skills: project bridge only (no native skill writes)',
231
230
  );
232
231
  if (opts.dryRun) console.log('Dry-run: no filesystem mutations');
233
232
  }
@@ -26,7 +26,6 @@ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
26
26
  import {
27
27
  ensureDir,
28
28
  listMdFiles,
29
- listSkillDirs,
30
29
  deploySkillDir,
31
30
  deploySkillsWithKernelRouting,
32
31
  isKernelSkill,
@@ -466,7 +465,7 @@ export async function deploy(opts) {
466
465
  if ((shouldDeploySkills || skillsOnly) && !opts.commandsOnly && !opts.rulesOnly) {
467
466
  const allSkillDirs = [];
468
467
 
469
- // Addon skills (aiwg-utils, ralph, etc.)
468
+ // Addon skills, including a direct addon source (#2873, #3035).
470
469
  allSkillDirs.push(...getAddonSkillDirs(srcRoot));
471
470
 
472
471
  // Framework skills
@@ -665,7 +664,11 @@ export function updateBundledManifest(kernelSkillNames, opts) {
665
664
  */
666
665
  export function migrateLegacySkillPath(opts) {
667
666
  const { dryRun, quiet } = opts;
668
- const legacyPath = path.join(os.homedir(), '.hermes', '.aiwg', 'skills');
667
+ // Resolve through the same HERMES_HOME contract as every other path in
668
+ // this provider (env var first, platform default otherwise): with
669
+ // HERMES_HOME set, the legacy tree lives under that root — never under
670
+ // the process home directory.
671
+ const legacyPath = path.join(getHermesHome(), '.aiwg', 'skills');
669
672
  const newPath = paths.skills;
670
673
 
671
674
  let legacyExists = false;
@@ -3,7 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { createHash } from 'node:crypto';
5
5
  import YAML from 'yaml';
6
- import { resolveOmpPaths } from '../../../src/providers/omp-paths.mjs';
6
+ import { importImpl } from '../../_resolve-impl.mjs';
7
7
  import { collectFrameworkArtifacts, getAddonFiles, isKernelSkill, listMdFiles, listSkillDirs, normalizeDeploymentMode, resolveAiwgRoot } from './base.mjs';
8
8
  export const name = 'omp';
9
9
  export const aliases = ['oh-my-pi'];
@@ -18,7 +18,10 @@ function parse(content) {
18
18
  }
19
19
  const render = (metadata, body) => `---\n${YAML.stringify(metadata)}---\n\n${body.trim()}\n`;
20
20
  function diagnostic(opts, message) { opts.diagnostics?.push(message); if (!opts.quiet) console.warn(`OMP: ${message}`); }
21
- import { transformAgent, mapModel } from '../../../src/providers/omp-agent.mjs';
21
+ // Resolved through the shared resolver: an installed package ships these
22
+ // modules under dist/src, not src, so a literal path into src fails there.
23
+ const { resolveOmpPaths } = await importImpl(import.meta.url, 'providers/omp-paths.mjs');
24
+ const { transformAgent, mapModel } = await importImpl(import.meta.url, 'providers/omp-agent.mjs');
22
25
  export { transformAgent, mapModel };
23
26
  const array = value => Array.isArray(value) ? value : typeof value === 'string' ? value.split(',').map(s => s.trim()).filter(Boolean) : [];
24
27
  export function transformCommand(src, content, opts = {}) {
@@ -120,6 +120,20 @@ function parseBehaviorFrontmatter(content) {
120
120
  return frontmatter;
121
121
  }
122
122
 
123
+ /** Encode a complete YAML double-quoted scalar. */
124
+ function yamlString(value) {
125
+ return JSON.stringify(value)
126
+ .replace(/\u0085/g, '\\u0085')
127
+ .replace(/\u2028/g, '\\u2028')
128
+ .replace(/\u2029/g, '\\u2029');
129
+ }
130
+
131
+ /** Keep metadata within a single line or block comment. */
132
+ function escapeComment(value) {
133
+ return String(value).replace(/\*\//g, '* /')
134
+ .replace(/[\r\n\u2028\u2029]/g, (char) => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0'));
135
+ }
136
+
123
137
  /**
124
138
  * Render the HOOK.md content for OpenClaw's hook loader.
125
139
  *
@@ -133,15 +147,15 @@ function parseBehaviorFrontmatter(content) {
133
147
  export function renderHookMd(behavior) {
134
148
  const events = Object.keys(behavior.hooks || {});
135
149
  const eventsList = events.length > 0
136
- ? events.map((e) => ` - ${e}`).join('\n')
150
+ ? events.map((e) => ` - ${yamlString(e)}`).join('\n')
137
151
  : ' []';
138
152
 
139
- const description = (behavior.description || '').replace(/"/g, '\\"').slice(0, 200);
153
+ const description = yamlString((behavior.description || '').slice(0, 200));
140
154
 
141
155
  return [
142
156
  '---',
143
- `name: ${behavior.name}`,
144
- `description: "${description}"`,
157
+ `name: ${yamlString(behavior.name)}`,
158
+ `description: ${description}`,
145
159
  'events:',
146
160
  eventsList,
147
161
  'handler: ./handler.js',
@@ -193,7 +207,7 @@ export function renderHandlerJs(behavior, scriptsRelDir = './scripts') {
193
207
 
194
208
  return `#!/usr/bin/env node
195
209
  // Generated by AIWG (PUW-008/PUW-009). Do not edit; regenerated on \`aiwg use\`.
196
- // Handler for OpenClaw hook \`${behavior.name}\`. Dispatches to scripts in
210
+ // Handler for OpenClaw hook \`${escapeComment(behavior.name)}\`. Dispatches to scripts in
197
211
  // ./scripts/ based on the event delivered by OpenClaw's hook loader.
198
212
  //
199
213
  // Stdin contract (AIWG canonical, per ADR-3 §3):