@ghl-ai/aw 0.1.58-beta.0 → 0.1.59

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/push.mjs CHANGED
@@ -662,6 +662,10 @@ async function publishProjectAwDocs(cwd, home, dryRun, scope = null) {
662
662
  // Auto-generate a branch name from the files being pushed.
663
663
  function generateBranchName(files, extraPaths = []) {
664
664
  const shortId = Date.now().toString(36).slice(-5);
665
+ const branchSafe = (value) => String(value || '')
666
+ .replace(/\//g, '-')
667
+ .replace(/[^A-Za-z0-9._-]+/g, '-')
668
+ .replace(/^-+|-+$/g, '') || 'item';
665
669
 
666
670
  if (files.length === 0 && extraPaths.length > 0) {
667
671
  return `${managedBranchPrefix(extraPaths)}-${shortId}`;
@@ -675,8 +679,8 @@ function generateBranchName(files, extraPaths = []) {
675
679
 
676
680
  if (files.length === 1) {
677
681
  const f = files[0];
678
- const nsSlug = f.namespace.replace(/\//g, '-');
679
- return `${prefix}/${nsSlug}-${f.type}-${f.slug}-${shortId}`;
682
+ const nsSlug = branchSafe(f.namespace);
683
+ return `${prefix}/${nsSlug}-${f.type}-${branchSafe(f.slug)}-${shortId}`;
680
684
  }
681
685
 
682
686
  if (namespaces.length === 1) {
@@ -698,7 +702,7 @@ function singular(type, count) {
698
702
  function parseCommitFiles(commits) {
699
703
  const result = [];
700
704
  for (const c of commits) {
701
- const m = c.message.match(/registry:\s+(add|remove|sync)\s+(agents|skills|commands|evals)\/([\w-]+)\s+(?:to|from)\s+([\w/]+)/);
705
+ const m = c.message.match(/registry:\s+(add|remove|sync)\s+(agents|skills|commands|evals)\/([\w/-]+)\s+(?:to|from)\s+([\w/]+)/);
702
706
  if (m) result.push({ verb: m[1], type: m[2], slug: m[3], namespace: m[4] });
703
707
  }
704
708
  return result;
@@ -900,13 +904,40 @@ function collectBatchFiles(folderAbsPath, registrySubDir) {
900
904
 
901
905
  // ── Parse type/namespace/slug from a registry-relative path ──────────
902
906
 
903
- function parseRegistryPath(relPath) {
907
+ function findSkillSlugParts(namespaceParts, skillParts, registrySubDir) {
908
+ if (skillParts.length === 0) return [];
909
+
910
+ if (registrySubDir) {
911
+ for (let i = skillParts.length; i > 0; i--) {
912
+ const candidate = join(registrySubDir, ...namespaceParts, 'skills', ...skillParts.slice(0, i));
913
+ if (existsSync(join(candidate, 'SKILL.md'))) return skillParts.slice(0, i);
914
+ }
915
+ }
916
+
917
+ const skillMdIdx = skillParts.findIndex(part => part === 'SKILL' || part === 'SKILL.md');
918
+ if (skillMdIdx > 0) return skillParts.slice(0, skillMdIdx);
919
+
920
+ return [skillParts[0]];
921
+ }
922
+
923
+ function parseRegistryPath(relPath, registrySubDir = null) {
904
924
  const parts = relPath.split('/');
905
925
  for (let i = 0; i < parts.length; i++) {
906
926
  if (PUSHABLE_TYPES.includes(parts[i]) && i + 1 < parts.length) {
927
+ const namespaceParts = parts.slice(0, i);
928
+ if (parts[i] === 'skills') {
929
+ const skillParts = parts.slice(i + 1).map(part => part.replace(/\.md$/, ''));
930
+ const slugParts = findSkillSlugParts(namespaceParts, skillParts, registrySubDir);
931
+ return {
932
+ type: parts[i],
933
+ namespace: namespaceParts.join('/'),
934
+ slug: slugParts.join('/'),
935
+ };
936
+ }
937
+
907
938
  return {
908
939
  type: parts[i],
909
- namespace: parts.slice(0, i).join('/'),
940
+ namespace: namespaceParts.join('/'),
910
941
  slug: parts[i + 1].replace(/\.md$/, ''),
911
942
  };
912
943
  }
@@ -1198,7 +1229,7 @@ export async function pushCommand(args) {
1198
1229
 
1199
1230
  if (staged.length > 0 || extraStaged.length > 0) {
1200
1231
  const files = staged.map(f => {
1201
- const meta = parseRegistryPath(f.registryPath);
1232
+ const meta = parseRegistryPath(f.registryPath, registrySubDir);
1202
1233
  const parts = f.registryPath.split('/');
1203
1234
  return {
1204
1235
  absPath: join(awHome, f.path),
@@ -1264,7 +1295,7 @@ export async function pushCommand(args) {
1264
1295
 
1265
1296
  const files = allEntries
1266
1297
  .map(f => {
1267
- const meta = parseRegistryPath(f.registryPath);
1298
+ const meta = parseRegistryPath(f.registryPath, registrySubDir);
1268
1299
  const parts = f.registryPath.split('/');
1269
1300
  return {
1270
1301
  absPath: join(awHome, f.path),
@@ -1369,7 +1400,7 @@ export async function pushCommand(args) {
1369
1400
  const deletedInFolder = folderChanges.deleted
1370
1401
  .filter(e => !folderPrefix || e.registryPath.startsWith(folderPrefix))
1371
1402
  .map(e => {
1372
- const meta = parseRegistryPath(e.registryPath);
1403
+ const meta = parseRegistryPath(e.registryPath, registrySubDir);
1373
1404
  const parts = e.registryPath.split('/');
1374
1405
  return {
1375
1406
  absPath: join(awHome, e.path),
@@ -1396,13 +1427,8 @@ export async function pushCommand(args) {
1396
1427
  }
1397
1428
 
1398
1429
  // Single file input
1399
- const regParts = resolved.registryPath.split('/');
1400
- let typeIdx = -1;
1401
- for (let i = regParts.length - 1; i >= 0; i--) {
1402
- if (PUSHABLE_TYPES.includes(regParts[i])) { typeIdx = i; break; }
1403
- }
1404
-
1405
- if (typeIdx === -1 || typeIdx + 1 >= regParts.length) {
1430
+ const meta = parseRegistryPath(resolved.registryPath, registrySubDir);
1431
+ if (!meta?.type || !meta.slug) {
1406
1432
  fmt.cancel([
1407
1433
  `Invalid push path: ${chalk.red(resolved.registryPath)}`,
1408
1434
  '',
@@ -1416,14 +1442,16 @@ export async function pushCommand(args) {
1416
1442
  return;
1417
1443
  }
1418
1444
 
1419
- const namespaceParts = regParts.slice(0, typeIdx);
1420
- const parentDir = regParts[typeIdx];
1421
- const slug = regParts[typeIdx + 1];
1422
- const namespacePath = namespaceParts.join('/');
1445
+ const parentDir = meta.type;
1446
+ const slug = meta.slug;
1447
+ const namespacePath = meta.namespace;
1423
1448
  const isDir = !isDeletedFile && statSync(absPath).isDirectory();
1449
+ const registryPathWithExt = resolved.registryPath.endsWith('.md')
1450
+ ? resolved.registryPath
1451
+ : `${resolved.registryPath}.md`;
1424
1452
  const registryTarget = isDir
1425
1453
  ? `${REGISTRY_DIR}/${namespacePath}/${parentDir}/${slug}`
1426
- : `${REGISTRY_DIR}/${namespacePath}/${parentDir}/${slug}.md`;
1454
+ : `${REGISTRY_DIR}/${registryPathWithExt}`;
1427
1455
 
1428
1456
  // Check if the file actually has changes before trying to commit
1429
1457
  const singleFileChanges = detectChanges(awHome, REGISTRY_DIR);
package/ecc.mjs CHANGED
@@ -12,7 +12,12 @@ 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.62";
15
+ export const AW_ECC_TAG = "v1.4.63";
16
+ const REQUIRED_ECC_FILES = [
17
+ "package.json",
18
+ "scripts/install-apply.js",
19
+ "scripts/sync-ecc-to-codex.sh",
20
+ ];
16
21
 
17
22
  const MARKETPLACE_NAME = "aw-marketplace";
18
23
  const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
@@ -85,11 +90,15 @@ async function cloneWithRefAsync(url, ref, dest) {
85
90
  }
86
91
  }
87
92
 
93
+ function hasRequiredEccFiles(dest) {
94
+ return REQUIRED_ECC_FILES.every((relPath) => existsSync(join(dest, relPath)));
95
+ }
96
+
88
97
  async function cloneOrUpdateAsync(tag, dest) {
89
98
  const overrideUrl = process.env.AW_ECC_CLONE_URL;
90
99
  const overrideRef = process.env.AW_ECC_CLONE_REF?.trim();
91
100
 
92
- if (existsSync(join(dest, ".git"))) {
101
+ if (existsSync(join(dest, ".git")) && hasRequiredEccFiles(dest)) {
93
102
  try {
94
103
  if (!overrideUrl && !overrideRef) {
95
104
  await runA(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
@@ -102,6 +111,7 @@ async function cloneOrUpdateAsync(tag, dest) {
102
111
  }
103
112
  // Restore working tree — pruneStaleHooks may have deleted tracked files
104
113
  await runA(`git -C ${dest} checkout -- .`);
114
+ if (!hasRequiredEccFiles(dest)) throw new Error("aw-ecc checkout is missing required files");
105
115
  return;
106
116
  } catch { /* fall through to fresh clone */ }
107
117
  }
@@ -188,7 +198,7 @@ function cloneOrUpdate(tag, dest) {
188
198
  // AW_ECC_CLONE_REF lets aw init install ECC from a non-default branch or tag.
189
199
  const overrideRef = process.env.AW_ECC_CLONE_REF?.trim();
190
200
 
191
- if (existsSync(join(dest, ".git"))) {
201
+ if (existsSync(join(dest, ".git")) && hasRequiredEccFiles(dest)) {
192
202
  try {
193
203
  if (!overrideUrl && !overrideRef) {
194
204
  run(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
@@ -201,6 +211,7 @@ function cloneOrUpdate(tag, dest) {
201
211
  }
202
212
  // Restore working tree — pruneStaleHooks may have deleted tracked files
203
213
  run(`git -C ${dest} checkout -- .`);
214
+ if (!hasRequiredEccFiles(dest)) throw new Error("aw-ecc checkout is missing required files");
204
215
  return;
205
216
  } catch { /* fall through to fresh clone */ }
206
217
  }
package/git.mjs CHANGED
@@ -453,6 +453,7 @@ export function detectChanges(awHome, registryDir) {
453
453
  if (!line || line.length < 3) continue;
454
454
  const xy = line.slice(0, 2);
455
455
  const filePath = line.slice(3).trim();
456
+ if (filePath === `${registryDir}/.aw-upgrade.log`) continue;
456
457
  const registryPath = filePath.startsWith(registryDir + '/') ? filePath.slice(registryDir.length + 1) : filePath;
457
458
  const entry = { path: filePath, registryPath };
458
459
 
@@ -485,6 +486,7 @@ export function getStagedFiles(awHome, registryDir) {
485
486
  if (!line) continue;
486
487
  const [status, filePath] = line.split('\t');
487
488
  if (!filePath?.startsWith(registryDir + '/')) continue;
489
+ if (filePath === `${registryDir}/.aw-upgrade.log`) continue;
488
490
  result.push({
489
491
  path: filePath,
490
492
  registryPath: filePath.slice(registryDir.length + 1),