@aiwg/cli 2026.8.15 → 2026.8.17

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.
@@ -454,6 +454,7 @@ function recordForEntry(cwd, entry, graphName, dependencyGraph, privacy, schemaV
454
454
  ...(entry.searchTerms?.length
455
455
  ? { aiwg_search_terms: uniqueSorted(entry.searchTerms) }
456
456
  : {}),
457
+ ...(entry.script ? { aiwg_script: entry.script } : {}),
457
458
  },
458
459
  },
459
460
  }
@@ -494,7 +494,7 @@ async function handleBuild(args) {
494
494
  console.log('');
495
495
  console.log('Default behavior (no --graph): builds all graphs with defaultBuild: true');
496
496
  console.log('Multi-graph builds run by buildOrder/buildTier (refs → citations → bibliography before heavy graphs)');
497
- console.log(' Built-in defaults: project (always), codebase (skipped if src/test/tools absent)');
497
+ console.log(' Built-in defaults: project (always), codebase (auto-detects JavaScript/TypeScript and Python layouts)');
498
498
  console.log('');
499
499
  console.log('Examples:');
500
500
  console.log(' aiwg index build');
@@ -210,6 +210,7 @@ function artifactTypeFromRecord(record) {
210
210
  function entryFromRecord(record) {
211
211
  const indexedFrontmatter = record.search?.frontmatter ?? {};
212
212
  const indexedSearchTerms = indexedFrontmatter.aiwg_search_terms;
213
+ const indexedScript = indexedFrontmatter.aiwg_script;
213
214
  return {
214
215
  path: record.source.path,
215
216
  type: artifactTypeFromRecord(record),
@@ -245,6 +246,11 @@ function entryFromRecord(record) {
245
246
  kernel: typeof record.search?.frontmatter?.kernel === "boolean"
246
247
  ? record.search.frontmatter.kernel
247
248
  : undefined,
249
+ script: indexedScript && typeof indexedScript === "object"
250
+ && typeof indexedScript.entrypoint === "string"
251
+ && typeof indexedScript.runtime === "string"
252
+ ? indexedScript
253
+ : undefined,
248
254
  operationalState: record.operational_state,
249
255
  };
250
256
  }
@@ -17,30 +17,10 @@ import { DEFAULT_INDEX_EXTENSIONS, INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX
17
17
  import { parseCitationSidecar, citationResultToEdges, buildRefToPathMap } from './citation-parser.js';
18
18
  import { writeIndexFile, resolveIndexDir, loadGraphIndexFile } from './index-reader.js';
19
19
  import { loadManifest, writeManifest, statMatches, makeEntry } from './checksum-manifest.js';
20
- import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
21
20
  import { normalizeOperationalState } from './operational-state.js';
22
21
  import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
23
22
  import { normalizeStateTransferProjection } from './state-transfer.js';
24
- function pathContains(parent, child) {
25
- const relative = path.relative(parent, child);
26
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
27
- }
28
- function toPosixPath(value) {
29
- return value.split(path.sep).join('/');
30
- }
31
- function indexPathFor(cwd, fullPath, graph) {
32
- if (!graph || graph === 'project') {
33
- const artifactRoot = resolveProjectAiwgDir(cwd);
34
- if (pathContains(artifactRoot, fullPath)) {
35
- const relative = toPosixPath(path.relative(artifactRoot, fullPath));
36
- return relative ? `${DEFAULT_PROJECT_AIWG_DIR}/${relative}` : DEFAULT_PROJECT_AIWG_DIR;
37
- }
38
- }
39
- const rel = path.relative(cwd, fullPath);
40
- if (!rel.startsWith('..') && !path.isAbsolute(rel))
41
- return toPosixPath(rel);
42
- return fullPath;
43
- }
23
+ import { collectGraphIndexFiles, findArtifactFiles, indexPathFor } from './index-files.js';
44
24
  function absoluteEntryPath(cwd, entryPath, graph) {
45
25
  if ((!graph || graph === 'project') && entryPath.startsWith(`${DEFAULT_PROJECT_AIWG_DIR}/`)) {
46
26
  return path.join(resolveProjectAiwgDir(cwd), entryPath.slice(DEFAULT_PROJECT_AIWG_DIR.length + 1));
@@ -587,31 +567,6 @@ function applyMetadataSupplements(entries, supplements, cwd) {
587
567
  }
588
568
  }
589
569
  }
590
- /**
591
- * Recursively find all indexable files under a directory
592
- */
593
- function findArtifactFiles(dir, extensions = [...DEFAULT_INDEX_EXTENSIONS]) {
594
- const results = [];
595
- if (!fs.existsSync(dir))
596
- return results;
597
- const entries = fs.readdirSync(dir, { withFileTypes: true });
598
- for (const entry of entries) {
599
- const fullPath = path.join(dir, entry.name);
600
- if (entry.isSymbolicLink() && !fs.existsSync(fullPath)) {
601
- continue;
602
- }
603
- if (entry.isDirectory()) {
604
- // Skip hidden dirs and .index
605
- if (entry.name.startsWith('.'))
606
- continue;
607
- results.push(...findArtifactFiles(fullPath, extensions));
608
- }
609
- else if (extensions.some(ext => entry.name.endsWith(ext))) {
610
- results.push(fullPath);
611
- }
612
- }
613
- return results;
614
- }
615
570
  /**
616
571
  * Build the artifact index
617
572
  */
@@ -798,24 +753,9 @@ export async function buildIndex(cwd, options = {}) {
798
753
  pruned: 0,
799
754
  };
800
755
  // Collect files from all scan directories
801
- const files = [];
802
- for (const dir of existingDirs) {
803
- files.push(...findArtifactFiles(dir, fileExtensions));
804
- }
805
- // WORKSPACE.md is the root of the project context graph. Index it and its
806
- // local Markdown-linked nodes without copying them into provider trees.
807
- if (!scope && (!graph || graph === 'project')) {
808
- const workspacePath = path.join(cwd, 'WORKSPACE.md');
809
- const contextFiles = [
810
- ...(fs.existsSync(workspacePath) ? [workspacePath] : []),
811
- ...await workspaceLinkedFiles(cwd),
812
- ];
813
- for (const contextFile of contextFiles) {
814
- if (fileExtensions.some((extension) => contextFile.endsWith(extension)) && !files.includes(contextFile)) {
815
- files.push(contextFile);
816
- }
817
- }
818
- }
756
+ const files = scope
757
+ ? existingDirs.flatMap(dir => findArtifactFiles(dir, fileExtensions))
758
+ : await collectGraphIndexFiles(cwd, graph);
819
759
  const entries = {};
820
760
  const tagIndex = {};
821
761
  const depGraph = {};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared artifact source-file enumeration.
3
+ *
4
+ * Index builds and coverage reporting must use the same file set so project
5
+ * context files cannot inflate the indexed count beyond the reported total.
6
+ *
7
+ * @implements jmagly/aiwg#146
8
+ */
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { DEFAULT_INDEX_EXTENSIONS, GRAPH_CONFIGS, resolveGraphScanDir, } from './types.js';
12
+ import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
13
+ import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
14
+ function pathContains(parent, child) {
15
+ const relative = path.relative(parent, child);
16
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
17
+ }
18
+ function toPosixPath(value) {
19
+ return value.split(path.sep).join('/');
20
+ }
21
+ export function indexPathFor(cwd, fullPath, graph) {
22
+ if (!graph || graph === 'project') {
23
+ const artifactRoot = resolveProjectAiwgDir(cwd);
24
+ if (pathContains(artifactRoot, fullPath)) {
25
+ const relative = toPosixPath(path.relative(artifactRoot, fullPath));
26
+ return relative ? `${DEFAULT_PROJECT_AIWG_DIR}/${relative}` : DEFAULT_PROJECT_AIWG_DIR;
27
+ }
28
+ }
29
+ const relative = path.relative(cwd, fullPath);
30
+ if (!relative.startsWith('..') && !path.isAbsolute(relative))
31
+ return toPosixPath(relative);
32
+ return fullPath;
33
+ }
34
+ /** Recursively find indexable files, excluding hidden directories such as .index. */
35
+ export function findArtifactFiles(dir, extensions = DEFAULT_INDEX_EXTENSIONS) {
36
+ const results = [];
37
+ if (!fs.existsSync(dir))
38
+ return results;
39
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
40
+ for (const entry of entries) {
41
+ const fullPath = path.join(dir, entry.name);
42
+ if (entry.isSymbolicLink() && !fs.existsSync(fullPath))
43
+ continue;
44
+ if (entry.isDirectory()) {
45
+ if (entry.name.startsWith('.'))
46
+ continue;
47
+ results.push(...findArtifactFiles(fullPath, extensions));
48
+ }
49
+ else if (extensions.some(extension => entry.name.endsWith(extension))) {
50
+ results.push(fullPath);
51
+ }
52
+ }
53
+ return results;
54
+ }
55
+ /** Return the exact current source-file set used by a standard graph build. */
56
+ export async function collectGraphIndexFiles(cwd, graph) {
57
+ const config = graph ? GRAPH_CONFIGS[graph] : undefined;
58
+ const scanDirs = config
59
+ ? config.scanDirs.map(directory => resolveGraphScanDir(cwd, directory))
60
+ : [resolveProjectAiwgDir(cwd)];
61
+ const extensions = config?.extensions ?? [...DEFAULT_INDEX_EXTENSIONS];
62
+ const files = new Set();
63
+ for (const scanDir of scanDirs) {
64
+ for (const file of findArtifactFiles(scanDir, extensions))
65
+ files.add(file);
66
+ }
67
+ if (!graph || graph === 'project') {
68
+ const workspacePath = path.join(cwd, 'WORKSPACE.md');
69
+ const contextFiles = [
70
+ ...(fs.existsSync(workspacePath) ? [workspacePath] : []),
71
+ ...await workspaceLinkedFiles(cwd),
72
+ ];
73
+ for (const contextFile of contextFiles) {
74
+ if (extensions.some(extension => contextFile.endsWith(extension)))
75
+ files.add(contextFile);
76
+ }
77
+ }
78
+ return [...files];
79
+ }
80
+ //# sourceMappingURL=index-files.js.map
@@ -7,40 +7,23 @@
7
7
  * @source @src/artifacts/types.ts
8
8
  * @tests @test/unit/artifacts/stats.test.ts
9
9
  */
10
- import fs from 'fs';
11
- import path from 'path';
12
- import { GRAPH_CONFIGS, loadUserGraphConfigs, resolveGraphScanDir } from './types.js';
10
+ import { GRAPH_CONFIGS, loadUserGraphConfigs } from './types.js';
13
11
  import { loadIndexStats, loadGraphIndexFile } from './index-reader.js';
14
- /**
15
- * Count total indexable files under scan directories (excluding .index/)
16
- */
17
- function countArtifactFiles(cwd, graphType) {
18
- const config = graphType ? GRAPH_CONFIGS[graphType] : undefined;
19
- const scanDirs = config
20
- ? config.scanDirs.map(d => resolveGraphScanDir(cwd, d))
21
- : [resolveGraphScanDir(cwd, '.aiwg')];
22
- const extensions = config?.extensions ?? ['.md', '.yaml', '.json'];
23
- let count = 0;
24
- function walk(dir) {
25
- if (!fs.existsSync(dir))
26
- return;
27
- const entries = fs.readdirSync(dir, { withFileTypes: true });
28
- for (const entry of entries) {
29
- const full = path.join(dir, entry.name);
30
- if (entry.isDirectory()) {
31
- if (entry.name.startsWith('.'))
32
- continue; // Skip .index, etc.
33
- walk(full);
34
- }
35
- else if (extensions.some(ext => entry.name.endsWith(ext))) {
36
- count++;
37
- }
38
- }
39
- }
40
- for (const dir of scanDirs) {
41
- walk(dir);
42
- }
43
- return count;
12
+ import { collectGraphIndexFiles, indexPathFor } from './index-files.js';
13
+ /** Calculate coverage over the same current file set used by the index builder. */
14
+ async function calculateCoverage(cwd, stats, graphType) {
15
+ const sourcePaths = new Set((await collectGraphIndexFiles(cwd, graphType))
16
+ .map(file => indexPathFor(cwd, file, graphType)));
17
+ const index = loadGraphIndexFile(cwd, 'metadata.json', graphType);
18
+ const indexed = index
19
+ ? Object.keys(index.entries).filter(entryPath => sourcePaths.has(entryPath)).length
20
+ : Math.min(stats.totalArtifacts, sourcePaths.size);
21
+ const totalFiles = sourcePaths.size;
22
+ return {
23
+ indexed,
24
+ totalFiles,
25
+ percentage: totalFiles > 0 ? Math.round((indexed / totalFiles) * 100) : 100,
26
+ };
44
27
  }
45
28
  /**
46
29
  * Show artifact index statistics
@@ -84,14 +67,10 @@ export async function showStats(cwd, options = {}) {
84
67
  // JSON mode: aggregate all graphs into one response
85
68
  const combined = {};
86
69
  for (const { type, stats: s } of availableGraphs) {
87
- const totalFiles = countArtifactFiles(cwd, type);
70
+ const coverage = await calculateCoverage(cwd, s, type);
88
71
  combined[type] = {
89
72
  ...s,
90
- coverage: {
91
- indexed: s.totalArtifacts,
92
- totalFiles,
93
- percentage: totalFiles > 0 ? Math.round((s.totalArtifacts / totalFiles) * 100) : 100,
94
- },
73
+ coverage,
95
74
  };
96
75
  }
97
76
  console.log(JSON.stringify(combined, null, 2));
@@ -108,14 +87,10 @@ export async function showStats(cwd, options = {}) {
108
87
  */
109
88
  async function renderStats(cwd, stats, options, graphType) {
110
89
  if (options.json) {
111
- const totalFiles = countArtifactFiles(cwd, graphType);
90
+ const coverage = await calculateCoverage(cwd, stats, graphType);
112
91
  console.log(JSON.stringify({
113
92
  ...stats,
114
- coverage: {
115
- indexed: stats.totalArtifacts,
116
- totalFiles,
117
- percentage: totalFiles > 0 ? Math.round((stats.totalArtifacts / totalFiles) * 100) : 100,
118
- },
93
+ coverage,
119
94
  }, null, 2));
120
95
  return;
121
96
  }
@@ -167,11 +142,8 @@ async function renderStats(cwd, stats, options, graphType) {
167
142
  }
168
143
  console.log('');
169
144
  // Coverage
170
- const totalFiles = countArtifactFiles(cwd, graphType);
171
- const coverage = totalFiles > 0
172
- ? Math.round((stats.totalArtifacts / totalFiles) * 100)
173
- : 100;
145
+ const coverage = await calculateCoverage(cwd, stats, graphType);
174
146
  console.log('Index Health:');
175
- console.log(` Coverage: ${stats.totalArtifacts}/${totalFiles} artifacts indexed (${coverage}%)`);
147
+ console.log(` Coverage: ${coverage.indexed}/${coverage.totalFiles} artifacts indexed (${coverage.percentage}%)`);
176
148
  }
177
149
  //# sourceMappingURL=stats.js.map
@@ -195,6 +195,60 @@ export const BUILTIN_GRAPH_CONFIGS = {
195
195
  * @implements #426
196
196
  */
197
197
  export const GRAPH_CONFIGS = { ...BUILTIN_GRAPH_CONFIGS };
198
+ function freshBuiltinGraphConfig(name) {
199
+ const config = BUILTIN_GRAPH_CONFIGS[name];
200
+ return {
201
+ ...config,
202
+ scanDirs: [...config.scanDirs],
203
+ extensions: [...config.extensions],
204
+ };
205
+ }
206
+ /**
207
+ * Detect conventional Python layouts without treating every top-level folder
208
+ * as source. A Python project manifest activates `.py`/`.pyi` support; package
209
+ * roots are immediate directories containing `__init__.py`, plus the common
210
+ * `tests/` and `scripts/` roots when present.
211
+ */
212
+ function detectPythonCodebaseConfig(cwd, base) {
213
+ const hasPythonManifest = ['pyproject.toml', 'setup.py', 'setup.cfg']
214
+ .some((manifest) => fs.existsSync(path.join(cwd, manifest)));
215
+ if (!hasPythonManifest)
216
+ return base;
217
+ const detectedRoots = [];
218
+ for (const root of ['tests', 'scripts']) {
219
+ if (fs.existsSync(path.join(cwd, root)))
220
+ detectedRoots.push(root);
221
+ }
222
+ const excluded = new Set([
223
+ '.aiwg', '.git', '.github', '.venv', 'venv', 'node_modules',
224
+ 'src', 'test', 'tests', 'tools', 'scripts', 'docs', 'documentation',
225
+ ]);
226
+ try {
227
+ for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
228
+ if (!entry.isDirectory() || excluded.has(entry.name) || entry.name.startsWith('.'))
229
+ continue;
230
+ if (fs.existsSync(path.join(cwd, entry.name, '__init__.py')))
231
+ detectedRoots.push(entry.name);
232
+ }
233
+ }
234
+ catch {
235
+ // Layout detection is best-effort; the immutable defaults still apply.
236
+ }
237
+ return {
238
+ ...base,
239
+ scanDirs: [...new Set([...base.scanDirs, ...detectedRoots])],
240
+ extensions: [...new Set([...base.extensions, '.py', '.pyi'])],
241
+ };
242
+ }
243
+ function applyBuiltinGraphOverride(base, override) {
244
+ if (!override)
245
+ return base;
246
+ return {
247
+ ...base,
248
+ scanDirs: override.scanDirs ? [...override.scanDirs] : base.scanDirs,
249
+ extensions: override.extensions ? [...override.extensions] : base.extensions,
250
+ };
251
+ }
198
252
  /**
199
253
  * Normalize metadataSupplements entries.
200
254
  *
@@ -400,6 +454,11 @@ export function loadModuleGraphConfigs(cwd, diagnostics) {
400
454
  * @implements #426 #726
401
455
  */
402
456
  export function loadUserGraphConfigs(cwd, diagnostics) {
457
+ // Built-ins are immutable in index.graphs, but codebase roots/extensions may
458
+ // be adapted through the explicitly bounded graphOverrides contract (#2123).
459
+ // Reset on every project load so a prior cwd cannot leak its override or
460
+ // detected Python package roots into a later build in the same process.
461
+ GRAPH_CONFIGS.codebase = detectPythonCodebaseConfig(cwd, freshBuiltinGraphConfig('codebase'));
403
462
  // Load module-declared graphs first (frameworks/addons)
404
463
  const moduleLoaded = loadModuleGraphConfigs(cwd, diagnostics);
405
464
  const loaded = [...moduleLoaded];
@@ -408,6 +467,7 @@ export function loadUserGraphConfigs(cwd, diagnostics) {
408
467
  // config.yaml is a
409
468
  // deprecated fallback so un-migrated corpora keep working.
410
469
  let graphs;
470
+ let graphOverrides;
411
471
  let fromDeprecatedYaml = false;
412
472
  // (a) Canonical: .aiwg/aiwg.config (JSON).
413
473
  try {
@@ -418,6 +478,9 @@ export function loadUserGraphConfigs(cwd, diagnostics) {
418
478
  const g = idx?.graphs;
419
479
  if (g && typeof g === 'object')
420
480
  graphs = g;
481
+ const overrides = idx?.graphOverrides;
482
+ if (overrides && typeof overrides === 'object')
483
+ graphOverrides = overrides;
421
484
  }
422
485
  }
423
486
  catch {
@@ -430,7 +493,7 @@ export function loadUserGraphConfigs(cwd, diagnostics) {
430
493
  });
431
494
  }
432
495
  // (b) Fallback: legacy .aiwg/config.yaml.
433
- if (!graphs) {
496
+ if (!graphs && !graphOverrides) {
434
497
  try {
435
498
  const configPath = projectAiwgPath(cwd, 'config.yaml');
436
499
  if (fs.existsSync(configPath)) {
@@ -441,12 +504,21 @@ export function loadUserGraphConfigs(cwd, diagnostics) {
441
504
  graphs = g;
442
505
  fromDeprecatedYaml = true;
443
506
  }
507
+ const overrides = idx?.graphOverrides;
508
+ if (overrides && typeof overrides === 'object') {
509
+ graphOverrides = overrides;
510
+ fromDeprecatedYaml = true;
511
+ }
444
512
  }
445
513
  }
446
514
  catch {
447
515
  // best-effort
448
516
  }
449
517
  }
518
+ const codebaseOverride = graphOverrides?.codebase;
519
+ if (codebaseOverride && typeof codebaseOverride === 'object' && !Array.isArray(codebaseOverride)) {
520
+ GRAPH_CONFIGS.codebase = applyBuiltinGraphOverride(GRAPH_CONFIGS.codebase, codebaseOverride);
521
+ }
450
522
  if (!graphs)
451
523
  return loaded;
452
524
  if (fromDeprecatedYaml && !yamlIndexDeprecationWarned) {
@@ -27,8 +27,10 @@ export const PROVIDER_CONFIGS = {
27
27
  },
28
28
  codex: {
29
29
  binary: 'codex',
30
- // Codex supports --full-auto (no approval prompts) and --approval-mode full-auto
31
- dangerousFlag: '--full-auto',
30
+ // Current Codex releases use the explicit bypass flag for unrestricted,
31
+ // non-interactive execution. Keep this centralized so every launcher maps
32
+ // AIWG's --dangerous option consistently.
33
+ dangerousFlag: '--dangerously-bypass-approvals-and-sandbox',
32
34
  name: 'OpenAI Codex',
33
35
  },
34
36
  hermes: {
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { mkdir, readFile } from 'node:fs/promises';
4
4
  import { homedir } from 'node:os';
5
5
  import path from 'node:path';
6
+ import { formatCockpitDoctor, runCockpitDoctor, } from '../../cockpit/doctor.js';
6
7
  export const COCKPIT_PACKAGE_NAME = '@aiwg/cockpit';
7
8
  export function cockpitHome() {
8
9
  return process.env.AIWG_COCKPIT_HOME || path.join(homedir(), '.aiwg', 'cockpit', 'package');
@@ -22,6 +23,16 @@ async function coreVersion(frameworkRoot) {
22
23
  function packageRoot(home = cockpitHome()) {
23
24
  return path.join(home, 'node_modules', '@aiwg', 'cockpit');
24
25
  }
26
+ function valueAfter(args, flag) {
27
+ const index = args.indexOf(flag);
28
+ return index >= 0 ? args[index + 1] : undefined;
29
+ }
30
+ function doctorFormat(args) {
31
+ if (args.includes('--json'))
32
+ return 'json';
33
+ const value = valueAfter(args, '--format');
34
+ return value === 'json' || value === 'markdown' ? value : 'text';
35
+ }
25
36
  export async function resolveCockpitInstall(home = cockpitHome()) {
26
37
  const root = packageRoot(home);
27
38
  const pkg = await readJson(path.join(root, 'package.json'));
@@ -95,6 +106,36 @@ export const cockpitHandler = {
95
106
  const install = await resolveCockpitInstall();
96
107
  const version = await coreVersion(ctx.frameworkRoot);
97
108
  const autoInstall = ctx.args.includes('--install') || ctx.args.includes('--yes') || ctx.args.includes('-y');
109
+ if (ctx.args[0] === 'doctor' || ctx.args.includes('--doctor')) {
110
+ const sourcePackageRoot = path.join(ctx.frameworkRoot, 'apps', 'cockpit');
111
+ const sourcePackage = !install.installed ? await readJson(path.join(sourcePackageRoot, 'package.json')) : null;
112
+ const doctorInstall = install.installed ? install : {
113
+ installed: Boolean(sourcePackage),
114
+ version: typeof sourcePackage?.version === 'string' ? sourcePackage.version : undefined,
115
+ packageRoot: sourcePackageRoot,
116
+ };
117
+ const topologyValue = valueAfter(ctx.args, '--topology');
118
+ const topology = topologyValue === 'ssh-local' || topologyValue === 'ssh-reverse'
119
+ ? topologyValue
120
+ : 'same-host';
121
+ const report = await runCockpitDoctor({
122
+ coreVersion: version,
123
+ cockpitInstalled: doctorInstall.installed,
124
+ cockpitVersion: doctorInstall.version,
125
+ cockpitPackageRoot: doctorInstall.packageRoot,
126
+ topology,
127
+ cockpitHost: valueAfter(ctx.args, '--cockpit-host'),
128
+ executorHost: valueAfter(ctx.args, '--executor-host'),
129
+ expectedExecutorVersion: valueAfter(ctx.args, '--executor-version'),
130
+ forwardEndpoint: valueAfter(ctx.args, '--forward-endpoint'),
131
+ runtimeFile: valueAfter(ctx.args, '--runtime-file'),
132
+ });
133
+ return {
134
+ exitCode: report.status === 'blocked' ? 1 : 0,
135
+ message: formatCockpitDoctor(report, doctorFormat(ctx.args)),
136
+ rawOutput: true,
137
+ };
138
+ }
98
139
  if (ctx.args.includes('--status')) {
99
140
  return {
100
141
  exitCode: 0,
@@ -91,7 +91,7 @@ function displayHelp() {
91
91
  ]);
92
92
  helpGroup('FEATURES', [
93
93
  ['features', 'Show optional feature install status'],
94
- ['cockpit [--status]', 'Launch the opt-in AIWG Cockpit control plane'],
94
+ ['cockpit [--status|doctor]', 'Launch Cockpit or diagnose its executor topology'],
95
95
  ]);
96
96
  helpGroup('VALIDATION', [
97
97
  ['validate-metadata [path]', 'Validate AIWG component metadata (defaults to agentic/code)'],
@@ -278,7 +278,8 @@ LFD LOOP CONTROLS (hard cumulative ceilings; loop stops with a best-output repor
278
278
  Spawnable: claude, opencode, codex, hermes
279
279
  --dangerous Enable unrestricted mode for the selected provider.
280
280
  Passes the provider's native flag (e.g. --dangerously-skip-permissions
281
- for claude/opencode, --full-auto for codex). No effect if the
281
+ for claude/opencode,
282
+ --dangerously-bypass-approvals-and-sandbox for codex). No effect if the
282
283
  provider doesn't have a dangerous mode flag.
283
284
  --params "<args>" Pass arbitrary args verbatim to the agent binary.
284
285
  Appended after all other flags. Quoted segments preserved.
@@ -123,7 +123,8 @@ AGENT OPTIONS:
123
123
  IDE-integrated (guidance only): copilot, cursor, factory, warp, windsurf
124
124
  --dangerous Enable unrestricted mode (skips permission prompts).
125
125
  Maps to the provider's native flag — e.g. claude gets
126
- --dangerously-skip-permissions, codex gets --full-auto.
126
+ --dangerously-skip-permissions, codex gets
127
+ --dangerously-bypass-approvals-and-sandbox.
127
128
  Has no effect on providers that don't support it.
128
129
  --params "<args>" Pass arbitrary args directly to the agent binary.
129
130
  Appended verbatim after all other flags. You are