@ghl-ai/aw 0.1.40-beta.0 → 0.1.41-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/init.mjs +1 -1
- package/commands/push.mjs +11 -6
- package/git.mjs +19 -101
- package/package.json +1 -1
package/commands/init.mjs
CHANGED
|
@@ -371,7 +371,7 @@ export async function initCommand(args) {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
// Determine sparse paths
|
|
374
|
-
const sparsePaths = [`.aw_registry/platform`, `content`, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`];
|
|
374
|
+
const sparsePaths = [`.aw_registry/platform`, `content`, RULES_SOURCE_DIR, `.aw_registry/AW-PROTOCOL.md`, `CODEOWNERS`, `.github/CODEOWNERS`];
|
|
375
375
|
if (folderName) {
|
|
376
376
|
sparsePaths.push(`.aw_registry/${folderName}`);
|
|
377
377
|
}
|
package/commands/push.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// commands/push.mjs — Push local agents/skills to registry via PR using persistent git clone
|
|
2
2
|
|
|
3
3
|
import { existsSync, statSync, readFileSync, appendFileSync } from 'node:fs';
|
|
4
|
-
import { join, dirname } from 'node:path';
|
|
4
|
+
import { join, dirname, relative } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { exec as execCb, execFile as execFileCb } from 'node:child_process';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
@@ -361,7 +361,11 @@ async function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = f
|
|
|
361
361
|
|
|
362
362
|
// CODEOWNERS for new namespaces (runs inside spinner so no silent gap)
|
|
363
363
|
const topNamespaces = [...new Set(files.map(f => f.namespace.split('/')[0]))];
|
|
364
|
-
|
|
364
|
+
// GitHub looks for CODEOWNERS in .github/ first (takes precedence over root).
|
|
365
|
+
// platform-docs has .github/CODEOWNERS as the authoritative file.
|
|
366
|
+
const codeownersPath = existsSync(join(awHome, '.github', 'CODEOWNERS'))
|
|
367
|
+
? join(awHome, '.github', 'CODEOWNERS')
|
|
368
|
+
: join(awHome, 'CODEOWNERS');
|
|
365
369
|
const newNamespaces = [];
|
|
366
370
|
const ghUser = await getGitHubUser();
|
|
367
371
|
for (const ns of topNamespaces) {
|
|
@@ -374,7 +378,8 @@ async function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = f
|
|
|
374
378
|
|
|
375
379
|
const pathsToStage = files.map(f => f.registryTarget);
|
|
376
380
|
if (newNamespaces.length > 0 && existsSync(codeownersPath)) {
|
|
377
|
-
|
|
381
|
+
// Stage the correct relative path (could be .github/CODEOWNERS or root CODEOWNERS)
|
|
382
|
+
pathsToStage.push(relative(awHome, codeownersPath));
|
|
378
383
|
}
|
|
379
384
|
// Also stage any extra paths (content/, CODEOWNERS manual edits) passed from the caller
|
|
380
385
|
for (const p of extraPaths) {
|
|
@@ -473,17 +478,17 @@ export async function pushCommand(args) {
|
|
|
473
478
|
if (!input) {
|
|
474
479
|
const rulesChanged = hasRulesChanges(cwd);
|
|
475
480
|
|
|
476
|
-
// Extra paths outside .aw_registry/ that aw also manages: content
|
|
481
|
+
// Extra paths outside .aw_registry/ that aw also manages: content/, CODEOWNERS, .github/CODEOWNERS.
|
|
477
482
|
// Detect staged variants for staged-mode and unstaged variants for auto-mode.
|
|
478
483
|
const getExtraStagedPaths = async () => {
|
|
479
484
|
try {
|
|
480
|
-
const { stdout } = await exec(`git -C "${awHome}" diff --cached --name-only -- content/ CODEOWNERS`);
|
|
485
|
+
const { stdout } = await exec(`git -C "${awHome}" diff --cached --name-only -- content/ CODEOWNERS .github/CODEOWNERS`);
|
|
481
486
|
return stdout.trim().split('\n').filter(Boolean);
|
|
482
487
|
} catch { return []; }
|
|
483
488
|
};
|
|
484
489
|
const getExtraChangedPaths = async () => {
|
|
485
490
|
try {
|
|
486
|
-
const { stdout } = await exec(`git -C "${awHome}" status --porcelain -- content/ CODEOWNERS`);
|
|
491
|
+
const { stdout } = await exec(`git -C "${awHome}" status --porcelain -- content/ CODEOWNERS .github/CODEOWNERS`);
|
|
487
492
|
// git status --porcelain prefix is XY (2 chars) + optional space + path.
|
|
488
493
|
// Staged-only files: `M path` (2-char prefix); unstaged files: ` M path` (3-char prefix).
|
|
489
494
|
// slice(2).trimStart() handles both cases correctly.
|
package/git.mjs
CHANGED
|
@@ -9,49 +9,6 @@ import { REGISTRY_BASE_BRANCH, REGISTRY_DIR, DOCS_SOURCE_DIR, RULES_SOURCE_DIR }
|
|
|
9
9
|
|
|
10
10
|
const exec = promisify(execCb);
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Env vars applied to every git command that touches the network.
|
|
14
|
-
* GIT_TERMINAL_PROMPT=0 prevents git from hanging when it would otherwise
|
|
15
|
-
* prompt for credentials (e.g. HTTPS URL with SSH-only auth configured).
|
|
16
|
-
* Instead, git exits immediately with a non-zero code that we can catch.
|
|
17
|
-
*/
|
|
18
|
-
const GIT_NET_ENV = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Convert an HTTPS GitHub URL to its SSH equivalent.
|
|
22
|
-
* e.g. https://github.com/Org/Repo.git → git@github.com:Org/Repo.git
|
|
23
|
-
*/
|
|
24
|
-
function toSshUrl(httpsUrl) {
|
|
25
|
-
const m = httpsUrl.match(/^https:\/\/github\.com\/(.+)$/);
|
|
26
|
-
return m ? `git@github.com:${m[1]}` : httpsUrl;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Detect whether the user's git is configured to prefer SSH for github.com.
|
|
31
|
-
* Checks: 1) git insteadOf config 2) gh CLI auth status
|
|
32
|
-
* Returns true if SSH is preferred.
|
|
33
|
-
*/
|
|
34
|
-
function prefersSsh() {
|
|
35
|
-
// Check git url."git@github.com:".insteadOf
|
|
36
|
-
try {
|
|
37
|
-
const out = execSync(
|
|
38
|
-
'git config --global --get-regexp "url\\.git@github\\.com.*\\.insteadOf"',
|
|
39
|
-
{ stdio: 'pipe', encoding: 'utf8', env: GIT_NET_ENV },
|
|
40
|
-
).trim();
|
|
41
|
-
if (out.includes('https://github.com')) return true;
|
|
42
|
-
} catch { /* not configured */ }
|
|
43
|
-
|
|
44
|
-
// Check gh auth — if protocol is ssh, prefer SSH
|
|
45
|
-
try {
|
|
46
|
-
const out = execSync('gh auth status 2>&1', {
|
|
47
|
-
stdio: 'pipe', encoding: 'utf8', env: GIT_NET_ENV, timeout: 5000,
|
|
48
|
-
});
|
|
49
|
-
if (/git protocol:\s*ssh/i.test(out)) return true;
|
|
50
|
-
} catch { /* gh not installed or not authed */ }
|
|
51
|
-
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
12
|
// ── Backward-compat: temp-dir sparse checkout (used by search.mjs) ────────────
|
|
56
13
|
|
|
57
14
|
/**
|
|
@@ -61,23 +18,12 @@ function prefersSsh() {
|
|
|
61
18
|
export function sparseCheckout(repo, paths) {
|
|
62
19
|
const tempDir = mkdtempSync(join(tmpdir(), 'aw-'));
|
|
63
20
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
execSync(`git clone --filter=blob:none --no-checkout "${url}" "${tempDir}"`, {
|
|
71
|
-
stdio: 'pipe', env: GIT_NET_ENV,
|
|
72
|
-
});
|
|
73
|
-
cloned = true;
|
|
74
|
-
break;
|
|
75
|
-
} catch {
|
|
76
|
-
// Clean up partial clone so next attempt can use the same tempDir
|
|
77
|
-
try { rmSync(join(tempDir, '.git'), { recursive: true, force: true }); } catch {}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (!cloned) {
|
|
21
|
+
const repoUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
|
|
22
|
+
try {
|
|
23
|
+
execSync(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${tempDir}"`, {
|
|
24
|
+
stdio: 'pipe',
|
|
25
|
+
});
|
|
26
|
+
} catch (e) {
|
|
81
27
|
throw new Error(`Failed to clone ${repo}. Check your git credentials and repo access.`);
|
|
82
28
|
}
|
|
83
29
|
|
|
@@ -101,20 +47,10 @@ export function sparseCheckout(repo, paths) {
|
|
|
101
47
|
export async function sparseCheckoutAsync(repo, paths) {
|
|
102
48
|
const tempDir = mkdtempSync(join(tmpdir(), 'aw-'));
|
|
103
49
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for (const url of urls) {
|
|
109
|
-
try {
|
|
110
|
-
await exec(`git clone --filter=blob:none --no-checkout "${url}" "${tempDir}"`, { env: GIT_NET_ENV });
|
|
111
|
-
cloned = true;
|
|
112
|
-
break;
|
|
113
|
-
} catch {
|
|
114
|
-
try { rmSync(join(tempDir, '.git'), { recursive: true, force: true }); } catch {}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (!cloned) {
|
|
50
|
+
const repoUrl = repo.startsWith('http') ? repo : `https://github.com/${repo}.git`;
|
|
51
|
+
try {
|
|
52
|
+
await exec(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${tempDir}"`);
|
|
53
|
+
} catch (e) {
|
|
118
54
|
throw new Error(`Failed to clone ${repo}. Check your git credentials and repo access.`);
|
|
119
55
|
}
|
|
120
56
|
|
|
@@ -170,9 +106,7 @@ export function isValidClone(awHome, repoUrl) {
|
|
|
170
106
|
if (!existsSync(join(awHome, '.git'))) return false;
|
|
171
107
|
try {
|
|
172
108
|
const remote = execSync('git remote get-url origin', { cwd: awHome, stdio: 'pipe', encoding: 'utf8' }).trim();
|
|
173
|
-
|
|
174
|
-
const normalize = (u) => u.replace(/\.git$/, '').replace(/^git@github\.com:/, 'https://github.com/');
|
|
175
|
-
return normalize(remote) === normalize(repoUrl);
|
|
109
|
+
return remote === repoUrl || remote === repoUrl.replace(/\.git$/, '') + '.git' || remote.replace(/\.git$/, '') === repoUrl.replace(/\.git$/, '');
|
|
176
110
|
} catch {
|
|
177
111
|
return false;
|
|
178
112
|
}
|
|
@@ -189,26 +123,10 @@ export async function initPersistentClone(repoUrl, awHome, sparsePaths) {
|
|
|
189
123
|
try { execSync(`rm -rf "${awHome}"`, { stdio: 'pipe' }); } catch {}
|
|
190
124
|
}
|
|
191
125
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
let cloned = false;
|
|
197
|
-
for (const url of urls) {
|
|
198
|
-
// Clean up any partial clone from a previous attempt
|
|
199
|
-
if (existsSync(awHome) && !isValidClone(awHome, url)) {
|
|
200
|
-
try { execSync(`rm -rf "${awHome}"`, { stdio: 'pipe' }); } catch {}
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
await exec(`git clone --filter=blob:none --no-checkout "${url}" "${awHome}"`, { env: GIT_NET_ENV });
|
|
204
|
-
cloned = true;
|
|
205
|
-
break;
|
|
206
|
-
} catch {
|
|
207
|
-
// try next URL
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (!cloned) {
|
|
211
|
-
throw new Error(`Failed to clone ${repoUrl}. Check your git credentials and repo access (HTTPS and SSH both failed).`);
|
|
126
|
+
try {
|
|
127
|
+
await exec(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${awHome}"`);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
throw new Error(`Failed to clone ${repoUrl}: ${e.message}`);
|
|
212
130
|
}
|
|
213
131
|
|
|
214
132
|
try {
|
|
@@ -368,7 +286,7 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
|
|
|
368
286
|
|
|
369
287
|
// ── 2. Fetch ──────────────────────────────────────────────────────────────
|
|
370
288
|
try {
|
|
371
|
-
await exec(`git -C "${awHome}" fetch origin ${REGISTRY_BASE_BRANCH}
|
|
289
|
+
await exec(`git -C "${awHome}" fetch origin ${REGISTRY_BASE_BRANCH}`);
|
|
372
290
|
} catch (e) {
|
|
373
291
|
throw new Error(`Failed to fetch from origin: ${e.message}`);
|
|
374
292
|
}
|
|
@@ -397,7 +315,7 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
|
|
|
397
315
|
// someone else pushed to the remote tracking branch since our last fetch.
|
|
398
316
|
if (isPushBranch) {
|
|
399
317
|
try {
|
|
400
|
-
await exec(`git -C "${awHome}" push --force-with-lease origin "${currentBranch}"
|
|
318
|
+
await exec(`git -C "${awHome}" push --force-with-lease origin "${currentBranch}"`);
|
|
401
319
|
} catch { /* non-blocking — divergence will be resolved on next aw push */ }
|
|
402
320
|
}
|
|
403
321
|
} catch {
|
|
@@ -524,7 +442,7 @@ export function updatePushBranch(awHome, pushBranchName) {
|
|
|
524
442
|
}
|
|
525
443
|
|
|
526
444
|
try {
|
|
527
|
-
execSync(`git -C "${awHome}" push origin "${pushBranchName}" --force`, { stdio: 'pipe'
|
|
445
|
+
execSync(`git -C "${awHome}" push origin "${pushBranchName}" --force`, { stdio: 'pipe' });
|
|
528
446
|
} catch (e) {
|
|
529
447
|
throw new Error(`Failed to push branch: ${e.message}`);
|
|
530
448
|
}
|
|
@@ -567,7 +485,7 @@ export async function createPushBranch(awHome, branchName, files, commitMsg, pre
|
|
|
567
485
|
}
|
|
568
486
|
|
|
569
487
|
try {
|
|
570
|
-
await exec(`git -C "${awHome}" push -u origin "${branchName}"
|
|
488
|
+
await exec(`git -C "${awHome}" push -u origin "${branchName}"`);
|
|
571
489
|
} catch (e) {
|
|
572
490
|
throw new Error(`Failed to push branch: ${e.message}`);
|
|
573
491
|
}
|