@lythos/cold-pool 0.9.48 → 0.9.49
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/package.json +1 -1
- package/src/cli.ts +0 -0
- package/src/fetch-plan.ts +15 -1
- package/src/parse-locator.test.ts +4 -0
- package/src/parse-locator.ts +23 -2
- package/src/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lythos/cold-pool",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.49",
|
|
4
4
|
"description": "Cold pool service layer — dedicated resource holder for skill repositories with intent/plan/execute primitives. Single owner of git side-effects; consumed by deck/curator/arena.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
package/src/cli.ts
CHANGED
|
File without changes
|
package/src/fetch-plan.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `executeFetchPlan` will refuse to clone (status: 'failed').
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync } from 'node:fs'
|
|
13
|
+
import { execFileSync } from 'node:child_process'
|
|
13
14
|
import type { ColdPool } from './cold-pool.js'
|
|
14
15
|
import type { Locator, FetchPlan, FetchResult, FetchIO } from './types.js'
|
|
15
16
|
import { gitClone } from './git-io.js'
|
|
@@ -29,7 +30,7 @@ export function buildFetchPlan(
|
|
|
29
30
|
locator,
|
|
30
31
|
cloneUrl,
|
|
31
32
|
targetDir,
|
|
32
|
-
ref: opts?.ref,
|
|
33
|
+
ref: opts?.ref ?? locator.ref, // explicit opts override locator's #ref
|
|
33
34
|
alreadyExists: existsSync(targetDir),
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -48,6 +49,19 @@ export function executeFetchPlan(plan: FetchPlan, io?: FetchIO): FetchResult {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
if (exists(plan.targetDir)) {
|
|
52
|
+
if (plan.ref) {
|
|
53
|
+
if (plan.ref.startsWith('-')) {
|
|
54
|
+
log(`⚠️ Ref "${plan.ref}" starts with dash — refusing to avoid git option injection`)
|
|
55
|
+
} else {
|
|
56
|
+
try {
|
|
57
|
+
log(`🔄 checking out ${plan.ref} in ${plan.targetDir}`)
|
|
58
|
+
execFileSync('git', ['-C', plan.targetDir, 'fetch', '--depth', '1', 'origin', plan.ref], { stdio: 'pipe' })
|
|
59
|
+
execFileSync('git', ['-C', plan.targetDir, 'checkout', plan.ref], { stdio: 'pipe' })
|
|
60
|
+
} catch {
|
|
61
|
+
log(`⚠️ Could not checkout ${plan.ref} — using current HEAD`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
51
65
|
log(`✓ already present: ${plan.targetDir}`)
|
|
52
66
|
return {
|
|
53
67
|
status: 'already-present',
|
|
@@ -11,6 +11,7 @@ describe('parseLocator — accepted forms', () => {
|
|
|
11
11
|
repo: 'skills',
|
|
12
12
|
skill: 'skills/pdf',
|
|
13
13
|
isLocalhost: false,
|
|
14
|
+
ref: null,
|
|
14
15
|
})
|
|
15
16
|
})
|
|
16
17
|
|
|
@@ -35,6 +36,7 @@ describe('parseLocator — accepted forms', () => {
|
|
|
35
36
|
repo: 'design-doc-mermaid',
|
|
36
37
|
skill: null,
|
|
37
38
|
isLocalhost: false,
|
|
39
|
+
ref: null,
|
|
38
40
|
})
|
|
39
41
|
})
|
|
40
42
|
|
|
@@ -52,6 +54,7 @@ describe('parseLocator — accepted forms', () => {
|
|
|
52
54
|
repo: 'my-skill',
|
|
53
55
|
skill: null,
|
|
54
56
|
isLocalhost: true,
|
|
57
|
+
ref: null,
|
|
55
58
|
})
|
|
56
59
|
})
|
|
57
60
|
|
|
@@ -99,6 +102,7 @@ describe('parseLocator — rejected forms (per ADR-20260502012643244 FQ-only)',
|
|
|
99
102
|
repo: 'skills',
|
|
100
103
|
skill: 'my-skill',
|
|
101
104
|
isLocalhost: true,
|
|
105
|
+
ref: null,
|
|
102
106
|
})
|
|
103
107
|
})
|
|
104
108
|
|
package/src/parse-locator.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Three accepted forms (everything else returns null):
|
|
5
5
|
* - `host.tld/owner/repo[/skill]` — remote skill
|
|
6
|
+
* - `host.tld/owner/repo[/skill]#ref` — remote skill at branch/tag/commit
|
|
6
7
|
* - `host.tld/owner/repo` — remote standalone (skill = null)
|
|
7
8
|
* - `localhost/owner/repo[/skill]` — local skill, same shape as remote
|
|
8
9
|
*
|
|
@@ -13,6 +14,9 @@
|
|
|
13
14
|
*
|
|
14
15
|
* Single-name `localhost/<name>` is rejected — that's a post-compaction
|
|
15
16
|
* agent invention, not the canonical form.
|
|
17
|
+
*
|
|
18
|
+
* `#ref` suffix (branch/tag/commit) is compatible with skills.sh's
|
|
19
|
+
* `parseFragmentRef`. The ref is passed to gitClone for checkout.
|
|
16
20
|
*/
|
|
17
21
|
import type { Locator } from './types.js'
|
|
18
22
|
|
|
@@ -20,7 +24,22 @@ export function parseLocator(input: string): Locator | null {
|
|
|
20
24
|
const trimmed = input.trim()
|
|
21
25
|
if (!trimmed) return null
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
// Extract #ref suffix (branch/tag/commit)
|
|
28
|
+
let ref: string | null = null
|
|
29
|
+
let pathPart = trimmed
|
|
30
|
+
const hashIdx = trimmed.indexOf('#')
|
|
31
|
+
if (hashIdx >= 0) {
|
|
32
|
+
pathPart = trimmed.slice(0, hashIdx)
|
|
33
|
+
ref = trimmed.slice(hashIdx + 1) || null
|
|
34
|
+
// Reject refs that look like git option injection or path traversal
|
|
35
|
+
if (ref && (ref.startsWith('-') || ref.includes('..'))) {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parts = pathPart.split('/')
|
|
41
|
+
// Reject empty segments (double slashes) and path traversal
|
|
42
|
+
if (parts.some(p => p === '' || p === '..' || p === '.')) return null
|
|
24
43
|
// Need at least host/owner/repo (3 segments) for any FQ form
|
|
25
44
|
if (parts.length < 3) return null
|
|
26
45
|
|
|
@@ -34,6 +53,7 @@ export function parseLocator(input: string): Locator | null {
|
|
|
34
53
|
owner: parts[1],
|
|
35
54
|
repo: parts[2],
|
|
36
55
|
skill: parts.length > 3 ? parts.slice(3).join('/') : null,
|
|
56
|
+
ref,
|
|
37
57
|
isLocalhost,
|
|
38
58
|
}
|
|
39
59
|
}
|
|
@@ -41,5 +61,6 @@ export function parseLocator(input: string): Locator | null {
|
|
|
41
61
|
/** Recompose an FQ locator string from a parsed `Locator`. */
|
|
42
62
|
export function formatLocator(locator: Locator): string {
|
|
43
63
|
const base = `${locator.host}/${locator.owner}/${locator.repo}`
|
|
44
|
-
|
|
64
|
+
const withSkill = locator.skill ? `${base}/${locator.skill}` : base
|
|
65
|
+
return locator.ref ? `${withSkill}#${locator.ref}` : withSkill
|
|
45
66
|
}
|