@lmzhen/dsh-evolution-plan-validator 0.1.0-rc.9 → 0.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_USER_CHAR_LIMIT, MAX_RESTRUCTURE_MOVES, RESTRUCTURE_TARGET_RE } from "@lmzhen/dsh-evolution-core";
1
2
  //#region lib/types/index.js
2
3
  /**
3
4
  * Deterministic validator for model-produced evolution plans.
@@ -16,7 +17,8 @@ const SKILL_ACTIONS = new Set([
16
17
  "patch",
17
18
  "delete",
18
19
  "write_file",
19
- "remove_file"
20
+ "remove_file",
21
+ "restructure"
20
22
  ]);
21
23
  const FORBIDDEN_KEYS = [
22
24
  "policy",
@@ -30,12 +32,12 @@ function hasValidEvidence(evidence, sessionSeq) {
30
32
  return evidence.every((item) => {
31
33
  if (!item || typeof item !== "object") return false;
32
34
  const record = item;
33
- const seq = typeof record.event_seq === "number" ? record.event_seq : typeof record.seq === "number" ? record.seq : Number(record.event_seq);
35
+ const seq = typeof record.event_seq === "number" ? record.event_seq : typeof record.seq === "number" ? record.seq : typeof record.event_seq === "string" && /^\d+$/.test(record.event_seq) ? Number(record.event_seq) : -1;
34
36
  return Number.isInteger(seq) && seq >= 0 && seq <= sessionSeq;
35
37
  });
36
38
  }
37
39
  function validateEvolutionPlan(plan, context) {
38
- const maxOps = context.maxOpsPerPlan ?? 32;
40
+ const maxOps = context.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN;
39
41
  const rejected = [];
40
42
  const memoryOps = [];
41
43
  const skillOps = [];
@@ -102,7 +104,7 @@ function validateMemoryOp(op, context, index) {
102
104
  const text = (op.facts ?? op.content ?? "").trim();
103
105
  if (action !== "remove" && text.length === 0) return `memory op ${index}: ${action} requires facts/content`;
104
106
  if (action !== "add" && !(op.old_text ?? "").trim()) return `memory op ${index}: ${action} requires old_text`;
105
- const budget = op.target === "user" ? context.maxUserChars ?? 1375 : context.maxMemoryChars ?? 2200;
107
+ const budget = op.target === "user" ? context.maxUserChars ?? DEFAULT_USER_CHAR_LIMIT : context.maxMemoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT;
106
108
  if (text.length > budget) return `memory op ${index}: content exceeds ${op.target === "user" ? "user" : "memory"} budget`;
107
109
  return null;
108
110
  }
@@ -116,7 +118,18 @@ function validateSkillOp(op, context, index) {
116
118
  if (!SKILL_ACTIONS.has(action)) return `skill op ${index}: unknown action ${action}`;
117
119
  if ((action === "create" || action === "edit" || action === "update") && !(op.content ?? "").trim()) return `skill op ${index}: ${action} requires content`;
118
120
  if (action === "patch" && !(op.old_string ?? "")) return `skill op ${index}: patch requires old_string`;
119
- if ((op.content ?? "").length > (context.maxSkillContentChars ?? 1e5)) return `skill op ${index}: content exceeds skill budget`;
121
+ if (action === "delete" && !(op.absorbed_into ?? "").trim()) return `skill op ${index}: delete requires absorbed_into`;
122
+ const writeContent = op.file_content ?? op.content ?? "";
123
+ if (action === "write_file" && !writeContent.trim()) return `skill op ${index}: write_file requires file_content`;
124
+ if (writeContent.length > (context.maxSkillContentChars ?? DEFAULT_SKILL_CONTENT_CHARS)) return `skill op ${index}: content exceeds skill budget`;
125
+ if (action === "restructure") {
126
+ if (!Array.isArray(op.restructure) || op.restructure.length === 0) return `skill op ${index}: restructure requires a non-empty restructure list`;
127
+ if (op.restructure.length > MAX_RESTRUCTURE_MOVES) return `skill op ${index}: restructure exceeds ${MAX_RESTRUCTURE_MOVES} moves`;
128
+ for (const [moveIndex, move] of op.restructure.entries()) {
129
+ if (!move || typeof move.heading !== "string" || !move.heading.trim()) return `skill op ${index}: restructure[${moveIndex}] requires a non-empty heading`;
130
+ if (typeof move.to_file !== "string" || !RESTRUCTURE_TARGET_RE.test(move.to_file)) return `skill op ${index}: restructure[${moveIndex}] to_file must be references/<topic>.md`;
131
+ }
132
+ }
120
133
  return null;
121
134
  }
122
135
  //#endregion
@@ -15,10 +15,17 @@ export interface SkillOp {
15
15
  action?: string;
16
16
  name?: string;
17
17
  content?: string;
18
+ /** write_file payload (the tool reads `file_content`, not `content`). */
19
+ file_content?: string;
18
20
  old_string?: string;
19
21
  new_string?: string;
20
22
  file_path?: string;
21
23
  absorbed_into?: string;
24
+ /** restructure payload: body sections moved to references/ (008 batch B). */
25
+ restructure?: Array<{
26
+ heading?: string;
27
+ to_file?: string;
28
+ } | null>;
22
29
  evidence?: unknown[];
23
30
  }
24
31
  export interface EvolutionPlan {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-plan-validator",
3
3
  "description": "Deterministic validator for model-produced evolution plans (community build)",
4
- "version": "0.1.0-rc.9",
4
+ "version": "0.2.0-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,13 +32,14 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@deepseek-ai/schemastery": "^3.18.1"
35
+ "@deepseek-ai/schemastery": "^3.18.1",
36
+ "@lmzhen/dsh-evolution-core": "^0.2.0-rc.1"
36
37
  },
37
38
  "peerDependencies": {
38
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
39
+ "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
39
40
  "@deepseek-ai/cordis": "^4.0.1"
40
41
  },
41
42
  "devDependencies": {
42
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6"
43
+ "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
43
44
  }
44
45
  }