@aiwg/cli 2026.8.7 → 2026.8.10

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 (76) hide show
  1. package/README.md +23 -8
  2. package/THIRD_PARTY_NOTICES.md +35 -0
  3. package/agentic/code/providers/capability-matrix.yaml +3 -3
  4. package/bin/aiwg.mjs +125 -0
  5. package/dist/src/artifacts/backends/graphology-backend.js +4 -3
  6. package/dist/src/artifacts/backends/sqlite-backend.js +4 -5
  7. package/dist/src/artifacts/cli.js +2 -2
  8. package/dist/src/artifacts/corpus-tools/cli.js +27 -0
  9. package/dist/src/artifacts/corpus-tools/profile-embed.js +3 -2
  10. package/dist/src/artifacts/corpus-tools/retrieval-lab.js +356 -0
  11. package/dist/src/artifacts/discover-facets.js +2 -2
  12. package/dist/src/artifacts/embedding-index.js +9 -8
  13. package/dist/src/artifacts/graph-backend.js +2 -2
  14. package/dist/src/artifacts/move.js +40 -2
  15. package/dist/src/artifacts/query-engine.js +21 -7
  16. package/dist/src/artifacts/repair.js +47 -0
  17. package/dist/src/artifacts/types.js +3 -3
  18. package/dist/src/cli/command-log.js +2 -2
  19. package/dist/src/cli/handlers/artifacts.js +50 -1
  20. package/dist/src/cli/handlers/cost-report.js +71 -0
  21. package/dist/src/cli/handlers/evidence.js +78 -0
  22. package/dist/src/cli/handlers/help.js +9 -0
  23. package/dist/src/cli/handlers/index.js +8 -3
  24. package/dist/src/cli/handlers/local-executor.js +4 -3
  25. package/dist/src/cli/handlers/refresh.js +20 -8
  26. package/dist/src/cli/handlers/regenerate.js +22 -12
  27. package/dist/src/cli/handlers/serve.js +15 -36
  28. package/dist/src/cli/handlers/sessions.js +5 -4
  29. package/dist/src/cli/handlers/setup-manifest.js +8 -1
  30. package/dist/src/cli/handlers/subcommands.js +30 -0
  31. package/dist/src/cli/handlers/use.js +272 -89
  32. package/dist/src/cli/handlers/utilities.js +149 -0
  33. package/dist/src/cli/handlers/workspace.js +10 -0
  34. package/dist/src/cli/help-generator.js +2 -1
  35. package/dist/src/cli/regenerate-selector.js +94 -0
  36. package/dist/src/cli/router.js +4 -1
  37. package/dist/src/cli/services/deployment-verification.js +596 -0
  38. package/dist/src/cli/skill-usage.js +2 -2
  39. package/dist/src/cli/workflow-orchestrator.js +1 -1
  40. package/dist/src/cli/workspace-signals.js +2 -2
  41. package/dist/src/config/aiwg-config.js +54 -27
  42. package/dist/src/config/cli.js +3 -3
  43. package/dist/src/config/project-artifacts-health.js +2 -0
  44. package/dist/src/config/project-artifacts-health.mjs +123 -0
  45. package/dist/src/config/project-artifacts-runtime.mjs +16 -0
  46. package/dist/src/config/project-artifacts.js +2 -1
  47. package/dist/src/cost/fleet-report.js +329 -0
  48. package/dist/src/evidence/bundle.js +256 -0
  49. package/dist/src/extensions/commands/definitions.js +77 -25
  50. package/dist/src/extensions/deployment-registration.js +6 -4
  51. package/dist/src/extensions/project-local-doctor.js +10 -6
  52. package/dist/src/extensions/project-local-gitignore.js +8 -4
  53. package/dist/src/extensions/project-quickref.js +197 -10
  54. package/dist/src/features/catalog.js +26 -0
  55. package/dist/src/features/cli.js +1 -3
  56. package/dist/src/features/runtime.js +17 -1
  57. package/dist/src/issues/cli.js +91 -7
  58. package/dist/src/mcp/server.mjs +1 -1
  59. package/dist/src/ops/registry.js +2 -2
  60. package/dist/src/policy/authorization.js +2 -2
  61. package/dist/src/providers/capability-matrix.yaml +3 -3
  62. package/dist/src/providers/provider-definitions.js +7 -5
  63. package/dist/src/providers/provider-definitions.mjs +1 -1
  64. package/dist/src/serve/pty-bridge.js +2 -8
  65. package/dist/src/serve/screen-reader.js +3 -6
  66. package/dist/src/smiths/context-pipeline/aiwg-md.js +2 -2
  67. package/dist/src/smiths/context-pipeline/finalization.js +18 -5
  68. package/dist/src/smiths/context-pipeline/generator.js +2 -2
  69. package/dist/src/smiths/context-pipeline/workspace-context.js +16 -17
  70. package/package.json +2 -1
  71. package/tools/agents/deploy-agents.mjs +10 -11
  72. package/tools/agents/providers/base.mjs +47 -5
  73. package/tools/agents/providers/openclaw.mjs +5 -2
  74. package/tools/agents/providers/windsurf.mjs +13 -24
  75. package/tools/plugin/package-plugins.mjs +53 -0
  76. package/tools/skills/deploy-skills-codex.mjs +21 -5
