@lmzhen/dsh-evolution-core 0.26.15 → 0.27.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.
Files changed (116) hide show
  1. package/README.md +16 -1
  2. package/lib/composition/index.js +3 -0
  3. package/lib/composition-D8Y3-ROd.js +604 -0
  4. package/lib/ensure-MEYMj9jn.js +222 -0
  5. package/lib/errors-Br2-uUSK.js +57 -0
  6. package/lib/http-routes-BP-S3KV-.js +149 -0
  7. package/lib/http-routes.js +2 -0
  8. package/lib/index.js +777 -840
  9. package/lib/restrictor.js +37 -0
  10. package/lib/subagent/index.js +186 -0
  11. package/lib/types/citations.js +192 -0
  12. package/lib/types/{preset-composition.d.ts → composition/composer.d.ts} +2 -2
  13. package/lib/types/composition/composer.js +681 -0
  14. package/lib/types/composition/ensure.d.ts +10 -0
  15. package/lib/types/composition/ensure.js +95 -0
  16. package/lib/types/composition/index.d.ts +5 -0
  17. package/lib/types/composition/index.js +7 -0
  18. package/lib/types/composition/preset-ids.d.ts +17 -0
  19. package/lib/types/composition/preset-ids.js +21 -0
  20. package/lib/types/composition/restricted-preset.d.ts +36 -0
  21. package/lib/types/composition/restricted-preset.js +42 -0
  22. package/lib/types/constants.js +223 -0
  23. package/lib/types/cost.js +67 -0
  24. package/lib/types/curator.d.ts +2 -0
  25. package/lib/types/curator.js +346 -0
  26. package/lib/types/diagnostics.d.ts +26 -0
  27. package/lib/types/diagnostics.js +23 -0
  28. package/lib/types/document-body.js +91 -0
  29. package/lib/types/drift-signals.js +319 -0
  30. package/lib/types/env.js +25 -0
  31. package/lib/types/errors.d.ts +4 -1
  32. package/lib/types/errors.js +59 -0
  33. package/lib/types/events.js +16 -0
  34. package/lib/types/evidence.js +65 -0
  35. package/lib/types/evolution-events.js +479 -0
  36. package/lib/types/fold-untrusted.js +44 -0
  37. package/lib/types/frontmatter.js +623 -0
  38. package/lib/types/fuzzy-match.js +135 -0
  39. package/lib/types/gates.js +51 -0
  40. package/lib/types/guards.js +27 -0
  41. package/lib/types/health-baseline.js +63 -0
  42. package/lib/types/http-routes.d.ts +67 -35
  43. package/lib/types/http-routes.js +135 -0
  44. package/lib/types/index.d.ts +13 -2
  45. package/lib/types/index.js +101 -0
  46. package/lib/types/instance-scope.js +69 -0
  47. package/lib/types/io.js +675 -0
  48. package/lib/types/learn-prompt.js +42 -0
  49. package/lib/types/limits.js +63 -0
  50. package/lib/types/live.js +107 -0
  51. package/lib/types/long-runs.js +96 -0
  52. package/lib/types/memory-store.d.ts +0 -16
  53. package/lib/types/memory-store.js +606 -0
  54. package/lib/types/mutations.js +95 -0
  55. package/lib/types/namespace-migration.js +156 -0
  56. package/lib/types/numeric.js +47 -0
  57. package/lib/types/opt-in.js +101 -0
  58. package/lib/types/package-identity.d.ts +14 -0
  59. package/lib/types/package-identity.js +47 -0
  60. package/lib/types/params.js +328 -0
  61. package/lib/types/payload.js +36 -0
  62. package/lib/types/platform-line.generated.d.ts +10 -0
  63. package/lib/types/platform-line.generated.js +12 -0
  64. package/lib/types/platform-services.d.ts +2 -2
  65. package/lib/types/platform-services.js +52 -0
  66. package/lib/types/probe.d.ts +9 -3
  67. package/lib/types/probe.js +122 -0
  68. package/lib/types/prompts.js +409 -0
  69. package/lib/types/quality.js +308 -0
  70. package/lib/types/reachability.d.ts +46 -0
  71. package/lib/types/reachability.js +122 -0
  72. package/lib/types/redact.js +257 -0
  73. package/lib/types/reference-rewrite.js +144 -0
  74. package/lib/types/reports.js +110 -0
  75. package/lib/types/restrictor.d.ts +11 -0
  76. package/lib/types/restrictor.js +48 -0
  77. package/lib/types/review-channel.js +59 -0
  78. package/lib/types/run-registry.js +345 -0
  79. package/lib/types/scope.js +7 -0
  80. package/lib/types/serial.js +17 -0
  81. package/lib/types/session-fold.d.ts +15 -0
  82. package/lib/types/session-fold.js +28 -0
  83. package/lib/types/session-projection.js +295 -0
  84. package/lib/types/signals.js +209 -0
  85. package/lib/types/skill-discovery.js +66 -0
  86. package/lib/types/skill-health.js +134 -0
  87. package/lib/types/skill-history.js +745 -0
  88. package/lib/types/skill-lint.js +112 -0
  89. package/lib/types/skill-reads.js +67 -0
  90. package/lib/types/skill-row-cells.js +123 -0
  91. package/lib/types/skill-store.d.ts +34 -3
  92. package/lib/types/skill-store.js +3528 -0
  93. package/lib/types/state-store.js +60 -0
  94. package/lib/types/store-ledger.d.ts +44 -0
  95. package/lib/types/store-ledger.js +117 -0
  96. package/lib/types/subagent/channel.d.ts +4 -0
  97. package/lib/types/subagent/channel.js +131 -0
  98. package/lib/types/subagent/index.d.ts +4 -0
  99. package/lib/types/subagent/index.js +5 -0
  100. package/lib/types/subagent/readiness.d.ts +6 -0
  101. package/lib/types/subagent/readiness.js +35 -0
  102. package/lib/types/subagent/types.d.ts +58 -0
  103. package/lib/types/subagent/types.js +2 -0
  104. package/lib/types/threats.js +338 -0
  105. package/lib/types/tool-dispatch.d.ts +5 -0
  106. package/lib/types/tool-dispatch.js +535 -0
  107. package/lib/types/tree-write.d.ts +53 -0
  108. package/lib/types/tree-write.js +40 -0
  109. package/lib/types/unit-members.js +121 -0
  110. package/lib/types/usage.js +474 -0
  111. package/lib/types/write-authority.d.ts +87 -0
  112. package/lib/types/write-authority.js +23 -0
  113. package/lib/types/write-gates.d.ts +111 -0
  114. package/lib/types/write-gates.js +344 -0
  115. package/lib/types/write-inventory.js +158 -0
  116. package/package.json +39 -11
