@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.
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/cli.js +1 -1
- package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -0
- package/dist/src/artifacts/index-builder.js +4 -64
- package/dist/src/artifacts/index-files.js +80 -0
- package/dist/src/artifacts/stats.js +22 -50
- package/dist/src/artifacts/types.js +73 -1
- package/dist/src/cli/agent-spawn.js +4 -2
- package/dist/src/cli/handlers/cockpit.js +41 -0
- package/dist/src/cli/handlers/help.js +1 -1
- package/dist/src/cli/handlers/ralph.js +2 -1
- package/dist/src/cli/handlers/sdlc-accelerate.js +2 -1
- package/dist/src/cli/handlers/use.js +117 -12
- package/dist/src/cli/handlers/workspace.js +24 -2
- package/dist/src/cli/services/deployment-verification.js +9 -2
- package/dist/src/cockpit/doctor.js +257 -0
- package/dist/src/config/aiwg-config.js +36 -3
- package/dist/src/providers/transformation-receipt-integration.js +130 -3
- package/dist/src/security/artifact-verifier.js +7 -1
- package/dist/src/serve/pty-bridge.js +6 -11
- package/dist/src/skills/run.js +15 -6
- package/package.json +2 -1
|
@@ -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 (
|
|
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
|
-
|
|
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
|
-
|
|
803
|
-
|
|
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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
|
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
|
|
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: ${
|
|
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
|
|
31
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|