@@ -4,7 +4,7 @@ import { createHash } from 'crypto';
4
4
  import path from 'path';
5
5
  import os from 'os';
6
6
  import { readAiwgConfig } from '../config/aiwg-config.js';
7
- import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath } from '../config/project-artifacts.js';
7
+ import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath, projectControlPath, } from '../config/project-artifacts.js';
8
8
  const DEFAULT_MAX_BYTES = 1_048_576;
9
9
  const DEFAULT_REPORT_LIMIT = 20;
10
10
  export async function maybeAppendCommandLog(input) {
@@ -157,7 +157,7 @@ async function findProjectRoot(startDir) {
157
157
  while (current !== path.dirname(current)) {
158
158
  if (existsSync(path.join(current, '.aiwg')) ||
159
159
  existsSync(path.join(current, PROJECT_AIWG_LOCATION_FILE)) ||
160
- existsSync(projectAiwgPath(current, 'aiwg.config'))) {
160
+ existsSync(projectControlPath(current, 'aiwg.config'))) {
161
161
  return current;
162
162
  }
163
163
  current = path.dirname(current);
@@ -1,12 +1,17 @@
1
1
  import { getProjectDir } from '../../config/aiwg-config.js';
2
2
  import { moveProjectArtifacts } from '../../artifacts/move.js';
3
+ import { repairProjectArtifacts } from '../../artifacts/repair.js';
4
+ import { resolveProjectAiwgDir } from '../../config/project-artifacts.js';
3
5
  function usage() {
4
6
  return [
5
7
  'aiwg artifacts — Manage the project AIWG artifact root',
6
8
  '',
7
9
  'Usage:',
10
+ ' aiwg artifacts path [--json]',
8
11
  ' aiwg artifacts move --to <path> [--from <path>] [--dry-run] [--no-reindex] [--no-sync]',
9
12
  ' aiwg artifacts attach --to <existing-path> [--dry-run] [--no-reindex] [--no-sync]',
13
+ ' aiwg artifacts repair --dry-run',
14
+ ' aiwg artifacts repair --apply',
10
15
  '',
11
16
  'Notes:',
12
17
  ' move relocates a local artifact root; attach adopts an existing populated root.',
@@ -32,9 +37,53 @@ export const artifactsHandler = {
32
37
  if (action === 'help' || ctx.args.includes('--help') || ctx.args.includes('-h')) {
33
38
  return { exitCode: 0, message: usage() };
34
39
  }
35
- if (action !== 'move' && action !== 'attach') {
40
+ if (action !== 'path' && action !== 'move' && action !== 'attach' && action !== 'repair') {
36
41
  return { exitCode: 1, message: `Unknown artifacts action: ${action}\n\n${usage()}` };
37
42
  }
43
+ if (action === 'path') {
44
+ const projectDir = getProjectDir(ctx, ctx.args);
45
+ const artifactRoot = resolveProjectAiwgDir(projectDir);
46
+ if (ctx.args.includes('--json')) {
47
+ return {
48
+ exitCode: 0,
49
+ message: JSON.stringify({
50
+ schema: 'aiwg.artifacts.path.v1',
51
+ project_root: projectDir,
52
+ artifact_root: artifactRoot,
53
+ }, null, 2),
54
+ rawOutput: true,
55
+ };
56
+ }
57
+ return { exitCode: 0, message: artifactRoot, rawOutput: true };
58
+ }
59
+ if (action === 'repair') {
60
+ try {
61
+ const applied = ctx.args.includes('--apply');
62
+ const result = await repairProjectArtifacts({
63
+ projectDir: getProjectDir(ctx, ctx.args),
64
+ apply: applied,
65
+ });
66
+ return {
67
+ exitCode: 0,
68
+ message: [
69
+ `${applied ? 'Repaired' : 'Artifact repair dry run for'} ${result.before.classification}`,
70
+ ` Local control plane: ${result.before.local_control_root}`,
71
+ ` External corpus: ${result.before.artifact_root}`,
72
+ ` Copy locally: ${result.copied.length ? result.copied.join(', ') : 'none'}`,
73
+ ` Remove local identical corpus copies: ${result.removed.length ? result.removed.join(', ') : 'none'}`,
74
+ ` Result: ${result.after.classification}`,
75
+ applied ? '' : 'No files changed. Re-run with --apply after reviewing this plan.',
76
+ ].filter(Boolean).join('\n'),
77
+ };
78
+ }
79
+ catch (error) {
80
+ return {
81
+ exitCode: 1,
82
+ error: error instanceof Error ? error : new Error(String(error)),
83
+ message: `Artifact repair failed: ${error instanceof Error ? error.message : String(error)}`,
84
+ };
85
+ }
86
+ }
38
87
  const to = valueAfter(ctx.args, '--to');
39
88
  if (!to) {
40
89
  return { exitCode: 1, message: `Error: --to <path> is required.\n\n${usage()}` };
@@ -0,0 +1,71 @@
1
+ /** OpenRouter fleet cost-report CLI handler. @issue #1187 */
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { FleetConfigMissingError, formatFleetSpendReport, generateFleetSpendReport, } from '../../cost/fleet-report.js';
5
+ function option(args, name) {
6
+ const index = args.indexOf(name);
7
+ const value = index >= 0 ? args[index + 1] : undefined;
8
+ return value && !value.startsWith('-') ? value : undefined;
9
+ }
10
+ function usage() {
11
+ return [
12
+ 'Usage: aiwg cost-report (--fleet | --key <key_ref>) [--source openrouter] [--config <fleet.yaml>] [--json]',
13
+ '',
14
+ 'Reads bot-to-key references from ~/.config/aiwg/fleet.yaml and credentials from',
15
+ '~/.config/aiwg/keys/<key_ref> or AIWG_OPENROUTER_KEY_<KEY_REF>.',
16
+ '',
17
+ 'AIWG observes and correlates spend; OpenRouter enforces all key limits and caps.',
18
+ ].join('\n');
19
+ }
20
+ export const costReportHandler = {
21
+ id: 'cost-report',
22
+ name: 'Cost Report',
23
+ description: 'Observe OpenRouter fleet spend and correlate it with local AIWG activity',
24
+ category: 'utility',
25
+ aliases: [],
26
+ async execute(ctx) {
27
+ if (ctx.args.includes('--help') || ctx.args.includes('-h'))
28
+ return { exitCode: 0, message: usage() };
29
+ const keyRef = option(ctx.args, '--key');
30
+ if (ctx.args.includes('--fleet') && keyRef)
31
+ return { exitCode: 2, message: 'Choose either --fleet or --key, not both.' };
32
+ if (!ctx.args.includes('--fleet') && !keyRef) {
33
+ return { exitCode: 2, message: `Select --fleet or --key <key_ref>.\n\n${usage()}` };
34
+ }
35
+ const source = option(ctx.args, '--source') ?? 'openrouter';
36
+ if (source !== 'openrouter')
37
+ return { exitCode: 2, message: `Unsupported source '${source}'. Only openrouter is supported.` };
38
+ const configuredPath = option(ctx.args, '--config');
39
+ const configPath = configuredPath
40
+ ? path.resolve(ctx.cwd, configuredPath)
41
+ : path.join(os.homedir(), '.config', 'aiwg', 'fleet.yaml');
42
+ try {
43
+ const capValue = option(ctx.args, '--monthly-cap');
44
+ const cap = capValue === undefined ? 0 : Number(capValue);
45
+ if (!Number.isFinite(cap) || cap < 0)
46
+ return { exitCode: 2, message: '--monthly-cap must be a non-negative number.' };
47
+ const report = await generateFleetSpendReport({
48
+ cwd: ctx.cwd,
49
+ configPath,
50
+ signal: ctx.signal,
51
+ ...(keyRef ? {
52
+ fleet: [{
53
+ bot: option(ctx.args, '--bot') ?? keyRef,
54
+ machine: option(ctx.args, '--machine') ?? os.hostname(),
55
+ key_ref: keyRef,
56
+ monthly_cap: cap,
57
+ }],
58
+ } : {}),
59
+ });
60
+ return {
61
+ exitCode: report.bots.some(bot => bot.error) ? 1 : 0,
62
+ message: ctx.args.includes('--json') ? JSON.stringify(report, null, 2) : formatFleetSpendReport(report),
63
+ };
64
+ }
65
+ catch (error) {
66
+ const message = error instanceof FleetConfigMissingError ? error.message : `Fleet cost report failed: ${error instanceof Error ? error.message : String(error)}`;
67
+ return { exitCode: 1, message };
68
+ }
69
+ },
70
+ };
71
+ //# sourceMappingURL=cost-report.js.map
@@ -0,0 +1,78 @@
1
+ /** Evidence bundle export and verification CLI. @issue #2039 */
2
+ import path from 'node:path';
3
+ import { createEvidenceBundle, verifyEvidenceBundle } from '../../evidence/bundle.js';
4
+ function values(args, flag) {
5
+ const result = [];
6
+ for (let index = 0; index < args.length; index++)
7
+ if (args[index] === flag && args[index + 1])
8
+ result.push(args[++index]);
9
+ return result;
10
+ }
11
+ function value(args, flag) { return values(args, flag)[0]; }
12
+ function versions(items) {
13
+ return Object.fromEntries(items.map(item => {
14
+ const index = item.indexOf('=');
15
+ if (index < 1 || index === item.length - 1)
16
+ throw new Error(`version '${item}' must use name=value`);
17
+ return [item.slice(0, index), item.slice(index + 1)];
18
+ }));
19
+ }
20
+ function usage() {
21
+ return [
22
+ 'Usage:',
23
+ ' aiwg evidence export --output <dir> [--activity-export <json>] [--report <file>] [--source <file>]',
24
+ ' [--eval-config <file>] [--provenance <file>] [--model-version name=value] [--tool-version name=value]',
25
+ ' [--check-only --not-run <reason>] [--json]',
26
+ ' aiwg evidence verify <bundle> [--expected-root <sha256>] [--json]',
27
+ ].join('\n');
28
+ }
29
+ export const evidenceHandler = {
30
+ id: 'evidence', name: 'Evidence', description: 'Export and verify portable evaluation evidence bundles', category: 'utility', aliases: [],
31
+ async execute(ctx) {
32
+ const [subcommand] = ctx.args;
33
+ if (!subcommand || subcommand === '--help' || subcommand === '-h')
34
+ return { exitCode: 0, message: usage() };
35
+ try {
36
+ if (subcommand === 'verify') {
37
+ const bundle = ctx.args[1];
38
+ if (!bundle || bundle.startsWith('-'))
39
+ return { exitCode: 2, message: usage() };
40
+ const expectedRoot = value(ctx.args, '--expected-root');
41
+ if (expectedRoot && !/^[0-9a-f]{64}$/i.test(expectedRoot))
42
+ return { exitCode: 2, message: '--expected-root must be a SHA-256 hex digest' };
43
+ const result = await verifyEvidenceBundle(path.resolve(ctx.cwd, bundle), expectedRoot?.toLowerCase());
44
+ return { exitCode: result.valid ? 0 : 1, message: ctx.args.includes('--json') ? JSON.stringify(result, null, 2) : [
45
+ `Evidence bundle: ${result.valid ? 'VALID' : 'INVALID'} (${result.status})`,
46
+ ...result.errors.map(error => `ERROR: ${error}`), ...result.warnings.map(warning => `WARN: ${warning}`),
47
+ ].join('\n') };
48
+ }
49
+ if (subcommand !== 'export')
50
+ return { exitCode: 2, message: usage() };
51
+ const output = value(ctx.args, '--output');
52
+ if (!output)
53
+ return { exitCode: 2, message: '--output is required.\n\n' + usage() };
54
+ const checkOnly = ctx.args.includes('--check-only');
55
+ const notRunReason = value(ctx.args, '--not-run');
56
+ if (checkOnly !== Boolean(notRunReason))
57
+ return { exitCode: 2, message: '--check-only and --not-run <reason> must be used together' };
58
+ const inputs = [
59
+ ...values(ctx.args, '--activity-export').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'activity-export' })),
60
+ ...values(ctx.args, '--report').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'report' })),
61
+ ...values(ctx.args, '--source').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'source' })),
62
+ ...values(ctx.args, '--eval-config').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'eval-config' })),
63
+ ...values(ctx.args, '--provenance').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'provenance' })),
64
+ ];
65
+ const manifest = await createEvidenceBundle({
66
+ output: path.resolve(ctx.cwd, output), inputs,
67
+ modelVersions: versions(values(ctx.args, '--model-version')),
68
+ toolVersions: { aiwg: process.env.npm_package_version ?? 'unknown', node: process.version, ...versions(values(ctx.args, '--tool-version')) },
69
+ checkOnly, notRunReason,
70
+ });
71
+ return { exitCode: 0, message: ctx.args.includes('--json') ? JSON.stringify(manifest, null, 2) : `Evidence bundle ${manifest.status}: ${path.resolve(ctx.cwd, output)}\nVerifier root: ${manifest.verifier.root}` };
72
+ }
73
+ catch (error) {
74
+ return { exitCode: 1, message: `Evidence command failed: ${error instanceof Error ? error.message : String(error)}` };
75
+ }
76
+ },
77
+ };
78
+ //# sourceMappingURL=evidence.js.map
@@ -95,6 +95,15 @@ function displayHelp() {
95
95
  ]);
