@ghl-ai/aw 0.1.43-beta.0 → 0.1.43-beta.2
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/commands/init.mjs +19 -0
- package/commands/push.mjs +42 -6
- package/ecc.mjs +1 -1
- package/link.mjs +26 -5
- package/package.json +1 -1
- package/registry.mjs +28 -9
- package/render-rules.mjs +21 -9
package/commands/init.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
existsSync,
|
|
9
|
+
mkdirSync,
|
|
9
10
|
writeFileSync,
|
|
10
11
|
symlinkSync,
|
|
11
12
|
lstatSync,
|
|
@@ -90,6 +91,22 @@ function syncHomeAndProjectInstructions(cwd, namespace) {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Create scaffold directories for a new team namespace so the developer
|
|
96
|
+
* has the standard folder structure ready for agents, skills, commands, and evals.
|
|
97
|
+
* No-op if the directories already exist.
|
|
98
|
+
*/
|
|
99
|
+
function scaffoldNamespace(awHome, folderName) {
|
|
100
|
+
const nsDir = join(awHome, REGISTRY_DIR, ...folderName.split('/'));
|
|
101
|
+
for (const type of ['agents', 'skills', 'commands', 'evals']) {
|
|
102
|
+
const dir = join(nsDir, type);
|
|
103
|
+
if (!existsSync(dir)) {
|
|
104
|
+
mkdirSync(dir, { recursive: true });
|
|
105
|
+
writeFileSync(join(dir, '.gitkeep'), '');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
// ── Ensure ~/.aw/.git/info/exclude has the whitelist block ─────────────
|
|
94
111
|
//
|
|
95
112
|
// Strategy: only .aw_registry/, .aw_rules/, content/ are tracked — everything
|
|
@@ -309,6 +326,7 @@ export async function initCommand(args) {
|
|
|
309
326
|
const newSparsePaths = [`.aw_registry/${folderName}`, 'content', RULES_SOURCE_DIR];
|
|
310
327
|
addToSparseCheckout(AW_HOME, newSparsePaths);
|
|
311
328
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
329
|
+
scaffoldNamespace(AW_HOME, folderName);
|
|
312
330
|
} else {
|
|
313
331
|
if (!silent) fmt.logStep('Already initialized — syncing...');
|
|
314
332
|
}
|
|
@@ -486,6 +504,7 @@ export async function initCommand(args) {
|
|
|
486
504
|
const cfg = config.create(GLOBAL_AW_DIR, { namespace: team || 'platform', user });
|
|
487
505
|
if (folderName) {
|
|
488
506
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
507
|
+
scaffoldNamespace(AW_HOME, folderName);
|
|
489
508
|
}
|
|
490
509
|
// Parallel batch A: rules sync (HOME + cwd are independent targets)
|
|
491
510
|
syncRulesTargets(HOME);
|
package/commands/push.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import * as fmt from '../fmt.mjs';
|
|
|
13
13
|
import { chalk } from '../fmt.mjs';
|
|
14
14
|
import { REGISTRY_REPO, REGISTRY_URL, REGISTRY_BASE_BRANCH, REGISTRY_DIR, AW_CO_AUTHOR } from '../constants.mjs';
|
|
15
15
|
import { resolveInput } from '../paths.mjs';
|
|
16
|
-
import { walkRegistryTree } from '../registry.mjs';
|
|
16
|
+
import { walkRegistryTree, getAllFiles } from '../registry.mjs';
|
|
17
17
|
import {
|
|
18
18
|
detectChanges,
|
|
19
19
|
getStagedFiles,
|
|
@@ -236,9 +236,11 @@ function collectBatchFiles(folderAbsPath, registrySubDir) {
|
|
|
236
236
|
return true;
|
|
237
237
|
})
|
|
238
238
|
.map(entry => {
|
|
239
|
-
const registryTarget =
|
|
240
|
-
? `${REGISTRY_DIR}/${entry.namespacePath}/${entry.type}/${entry.
|
|
241
|
-
:
|
|
239
|
+
const registryTarget = entry.evalRelPath
|
|
240
|
+
? `${REGISTRY_DIR}/${entry.namespacePath}/${entry.type}/${entry.evalRelPath}`
|
|
241
|
+
: (entry.type === 'skills' || entry.type === 'evals')
|
|
242
|
+
? `${REGISTRY_DIR}/${entry.namespacePath}/${entry.type}/${entry.slug}/${entry.skillRelPath || entry.filename}`
|
|
243
|
+
: `${REGISTRY_DIR}/${entry.namespacePath}/${entry.type}/${entry.filename}`;
|
|
242
244
|
return {
|
|
243
245
|
absPath: entry.sourcePath,
|
|
244
246
|
registryTarget,
|
|
@@ -267,6 +269,36 @@ function parseRegistryPath(relPath) {
|
|
|
267
269
|
return null;
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
// ── Colocated eval resolution ─────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Find colocated eval files for a given agent or command.
|
|
276
|
+
* Evals live at <type>/evals/<slug>/eval-*.md alongside their parent artifact.
|
|
277
|
+
* Only returns files that have pending changes (present in changedPaths).
|
|
278
|
+
*/
|
|
279
|
+
function resolveColocatedEvals(registrySubDir, namespace, parentType, slug, changedPaths) {
|
|
280
|
+
if (parentType !== 'agents' && parentType !== 'commands') return [];
|
|
281
|
+
|
|
282
|
+
const evalsDir = join(registrySubDir, namespace, parentType, 'evals', slug);
|
|
283
|
+
if (!existsSync(evalsDir) || !statSync(evalsDir).isDirectory()) return [];
|
|
284
|
+
|
|
285
|
+
return getAllFiles(evalsDir)
|
|
286
|
+
.map(file => {
|
|
287
|
+
const relFromRegistryBase = file.slice(registrySubDir.length + 1);
|
|
288
|
+
const registryTarget = `${REGISTRY_DIR}/${relFromRegistryBase}`;
|
|
289
|
+
return { absPath: file, registryTarget };
|
|
290
|
+
})
|
|
291
|
+
.filter(f => changedPaths.has(f.registryTarget))
|
|
292
|
+
.map(f => ({
|
|
293
|
+
...f,
|
|
294
|
+
type: parentType,
|
|
295
|
+
namespace,
|
|
296
|
+
slug,
|
|
297
|
+
isDir: false,
|
|
298
|
+
deleted: false,
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
|
|
270
302
|
// ── CODEOWNERS helpers ────────────────────────────────────────────────
|
|
271
303
|
|
|
272
304
|
async function getGitHubUser() {
|
|
@@ -727,7 +759,7 @@ export async function pushCommand(args) {
|
|
|
727
759
|
return;
|
|
728
760
|
}
|
|
729
761
|
|
|
730
|
-
|
|
762
|
+
const mainFile = {
|
|
731
763
|
absPath,
|
|
732
764
|
registryTarget,
|
|
733
765
|
type: parentDir,
|
|
@@ -735,7 +767,11 @@ export async function pushCommand(args) {
|
|
|
735
767
|
slug,
|
|
736
768
|
isDir,
|
|
737
769
|
deleted: isDeletedFile,
|
|
738
|
-
}
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
// Include colocated evals for agents/commands (agents/evals/<slug>/*, commands/evals/<slug>/*)
|
|
773
|
+
const colocatedEvals = resolveColocatedEvals(registrySubDir, namespacePath, parentDir, slug, singleChangedPaths);
|
|
774
|
+
await doPush([mainFile, ...colocatedEvals], awHome, dryRun, worktreeFlow);
|
|
739
775
|
}
|
|
740
776
|
|
|
741
777
|
// ── Utilities ─────────────────────────────────────────────────────────
|
package/ecc.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { applyStoredStartupPreferences } from "./startup.mjs";
|
|
|
12
12
|
|
|
13
13
|
const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
|
|
14
14
|
const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
|
|
15
|
-
export const AW_ECC_TAG = "v1.4.
|
|
15
|
+
export const AW_ECC_TAG = "v1.4.43";
|
|
16
16
|
|
|
17
17
|
const MARKETPLACE_NAME = "aw-marketplace";
|
|
18
18
|
const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
|
package/link.mjs
CHANGED
|
@@ -47,6 +47,8 @@ function listDirs(dir) {
|
|
|
47
47
|
* platform/frontend/agents/ → { typeDirPath: '.../frontend/agents', segments: ['frontend'] }
|
|
48
48
|
*/
|
|
49
49
|
function findNestedTypeDirs(nsDir, typeName) {
|
|
50
|
+
// Types whose directories may contain colocated evals
|
|
51
|
+
const COLOCATED_EVAL_PARENTS = new Set(['agents', 'commands']);
|
|
50
52
|
const results = [];
|
|
51
53
|
function walk(dir, segments) {
|
|
52
54
|
if (!existsSync(dir)) return;
|
|
@@ -56,6 +58,8 @@ function findNestedTypeDirs(nsDir, typeName) {
|
|
|
56
58
|
results.push({ typeDirPath: join(dir, entry.name), segments });
|
|
57
59
|
} else if (!ALL_KNOWN_TYPES.has(entry.name)) {
|
|
58
60
|
walk(join(dir, entry.name), [...segments, entry.name]);
|
|
61
|
+
} else if (typeName === 'evals' && COLOCATED_EVAL_PARENTS.has(entry.name)) {
|
|
62
|
+
walk(join(dir, entry.name), [...segments, entry.name]);
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
}
|
|
@@ -187,20 +191,37 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
|
|
|
187
191
|
// Evals: per-eval-dir symlinks (recursive for nested domain dirs)
|
|
188
192
|
for (const ns of namespaces) {
|
|
189
193
|
for (const { typeDirPath: evalsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'evals')) {
|
|
190
|
-
|
|
191
|
-
const subDir = join(evalsDir, subType);
|
|
192
|
-
for (const evalName of listDirs(subDir)) {
|
|
193
|
-
const flat = [ns, ...segments, subType, evalName].join('-');
|
|
194
|
+
const isColocated = segments.some(s => s === 'agents' || s === 'commands');
|
|
194
195
|
|
|
196
|
+
if (isColocated) {
|
|
197
|
+
// Colocated evals: agents/evals/<slug>/ — one level of slug dirs
|
|
198
|
+
for (const evalSlug of listDirs(evalsDir)) {
|
|
199
|
+
const flat = [ns, ...segments, evalSlug].join('-');
|
|
195
200
|
for (const ide of IDE_DIRS) {
|
|
196
201
|
const linkDir = join(cwd, ide, 'evals');
|
|
197
202
|
mkdirSync(linkDir, { recursive: true });
|
|
198
203
|
const linkPath = join(linkDir, flat);
|
|
199
|
-
const targetPath = join(
|
|
204
|
+
const targetPath = join(evalsDir, evalSlug);
|
|
200
205
|
const relTarget = relative(linkDir, targetPath);
|
|
201
206
|
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
202
207
|
}
|
|
203
208
|
}
|
|
209
|
+
} else {
|
|
210
|
+
// Standard evals: evals/<subType>/<slug>/ — two levels of dirs
|
|
211
|
+
for (const subType of listDirs(evalsDir)) {
|
|
212
|
+
const subDir = join(evalsDir, subType);
|
|
213
|
+
for (const evalName of listDirs(subDir)) {
|
|
214
|
+
const flat = [ns, ...segments, subType, evalName].join('-');
|
|
215
|
+
for (const ide of IDE_DIRS) {
|
|
216
|
+
const linkDir = join(cwd, ide, 'evals');
|
|
217
|
+
mkdirSync(linkDir, { recursive: true });
|
|
218
|
+
const linkPath = join(linkDir, flat);
|
|
219
|
+
const targetPath = join(subDir, evalName);
|
|
220
|
+
const relTarget = relative(linkDir, targetPath);
|
|
221
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
204
225
|
}
|
|
205
226
|
}
|
|
206
227
|
}
|
package/package.json
CHANGED
package/registry.mjs
CHANGED
|
@@ -84,18 +84,37 @@ export function walkRegistryTree(baseDir, baseName) {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
} else {
|
|
87
|
-
// Agents, commands — flat files
|
|
87
|
+
// Agents, commands — flat files + colocated evals (same pattern as skills)
|
|
88
88
|
for (const fileEntry of readdirSync(fullPath)) {
|
|
89
89
|
if (fileEntry === '.gitkeep' || fileEntry.startsWith('.')) continue;
|
|
90
90
|
const filePath = join(fullPath, fileEntry);
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
if (statSync(filePath).isFile()) {
|
|
92
|
+
const slug = fileEntry.replace(/\.md$/, '');
|
|
93
|
+
const registryPath = `${namespace}/${typeDir}/${slug}`;
|
|
94
|
+
entries.push({
|
|
95
|
+
slug, type: typeDir, sourcePath: filePath,
|
|
96
|
+
isDirectory: false, namespacePath: namespace, registryPath,
|
|
97
|
+
filename: fileEntry,
|
|
98
|
+
});
|
|
99
|
+
} else if (fileEntry === 'evals' && statSync(filePath).isDirectory()) {
|
|
100
|
+
for (const file of getAllFiles(filePath)) {
|
|
101
|
+
const relFromType = relative(fullPath, file);
|
|
102
|
+
const relFromEvals = relative(filePath, file);
|
|
103
|
+
const parts = relFromEvals.split('/');
|
|
104
|
+
const parentSlug = parts.length > 1 ? parts[0] : '';
|
|
105
|
+
const filename = parts[parts.length - 1];
|
|
106
|
+
entries.push({
|
|
107
|
+
slug: parentSlug || filename.replace(/\.md$/, ''),
|
|
108
|
+
type: typeDir,
|
|
109
|
+
sourcePath: file,
|
|
110
|
+
isDirectory: false,
|
|
111
|
+
namespacePath: namespace,
|
|
112
|
+
registryPath: `${namespace}/${typeDir}/${relFromType.replace(/\.md$/, '')}`,
|
|
113
|
+
filename,
|
|
114
|
+
evalRelPath: relFromType,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
99
118
|
}
|
|
100
119
|
}
|
|
101
120
|
} else if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
|
package/render-rules.mjs
CHANGED
|
@@ -344,6 +344,8 @@ For every non-trivial request, execute these steps in order before any substanti
|
|
|
344
344
|
2. **Select route** — using the decision tree below, pick the smallest correct AW route.
|
|
345
345
|
|
|
346
346
|
3. **Read the stage skill** — you MUST Read the matching skill file before responding:
|
|
347
|
+
- /aw-adk → Read aw-adk/SKILL.md
|
|
348
|
+
- /aw-publish → Read aw-publish/SKILL.md
|
|
347
349
|
- /aw-plan → Read aw-plan/SKILL.md
|
|
348
350
|
- /aw-build → Read aw-build/SKILL.md
|
|
349
351
|
- /aw-investigate → Read aw-investigate/SKILL.md
|
|
@@ -362,21 +364,31 @@ Stating a route without Reading the skill file is NOT compliance.
|
|
|
362
364
|
## Route Decision Tree
|
|
363
365
|
|
|
364
366
|
\`\`\`text
|
|
365
|
-
|
|
366
|
-
├──
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
└──
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
└──
|
|
367
|
+
Is the user authoring/editing a CASRE artifact (command, agent, skill, rule, eval) in .aw_registry/?
|
|
368
|
+
├── YES → /aw-adk
|
|
369
|
+
└── NO → Is the user pushing registry content (.aw_registry/ or .aw_rules/) to remote platform-docs?
|
|
370
|
+
├── YES → /aw-publish
|
|
371
|
+
└── NO → Does an approved plan/spec already exist for this exact work?
|
|
372
|
+
├── NO → Is the request about a bug, alert, or unclear failure?
|
|
373
|
+
│ ├── YES → /aw-investigate
|
|
374
|
+
│ └── NO → /aw-plan ← DEFAULT for anything new
|
|
375
|
+
└── YES → Is the work implemented and needs testing/review?
|
|
376
|
+
├── YES → /aw-test or /aw-review
|
|
377
|
+
└── NO → Is this a deploy or release action?
|
|
378
|
+
├── YES → /aw-deploy or /aw-ship
|
|
379
|
+
└── NO → /aw-build
|
|
374
380
|
\`\`\`
|
|
375
381
|
|
|
382
|
+
**ADK triggers**: "create an agent/skill/command/rule/eval", "score my skill", "audit all agents", "ADK", "developer kit", "fix lint errors" on registry artifacts.
|
|
383
|
+
|
|
384
|
+
**Publish triggers**: "push this agent/skill/rule to the registry", "publish registry changes", "sync to platform-docs", "aw push", "aw push-rules". NOT regular git push, app PRs, or deploys.
|
|
385
|
+
|
|
376
386
|
**Plan-first rule**: New endpoints, services, schemas, features, architecture changes, and integrations ALL go through /aw-plan first. /aw-build requires an approved plan or a mechanical change.
|
|
377
387
|
|
|
378
388
|
## Default Assumptions
|
|
379
389
|
|
|
390
|
+
- /aw-adk → create, improve, fix, score, audit, or health-check CASRE artifacts in .aw_registry/.
|
|
391
|
+
- /aw-publish → dry-run first, show diff, confirm, then real push. Never auto-publish.
|
|
380
392
|
- /aw-plan → write file artifacts under .aw_docs/features/<slug>/ unless user says "chat only".
|
|
381
393
|
- /aw-build → code changes with tests.
|
|
382
394
|
- /aw-investigate → reproduction + root cause evidence.
|