@lmzhen/dsh-evolution-core 0.30.37 → 0.30.38

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 (36) hide show
  1. package/lib/index.js +3776 -2300
  2. package/lib/types/evolution-events.d.ts +6 -2
  3. package/lib/types/evolution-events.js +6 -0
  4. package/lib/types/extract-plan.js +17 -11
  5. package/lib/types/extract-prompt.d.ts +1 -5
  6. package/lib/types/extract-prompt.js +2 -0
  7. package/lib/types/extract-request.d.ts +32 -0
  8. package/lib/types/extract-request.js +155 -0
  9. package/lib/types/index.d.ts +4 -0
  10. package/lib/types/index.js +4 -0
  11. package/lib/types/mutation-storage-adapter.d.ts +14 -0
  12. package/lib/types/mutation-storage-adapter.js +107 -0
  13. package/lib/types/mutation-unit.d.ts +164 -0
  14. package/lib/types/mutation-unit.js +509 -0
  15. package/lib/types/prompts.d.ts +3 -3
  16. package/lib/types/prompts.js +8 -8
  17. package/lib/types/proposal-view.d.ts +4 -0
  18. package/lib/types/proposal-view.js +3 -1
  19. package/lib/types/skill-health.d.ts +5 -0
  20. package/lib/types/skill-health.js +3 -0
  21. package/lib/types/skill-history.d.ts +2 -0
  22. package/lib/types/skill-history.js +3 -0
  23. package/lib/types/skill-lint.d.ts +2 -0
  24. package/lib/types/skill-lint.js +13 -2
  25. package/lib/types/skill-model.d.ts +45 -0
  26. package/lib/types/skill-model.js +74 -0
  27. package/lib/types/skill-store.d.ts +22 -1
  28. package/lib/types/skill-store.js +415 -274
  29. package/lib/types/unit-members.js +3 -3
  30. package/lib/types/unit-storage.d.ts +10 -0
  31. package/lib/types/unit-transaction.d.ts +1 -1
  32. package/lib/types/unit-undo-plan.js +5 -3
  33. package/lib/types/unit-undo.d.ts +1 -1
  34. package/lib/types/unit-undo.js +18 -3
  35. package/package.json +1 -1
  36. package/persisted-write-inventory.json +3 -3
@@ -50,9 +50,9 @@ export function resolveUnitMembersFromIndexes(head, indexes, readEntries) {
50
50
  for (const path of shardPaths)
51
51
  if (!wanted.includes(path))
52
52
  conflicts.push({ kind: 'foreign', path });
53
- for (const path of wanted)
54
- if (shardPaths.filter(p => p === path).length > 1)
55
- conflicts.push({ kind: 'duplicated', path });
53
+ // No duplicated-path check: the manifest path is skill-relative and carries no skill, so one
54
+ // path in TWO skills (extract's source support removal + target support write) is the normal
55
+ // shape. A path-only duplicate refusal made every cross-skill support move un-undoable.
56
56
  }
57
57
  return { members, missing, conflicts };
58
58
  }
@@ -23,6 +23,16 @@ export interface UnitSnapshot {
23
23
  }[];
24
24
  readonly origin: string;
25
25
  readonly summary: string;
26
+ /** Tree-change units reuse this store; merge snapshots leave these absent. */
27
+ readonly kind?: 'merge' | 'tree-change';
28
+ readonly mutationState?: 'prepared' | 'committed' | 'undone';
29
+ readonly ops?: readonly {
30
+ readonly target: string;
31
+ readonly before: string | null;
32
+ readonly after: string | null;
33
+ readonly skill?: string;
34
+ }[];
35
+ readonly cleanup?: readonly string[];
26
36
  readonly validationPlan?: {
27
37
  readonly planVersion: 1;
28
38
  readonly stageDigest: string;
@@ -214,7 +214,7 @@ export declare class UnitTransactions implements UnitTransaction {
214
214
  phase: "target-pending" | "target-written" | "source-archiving" | "sources-archived";
215
215
  } | {
216
216
  ok: boolean;
217
- phase: "failed" | "prepared" | "support-pending" | "unit-written" | "committed" | "rolled-back";
217
+ phase: "failed" | "prepared" | "unit-written" | "committed" | "support-pending" | "rolled-back";
218
218
  }>;
219
219
  }
220
220
  //# sourceMappingURL=unit-transaction.d.ts.map
@@ -13,6 +13,7 @@
13
13
  * means the caller writes nothing.
14
14
  */
15
15
  import { BASELINE_ACTION, entryTarget } from "./skill-history.js";
