@lmzhen/dsh-evolution-core 0.3.33 → 0.3.35
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 +43 -11
- package/lib/types/threats.d.ts +8 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { basename, dirname, join } from "node:path";
|
|
2
|
-
import { cp, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { cp, lstat, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { load } from "js-yaml";
|
|
@@ -149,11 +149,20 @@ function nodeEvolutionIo() {
|
|
|
149
149
|
const lock = `${path}.lock`;
|
|
150
150
|
let myClaim = "";
|
|
151
151
|
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
152
|
+
let lockHandle = null;
|
|
152
153
|
try {
|
|
153
154
|
myClaim = `${process.pid}:${randomBytes(4).toString("hex")}`;
|
|
154
|
-
await
|
|
155
|
+
lockHandle = await open(lock, "wx");
|
|
156
|
+
await lockHandle.writeFile(myClaim);
|
|
157
|
+
await lockHandle.close();
|
|
158
|
+
lockHandle = null;
|
|
155
159
|
} catch (error) {
|
|
156
160
|
const code = error?.code;
|
|
161
|
+
if (lockHandle) {
|
|
162
|
+
await lockHandle.close().catch(() => {});
|
|
163
|
+
await rm(lock, { force: true }).catch(() => {});
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
157
166
|
if (code !== "EEXIST" && code !== "EPERM") throw error;
|
|
158
167
|
try {
|
|
159
168
|
const st = await stat(lock);
|
|
@@ -169,13 +178,16 @@ function nodeEvolutionIo() {
|
|
|
169
178
|
}
|
|
170
179
|
continue;
|
|
171
180
|
}
|
|
172
|
-
|
|
181
|
+
const staleDead = Number.isInteger(holder) && holder > 0 && Date.now() - st.mtimeMs > 1e3 && !holderAlive;
|
|
182
|
+
const staleEmpty = holderContent === "" && Date.now() - st.mtimeMs > 1e3;
|
|
183
|
+
if (staleDead || staleEmpty) {
|
|
173
184
|
if (await readFile(lock, "utf8").catch(() => "") === holderContent) {
|
|
174
185
|
const ticket = `${lock}.next`;
|
|
175
186
|
try {
|
|
176
187
|
const ticketBody = await readFile(ticket, "utf8").catch(() => "");
|
|
188
|
+
const ticketMtime = await stat(ticket).then((s) => s.mtimeMs, () => 0);
|
|
177
189
|
const ticketHolder = Number(ticketBody.split(":")[0] ?? "");
|
|
178
|
-
if ((!Number.isInteger(ticketHolder) || ticketHolder <= 0 || !isAlive(ticketHolder) || Date.now() -
|
|
190
|
+
if ((!Number.isInteger(ticketHolder) || ticketHolder <= 0 || !isAlive(ticketHolder) || Date.now() - ticketMtime > 1e3) && (ticketBody !== "" || Date.now() - ticketMtime > 1e3)) await rm(ticket, { force: true }).catch(() => {});
|
|
179
191
|
} catch {}
|
|
180
192
|
try {
|
|
181
193
|
await writeFile(ticket, `${process.pid}:${randomBytes(4).toString("hex")}`, { flag: "wx" });
|
|
@@ -217,8 +229,24 @@ function nodeEvolutionIo() {
|
|
|
217
229
|
return;
|
|
218
230
|
}
|
|
219
231
|
const prefix = `${base}.`;
|
|
232
|
+
const lockName = `${base}.lock`;
|
|
233
|
+
const ticketName = `${lockName}.next`;
|
|
220
234
|
for (const name of entries) {
|
|
221
|
-
if (!name.startsWith(prefix) ||
|
|
235
|
+
if (!name.startsWith(prefix) || name === lockName) continue;
|
|
236
|
+
if (!name.endsWith(".tmp")) {
|
|
237
|
+
if (name === ticketName) {
|
|
238
|
+
const ticketPath = join(dir, name);
|
|
239
|
+
try {
|
|
240
|
+
const body = await readFile(ticketPath, "utf8").catch(() => "");
|
|
241
|
+
const holder = Number(body.split(":")[0] ?? "");
|
|
242
|
+
const st = await stat(ticketPath);
|
|
243
|
+
const dead = !Number.isInteger(holder) || holder <= 0 || !isAlive(holder);
|
|
244
|
+
const old = Date.now() - st.mtimeMs > 1e3;
|
|
245
|
+
if (dead || old) await rm(ticketPath, { force: true });
|
|
246
|
+
} catch {}
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
222
250
|
const tmpPath = join(dir, name);
|
|
223
251
|
const holder = Number(name.slice(prefix.length, name.length - 4).split(".")[0] ?? "");
|
|
224
252
|
try {
|
|
@@ -1779,9 +1807,13 @@ const SCOPE_ORDER = {
|
|
|
1779
1807
|
strict: 3
|
|
1780
1808
|
};
|
|
1781
1809
|
const NO_SCAN_OPTIONS = {};
|
|
1782
|
-
/**
|
|
1783
|
-
*
|
|
1784
|
-
* window
|
|
1810
|
+
/** Minimum window size for the full-coverage scan (V6-05, 0.3.35). With the
|
|
1811
|
+
* proportional half-window step below, the overlap is `ceil(w/2)` — only a
|
|
1812
|
+
* window at or above this floor keeps the overlap above the longest pattern
|
|
1813
|
+
* span (~530 chars: `curl [^\n]{0,512} ...`), so a match straddling a window
|
|
1814
|
+
* boundary is fully inside at least one window (E-12, 0.3.16). The clamp
|
|
1815
|
+
* falls back to the default for a smaller caller value instead of risking a
|
|
1816
|
+
* blind zone. */
|
|
1785
1817
|
const PATTERN_OVERLAP = 4096;
|
|
1786
1818
|
/**
|
|
1787
1819
|
* Scan text at `scope`. Patterns are cumulative: `strict` includes all scopes.
|
|
@@ -1791,7 +1823,7 @@ const PATTERN_OVERLAP = 4096;
|
|
|
1791
1823
|
* characters (skill files may run to 100,000) is no longer a blind zone.
|
|
1792
1824
|
*/
|
|
1793
1825
|
function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_SCAN_OPTIONS) {
|
|
1794
|
-
const windowSize = clampedNumber(maxScanChars, 65536, { min:
|
|
1826
|
+
const windowSize = clampedNumber(maxScanChars, 65536, { min: 4097 });
|
|
1795
1827
|
const findings = [];
|
|
1796
1828
|
if (ZERO_WIDTH_CHARS.test(text)) findings.push({
|
|
1797
1829
|
label: "unicode_zero_width",
|
|
@@ -1807,7 +1839,7 @@ function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_
|
|
|
1807
1839
|
const windows = [];
|
|
1808
1840
|
if (normalized.length <= windowSize) windows.push(normalized);
|
|
1809
1841
|
else {
|
|
1810
|
-
const step = Math.max(
|
|
1842
|
+
const step = Math.max(Math.floor(windowSize / 2), 1);
|
|
1811
1843
|
for (let start = 0; start < normalized.length; start += step) windows.push(normalized.slice(start, start + windowSize));
|
|
1812
1844
|
}
|
|
1813
1845
|
const excluded = new Set(options.excludeLabels ?? []);
|
|
@@ -4738,4 +4770,4 @@ function evolutionHome(env = process.env) {
|
|
|
4738
4770
|
return join(evolutionRoot(env), "evolution");
|
|
4739
4771
|
}
|
|
4740
4772
|
//#endregion
|
|
4741
|
-
export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, clampedNumber, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldTurn, frontmatterBlock, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, markAgentCreated, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, pendingSelfCleanup, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
|
|
4773
|
+
export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, PATTERN_OVERLAP, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, clampedNumber, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldTurn, frontmatterBlock, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, markAgentCreated, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, pendingSelfCleanup, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
|
package/lib/types/threats.d.ts
CHANGED
|
@@ -23,6 +23,14 @@ export interface ScanOptions {
|
|
|
23
23
|
/** Pattern labels to skip during this scan. */
|
|
24
24
|
excludeLabels?: readonly string[];
|
|
25
25
|
}
|
|
26
|
+
/** Minimum window size for the full-coverage scan (V6-05, 0.3.35). With the
|
|
27
|
+
* proportional half-window step below, the overlap is `ceil(w/2)` — only a
|
|
28
|
+
* window at or above this floor keeps the overlap above the longest pattern
|
|
29
|
+
* span (~530 chars: `curl [^\n]{0,512} ...`), so a match straddling a window
|
|
30
|
+
* boundary is fully inside at least one window (E-12, 0.3.16). The clamp
|
|
31
|
+
* falls back to the default for a smaller caller value instead of risking a
|
|
32
|
+
* blind zone. */
|
|
33
|
+
export declare const PATTERN_OVERLAP = 4096;
|
|
26
34
|
/**
|
|
27
35
|
* Scan text at `scope`. Patterns are cumulative: `strict` includes all scopes.
|
|
28
36
|
* `options.excludeLabels` removes matching patterns without changing `scope`.
|
package/package.json
CHANGED