@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 +15 -4
- package/cli.mjs +2 -2
- package/commands/drop.mjs +1 -1
- package/commands/init.mjs +26 -5
- package/commands/nuke.mjs +1 -1
- package/commands/pull.mjs +3 -3
- package/commands/push.mjs +15 -4
- package/commands/search.mjs +1 -1
- package/constants.mjs +1 -1
- package/git.mjs +1 -1
- package/glob.mjs +2 -2
- package/integrate.mjs +64 -65
- package/link.mjs +121 -99
- package/package.json +1 -1
- package/paths.mjs +5 -5
- package/plan.mjs +1 -1
- package/registry.mjs +2 -2
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
75
|
-
|
|
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: "
|
|
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 &&
|
|
124
|
-
|
|
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
|
|
128
|
-
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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
|
|
70
|
-
for (let i =
|
|
71
|
-
if (
|
|
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(
|
|
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);
|
package/commands/search.mjs
CHANGED
|
@@ -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', '
|
|
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 = '
|
|
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., ["
|
|
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
|
-
// "
|
|
4
|
-
// "
|
|
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: "
|
|
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
|
-
├──
|
|
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
|
|
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,
|
|
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 (\`
|
|
207
|
-
- \`.aw_registry/
|
|
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
|
-
- **
|
|
214
|
-
- **${team}/ can reference
|
|
215
|
-
- **
|
|
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
|
-
#
|
|
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 —
|
|
225
|
+
# ${team}/frontend/agents/frontend-developer.md — platform- prefix for platform skills
|
|
227
226
|
skills:
|
|
228
|
-
-
|
|
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/
|
|
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/
|
|
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 (
|
|
286
|
+
### Platform Agents (platform-)
|
|
288
287
|
|
|
289
|
-
Shared infrastructure agents — always available, self-contained. Loaded from \`.aw_registry/
|
|
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
|
-
| \`
|
|
294
|
-
| \`
|
|
295
|
-
| \`
|
|
296
|
-
| \`
|
|
297
|
-
| \`
|
|
298
|
-
| \`
|
|
299
|
-
| \`
|
|
300
|
-
| \`
|
|
301
|
-
| \`
|
|
302
|
-
| \`
|
|
303
|
-
| \`
|
|
304
|
-
| \`
|
|
305
|
-
| \`
|
|
306
|
-
| \`
|
|
307
|
-
| \`
|
|
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 \`
|
|
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}-
|
|
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
|
|
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
|
-
- **\`
|
|
341
|
+
- **\`platform-\` prefix** → \`.aw_registry/platform/skills/<name-without-platform->/SKILL.md\`
|
|
343
342
|
|
|
344
343
|
\`\`\`yaml
|
|
345
|
-
# .aw_registry/
|
|
344
|
+
# .aw_registry/platform/agents/security-reviewer.md — platform skills directly
|
|
346
345
|
skills:
|
|
347
|
-
- security-review
|
|
348
|
-
- platform-services-authentication-authorization # → .aw_registry/
|
|
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 —
|
|
349
|
+
# .aw_registry/${team}/frontend/agents/frontend-developer.md — platform- prefix for platform skills
|
|
351
350
|
skills:
|
|
352
|
-
-
|
|
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: "
|
|
368
|
-
Agent(subagent_type: "${team}:
|
|
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
|
|
378
|
-
├── frontend-developer.md
|
|
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/
|
|
390
|
-
.claude/evals/${team}-agents-frontend-developer/ # Team agent evals
|
|
391
|
-
.claude/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: \`/
|
|
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'
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
122
|
+
// Per-file types: agents (recursive for nested domain dirs)
|
|
97
123
|
for (const type of FILE_TYPES) {
|
|
98
|
-
const
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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,
|
|
149
|
+
const linkDir = join(cwd, ide, 'skills');
|
|
106
150
|
mkdirSync(linkDir, { recursive: true });
|
|
107
151
|
const linkPath = join(linkDir, flat);
|
|
108
|
-
const targetPath = join(
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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(
|
|
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
|
-
//
|
|
205
|
+
// Commands: per-file symlinks (recursive for nested domain dirs)
|
|
139
206
|
for (const ns of namespaces) {
|
|
140
|
-
const
|
|
141
|
-
|
|
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, '
|
|
212
|
+
const linkDir = join(cwd, ide, 'commands', 'aw');
|
|
150
213
|
mkdirSync(linkDir, { recursive: true });
|
|
151
|
-
const linkPath = join(linkDir,
|
|
152
|
-
const targetPath = join(
|
|
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
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', '
|
|
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/
|
|
15
|
-
* →
|
|
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
|
-
*
|
|
38
|
-
* →
|
|
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', '
|
|
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', '
|
|
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
|
|
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);
|