@ghl-ai/aw 0.1.24 → 0.1.25-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/apply.mjs CHANGED
@@ -17,9 +17,14 @@ function writeConflictMarkers(filePath, localContent, registryContent) {
17
17
  /**
18
18
  * Apply actions to the workspace.
19
19
  * All actions are file-level (no directory-level operations).
20
- * Returns count of files with conflicts.
20
+ *
21
+ * @param {Array} actions
22
+ * @param {{ teamNS?: string }} opts
23
+ * teamNS — when set, replaces all occurrences of `$TEAM_NS` in file content
24
+ * with this value. Used when pulling [template] as a renamed team namespace.
25
+ * @returns {number} count of files with conflicts
21
26
  */
22
- export function applyActions(actions) {
27
+ export function applyActions(actions, { teamNS } = {}) {
23
28
  let conflicts = 0;
24
29
 
25
30
  for (const act of actions) {
@@ -27,12 +32,18 @@ export function applyActions(actions) {
27
32
  case 'ADD':
28
33
  case 'UPDATE':
29
34
  mkdirSync(dirname(act.targetPath), { recursive: true });
30
- cpSync(act.sourcePath, act.targetPath);
35
+ if (teamNS && act.sourcePath.endsWith('.md')) {
36
+ const content = readFileSync(act.sourcePath, 'utf8').replaceAll('$TEAM_NS', teamNS);
37
+ writeFileSync(act.targetPath, content);
38
+ } else {
39
+ cpSync(act.sourcePath, act.targetPath);
40
+ }
31
41
  break;
32
42
 
33
43
  case 'CONFLICT': {
34
44
  const local = existsSync(act.targetPath) ? readFileSync(act.targetPath, 'utf8') : '';
35
- const registry = readFileSync(act.sourcePath, 'utf8');
45
+ let registry = readFileSync(act.sourcePath, 'utf8');
46
+ if (teamNS) registry = registry.replaceAll('$TEAM_NS', teamNS);
36
47
  mkdirSync(dirname(act.targetPath), { recursive: true });
37
48
  writeConflictMarkers(act.targetPath, local, registry);
38
49
  conflicts++;
package/cli.mjs CHANGED
@@ -71,8 +71,8 @@ function printHelp() {
71
71
  const sec = (title) => `\n ${chalk.bold.underline(title)}`;
72
72
  const help = [
73
73
  sec('Setup'),
74
- cmd('aw init', 'Initialize workspace & pull ghl platform layer'),
75
- cmd('aw init --namespace <team>', 'Init with ghl + team template'),
74
+ cmd('aw init --namespace <team>', 'Initialize workspace (required)'),
75
+ ` ${chalk.dim('Teams: revex, mobile, commerce, leadgen, crm, marketplace, ai')}`,
76
76
 
77
77
  sec('Download'),
78
78
  cmd('aw pull', 'Re-pull all synced paths (like git pull)'),
package/commands/drop.mjs CHANGED
@@ -81,7 +81,7 @@ function deleteMatchingFiles(workspaceDir, path) {
81
81
 
82
82
  /**
83
83
  * Convert manifest key to registry path.
84
- * Manifest key now mirrors registry: "ghl/agents/architecture-reviewer.md" → "ghl/agents/architecture-reviewer"
84
+ * Manifest key now mirrors registry: "platform/agents/architecture-reviewer.md" → "platform/agents/architecture-reviewer"
85
85
  */
86
86
  function manifestKeyToRegistryPath(manifestKey) {
87
87
  return manifestKey.replace(/\.md$/, '');
package/commands/init.mjs CHANGED
@@ -112,24 +112,45 @@ function saveManifest(data) {
112
112
  }
113
113
 
114
114
 
115
+ const ALLOWED_NAMESPACES = ['revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'];
116
+
115
117
  export async function initCommand(args) {
116
118
  const namespace = args['--namespace'] || null;
117
119
  let user = args['--user'] || '';
120
+ const silent = args['--silent'] === true;
118
121
 
119
122
  fmt.intro(`aw init ${chalk.dim('v' + VERSION)}`);
120
123
 
121
124
  // ── Validate ──────────────────────────────────────────────────────────
122
125
 
123
- if (namespace && !/^[a-z][a-z0-9-]{1,38}[a-z0-9]$/.test(namespace)) {
124
- fmt.cancel(`Invalid namespace '${namespace}' — must match: ^[a-z][a-z0-9-]{1,38}[a-z0-9]$`);
126
+ if (!namespace && !silent) {
127
+ const list = ALLOWED_NAMESPACES.map(n => chalk.cyan(n)).join(', ');
128
+ fmt.cancel([
129
+ `Missing required ${chalk.bold('--namespace')} flag`,
130
+ '',
131
+ ` ${chalk.dim('Usage:')} aw init --namespace <team>`,
132
+ ` ${chalk.dim('Teams:')} ${list}`,
133
+ '',
134
+ ` ${chalk.dim('Example:')} ${chalk.bold('aw init --namespace commerce')}`,
135
+ ].join('\n'));
125
136
  }
126
137
 
127
- if (namespace === 'ghl') {
128
- fmt.cancel("'ghl' is a reserved namespace it is the shared platform layer");
138
+ if (namespace && !ALLOWED_NAMESPACES.includes(namespace)) {
139
+ const list = ALLOWED_NAMESPACES.map(n => chalk.cyan(n)).join(', ');
140
+ fmt.cancel([
141
+ `Unknown namespace ${chalk.red(namespace)}`,
142
+ '',
143
+ ` ${chalk.dim('Allowed:')} ${list}`,
144
+ '',
145
+ ` ${chalk.dim('Example:')} ${chalk.bold('aw init --namespace commerce')}`,
146
+ ].join('\n'));
147
+ }
148
+
149
+ if (namespace && !/^[a-z][a-z0-9-]{1,38}[a-z0-9]$/.test(namespace)) {
150
+ fmt.cancel(`Invalid namespace '${namespace}' — must match: ^[a-z][a-z0-9-]{1,38}[a-z0-9]$`);
129
151
  }
130
152
 
131
153
  const isExisting = config.exists(GLOBAL_AW_DIR);
132
- const silent = args['--silent'] === true;
133
154
  const cwd = process.cwd();
134
155
 
135
156
  // ── Fast path: already initialized → just pull + link ─────────────────
package/commands/nuke.mjs CHANGED
@@ -14,7 +14,7 @@ const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
14
14
  const MANIFEST_PATH = join(GLOBAL_AW_DIR, '.aw-manifest.json');
15
15
 
16
16
  const IDE_DIRS = ['.claude', '.cursor', '.codex', '.agents'];
17
- const CONTENT_TYPES = ['agents', 'skills', 'commands', 'blueprints', 'evals'];
17
+ const CONTENT_TYPES = ['agents', 'skills', 'commands', 'evals'];
18
18
 
19
19
  function loadManifest() {
20
20
  if (!existsSync(MANIFEST_PATH)) return null;
package/commands/pull.mjs CHANGED
@@ -157,7 +157,7 @@ export function pullCommand(args) {
157
157
  // Apply
158
158
  const s2 = log.spinner();
159
159
  s2.start('Applying changes...');
160
- const conflictCount = applyActions(actions);
160
+ const conflictCount = applyActions(actions, { teamNS: renameNamespace || undefined });
161
161
  updateManifest(workspaceDir, actions, cfg.namespace);
162
162
  s2.stop('Changes applied');
163
163
 
@@ -212,7 +212,7 @@ function printDryRun(actions, verbose) {
212
212
  const counts = { ADD: 0, UPDATE: 0, CONFLICT: 0, ORPHAN: 0, UNCHANGED: 0 };
213
213
  const lines = [];
214
214
 
215
- for (const type of ['agents', 'skills', 'commands', 'blueprints', 'evals']) {
215
+ for (const type of ['agents', 'skills', 'commands', 'evals']) {
216
216
  const items = actions.filter(a => a.type === type);
217
217
  if (items.length === 0) continue;
218
218
 
@@ -236,7 +236,7 @@ function printDryRun(actions, verbose) {
236
236
  function printSummary(actions, verbose, conflictCount) {
237
237
  const conflicts = actions.filter(a => a.action === 'CONFLICT');
238
238
 
239
- for (const type of ['agents', 'skills', 'commands', 'blueprints', 'evals']) {
239
+ for (const type of ['agents', 'skills', 'commands', 'evals']) {
240
240
  const typeActions = actions.filter(a => a.type === type);
241
241
  if (typeActions.length === 0) continue;
242
242
 
package/commands/push.mjs CHANGED
@@ -64,15 +64,26 @@ export function pushCommand(args) {
64
64
  }
65
65
 
66
66
  // Parse registry path to get type, namespace, slug
67
+ // The path must contain agents/, skills/, or commands/ as a parent directory.
68
+ // Nesting before that doesn't matter (e.g. platform/services/agents/ is fine).
67
69
  const regParts = resolved.registryPath.split('/');
68
70
  let typeIdx = -1;
69
- const validTypes = ['agents', 'skills', 'commands', 'blueprints', 'evals'];
70
- for (let i = 0; i < regParts.length; i++) {
71
- if (validTypes.includes(regParts[i])) { typeIdx = i; break; }
71
+ const PUSHABLE_TYPES = ['agents', 'skills', 'commands', 'evals'];
72
+ for (let i = regParts.length - 1; i >= 0; i--) {
73
+ if (PUSHABLE_TYPES.includes(regParts[i])) { typeIdx = i; break; }
72
74
  }
73
75
 
74
76
  if (typeIdx === -1 || typeIdx + 1 >= regParts.length) {
75
- fmt.cancel(`Could not determine type from path: ${resolved.registryPath}`);
77
+ fmt.cancel([
78
+ `Invalid push path: ${chalk.red(resolved.registryPath)}`,
79
+ '',
80
+ ` Content must live under an ${chalk.bold('agents/')}, ${chalk.bold('skills/')}, ${chalk.bold('commands/')}, or ${chalk.bold('evals/')} directory.`,
81
+ '',
82
+ ` ${chalk.dim('Valid examples:')}`,
83
+ ` aw push .aw_registry/commerce/quality/agents/unit-tester.md`,
84
+ ` aw push .aw_registry/platform/services/skills/development`,
85
+ ` aw push .aw_registry/commerce/shared/commands/ship.md`,
86
+ ].join('\n'));
76
87
  }
77
88
 
78
89
  const namespaceParts = regParts.slice(0, typeIdx);
@@ -94,7 +94,7 @@ function searchLocal(workspaceDir, query) {
94
94
  if (!nsEntry.isDirectory() || nsEntry.name.startsWith('.')) continue;
95
95
  const ns = nsEntry.name;
96
96
 
97
- for (const type of ['agents', 'skills', 'commands', 'blueprints', 'evals']) {
97
+ for (const type of ['agents', 'skills', 'commands', 'evals']) {
98
98
  const typeDir = join(workspaceDir, ns, type);
99
99
  if (!existsSync(typeDir)) continue;
100
100
 
package/constants.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // constants.mjs — Single source of truth for registry settings.
2
2
 
3
3
  /** Base branch for PRs and sync checkout */
4
- export const REGISTRY_BASE_BRANCH = 'master';
4
+ export const REGISTRY_BASE_BRANCH = 'chore/restructure';
5
5
 
6
6
  /** Default registry repository */
7
7
  export const REGISTRY_REPO = 'GoHighLevel/ghl-agentic-workspace';
package/git.mjs CHANGED
@@ -48,7 +48,7 @@ export function cleanup(tempDir) {
48
48
 
49
49
  /**
50
50
  * Compute sparse checkout paths from include paths.
51
- * e.g., ["ghl", "dev/agents/debugger"] -> ["registry/ghl", "registry/dev/agents/debugger"]
51
+ * e.g., ["platform", "dev/agents/debugger"] -> ["registry/platform", "registry/dev/agents/debugger"]
52
52
  */
53
53
  export function includeToSparsePaths(paths) {
54
54
  const result = new Set();
package/glob.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // glob.mjs — Simple path matching. Zero dependencies.
2
2
  // No wildcards. Path prefix matching only.
3
- // "ghl" matches "ghl/agents/foo", "ghl/skills/bar", etc.
4
- // "ghl/agents/foo" matches exactly "ghl/agents/foo"
3
+ // "platform" matches "platform/agents/foo", "platform/skills/bar", etc.
4
+ // "platform/agents/foo" matches exactly "platform/agents/foo"
5
5
  // "dev/skills/my-skill/references" matches "dev/skills/my-skill" (parent match)
6
6
 
7
7
  /**
package/integrate.mjs CHANGED
@@ -131,7 +131,7 @@ Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\`
131
131
 
132
132
  ## Routing Rule (ABSOLUTE)
133
133
 
134
- > **Every non-trivial task MUST call \`Skill(skill: "ghl-ai-task-router")\` BEFORE any response.**
134
+ > **Every non-trivial task MUST call \`Skill(skill: "platform-ai-task-router")\` BEFORE any response.**
135
135
  >
136
136
  > **Trivial** (do directly): typo fixes, single-line edits, git ops, file exploration, factual code questions.
137
137
  >
@@ -143,17 +143,16 @@ Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\`
143
143
 
144
144
  \`\`\`
145
145
  .aw_registry/
146
- ├── ghl/ # Platform layer (shared, read-only)
146
+ ├── platform/ # Platform layer (shared, read-only)
147
147
  │ ├── agents/ # 15 platform agents (reviewers, engineers, designer)
148
148
  │ ├── skills/ # 77 platform skills (SKILL.md files)
149
- │ ├── commands/ # Platform commands (health-check, help, discover)
149
+ │ ├── commands/ # Platform commands (grafana-health, help, discover)
150
150
  │ └── evals/ # Platform agent & skill quality tests
151
151
 
152
152
  ├── ${team}/ # Team layer (from template, customizable)
153
153
  │ ├── agents/ # Team agents (developers, testers, PM, coordinator)
154
154
  │ ├── skills/ # Team-specific skills
155
155
  │ ├── commands/ # Workflow commands (ship, plan, review, deploy, etc.)
156
- │ ├── blueprints/ # Development workflow blueprints
157
156
  │ └── evals/ # Team agent quality tests
158
157
 
159
158
  └── .sync-config.json # Tracks synced paths and team namespace
@@ -170,7 +169,7 @@ Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\`
170
169
  └── tasks/BOARD.md # Task board
171
170
  \`\`\`
172
171
 
173
- Symlinked to \`.claude/{agents,skills,commands/aw,blueprints,evals}\`.
172
+ Symlinked to \`.claude/{agents,skills,commands/aw,evals}\`.
174
173
 
175
174
  ## Local-First Operations
176
175
 
@@ -203,29 +202,29 @@ stitch/* → External design generation
203
202
 
204
203
  ## Symlink Naming
205
204
 
206
- All symlinks are prefixed with their namespace (\`ghl-\` or \`${team}-\`):
207
- - \`.aw_registry/ghl/agents/security-reviewer.md\` → \`.claude/agents/ghl-security-reviewer.md\`
208
- - \`.aw_registry/${team}/agents/frontend-developer.md\` → \`.claude/agents/${team}-frontend-developer.md\`
205
+ All symlinks are prefixed with their namespace (\`platform-\` or \`${team}-\`):
206
+ - \`.aw_registry/platform/review/agents/security-reviewer.md\` → \`.claude/agents/platform-review-security-reviewer.md\`
207
+ - \`.aw_registry/${team}/frontend/agents/frontend-developer.md\` → \`.claude/agents/${team}-frontend-developer.md\`
209
208
  - \`.aw_registry/${team}/commands/ship.md\` → \`.claude/commands/aw/${team}-ship.md\`
210
209
 
211
210
  ## Dependency Rule
212
211
 
213
- - **ghl/ is self-contained** — platform agents reference only platform skills
214
- - **${team}/ can reference ghl/** — team agents use \`ghl-*\` prefixed skill names
215
- - **ghl/ never references ${team}/** — platform layer knows nothing about team-specific resources
212
+ - **platform/ is self-contained** — platform agents reference only platform skills
213
+ - **${team}/ can reference platform/** — team agents use \`platform-*\` prefixed skill names
214
+ - **platform/ never references ${team}/** — platform layer knows nothing about team-specific resources
216
215
 
217
216
  ## How Agents Connect to Skills
218
217
 
219
218
  Agents declare skills in frontmatter \`skills:\` list:
220
219
  \`\`\`yaml
221
- # ghl/agents/security-reviewer.md — platform skills directly
220
+ # platform/review/agents/security-reviewer.md — platform skills directly
222
221
  skills:
223
- - security-review
222
+ - platform-review-security-review
224
223
  - platform-services-authentication-authorization
225
224
 
226
- # ${team}/agents/frontend-developer.md — ghl- prefix for platform skills
225
+ # ${team}/frontend/agents/frontend-developer.md — platform- prefix for platform skills
227
226
  skills:
228
- - ghl-vue-development # → ghl/skills/vue-development
227
+ - platform-frontend-vue-development # → platform/frontend/skills/vue-development
229
228
  - create-prd # → ${team}/skills/create-prd (local)
230
229
  \`\`\`
231
230
 
@@ -272,11 +271,11 @@ Two-layer agent system with local-first orchestration. Agents are loaded from \`
272
271
  Commands load agents entirely from local files — no MCP calls for agent/skill loading:
273
272
 
274
273
  \`\`\`
275
- 1. Read .aw_registry/ghl/agents/<slug>.md → Agent identity, skills[] list, capabilities
274
+ 1. Read .aw_registry/platform/agents/<slug>.md → Agent identity, skills[] list, capabilities
276
275
  OR .aw_registry/${team}/agents/<slug>.md
277
276
 
278
277
  2. For each skill in the agent's skills[] frontmatter:
279
- Read .aw_registry/ghl/skills/<slug>/SKILL.md → Full skill body (patterns, checklists)
278
+ Read .aw_registry/platform/skills/<slug>/SKILL.md → Full skill body (patterns, checklists)
280
279
  OR .aw_registry/${team}/skills/<slug>/SKILL.md
281
280
 
282
281
  3. Read .aw_docs/learnings/<slug>.md → Prior learnings from past runs
@@ -284,72 +283,72 @@ Commands load agents entirely from local files — no MCP calls for agent/skill
284
283
  4. Call MCP memory/search({query: "..."}) → Recalled memories (only MCP call)
285
284
  \`\`\`
286
285
 
287
- ### Platform Agents (ghl-)
286
+ ### Platform Agents (platform-)
288
287
 
289
- Shared infrastructure agents — always available, self-contained. Loaded from \`.aw_registry/ghl/agents/\`.
288
+ Shared infrastructure agents — always available, self-contained. Loaded from \`.aw_registry/platform/agents/\`.
290
289
 
291
290
  | Agent | Role | Model Tier |
292
291
  |-------|------|------------|
293
- | \`ghl-architecture-reviewer\` | Architecture review, ADR compliance, coupling detection | Sonnet |
294
- | \`ghl-security-reviewer\` | Security review, auth patterns, tenant isolation | Sonnet |
295
- | \`ghl-performance-reviewer\` | Performance review, bundle analysis, N+1 detection | Sonnet |
296
- | \`ghl-frontend-engineer\` | MFA architecture, event-bus, shared packages | Sonnet |
297
- | \`ghl-services-engineer\` | Inter-service comms, workers, circuit breakers | Sonnet |
298
- | \`ghl-infra-engineer\` | Kubernetes, Terraform, Jenkins, deployment | Sonnet |
299
- | \`ghl-database-engineer\` | MongoDB, Redis, Elasticsearch, Firestore, migrations | Sonnet |
300
- | \`ghl-product-designer\` | HighRise design system, accessibility, typography | Sonnet |
301
- | \`ghl-frontend-core-reviewer\` | Vue 3 patterns, composables, Pinia stores | Sonnet |
302
- | \`ghl-frontend-i18n-reviewer\` | i18n compliance, translation keys | Haiku |
303
- | \`ghl-maintainability-reviewer\` | Code duplication, naming, complexity | Haiku |
304
- | \`ghl-reliability-reviewer\` | Error handling, null guards, SSR safety | Haiku |
305
- | \`ghl-sdet-engineer\` | Playwright POM, quality gates, performance testing | Haiku |
306
- | \`ghl-devops-automator\` | Docker, Jenkins pipelines, deployment strategies | Haiku |
307
- | \`ghl-data-engineer\` | CDC pipelines, ClickHouse, analytics data models | Haiku |
292
+ | \`platform-review-architecture-reviewer\` | Architecture review, ADR compliance, coupling detection | Sonnet |
293
+ | \`platform-review-security-reviewer\` | Security review, auth patterns, tenant isolation | Sonnet |
294
+ | \`platform-review-performance-reviewer\` | Performance review, bundle analysis, N+1 detection | Sonnet |
295
+ | \`platform-frontend-code-writer\` | MFA architecture, event-bus, shared packages | Sonnet |
296
+ | \`platform-services-engineer\` | Inter-service comms, workers, circuit breakers | Sonnet |
297
+ | \`platform-infra-engineer\` | Kubernetes, Terraform, Jenkins, deployment | Sonnet |
298
+ | \`platform-data-db-engineer\` | MongoDB, Redis, Elasticsearch, Firestore, migrations | Sonnet |
299
+ | \`platform-design-designer\` | HighRise design system, accessibility, typography | Sonnet |
300
+ | \`platform-frontend-code-reviewer\` | Vue 3 patterns, composables, Pinia stores | Sonnet |
301
+ | \`platform-frontend-i18n-reviewer\` | i18n compliance, translation keys | Haiku |
302
+ | \`platform-review-maintainability-reviewer\` | Code duplication, naming, complexity | Haiku |
303
+ | \`platform-review-reliability-reviewer\` | Error handling, null guards, SSR safety | Haiku |
304
+ | \`platform-sdet-engineer\` | Playwright POM, quality gates, performance testing | Haiku |
305
+ | \`platform-devops-automator\` | Docker, Jenkins pipelines, deployment strategies | Haiku |
306
+ | \`platform-data-engineer\` | CDC pipelines, ClickHouse, analytics data models | Haiku |
308
307
 
309
308
  ### Team Agents (${team}-)
310
309
 
311
- Team-specific agents — customizable. Loaded from \`.aw_registry/${team}/agents/\`. Can reference platform skills via \`ghl-*\` prefix.
310
+ Team-specific agents — customizable. Loaded from \`.aw_registry/${team}/agents/\`. Can reference platform skills via \`platform-*\` prefix.
312
311
 
313
312
  | Agent | Role | Model Tier |
314
313
  |-------|------|------------|
315
- | \`${team}-coordinator\` | Task decomposition, quality gate enforcement, orchestration | Opus |
316
- | \`${team}-architect\` | ADR creation, data flow design, system architecture | Opus |
314
+ | \`${team}-shared-coordinator\` | Task decomposition, quality gate enforcement, orchestration | Opus |
315
+ | \`${team}-shared-architect\` | ADR creation, data flow design, system architecture | Opus |
317
316
  | \`${team}-product-manager\` | PRDs, feature prioritization, sprint planning | Opus |
318
317
  | \`${team}-frontend-developer\` | Vue components, composables, TypeScript | Sonnet |
319
318
  | \`${team}-backend-developer\` | NestJS services, API endpoints, workers | Sonnet |
320
- | \`${team}-manager\` | Spec writing, user stories, team coordination | Sonnet |
321
- | \`${team}-unit-tester\` | Component tests, composable tests, BDD | Haiku |
322
- | \`${team}-e2e-tester\` | Cypress tests, Gherkin scenarios | Haiku |
323
- | \`${team}-mutation-tester\` | Stryker mutation analysis, test improvement | Haiku |
324
- | \`${team}-release-engineer\` | Release checklists, rollback plans, deploy | Sonnet |
319
+ | \`${team}-product-owner\` | Spec writing, user stories, team coordination | Sonnet |
320
+ | \`${team}-quality-unit-tester\` | Component tests, composable tests, BDD | Haiku |
321
+ | \`${team}-quality-e2e-tester\` | Cypress tests, Gherkin scenarios | Haiku |
322
+ | \`${team}-quality-mutation-tester\` | Stryker mutation analysis, test improvement | Haiku |
323
+ | \`${team}-infra-release-engineer\` | Release checklists, rollback plans, deploy | Sonnet |
325
324
  | \`${team}-design-reviewer\` | Design token audit, accessibility review | Haiku |
326
325
  | \`${team}-design-auditor\` | Dark mode audit, design token verification | Haiku |
327
- | \`${team}-technical-writer\` | API docs, migration guides | Haiku |
328
- | \`${team}-api-tester\` | API contract tests, auth flow tests | Haiku |
329
- | \`${team}-visual-tester\` | Screenshot comparison, visual test plans | Haiku |
330
- | \`${team}-responsive-tester\` | Breakpoint audit, touch target compliance | Haiku |
331
- | \`${team}-performance-benchmarker\` | Core Web Vitals, k6 scripts | Haiku |
332
- | \`${team}-stitch-designer\` | Multi-screen flows, screen prompts | Haiku |
333
- | \`${team}-animation-specialist\` | Transitions, reduced motion compliance | Haiku |
334
- | \`${team}-ux-researcher\` | Interview scripts, usability test plans | Haiku |
335
- | \`${team}-onboarding-optimizer\` | Empty state design, onboarding flow critique | Haiku |
326
+ | \`${team}-shared-technical-writer\` | API docs, migration guides | Haiku |
327
+ | \`${team}-quality-api-tester\` | API contract tests, auth flow tests | Haiku |
328
+ | \`${team}-quality-visual-tester\` | Screenshot comparison, visual test plans | Haiku |
329
+ | \`${team}-quality-responsive-tester\` | Breakpoint audit, touch target compliance | Haiku |
330
+ | \`${team}-quality-performance-benchmarker\` | Core Web Vitals, k6 scripts | Haiku |
331
+ | \`${team}-design-stitch\` | Multi-screen flows, screen prompts | Haiku |
332
+ | \`${team}-design-animation-specialist\` | Transitions, reduced motion compliance | Haiku |
333
+ | \`${team}-design-ux-researcher\` | Interview scripts, usability test plans | Haiku |
334
+ | \`${team}-product-onboarding-optimizer\` | Empty state design, onboarding flow critique | Haiku |
336
335
 
337
336
  ### Skill Resolution
338
337
 
339
338
  Agents declare skills in their frontmatter \`skills:\` list. Commands read SKILL.md files directly from disk:
340
339
 
341
340
  - **No prefix** → \`.aw_registry/${team}/skills/<name>/SKILL.md\`
342
- - **\`ghl-\` prefix** → \`.aw_registry/ghl/skills/<name-without-ghl->/SKILL.md\`
341
+ - **\`platform-\` prefix** → \`.aw_registry/platform/skills/<name-without-platform->/SKILL.md\`
343
342
 
344
343
  \`\`\`yaml
345
- # .aw_registry/ghl/agents/security-reviewer.md — platform skills directly
344
+ # .aw_registry/platform/agents/security-reviewer.md — platform skills directly
346
345
  skills:
347
- - security-review # → .aw_registry/ghl/skills/security-review/SKILL.md
348
- - platform-services-authentication-authorization # → .aw_registry/ghl/skills/platform-services-.../SKILL.md
346
+ - platform-review-security-review # → .aw_registry/platform/review/skills/security-review/SKILL.md
347
+ - platform-services-authentication-authorization # → .aw_registry/platform/services/skills/authentication-authorization/SKILL.md
349
348
 
350
- # .aw_registry/${team}/agents/frontend-developer.md — ghl- prefix for platform skills
349
+ # .aw_registry/${team}/frontend/agents/frontend-developer.md — platform- prefix for platform skills
351
350
  skills:
352
- - ghl-vue-development # → .aw_registry/ghl/skills/vue-development/SKILL.md
351
+ - platform-frontend-vue-development # → .aw_registry/platform/frontend/skills/vue-development/SKILL.md
353
352
  - create-prd # → .aw_registry/${team}/skills/create-prd/SKILL.md
354
353
  \`\`\`
355
354
 
@@ -364,8 +363,8 @@ skills:
364
363
  /aw:${team}-brainstorm # Requirement exploration with architect + PM
365
364
 
366
365
  # Via Agent tool (direct invocation — no run tracking)
367
- Agent(subagent_type: "ghl:security-reviewer", prompt: "Review this PR for security issues")
368
- Agent(subagent_type: "${team}:frontend-developer", prompt: "Create a Vue component for...")
366
+ Agent(subagent_type: "platform-review:security-reviewer", prompt: "Review this PR for security issues")
367
+ Agent(subagent_type: "${team}-frontend:developer", prompt: "Create a Vue component for...")
369
368
  \`\`\`
370
369
 
371
370
  ### Learnings & Knowledge
@@ -374,8 +373,8 @@ Agent knowledge accumulates across runs via \`.aw_docs/learnings/\`:
374
373
 
375
374
  \`\`\`
376
375
  .aw_docs/learnings/
377
- ├── backend-developer.md # Learnings from all runs where this agent worked
378
- ├── frontend-developer.md # Appended after each workflow close
376
+ ├── backend-developer.md # Learnings from all runs where this agent worked
377
+ ├── frontend-developer.md # Appended after each workflow close
379
378
  ├── architecture-reviewer.md # Prior learnings loaded before each run
380
379
  └── _pending-sync.jsonl # Queue for eager sync to MCP memory/store
381
380
  \`\`\`
@@ -386,12 +385,12 @@ Before each run, prior learnings are read from these files and injected into age
386
385
 
387
386
  Each agent has quality test cases in \`.claude/evals/\`:
388
387
  \`\`\`
389
- .claude/evals/ghl-agents-security-reviewer/ # Platform agent evals
390
- .claude/evals/${team}-agents-frontend-developer/ # Team agent evals
391
- .claude/evals/ghl-skills-vue-development/ # Skill evals
388
+ .claude/evals/platform-agents-security-reviewer/ # Platform agent evals
389
+ .claude/evals/${team}-frontend-agents-frontend-developer/ # Team agent evals
390
+ .claude/evals/platform-frontend-skills-vue-development/ # Skill evals
392
391
  \`\`\`
393
392
 
394
- Run with: \`/ghl:eval agent:<slug>\` or \`/ghl:eval skill:<slug>\`
393
+ Run with: \`/platform:eval agent:<slug>\` or \`/platform:eval skill:<slug>\`
395
394
  `;
396
395
  }
397
396
 
package/link.mjs CHANGED
@@ -7,7 +7,8 @@ import * as fmt from './fmt.mjs';
7
7
 
8
8
  const IDE_DIRS = ['.claude', '.cursor', '.codex'];
9
9
  // Per-file symlink types
10
- const FILE_TYPES = ['agents', 'blueprints'];
10
+ const FILE_TYPES = ['agents'];
11
+ const ALL_KNOWN_TYPES = new Set([...FILE_TYPES, 'skills', 'commands', 'evals']);
11
12
 
12
13
  /**
13
14
  * List namespace directories inside .aw_registry/ (skip dotfiles).
@@ -29,40 +30,61 @@ function listDirs(dir) {
29
30
  .map(d => d.name);
30
31
  }
31
32
 
33
+ /**
34
+ * Recursively find directories named `typeName` within `nsDir`,
35
+ * skipping other known type directories to avoid false matches.
36
+ * Returns [{typeDirPath, segments}] where segments are the intermediate
37
+ * directory names between nsDir and the type dir.
38
+ *
39
+ * Example: nsDir=.aw_registry/platform, typeName='agents'
40
+ * platform/agents/ → { typeDirPath: '.../agents', segments: [] }
41
+ * platform/frontend/agents/ → { typeDirPath: '.../frontend/agents', segments: ['frontend'] }
42
+ */
43
+ function findNestedTypeDirs(nsDir, typeName) {
44
+ const results = [];
45
+ function walk(dir, segments) {
46
+ if (!existsSync(dir)) return;
47
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
48
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
49
+ if (entry.name === typeName) {
50
+ results.push({ typeDirPath: join(dir, entry.name), segments });
51
+ } else if (!ALL_KNOWN_TYPES.has(entry.name)) {
52
+ walk(join(dir, entry.name), [...segments, entry.name]);
53
+ }
54
+ }
55
+ }
56
+ walk(nsDir, []);
57
+ return results;
58
+ }
59
+
32
60
  /**
33
61
  * Clean all symlinks from IDE type directories before re-linking.
34
62
  */
35
63
  function cleanIdeSymlinks(cwd) {
36
64
  for (const ide of IDE_DIRS) {
37
- for (const type of [...FILE_TYPES, 'skills', 'evals']) {
38
- const dir = join(cwd, ide, type);
39
- if (!existsSync(dir)) continue;
40
- for (const entry of readdirSync(dir)) {
41
- const p = join(dir, entry);
42
- try {
43
- if (lstatSync(p).isSymbolicLink()) unlinkSync(p);
44
- } catch { /* best effort */ }
45
- }
46
- }
47
- // Clean command symlinks (both flat legacy and namespaced aw/)
48
- const cmdDir = join(cwd, ide, 'commands');
49
- if (!existsSync(cmdDir)) continue;
50
- for (const entry of readdirSync(cmdDir)) {
51
- const p = join(cmdDir, entry);
52
- try {
53
- if (lstatSync(p).isSymbolicLink()) unlinkSync(p);
54
- } catch { /* best effort */ }
55
- }
56
- // Clean inside commands/aw/ namespace dir
57
- const awCmdDir = join(cmdDir, 'aw');
58
- if (existsSync(awCmdDir)) {
59
- for (const entry of readdirSync(awCmdDir)) {
60
- const p = join(awCmdDir, entry);
61
- try {
62
- if (lstatSync(p).isSymbolicLink()) unlinkSync(p);
63
- } catch { /* best effort */ }
65
+ const ideDir = join(cwd, ide);
66
+ if (!existsSync(ideDir)) continue;
67
+ cleanSymlinksRecursive(ideDir);
68
+ }
69
+ // Also clean .agents/skills/
70
+ const agentsSkillsDir = join(cwd, '.agents', 'skills');
71
+ if (existsSync(agentsSkillsDir)) cleanSymlinksRecursive(agentsSkillsDir);
72
+ }
73
+
74
+ /**
75
+ * Remove all symlinks in a directory (non-recursive into subdirs,
76
+ * but does walk immediate child directories one level deep).
77
+ */
78
+ function cleanSymlinksRecursive(dir) {
79
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
80
+ const p = join(dir, entry.name);
81
+ try {
82
+ if (lstatSync(p).isSymbolicLink()) {
83
+ unlinkSync(p);
84
+ } else if (entry.isDirectory()) {
85
+ cleanSymlinksRecursive(p);
64
86
  }
65
- }
87
+ } catch { /* best effort */ }
66
88
  }
67
89
  }
68
90
 
@@ -76,10 +98,14 @@ function flatName(ns, name) {
76
98
  /**
77
99
  * Create/refresh all IDE symlinks.
78
100
  *
79
- * - agents, blueprints: per-file symlinks with flat names
101
+ * - agents: per-file symlinks with flat names
80
102
  * - skills: per-skill directory symlinks with flat names
81
103
  * - commands: directory symlink per namespace → .claude/commands/<ns>/
82
104
  * (Claude Code needs a directory at .claude/commands/<ns>/ for /<ns>:* autocomplete)
105
+ *
106
+ * Note: [template] namespace is renamed to the team name by `aw init`
107
+ * (via pullCommand with _renameNamespace) before linking. $TEAM_NS in
108
+ * file content is resolved by LLMs at runtime using AW-PROTOCOL.md context.
83
109
  */
84
110
  export function linkWorkspace(cwd) {
85
111
  const awDir = join(cwd, '.aw_registry');
@@ -93,19 +119,37 @@ export function linkWorkspace(cwd) {
93
119
  const namespaces = listNamespaceDirs(awDir);
94
120
 
95
121
  for (const ns of namespaces) {
96
- // Per-file types: agents, blueprints
122
+ // Per-file types: agents (recursive for nested domain dirs)
97
123
  for (const type of FILE_TYPES) {
98
- const typeDir = join(awDir, ns, type);
99
- if (!existsSync(typeDir)) continue;
124
+ for (const { typeDirPath, segments } of findNestedTypeDirs(join(awDir, ns), type)) {
125
+ for (const file of readdirSync(typeDirPath).filter(f => f.endsWith('.md') && !f.startsWith('.'))) {
126
+ const flat = [ns, ...segments, file].join('-');
127
+
128
+ for (const ide of IDE_DIRS) {
129
+ const linkDir = join(cwd, ide, type);
130
+ mkdirSync(linkDir, { recursive: true });
131
+ const linkPath = join(linkDir, flat);
132
+ const targetPath = join(typeDirPath, file);
133
+ const relTarget = relative(linkDir, targetPath);
134
+ try {
135
+ execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
136
+ created++;
137
+ } catch { /* best effort */ }
138
+ }
139
+ }
140
+ }
141
+ }
100
142
 
101
- for (const file of readdirSync(typeDir).filter(f => f.endsWith('.md') && !f.startsWith('.'))) {
102
- const flat = flatName(ns, file);
143
+ // Skills: per-skill directory symlinks (recursive for nested domain dirs)
144
+ for (const { typeDirPath: skillsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'skills')) {
145
+ for (const skill of listDirs(skillsDir)) {
146
+ const flat = [ns, ...segments, skill].join('-');
103
147
 
104
148
  for (const ide of IDE_DIRS) {
105
- const linkDir = join(cwd, ide, type);
149
+ const linkDir = join(cwd, ide, 'skills');
106
150
  mkdirSync(linkDir, { recursive: true });
107
151
  const linkPath = join(linkDir, flat);
108
- const targetPath = join(awDir, ns, type, file);
152
+ const targetPath = join(skillsDir, skill);
109
153
  const relTarget = relative(linkDir, targetPath);
110
154
  try {
111
155
  execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
@@ -114,19 +158,42 @@ export function linkWorkspace(cwd) {
114
158
  }
115
159
  }
116
160
  }
161
+ }
117
162
 
118
- // Skills: per-skill directory symlinks
119
- const skillsDir = join(awDir, ns, 'skills');
120
- if (!existsSync(skillsDir)) continue;
121
- for (const skill of listDirs(skillsDir)) {
122
- const flat = flatName(ns, skill);
163
+ // Evals: per-eval-dir symlinks (recursive for nested domain dirs)
164
+ for (const ns of namespaces) {
165
+ for (const { typeDirPath: evalsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'evals')) {
166
+ for (const subType of listDirs(evalsDir)) {
167
+ const subDir = join(evalsDir, subType);
168
+ for (const evalName of listDirs(subDir)) {
169
+ const flat = [ns, ...segments, subType, evalName].join('-');
170
+
171
+ for (const ide of IDE_DIRS) {
172
+ const linkDir = join(cwd, ide, 'evals');
173
+ mkdirSync(linkDir, { recursive: true });
174
+ const linkPath = join(linkDir, flat);
175
+ const targetPath = join(subDir, evalName);
176
+ const relTarget = relative(linkDir, targetPath);
177
+ try {
178
+ execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
179
+ created++;
180
+ } catch { /* best effort */ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
123
186
 
124
- for (const ide of IDE_DIRS) {
125
- const linkDir = join(cwd, ide, 'skills');
126
- mkdirSync(linkDir, { recursive: true });
127
- const linkPath = join(linkDir, flat);
187
+ // Codex per-skill symlinks: .agents/skills/<name>
188
+ const agentsSkillsDir = join(cwd, '.agents/skills');
189
+ for (const ns of namespaces) {
190
+ for (const { typeDirPath: skillsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'skills')) {
191
+ mkdirSync(agentsSkillsDir, { recursive: true });
192
+ for (const skill of listDirs(skillsDir)) {
193
+ const flat = [ns, ...segments, skill].join('-');
194
+ const linkPath = join(agentsSkillsDir, flat);
128
195
  const targetPath = join(skillsDir, skill);
129
- const relTarget = relative(linkDir, targetPath);
196
+ const relTarget = relative(agentsSkillsDir, targetPath);
130
197
  try {
131
198
  execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
132
199
  created++;
@@ -135,21 +202,17 @@ export function linkWorkspace(cwd) {
135
202
  }
136
203
  }
137
204
 
138
- // Evals: per-eval-dir symlinks (evals/agents/<name>/, evals/skills/<name>/)
205
+ // Commands: per-file symlinks (recursive for nested domain dirs)
139
206
  for (const ns of namespaces) {
140
- const evalsDir = join(awDir, ns, 'evals');
141
- if (!existsSync(evalsDir)) continue;
142
-
143
- for (const subType of listDirs(evalsDir)) {
144
- const subDir = join(evalsDir, subType);
145
- for (const evalName of listDirs(subDir)) {
146
- const flat = `${ns}-${subType}-${evalName}`;
207
+ for (const { typeDirPath: commandsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'commands')) {
208
+ for (const file of readdirSync(commandsDir).filter(f => f.endsWith('.md') && !f.startsWith('.'))) {
209
+ const cmdFileName = [ns, ...segments, file].join('-');
147
210
 
148
211
  for (const ide of IDE_DIRS) {
149
- const linkDir = join(cwd, ide, 'evals');
212
+ const linkDir = join(cwd, ide, 'commands', 'aw');
150
213
  mkdirSync(linkDir, { recursive: true });
151
- const linkPath = join(linkDir, flat);
152
- const targetPath = join(subDir, evalName);
214
+ const linkPath = join(linkDir, cmdFileName);
215
+ const targetPath = join(commandsDir, file);
153
216
  const relTarget = relative(linkDir, targetPath);
154
217
  try {
155
218
  execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
@@ -160,47 +223,6 @@ export function linkWorkspace(cwd) {
160
223
  }
161
224
  }
162
225
 
163
- // Codex per-skill symlinks: .agents/skills/<name>
164
- const agentsSkillsDir = join(cwd, '.agents/skills');
165
- for (const ns of namespaces) {
166
- const skillsDir = join(awDir, ns, 'skills');
167
- if (!existsSync(skillsDir)) continue;
168
- mkdirSync(agentsSkillsDir, { recursive: true });
169
- for (const skill of listDirs(skillsDir)) {
170
- const flat = flatName(ns, skill);
171
- const linkPath = join(agentsSkillsDir, flat);
172
- const targetPath = join(skillsDir, skill);
173
- const relTarget = relative(agentsSkillsDir, targetPath);
174
- try {
175
- execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
176
- created++;
177
- } catch { /* best effort */ }
178
- }
179
- }
180
-
181
- // Commands: per-file symlinks from <ns>/commands/ → .claude/commands/aw/
182
- // All commands (hand-written + generated) live in namespace/commands/ as single source of truth
183
- for (const ns of namespaces) {
184
- const commandsDir = join(awDir, ns, 'commands');
185
- if (!existsSync(commandsDir)) continue;
186
-
187
- for (const file of readdirSync(commandsDir).filter(f => f.endsWith('.md') && !f.startsWith('.'))) {
188
- const cmdFileName = `${ns}-${file}`;
189
-
190
- for (const ide of IDE_DIRS) {
191
- const linkDir = join(cwd, ide, 'commands', 'aw');
192
- mkdirSync(linkDir, { recursive: true });
193
- const linkPath = join(linkDir, cmdFileName);
194
- const targetPath = join(commandsDir, file);
195
- const relTarget = relative(linkDir, targetPath);
196
- try {
197
- execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
198
- created++;
199
- } catch { /* best effort */ }
200
- }
201
- }
202
- }
203
-
204
226
  // AW-PROTOCOL.md: symlink into each IDE dir root so commands can always find it
205
227
  const protocolSrc = join(awDir, 'AW-PROTOCOL.md');
206
228
  if (existsSync(protocolSrc)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.24",
3
+ "version": "0.1.25-beta.1",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {
package/paths.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  import { existsSync, statSync, lstatSync, readlinkSync } from 'node:fs';
4
4
  import { join, resolve, relative, basename, dirname } from 'node:path';
5
5
 
6
- const VALID_TYPES = new Set(['agents', 'skills', 'commands', 'blueprints', 'evals']);
6
+ const VALID_TYPES = new Set(['agents', 'skills', 'commands', 'evals']);
7
7
 
8
8
  // IDE dirs that may contain symlinks into .aw_registry/
9
9
  const IDE_PREFIXES = ['.claude/', '.cursor/', '.codex/', '.agents/'];
@@ -11,8 +11,8 @@ const IDE_PREFIXES = ['.claude/', '.cursor/', '.codex/', '.agents/'];
11
11
  /**
12
12
  * Convert a local workspace path to a registry path.
13
13
  *
14
- * .aw_registry/ghl/agents/architecture-reviewer.md
15
- * → ghl/agents/architecture-reviewer
14
+ * .aw_registry/platform/agents/architecture-reviewer.md
15
+ * → platform/agents/architecture-reviewer
16
16
  *
17
17
  * .aw_registry/dev-nitro/skills/my-skill/SKILL.md
18
18
  * → dev-nitro/skills/my-skill/SKILL
@@ -34,8 +34,8 @@ export function localToRegistry(localPath) {
34
34
  /**
35
35
  * Convert a registry path to a local workspace path.
36
36
  *
37
- * ghl/agents/architecture-reviewer
38
- * → ghl/agents/architecture-reviewer.md
37
+ * platform/agents/architecture-reviewer
38
+ * → platform/agents/architecture-reviewer.md
39
39
  *
40
40
  * dev-nitro/skills/my-skill/SKILL
41
41
  * → dev-nitro/skills/my-skill/SKILL.md
package/plan.mjs CHANGED
@@ -102,7 +102,7 @@ export function computePlan(registryDirs, workspaceDir, includePatterns = [], {
102
102
  // Find the type segment
103
103
  let typeIdx = -1;
104
104
  for (let i = 0; i < parts.length; i++) {
105
- if (['agents', 'skills', 'commands', 'blueprints', 'evals'].includes(parts[i])) {
105
+ if (['agents', 'skills', 'commands', 'evals'].includes(parts[i])) {
106
106
  typeIdx = i;
107
107
  break;
108
108
  }
package/registry.mjs CHANGED
@@ -4,7 +4,7 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
4
4
  import { join, relative } from 'node:path';
5
5
  import { createHash } from 'node:crypto';
6
6
 
7
- const TYPE_DIRS = new Set(['agents', 'skills', 'commands', 'blueprints', 'evals']);
7
+ const TYPE_DIRS = new Set(['agents', 'skills', 'commands', 'evals']);
8
8
 
9
9
  export function sha256(content) {
10
10
  return createHash('sha256').update(content).digest('hex');
@@ -83,7 +83,7 @@ export function walkRegistryTree(baseDir, baseName) {
83
83
  });
84
84
  }
85
85
  } else {
86
- // Agents, commands, blueprints — flat files
86
+ // Agents, commands — flat files
87
87
  for (const fileEntry of readdirSync(fullPath)) {
88
88
  if (fileEntry === '.gitkeep' || fileEntry.startsWith('.')) continue;
89
89
  const filePath = join(fullPath, fileEntry);