96
96
  helpGroup('VALIDATION', [
97
97
  ['validate-metadata [path]', 'Validate AIWG component metadata (defaults to agentic/code)'],
98
+ ['context-firewall [scan]', 'Audit provider context, trust, drift, poisoning signals, and budget'],
99
+ ['context-firewall baseline', 'Plan or explicitly write the reviewed context baseline'],
100
+ ]);
101
+ helpGroup('METRICS', [
102
+ ['cost-report --fleet', 'Observe OpenRouter per-bot MTD spend and correlate local activity'],
103
+ ]);
104
+ helpGroup('EVIDENCE', [
105
+ ['evidence export --output <dir>', 'Package portable activity, report, source, eval, and provenance evidence'],
106
+ ['evidence verify <bundle>', 'Verify every member hash and the bundle checkpoint'],
98
107
  ]);
99
108
  helpGroup('SCAFFOLDING', [
100
109
  ['new-bundle <name>', 'Create project-local bundle (--type extension|addon|framework|plugin|provider, --starter skill|rule|agent|minimal, --dry-run)'],
@@ -16,7 +16,7 @@ import { versionHandler } from './version.js';
16
16
  import { authHandler } from './auth.js';
17
17
  import { useHandler } from './use.js';
18
18
  import { statusHandler, wizardHandler, migrateWorkspaceHandler, rollbackWorkspaceHandler, workspaceHandlers, } from './workspace.js';
19
- import { prefillCardsHandler, contributeStartHandler, validateMetadataHandler, doctorHandler, updateHandler, utilityHandlers, } from './utilities.js';
19
+ import { prefillCardsHandler, contributeStartHandler, validateMetadataHandler, doctorHandler, contextFirewallHandler, updateHandler, utilityHandlers, } from './utilities.js';
20
20
  import { skillLintHandler } from './skill-lint.js';
21
21
  import { addAgentHandler, addCommandHandler, addSkillHandler, addBehaviorHandler, addTemplateHandler, scaffoldAddonHandler, scaffoldExtensionHandler, scaffoldFrameworkHandler, scaffoldingHandlers, } from './scaffolding.js';
22
22
  import { behaviorHandler, daemonInitHandler, daemonHandlers, } from './daemon.js';
@@ -59,12 +59,14 @@ import { skillUsageHandler } from './skill-usage.js';
59
59
  import { modelsHandler } from './models.js';
60
60
  import { versionsHandler } from './resource-versions.js';
61
61
  import { jobHandler } from './job.js';
62
+ import { costReportHandler } from './cost-report.js';
63
+ import { evidenceHandler } from './evidence.js';
62
64
  // Re-export individual handlers
63
65
  export {
64
66
  // Maintenance
65
- helpHandler, versionHandler, authHandler, doctorHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
67
+ helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
66
68
  // Framework management
67
- useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler,
69
+ useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler,
68
70
  // Project
69
71
  newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
70
72
  // Workspace
@@ -118,6 +120,7 @@ export const allHandlers = [
118
120
  versionHandler,
119
121
  authHandler,
120
122
  doctorHandler,
123
+ contextFirewallHandler,
121
124
  updateHandler,
122
125
  refreshHandler,
123
126
  regenerateHandler,
@@ -143,6 +146,8 @@ export const allHandlers = [
143
146
  issueAuditHandler,
144
147
  runHandler,
145
148
  jobHandler,
149
+ costReportHandler,
150
+ evidenceHandler,
146
151
  // Workspace management
147
152
  ...workspaceHandlers,
148
153
  // Subcommand handlers (MCP, catalog, index, skills)
@@ -19,6 +19,7 @@
19
19
  import path from 'path';
20
20
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
21
21
  import { randomUUID } from 'crypto';
22
+ import { pathToFileURL } from 'node:url';
22
23
  import { projectAiwgPath } from '../../config/project-artifacts.js';
23
24
  const DEFAULT_PORT = 8200;
24
25
  const DEFAULT_BIND = '127.0.0.1';
@@ -131,7 +132,7 @@ export const localExecutorServeHandler = {
131
132
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
133
  let DaemonSupervisor;
133
134
  try {
134
- const shimMod = await (new Function('m', 'return import(m)'))(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'executor-shim.mjs'));
135
+ const shimMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'executor-shim.mjs')).href);
135
136
  ExecutorShim = shimMod.ExecutorShim;
136
137
  startExecutorServer = shimMod.startExecutorServer;
137
138
  }
@@ -142,7 +143,7 @@ export const localExecutorServeHandler = {
142
143
  };
143
144
  }
144
145
  try {
145
- const dsMod = await (new Function('m', 'return import(m)'))(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'daemon-supervisor.mjs'));
146
+ const dsMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'daemon-supervisor.mjs')).href);
146
147
  DaemonSupervisor = dsMod.DaemonSupervisor;
