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.
@@ -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. That is exactly the material a certification
9
- * question bank covers.
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 instead of the question bank itself:
12
- * one topic, the scenarios that teach it, and a list of short question/answer
13
- * pairs. Keeping the bank out of the package matters for two reasons. Most of its
14
- * questions are three-sentence reading exercises that say nothing, and shipping
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 curriculum knowledge derived from a certification question bank: short ' +
614
- 'question/answer pairs filed by topic, and the scenarios that teach each topic. ' +
615
- 'Pass scenario to list what one lab teaches, or query to search the index.',
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 scenario')
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),
@@ -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 derived from a certification question bank. ' +
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 { scenario, topics: knowledgeTopics(), points: knowledgeFor(scenario) }
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 scenario')
406
+ if (!query) throw new Error('pass query, scenario or syllabus')
395
407
  return { query, points: searchKnowledge(query) }
396
408
  },
397
409
  },