dsh-ensp 0.5.0 → 0.6.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/README.en.md +18 -2
- package/README.md +18 -2
- package/docs/SCENARIOS.md +184 -0
- package/docs/plugin-detail.en.md +1 -2
- package/docs/plugin-detail.zh.md +1 -2
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +13 -5
- package/scripts/gen-curriculum.mjs +181 -0
- package/scripts/gen-knowledge.mjs +67 -32
- package/scripts/gen-scenarios-doc.mjs +11 -0
- package/src/cli/ensp.mjs +36 -2
- package/src/core/curriculum.json +1163 -0
- package/src/core/curriculum.mjs +70 -0
- package/src/core/index.mjs +8 -0
- package/src/core/knowledge.json +677 -130
- package/src/core/knowledge.mjs +7 -8
- package/src/dsh/index.mjs +30 -5
- package/src/mcp/server.mjs +17 -5
package/src/core/knowledge.mjs
CHANGED
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
* not carry commands, because the whole point of the tool is to catch a copied
|
|
6
6
|
* command that does not fit. What they were missing is the vocabulary the lab is
|
|
7
7
|
* teaching: which LSA belongs to which area, which attribute wins a BGP decision,
|
|
8
|
-
* which port role only MSTP has.
|
|
9
|
-
*
|
|
8
|
+
* which port role only MSTP has. Short, checkable statements about those terms
|
|
9
|
+
* are what a learner needs before touching a device.
|
|
10
10
|
*
|
|
11
|
-
* This module reads a small generated index
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* a copyrighted question bank inside a GPL package is not a reasonable default.
|
|
11
|
+
* This module reads a small generated index: one topic, the scenarios that teach
|
|
12
|
+
* it, and a list of short question/answer pairs. Shipping only the index keeps the
|
|
13
|
+
* package small and keeps third-party source material out of a GPL distribution,
|
|
14
|
+
* while `npm run knowledge` still makes every entry reproducible.
|
|
16
15
|
*
|
|
17
16
|
* @module dsh-ensp/knowledge
|
|
18
17
|
*/
|
|
@@ -95,7 +94,7 @@ export function searchKnowledge(query) {
|
|
|
95
94
|
export function renderKnowledge(scenarioId, { limit = 10 } = {}) {
|
|
96
95
|
const points = knowledgeFor(scenarioId).slice(0, limit)
|
|
97
96
|
if (!points.length) return ''
|
|
98
|
-
const lines = ['', '##
|
|
97
|
+
const lines = ['', '## 知识点(按主题归类)']
|
|
99
98
|
for (const p of points) {
|
|
100
99
|
lines.push(`- ${p.q}`)
|
|
101
100
|
lines.push(` 答: ${p.a}`)
|
package/src/dsh/index.mjs
CHANGED
|
@@ -49,6 +49,7 @@ import { SCENARIOS, SCENARIO_IDS, renderScenario, searchScenarios, byLevel } fro
|
|
|
49
49
|
import { doctor, modelsOf } from '../core/doctor.mjs'
|
|
50
50
|
import { runAcceptance, hasChecks } from '../core/acceptance.mjs'
|
|
51
51
|
import { knowledgeFor, searchKnowledge, knowledgeTopics, renderKnowledge } from '../core/knowledge.mjs'
|
|
52
|
+
import { curriculumFor, renderCurriculum, searchCurriculum } from '../core/curriculum.mjs'
|
|
52
53
|
import { VERSION } from '../core/version.mjs'
|
|
53
54
|
|
|
54
55
|
/** Declared hard dependency: the tool registry. */
|
|
@@ -582,6 +583,7 @@ export function apply(ctx) {
|
|
|
582
583
|
schema: obj({
|
|
583
584
|
id: req(str()),
|
|
584
585
|
brief: req(str()),
|
|
586
|
+
curriculum: req(arr()),
|
|
585
587
|
error: req(str()),
|
|
586
588
|
}),
|
|
587
589
|
render(args, value) {
|
|
@@ -600,7 +602,14 @@ export function apply(ctx) {
|
|
|
600
602
|
(hasChecks(id)
|
|
601
603
|
? `\n\n验收: ensp_verify { scenario: "${id}", topology: "${id}.topo" }`
|
|
602
604
|
: '\n\n验收: 无专门断言,ensp_verify 会逐条跑场景里的验证命令') +
|
|
605
|
+
renderCurriculum(id) +
|
|
603
606
|
renderKnowledge(id),
|
|
607
|
+
curriculum: curriculumFor(id).map((c) => ({
|
|
608
|
+
level: c.level,
|
|
609
|
+
title: c.title,
|
|
610
|
+
page: c.page,
|
|
611
|
+
verify: c.verify,
|
|
612
|
+
})),
|
|
604
613
|
}
|
|
605
614
|
},
|
|
606
615
|
})
|
|
@@ -610,12 +619,13 @@ export function apply(ctx) {
|
|
|
610
619
|
add(tools, {
|
|
611
620
|
name: 'ensp_knowledge',
|
|
612
621
|
description:
|
|
613
|
-
'Look up
|
|
614
|
-
'
|
|
615
|
-
'
|
|
622
|
+
'Look up two reference indexes. Pass scenario to list what one lab teaches, query to ' +
|
|
623
|
+
'search short term/answer notes, or syllabus to search the vendor manual chapter index ' +
|
|
624
|
+
'(chapter goals plus the display commands each chapter uses to prove its result).',
|
|
616
625
|
parameters: {
|
|
617
626
|
query: str('Keyword, e.g. "LSA" or "MED".'),
|
|
618
627
|
scenario: str('Scenario id, e.g. "ospf-lsa-types".'),
|
|
628
|
+
syllabus: str('Keyword to search the manual chapter index, e.g. "MPLS" or "BFD".'),
|
|
619
629
|
},
|
|
620
630
|
output: {
|
|
621
631
|
schema: obj({
|
|
@@ -628,7 +638,22 @@ export function apply(ctx) {
|
|
|
628
638
|
return text(value.points || '(no knowledge point matched)')
|
|
629
639
|
},
|
|
630
640
|
},
|
|
631
|
-
async execute({ query, scenario }) {
|
|
641
|
+
async execute({ query, scenario, syllabus }) {
|
|
642
|
+
if (syllabus) {
|
|
643
|
+
const chapters = searchCurriculum(syllabus)
|
|
644
|
+
return {
|
|
645
|
+
scope: `syllabus:${syllabus}`,
|
|
646
|
+
count: chapters.length,
|
|
647
|
+
points: chapters
|
|
648
|
+
.map(
|
|
649
|
+
(c) =>
|
|
650
|
+
`${c.level} · ${c.title} (p${c.page})\n` +
|
|
651
|
+
(c.scenarios.length ? ` 场景: ${c.scenarios.join(', ')}\n` : '') +
|
|
652
|
+
(c.verify.length ? ` 验证: ${c.verify.slice(0, 4).join(' / ')}` : ''),
|
|
653
|
+
)
|
|
654
|
+
.join('\n'),
|
|
655
|
+
}
|
|
656
|
+
}
|
|
632
657
|
if (scenario) {
|
|
633
658
|
if (!SCENARIOS[scenario]) throw new Error(`unknown scenario "${scenario}"`)
|
|
634
659
|
const points = knowledgeFor(scenario)
|
|
@@ -638,7 +663,7 @@ export function apply(ctx) {
|
|
|
638
663
|
points: points.map((p) => `#${p.n} [${p.topic}] ${p.q}\n 答: ${p.a}`).join('\n'),
|
|
639
664
|
}
|
|
640
665
|
}
|
|
641
|
-
if (!query) throw new Error('pass query or
|
|
666
|
+
if (!query) throw new Error('pass query, scenario or syllabus')
|
|
642
667
|
const points = searchKnowledge(query)
|
|
643
668
|
return {
|
|
644
669
|
scope: String(query),
|
package/src/mcp/server.mjs
CHANGED
|
@@ -41,6 +41,7 @@ import { calibrateAnchors, inspectAnchors, DEFAULT_ANCHORS } from '../core/calib
|
|
|
41
41
|
import { doctor, modelsOf } from '../core/doctor.mjs'
|
|
42
42
|
import { runAcceptance, hasChecks, EXPECTATIONS } from '../core/acceptance.mjs'
|
|
43
43
|
import { knowledgeFor, searchKnowledge, knowledgeTopics } from '../core/knowledge.mjs'
|
|
44
|
+
import { curriculumFor, renderCurriculum, searchCurriculum } from '../core/curriculum.mjs'
|
|
44
45
|
import { pushConfig, pushConfigMany, queryDevice } from '../core/provision.mjs'
|
|
45
46
|
import { SCENARIOS, SCENARIO_IDS, byLevel, renderScenario, searchScenarios } from '../core/presets.mjs'
|
|
46
47
|
import { PACKAGE_NAME, VERSION } from '../core/version.mjs'
|
|
@@ -327,12 +328,13 @@ const TOOLS = [
|
|
|
327
328
|
if (!SCENARIOS[id]) throw new Error(`unknown scenario "${id}"`)
|
|
328
329
|
return {
|
|
329
330
|
id,
|
|
330
|
-
brief: renderScenario(id),
|
|
331
|
+
brief: renderScenario(id) + renderCurriculum(id),
|
|
331
332
|
data: SCENARIOS[id],
|
|
332
333
|
hasTopology: hasLab(id),
|
|
333
334
|
hasCuratedChecks: hasChecks(id),
|
|
334
335
|
...(hasLab(id) ? { devices: labDevices(id), addressing: labAddressing(id) } : {}),
|
|
335
336
|
knowledge: knowledgeFor(id),
|
|
337
|
+
curriculum: curriculumFor(id),
|
|
336
338
|
}
|
|
337
339
|
},
|
|
338
340
|
},
|
|
@@ -378,20 +380,30 @@ const TOOLS = [
|
|
|
378
380
|
{
|
|
379
381
|
name: 'ensp_knowledge',
|
|
380
382
|
description:
|
|
381
|
-
'Search the curriculum knowledge index
|
|
383
|
+
'Search the curriculum knowledge index: short factual statements about the terms each '
|
|
384
|
+
+ 'scenario exercises, grouped by topic. ' +
|
|
382
385
|
'Use it to look up the vocabulary a scenario teaches (which LSA belongs to which area, ' +
|
|
383
386
|
'which BGP attribute wins, which port role is MSTP only) without leaving the lab. ' +
|
|
384
387
|
'Returns short question/answer pairs plus the scenarios that teach each topic.',
|
|
385
388
|
inputSchema: obj({
|
|
386
389
|
query: { type: 'string', description: 'keyword, e.g. "LSA" or "MED"' },
|
|
387
390
|
scenario: { type: 'string', description: 'scenario id, to list what that lab teaches' },
|
|
391
|
+
syllabus: { type: 'string', description: 'keyword to search the manual chapter index instead' },
|
|
388
392
|
}),
|
|
389
|
-
async run({ query, scenario }) {
|
|
393
|
+
async run({ query, scenario, syllabus }) {
|
|
394
|
+
if (syllabus) {
|
|
395
|
+
return { syllabus, chapters: searchCurriculum(syllabus) }
|
|
396
|
+
}
|
|
390
397
|
if (scenario) {
|
|
391
398
|
if (!SCENARIOS[scenario]) throw new Error(`unknown scenario "${scenario}"`)
|
|
392
|
-
return {
|
|
399
|
+
return {
|
|
400
|
+
scenario,
|
|
401
|
+
topics: knowledgeTopics(),
|
|
402
|
+
points: knowledgeFor(scenario),
|
|
403
|
+
curriculum: curriculumFor(scenario),
|
|
404
|
+
}
|
|
393
405
|
}
|
|
394
|
-
if (!query) throw new Error('pass query or
|
|
406
|
+
if (!query) throw new Error('pass query, scenario or syllabus')
|
|
395
407
|
return { query, points: searchKnowledge(query) }
|
|
396
408
|
},
|
|
397
409
|
},
|