greprag 5.85.3 → 5.85.6
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.
|
@@ -426,10 +426,9 @@ function acquiresTargetLock(dir, target) {
|
|
|
426
426
|
return false;
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
|
-
/**
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
* must not excuse a merge. */
|
|
429
|
+
/** A checked PowerShell acquisition can precede the try or start its body.
|
|
430
|
+
* Inspect direct statements along that execution sequence, keeping nested
|
|
431
|
+
* blocks opaque: a conditional acquire or a caught throw cannot excuse a merge. */
|
|
433
432
|
function powerShellTryTakesLock(command, mergeStart, cwd, target) {
|
|
434
433
|
const masked = maskQuoted(command);
|
|
435
434
|
const tryStart = /(?:^|[;\n])\s*try\s*\{/gi;
|
|
@@ -442,7 +441,10 @@ function powerShellTryTakesLock(command, mergeStart, cwd, target) {
|
|
|
442
441
|
const finallyMatch = /^\s*finally\s*\{/.exec(suffix);
|
|
443
442
|
if (!finallyMatch || matchingBrace(masked, close + 1 + finallyMatch[0].lastIndexOf('{')) < 0)
|
|
444
443
|
continue;
|
|
445
|
-
const statements =
|
|
444
|
+
const statements = [
|
|
445
|
+
...powerShellStatements(command, masked, 0, match.index),
|
|
446
|
+
...powerShellStatements(command, masked, open + 1, close),
|
|
447
|
+
];
|
|
446
448
|
const mergeIndex = statements.findIndex(s => mergeStart >= s.start && mergeStart < s.end);
|
|
447
449
|
if (mergeIndex < 2 || mergeGlobalOpts(statements[mergeIndex].text)?.length !== 0)
|
|
448
450
|
continue;
|
package/dist/commands/skill.js
CHANGED
|
@@ -411,7 +411,7 @@ async function runSkillMirrorSync(args) {
|
|
|
411
411
|
}
|
|
412
412
|
const hashes = await (0, skill_git_ingest_1.fetchMirrorHashes)(apiUrl, apiKey, projectId);
|
|
413
413
|
const ingest = await (0, skill_git_ingest_1.ingestCommittedSkills)({ apiUrl, apiKey, sources, mirrorHashes: hashes, force: args.includes('--force') });
|
|
414
|
-
console.log(`mirror: ${ingest.uploaded.length} skill(s) ingested from git${ingest.uploaded.length ? ` (${ingest.uploaded.join(', ')})` : ''}, ${ingest.current} current`);
|
|
414
|
+
console.log(`mirror: ${ingest.uploaded.length} skill(s) ingested from git${ingest.uploaded.length ? ` (${ingest.uploaded.join(', ')})` : ''}, ${ingest.retired.length} retired${ingest.retired.length ? ` (${ingest.retired.join(', ')})` : ''}, ${ingest.current} current`);
|
|
415
415
|
for (const failure of ingest.failed)
|
|
416
416
|
console.error(`mirror: ${failure.name} failed — ${failure.error}`);
|
|
417
417
|
if (requested === 'claude-code')
|
|
@@ -583,12 +583,15 @@ function applyNativeSkillMirror(params) {
|
|
|
583
583
|
const projectRoot = projectDir ? writerOwnedProjectSkillRoot(params.platform, projectDir, homeDir) : null;
|
|
584
584
|
if (projectRoot)
|
|
585
585
|
rootState(projectRoot);
|
|
586
|
+
const keepGlobal = new Set(params.globalRoster || []);
|
|
586
587
|
for (const [root, entry] of roots) {
|
|
587
588
|
if (params.pruneGlobalRoot === false && root === globalRoot)
|
|
588
589
|
continue;
|
|
589
590
|
for (const [skillName, managed] of Object.entries(entry.state.skills)) {
|
|
590
591
|
if (entry.present.has(skillName))
|
|
591
592
|
continue;
|
|
593
|
+
if (root === globalRoot && keepGlobal.has(skillName))
|
|
594
|
+
continue;
|
|
592
595
|
const target = targetPath(root, skillName);
|
|
593
596
|
const currentFiles = (0, skill_mirror_files_1.isSkillFolderOwnedElsewhere)(target) ? null : readManagedFiles(target, managed);
|
|
594
597
|
if (!currentFiles || (0, skill_mirror_files_1.computeSkillFileHash)(currentFiles) !== managed.hash) {
|
|
@@ -668,13 +671,25 @@ async function syncNativeSkillMirror(params) {
|
|
|
668
671
|
throw new Error('skill mirror snapshot incomplete; existing native adapters preserved');
|
|
669
672
|
}
|
|
670
673
|
const skills = fetched;
|
|
674
|
+
// In a project the effective list can hide a replaced global, so the global
|
|
675
|
+
// root is pruned against the plain global list (a skill retired in git).
|
|
676
|
+
let globalRoster;
|
|
677
|
+
if (params.projectId) {
|
|
678
|
+
const globals = await fetch(`${params.apiUrl}/v1/skillmirror?scope=global`, {
|
|
679
|
+
headers: { Authorization: `Bearer ${params.apiKey}` },
|
|
680
|
+
});
|
|
681
|
+
if (!globals.ok)
|
|
682
|
+
throw new Error(`skill mirror list failed: HTTP ${globals.status}`);
|
|
683
|
+
const body = await globals.json();
|
|
684
|
+
globalRoster = (body.skills || []).map(row => row.skillName).filter((n) => typeof n === 'string');
|
|
685
|
+
}
|
|
671
686
|
return applyNativeSkillMirror({
|
|
672
687
|
platform: params.platform,
|
|
673
688
|
skills,
|
|
674
689
|
homeDir: params.homeDir,
|
|
675
690
|
projectDir: params.projectDir,
|
|
676
691
|
prune: true,
|
|
677
|
-
|
|
692
|
+
globalRoster,
|
|
678
693
|
});
|
|
679
694
|
}
|
|
680
695
|
function nativeSkillMirrorSummary(result) {
|
|
@@ -4636,12 +4636,15 @@ function applyNativeSkillMirror(params) {
|
|
|
4636
4636
|
const projectRoot = projectDir ? writerOwnedProjectSkillRoot(params.platform, projectDir, homeDir) : null;
|
|
4637
4637
|
if (projectRoot)
|
|
4638
4638
|
rootState(projectRoot);
|
|
4639
|
+
const keepGlobal = new Set(params.globalRoster || []);
|
|
4639
4640
|
for (const [root, entry] of roots) {
|
|
4640
4641
|
if (params.pruneGlobalRoot === false && root === globalRoot)
|
|
4641
4642
|
continue;
|
|
4642
4643
|
for (const [skillName, managed] of Object.entries(entry.state.skills)) {
|
|
4643
4644
|
if (entry.present.has(skillName))
|
|
4644
4645
|
continue;
|
|
4646
|
+
if (root === globalRoot && keepGlobal.has(skillName))
|
|
4647
|
+
continue;
|
|
4645
4648
|
const target = targetPath(root, skillName);
|
|
4646
4649
|
const currentFiles = isSkillFolderOwnedElsewhere(target) ? null : readManagedFiles(target, managed);
|
|
4647
4650
|
if (!currentFiles || computeSkillFileHash(currentFiles) !== managed.hash) {
|
package/dist/skill-git-ingest.js
CHANGED
|
@@ -52,6 +52,8 @@ exports.globalSkillSource = globalSkillSource;
|
|
|
52
52
|
exports.projectSkillSource = projectSkillSource;
|
|
53
53
|
exports.listCommittedSkills = listCommittedSkills;
|
|
54
54
|
exports.readCommittedSkillFiles = readCommittedSkillFiles;
|
|
55
|
+
exports.deletionCommit = deletionCommit;
|
|
56
|
+
exports.deletedSkills = deletedSkills;
|
|
55
57
|
exports.ingestCommittedSkills = ingestCommittedSkills;
|
|
56
58
|
exports.fetchMirrorHashes = fetchMirrorHashes;
|
|
57
59
|
exports.gitSkillSources = gitSkillSources;
|
|
@@ -193,6 +195,36 @@ function lastCommit(repo, folder) {
|
|
|
193
195
|
const [hash = 'unknown', subject = ''] = out.split('\0');
|
|
194
196
|
return { hash, subject };
|
|
195
197
|
}
|
|
198
|
+
/** The commit that deleted a skill folder's SKILL.md from the source, or null
|
|
199
|
+
* when git never held it there (a skill mirrored from elsewhere is not
|
|
200
|
+
* retired by a source that never owned it). */
|
|
201
|
+
function deletionCommit(source, name) {
|
|
202
|
+
const out = (git(source.repo, ['log', '-1', '--diff-filter=D', '--format=%h%x00%s', 'HEAD', '--',
|
|
203
|
+
`${source.prefix}/${name}/SKILL.md`]) || '').trim();
|
|
204
|
+
if (!out)
|
|
205
|
+
return null;
|
|
206
|
+
const [hash = '', subject = ''] = out.split('\0');
|
|
207
|
+
return hash ? { hash, subject } : null;
|
|
208
|
+
}
|
|
209
|
+
/** Mirrored skills of this source's scope whose folder git deleted: absent at
|
|
210
|
+
* HEAD, not bundled, not a link on disk, and deleted by a commit in this repo. */
|
|
211
|
+
function deletedSkills(source, committed, mirrorNames) {
|
|
212
|
+
const present = new Set(committed.map(s => s.name));
|
|
213
|
+
const out = [];
|
|
214
|
+
for (const name of mirrorNames) {
|
|
215
|
+
if (present.has(name) || !SKILL_NAME_RE.test(name))
|
|
216
|
+
continue;
|
|
217
|
+
if (source.scope === 'global' && skill_landing_1.BUNDLED_SKILLS.includes(name))
|
|
218
|
+
continue;
|
|
219
|
+
const folder = path.join(source.repo, ...source.prefix.split('/'), name);
|
|
220
|
+
if (fs.existsSync(folder))
|
|
221
|
+
continue;
|
|
222
|
+
const commit = deletionCommit(source, name);
|
|
223
|
+
if (commit)
|
|
224
|
+
out.push({ name, commit });
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
196
228
|
function readState() {
|
|
197
229
|
try {
|
|
198
230
|
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
|
@@ -215,10 +247,34 @@ function stateKey(source, name) {
|
|
|
215
247
|
* `mirrorHashes` maps `<scope>:<name>` to the mirror's contentHash. `force`
|
|
216
248
|
* re-reads folders whose tree hash is unchanged since the last run. */
|
|
217
249
|
async function ingestCommittedSkills(params) {
|
|
218
|
-
const result = { uploaded: [], current: 0, failed: [] };
|
|
250
|
+
const result = { uploaded: [], retired: [], current: 0, failed: [] };
|
|
219
251
|
const state = readState();
|
|
220
252
|
for (const source of params.sources) {
|
|
221
253
|
const all = listCommittedSkills(source);
|
|
254
|
+
const mirrorNames = [...params.mirrorHashes.keys()]
|
|
255
|
+
.filter(k => k.startsWith(`${source.scope}:`)).map(k => k.slice(source.scope.length + 1));
|
|
256
|
+
for (const gone of deletedSkills(source, all, mirrorNames)) {
|
|
257
|
+
try {
|
|
258
|
+
const res = await fetch(`${params.apiUrl}/v1/skillmirror/${encodeURIComponent(gone.name)}/retire`, {
|
|
259
|
+
method: 'POST',
|
|
260
|
+
headers: { 'Authorization': `Bearer ${params.apiKey}`, 'Content-Type': 'application/json' },
|
|
261
|
+
body: JSON.stringify({
|
|
262
|
+
scope: source.scope,
|
|
263
|
+
...(source.scope === 'project' ? { projectId: source.projectId } : {}),
|
|
264
|
+
reason: `deleted in git ${gone.commit.hash}: ${gone.commit.subject}`.slice(0, 500),
|
|
265
|
+
author: { via: 'git-ingest', commit: gone.commit.hash },
|
|
266
|
+
}),
|
|
267
|
+
signal: AbortSignal.timeout(15_000),
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok && res.status !== 404)
|
|
270
|
+
throw new Error(`HTTP ${res.status}`);
|
|
271
|
+
delete state[stateKey(source, gone.name)];
|
|
272
|
+
result.retired.push(gone.name);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
result.failed.push({ name: gone.name, error: `retire: ${err instanceof Error ? err.message : String(err)}` });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
222
278
|
const changed = params.force ? all : all.filter(s => state[stateKey(source, s.name)] !== s.tree);
|
|
223
279
|
result.current += all.length - changed.length;
|
|
224
280
|
const files = readCommittedSkillFiles(source.repo, changed);
|