@ghl-ai/aw 0.1.70-beta.2 → 0.1.70-beta.4
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 +225 -150
- package/c4/cursorRulesShim.mjs +41 -19
- package/c4/diagnostics.mjs +42 -23
- package/c4/eccRegistryBridge.mjs +236 -0
- package/c4/index.mjs +2 -1
- package/c4/preflight.mjs +18 -12
- package/c4/templates/scripts/aw-c4-bootstrap.sh +14 -14
- package/codex.mjs +4 -13
- package/commands/c4.mjs +34 -31
- package/commands/doctor.mjs +61 -24
- package/commands/init.mjs +45 -23
- package/commands/pull.mjs +1 -2
- package/constants.mjs +0 -3
- package/ecc.mjs +74 -41
- package/git.mjs +9 -12
- package/hooks/codex-home.mjs +3 -3
- package/integrate.mjs +61 -5
- package/integrations.mjs +74 -3
- package/link.mjs +28 -47
- package/package.json +1 -1
- package/startup.mjs +22 -7
package/commands/c4.mjs
CHANGED
|
@@ -101,13 +101,11 @@ function safeReaddirSync(dir) {
|
|
|
101
101
|
try { return readdirSync(dir); } catch { return []; }
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function safeListNamespaceDirs(dir
|
|
105
|
-
const readDir = fsApi.readdirSync ?? readdirSync;
|
|
106
|
-
const stat = fsApi.statSync ?? statSync;
|
|
104
|
+
function safeListNamespaceDirs(dir) {
|
|
107
105
|
try {
|
|
108
|
-
return
|
|
106
|
+
return readdirSync(dir).filter((entry) => {
|
|
109
107
|
if (entry.startsWith('.')) return false;
|
|
110
|
-
try { return
|
|
108
|
+
try { return statSync(join(dir, entry)).isDirectory(); } catch { return false; }
|
|
111
109
|
});
|
|
112
110
|
} catch { return []; }
|
|
113
111
|
}
|
|
@@ -134,15 +132,17 @@ function setupGitAuth({ token, c4, home, cwd }) {
|
|
|
134
132
|
c4.ensureOriginRemote({ cwd });
|
|
135
133
|
}
|
|
136
134
|
|
|
137
|
-
function resolveCodexRouterSkillPath({ awHome }) {
|
|
138
|
-
|
|
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');
|
|
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 (injector / claude
|
|
145
|
+
* codex-specific status ticks. Hard failures (bridge / injector / claude
|
|
146
146
|
* marketplace / slim card) bubble up as thrown Errors.
|
|
147
147
|
* ───────────────────────────────────────────────────────────────────────── */
|
|
148
148
|
|
|
@@ -165,8 +165,9 @@ 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 });
|
|
168
169
|
c4.ensureCodexHooksFlag(home);
|
|
169
|
-
const routerSkillPath = resolveCodexRouterSkillPath({ awHome });
|
|
170
|
+
const routerSkillPath = resolveCodexRouterSkillPath({ awHome, eccHome, existsSync });
|
|
170
171
|
// Pass hookPath explicitly: defaultHookPath() reads os.homedir() at module
|
|
171
172
|
// load time, which ignores the orchestrator's `home` (env.HOME). Without
|
|
172
173
|
// this, installs land in the real ~/.codex even when HOME is overridden
|
|
@@ -192,7 +193,7 @@ function runSelfTests({ harness, c4, home, awHome, eccHome }) {
|
|
|
192
193
|
if (!skill.ok) failures.push('skill-resolution');
|
|
193
194
|
|
|
194
195
|
// diagnoseCommandResolution is warn-only — does NOT add to failures.
|
|
195
|
-
const command = c4.diagnoseCommandResolution({ harness, home
|
|
196
|
+
const command = c4.diagnoseCommandResolution({ harness, home });
|
|
196
197
|
|
|
197
198
|
let view;
|
|
198
199
|
let injector;
|
|
@@ -410,27 +411,12 @@ export async function c4Command(rawArgs, overrides = {}) {
|
|
|
410
411
|
// check, so we specifically look for directories (namespaces like
|
|
411
412
|
// "platform") to confirm the registry content was actually pulled.
|
|
412
413
|
{
|
|
413
|
-
const registryNamespaces = safeListNamespaceDirs(awRegistry
|
|
414
|
+
const registryNamespaces = safeListNamespaceDirs(awRegistry);
|
|
414
415
|
if (registryNamespaces.length === 0) {
|
|
415
416
|
writer.stderr('[aw-c4] registry has no namespace directories after init — retrying with aw pull\n');
|
|
416
417
|
const pullRes = spawnSync('aw', ['pull'], { stdio: 'pipe' });
|
|
417
|
-
if (pullRes?.status !== 0 || safeListNamespaceDirs(awRegistry
|
|
418
|
-
writer.stderr('[aw-c4] FATAL: registry
|
|
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');
|
|
418
|
+
if (pullRes?.status !== 0 || safeListNamespaceDirs(awRegistry).length === 0) {
|
|
419
|
+
writer.stderr('[aw-c4] FATAL: registry commands were not fetched\n');
|
|
434
420
|
return exit(1);
|
|
435
421
|
}
|
|
436
422
|
}
|
|
@@ -453,9 +439,26 @@ export async function c4Command(rawArgs, overrides = {}) {
|
|
|
453
439
|
writer.stdout('[aw-c4] MCP disabled; skipping registerGhlAiMcp\n');
|
|
454
440
|
}
|
|
455
441
|
|
|
456
|
-
// Step 12 — slash surface
|
|
457
|
-
|
|
458
|
-
|
|
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
|
+
}
|
|
459
462
|
|
|
460
463
|
// Step 12b — Cursor Cloud slash-expand rule (no-op on other harnesses).
|
|
461
464
|
// The model-side workaround for Cursor Cloud's chat UI not pre-expanding
|
package/commands/doctor.mjs
CHANGED
|
@@ -523,14 +523,22 @@ function findBrokenRuleReferences(filePaths) {
|
|
|
523
523
|
return broken;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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)));
|
|
534
542
|
}
|
|
535
543
|
|
|
536
544
|
function buildDoctorChecks(homeDir, cwd) {
|
|
@@ -540,8 +548,8 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
540
548
|
const rulesDir = resolveRulesDir(homeDir);
|
|
541
549
|
|
|
542
550
|
const usingAwHookPath = [
|
|
543
|
-
join(homeDir, '.aw_registry', '
|
|
544
|
-
join(homeDir, '.aw', '.aw_registry', '
|
|
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'),
|
|
545
553
|
].find(existsSync);
|
|
546
554
|
|
|
547
555
|
checks.push(usingAwHookPath
|
|
@@ -578,19 +586,6 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
578
586
|
);
|
|
579
587
|
}
|
|
580
588
|
|
|
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
|
-
|
|
594
589
|
if (cwd !== homeDir) {
|
|
595
590
|
const projectAgentsPath = join(cwd, 'AGENTS.md');
|
|
596
591
|
const projectClaudePath = join(cwd, 'CLAUDE.md');
|
|
@@ -651,10 +646,12 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
651
646
|
const missingBundleFiles = missingFiles(claudePluginRoot, [
|
|
652
647
|
'.claude-plugin/plugin.json',
|
|
653
648
|
'hooks/hooks.json',
|
|
649
|
+
'skills/using-aw-skills/SKILL.md',
|
|
650
|
+
'skills/using-aw-skills/hooks/session-start.sh',
|
|
654
651
|
]);
|
|
655
652
|
checks.push(
|
|
656
653
|
missingBundleFiles.length === 0 && claudeSessionStartStatus.ok
|
|
657
|
-
? makeCheck('claude-plugin-bundle', 'Claude plugin bundle', 'pass', 'Claude plugin bundle contains AW routing hooks')
|
|
654
|
+
? makeCheck('claude-plugin-bundle', 'Claude plugin bundle', 'pass', 'Claude plugin bundle contains AW routing hooks and router skill files')
|
|
658
655
|
: makeCheck(
|
|
659
656
|
'claude-plugin-bundle',
|
|
660
657
|
'Claude plugin bundle',
|
|
@@ -670,8 +667,21 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
670
667
|
),
|
|
671
668
|
);
|
|
672
669
|
|
|
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
|
+
);
|
|
673
682
|
} else {
|
|
674
683
|
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'));
|
|
675
685
|
}
|
|
676
686
|
|
|
677
687
|
const claudeLegacyHooks = parseLegacyClaudeHookTargets(claudeSettings?.hooks?.SessionStart || []);
|
|
@@ -857,6 +867,19 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
857
867
|
: makeCheck('codex-references', 'Codex shared references', 'fail', 'Codex shared references are missing', projectRelinkFix(homeDir, cwd, '~/.codex/references')),
|
|
858
868
|
);
|
|
859
869
|
|
|
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
|
+
|
|
860
883
|
const codexAgentsPath = join(homeDir, '.codex', 'AGENTS.md');
|
|
861
884
|
checks.push(
|
|
862
885
|
existsSync(codexAgentsPath) && textHasManagedRouterBridge(readText(codexAgentsPath)) && textHasRulesReference(readText(codexAgentsPath))
|
|
@@ -921,6 +944,20 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
921
944
|
: makeCheck('cursor-install-state', 'Cursor install state', 'fail', 'Cursor install-state file is missing', globalInstallStateFix(homeDir, cwd, 'Cursor install state')),
|
|
922
945
|
);
|
|
923
946
|
|
|
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
|
+
|
|
924
961
|
const cursorMcp = jsonMcpHealth(join(homeDir, '.cursor', 'mcp.json'));
|
|
925
962
|
checks.push(
|
|
926
963
|
cursorMcp.present && cursorMcp.url && cursorMcp.authorization
|
package/commands/init.mjs
CHANGED
|
@@ -35,7 +35,7 @@ import { loadConfig as ensureTelemetryConfig } from '../telemetry.mjs';
|
|
|
35
35
|
import { installAwEcc, AW_ECC_TAG } from '../ecc.mjs';
|
|
36
36
|
import { removeWorkspaceHookDefaults } from '../codex.mjs';
|
|
37
37
|
import { readHookManifest, pruneStaleHooks, writeHookManifest } from '../hook-cleanup.mjs';
|
|
38
|
-
import { installIntegration, autoInstallIntegrations } from '../integrations.mjs';
|
|
38
|
+
import { installIntegration, autoInstallIntegrations, getSuggestedIntegrationStatuses } from '../integrations.mjs';
|
|
39
39
|
import {
|
|
40
40
|
initPersistentClone,
|
|
41
41
|
isValidClone,
|
|
@@ -49,16 +49,7 @@ import {
|
|
|
49
49
|
syncWorktreeSparseCheckout,
|
|
50
50
|
findNearestWorktree,
|
|
51
51
|
} from '../git.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';
|
|
52
|
+
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
|
|
62
53
|
import { syncFileTree } from '../file-tree.mjs';
|
|
63
54
|
import { installAwUsageHooks, formatAwUsageHooksInstallReport } from '../install-aw-usage-hooks.mjs';
|
|
64
55
|
|
|
@@ -86,6 +77,30 @@ function writeHookManifestBestEffort(manifest, context) {
|
|
|
86
77
|
}
|
|
87
78
|
}
|
|
88
79
|
|
|
80
|
+
function formatIntegrationStatusSummary(statuses, installedNow = []) {
|
|
81
|
+
if (!statuses || statuses.length === 0) return null;
|
|
82
|
+
|
|
83
|
+
const installedNowSet = new Set(installedNow);
|
|
84
|
+
const lines = statuses.map((entry) => {
|
|
85
|
+
if (entry.status === 'installed') {
|
|
86
|
+
const note = installedNowSet.has(entry.label) ? '(installed now)' : '(installed)';
|
|
87
|
+
return ` ${chalk.green('✓')} ${entry.label} ${chalk.dim(note)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (entry.status === 'skipped') {
|
|
91
|
+
const note = entry.reason ? `(skipped: ${entry.reason})` : '(skipped)';
|
|
92
|
+
return ` ${chalk.yellow('!')} ${entry.label} ${chalk.dim(note)}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return ` ${chalk.dim('○')} ${entry.label} ${chalk.dim('(not installed)')}`;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
` ${chalk.green('✓')} Integrations:`,
|
|
100
|
+
...lines,
|
|
101
|
+
].join('\n');
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
export async function syncEccAfterAwUpdate(updateResult, cwd, { silent = false } = {}) {
|
|
90
105
|
if (updateResult?.status !== 'upgraded' || !updateResult.packageRoot) {
|
|
91
106
|
return { synced: false, reason: updateResult?.reason || updateResult?.status || 'not-upgraded' };
|
|
@@ -282,6 +297,7 @@ function installIdeTasks() {
|
|
|
282
297
|
}
|
|
283
298
|
|
|
284
299
|
const ALLOWED_NAMESPACES = ['platform', 'revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'];
|
|
300
|
+
const DEFAULT_NAMESPACE = 'platform';
|
|
285
301
|
|
|
286
302
|
export async function initCommand(args) {
|
|
287
303
|
let namespace = args['--namespace'] || null;
|
|
@@ -307,6 +323,7 @@ export async function initCommand(args) {
|
|
|
307
323
|
let team = nsParts[0] || null;
|
|
308
324
|
let subTeam = nsParts[1] || null;
|
|
309
325
|
let folderName = subTeam ? `${team}/${subTeam}` : team;
|
|
326
|
+
const activeNamespace = () => team || DEFAULT_NAMESPACE;
|
|
310
327
|
|
|
311
328
|
if (team && !ALLOWED_NAMESPACES.includes(team)) {
|
|
312
329
|
const list = ALLOWED_NAMESPACES.map(n => chalk.cyan(n)).join(', ');
|
|
@@ -399,7 +416,7 @@ export async function initCommand(args) {
|
|
|
399
416
|
const isNewSubTeam = folderName && cfg && !cfg.include.includes(folderName);
|
|
400
417
|
if (isNewSubTeam) {
|
|
401
418
|
if (!silent) fmt.logStep(`Adding sub-team ${chalk.cyan(folderName)}...`);
|
|
402
|
-
const newSparsePaths = [
|
|
419
|
+
const newSparsePaths = [`.aw_registry/${folderName}`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR];
|
|
403
420
|
addToSparseCheckout(AW_HOME, newSparsePaths);
|
|
404
421
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
405
422
|
scaffoldNamespace(AW_HOME, folderName);
|
|
@@ -447,8 +464,9 @@ export async function initCommand(args) {
|
|
|
447
464
|
await installAwEcc(cwd, { silent });
|
|
448
465
|
|
|
449
466
|
ensureAwRuntimeHook(HOME);
|
|
450
|
-
|
|
451
|
-
|
|
467
|
+
const effectiveNamespace = freshCfg?.namespace || activeNamespace();
|
|
468
|
+
syncHomeAndProjectInstructions(cwd, effectiveNamespace);
|
|
469
|
+
await setupMcp(HOME, effectiveNamespace, { silent });
|
|
452
470
|
await maybeConfigureContextMode(args, { silent });
|
|
453
471
|
applyStoredStartupPreferences(HOME);
|
|
454
472
|
const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
|
|
@@ -493,8 +511,10 @@ export async function initCommand(args) {
|
|
|
493
511
|
|
|
494
512
|
// Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
|
|
495
513
|
let installedIntegrations = [];
|
|
514
|
+
let integrationStatuses = [];
|
|
496
515
|
if (!silent && !skipIntegrations && !isNewSubTeam) {
|
|
497
|
-
installedIntegrations = await autoInstallIntegrations(
|
|
516
|
+
installedIntegrations = await autoInstallIntegrations(effectiveNamespace, { silent });
|
|
517
|
+
integrationStatuses = getSuggestedIntegrationStatuses(effectiveNamespace, { home: HOME });
|
|
498
518
|
}
|
|
499
519
|
|
|
500
520
|
if (silent) {
|
|
@@ -517,7 +537,7 @@ export async function initCommand(args) {
|
|
|
517
537
|
? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
|
|
518
538
|
: null,
|
|
519
539
|
cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Project linked` : null,
|
|
520
|
-
|
|
540
|
+
formatIntegrationStatusSummary(integrationStatuses, installedIntegrations),
|
|
521
541
|
].filter(Boolean).join('\n'));
|
|
522
542
|
}
|
|
523
543
|
return;
|
|
@@ -546,13 +566,13 @@ export async function initCommand(args) {
|
|
|
546
566
|
}
|
|
547
567
|
|
|
548
568
|
// Determine sparse paths
|
|
549
|
-
const sparsePaths = [
|
|
569
|
+
const sparsePaths = [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
|
|
550
570
|
if (folderName) {
|
|
551
571
|
sparsePaths.push(`.aw_registry/${folderName}`);
|
|
552
572
|
}
|
|
553
573
|
|
|
554
574
|
fmt.note([
|
|
555
|
-
|
|
575
|
+
`${chalk.dim('namespace:')} ${folderName || activeNamespace()}`,
|
|
556
576
|
user ? `${chalk.dim('user:')} ${user}` : null,
|
|
557
577
|
`${chalk.dim('version:')} v${VERSION}`,
|
|
558
578
|
].filter(Boolean).join('\n'), 'Config');
|
|
@@ -592,7 +612,7 @@ export async function initCommand(args) {
|
|
|
592
612
|
}
|
|
593
613
|
|
|
594
614
|
// Create sync config — default to 'platform' when no namespace specified
|
|
595
|
-
const cfg = config.create(GLOBAL_AW_DIR, { namespace:
|
|
615
|
+
const cfg = config.create(GLOBAL_AW_DIR, { namespace: activeNamespace(), user });
|
|
596
616
|
if (folderName) {
|
|
597
617
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
598
618
|
scaffoldNamespace(AW_HOME, folderName);
|
|
@@ -614,8 +634,8 @@ export async function initCommand(args) {
|
|
|
614
634
|
|
|
615
635
|
// Parallel batch B: post-ECC setup (instructions and MCP are independent)
|
|
616
636
|
const [, mcpFiles] = await Promise.all([
|
|
617
|
-
Promise.resolve(syncHomeAndProjectInstructions(cwd,
|
|
618
|
-
setupMcp(HOME,
|
|
637
|
+
Promise.resolve(syncHomeAndProjectInstructions(cwd, activeNamespace())),
|
|
638
|
+
setupMcp(HOME, activeNamespace(), { silent }),
|
|
619
639
|
]);
|
|
620
640
|
await maybeConfigureContextMode(args, { silent });
|
|
621
641
|
// applyStoredStartupPreferences reads settings written by ECC — keep after batch B
|
|
@@ -686,8 +706,10 @@ export async function initCommand(args) {
|
|
|
686
706
|
|
|
687
707
|
// Auto-install suggested integrations (Codex, Caveman, Graphify, etc) - unless --no-integrations
|
|
688
708
|
let installedIntegrations = [];
|
|
709
|
+
let integrationStatuses = [];
|
|
689
710
|
if (!silent && !skipIntegrations) {
|
|
690
|
-
installedIntegrations = await autoInstallIntegrations(
|
|
711
|
+
installedIntegrations = await autoInstallIntegrations(activeNamespace(), { silent });
|
|
712
|
+
integrationStatuses = getSuggestedIntegrationStatuses(activeNamespace(), { home: HOME });
|
|
691
713
|
}
|
|
692
714
|
|
|
693
715
|
// Offer to update if a newer version is available
|
|
@@ -710,7 +732,7 @@ export async function initCommand(args) {
|
|
|
710
732
|
awUsageHooksReport ? ` ${chalk.green('✓')} ${awUsageHooksReport}` : null,
|
|
711
733
|
` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
|
|
712
734
|
cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
|
|
713
|
-
|
|
735
|
+
formatIntegrationStatusSummary(integrationStatuses, installedIntegrations),
|
|
714
736
|
'',
|
|
715
737
|
` ${chalk.dim('Existing repos:')} ${chalk.bold('cd <project> && aw link')}`,
|
|
716
738
|
` ${chalk.dim('New clones:')} auto-linked via git hook`,
|
package/commands/pull.mjs
CHANGED
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
REGISTRY_DIR,
|
|
28
28
|
REGISTRY_URL,
|
|
29
|
-
AW_REGISTRY_NAMESPACE_DIR,
|
|
30
29
|
DOCS_SOURCE_DIR,
|
|
31
30
|
AW_DOCS_DIR,
|
|
32
31
|
RULES_SOURCE_DIR,
|
|
@@ -94,7 +93,7 @@ export async function pullCommand(args) {
|
|
|
94
93
|
// Ensure platform pulls also fetch docs and rules on older installs that
|
|
95
94
|
// pre-date the new sparse-checkout paths.
|
|
96
95
|
if (input === 'platform') {
|
|
97
|
-
addToSparseCheckout(AW_HOME, [
|
|
96
|
+
addToSparseCheckout(AW_HOME, [`.aw_registry/platform`, DOCS_SOURCE_DIR, AW_DOCS_DIR, RULES_SOURCE_DIR]);
|
|
98
97
|
if (!cfg.include.includes('platform')) {
|
|
99
98
|
config.addPattern(GLOBAL_AW_DIR, 'platform');
|
|
100
99
|
}
|
package/constants.mjs
CHANGED
|
@@ -19,9 +19,6 @@ 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
|
-
|
|
25
22
|
/** Directory in platform-docs repo containing documentation (pulled into platform/docs/) */
|
|
26
23
|
export const DOCS_SOURCE_DIR = 'content';
|
|
27
24
|
|
package/ecc.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { promisify } from "node:util";
|
|
|
3
3
|
const execAsync = promisify(execCb);
|
|
4
4
|
import {
|
|
5
5
|
existsSync, readFileSync, readdirSync,
|
|
6
|
-
mkdirSync, rmSync, writeFileSync,
|
|
6
|
+
mkdirSync, rmSync, writeFileSync, renameSync,
|
|
7
7
|
} from "node:fs";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
@@ -16,6 +16,7 @@ export const AW_ECC_TAG = "v1.4.66";
|
|
|
16
16
|
const REQUIRED_ECC_FILES = [
|
|
17
17
|
"package.json",
|
|
18
18
|
"scripts/install-apply.js",
|
|
19
|
+
"scripts/sync-ecc-to-codex.sh",
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
const MARKETPLACE_NAME = "aw-marketplace";
|
|
@@ -23,31 +24,19 @@ const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
|
|
|
23
24
|
|
|
24
25
|
function eccDir() { return join(homedir(), ".aw-ecc"); }
|
|
25
26
|
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
27
|
+
// "claude" uses a no-commands module set so hooks, rules, shared references,
|
|
28
|
+
// and install-state land in ~/.claude/ — while slash commands stay owned by
|
|
29
|
+
// the marketplace plugin under /aw:*.
|
|
30
|
+
// Using `--without baseline:commands` with broader profiles is unsafe because
|
|
31
|
+
// some optional modules depend on commands-core and cause install-apply to fail.
|
|
30
32
|
const FILE_COPY_TARGETS = ["claude", "cursor", "codex"];
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cursor: [
|
|
39
|
-
"rules-core",
|
|
40
|
-
"agents-core",
|
|
41
|
-
"hooks-runtime",
|
|
42
|
-
"platform-configs",
|
|
43
|
-
],
|
|
44
|
-
codex: [
|
|
45
|
-
"rules-core",
|
|
46
|
-
"agents-core",
|
|
47
|
-
"hooks-runtime",
|
|
48
|
-
"platform-configs",
|
|
49
|
-
],
|
|
50
|
-
};
|
|
33
|
+
const CLAUDE_FILE_COPY_MODULES = [
|
|
34
|
+
"rules-core",
|
|
35
|
+
"agents-core",
|
|
36
|
+
"hooks-runtime",
|
|
37
|
+
"platform-configs",
|
|
38
|
+
"workflow-quality",
|
|
39
|
+
];
|
|
51
40
|
|
|
52
41
|
const TARGET_STATE = {
|
|
53
42
|
claude: { state: ".claude/ecc/install-state.json" },
|
|
@@ -252,11 +241,12 @@ function cloneOrUpdate(tag, dest) {
|
|
|
252
241
|
|
|
253
242
|
/**
|
|
254
243
|
* Transform canonical /aw: references to Cursor-compatible /aw- in installed
|
|
255
|
-
* rule files. Cursor
|
|
256
|
-
*
|
|
244
|
+
* skill and rule files. Cursor namespaces commands via directory structure
|
|
245
|
+
* (commands/aw/plan.md → /aw-plan) rather than colons.
|
|
257
246
|
*/
|
|
258
247
|
function transformCursorAwRefs(home) {
|
|
259
248
|
const dirs = [
|
|
249
|
+
join(home, ".cursor", "skills"),
|
|
260
250
|
join(home, ".cursor", "rules"),
|
|
261
251
|
];
|
|
262
252
|
for (const dir of dirs) {
|
|
@@ -290,16 +280,57 @@ function uninstallClaudePlugin() {
|
|
|
290
280
|
try { run(`claude plugin marketplace remove ${MARKETPLACE_NAME}`); } catch { /* not registered */ }
|
|
291
281
|
}
|
|
292
282
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Move ecc command files from ~/.cursor/commands/*.md → ~/.cursor/commands/aw/*.md
|
|
285
|
+
* so Cursor exposes them as /aw:tdd, /aw:plan — consistent with Claude Code's plugin namespace.
|
|
286
|
+
* Also updates the ecc-install-state.json paths so nuke can clean them up correctly.
|
|
287
|
+
*/
|
|
288
|
+
function namespaceCursorCommands(home) {
|
|
289
|
+
const commandsDir = join(home, ".cursor", "commands");
|
|
290
|
+
const awDir = join(commandsDir, "aw");
|
|
291
|
+
if (!existsSync(commandsDir)) return;
|
|
292
|
+
|
|
293
|
+
// Move any flat .md files (not already in a subdirectory) into aw/
|
|
294
|
+
const moved = [];
|
|
295
|
+
for (const file of readdirSync(commandsDir)) {
|
|
296
|
+
if (!file.endsWith(".md") || file.startsWith(".")) continue;
|
|
297
|
+
const src = join(commandsDir, file);
|
|
298
|
+
mkdirSync(awDir, { recursive: true });
|
|
299
|
+
const dest = join(awDir, file);
|
|
299
300
|
try {
|
|
300
|
-
|
|
301
|
+
renameSync(src, dest);
|
|
302
|
+
moved.push({ from: src, to: dest });
|
|
301
303
|
} catch { /* best effort */ }
|
|
302
304
|
}
|
|
305
|
+
|
|
306
|
+
if (moved.length === 0) return;
|
|
307
|
+
|
|
308
|
+
// Update install-state so nuke removes files from the new aw/ location
|
|
309
|
+
const statePath = join(home, ".cursor", "ecc-install-state.json");
|
|
310
|
+
if (!existsSync(statePath)) return;
|
|
311
|
+
try {
|
|
312
|
+
const state = JSON.parse(readFileSync(statePath, "utf8"));
|
|
313
|
+
const pathMap = new Map(moved.map(({ from, to }) => [from, to]));
|
|
314
|
+
state.operations = (state.operations || []).map((op) => {
|
|
315
|
+
const newPath = op.destinationPath && pathMap.get(op.destinationPath);
|
|
316
|
+
return newPath ? { ...op, destinationPath: newPath } : op;
|
|
317
|
+
});
|
|
318
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
319
|
+
} catch { /* best effort */ }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Run scripts/sync-ecc-to-codex.sh from the cloned ecc repo.
|
|
324
|
+
* Generates ~/.codex/prompts/*.md (Codex equivalent of slash commands),
|
|
325
|
+
* using aw-*.md for AW-namespaced commands and ecc-*.md for the rest,
|
|
326
|
+
* and merges ~/.codex/AGENTS.md. Best-effort — failure doesn't block init.
|
|
327
|
+
*/
|
|
328
|
+
function syncEccToCodex(repoDir) {
|
|
329
|
+
const syncScript = join(repoDir, "scripts", "sync-ecc-to-codex.sh");
|
|
330
|
+
if (!existsSync(syncScript)) return;
|
|
331
|
+
try {
|
|
332
|
+
run(`bash "${syncScript}"`, { cwd: homedir() });
|
|
333
|
+
} catch { /* best effort — codex sync failure is non-blocking */ }
|
|
303
334
|
}
|
|
304
335
|
|
|
305
336
|
export async function installAwEcc(
|
|
@@ -316,7 +347,6 @@ export async function installAwEcc(
|
|
|
316
347
|
try {
|
|
317
348
|
if (eccSpinner) eccSpinner.start('Cloning aw-ecc engine...');
|
|
318
349
|
await cloneOrUpdateAsync(AW_ECC_TAG, repoDir);
|
|
319
|
-
pruneEccAuthoredSkillsAndCommands(repoDir);
|
|
320
350
|
if (eccSpinner) eccSpinner.message('Installing aw-ecc dependencies...');
|
|
321
351
|
|
|
322
352
|
// Claude Code: plugin install via marketplace CLI (proper agent dispatch)
|
|
@@ -351,17 +381,20 @@ export async function installAwEcc(
|
|
|
351
381
|
|
|
352
382
|
// Always use HOME as cwd so files land in ~/.<target>/ globally.
|
|
353
383
|
const runCwd = homedir();
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
: `--target ${target}`;
|
|
384
|
+
const installArgs = target === "claude"
|
|
385
|
+
? `--target ${target} --modules ${CLAUDE_FILE_COPY_MODULES.join(",")}`
|
|
386
|
+
: `--target ${target} --profile full`;
|
|
358
387
|
run(
|
|
359
388
|
`node ${join(repoDir, "scripts/install-apply.js")} ${installArgs}`,
|
|
360
389
|
{ cwd: runCwd },
|
|
361
390
|
);
|
|
362
391
|
if (target === "cursor") {
|
|
392
|
+
namespaceCursorCommands(runCwd);
|
|
363
393
|
transformCursorAwRefs(home);
|
|
364
394
|
}
|
|
395
|
+
if (target === "codex") {
|
|
396
|
+
syncEccToCodex(repoDir);
|
|
397
|
+
}
|
|
365
398
|
restoreProtectedConfigs(snapshot);
|
|
366
399
|
} catch { /* target not supported — skip */ }
|
|
367
400
|
}));
|
|
@@ -413,8 +446,8 @@ export function uninstallAwEcc({ silent = false } = {}) {
|
|
|
413
446
|
} catch { /* corrupted state — skip */ }
|
|
414
447
|
}
|
|
415
448
|
|
|
416
|
-
// Codex: remove
|
|
417
|
-
// (not tracked in install-state — cleaned via manifests, with prefix fallback)
|
|
449
|
+
// Codex: remove generated prompt files from sync-ecc-to-codex.sh
|
|
450
|
+
// (not tracked in install-state — cleaned via manifests, with prefix fallback)
|
|
418
451
|
const codexPromptsDir = join(HOME, ".codex", "prompts");
|
|
419
452
|
if (existsSync(codexPromptsDir)) {
|
|
420
453
|
try {
|