@lmzhen/dsh-evolution-core 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Citation resolution for skill bodies and support files (design §5.2).
3
+ *
4
+ * WHY this exists beside `supportRefs` (skill-store.ts): that scanner answers
5
+ * "does this text contain a path-shaped token", which is the right question
6
+ * when a package is being ARCHIVED — every such path may dangle — and the wrong
7
+ * one when a section is only being MOVED, where nothing leaves and the only
8
+ * thing that changes is the directory a citation is read from. Measured false
9
+ * positives of the token scan: a URL tail (`https://host/references/x.md`), a
10
+ * category path the original Hermes documents as legitimate
11
+ * (`skills/scripts/foo.py`), and a prose command line
12
+ * (`npm run scripts/build.mjs`).
13
+ *
14
+ * CONTRACT: a token whose path STARTS WITH a support directory is a citation of
15
+ * this skill and is resolved from the SKILL ROOT, in whichever file it is
16
+ * written. A token that merely contains a support directory further along its
17
+ * path belongs to something else: it is reported as `foreign` and never
18
+ * resolved.
19
+ *
20
+ * PURE: existence is decided against the caller's file list, so identical
21
+ * inputs always answer the same report.
22
+ */
23
+ /** Support directories that make a path a citation of the owning skill. */
24
+ export declare const CITATION_SUPPORT_DIRS: readonly string[];
25
+ /** How one path-shaped token was classified. */
26
+ export type CitationKind =
27
+ /** Starts with a support directory: a citation of this skill. */
28
+ 'citation'
29
+ /** Inside a fenced code block: illustrative, never resolved. */
30
+ | 'fence'
31
+ /** Tail of a URL. */
32
+ | 'url'
33
+ /** Contains a support directory further along its path (`skills/scripts/foo.py`). */
34
+ | 'foreign'
35
+ /** A command line naming a path (`npm run scripts/build.mjs`). */
36
+ | 'prose';
37
+ /** One scanned token with its classification and resolution. */
38
+ export interface CitationRef {
39
+ /** The matched token, verbatim, after trailing sentence punctuation is stripped. */
40
+ raw: string;
41
+ /** 1-based line number in the scanned content. */
42
+ line: number;
43
+ /** Skill-root-relative target, or null when the token is not a citation of this skill. */
44
+ target: string | null;
45
+ /** The base a citation is read from; this scanner resolves the skill root only. */
46
+ base: 'root';
47
+ kind: CitationKind;
48
+ /** Whether `target` names a file in the caller's file list. */
49
+ exists: boolean;
50
+ /** `#fragment` carried by the token, when present. */
51
+ fragment?: string | undefined;
52
+ }
53
+ /** One scan of a body or support file. */
54
+ export interface CitationReport {
55
+ refs: readonly CitationRef[];
56
+ /** Citations whose target is provably absent from the file list. */
57
+ dangling: readonly CitationRef[];
58
+ /** Citations a non-recursive listing cannot decide (target sits under a listed directory). */
59
+ unverified: readonly CitationRef[];
60
+ /** Path-shaped tokens that are not citations of this skill (foreign/url/prose/fence). */
61
+ foreign: readonly CitationRef[];
62
+ /** True when the scan stopped at the budget: a partial report never reads as clean. */
63
+ truncated: boolean;
64
+ }
65
+ /** Scan budget: bounded so a pathological body cannot stall a maintenance run. */
66
+ export declare const DEFAULT_CITATION_REF_BUDGET = 500;
67
+ /**
68
+ * Resolve every path-shaped token in `content`.
69
+ * @param input - content, the skill-root-relative path of its owner, the skill's
70
+ * file list, and an optional scan budget.
71
+ * @returns the refs plus the dangling and foreign subsets; `truncated` marks a
72
+ * report that stopped at the budget.
73
+ */
74
+ export declare function resolveCitations(input: {
75
+ content: string;
76
+ file: string;
77
+ files: readonly string[];
78
+ budget?: number | undefined;
79
+ }): CitationReport;
80
+ /** The sanctioned body hook (design §5.6): a list item whose payload is
81
+ * exactly one support-file path, so the file stays discoverable in the body. */
82
+ export declare const HOOK_LINE_RE: RegExp;
83
+ /** The retirement escape hatch (design §5.6): a standalone body comment that
84
+ * names the support file it exempts and the reason it must survive. The path
85
+ * is part of the marker because the proposal list is per FILE, and a reason is
86
+ * required — an unexplained exemption is what this hook exists to prevent. */
87
+ export declare const KEEP_LINE_RE: RegExp;
88
+ /** Whether a skill-relative path names a FILE rather than a directory: the same
89
+ * rule the scanner applies to a token's final segment (`name.ext`). The support
90
+ * listing is one level deep and includes directory entries (a real library has
91
+ * `references/archive`), which no pointer or retirement list may treat as a file.
92
+ * @param path - a skill-root-relative support path.
93
+ * @returns true when the final segment is file-shaped.
94
+ */
95
+ export declare function isFileShapedPath(path: string): boolean;
96
+ /** Result of one hook scan over a body. */
97
+ export interface BodyHookScan {
98
+ /** Targets named by a sanctioned hook line, in file order, deduplicated. */
99
+ targets: readonly string[];
100
+ /** Target -> the reason its `keep` marker records. */
101
+ kept: ReadonlyMap<string, string>;
102
+ }
103
+ /**
104
+ * Scan a body for sanctioned hooks and `keep` markers.
105
+ * @param content - the SKILL.md body (frontmatter included).
106
+ * @returns hook targets plus the `keep` reasons keyed by target.
107
+ */
108
+ export declare function scanBodyHooks(content: string): BodyHookScan;
109
+ //# sourceMappingURL=citations.d.ts.map
@@ -52,6 +52,26 @@ export declare const PROTECTED_BUILTIN_SKILLS: ReadonlySet<string>;
52
52
  export declare const MAX_SKILL_NAME_LENGTH = 64;
