@lmzhen/dsh-evolution-core 0.3.13 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -2,6 +2,7 @@ import { basename, dirname, join } from "node:path";
2
2
  import { cp, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { homedir } from "node:os";
5
+ import { load } from "js-yaml";
5
6
  //#region lib/types/io.js
6
7
  /**
7
8
  * Structural IO seam for the evolution plugin family.
@@ -2116,6 +2117,38 @@ async function recordMutation(root, io, record, cap = 500) {
2116
2117
  });
2117
2118
  }
2118
2119
  //#endregion
2120
+ //#region lib/types/preset-composition.js
2121
+ /**
2122
+ * Build the user-root Evolution preset composition from the RUNTIME platform's
2123
+ * `standard` preset rows plus the evolution delta rows (P1-1 follow-up,
2124
+ * 0.3.15): the agent-preset registry mounts ONE composition file verbatim, so
2125
+ * a delta-only `agent.cordis.yml` would produce an agent carrying only the
2126
+ * delta rows.
2127
+ *
2128
+ * Same contract as `install-layered.mjs` `generateAgentPreset` (the source
2129
+ * install path) — installer.spec pins byte parity between the two.
2130
+ *
2131
+ * Row ids are read from `- id:` lines; an id present in both fragments would
2132
+ * mount twice and could shadow the platform row, so it fails loud.
2133
+ * @param standardComposition - the runtime `standard` preset composition.
2134
+ * @param deltaComposition - the evolution delta fragment.
2135
+ * @returns the composed preset composition (standard rows first, then delta).
2136
+ */
2137
+ function composePresetComposition(standardComposition, deltaComposition) {
2138
+ const standardIds = compositionRowIds(standardComposition);
2139
+ const collisions = [...compositionRowIds(deltaComposition)].filter((id) => standardIds.has(id)).sort();
2140
+ if (collisions.length > 0) throw new Error(`evolution preset composition: delta rows collide with runtime standard rows: ${collisions.join(", ")}`);
2141
+ return `${standardComposition.replace(/\s+$/, "")}\n\n${deltaComposition.trim()}\n`;
2142
+ }
2143
+ function compositionRowIds(composition) {
2144
+ const ids = /* @__PURE__ */ new Set();
2145
+ for (const line of composition.split("\n")) {
2146
+ const match = /^- id:\s*(\S+)/.exec(line);
2147
+ if (match) ids.add(match[1] ?? "");
2148
+ }
2149
+ return ids;
2150
+ }
2151
+ //#endregion
2119
2152
  //#region lib/types/quality.js
2120
2153
  /**
2121
2154
  * Quality scoring and near-duplicate detection for the curated skill library.
@@ -2501,15 +2534,42 @@ function markerEntryName(marker) {
2501
2534
  function markerPath(dir, marker) {
2502
2535
  return join(dir, markerEntryName(marker));
2503
2536
  }
2504
- function parseFrontmatter(content) {
2537
+ /**
2538
+ * Shared frontmatter block detection (P3-3 single owner): opening line `---`
2539
+ * and closing line exactly `---` (both trimmed). Used by `parseFrontmatter`,
2540
+ * `frontmatterYamlUnsafeValues` and `normalizeFrontmatter` so the three can
2541
+ * never disagree about where the block ends (the loose `indexOf('\n---')`
2542
+ * form matched `\n----` and was replaced by this strict line rule).
2543
+ */
2544
+ function frontmatterBlock(content) {
2505
2545
  if (!content.trimStart().startsWith("---")) return null;
2506
- const end = content.indexOf("\n---", 3);
2546
+ const nl = content.includes("\r\n") ? "\r\n" : "\n";
2547
+ const lines = content.split(nl);
2548
+ if ((lines[0] ?? "").trim() !== "---") return null;
2549
+ let end = -1;
2550
+ for (let i = 1; i < lines.length; i++) {
2551
+ const line = lines[i];
2552
+ if (line === void 0) continue;
2553
+ if (line.trim() === "---") {
2554
+ end = i;
2555
+ break;
2556
+ }
2557
+ }
2507
2558
  if (end < 0) return null;
2508
- const block = content.slice(3, end);
2509
- const body = content.slice(end + 4).trim();
2559
+ return {
2560
+ block: lines.slice(1, end).join(nl),
2561
+ lines,
2562
+ end,
2563
+ nl
2564
+ };
2565
+ }
2566
+ function parseFrontmatter(content) {
2567
+ const found = frontmatterBlock(content);
2568
+ if (!found) return null;
2569
+ const body = found.lines.slice(found.end + 1).join(found.nl).trim();
2510
2570
  if (!body) return null;
2511
2571
  const frontmatter = {};
2512
- for (const line of block.split("\n")) {
2572
+ for (const line of found.block.split(found.nl)) {
2513
2573
  const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
2514
2574
  if (match) {
2515
2575
  const [, key, value] = match;
@@ -2528,7 +2588,10 @@ function parseFrontmatter(content) {
2528
2588
  * silently split family-visibility from platform-visibility (0.3.11
2529
2589
  * inkos-harness case: the description carried "…: " and the catalog dropped
2530
2590
  * the whole skill). Already-quoted values and well-formed flow collections
2531
- * (`[a, b]` / `{a: b}`) are considered safe. */
2591
+ * (`[a, b]` / `{a: b}`) are considered safe. This rule is only the FAST
2592
+ * PATH — the write path re-verifies every rewrite with the real YAML parser
2593
+ * (see normalizeFrontmatter), so an incomplete approximation can never
2594
+ * corrupt a multiline flow value (P3-4). */
2532
2595
  function yamlPlainScalarNeedsQuotes(value) {
2533
2596
  if (value.length === 0) return false;
2534
2597
  if (value.length >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) return false;
@@ -2545,23 +2608,9 @@ function yamlPlainScalarNeedsQuotes(value) {
2545
2608
  * path. Single-line entries only; lines with embedded line breaks skip. */
2546
2609
  function frontmatterYamlUnsafeValues(content) {
2547
2610
  const found = [];
2548
- if (!content.trimStart().startsWith("---")) return found;
2549
- const nl = content.includes("\r\n") ? "\r\n" : "\n";
2550
- const lines = content.split(nl);
2551
- if ((lines[0] ?? "").trim() !== "---") return found;
2552
- let end = -1;
2553
- for (let i = 1; i < lines.length; i++) {
2554
- const line = lines[i];
2555
- if (line === void 0) continue;
2556
- if (line.trim() === "---") {
2557
- end = i;
2558
- break;
2559
- }
2560
- }
2561
- if (end < 0) return found;
2562
- for (let i = 1; i < end; i++) {
2563
- const line = lines[i];
2564
- if (line === void 0) continue;
2611
+ const block = frontmatterBlock(content);
2612
+ if (!block) return found;
2613
+ for (const line of block.block.split(block.nl)) {
2565
2614
  if (line.includes("\n") || line.includes("\r")) continue;
2566
2615
  const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
2567
2616
  if (!match) continue;
@@ -2579,43 +2628,25 @@ function frontmatterYamlUnsafeValues(content) {
2579
2628
  * Normalize a SKILL.md frontmatter block into catalog-loadable YAML: values
2580
2629
  * that YAML forbids unquoted get quotes — double quotes normally, single
2581
2630
  * quotes (with `''` doubling) when the value contains `"` or `\` (both legal
2582
- * unescaped inside single-quoted YAML). Idempotent (a normalized block
2583
- * passes through unchanged); only single-line `key: value` entries are
2584
- * touched; body text is never modified; a key line carrying an embedded
2585
- * line break (mixed ending styles) is left untouched rather than risking
2586
- * continuation-line data loss. Line-ending style of the block is preserved.
2631
+ * unescaped inside single-quoted YAML). Idempotent; only single-line
2632
+ * `key: value` entries are touched; body text is never modified; line-ending
2633
+ * style is preserved. **Every rewrite is re-verified with the real YAML
2634
+ * parser** (js-yaml — the same parser the platform catalog uses): if the
2635
+ * rewritten block no longer parses, or a rewritten value's parsed content
2636
+ * differs from the original, the rewrite is rolled back and reported in
2637
+ * `issues` (fail-loud, never a silent value corruption — P3-4).
2587
2638
  */
2588
2639
  function normalizeFrontmatter(content) {
2589
- if (!content.trimStart().startsWith("---")) return {
2590
- content,
2591
- changed: false,
2592
- fields: [],
2593
- issues: []
2594
- };
2595
- const nl = content.includes("\r\n") ? "\r\n" : "\n";
2596
- const lines = content.split(nl);
2597
- if ((lines[0] ?? "").trim() !== "---") return {
2598
- content,
2599
- changed: false,
2600
- fields: [],
2601
- issues: []
2602
- };
2603
- let end = -1;
2604
- for (let i = 1; i < lines.length; i++) {
2605
- const line = lines[i];
2606
- if (line === void 0) continue;
2607
- if (line.trim() === "---") {
2608
- end = i;
2609
- break;
2610
- }
2611
- }
2612
- if (end < 0) return {
2640
+ const block = frontmatterBlock(content);
2641
+ if (!block) return {
2613
2642
  content,
2614
2643
  changed: false,
2615
2644
  fields: [],
2616
2645
  issues: []
2617
2646
  };
2647
+ const { lines, end, nl } = block;
2618
2648
  const unsafe = new Map(frontmatterYamlUnsafeValues(content).map((entry) => [entry.key, entry.value]));
2649
+ const originalValues = new Map(unsafe);
2619
2650
  const fields = [];
2620
2651
  const issues = [];
2621
2652
  let changed = false;
@@ -2635,12 +2666,30 @@ function normalizeFrontmatter(content) {
2635
2666
  fields.push(key);
2636
2667
  changed = true;
2637
2668
  }
2638
- return {
2639
- content: changed ? lines.join(nl) : content,
2640
- changed,
2641
- fields,
2669
+ if (!changed) return {
2670
+ content,
2671
+ changed: false,
2672
+ fields: [],
2642
2673
  issues
2643
2674
  };
2675
+ const rewrittenBlock = lines.slice(1, end).join(nl);
2676
+ try {
2677
+ const parsed = load(rewrittenBlock);
2678
+ for (const key of fields) if (String(parsed[key]) !== originalValues.get(key)) throw new Error(`rewritten value for ${key} differs from the original`);
2679
+ return {
2680
+ content: lines.join(nl),
2681
+ changed: true,
2682
+ fields,
2683
+ issues
2684
+ };
2685
+ } catch (error) {
2686
+ return {
2687
+ content,
2688
+ changed: false,
2689
+ fields: [],
2690
+ issues: [`frontmatter rewrite verification failed (${error instanceof Error ? error.message : String(error)}) — quoting skipped; wrap this value manually`]
2691
+ };
2692
+ }
2644
2693
  }
2645
2694
  /**
2646
2695
  * Skill names referenced by a SKILL.md's `related_skills` frontmatter
@@ -4154,4 +4203,4 @@ function evolutionHome(env = process.env) {
4154
4203
  return join(env.DSH_HOME ?? join(homedir(), ".dsh"), "evolution");
4155
4204
  }
4156
4205
  //#endregion
4157
- export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EvolutionGateSet, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, findDriftSignal, foldCuratorFields, foldTurn, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
4206
+ export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EvolutionGateSet, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, findDriftSignal, foldCuratorFields, foldTurn, frontmatterBlock, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
@@ -15,6 +15,7 @@ export * from './io.ts';
15
15
  export * from './learn-prompt.ts';
16
16
  export * from './memory-store.ts';
17
17
  export * from './mutations.ts';
18
+ export * from './preset-composition.ts';
18
19
  export * from './prompts.ts';
19
20
  export * from './quality.ts';
20
21
  export * from './redact.ts';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Build the user-root Evolution preset composition from the RUNTIME platform's
3
+ * `standard` preset rows plus the evolution delta rows (P1-1 follow-up,
4
+ * 0.3.15): the agent-preset registry mounts ONE composition file verbatim, so
5
+ * a delta-only `agent.cordis.yml` would produce an agent carrying only the
6
+ * delta rows.
7
+ *
8
+ * Same contract as `install-layered.mjs` `generateAgentPreset` (the source
9
+ * install path) — installer.spec pins byte parity between the two.
10
+ *
11
+ * Row ids are read from `- id:` lines; an id present in both fragments would
12
+ * mount twice and could shadow the platform row, so it fails loud.
13
+ * @param standardComposition - the runtime `standard` preset composition.
14
+ * @param deltaComposition - the evolution delta fragment.
15
+ * @returns the composed preset composition (standard rows first, then delta).
16
+ */
17
+ export declare function composePresetComposition(standardComposition: string, deltaComposition: string): string;
18
+ //# sourceMappingURL=preset-composition.d.ts.map
@@ -100,6 +100,19 @@ export interface Frontmatter {
100
100
  description?: string;
101
101
  [key: string]: unknown;
102
102
  }
103
+ /**
104
+ * Shared frontmatter block detection (P3-3 single owner): opening line `---`
105
+ * and closing line exactly `---` (both trimmed). Used by `parseFrontmatter`,
106
+ * `frontmatterYamlUnsafeValues` and `normalizeFrontmatter` so the three can
107
+ * never disagree about where the block ends (the loose `indexOf('\n---')`
108
+ * form matched `\n----` and was replaced by this strict line rule).
109
+ */
110
+ export declare function frontmatterBlock(content: string): {
111
+ block: string;
112
+ lines: string[];
113
+ end: number;
114
+ nl: string;
115
+ } | null;
103
116
  export declare function parseFrontmatter(content: string): {
104
117
  frontmatter: Frontmatter;
105
118
  body: string;
@@ -111,7 +124,10 @@ export declare function parseFrontmatter(content: string): {
111
124
  * silently split family-visibility from platform-visibility (0.3.11
112
125
  * inkos-harness case: the description carried "…: " and the catalog dropped
113
126
  * the whole skill). Already-quoted values and well-formed flow collections
114
- * (`[a, b]` / `{a: b}`) are considered safe. */
127
+ * (`[a, b]` / `{a: b}`) are considered safe. This rule is only the FAST
128
+ * PATH — the write path re-verifies every rewrite with the real YAML parser
129
+ * (see normalizeFrontmatter), so an incomplete approximation can never
130
+ * corrupt a multiline flow value (P3-4). */
115
131
  export declare function yamlPlainScalarNeedsQuotes(value: string): boolean;
116
132
  /** Raw-line scan of the frontmatter block: entries whose UNQUOTED value is
117
133
  * YAML-unsafe for the strict platform catalog. Operates on the ORIGINAL line
@@ -127,20 +143,23 @@ export interface FrontmatterNormalizeResult {
127
143
  changed: boolean;
128
144
  /** Frontmatter keys whose values were auto-quoted. */
129
145
  fields: string[];
130
- /** Values that cannot be auto-quoted safely (control characters only —
131
- * double/single-quote fallback covers `"`/`\`/`'`, so a quote-containing
132
- * value no longer traps the write path in an unfixable loop). */
146
+ /** Values that cannot be auto-quoted safely (control characters, or a
147
+ * rewrite that failed the real-parser verification — a multiline flow
148
+ * collection line etc. is left untouched and reported here, so the write
149
+ * path rejects instead of silently damaging a value; 0.3.14). */
133
150
  issues: string[];
134
151
  }
135
152
  /**
136
153
  * Normalize a SKILL.md frontmatter block into catalog-loadable YAML: values
137
154
  * that YAML forbids unquoted get quotes — double quotes normally, single
138
155
  * quotes (with `''` doubling) when the value contains `"` or `\` (both legal
139
- * unescaped inside single-quoted YAML). Idempotent (a normalized block
140
- * passes through unchanged); only single-line `key: value` entries are
141
- * touched; body text is never modified; a key line carrying an embedded
142
- * line break (mixed ending styles) is left untouched rather than risking
143
- * continuation-line data loss. Line-ending style of the block is preserved.
156
+ * unescaped inside single-quoted YAML). Idempotent; only single-line
157
+ * `key: value` entries are touched; body text is never modified; line-ending
158
+ * style is preserved. **Every rewrite is re-verified with the real YAML
159
+ * parser** (js-yaml — the same parser the platform catalog uses): if the
160
+ * rewritten block no longer parses, or a rewritten value's parsed content
161
+ * differs from the original, the rewrite is rolled back and reported in
162
+ * `issues` (fail-loud, never a silent value corruption — P3-4).
144
163
  */
145
164
  export declare function normalizeFrontmatter(content: string): FrontmatterNormalizeResult;
146
165
  /**
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.3.13",
4
+ "version": "0.3.15",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,12 +31,16 @@
31
31
  "lib/types/invariant.d.ts"
32
32
  ],
33
33
  "license": "MIT",
34
+ "dependencies": {
35
+ "js-yaml": "^4.2.0"
36
+ },
34
37
  "peerDependencies": {
35
38
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
36
39
  "@deepseek-ai/cordis": "^4.0.1",
37
40
  "@deepseek-ai/dsh-session": "^0.1.1-rc.2"
38
41
  },
39
42
  "devDependencies": {
43
+ "@types/js-yaml": "^4.0.9",
40
44
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
41
45
  "@deepseek-ai/cordis": "^4.0.1",
42
46
  "@deepseek-ai/dsh-session": "^0.1.1-rc.2"