@@ -0,0 +1,10 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import { type RestrictedPresetRequest, type RestrictedPresetState } from './restricted-preset.ts';
3
+ export interface EnsureRestrictedPresetArgs extends RestrictedPresetRequest {
4
+ }
5
+ export declare function ensureRestrictedPreset(ctx: Context, req: EnsureRestrictedPresetArgs): Promise<RestrictedPresetState>;
6
+ /** Read-only view of the current registration (doctor runtime row). */
7
+ export declare function restrictedPresetState(ctx: Context, kind: RestrictedPresetRequest['kind']): RestrictedPresetState | undefined;
8
+ /** Release the registration for one kind (HMR/tests); the fiber effect also calls this. */
9
+ export declare function disposeRestrictedPreset(ctx: Context, kind: RestrictedPresetRequest['kind']): Promise<void>;
10
+ //# sourceMappingURL=ensure.d.ts.map
@@ -0,0 +1,95 @@
1
+ import { normalizeAllow } from "./preset-ids.js";
2
+ import { composeRestrictedPresetDefinition, } from "./restricted-preset.js";
3
+ const SLOTS = new WeakMap();
4
+ function slotFor(ctx, kind) {
5
+ let byKind = SLOTS.get(ctx);
6
+ if (byKind === undefined) {
7
+ byKind = new Map();
8
+ SLOTS.set(ctx, byKind);
9
+ }
10
+ let slot = byKind.get(kind);
11
+ if (slot === undefined) {
12
+ slot = {};
13
+ byKind.set(kind, slot);
14
+ }
15
+ return slot;
16
+ }
17
+ function registryOf(ctx) {
18
+ const candidate = ctx.get('agentPresets');
19
+ if (candidate === undefined)
20
+ return undefined;
21
+ if (typeof candidate.readDocument !== 'function' || typeof candidate.register !== 'function')
22
+ return undefined;
23
+ return candidate;
24
+ }
25
+ function installReleaseEffect(ctx, slot) {
26
+ if (slot.effectInstalled === true)
27
+ return;
28
+ const effect = ctx.effect;
29
+ if (effect === undefined)
30
+ return;
31
+ slot.effectInstalled = true;
32
+ effect.call(ctx, () => () => { void releaseRestrictedPreset(ctx, undefined, slot); }, 'evolution-restricted-preset');
33
+ }
34
+ async function releaseRestrictedPreset(ctx, kind, explicitSlot) {
35
+ const slot = explicitSlot ?? (kind === undefined ? undefined : slotFor(ctx, kind));
36
+ if (slot === undefined)
37
+ return;
38
+ const release = slot.release;
39
+ slot.release = undefined;
40
+ slot.readyKey = undefined;
41
+ slot.state = undefined;
42
+ if (release !== undefined)
43
+ await release();
44
+ }
45
+ export async function ensureRestrictedPreset(ctx, req) {
46
+ const allow = normalizeAllow(req.allow);
47
+ const key = req.parentPresetId + '\n' + allow.join('\n');
48
+ const slot = slotFor(ctx, req.kind);
49
+ if (slot.readyKey === key && slot.state !== undefined && slot.state.kind === 'ready')
50
+ return slot.state;
51
+ if (slot.inflight !== undefined)
52
+ return slot.inflight;
53
+ slot.inflight = (async () => {
54
+ await releaseRestrictedPreset(ctx, req.kind, slot);
55
+ if (allow.length === 0) {
56
+ slot.state = { kind: 'failed', code: 'E-324', reason: 'allow set is empty' };
57
+ return slot.state;
58
+ }
59
+ const registry = registryOf(ctx);
60
+ if (registry === undefined) {
61
+ slot.state = { kind: 'failed', code: 'E-324', reason: 'agentPresets service unavailable or incomplete' };
62
+ return slot.state;
63
+ }
64
+ try {
65
+ const document = await registry.readDocument(req.parentPresetId);
66
+ const definition = composeRestrictedPresetDefinition(document, { kind: req.kind, parentPresetId: req.parentPresetId, allow });
67
+ const release = await registry.register(definition);
68
+ slot.release = release;
69
+ slot.readyKey = key;
70
+ slot.state = { kind: 'ready', presetId: definition.id, parentPresetId: req.parentPresetId, allow };
71
+ installReleaseEffect(ctx, slot);
72
+ return slot.state;
73
+ }
74
+ catch (error) {
75
+ slot.readyKey = undefined;
76
+ slot.state = { kind: 'failed', code: 'E-324', reason: error instanceof Error ? error.message : String(error) };
77
+ return slot.state;
78
+ }
79
+ })();
80
+ try {
81
+ return await slot.inflight;
82
+ }
83
+ finally {
84
+ slot.inflight = undefined;
85
+ }
86
+ }
87
+ /** Read-only view of the current registration (doctor runtime row). */
88
+ export function restrictedPresetState(ctx, kind) {
89
+ return SLOTS.get(ctx)?.get(kind)?.state;
90
+ }
91
+ /** Release the registration for one kind (HMR/tests); the fiber effect also calls this. */
92
+ export async function disposeRestrictedPreset(ctx, kind) {
93
+ await releaseRestrictedPreset(ctx, kind);
94
+ }
95
+ //# sourceMappingURL=ensure.js.map
@@ -0,0 +1,5 @@
1
+ export * from './composer.ts';
2
+ export * from './preset-ids.ts';
3
+ export * from './restricted-preset.ts';
4
+ export * from './ensure.ts';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ // Composition subpath: the ONE composer implementation + preset identities + the restricted
2
+ // preset lifecycle (see restricted-preset.ts / ensure.ts).
3
+ export * from "./composer.js";
4
+ export * from "./preset-ids.js";
5
+ export * from "./restricted-preset.js";
6
+ export * from "./ensure.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ export type RestrictedPresetKind = 'reviewer' | 'maintainer';
2
+ export declare const EVOLUTION_PRESET_IDS: readonly ["evolution", "evolution-ptc", "evolution-cordis", "evolution-minimal"];
3
+ export declare const REVIEWER_PRESET_ID: "evolution-reviewer-readonly";
4
+ export declare const MAINTAINER_PRESET_ID: "evolution-maintainer-readonly";
5
+ export declare const RESTRICTED_TOOLS_ROW_ID: "evolution-restricted-tools";
6
+ export declare const RESTRICTOR_MODULE: string;
7
+ export declare const RESTRICTED_PRESET_DELIVERY: "runtime" | "installer";
8
+ export interface RestrictedPresetDisplay {
9
+ readonly name: string;
10
+ readonly description: string;
11
+ readonly order: number;
12
+ }
13
+ export declare const RESTRICTED_PRESET_DISPLAY: Readonly<Record<RestrictedPresetKind, RestrictedPresetDisplay>>;
14
+ export declare function restrictedPresetId(kind: RestrictedPresetKind): string;
15
+ /** Trim, drop blanks, de-duplicate and sort: ONE spelling for an allow/deny set. */
16
+ export declare function normalizeAllow(names: readonly string[]): readonly string[];
17
+ //# sourceMappingURL=preset-ids.d.ts.map
@@ -0,0 +1,21 @@
1
+ // The frozen preset identities and restricted-preset display/transport constants.
2
+ import { scopedPackageName } from "../package-identity.js";
3
+ // Derived from bases.json (single source: packages/evolution-agent/bases.json).
4
+ export const EVOLUTION_PRESET_IDS = ['evolution', 'evolution-ptc', 'evolution-cordis', 'evolution-minimal'];
5
+ export const REVIEWER_PRESET_ID = 'evolution-reviewer-readonly';
6
+ export const MAINTAINER_PRESET_ID = 'evolution-maintainer-readonly';
7
+ export const RESTRICTED_TOOLS_ROW_ID = 'evolution-restricted-tools';
8
+ export const RESTRICTOR_MODULE = scopedPackageName('dsh-evolution-core/restrictor', import.meta.url);
9
+ export const RESTRICTED_PRESET_DELIVERY = 'runtime';
10
+ export const RESTRICTED_PRESET_DISPLAY = {
11
+ reviewer: { name: 'evolution reviewer (read-only)', description: 'Internal restricted preset used by the family review channel.', order: 900 },
12
+ maintainer: { name: 'evolution maintainer (read-only)', description: 'Internal restricted preset used by the family maintenance channel.', order: 901 },
13
+ };
14
+ export function restrictedPresetId(kind) {
15
+ return kind === 'reviewer' ? REVIEWER_PRESET_ID : MAINTAINER_PRESET_ID;
16
+ }
17
+ /** Trim, drop blanks, de-duplicate and sort: ONE spelling for an allow/deny set. */
18
+ export function normalizeAllow(names) {
19
+ return [...new Set(names.map(name => name.trim()).filter(name => name !== ''))].sort();
20
+ }
21
+ //# sourceMappingURL=preset-ids.js.map
@@ -0,0 +1,36 @@
1
+ import * as yaml from 'js-yaml';
2
+ export declare const entryListSchema: yaml.Schema;
3
+ import { type RestrictedPresetKind } from './preset-ids.ts';
4
+ export interface PresetDocumentLike {
5
+ readonly agentPreset: string;
6
+ readonly content: string;
7
+ }
8
+ export interface PresetRegistryLike {
9
+ readDocument(agentPreset: string): Promise<PresetDocumentLike>;
10
+ register(definition: RestrictedPresetDefinition): Promise<() => Promise<void>>;
11
+ }
12
+ export interface RestrictedPresetDefinition {
13
+ readonly id: string;
14
+ readonly name: string;
15
+ readonly description: string;
16
+ readonly order: number;
17
+ readonly plugins: readonly unknown[];
18
+ }
19
+ export interface RestrictedPresetRequest {
20
+ readonly kind: RestrictedPresetKind;
21
+ readonly parentPresetId: string;
22
+ readonly allow: readonly string[];
23
+ }
24
+ export type RestrictedPresetState = {
25
+ readonly kind: 'ready';
26
+ readonly presetId: string;
27
+ readonly parentPresetId: string;
28
+ readonly allow: readonly string[];
29
+ } | {
30
+ readonly kind: 'failed';
31
+ readonly code: 'E-324';
32
+ readonly reason: string;
33
+ };
34
+ export declare function parsePresetRows(content: string): readonly unknown[];
35
+ export declare function composeRestrictedPresetDefinition(source: PresetDocumentLike, req: RestrictedPresetRequest): RestrictedPresetDefinition;
36
+ //# sourceMappingURL=restricted-preset.d.ts.map
@@ -0,0 +1,42 @@
1
+ // Build one restricted preset DEFINITION by cloning the live parent preset rows and appending the
2
+ // single restrictor row. The parent rows are read through agentPresets.readDocument (the Loader's
3
+ // own YAML dialect), so the child keeps every base row the parent had.
4
+ import * as yaml from 'js-yaml';
5
+ import { errorText } from "../errors.js";
6
+ // The Loader's entry-list YAML dialect: `JSON_SCHEMA` plus one `!!js` scalar tag whose node keeps
7
+ // the source text for activation-time evaluation. It is DEFINED here rather than imported from the
8
+ // platform's include package: the family library has to load in a tree where only the family libs
9
+ // are built (the CI overlay runs the family tests against platform sources without building the
10
+ // platform), and the two definitions must round-trip the same documents. The shape (JSON_SCHEMA
11
+ // extended with the js tag, node `{ __jsExpr }`) is pinned by the parent document the registry
12
+ // hands back and by its golden tests.
13
+ const JsExpr = new yaml.Type('tag:yaml.org,2002:js', {
14
+ kind: 'scalar',
15
+ resolve: (data) => typeof data === 'string',
16
+ construct: (data) => ({ __jsExpr: data }),
17
+ represent: data => data.__jsExpr,
18
+ });
19
+ export const entryListSchema = yaml.JSON_SCHEMA.extend(JsExpr);
20
+ import { RESTRICTED_PRESET_DISPLAY, RESTRICTED_TOOLS_ROW_ID, RESTRICTOR_MODULE, restrictedPresetId, normalizeAllow, } from "./preset-ids.js";
21
+ export function parsePresetRows(content) {
22
+ const parsed = yaml.load(content, { schema: entryListSchema });
23
+ if (!Array.isArray(parsed))
24
+ throw new Error(errorText('e-324-restricted-preset-unavailable', { a1: 'parent preset composition is not a top-level list' }));
25
+ return parsed;
26
+ }
27
+ export function composeRestrictedPresetDefinition(source, req) {
28
+ const allow = normalizeAllow(req.allow);
29
+ if (allow.length === 0)
30
+ throw new Error(errorText('e-324-restricted-preset-unavailable', { a1: 'restricted preset needs a non-empty allow set' }));
31
+ const rows = parsePresetRows(source.content);
32
+ const display = RESTRICTED_PRESET_DISPLAY[req.kind];
33
+ const restrictorRow = { id: RESTRICTED_TOOLS_ROW_ID, name: RESTRICTOR_MODULE, config: { allow, presetId: restrictedPresetId(req.kind) } };
34
+ return {
35
+ id: restrictedPresetId(req.kind),
36
+ name: display.name,
37
+ description: display.description,
38
+ order: display.order,
39
+ plugins: [...rows, restrictorRow],
40
+ };
41
+ }
42
+ //# sourceMappingURL=restricted-preset.js.map
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Shared constants for the dsh-evolution plugin family.
3
+ *
4
+ * Two classes of value live here, deliberately separated by section so future
5
+ * edits do not blur the semantic boundary:
6
+ *
7
+ * 1. **Fixed protocol/format/security invariants** — changing these breaks an
8
+ * on-disk format, a naming/format contract, a path-security boundary, or a
9
+ * cross-component invariant. They are NOT exposed as deployment config.
10
+ *
11
+ * 2. **Cross-package shared tunable defaults** — the same semantic default is
12
+ * read (with a config override path) by more than one package (e.g.
13
+ * `evolution-policy` and `evolution-curator` both default `staleAfterDays`
14
+ * to 30). Centralizing them here means one authoritative default: a config
15
+ * override still applies per package, but the fallback is single-sourced.
16
+ *
17
+ * Package-private tunables (used by exactly one package) stay in that package,
18
+ * not here — see evolution-replay's `DEFAULT_WEIGHTS` and evolution-feedback's
19
+ * threshold, which are intentionally left where they are used.
20
+ * @module @lmzhen/dsh-evolution-core
21
+ */
22
+ // ── Fixed protocol / format / security invariants ────────────────────────────
23
+ /**
24
+ * Required argument names per `skill_manage` action — the SINGLE SOURCE read
25
+ * by the tool's argument gate (tool-skill-manage write-gates.ts) and the plan
26
+ * validator (evolution-plan-validator), so the two can never drift.
27
+ * OPT-05 (2026-09): the plan validator used to accept a `write_file`/
28
+ * `remove_file` op without `file_path` while the executor required it — the
29
+ * staged write then failed at EVERY approve until rejected.
30
+ * Rows here are the op-level requirements only: `delete` additionally
31
+ * requires `absorbed_into` at the PLAN layer (review passes may only delete
32
+ * into an umbrella) and `pin`/`unpin` are tool-only actions — each consumer
33
+ * adds its own extras on top of this table. An empty-string argument is NOT
34
+ * caught here (the tool's gate deliberately lets it reach the library for a
35
+ * more specific remedy message); the validator adds its own `.trim()`
36
+ * emptiness checks for payload fields.
37
+ */
38
+ export const SKILL_ACTION_REQUIRED_FIELDS = {
39
+ create: ['name', 'content'],
40
+ edit: ['name', 'content'],
41
+ update: ['name', 'content'],
42
+ patch: ['name', 'old_string', 'new_string'],
43
+ delete: ['name'],
44
+ write_file: ['name', 'file_path', 'file_content'],
45
+ remove_file: ['name', 'file_path'],
46
+ restructure: ['name'],
47
+ pin: ['name'],
48
+ unpin: ['name'],
49
+ };
50
+ /** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
51
+ * 计划 B-4 (v18): tightened to the UPSTREAM `SKILL_NAME` shape
52
+ * (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:21). The
53
+ * old form admitted trailing/consecutive hyphens, which upstream
54
+ * `validateCandidate` throws on — and that throw aborts the WHOLE `ctx.skills`
55
+ * collection. The catalog provider still filters such legacy tree entries so
56
+ * an existing tree cannot break a session. */
57
+ export const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
58
+ /** Allowed skill support-file subdirectories (path-traversal boundary). */
59
+ export const SUPPORT_DIRS = ['references', 'templates', 'scripts', 'assets'];
60
+ /** Delimiter between durable memory entries (on-disk storage format). */
61
+ export const ENTRY_DELIMITER = '\n§\n';
62
+ /** Built-in skill names the curator must never lifecycle-manage. */
63
+ export const PROTECTED_BUILTIN_SKILLS = new Set(['plan']);
64
+ // ── Skill-limit bounds ───────────────────────────────────────────────────────
65
+ // These are the values that make up `DEFAULT_SKILL_LIMITS` (the shared default
66
+ // the tool config lowers/raises from). Kept under their historic names so the
67
+ // public export surface is unchanged.
68
+ export const MAX_SKILL_NAME_LENGTH = 64;
69
+ export const MAX_DESCRIPTION_LENGTH = 1024;
70
+ export const MAX_SKILL_CONTENT_CHARS = 100_000;
71
+ /** How many content versions to retain per skill (skill-history.ts). 20 mirrors the count
72
+ * KiroCrew settled on for the same job (its `MAX_SKILL_VERSIONS`), and is an E2 deployment
73
+ * parameter — the value is a retention policy, not a protocol constant. */
74
+ export const DEFAULT_SKILL_VERSION_KEEP = 20;
75
+ /** The authoring DISCIPLINE band, taken from the upstream standard (archive §5):
76
+ * peer skills sit at 8-14k characters and a body pushing past 20k belongs in
77
+ * `references/*.md`. Deliberately separate from `MAX_SKILL_CONTENT_CHARS`: the
78
+ * hard ceiling is a deployment-tunable limit, this band is the authoring
79
+ * standard — deriving the band from the ceiling is exactly what let a 40k
80
+ * ceiling hide a 99k body without a single signal saying "split me" (V3). */
81
+ export const AUTHORING_SPLIT_LINE_CHARS = 20_000;
82
+ /** Upstream's conversion basis for CHARACTER LIMITS: 2.75 chars/token, labelled
83
+ * model-independent in the config template (\`cli-config.yaml.example:538\`), and the
84
+ * basis behind every quoted token figure there — memory 2200 chars ≈ 800 tokens,
85
+ * user 1375 ≈ 500, SKILL.md 100_000 ≈ 36k (\`tools/skill_manager_tool.py:455\`).
86
+ * Limits are deliberately conservative, so this is the basis a BORROWED LIMIT must
87
+ * be converted with. */
88
+ export const UPSTREAM_LIMIT_CHARS_PER_TOKEN = 2.75;
89
+ /** The platform's own estimate basis: \`CHARS_PER_TOKEN = 4\` in
90
+ * \`@deepseek-ai/dsh-token-meter/estimate.ts\` (its comment reads "used until exact
91
+ * tokenization is needed"), matching upstream's ESTIMATE-side heuristic — "~4
92
+ * chars/token is the usual English heuristic" (\`agent/prompt_builder.py:1179\`).
93
+ * Estimates use this; limits use the constant above. */
94
+ export const PLATFORM_ESTIMATE_CHARS_PER_TOKEN = 4;
95
+ export const MAX_SKILL_FILE_BYTES = 1_048_576;
96
+ // ── Cross-package shared tunable defaults ────────────────────────────────────
97
+ // Fallback defaults when no config override is set. Single-sourced here;
98
+ // packages reference them via `z.default(DEFAULT_*)` or `config.x ?? DEFAULT_*`.
99
+ export const DEFAULT_REVIEW_MEMORY_INTERVAL = 10;
100
+ export const DEFAULT_REVIEW_SKILL_INTERVAL = 10;
101
+ /** Skill-review completion channel trigger mode: 'cadence' | 'completion' | 'both'.
102
+ * 0.3.39 (V6-53 follow-up): default is 'cadence' — since 0.3.38 the cadence
103
+ * channel's review is executed at conversation END (deferred), so 'both' would
104
+ * inject a second (task-complete) prompt at the same boundary; the cadence
105
+ * deferral alone IS the end-of-conversation summary. */
106
+ export const DEFAULT_SKILL_REVIEW_TRIGGER = 'cadence';
107
+ /** Cumulative session tool calls before a session counts as "proven long" for the completion channel. */
108
+ export const DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS = 20;
109
+ export const DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS = 3;
110
+ export const DEFAULT_SUBSTANTIVE_MIN_USER_CHARS = 200;
111
+ export const DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS = 500;
112
+ export const DEFAULT_MAX_OPS_PER_PLAN = 32;
113
+ export const DEFAULT_CURATOR_INTERVAL_HOURS = 168;
114
+ export const DEFAULT_MIN_IDLE_HOURS = 2;
115
+ export const DEFAULT_STALE_AFTER_DAYS = 30;
116
+ export const DEFAULT_ARCHIVE_AFTER_DAYS = 90;
117
+ /**
118
+ * Support-file count above which `references-sprawl` warns. **NOT CALIBRATED YET** — 60 is a
119
+ * placeholder: the platform corpus is empty (no platform skills available here), so the admission
120
+ * band cannot be checked. Until `gen-health-baseline` reports both corpora, the rule is
121
+ * REPORT-ONLY: its count must not be shown as a number on any surface (plan G4 ③).
122
+ */
123
+ export const SUPPORT_SPRAWL_FILES = 60;
124
+ export const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
125
+ export const DEFAULT_USER_CHAR_LIMIT = 1375;
126
+ /** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
127
+ export const DEFAULT_CONSOLIDATION_FAILURES = 3;
128
+ /** F-20 (v18): the authored-body budget and the hard ceiling are the same
129
+ * number today. Derive it so a future divergence is one edit, not two names
130
+ * that silently disagree. */
131
+ export const DEFAULT_SKILL_CONTENT_CHARS = MAX_SKILL_CONTENT_CHARS;
132
+ /** P3-19 (v14): defaults that were written twice (schema `.default()` AND the
133
+ * clamp fallback literal) now have one home per value. */
134
+ export const DEFAULT_REVIEW_TIMEOUT_MS = 120_000;
135
+ export const DEFAULT_REVIEW_CONTEXT_MESSAGES = 60;
136
+ export const DEFAULT_REVIEW_MESSAGE_CHARS = 2_000;
137
+ export const DEFAULT_CURATOR_BOOT_GRACE_SECONDS = 10;
138
+ export const DEFAULT_CURATOR_REVIEW_MAX_TOKENS = 2_048;
139
+ /** Threat-scan WINDOW SIZE (not a total cap — E-12: the whole text is scanned in
140
+ * overlapping windows, so content beyond this stays in scope). The coverage
141
+ * floor is `PATTERN_OVERLAP + 1` (V6-05). Single home for the core scanners'
142
+ * default parameter and clamp fallback, plus evolution-threat's Config default
143
+ * — the two packages previously wrote 65_536 independently (G0/S0.1). */
144
+ export const DEFAULT_THREAT_MAX_SCAN_CHARS = 65_536;
145
+ /** 0.3.17 (S3.10, T-1): control-plane fields a model-facing write call may
146
+ * never carry — single source for plan-validator, evolution-policy and the
147
+ * threat scanner (they used to each hardcode the list).
148
+ * P2-13 (v37): staged_from_sha256 joins the list — it is the replay's own
149
+ * staleness anchor, and the tool-arguments root is an OPEN object, so a model
150
+ * could otherwise choose the anchor that decides the write's outcome. */
151
+ export const FORBIDDEN_CONTROL_KEYS = ['policy', 'threshold', 'prompt_hash', 'model_route', 'evolution_config', 'staged_from_sha256'];
152
+ /** 0.3.17 (S3.10): the model-facing write tools the policy guard and threat
153
+ * scanner cover. */
154
+ export const EVOLUTION_WRITE_TOOLS = ['memory', 'skill_manage'];
155
+ /** Hermes authoring quality bar for descriptions (the 60-char Rule). The
156
+ * platform's own index limit stays in validateFrontmatter; this bar is the
157
+ * target the authoring standard names, enforced as ADVISORY feedback.
158
+ * 0.3.16 (T-4): moved here from skill-store.ts so drift-signals (pure, no IO)
159
+ * can reference it without importing the skill-store module. */
160
+ export const AUTHORING_DESCRIPTION_BAR = 60;
161
+ /** The PLATFORM's default cap on the description a skill catalog shows —
162
+ * `catalogDescriptionMaxLength` on the platform's `tool-skill` row (default 500).
163
+ *
164
+ * Recorded here because it is the third number in the description-length relationship, and the one
165
+ * that is easiest to misread as a family rule: the family's own host/all rows SET that field to
166
+ * `AUTHORING_DESCRIPTION_BAR` (60), so what a family deployment truncates at is the ROW's value —
167
+ * this default only governs compositions that do not override it. It is NOT a refusal threshold:
168
+ * the only refusal is `MAX_DESCRIPTION_LENGTH` / the row's `maxDescriptionLength`, and the catalog
169
+ * cut is a view, not a validation. See tool-skill-manage/README.md for the three-number table. */
170
+ export const PLATFORM_CATALOG_DESCRIPTION_DEFAULT = 500;
171
+ /** The split hint both size refusals share (0.5.0 V1). The same sentence used to
172
+ * be copied into validateFrontmatter AND the patch path, and neither copy named
173
+ * where the content should go — the upstream cap message names the destination
174
+ * directories, and that is the part a model actually acts on. */
175
+ export const CONTENT_SPLIT_HINT = '建议拆成更小的 SKILL.md,把支撑文件放进 references/ 或 templates/。';
176
+ /** V27 G2.4: the largest millisecond delay a timer accepts. `AbortSignal.timeout`
177
+ * (and `setTimeout`) coerce anything larger to 1ms after a Node warning, so a
178
+ * timeout configured above this ceiling silently collapses to "immediately
179
+ * aborted". The curator's review timeout and the review timeout each carried
180
+ * their own copy of the literal; the bound is one protocol constant.
181
+ * (v19 P2-10 corrected the value from 2^32-1 to Node's real 2^31-1 ceiling.) */
182
+ export const MAX_TIMER_DELAY_MS = 2_147_483_647;
183
+ /**
184
+ * Model-facing budget of one `maintenance_probe` call (ms) — a HANG GUARD, not a
185
+ * performance knob.
186
+ *
187
+ * Measured (2026-10-10): one probe costs 188/220/225 ms on the deployed library
188
+ * (28 skills / 418 KB) and scales linearly at ~5.5 ms per skill (100 skills
189
+ * 0.4-0.6 s, 400 skills 1.9-2.5 s), so this default is ~50x the worst measured
190
+ * case and ~600x the deployed one — it will not fire in normal use, and the
191
+ * ~305 s desktop interaction wall is ~5e4 skills away.
192
+ *
193
+ * It exists for the state the probe cannot report itself: a provider that never
194
+ * answers. The platform's tool-timeout guard reads this budget off the tool
195
+ * definition and turns the hang into a diagnosable `tool call timed out after
196
+ * <ms>ms` error result, instead of a request that ends with nothing to read.
197
+ * Reopen the default when the library approaches ~1e4 skills, when the
198
+ * `evolutionIo` provider stops being local, or when a model leg enters a tool's
199
+ * execute (the same conditions the probe's own note lists).
200
+ */
201
+ export const DEFAULT_PROBE_TIMEOUT_MS = 120_000;
202
+ /** V27 G2.4: the model each review/curation leg defaults to. The policy schema,
203
+ * the policy resolver and the curator's LLM nomination pass each carried their
204
+ * own copy of these strings — a deployment that changed the policy default used
205
+ * to leave the curator passing a different model than the reviews. */
206
+ export const DEFAULT_MEMORY_REVIEW_MODEL = 'deepseek-v4-flash';
207
+ export const DEFAULT_SKILL_REVIEW_MODEL = 'deepseek-v4-pro';
208
+ export const DEFAULT_CURATOR_MODEL = 'deepseek-v4-pro';
209
+ // ── Model-visible prompt-section orders ──────────────────────────────────────
210
+ // The platform's `PromptSection.order` scale is a platform-internal constant
211
+ // table (`SECTION_ORDERS`, core/system-prompt), not a published contract: the
212
+ // 0.1.5 line re-scaled every first-party section from -1000 to 10200
213
+ // (`DEPLOYMENT_PERSONA_SUFFIX` is the highest), so a third-party literal that
214
+ // used to sort last could silently sort into the middle of the tool guidance.
215
+ // Both family sections are operational guidance for the model, so they belong
216
+ // AFTER every first-party section on every line. These values sit above 0.1.5's
217
+ // maximum; scripts/verify-platform-contract.mjs --upstream re-reads the
218
+ // platform's scale and fails when it grows past them.
219
+ /** Order of the `evolution:memory-guidance` section (before the skills one). */
220
+ export const MEMORY_GUIDANCE_SECTION_ORDER = 11000;
221
+ /** Order of the `evolution-skills-guidance` section (last of the two). */
222
+ export const SKILLS_GUIDANCE_SECTION_ORDER = 11100;
223
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Context-cost accounting for skill bodies (design §5.2).
3
+ *
4
+ * A raw character count is a poor budget: the same 100k characters cost very
5
+ * different amounts of context in Chinese-heavy prose and in English. The
6
+ * weighted unit makes the two comparable, and the token range exists only to
7
+ * make the load cost visible next to the character count — it is an ESTIMATE,
8
+ * never a measurement.
9
+ *
10
+ * Accounting basis: the ON-DISK form (`skillMdOnDisk()`, the same view the
11
+ * write limit uses), so a body that is exactly at the limit cannot read as
12
+ * over-limit here and pass there (the v37 P2-1 deadlock class).
13
+ */
14
+ import { UPSTREAM_LIMIT_CHARS_PER_TOKEN } from "./constants.js";
15
+ import { skillMdOnDisk } from "./frontmatter.js";
16
+ /** Token weight of one CJK / kana / full-width code unit on the LIMIT basis —
17
+ * upstream's model-independent conversion is 2.75 chars/token for prose, and CJK
18
+ * prose is ~1 token per character (the conservative end of the 0.6–1.0 range the
19
+ * estimate side reports). Thresholds are compared on THIS basis so a borrowed
20
+ * character line keeps its meaning; the estimate range below reports the spread. */
21
+ export const TOKEN_CJK_WEIGHT = 1;
22
+ /** Token weight of one non-CJK code unit on the LIMIT basis (1 / 2.75 ≈ 0.364). */
23
+ export const TOKEN_ASCII_WEIGHT = 1 / UPSTREAM_LIMIT_CHARS_PER_TOKEN;
24
+ /** Token-per-unit bounds of the estimate range (CJK: 0.6–1.0, non-CJK: 1/4–1/3).
25
+ * The range is the 4-chars/token estimate family, so on ASCII-heavy bodies the
26
+ * LIMIT-basis `tokens` point can sit above `tokensHigh` (1/2.75 > 1/3): the two
27
+ * answer different questions and are never nested by construction. */
28
+ export const COST_CJK_TOKEN_LOW = 0.6;
29
+ export const COST_CJK_TOKEN_HIGH = 1;
30
+ export const COST_ASCII_TOKEN_LOW = 0.25;
31
+ export const COST_ASCII_TOKEN_HIGH = 1 / 3;
32
+ /** The token line a borrowed CHARACTER threshold draws for ONE body: 20k characters
33
+ * of THIS composition, expressed in the same tokens `bodyCost` counts. Comparing
34
+ * `tokens` against this line is deliberately equivalent to "chars >= line" — that
35
+ * equivalence is what keeps a borrowed line's textual effect identical — while the
36
+ * judgment itself, and every number reported, stays on the token scale.
37
+ * @param lineChars - the borrowed character line (whole-body characters).
38
+ * @param cost - the body's own cost breakdown.
39
+ * @returns the equivalent token line (0 when the body is empty). */
40
+ export function tokenLineFor(lineChars, cost) {
41
+ if (cost.chars === 0)
42
+ return 0;
43
+ return Math.round((lineChars * cost.tokens) / cost.chars);
44
+ }
45
+ /** CJK ideographs, kana, full-width forms and CJK punctuation. */
46
+ const CJK_RE = /[\u3000-\u303f\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff00-\uffef]/g;
47
+ /**
48
+ * Cost of one skill body.
49
+ * @param content - the body text as written (trailing whitespace is normalized
50
+ * away by the on-disk accounting, so callers need not pre-trim).
51
+ * @returns the cost breakdown; an empty body answers all-zero.
52
+ */
53
+ export function bodyCost(content) {
54
+ const onDisk = skillMdOnDisk(content);
55
+ const cjk = [...onDisk.matchAll(CJK_RE)].length;
56
+ const chars = onDisk.length;
57
+ const ascii = chars - cjk;
58
+ return {
59
+ chars,
60
+ cjk,
61
+ ascii,
62
+ tokens: Math.round(cjk * TOKEN_CJK_WEIGHT + ascii * TOKEN_ASCII_WEIGHT),
63
+ tokensLow: Math.round(cjk * COST_CJK_TOKEN_LOW + ascii * COST_ASCII_TOKEN_LOW),
64
+ tokensHigh: Math.round(cjk * COST_CJK_TOKEN_HIGH + ascii * COST_ASCII_TOKEN_HIGH),
65
+ };
66
+ }
67
+ //# sourceMappingURL=cost.js.map
@@ -93,6 +93,7 @@ export interface CuratorRunReport {
93
93
  llmReviewEnabled?: boolean;
94
94
  /** V6-35 (0.3.36): lenient-parse shape notes from the LLM nomination block. */
95
95
  nominationsWarnings?: string[];
96
+ scanWarnings?: string[];
96
97
  /** 0.5.0 V1 (design §16.6-④): archived entries past the retention window that
97
98
  * the CURRENT policy keeps rather than deletes. Present (even empty) only when
98
99
  * the retention policy was `report`, so a report cannot read as "nothing
@@ -117,6 +118,7 @@ export interface CuratorReportInput {
117
118
  snapshotPath?: string;
118
119
  llmReviewEnabled?: boolean;
119
120
  nominationsWarnings?: readonly string[];
121
+ scanWarnings?: readonly string[];
120
122
  wouldPrune?: readonly string[] | null;
121
123
  }
122
124
  export declare function buildCuratorRunReport(input: CuratorReportInput): CuratorRunReport;