53
53
  export declare const MAX_DESCRIPTION_LENGTH = 1024;
54
54
  export declare const MAX_SKILL_CONTENT_CHARS = 100000;
55
+ /** The authoring DISCIPLINE band, taken from the upstream standard (archive §5):
56
+ * peer skills sit at 8-14k characters and a body pushing past 20k belongs in
57
+ * `references/*.md`. Deliberately separate from `MAX_SKILL_CONTENT_CHARS`: the
58
+ * hard ceiling is a deployment-tunable limit, this band is the authoring
59
+ * standard — deriving the band from the ceiling is exactly what let a 40k
60
+ * ceiling hide a 99k body without a single signal saying "split me" (V3). */
61
+ export declare const AUTHORING_SPLIT_LINE_CHARS = 20000;
62
+ /** Upstream's conversion basis for CHARACTER LIMITS: 2.75 chars/token, labelled
63
+ * model-independent in the config template (\`cli-config.yaml.example:538\`), and the
64
+ * basis behind every quoted token figure there — memory 2200 chars ≈ 800 tokens,
65
+ * user 1375 ≈ 500, SKILL.md 100_000 ≈ 36k (\`tools/skill_manager_tool.py:455\`).
66
+ * Limits are deliberately conservative, so this is the basis a BORROWED LIMIT must
67
+ * be converted with. */
68
+ export declare const UPSTREAM_LIMIT_CHARS_PER_TOKEN = 2.75;
69
+ /** The platform's own estimate basis: \`CHARS_PER_TOKEN = 4\` in
70
+ * \`@deepseek-ai/dsh-token-meter/estimate.ts\` (its comment reads "used until exact
71
+ * tokenization is needed"), matching upstream's ESTIMATE-side heuristic — "~4
72
+ * chars/token is the usual English heuristic" (\`agent/prompt_builder.py:1179\`).
73
+ * Estimates use this; limits use the constant above. */
74
+ export declare const PLATFORM_ESTIMATE_CHARS_PER_TOKEN = 4;
55
75
  export declare const MAX_SKILL_FILE_BYTES = 1048576;
56
76
  export declare const DEFAULT_REVIEW_MEMORY_INTERVAL = 10;
57
77
  export declare const DEFAULT_REVIEW_SKILL_INTERVAL = 10;
@@ -86,6 +106,12 @@ export declare const DEFAULT_REVIEW_CONTEXT_MESSAGES = 60;
86
106
  export declare const DEFAULT_REVIEW_MESSAGE_CHARS = 2000;
87
107
  export declare const DEFAULT_CURATOR_BOOT_GRACE_SECONDS = 10;
88
108
  export declare const DEFAULT_CURATOR_REVIEW_MAX_TOKENS = 2048;
109
+ /** Threat-scan WINDOW SIZE (not a total cap — E-12: the whole text is scanned in
110
+ * overlapping windows, so content beyond this stays in scope). The coverage
111
+ * floor is `PATTERN_OVERLAP + 1` (V6-05). Single home for the core scanners'
112
+ * default parameter and clamp fallback, plus evolution-threat's Config default
113
+ * — the two packages previously wrote 65_536 independently (G0/S0.1). */
114
+ export declare const DEFAULT_THREAT_MAX_SCAN_CHARS = 65536;
89
115
  /** 0.3.17 (S3.10, T-1): control-plane fields a model-facing write call may
90
116
  * never carry — single source for plan-validator, evolution-policy and the
91
117
  * threat scanner (they used to each hardcode the list).
@@ -102,6 +128,11 @@ export declare const EVOLUTION_WRITE_TOOLS: readonly ["memory", "skill_manage"];
102
128
  * 0.3.16 (T-4): moved here from skill-store.ts so drift-signals (pure, no IO)
103
129
  * can reference it without importing the skill-store module. */
104
130
  export declare const AUTHORING_DESCRIPTION_BAR = 60;
131
+ /** The split hint both size refusals share (0.5.0 V1). The same sentence used to
132
+ * be copied into validateFrontmatter AND the patch path, and neither copy named
133
+ * where the content should go — the upstream cap message names the destination
134
+ * directories, and that is the part a model actually acts on. */
135
+ export declare const CONTENT_SPLIT_HINT = "Consider splitting into a smaller SKILL.md with supporting files in references/ or templates.";
105
136
  /** V27 G2.4: the largest millisecond delay a timer accepts. `AbortSignal.timeout`
106
137
  * (and `setTimeout`) coerce anything larger to 1ms after a Node warning, so a
107
138
  * timeout configured above this ceiling silently collapses to "immediately
@@ -0,0 +1,63 @@
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
+ /** Token weight of one CJK / kana / full-width code unit on the LIMIT basis —
15
+ * upstream's model-independent conversion is 2.75 chars/token for prose, and CJK
16
+ * prose is ~1 token per character (the conservative end of the 0.6–1.0 range the
17
+ * estimate side reports). Thresholds are compared on THIS basis so a borrowed
18
+ * character line keeps its meaning; the estimate range below reports the spread. */
19
+ export declare const TOKEN_CJK_WEIGHT = 1;
20
+ /** Token weight of one non-CJK code unit on the LIMIT basis (1 / 2.75 ≈ 0.364). */
21
+ export declare const TOKEN_ASCII_WEIGHT: number;
22
+ /** Token-per-unit bounds of the estimate range (CJK: 0.6–1.0, non-CJK: 1/4–1/3).
23
+ * The range is the 4-chars/token estimate family, so on ASCII-heavy bodies the
24
+ * LIMIT-basis `tokens` point can sit above `tokensHigh` (1/2.75 > 1/3): the two
25
+ * answer different questions and are never nested by construction. */
26
+ export declare const COST_CJK_TOKEN_LOW = 0.6;
27
+ export declare const COST_CJK_TOKEN_HIGH = 1;
28
+ export declare const COST_ASCII_TOKEN_LOW = 0.25;
29
+ export declare const COST_ASCII_TOKEN_HIGH: number;
30
+ /** The token line a borrowed CHARACTER threshold draws for ONE body: 20k characters
31
+ * of THIS composition, expressed in the same tokens `bodyCost` counts. Comparing
32
+ * `tokens` against this line is deliberately equivalent to "chars >= line" — that
33
+ * equivalence is what keeps a borrowed line's textual effect identical — while the
34
+ * judgment itself, and every number reported, stays on the token scale.
35
+ * @param lineChars - the borrowed character line (whole-body characters).
36
+ * @param cost - the body's own cost breakdown.
37
+ * @returns the equivalent token line (0 when the body is empty). */
38
+ export declare function tokenLineFor(lineChars: number, cost: BodyCost): number;
39
+ /** Cost of one body: raw counts, the weighted total, and the estimate range. */
40
+ export interface BodyCost {
41
+ /** Code units of the on-disk form — the same measure `maxSkillContentChars` bounds. */
42
+ chars: number;
43
+ /** Code units matched by the CJK ranges. */
44
+ cjk: number;
45
+ /** Code units that are not CJK (includes surrogate halves and syntax). */
46
+ ascii: number;
47
+ /** Single token count on the LIMIT basis: `cjk * TOKEN_CJK_WEIGHT + ascii *
48
+ * TOKEN_ASCII_WEIGHT`, rounded. This is the number every threshold compares
49
+ * against, so a threshold borrowed as a character line converts faithfully. */
50
+ tokens: number;
51
+ /** Lower bound of the token estimate. */
52
+ tokensLow: number;
53
+ /** Upper bound of the token estimate. */
54
+ tokensHigh: number;
55
+ }
56
+ /**
57
+ * Cost of one skill body.
58
+ * @param content - the body text as written (trailing whitespace is normalized
59
+ * away by the on-disk accounting, so callers need not pre-trim).
60
+ * @returns the cost breakdown; an empty body answers all-zero.
61
+ */
62
+ export declare function bodyCost(content: string): BodyCost;
63
+ //# sourceMappingURL=cost.d.ts.map
@@ -81,6 +81,12 @@ export interface CuratorRunReport {
81
81
  llmReviewEnabled?: boolean;
82
82
  /** V6-35 (0.3.36): lenient-parse shape notes from the LLM nomination block. */
83
83
  nominationsWarnings?: string[];
84
+ /** 0.5.0 V1 (design §16.6-④): archived entries past the retention window that
85
+ * the CURRENT policy keeps rather than deletes. Present (even empty) only when
86
+ * the retention policy was `report`, so a report cannot read as "nothing
87
+ * expired" for a run that pruned under a different policy. `null` means the
88
+ * listing could not be read — unknown, never "nothing expired". */
89
+ wouldPrune?: string[] | null;
84
90
  }
85
91
  export interface CuratorReportInput {
86
92
  runId: string;
@@ -99,6 +105,7 @@ export interface CuratorReportInput {
99
105
  snapshotPath?: string;
100
106
  llmReviewEnabled?: boolean;
101
107
  nominationsWarnings?: readonly string[];
108
+ wouldPrune?: readonly string[] | null;
102
109
  }
103
110
  export declare function buildCuratorRunReport(input: CuratorReportInput): CuratorRunReport;
104
111
  /**
@@ -10,6 +10,8 @@
10
10
  *
11
11
  * Distinct from `signals.ts` — the session-level review signal gate.
12
12
  */
13
+ import { type CitationReport } from './citations.ts';
14
+ import { type BodyCost } from './cost.ts';
13
15
  /** One skill's library state; the assembler (not this module) reads IO. */
14
16
  export interface DriftSkillSnapshot {
15
17
  name: string;
@@ -27,6 +29,23 @@ export interface DriftSkillSnapshot {
27
29
  protected?: string | null | undefined;
28
30
  /** Frontmatter values the strict-YAML platform catalog cannot load (0.3.11). */
29
31
  catalogInvalid?: boolean | undefined;
32
+ /** Weighted context cost of the body (design §5.2); undefined = not measured. */
33
+ cost?: BodyCost | undefined;
34
+ /** Citation scan of the body (design §5.2); undefined = not scanned. */
35
+ citations?: CitationReport | undefined;
36
+ /** Per-support-file read counts (design §5.5); undefined = no evidence. */
37
+ demand?: Readonly<Record<string, number>> | undefined;
38
+ /** Idle age of the owning skill (design §5.6); undefined = no record to age. */
39
+ liveness?: SkillLiveness | undefined;
40
+ /** Character counts of support files that can possibly exceed the content cap
41
+ * (design §16.6, V4); undefined = not measured. The assembler pre-filters by
42
+ * byte size, which is complete for the oversize question. */
43
+ supportChars?: Readonly<Record<string, number>> | undefined;
44
+ }
45
+ /** Retirement-proposal input for one skill (design §5.6). */
46
+ export interface SkillLiveness {
47
+ /** Idle days since the lifecycle age anchor (`last activity ?? created_at`). */
48
+ idleDays: number;
30
49
  }
31
50
  /** verdict=over means "relatively positioned above the threshold", never a violation. */
32
51
  type DriftVerdict = 'pass' | 'over' | 'unknown';
@@ -56,11 +75,39 @@ export interface DriftReport {
56
75
  /** Physical line length at/above which a body line is reported overlong (011 §4). */
57
76
  export declare const DRIFT_MAX_LINE_CHARS = 1500;
58
77
  /** Signal-set version: bump whenever ids/thresholds change (011 §7 version coupling). */
59
- export declare const DRIFT_SIGNALS_VERSION = "1";
78
+ export declare const DRIFT_SIGNALS_VERSION = "3";
60
79
  /** Render-time nouns for the MAINTAIN_PROMPT placeholders (single vocabulary with the facts block). */
61
80
  export declare const DRIFT_SIGNAL_NOUNS: Readonly<Record<string, string>>;
62
81
  /** Detect support files the body never references (by basename or relative path). */
63
82
  export declare function missingSupportPointers(body: string, supportFiles: readonly string[]): string[];
83
+ /** Support files the body mentions WITHOUT the sanctioned hook form (design
84
+ * §5.6). Such a mention still counts as a pointer for `pointer_missing`, but
85
+ * only the hook form keeps the file discoverable after a move — detail only.
86
+ * A file carrying a `keep` marker is exempt: its mention IS the marker. */
87
+ export declare function unhookedSupportPointers(body: string, supportFiles: readonly string[]): string[];
88
+ /** Why a retirement report lists nothing (design §5.6). */
89
+ export type RetirementStatus = 'listed' | 'none' | 'no-age' | 'unscanned';
90
+ /** One support file proposed for retirement review. */
91
+ export interface RetirementCandidate {
92
+ path: string;
93
+ /** Whole idle days of the owning skill at scan time. */
94
+ idleDays: number;
95
+ }
96
+ /** Retirement proposal for one skill: candidates plus the reason when empty. */
97
+ export interface RetirementReport {
98
+ candidates: readonly RetirementCandidate[];
99
+ status: RetirementStatus;
100
+ }
101
+ /** Support files with no readers, no citations and no `keep` marker whose owning
102
+ * skill has been idle for at least the lifecycle stale window. PROPOSAL INPUT
103
+ * only — nothing retires a file on its own — and an unmeasurable input (no age
104
+ * evidence, no citation scan) yields an empty list WITH its reason, never a
105
+ * silent "nothing qualifies".
106
+ * @param snapshot - the skill's drift snapshot.
107
+ * @param supportFiles - its enumerated support files, in listing order.
108
+ * @returns the candidates plus the status that explains an empty list.
109
+ */
110
+ export declare function retirementReport(snapshot: DriftSkillSnapshot, supportFiles: readonly string[]): RetirementReport;
64
111
  /** Duplicate `## heading` occurrences: singleton results default to head of the file. */
65
112
  export declare function duplicateHeadings(body: string): Array<{
66
113
  heading: string;
@@ -134,6 +134,14 @@ export declare function normalizeFrontmatter(content: string): FrontmatterNormal
134
134
  * excluded. Pure and deduplicated.
135
135
  */
136
136
  export declare function relatedSkillNames(content: string, exclude?: string): string[];
137
+ /** S1.2 (v37 P2-1): the content limit applies to the bytes that LAND ON DISK.
138
+ * Every write normalizes with `trimEnd() + '\n'`, so judging the raw argument let
139
+ * a 100_000-character body with no trailing newline land as 100_001 bytes — and
140
+ * every later patch/update of that skill was then refused, which made it
141
+ * unmaintainable through `skill_manage` with no repair path at all. */
142
+ /** The bytes that land on disk for a SKILL.md write — the ONE accounting basis
143
+ * shared by the content limit and the cost estimate (design §5.2). */
144
+ export declare function skillMdOnDisk(content: string): string;
137
145
  /** Whether `content` would exceed `limit` once written. */
138
146
  export declare function exceedsContentLimit(content: string, limit: number): boolean;
139
147
  /** S1.2: the repair path — a write that makes an already-over-limit file smaller.
@@ -25,6 +25,9 @@
25
25
  * `evolution-events.ts`, `io.ts` (the ctx.evolutionIo seam itself).
26
26
  * @module @lmzhen/dsh-evolution-core
27
27
  */
28
+ export * from './citations.ts';
29
+ export * from './reference-rewrite.ts';
30
+ export * from './cost.ts';
28
31
  export * from './curator.ts';
29
32
  export * from './evolution-events.ts';
30
33
  export * from './gates.ts';
@@ -53,6 +56,7 @@ export * from './threats.ts';
53
56
  export * from './tool-dispatch.ts';
54
57
  export * from './usage.ts';
55
58
  export * from './constants.ts';
59
+ export * from './params.ts';
56
60
  export * from './numeric.ts';
57
61
  export * from './opt-in.ts';
58
62
  //# sourceMappingURL=index.d.ts.map
@@ -5,11 +5,79 @@
5
5
  * store share one declaration site. Re-exported by skill-store.ts: the package
6
6
  * export surface is unchanged.
7
7
  */
8
+ /** How a restructure treats a section carrying support-file citations (design §2.2). */
9
+ export type CitationPolicy = 'verify' | 'refuse';
10
+ /** `verify` checks that every cited target exists and otherwise allows the move;
11
+ * `refuse` restores the pre-0.5 behaviour of refusing ANY citation-carrying
12
+ * section. Resolved at the call site, so every existing limits object keeps
13
+ * working (unknown policy never silently changes a write path). */
14
+ export declare const DEFAULT_CITATION_POLICY: CitationPolicy;
15
+ /** How a consolidation treats the source's support files (design §16.7).
16
+ * `off` refuses as before; `plan` keeps refusing but reports what a re-home
17
+ * would have to rewrite; `apply` (V2) performs the re-home and the rewrites, but
18
+ * only when the plan proves it leaves NOTHING dangling — otherwise it refuses
19
+ * exactly like `plan`. */
20
+ export type ReferenceRewritePolicy = 'off' | 'plan' | 'apply';
21
+ /** Default: report the plan on refusal, never write (behaviour unchanged). */
22
+ export declare const DEFAULT_REFERENCE_REWRITE_POLICY: ReferenceRewritePolicy;
23
+ /** What happens to `.archive` entries past the retention window (design §16.6-④).
24
+ * `report` (the default) names them and deletes nothing; `prune` restores the
25
+ * pre-0.5 deletion, which upstream never does. */
26
+ export type ArchiveRetentionPolicy = 'report' | 'prune';
27
+ /** Default: report-only, because upstream's hard invariant is never to delete. */
28
+ export declare const DEFAULT_ARCHIVE_RETENTION_POLICY: ArchiveRetentionPolicy;
29
+ /** How the 100k-character cap treats SUPPORT files (design §16.6, V4). Upstream
30
+ * applies the cap to every written file; we land it in `report` mode first (the
31
+ * write goes through with an advisory) and `enforce` refuses — with the same
32
+ * net-shrink repair path SKILL.md already has, so an over-cap legacy file can
33
+ * always be brought back under the cap instead of becoming unmaintainable. */
34
+ export type SupportFileCharPolicy = 'report' | 'enforce';
35
+ /** Default: report, so the cap cannot brick an existing oversize file on upgrade
36
+ * (the live library carries a 189k-character release log today). */
37
+ export declare const DEFAULT_SUPPORT_FILE_CHAR_POLICY: SupportFileCharPolicy;
38
+ /** The four stage defaults in ONE object, so the policy schema's `z.default(...)`
39
+ * calls and the store's fallbacks cannot drift apart. */
40
+ export declare const POLICY_STAGE_DEFAULTS: Readonly<{
41
+ citationPolicy: "verify";
42
+ referenceRewrite: "plan";
43
+ archiveRetention: "report";
44
+ supportFileCharPolicy: "report";
45
+ }>;
46
+ /** The policy-snapshot fields that select a write-behaviour STAGE (design §16).
47
+ * Structural, not imported from evolution-policy, so core stays a leaf. */
48
+ export interface PolicyStageFields {
49
+ citationPolicy?: CitationPolicy | undefined;
50
+ referenceRewrite?: ReferenceRewritePolicy | undefined;
51
+ archiveRetention?: ArchiveRetentionPolicy | undefined;
52
+ supportFileCharPolicy?: SupportFileCharPolicy | undefined;
53
+ }
54
+ /**
55
+ * The ONE conversion from the deployment policy snapshot to library limits
56
+ * (design §16.7): every plugin that owns a writable SkillLibrary spreads this into
57
+ * its limits, so a stage selected in cordis.yml reaches every write path. A
58
+ * per-plugin copy would be the third home for the same threshold.
59
+ *
60
+ * Only PRESENT fields are copied: an absent policy field must fall through to the
61
+ * library default rather than pinning `undefined` onto an optional limit (which
62
+ * `exactOptionalPropertyTypes` forbids and which would defeat the `?? DEFAULT`
63
+ * resolution inside the store).
64
+ * @param snapshot - the evolutionPolicy snapshot, or undefined when unmounted.
65
+ * @returns the stage fields the snapshot actually carries.
66
+ */
67
+ export declare function policyStageLimits(snapshot: PolicyStageFields | undefined): PolicyStageFields;
8
68
  export interface SkillLimits {
9
69
  maxNameLength: number;
10
70
  maxDescriptionLength: number;
11
71
  maxSkillContentChars: number;
12
72
  maxSkillFileBytes: number;
73
+ /** See CitationPolicy. Optional so existing limits objects stay valid. */
74
+ citationPolicy?: CitationPolicy | undefined;
75
+ /** See ReferenceRewritePolicy; absent means the default (`plan`). */
76
+ referenceRewrite?: ReferenceRewritePolicy | undefined;
77
+ /** See ArchiveRetentionPolicy; absent means the default (`report`). */
78
+ archiveRetention?: ArchiveRetentionPolicy | undefined;
79
+ /** See SupportFileCharPolicy; absent means the default (`report`). */
80
+ supportFileCharPolicy?: SupportFileCharPolicy | undefined;
13
81
  }
14
82
  export declare const DEFAULT_SKILL_LIMITS: SkillLimits;
15
83
  //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Parameter id consolidation (G0/S0.2): one semantic gets ONE id.
3
+ *
4
+ * Nine places in this family carry the same value under two carriers: three use
5
+ * the SAME name in two carriers (reviewMode, staleAfterDays, archiveAfterDays —
6
+ * resolved by the existing policy-shadows-row rule) and six use DIFFERENT names.
7
+ * This module owns the six: the policy/snapshot name is the canonical id, the
8
+ * plugin-row name is a deprecated alias kept readable for one minor version
9
+ * (0.6.x) and removable in 0.7.0.
10
+ *
11
+ * Reading stays compatible (a carrier still spelling the legacy name resolves),
12
+ * writing is strict (the write path accepts canonical ids only, so no new
13
+ * document is created under a deprecated name).
14
+ * @module
15
+ */
16
+ /** Deprecated alias (plugin-row name) -> canonical id (policy/snapshot name). */
17
+ export declare const PARAM_ALIASES: Readonly<Record<string, string>>;
18
+ /**
19
+ * Resolve any parameter id to its canonical form.
20
+ * @param id - canonical id or deprecated alias.
21
+ * @returns the canonical id; ids without an alias pass through unchanged.
22
+ */
23
+ export declare function resolveParamId(id: string): string;
24
+ /**
25
+ * Whether an id is a deprecated alias.
26
+ * @param id - parameter id to test.
27
+ * @returns true when the id must be migrated to its canonical form.
28
+ */
29
+ export declare function isDeprecatedParamId(id: string): boolean;
30
+ /**
31
+ * Guard for the write path: only canonical ids may be written.
32
+ * @param id - parameter id a caller intends to write.
33
+ * @returns the canonical id.
34
+ * @throws {Error} when the id is a deprecated alias; the message names both ids.
35
+ */
36
+ export declare function canonicalWriteId(id: string): string;
37
+ /**
38
+ * Read a parameter from a carrier that may still spell the legacy name.
39
+ * @param carrier - config/snapshot object to read from, or undefined.
40
+ * @param id - canonical id (a deprecated alias is accepted and resolved first).
41
+ * @returns the canonical value when present, else the alias value, else undefined.
42
+ */
43
+ export declare function readParam(carrier: object | undefined, id: string): unknown;
44
+ /** Exposure group (design §7.2): the unit a developer changes together. */
45
+ export type ParamGroup = 'library' | 'write-caps' | 'review' | 'memory' | 'curator' | 'deployment' | 'internal';
46
+ /** Exposure tier (design §7.1): the interface combination a parameter gets. */
47
+ export type ParamTier = 'E0' | 'E1' | 'E2' | 'E3' | 'E4';
48
+ /** Where the authoritative value lives. */
49
+ export type ParamAuthority = 'code' | 'cordis' | 'install';
50
+ /**
51
+ * One parameter's exposure contract (design §8.1).
52
+ *
53
+ * MACHINE-READ CONTRACT: the entries below are written ONE PER LINE with the
54
+ * key order id, group, tier, authority, owner, applies, docAnchor, summary so
55
+ * the .mjs generators (`gen-param-docs.mjs`, `verify-param-registry.mjs`) can
56
+ * parse this text without importing TypeScript. `param-registry.spec.ts`
57
+ * asserts that the parsed text and this runtime array agree, so the two sides
58
+ * cannot drift.
59
+ *
60
+ * `applies` is the SETTINGS-side timing: 'none' means the parameter is not
61
+ * writable through the user layer (a cordis.yml change still follows the
62
+ * deployment's patch-reload policy).
63
+ */
64
+ export interface ParamExposure {
65
+ id: string;
66
+ group: ParamGroup;
67
+ tier: ParamTier;
68
+ authority: ParamAuthority;
69
+ owner: string;
70
+ applies: 'live' | 'restart' | 'none';
71
+ docAnchor: string;
72
+ summary: string;
73
+ }
74
+ /**
75
+ * The parameter registry. G1/S1.1 seeds it with the review group (G-C); the
76
+ * remaining groups land in S2.1. E3 = behaviour preference the user may change
77
+ * (live); E2 = resource or identity knob that stays with the deployment.
78
+ */
79
+ export declare const PARAM_EXPOSURE: readonly ParamExposure[];
80
+ /** The settings namespace each OWNER PACKAGE registers (design §7.2).
81
+ *
82
+ * Keyed by owner rather than by group because a namespace has exactly one
83
+ * registrant (the platform refuses a second registration of the same name),
84
+ * while a group may span packages — group 'memory' is served by memory-files and
85
+ * tool-memory, each owning its own section. A package without an entry has no
86
+ * user layer (its knobs stay deployment-only). */
87
+ export declare const PARAM_NAMESPACES: Readonly<Record<string, string>>;
88
+ /** Structural view of one registered settings scope (platform Service Definition).
89
+ * Declared locally so this module keeps its zero-import, zero-dependency shape. */
90
+ interface SettingsScopeLike {
91
+ get(): unknown;
92
+ watch(callback: (next: unknown, prev: unknown) => void): () => void;
93
+ }
94
+ /** Structural view of the platform settings provider; only the members the
95
+ * family uses are named. A missing `describe` disables the user layer loudly
96
+ * (see {@link paramSectionOverrides}) instead of reading as 'no overrides'. */
97
+ export interface SettingsProviderLike {
98
+ register(namespace: string, schema: unknown, options: {
99
+ base: unknown;
100
+ applies?: 'live' | 'restart';
101
+ /** Owner-side refusal of a resolved section (cross-field rules the schema
102
+ * cannot express); throwing refuses the WRITE that produced the value. */
103
+ validate?: (value: unknown) => void;
104
+ }): SettingsScopeLike;
105
+ /** Merge a patch into one namespace's user layer. A stale `expectedRevision`
106
+ * rejects with the platform's SETTINGS_CONFLICT error. */
107
+ update?(namespace: string, patch: object, expectedRevision?: number): Promise<void>;
108
+ describe?(options?: {
109
+ redactSecrets?: boolean;
110
+ }): {
111
+ ns: string;
112
+ /** Current resolved section (schema defaults < base < user). */
113
+ value?: unknown;
114
+ /** Raw user section: a key's PRESENCE marks a user override. */
115
+ user?: Record<string, unknown>;
116
+ /** Monotonic revision of that raw section; a write sends it back. */
117
+ revision?: number;
118
+ /** Owner's declared effect timing. */
119
+ applies?: 'live' | 'restart';
120
+ }[];
121
+ }
122
+ /** Hooks a caller may supply when a section attaches to the settings service.
123
+ * @typeParam T - the section's value type (what `validate` inspects). */
124
+ export interface ParamSectionOptions<T extends object = object> {
125
+ /** Called once when the user layer turns unreadable (a warning, never silent). */
126
+ warn?: (message: string) => void;
127
+ /** Called after every committed change that the reader can observe — the place
128
+ * to REBUILD registration-level facts (the platform's own `installSection`
129
+ * documents the same hook shape). Consumers that read at use time pass nothing. */
130
+ onChange?: () => void;
131
+ /** Refuse a resolved section the owner could not act on: a cross-field rule the
132
+ * schema cannot express (the platform applies this hook to the RESOLVED section,
133
+ * so a user value is judged together with the deployment layer beneath it).
134
+ * Throwing refuses the write that produced the value. */
135
+ validate?: (value: T) => void;
136
+ }
137
+ /** Reader for one parameter section: presence-aware user overrides. */
138
+ export interface ParamOverrides<T extends object> {
139
+ /** The namespace this reader is bound to. */
140
+ readonly namespace: string;
141
+ /** The user-set value for one key, or undefined when the user never set it. */
142
+ get<K extends keyof T & string>(key: K): T[K] | undefined;
143
+ /** The resolved section (defaults < base < user) — display and tests. */
144
+ resolved(): T;
145
+ }
146
+ /**
147
+ * G3: expose one parameter section to the user layer.
148
+ *
149
+ * Precedence stays 'user > deployment > default': the caller keeps reading its
150
+ * deployment carriers (policy snapshot, then the plugin row) and consults
151
+ * {@link ParamOverrides.get} FIRST — an unset key returns undefined, so the
152
+ * deployment value keeps winning and the family's shadowing rules survive.
153
+ *
154
+ * Failure posture: a provider without `describe` (or one whose describe throws)
155
+ * leaves the user layer UNAVAILABLE and warns once — deployment values then
156
+ * apply. Treating an unreadable user layer as 'no overrides' would silently
157
+ * ignore a setting the user did write, so the warning names it.
158
+ * @typeParam T - the section's value type.
159
+ * @param provider - the platform settings provider, or undefined when absent.
160
+ * @param namespace - namespace to register.
161
+ * @param schema - schemastery schema the platform validates against.
162
+ * @param base - composition base layer (the plugin row's values).
163
+ * @param options - warning sink and the change hook.
164
+ * @returns a reader bound to the namespace.
165
+ */
166
+ export declare function paramSectionOverrides<T extends object>(provider: SettingsProviderLike | undefined, namespace: string, schema: unknown, base: T, options?: ParamSectionOptions<T>): ParamOverrides<T>;
167
+ /** Minimal structural view of the cordis context used to attach a section.
168
+ * The callback takes `unknown` on purpose: cordis's own `inject` declares a
169
+ * `Context` parameter, and a callback accepting `unknown` is assignable to it
170
+ * (parameter contravariance) while a narrower shape is not. */
171
+ export interface SettingsHostLike {
172
+ inject(names: string[], callback: (ctx: unknown) => void): unknown;
173
+ }
174
+ /**
175
+ * Attach a parameter section through the optional settings service.
176
+ * @typeParam T - the section's value type.
177
+ * @param host - the plugin context (structurally typed).
178
+ * @param namespace - namespace to register.
179
+ * @param schema - schemastery schema the platform validates against.
180
+ * @param base - composition base layer (the plugin row's values).
181
+ * @param options - warning sink and the change hook.
182
+ * @returns a reader that follows the provider when it appears.
183
+ */
184
+ export declare function installParamSection<T extends object>(host: SettingsHostLike, namespace: string, schema: unknown, base: T, options?: ParamSectionOptions<T>): ParamOverrides<T>;
185
+ /**
186
+ * Number-typed read over {@link readParam}: the family's tunables are numbers,
187
+ * and a value of another type reads as absent so the caller's default applies
188
+ * (the same outcome the numeric clamps produce for a malformed value).
189
+ * @param carrier - config/snapshot object to read from, or undefined.
190
+ * @param id - canonical id (a deprecated alias is accepted and resolved first).
191
+ * @returns the resolved number, or undefined when absent or not a number.
192
+ */
193
+ export declare function readNumberParam(carrier: object | undefined, id: string): number | undefined;
194
+ export {};
195
+ //# sourceMappingURL=params.d.ts.map