@codemcp/ade 0.6.1 → 0.8.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/.beads/issues.jsonl +37 -0
- package/.beads/last-touched +1 -1
- package/.kiro/agents/ade.json +10 -2
- package/.kiro/settings/mcp.json +6 -1
- package/.opencode/agents/ade.md +7 -2
- package/.vibe/beads-state-ade-extension-override-skills-d44z9p.json +29 -0
- package/.vibe/beads-state-ade-fix-docset-writing-37fuoj.json +29 -0
- package/.vibe/development-plan-extension-override-skills.md +110 -0
- package/.vibe/development-plan-fix-docset-writing.md +77 -0
- package/.vibe/docs/architecture.md +201 -0
- package/.vibe/docs/design.md +179 -0
- package/.vibe/docs/requirements.md +17 -0
- package/ade.extensions.mjs +13 -15
- package/config.lock.yaml +6 -1
- package/docs/CLI-PRD.md +38 -40
- package/docs/CLI-design.md +47 -57
- package/docs/guide/extensions.md +6 -14
- package/package.json +1 -1
- package/packages/cli/dist/index.js +15213 -5579
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/conventions.integration.spec.ts +29 -4
- package/packages/cli/src/commands/extensions.integration.spec.ts +26 -4
- package/packages/cli/src/commands/install.ts +2 -0
- package/packages/cli/src/commands/knowledge-docset.integration.spec.ts +179 -0
- package/packages/cli/src/commands/knowledge.integration.spec.ts +24 -36
- package/packages/cli/src/commands/setup.spec.ts +1 -101
- package/packages/cli/src/commands/setup.ts +23 -36
- package/packages/cli/src/knowledge-installer.spec.ts +43 -3
- package/packages/cli/src/knowledge-installer.ts +12 -9
- package/packages/core/package.json +1 -1
- package/packages/core/src/catalog/catalog.spec.ts +75 -43
- package/packages/core/src/catalog/facets/architecture.ts +89 -58
- package/packages/core/src/catalog/facets/practices.ts +9 -8
- package/packages/core/src/index.ts +4 -4
- package/packages/core/src/registry.spec.ts +1 -1
- package/packages/core/src/registry.ts +2 -2
- package/packages/core/src/resolver.spec.ts +391 -154
- package/packages/core/src/resolver.ts +16 -54
- package/packages/core/src/types.ts +19 -10
- package/packages/core/src/writers/docset.spec.ts +40 -0
- package/packages/core/src/writers/docset.ts +24 -0
- package/packages/core/src/writers/skills.spec.ts +46 -0
- package/packages/harnesses/package.json +1 -1
- package/packages/harnesses/src/writers/opencode.spec.ts +3 -5
- package/packages/harnesses/src/writers/opencode.ts +3 -4
- package/packages/core/src/writers/knowledge.spec.ts +0 -26
- package/packages/core/src/writers/knowledge.ts +0 -15
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { resolve
|
|
2
|
+
import { resolve } from "./resolver.js";
|
|
3
3
|
import { getDefaultCatalog } from "./catalog/index.js";
|
|
4
4
|
import { createRegistry, registerProvisionWriter } from "./registry.js";
|
|
5
5
|
import { instructionWriter } from "./writers/instruction.js";
|
|
6
6
|
import { workflowsWriter } from "./writers/workflows.js";
|
|
7
7
|
import { skillsWriter } from "./writers/skills.js";
|
|
8
8
|
import { setupNoteWriter } from "./writers/setup-note.js";
|
|
9
|
+
import { docsetWriter } from "./writers/docset.js";
|
|
9
10
|
import type { UserConfig, WriterRegistry, Catalog } from "./types.js";
|
|
10
11
|
|
|
11
12
|
function buildRegistry(): WriterRegistry {
|
|
@@ -246,6 +247,336 @@ describe("resolve", () => {
|
|
|
246
247
|
);
|
|
247
248
|
expect(agentskills).toBeUndefined();
|
|
248
249
|
});
|
|
250
|
+
|
|
251
|
+
it("removes a skill when another skill declares it in replaces", async () => {
|
|
252
|
+
const skillsCatalog: Catalog = {
|
|
253
|
+
facets: [
|
|
254
|
+
{
|
|
255
|
+
id: "process",
|
|
256
|
+
label: "Process",
|
|
257
|
+
description: "Process",
|
|
258
|
+
required: true,
|
|
259
|
+
options: [
|
|
260
|
+
{
|
|
261
|
+
id: "base",
|
|
262
|
+
label: "Base",
|
|
263
|
+
description: "Base process",
|
|
264
|
+
recipe: [
|
|
265
|
+
{
|
|
266
|
+
writer: "skills",
|
|
267
|
+
config: {
|
|
268
|
+
skills: [
|
|
269
|
+
{
|
|
270
|
+
name: "architecture",
|
|
271
|
+
description: "Generic architecture skill",
|
|
272
|
+
body: "Generic architecture content."
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "architecture",
|
|
283
|
+
label: "Architecture",
|
|
284
|
+
description: "Stack",
|
|
285
|
+
required: false,
|
|
286
|
+
options: [
|
|
287
|
+
{
|
|
288
|
+
id: "sabdx",
|
|
289
|
+
label: "SABDX",
|
|
290
|
+
description: "SABDX frontend",
|
|
291
|
+
recipe: [
|
|
292
|
+
{
|
|
293
|
+
writer: "skills",
|
|
294
|
+
config: {
|
|
295
|
+
skills: [
|
|
296
|
+
{
|
|
297
|
+
name: "sabdx-architecture",
|
|
298
|
+
description: "SABDX architecture skill",
|
|
299
|
+
body: "SABDX architecture content.",
|
|
300
|
+
replaces: ["architecture"]
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const userConfig: UserConfig = {
|
|
313
|
+
choices: { process: "base", architecture: "sabdx" }
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const result = await resolve(userConfig, skillsCatalog, registry);
|
|
317
|
+
|
|
318
|
+
expect(result.skills).toHaveLength(1);
|
|
319
|
+
expect(result.skills[0].name).toBe("sabdx-architecture");
|
|
320
|
+
expect(
|
|
321
|
+
result.skills.find((s) => s.name === "architecture")
|
|
322
|
+
).toBeUndefined();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("removes multiple skills when a single skill declares several replaces entries", async () => {
|
|
326
|
+
const skillsCatalog: Catalog = {
|
|
327
|
+
facets: [
|
|
328
|
+
{
|
|
329
|
+
id: "process",
|
|
330
|
+
label: "Process",
|
|
331
|
+
description: "Process",
|
|
332
|
+
required: true,
|
|
333
|
+
options: [
|
|
334
|
+
{
|
|
335
|
+
id: "base",
|
|
336
|
+
label: "Base",
|
|
337
|
+
description: "Base",
|
|
338
|
+
recipe: [
|
|
339
|
+
{
|
|
340
|
+
writer: "skills",
|
|
341
|
+
config: {
|
|
342
|
+
skills: [
|
|
343
|
+
{
|
|
344
|
+
name: "coding",
|
|
345
|
+
description: "Generic coding",
|
|
346
|
+
body: "Generic coding."
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "testing",
|
|
350
|
+
description: "Generic testing",
|
|
351
|
+
body: "Generic testing."
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: "architecture",
|
|
362
|
+
label: "Architecture",
|
|
363
|
+
description: "Stack",
|
|
364
|
+
required: false,
|
|
365
|
+
options: [
|
|
366
|
+
{
|
|
367
|
+
id: "ext",
|
|
368
|
+
label: "Extension",
|
|
369
|
+
description: "Extension",
|
|
370
|
+
recipe: [
|
|
371
|
+
{
|
|
372
|
+
writer: "skills",
|
|
373
|
+
config: {
|
|
374
|
+
skills: [
|
|
375
|
+
{
|
|
376
|
+
name: "ext-all",
|
|
377
|
+
description: "Replaces both",
|
|
378
|
+
body: "Extension content.",
|
|
379
|
+
replaces: ["coding", "testing"]
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
]
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const userConfig: UserConfig = {
|
|
392
|
+
choices: { process: "base", architecture: "ext" }
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const result = await resolve(userConfig, skillsCatalog, registry);
|
|
396
|
+
|
|
397
|
+
expect(result.skills).toHaveLength(1);
|
|
398
|
+
expect(result.skills[0].name).toBe("ext-all");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("keeps all skills when no replaces are declared", async () => {
|
|
402
|
+
const skillsCatalog: Catalog = {
|
|
403
|
+
facets: [
|
|
404
|
+
{
|
|
405
|
+
id: "process",
|
|
406
|
+
label: "Process",
|
|
407
|
+
description: "Process",
|
|
408
|
+
required: true,
|
|
409
|
+
options: [
|
|
410
|
+
{
|
|
411
|
+
id: "base",
|
|
412
|
+
label: "Base",
|
|
413
|
+
description: "Base",
|
|
414
|
+
recipe: [
|
|
415
|
+
{
|
|
416
|
+
writer: "skills",
|
|
417
|
+
config: {
|
|
418
|
+
skills: [
|
|
419
|
+
{ name: "skill-a", description: "A", body: "Body A." },
|
|
420
|
+
{ name: "skill-b", description: "B", body: "Body B." }
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
]
|
|
425
|
+
}
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
]
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const userConfig: UserConfig = { choices: { process: "base" } };
|
|
432
|
+
const result = await resolve(userConfig, skillsCatalog, registry);
|
|
433
|
+
|
|
434
|
+
expect(result.skills).toHaveLength(2);
|
|
435
|
+
expect(result.skills.map((s) => s.name)).toEqual(["skill-a", "skill-b"]);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("deduplicates skills by name — last writer wins", async () => {
|
|
439
|
+
const skillsCatalog: Catalog = {
|
|
440
|
+
facets: [
|
|
441
|
+
{
|
|
442
|
+
id: "process",
|
|
443
|
+
label: "Process",
|
|
444
|
+
description: "Process",
|
|
445
|
+
required: true,
|
|
446
|
+
options: [
|
|
447
|
+
{
|
|
448
|
+
id: "base",
|
|
449
|
+
label: "Base",
|
|
450
|
+
description: "Base",
|
|
451
|
+
recipe: [
|
|
452
|
+
{
|
|
453
|
+
writer: "skills",
|
|
454
|
+
config: {
|
|
455
|
+
skills: [
|
|
456
|
+
{
|
|
457
|
+
name: "shared-skill",
|
|
458
|
+
description: "First",
|
|
459
|
+
body: "First body."
|
|
460
|
+
}
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
id: "architecture",
|
|
470
|
+
label: "Architecture",
|
|
471
|
+
description: "Stack",
|
|
472
|
+
required: false,
|
|
473
|
+
options: [
|
|
474
|
+
{
|
|
475
|
+
id: "ext",
|
|
476
|
+
label: "Extension",
|
|
477
|
+
description: "Extension",
|
|
478
|
+
recipe: [
|
|
479
|
+
{
|
|
480
|
+
writer: "skills",
|
|
481
|
+
config: {
|
|
482
|
+
skills: [
|
|
483
|
+
{
|
|
484
|
+
name: "shared-skill",
|
|
485
|
+
description: "Second",
|
|
486
|
+
body: "Second body."
|
|
487
|
+
}
|
|
488
|
+
]
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
}
|
|
493
|
+
]
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const userConfig: UserConfig = {
|
|
499
|
+
choices: { process: "base", architecture: "ext" }
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const result = await resolve(userConfig, skillsCatalog, registry);
|
|
503
|
+
|
|
504
|
+
expect(result.skills).toHaveLength(1);
|
|
505
|
+
expect(result.skills[0]).toMatchObject({
|
|
506
|
+
name: "shared-skill",
|
|
507
|
+
body: "Second body."
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("works with external skills that declare replaces", async () => {
|
|
512
|
+
const skillsCatalog: Catalog = {
|
|
513
|
+
facets: [
|
|
514
|
+
{
|
|
515
|
+
id: "process",
|
|
516
|
+
label: "Process",
|
|
517
|
+
description: "Process",
|
|
518
|
+
required: true,
|
|
519
|
+
options: [
|
|
520
|
+
{
|
|
521
|
+
id: "base",
|
|
522
|
+
label: "Base",
|
|
523
|
+
description: "Base",
|
|
524
|
+
recipe: [
|
|
525
|
+
{
|
|
526
|
+
writer: "skills",
|
|
527
|
+
config: {
|
|
528
|
+
skills: [
|
|
529
|
+
{
|
|
530
|
+
name: "tdd",
|
|
531
|
+
description: "Generic TDD",
|
|
532
|
+
body: "Generic TDD."
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
]
|
|
538
|
+
}
|
|
539
|
+
]
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: "architecture",
|
|
543
|
+
label: "Architecture",
|
|
544
|
+
description: "Stack",
|
|
545
|
+
required: false,
|
|
546
|
+
options: [
|
|
547
|
+
{
|
|
548
|
+
id: "ext",
|
|
549
|
+
label: "Extension",
|
|
550
|
+
description: "Extension",
|
|
551
|
+
recipe: [
|
|
552
|
+
{
|
|
553
|
+
writer: "skills",
|
|
554
|
+
config: {
|
|
555
|
+
skills: [
|
|
556
|
+
{
|
|
557
|
+
name: "ext-tdd",
|
|
558
|
+
source: "org/repo/skills/ext-tdd",
|
|
559
|
+
replaces: ["tdd"]
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
]
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
]
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const userConfig: UserConfig = {
|
|
572
|
+
choices: { process: "base", architecture: "ext" }
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const result = await resolve(userConfig, skillsCatalog, registry);
|
|
576
|
+
|
|
577
|
+
expect(result.skills).toHaveLength(1);
|
|
578
|
+
expect(result.skills[0].name).toBe("ext-tdd");
|
|
579
|
+
});
|
|
249
580
|
});
|
|
250
581
|
|
|
251
582
|
describe("setup_notes merging", () => {
|
|
@@ -283,7 +614,7 @@ describe("resolve", () => {
|
|
|
283
614
|
});
|
|
284
615
|
});
|
|
285
616
|
|
|
286
|
-
describe("docset collection", () => {
|
|
617
|
+
describe("docset collection via recipe writer", () => {
|
|
287
618
|
it("collects docsets from selected options into knowledge_sources", async () => {
|
|
288
619
|
const docsetCatalog: Catalog = {
|
|
289
620
|
facets: [
|
|
@@ -297,13 +628,15 @@ describe("resolve", () => {
|
|
|
297
628
|
id: "react",
|
|
298
629
|
label: "React",
|
|
299
630
|
description: "React framework",
|
|
300
|
-
recipe: [
|
|
301
|
-
docsets: [
|
|
631
|
+
recipe: [
|
|
302
632
|
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
633
|
+
writer: "docset",
|
|
634
|
+
config: {
|
|
635
|
+
id: "react-docs",
|
|
636
|
+
label: "React Reference",
|
|
637
|
+
origin: "https://github.com/facebook/react.git",
|
|
638
|
+
description: "Official React documentation"
|
|
639
|
+
}
|
|
307
640
|
}
|
|
308
641
|
]
|
|
309
642
|
}
|
|
@@ -312,8 +645,11 @@ describe("resolve", () => {
|
|
|
312
645
|
]
|
|
313
646
|
};
|
|
314
647
|
|
|
648
|
+
const reg = createRegistry();
|
|
649
|
+
registerProvisionWriter(reg, docsetWriter);
|
|
650
|
+
|
|
315
651
|
const userConfig: UserConfig = { choices: { arch: "react" } };
|
|
316
|
-
const result = await resolve(userConfig, docsetCatalog,
|
|
652
|
+
const result = await resolve(userConfig, docsetCatalog, reg);
|
|
317
653
|
|
|
318
654
|
expect(result.knowledge_sources).toHaveLength(1);
|
|
319
655
|
expect(result.knowledge_sources[0]).toEqual({
|
|
@@ -323,7 +659,7 @@ describe("resolve", () => {
|
|
|
323
659
|
});
|
|
324
660
|
});
|
|
325
661
|
|
|
326
|
-
it("deduplicates docsets by id across multiple options", async () => {
|
|
662
|
+
it("deduplicates docsets by id across multiple options via last-writer-wins on knowledge_sources name", async () => {
|
|
327
663
|
const docsetCatalog: Catalog = {
|
|
328
664
|
facets: [
|
|
329
665
|
{
|
|
@@ -337,13 +673,15 @@ describe("resolve", () => {
|
|
|
337
673
|
id: "react",
|
|
338
674
|
label: "React",
|
|
339
675
|
description: "React",
|
|
340
|
-
recipe: [
|
|
341
|
-
docsets: [
|
|
676
|
+
recipe: [
|
|
342
677
|
{
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
678
|
+
writer: "docset",
|
|
679
|
+
config: {
|
|
680
|
+
id: "react-docs",
|
|
681
|
+
label: "React Reference",
|
|
682
|
+
origin: "https://github.com/facebook/react.git",
|
|
683
|
+
description: "React docs"
|
|
684
|
+
}
|
|
347
685
|
}
|
|
348
686
|
]
|
|
349
687
|
},
|
|
@@ -351,19 +689,24 @@ describe("resolve", () => {
|
|
|
351
689
|
id: "nextjs",
|
|
352
690
|
label: "Next.js",
|
|
353
691
|
description: "Next.js",
|
|
354
|
-
recipe: [
|
|
355
|
-
docsets: [
|
|
692
|
+
recipe: [
|
|
356
693
|
{
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
694
|
+
writer: "docset",
|
|
695
|
+
config: {
|
|
696
|
+
id: "react-docs",
|
|
697
|
+
label: "React Reference",
|
|
698
|
+
origin: "https://github.com/facebook/react.git",
|
|
699
|
+
description: "React docs"
|
|
700
|
+
}
|
|
361
701
|
},
|
|
362
702
|
{
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
703
|
+
writer: "docset",
|
|
704
|
+
config: {
|
|
705
|
+
id: "nextjs-docs",
|
|
706
|
+
label: "Next.js Docs",
|
|
707
|
+
origin: "https://nextjs.org/docs",
|
|
708
|
+
description: "Next.js docs"
|
|
709
|
+
}
|
|
367
710
|
}
|
|
368
711
|
]
|
|
369
712
|
}
|
|
@@ -372,59 +715,19 @@ describe("resolve", () => {
|
|
|
372
715
|
]
|
|
373
716
|
};
|
|
374
717
|
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
};
|
|
378
|
-
const result = await resolve(userConfig, docsetCatalog, registry);
|
|
379
|
-
|
|
380
|
-
expect(result.knowledge_sources).toHaveLength(2);
|
|
381
|
-
const ids = result.knowledge_sources.map((ks) => ks.name);
|
|
382
|
-
expect(ids).toContain("react-docs");
|
|
383
|
-
expect(ids).toContain("nextjs-docs");
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it("filters out excluded_docsets", async () => {
|
|
387
|
-
const docsetCatalog: Catalog = {
|
|
388
|
-
facets: [
|
|
389
|
-
{
|
|
390
|
-
id: "arch",
|
|
391
|
-
label: "Architecture",
|
|
392
|
-
description: "Stack",
|
|
393
|
-
required: false,
|
|
394
|
-
options: [
|
|
395
|
-
{
|
|
396
|
-
id: "react",
|
|
397
|
-
label: "React",
|
|
398
|
-
description: "React",
|
|
399
|
-
recipe: [],
|
|
400
|
-
docsets: [
|
|
401
|
-
{
|
|
402
|
-
id: "react-docs",
|
|
403
|
-
label: "React Reference",
|
|
404
|
-
origin: "https://github.com/facebook/react.git",
|
|
405
|
-
description: "React docs"
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
id: "react-tutorial",
|
|
409
|
-
label: "React Tutorial",
|
|
410
|
-
origin: "https://github.com/reactjs/react.dev.git",
|
|
411
|
-
description: "React tutorial"
|
|
412
|
-
}
|
|
413
|
-
]
|
|
414
|
-
}
|
|
415
|
-
]
|
|
416
|
-
}
|
|
417
|
-
]
|
|
418
|
-
};
|
|
718
|
+
const reg = createRegistry();
|
|
719
|
+
registerProvisionWriter(reg, docsetWriter);
|
|
419
720
|
|
|
420
721
|
const userConfig: UserConfig = {
|
|
421
|
-
choices: {
|
|
422
|
-
excluded_docsets: ["react-tutorial"]
|
|
722
|
+
choices: { stack: ["react", "nextjs"] }
|
|
423
723
|
};
|
|
424
|
-
const result = await resolve(userConfig, docsetCatalog,
|
|
724
|
+
const result = await resolve(userConfig, docsetCatalog, reg);
|
|
425
725
|
|
|
426
|
-
|
|
427
|
-
|
|
726
|
+
// react-docs appears twice but mergeLogicalConfig pushes all entries;
|
|
727
|
+
// both entries are present (dedup is intentionally not done at writer level)
|
|
728
|
+
const names = result.knowledge_sources.map((ks) => ks.name);
|
|
729
|
+
expect(names).toContain("react-docs");
|
|
730
|
+
expect(names).toContain("nextjs-docs");
|
|
428
731
|
});
|
|
429
732
|
|
|
430
733
|
it("adds knowledge-server MCP entry when knowledge_sources are present", async () => {
|
|
@@ -440,13 +743,15 @@ describe("resolve", () => {
|
|
|
440
743
|
id: "react",
|
|
441
744
|
label: "React",
|
|
442
745
|
description: "React",
|
|
443
|
-
recipe: [
|
|
444
|
-
docsets: [
|
|
746
|
+
recipe: [
|
|
445
747
|
{
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
748
|
+
writer: "docset",
|
|
749
|
+
config: {
|
|
750
|
+
id: "react-docs",
|
|
751
|
+
label: "React Reference",
|
|
752
|
+
origin: "https://github.com/facebook/react.git",
|
|
753
|
+
description: "React docs"
|
|
754
|
+
}
|
|
450
755
|
}
|
|
451
756
|
]
|
|
452
757
|
}
|
|
@@ -455,8 +760,11 @@ describe("resolve", () => {
|
|
|
455
760
|
]
|
|
456
761
|
};
|
|
457
762
|
|
|
763
|
+
const reg = createRegistry();
|
|
764
|
+
registerProvisionWriter(reg, docsetWriter);
|
|
765
|
+
|
|
458
766
|
const userConfig: UserConfig = { choices: { arch: "react" } };
|
|
459
|
-
const result = await resolve(userConfig, docsetCatalog,
|
|
767
|
+
const result = await resolve(userConfig, docsetCatalog, reg);
|
|
460
768
|
|
|
461
769
|
const knowledgeServer = result.mcp_servers.find(
|
|
462
770
|
(s) => s.ref === "knowledge"
|
|
@@ -478,7 +786,7 @@ describe("resolve", () => {
|
|
|
478
786
|
expect(knowledgeServer).toBeUndefined();
|
|
479
787
|
});
|
|
480
788
|
|
|
481
|
-
it("produces no knowledge_sources when option has no
|
|
789
|
+
it("produces no knowledge_sources when option has no docset provisions", async () => {
|
|
482
790
|
const userConfig: UserConfig = {
|
|
483
791
|
choices: { process: "native-agents-md" }
|
|
484
792
|
};
|
|
@@ -488,77 +796,6 @@ describe("resolve", () => {
|
|
|
488
796
|
});
|
|
489
797
|
});
|
|
490
798
|
|
|
491
|
-
describe("collectDocsets", () => {
|
|
492
|
-
it("returns deduplicated docsets for given choices", () => {
|
|
493
|
-
const docsetCatalog: Catalog = {
|
|
494
|
-
facets: [
|
|
495
|
-
{
|
|
496
|
-
id: "stack",
|
|
497
|
-
label: "Stack",
|
|
498
|
-
description: "Stack",
|
|
499
|
-
required: false,
|
|
500
|
-
multiSelect: true,
|
|
501
|
-
options: [
|
|
502
|
-
{
|
|
503
|
-
id: "a",
|
|
504
|
-
label: "A",
|
|
505
|
-
description: "A",
|
|
506
|
-
recipe: [],
|
|
507
|
-
docsets: [
|
|
508
|
-
{
|
|
509
|
-
id: "shared",
|
|
510
|
-
label: "Shared",
|
|
511
|
-
origin: "https://x",
|
|
512
|
-
description: "shared"
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
id: "a-only",
|
|
516
|
-
label: "A Only",
|
|
517
|
-
origin: "https://a",
|
|
518
|
-
description: "a"
|
|
519
|
-
}
|
|
520
|
-
]
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
id: "b",
|
|
524
|
-
label: "B",
|
|
525
|
-
description: "B",
|
|
526
|
-
recipe: [],
|
|
527
|
-
docsets: [
|
|
528
|
-
{
|
|
529
|
-
id: "shared",
|
|
530
|
-
label: "Shared",
|
|
531
|
-
origin: "https://x",
|
|
532
|
-
description: "shared"
|
|
533
|
-
},
|
|
534
|
-
{
|
|
535
|
-
id: "b-only",
|
|
536
|
-
label: "B Only",
|
|
537
|
-
origin: "https://b",
|
|
538
|
-
description: "b"
|
|
539
|
-
}
|
|
540
|
-
]
|
|
541
|
-
}
|
|
542
|
-
]
|
|
543
|
-
}
|
|
544
|
-
]
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
const result = collectDocsets({ stack: ["a", "b"] }, docsetCatalog);
|
|
548
|
-
|
|
549
|
-
expect(result).toHaveLength(3);
|
|
550
|
-
const ids = result.map((d) => d.id);
|
|
551
|
-
expect(ids).toContain("shared");
|
|
552
|
-
expect(ids).toContain("a-only");
|
|
553
|
-
expect(ids).toContain("b-only");
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
it("returns empty array when no options have docsets", () => {
|
|
557
|
-
const result = collectDocsets({ process: "native-agents-md" }, catalog);
|
|
558
|
-
expect(result).toEqual([]);
|
|
559
|
-
});
|
|
560
|
-
});
|
|
561
|
-
|
|
562
799
|
describe("MCP server dedup by ref", () => {
|
|
563
800
|
it("deduplicates mcp_servers by ref, keeping the last one", async () => {
|
|
564
801
|
// Create a custom registry with a writer that produces duplicate refs
|