@ghl-ai/aw 0.1.38-beta.8 → 0.1.39-beta.0

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/cli.mjs CHANGED
@@ -18,9 +18,6 @@ const COMMANDS = {
18
18
  'push-rules': () => import('./commands/push-rules.mjs').then(m => m.pushRulesCommand),
19
19
  drop: () => import('./commands/drop.mjs').then(m => m.dropCommand),
20
20
  status: () => import('./commands/status.mjs').then(m => m.statusCommand),
21
- doctor: () => import('./commands/doctor.mjs').then(m => m.doctorCommand),
22
- routing: () => import('./commands/startup.mjs').then(m => m.routingCommand),
23
- startup: () => import('./commands/startup.mjs').then(m => m.startupCommand),
24
21
  search: () => import('./commands/search.mjs').then(m => m.searchCommand),
25
22
  link: () => import('./commands/link-project.mjs').then(m => m.linkProjectCommand),
26
23
  nuke: () => import('./commands/nuke.mjs').then(m => m.nukeCommand),
@@ -100,11 +97,7 @@ function printHelp() {
100
97
 
101
98
  sec('Manage'),
102
99
  cmd('aw status', 'Show synced paths, modified files & conflicts'),
103
- cmd('aw doctor', 'Run a health check for routing, MCP, plugin, and AW ECC surfaces'),
104
100
  cmd('aw link', 'Link current project as a git worktree (wires IDE symlinks)'),
105
- cmd('aw routing status', 'Show global AW session-routing mode for Claude/Cursor/Codex'),
106
- cmd('aw routing disable', 'Disable automatic AW session routing globally'),
107
- cmd('aw routing enable', 'Re-enable automatic AW session routing globally'),
108
101
  cmd('aw drop <path>', 'Stop syncing or delete local content'),
109
102
  cmd('aw nuke', 'Remove entire .aw_registry/ & start fresh'),
110
103
  cmd('aw daemon install', 'Auto-pull on a schedule (macOS launchd / Linux cron)'),
package/commands/init.mjs CHANGED
@@ -23,14 +23,12 @@ import * as config from '../config.mjs';
23
23
  import * as fmt from '../fmt.mjs';
24
24
  import { chalk } from '../fmt.mjs';
25
25
  import { linkWorkspace } from '../link.mjs';
26
- import { generateCommands, copyInstructions, initAwDocs, syncHomeHarnessInstructions } from '../integrate.mjs';
26
+ import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs';
27
27
  import { setupMcp } from '../mcp.mjs';
28
- import { applyStoredStartupPreferences, ensureAwRuntimeHook } from '../startup.mjs';
29
28
  import { installLocalCommitHook } from '../hooks.mjs';
30
29
  import { autoUpdate, promptUpdate } from '../update.mjs';
31
30
  import { installGlobalHooks } from '../hooks.mjs';
32
31
  import { installAwEcc } from '../ecc.mjs';
33
- import { removeWorkspaceHookDefaults } from '../codex.mjs';
34
32
  import {
35
33
  initPersistentClone,
36
34
  isValidClone,
@@ -59,19 +57,6 @@ const HOME = (() => { try { return realpathSync(_rawHome); } catch { return _raw
59
57
  const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
60
58
  const AW_HOME = join(HOME, '.aw');
61
59
 
62
- function syncInstructionsAndAwDocs(targetDir, namespace) {
63
- copyInstructions(targetDir, null, namespace);
64
- initAwDocs(targetDir);
65
- }
66
-
67
- function syncHomeAndProjectInstructions(cwd, namespace) {
68
- syncHomeHarnessInstructions(HOME);
69
- initAwDocs(HOME);
70
- if (cwd !== HOME) {
71
- syncInstructionsAndAwDocs(cwd, namespace);
72
- }
73
- }
74
-
75
60
  // ── Ensure ~/.aw/.gitignore has personal/local entries ───────────────────
76
61
 
77
62
  const AW_GITIGNORE_ENTRIES = [
@@ -275,11 +260,9 @@ export async function initCommand(args) {
275
260
  }
276
261
 
277
262
  await installAwEcc(cwd, { silent });
278
- ensureAwRuntimeHook(HOME);
279
- syncHomeAndProjectInstructions(cwd, freshCfg?.namespace || team);
263
+ copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
264
+ initAwDocs(HOME);
280
265
  await setupMcp(HOME, freshCfg?.namespace || team, { silent });
281
- applyStoredStartupPreferences(HOME);
282
- const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
283
266
  installGlobalHooks();
284
267
 
285
268
  // Remove old local .git/hooks/post-checkout that pre-dates core.hooksPath (creates stale .aw_registry symlink)
@@ -321,9 +304,6 @@ export async function initCommand(args) {
321
304
  '',
322
305
  ` ${chalk.green('✓')} Registry synced`,
323
306
  ` ${chalk.green('✓')} IDE refreshed — ${chalk.bold(symlinks)} symlinks · ${chalk.bold(commands)} commands`,
324
- removedLegacyStartupFiles.length > 0
325
- ? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
326
- : null,
327
307
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Project linked` : null,
328
308
  ].filter(Boolean).join('\n'));
329
309
  }
@@ -410,11 +390,9 @@ export async function initCommand(args) {
410
390
 
411
391
  // Step 3: Setup tasks, MCP, hooks
412
392
  await installAwEcc(cwd, { silent });
413
- ensureAwRuntimeHook(HOME);
414
- syncHomeAndProjectInstructions(cwd, team);
393
+ const instructionFiles = copyInstructions(HOME, null, team) || [];
394
+ initAwDocs(HOME);
415
395
  const mcpFiles = await setupMcp(HOME, team, { silent }) || [];
416
- applyStoredStartupPreferences(HOME);
417
- const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
418
396
  const hooksInstalled = installGlobalHooks();
419
397
  installIdeTasks();
420
398
 
@@ -461,10 +439,6 @@ export async function initCommand(args) {
461
439
  ` ${chalk.green('✓')} Source of truth: ~/.aw/ (git clone)`,
462
440
  ` ${chalk.green('✓')} Symlink: ~/.aw_registry/ → ~/.aw/.aw_registry/`,
463
441
  ` ${chalk.green('✓')} IDE integration: ~/.claude/, ~/.cursor/, ~/.codex/`,
464
- cwd !== HOME ? ` ${chalk.green('✓')} Global startup managed from ~/.claude/, ~/.cursor/, ~/.codex/` : null,
465
- removedLegacyStartupFiles.length > 0
466
- ? ` ${chalk.green('✓')} Removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
467
- : null,
468
442
  hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
469
443
  ` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
470
444
  cwd !== HOME && isWorktree(join(cwd, '.aw')) ? ` ${chalk.green('✓')} Linked in current project` : null,
@@ -9,8 +9,6 @@ import { addProjectWorktree, isWorktree, isValidClone } from '../git.mjs';
9
9
  import { REGISTRY_DIR, REGISTRY_URL } from '../constants.mjs';
10
10
  import { linkWorkspace } from '../link.mjs';
11
11
  import { generateCommands } from '../integrate.mjs';
12
- import { removeWorkspaceHookDefaults } from '../codex.mjs';
13
- import { applyStoredStartupPreferences } from '../startup.mjs';
14
12
  import { installLocalCommitHook } from '../hooks.mjs';
15
13
 
16
14
  const HOME = homedir();
@@ -43,11 +41,8 @@ export function linkProjectCommand(args) {
43
41
  const awDirForLinks = existsSync(projectRegistryDir) ? projectRegistryDir : null;
44
42
  const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
45
43
  const commands = generateCommands(HOME, { silent: true });
46
- applyStoredStartupPreferences(HOME);
47
44
  installLocalCommitHook(cwd);
48
- const removedLegacyStartupFiles = removeWorkspaceHookDefaults(cwd);
49
- fmt.logSuccess(`Already linked — refreshed ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands${removedLegacyStartupFiles.length > 0 ? ` · removed ${removedLegacyStartupFiles.length} legacy repo startup file${removedLegacyStartupFiles.length > 1 ? 's' : ''}` : ''}`);
50
-
45
+ fmt.logSuccess(`Already linked refreshed ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands`);
51
46
  return;
52
47
  }
53
48
 
@@ -57,19 +52,13 @@ export function linkProjectCommand(args) {
57
52
  const awDirForLinks = existsSync(projectRegistryDir) ? projectRegistryDir : null;
58
53
  const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
59
54
  const commands = generateCommands(HOME, { silent: true });
60
- applyStoredStartupPreferences(HOME);
61
55
  installLocalCommitHook(cwd);
62
- const removedLegacyStartupFiles = removeWorkspaceHookDefaults(cwd);
63
56
  fmt.logSuccess([
64
57
  `Project linked — ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands`,
65
58
  '',
66
59
  ` ${chalk.green('✓')} ${chalk.dim('.aw/')} git worktree (IDE git panel enabled)`,
67
60
  ` ${chalk.green('✓')} ${chalk.dim(`.aw/${REGISTRY_DIR}/`)} registry content`,
68
61
  ` ${chalk.green('✓')} ${chalk.dim('.claude/.cursor/.codex/')} IDE symlinks wired`,
69
- ` ${chalk.green('✓')} ${chalk.dim('startup mode')} global-first via ~/.claude/, ~/.cursor/, ~/.codex/`,
70
- removedLegacyStartupFiles.length > 0
71
- ? ` ${chalk.green('✓')} ${chalk.dim('legacy repo hooks')} removed ${removedLegacyStartupFiles.length} AW-managed file${removedLegacyStartupFiles.length > 1 ? 's' : ''}`
72
- : null,
73
62
  ].join('\n'));
74
63
  } catch (e) {
75
64
  fmt.cancel(`Failed to link project: ${e.message}`);
package/commands/nuke.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Safety guarantee: NEVER deletes files that AW didn't create.
4
4
 
5
- import { dirname, join } from 'node:path';
5
+ import { join } from 'node:path';
6
6
  import { existsSync, rmSync, lstatSync, unlinkSync, readdirSync, readFileSync, readlinkSync, writeFileSync } from 'node:fs';
7
7
  import { homedir } from 'node:os';
8
8
  import { execSync, exec as execCb } from 'node:child_process';
@@ -15,14 +15,13 @@ import { removeGlobalHooks } from '../hooks.mjs';
15
15
  import { uninstallAwEcc } from '../ecc.mjs';
16
16
  import { removeMcpConfig } from '../mcp.mjs';
17
17
  import { listProjectWorktrees } from '../git.mjs';
18
- import { removeWorkspaceHookDefaults } from '../codex.mjs';
19
18
 
20
19
  const HOME = homedir();
21
20
  const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
22
21
  const MANIFEST_PATH = join(GLOBAL_AW_DIR, '.aw-manifest.json');
23
22
 
24
23
  const IDE_DIRS = ['.claude', '.cursor', '.codex', '.agents'];
25
- const CONTENT_TYPES = ['agents', 'skills', 'commands', 'evals', 'references'];
24
+ const CONTENT_TYPES = ['agents', 'skills', 'commands', 'evals'];
26
25
 
27
26
  function loadManifest() {
28
27
  if (!existsSync(MANIFEST_PATH)) return null;
@@ -140,11 +139,7 @@ async function removeProjectSymlinks() {
140
139
  { encoding: 'utf8', timeout: 30000 }
141
140
  );
142
141
  for (const linkPath of registryLinks.trim().split('\n').filter(Boolean)) {
143
- try {
144
- removeWorkspaceHookDefaults(dirname(linkPath));
145
- unlinkSync(linkPath);
146
- removed++;
147
- } catch { /* best effort */ }
142
+ try { unlinkSync(linkPath); removed++; } catch { /* best effort */ }
148
143
  }
149
144
 
150
145
  // Also remove legacy local .git/hooks/post-checkout installed by old aw versions
@@ -222,12 +217,7 @@ export async function nukeCommand(args) {
222
217
  // Remove stale local .aw_registry symlink if present (skip if cwd IS home — that's the global one)
223
218
  if (process.cwd() !== HOME) {
224
219
  const local = join(process.cwd(), '.aw_registry');
225
- try {
226
- if (lstatSync(local).isSymbolicLink()) {
227
- removeWorkspaceHookDefaults(process.cwd());
228
- unlinkSync(local);
229
- }
230
- } catch { /* fine */ }
220
+ try { if (lstatSync(local).isSymbolicLink()) unlinkSync(local); } catch { /* fine */ }
231
221
  }
232
222
 
233
223
  const manifest = loadManifest();
package/commands/pull.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  existsSync,
5
5
  lstatSync,
6
6
  } from 'node:fs';
7
- import { dirname, join, extname } from 'node:path';
7
+ import { join, extname } from 'node:path';
8
8
  import { homedir } from 'node:os';
9
9
  import { exec as execCb, execSync } from 'node:child_process';
10
10
  import { promisify } from 'node:util';
@@ -30,9 +30,7 @@ import {
30
30
  } from '../constants.mjs';
31
31
  import { collectAllPaths, syncFileTree } from '../file-tree.mjs';
32
32
  import { linkWorkspace } from '../link.mjs';
33
- import { generateCommands, copyInstructions, syncHomeHarnessInstructions } from '../integrate.mjs';
34
- import { removeWorkspaceHookDefaults } from '../codex.mjs';
35
- import { applyStoredStartupPreferences, ensureAwRuntimeHook } from '../startup.mjs';
33
+ import { generateCommands, copyInstructions } from '../integrate.mjs';
36
34
 
37
35
  const HOME = homedir();
38
36
  const AW_HOME = join(HOME, '.aw');
@@ -247,7 +245,6 @@ export async function pullCommand(args) {
247
245
  if (!args._skipIntegrate) {
248
246
  const projectRegistryDir = cwd !== HOME ? join(cwd, '.aw', REGISTRY_DIR) : null;
249
247
  const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
250
- const startupCleanupDir = localAw ? dirname(localAw) : (cwd !== HOME ? cwd : null);
251
248
 
252
249
  if (!silent) {
253
250
  const ideSpinner = log.spinner();
@@ -255,18 +252,12 @@ export async function pullCommand(args) {
255
252
  const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
256
253
  ideSpinner.message('Generating commands...');
257
254
  const commands = generateCommands(HOME, { silent: true });
258
- syncHomeHarnessInstructions(HOME);
259
- ensureAwRuntimeHook(HOME);
260
- applyStoredStartupPreferences(HOME);
261
- if (startupCleanupDir) removeWorkspaceHookDefaults(startupCleanupDir);
255
+ copyInstructions(HOME, null, cfg.namespace);
262
256
  ideSpinner.stop(`IDE wired — ${chalk.bold(symlinks)} symlink${symlinks !== 1 ? 's' : ''}, ${chalk.bold(commands)} command${commands !== 1 ? 's' : ''}`);
263
257
  } else {
264
258
  linkWorkspace(HOME, awDirForLinks, { silent: true });
265
259
  generateCommands(HOME, { silent: true });
266
- syncHomeHarnessInstructions(HOME);
267
- ensureAwRuntimeHook(HOME);
268
- applyStoredStartupPreferences(HOME);
269
- if (startupCleanupDir) removeWorkspaceHookDefaults(startupCleanupDir);
260
+ copyInstructions(HOME, null, cfg.namespace);
270
261
  }
271
262
  }
272
263
 
@@ -299,73 +290,3 @@ function registerMcp(namespace) {
299
290
  fmt.logWarn('MCP registration failed (pull still successful)');
300
291
  }
301
292
  }
302
- function printDryRun(actions, verbose) {
303
- const counts = { ADD: 0, UPDATE: 0, CONFLICT: 0, ORPHAN: 0, UNCHANGED: 0 };
304
- const lines = [];
305
-
306
- for (const type of ['agents', 'skills', 'commands', 'evals', 'references']) {
307
- const items = actions.filter(a => a.type === type);
308
- if (items.length === 0) continue;
309
-
310
- lines.push(chalk.bold(`${type}/`));
311
- for (const act of items.sort((a, b) => a.targetFilename.localeCompare(b.targetFilename))) {
312
- counts[act.action] = (counts[act.action] || 0) + 1;
313
- if (!verbose && act.action === 'UNCHANGED') continue;
314
- const ns = act.namespacePath ? chalk.dim(` [${act.namespacePath}]`) : '';
315
- lines.push(` ${fmt.actionLabel(act.action)} ${act.targetFilename}${ns}`);
316
- }
317
- }
318
-
319
- if (lines.length > 0) {
320
- fmt.note(lines.join('\n'), 'Dry Run');
321
- }
322
-
323
- fmt.logInfo(`Summary: ${fmt.countSummary(counts)}`);
324
- fmt.logWarn('No files modified (--dry-run)');
325
- }
326
-
327
- function printSummary(actions, verbose, conflictCount) {
328
- const conflicts = actions.filter(a => a.action === 'CONFLICT');
329
-
330
- for (const type of ['agents', 'skills', 'commands', 'evals', 'references']) {
331
- const typeActions = actions.filter(a => a.type === type);
332
- if (typeActions.length === 0) continue;
333
-
334
- const counts = { ADD: 0, UPDATE: 0, CONFLICT: 0, ORPHAN: 0, UNCHANGED: 0 };
335
- for (const a of typeActions) counts[a.action]++;
336
-
337
- const parts = [];
338
- if (counts.ADD > 0) parts.push(chalk.green(`${counts.ADD} new`));
339
- if (counts.UPDATE > 0) parts.push(chalk.cyan(`${counts.UPDATE} updated`));
340
- if (counts.CONFLICT > 0) parts.push(chalk.red(`${counts.CONFLICT} conflict`));
341
- if (counts.ORPHAN > 0) parts.push(chalk.yellow(`${counts.ORPHAN} removed`));
342
- const detail = parts.length > 0 ? ` (${parts.join(', ')})` : '';
343
-
344
- fmt.logSuccess(`${typeActions.length} ${type} pulled${detail}`);
345
-
346
- if (verbose) {
347
- for (const a of typeActions.filter(a => a.action !== 'UNCHANGED')) {
348
- const ns = a.namespacePath ? chalk.dim(` [${a.namespacePath}]`) : '';
349
- fmt.logMessage(` ${fmt.actionLabel(a.action)} ${a.targetFilename}${ns}`);
350
- }
351
- }
352
- }
353
-
354
- if (conflicts.length > 0) {
355
- const conflictLines = conflicts.map(c => {
356
- return `${chalk.red('both modified:')} ${c.type}/${c.targetFilename}`;
357
- }).join('\n');
358
-
359
- fmt.note(
360
- conflictLines + '\n\n' +
361
- chalk.dim('Fix conflicts, then re-run pull to verify.\n') +
362
- chalk.dim('grep -r "<<<<<<< " .aw_registry/'),
363
- chalk.red('Merge Conflicts')
364
- );
365
-
366
- fmt.outro(chalk.red('Pull completed with conflicts — resolve and re-run'));
367
- process.exit(1);
368
- }
369
-
370
- fmt.outro('Pull complete');
371
- }
package/commands/push.mjs CHANGED
@@ -29,7 +29,7 @@ import { hasRulesChanges, isRulesPushInput, pushRulesCommand } from './push-rule
29
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
30
30
  const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
31
31
 
32
- const PUSHABLE_TYPES = ['agents', 'skills', 'commands', 'evals', 'references'];
32
+ const PUSHABLE_TYPES = ['agents', 'skills', 'commands', 'evals'];
33
33
 
34
34
  // ── PR content generation ────────────────────────────────────────────
35
35
 
@@ -635,8 +635,8 @@ export async function pushCommand(args) {
635
635
  const registryAbsPath = join(registrySubDir, relFromRegistry);
636
636
  const allFiles = collectBatchFiles(registryAbsPath, registrySubDir);
637
637
  if (allFiles.length === 0) {
638
- // Folder/namespace input batch push
639
- fmt.cancel(`Nothing to push in ${chalk.cyan(input)} — no agents, skills, commands, evals, or references found.`);
638
+ fmt.cancel(`Nothing to push in ${chalk.cyan(input)} no agents, skills, commands, or evals found.`);
639
+ return;
640
640
  }
641
641
  // Only include files that actually have changes (new or modified)
642
642
  const folderChanges = detectChanges(awHome, REGISTRY_DIR);
@@ -688,7 +688,7 @@ export async function pushCommand(args) {
688
688
  fmt.cancel([
689
689
  `Invalid push path: ${chalk.red(resolved.registryPath)}`,
690
690
  '',
691
- ` Content must live under an ${chalk.bold('agents/')}, ${chalk.bold('skills/')}, ${chalk.bold('commands/')}, ${chalk.bold('evals/')}, or ${chalk.bold('references/')} directory.`,
691
+ ` Content must live under an ${chalk.bold('agents/')}, ${chalk.bold('skills/')}, ${chalk.bold('commands/')}, or ${chalk.bold('evals/')} directory.`,
692
692
  '',
693
693
  ` ${chalk.dim('Valid examples:')}`,
694
694
  ` aw push .aw_registry/commerce/quality/agents/unit-tester.md`,
@@ -98,7 +98,7 @@ function searchLocal(workspaceDir, query) {
98
98
  if (!nsEntry.isDirectory() || nsEntry.name.startsWith('.')) continue;
99
99
  const ns = nsEntry.name;
100
100
 
101
- for (const type of ['agents', 'skills', 'commands', 'evals', 'references']) {
101
+ for (const type of ['agents', 'skills', 'commands', 'evals']) {
102
102
  const typeDir = join(workspaceDir, ns, type);
103
103
  if (!existsSync(typeDir)) continue;
104
104
 
@@ -1,8 +1,5 @@
1
- // commands/telemetry.mjs — `aw telemetry [enable|disable|status|flush-queue]`
1
+ // commands/telemetry.mjs — `aw telemetry [enable|disable|status]`
2
2
 
3
- import { existsSync, readFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { homedir } from 'node:os';
6
3
  import { enableTelemetry, disableTelemetry, getStatus } from '../telemetry.mjs';
7
4
  import * as fmt from '../fmt.mjs';
8
5
  import { chalk } from '../fmt.mjs';
@@ -22,66 +19,13 @@ export async function telemetryCommand(args) {
22
19
  return;
23
20
  }
24
21
 
25
- if (sub === 'flush-queue') {
26
- await flushQueueCommand();
27
- return;
28
- }
29
-
30
22
  // status (default)
31
23
  const status = getStatus();
32
24
  fmt.intro('aw telemetry');
33
25
  fmt.logStep(`Status: ${status.enabled ? chalk.green('enabled') : chalk.red('disabled')}`);
34
26
  fmt.logStep(`Machine ID: ${chalk.dim(status.machine_id)}`);
35
27
  fmt.logStep(`Config: ${chalk.dim(status.config_path)}`);
36
-
37
- // Show queue depth if available
38
- const queueFile = join(homedir(), '.aw', 'telemetry', 'queue.jsonl');
39
- if (existsSync(queueFile)) {
40
- try {
41
- const lines = readFileSync(queueFile, 'utf8').trim().split('\n').filter(Boolean);
42
- fmt.logStep(`Queue: ${chalk.yellow(lines.length)} pending prompt(s)`);
43
- } catch { /* best effort */ }
44
- }
45
-
46
28
  fmt.logMessage('');
47
- fmt.logMessage(` ${chalk.dim('aw telemetry disable')} — opt out of anonymous analytics`);
48
- fmt.logMessage(` ${chalk.dim('aw telemetry enable')} — re-enable analytics`);
49
- fmt.logMessage(` ${chalk.dim('aw telemetry flush-queue')} — manually flush pending queue`);
50
- }
51
-
52
- async function flushQueueCommand() {
53
- const eccBase = join(homedir(), '.aw-ecc');
54
- const libPath = join(eccBase, 'scripts', 'hooks', 'capabilities', 'telemetry', 'telemetry-lib.js');
55
-
56
- if (!existsSync(libPath)) {
57
- fmt.logWarn('Telemetry library not found. Run aw init first.');
58
- return;
59
- }
60
-
61
- const {
62
- readQueue,
63
- flushQueueToApi,
64
- getNamespace,
65
- buildTelemetryHeaders,
66
- } = await import(libPath);
67
-
68
- const entries = readQueue();
69
- if (entries.length === 0) {
70
- fmt.logSuccess('Queue is empty — nothing to flush.');
71
- return;
72
- }
73
-
74
- fmt.logStep(`Flushing ${entries.length} pending prompt(s)...`);
75
-
76
- const namespace = getNamespace();
77
- const headers = buildTelemetryHeaders(namespace);
78
- const result = await flushQueueToApi(headers);
79
-
80
- if (result.flushed > 0) {
81
- fmt.logSuccess(`Flushed ${result.flushed} prompt(s) to API.`);
82
- } else if (result.failed) {
83
- fmt.logWarn('Flush failed — entries remain in queue for retry.');
84
- } else {
85
- fmt.logSuccess('Nothing to flush.');
86
- }
29
+ fmt.logMessage(` ${chalk.dim('aw telemetry disable')} — opt out of anonymous analytics`);
30
+ fmt.logMessage(` ${chalk.dim('aw telemetry enable')} — re-enable analytics`);
87
31
  }
package/ecc.mjs CHANGED
@@ -6,30 +6,21 @@ import {
6
6
  import { dirname, join } from "node:path";
7
7
  import { homedir } from "node:os";
8
8
  import * as fmt from "./fmt.mjs";
9
- import { applyStoredStartupPreferences } from "./startup.mjs";
10
9
 
11
10
  const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
12
11
  const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
13
- export const AW_ECC_TAG = "v1.0.2";
12
+ const AW_ECC_TAG = "v1.4.24";
14
13
 
15
14
  const MARKETPLACE_NAME = "aw-marketplace";
16
15
  const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
17
16
 
18
17
  function eccDir() { return join(homedir(), ".aw-ecc"); }
19
18
 
20
- // "claude" uses a no-commands module set so hooks, rules, shared references,
21
- // and install-state land in ~/.claude/ — while slash commands stay owned by
22
- // the marketplace plugin under /aw:*.
23
- // Using `--without baseline:commands` with broader profiles is unsafe because
24
- // some optional modules depend on commands-core and cause install-apply to fail.
19
+ // "claude" runs install-apply.js --without baseline:commands so hooks, rules,
20
+ // and agents land in ~/.claude/ — but commands are skipped because the plugin
21
+ // marketplace (installClaudePlugin) already registers them under /aw:tdd.
22
+ // File-copying commands would create a duplicate flat /tdd alongside /aw:tdd.
25
23
  const FILE_COPY_TARGETS = ["claude", "cursor", "codex"];
26
- const CLAUDE_FILE_COPY_MODULES = [
27
- "rules-core",
28
- "agents-core",
29
- "hooks-runtime",
30
- "platform-configs",
31
- "workflow-quality",
32
- ];
33
24
 
34
25
  const TARGET_STATE = {
35
26
  claude: { state: ".claude/ecc/install-state.json" },
@@ -49,20 +40,6 @@ function run(cmd, opts = {}) {
49
40
  return execSync(cmd, { stdio: "pipe", ...opts });
50
41
  }
51
42
 
52
- function fetchOverrideRef(dest, ref) {
53
- run(`git -C ${dest} fetch --quiet --depth 1 origin ${ref}`);
54
- run(`git -C ${dest} checkout --quiet FETCH_HEAD`);
55
- }
56
-
57
- function cloneWithRef(url, ref, dest) {
58
- try {
59
- run(`git clone --quiet --depth 1 --branch ${ref} "${url}" "${dest}"`);
60
- } catch {
61
- run(`git clone --quiet --depth 1 "${url}" "${dest}"`);
62
- fetchOverrideRef(dest, ref);
63
- }
64
- }
65
-
66
43
  function readIfExists(path) {
67
44
  try { return existsSync(path) ? readFileSync(path, "utf8") : null; } catch { return null; }
68
45
  }
@@ -119,39 +96,20 @@ function relProtectedPath(absPath, home) {
119
96
  function cloneOrUpdate(tag, dest) {
120
97
  // AW_ECC_CLONE_URL overrides the remote (used in tests to point at a local fake repo)
121
98
  const overrideUrl = process.env.AW_ECC_CLONE_URL;
122
- // AW_ECC_CLONE_REF lets aw init install ECC from a non-default branch or tag.
123
- const overrideRef = process.env.AW_ECC_CLONE_REF?.trim();
124
99
 
125
100
  if (existsSync(join(dest, ".git"))) {
126
101
  try {
127
- if (!overrideUrl && !overrideRef) {
102
+ if (!overrideUrl) {
128
103
  run(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
129
104
  run(`git -C ${dest} checkout --quiet ${tag}`);
130
- } else {
131
- if (overrideUrl) {
132
- run(`git -C ${dest} remote set-url origin "${overrideUrl}"`);
133
- }
134
- fetchOverrideRef(dest, overrideRef || tag);
135
105
  }
136
106
  return;
137
107
  } catch { /* fall through to fresh clone */ }
138
108
  }
139
109
  if (existsSync(dest)) rmSync(dest, { recursive: true, force: true });
140
110
  if (overrideUrl) {
141
- if (overrideRef) {
142
- cloneWithRef(overrideUrl, overrideRef, dest);
143
- } else {
144
- run(`git clone --quiet --depth 1 "${overrideUrl}" "${dest}"`);
145
- }
111
+ run(`git clone --quiet --depth 1 "${overrideUrl}" "${dest}"`);
146
112
  } else {
147
- if (overrideRef) {
148
- try {
149
- cloneWithRef(AW_ECC_REPO_SSH, overrideRef, dest);
150
- } catch {
151
- cloneWithRef(AW_ECC_REPO_HTTPS, overrideRef, dest);
152
- }
153
- return;
154
- }
155
113
  try {
156
114
  run(`git clone --quiet --depth 1 --branch ${tag} ${AW_ECC_REPO_SSH} ${dest}`);
157
115
  } catch {
@@ -216,8 +174,7 @@ function namespaceCursorCommands(home) {
216
174
 
217
175
  /**
218
176
  * Run scripts/sync-ecc-to-codex.sh from the cloned ecc repo.
219
- * Generates ~/.codex/prompts/*.md (Codex equivalent of slash commands),
220
- * using aw-*.md for AW-namespaced commands and ecc-*.md for the rest,
177
+ * Generates ~/.codex/prompts/ecc-*.md (Codex equivalent of slash commands)
221
178
  * and merges ~/.codex/AGENTS.md. Best-effort — failure doesn't block init.
222
179
  */
223
180
  function syncEccToCodex(repoDir) {
@@ -241,9 +198,6 @@ export async function installAwEcc(
241
198
  try {
242
199
  cloneOrUpdate(AW_ECC_TAG, repoDir);
243
200
 
244
- // Ensure telemetry state directory exists (vendor-agnostic, shared across IDEs)
245
- mkdirSync(join(home, ".aw", "telemetry"), { recursive: true });
246
-
247
201
  // Claude Code: plugin install via marketplace CLI (proper agent dispatch)
248
202
  if (targets.includes("claude")) {
249
203
  try {
@@ -265,14 +219,11 @@ export async function installAwEcc(
265
219
 
266
220
  // Always use HOME as cwd so files land in ~/.<target>/ globally.
267
221
  const runCwd = homedir();
268
- // For claude: install the safe no-commands module set. The plugin
269
- // already owns /aw:* command registration, and broader profiles with
270
- // `--without baseline:commands` can fail on commands-core deps.
271
- const installArgs = target === "claude"
272
- ? `--target ${target} --modules ${CLAUDE_FILE_COPY_MODULES.join(",")}`
273
- : `--target ${target} --profile full`;
222
+ // For claude: skip commands (plugin handles them as /aw:tdd) but
223
+ // install hooks, rules, agents into ~/.claude/.
224
+ const withoutFlag = target === "claude" ? " --without baseline:commands" : "";
274
225
  run(
275
- `node ${join(repoDir, "scripts/install-apply.js")} ${installArgs}`,
226
+ `node ${join(repoDir, "scripts/install-apply.js")} --target ${target} --profile full${withoutFlag}`,
276
227
  { cwd: runCwd },
277
228
  );
278
229
  // Move cursor commands into aw/ subfolder for namespace consistency
@@ -280,7 +231,7 @@ export async function installAwEcc(
280
231
  if (target === "cursor") {
281
232
  namespaceCursorCommands(runCwd);
282
233
  }
283
- // Run sync script for codex: generates ~/.codex/prompts/*.md and
234
+ // Run sync script for codex: generates ~/.codex/prompts/ecc-*.md and
284
235
  // merges AGENTS.md — Codex has no slash commands, so prompts are the
285
236
  // equivalent of commands.
286
237
  if (target === "codex") {
@@ -294,8 +245,6 @@ export async function installAwEcc(
294
245
  }
295
246
  }
296
247
 
297
- applyStoredStartupPreferences();
298
-
299
248
  if (!silent) fmt.logSuccess("aw-ecc engine installed");
300
249
  } catch (err) {
301
250
  if (!silent) fmt.logWarn(`aw-ecc install failed: ${err.message}`);
@@ -339,42 +288,17 @@ export function uninstallAwEcc({ silent = false } = {}) {
339
288
  } catch { /* corrupted state — skip */ }
340
289
  }
341
290
 
342
- // Codex: remove generated prompt files from sync-ecc-to-codex.sh
343
- // (not tracked in install-state — cleaned via manifests, with prefix fallback)
291
+ // Codex: remove ecc prompt files generated by sync-ecc-to-codex.sh
292
+ // (not tracked in install-state — cleaned by prefix pattern ecc-*.md)
344
293
  const codexPromptsDir = join(HOME, ".codex", "prompts");
345
294
  if (existsSync(codexPromptsDir)) {
346
295
  try {
347
- const promptFiles = new Set();
348
- for (const manifestName of ["ecc-prompts-manifest.txt", "ecc-extension-prompts-manifest.txt"]) {
349
- const manifestPath = join(codexPromptsDir, manifestName);
350
- if (!existsSync(manifestPath)) continue;
351
- try {
352
- const entries = readFileSync(manifestPath, "utf8")
353
- .split(/\r?\n/)
354
- .map((line) => line.trim())
355
- .filter(Boolean);
356
- for (const entry of entries) promptFiles.add(entry);
357
- } catch { /* best effort */ }
358
- rmSync(manifestPath, { force: true });
359
- }
360
-
361
- for (const file of promptFiles) {
362
- const promptPath = join(codexPromptsDir, file);
363
- if (existsSync(promptPath)) {
364
- rmSync(promptPath, { force: true });
296
+ for (const file of readdirSync(codexPromptsDir)) {
297
+ if (file.startsWith("ecc-") && file.endsWith(".md")) {
298
+ rmSync(join(codexPromptsDir, file), { force: true });
365
299
  removed++;
366
300
  }
367
301
  }
368
-
369
- for (const file of readdirSync(codexPromptsDir)) {
370
- const isLegacyGeneratedPrompt = file.endsWith(".md")
371
- && (file.startsWith("ecc-") || file.startsWith("aw-"));
372
- if (!isLegacyGeneratedPrompt) continue;
373
-
374
- rmSync(join(codexPromptsDir, file), { force: true });
375
- removed++;
376
- }
377
-
378
302
  pruneEmptyParents(codexPromptsDir, join(HOME, ".codex"));
379
303
  } catch { /* best effort */ }
380
304
  }