@lmzhen/dsh-evolution-core 0.3.67 → 0.3.68
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 +528 -196
- package/lib/types/constants.d.ts +14 -0
- package/lib/types/curator.d.ts +14 -0
- package/lib/types/events.d.ts +10 -0
- package/lib/types/evolution-events.d.ts +9 -6
- package/lib/types/io.d.ts +65 -0
- package/lib/types/memory-store.d.ts +48 -13
- package/lib/types/skill-store.d.ts +84 -13
- package/lib/types/threats.d.ts +3 -0
- package/package.json +1 -1
package/lib/types/constants.d.ts
CHANGED
|
@@ -83,4 +83,18 @@ export declare const EVOLUTION_WRITE_TOOLS: readonly ["memory", "skill_manage"];
|
|
|
83
83
|
* 0.3.16 (T-4): moved here from skill-store.ts so drift-signals (pure, no IO)
|
|
84
84
|
* can reference it without importing the skill-store module. */
|
|
85
85
|
export declare const AUTHORING_DESCRIPTION_BAR = 60;
|
|
86
|
+
/** V27 G2.4: the largest millisecond delay a timer accepts. `AbortSignal.timeout`
|
|
87
|
+
* (and `setTimeout`) coerce anything larger to 1ms after a Node warning, so a
|
|
88
|
+
* timeout configured above this ceiling silently collapses to "immediately
|
|
89
|
+
* aborted". The curator's review timeout and the review timeout each carried
|
|
90
|
+
* their own copy of the literal; the bound is one protocol constant.
|
|
91
|
+
* (v19 P2-10 corrected the value from 2^32-1 to Node's real 2^31-1 ceiling.) */
|
|
92
|
+
export declare const MAX_TIMER_DELAY_MS = 2147483647;
|
|
93
|
+
/** V27 G2.4: the model each review/curation leg defaults to. The policy schema,
|
|
94
|
+
* the policy resolver and the curator's LLM nomination pass each carried their
|
|
95
|
+
* own copy of these strings — a deployment that changed the policy default used
|
|
96
|
+
* to leave the curator passing a different model than the reviews. */
|
|
97
|
+
export declare const DEFAULT_MEMORY_REVIEW_MODEL = "deepseek-v4-flash";
|
|
98
|
+
export declare const DEFAULT_SKILL_REVIEW_MODEL = "deepseek-v4-pro";
|
|
99
|
+
export declare const DEFAULT_CURATOR_MODEL = "deepseek-v4-pro";
|
|
86
100
|
//# sourceMappingURL=constants.d.ts.map
|
package/lib/types/curator.d.ts
CHANGED
|
@@ -57,7 +57,17 @@ export interface CuratorRunReport {
|
|
|
57
57
|
llmNominations: string[];
|
|
58
58
|
archiveCandidates: string[];
|
|
59
59
|
archived: CuratorArchivedSkill[];
|
|
60
|
+
/** Failures attributable to a SKILL NAME (`<name>: …`). V27 CUR-2: this field
|
|
61
|
+
* is the skill-attributable subset only — run-level facts (an abort, a phase
|
|
62
|
+
* that could not start) live in `aborted`/`unattributed` so a report can no
|
|
63
|
+
* longer show `failed: 0` for a run that was cut short. */
|
|
60
64
|
failed: CuratorFailedSkill[];
|
|
65
|
+
/** V27 CUR-2: the run stopped early (e.g. disposed mid-run, timeout). The
|
|
66
|
+
* string names the phase that did not complete; absent on a full run. */
|
|
67
|
+
aborted?: string;
|
|
68
|
+
/** V27 CUR-2: error strings that could not be attributed to a skill name.
|
|
69
|
+
* They were previously collected in memory and then dropped on write. */
|
|
70
|
+
unattributed?: string[];
|
|
61
71
|
/** Consolidations actually executed this run (source absorbed into target). */
|
|
62
72
|
consolidated?: CuratorConsolidation[];
|
|
63
73
|
snapshotPath?: string;
|
|
@@ -75,6 +85,10 @@ export interface CuratorReportInput {
|
|
|
75
85
|
archiveCandidates: readonly string[];
|
|
76
86
|
archived: readonly CuratorArchivedSkill[];
|
|
77
87
|
failed: readonly CuratorFailedSkill[];
|
|
88
|
+
/** V27 CUR-2: run-level abort reason (see CuratorRunReport.aborted). */
|
|
89
|
+
aborted?: string;
|
|
90
|
+
/** V27 CUR-2: errors with no skill name to attribute them to. */
|
|
91
|
+
unattributed?: readonly string[];
|
|
78
92
|
consolidated?: readonly CuratorConsolidation[];
|
|
79
93
|
snapshotPath?: string;
|
|
80
94
|
llmReviewEnabled?: boolean;
|
package/lib/types/events.d.ts
CHANGED
|
@@ -39,12 +39,22 @@ export interface EvolutionPlanAppliedEvent {
|
|
|
39
39
|
policyFingerprint?: string | undefined;
|
|
40
40
|
memoryApplied: number;
|
|
41
41
|
skillApplied: number;
|
|
42
|
+
/** Validation rejects ONLY (see the contract note below). V27 R-03: a plan op
|
|
43
|
+
* that was skipped because the session had not read the skill is NOT a
|
|
44
|
+
* validation reject and now travels in `skippedUnread`. */
|
|
42
45
|
rejectedOps: number;
|
|
43
46
|
/** 0.3.31 (V5-19): execution-layer failures — ops that reached execution but
|
|
44
47
|
* did not land (non-throw `ok:false` results). `rejectedOps` counts only
|
|
45
48
|
* VALIDATION rejects; a consumer that treats rejectedOps as "work not done"
|
|
46
49
|
* would otherwise miss a partial application. */
|
|
47
50
|
executionFailures?: number | undefined;
|
|
51
|
+
/** V27 R-03: "not done" has a third, independent cause — an op naming a skill
|
|
52
|
+
* this session never read (the review pipeline refuses to touch unread
|
|
53
|
+
* skills). It is neither a validation reject nor an execution failure, so it
|
|
54
|
+
* has its own field; folding it into `rejectedOps` broke that field's stated
|
|
55
|
+
* contract and made the replay leaderboard penalize one refusal twice
|
|
56
|
+
* (rejectedOps weight AND the executionFailures dimension). */
|
|
57
|
+
skippedUnread?: number | undefined;
|
|
48
58
|
/** First execution-layer failure message (abort reason or op failure). */
|
|
49
59
|
executionError?: string | undefined;
|
|
50
60
|
evidenceQuotes?: number | undefined;
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Usage events (C semantics, rc.73+): `type:'usage'` records are the
|
|
10
10
|
* OBSERVATION WINDOW ANCHOR — written once, when the library's first observed
|
|
11
|
-
* read (`view_count` 0 -> 1) happens.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* and `window.opened` pins
|
|
11
|
+
* read (`view_count` 0 -> 1) happens. The anchor is the durable timeline record
|
|
12
|
+
* of that moment: the churn-suppression gate itself (`usageObserved()`) reads
|
|
13
|
+
* the usage SIDECAR's own first-view evidence, since reads were invisible to it
|
|
14
|
+
* pre-A2 and no sidecar record can reach `view_count > 0` without the same
|
|
15
|
+
* 0 -> 1 transition. `counts` on the event is a cumulative library-wide
|
|
16
|
+
* snapshot (skills/views/use/patches) at that moment, and `window.opened` pins
|
|
17
|
+
* the window start for the timeline. (V27 G3.3: `verify-event-pairing` requires
|
|
18
|
+
* every persisted type to have a production reader or a declared external
|
|
19
|
+
* contract — this one is the latter.)
|
|
17
20
|
*
|
|
18
21
|
* Rotation (rc.71, 007 design): when the active log reaches
|
|
19
22
|
* `EVENT_LOG_ROTATE_AT` the older half is split into an archive
|
package/lib/types/io.d.ts
CHANGED
|
@@ -146,6 +146,71 @@ export declare const LOCK_BODY_RE: RegExp;
|
|
|
146
146
|
* `*.lock`) — callers leave such files alone. A shape-matching body always
|
|
147
147
|
* yields a number (possibly `0`, which `isProcessAlive` treats as dead). */
|
|
148
148
|
export declare function parseLockBody(body: string): number | null;
|
|
149
|
+
/**
|
|
150
|
+
* V27 G0.2 (EVO-IO-01): the write lock named by this claim is no longer ours
|
|
151
|
+
* at the commit point — a takeover reclaimed it while we were inside the
|
|
152
|
+
* critical section. The lock layer converts this into a retry of the whole
|
|
153
|
+
* read-modify-write; it must never reach a caller as a successful write.
|
|
154
|
+
*/
|
|
155
|
+
export declare class LostWriteLock extends Error {
|
|
156
|
+
constructor();
|
|
157
|
+
}
|
|
158
|
+
/** V27 G1.1: the three takeover windows, as protocol constants at ONE place
|
|
159
|
+
* (the inline copies that used to live in the acquisition loop, plus the dead
|
|
160
|
+
* branch's bare `1000`, are gone). */
|
|
161
|
+
/** A named holder that is gone, past this age, is reclaimed. Fits inside the
|
|
162
|
+
* `lockAttempts * 50ms` retry budget so a dead holder's lock is reachable
|
|
163
|
+
* within one budget (v19 gate arithmetic: budget >= 2 x threshold). */
|
|
164
|
+
export declare const DEAD_LOCK_TAKEOVER_MS = 1000;
|
|
165
|
+
/** No body at all: nothing attributes the lock to a holder, so this is the one
|
|
166
|
+
* branch that cannot probe liveness — it must outlast any plausible stall
|
|
167
|
+
* between create and body write (V27 G0.2: 1s was below what a loaded machine
|
|
168
|
+
* actually took, which let a peer delete a live holder's lock). */
|
|
169
|
+
export declare const EMPTY_LOCK_TAKEOVER_MS = 30000;
|
|
170
|
+
/** A body with no parseable pid (crash mid-write): 1h, far above any legal hold
|
|
171
|
+
* and far below "forever". */
|
|
172
|
+
export declare const LOCK_TEAR_TAKEOVER_MS = 3600000;
|
|
173
|
+
/**
|
|
174
|
+
* V27 G1.3: the error `commitTmp` throws when the rename landed but the parent
|
|
175
|
+
* directory fsync failed — the bytes ARE visible, only their durability is
|
|
176
|
+
* unconfirmed. A consumer that treats it as a plain failure reports "not
|
|
177
|
+
* written" for a write that happened (and a two-phase caller may try to roll
|
|
178
|
+
* back a visible write). Every transaction consumer must therefore treat this
|
|
179
|
+
* shape as SUCCESS-with-warning, never as a rejection.
|
|
180
|
+
*/
|
|
181
|
+
export declare function isCommittedWarning(error: unknown): boolean;
|
|
182
|
+
/** V27 G1.1: the takeover branches, as a value. */
|
|
183
|
+
export type TakeoverDecision = 'none' | 'dead' | 'empty' | 'corrupt';
|
|
184
|
+
/** V27 G1.1: one lock observation, plus the liveness probe for its pid. */
|
|
185
|
+
export interface TakeoverProbe {
|
|
186
|
+
/** Raw lock body. An empty string means the file exists with no content. */
|
|
187
|
+
body: string;
|
|
188
|
+
/** Lock mtime in epoch ms. */
|
|
189
|
+
mtimeMs: number;
|
|
190
|
+
/** Liveness probe for a pid (injected so the decision is a pure function). */
|
|
191
|
+
alive: (pid: number) => boolean;
|
|
192
|
+
/** Evaluation instant (defaults to now). */
|
|
193
|
+
nowMs?: number;
|
|
194
|
+
/** Threshold overrides — production callers use the protocol defaults. */
|
|
195
|
+
deadAfterMs?: number;
|
|
196
|
+
emptyAfterMs?: number;
|
|
197
|
+
corruptAfterMs?: number;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* V27 G1.1: the lock-takeover decision as ONE pure function, so the protocol is
|
|
201
|
+
* testable and exhaustive instead of being an inline expression inside the
|
|
202
|
+
* acquisition loop:
|
|
203
|
+
* - `none` the lock is fresh, or its holder is alive → wait, never steal;
|
|
204
|
+
* - `dead` a named holder that is gone, past the dead threshold;
|
|
205
|
+
* - `empty` no body at all: nothing attributes it to a holder, so only the
|
|
206
|
+
* wide `emptyAfterMs` window may reclaim it;
|
|
207
|
+
* - `corrupt` a body with no parseable pid (a crash mid-write), past the 1h
|
|
208
|
+
* tear threshold.
|
|
209
|
+
* The age thresholds compare against `nowMs - mtimeMs` with `>` so a lock whose
|
|
210
|
+
* age EQUALS the threshold is not yet reclaimed (the boundary the v19 gate fix
|
|
211
|
+
* pinned).
|
|
212
|
+
*/
|
|
213
|
+
export declare function decideTakeover(probe: TakeoverProbe): TakeoverDecision;
|
|
149
214
|
/**
|
|
150
215
|
* Build the Node IO backend. `lockAttempts` scales the write-lock retry budget
|
|
151
216
|
* (attempts × 50ms); the default 40 (~2s, rc.69) covers production contention,
|
|
@@ -100,6 +100,18 @@ export declare class MemoryStore {
|
|
|
100
100
|
* silently overwritten.
|
|
101
101
|
*/
|
|
102
102
|
private oversizedRefusal;
|
|
103
|
+
/**
|
|
104
|
+
* The ONE memory write skeleton (V27 G2.5): oversized read-guard pre-transact,
|
|
105
|
+
* one transaction over the target path, and the C-01 structured refusal when a
|
|
106
|
+
* backend never invokes the task. `addChained` and `applyBatchChained` supply
|
|
107
|
+
* only their own in-transaction core, so the two write paths cannot drift in
|
|
108
|
+
* their guard order, their missing-file handling or their error text.
|
|
109
|
+
*
|
|
110
|
+
* @param target - memory target being written
|
|
111
|
+
* @param core - the in-transaction read-modify-write for the locked body
|
|
112
|
+
* @returns the core's result, or the oversized / contract-violation refusal
|
|
113
|
+
*/
|
|
114
|
+
private chainedWrite;
|
|
103
115
|
add(target: MemoryTarget, facts: string): Promise<MemoryApplyResult>;
|
|
104
116
|
private addChained;
|
|
105
117
|
/**
|
|
@@ -108,26 +120,49 @@ export declare class MemoryStore {
|
|
|
108
120
|
* (`current`) — never a second IO read. `write: null` means "no change".
|
|
109
121
|
*/
|
|
110
122
|
private addCore;
|
|
111
|
-
/**
|
|
112
|
-
|
|
123
|
+
/**
|
|
124
|
+
* The single drift predicate. `raw` is in canonical form when it byte-matches
|
|
125
|
+
* `render(normalizeEntries(raw))`; anything else means it was edited outside
|
|
126
|
+
* MemoryStore (empty/`§`-only entries, stray blank lines, leading or trailing
|
|
127
|
+
* delimiters — structural anomalies the writer would quietly normalize away).
|
|
128
|
+
* Both write paths derive this from their locked view and `detectDrift` from
|
|
129
|
+
* a fresh read, so a write and a later read can never disagree about the same
|
|
130
|
+
* bytes.
|
|
131
|
+
*
|
|
132
|
+
* An absent, empty, or whitespace-only body is the "never written" state
|
|
133
|
+
* (rc.42 audit P1-6): it parses to zero entries, and the canonical form
|
|
134
|
+
* `'\n'` can never byte-match it, so flagging it would permanently refuse
|
|
135
|
+
* every write path — including the repairs the model would need to make.
|
|
136
|
+
* Such files are adopted instead of flagged.
|
|
137
|
+
*
|
|
138
|
+
* @param target - memory target whose char limit bounds one parsed entry
|
|
139
|
+
* @param raw - on-disk body, or `null` when the file does not exist
|
|
140
|
+
* @returns whether these bytes count as externally drifted
|
|
141
|
+
*/
|
|
142
|
+
private drifted;
|
|
143
|
+
/**
|
|
144
|
+
* Drift refusal for a body already read under the write lock, or `null` when
|
|
145
|
+
* the body is canonical. Both write paths return this unchanged, so their
|
|
146
|
+
* refusals stay byte-identical and each carries the same backup.
|
|
147
|
+
*
|
|
148
|
+
* @param target - memory target that owns the drifted file
|
|
149
|
+
* @param raw - locked file body
|
|
150
|
+
* @returns the refusal to hand back, or `null` to continue writing
|
|
151
|
+
*/
|
|
152
|
+
private driftRefusal;
|
|
113
153
|
applyBatch(target: MemoryTarget, operations: MemoryOperation[]): Promise<MemoryApplyResult>;
|
|
114
154
|
private applyBatchChained;
|
|
115
155
|
/** Batch RMW inside the transaction. `write: null` = failure/no-op, disk untouched. */
|
|
116
156
|
private applyBatchCore;
|
|
117
157
|
renderContext(): Promise<string>;
|
|
118
158
|
/**
|
|
119
|
-
* Detect on-disk drift
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* edited outside MemoryStore. Purely single-canonical content reaches the
|
|
124
|
-
* same serialization and returns false, so a normal write is never flagged.
|
|
159
|
+
* Detect on-disk drift for a caller that holds no locked view: `true` when the
|
|
160
|
+
* file is not in canonical form, or when its size trips the read guard. The
|
|
161
|
+
* write paths apply the same predicate (`drifted`) to the body they read under
|
|
162
|
+
* the lock, so a write and a follow-up read agree about the same bytes.
|
|
125
163
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* `'\n'` can never byte-match it and every write path was permanently
|
|
129
|
-
* refused with "External drift detected" — including the repairs the model
|
|
130
|
-
* would need to make. Such files are adopted instead of flagged.
|
|
164
|
+
* @param target - memory target to inspect
|
|
165
|
+
* @returns whether the file on disk counts as externally drifted
|
|
131
166
|
*/
|
|
132
167
|
detectDrift(target: MemoryTarget): Promise<boolean>;
|
|
133
168
|
}
|
|
@@ -113,21 +113,31 @@ export declare function skillsRoot(env?: NodeJS.ProcessEnv): string;
|
|
|
113
113
|
export declare function resolveSkillsRoot(config?: {
|
|
114
114
|
root?: string | undefined;
|
|
115
115
|
}): string;
|
|
116
|
-
/** E-7 (v18): every family row reads ONE root key. `root` is
|
|
117
|
-
* `skillsRoot`
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
116
|
+
/** E-7 (v18) → V27 G2.4 (M-08): every family row reads ONE root key. `root` is
|
|
117
|
+
* canonical; the `skillsRoot` alias was honoured for one minor version and its
|
|
118
|
+
* window closed at 0.3.65 — it is now two releases past expiry, so this
|
|
119
|
+
* resolver no longer reads it at all. A deployment that still sets the alias
|
|
120
|
+
* must fail LOUDLY at load (see {@link assertSkillsRootAliasRetired}): silently
|
|
121
|
+
* ignoring a config key leaves the deployment pointing at a root nobody reads,
|
|
122
|
+
* which is the worst form of compatibility.
|
|
123
|
+
* @param config - the raw plugin config.
|
|
124
|
+
* @returns the effective root (empty when the key is unset or blank).
|
|
123
125
|
*/
|
|
124
126
|
export declare function resolveRootConfig(config?: {
|
|
125
127
|
root?: string | undefined;
|
|
126
|
-
skillsRoot?: string | undefined;
|
|
127
128
|
}): {
|
|
128
129
|
root: string;
|
|
129
|
-
usedDeprecatedAlias: boolean;
|
|
130
130
|
};
|
|
131
|
+
/** V27 G2.4 (M-08): the retirement gate for the expired `skillsRoot` alias.
|
|
132
|
+
* Called at each plugin's load boundary (before the root is resolved), it turns
|
|
133
|
+
* a stale key into an explicit load error naming the replacement — the
|
|
134
|
+
* fail-loud form the plan requires instead of a silent no-op.
|
|
135
|
+
* @param config - the raw plugin config (the alias field stays DECLARED in each
|
|
136
|
+
* schema so the loader can hand it here instead of dropping it).
|
|
137
|
+
*/
|
|
138
|
+
export declare function assertSkillsRootAliasRetired(config?: {
|
|
139
|
+
skillsRoot?: string | undefined;
|
|
140
|
+
}): void;
|
|
131
141
|
/**
|
|
132
142
|
* Map a requesting session onto the two origin surfaces (rc.44 plan M2-2.3):
|
|
133
143
|
* the APPROVAL surface treats every delegated subagent as the autonomous
|
|
@@ -156,10 +166,17 @@ export interface Frontmatter {
|
|
|
156
166
|
}
|
|
157
167
|
/**
|
|
158
168
|
* Shared frontmatter block detection (P3-3 single owner): opening line `---`
|
|
159
|
-
* and closing line exactly
|
|
169
|
+
* and closing line exactly `---`. Used by `parseFrontmatter`,
|
|
160
170
|
* `frontmatterYamlUnsafeValues` and `normalizeFrontmatter` so the three can
|
|
161
171
|
* never disagree about where the block ends (the loose `indexOf('\n---')`
|
|
162
172
|
* form matched `\n----` and was replaced by this strict line rule).
|
|
173
|
+
*
|
|
174
|
+
* V27 G2.1: both fence lines are matched EXACTLY, tolerating only a trailing
|
|
175
|
+
* `\r` — the same rule the upstream filesystem catalog uses
|
|
176
|
+
* (`skill-filesystem.parseFrontmatter`). The former `.trim()` comparison
|
|
177
|
+
* accepted ` --- `, so an indented fence loaded in the family while the
|
|
178
|
+
* platform ignored the file: family visibility split from platform visibility,
|
|
179
|
+
* which is exactly what a strict-YAML frontmatter is supposed to prevent.
|
|
163
180
|
*/
|
|
164
181
|
export declare function frontmatterBlock(content: string): {
|
|
165
182
|
block: string;
|
|
@@ -167,10 +184,51 @@ export declare function frontmatterBlock(content: string): {
|
|
|
167
184
|
end: number;
|
|
168
185
|
nl: string;
|
|
169
186
|
} | null;
|
|
170
|
-
|
|
187
|
+
/**
|
|
188
|
+
* One frontmatter read (V27 G2.1): the values, the body, and every signal the
|
|
189
|
+
* strict-YAML platform catalog derives from the same block. Returned by
|
|
190
|
+
* {@link parseFrontmatter} so a caller never has to parse the block twice to
|
|
191
|
+
* reach a description and the catalog verdict.
|
|
192
|
+
*/
|
|
193
|
+
export interface FrontmatterRead {
|
|
171
194
|
frontmatter: Frontmatter;
|
|
172
195
|
body: string;
|
|
173
|
-
|
|
196
|
+
/** Raw entries whose UNQUOTED value the strict catalog cannot load as
|
|
197
|
+
* written. Quotes are included, so a value already normalized by the write
|
|
198
|
+
* path (`normalizeFrontmatter`) is never re-flagged. */
|
|
199
|
+
unsafeValues: Array<{
|
|
200
|
+
key: string;
|
|
201
|
+
value: string;
|
|
202
|
+
}>;
|
|
203
|
+
/** Whether the frontmatter is not valid AS WRITTEN for the strict platform
|
|
204
|
+
* catalog: the strict parser rejects the block, or an unquoted value would
|
|
205
|
+
* read as something other than its text (a dropped ` # ` comment, a
|
|
206
|
+
* number/bool shorthand the catalog refuses as a string field). The write
|
|
207
|
+
* path quotes such a value on its next edit. */
|
|
208
|
+
catalogInvalid: boolean;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Parse a SKILL.md: its frontmatter values and body, or `null` when the file
|
|
212
|
+
* has no frontmatter block or no body. Every consumer of frontmatter values
|
|
213
|
+
* goes through here — the write path's validation, `list()`'s published
|
|
214
|
+
* description, `relatedSkillNames` and the audit — so all of them read the same
|
|
215
|
+
* bytes the same way.
|
|
216
|
+
*
|
|
217
|
+
* @param content - the SKILL.md text.
|
|
218
|
+
* @returns the read, or `null` when there is no block or no body.
|
|
219
|
+
*/
|
|
220
|
+
export declare function parseFrontmatter(content: string): FrontmatterRead | null;
|
|
221
|
+
/**
|
|
222
|
+
* Whether this file's frontmatter is valid as written for the strict platform
|
|
223
|
+
* catalog (see `FrontmatterRead.catalogInvalid`). Body-independent (a body-less
|
|
224
|
+
* file is still judged), and derived from the same read as `parseFrontmatter` —
|
|
225
|
+
* so the audit's verdict and the values the family publishes for one file can
|
|
226
|
+
* never disagree (V27 G2.1).
|
|
227
|
+
*
|
|
228
|
+
* @param content - the SKILL.md text.
|
|
229
|
+
* @returns `true` when the strict parser rejects the block or an unquoted value would read as something else.
|
|
230
|
+
*/
|
|
231
|
+
export declare function frontmatterCatalogInvalid(content: string): boolean;
|
|
174
232
|
/** YAML plain-scalar hazards that make an UNQUOTED frontmatter value
|
|
175
233
|
* unloadable to the platform catalog (strict YAML parser): `: ` (mapping
|
|
176
234
|
* separator), ` #` (comment start), a trailing `:` (a mapping marker),
|
|
@@ -190,7 +248,10 @@ export declare function yamlPlainScalarNeedsQuotes(value: string): boolean;
|
|
|
190
248
|
* YAML-unsafe for the strict platform catalog. Operates on the ORIGINAL line
|
|
191
249
|
* value (quotes included), so a value already wrapped by
|
|
192
250
|
* `normalizeFrontmatter` is never re-flagged — one source with the write
|
|
193
|
-
* path.
|
|
251
|
+
* path. V27 G2.1: delegates to the shared scan, which
|
|
252
|
+
* `parseFrontmatter(...).catalogInvalid` also uses, so the audit view and the
|
|
253
|
+
* read view of one file can never disagree. Independent of the body: a
|
|
254
|
+
* body-less file is still reported here. */
|
|
194
255
|
export declare function frontmatterYamlUnsafeValues(content: string): Array<{
|
|
195
256
|
key: string;
|
|
196
257
|
value: string;
|
|
@@ -508,6 +569,16 @@ export declare class SkillLibrary {
|
|
|
508
569
|
* 64 chars each (the name-rule maximum — real skill names always fit).
|
|
509
570
|
*/
|
|
510
571
|
private sanitizeSkippedNames;
|
|
572
|
+
/**
|
|
573
|
+
* V27 G0.4 (core-a-01): the per-entry gate `skipped` already has. A corrupted
|
|
574
|
+
* or hand-edited manifest could carry `extras: [123]`: the array check passed,
|
|
575
|
+
* `SNAPSHOT_EXTRA_NAME_RE.test(123)` coerced the number to the string "123"
|
|
576
|
+
* and matched, and the value then threw `TypeError` inside `path.join` — which
|
|
577
|
+
* `readSnapshotExtras` reached only AFTER a whole-tree restore had committed.
|
|
578
|
+
* Extras are path components under `extras/`, so they take the entry gate too;
|
|
579
|
+
* bounded like `skipped` so a hostile manifest cannot grow the read set.
|
|
580
|
+
*/
|
|
581
|
+
private sanitizeExtraNames;
|
|
511
582
|
readSnapshotManifest(path: string): Promise<SnapshotManifest | null>;
|
|
512
583
|
/** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
|
|
513
584
|
private retainSnapshots;
|
package/lib/types/threats.d.ts
CHANGED
|
@@ -28,6 +28,9 @@ export interface ThreatFinding {
|
|
|
28
28
|
* benign phrasing (e.g. a skill that legitimately opens with "You are now a ...")
|
|
29
29
|
* can exclude that label by name here. This is opt-in and never widens strict
|
|
30
30
|
* scope; it only permits callers to drop a known-innocent match.
|
|
31
|
+
* V27 G-1: an exclusion drops the FINDING for that label — it never removes the
|
|
32
|
+
* de-obfuscation the other patterns are matched against, so exempting the
|
|
33
|
+
* unicode rules cannot re-open a splitting bypass.
|
|
31
34
|
*/
|
|
32
35
|
export interface ScanOptions {
|
|
33
36
|
/** Pattern labels to skip during this scan. */
|
package/package.json
CHANGED