@lmzhen/dsh-evolution-skill-discovery 0.30.43 → 0.31.1

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/lib/index.js CHANGED
@@ -1,216 +1,8 @@
1
- import { createHash } from "node:crypto";
2
- import { join, resolve } from "node:path";
1
+ import { $ as DomainMapCorruptError, A as ViewStateRegistry, B as renderProjectionContext, C as DOMAIN_GENERATION_PROMPT, D as DOMAIN_GENERATION_PROMPT_VERSION, E as DOMAIN_GENERATION_PROMPT_V2, F as applyProjectedDecision, G as restoreCatalog, H as captureCatalog, I as applyReplacementDecision, J as projectCatalog, K as buildDomainProjection, L as applyRestoreUpdate, M as canProject, N as deriveViewState, O as buildDomainGenerationPrompt, P as applyCapturedProjection, Q as DOMAIN_MAP_SCHEMA_VERSION, R as captureDecisionCatalog, S as parseDomainGenerationProposal, T as DOMAIN_GENERATION_PROMPT_V1, U as renderCatalog, V as restoreCapturedProjection, W as renderCatalogUpdate, X as evaluateDomainQuality, Y as sanitizeUntrustedText, Z as ProjectionJournal, _ as buildGenerationRequest, a as DISCOVERY_AWARENESS, at as mergeDomainPolicy, b as DomainGenerationError, c as AssignmentWorker, ct as bootstrapDomainMapWithFacts, d as MemoryAssignmentQueue, dt as validateDomainGeneration, et as DomainMapFileStore, f as assignmentKey, g as ViewSnapshotStore, h as validateProposal, i as shouldRegenerateDescription, it as DomainPolicyValidationError, j as canHide, k as ProjectionRuntime, l as JsonAssignmentQueue, lt as domainGenerationMode, m as proposalDigest, n as descriptionIsServable, nt as DomainMapVersionConflict, o as installDiscoveryAwareness, ot as DomainGenerationCoordinator, p as coveredByDomain, q as hiddenRecoverable, r as renderDomainDescription, rt as DomainPolicyStore, s as installAssignmentSidecar, st as bootstrapDomainMap, t as descriptionHashOf, tt as DomainMapValidationError, u as DomainAssignmentService, ut as mintDomainId, v as resolveGenerationPort, w as DOMAIN_GENERATION_PROMPT_HASH, x as LlmDomainGenerationPort, y as bootstrapFactsFromRows, z as findCatalogMessage } from "./domain-description-3MIOYgVH.js";
2
+ import { _ as skillKeyOf, a as SKILL_SEARCH_FIELDS, b as viewKeyOf, c as SkillSearchCache, d as SkillIndex, f as catalogRevisionOf, g as skillIndexRowOf, h as searchIndexKeyOf, i as searchWithDegraded, l as SkillSearchIndex, m as projectionDigestOf, n as isSkillSearch, o as SKILL_SEARCH_INDEX_SCHEMA_VERSION, p as policyDigestOf, r as registerSkillSearchTool, s as SKILL_SEARCH_TOKENIZER_VERSION, t as SKILL_SEARCH_DESCRIPTION, u as searchTerms, v as threeFieldHashOf, y as viewCatalogDigestOf } from "./tool-CvUTwKvW.js";
3
+ import { join } from "node:path";
4
+ import { authorityFrom, evolutionIoAdapter, evolutionRoot, newSkillLibrary } from "@lmzhen/dsh-evolution-core";
3
5
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
4
- import MiniSearch from "minisearch";
5
- import { authorityFrom, evolutionIoAdapter, evolutionRoot, isRecord, newSkillLibrary, parseFrontmatter } from "@lmzhen/dsh-evolution-core";
6
- import { defineTool } from "@deepseek-ai/dsh-tools";
7
- //#region lib/types/identity.js
8
- function sha256(value) {
9
- return createHash("sha256").update(value).digest("hex");
10
- }
11
- function canonical(value) {
12
- if (value instanceof Set) return { $set: [...value].map(canonical).sort((left, right) => JSON.stringify(left) < JSON.stringify(right) ? -1 : JSON.stringify(left) > JSON.stringify(right) ? 1 : 0) };
13
- if (value instanceof Map) return { $map: [...value.entries()].map(([key, entry]) => [canonical(key), canonical(entry)]).sort((left, right) => JSON.stringify(left) < JSON.stringify(right) ? -1 : JSON.stringify(left) > JSON.stringify(right) ? 1 : 0) };
14
- if (Array.isArray(value)) return value.map(canonical);
15
- if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => [key, canonical(entry)]));
16
- return value;
17
- }
18
- function digest$1(value) {
19
- return sha256(JSON.stringify(canonical(value)));
20
- }
21
- /** @returns stable key for the winning provider/source/name tuple. */
22
- function skillKeyOf(summary) {
23
- return JSON.stringify([
24
- summary.provider,
25
- summary.source,
26
- summary.name
27
- ]);
28
- }
29
- /** @returns stable hash of the canonical cwd and optional scope key. */
30
- function viewKeyOf(input) {
31
- return digest$1([resolve(input.cwd), input.scopeKey ?? null]);
32
- }
33
- /** @returns hash of the three fields used by discovery/retrieval. */
34
- function threeFieldHashOf(input) {
35
- return digest$1([
36
- input.name,
37
- input.description,
38
- input.whenToUse ?? null
39
- ]);
40
- }
41
- /** @returns normalized row for one skill summary. */
42
- function skillIndexRowOf(summary, managed, protectedSkill = false, pinned = false) {
43
- return {
44
- skillKey: skillKeyOf(summary),
45
- name: summary.name,
46
- description: summary.description,
47
- ...summary.whenToUse === void 0 ? {} : { whenToUse: summary.whenToUse },
48
- provider: summary.provider,
49
- source: summary.source,
50
- managed,
51
- modelInvocable: summary.invocation.modelInvocable,
52
- protected: protectedSkill,
53
- pinned,
54
- threeFieldHash: threeFieldHashOf(summary)
55
- };
56
- }
57
- /** @returns digest of the sorted rows plus completeness. */
58
- function catalogRevisionOf(rows, complete) {
59
- return digest$1([complete, rows.map((row) => [
60
- row.skillKey,
61
- row.name,
62
- row.description,
63
- row.whenToUse ?? null,
64
- row.provider,
65
- row.source,
66
- row.managed,
67
- row.modelInvocable,
68
- row.protected,
69
- row.pinned,
70
- row.threeFieldHash
71
- ])]);
72
- }
73
- /** @returns digest of a concrete per-view snapshot. */
74
- function viewCatalogDigestOf(viewKey, rows, complete) {
75
- return digest$1([viewKey, catalogRevisionOf(rows, complete)]);
76
- }
77
- /** @returns digest of effective policy plus its revisions. */
78
- function policyDigestOf(input) {
79
- return digest$1([
80
- input.globalPolicyRevision,
81
- input.workspaceOverlayRevision ?? null,
82
- input.scopeOverlayRevision ?? null,
83
- input.effectivePolicy
84
- ]);
85
- }
86
- /** @returns view-cache identity for one projection. */
87
- function projectionDigestOf(input) {
88
- return digest$1([
89
- input.viewKey,
90
- input.mapVersion,
91
- input.policyDigest,
92
- input.viewCatalogDigest,
93
- input.adapterVersion,
94
- input.projectionSchemaVersion,
95
- input.discoveryMode
96
- ]);
97
- }
98
- /** @returns view-cache identity for one search index. */
99
- function searchIndexKeyOf(input) {
100
- return digest$1([
101
- input.viewKey,
102
- input.viewCatalogDigest,
103
- input.policyDigest,
104
- input.tokenizerVersion,
105
- input.indexSchemaVersion,
106
- input.discoveryMode
107
- ]);
108
- }
109
- //#endregion
110
- //#region lib/types/security.js
111
- /**
112
- * Treat description text as data: strip control characters, escape HTML
113
- * delimiters, and cap length. It never turns data into instructions.
114
- * @param value - untrusted domain/skill text.
115
- * @param limit - maximum characters retained.
116
- * @returns escaped text plus truncation flag.
117
- */
118
- function sanitizeUntrustedText(value, limit = 2e3) {
119
- const escaped = value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
120
- return escaped.length <= limit ? {
121
- text: escaped,
122
- truncated: false
123
- } : {
124
- text: escaped.slice(0, limit),
125
- truncated: true
126
- };
127
- }
128
- //#endregion
129
- //#region lib/types/projection.js
130
- function escapeData$1(value) {
131
- return sanitizeUntrustedText(value, 2e3).text;
132
- }
133
- function renderCards(cards) {
134
- if (cards.length === 0) return "";
135
- return [
136
- "<system-reminder>",
137
- "Domain cards summarize where skills cluster; they support awareness, not retrieval. skill_search matches task text to skill summaries, skill(name) loads an exact skill, and the full catalog remains the fallback.",
138
- "<skill_domains>",
139
- ...cards.map((card) => [
140
- "- ",
141
- escapeData$1(card.id),
142
- " ",
143
- escapeData$1(card.title),
144
- ": ",
145
- escapeData$1(card.purpose),
146
- " triggers=",
147
- card.triggers.map(escapeData$1).join(","),
148
- " | managed=",
149
- String(card.managedModelInvocableCount),
150
- " external=",
151
- String(card.externalCatalogCount),
152
- " pending=",
153
- String(card.pendingCount),
154
- " state=",
155
- card.state,
156
- " descriptionStatus=",
157
- card.descriptionStatus,
158
- " asOf=",
159
- escapeData$1(card.asOf),
160
- " mapVersion=",
161
- escapeData$1(card.mapVersion),
162
- " mapStale=",
163
- String(card.mapStale)
164
- ].join("")),
165
- "</skill_domains>",
166
- "</system-reminder>"
167
- ].join("\n");
168
- }
169
- /**
170
- * Pure catalog projection: hide only active managed entries, preserve every
171
- * other entry, and render L1 cards. Incomplete input fails open.
172
- * @param input - view rows, active managed keys, cards, and identity inputs.
173
- * @returns projected entries/context/hidden names and digest.
174
- */
175
- function projectCatalog(input) {
176
- if (!input.complete) return {
177
- catalogEntries: input.entries,
178
- context: "",
179
- hiddenNames: [],
180
- scope: input.scope,
181
- digest: projectionDigestOf({
182
- viewKey: input.viewKey,
183
- mapVersion: input.mapVersion,
184
- policyDigest: input.policyDigest,
185
- viewCatalogDigest: input.viewCatalogDigest,
186
- adapterVersion: input.adapterVersion,
187
- projectionSchemaVersion: input.projectionSchemaVersion,
188
- discoveryMode: input.discoveryMode
189
- }),
190
- complete: false
191
- };
192
- const activeByName = /* @__PURE__ */ new Map();
193
- for (const row of input.rows) if (row.managed && row.modelInvocable && !row.protected && input.activeManagedSkillKeys.has(row.skillKey)) activeByName.set(row.name, row);
194
- const hiddenNames = input.entries.filter((entry) => activeByName.has(entry.name)).map((entry) => entry.name).sort();
195
- const hidden = new Set(hiddenNames);
196
- return {
197
- catalogEntries: input.entries.filter((entry) => !hidden.has(entry.name)),
198
- context: renderCards(input.cards),
199
- hiddenNames,
200
- scope: input.scope,
201
- digest: projectionDigestOf({
202
- viewKey: input.viewKey,
203
- mapVersion: input.mapVersion,
204
- policyDigest: input.policyDigest,
205
- viewCatalogDigest: input.viewCatalogDigest,
206
- adapterVersion: input.adapterVersion,
207
- projectionSchemaVersion: input.projectionSchemaVersion,
208
- discoveryMode: input.discoveryMode
209
- }),
210
- complete: true
211
- };
212
- }
213
- //#endregion
214
6
  //#region lib/types/adapter.js
