@ghl-ai/aw 0.1.69 → 0.1.70-beta.1
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/c4/claudePluginRegistry.mjs +3 -3
- package/c4/commandSurface.mjs +122 -224
- package/c4/cursorRulesShim.mjs +19 -41
- package/c4/diagnostics.mjs +23 -42
- package/c4/index.mjs +1 -2
- package/c4/preflight.mjs +12 -18
- package/c4/templates/scripts/aw-c4-bootstrap.sh +14 -14
- package/codex.mjs +13 -4
- package/commands/c4.mjs +31 -34
- package/commands/doctor.mjs +24 -61
- package/commands/init.mjs +12 -3
- package/commands/pull.mjs +2 -1
- package/constants.mjs +3 -0
- package/ecc.mjs +41 -74
- package/git.mjs +12 -9
- package/hooks/codex-home.mjs +3 -3
- package/link.mjs +13 -11
- package/package.json +1 -1
- package/startup.mjs +7 -22
- package/c4/eccRegistryBridge.mjs +0 -236
package/c4/diagnostics.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* diagnoseAwRouterView({ awHome })
|
|
11
11
|
* Codex-relevant: replays the upstream `find * /skills/* /SKILL.md`
|
|
12
|
-
* enumeration logic against ~/.aw/.aw_registry/
|
|
12
|
+
* enumeration logic against ~/.aw/.aw_registry/aw/skills/
|
|
13
13
|
* and reports which expected stage skills are visible to the
|
|
14
14
|
* SessionStart hook. Catches Bug A failures (registry path empty
|
|
15
15
|
* post-init).
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Tests both that the wrapper is registered AND that it actually
|
|
23
23
|
* emits the router (catches "wrapper installed but does nothing").
|
|
24
24
|
*
|
|
25
|
-
* diagnoseSkillResolution({ harness, home, awHome,
|
|
25
|
+
* diagnoseSkillResolution({ harness, home, awHome, skills })
|
|
26
26
|
* Multi-path × multi-skill probe. For each skill, checks four
|
|
27
27
|
* candidate paths in priority order. Top-level ok=true ONLY if
|
|
28
28
|
* using-aw-skills AND every stage skill the slim card promises
|
|
@@ -146,8 +146,8 @@ function slashShimSummaryToken(action) {
|
|
|
146
146
|
* ───────────────────────────────────────────────────────────────────────── */
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
* Replay of the
|
|
150
|
-
* find <awHome>/.aw_registry/
|
|
149
|
+
* Replay of the AW registry skill scan:
|
|
150
|
+
* find <awHome>/.aw_registry/aw/skills/<skill>/SKILL.md
|
|
151
151
|
*
|
|
152
152
|
* @param {object} opts
|
|
153
153
|
* @param {string} opts.awHome
|
|
@@ -156,8 +156,8 @@ function slashShimSummaryToken(action) {
|
|
|
156
156
|
export function diagnoseAwRouterView({ awHome } = {}) {
|
|
157
157
|
if (!awHome) throw new Error('diagnoseAwRouterView: awHome is required');
|
|
158
158
|
|
|
159
|
-
const
|
|
160
|
-
const enumerableSkills =
|
|
159
|
+
const awSkillsRoot = join(awHome, '.aw_registry/aw/skills');
|
|
160
|
+
const enumerableSkills = enumerateAwSkills(awSkillsRoot);
|
|
161
161
|
const expected = [...EXPECTED_STAGE_SKILLS];
|
|
162
162
|
const missing = expected.filter((s) => !enumerableSkills.includes(s));
|
|
163
163
|
const ok = missing.length === 0;
|
|
@@ -166,38 +166,23 @@ export function diagnoseAwRouterView({ awHome } = {}) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
* Walk <root>/<
|
|
170
|
-
* upstream find pattern. Returns the deduped sorted list of skill
|
|
171
|
-
* directory names.
|
|
169
|
+
* Walk <root>/<skill>/SKILL.md and return the deduped sorted skill names.
|
|
172
170
|
*/
|
|
173
|
-
function
|
|
174
|
-
if (!existsSync(
|
|
171
|
+
function enumerateAwSkills(awSkillsRoot) {
|
|
172
|
+
if (!existsSync(awSkillsRoot)) return [];
|
|
175
173
|
const set = new Set();
|
|
176
|
-
let
|
|
174
|
+
let skills;
|
|
177
175
|
try {
|
|
178
|
-
|
|
176
|
+
skills = readdirSync(awSkillsRoot, { withFileTypes: true });
|
|
179
177
|
} catch {
|
|
180
|
-
// readdir on the
|
|
178
|
+
// readdir on the AW skills root failed (EACCES, ENOENT). Treat as
|
|
181
179
|
// "no skills enumerable" — the caller's missing-list will surface it.
|
|
182
180
|
return [];
|
|
183
181
|
}
|
|
184
|
-
for (const
|
|
185
|
-
if (!
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
let skills;
|
|
189
|
-
try {
|
|
190
|
-
skills = readdirSync(skillsDir, { withFileTypes: true });
|
|
191
|
-
} catch {
|
|
192
|
-
// readdir on a team's skills/ subdirectory failed — skip this team
|
|
193
|
-
// dir but continue probing siblings.
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
for (const skill of skills) {
|
|
197
|
-
if (!skill.isDirectory()) continue;
|
|
198
|
-
const skillMd = join(skillsDir, skill.name, 'SKILL.md');
|
|
199
|
-
if (existsSync(skillMd)) set.add(skill.name);
|
|
200
|
-
}
|
|
182
|
+
for (const skill of skills) {
|
|
183
|
+
if (!skill.isDirectory()) continue;
|
|
184
|
+
const skillMd = join(awSkillsRoot, skill.name, 'SKILL.md');
|
|
185
|
+
if (existsSync(skillMd)) set.add(skill.name);
|
|
201
186
|
}
|
|
202
187
|
return [...set].sort();
|
|
203
188
|
}
|
|
@@ -337,7 +322,6 @@ function envelopeContainsRouter(output) {
|
|
|
337
322
|
* @param {Harness} opts.harness
|
|
338
323
|
* @param {string} opts.home
|
|
339
324
|
* @param {string} opts.awHome
|
|
340
|
-
* @param {string} opts.eccHome
|
|
341
325
|
* @param {string[]} [opts.skills] Default: DEFAULT_SKILL_LIST
|
|
342
326
|
* @returns {{
|
|
343
327
|
* perSkill: SkillResolutionResult[],
|
|
@@ -350,12 +334,11 @@ export function diagnoseSkillResolution(opts = {}) {
|
|
|
350
334
|
if (!opts.harness) throw new Error('diagnoseSkillResolution: harness is required');
|
|
351
335
|
if (!opts.home) throw new Error('diagnoseSkillResolution: home is required');
|
|
352
336
|
if (!opts.awHome) throw new Error('diagnoseSkillResolution: awHome is required');
|
|
353
|
-
if (!opts.eccHome) throw new Error('diagnoseSkillResolution: eccHome is required');
|
|
354
337
|
|
|
355
|
-
const { harness, home, awHome
|
|
338
|
+
const { harness, home, awHome } = opts;
|
|
356
339
|
const skills = opts.skills ?? [...DEFAULT_SKILL_LIST];
|
|
357
340
|
|
|
358
|
-
const perSkill = skills.map((skill) => probeOneSkill(skill, harness, home, awHome
|
|
341
|
+
const perSkill = skills.map((skill) => probeOneSkill(skill, harness, home, awHome));
|
|
359
342
|
const routerSkill = perSkill.find((e) => e.skill === ROUTER_SKILL) ?? {
|
|
360
343
|
skill: ROUTER_SKILL,
|
|
361
344
|
candidatePathsChecked: [],
|
|
@@ -374,11 +357,11 @@ function harnessSkillsDir(harness, home) {
|
|
|
374
357
|
if (harness === 'claude-web') return join(home, '.claude/skills');
|
|
375
358
|
if (harness === 'cursor-cloud') return join(home, '.cursor/skills');
|
|
376
359
|
// codex-web: registry path is the canonical source; the per-harness skills
|
|
377
|
-
// dir is conventionally absent. We still probe the registry
|
|
360
|
+
// dir is conventionally absent. We still probe the AW registry path.
|
|
378
361
|
return null;
|
|
379
362
|
}
|
|
380
363
|
|
|
381
|
-
function probeOneSkill(skill, harness, home, awHome
|
|
364
|
+
function probeOneSkill(skill, harness, home, awHome) {
|
|
382
365
|
const harnessDir = harnessSkillsDir(harness, home);
|
|
383
366
|
const candidatePathsChecked = [];
|
|
384
367
|
|
|
@@ -386,8 +369,7 @@ function probeOneSkill(skill, harness, home, awHome, eccHome) {
|
|
|
386
369
|
candidatePathsChecked.push(join(harnessDir, skill, 'SKILL.md'));
|
|
387
370
|
candidatePathsChecked.push(join(harnessDir, `platform-core-${skill}`, 'SKILL.md'));
|
|
388
371
|
}
|
|
389
|
-
candidatePathsChecked.push(join(awHome, '.aw_registry/
|
|
390
|
-
candidatePathsChecked.push(join(eccHome, 'skills', skill, 'SKILL.md'));
|
|
372
|
+
candidatePathsChecked.push(join(awHome, '.aw_registry/aw/skills', skill, 'SKILL.md'));
|
|
391
373
|
|
|
392
374
|
let found = null;
|
|
393
375
|
for (const p of candidatePathsChecked) {
|
|
@@ -544,7 +526,7 @@ function enumerateConfigKeysOnly(filePath) {
|
|
|
544
526
|
}
|
|
545
527
|
|
|
546
528
|
function sampleRegistrySkillPaths(awHome, max) {
|
|
547
|
-
const root = join(awHome, '.aw_registry/
|
|
529
|
+
const root = join(awHome, '.aw_registry/aw');
|
|
548
530
|
if (!existsSync(root)) return [' <registry not provisioned>'];
|
|
549
531
|
const out = [];
|
|
550
532
|
walkSkillMd(root, out, max);
|
|
@@ -569,8 +551,7 @@ function walkSkillMd(dir, out, max) {
|
|
|
569
551
|
} else if (e.isDirectory()) {
|
|
570
552
|
walkSkillMd(p, out, max);
|
|
571
553
|
} else if (e.isSymbolicLink()) {
|
|
572
|
-
// Cheap stat check — symlinks pointing at SKILL.md
|
|
573
|
-
// should still appear in the dump.
|
|
554
|
+
// Cheap stat check — symlinks pointing at SKILL.md should still appear.
|
|
574
555
|
try {
|
|
575
556
|
const st = statSync(p);
|
|
576
557
|
if (st.isFile() && e.name === 'SKILL.md') out.push(p);
|
package/c4/index.mjs
CHANGED
|
@@ -29,7 +29,6 @@ export {
|
|
|
29
29
|
verifyAuth,
|
|
30
30
|
preflightPlatformDocs,
|
|
31
31
|
} from './gitAuth.mjs';
|
|
32
|
-
export { applyEccRegistryBridge } from './eccRegistryBridge.mjs';
|
|
33
32
|
export {
|
|
34
33
|
SLIM_CARD_HARD_CEILING_BYTES,
|
|
35
34
|
REQUIRED_ENFORCEMENT_PHRASES,
|
|
@@ -49,7 +48,7 @@ export {
|
|
|
49
48
|
export { MCP_URL_DEFAULT, registerGhlAiMcp } from './mcpServer.mjs';
|
|
50
49
|
export { probeMcpServer } from './mcpSmokeProbe.mjs';
|
|
51
50
|
export { ensureClaudeMarketplace } from './claudePluginRegistry.mjs';
|
|
52
|
-
export { ensureCommandSurface,
|
|
51
|
+
export { ensureCommandSurface, diagnoseCommandResolution } from './commandSurface.mjs';
|
|
53
52
|
export { installCursorSlashShim, CURSOR_SLASH_SHIM_RULE } from './cursorRulesShim.mjs';
|
|
54
53
|
export { ensureRepoLocalClaudeSettings } from './repoLocalClaudeSettings.mjs';
|
|
55
54
|
export { copyRepoRootInstructions } from './repoRootInstructions.mjs';
|
package/c4/preflight.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* c4/preflight.mjs — preflight aggregator for `aw c4`.
|
|
3
3
|
*
|
|
4
4
|
* Composes harness, token resolution, REST-API auth verification, gh CLI
|
|
5
|
-
* presence,
|
|
5
|
+
* presence, AW registry-skill probe, and disk-free reporting into a single
|
|
6
6
|
* PreflightResult shape with per-failure recommendation strings. Pure
|
|
7
7
|
* orchestration over already-tested c4 primitives — no new I/O patterns.
|
|
8
8
|
*
|
|
@@ -71,10 +71,10 @@ const REC = Object.freeze({
|
|
|
71
71
|
`Insufficient disk: ${mb}MB free (aw c4 needs at least ${DISK_FREE_THRESHOLD_MB}MB ` +
|
|
72
72
|
'for ECC clone + registry pull).',
|
|
73
73
|
bridgeWillInstall:
|
|
74
|
-
'
|
|
75
|
-
'
|
|
74
|
+
'AW registry skills will be installed during this run from platform-docs ' +
|
|
75
|
+
'(Codex uses ~/.aw/.aw_registry/aw/skills for stage routing).',
|
|
76
76
|
bridgeFailed:
|
|
77
|
-
'
|
|
77
|
+
'AW registry skills are not yet provisioned — aw init will create them ' +
|
|
78
78
|
'on the first c4 run; re-run preflight afterwards.',
|
|
79
79
|
});
|
|
80
80
|
|
|
@@ -120,22 +120,18 @@ function classifyAuthError(verifyResult) {
|
|
|
120
120
|
return 'unknown';
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
function probeBridgeStatus(harness, awHome
|
|
123
|
+
function probeBridgeStatus(harness, awHome) {
|
|
124
124
|
if (harness !== 'codex-web') return 'ok';
|
|
125
125
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
const bridgedSkillPath = join(
|
|
126
|
+
// AW skills are now owned by platform-docs under the top-level aw namespace.
|
|
127
|
+
// If aw-plan is already present in the registry path, routing is ready.
|
|
128
|
+
const awSkillPath = join(
|
|
130
129
|
awHome,
|
|
131
|
-
'.aw_registry/
|
|
130
|
+
'.aw_registry/aw/skills/aw-plan/SKILL.md',
|
|
132
131
|
);
|
|
133
|
-
if (existsSync(
|
|
132
|
+
if (existsSync(awSkillPath)) return 'ok';
|
|
134
133
|
|
|
135
|
-
|
|
136
|
-
// (The aw-plan skill source must exist somewhere — the bridge follows
|
|
137
|
-
// applyEccRegistryBridge's contract; preflight only certifies feasibility.)
|
|
138
|
-
if (existsSync(eccHome) && existsSync(awHome)) return 'will-install';
|
|
134
|
+
if (existsSync(awHome)) return 'will-install';
|
|
139
135
|
|
|
140
136
|
return 'failed';
|
|
141
137
|
}
|
|
@@ -180,7 +176,6 @@ function buildRecommendations({
|
|
|
180
176
|
* @param {typeof globalThis.fetch} [opts.fetchImpl]
|
|
181
177
|
* @param {string} [opts.home] Defaults to env.HOME.
|
|
182
178
|
* @param {string} [opts.awHome] Defaults to <home>/.aw.
|
|
183
|
-
* @param {string} [opts.eccHome] Defaults to <home>/.aw-ecc.
|
|
184
179
|
* @param {number} [opts.diskFreeMb] Inject for tests; production defaults to a real statfs probe.
|
|
185
180
|
* @returns {Promise<PreflightResult>}
|
|
186
181
|
*/
|
|
@@ -200,7 +195,6 @@ export async function runPreflight(opts = {}) {
|
|
|
200
195
|
// when env is the injected `{}`, opts.home should have been passed instead.
|
|
201
196
|
const home = opts.home ?? env.HOME ?? '~';
|
|
202
197
|
const awHome = opts.awHome ?? join(home, '.aw');
|
|
203
|
-
const eccHome = opts.eccHome ?? join(home, '.aw-ecc');
|
|
204
198
|
|
|
205
199
|
// 1. Token resolution.
|
|
206
200
|
const { token, source: tokenSource } = getGithubToken(env);
|
|
@@ -221,7 +215,7 @@ export async function runPreflight(opts = {}) {
|
|
|
221
215
|
const ghCliPresent = runShell(shell, 'command -v gh').ok;
|
|
222
216
|
|
|
223
217
|
// 4. Bridge status (codex-web only; other harnesses always 'ok').
|
|
224
|
-
const bridgeStatus = probeBridgeStatus(harness, awHome
|
|
218
|
+
const bridgeStatus = probeBridgeStatus(harness, awHome);
|
|
225
219
|
|
|
226
220
|
// 5. Disk free MB. For tests, callers inject the value directly; in
|
|
227
221
|
// production the orchestrator passes a measured number. Defaulting to
|
|
@@ -129,30 +129,30 @@ aw_c4_exit=$?
|
|
|
129
129
|
|
|
130
130
|
# Defense-in-depth: `aw c4` delegates registry sync to `aw init --silent`,
|
|
131
131
|
# which can fail to fetch in silent mode without surfacing an error. When
|
|
132
|
-
# that happens, the registry (~/.aw/.aw_registry) may be empty or incomplete
|
|
133
|
-
#
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
local
|
|
137
|
-
if [ ! -d "$
|
|
132
|
+
# that happens, the registry (~/.aw/.aw_registry) may be empty or incomplete.
|
|
133
|
+
# Re-run `aw pull` post-c4 if the AW skill count looks low.
|
|
134
|
+
verify_registry_skills() {
|
|
135
|
+
local aw_registry_dir="$HOME/.aw/.aw_registry"
|
|
136
|
+
local aw_skills_dir="$aw_registry_dir/aw/skills"
|
|
137
|
+
if [ ! -d "$aw_registry_dir" ]; then
|
|
138
138
|
echo "[aw-c4-bootstrap] registry dir missing — running aw init + pull"
|
|
139
139
|
aw init --no-integrations --silent 2>&1 | tail -3 || true
|
|
140
140
|
aw pull 2>&1 | tail -5 || true
|
|
141
141
|
return
|
|
142
142
|
fi
|
|
143
143
|
|
|
144
|
-
local
|
|
145
|
-
|
|
146
|
-
if [ "${
|
|
147
|
-
echo "[aw-c4-bootstrap] only ${
|
|
144
|
+
local skill_count
|
|
145
|
+
skill_count=$( (find "$aw_skills_dir" -name 'SKILL.md' 2>/dev/null || true) | wc -l | tr -d ' ' )
|
|
146
|
+
if [ "${skill_count:-0}" -lt 5 ]; then
|
|
147
|
+
echo "[aw-c4-bootstrap] only ${skill_count} AW registry skills found — running aw pull"
|
|
148
148
|
aw pull 2>&1 | tail -5 || true
|
|
149
149
|
local new_count
|
|
150
|
-
new_count=$(find "$
|
|
151
|
-
echo "[aw-c4-bootstrap] registry
|
|
150
|
+
new_count=$( (find "$aw_skills_dir" -name 'SKILL.md' 2>/dev/null || true) | wc -l | tr -d ' ' )
|
|
151
|
+
echo "[aw-c4-bootstrap] AW registry skills: ${skill_count} -> ${new_count}"
|
|
152
152
|
else
|
|
153
|
-
echo "[aw-c4-bootstrap] registry: ${
|
|
153
|
+
echo "[aw-c4-bootstrap] registry: ${skill_count} AW skills OK"
|
|
154
154
|
fi
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
verify_registry_skills || true
|
|
157
157
|
|
|
158
158
|
exit "$aw_c4_exit"
|
package/codex.mjs
CHANGED
|
@@ -88,10 +88,19 @@ set -euo pipefail
|
|
|
88
88
|
|
|
89
89
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
90
90
|
ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
|
|
91
|
-
TARGET_SCRIPT="
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
TARGET_SCRIPT=""
|
|
92
|
+
for candidate in \
|
|
93
|
+
"\$ROOT_DIR/.aw/.aw_registry/aw/skills/using-aw-skills/hooks/session-start.sh" \
|
|
94
|
+
"\$ROOT_DIR/.aw_registry/aw/skills/using-aw-skills/hooks/session-start.sh"
|
|
95
|
+
do
|
|
96
|
+
if [[ -f "\$candidate" ]]; then
|
|
97
|
+
TARGET_SCRIPT="\$candidate"
|
|
98
|
+
break
|
|
99
|
+
fi
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
if [[ -z "\$TARGET_SCRIPT" ]]; then
|
|
103
|
+
echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "WARNING: AW session-start hook not found at .aw_registry/aw/skills/using-aw-skills/hooks/session-start.sh. Run aw pull platform or aw init."}}'
|
|
95
104
|
exit 0
|
|
96
105
|
fi
|
|
97
106
|
|
package/commands/c4.mjs
CHANGED
|
@@ -101,11 +101,13 @@ function safeReaddirSync(dir) {
|
|
|
101
101
|
try { return readdirSync(dir); } catch { return []; }
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function safeListNamespaceDirs(dir) {
|
|
104
|
+
function safeListNamespaceDirs(dir, fsApi = {}) {
|
|
105
|
+
const readDir = fsApi.readdirSync ?? readdirSync;
|
|
106
|
+
const stat = fsApi.statSync ?? statSync;
|
|
105
107
|
try {
|
|
106
|
-
return
|
|
108
|
+
return readDir(dir).filter((entry) => {
|
|
107
109
|
if (entry.startsWith('.')) return false;
|
|
108
|
-
try { return
|
|
110
|
+
try { return stat(join(dir, entry)).isDirectory(); } catch { return false; }
|
|
109
111
|
});
|
|
110
112
|
} catch { return []; }
|
|
111
113
|
}
|
|
@@ -132,17 +134,15 @@ function setupGitAuth({ token, c4, home, cwd }) {
|
|
|
132
134
|
c4.ensureOriginRemote({ cwd });
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
function resolveCodexRouterSkillPath({ awHome
|
|
136
|
-
|
|
137
|
-
if (existsSync(eccRouterSkillPath)) return eccRouterSkillPath;
|
|
138
|
-
return join(awHome, '.aw_registry/platform/core/skills/using-aw-skills/SKILL.md');
|
|
137
|
+
function resolveCodexRouterSkillPath({ awHome }) {
|
|
138
|
+
return join(awHome, '.aw_registry/aw/skills/using-aw-skills/SKILL.md');
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
142
142
|
* Per-harness branch (step 10).
|
|
143
143
|
*
|
|
144
144
|
* Returns `{ ok, bridge, injector }` so the summary line can pick up
|
|
145
|
-
* codex-specific status ticks. Hard failures (
|
|
145
|
+
* codex-specific status ticks. Hard failures (injector / claude
|
|
146
146
|
* marketplace / slim card) bubble up as thrown Errors.
|
|
147
147
|
* ───────────────────────────────────────────────────────────────────────── */
|
|
148
148
|
|
|
@@ -165,9 +165,8 @@ function runHarnessBranch({ harness, c4, home, eccHome, cwd, writer, existsSync
|
|
|
165
165
|
}
|
|
166
166
|
if (harness === 'codex-web') {
|
|
167
167
|
const awHome = join(home, '.aw');
|
|
168
|
-
c4.applyEccRegistryBridge({ awHome, eccHome });
|
|
169
168
|
c4.ensureCodexHooksFlag(home);
|
|
170
|
-
const routerSkillPath = resolveCodexRouterSkillPath({ awHome
|
|
169
|
+
const routerSkillPath = resolveCodexRouterSkillPath({ awHome });
|
|
171
170
|
// Pass hookPath explicitly: defaultHookPath() reads os.homedir() at module
|
|
172
171
|
// load time, which ignores the orchestrator's `home` (env.HOME). Without
|
|
173
172
|
// this, installs land in the real ~/.codex even when HOME is overridden
|
|
@@ -193,7 +192,7 @@ function runSelfTests({ harness, c4, home, awHome, eccHome }) {
|
|
|
193
192
|
if (!skill.ok) failures.push('skill-resolution');
|
|
194
193
|
|
|
195
194
|
// diagnoseCommandResolution is warn-only — does NOT add to failures.
|
|
196
|
-
const command = c4.diagnoseCommandResolution({ harness, home });
|
|
195
|
+
const command = c4.diagnoseCommandResolution({ harness, home, awHome });
|
|
197
196
|
|
|
198
197
|
let view;
|
|
199
198
|
let injector;
|
|
@@ -411,12 +410,27 @@ export async function c4Command(rawArgs, overrides = {}) {
|
|
|
411
410
|
// check, so we specifically look for directories (namespaces like
|
|
412
411
|
// "platform") to confirm the registry content was actually pulled.
|
|
413
412
|
{
|
|
414
|
-
const registryNamespaces = safeListNamespaceDirs(awRegistry);
|
|
413
|
+
const registryNamespaces = safeListNamespaceDirs(awRegistry, fs);
|
|
415
414
|
if (registryNamespaces.length === 0) {
|
|
416
415
|
writer.stderr('[aw-c4] registry has no namespace directories after init — retrying with aw pull\n');
|
|
417
416
|
const pullRes = spawnSync('aw', ['pull'], { stdio: 'pipe' });
|
|
418
|
-
if (pullRes?.status !== 0 || safeListNamespaceDirs(awRegistry).length === 0) {
|
|
419
|
-
writer.stderr('[aw-c4] FATAL: registry
|
|
417
|
+
if (pullRes?.status !== 0 || safeListNamespaceDirs(awRegistry, fs).length === 0) {
|
|
418
|
+
writer.stderr('[aw-c4] FATAL: registry skills were not fetched\n');
|
|
419
|
+
return exit(1);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Step 9b — verify active AW skills exist. `/aw:*` adapters point directly
|
|
425
|
+
// at ~/.aw/.aw_registry/aw/skills/<skill>/SKILL.md, so an empty registry is
|
|
426
|
+
// not enough; the AW namespace must be present.
|
|
427
|
+
{
|
|
428
|
+
const awSkillsDir = join(awRegistry, 'aw', 'skills');
|
|
429
|
+
if (!fs.existsSync(awSkillsDir)) {
|
|
430
|
+
writer.stderr('[aw-c4] AW skills missing after init — retrying with aw pull\n');
|
|
431
|
+
const pullRes = spawnSync('aw', ['pull'], { stdio: 'pipe' });
|
|
432
|
+
if (pullRes?.status !== 0 || !fs.existsSync(awSkillsDir)) {
|
|
433
|
+
writer.stderr('[aw-c4] FATAL: AW skills were not fetched\n');
|
|
420
434
|
return exit(1);
|
|
421
435
|
}
|
|
422
436
|
}
|
|
@@ -439,26 +453,9 @@ export async function c4Command(rawArgs, overrides = {}) {
|
|
|
439
453
|
writer.stdout('[aw-c4] MCP disabled; skipping registerGhlAiMcp\n');
|
|
440
454
|
}
|
|
441
455
|
|
|
442
|
-
// Step 12 — slash
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
// Step 12a — full registry command surface.
|
|
446
|
-
// `ensureCommandSurface` only links the 10 AW routing-stage commands from
|
|
447
|
-
// ~/.aw-ecc/commands/. The full registry (~100+ domain commands like
|
|
448
|
-
// platform-review-security-hardening) lives in ~/.aw/.aw_registry/ and is
|
|
449
|
-
// populated by `aw init` (step 7). Link them all into the harness command
|
|
450
|
-
// dir so `/aw:*` resolution works for every registered command.
|
|
451
|
-
const registryCmdResult = safe(
|
|
452
|
-
'ensureRegistryCommandSurface',
|
|
453
|
-
() => c4.ensureRegistryCommandSurface({ harness, home, awRegistryDir: awRegistry }),
|
|
454
|
-
writer,
|
|
455
|
-
);
|
|
456
|
-
if (registryCmdResult.ok) {
|
|
457
|
-
const { linked, skipped } = registryCmdResult.value;
|
|
458
|
-
if (linked > 0) {
|
|
459
|
-
writer.stdout(`[aw-c4] registry commands: ${linked} linked, ${skipped} skipped\n`);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
456
|
+
// Step 12 — slash surface backed by active AW skills. No ECC command files
|
|
457
|
+
// are linked; each adapter points directly at a registry SKILL.md.
|
|
458
|
+
safe('ensureCommandSurface', () => c4.ensureCommandSurface({ harness, home, awHome }), writer);
|
|
462
459
|
|
|
463
460
|
// Step 12b — Cursor Cloud slash-expand rule (no-op on other harnesses).
|
|
464
461
|
// The model-side workaround for Cursor Cloud's chat UI not pre-expanding
|
package/commands/doctor.mjs
CHANGED
|
@@ -523,22 +523,14 @@ function findBrokenRuleReferences(filePaths) {
|
|
|
523
523
|
return broken;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
.
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
.map(route => `${route}.md`)
|
|
535
|
-
.filter(fileName => !existsSync(join(commandsDir, fileName)));
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function missingCoreClaudeCommandFiles(pluginRoot) {
|
|
539
|
-
return EXPECTED_AW_ROUTES
|
|
540
|
-
.map(route => `commands/${route}.md`)
|
|
541
|
-
.filter(relativePath => !existsSync(join(pluginRoot, relativePath)));
|
|
526
|
+
function missingCoreAwSkillFiles(awRegistryDir) {
|
|
527
|
+
const expectedSkills = [
|
|
528
|
+
'using-aw-skills',
|
|
529
|
+
...EXPECTED_AW_ROUTES.map(route => `aw-${route}`),
|
|
530
|
+
];
|
|
531
|
+
return expectedSkills
|
|
532
|
+
.map(skillName => `aw/skills/${skillName}/SKILL.md`)
|
|
533
|
+
.filter(relativePath => !awRegistryDir || !existsSync(join(awRegistryDir, relativePath)));
|
|
542
534
|
}
|
|
543
535
|
|
|
544
536
|
function buildDoctorChecks(homeDir, cwd) {
|
|
@@ -548,8 +540,8 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
548
540
|
const rulesDir = resolveRulesDir(homeDir);
|
|
549
541
|
|
|
550
542
|
const usingAwHookPath = [
|
|
551
|
-
join(homeDir, '.aw_registry', '
|
|
552
|
-
join(homeDir, '.aw', '.aw_registry', '
|
|
543
|
+
join(homeDir, '.aw_registry', 'aw', 'skills', 'using-aw-skills', 'hooks', 'session-start.sh'),
|
|
544
|
+
join(homeDir, '.aw', '.aw_registry', 'aw', 'skills', 'using-aw-skills', 'hooks', 'session-start.sh'),
|
|
553
545
|
].find(existsSync);
|
|
554
546
|
|
|
555
547
|
checks.push(usingAwHookPath
|
|
@@ -586,6 +578,19 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
586
578
|
);
|
|
587
579
|
}
|
|
588
580
|
|
|
581
|
+
const missingAwSkills = missingCoreAwSkillFiles(awRegistryDir);
|
|
582
|
+
checks.push(
|
|
583
|
+
missingAwSkills.length === 0
|
|
584
|
+
? makeCheck('aw-skills-source', 'AW skills source', 'pass', 'AW core route skills are synced under ~/.aw/.aw_registry/aw/skills')
|
|
585
|
+
: makeCheck(
|
|
586
|
+
'aw-skills-source',
|
|
587
|
+
'AW skills source',
|
|
588
|
+
'fail',
|
|
589
|
+
`AW registry is missing core skill files: ${missingAwSkills.join(', ')}`,
|
|
590
|
+
'Run `aw init` or `aw pull platform` to sync AW skills into ~/.aw/.aw_registry/aw/skills.',
|
|
591
|
+
),
|
|
592
|
+
);
|
|
593
|
+
|
|
589
594
|
if (cwd !== homeDir) {
|
|
590
595
|
const projectAgentsPath = join(cwd, 'AGENTS.md');
|
|
591
596
|
const projectClaudePath = join(cwd, 'CLAUDE.md');
|
|
@@ -646,12 +651,10 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
646
651
|
const missingBundleFiles = missingFiles(claudePluginRoot, [
|
|
647
652
|
'.claude-plugin/plugin.json',
|
|
648
653
|
'hooks/hooks.json',
|
|
649
|
-
'skills/using-aw-skills/SKILL.md',
|
|
650
|
-
'skills/using-aw-skills/hooks/session-start.sh',
|
|
651
654
|
]);
|
|
652
655
|
checks.push(
|
|
653
656
|
missingBundleFiles.length === 0 && claudeSessionStartStatus.ok
|
|
654
|
-
? makeCheck('claude-plugin-bundle', 'Claude plugin bundle', 'pass', 'Claude plugin bundle contains AW routing hooks
|
|
657
|
+
? makeCheck('claude-plugin-bundle', 'Claude plugin bundle', 'pass', 'Claude plugin bundle contains AW routing hooks')
|
|
655
658
|
: makeCheck(
|
|
656
659
|
'claude-plugin-bundle',
|
|
657
660
|
'Claude plugin bundle',
|
|
@@ -667,21 +670,8 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
667
670
|
),
|
|
668
671
|
);
|
|
669
672
|
|
|
670
|
-
const missingPluginCommands = missingCoreClaudeCommandFiles(claudePluginRoot);
|
|
671
|
-
checks.push(
|
|
672
|
-
missingPluginCommands.length === 0
|
|
673
|
-
? makeCheck('claude-plugin-commands', 'Claude public commands', 'pass', 'Claude plugin bundle exposes the current AW command surface (primary, conditional, and compatibility routes)')
|
|
674
|
-
: makeCheck(
|
|
675
|
-
'claude-plugin-commands',
|
|
676
|
-
'Claude public commands',
|
|
677
|
-
'fail',
|
|
678
|
-
`Claude plugin bundle is missing core command files: ${missingPluginCommands.join(', ')}`,
|
|
679
|
-
'Refresh the AW Claude plugin so the plugin bundle includes the full AW public command surface.',
|
|
680
|
-
),
|
|
681
|
-
);
|
|
682
673
|
} else {
|
|
683
674
|
checks.push(makeCheck('claude-plugin-bundle', 'Claude plugin bundle', 'warn', 'Claude plugin bundle is not installed', 'Enable/install the AW Claude plugin, then rerun `aw doctor`.'));
|
|
684
|
-
checks.push(makeCheck('claude-plugin-commands', 'Claude public commands', 'warn', 'Claude plugin command bundle could not be inspected because the plugin is not installed'));
|
|
685
675
|
}
|
|
686
676
|
|
|
687
677
|
const claudeLegacyHooks = parseLegacyClaudeHookTargets(claudeSettings?.hooks?.SessionStart || []);
|
|
@@ -867,19 +857,6 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
867
857
|
: makeCheck('codex-references', 'Codex shared references', 'fail', 'Codex shared references are missing', projectRelinkFix(homeDir, cwd, '~/.codex/references')),
|
|
868
858
|
);
|
|
869
859
|
|
|
870
|
-
const missingPrompts = missingCorePromptFiles(join(homeDir, '.codex', 'prompts'));
|
|
871
|
-
checks.push(
|
|
872
|
-
missingPrompts.length === 0
|
|
873
|
-
? makeCheck('codex-prompts', 'Codex prompts', 'pass', 'Codex prompt sync produced the current AW prompt surface (primary, conditional, and compatibility routes)')
|
|
874
|
-
: makeCheck(
|
|
875
|
-
'codex-prompts',
|
|
876
|
-
'Codex prompts',
|
|
877
|
-
'fail',
|
|
878
|
-
`Codex is missing core prompt files: ${missingPrompts.join(', ')}`,
|
|
879
|
-
'Run `aw init` or refresh the AW ECC bundle to regenerate the Codex prompts.',
|
|
880
|
-
),
|
|
881
|
-
);
|
|
882
|
-
|
|
883
860
|
const codexAgentsPath = join(homeDir, '.codex', 'AGENTS.md');
|
|
884
861
|
checks.push(
|
|
885
862
|
existsSync(codexAgentsPath) && textHasManagedRouterBridge(readText(codexAgentsPath)) && textHasRulesReference(readText(codexAgentsPath))
|
|
@@ -944,20 +921,6 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
944
921
|
: makeCheck('cursor-install-state', 'Cursor install state', 'fail', 'Cursor install-state file is missing', globalInstallStateFix(homeDir, cwd, 'Cursor install state')),
|
|
945
922
|
);
|
|
946
923
|
|
|
947
|
-
const cursorCommandsDir = join(homeDir, '.cursor', 'commands', 'aw');
|
|
948
|
-
const missingCursorCommands = missingCoreCursorCommandFiles(cursorCommandsDir);
|
|
949
|
-
checks.push(
|
|
950
|
-
missingCursorCommands.length === 0
|
|
951
|
-
? makeCheck('cursor-commands', 'Cursor public commands', 'pass', 'Cursor has the current AW command surface under ~/.cursor/commands/aw/')
|
|
952
|
-
: makeCheck(
|
|
953
|
-
'cursor-commands',
|
|
954
|
-
'Cursor public commands',
|
|
955
|
-
'fail',
|
|
956
|
-
`Cursor is missing core command files: ${missingCursorCommands.join(', ')}`,
|
|
957
|
-
projectRelinkFix(homeDir, cwd, 'AW command files under ~/.cursor/commands/aw/'),
|
|
958
|
-
),
|
|
959
|
-
);
|
|
960
|
-
|
|
961
924
|
const cursorMcp = jsonMcpHealth(join(homeDir, '.cursor', 'mcp.json'));
|
|
962
925
|
checks.push(
|
|
963
926
|
cursorMcp.present && cursorMcp.url && cursorMcp.authorization
|
package/commands/init.mjs
CHANGED
|
@@ -49,7 +49,16 @@ import {
|
|
|
49
49
|
syncWorktreeSparseCheckout,
|
|
50
50
|
findNearestWorktree,
|
|
51
51
|
} from '../git.mjs';
|
|
52
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
REGISTRY_DIR,
|
|
54
|
+
REGISTRY_REPO,
|
|
55
|
+
REGISTRY_URL,
|
|
56
|
+
AW_REGISTRY_NAMESPACE_DIR,
|
|
57
|
+
DOCS_SOURCE_DIR,
|
|
58
|
+
AW_DOCS_DIR,
|
|
59
|
+
RULES_SOURCE_DIR,
|
|
60
|
+
RULES_RUNTIME_DIR,
|
|
61
|
+
} from '../constants.mjs';
|
|
53
62
|
import { syncFileTree } from '../file-tree.mjs';
|
|
54
63
|
import { installAwUsageHooks, formatAwUsageHooksInstallReport } from '../install-aw-usage-hooks.mjs';
|
|
55
64
|
|
|
@@ -390,7 +399,7 @@ export async function initCommand(args) {
|
|
|
390
399
|
const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
|
|
391
400
|
if (isNewSubTeam) {
|
|
392
401
|
if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
|
|
393
|
-
const newSparsePaths = [`.aw_registry/${folderName}`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR];
|
|
402
|
+
const newSparsePaths = [AW_REGISTRY_NAMESPACE_DIR, `.aw_registry/${folderName}`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR];
|
|
394
403
|
addToSparseCheckout(AW_HOME, newSparsePaths);
|
|
395
404
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
396
405
|
scaffoldNamespace(AW_HOME, folderName);
|
|
@@ -537,7 +546,7 @@ export async function initCommand(args) {
|
|
|
537
546
|
}
|
|
538
547
|
|
|
539
548
|
// Determine sparse paths
|
|
540
|
-
const sparsePaths = [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
|
|
549
|
+
const sparsePaths = [AW_REGISTRY_NAMESPACE_DIR, `.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
|
|
541
550
|
if (folderName) {
|
|
542
551
|
sparsePaths.push(`.aw_registry/${folderName}`);
|
|
543
552
|
}
|
package/commands/pull.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
REGISTRY_DIR,
|
|
28
28
|
REGISTRY_URL,
|
|
29
|
+
AW_REGISTRY_NAMESPACE_DIR,
|
|
29
30
|
DOCS_SOURCE_DIR,
|
|
30
31
|
AW_DOCS_DIR,
|
|
31
32
|
RULES_SOURCE_DIR,
|
|
@@ -93,7 +94,7 @@ export async function pullCommand(args) {
|
|
|
93
94
|
// Ensure platform pulls also fetch docs and rules on older installs that
|
|
94
95
|
// pre-date the new sparse-checkout paths.
|
|
95
96
|
if (input === 'platform') {
|
|
96
|
-
addToSparseCheckout(AW_HOME, [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR]);
|
|
97
|
+
addToSparseCheckout(AW_HOME, [AW_REGISTRY_NAMESPACE_DIR, `.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR]);
|
|
97
98
|
if (!cfg.include.includes('platform')) {
|
|
98
99
|
config.addPattern(GLOBAL_AW_DIR, 'platform');
|
|
99
100
|
}
|
package/constants.mjs
CHANGED
|
@@ -19,6 +19,9 @@ export const REGISTRY_URL = process.env.AW_REGISTRY_URL
|
|
|
19
19
|
/** Directory inside the registry repo that holds platform/ and [template]/ */
|
|
20
20
|
export const REGISTRY_DIR = '.aw_registry';
|
|
21
21
|
|
|
22
|
+
/** Top-level AW namespace in the registry. Holds active AW-owned skills. */
|
|
23
|
+
export const AW_REGISTRY_NAMESPACE_DIR = `${REGISTRY_DIR}/aw`;
|
|
24
|
+
|
|
22
25
|
/** Directory in platform-docs repo containing documentation (pulled into platform/docs/) */
|
|
23
26
|
export const DOCS_SOURCE_DIR = 'content';
|
|
24
27
|
|