@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.
@@ -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/platform/core/skills/
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, eccHome, skills })
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 upstream session-start.sh enumerator scan:
150
- * find <awHome>/.aw_registry/platform -path STAR/skills/STAR/SKILL.md
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 platformRoot = join(awHome, '.aw_registry/platform');
160
- const enumerableSkills = enumerateRegistrySkills(platformRoot);
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>/<team>/skills/<skill>/SKILL.md two levels deep, mirroring the
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 enumerateRegistrySkills(platformRoot) {
174
- if (!existsSync(platformRoot)) return [];
171
+ function enumerateAwSkills(awSkillsRoot) {
172
+ if (!existsSync(awSkillsRoot)) return [];
175
173
  const set = new Set();
176
- let teams;
174
+ let skills;
177
175
  try {
178
- teams = readdirSync(platformRoot, { withFileTypes: true });
176
+ skills = readdirSync(awSkillsRoot, { withFileTypes: true });
179
177
  } catch {
180
- // readdir on the platform root failed (EACCES, ENOENT). Treat as
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 team of teams) {
185
- if (!team.isDirectory()) continue;
186
- const skillsDir = join(platformRoot, team.name, 'skills');
187
- if (!existsSync(skillsDir)) continue;
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, eccHome } = opts;
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, eccHome));
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 + ECC paths.
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, eccHome) {
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/platform/core/skills', skill, 'SKILL.md'));
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/platform');
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 (the bridge case)
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, ensureRegistryCommandSurface, diagnoseCommandResolution } from './commandSurface.mjs';
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, ECC bridge-status probe, and disk-free reporting into a single
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
- 'ECC registry bridge will be installed during this run (Codex only ' +
75
- 'Bug A workaround that symlinks ECC stage skills into ~/.aw/.aw_registry).',
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
- 'ECC and/or AW registry not yet provisioned — aw init will create them ' +
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, eccHome) {
123
+ function probeBridgeStatus(harness, awHome) {
124
124
  if (harness !== 'codex-web') return 'ok';
125
125
 
126
- // Bridge installs ~/.aw/.aw_registry/platform/core/skills/<aw-*>/SKILL.md
127
- // symlinks pointing at ~/.aw-ecc/skills/<aw-*>/SKILL.md. If aw-plan is
128
- // already present in the registry path, the bridge is a no-op (ok).
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/platform/core/skills/aw-plan/SKILL.md',
130
+ '.aw_registry/aw/skills/aw-plan/SKILL.md',
132
131
  );
133
- if (existsSync(bridgedSkillPath)) return 'ok';
132
+ if (existsSync(awSkillPath)) return 'ok';
134
133
 
135
- // Bridge can install when both ECC home and the registry skeleton exist.
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, eccHome);
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
- # and only the 10 hardcoded stage commands get linked. Re-run `aw pull`
134
- # post-c4 if the command count looks low.
135
- verify_registry_commands() {
136
- local aw_cmd_dir="$HOME/.aw/.aw_registry"
137
- if [ ! -d "$aw_cmd_dir" ]; then
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 cmd_count
145
- cmd_count=$(find "$aw_cmd_dir" -name '*.md' -path '*/commands/*' ! -path '*/evals/*' 2>/dev/null | wc -l | tr -d ' ')
146
- if [ "${cmd_count:-0}" -lt 20 ]; then
147
- echo "[aw-c4-bootstrap] only ${cmd_count} registry commands found — running aw pull"
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 "$aw_cmd_dir" -name '*.md' -path '*/commands/*' ! -path '*/evals/*' 2>/dev/null | wc -l | tr -d ' ')
151
- echo "[aw-c4-bootstrap] registry commands: ${cmd_count} ${new_count}"
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: ${cmd_count} commands OK"
153
+ echo "[aw-c4-bootstrap] registry: ${skill_count} AW skills OK"
154
154
  fi
155
155
  }
156
- verify_registry_commands || true
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="\$ROOT_DIR/.aw_registry/platform/core/skills/using-aw-skills/hooks/session-start.sh"
92
-
93
- if [[ ! -f "\$TARGET_SCRIPT" ]]; then
94
- echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "WARNING: AW session-start hook not found at .aw_registry/platform/core/skills/using-aw-skills/hooks/session-start.sh. Run aw pull platform or aw init."}}'
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 readdirSync(dir).filter((entry) => {
108
+ return readDir(dir).filter((entry) => {
107
109
  if (entry.startsWith('.')) return false;
108
- try { return statSync(join(dir, entry)).isDirectory(); } catch { return false; }
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, eccHome, existsSync = fsExistsSync }) {
136
- const eccRouterSkillPath = join(eccHome, 'skills/using-aw-skills/SKILL.md');
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 (bridge / injector / claude
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, eccHome, existsSync });
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 commands were not fetched\n');
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 command surface (10 stage commands from ECC).
443
- safe('ensureCommandSurface', () => c4.ensureCommandSurface({ harness, home, eccHome }), writer);
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
@@ -523,22 +523,14 @@ function findBrokenRuleReferences(filePaths) {
523
523
  return broken;
524
524
  }
525
525
 
526
- function missingCorePromptFiles(promptsDir) {
527
- return EXPECTED_AW_ROUTES
528
- .map(route => `aw-${route}.md`)
529
- .filter(fileName => !existsSync(join(promptsDir, fileName)));
530
- }
531
-
532
- function missingCoreCursorCommandFiles(commandsDir) {
533
- return EXPECTED_AW_ROUTES
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', 'platform', 'core', 'skills', 'using-aw-skills', 'hooks', 'session-start.sh'),
552
- join(homeDir, '.aw', '.aw_registry', 'platform', 'core', 'skills', 'using-aw-skills', 'hooks', 'session-start.sh'),
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 and router skill files')
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 { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
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