147
148
  }
148
149
  catch (err) {
@@ -155,7 +156,7 @@ export const localExecutorServeHandler = {
155
156
  // requires it. Try loading from ralph-external/orchestrator.mjs.
156
157
  let agentSupervisorInstance = null;
157
158
  try {
158
- const orchMod = await (new Function('m', 'return import(m)'))(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'orchestrator.mjs'));
159
+ const orchMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'orchestrator.mjs')).href);
159
160
  const OrchClass = orchMod.Orchestrator ?? orchMod.default;
160
161
  if (OrchClass) {
161
162
  const orch = new OrchClass({ maxConcurrent: opts.maxConcurrency });
@@ -149,7 +149,7 @@ export function collectModelDeployArgs(args) {
149
149
  export const refreshHandler = {
150
150
  id: 'refresh',
151
151
  name: 'Refresh',
152
- description: 'Refresh AIWG to latest version and re-deploy all frameworks',
152
+ description: 'Refresh AIWG to latest version and re-deploy installed frameworks',
153
153
  category: 'maintenance',
154
154
  aliases: ['--refresh', 'sync', '--sync'],
155
155
  async execute(ctx) {
@@ -241,13 +241,24 @@ export const refreshHandler = {
241
241
  if (!quiet)
242
242
  ui.dim(' Skipping package update (--skip-update)');
243
243
  }
244
- // Step 4: Re-deploy frameworks
245
- const frameworks = frameworksArg ? frameworksArg.split(',') : undefined;
244
+ // Step 4: Re-deploy frameworks. Both the default form and --all mean
245
+ // "all installed", never the `aiwg use all` expansion meta-target. This
246
+ // preserves the operator's selected footprint and removal symmetry.
247
+ const refreshConfig = await readAiwgConfig(ctx.cwd);
248
+ const installedFrameworks = Object.keys(refreshConfig?.installed ?? {});
249
+ const requestedFrameworks = frameworksArg
250
+ ? frameworksArg.split(',').map(item => item.trim()).filter(Boolean)
251
+ : [];
252
+ const frameworks = !frameworksArg || requestedFrameworks.includes('all')
253
+ ? installedFrameworks
254
+ : requestedFrameworks;
246
255
  if (!quiet)
247
256
  ui.info(dryRun ? 'Would re-deploy frameworks...' : 'Re-deploying frameworks...');
248
257
  if (!dryRun) {
249
- const deployTarget = frameworks || ['all'];
250
- for (const fw of deployTarget) {
258
+ if (frameworks.length === 0 && !quiet) {
259
+ ui.dim(' No installed frameworks or addons to re-deploy');
260
+ }
261
+ for (const fw of frameworks) {
251
262
  const providerArgs = ['--provider', detectedProvider, ...modelDeployArgs];
252
263
  const useResult = await runner.run('tools/cli/deploy.mjs', [fw, ...providerArgs], { capture: quiet });
253
264
  if (useResult.exitCode === 0) {
@@ -261,9 +272,10 @@ export const refreshHandler = {
261
272
  }
262
273
  }
263
274
  else {
264
- const targets = frameworks || ['all installed frameworks'];
265
275
  if (!quiet) {
266
- for (const fw of targets) {
276
+ if (frameworks.length === 0)
277
+ ui.dim(' No installed frameworks or addons');
278
+ for (const fw of frameworks) {
267
279
  ui.dim(` Would re-deploy: ${fw}`);
268
280
  }
269
281
  }
@@ -419,7 +431,7 @@ export const refreshHandler = {
419
431
  const output = JSON.stringify({
420
432
  status: dryRun ? 'dry-run' : 'refreshed',
421
433
  provider: detectedProvider,
422
- frameworks: frameworks || ['all'],
434
+ frameworks,
423
435
  skipUpdate,
424
436
  channel: channel || undefined,
425
437
  staleAgentRemovals,
@@ -25,7 +25,8 @@ import * as ui from '../ui.js';
25
25
  import { generate as generateContextFiles, discoverDeployedArtifacts, shouldEmitContextFiles, buildNormalizedAiwgMd, writeNormalizedAiwgMd, injectLegacyContext, migrateWorkspaceContext, extractExistingProjectContext, } from '../../smiths/context-pipeline/index.js';
26
26
  import { resolveActiveProvider } from '../provider-resolution.js';
27
27
  import { getProviderContextDiscoveryPathStrings } from '../../providers/provider-definitions.js';
28
- import { projectAiwgPath } from '../../config/project-artifacts.js';
28
+ import { projectControlPath } from '../../config/project-artifacts.js';
29
+ import { selectRegenerateBranch } from '../regenerate-selector.js';
29
30
  async function handleRegenerate(args, cwd) {
30
31
  if (args.includes('--help') || args.includes('-h')) {
31
32
  console.log(`
@@ -40,7 +41,8 @@ async function handleRegenerate(args, cwd) {
40
41
  or commands — use 'aiwg refresh' for that.
41
42
 
42
43
  Options:
43
- --workspace Canonical WORKSPACE.md AIWG.md graph (default)
44
+ (no branch flag) Intelligently select workspace refresh or adoption preview
45
+ --workspace Explicit canonical WORKSPACE.md → AIWG.md graph
44
46
  --existing-project Transactionally extract an established project into WORKSPACE.md
45
47
  --legacy, --full-inject Legacy inline compatibility branch
46
48
  --apply Apply --existing-project after its mandatory preflight
@@ -57,6 +59,7 @@ async function handleRegenerate(args, cwd) {
57
59
  aiwg regenerate --workspace
58
60
  aiwg regenerate --existing-project --dry-run
59
61
  aiwg regenerate --existing-project --apply
62
+ aiwg regenerate --apply
60
63
  aiwg regenerate --full-inject
61
64
  aiwg regenerate --dry-run
62
65
  aiwg regenerate --provider codex
@@ -69,9 +72,9 @@ async function handleRegenerate(args, cwd) {
69
72
  const skipAiwgMd = args.includes('--no-aiwg-md');
70
73
  const skipAgentsMd = args.includes('--no-agents-md');
71
74
  const skipWorkspaceMd = args.includes('--no-workspace-md');
72
- const legacy = args.includes('--legacy') || args.includes('--full-inject');
73
- const workspace = args.includes('--workspace');
74
- const existingProject = args.includes('--existing-project');
75
+ const requestedLegacy = args.includes('--legacy') || args.includes('--full-inject');
76
+ const requestedWorkspace = args.includes('--workspace');
77
+ const requestedExistingProject = args.includes('--existing-project');
75
78
  const apply = args.includes('--apply');
76
79
  const valueFlags = new Set(['--provider']);
77
80
  const booleanFlags = new Set([
@@ -95,23 +98,27 @@ async function handleRegenerate(args, cwd) {
95
98
  });
96
99
  }
97
100
  }
98
- const selectedBranches = Number(legacy) + Number(workspace) + Number(existingProject);
101
+ const selectedBranches = Number(requestedLegacy) + Number(requestedWorkspace) + Number(requestedExistingProject);
99
102
  if (selectedBranches > 1)
100
103
  throw new AiwgError({
101
104
  code: 'ERR_USAGE_CONFLICTING_FLAGS',
102
105
  message: 'Choose exactly one regenerate branch: --workspace, --existing-project, or --full-inject.',
103
106
  exitCode: EXIT_CODES.USAGE,
104
107
  });
105
- if (apply && !existingProject)
108
+ if (dryRun && apply)
106
109
  throw new AiwgError({
107
110
  code: 'ERR_USAGE_CONFLICTING_FLAGS',
108
- message: '--apply is only valid with --existing-project.',
111
+ message: 'Choose either --dry-run or --apply.',
109
112
  exitCode: EXIT_CODES.USAGE,
110
113
  });
111
- if (existingProject && dryRun && apply)
114
+ const selection = await selectRegenerateBranch(cwd, args);
115
+ const legacy = selection.branch === 'legacy';
116
+ const existingProject = selection.branch === 'existing-project';
117
+ if (apply && !existingProject)
112
118
  throw new AiwgError({
113
119
  code: 'ERR_USAGE_CONFLICTING_FLAGS',
114
- message: 'Choose either --dry-run or --apply for --existing-project.',
120
+ message: '--apply is only valid when the existing-project branch is selected.',
121
+ hint: 'Use `aiwg regenerate --existing-project --apply`, or run without --apply to inspect the selected branch.',
115
122
  exitCode: EXIT_CODES.USAGE,
116
123
  });
117
124
  if (existingProject && (force || skipAiwgMd || skipAgentsMd || skipWorkspaceMd))
@@ -140,6 +147,9 @@ async function handleRegenerate(args, cwd) {
140
147
  console.log(` Provider: ${provider}`);
141
148
  console.log(` Target: ${target}`);
142
149
  console.log(` Branch: ${legacy ? 'legacy full injection' : existingProject ? 'canonical existing-project extraction' : 'canonical workspace graph'}`);
150
+ console.log(` Selected: ${selection.explicit ? 'explicit' : 'inferred'} — ${selection.reason}`);
151
+ if (selection.evidence.length > 0)
152
+ console.log(` Evidence: ${selection.evidence.join(', ')}`);
143
153
  if (existingProject) {
144
154
  const preflight = await migrateWorkspaceContext(target, {
145
155
  dryRun: true,
@@ -188,7 +198,7 @@ async function handleRegenerate(args, cwd) {
188
198
  if (legacy) {
189
199
  if (skipWorkspaceMd)
190
200
  console.log(' Note: --no-workspace-md is implicit in legacy mode.');
191
- const normalizedPath = projectAiwgPath(target, 'AIWG.md');
201
+ const normalizedPath = projectControlPath(target, 'AIWG.md');
192
202
  let existing = '';
193
203
  try {
194
204
  existing = await fs.readFile(normalizedPath, 'utf8');
@@ -236,7 +246,7 @@ async function handleRegenerate(args, cwd) {
236
246
  console.log(` Would regenerate:`);
237
247
  if (!skipWorkspaceMd)
238
248
  console.log(` - ${path.join(target, 'WORKSPACE.md')} (managed graph; operator section preserved)`);
239
- console.log(` - ${projectAiwgPath(target, 'AIWG.md')}`);
249
+ console.log(` - ${projectControlPath(target, 'AIWG.md')}`);
240
250
  if (!skipAiwgMd)
241
251
  console.log(` - ${aiwgMd}`);
242
252
  if (provider === 'claude') {
@@ -10,7 +10,6 @@
10
10
  */
11
11
  import path from 'path';
12
12
  import { existsSync, readFileSync } from 'fs';
13
- import { spawnSync } from 'child_process';
14
13
  import { createPtyWsHandler, registry as ptyRegistry } from '../../serve/pty-bridge.js';
15
14
  import { telemetryStore, createEvent } from '../../serve/telemetry.js';
16
15
  import { sandboxRegistry, normalizeSandboxEvent, } from '../../serve/sandbox-registry.js';
@@ -21,6 +20,7 @@ import { executorRegistry, validateRegisterPayload, validateDispatchPayload, val
21
20
  import { handleWebhook, IdempotencyCache, PushSecretRegistry, } from '../../a2a/webhook.js';
22
21
  import { AiwgError, EXIT_CODES } from '../errors.js';
23
22
  import { projectAiwgPath } from '../../config/project-artifacts.js';
23
+ import { loadFeaturePackage } from '../../features/runtime.js';
24
24
  // A2A push-notification state — module-scoped so the test harness can
25
25
  // monkey-patch them in if needed. One process serves one set of secrets.
26
26
  const pushSecretRegistry = new PushSecretRegistry();
@@ -190,11 +190,10 @@ async function setupWebSockets(httpServer, readOnly) {
190
190
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
191
  let wsMod;
192
192
  try {
193
- // @ts-expect-error ws lacks bundled types; we use the runtime constructor only
194
- wsMod = await import('ws');
193
+ wsMod = await loadFeaturePackage('ws');
195
194
  }
196
195
  catch {
197
- console.warn('[serve] ws package not available WebSocket routes disabled. Install with: npm install ws');
196
+ console.warn('[serve] WebSocket routes disabledrun `aiwg features install webserver` to enable them.');
198
197
  return;
199
198
  }
200
199
  // ws ships as CJS; ESM import may wrap in .default
@@ -510,40 +509,19 @@ export async function startServer(opts) {
510
509
  // import path raises ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING (#1277).
511
510
  // hono is an optionalDependency; tsc may not find its types under
512
511
  // `npm ci --omit=optional` (e.g. metadata-validation workflow). The
513
- // try/catch + auto-install fallback below handles that at runtime.
512
+ // try/catch below emits the managed feature-install route at runtime.
514
513
  // @ts-ignore — optional dep; may not be installed at typecheck time
515
- honoMod = await import('hono');
514
+ honoMod = await loadFeaturePackage('hono');
516
515
  // @ts-ignore — optional dep; may not be installed at typecheck time
517
- nodeMod = await import('@hono/node-server');
516
+ nodeMod = await loadFeaturePackage('@hono/node-server');
518
517
  }
519
518
  catch {
520
- // Auto-install optional serve dependencies on first use
521
- console.log('Installing serve dependencies (hono, @hono/node-server, ws)...');
522
- const result = spawnSync('npm', ['install', '--save-optional', 'hono', '@hono/node-server', 'ws'], { stdio: 'inherit' });
523
- if (result.status !== 0) {
524
- throw new AiwgError({
525
- code: 'ERR_SERVE_DEPS_INSTALL_FAILED',
526
- message: 'Failed to install serve dependencies (hono, @hono/node-server, ws)',
527
- hint: 'Install manually: npm install hono @hono/node-server ws',
528
- exitCode: EXIT_CODES.GENERAL,
529
- });
530
- }
531
- // Retry imports after install
532
- try {
533
- // @ts-ignore — optional dep; may not be installed at typecheck time
534
- honoMod = await import('hono');
535
- // @ts-ignore — optional dep; may not be installed at typecheck time
536
- nodeMod = await import('@hono/node-server');
537
- }
538
- catch (err) {
539
- throw new AiwgError({
540
- code: 'ERR_SERVE_DEPS_LOAD_FAILED',
541
- message: 'Serve dependencies installed but could not be loaded',
542
- hint: 'Try: npm install hono @hono/node-server ws',
543
- exitCode: EXIT_CODES.GENERAL,
544
- cause: err,
545
- });
546
- }
519
+ throw new AiwgError({
520
+ code: 'ERR_SERVE_DEPS_MISSING',
521
+ message: 'The optional webserver feature is not available',
522
+ hint: 'Run `aiwg features install webserver`, then retry `aiwg serve`.',
523
+ exitCode: EXIT_CODES.GENERAL,
524
+ });
547
525
  }
548
526
  const { Hono } = honoMod;
549
527
  const { serve } = nodeMod;
@@ -1692,7 +1670,8 @@ export async function startServer(opts) {
1692
1670
  const webDistDir = path.join(opts.frameworkRoot, 'apps', 'web', 'dist');
1693
1671
  if (existsSync(webDistDir)) {
1694
1672
  try {
1695
- const { serveStatic } = await (new Function('m', 'return import(m)'))('@hono/node-server/serve-static');
1673
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1674
+ const { serveStatic } = await loadFeaturePackage('@hono/node-server/serve-static');
1696
1675
  app.use('/*', serveStatic({ root: webDistDir }));
1697
1676
  }
1698
1677
  catch {
@@ -1788,7 +1767,7 @@ export const serveHandler = {
1788
1767
  if (open) {
1789
1768
  try {
1790
1769
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1791
- const openMod = await (new Function('m', 'return import(m)'))('open');
1770
+ const openMod = await loadFeaturePackage('open');
1792
1771
  const openBrowser = openMod.default ?? openMod;
1793
1772
  await openBrowser(url);
1794
1773
  }
@@ -1203,15 +1203,16 @@ function projectRootCandidate(start) {
1203
1203
  catch {
1204
1204
  current = resolve(start);
1205
1205
  }
1206
- let gitRoot = null;
1207
1206
  while (true) {
1208
1207
  if (existsSync(resolve(current, '.aiwg', 'aiwg.config')))
1209
1208
  return current;
1210
- if (!gitRoot && existsSync(resolve(current, '.git')))
1211
- gitRoot = current;
1209
+ // A repository root is the project boundary. Do not let an unrelated
1210
+ // ancestor workspace configuration capture session catalog reads.
1211
+ if (existsSync(resolve(current, '.git')))
1212
+ return current;
1212
1213
  const parent = dirname(current);
1213
1214
  if (parent === current)
1214
- return gitRoot;
1215
+ return null;
1215
1216
  current = parent;
1216
1217
  }
1217
1218
  }