@lmzhen/dsh-evolution-core 0.30.36 → 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.
- package/lib/index.js +3776 -2300
- package/lib/types/evolution-events.d.ts +6 -2
- package/lib/types/evolution-events.js +6 -0
- package/lib/types/extract-plan.js +17 -11
- package/lib/types/extract-prompt.d.ts +1 -5
- package/lib/types/extract-prompt.js +2 -0
- package/lib/types/extract-request.d.ts +32 -0
- package/lib/types/extract-request.js +155 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/index.js +4 -0
- package/lib/types/mutation-storage-adapter.d.ts +14 -0
- package/lib/types/mutation-storage-adapter.js +107 -0
- package/lib/types/mutation-unit.d.ts +164 -0
- package/lib/types/mutation-unit.js +509 -0
- package/lib/types/prompts.d.ts +3 -3
- package/lib/types/prompts.js +8 -8
- package/lib/types/proposal-view.d.ts +4 -0
- package/lib/types/proposal-view.js +3 -1
- package/lib/types/skill-health.d.ts +5 -0
- package/lib/types/skill-health.js +3 -0
- package/lib/types/skill-history.d.ts +2 -0
- package/lib/types/skill-history.js +3 -0
- package/lib/types/skill-lint.d.ts +2 -0
- package/lib/types/skill-lint.js +13 -2
- package/lib/types/skill-model.d.ts +45 -0
- package/lib/types/skill-model.js +74 -0
- package/lib/types/skill-store.d.ts +22 -1
- package/lib/types/skill-store.js +415 -274
- package/lib/types/unit-members.js +3 -3
- package/lib/types/unit-storage.d.ts +10 -0
- package/lib/types/unit-transaction.d.ts +1 -1
- package/lib/types/unit-undo-plan.js +5 -3
- package/lib/types/unit-undo.d.ts +1 -1
- package/lib/types/unit-undo.js +18 -3
- package/package.json +1 -1
- package/persisted-write-inventory.json +3 -3
|
@@ -23,6 +23,8 @@ export interface SkillHealthThresholds {
|
|
|
23
23
|
/** Patch count at/above this with zero reads -> 'warn' (write-ghost: the
|
|
24
24
|
* skill is churned but nothing ever loads it). */
|
|
25
25
|
churnMinPatches: number;
|
|
26
|
+
/** Hard-cap hits at/above this with a large support closure -> scope-overloaded advisory. */
|
|
27
|
+
hardCapMinHits: number;
|
|
26
28
|
}
|
|
27
29
|
export declare const DEFAULT_HEALTH_THRESHOLDS: SkillHealthThresholds;
|
|
28
30
|
/**
|
|
@@ -67,6 +69,8 @@ interface SkillHealthSnapshot {
|
|
|
67
69
|
patchCount?: number | undefined;
|
|
68
70
|
/** Usage-side view count, when the caller has it (A2 churn dimension). */
|
|
69
71
|
readCount?: number | undefined;
|
|
72
|
+
/** Mechanical hard-cap-hit count, when the caller has it; null = unknown. */
|
|
73
|
+
hardCapHits?: number | null | undefined;
|
|
70
74
|
}
|
|
71
75
|
interface SkillHealthDim {
|
|
72
76
|
bodyChars: number;
|
|
@@ -81,6 +85,7 @@ interface SkillHealthDim {
|
|
|
81
85
|
/** Usage churn facts; null when the caller supplied no counts. */
|
|
82
86
|
churnPatches: number | null;
|
|
83
87
|
churnReads: number | null;
|
|
88
|
+
hardCapHits: number | null;
|
|
84
89
|
}
|
|
85
90
|
export interface SkillHealthAssessment {
|
|
86
91
|
verdict: SkillHealthVerdict;
|
|
@@ -23,6 +23,7 @@ export const DEFAULT_HEALTH_THRESHOLDS = {
|
|
|
23
23
|
softBodyChars: AUTHORING_SPLIT_LINE_CHARS,
|
|
24
24
|
stampDensityPerKb: 2,
|
|
25
25
|
churnMinPatches: 20,
|
|
26
|
+
hardCapMinHits: 3,
|
|
26
27
|
};
|
|
27
28
|
/**
|
|
28
29
|
* Stamp regex shared by health assessment and the maintenance probe (single
|
|
@@ -72,6 +73,7 @@ export function assessStructureHealth(snapshot, thresholds = DEFAULT_HEALTH_THRE
|
|
|
72
73
|
supportFiles: snapshot.supportFiles ?? null,
|
|
73
74
|
churnPatches: null,
|
|
74
75
|
churnReads: null,
|
|
76
|
+
hardCapHits: snapshot.hardCapHits ?? null,
|
|
75
77
|
};
|
|
76
78
|
// V6: the JUDGMENT runs on the token scale. The configured number stays the
|
|
77
79
|
// borrowed UPSTREAM character line, converted per body composition, so the
|
|
@@ -115,6 +117,7 @@ export function assessStructureHealth(snapshot, thresholds = DEFAULT_HEALTH_THRE
|
|
|
115
117
|
overTwiceSoftLine: overTwice,
|
|
116
118
|
stampLimitPerKb: thresholds.stampDensityPerKb,
|
|
117
119
|
churnMinPatches: thresholds.churnMinPatches,
|
|
120
|
+
hardCapMinHits: thresholds.hardCapMinHits,
|
|
118
121
|
// This engine's snapshot always carries a MEASURED body (its type has no null arm yet); the
|
|
119
122
|
// unreadable case becomes expressible when the caller's type does, and the rule already waits.
|
|
120
123
|
readable: true,
|
|
@@ -73,6 +73,8 @@ export interface SkillVersion {
|
|
|
73
73
|
* can interleave (the index is appended after the content lock is released); this link is computed
|
|
74
74
|
* from the bytes a write lock actually read, so it is the authoritative predecessor. */
|
|
75
75
|
beforeHash?: string;
|
|
76
|
+
/** True on the synthetic entry that records a REMOVAL; its hash is the removed bytes. */
|
|
77
|
+
removed?: boolean;
|
|
76
78
|
/** One line saying what this write changed, when the deployment turned the summarizer on
|
|
77
79
|
* (registry `skillVersionSummary`). Absent is a first-class state: the faces say so instead of
|
|
78
80
|
* inventing a sentence, and a version recorded before the feature existed simply has none. */
|
|
@@ -213,6 +213,7 @@ function readVersionEntry(entry) {
|
|
|
213
213
|
const summary = typeof candidate.summary === 'string' && candidate.summary !== '' ? candidate.summary : undefined;
|
|
214
214
|
const path = typeof candidate.path === 'string' && candidate.path !== '' ? candidate.path : undefined;
|
|
215
215
|
const unitId = typeof candidate.unitId === 'string' && candidate.unitId !== '' ? candidate.unitId : undefined;
|
|
216
|
+
const removed = candidate.removed === true ? true : undefined;
|
|
216
217
|
// The fold must carry unitHead or the next write drops it for good (the G3a lesson).
|
|
217
218
|
const unitHead = candidate.unitHead;
|
|
218
219
|
const base = {
|
|
@@ -224,6 +225,7 @@ function readVersionEntry(entry) {
|
|
|
224
225
|
...summary === undefined ? {} : { summary },
|
|
225
226
|
...path === undefined ? {} : { path },
|
|
226
227
|
...unitId === undefined ? {} : { unitId },
|
|
228
|
+
...removed === undefined ? {} : { removed },
|
|
227
229
|
...unitHead === undefined ? {} : { unitHead },
|
|
228
230
|
};
|
|
229
231
|
return typeof candidate.beforeHash === 'string' && candidate.beforeHash !== ''
|
|
@@ -353,6 +355,7 @@ export function nextHistoryIndex(versions, input, keep) {
|
|
|
353
355
|
action: input.action,
|
|
354
356
|
hash: removalHash,
|
|
355
357
|
chars: input.before?.length ?? 0,
|
|
358
|
+
removed: true,
|
|
356
359
|
...pathField,
|
|
357
360
|
...input.unitId === undefined ? {} : { unitId: input.unitId },
|
|
358
361
|
// F-G3b-1: a removal-only landing IS the last entry of its unit, so it carries the head;
|
|
@@ -21,6 +21,7 @@ export interface SkillDims {
|
|
|
21
21
|
stampHits: number | null;
|
|
22
22
|
churnPatches: number | null;
|
|
23
23
|
churnReads: number | null;
|
|
24
|
+
hardCapHits: number | null;
|
|
24
25
|
}
|
|
25
26
|
/** The judgement itself, computed by `skill-health.ts` on its own scales; this module never
|
|
26
27
|
* recomputes it from raw numbers (that is exactly how a second set of thresholds appears). */
|
|
@@ -33,6 +34,7 @@ export interface LintFacts {
|
|
|
33
34
|
stampLimitPerKb: number;
|
|
34
35
|
/** The engine's own churn threshold — the rule must use the SAME number, not a literal. */
|
|
35
36
|
churnMinPatches: number;
|
|
37
|
+
hardCapMinHits: number;
|
|
36
38
|
/** True when the caller could read the body at all. */
|
|
37
39
|
readable: boolean;
|
|
38
40
|
}
|
package/lib/types/skill-lint.js
CHANGED
|
@@ -25,14 +25,14 @@ export const RULES = [
|
|
|
25
25
|
id: 'body-oversize-2x',
|
|
26
26
|
severity: 'error',
|
|
27
27
|
run: facts => facts.overTwiceSoftLine
|
|
28
|
-
? { severity: 'error', rule: 'body-oversize-2x', message: msg([facts.skillName, ': body is at or past twice the soft line (', facts.softLineChars, ' chars)']), count: 1 }
|
|
28
|
+
? { severity: 'error', rule: 'body-oversize-2x', message: msg([facts.skillName, ': body is at or past twice the soft line (', facts.softLineChars, ' chars); keeping the current skill is valid']), count: 1 }
|
|
29
29
|
: null,
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
id: 'body-oversize',
|
|
33
33
|
severity: 'warn',
|
|
34
34
|
run: facts => !facts.overTwiceSoftLine && facts.overSoftLine
|
|
35
|
-
? { severity: 'warn', rule: 'body-oversize', message: msg([facts.skillName, ': body is at or above the soft line (', facts.softLineChars, ' chars)']), count: 1 }
|
|
35
|
+
? { severity: 'warn', rule: 'body-oversize', message: msg([facts.skillName, ': body is at or above the soft line (', facts.softLineChars, ' chars); keep is valid — size alone does not require a split']), count: 1 }
|
|
36
36
|
: null,
|
|
37
37
|
},
|
|
38
38
|
{
|
|
@@ -63,6 +63,17 @@ export const RULES = [
|
|
|
63
63
|
return { severity: 'warn', rule: 'references-sprawl', message: msg([facts.skillName, ': ', files, ' support files']), count: files };
|
|
64
64
|
},
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
id: 'scope-overloaded',
|
|
68
|
+
severity: 'warn',
|
|
69
|
+
run: (facts) => {
|
|
70
|
+
const hits = facts.dims.hardCapHits;
|
|
71
|
+
const files = facts.dims.supportFiles;
|
|
72
|
+
if (hits === null || files === null || hits < facts.hardCapMinHits || files < 3)
|
|
73
|
+
return null;
|
|
74
|
+
return { severity: 'warn', rule: 'scope-overloaded', message: msg([facts.skillName, ': ', hits, ' hard-cap hit(s) with ', files, ' support files — remaining growth may be scope']), count: hits };
|
|
75
|
+
},
|
|
76
|
+
},
|
|
66
77
|
{
|
|
67
78
|
id: 'write-ghost',
|
|
68
79
|
severity: 'warn',
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SkillSummary } from './skill-store.ts';
|
|
2
|
+
export interface SkillListQuery {
|
|
3
|
+
withContent?: boolean;
|
|
4
|
+
withFrontmatter?: boolean;
|
|
5
|
+
withSupport?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SkillSupportFile {
|
|
8
|
+
path: string;
|
|
9
|
+
bytes: number;
|
|
10
|
+
sha256: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SkillDocumentHashes {
|
|
13
|
+
threeField: string;
|
|
14
|
+
content: string;
|
|
15
|
+
support: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SkillDocument {
|
|
18
|
+
summary: SkillSummary;
|
|
19
|
+
content: string;
|
|
20
|
+
frontmatter: Record<string, unknown>;
|
|
21
|
+
support: SkillSupportFile[];
|
|
22
|
+
hashes: SkillDocumentHashes;
|
|
23
|
+
readable: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface SkillModelProvider {
|
|
26
|
+
list(query?: SkillListQuery): Promise<SkillSummary[]>;
|
|
27
|
+
read(name: string): Promise<string | null>;
|
|
28
|
+
listSupportFiles(name: string): Promise<readonly string[]>;
|
|
29
|
+
readSupportFile(name: string, path: string): Promise<string | null>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Canonical read face for skill consumers.
|
|
33
|
+
*
|
|
34
|
+
* This is an adapter, not a second store: the provider owns IO and summary
|
|
35
|
+
* extraction. Every consumer reads the same SkillDocument contract so owner,
|
|
36
|
+
* health, panel and future domain/search surfaces cannot drift on parsing,
|
|
37
|
+
* whenToUse or support hashing.
|
|
38
|
+
*/
|
|
39
|
+
export declare class SkillModel {
|
|
40
|
+
private readonly provider;
|
|
41
|
+
constructor(provider: SkillModelProvider);
|
|
42
|
+
list(query?: SkillListQuery): Promise<SkillSummary[]>;
|
|
43
|
+
get(name: string): Promise<SkillDocument | null>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=skill-model.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { contentHash } from "./mutations.js";
|
|
2
|
+
function canonical(value) {
|
|
3
|
+
if (value === null || typeof value !== 'object')
|
|
4
|
+
return JSON.stringify(value);
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return '[' + value.map(canonical).join(',') + ']';
|
|
7
|
+
const record = value;
|
|
8
|
+
return '{' + Object.keys(record).sort().map(key => JSON.stringify(key) + ':' + canonical(record[key])).join(',') + '}';
|
|
9
|
+
}
|
|
10
|
+
function threeFieldHash(summary) {
|
|
11
|
+
return contentHash(canonical([
|
|
12
|
+
summary.name,
|
|
13
|
+
summary.description,
|
|
14
|
+
summary.whenToUse ?? '',
|
|
15
|
+
]));
|
|
16
|
+
}
|
|
17
|
+
function supportHash(files) {
|
|
18
|
+
return contentHash(canonical(files.map(file => [file.path, file.sha256, file.bytes])));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Canonical read face for skill consumers.
|
|
22
|
+
*
|
|
23
|
+
* This is an adapter, not a second store: the provider owns IO and summary
|
|
24
|
+
* extraction. Every consumer reads the same SkillDocument contract so owner,
|
|
25
|
+
* health, panel and future domain/search surfaces cannot drift on parsing,
|
|
26
|
+
* whenToUse or support hashing.
|
|
27
|
+
*/
|
|
28
|
+
export class SkillModel {
|
|
29
|
+
provider;
|
|
30
|
+
constructor(provider) {
|
|
31
|
+
this.provider = provider;
|
|
32
|
+
}
|
|
33
|
+
async list(query = {}) {
|
|
34
|
+
return await this.provider.list(query);
|
|
35
|
+
}
|
|
36
|
+
async get(name) {
|
|
37
|
+
const summaries = await this.provider.list({ withFrontmatter: true });
|
|
38
|
+
const summary = summaries.find(candidate => candidate.name === name);
|
|
39
|
+
if (summary === undefined)
|
|
40
|
+
return null;
|
|
41
|
+
let content = null;
|
|
42
|
+
let readable = true;
|
|
43
|
+
try {
|
|
44
|
+
content = await this.provider.read(name);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
readable = false;
|
|
48
|
+
}
|
|
49
|
+
if (content === null)
|
|
50
|
+
readable = false;
|
|
51
|
+
const paths = readable ? await this.provider.listSupportFiles(name) : [];
|
|
52
|
+
const support = [];
|
|
53
|
+
for (const path of paths) {
|
|
54
|
+
const file = await this.provider.readSupportFile(name, path);
|
|
55
|
+
if (file === null)
|
|
56
|
+
continue;
|
|
57
|
+
support.push({ path, bytes: Buffer.byteLength(file, 'utf8'), sha256: contentHash(file) });
|
|
58
|
+
}
|
|
59
|
+
support.sort((a, b) => a.path.localeCompare(b.path));
|
|
60
|
+
return {
|
|
61
|
+
summary,
|
|
62
|
+
content: content ?? '',
|
|
63
|
+
frontmatter: summary.frontmatter ?? {},
|
|
64
|
+
support,
|
|
65
|
+
hashes: {
|
|
66
|
+
threeField: threeFieldHash(summary),
|
|
67
|
+
content: contentHash(content ?? ''),
|
|
68
|
+
support: supportHash(support),
|
|
69
|
+
},
|
|
70
|
+
readable,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=skill-model.js.map
|
|
@@ -36,6 +36,8 @@ import { type WriteAuthority } from './write-authority.ts';
|
|
|
36
36
|
import { transactIo, type EvolutionIoLike } from './io.ts';
|
|
37
37
|
import { type Probe } from './probe.ts';
|
|
38
38
|
import type { SkillLimits } from './limits.ts';
|
|
39
|
+
import { type MutationResult } from './mutation-unit.ts';
|
|
40
|
+
import { SkillModel } from './skill-model.ts';
|
|
39
41
|
import { type ExtractPlanDraft } from './extract-plan.ts';
|
|
40
42
|
import { type MutationRecord } from './mutations.ts';
|
|
41
43
|
import { type RevisionAnchor, type RevisionFile } from './skill-revision.ts';
|
|
@@ -326,6 +328,8 @@ export declare function validateRestructureTarget(filePath: string): string | nu
|
|
|
326
328
|
export declare function validateSupportPath(filePath: string): string | null;
|
|
327
329
|
export declare class SkillLibrary {
|
|
328
330
|
readonly root: string;
|
|
331
|
+
/** The ONE read face owner/health/panel consume; a thin adapter, not a store. */
|
|
332
|
+
readonly model: SkillModel;
|
|
329
333
|
readonly limits: SkillLimits;
|
|
330
334
|
private readonly io;
|
|
331
335
|
private readonly onMutation;
|
|
@@ -349,6 +353,20 @@ export declare class SkillLibrary {
|
|
|
349
353
|
* but it is a deployment condition, not a per-write event, so it warns once per instance. */
|
|
350
354
|
private historyWarned;
|
|
351
355
|
constructor(root?: string, io?: EvolutionIoLike, limits?: SkillLimits, onMutation?: (event: EvolutionSkillMutatedEvent) => void, transact?: typeof transactIo, threatExemptLabels?: readonly string[], authority?: WriteAuthority);
|
|
356
|
+
/**
|
|
357
|
+
* Transitional root lock for mutators that still own their write path. The
|
|
358
|
+
* extract/restructure path takes the same lock through MutationUnit, so those
|
|
359
|
+
* two paths cannot interleave with create/update/patch across processes.
|
|
360
|
+
*/
|
|
361
|
+
private unitHeadForOps;
|
|
362
|
+
private withMutationLock;
|
|
363
|
+
private recovery;
|
|
364
|
+
/** Run the boot recovery once per library instance, before the first write. */
|
|
365
|
+
private ensureRecovered;
|
|
366
|
+
/** Public boot hook: idempotent with the first-write recovery. */
|
|
367
|
+
recoverOnBoot(): Promise<void>;
|
|
368
|
+
/** Recover every prepared tree-change snapshot in the shared unit store. */
|
|
369
|
+
recoverPreparedUnits(): Promise<MutationResult[]>;
|
|
352
370
|
/**
|
|
353
371
|
* Run one single-file read-modify-write for a mutator. When `transact` was
|
|
354
372
|
* injected the read and the write run inside it (cross-process atomicity);
|
|
@@ -358,7 +376,6 @@ export declare class SkillLibrary {
|
|
|
358
376
|
* mutation event are issued ONLY when a write actually lands, so a no-op
|
|
359
377
|
* never inflates the mutation-maturity counter.
|
|
360
378
|
*/
|
|
361
|
-
private runSingleWrite;
|
|
362
379
|
/**
|
|
363
380
|
* v28 G1.1 (EVO-IO-02): shared compensating cleanup for a locked write that
|
|
364
381
|
* found no SKILL.md — remove the possibly-resurrected directory ONLY when it
|
|
@@ -519,7 +536,10 @@ export declare class SkillLibrary {
|
|
|
519
536
|
assessHealth(rawName: string, thresholds?: SkillHealthThresholds, counts?: {
|
|
520
537
|
patchCount?: number;
|
|
521
538
|
readCount?: number;
|
|
539
|
+
hardCapHits?: number | null;
|
|
522
540
|
}): Promise<SkillHealthAssessment | null>;
|
|
541
|
+
/** S2: mechanical hard-cap event; failure must not change the refusal. */
|
|
542
|
+
private recordHardCapHit;
|
|
523
543
|
/** Best-effort audit trail entry; never blocks the mutation.
|
|
524
544
|
*
|
|
525
545
|
* Two independent best-effort writes run here, because either one failing must not cost the
|
|
@@ -565,6 +585,7 @@ export declare class SkillLibrary {
|
|
|
565
585
|
create(name: string, content: string, origin?: WriteOrigin, options?: {
|
|
566
586
|
unitId?: string;
|
|
567
587
|
unitHead?: UnitHead;
|
|
588
|
+
managed?: boolean;
|
|
568
589
|
}): Promise<SkillActionResult>;
|
|
569
590
|
private createCore;
|
|
570
591
|
update(rawName: string, content: string, origin?: WriteOrigin, anchor?: WriteAnchor): Promise<SkillActionResult>;
|