mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 04:00:46 +08:00
Register MANIFEST paths.localSkills alongside pack _skills.
Parse packaging-generated paths and load each local_skills/*/SKILL.md with the same hasSkills toggles and provider as shared packs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6dee69be31
commit
53c47fbc6a
18 changed files with 662 additions and 156 deletions
|
|
@ -11,6 +11,7 @@ import test from 'node:test'
|
|||
import {
|
||||
findManifest,
|
||||
parseManifest,
|
||||
parsePaths,
|
||||
resolveKbRoot,
|
||||
} from '../src/netx/kb-manifest.ts'
|
||||
|
||||
|
|
@ -190,5 +191,41 @@ test('contract content flags hasRegions etc. are accepted', () => {
|
|||
assert.equal(snap.content.hasRegions, true)
|
||||
assert.equal(snap.content.hasSkills, true)
|
||||
assert.equal(snap.content.hasCommon, true)
|
||||
assert.deepEqual(snap.paths, {})
|
||||
})
|
||||
})
|
||||
|
||||
test('paths.skills / paths.localSkills parsed; blank skipped', () => {
|
||||
const parsed = parseManifest(validManifest({
|
||||
paths: {
|
||||
skills: '_skills',
|
||||
localSkills: 'regions/印尼-Indonesia/IOH/_local/local_skills',
|
||||
rca: 'regions/印尼-Indonesia/IOH/00_有效RCA',
|
||||
empty: ' ',
|
||||
bad: 12,
|
||||
},
|
||||
}))
|
||||
assert.equal(parsed.paths.skills, '_skills')
|
||||
assert.equal(parsed.paths.localSkills, 'regions/印尼-Indonesia/IOH/_local/local_skills')
|
||||
assert.equal(parsed.paths.rca, 'regions/印尼-Indonesia/IOH/00_有效RCA')
|
||||
assert.equal(parsed.paths.empty, undefined)
|
||||
assert.equal(parsed.paths.bad, undefined)
|
||||
})
|
||||
|
||||
test('paths missing → empty object; invalid type rejected', () => {
|
||||
assert.deepEqual(parsePaths(undefined), {})
|
||||
assert.deepEqual(parsePaths(null), {})
|
||||
assert.throws(() => parsePaths([]), /paths must be an object/)
|
||||
withTemp((root) => {
|
||||
writeFileSync(join(root, 'MANIFEST.json'), validManifest({
|
||||
paths: {
|
||||
skills: '_skills',
|
||||
localSkills: 'regions/ID/IOH/_local/local_skills',
|
||||
},
|
||||
}), 'utf8')
|
||||
const snap = resolveKbRoot(root)
|
||||
assert.equal(snap.status, 'configured')
|
||||
assert.equal(snap.paths.skills, '_skills')
|
||||
assert.equal(snap.paths.localSkills, 'regions/ID/IOH/_local/local_skills')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* KB pack skill loader from realRoot/_skills.
|
||||
* KB pack skill loader from realRoot/_skills (+ optional paths.localSkills).
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict'
|
||||
|
|
@ -8,7 +8,12 @@ import { tmpdir } from 'node:os'
|
|||
import { join } from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { kbPackSkillsEnabled, registerKbPackSkills } from '../src/netx/kb-pack-skills.ts'
|
||||
import {
|
||||
kbPackSkillsEnabled,
|
||||
registerKbPackSkills,
|
||||
resolveKbLocalSkillsRoot,
|
||||
resolveKbSkillsRoot,
|
||||
} from '../src/netx/kb-pack-skills.ts'
|
||||
|
||||
async function withTemp(run) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'netxops-kb-pack-'))
|
||||
|
|
@ -19,7 +24,7 @@ async function withTemp(run) {
|
|||
}
|
||||
}
|
||||
|
||||
function configuredSnap(realRoot, content = {}) {
|
||||
function configuredSnap(realRoot, content = {}, paths = {}) {
|
||||
return {
|
||||
status: 'configured',
|
||||
realRoot,
|
||||
|
|
@ -34,6 +39,7 @@ function configuredSnap(realRoot, content = {}) {
|
|||
hasSkills: true,
|
||||
...content,
|
||||
},
|
||||
paths: { ...paths },
|
||||
errorMessage: '',
|
||||
}
|
||||
}
|
||||
|
|
@ -93,3 +99,108 @@ test('registerKbPackSkills no-op when disabled or hasSkills false', async () =>
|
|||
await registerKbPackSkills(ctx, configuredSnap('/nope', { hasSkills: false }), { enabled: true })
|
||||
assert.equal(registered.length, 0)
|
||||
})
|
||||
|
||||
test('resolveKbSkillsRoot uses paths.skills or defaults to _skills', () => {
|
||||
const snap = configuredSnap('/kb')
|
||||
assert.equal(resolveKbSkillsRoot(snap), join('/kb', '_skills'))
|
||||
assert.equal(
|
||||
resolveKbSkillsRoot(configuredSnap('/kb', {}, { skills: 'pack/skills' })),
|
||||
join('/kb', 'pack/skills'),
|
||||
)
|
||||
})
|
||||
|
||||
test('resolveKbLocalSkillsRoot null when missing; joins when set', () => {
|
||||
assert.equal(resolveKbLocalSkillsRoot(configuredSnap('/kb')), null)
|
||||
assert.equal(
|
||||
resolveKbLocalSkillsRoot(configuredSnap('/kb', {}, {
|
||||
localSkills: 'regions/ID/IOH/_local/local_skills',
|
||||
})),
|
||||
join('/kb', 'regions/ID/IOH/_local/local_skills'),
|
||||
)
|
||||
})
|
||||
|
||||
test('registerKbPackSkills also loads paths.localSkills/*/SKILL.md', async () => {
|
||||
await withTemp(async (root) => {
|
||||
const packDir = join(root, '_skills', 'kb-troubleshoot')
|
||||
mkdirSync(packDir, { recursive: true })
|
||||
writeFileSync(join(packDir, 'SKILL.md'), `---
|
||||
name: kb-troubleshoot
|
||||
description: KB triage playbook
|
||||
---
|
||||
|
||||
# kb-troubleshoot
|
||||
`, 'utf8')
|
||||
|
||||
const localRel = join('regions', 'ID', 'IOH', '_local', 'local_skills')
|
||||
const localDir = join(root, localRel, 'ioh-local-rca')
|
||||
mkdirSync(localDir, { recursive: true })
|
||||
writeFileSync(join(localDir, 'SKILL.md'), `---
|
||||
name: ioh-local-rca
|
||||
description: IOH local RCA playbook
|
||||
---
|
||||
|
||||
# ioh-local-rca
|
||||
`, 'utf8')
|
||||
|
||||
const registered = []
|
||||
const ctx = {
|
||||
logger: { info() {}, warn() {} },
|
||||
skills: {
|
||||
register(skill) {
|
||||
registered.push(skill)
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await registerKbPackSkills(ctx, configuredSnap(root, {}, {
|
||||
skills: '_skills',
|
||||
localSkills: localRel.replace(/\\/g, '/'),
|
||||
}), { enabled: true })
|
||||
|
||||
const names = registered.map((s) => s.name).sort()
|
||||
assert.deepEqual(names, ['ioh-local-rca', 'kb-troubleshoot'])
|
||||
assert.ok(registered.every((s) => s.provider === 'netxops-kb-pack'))
|
||||
})
|
||||
})
|
||||
|
||||
test('registerKbPackSkills skips missing or empty localSkills', async () => {
|
||||
await withTemp(async (root) => {
|
||||
const skillDir = join(root, '_skills', 'kb-troubleshoot')
|
||||
mkdirSync(skillDir, { recursive: true })
|
||||
writeFileSync(join(skillDir, 'SKILL.md'), `---
|
||||
name: kb-troubleshoot
|
||||
description: KB triage playbook
|
||||
---
|
||||
|
||||
# kb-troubleshoot
|
||||
`, 'utf8')
|
||||
|
||||
const emptyLocal = join(root, '_local', 'local_skills')
|
||||
mkdirSync(emptyLocal, { recursive: true })
|
||||
|
||||
const registered = []
|
||||
const ctx = {
|
||||
logger: { info() {}, warn() {} },
|
||||
skills: {
|
||||
register(skill) {
|
||||
registered.push(skill)
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await registerKbPackSkills(ctx, configuredSnap(root, {}, {
|
||||
localSkills: '_local/local_skills',
|
||||
}), { enabled: true })
|
||||
assert.equal(registered.length, 1)
|
||||
assert.equal(registered[0].name, 'kb-troubleshoot')
|
||||
|
||||
registered.length = 0
|
||||
await registerKbPackSkills(ctx, configuredSnap(root, {}, {
|
||||
localSkills: '_local/does-not-exist',
|
||||
}), { enabled: true })
|
||||
assert.equal(registered.length, 1)
|
||||
assert.equal(registered[0].name, 'kb-troubleshoot')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,11 +32,14 @@ test('publishKbContext updates store', () => {
|
|||
hasCommon: false,
|
||||
hasSkills: false,
|
||||
},
|
||||
paths: { skills: '_skills', localSkills: 'regions/ID/IOH/_local/local_skills' },
|
||||
errorMessage: '',
|
||||
})
|
||||
const snap = getKbContext()
|
||||
assert.equal(snap.status, 'configured')
|
||||
assert.equal(snap.operatorName, 'IOH')
|
||||
assert.equal(snap.paths.skills, '_skills')
|
||||
assert.equal(snap.paths.localSkills, 'regions/ID/IOH/_local/local_skills')
|
||||
applyKbEnv(snap)
|
||||
assert.equal(process.env.KB_OPERATOR, 'IOH')
|
||||
assert.equal(process.env.KB_COUNTRY, 'ID')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue