@lmzhen/dsh-evolution-core 0.23.0 → 0.25.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.
@@ -95,6 +95,13 @@ export declare const DEFAULT_CURATOR_INTERVAL_HOURS = 168;
95
95
  export declare const DEFAULT_MIN_IDLE_HOURS = 2;
96
96
  export declare const DEFAULT_STALE_AFTER_DAYS = 30;
97
97
  export declare const DEFAULT_ARCHIVE_AFTER_DAYS = 90;
98
+ /**
99
+ * Support-file count above which `references-sprawl` warns. **NOT CALIBRATED YET** — 60 is a
100
+ * placeholder: the platform corpus is empty (no platform skills available here), so the admission
101
+ * band cannot be checked. Until `gen-health-baseline` reports both corpora, the rule is
102
+ * REPORT-ONLY: its count must not be shown as a number on any surface (plan G4 ③).
103
+ */
104
+ export declare const SUPPORT_SPRAWL_FILES = 60;
98
105
  export declare const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
99
106
  export declare const DEFAULT_USER_CHAR_LIMIT = 1375;
100
107
  /** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
@@ -146,7 +153,7 @@ export declare const PLATFORM_CATALOG_DESCRIPTION_DEFAULT = 500;
146
153
  * be copied into validateFrontmatter AND the patch path, and neither copy named
147
154
  * where the content should go — the upstream cap message names the destination
148
155
  * directories, and that is the part a model actually acts on. */
149
- export declare const CONTENT_SPLIT_HINT = "Consider splitting into a smaller SKILL.md with supporting files in references/ or templates.";
156
+ export declare const CONTENT_SPLIT_HINT = "\u5EFA\u8BAE\u62C6\u6210\u66F4\u5C0F\u7684 SKILL.md\uFF0C\u628A\u652F\u6491\u6587\u4EF6\u653E\u8FDB references/ \u6216 templates/\u3002";
150
157
  /** V27 G2.4: the largest millisecond delay a timer accepts. `AbortSignal.timeout`
151
158
  * (and `setTimeout`) coerce anything larger to 1ms after a Node warning, so a
152
159
  * timeout configured above this ceiling silently collapses to "immediately
@@ -20,4 +20,37 @@ export interface ReadingStats {
20
20
  * @returns the counts, so the panel can say what it is not showing.
21
21
  */
22
22
  export declare function readingStats(full: string, shown: string): ReadingStats;
23
+ /**
24
+ * One H2 section of a body: its title, its line span and how many times that title occurs.
25
+ * @property count - occurrences of `title` in the SAME body; a candidate list may only offer 1.
26
+ */
27
+ export interface H2Section {
28
+ title: string;
29
+ start: number;
30
+ end: number;
31
+ count: number;
32
+ }
33
+ /**
34
+ * The H2 sections of a body, in document order — the ONE scan (G3).
35
+ *
36
+ * Both the restructure candidate list and the store anchor validation call this function, so a
37
+ * candidate can only name a heading that exists EXACTLY ONCE and has content: the two rejections a
38
+ * hand-written anchor used to hit ("no such heading", "ambiguous anchor") stop being reachable.
39
+ * H2 is the section boundary (a section spans to the next H2 or EOF), matching the store.
40
+ * @param body - the markdown after the frontmatter block.
41
+ * @returns one row per H2 heading, with `count` filled in a second pass.
42
+ */
43
+ export declare function h2Sections(body: string): H2Section[];
44
+ /** One move the panel can hand to `restructure` without the store refusing it. */
45
+ export interface RestructureCandidate {
46
+ heading: string;
47
+ toFile: string;
48
+ }
49
+ /**
50
+ * The sections a restructure proposal may name: heading unique, section non-empty. Derived from
51
+ * `h2Sections`, i.e. from the SAME scan the store validates against.
52
+ * @param body - the markdown after the frontmatter block.
53
+ * @returns move-ready pairs in document order; empty when nothing qualifies.
54
+ */
55
+ export declare function restructureCandidates(body: string): RestructureCandidate[];
23
56
  //# sourceMappingURL=document-body.d.ts.map
@@ -12,33 +12,33 @@
12
12
  * @module @lmzhen/dsh-evolution-core
13
13
  */
14
14
  export declare const EVOLUTION_ERRORS: Readonly<{
15
- readonly 'e-305-this-invocation-carries-no': "E-305: this invocation carries no agent — `%a1%` needs a session-backed call (run it from a session in the GUI or the CLI).";
16
- readonly 'e-306-this-deployment-stages-foreground': "E-306: this deployment stages foreground writes, but `/evolution %a1%` is not replayable through the skill runner — there is nothing to stage. Run it from a session whose approval policy is 'never', or set `stageForeground: false` on the evolution-approval row, then repeat the command.";
17
- readonly 'e-301-approval-service-not-mounted': "E-301: approval service not mounted — pending writes cannot be listed or replayed. Next: the evolution-approval row ships with evolution-host/evolution-all — run /evolution doctor to see which services are mounted.";
18
- readonly 'e-301-approval-service-not-mounted-2': "E-301: approval service not mounted. Next: the evolution-approval row ships with evolution-host/evolution-all — run /evolution doctor to see which services are mounted.";
19
- readonly 'e-304-the-mounted-approval-service': "E-304: the mounted approval service predates the release capability — reject the record instead.";
20
- readonly 'e-302-curator-service-not-mounted': "E-302: curator service not mounted. Next: mount the evolution-curator row (evolution-host/evolution-all) and run /evolution doctor.";
21
- readonly 'e-305-the-invocation-agent-exposes': "E-305: the invocation agent exposes neither `followup` nor `inject` — %a1% has no delivery channel.";
22
- readonly 'e-306-this-deployment-stages-foreground-2': "E-306: this deployment stages foreground writes, but this invocation carries no agent session — `/evolution restructure` would stage a record with no session attribution. Run it from a session in the GUI or the CLI, or set `stageForeground: false` on the evolution-approval row, then repeat the command.";
23
- readonly 'e-303-replay-service-not-mounted': "E-303: replay service not mounted. Next: mount the evolution-replay row (evolution-host/evolution-all) and run /evolution doctor.";
24
- readonly 'e-307-the-settings-service-could': "E-307: the settings service could not report its sections (%a1%). /evolution params needs the user layer to tell an override from a deployment value; run /evolution doctor to see which services are mounted.";
25
- readonly 'e-308-unknown-parameter-group-group': "E-308: unknown parameter group \"%a1%\" — known groups: %a2%.";
26
- readonly 'e-311-no-settings-service-is': "E-311: no settings service is mounted, so there is no user layer to write. Mount the settings row (packages/settings/settings-file) and retry; this command never edits cordis.yml — a deployment value belongs to the deployment. /evolution doctor lists the mounted services.";
27
- readonly 'e-312-error-instanceof-Error-error': "E-312: %a1% — /evolution params lists the canonical ids.";
28
- readonly 'e-313-unknown-parameter-id-evolution': "E-313: unknown parameter \"%a1%\" — /evolution params lists every registered id with its tier and user layer.";
29
- readonly 'e-314-id-is-a-deployment': "E-314: \"%a1%\" is a deployment parameter (tier %a2%, owner %a3%) — it is written in cordis.yml on its plugin or policy row, not from a session. /evolution params shows who may write each row.";
30
- readonly 'e-315-id-has-no-user': "E-315: \"%a1%\" has no user layer — owner %a2% publishes no settings namespace, so the value stays with the deployment.";
31
- readonly 'e-307-the-settings-service-could-2': "E-307: the settings service could not report its sections (%a1%). /evolution policy set needs the current revision to write safely; run /evolution doctor to see which services are mounted.";
32
- readonly 'e-316-namespace-namespace-is-not': "E-316: namespace \"%a1%\" is not registered — owner %a2% is not mounted in this composition, so its parameters cannot be written from here. /evolution params marks those rows 'unregistered'.";
33
- readonly 'e-309-revision-conflict-you-sent': "E-309: revision conflict — you sent %a1%, the document stands at %a2%. Re-read with /evolution params --json and retry.";
34
- readonly 'e-309-revision-conflict-message-Re': "E-309: revision conflict — %a1%. Re-read with /evolution params --json and retry.";
35
- readonly 'e-310-the-settings-service-refused': "E-310: the settings service refused the write: %a1% — the owning plugin's rule stands (a cross-field pair, or a cap that may only be tightened). /evolution params shows the current value.";
36
- readonly 'e-317-skill-write-not-confirmed': "E-317: skill \"%a1%\" was not %a2% — the confirmation prompt was declined or dismissed, so nothing was written. The prompt appears on every create and bare delete; repeat the call only if the operator asks for it.";
37
- readonly 'e-318-skill-write-without-a-read': "E-318: skill \"%a1%\" was not read in this session, so this write is refused. Read it with the `skill` tool first (a read that failed does not count), then repeat the write.";
38
- readonly 'e-320-the-profile-home-is-not': "E-320: the profile home is not reported by this host, so the legacy settings document cannot be located. /evolution migrate reads `<profile home>/settings.yaml.imported`; run it from a profile-launched session.";
39
- readonly 'e-322-a-parameter-would-break-a': "E-322: %a1% must be at least %a2%, but this write sets it to %a3%. Nothing was written.";
40
- readonly 'e-321-no-io-provider-is': "E-321: no evolution-io provider is mounted, so the legacy settings document cannot be read. Mount the evolution-io row (evolution-host/evolution-all) and retry.";
41
- readonly 'e-319-skill-write-confirm-timed-out': "E-319: skill \"%a1%\" was not %a2% — the confirmation prompt went unanswered for %a3% second(s), so the write was cancelled. Nobody was there to decide: set `skillWriteConfirm` to `auto` to write without a prompt, keep `ask` to wait until someone answers, or raise `skillWriteConfirmTimeoutSeconds`.";
15
+ readonly 'e-305-this-invocation-carries-no': "E-305: 本次调用没有携带 agent —— `%a1%` 需要由会话支撑的调用(请在 GUI 或 CLI 的会话里运行)。";
16
+ readonly 'e-306-this-deployment-stages-foreground': "E-306: 本部署会暂存前台写入,但 `/evolution %a1%` 无法通过技能运行器回放 —— 没有可暂存的内容。请在批准策略为 'never' 的会话里运行,或在 evolution-approval 行上设置 `stageForeground: false`,然后重试该命令。";
17
+ readonly 'e-301-approval-service-not-mounted': "E-301: 批准服务未挂载:待批准写入无法列出或回放。挂载 evolution-approval 行(随 evolution-host/evolution-all 提供)后,运行 /evolution doctor 可查看已挂载的服务。";
18
+ readonly 'e-301-approval-service-not-mounted-2': "E-301: 批准服务未挂载。下一步:evolution-approval 行随 evolution-host/evolution-all 一起提供 —— 运行 /evolution doctor 查看已挂载的服务。";
19
+ readonly 'e-304-the-mounted-approval-service': "E-304: 已挂载的批准服务早于本能力发布 —— 请改为驳回该记录。";
20
+ readonly 'e-302-curator-service-not-mounted': "E-302: 策展服务未挂载。下一步:挂载 evolution-curator 行(evolution-host/evolution-all),并运行 /evolution doctor。";
21
+ readonly 'e-305-the-invocation-agent-exposes': "E-305: 调用 agent 既没有暴露 `followup` 也没有 `inject` —— %a1% 没有投递通道。";
22
+ readonly 'e-306-this-deployment-stages-foreground-2': "E-306: 本部署会暂存前台写入,但本次调用没有 agent 会话 —— `/evolution restructure` 会暂存一条没有会话归属的记录。请在 GUI 或 CLI 的会话里运行,或在 `stageForeground: false` 行上设置,然后重试该命令。";
23
+ readonly 'e-303-replay-service-not-mounted': "E-303: 回放服务未挂载。下一步:挂载 evolution-replay 行(evolution-host/evolution-all),并运行 /evolution doctor。";
24
+ readonly 'e-307-the-settings-service-could': "E-307: 设置服务无法报告它的分区(%a1%)。/evolution params 需要用户层来区分覆盖值与部署值;运行 /evolution doctor 查看已挂载的服务。";
25
+ readonly 'e-308-unknown-parameter-group-group': "E-308: 未知的参数分组「%a1%」—— 已知分组:%a2%。";
26
+ readonly 'e-311-no-settings-service-is': "E-311: 没有挂载设置服务,因此没有可写入的用户层。请挂载 settings 行(packages/settings/settings-file)后重试;本命令从不修改 cordis.yml —— 部署值属于部署。/evolution doctor 会列出已挂载的服务。";
27
+ readonly 'e-312-error-instanceof-Error-error': "E-312: %a1% —— /evolution params 会列出规范 id。";
28
+ readonly 'e-313-unknown-parameter-id-evolution': "E-313: 未知参数「%a1%」—— /evolution params 会列出每个已注册 id 及其层级与用户层。";
29
+ readonly 'e-314-id-is-a-deployment': "E-314: 「%a1%」是部署参数(层级 %a2%,归属 %a3%)—— 它写在 cordis.yml 的插件行或策略行上,而不是从会话写入。/evolution params 会显示每一行由谁可写。";
30
+ readonly 'e-315-id-has-no-user': "E-315: 「%a1%」没有用户层 —— 归属 %a2% 没有发布设置命名空间,因此该值留在部署层。";
31
+ readonly 'e-307-the-settings-service-could-2': "E-307: 设置服务无法报告它的分区(%a1%)。/evolution policy set 需要当前修订号才能安全写入;运行 /evolution doctor 查看已挂载的服务。";
32
+ readonly 'e-316-namespace-namespace-is-not': "E-316: namespace \"%a1%\" 未注册 —— 归属 %a2% 在本组合中未挂载,因此无法从这里写入它的参数。 /evolution params 会把那些行标为 'unregistered'.";
33
+ readonly 'e-309-revision-conflict-you-sent': "E-309: 修订冲突 —— 你提交的是 %a1%,文档当前为 %a2%。请用 /evolution params --json 重新读取后重试。";
34
+ readonly 'e-309-revision-conflict-message-Re': "E-309: 修订冲突 —— %a1%。请用 /evolution params --json 重新读取后重试。";
35
+ readonly 'e-310-the-settings-service-refused': "E-310: 设置服务拒绝了这次写入:%a1% —— 归属插件的规则优先(跨字段成对约束,或只能收紧的上限)。/evolution params 会显示当前值。";
36
+ readonly 'e-317-skill-write-not-confirmed': "E-317: 技能 \"%a1%\" 未被%a2% —— 确认提示被拒绝或关闭,因此没有写入任何内容。每次创建与裸删除都会出现该提示;只有操作者要求时才重复调用。";
37
+ readonly 'e-318-skill-write-without-a-read': "E-318: 本会话没有读取技能 \"%a1%\",因此拒绝这次写入。请先用 `skill` 工具读取它(失败的读取不算),然后重复该写入。";
38
+ readonly 'e-320-the-profile-home-is-not': "E-320: 本宿主没有报告 profile home,因此无法定位旧设置文档。/evolution migrate 读取 `<profile home>/settings.yaml.imported`;请在由 profile 启动的会话里运行。";
39
+ readonly 'e-322-a-parameter-would-break-a': "E-322: %a1% 至少应为 %a2%,但这次写入把它设成 %a3%。未写入任何内容。";
40
+ readonly 'e-321-no-io-provider-is': "E-321: 没有挂载 evolution-io 提供方,因此无法读取旧设置文档。请挂载 evolution-io 行(evolution-host/evolution-all)后重试。";
41
+ readonly 'e-319-skill-write-confirm-timed-out': "E-319: 技能 \"%a1%\" 未被%a2% —— 确认提示在 %a3% 秒内无人应答,因此该写入被取消。没有人在场决定:把 `skillWriteConfirm` 设为 `auto` 可免提示直接写入,保留 `ask` 则一直等到有人应答,或提高 `skillWriteConfirmTimeoutSeconds`。";
42
42
  }>;
43
43
  /** One table key. */
44
44
  export type EvolutionErrorId = keyof typeof EVOLUTION_ERRORS;
@@ -0,0 +1,37 @@
1
+ import { type LintFacts } from './skill-lint.ts';
2
+ export type Corpus = 'user' | 'platform';
3
+ export interface CorpusSkill {
4
+ name: string;
5
+ corpus: Corpus;
6
+ facts: LintFacts;
7
+ }
8
+ export interface RuleAdmission {
9
+ platformHits: number;
10
+ userHits: number;
11
+ userHitRate: number;
12
+ }
13
+ export interface HealthBaseline {
14
+ /** Frozen unit and scope: a rate is only comparable when both are named (contract §4). */
15
+ unit: 'chars';
16
+ scope: 'references markdown file count';
17
+ corpora: {
18
+ user: number;
19
+ platform: number;
20
+ };
21
+ values: Record<string, RuleAdmission>;
22
+ }
23
+ export interface AdmissionThresholds {
24
+ /** A user-library hit rate below this is too rare to be worth a rule. */
25
+ minUserHitRate: number;
26
+ /** …and above this it is furniture (every skill trips it, so it carries no signal). */
27
+ maxUserHitRate: number;
28
+ }
29
+ export declare const DEFAULT_ADMISSION: AdmissionThresholds;
30
+ export declare function buildHealthBaseline(skills: readonly CorpusSkill[]): HealthBaseline;
31
+ /** The admission verdict for ONE rule id (contract §4): platform corpus must not trip it, and the
32
+ * user-library hit rate must land inside the band. Every failure names the number it failed on. */
33
+ export declare function admissionVerdict(baseline: HealthBaseline, ruleId: string, thresholds?: AdmissionThresholds): {
34
+ admitted: boolean;
35
+ failures: string[];
36
+ };
37
+ //# sourceMappingURL=health-baseline.d.ts.map
@@ -34,6 +34,8 @@
34
34
  export * from './citations.ts';
35
35
  export * from './reference-rewrite.ts';
36
36
  export * from './cost.ts';
37
+ export * from './skill-lint.ts';
38
+ export * from './health-baseline.ts';
37
39
  export * from './curator.ts';
38
40
  export * from './evolution-events.ts';
39
41
  export * from './fuzzy-match.ts';
@@ -17,6 +17,9 @@ export interface MutationRecord {
17
17
  afterVersion?: number;
18
18
  summary: string;
19
19
  at: string;
20
+ /** G5: who caused this mutation — a write face ('foreground', 'background_review', …) or a
21
+ * migration step. Optional: records written before the ledger carried it stay readable. */
22
+ origin?: string;
20
23
  }
21
24
  export declare const DEFAULT_MUTATION_CAP = 500;
22
25
  /** Version of the `.mutations.json` file shape; writers always emit the current one. */
@@ -9,6 +9,7 @@
9
9
  * stay in the review/curator judgment layer. Never become a 7th factor of
10
10
  * `computeQualityScores` (different dimension, different consumers).
11
11
  */
12
+ import { type LintFinding } from './skill-lint.ts';
12
13
  export interface SkillHealthThresholds {
13
14
  /** Soft body limit: a body of `softBodyChars` or MORE -> 'warn'; >= 2x ->
14
15
  * 'needs-restructure'. V6-34 (0.3.37): the doc comment used to claim
@@ -60,6 +61,8 @@ interface SkillHealthSnapshot {
60
61
  bodyText?: string | undefined;
61
62
  /** Number of non-empty support groups (references/ templates/ scripts/). */
62
63
  supportGroups: number;
64
+ /** Support FILES under references/ (contract §4), when the caller counted them; null = not measured. */
65
+ supportFiles?: number | undefined;
63
66
  /** Usage-side patch count, when the caller has it (A2 churn dimension). */
64
67
  patchCount?: number | undefined;
65
68
  /** Usage-side view count, when the caller has it (A2 churn dimension). */
@@ -70,7 +73,11 @@ interface SkillHealthDim {
70
73
  /** The same body on the token scale (limit basis) — the judged quantity. */
71
74
  bodyTokens: number;
72
75
  stampDensityPerKb: number | null;
76
+ /** Raw stamp hits — the number the stamp rule reports as `count`. */
77
+ stampHits: number | null;
73
78
  supportGroups: number;
79
+ /** Support FILES under references/; null = the caller could not count them (never 0-as-unknown). */
80
+ supportFiles: number | null;
74
81
  /** Usage churn facts; null when the caller supplied no counts. */
75
82
  churnPatches: number | null;
76
83
  churnReads: number | null;
@@ -78,8 +85,23 @@ interface SkillHealthDim {
78
85
  export interface SkillHealthAssessment {
79
86
  verdict: SkillHealthVerdict;
80
87
  dims: SkillHealthDim;
81
- reasons: string[];
88
+ /** The same judgement as `reasons`, in diffable form: (rule id, severity, count). G1. */
89
+ findings: LintFinding[];
82
90
  }
91
+ /**
92
+ * The findings for ONE body, computed by the engine that owns the judgement (G4).
93
+ *
94
+ * Deliberately a thin call into `assessStructureHealth`: a later consumer (the write receipt) needs
95
+ * findings for a body it is about to store, and re-deriving the facts anywhere else would be a
96
+ * SECOND judgement — the token-scale line, the /1024 stamp density and the soft/2 support threshold
97
+ * all live in one place and must stay there.
98
+ * @param skillName - the skill the body belongs to (used in messages).
99
+ * @param content - the WHOLE SKILL.md text, frontmatter included (the engine's own convention).
100
+ * @param supportGroups - non-empty support dirs; pass the real count, never a guess.
101
+ * @param thresholds - the configured thresholds; defaults to the shipped ones.
102
+ * @returns the findings, in rule order.
103
+ */
104
+ export declare function lintBody(skillName: string, content: string, supportGroups: number, thresholds?: SkillHealthThresholds): LintFinding[];
83
105
  export declare function assessStructureHealth(snapshot: SkillHealthSnapshot, thresholds?: SkillHealthThresholds): SkillHealthAssessment;
84
106
  export {};
85
107
  //# sourceMappingURL=skill-health.d.ts.map
@@ -0,0 +1,59 @@
1
+ export type LintSeverity = 'warn' | 'error';
2
+ export interface LintFinding {
3
+ severity: LintSeverity;
4
+ /** Stable rule id; findings are diffed by (rule, count), never by message text. */
5
+ rule: string;
6
+ message: string;
7
+ /** How many times the rule bit — a name-only comparison hid one site becoming nine. */
8
+ count: number;
9
+ }
10
+ /** Size facts the deciding engine already measured. `null` means NOT MEASURED, never fine: a rule
11
+ * that needs a missing fact stays silent rather than inventing a pass or a failure. */
12
+ export interface SkillDims {
13
+ bodyChars: number | null;
14
+ bodyTokens: number | null;
15
+ stampDensityPerKb: number | null;
16
+ /** Support GROUPS (non-empty references/templates/scripts/assets dirs) — the old dimension. */
17
+ supportGroups: number;
18
+ /** Support FILES under references/ (contract §4), or null when the caller cannot count them. */
19
+ supportFiles: number | null;
20
+ /** Raw stamp hits behind `stampDensityPerKb` — the COUNT this rule reports (never a fixed 1). */
21
+ stampHits: number | null;
22
+ churnPatches: number | null;
23
+ churnReads: number | null;
24
+ }
25
+ /** The judgement itself, computed by `skill-health.ts` on its own scales; this module never
26
+ * recomputes it from raw numbers (that is exactly how a second set of thresholds appears). */
27
+ export interface LintFacts {
28
+ skillName: string;
29
+ dims: SkillDims;
30
+ softLineChars: number;
31
+ overSoftLine: boolean;
32
+ overTwiceSoftLine: boolean;
33
+ stampLimitPerKb: number;
34
+ /** The engine's own churn threshold — the rule must use the SAME number, not a literal. */
35
+ churnMinPatches: number;
36
+ /** True when the caller could read the body at all. */
37
+ readable: boolean;
38
+ }
39
+ interface LintRule {
40
+ readonly id: string;
41
+ readonly severity: LintSeverity;
42
+ run(facts: LintFacts): LintFinding | null;
43
+ }
44
+ /** One rule yields AT MOST one finding (contract §3). `body-oversize-2x` is the only rule whose
45
+ * severity can escalate a verdict to needs-restructure. */
46
+ export declare const RULES: readonly LintRule[];
47
+ export declare function lintSkill(facts: LintFacts): LintFinding[];
48
+ /** What a write introduced, what got worse, what got fixed. A rule whose count grew (one site to
49
+ * nine) counts as worsened — comparing by rule name alone reported nothing. */
50
+ export declare function diffFindings(before: readonly LintFinding[], after: readonly LintFinding[]): {
51
+ introduced: LintFinding[];
52
+ worsened: LintFinding[];
53
+ resolved: string[];
54
+ };
55
+ /** Support files = markdown files under references/ (contract §4: unit frozen, underscore-prefixed
56
+ * excluded). Paths are supplied by the caller — this module never touches the filesystem. */
57
+ export declare function countReferenceFiles(paths: readonly string[]): number;
58
+ export {};
59
+ //# sourceMappingURL=skill-lint.d.ts.map
@@ -385,6 +385,12 @@ export declare class SkillLibrary {
385
385
  /** Whether the skill carries the pinned marker (the marker is the factual source; usage.pinned mirrors it). */
386
386
  isPinned(rawName: string): Promise<boolean>;
387
387
  /** Count non-empty support subdirectories (richness input for quality scoring). */
388
+ /** G4 ⑤-d: the dangling targets THIS write introduced, as a receipt note (empty when none).
389
+ *
390
+ * Uses the SAME resolver as the drift signal, so a receipt and a drift row can never disagree;
391
+ * an absent or unknown listing reports nothing rather than inventing a dead link.
392
+ */
393
+ private danglingNoteFor;
388
394
  countSupportDirs(rawName: string): Promise<number>;
389
395
  /**
390
396
  * Support-file paths (`references/x.md`) under SUPPORT_DIRS for one skill,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.23.0",
4
+ "version": "0.25.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },