@lmzhen/dsh-evolution-review 0.3.70 → 0.3.72
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 +59 -41
- package/package.json +11 -11
package/lib/index.js
CHANGED
|
@@ -342,13 +342,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
342
342
|
const io = ctx.get("evolutionIo");
|
|
343
343
|
if (!io) return hashes;
|
|
344
344
|
const library = new SkillLibrary(resolveSkillsRoot({ root: rootConfig.root }), evolutionIoAdapter(() => io.provider()));
|
|
345
|
-
for (const summary of await library.list().catch((error) => {
|
|
345
|
+
for (const summary of await library.list({ withContent: true }).catch((error) => {
|
|
346
346
|
ctx.logger.warn(`dsh-evolution-review: skill tree scan failed (${error instanceof Error ? error.message : String(error)}) — pre-run staleness hashes are unavailable; full-content updates will not be drift-checked this review`);
|
|
347
347
|
return [];
|
|
348
|
-
}))
|
|
349
|
-
const text = await library.read(summary.name).catch(() => null);
|
|
350
|
-
if (text !== null) hashes.set(summary.name, contentHash(text));
|
|
351
|
-
}
|
|
348
|
+
})) if (summary.content !== void 0) hashes.set(summary.name, contentHash(summary.content));
|
|
352
349
|
return hashes;
|
|
353
350
|
}
|
|
354
351
|
async function trySubagentReview(session, agent, kind, signal) {
|
|
@@ -379,6 +376,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
379
376
|
const agentOptions = { model };
|
|
380
377
|
if (config.reviewProvider) agentOptions.provider = config.reviewProvider;
|
|
381
378
|
const preRunHashes = await treeSkillHashes();
|
|
379
|
+
const sessionSeqAtPlanTime = session.seq - 1;
|
|
382
380
|
const run = await subagents.start("spawn", {
|
|
383
381
|
label: "dsh-evolution-review",
|
|
384
382
|
prompt: [{
|
|
@@ -401,14 +399,17 @@ function apply(ctx, rawConfig = {}) {
|
|
|
401
399
|
} catch (emitError) {
|
|
402
400
|
ctx.logger.warn(`dsh-evolution-review: review-error emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
403
401
|
}
|
|
404
|
-
|
|
402
|
+
const failureShape = result;
|
|
403
|
+
const stopDetail = typeof failureShape.stopReason === "string" ? failureShape.stopReason : void 0;
|
|
404
|
+
const diagDetail = typeof failureShape.diagnostic === "string" ? failureShape.diagnostic : void 0;
|
|
405
|
+
ctx.logger.warn(`dsh-evolution-review: review subagent returned no structured plan${stopDetail !== void 0 ? ` (stopReason=${stopDetail}${diagDetail !== void 0 ? `; diagnostic=${diagDetail}` : ""})` : ""}`);
|
|
405
406
|
return false;
|
|
406
407
|
}
|
|
407
408
|
const childReads = run.localAgent ? collectReadSkillNames(run.localAgent.session) : /* @__PURE__ */ new Set();
|
|
408
409
|
const plan = result.structured;
|
|
409
410
|
const policyFingerprint = fingerprintPolicy(snapshot);
|
|
410
411
|
const validation = validateEvolutionPlan(plan, {
|
|
411
|
-
sessionSeq:
|
|
412
|
+
sessionSeq: sessionSeqAtPlanTime,
|
|
412
413
|
maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
|
|
413
414
|
protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
|
|
414
415
|
maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
|
|
@@ -559,8 +560,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
559
560
|
...op,
|
|
560
561
|
evidence: op.evidence
|
|
561
562
|
};
|
|
563
|
+
let stageCurrent = null;
|
|
562
564
|
if ((args.action === "update" || args.action === "edit") && hashLibrary && typeof args.name === "string" && args.name !== "") {
|
|
563
|
-
|
|
565
|
+
stageCurrent = await hashLibrary.read(args.name).catch(() => null);
|
|
564
566
|
if (stageCurrent !== null) args.staged_from_sha256 = contentHash(stageCurrent);
|
|
565
567
|
}
|
|
566
568
|
if ((args.action === "write_file" || args.action === "remove_file") && hashLibrary && typeof args.name === "string" && args.name !== "" && typeof args.file_path === "string" && args.file_path !== "") {
|
|
@@ -568,13 +570,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
568
570
|
if (stageFile !== void 0) args.staged_from_sha256 = stageFile === null ? "absent" : contentHash(stageFile);
|
|
569
571
|
}
|
|
570
572
|
const opName = typeof args.name === "string" ? args.name : "";
|
|
571
|
-
const hashChecked = (args.action === "update" || args.action === "edit") && opName !== "" && preRunHashes?.has(opName) === true;
|
|
573
|
+
const hashChecked = (args.action === "update" || args.action === "edit" || args.action === "patch") && opName !== "" && preRunHashes?.has(opName) === true;
|
|
572
574
|
if (hashChecked) {
|
|
573
|
-
const live = await hashLibrary
|
|
575
|
+
const live = args.action === "patch" ? hashLibrary ? await hashLibrary.read(opName).catch(() => null) : null : stageCurrent;
|
|
574
576
|
const preRun = preRunHashes.get(opName);
|
|
575
577
|
if (live === null || preRun !== void 0 && contentHash(live) !== preRun) {
|
|
576
578
|
ok = false;
|
|
577
|
-
failedOps.push(`skill ${args.action} ${args.name}: the skill changed while this review ran — update refused as stale; produce a fresh plan`);
|
|
579
|
+
failedOps.push(`skill ${args.action} ${args.name}: the skill changed while this review ran — ${args.action === "patch" ? "patch" : "update"} refused as stale; produce a fresh plan`);
|
|
578
580
|
continue;
|
|
579
581
|
}
|
|
580
582
|
}
|
|
@@ -679,32 +681,14 @@ function apply(ctx, rawConfig = {}) {
|
|
|
679
681
|
ok: false,
|
|
680
682
|
message: `Skill "${name}" is protected by the current policy (protectedSkillNames); autonomous writes are refused.`
|
|
681
683
|
};
|
|
682
|
-
if (op.action === "write_file" || op.action === "remove_file") {
|
|
683
|
-
const expected = op.staged_from_sha256;
|
|
684
|
-
if (typeof expected === "string" && expected !== "") {
|
|
685
|
-
const current = await library.readSupportFile(name, op.file_path ?? "").catch(() => void 0);
|
|
686
|
-
const actual = current === void 0 || current === null ? "absent" : contentHash(current);
|
|
687
|
-
if (current === void 0 || actual !== expected) return {
|
|
688
|
-
ok: false,
|
|
689
|
-
message: `Support file "${op.file_path ?? ""}" of "${name}" changed since this plan was produced — the staged file operation was refused as stale. Re-read the skill tree and produce a fresh plan.`
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
684
|
if (op.action === "create") {
|
|
694
685
|
const created = await library.create(name, op.content ?? "", origin);
|
|
695
686
|
if (created.ok) await ctx.get("skillUsage")?.markAgentCreated?.(name);
|
|
696
687
|
return created;
|
|
697
688
|
}
|
|
698
689
|
if (op.action === "edit" || op.action === "update") {
|
|
699
|
-
const
|
|
700
|
-
if (
|
|
701
|
-
const current = await library.read(name).catch(() => null);
|
|
702
|
-
if (current !== null && contentHash(current) !== expected) return {
|
|
703
|
-
ok: false,
|
|
704
|
-
message: `Skill "${name}" changed since this plan was produced — the full-content update was refused as stale. Re-read the skill and produce a fresh plan.`
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
const updated = await library.update(name, op.content ?? "", origin);
|
|
690
|
+
const updated = await library.update(name, op.content ?? "", origin, anchorOf(op));
|
|
691
|
+
if (updated.stale === true) return staleRefusal(updated, name, op.file_path);
|
|
708
692
|
if (updated.ok) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
709
693
|
return updated;
|
|
710
694
|
}
|
|
@@ -724,17 +708,14 @@ function apply(ctx, rawConfig = {}) {
|
|
|
724
708
|
return archived;
|
|
725
709
|
}
|
|
726
710
|
if (op.action === "write_file" || op.action === "remove_file") {
|
|
727
|
-
const
|
|
728
|
-
if (
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
ok: false,
|
|
733
|
-
message: `Support file "${op.file_path ?? ""}" of "${name}" changed since this plan was produced — the staged file operation was refused as stale. Re-read the skill tree and produce a fresh plan.`
|
|
734
|
-
};
|
|
711
|
+
const anchor = anchorOf(op);
|
|
712
|
+
if (op.action === "write_file") {
|
|
713
|
+
const written = await library.writeSupportFile(name, op.file_path ?? "", op.file_content ?? "", origin, anchor);
|
|
714
|
+
if (written.stale === true) return staleRefusal(written, name, op.file_path);
|
|
715
|
+
return written;
|
|
735
716
|
}
|
|
736
|
-
|
|
737
|
-
|
|
717
|
+
const removedSupport = await library.removeSupportFile(name, op.file_path ?? "", origin, anchor);
|
|
718
|
+
if (removedSupport.stale === true) return staleRefusal(removedSupport, name, op.file_path);
|
|
738
719
|
if (removedSupport.ok) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
739
720
|
return removedSupport;
|
|
740
721
|
}
|
|
@@ -771,6 +752,43 @@ function shouldCompletionReview(reason, sessionToolCalls, minToolCalls) {
|
|
|
771
752
|
* `skill_search` discovery pair, so that branch was dead. `skill_manage` has no
|
|
772
753
|
* per-skill read action (its `list`/`review` are whole-library), so a specific
|
|
773
754
|
* skill read through it cannot be tracked — see README Known Limitations. */
|
|
755
|
+
/**
|
|
756
|
+
* v35 C9: the staging anchor of one accepted plan op, in the shape the library
|
|
757
|
+
* verifies against the bytes it reads under the write's own lock. The `absent`
|
|
758
|
+
* sentinel is the same one the tool's staging attaches for a support file that
|
|
759
|
+
* did not exist when the plan was produced.
|
|
760
|
+
* The staged field rides OUTSIDE the plan schema (the staging channel attaches
|
|
761
|
+
* it to the op payload), so it is read defensively rather than typed on SkillOp.
|
|
762
|
+
* @param op - one accepted plan op.
|
|
763
|
+
* @returns the anchor, or `undefined` when the op carries none.
|
|
764
|
+
*/
|
|
765
|
+
function anchorOf(op) {
|
|
766
|
+
const expected = op.staged_from_sha256;
|
|
767
|
+
if (typeof expected !== "string" || expected === "") return void 0;
|
|
768
|
+
return expected === "absent" ? { absent: true } : { sha256: expected };
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Translate the library's anchor refusal into this channel's staging wording.
|
|
772
|
+
* The library reports WHICH drift it saw (drift / missing / unverifiable); the
|
|
773
|
+
* vocabulary ("this plan was produced", "produce a fresh plan") is the review
|
|
774
|
+
* channel's. A body op whose skill vanished keeps the historical not-found
|
|
775
|
+
* report: the former lock-free check only refused on a DRIFTED body, so a
|
|
776
|
+
* deleted target fell through to the write's own not-found result.
|
|
777
|
+
* @param result - the library's stale result.
|
|
778
|
+
* @param name - the skill the op targets.
|
|
779
|
+
* @param filePath - the support-file path for file ops; omitted for body ops.
|
|
780
|
+
* @returns the refusal to return from the direct path.
|
|
781
|
+
*/
|
|
782
|
+
function staleRefusal(result, name, filePath) {
|
|
783
|
+
if (filePath === void 0 && result.anchor === "missing") return {
|
|
784
|
+
ok: false,
|
|
785
|
+
message: `Skill "${name}" not found.`
|
|
786
|
+
};
|
|
787
|
+
return {
|
|
788
|
+
ok: false,
|
|
789
|
+
message: filePath === void 0 ? `Skill "${name}" changed since this plan was produced — the full-content update was refused as stale. Re-read the skill and produce a fresh plan.` : `Support file "${filePath}" of "${name}" changed since this plan was produced — the staged file operation was refused as stale. Re-read the skill tree and produce a fresh plan.`
|
|
790
|
+
};
|
|
791
|
+
}
|
|
774
792
|
function collectReadSkillNames(session) {
|
|
775
793
|
const callNames = /* @__PURE__ */ new Map();
|
|
776
794
|
const okCallIds = /* @__PURE__ */ new Set();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-review",
|
|
3
3
|
"description": "Background review orchestration (community build)",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.72",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
35
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
36
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.72",
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.3.72",
|
|
36
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.72"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
43
43
|
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
44
44
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
45
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
46
|
-
"@lmzhen/dsh-evolution-policy": "^0.3.
|
|
45
|
+
"@lmzhen/dsh-evolution-state": "^0.3.72",
|
|
46
|
+
"@lmzhen/dsh-evolution-policy": "^0.3.72"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"@deepseek-ai/dsh-session-persistence": "^0.1.5-rc.2",
|
|
55
55
|
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.5-rc.2",
|
|
56
56
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
57
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
58
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
59
|
-
"@lmzhen/dsh-evolution-curator": "^0.3.
|
|
60
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
61
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
57
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.72",
|
|
58
|
+
"@lmzhen/dsh-evolution-core": "^0.3.72",
|
|
59
|
+
"@lmzhen/dsh-evolution-curator": "^0.3.72",
|
|
60
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.72",
|
|
61
|
+
"@lmzhen/dsh-evolution-state": "^0.3.72"
|
|
62
62
|
}
|
|
63
63
|
}
|