215
7
  /** @returns true for the platform catalog message. */
216
8
  function isCatalogMessage(message) {
@@ -219,7 +11,7 @@ function isCatalogMessage(message) {
219
11
  function escapeData(value) {
220
12
  return sanitizeUntrustedText(value, 2e3).text;
221
13
  }
222
- function renderCatalog(entries) {
14
+ function renderCatalog$1(entries) {
223
15
  if (entries.length === 0) return [];
224
16
  return [
225
17
  "<system-reminder>",
@@ -247,7 +39,7 @@ function projectionPresent(agent, digest) {
247
39
  return false;
248
40
  }
249
41
  function renderProjection(result) {
250
- const text = [...renderCatalog(result.catalogEntries), ...result.context === "" ? [] : [result.context]].join("\n");
42
+ const text = [...renderCatalog$1(result.catalogEntries), ...result.context === "" ? [] : [result.context]].join("\n");
251
43
  if (text === "") return void 0;
252
44
  return createUserMessage({
253
45
  content: [{
@@ -350,337 +142,6 @@ function mayAdvance(current, next) {
350
142
  return stageIndex(next) === stageIndex(current) + 1;
351
143
  }
352
144
  //#endregion
353
- //#region lib/types/search.js
354
- /** Tokenizer version; changing the algorithm requires rebuilding indexes. */
355
- const SKILL_SEARCH_TOKENIZER_VERSION = "cjk-bigram-v1";
356
- /** Fields indexed by skill search; no body/path. */
357
- const SKILL_SEARCH_FIELDS = [
358
- "name",
359
- "description",
360
- "whenToUse"
361
- ];
362
- /** Index schema version; bump when indexed fields/options change. */
363
- const SKILL_SEARCH_INDEX_SCHEMA_VERSION = "skill-search-v1";
364
- const CJK = String.raw`[[\p{scx=Han}\p{scx=Hira}\p{scx=Kana}\p{scx=Hang}]&&[\p{L}\p{M}\p{N}]]`;
365
- const RUN = new RegExp(String.raw`${CJK}+|[[\p{L}\p{M}\p{N}]--${CJK}]+`, "gv");
366
- const CJK_START = new RegExp(String.raw`^${CJK}`, "v");
367
- /**
368
- * Split NFKC text into CJK overlapping bigrams plus tail, and ASCII/other runs.
369
- * Parity source: plugin-market/src/search.ts searchTerms.
370
- * @param text - indexed or queried text.
371
- * @returns terms in text order; callers lowercase.
372
- */
373
- function searchTerms(text) {
374
- return [...text.normalize("NFKC").matchAll(RUN)].flatMap(([run]) => {
375
- if (!CJK_START.test(run)) return [run];
376
- const chars = Array.from(run);
377
- return [...chars.slice(1).map((char, position) => chars[position] + char), chars.at(-1)];
378
- });
379
- }
380
- function clampLimit(limit) {
381
- if (limit === void 0) return 3;
382
- return Math.min(20, Math.max(1, Math.trunc(limit)));
383
- }
384
- function build(rows) {
385
- const engine = new MiniSearch({
386
- fields: [...SKILL_SEARCH_FIELDS],
387
- storeFields: [...SKILL_SEARCH_FIELDS],
388
- tokenize: searchTerms,
389
- processTerm: (term) => term.toLowerCase(),
390
- searchOptions: {
391
- boost: {
392
- name: 3,
393
- whenToUse: 2
394
- },
395
- prefix: true,
396
- combineWith: "AND",
397
- fuzzy: (term) => term.length >= 4 && !CJK_START.test(term) ? .2 : false,
398
- maxFuzzy: 2
399
- }
400
- });
401
- const seen = /* @__PURE__ */ new Set();
402
- const documents = [];
403
- for (const row of rows) {
404
- if (!row.modelInvocable || row.protected || row.pinned || seen.has(row.skillKey)) continue;
405
- seen.add(row.skillKey);
406
- documents.push({
407
- id: row.skillKey,
408
- name: row.name,
409
- description: row.description,
410
- whenToUse: row.whenToUse ?? ""
411
- });
412
- }
413
- engine.addAll(documents);
414
- return engine;
415
- }
416
- /** Per-view MiniSearch index; no global engine and no projection-only filtering. */
417
- var SkillSearchIndex = class SkillSearchIndex {
418
- engine;
419
- staleState = false;
420
- viewKey;
421
- constructor(viewKey, engine) {
422
- this.viewKey = viewKey;
423
- this.engine = engine;
424
- }
425
- /** @returns an index over every modelInvocable row in this view. */
426
- static fromRows(viewKey, rows) {
427
- return new SkillSearchIndex(viewKey, build(rows));
428
- }
429
- /** Mark the current index stale before a rebuild finishes. */
430
- markStale() {
431
- this.staleState = true;
432
- }
433
- /** Rebuild from this view's full rows; a failed rebuild keeps the old engine stale. */
434
- async rebuild(rows) {
435
- this.staleState = true;
436
- try {
437
- this.engine = build(rows);
438
- this.staleState = false;
439
- return true;
440
- } catch {
441
- return false;
442
- }
443
- }
444
- /** @returns hits, truncation, and whether the index is stale. */
445
- async search(query, options) {
446
- const limit = clampLimit(query.limit);
447
- const terms = searchTerms(query.query);
448
- if (terms.length === 0) return {
449
- results: [],
450
- truncated: false,
451
- stale: this.staleState
452
- };
453
- let fallback = false;
454
- let hits = this.engine.search(query.query, { combineWith: "AND" });
455
- if (hits.length === 0 && terms.length > 1) {
456
- const relaxed = this.engine.search(query.query, { combineWith: "OR" });
457
- fallback = relaxed.length > 0;
458
- hits = relaxed;
459
- }
460
- const allowed = options?.allowedSkillKeys;
461
- const filtered = allowed === void 0 ? hits : hits.filter((hit) => allowed.has(String(hit.id)));
462
- return {
463
- results: filtered.slice(0, limit).map((hit) => {
464
- const fields = new Set(Object.values(hit.match).flat());
465
- return {
466
- name: String(hit.name),
467
- description: String(hit.description),
468
- ...hit.whenToUse === void 0 || hit.whenToUse === "" ? {} : { whenToUse: String(hit.whenToUse) },
469
- matchedBy: fields.has("name") ? "name" : fields.has("whenToUse") ? "whenToUse" : "description"
470
- };
471
- }),
472
- truncated: filtered.length > limit,
473
- ...fallback ? { fallback: true } : {},
474
- stale: this.staleState
475
- };
476
- }
477
- };
478
- /** Per-view cache keyed by searchIndexKey; never shares one engine across views. */
479
- var SkillSearchCache = class {
480
- entries = /* @__PURE__ */ new Map();
481
- /**
482
- * Get or build one view index. A failed rebuild retains the previous engine
483
- * and marks it stale; no previous engine means undefined.
484
- * @param key - identity inputs for searchIndexKey.
485
- * @param rows - current full rows for the view.
486
- * @returns index plus stale state, or undefined when no index is available.
487
- */
488
- async get(key, rows) {
489
- const cacheKey = searchIndexKeyOf({
490
- viewKey: key.viewKey,
491
- viewCatalogDigest: key.viewCatalogDigest,
492
- policyDigest: key.policyDigest,
493
- tokenizerVersion: SKILL_SEARCH_TOKENIZER_VERSION,
494
- indexSchemaVersion: SKILL_SEARCH_INDEX_SCHEMA_VERSION,
495
- discoveryMode: key.discoveryMode
496
- });
497
- const existing = this.entries.get(cacheKey);
498
- if (existing !== void 0) return {
499
- index: existing.index,
500
- stale: false
501
- };
502
- try {
503
- const index = SkillSearchIndex.fromRows(key.viewKey, rows);
504
- this.entries.set(cacheKey, {
505
- key: cacheKey,
506
- viewKey: key.viewKey,
507
- index
508
- });
509
- return {
510
- index,
511
- stale: false
512
- };
513
- } catch {
514
- const old = [...this.entries.values()].find((entry) => entry.viewKey === key.viewKey);
515
- if (old === void 0) return void 0;
516
- old.index.markStale();
517
- return {
518
- index: old.index,
519
- stale: true
520
- };
521
- }
522
- }
523
- /** Drop every index for one view. */
524
- invalidate(viewKey) {
525
- for (const [cacheKey, entry] of this.entries) if (entry.viewKey === viewKey) this.entries.delete(cacheKey);
526
- }
527
- /** Drop every cached view index. */
528
- clear() {
529
- this.entries.clear();
530
- }
531
- };
532
- //#endregion
533
- //#region lib/types/domain-map-store.js
534
- /** File schema version supported by this store. */
535
- const DOMAIN_MAP_SCHEMA_VERSION = 1;
536
- /** Typed failure for an invalid persisted file. */
537
- var DomainMapCorruptError = class extends Error {
538
- code = "domain-map-corrupt";
539
- constructor(message) {
540
- super(message);
541
- this.name = "DomainMapCorruptError";
542
- }
543
- };
544
- /** Typed failure for a CAS mismatch. */
545
- var DomainMapVersionConflict = class extends Error {
546
- code = "domain-map-version-conflict";
547
- expected;
548
- actual;
549
- constructor(expected, actual) {
550
- super("domain map version conflict: expected " + expected + ", found " + actual);
551
- this.expected = expected;
552
- this.actual = actual;
553
- this.name = "DomainMapVersionConflict";
554
- }
555
- };
556
- /** Typed failure for an invalid write. */
557
- var DomainMapValidationError = class extends Error {
558
- code = "domain-map-invalid";
559
- constructor(message) {
560
- super(message);
561
- this.name = "DomainMapValidationError";
562
- }
563
- };
564
- function emptySnapshot() {
565
- return {
566
- mapVersion: "0",
567
- domains: [],
568
- edges: []
569
- };
570
- }
571
- function assertNode(node) {
572
- if (!isRecord(node) || typeof node.id !== "string" || node.id.length === 0) throw new DomainMapCorruptError("domain node id is missing");
573
- if (typeof node.name !== "string" || typeof node.purpose !== "string" || typeof node.description !== "string") throw new DomainMapCorruptError("domain node text fields are missing");
574
- if (!Array.isArray(node.inScope) || !Array.isArray(node.outOfScope) || !Array.isArray(node.triggers) || !Array.isArray(node.parentIds)) throw new DomainMapCorruptError("domain node arrays are invalid");
575
- if (node.status !== "active" && node.status !== "provisional" && node.status !== "deprecated") throw new DomainMapCorruptError("domain node status is invalid");
576
- }
577
- function assertEdge(edge) {
578
- if (!isRecord(edge) || typeof edge.skillKey !== "string" || typeof edge.domainId !== "string") throw new DomainMapCorruptError("skill edge identity is missing");
579
- if (edge.role !== "primary" && edge.role !== "secondary") throw new DomainMapCorruptError("skill edge role is invalid");
580
- if (edge.status !== "active" && edge.status !== "stale" && edge.status !== "pending") throw new DomainMapCorruptError("skill edge status is invalid");
581
- if (typeof edge.confidence !== "number" || edge.confidence < 0 || edge.confidence > 1) throw new DomainMapCorruptError("skill edge confidence is invalid");
582
- }
583
- function validateWrite(next) {
584
- if (next.mapVersion.length === 0) throw new DomainMapValidationError("mapVersion must be non-empty");
585
- const ids = /* @__PURE__ */ new Set();
586
- for (const node of next.domains) {
587
- assertNode(node);
588
- if (ids.has(node.id)) throw new DomainMapValidationError("duplicate domain id " + node.id);
589
- ids.add(node.id);
590
- }
591
- for (const node of next.domains) for (const parent of node.parentIds) {
592
- if (parent === node.id) throw new DomainMapValidationError("domain self-parent " + node.id);
593
- if (!ids.has(parent)) throw new DomainMapValidationError("unknown parent domain " + parent);
594
- }
595
- const edgeKeys = /* @__PURE__ */ new Set();
596
- const primaries = /* @__PURE__ */ new Map();
597
- for (const edge of next.edges) {
598
- assertEdge(edge);
599
- if (!ids.has(edge.domainId)) throw new DomainMapValidationError("unknown edge domain " + edge.domainId);
600
- const key = edge.skillKey + "\0" + edge.domainId;
601
- if (edgeKeys.has(key)) throw new DomainMapValidationError("duplicate skill/domain edge");
602
- edgeKeys.add(key);
603
- if (edge.role === "primary") {
604
- const count = (primaries.get(edge.skillKey) ?? 0) + 1;
605
- if (count > 1) throw new DomainMapValidationError("more than one primary domain for " + edge.skillKey);
606
- primaries.set(edge.skillKey, count);
607
- }
608
- }
609
- }
610
- /**
611
- * JSON-backed DomainMap owner. One file, schemaVersion, CAS mapVersion, and a
612
- * best-effort .bak for rollback; no evolution-state tables or graph DB.
613
- */
614
- var DomainMapFileStore = class {
615
- queue = Promise.resolve();
616
- io;
617
- path;
618
- now;
619
- constructor(io, path, now = Date.now) {
620
- this.io = io;
621
- this.path = path;
622
- this.now = now;
623
- }
624
- get backupPath() {
625
- return this.path + ".bak";
626
- }
627
- parse(content) {
628
- if (content === null) return emptySnapshot();
629
- let value;
630
- try {
631
- value = JSON.parse(content);
632
- } catch {
633
- throw new DomainMapCorruptError("domain map is not valid JSON");
634
- }
635
- if (!isRecord(value) || value.schemaVersion !== 1 || typeof value.mapVersion !== "string" || !Array.isArray(value.domains) || !Array.isArray(value.edges)) throw new DomainMapCorruptError("domain map schema is unsupported");
636
- for (const node of value.domains) assertNode(node);
637
- for (const edge of value.edges) assertEdge(edge);
638
- return {
639
- mapVersion: value.mapVersion,
640
- domains: value.domains,
641
- edges: value.edges
642
- };
643
- }
644
- async read() {
645
- return this.parse(await this.io.readText(this.path));
646
- }
647
- async commitNow(current, expected, next) {
648
- const snapshot = this.parse(current);
649
- if (snapshot.mapVersion !== expected) throw new DomainMapVersionConflict(expected, snapshot.mapVersion);
650
- if (next.mapVersion === expected) throw new DomainMapValidationError("mapVersion must change on commit");
651
- validateWrite(next);
652
- if (current !== null) await this.io.writeText(this.backupPath, current);
653
- const file = {
654
- schemaVersion: 1,
655
- mapVersion: next.mapVersion,
656
- updatedAt: this.now(),
657
- domains: next.domains,
658
- edges: next.edges
659
- };
660
- return JSON.stringify(file);
661
- }
662
- async commit(expectedMapVersion, next) {
663
- const run = async () => {
664
- if (this.io.transact !== void 0) await this.io.transact(this.path, (current) => this.commitNow(current, expectedMapVersion, next));
665
- else {
666
- const current = await this.io.readText(this.path);
667
- await this.io.writeText(this.path, await this.commitNow(current, expectedMapVersion, next));
668
- }
669
- };
670
- const pending = this.queue.then(run, run);
671
- this.queue = pending.catch(() => {});
672
- await pending;
673
- return this.read();
674
- }
675
- async rollback() {
676
- const backup = await this.io.readText(this.backupPath);
677
- if (backup === null) throw new DomainMapCorruptError("domain map backup is missing");
678
- const snapshot = this.parse(backup);
679
- await this.io.writeText(this.path, backup);
680
- return snapshot;
681
- }
682
- };
683
- //#endregion
684
145
  //#region lib/types/domain-view.js
685
146
  /**
686
147
  * Count visible skills by active domain membership.
@@ -706,219 +167,6 @@ function domainViewCounts(snapshot, visibleSkillKeys) {
706
167
  };
707
168
  }
708
169
  //#endregion
709
- //#region lib/types/domain-policy.js
710
- /** Invalid policy layer (e.g. attempts to edit taxonomy). */
711
- var DomainPolicyValidationError = class extends Error {
712
- code = "domain-policy-invalid";
713
- constructor(message) {
714
- super(message);
715
- this.name = "DomainPolicyValidationError";
716
- }
717
- };
718
- const ALLOWED = new Set([
719
- "mode",
720
- "pinDomains",
721
- "disableDomains",
722
- "revision"
723
- ]);
724
- const MODES$1 = new Set([
725
- "off",
726
- "auto",
727
- "on"
728
- ]);
729
- function checkLayer(layer) {
730
- for (const key of Object.keys(layer)) if (!ALLOWED.has(key)) throw new DomainPolicyValidationError("unsupported policy field " + key);
731
- if (layer.mode !== void 0 && !MODES$1.has(layer.mode)) throw new DomainPolicyValidationError("unsupported policy mode");
732
- }
733
- function knownOnly(values, known, degraded) {
734
- const result = /* @__PURE__ */ new Set();
735
- for (const value of values ?? []) {
736
- if (known !== void 0 && !known.has(value)) {
737
- degraded.add(value);
738
- continue;
739
- }
740
- result.add(value);
741
- }
742
- return result;
743
- }
744
- /**
745
- * Merge global -> workspace -> scope policy. Disable wins over pin; unknown
746
- * domains are ignored and reported as degraded.
747
- * @param global - mandatory global layer.
748
- * @param workspace - optional workspace overlay.
749
- * @param scope - optional scope overlay.
750
- * @param knownDomainIds - optional known ids for validation.
751
- * @returns immutable effective policy.
752
- */
753
- function mergeDomainPolicy(global, workspace, scope, knownDomainIds) {
754
- checkLayer(global);
755
- if (workspace) checkLayer(workspace);
756
- if (scope) checkLayer(scope);
757
- const degraded = /* @__PURE__ */ new Set();
758
- const blocked = new Set([
759
- ...knownOnly(global.disableDomains, knownDomainIds, degraded),
760
- ...knownOnly(workspace?.disableDomains, knownDomainIds, degraded),
761
- ...knownOnly(scope?.disableDomains, knownDomainIds, degraded)
762
- ]);
763
- const pinned = new Set([
764
- ...knownOnly(global.pinDomains, knownDomainIds, degraded),
765
- ...knownOnly(workspace?.pinDomains, knownDomainIds, degraded),
766
- ...knownOnly(scope?.pinDomains, knownDomainIds, degraded)
767
- ]);
768
- for (const id of blocked) pinned.delete(id);
769
- return {
770
- mode: scope?.mode ?? workspace?.mode ?? global.mode ?? "off",
771
- pinned,
772
- disabled: blocked,
773
- degraded: [...degraded].sort(),
774
- globalRevision: global.revision,
775
- workspaceRevision: workspace?.revision,
776
- scopeRevision: scope?.revision
777
- };
778
- }
779
- //#endregion
780
- //#region lib/types/domain-generation.js
781
- /** @returns stable id derived from title, not the title itself. */
782
- function mintDomainId(title) {
783
- return "domain-" + createHash("sha256").update(title).digest("hex").slice(0, 12);
784
- }
785
- /** @returns direct for <=100 skills; prechunk-eligible above 100. */
786
- function domainGenerationMode(skillCount) {
787
- if (!Number.isSafeInteger(skillCount) || skillCount < 0) throw new RangeError("skillCount must be a non-negative safe integer");
788
- return skillCount <= 100 ? "direct" : "prechunk-eligible";
789
- }
790
- function nonEmpty(value) {
791
- return typeof value === "string" && value.trim().length > 0;
792
- }
793
- function nonEmptyList(value) {
794
- return Array.isArray(value) && value.length > 0 && value.every(nonEmpty);
795
- }
796
- function forbiddenKeys(value) {
797
- return [
798
- "primaryParent",
799
- "primaryParentId",
800
- "parent",
801
- "embedding",
802
- "embeddingVector",
803
- "taxonomyMethod"
804
- ].filter((key) => key in value);
805
- }
806
- /**
807
- * Validate an initial generation proposal and mint ids. Does not call an LLM
808
- * and does not read skill bodies; embedding taxonomy is rejected.
809
- * @param input - skills the proposal was generated from.
810
- * @param proposal - LLM output.
811
- * @returns normalized domains or stable errors.
812
- */
813
- function validateDomainGeneration(input, proposal) {
814
- const errors = [];
815
- if (proposal.domains.length < 8 || proposal.domains.length > 12) errors.push("domain count must be 8-12");
816
- const known = new Set(input.skills.map((skill) => skill.name));
817
- const ids = /* @__PURE__ */ new Set();
818
- const titles = /* @__PURE__ */ new Set();
819
- const domains = [];
820
- for (const candidate of proposal.domains) {
821
- const bad = forbiddenKeys(candidate);
822
- if (bad.length > 0) errors.push("forbidden fields: " + bad.join(","));
823
- if (!nonEmpty(candidate.title) || !nonEmpty(candidate.purpose)) errors.push("domain title/purpose is empty");
824
- if (!nonEmptyList(candidate.inScope) || !nonEmptyList(candidate.outOfScope) || !nonEmptyList(candidate.triggers)) errors.push("domain scope/triggers are empty");
825
- if (!Array.isArray(candidate.evidenceSkillKeys) || candidate.evidenceSkillKeys.length === 0) errors.push("domain evidence is empty");
826
- if (candidate.evidenceSkillKeys?.some((key) => !known.has(key)) === true) errors.push("domain evidence references unknown skill");
827
- const id = mintDomainId(candidate.title);
828
- if (ids.has(id)) errors.push("duplicate minted domain id");
829
- if (titles.has(candidate.title)) errors.push("duplicate domain title");
830
- ids.add(id);
831
- titles.add(candidate.title);
832
- domains.push({
833
- ...candidate,
834
- id
835
- });
836
- }
837
- if ("embedding" in proposal || "taxonomyMethod" in proposal) errors.push("embedding taxonomy is not allowed");
838
- return errors.length === 0 ? {
839
- ok: true,
840
- domains
841
- } : {
842
- ok: false,
843
- errors
844
- };
845
- }
846
- //#endregion
847
- //#region lib/types/domain-description.js
848
- /** @returns true only for structural/definition events; normal skill add does not rewrite. */
849
- function shouldRegenerateDescription(event) {
850
- return event !== "skill-add";
851
- }
852
- /** @returns true when the existing description may still serve. */
853
- function descriptionIsServable(status) {
854
- return status === "fresh" || status === "stale";
855
- }
856
- /** @returns hash of child ids/description versions, never member skill names. */
857
- function descriptionHashOf(domain, children) {
858
- const childRefs = children.map((child) => JSON.stringify([child.id, child.descriptionVersion])).sort();
859
- return createHash("sha256").update(JSON.stringify([
860
- domain.id,
861
- domain.descriptionVersion,
862
- childRefs
863
- ])).digest("hex");
864
- }
865
- /**
866
- * Render a domain description from definition fields only. It never appends
867
- * evidence/member skill names and is capped to 600 characters.
868
- * @param domain - domain definition.
869
- * @returns description text.
870
- */
871
- function renderDomainDescription(domain) {
872
- return [
873
- domain.name + ":" + domain.purpose,
874
- "适用:" + domain.inScope.join(";"),
875
- "不适用:" + domain.outOfScope.join(";"),
876
- "触发:" + domain.triggers.join(";")
877
- ].join("\n").slice(0, 600);
878
- }
879
- //#endregion
880
- //#region lib/types/domain-quality.js
881
- function tokens(values) {
882
- return new Set(values.join(" ").toLowerCase().split(/[^\p{Letter}\p{Number}]+/u).filter((token) => token.length > 0));
883
- }
884
- function jaccard(left, right) {
885
- if (left.size === 0 && right.size === 0) return 1;
886
- let overlap = 0;
887
- for (const token of left) if (right.has(token)) overlap += 1;
888
- return overlap / (left.size + right.size - overlap);
889
- }
890
- /**
891
- * Evaluate whether a domain definition is specific, triggered, and distinct.
892
- * @param domain - candidate domain.
893
- * @param peers - other visible domains.
894
- * @returns auditable issues; does not mutate the candidate.
895
- */
896
- function evaluateDomainQuality(domain, peers) {
897
- const issues = [];
898
- if (domain.purpose.trim().length === 0) issues.push("empty-purpose");
899
- else if (domain.purpose.trim().length < 8) issues.push("vague-purpose");
900
- if (domain.inScope.length === 0) issues.push("empty-in-scope");
901
- if (domain.outOfScope.length === 0) issues.push("empty-out-of-scope");
902
- if (domain.triggers.length === 0) issues.push("empty-triggers");
903
- const own = tokens([...domain.inScope, ...domain.triggers]);
904
- for (const peer of peers) {
905
- if (domain.name.trim().toLowerCase() === peer.name.trim().toLowerCase() || domain.purpose.trim().toLowerCase() === peer.purpose.trim().toLowerCase()) {
906
- issues.push("indistinguishable");
907
- break;
908
- }
909
- if (jaccard(own, tokens([...peer.inScope, ...peer.triggers])) > .8) {
910
- issues.push("overlaps-peer");
911
- break;
912
- }
913
- }
914
- const unique = [...new Set(issues)];
915
- return {
916
- passed: unique.length === 0,
917
- issues: unique,
918
- score: Math.max(0, 1 - unique.length * .2)
919
- };
920
- }
921
- //#endregion
922
170
  //#region lib/types/domain-metrics.js
923
171
  /**
924
172
  * @param snapshot - domain facts.
@@ -974,130 +222,6 @@ function precision(expected, predicted) {
974
222
  return predicted.filter((id) => wanted.has(id)).length / predicted.length;
975
223
  }
976
224
  //#endregion
977
- //#region lib/types/domain-projection.js
978
- function qualityPasses(node, domains) {
979
- return evaluateDomainQuality(node, domains.filter((peer) => peer.id !== node.id)).passed;
980
- }
981
- /**
982
- * Build L1 cards and the hidden set for one view, then delegate the pure
983
- * catalog rewrite. Structural staleness fails open; description staleness does not.
984
- * @param input - rows, domain facts, policy, and identity inputs.
985
- * @returns projection result with cards and hidden names.
986
- */
987
- function hiddenRecoverable(hiddenNames, searchResultNames) {
988
- return hiddenNames.every((name) => searchResultNames.has(name));
989
- }
990
- function buildDomainProjection(input) {
991
- const mode = input.mode ?? "on";
992
- if (mode === "off") return projectCatalog({
993
- entries: input.entries,
994
- rows: input.rows,
995
- activeManagedSkillKeys: /* @__PURE__ */ new Set(),
996
- cards: [],
997
- viewKey: input.viewKey,
998
- scope: input.scope,
999
- mapVersion: input.mapVersion,
1000
- policyDigest: input.policyDigest,
1001
- viewCatalogDigest: input.viewCatalogDigest,
1002
- adapterVersion: input.adapterVersion,
1003
- projectionSchemaVersion: input.projectionSchemaVersion,
1004
- discoveryMode: input.discoveryMode,
1005
- complete: false
1006
- });
1007
- const staleByAge = input.staleAfterHours !== void 0 && input.snapshot.domains.some((node) => input.now !== void 0 && input.now - node.updatedAt > input.staleAfterHours * 36e5);
1008
- const mapStale = input.mapStale === true || staleByAge;
1009
- if (!(input.complete && !mapStale && input.searchAvailable !== false)) return projectCatalog({
1010
- entries: input.entries,
1011
- rows: input.rows,
1012
- activeManagedSkillKeys: /* @__PURE__ */ new Set(),
1013
- cards: [],
1014
- viewKey: input.viewKey,
1015
- scope: input.scope,
1016
- mapVersion: input.mapVersion,
1017
- policyDigest: input.policyDigest,
1018
- viewCatalogDigest: input.viewCatalogDigest,
1019
- adapterVersion: input.adapterVersion,
1020
- projectionSchemaVersion: input.projectionSchemaVersion,
1021
- discoveryMode: input.discoveryMode,
1022
- complete: false
1023
- });
1024
- const rowByKey = new Map(input.rows.map((row) => [row.skillKey, row]));
1025
- const domains = input.snapshot.domains.filter((node) => node.status === "active" || node.status === "provisional");
1026
- const cardDomains = domains.filter((node) => node.parentIds.length <= (input.maxVisibleDepth ?? 3) - 1 && node.parentIds.length === 0).filter((node) => !input.policy.disabled.has(node.id)).filter((node) => input.policy.pinned.has(node.id) || qualityPasses(node, domains)).sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0).slice(0, Math.max(1, input.maxCards ?? 12));
1027
- const domainById = new Map(domains.map((node) => [node.id, node]));
1028
- const cards = cardDomains.map((node) => {
1029
- let managedModelInvocableCount = 0;
1030
- let externalCatalogCount = 0;
1031
- let pendingCount = 0;
1032
- for (const edge of input.snapshot.edges) {
1033
- if (edge.domainId !== node.id) continue;
1034
- if (edge.status === "pending") {
1035
- pendingCount += 1;
1036
- continue;
1037
- }
1038
- if (edge.status !== "active") continue;
1039
- const row = rowByKey.get(edge.skillKey);
1040
- if (row === void 0) continue;
1041
- if (row.managed && row.modelInvocable && !row.protected && !row.pinned) managedModelInvocableCount += 1;
1042
- else if (!row.managed) externalCatalogCount += 1;
1043
- }
1044
- return {
1045
- id: node.id,
1046
- title: node.name,
1047
- purpose: node.purpose.slice(0, 80),
1048
- descriptionStatus: node.descriptionStatus,
1049
- triggers: node.triggers.slice(0, 3),
1050
- managedModelInvocableCount,
1051
- externalCatalogCount,
1052
- managedCount: managedModelInvocableCount,
1053
- externalCount: externalCatalogCount,
1054
- pendingCount,
1055
- state: node.status === "provisional" ? "provisional" : "active",
1056
- asOf: new Date(node.updatedAt).toISOString(),
1057
- mapVersion: input.mapVersion,
1058
- mapStale
1059
- };
1060
- });
1061
- const budget = input.tokenBudget;
1062
- if (budget !== void 0 && budget > 0) while (cards.length > 1 && Math.ceil(JSON.stringify(cards).length / 4) > budget) cards.pop();
1063
- const cardIds = new Set(cards.map((card) => card.id));
1064
- const wouldHide = /* @__PURE__ */ new Set();
1065
- for (const edge of input.snapshot.edges) {
1066
- if (edge.status !== "active" || edge.role !== "primary") continue;
1067
- const node = domainById.get(edge.domainId);
1068
- const row = rowByKey.get(edge.skillKey);
1069
- if (node === void 0 || row === void 0) continue;
1070
- if (node.status !== "active" || !cardIds.has(node.id) || !row.managed || !row.modelInvocable || row.protected || row.pinned) continue;
1071
- if (input.policy.disabled.has(node.id)) continue;
1072
- wouldHide.add(edge.skillKey);
1073
- }
1074
- const activeManaged = mode === "on" ? wouldHide : /* @__PURE__ */ new Set();
1075
- const projected = projectCatalog({
1076
- entries: input.entries,
1077
- rows: input.rows,
1078
- activeManagedSkillKeys: activeManaged,
1079
- cards,
1080
- viewKey: input.viewKey,
1081
- scope: input.scope,
1082
- mapVersion: input.mapVersion,
1083
- policyDigest: input.policyDigest,
1084
- viewCatalogDigest: input.viewCatalogDigest,
1085
- adapterVersion: input.adapterVersion,
1086
- projectionSchemaVersion: input.projectionSchemaVersion,
1087
- discoveryMode: input.discoveryMode,
1088
- complete: true
1089
- });
1090
- return mode === "on" ? projected : {
1091
- ...projected,
1092
- shadowHiddenNames: [...wouldHide],
1093
- shadowRecoveredNames: [...wouldHide]
1094
- };
1095
- }
1096
- /** @returns true when shadow evidence is sufficient to promote to on. */
1097
- function canPromoteShadow(input) {
1098
- return input.samples >= 20 && input.hiddenRecoverableRate >= 1 && input.silentMisses === 0;
1099
- }
1100
- //#endregion
1101
225
  //#region lib/types/session-projection.js
1102
226
  /** @returns true only for a new, fully-enabled session in the current epoch. */
1103
227
  function shouldHideCatalog(meta, currentFeatureEpoch) {
@@ -1122,492 +246,6 @@ function shouldInstallAwarenessOnResume(record, featureEpoch) {
1122
246
  return record !== void 0 && record.discoveryMode !== "off" && record.featureEpoch === featureEpoch;
1123
247
  }
1124
248
  //#endregion
1125
- //#region lib/types/tool.js
1126
- /** Static model-facing description; no dynamic domain list/tree. */
1127
- const SKILL_SEARCH_DESCRIPTION = "Search skill summaries by name, description, or whenToUse when coverage is uncertain. Results are summaries, not bodies; a matching name can be loaded directly. Empty results are inconclusive; the full catalog remains available.";
1128
- /** @returns response or a degraded hint; never silent empty on failure. */
1129
- async function searchWithDegraded(provider, query, allowedSkillKeys, agent) {
1130
- if (provider === void 0) return {
1131
- results: [],
1132
- truncated: false,
1133
- degraded: true,
1134
- hint: "Skill search is unavailable. Use the full catalog or load an exact skill by name with skill(name)."
1135
- };
1136
- try {
1137
- return await provider.search(query, allowedSkillKeys === void 0 ? void 0 : { allowedSkillKeys }, agent);
1138
- } catch {
1139
- return {
1140
- results: [],
1141
- truncated: false,
1142
- degraded: true,
1143
- hint: "Skill search failed. Use the full catalog or load an exact skill by name with skill(name)."
1144
- };
1145
- }
1146
- }
1147
- function toToolResponse(response) {
1148
- return {
1149
- results: response.results.map((hit) => ({
1150
- name: hit.name,
1151
- description: hit.description,
1152
- ...hit.whenToUse === void 0 ? {} : { whenToUse: hit.whenToUse },
1153
- ...hit.matchedBy === void 0 ? {} : { matchedBy: hit.matchedBy }
1154
- })),
1155
- truncated: response.truncated,
1156
- ...response.fallback === void 0 ? {} : { fallback: response.fallback },
1157
- ...response.stale === void 0 ? {} : { stale: response.stale },
1158
- ...response.degraded === void 0 ? {} : { degraded: response.degraded },
1159
- ...response.hint === void 0 ? {} : { hint: response.hint }
1160
- };
1161
- }
1162
- /**
1163
- * Register skill_search on the optional tools service. The description/schema are static; the
1164
- * provider is the only dynamic dependency.
1165
- * platform-declaration anchor: tools is declared in core PLATFORM_SERVICE_PROBES.
1166
- * @param ctx - plugin context.
1167
- * @param provider - search service or undefined when unavailable.
1168
- * @returns tool disposer.
1169
- */
1170
- function registerSkillSearchTool(ctx, provider) {
1171
- const tools = ctx.get("tools");
1172
- if (tools === void 0) return () => {};
1173
- return tools.register(defineTool({
1174
- name: "skill_search",
1175
- description: SKILL_SEARCH_DESCRIPTION,
1176
- parameters: {
1177
- query: {
1178
- type: "string",
1179
- required: true,
1180
- description: "Search terms; CJK and ASCII are supported."
1181
- },
1182
- limit: {
1183
- type: "integer",
1184
- description: "Maximum results; defaults to 3, capped at 20."
1185
- }
1186
- },
1187
- output: {
1188
- schema: {
1189
- type: "object",
1190
- additionalProperties: false,
1191
- properties: {
1192
- results: {
1193
- type: "array",
1194
- required: true,
1195
- items: {
1196
- type: "object",
1197
- additionalProperties: false,
1198
- properties: {
1199
- name: {
1200
- type: "string",
1201
- required: true
1202
- },
1203
- description: {
1204
- type: "string",
1205
- required: true
1206
- },
1207
- whenToUse: { type: "string" },
1208
- matchedBy: { type: "string" }
1209
- }
1210
- }
1211
- },
1212
- truncated: {
1213
- type: "boolean",
1214
- required: true
1215
- },
1216
- fallback: { type: "boolean" },
1217
- stale: { type: "boolean" },
1218
- degraded: { type: "boolean" },
1219
- hint: { type: "string" }
1220
- }
1221
- },
1222
- render: (_args, value) => [{
1223
- type: "text",
1224
- text: value.degraded === true ? "Skill search is degraded: " + (value.hint ?? "use the full catalog or skill(name).") : "Found " + value.results.length + " skill result(s)" + (value.fallback === true ? " (OR fallback)." : ".") + (value.stale === true ? " (stale index)." : "") + "\n" + value.results.map((result) => "- " + result.name + ": " + result.description + (result.whenToUse === void 0 ? "" : " | when: " + result.whenToUse)).join("\n")
1225
- }]
1226
- },
1227
- async execute(args, exec) {
1228
- try {
1229
- const allowed = await provider?.allowedSkillKeys?.(exec.agent);
1230
- return toToolResponse(await searchWithDegraded(provider, {
1231
- query: args.query,
1232
- limit: args.limit
1233
- }, allowed, exec.agent));
1234
- } catch (error) {
1235
- return {
1236
- results: [],
1237
- truncated: false,
1238
- degraded: true,
1239
- hint: "Skill search failed: " + (error instanceof Error ? error.message : String(error))
1240
- };
1241
- }
1242
- }
1243
- }));
1244
- }
1245
- /** @returns true only for a search implementation; used by composition wiring. */
1246
- function isSkillSearch(value) {
1247
- return typeof value === "object" && value !== null && typeof value.search === "function";
1248
- }
1249
- //#endregion
1250
- //#region lib/types/assignment.js
1251
- /** In-memory idempotent queue; production adapter can persist by key. */
1252
- var MemoryAssignmentQueue = class {
1253
- entries = /* @__PURE__ */ new Map();
1254
- async enqueue(input) {
1255
- const existing = this.entries.get(input.key);
1256
- if (existing !== void 0) return {
1257
- entry: existing,
1258
- duplicate: true
1259
- };
1260
- const entry = {
1261
- ...input,
1262
- status: "queued"
1263
- };
1264
- this.entries.set(input.key, entry);
1265
- return {
1266
- entry,
1267
- duplicate: false
1268
- };
1269
- }
1270
- async update(key, status, pendingReason) {
1271
- const entry = this.entries.get(key);
1272
- if (entry !== void 0) {
1273
- entry.status = status;
1274
- entry.pendingReason = pendingReason;
1275
- }
1276
- }
1277
- list() {
1278
- return [...this.entries.values()];
1279
- }
1280
- };
1281
- function digest(value) {
1282
- return createHash("sha256").update(JSON.stringify(value)).digest("hex");
1283
- }
1284
- /** @returns idempotency key for one proposal observation. */
1285
- function assignmentKey(input) {
1286
- return digest([
1287
- input.skillKey,
1288
- input.threeFieldHash,
1289
- input.proposalDigest,
1290
- input.mapVersion
1291
- ]);
1292
- }
1293
- /** @returns stable proposal digest; undefined proposal is digest of null. */
1294
- function proposalDigest(proposal) {
1295
- return digest(proposal ?? null);
1296
- }
1297
- /** @returns a closed-set pending reason or undefined when the proposal can enqueue. */
1298
- function validateProposal(proposal) {
1299
- if (proposal.mode !== "existing" && proposal.mode !== "new") return "schema-invalid";
1300
- if (typeof proposal.confidence !== "number" || proposal.confidence < 0 || proposal.confidence > 1 || typeof proposal.reason !== "string" || typeof proposal.mapVersion !== "string") return "schema-invalid";
1301
- if (proposal.mode === "existing" && (typeof proposal.id !== "string" || proposal.id.length === 0)) return "invalid-id";
1302
- if (proposal.mode === "new") {
1303
- const proposed = proposal.proposed;
1304
- if (proposed === void 0 || proposed.title.trim().length === 0 || proposed.purpose.trim().length === 0 || proposed.inScope.length === 0 || proposed.outOfScope.length === 0 || proposed.triggers.length === 0) return "new-domain-conflict";
1305
- }
1306
- }
1307
- /** @returns true only when whenToUse is covered by in/out/triggers; names alone do not count. */
1308
- function coveredByDomain(whenToUse, domain) {
1309
- const text = whenToUse.toLowerCase();
1310
- if (text.length === 0) return false;
1311
- if (domain.outOfScope.some((value) => value.length > 0 && text.includes(value.toLowerCase()))) return false;
1312
- return domain.triggers.some((value) => value.length > 0 && text.includes(value.toLowerCase())) || domain.inScope.some((value) => value.length > 0 && text.includes(value.toLowerCase()));
1313
- }
1314
- /** One assignment sidecar service; the only map write path for create. */
1315
- var DomainAssignmentService = class {
1316
- queue;
1317
- store;
1318
- options;
1319
- constructor(queue, store, options) {
1320
- this.queue = queue;
1321
- this.store = store;
1322
- this.options = options;
1323
- }
1324
- /** Stage a create proposal without queueing it. */
1325
- stage(submission) {
1326
- return {
1327
- id: digest([
1328
- "staged",
1329
- submission.skillKey,
1330
- submission.threeFieldHash
1331
- ]),
1332
- submission
1333
- };
1334
- }
1335
- /** Approve a staged create; only this call enqueues. */
1336
- async approve(staged) {
1337
- return this.submit(staged.submission);
1338
- }
1339
- /**
1340
- * Submit a create proposal after SkillLibrary.create succeeded.
1341
- * @param submission - skill identity, three-field hash, permission, proposal.
1342
- * @returns queue/pending/unclassified; never rolls back create.
1343
- */
1344
- async submit(submission) {
1345
- if (!this.options.createClassificationEnabled) return {
1346
- kind: "unclassified",
1347
- reason: "feature-off"
1348
- };
1349
- if (!submission.modelInvocable) return {
1350
- kind: "unclassified",
1351
- reason: "not-model-invocable"
1352
- };
1353
- if (submission.proposal === void 0) {
1354
- if (this.options.createCardsAvailable === true) {
1355
- const reason = "schema-invalid";
1356
- return {
1357
- kind: "pending",
1358
- entry: {
1359
- key: digest([
1360
- "missing-proposal",
1361
- submission.skillKey,
1362
- submission.threeFieldHash
1363
- ]),
1364
- submission,
1365
- proposalDigest: proposalDigest(void 0),
1366
- createdAt: this.now(),
1367
- status: "pending",
1368
- pendingReason: reason
1369
- },
1370
- reason
1371
- };
1372
- }
1373
- return {
1374
- kind: "unclassified",
1375
- reason: "no-proposal"
1376
- };
1377
- }
1378
- const invalid = validateProposal(submission.proposal);
1379
- const pdigest = proposalDigest(submission.proposal);
1380
- const key = assignmentKey({
1381
- skillKey: submission.skillKey,
1382
- threeFieldHash: submission.threeFieldHash,
1383
- proposalDigest: pdigest,
1384
- mapVersion: submission.proposal.mapVersion
1385
- });
1386
- let queued;
1387
- try {
1388
- queued = await this.queue.enqueue({
1389
- key,
1390
- submission,
1391
- proposalDigest: pdigest,
1392
- createdAt: submission.now ?? this.now()
1393
- });
1394
- } catch {
1395
- return {
1396
- kind: "pending",
1397
- entry: {
1398
- key,
1399
- submission,
1400
- proposalDigest: pdigest,
1401
- createdAt: submission.now ?? this.now(),
1402
- status: "pending",
1403
- pendingReason: "queue-unavailable"
1404
- },
1405
- reason: "queue-unavailable"
1406
- };
1407
- }
1408
- if (invalid !== void 0) {
1409
- await this.queue.update(key, "pending", invalid);
1410
- return {
1411
- kind: "pending",
1412
- entry: {
1413
- ...queued.entry,
1414
- status: "pending",
1415
- pendingReason: invalid
1416
- },
1417
- reason: invalid
1418
- };
1419
- }
1420
- return {
1421
- kind: "queued",
1422
- entry: queued.entry,
1423
- duplicate: queued.duplicate
1424
- };
1425
- }
1426
- /** Process one queued entry with the current map; CAS failure becomes pending. */
1427
- async process(entry) {
1428
- const proposal = entry.submission.proposal;
1429
- if (proposal === void 0) return {
1430
- kind: "pending",
1431
- entry,
1432
- reason: "schema-invalid"
1433
- };
1434
- const current = await this.store.read();
1435
- if (proposal.mapVersion !== current.mapVersion) {
1436
- await this.queue.update(entry.key, "pending", "map-version-stale");
1437
- return {
1438
- kind: "pending",
1439
- entry: {
1440
- ...entry,
1441
- status: "pending",
1442
- pendingReason: "map-version-stale"
1443
- },
1444
- reason: "map-version-stale"
1445
- };
1446
- }
1447
- try {
1448
- const next = buildNext(current, entry, proposal, this.options.provisionalThreshold ?? 3);
1449
- const committed = await this.store.commit(current.mapVersion, {
1450
- mapVersion: next.mapVersion,
1451
- domains: next.domains,
1452
- edges: next.edges
1453
- });
1454
- const status = next.provisional ? "provisional" : "applied";
1455
- await this.queue.update(entry.key, status);
1456
- return {
1457
- kind: status,
1458
- entry: {
1459
- ...entry,
1460
- status
1461
- },
1462
- mapVersion: committed.mapVersion
1463
- };
1464
- } catch (error) {
1465
- if (error instanceof DomainMapVersionConflict) {
1466
- await this.queue.update(entry.key, "pending", "map-version-stale");
1467
- return {
1468
- kind: "pending",
1469
- entry: {
1470
- ...entry,
1471
- status: "pending",
1472
- pendingReason: "map-version-stale"
1473
- },
1474
- reason: "map-version-stale"
1475
- };
1476
- }
1477
- const reason = error instanceof Error && (error.message === "unknown-domain" || error.message === "new-domain-conflict" || error.message === "schema-invalid") ? error.message : "queue-unavailable";
1478
- await this.queue.update(entry.key, "pending", reason);
1479
- return {
1480
- kind: "pending",
1481
- entry: {
1482
- ...entry,
1483
- status: "pending",
1484
- pendingReason: reason
1485
- },
1486
- reason
1487
- };
1488
- }
1489
- }
1490
- /** Manually confirm one provisional domain; no semantic reclassification. */
1491
- async confirm(domainId) {
1492
- const current = await this.store.read();
1493
- const domain = current.domains.find((node) => node.id === domainId);
1494
- if (domain === void 0 || domain.status !== "provisional") return false;
1495
- const domains = current.domains.map((node) => node.id === domainId ? {
1496
- ...node,
1497
- status: "active",
1498
- updatedAt: this.now()
1499
- } : node);
1500
- await this.store.commit(current.mapVersion, {
1501
- mapVersion: nextMapVersion(current.mapVersion),
1502
- domains,
1503
- edges: current.edges
1504
- });
1505
- return true;
1506
- }
1507
- now() {
1508
- return this.options.now?.() ?? Date.now();
1509
- }
1510
- };
1511
- function nextMapVersion(version) {
1512
- const numeric = Number(version);
1513
- return Number.isSafeInteger(numeric) && numeric >= 0 ? String(numeric + 1) : version + "+1";
1514
- }
1515
- function buildNext(current, entry, proposal, threshold) {
1516
- const edge = {
1517
- skillKey: entry.submission.skillKey,
1518
- domainId: "",
1519
- role: "primary",
1520
- confidence: proposal.confidence,
1521
- source: "create",
1522
- skillHash: entry.submission.threeFieldHash,
1523
- mapVersion: nextMapVersion(current.mapVersion),
1524
- status: "active",
1525
- assignedAt: entry.createdAt
1526
- };
1527
- const secondaries = (proposal.secondaryDomainIds ?? []).map((id) => {
1528
- const secondary = current.domains.find((node) => node.id === id);
1529
- if (secondary === void 0) throw new Error("unknown-domain");
1530
- return {
1531
- ...edge,
1532
- domainId: secondary.id,
1533
- role: "secondary"
1534
- };
1535
- });
1536
- if (proposal.mode === "existing") {
1537
- const domain = current.domains.find((node) => node.id === proposal.id);
1538
- if (domain === void 0) throw new Error("unknown-domain");
1539
- if (!coveredByDomain(entry.submission.whenToUse ?? "", domain)) throw new Error("new-domain-conflict");
1540
- const members = new Set(current.edges.filter((item) => item.domainId === domain.id).map((item) => item.skillKey));
1541
- members.add(entry.submission.skillKey);
1542
- const promote = domain.status === "provisional" && members.size >= threshold;
1543
- const domains = promote ? current.domains.map((node) => node.id === domain.id ? {
1544
- ...node,
1545
- status: "active",
1546
- updatedAt: entry.createdAt
1547
- } : node) : current.domains;
1548
- return {
1549
- mapVersion: nextMapVersion(current.mapVersion),
1550
- domains,
1551
- edges: [
1552
- ...current.edges,
1553
- {
1554
- ...edge,
1555
- domainId: domain.id
1556
- },
1557
- ...secondaries.filter((secondary) => secondary.domainId !== domain.id)
1558
- ],
1559
- provisional: domain.status === "provisional" && !promote
1560
- };
1561
- }
1562
- const proposed = proposal.proposed;
1563
- if (proposed === void 0) throw new Error("schema-invalid");
1564
- const normalizedTitle = proposed.title.trim().toLowerCase();
1565
- const existing = current.domains.find((node) => node.name.trim().toLowerCase() === normalizedTitle);
1566
- if (existing !== void 0 && existing.status !== "provisional") throw new Error("new-domain-conflict");
1567
- const id = existing?.id ?? mintDomainId(proposed.title);
1568
- if (current.domains.some((node) => node.id !== id && node.triggers.some((trigger) => proposed.triggers.includes(trigger)))) throw new Error("new-domain-conflict");
1569
- const domain = existing ?? {
1570
- id,
1571
- name: proposed.title,
1572
- purpose: proposed.purpose,
1573
- description: proposed.purpose,
1574
- inScope: proposed.inScope,
1575
- outOfScope: proposed.outOfScope,
1576
- triggers: proposed.triggers,
1577
- parentIds: [],
1578
- status: "provisional",
1579
- version: 1,
1580
- createdAt: entry.createdAt,
1581
- updatedAt: entry.createdAt,
1582
- descriptionHash: "pending",
1583
- descriptionVersion: 1,
1584
- descriptionStatus: "fresh",
1585
- provenance: { source: "create" },
1586
- quality: {}
1587
- };
1588
- const members = new Set(current.edges.filter((item) => item.domainId === id).map((item) => item.skillKey));
1589
- members.add(entry.submission.skillKey);
1590
- const promote = members.size >= threshold;
1591
- const finalDomain = promote ? {
1592
- ...domain,
1593
- status: "active",
1594
- updatedAt: entry.createdAt
1595
- } : domain;
1596
- return {
1597
- mapVersion: nextMapVersion(current.mapVersion),
1598
- domains: existing === void 0 ? [...current.domains, finalDomain] : current.domains.map((node) => node.id === id ? finalDomain : node),
1599
- edges: [
1600
- ...current.edges,
1601
- {
1602
- ...edge,
1603
- domainId: id
1604
- },
1605
- ...secondaries.filter((secondary) => secondary.domainId !== id)
1606
- ],
1607
- provisional: !promote
1608
- };
1609
- }
1610
- //#endregion
1611
249
  //#region lib/types/maintenance.js
1612
250
  /** Maintenance defaults from PLAN/MAP-009. */
1613
251
  const MAINTENANCE_DEFAULTS = Object.freeze({
@@ -1865,62 +503,6 @@ var DiscoveryRuntime = class {
1865
503
  }
1866
504
  };
1867
505
  //#endregion
1868
- //#region lib/types/assignment-sidecar.js
1869
- function submitCreate(service, args, options) {
1870
- if (typeof args.name !== "string" || args.name.length === 0) return void 0;
1871
- const parsed = parseFrontmatter(typeof args.content === "string" ? args.content : "");
1872
- const description = parsed?.frontmatter.description ?? "";
1873
- const rawWhenToUse = parsed?.frontmatter.whenToUse;
1874
- const whenToUse = typeof rawWhenToUse === "string" ? rawWhenToUse : void 0;
1875
- const domain = args.domain;
1876
- const proposal = typeof domain === "object" && domain !== null ? domain : void 0;
1877
- return service.submit({
1878
- skillKey: skillKeyOf({
1879
- provider: options.provider ?? "dsh-evolution",
1880
- source: options.source ?? "user-dsh",
1881
- name: args.name
1882
- }),
1883
- threeFieldHash: threeFieldHashOf({
1884
- name: args.name,
1885
- description,
1886
- ...whenToUse === void 0 ? {} : { whenToUse }
1887
- }),
1888
- modelInvocable: parsed?.frontmatter["disable-model-invocation"] !== true,
1889
- ...whenToUse === void 0 ? {} : { whenToUse },
1890
- ...proposal === void 0 ? {} : { proposal }
1891
- }).then(() => options.onSubmitted?.());
1892
- }
1893
- /**
1894
- * Observe successful create calls and submit their optional domain proposal.
1895
- * Direct creates submit immediately; staged creates submit only after approval.
1896
- * @param ctx - plugin context with tools and approval events.
1897
- * @param service - the one assignment service.
1898
- * @param options - provider/source identity for skillKey.
1899
- * @returns disposer for both listeners.
1900
- */
1901
- function installAssignmentSidecar(ctx, service, options = {}) {
1902
- const stopResult = ctx.on("tools/result", (exec, result) => {
1903
- if (result.isError || exec.name !== "skill_manage" || exec.agent === void 0) return;
1904
- if (result.value?.pending_id !== void 0) return;
1905
- const args = exec.arguments;
1906
- if (typeof args !== "object" || args === null) return;
1907
- const record = args;
1908
- if (record.action !== "create") return;
1909
- submitCreate(service, record, options)?.catch(() => {});
1910
- });
1911
- const stopApproval = ctx.on("evolution/approval-decided", ({ record, action, ok }) => {
1912
- if (!ok || action !== "approve" || record.kind !== "skill" || typeof record.args !== "object" || record.args === null) return;
1913
- const raw = record.args;
1914
- const operation = typeof raw.operation === "object" && raw.operation !== null ? raw.operation : raw;
1915
- if (operation.action !== "create") return;
1916
- submitCreate(service, operation, options)?.catch(() => {});
1917
- });
1918
- return () => {
1919
- stopResult();
1920
- stopApproval();
1921
- };
1922
- }
1923
- //#endregion
1924
506
  //#region lib/types/search-judge.js
1925
507
  /** Validate a judge packet without accepting partial query coverage. */
1926
508
  function validateJudgeRecord(record, expectedQueryIds) {
@@ -1954,188 +536,6 @@ function aggregateJudge(records, expectedQueryIds) {
1954
536
  };
1955
537
  }
1956
538
  //#endregion
1957
- //#region lib/types/awareness.js
1958
- /** Static 1–2 sentence awareness; no domain list/tree. */
1959
- const DISCOVERY_AWARENESS = "When skill coverage for a task is uncertain, the domain cards and skill_search can reduce it; search is optional, not a required step.";
1960
- /**
1961
- * Register the static awareness section; absent systemPrompt is a no-op.
1962
- * platform-declaration anchor: the optional systemPrompt service declared in core PLATFORM_SERVICE_PROBES.
1963
- */
1964
- function installDiscoveryAwareness(ctx) {
1965
- const prompt = ctx.get("systemPrompt");
1966
- if (prompt === void 0) return () => {};
1967
- return prompt.section({
1968
- name: "evolution-skill-discovery-awareness",
1969
- order: 40,
1970
- text: DISCOVERY_AWARENESS,
1971
- interpolate: false
1972
- });
1973
- }
1974
- //#endregion
1975
- //#region lib/types/assignment-queue.js
1976
- /** Durable assignment queue; process-local cache over one JSON file. */
1977
- var JsonAssignmentQueue = class {
1978
- entries = /* @__PURE__ */ new Map();
1979
- loaded = false;
1980
- io;
1981
- path;
1982
- constructor(io, path) {
1983
- this.io = io;
1984
- this.path = path;
1985
- }
1986
- async load() {
1987
- if (this.loaded) return;
1988
- this.loaded = true;
1989
- const content = await this.io.readText(this.path);
1990
- if (content === null) return;
1991
- try {
1992
- const parsed = JSON.parse(content);
1993
- if (parsed.schemaVersion !== 1 || !Array.isArray(parsed.entries)) return;
1994
- for (const entry of parsed.entries) this.entries.set(entry.key, entry);
1995
- } catch {}
1996
- }
1997
- async persist() {
1998
- await this.io.writeText(this.path, JSON.stringify({
1999
- schemaVersion: 1,
2000
- entries: [...this.entries.values()]
2001
- }));
2002
- }
2003
- async enqueue(input) {
2004
- await this.load();
2005
- const existing = this.entries.get(input.key);
2006
- if (existing !== void 0) return {
2007
- entry: existing,
2008
- duplicate: true
2009
- };
2010
- const entry = {
2011
- ...input,
2012
- status: "queued"
2013
- };
2014
- this.entries.set(input.key, entry);
2015
- await this.persist();
2016
- return {
2017
- entry,
2018
- duplicate: false
2019
- };
2020
- }
2021
- async update(key, status, pendingReason) {
2022
- await this.load();
2023
- const entry = this.entries.get(key);
2024
- if (entry === void 0) return;
2025
- entry.status = status;
2026
- entry.pendingReason = pendingReason;
2027
- await this.persist();
2028
- }
2029
- list() {
2030
- return [...this.entries.values()];
2031
- }
2032
- };
2033
- //#endregion
2034
- //#region lib/types/worker.js
2035
- /** Drivable worker; the composition may call drain on a schedule. */
2036
- var AssignmentWorker = class {
2037
- service;
2038
- queue;
2039
- constructor(service, queue) {
2040
- this.service = service;
2041
- this.queue = queue;
2042
- }
2043
- /** Process queued entries once; returns processed count. */
2044
- async drain(limit = 10) {
2045
- const entries = this.queue.list().filter((entry) => entry.status === "queued").slice(0, Math.max(0, limit));
2046
- for (const entry of entries) await this.service.process(entry);
2047
- return entries.length;
2048
- }
2049
- };
2050
- //#endregion
2051
- //#region lib/types/domain-bootstrap.js
2052
- /**
2053
- * Write an initial LLM domain proposal into the map. Code owns IDs and
2054
- * validation; no embedding taxonomy and no skill body is read.
2055
- * @param store - domain map owner.
2056
- * @param proposal - validated 8–12 domain proposal.
2057
- * @param skillKeys - evidence skill name -> stable skillKey.
2058
- * @param expectedMapVersion - CAS precondition.
2059
- * @returns committed snapshot.
2060
- */
2061
- async function bootstrapDomainMap(store, proposal, skillKeys, expectedMapVersion) {
2062
- const checked = validateDomainGeneration({ skills: Object.keys(skillKeys).map((name) => ({
2063
- name,
2064
- description: name
2065
- })) }, proposal);
2066
- if (!checked.ok) throw new Error("invalid domain proposal: " + checked.errors.join("; "));
2067
- const now = Date.now();
2068
- const domains = checked.domains.map((domain) => ({
2069
- id: domain.id,
2070
- name: domain.title,
2071
- purpose: domain.purpose,
2072
- description: domain.purpose,
2073
- inScope: domain.inScope,
2074
- outOfScope: domain.outOfScope,
2075
- triggers: domain.triggers,
2076
- parentIds: [],
2077
- status: "provisional",
2078
- version: 1,
2079
- createdAt: now,
2080
- updatedAt: now,
2081
- descriptionHash: "bootstrap",
2082
- descriptionVersion: 1,
2083
- descriptionStatus: "fresh",
2084
- provenance: { source: "llm" },
2085
- quality: {}
2086
- }));
2087
- const primary = /* @__PURE__ */ new Set();
2088
- const edges = [];
2089
- for (const [domainIndex, domain] of checked.domains.entries()) for (const name of domain.evidenceSkillKeys) {
2090
- const skillKey = skillKeys[name];
2091
- if (skillKey === void 0) continue;
2092
- const role = primary.has(skillKey) ? "secondary" : "primary";
2093
- if (role === "primary") primary.add(skillKey);
2094
- edges.push({
2095
- skillKey,
2096
- domainId: checked.domains[domainIndex].id,
2097
- role,
2098
- confidence: 1,
2099
- source: "create",
2100
- skillHash: "bootstrap",
2101
- mapVersion: expectedMapVersion,
2102
- status: "active",
2103
- assignedAt: now
2104
- });
2105
- }
2106
- return store.commit(expectedMapVersion, {
2107
- mapVersion: String(Number(expectedMapVersion) + 1),
2108
- domains,
2109
- edges
2110
- });
2111
- }
2112
- //#endregion
2113
- //#region lib/types/domain-generation-prompt.js
2114
- /** Frozen initial domain-generation prompt; caller owns schema/IDs. */
2115
- const DOMAIN_GENERATION_PROMPT_VERSION = "v2";
2116
- const DOMAIN_GENERATION_PROMPT_V1 = `Group the provided skills into first-level domains.
2117
- Use only name, description, and whenToUse; do not infer from skill bodies.
2118
- Produce 8–12 domains that are coherent, distinct, task-recognizable, and useful as an awareness layer. A single skill is not a domain. Each domain has a name, one-sentence purpose, in-scope, out-of-scope, triggers, and evidence skill names that actually support it.
2119
- Prefer clear boundaries over exhaustive coverage. If the evidence is weak, leave the skill unassigned rather than forcing it into a domain. Together the domains should cover the library where evidence supports it. Domain IDs are assigned by the caller.
2120
- Return only the structured result.`;
2121
- /** @returns a parameterized initial-domain prompt; default 8–12. */
2122
- function buildDomainGenerationPrompt(options = {}) {
2123
- const requestedMin = options.minDomains ?? 8;
2124
- const requestedMax = options.maxDomains ?? 12;
2125
- const valid = Number.isSafeInteger(requestedMin) && Number.isSafeInteger(requestedMax) && requestedMin >= 2 && requestedMax >= requestedMin && requestedMax <= 20;
2126
- return `You are designing a first-level domain map for a skill library.
2127
- Input is limited to each skill's name, description, and whenToUse; do not infer from skill bodies.
2128
- Choose ${valid ? requestedMin : 8}–${valid ? requestedMax : 12} domains that help a model decide where to look. Domains are an awareness layer, not a complete taxonomy.
2129
- Each domain needs a name, one-sentence purpose, in-scope, out-of-scope, triggers, and evidence skill names copied from the input.
2130
- Keep domains coherent, distinguishable, and recognizable from a task description; a single skill is not a domain. Do not invent skills or domains.
2131
- Prefer clear boundaries over exhaustive coverage. Leave a skill unassigned when evidence is weak rather than forcing it. The caller assigns IDs.
2132
- Return only the structured result.`;
2133
- }
2134
- const DOMAIN_GENERATION_PROMPT_V2 = buildDomainGenerationPrompt();
2135
- const DOMAIN_GENERATION_PROMPT_HASH = createHash("sha256").update(DOMAIN_GENERATION_PROMPT_V2, "utf8").digest("hex");
2136
- /** Active prompt; v1 remains frozen for traceability. */
2137
- const DOMAIN_GENERATION_PROMPT = DOMAIN_GENERATION_PROMPT_V2;
2138
- //#endregion
2139
539
  //#region lib/types/domain-generation-service.js
2140
540
  /**
2141
541
  * Generate and persist the initial map only when the map has no domains.
@@ -2156,61 +556,6 @@ async function initialDomainMap(store, port, skills, skillKeys, expectedMapVersi
2156
556
  }), skillKeys, expectedMapVersion);
2157
557
  }
2158
558
  //#endregion
2159
- //#region lib/types/shadow-evidence.js
2160
- /** Durable shadow-run evidence; every complete shadow projection is one sample. */
2161
- var ShadowEvidenceStore = class {
2162
- io;
2163
- path;
2164
- samples = 0;
2165
- recovered = 0;
2166
- hidden = 0;
2167
- silentMisses = 0;
2168
- loaded = false;
2169
- constructor(io, path) {
2170
- this.io = io;
2171
- this.path = path;
2172
- }
2173
- async load() {
2174
- if (this.loaded) return;
2175
- this.loaded = true;
2176
- const content = await this.io.readText(this.path);
2177
- if (content === null) return;
2178
- try {
2179
- const parsed = JSON.parse(content);
2180
- if (parsed.schemaVersion === 1 && Number.isSafeInteger(parsed.samples) && parsed.samples >= 0) {
2181
- this.samples = parsed.samples;
2182
- this.recovered = Number.isSafeInteger(parsed.recovered) && parsed.recovered >= 0 ? parsed.recovered : 0;
2183
- this.hidden = Number.isSafeInteger(parsed.hidden) && parsed.hidden >= 0 ? parsed.hidden : 0;
2184
- this.silentMisses = Number.isSafeInteger(parsed.silentMisses) && parsed.silentMisses >= 0 ? parsed.silentMisses : 0;
2185
- }
2186
- } catch {}
2187
- }
2188
- async record(result) {
2189
- if (result.shadowHiddenNames === void 0 || result.shadowHiddenNames.length === 0) return;
2190
- await this.load();
2191
- this.samples += 1;
2192
- this.hidden += result.shadowHiddenNames.length;
2193
- this.recovered += result.shadowRecoveredNames?.length ?? 0;
2194
- await this.io.writeText(this.path, JSON.stringify({
2195
- schemaVersion: 1,
2196
- samples: this.samples,
2197
- recovered: this.recovered,
2198
- hidden: this.hidden,
2199
- silentMisses: this.silentMisses
2200
- }));
2201
- }
2202
- evidence() {
2203
- return {
2204
- samples: this.samples,
2205
- hiddenRecoverableRate: this.hidden > 0 ? this.recovered / this.hidden : 0,
2206
- silentMisses: this.silentMisses
2207
- };
2208
- }
2209
- canPromote() {
2210
- return canPromoteShadow(this.evidence());
2211
- }
2212
- };
2213
- //#endregion
2214
559
  //#region lib/types/plugin.js
2215
560
  const name = "evolution-skill-discovery";
2216
561
  const inject = [
@@ -2286,11 +631,6 @@ function apply(ctx, config) {
2286
631
  pinDomains: config.pinDomains,
2287
632
  disableDomains: config.disableDomains
2288
633
  });
2289
- const shadowStore = new ShadowEvidenceStore(io, config.shadowEvidencePath ?? join(dshHome, "evolution", "shadow-evidence.json"));
2290
- let promoted = config.shadowEvidence !== void 0 && canPromoteShadow(config.shadowEvidence);
2291
- shadowStore.load().then(() => {
2292
- if (shadowStore.canPromote()) promoted = true;
2293
- }).catch(() => {});
2294
634
  const policyDigest = policyDigestOf({
2295
635
  globalPolicyRevision: "config",
2296
636
  effectivePolicy: {
@@ -2315,25 +655,17 @@ function apply(ctx, config) {
2315
655
  staleAfterHours: config.staleAfterHours,
2316
656
  mode: config.mode === "auto" ? "shadow" : config.mode === "shadow" ? "shadow" : config.mode === "on" ? "on" : "off",
2317
657
  autoMode: config.mode === "auto",
2318
- promoted: () => promoted,
2319
- onProjection: (result) => {
2320
- shadowStore.record(result).then(() => {
2321
- if (shadowStore.canPromote()) promoted = true;
2322
- }).catch(() => {});
2323
- },
2324
658
  mapStale: config.mapStale ?? false,
2325
659
  searchAvailable: config.searchAvailable ?? true
2326
660
  });
661
+ if (config.mode !== "off") installDiscoveryAwareness(ctx);
2327
662
  ctx.on("agent/created", async ({ agent, source }) => {
2328
663
  try {
2329
- if (source === "startup") {
2330
- await sessions.put({
2331
- sessionId: String(agent.session.id),
2332
- discoveryMode: config.mode === "on" ? "on" : config.mode === "shadow" || config.mode === "auto" ? "shadow" : "off",
2333
- featureEpoch: config.featureEpoch
2334
- });
2335
- if (config.mode !== "off") installDiscoveryAwareness(agent.ctx);
2336
- } else if (shouldInstallAwarenessOnResume(await sessions.get(String(agent.session.id)), config.featureEpoch)) installDiscoveryAwareness(agent.ctx);
664
+ if (source === "startup") await sessions.put({
665
+ sessionId: String(agent.session.id),
666
+ discoveryMode: config.mode === "on" ? "on" : config.mode === "shadow" || config.mode === "auto" ? "shadow" : "off",
667
+ featureEpoch: config.featureEpoch
668
+ });
2337
669
  } catch {}
2338
670
  });
2339
671
  installCatalogProjection(ctx, runtime, {
@@ -2429,37 +761,4 @@ function apply(ctx, config) {
2429
761
  });
2430
762
  }
2431
763
  //#endregion
2432
- //#region lib/types/index.js
2433
- function bySkillKey(left, right) {
2434
- return left.skillKey < right.skillKey ? -1 : left.skillKey > right.skillKey ? 1 : 0;
2435
- }
2436
- /** Read-only view model; it does not cache or own a provider. */
2437
- var SkillIndex = class {
2438
- source;
2439
- constructor(source) {
2440
- this.source = source;
2441
- }
2442
- /**
2443
- * Read one view from the source.
2444
- * @param query - cwd/scope and cancellation for this observation.
2445
- * @returns normalized rows and completeness; incomplete data is not hidden.
2446
- */
2447
- async read(query) {
2448
- const [snapshot, managed, protectedKeys, pinnedKeys] = await Promise.all([
2449
- this.source.snapshot(query),
2450
- this.source.managedSkillKeys(),
2451
- this.source.protectedSkillKeys?.() ?? Promise.resolve(/* @__PURE__ */ new Set()),
2452
- this.source.pinnedSkillKeys?.() ?? Promise.resolve(/* @__PURE__ */ new Set())
2453
- ]);
2454
- const rows = snapshot.skills.map((summary) => skillIndexRowOf(summary, managed.has(skillKeyOf(summary)), protectedKeys.has(skillKeyOf(summary)), pinnedKeys.has(skillKeyOf(summary)))).sort(bySkillKey);
2455
- return {
2456
- viewKey: viewKeyOf(query),
2457
- catalogRevision: catalogRevisionOf(rows, snapshot.complete),
2458
- viewCatalogDigest: viewCatalogDigestOf(viewKeyOf(query), rows, snapshot.complete),
2459
- rows,
2460
- complete: snapshot.complete
2461
- };
2462
- }
2463
- };
2464
- //#endregion
2465
- export { AssignmentWorker, DISCOVERY_AWARENESS, DISCOVERY_STAGES, DOMAIN_GENERATION_PROMPT, DOMAIN_GENERATION_PROMPT_HASH, DOMAIN_GENERATION_PROMPT_V1, DOMAIN_GENERATION_PROMPT_V2, DOMAIN_GENERATION_PROMPT_VERSION, DOMAIN_MAP_SCHEMA_VERSION, DiscoveryRuntime, DiscoverySessionStore, DomainAssignmentService, DomainMapCorruptError, DomainMapFileStore, DomainMapValidationError, DomainMapVersionConflict, DomainPolicyValidationError, JsonAssignmentQueue, MAINTENANCE_DEFAULTS, MemoryAssignmentQueue, SKILL_MANAGE_DESCRIPTION, SKILL_SEARCH_DESCRIPTION, SKILL_SEARCH_FIELDS, SKILL_SEARCH_INDEX_SCHEMA_VERSION, SKILL_SEARCH_TOKENIZER_VERSION, ShadowEvidenceStore, SkillIndex, SkillSearchCache, SkillSearchIndex, V0_DISCOVERY_FEATURES, aggregateJudge, apply, applyCatalogProjection, assignmentKey, auditDomainMap, bootstrapDomainMap, buildDomainGenerationPrompt, buildDomainProjection, canClassifyAtCreate, canPromoteShadow, catalogRevisionOf, coarsenCandidates, computeBehaviorMetrics, computeDomainMetrics, coveredByDomain, descriptionHashOf, descriptionIsServable, domainGenerationMode, domainViewCounts, evaluateDomainQuality, hiddenRecoverable, initialDomainMap, inject, installAssignmentSidecar, installCatalogProjection, installDiscoveryAwareness, isDiscoveryUserPolicy, isSkillSearch, mayAdvance, mergeDomainPolicy, mintDomainId, name, policyDigestOf, projectCatalog, projectionBudgetMode, projectionDigestOf, proposalDigest, registerSkillSearchTool, renderDomainDescription, sanitizeUntrustedText, searchIndexKeyOf, searchTerms, searchWithDegraded, shouldEmitAwareness, shouldEmitDigest, shouldHideCatalog, shouldInstallAwarenessOnResume, shouldProjectPersistedSession, shouldRegenerateDescription, skillIndexRowOf, skillKeyOf, splitCandidates, stageIndex, threeFieldHashOf, validateDomainGeneration, validateJudgeRecord, validateProposal, viewCatalogDigestOf, viewKeyOf };
764
+ export { AssignmentWorker, DISCOVERY_AWARENESS, DISCOVERY_STAGES, DOMAIN_GENERATION_PROMPT, DOMAIN_GENERATION_PROMPT_HASH, DOMAIN_GENERATION_PROMPT_V1, DOMAIN_GENERATION_PROMPT_V2, DOMAIN_GENERATION_PROMPT_VERSION, DOMAIN_MAP_SCHEMA_VERSION, DiscoveryRuntime, DiscoverySessionStore, DomainAssignmentService, DomainGenerationCoordinator, DomainGenerationError, DomainMapCorruptError, DomainMapFileStore, DomainMapValidationError, DomainMapVersionConflict, DomainPolicyStore, DomainPolicyValidationError, JsonAssignmentQueue, LlmDomainGenerationPort, MAINTENANCE_DEFAULTS, MemoryAssignmentQueue, ProjectionJournal, ProjectionRuntime, SKILL_MANAGE_DESCRIPTION, SKILL_SEARCH_DESCRIPTION, SKILL_SEARCH_FIELDS, SKILL_SEARCH_INDEX_SCHEMA_VERSION, SKILL_SEARCH_TOKENIZER_VERSION, SkillIndex, SkillSearchCache, SkillSearchIndex, V0_DISCOVERY_FEATURES, ViewSnapshotStore, ViewStateRegistry, aggregateJudge, apply, applyCapturedProjection, applyCatalogProjection, applyProjectedDecision, applyReplacementDecision, applyRestoreUpdate, assignmentKey, auditDomainMap, bootstrapDomainMap, bootstrapDomainMapWithFacts, bootstrapFactsFromRows, buildDomainGenerationPrompt, buildDomainProjection, buildGenerationRequest, canClassifyAtCreate, canHide, canProject, captureCatalog, captureDecisionCatalog, catalogRevisionOf, coarsenCandidates, computeBehaviorMetrics, computeDomainMetrics, coveredByDomain, deriveViewState, descriptionHashOf, descriptionIsServable, domainGenerationMode, domainViewCounts, evaluateDomainQuality, findCatalogMessage, hiddenRecoverable, initialDomainMap, inject, installAssignmentSidecar, installCatalogProjection, installDiscoveryAwareness, isDiscoveryUserPolicy, isSkillSearch, mayAdvance, mergeDomainPolicy, mintDomainId, name, parseDomainGenerationProposal, policyDigestOf, projectCatalog, projectionBudgetMode, projectionDigestOf, proposalDigest, registerSkillSearchTool, renderCatalog, renderCatalogUpdate, renderDomainDescription, renderProjectionContext, resolveGenerationPort, restoreCapturedProjection, restoreCatalog, sanitizeUntrustedText, searchIndexKeyOf, searchTerms, searchWithDegraded, shouldEmitAwareness, shouldEmitDigest, shouldHideCatalog, shouldInstallAwarenessOnResume, shouldProjectPersistedSession, shouldRegenerateDescription, skillIndexRowOf, skillKeyOf, splitCandidates, stageIndex, threeFieldHashOf, validateDomainGeneration, validateJudgeRecord, validateProposal, viewCatalogDigestOf, viewKeyOf };