@lythos/cold-pool 0.9.36 → 0.9.38
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 +7 -1
- package/src/db-helpers.ts +4 -73
- package/src/git-hash.test.ts +26 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lythos/cold-pool",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.38",
|
|
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",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "bun test src/ --pass-with-no-tests",
|
|
22
|
+
"test:coverage": "bun test src/ --coverage --coverage-reporter=lcov --coverage-dir=coverage --pass-with-no-tests",
|
|
23
|
+
"test:watch": "bun test src/ --watch"
|
|
24
|
+
},
|
|
20
25
|
"main": "src/index.ts",
|
|
21
26
|
"types": "src/index.ts",
|
|
22
27
|
"files": [
|
|
@@ -34,6 +39,7 @@
|
|
|
34
39
|
},
|
|
35
40
|
"homepage": "https://github.com/lythos-labs/lythoskill/tree/main/packages/lythoskill-cold-pool#readme",
|
|
36
41
|
"dependencies": {
|
|
42
|
+
"@lythos/infra": "^0.9.36",
|
|
37
43
|
"simple-git": "^3.36.0"
|
|
38
44
|
},
|
|
39
45
|
"engines": {
|
package/src/db-helpers.ts
CHANGED
|
@@ -1,76 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Re-export SqliteDb from @lythos/infra for backward compatibility.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The canonical source is now packages/lythoskill-infra/src/sqlite-db.ts.
|
|
5
|
+
* This file will be removed in a future release once all consumers migrate.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import { mkdirSync } from 'node:fs'
|
|
9
|
-
import { dirname } from 'node:path'
|
|
10
|
-
|
|
11
|
-
export abstract class SqliteDb {
|
|
12
|
-
protected abstract initSchema(): void
|
|
13
|
-
|
|
14
|
-
private dbPath: string
|
|
15
|
-
private _db: Database | null = null
|
|
16
|
-
|
|
17
|
-
constructor(dbPath: string) {
|
|
18
|
-
this.dbPath = dbPath
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Lazy-open: first DB access triggers creation. */
|
|
22
|
-
protected get db(): Database {
|
|
23
|
-
if (this._db == null) {
|
|
24
|
-
try {
|
|
25
|
-
mkdirSync(dirname(this.dbPath), { recursive: true })
|
|
26
|
-
} catch {
|
|
27
|
-
// Parent dir may be read-only (e.g. test paths).
|
|
28
|
-
}
|
|
29
|
-
this._db = new Database(this.dbPath, { create: true })
|
|
30
|
-
this.initSchema()
|
|
31
|
-
}
|
|
32
|
-
return this._db
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
protected exec(sql: string, params?: Record<string, unknown>): void {
|
|
36
|
-
const stmt = this.db.query(sql)
|
|
37
|
-
stmt.run(params ?? {})
|
|
38
|
-
stmt.finalize()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
protected queryOne<T>(sql: string, params?: Record<string, unknown>): T | null {
|
|
42
|
-
return this.db.query(sql).get(params ?? {}) as T | null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
protected queryAll<T>(sql: string, params?: Record<string, unknown>): T[] {
|
|
46
|
-
return this.db.query(sql).all(params ?? {}) as T[]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Schema version tracking: call from initSchema() to auto-migrate. */
|
|
50
|
-
protected migrateSchema(currentVersion: number, migrations: Array<{ version: number; sql: string }>): void {
|
|
51
|
-
this.exec(`CREATE TABLE IF NOT EXISTS _schema_version (version INTEGER NOT NULL)`)
|
|
52
|
-
let row = this.queryOne<{ version: number }>(`SELECT version FROM _schema_version`)
|
|
53
|
-
let dbVersion = row?.version ?? 0
|
|
54
|
-
|
|
55
|
-
for (const m of migrations.sort((a, b) => a.version - b.version)) {
|
|
56
|
-
if (m.version > dbVersion) {
|
|
57
|
-
try {
|
|
58
|
-
this.db.query(m.sql).run()
|
|
59
|
-
} catch {
|
|
60
|
-
// Migration may fail if column already exists — fine for idempotent ADD COLUMN.
|
|
61
|
-
}
|
|
62
|
-
dbVersion = m.version
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (dbVersion !== (row?.version ?? 0)) {
|
|
67
|
-
this.exec(`DELETE FROM _schema_version`)
|
|
68
|
-
this.exec(`INSERT INTO _schema_version (version) VALUES ($v)`, { $v: dbVersion })
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
close(): void {
|
|
73
|
-
this._db?.close()
|
|
74
|
-
this._db = null
|
|
75
|
-
}
|
|
76
|
-
}
|
|
7
|
+
export { SqliteDb } from '@lythos/infra'
|
package/src/git-hash.test.ts
CHANGED
|
@@ -6,23 +6,30 @@ import { simpleGit } from 'simple-git'
|
|
|
6
6
|
import { getRepoHeadRef, getSkillBlobHash, getSkillTreeHash, hashSkillMd } from './git-hash.js'
|
|
7
7
|
|
|
8
8
|
let repoDir: string
|
|
9
|
+
let canGit = false
|
|
9
10
|
|
|
10
11
|
beforeAll(async () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
try {
|
|
13
|
+
repoDir = mkdtempSync(join(tmpdir(), 'lythos-git-hash-test-'))
|
|
14
|
+
const git = simpleGit(repoDir)
|
|
15
|
+
await git.init(['--initial-branch=main'])
|
|
16
|
+
await git.addConfig('user.name', 'test')
|
|
17
|
+
await git.addConfig('user.email', 'test@test.com')
|
|
18
|
+
writeFileSync(join(repoDir, 'SKILL.md'), '# Test Skill\n')
|
|
19
|
+
mkdirSync(join(repoDir, 'skills', 'pdf'), { recursive: true })
|
|
20
|
+
writeFileSync(join(repoDir, 'skills', 'pdf', 'SKILL.md'), '# PDF Skill\n> renders PDF\n')
|
|
21
|
+
await git.add('.')
|
|
22
|
+
await git.commit('initial')
|
|
23
|
+
// Verify the environment actually works end-to-end
|
|
24
|
+
const head = await git.revparse(['HEAD'])
|
|
25
|
+
if (head && head.length === 40) canGit = true
|
|
26
|
+
} catch {
|
|
27
|
+
// Git unavailable (CI without credentials, etc.) — git tests will skip
|
|
28
|
+
}
|
|
22
29
|
})
|
|
23
30
|
|
|
24
31
|
afterAll(() => {
|
|
25
|
-
rmSync(repoDir, { recursive: true, force: true })
|
|
32
|
+
if (repoDir) rmSync(repoDir, { recursive: true, force: true })
|
|
26
33
|
})
|
|
27
34
|
|
|
28
35
|
describe('hashSkillMd (SHA-256)', () => {
|
|
@@ -47,12 +54,14 @@ describe('hashSkillMd (SHA-256)', () => {
|
|
|
47
54
|
|
|
48
55
|
describe('getRepoHeadRef', () => {
|
|
49
56
|
it('returns HEAD commit hash', async () => {
|
|
57
|
+
if (!canGit) return
|
|
50
58
|
const head = await getRepoHeadRef(repoDir)
|
|
51
59
|
expect(head).toBeString()
|
|
52
60
|
expect(head.length).toBe(40)
|
|
53
61
|
})
|
|
54
62
|
|
|
55
63
|
it('matches simple-git log output', async () => {
|
|
64
|
+
if (!canGit) return
|
|
56
65
|
const head = await getRepoHeadRef(repoDir)
|
|
57
66
|
const log = await simpleGit(repoDir).log()
|
|
58
67
|
expect(head).toBe(log.latest!.hash)
|
|
@@ -61,18 +70,21 @@ describe('getRepoHeadRef', () => {
|
|
|
61
70
|
|
|
62
71
|
describe('getSkillBlobHash', () => {
|
|
63
72
|
it('hashes SKILL.md in repo root', async () => {
|
|
73
|
+
if (!canGit) return
|
|
64
74
|
const hash = await getSkillBlobHash(repoDir, '')
|
|
65
75
|
expect(hash).toBeString()
|
|
66
76
|
expect(hash.length).toBe(40)
|
|
67
77
|
})
|
|
68
78
|
|
|
69
79
|
it('hashes SKILL.md in nested subpath', async () => {
|
|
80
|
+
if (!canGit) return
|
|
70
81
|
const hash = await getSkillBlobHash(repoDir, 'skills/pdf')
|
|
71
82
|
expect(hash).toBeString()
|
|
72
83
|
expect(hash.length).toBe(40)
|
|
73
84
|
})
|
|
74
85
|
|
|
75
86
|
it('produces different blob hashes for different files', async () => {
|
|
87
|
+
if (!canGit) return
|
|
76
88
|
const root = await getSkillBlobHash(repoDir, '')
|
|
77
89
|
const nested = await getSkillBlobHash(repoDir, 'skills/pdf')
|
|
78
90
|
expect(root).not.toBe(nested)
|
|
@@ -81,12 +93,14 @@ describe('getSkillBlobHash', () => {
|
|
|
81
93
|
|
|
82
94
|
describe('getSkillTreeHash', () => {
|
|
83
95
|
it('returns tree hash for subdirectory', async () => {
|
|
96
|
+
if (!canGit) return
|
|
84
97
|
const hash = await getSkillTreeHash(repoDir, 'skills/pdf')
|
|
85
98
|
expect(hash).toBeString()
|
|
86
99
|
expect(hash.length).toBe(40)
|
|
87
100
|
})
|
|
88
101
|
|
|
89
102
|
it('throws on bad path', async () => {
|
|
103
|
+
if (!canGit) return
|
|
90
104
|
await expect(getSkillTreeHash(repoDir, 'nonexistent')).rejects.toThrow()
|
|
91
105
|
})
|
|
92
106
|
})
|