16
+ import { contentHash } from "./mutations.js";
16
17
  function slotsOf(entries) {
17
18
  const body = {};
18
19
  const support = new Map();
@@ -72,13 +73,14 @@ export function planUnitUndoAcross(members) {
72
73
  for (const [path, slot] of support) {
73
74
  if (slot.newest === undefined)
74
75
  continue;
75
- const preUnitHash = slot.baseline !== undefined ? slot.baseline.hash : slot.oldest?.beforeHash;
76
+ const removal = slot.oldest?.removed === true;
77
+ const preUnitHash = removal ? slot.oldest?.hash : slot.baseline !== undefined ? slot.baseline.hash : slot.oldest?.beforeHash;
76
78
  ops.push({
77
79
  skill: member.skill,
78
80
  path,
79
- remove: preUnitHash === undefined,
81
+ remove: !removal && preUnitHash === undefined,
80
82
  ...(preUnitHash === undefined ? {} : { preUnitHash }),
81
- expectedHash: slot.newest.hash,
83
+ expectedHash: removal ? contentHash('') : slot.newest.hash,
82
84
  });
83
85
  }
84
86
  if (body.newest !== undefined) {
@@ -12,7 +12,7 @@ export interface UnitUndoSink {
12
12
  updateSkill(skill: string, body: string, anchor: {
13
13
  sha256: string;
14
14
  }): Promise<SkillActionResult>;
15
- writeSupportFile(skill: string, path: string, content: string, anchor: {
15
+ writeSupportFile(skill: string, path: string, content: string, anchor?: {
16
16
  sha256: string;
17
17
  }): Promise<SkillActionResult>;
18
18
  removeSupportFile(skill: string, path: string, anchor: {
@@ -25,6 +25,21 @@ export async function executeUnitUndo(plan, sink) {
25
25
  for (const op of plan.ops) {
26
26
  const live = op.path === '' ? await sink.readSkill(op.skill) : await sink.readSupport(op.skill, op.path);
27
27
  if (live === null) {
28
+ // The unit may have DELETED this path (extract moving an exclusive support file). Its
29
+ // after-hash is then the absent-content hash, and undo must recreate it from the pre-unit
30
+ // blob. A remove op against an already-absent path is a no-op.
31
+ // A remove op against an already-absent path is a no-op (the unit's deletion landed).
32
+ if (op.remove)
33
+ continue;
34
+ if (op.expectedHash === contentHash('')) {
35
+ const bytes = op.preUnitHash === undefined ? null : await sink.readBlob(op.preUnitHash);
36
+ if (bytes === null) {
37
+ refusals.push(`${op.skill}/${op.path === '' ? 'SKILL.md' : op.path}(单元前字节读不到:blob ${op.preUnitHash ?? '(missing)'})`);
38
+ continue;
39
+ }
40
+ prepared.push({ ...op, bytes, live: '', absent: true });
41
+ continue;
42
+ }
28
43
  refusals.push(`${op.skill}/${op.path === '' ? 'SKILL.md' : op.path}(盘上文件不存在)`);
29
44
  continue;
30
45
  }
@@ -33,7 +48,7 @@ export async function executeUnitUndo(plan, sink) {
33
48
  continue;
34
49
  }
35
50
  if (op.remove) {
36
- prepared.push({ ...op, bytes: '', live });
51
+ prepared.push({ ...op, bytes: '', live, absent: false });
37
52
  continue;
38
53
  }
39
54
  const bytes = op.preUnitHash === undefined ? null : await sink.readBlob(op.preUnitHash);
@@ -41,7 +56,7 @@ export async function executeUnitUndo(plan, sink) {
41
56
  refusals.push(`${op.skill}/${op.path === '' ? 'SKILL.md' : op.path}(单元前字节读不到:blob ${op.preUnitHash ?? '(missing)'})`);
42
57
  continue;
43
58
  }
44
- prepared.push({ ...op, bytes, live });
59
+ prepared.push({ ...op, bytes, live, absent: false });
45
60
  }
46
61
  if (refusals.length > 0) {
47
62
  return { ok: false, message: `整单元撤销未开始(零写)—— 预检未通过:${refusals.join(';')}。修好后可重试。`, reverted: [] };
@@ -59,7 +74,7 @@ export async function executeUnitUndo(plan, sink) {
59
74
  for (const op of supportOps) {
60
75
  const result = op.remove
61
76
  ? await sink.removeSupportFile(op.skill, op.path, { sha256: op.expectedHash })
62
- : await sink.writeSupportFile(op.skill, op.path, op.bytes, { sha256: op.expectedHash });
77
+ : op.absent ? await sink.writeSupportFile(op.skill, op.path, op.bytes) : await sink.writeSupportFile(op.skill, op.path, op.bytes, { sha256: op.expectedHash });
63
78
  if (!result.ok)
64
79
  return { ok: false, message: `${op.skill}/${op.path}:${result.message} —— 撤销未完成(已回退:${done()})。`, reverted };
65
80
  reverted.push(op.skill + '/' + op.path);
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.30.37",
4
+ "version": "0.30.38",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -122,10 +122,10 @@
122
122
  "id": "skill-tree",
123
123
  "path": "<skillsRoot>/**",
124
124
  "writer": "evolution-core/src/skill-store.ts",
125
- "serializedBy": "transact",
126
- "marker": "this.transact(this.io, path",
125
+ "serializedBy": "write-lock",
126
+ "marker": "fileMutationLock(",
127
127
  "state": [],
128
- "note": "The skill tree itself. G2 S4: the family write-lock protocol is retired; every byte writer goes through the provider's versioned read-modify-write (`transact`), so this entry is serialized by the same seam as the rest. Destructive movers refuse only when a support directory cannot be listed (fail-closed)."
128
+ "note": "The skill tree itself. G1: ordinary byte writers take the root mutation lock (fileMutationLock) and still run their per-file read-modify-write through `transact`; the root lock is what keeps extract/restructure/update/patch/create from interleaving. Destructive movers refuse only when a support directory cannot be listed (fail-closed)."
129
129
  },
130
130
  {
131
131
  "id": "activity-log",