@lmzhen/dsh-evolution-core 0.3.36 → 0.3.37
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 +64 -18
- package/lib/types/memory-store.d.ts +8 -0
- package/lib/types/skill-health.d.ts +4 -2
- package/lib/types/usage.d.ts +4 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -423,6 +423,7 @@ function parseUsage(raw) {
|
|
|
423
423
|
if (raw === null) return map;
|
|
424
424
|
try {
|
|
425
425
|
const parsed = JSON.parse(raw);
|
|
426
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return map;
|
|
426
427
|
for (const [name, record] of Object.entries(parsed)) map.set(name, normalizeUsageRecord(record));
|
|
427
428
|
} catch {}
|
|
428
429
|
return map;
|
|
@@ -499,6 +500,10 @@ function foldCuratorFields(disk, curated, stateOwned) {
|
|
|
499
500
|
if (stateOwned === void 0 || stateOwned.has(name)) applyCuratorLifecycleFields(diskRecord, record);
|
|
500
501
|
}
|
|
501
502
|
}
|
|
503
|
+
/** Whole-file usage write (V6-37, 0.3.37): this is the ONE path that bypasses
|
|
504
|
+
* the malformed-defense and the transact lock — prefer `mutateUsage` for any
|
|
505
|
+
* read-modify-write so a concurrent writer cannot lose its update and a
|
|
506
|
+
* malformed sidecar stays recoverable. Kept for fixture/test seeding. */
|
|
502
507
|
async function saveUsage(root, map, io = nodeEvolutionIo()) {
|
|
503
508
|
const obj = Object.fromEntries(map.entries());
|
|
504
509
|
await io.writeText(usageFile(root), JSON.stringify(obj, null, 2));
|
|
@@ -1601,6 +1606,24 @@ function buildLearnPrompt(userRequest) {
|
|
|
1601
1606
|
].join("\n");
|
|
1602
1607
|
}
|
|
1603
1608
|
//#endregion
|
|
1609
|
+
//#region lib/types/serial.js
|
|
1610
|
+
/**
|
|
1611
|
+
* A process-local serial task queue: each task starts only after the previous
|
|
1612
|
+
* one settles (success or failure), so read-modify-write sequences that share
|
|
1613
|
+
* one file never interleave inside this process. The durable cross-process
|
|
1614
|
+
* serialization layer is the IO backend's transact lock; this chain is the
|
|
1615
|
+
* second layer (0.3.17 S2.8, T-1: the shape was duplicated in state-json and
|
|
1616
|
+
* memory-files — one factory now).
|
|
1617
|
+
*/
|
|
1618
|
+
function makeSerialQueue() {
|
|
1619
|
+
let chain = Promise.resolve();
|
|
1620
|
+
return (task) => {
|
|
1621
|
+
const run = chain.then(task, task);
|
|
1622
|
+
chain = run.then(() => void 0, () => void 0);
|
|
1623
|
+
return run;
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
//#endregion
|
|
1604
1627
|
//#region lib/types/numeric.js
|
|
1605
1628
|
/**
|
|
1606
1629
|
* Numeric config clamping for the dsh-evolution plugin family.
|
|
@@ -1962,6 +1985,12 @@ var MemoryStore = class {
|
|
|
1962
1985
|
root;
|
|
1963
1986
|
maxFailures;
|
|
1964
1987
|
io;
|
|
1988
|
+
/** V6-16 (0.3.37): same-process RMW serialization (the SkillLibrary queue) —
|
|
1989
|
+
* on a backend WITHOUT a transact lock two concurrent callers compute on the
|
|
1990
|
+
* same old content and the last rename wins, silently dropping one op's
|
|
1991
|
+
* update. The node backend's cross-process lock already serializes; this
|
|
1992
|
+
* chain covers the no-transact custom backends. */
|
|
1993
|
+
serial = makeSerialQueue();
|
|
1965
1994
|
failureCount = 0;
|
|
1966
1995
|
lastFailureAt = 0;
|
|
1967
1996
|
constructor(options = {}) {
|
|
@@ -2068,6 +2097,9 @@ var MemoryStore = class {
|
|
|
2068
2097
|
};
|
|
2069
2098
|
}
|
|
2070
2099
|
async add(target, facts) {
|
|
2100
|
+
return await this.serial(() => this.addChained(target, facts));
|
|
2101
|
+
}
|
|
2102
|
+
async addChained(target, facts) {
|
|
2071
2103
|
if (!facts.trim()) return {
|
|
2072
2104
|
ok: false,
|
|
2073
2105
|
message: "Content cannot be empty.",
|
|
@@ -2173,6 +2205,9 @@ var MemoryStore = class {
|
|
|
2173
2205
|
return render(entries) !== raw;
|
|
2174
2206
|
}
|
|
2175
2207
|
async applyBatch(target, operations) {
|
|
2208
|
+
return await this.serial(() => this.applyBatchChained(target, operations));
|
|
2209
|
+
}
|
|
2210
|
+
async applyBatchChained(target, operations) {
|
|
2176
2211
|
if (operations.length === 0) return {
|
|
2177
2212
|
ok: false,
|
|
2178
2213
|
message: "operations list is empty.",
|
|
@@ -2246,6 +2281,17 @@ var MemoryStore = class {
|
|
|
2246
2281
|
if (!working.some((entry) => stripDatePrefix(entry) === body)) working.push(this.addDatePrefix ? `## ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}\n${body}` : body);
|
|
2247
2282
|
continue;
|
|
2248
2283
|
}
|
|
2284
|
+
const rawAction = op.action;
|
|
2285
|
+
if (rawAction !== "remove" && rawAction !== "replace") return {
|
|
2286
|
+
result: {
|
|
2287
|
+
ok: false,
|
|
2288
|
+
message: `Operation ${position}: unknown action "${String(rawAction)}" (expected add/remove/replace). No operations were applied.${previewEntries(entries)}`,
|
|
2289
|
+
entries,
|
|
2290
|
+
chars: entries.join(ENTRY_DELIMITER).length,
|
|
2291
|
+
limit: this.limitFor(target)
|
|
2292
|
+
},
|
|
2293
|
+
write: null
|
|
2294
|
+
};
|
|
2249
2295
|
const needle = (op.old_text ?? "").trim();
|
|
2250
2296
|
if (!needle) return {
|
|
2251
2297
|
result: {
|
|
@@ -2665,24 +2711,6 @@ function redactSecrets(text) {
|
|
|
2665
2711
|
return out;
|
|
2666
2712
|
}
|
|
2667
2713
|
//#endregion
|
|
2668
|
-
//#region lib/types/serial.js
|
|
2669
|
-
/**
|
|
2670
|
-
* A process-local serial task queue: each task starts only after the previous
|
|
2671
|
-
* one settles (success or failure), so read-modify-write sequences that share
|
|
2672
|
-
* one file never interleave inside this process. The durable cross-process
|
|
2673
|
-
* serialization layer is the IO backend's transact lock; this chain is the
|
|
2674
|
-
* second layer (0.3.17 S2.8, T-1: the shape was duplicated in state-json and
|
|
2675
|
-
* memory-files — one factory now).
|
|
2676
|
-
*/
|
|
2677
|
-
function makeSerialQueue() {
|
|
2678
|
-
let chain = Promise.resolve();
|
|
2679
|
-
return (task) => {
|
|
2680
|
-
const run = chain.then(task, task);
|
|
2681
|
-
chain = run.then(() => void 0, () => void 0);
|
|
2682
|
-
return run;
|
|
2683
|
-
};
|
|
2684
|
-
}
|
|
2685
|
-
//#endregion
|
|
2686
2714
|
//#region lib/types/skill-health.js
|
|
2687
2715
|
/**
|
|
2688
2716
|
* Skill structure-health domain (rc.73 A1, 008 design): a SECOND assessment
|
|
@@ -2772,6 +2800,7 @@ function observeEvent(signal, event) {
|
|
|
2772
2800
|
return;
|
|
2773
2801
|
}
|
|
2774
2802
|
if (event.type === "assistant/message") {
|
|
2803
|
+
if (!Array.isArray(event.data.message.content)) return;
|
|
2775
2804
|
const text = event.data.message.content.map((block) => block.type === "text" ? block.text : "").join(" ");
|
|
2776
2805
|
signal.assistantChars += text.length;
|
|
2777
2806
|
return;
|
|
@@ -3339,6 +3368,12 @@ function fuzzyIndexOf(content, pattern, from = 0) {
|
|
|
3339
3368
|
}
|
|
3340
3369
|
return null;
|
|
3341
3370
|
}
|
|
3371
|
+
/** V6-17 (0.3.37): the fuzzy-patch scan is O(n·m) with no input bound; a
|
|
3372
|
+
* non-exact anchor past these budgets would block the event loop (measured
|
|
3373
|
+
* ~6s at 20k×20k). Exact matches go through the fast `includes` path and stay
|
|
3374
|
+
* allowed regardless of size. */
|
|
3375
|
+
const FUZZY_MAX_PATTERN_CHARS = 4096;
|
|
3376
|
+
const FUZZY_MAX_WORK = 8e6;
|
|
3342
3377
|
/** Trim leading whitespace of the first line and trailing whitespace of the last line. */
|
|
3343
3378
|
function trimPatternBoundaries(pattern) {
|
|
3344
3379
|
const from = pattern.search(/\S/);
|
|
@@ -3483,6 +3518,10 @@ var SkillLibrary = class {
|
|
|
3483
3518
|
if (next !== null && next !== current) await this.io.writeText(path, next);
|
|
3484
3519
|
}
|
|
3485
3520
|
const o = outcome;
|
|
3521
|
+
if (o === void 0 || typeof o !== "object" || !Object.prototype.hasOwnProperty.call(o, "write")) return {
|
|
3522
|
+
ok: false,
|
|
3523
|
+
message: "internal error: the write transaction did not invoke the task; no write was performed"
|
|
3524
|
+
};
|
|
3486
3525
|
if (o.write !== null && o.audit) await this.audit(o.audit.skillName, o.audit.action, o.audit.before, o.audit.after, o.audit.summary);
|
|
3487
3526
|
if (o.write !== null && o.event) this.notifyMutation(o.event);
|
|
3488
3527
|
return o.result;
|
|
@@ -3875,6 +3914,13 @@ var SkillLibrary = class {
|
|
|
3875
3914
|
},
|
|
3876
3915
|
write: null
|
|
3877
3916
|
};
|
|
3917
|
+
if (!md.includes(oldString) && (oldString.length > FUZZY_MAX_PATTERN_CHARS || md.length * oldString.length > FUZZY_MAX_WORK)) return {
|
|
3918
|
+
result: {
|
|
3919
|
+
ok: false,
|
|
3920
|
+
message: `old_string too large for fuzzy match (${oldString.length} chars in ${patchLabel}); use update for a full rewrite or a narrower anchor.`
|
|
3921
|
+
},
|
|
3922
|
+
write: null
|
|
3923
|
+
};
|
|
3878
3924
|
const patched = fuzzyPatch(md, oldString, newString, replaceAll);
|
|
3879
3925
|
if (patched === null) return {
|
|
3880
3926
|
result: {
|
|
@@ -34,6 +34,12 @@ export declare class MemoryStore {
|
|
|
34
34
|
readonly root: string;
|
|
35
35
|
private readonly maxFailures;
|
|
36
36
|
private readonly io;
|
|
37
|
+
/** V6-16 (0.3.37): same-process RMW serialization (the SkillLibrary queue) —
|
|
38
|
+
* on a backend WITHOUT a transact lock two concurrent callers compute on the
|
|
39
|
+
* same old content and the last rename wins, silently dropping one op's
|
|
40
|
+
* update. The node backend's cross-process lock already serializes; this
|
|
41
|
+
* chain covers the no-transact custom backends. */
|
|
42
|
+
private readonly serial;
|
|
37
43
|
private failureCount;
|
|
38
44
|
private lastFailureAt;
|
|
39
45
|
constructor(options?: MemoryStoreOptions);
|
|
@@ -71,6 +77,7 @@ export declare class MemoryStore {
|
|
|
71
77
|
*/
|
|
72
78
|
private oversizedRefusal;
|
|
73
79
|
add(target: MemoryTarget, facts: string): Promise<MemoryApplyResult>;
|
|
80
|
+
private addChained;
|
|
74
81
|
/**
|
|
75
82
|
* Single-entry add inside the transaction: shared checks (oversized,
|
|
76
83
|
* drift, threat) and the content computation. `raw` is the locked view
|
|
@@ -80,6 +87,7 @@ export declare class MemoryStore {
|
|
|
80
87
|
/** Canonical-form drift check derived from the locked view (same formula as `detectDrift`, no second read). */
|
|
81
88
|
private driftFromRaw;
|
|
82
89
|
applyBatch(target: MemoryTarget, operations: MemoryOperation[]): Promise<MemoryApplyResult>;
|
|
90
|
+
private applyBatchChained;
|
|
83
91
|
/** Batch RMW inside the transaction. `write: null` = failure/no-op, disk untouched. */
|
|
84
92
|
private applyBatchCore;
|
|
85
93
|
renderContext(): Promise<string>;
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* `computeQualityScores` (different dimension, different consumers).
|
|
11
11
|
*/
|
|
12
12
|
export interface SkillHealthThresholds {
|
|
13
|
-
/** Soft body limit: body
|
|
14
|
-
* '
|
|
13
|
+
/** Soft body limit: a body of `softBodyChars` or MORE -> 'warn'; >= 2x ->
|
|
14
|
+
* 'needs-restructure'. V6-34 (0.3.37): the doc comment used to claim
|
|
15
|
+
* "at/below stay healthy" while the engine warns at `>=` — fixed to the
|
|
16
|
+
* implementation edge (the reason copy said "above"). */
|
|
15
17
|
softBodyChars: number;
|
|
16
18
|
/** Stamp-density ceiling per KB of body text: rc.NN / commit shas / ISO
|
|
17
19
|
* dates per KB at/above this -> 'warn' (log-like content living in the
|
package/lib/types/usage.d.ts
CHANGED
|
@@ -73,6 +73,10 @@ export declare function applyCuratorMetaFields(disk: UsageRecord, curated: Usage
|
|
|
73
73
|
* by a stale snapshot; without it both pairs apply everywhere.
|
|
74
74
|
*/
|
|
75
75
|
export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap, stateOwned?: ReadonlySet<string>): void;
|
|
76
|
+
/** Whole-file usage write (V6-37, 0.3.37): this is the ONE path that bypasses
|
|
77
|
+
* the malformed-defense and the transact lock — prefer `mutateUsage` for any
|
|
78
|
+
* read-modify-write so a concurrent writer cannot lose its update and a
|
|
79
|
+
* malformed sidecar stays recoverable. Kept for fixture/test seeding. */
|
|
76
80
|
export declare function saveUsage(root: string, map: UsageMap, io?: EvolutionIoLike): Promise<void>;
|
|
77
81
|
export declare function getRecord(map: UsageMap, name: string): UsageRecord;
|
|
78
82
|
export declare function bumpView(map: UsageMap, name: string, when?: Date): void;
|
package/package.json
CHANGED