dsh-continual-evolve 0.8.0 → 0.9.0
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/README.md +7 -3
- package/README.zh.md +7 -3
- package/lib/benchmark-command.js +1 -1
- package/lib/fate.js +10 -10
- package/lib/memory-agent.d.ts +8 -1
- package/lib/memory-agent.js +12 -0
- package/lib/notify.js +5 -4
- package/lib/planner.d.ts +1 -1
- package/lib/planner.js +7 -1
- package/lib/review-scheduler.js +3 -4
- package/lib/review.d.ts +1 -1
- package/lib/review.js +8 -3
- package/lib/skill-render.js +2 -1
- package/lib/skillquality.d.ts +7 -2
- package/lib/skillquality.js +12 -3
- package/lib/types.d.ts +9 -11
- package/lib/types.js +14 -2
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
|
+
[]()
|
|
12
|
+
[]()
|
|
13
|
+
[]()
|
|
11
14
|
|
|
12
15
|
Continual self-evolution for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): a versioned, auditable, rollback-safe harness state layer — prompt notes, memories, skills, subagent specs — refined from session trajectories.
|
|
13
16
|
|
|
@@ -139,8 +142,9 @@ before aborting.
|
|
|
139
142
|
|
|
140
143
|
```bash
|
|
141
144
|
pnpm install && pnpm build # deps + tsc -> lib/
|
|
142
|
-
pnpm test # vitest (
|
|
145
|
+
pnpm test # vitest (1031 tests)
|
|
143
146
|
pnpm test:coverage # v8 coverage, thresholds enforced in CI
|
|
147
|
+
pnpm coverage:gaps # locate uncovered lines per file (read-only)
|
|
144
148
|
pnpm lint # oxlint src test
|
|
145
149
|
```
|
|
146
150
|
|
|
@@ -148,7 +152,7 @@ Project layout:
|
|
|
148
152
|
|
|
149
153
|
```
|
|
150
154
|
├── src/ # engine, tools, commands, memory agent, recall, projection, gate, fate, benchmark, injection + token usage…
|
|
151
|
-
├── test/ # vitest suites (
|
|
155
|
+
├── test/ # vitest suites (50 files)
|
|
152
156
|
├── lib/ # build output (tsc)
|
|
153
157
|
├── docs/
|
|
154
158
|
│ ├── design.md # full design doc (hardening matrix)
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
|
+
[]()
|
|
12
|
+
[]()
|
|
13
|
+
[]()
|
|
11
14
|
|
|
12
15
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的持续自进化插件:一套**版本化、可审计、可回滚**的 harness 状态层——提示词补充、记忆、技能、子代理规格——从会话轨迹中沉淀而来。
|
|
13
16
|
|
|
@@ -123,8 +126,9 @@ profile patch 示例:
|
|
|
123
126
|
|
|
124
127
|
```bash
|
|
125
128
|
pnpm install && pnpm build # 依赖 + tsc -> lib/
|
|
126
|
-
pnpm test # vitest(
|
|
129
|
+
pnpm test # vitest(1031 例)
|
|
127
130
|
pnpm test:coverage # v8 覆盖率,CI 强制阈值
|
|
131
|
+
pnpm coverage:gaps # 定位各文件未覆盖行(只读)
|
|
128
132
|
pnpm lint # oxlint src test
|
|
129
133
|
```
|
|
130
134
|
|
|
@@ -132,7 +136,7 @@ pnpm lint # oxlint src test
|
|
|
132
136
|
|
|
133
137
|
```
|
|
134
138
|
├── src/ # 引擎、工具、命令、memory Agent、召回、投影、门禁、fate、benchmark、注入 + token 用量…
|
|
135
|
-
├── test/ # vitest 测试套件(
|
|
139
|
+
├── test/ # vitest 测试套件(50 个文件)
|
|
136
140
|
├── lib/ # 构建产物(tsc)
|
|
137
141
|
├── docs/
|
|
138
142
|
│ ├── design.md # 完整设计文档(硬化矩阵)
|
package/lib/benchmark-command.js
CHANGED
|
@@ -106,7 +106,7 @@ export async function executeBenchmarkCommand(ctx, engine, invocation, rest, run
|
|
|
106
106
|
for (const d of board.decisions) {
|
|
107
107
|
lines.push(`decision: ${d.accepted ? "ACCEPTED" : "rejected"} ${d.candidateLabel} — ${d.reasons.join("; ") || "ok"}`);
|
|
108
108
|
}
|
|
109
|
-
return success(lines.join("\n")
|
|
109
|
+
return success(lines.join("\n"));
|
|
110
110
|
}
|
|
111
111
|
case "run": {
|
|
112
112
|
const bid = stripAngleBrackets(args[0] ?? "");
|
package/lib/fate.js
CHANGED
|
@@ -241,7 +241,10 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
241
241
|
const plan = planLocalFates(assessment.items, candidates, globalState, config.promotionPolicy);
|
|
242
242
|
const needsDialog = plan.promotable.length + plan.splits.length + plan.reviewArchives.length > 0;
|
|
243
243
|
let consent = { approved: false, asked: false, reason: "nothing-to-ask" };
|
|
244
|
-
|
|
244
|
+
// Turn-path tightening (2026-09-25): turn_snapshot/turn_interval never open
|
|
245
|
+
// a dialog — governed actions are deferred to `/evolve wrapup`. Only an
|
|
246
|
+
// explicit goal_blocked signal (own streak counter) may consult.
|
|
247
|
+
if (reason === "goal_blocked" && needsDialog) {
|
|
245
248
|
consent = await consultLocalFates(ctx, agent, plan, state);
|
|
246
249
|
}
|
|
247
250
|
if (consent.approved) {
|
|
@@ -255,9 +258,6 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
255
258
|
rationale: `fate: ${assessment.rationale} (${applied.join("; ")})`,
|
|
256
259
|
refinementId: results.map((result) => result.id).join(","),
|
|
257
260
|
});
|
|
258
|
-
if (config.notifyOnAutoReview && (reason === "turn_snapshot" || reason === "turn_interval") && applied.length > 0) {
|
|
259
|
-
notifyFateApplied(ctx, agent, applied);
|
|
260
|
-
}
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
263
|
if (consent.reason === "declined" || consent.reason === "unavailable" || consent.reason === "error") {
|
|
@@ -273,18 +273,18 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
273
273
|
});
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
276
|
-
if (reason
|
|
277
|
-
//
|
|
278
|
-
// governed actions are deferred with an audit record.
|
|
276
|
+
if (needsDialog && reason !== "goal_blocked") {
|
|
277
|
+
// Turn/compact path: no dialog. Only deterministic silent archives apply;
|
|
278
|
+
// governed actions are deferred with an audit record pointing at wrapup.
|
|
279
279
|
const { applied, results } = applyLocalFates(engine, sessionId, plan, localState, "silent-only");
|
|
280
280
|
if (applied.length > 0) {
|
|
281
|
-
logger.info(`auto-review local-fate (
|
|
281
|
+
logger.info(`auto-review local-fate (${reason}) [${sessionId}]: silent-archived ${applied.length} — ${assessment.rationale}`);
|
|
282
282
|
record({
|
|
283
283
|
sessionId,
|
|
284
284
|
reason,
|
|
285
285
|
turnsSinceLastReview: turnsSinceFate,
|
|
286
286
|
outcome: "approved",
|
|
287
|
-
rationale: `fate (
|
|
287
|
+
rationale: `fate (${reason}): ${assessment.rationale} (${applied.join("; ")})`,
|
|
288
288
|
refinementId: results.map((result) => result.id).join(","),
|
|
289
289
|
});
|
|
290
290
|
}
|
|
@@ -293,7 +293,7 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
293
293
|
reason,
|
|
294
294
|
turnsSinceLastReview: turnsSinceFate,
|
|
295
295
|
outcome: "deferred",
|
|
296
|
-
rationale: `fate (
|
|
296
|
+
rationale: `fate (${reason}): ${plan.promotable.length} promotes, ${plan.splits.length} splits, ${plan.reviewArchives.length} review-archives deferred — run /evolve wrapup for a full session exit`,
|
|
297
297
|
});
|
|
298
298
|
return;
|
|
299
299
|
}
|
package/lib/memory-agent.d.ts
CHANGED
|
@@ -20,6 +20,13 @@ import type { EntrySource, HarnessEntry, HarnessScope, HarnessState, RefinementE
|
|
|
20
20
|
export declare const MEMORY_AGENT_MAX_TURNS = 5;
|
|
21
21
|
/** Bound one proposal so approval text and engine compensation stay reviewable. */
|
|
22
22
|
export declare const MEMORY_AGENT_MAX_EDITS = 20;
|
|
23
|
+
/**
|
|
24
|
+
* System-prompt size budget (chars): the prompt ships on EVERY extractor
|
|
25
|
+
* turn (up to five per snapshot), so each added line is recurring token
|
|
26
|
+
* spend, not one-off text. Raise this cap only in the same commit that
|
|
27
|
+
* justifies the added lines (regression pressure beats silent bloat).
|
|
28
|
+
*/
|
|
29
|
+
export declare const MEMORY_AGENT_SYSTEM_PROMPT_BUDGET_CHARS = 3000;
|
|
23
30
|
/** The complete tool-name allowlist exposed by the memory agent. */
|
|
24
31
|
export declare const MEMORY_AGENT_TOOL_NAMES: readonly ["memory_search", "memory_propose"];
|
|
25
32
|
export type MemoryAgentToolName = (typeof MEMORY_AGENT_TOOL_NAMES)[number];
|
|
@@ -54,7 +61,7 @@ export interface MemoryApplicationResult {
|
|
|
54
61
|
/** Provider-facing schemas for the closed memory-only tool set. */
|
|
55
62
|
export declare const MEMORY_AGENT_TOOL_SCHEMAS: readonly ToolSchema[];
|
|
56
63
|
/** Instructions for the dedicated loop; model text is always untrusted. */
|
|
57
|
-
export declare const MEMORY_AGENT_SYSTEM_PROMPT = "You are the dedicated background memory extraction agent for a continual-evolution harness.\n\nYou receive only an incremental conversation checkpoint and a frozen manifest of existing memory entries. Your entire job is to decide whether this checkpoint contains a durable fact worth remembering. Never propose prompt notes, skills, subagent specs, code changes, or general conversation summaries.\n\nRules:\n- Use memory_search before creating when the manifest may already cover the topic. Update an existing entry instead of creating a near-duplicate.\n- Every memory entry contains exactly one fact. Classify it with metadata.memoryType = user | feedback | project | reference.\n- feedback and project memories must state the fact, Why it matters, and How to apply it next time.\n- Never invent a user preference. Ground every proposal in the supplied conversation evidence and existing memory.\n- Every edit must have kind=memory, an explicit targetScope, and a coherent blastRadius.\n- targetScope=local is session staging. project persists within the current repository. global persists across projects. project/global require human approval before application.\n- update/delete/archive must name an existing id from the same targetScope. Prefer archive over delete when the fact is obsolete but should remain restorable.\n- Do not remember: code structure or file paths re-readable from the repository; git history, diffs, commit hashes, or CI logs; content already written in project instruction files; temporary session state, one-off debugging trails, or current task progress; model-only speculation without conversation evidence.\n- You cannot read or write source files, call agents, use MCP, use the network, or mutate the harness directly. memory_propose is the only finish tool and never writes state itself.\n- If no durable memory is justified, call memory_propose with edits=[].\n- Always finish by calling memory_propose. Do not return unproposed prose.";
|
|
64
|
+
export declare const MEMORY_AGENT_SYSTEM_PROMPT = "You are the dedicated background memory extraction agent for a continual-evolution harness.\n\nYou receive only an incremental conversation checkpoint and a frozen manifest of existing memory entries. Your entire job is to decide whether this checkpoint contains a durable fact worth remembering. Never propose prompt notes, skills, subagent specs, code changes, or general conversation summaries.\n\nRules:\n- Use memory_search before creating when the manifest may already cover the topic. Update an existing entry instead of creating a near-duplicate.\n- Every memory entry contains exactly one fact. Classify it with metadata.memoryType = user | feedback | project | reference.\n- feedback and project memories must state the fact, Why it matters, and How to apply it next time.\n- Never invent a user preference. Ground every proposal in the supplied conversation evidence and existing memory.\n- Every edit must have kind=memory, an explicit targetScope, and a coherent blastRadius.\n- targetScope=local is session staging. project persists within the current repository. global persists across projects. project/global require human approval before application.\n- update/delete/archive must name an existing id from the same targetScope. Prefer archive over delete when the fact is obsolete but should remain restorable.\n- Do not remember: code structure or file paths re-readable from the repository; git history, diffs, commit hashes, or CI logs; content already written in project instruction files; temporary session state, one-off debugging trails, or current task progress; model-only speculation without conversation evidence.\n- Be specific enough to act on: prefer a fact that tells the next session what to DO over a summary of what happened. If the fact is obvious from the conversation itself with no future action attached, skip it.\n- Save only what is surprising or non-obvious: routine chatter, restated context, and facts with no bearing on future work are no-ops, not memories.\n- Granularity examples (vague \u2192 reject; sharp \u2192 save):\n - \"user communicates in Chinese\" \u2192 \"user reads English with effort \u2014 write memory and handoff content in Chinese prose\".\n - \"user works on the desktop project\" \u2192 \"desktop profile loads npm-installed plugin copies \u2014 source edits need a release before they take effect\".\n- You cannot read or write source files, call agents, use MCP, use the network, or mutate the harness directly. memory_propose is the only finish tool and never writes state itself.\n- If no durable memory is justified, call memory_propose with edits=[].\n- Always finish by calling memory_propose. Do not return unproposed prose.";
|
|
58
65
|
/** Build the frozen memory manifest from a merged harness view. */
|
|
59
66
|
export declare function buildMemoryManifest(state: HarnessState): MemoryManifestEntry[];
|
|
60
67
|
/** Render a bounded manifest index; full bodies remain available through memory_search. */
|
package/lib/memory-agent.js
CHANGED
|
@@ -15,6 +15,13 @@ import { validateBlastRadiusScope, validateEdit } from "./validate.js";
|
|
|
15
15
|
export const MEMORY_AGENT_MAX_TURNS = 5;
|
|
16
16
|
/** Bound one proposal so approval text and engine compensation stay reviewable. */
|
|
17
17
|
export const MEMORY_AGENT_MAX_EDITS = 20;
|
|
18
|
+
/**
|
|
19
|
+
* System-prompt size budget (chars): the prompt ships on EVERY extractor
|
|
20
|
+
* turn (up to five per snapshot), so each added line is recurring token
|
|
21
|
+
* spend, not one-off text. Raise this cap only in the same commit that
|
|
22
|
+
* justifies the added lines (regression pressure beats silent bloat).
|
|
23
|
+
*/
|
|
24
|
+
export const MEMORY_AGENT_SYSTEM_PROMPT_BUDGET_CHARS = 3000;
|
|
18
25
|
/** The complete tool-name allowlist exposed by the memory agent. */
|
|
19
26
|
export const MEMORY_AGENT_TOOL_NAMES = ["memory_search", "memory_propose"];
|
|
20
27
|
/** Provider-facing schemas for the closed memory-only tool set. */
|
|
@@ -85,6 +92,11 @@ Rules:
|
|
|
85
92
|
- targetScope=local is session staging. project persists within the current repository. global persists across projects. project/global require human approval before application.
|
|
86
93
|
- update/delete/archive must name an existing id from the same targetScope. Prefer archive over delete when the fact is obsolete but should remain restorable.
|
|
87
94
|
- Do not remember: code structure or file paths re-readable from the repository; git history, diffs, commit hashes, or CI logs; content already written in project instruction files; temporary session state, one-off debugging trails, or current task progress; model-only speculation without conversation evidence.
|
|
95
|
+
- Be specific enough to act on: prefer a fact that tells the next session what to DO over a summary of what happened. If the fact is obvious from the conversation itself with no future action attached, skip it.
|
|
96
|
+
- Save only what is surprising or non-obvious: routine chatter, restated context, and facts with no bearing on future work are no-ops, not memories.
|
|
97
|
+
- Granularity examples (vague → reject; sharp → save):
|
|
98
|
+
- "user communicates in Chinese" → "user reads English with effort — write memory and handoff content in Chinese prose".
|
|
99
|
+
- "user works on the desktop project" → "desktop profile loads npm-installed plugin copies — source edits need a release before they take effect".
|
|
88
100
|
- You cannot read or write source files, call agents, use MCP, use the network, or mutate the harness directly. memory_propose is the only finish tool and never writes state itself.
|
|
89
101
|
- If no durable memory is justified, call memory_propose with edits=[].
|
|
90
102
|
- Always finish by calling memory_propose. Do not return unproposed prose.`;
|
package/lib/notify.js
CHANGED
|
@@ -53,16 +53,17 @@ export function buildMemoryReceipt(receipt) {
|
|
|
53
53
|
if (receipt.outcome === "noop") {
|
|
54
54
|
return `🧠 记忆提取:本轮无可沉淀的持久事实(no-op,${stats}),未写入任何条目。`;
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const results = receipt.results ?? [];
|
|
57
|
+
const declinedScopes = receipt.declinedScopes ?? [];
|
|
58
|
+
if (receipt.outcome === "declined" || results.length === 0) {
|
|
59
|
+
const scopes = declinedScopes.join("、") || "持久化作用域";
|
|
58
60
|
return `🧠 记忆提取:${scopes}的写入未获批准,已跳过(${stats}),未写入任何条目。`;
|
|
59
61
|
}
|
|
60
|
-
const results = receipt.results ?? [];
|
|
61
62
|
const applied = results.flatMap((result) => result.appliedEdits.filter((edit) => edit.applied));
|
|
62
63
|
const lines = applied.slice(0, 12).map((edit) => `- 记忆「${edit.title ?? edit.id}」(${edit.id})`);
|
|
63
64
|
if (applied.length > 12)
|
|
64
65
|
lines.push(`- …另有 ${applied.length - 12} 条(见 /evolve history)`);
|
|
65
|
-
const declined =
|
|
66
|
+
const declined = declinedScopes.length > 0 ? `\n另有作用域${declinedScopes.join("、")}未经批准(已跳过)。` : "";
|
|
66
67
|
const rollbacks = [...new Set(results.map((result) => result.id))].map((id) => `/evolve rollback ${id}`).join(";");
|
|
67
68
|
return [
|
|
68
69
|
`🧠 记忆提取:本轮沉淀 ${applied.length} 条记忆(${results.length} 个作用域批次,${stats}):`,
|
package/lib/planner.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
|
11
11
|
import type { HarnessState, RefinementProposal, RefinementResult } from "./types.js";
|
|
12
12
|
import { type PrefixCacheOptions } from "./prefix-cache.js";
|
|
13
13
|
import { type TokenUsageTarget } from "./token-usage.js";
|
|
14
|
-
export declare const PLANNER_SYSTEM_PROMPT = "You are the /evolve continual harness subsystem.\n\nYour job is to improve the editable continual harness state. Instead of\nsummarizing the conversation you emit precise Create, Update, or Delete edits\nto reusable state: prompt notes, memories, skills, and subagent specs.\n\nRules:\n- The base system prompt is immutable and MUST NOT be rewritten (never edit id \"base_system_prompt\").\n- Prefer small evidence-backed edits. If no useful edit is justified, return an empty edits array.\n- prompt = narrow behavioral policy addendums; memory = ONE FACT per entry, typed via metadata.memoryType\n- (user = who the user is; feedback = a pitfall/correction or confirmed approach, MUST carry Why + How to apply;\n- project = work goal/constraint not derivable from the repo; reference = URL/dashboard/ticket pointer).\n- Memory rules: one fact per entry (title = one-line retrieval hook, content = the fact alone);\n- never save what the repo already records (code structure, past fixes, git history, AGENTS.md) or what only\n- matters to this conversation \u2014 save the non-obvious point instead; before creating, update the existing\n- entry when one already covers the fact (creates carry metadata {memoryType} or they are rejected);\n- skill = repeatable procedures (must carry a python reference {type:\"python\", import, callable}\n and an arguments object); subagent = reusable delegation roles.\n- Skill entries are authored to the DSH skill quality standard\n (skill-creator, distilled from the official deepseek-harness 11 skills;\n the full facts are provided in the <skill_quality_standard> block below):\n only for a REAL trigger scenario grounded in the trajectory\n (who, in what real task, what signal) \u2014 never invent one to pad the store;\n never duplicate the official 11 skills or existing entries; content is a\n SKILL.md document (frontmatter routing with \"use when / do not use when\"\n description, boundary declaration, prerequisites and exclusions, layered\n information, verifiable completion criteria). Self-check every proposed\n skill against the 7 structural features and state the result in its\n reason field.\n- Repeated multi-step workflows (session start/end routines, recurring\n wrap-up or handoff procedures) may be proposed as guidance skills:\n kind=skill, skill_kind=\"guidance\", content = a SKILL.md document (no\n python reference \u2014 executable skills keep requiring reference +\n arguments). Only propose with repeated evidence in the trajectory, never\n for one-off flows. Guidance skills materialize as discoverable SKILL.md\n files under <skillsRoot>/<kebab-name>/SKILL.md and are always offered to\n the user for a decision before they land.\n- Local edits are session-scoped; global edits persist across sessions.\n- Gap C2: EVERY edit MUST include a \"blastRadius\" field indicating how broadly\n the edit applies: \"general\" (cross-project tactical rule, applies everywhere),\n \"project\" (valid for the current project/repo), or \"session\" (one-off,\n specific to this session's context). The review gate validates that\n local-scope edits use \"session\" or \"project\", and global-scope edits use\n \"general\" or \"project\". When in doubt, prefer narrower blast radius.\n- Ground every edit in evidence: the session trajectory (recent direct user\n messages) is provided when available; prefer edits backed by it over\n speculation, and never invent preferences the user did not express.\n- Stale entries (superseded by newer ones, never referenced in recent\n trajectories, obsolete facts): propose action \"archive\" instead of\n \"delete\" \u2014 archive hides the entry from injection while keeping its data\n restorable; it requires only kind + id.\n- Output JSON only, exactly this shape:\n{\n \"summary\": \"one sentence\",\n \"rationale\": \"why these edits are justified by the evidence\",\n \"expectedOutcome\": \"what should improve and how to validate it\",\n \"edits\": [\n {\n \"action\": \"create|update|delete\",\n \"kind\": \"prompt|memory|skill|subagent\",\n \"id\": \"stable id for update/delete, optional for create\",\n \"title\": \"required for create/update except delete\",\n \"content\": \"required for create/update except delete\",\n \"path\": \"optional grouping path\",\n \"reference\": {\"type\":\"python\",\"import\":\"pkg.mod\",\"callable\":\"fn\"} ,\n \"arguments\": {\"name\": {\"type\":\"string\",\"required\":true,\"description\":\"...\"}},\n \"skill_kind\": \"executable|guidance (optional; skill kind only \u2014 guidance = SKILL.md document without reference)\",\n \"metadata\": {},\n \"reason\": \"why this edit is useful\",\n \"blastRadius\": \"general|project|session\"\n }\n ]\n}";
|
|
14
|
+
export declare const PLANNER_SYSTEM_PROMPT = "You are the /evolve continual harness subsystem.\n\nYour job is to improve the editable continual harness state. Instead of\nsummarizing the conversation you emit precise Create, Update, or Delete edits\nto reusable state: prompt notes, memories, skills, and subagent specs.\n\nRules:\n- The base system prompt is immutable and MUST NOT be rewritten (never edit id \"base_system_prompt\").\n- Prefer small evidence-backed edits. If no useful edit is justified, return an empty edits array.\n- Repeated evidence is mandatory for prompt/skill/subagent proposals: the same\n pattern must recur at least twice in the trajectory. A single one-off\n interaction never justifies a prompt, skill, or subagent edit \u2014 return an\n empty edits array with a rationale instead. Session-scoped local prompt\n landings converge on /evolve wrapup human review; the turn pipeline must\n not silently persist one-off abstractions.\n- prompt = narrow behavioral policy addendums; memory = ONE FACT per entry, typed via metadata.memoryType\n- (user = who the user is; feedback = a pitfall/correction or confirmed approach, MUST carry Why + How to apply;\n- project = work goal/constraint not derivable from the repo; reference = URL/dashboard/ticket pointer).\n- Memory rules: one fact per entry (title = one-line retrieval hook, content = the fact alone);\n- never save what the repo already records (code structure, past fixes, git history, AGENTS.md) or what only\n- matters to this conversation \u2014 save the non-obvious point instead; before creating, update the existing\n- entry when one already covers the fact (creates carry metadata {memoryType} or they are rejected);\n- skill = repeatable procedures (must carry a python reference {type:\"python\", import, callable}\n and an arguments object); subagent = reusable delegation roles.\n- Skill entries are authored to the DSH skill quality standard\n (skill-creator, distilled from the official deepseek-harness 11 skills;\n the full facts are provided in the <skill_quality_standard> block below):\n only for a REAL trigger scenario grounded in the trajectory\n (who, in what real task, what signal) \u2014 never invent one to pad the store;\n never duplicate the official 11 skills or existing entries; content is a\n SKILL.md document (frontmatter routing with \"use when / do not use when\"\n description, boundary declaration, prerequisites and exclusions, layered\n information, verifiable completion criteria). Self-check every proposed\n skill against the 7 structural features and state the result in its\n reason field.\n- Repeated multi-step workflows (session start/end routines, recurring\n wrap-up or handoff procedures) may be proposed as guidance skills:\n kind=skill, skill_kind=\"guidance\", content = a SKILL.md document (no\n python reference \u2014 executable skills keep requiring reference +\n arguments). Only propose with repeated evidence in the trajectory, never\n for one-off flows. Guidance skills materialize as discoverable SKILL.md\n files under <skillsRoot>/<kebab-name>/SKILL.md and are always offered to\n the user for a decision before they land.\n- Local edits are session-scoped; global edits persist across sessions.\n- Gap C2: EVERY edit MUST include a \"blastRadius\" field indicating how broadly\n the edit applies: \"general\" (cross-project tactical rule, applies everywhere),\n \"project\" (valid for the current project/repo), or \"session\" (one-off,\n specific to this session's context). The review gate validates that\n local-scope edits use \"session\" or \"project\", and global-scope edits use\n \"general\" or \"project\". When in doubt, prefer narrower blast radius.\n- Ground every edit in evidence: the session trajectory (recent direct user\n messages) is provided when available; prefer edits backed by it over\n speculation, and never invent preferences the user did not express.\n- Stale entries (superseded by newer ones, never referenced in recent\n trajectories, obsolete facts): propose action \"archive\" instead of\n \"delete\" \u2014 archive hides the entry from injection while keeping its data\n restorable; it requires only kind + id.\n- Output JSON only, exactly this shape:\n{\n \"summary\": \"one sentence\",\n \"rationale\": \"why these edits are justified by the evidence\",\n \"expectedOutcome\": \"what should improve and how to validate it\",\n \"edits\": [\n {\n \"action\": \"create|update|delete\",\n \"kind\": \"prompt|memory|skill|subagent\",\n \"id\": \"stable id for update/delete, optional for create\",\n \"title\": \"required for create/update except delete\",\n \"content\": \"required for create/update except delete\",\n \"path\": \"optional grouping path\",\n \"reference\": {\"type\":\"python\",\"import\":\"pkg.mod\",\"callable\":\"fn\"} ,\n \"arguments\": {\"name\": {\"type\":\"string\",\"required\":true,\"description\":\"...\"}},\n \"skill_kind\": \"executable|guidance (optional; skill kind only \u2014 guidance = SKILL.md document without reference)\",\n \"metadata\": {},\n \"reason\": \"why this edit is useful\",\n \"blastRadius\": \"general|project|session\"\n }\n ]\n}";
|
|
15
15
|
export interface PlanOptions {
|
|
16
16
|
agent: Agent;
|
|
17
17
|
state: HarnessState;
|
package/lib/planner.js
CHANGED
|
@@ -14,6 +14,12 @@ to reusable state: prompt notes, memories, skills, and subagent specs.
|
|
|
14
14
|
Rules:
|
|
15
15
|
- The base system prompt is immutable and MUST NOT be rewritten (never edit id "base_system_prompt").
|
|
16
16
|
- Prefer small evidence-backed edits. If no useful edit is justified, return an empty edits array.
|
|
17
|
+
- Repeated evidence is mandatory for prompt/skill/subagent proposals: the same
|
|
18
|
+
pattern must recur at least twice in the trajectory. A single one-off
|
|
19
|
+
interaction never justifies a prompt, skill, or subagent edit — return an
|
|
20
|
+
empty edits array with a rationale instead. Session-scoped local prompt
|
|
21
|
+
landings converge on /evolve wrapup human review; the turn pipeline must
|
|
22
|
+
not silently persist one-off abstractions.
|
|
17
23
|
- prompt = narrow behavioral policy addendums; memory = ONE FACT per entry, typed via metadata.memoryType
|
|
18
24
|
- (user = who the user is; feedback = a pitfall/correction or confirmed approach, MUST carry Why + How to apply;
|
|
19
25
|
- project = work goal/constraint not derivable from the repo; reference = URL/dashboard/ticket pointer).
|
|
@@ -85,7 +91,7 @@ export async function planWithLlm(ctx, options) {
|
|
|
85
91
|
}
|
|
86
92
|
const scopeInstruction = options.global
|
|
87
93
|
? "Requested scope: global. Only propose stable cross-session lessons, durable preferences, reusable skills/subagents, or explicitly project-qualified facts."
|
|
88
|
-
: "Requested scope: local.
|
|
94
|
+
: "Requested scope: local. Propose session-scoped edits only when the trajectory shows repeated evidence (>=2 occurrences of the same pattern); a single one-off interaction returns an empty edits array. Global entries are read-only context — do not propose update/delete for them.";
|
|
89
95
|
// Ground the plan in the caller's session: Route A carries the context
|
|
90
96
|
// as a session-derived message prefix (cache-eligible on providers with
|
|
91
97
|
// prompt caching) and drops the auto-extracted trajectory block; Route B
|
package/lib/review-scheduler.js
CHANGED
|
@@ -68,10 +68,9 @@ export function createReviewScheduler(execute, cursorOf) {
|
|
|
68
68
|
bestCursor = candidate;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
71
|
+
// bestIndex is always set here: the error purge above leaves only
|
|
72
|
+
// acquired snapshots in `settled` (SnapshotAcquisition has exactly
|
|
73
|
+
// two variants), so the first settled entry takes the slot.
|
|
75
74
|
const chosen = pending[bestIndex];
|
|
76
75
|
pending.splice(bestIndex, 1);
|
|
77
76
|
for (let index = pending.length - 1; index >= 0; index -= 1) {
|
package/lib/review.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface ReviewOptions {
|
|
|
44
44
|
/** Direct-call token ledger destination; the review phase is fixed by this module. */
|
|
45
45
|
tokenUsage?: TokenUsageTarget;
|
|
46
46
|
}
|
|
47
|
-
export declare const AUTO_REVIEW_SYSTEM_PROMPT = "You are the automatic /evolve review gate.\n\nDecide whether this checkpoint should continue the non-memory /evolve path.\nA dedicated memory extraction agent has already processed the same snapshot\nwith a frozen memory manifest, so do NOT approve solely to create, update,\narchive, or delete a memory entry. Decline with a rationale that the memory\nphase owns that evidence. For the remaining kinds, approve repeated
|
|
47
|
+
export declare const AUTO_REVIEW_SYSTEM_PROMPT = "You are the automatic /evolve review gate.\n\nDecide whether this checkpoint should continue the non-memory /evolve path.\nA dedicated memory extraction agent has already processed the same snapshot\nwith a frozen memory manifest, so do NOT approve solely to create, update,\narchive, or delete a memory entry. Decline with a rationale that the memory\nphase owns that evidence. For the remaining kinds, approve ONLY when the\ntrajectory shows repeated evidence: the same failure, tactic, delegation\nrole, or behavioral policy recurs at least twice across turns. A single\none-off interaction defaults to decline no matter how useful it looks for\nfuture turns \u2014 one data point is never enough to generalize. When approving,\ncite the repeated spans in the instructions so the planner can ground its\nedits.\n\nThe current harness state below includes GLOBAL entries (scope=global) plus\nthis session's local entries (scope=local). When a topic is already covered\nby a global entry, do NOT approve a local duplicate of it \u2014 decline and say\nin the rationale that the topic is already covered globally.\n\nReject one-off noise, unsupported hypotheses, transient tool outputs, and\nrequests that carry no reusable content.\n\nStale local entries (superseded, long-unused, obsolete facts) are a valid\nrefine target: approve with instructions naming the entry ids, and tell the\nplanner to archive them (archive hides from injection, data stays restorable)\nrather than delete.\n\nSkill-related trajectories (the evidence concerns creating or improving a\nskill entry) are judged against the DSH skill quality standard (skill-audit\ndimensions: frontmatter routing, the 7 structural features, paragraph\nskeleton, no duplication of the official 11 skills or covered skills).\nApprove only when the trajectory shows a REAL trigger scenario and the\nresulting skill would meet the standard; otherwise decline and say in the\nrationale what must improve \u2014 drafting follows skill-creator, and the\nplanner receives the standard as its <skill_quality_standard> block.\n\nRepeated multi-step workflows (session start/end routines, recurring\nwrap-up or handoff procedures) are a valid refine target: approve with\ninstructions telling the planner to propose a guidance skill (kind=skill,\nskill_kind=guidance \u2014 a SKILL.md document, no python reference). Only\npropose when the same workflow recurs in the trajectory \u2014 never for\none-off flows. Auto-created skills are always offered to the user for a\ndecision before they land; the gate never writes a skill silently.\n\nReturn JSON only:\n{\n \"shouldRefine\": true|false,\n \"rationale\": \"short reason\",\n \"instructions\": \"optional concise instructions for /evolve if shouldRefine is true\"\n}";
|
|
48
48
|
/** Parse the gate's JSON reply. */
|
|
49
49
|
export declare function parseAutoRefineReview(text: string): AutoRefineReview;
|
|
50
50
|
/** Serialize surface events to bounded role-prefixed text. */
|
package/lib/review.js
CHANGED
|
@@ -10,8 +10,13 @@ Decide whether this checkpoint should continue the non-memory /evolve path.
|
|
|
10
10
|
A dedicated memory extraction agent has already processed the same snapshot
|
|
11
11
|
with a frozen memory manifest, so do NOT approve solely to create, update,
|
|
12
12
|
archive, or delete a memory entry. Decline with a rationale that the memory
|
|
13
|
-
phase owns that evidence. For the remaining kinds, approve
|
|
14
|
-
|
|
13
|
+
phase owns that evidence. For the remaining kinds, approve ONLY when the
|
|
14
|
+
trajectory shows repeated evidence: the same failure, tactic, delegation
|
|
15
|
+
role, or behavioral policy recurs at least twice across turns. A single
|
|
16
|
+
one-off interaction defaults to decline no matter how useful it looks for
|
|
17
|
+
future turns — one data point is never enough to generalize. When approving,
|
|
18
|
+
cite the repeated spans in the instructions so the planner can ground its
|
|
19
|
+
edits.
|
|
15
20
|
|
|
16
21
|
The current harness state below includes GLOBAL entries (scope=global) plus
|
|
17
22
|
this session's local entries (scope=local). When a topic is already covered
|
|
@@ -119,7 +124,7 @@ export async function reviewAutoRefine(ctx, options) {
|
|
|
119
124
|
`<current_harness_state>\n${formatHarnessStateForPrompt(state)}\n</current_harness_state>`,
|
|
120
125
|
`<refinement_history>\n${historyForPrompt(history)}\n</refinement_history>`,
|
|
121
126
|
conversationBlock,
|
|
122
|
-
"Return shouldRefine=true when the trajectory
|
|
127
|
+
"Return shouldRefine=true only when the trajectory shows repeated evidence (>=2 occurrences) useful to this session's future turns. A single one-off interaction defaults to shouldRefine=false. Prefer local edits; do not ask for global refinement here.",
|
|
123
128
|
]
|
|
124
129
|
.filter(Boolean)
|
|
125
130
|
.join("\n\n");
|
package/lib/skill-render.js
CHANGED
|
@@ -57,7 +57,8 @@ export function renderSkillMarkdown(entry) {
|
|
|
57
57
|
lines.push(`- ${key}: ${JSON.stringify(value)}`);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
const args = entry.arguments;
|
|
61
|
+
if (args && typeof args === "object" && Object.keys(args).length > 0) {
|
|
61
62
|
lines.push("", "## Arguments", "```json", JSON.stringify(entry.arguments, null, 2), "```");
|
|
62
63
|
}
|
|
63
64
|
return `${lines.join("\n").trimEnd()}\n`;
|
package/lib/skillquality.d.ts
CHANGED
|
@@ -54,8 +54,11 @@ export declare function validateRenderedSkillMarkdown(markdown: string): string[
|
|
|
54
54
|
* parsed instead of the generated one (the platform reads the FIRST
|
|
55
55
|
* closing `---`), so the file could be ignored or routed wrongly;
|
|
56
56
|
* - resource references (`references/…`, `scripts/…`) must be skill-local
|
|
57
|
-
* relative paths —
|
|
58
|
-
*
|
|
57
|
+
* relative paths — an embedded `/../` after the category escapes the skill
|
|
58
|
+
* directory and is rejected. A leading `../` instead addresses a sibling
|
|
59
|
+
* skill (cross-skill interlink, e.g. `../skill-creator/…`) and is allowed:
|
|
60
|
+
* it is not dangling-checked since cross-skill targets live outside this
|
|
61
|
+
* entry's shipped files (see skillResourceRefs).
|
|
59
62
|
* Returns human-readable problems; an empty array means the content is
|
|
60
63
|
* mechanically acceptable.
|
|
61
64
|
*/
|
|
@@ -73,6 +76,8 @@ export declare function validateRenderedSkill(entry: HarnessEntry): string[];
|
|
|
73
76
|
* targets starting with the category, plus backticked/prose paths carrying
|
|
74
77
|
* a filename extension. Used after materialization to warn about dangling
|
|
75
78
|
* references (a body referencing a resource the entry never ships).
|
|
79
|
+
* Leading-`../` cross-skill interlinks are skipped, not warned: they resolve
|
|
80
|
+
* against sibling skill directories, never against shipped files.
|
|
76
81
|
*/
|
|
77
82
|
export declare function skillResourceRefs(content: string): string[];
|
|
78
83
|
/** Kebab-case name under which the entry materializes (exported for diagnostics). */
|
package/lib/skillquality.js
CHANGED
|
@@ -244,8 +244,11 @@ export function validateRenderedSkillMarkdown(markdown) {
|
|
|
244
244
|
* parsed instead of the generated one (the platform reads the FIRST
|
|
245
245
|
* closing `---`), so the file could be ignored or routed wrongly;
|
|
246
246
|
* - resource references (`references/…`, `scripts/…`) must be skill-local
|
|
247
|
-
* relative paths —
|
|
248
|
-
*
|
|
247
|
+
* relative paths — an embedded `/../` after the category escapes the skill
|
|
248
|
+
* directory and is rejected. A leading `../` instead addresses a sibling
|
|
249
|
+
* skill (cross-skill interlink, e.g. `../skill-creator/…`) and is allowed:
|
|
250
|
+
* it is not dangling-checked since cross-skill targets live outside this
|
|
251
|
+
* entry's shipped files (see skillResourceRefs).
|
|
249
252
|
* Returns human-readable problems; an empty array means the content is
|
|
250
253
|
* mechanically acceptable.
|
|
251
254
|
*/
|
|
@@ -260,8 +263,12 @@ export function validateSkillEntryContent(content) {
|
|
|
260
263
|
problems.push("skill content must not start with a `---` frontmatter block (the materializer generates frontmatter from id/title; a body-level `---` would shadow it and the platform could IGNORE the file)");
|
|
261
264
|
}
|
|
262
265
|
for (const match of trimmed.matchAll(/(?<![\w])(references|scripts)\/[^\s)]+/g)) {
|
|
266
|
+
// match[0] always starts with `references/` or `scripts/` (the regex
|
|
267
|
+
// anchors it there), so only an embedded `/../` can escape the skill
|
|
268
|
+
// directory — the ../-prefix, absolute-path, and drive-letter
|
|
269
|
+
// spellings can never match and are intentionally not checked.
|
|
263
270
|
const ref = match[0] ?? "";
|
|
264
|
-
if (ref.
|
|
271
|
+
if (ref.includes("/../")) {
|
|
265
272
|
problems.push(`skill content resource reference escapes the skill directory: ${ref}`);
|
|
266
273
|
}
|
|
267
274
|
}
|
|
@@ -282,6 +289,8 @@ export function validateRenderedSkill(entry) {
|
|
|
282
289
|
* targets starting with the category, plus backticked/prose paths carrying
|
|
283
290
|
* a filename extension. Used after materialization to warn about dangling
|
|
284
291
|
* references (a body referencing a resource the entry never ships).
|
|
292
|
+
* Leading-`../` cross-skill interlinks are skipped, not warned: they resolve
|
|
293
|
+
* against sibling skill directories, never against shipped files.
|
|
285
294
|
*/
|
|
286
295
|
export function skillResourceRefs(content) {
|
|
287
296
|
const refs = new Set();
|
package/lib/types.d.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type model for the continual-evolution harness state.
|
|
3
|
-
*
|
|
4
|
-
* Design provenance: the state model follows the continual-harness design
|
|
5
|
-
* validated in prime-agent's `/refine` subsystem (MIT): versioned entries
|
|
6
|
-
* keyed by kind, an append-only refinement history carrying an evidence
|
|
7
|
-
* trail, atomic persistence, and deterministic inverse-op rollback. The
|
|
8
|
-
* code here is an original implementation, written for the DeepSeek Harness
|
|
9
|
-
* plugin surface.
|
|
10
|
-
*/
|
|
11
1
|
/** What a harness entry can be. */
|
|
12
2
|
export type RefinementKind = "prompt" | "memory" | "skill" | "subagent";
|
|
13
3
|
/**
|
|
@@ -201,7 +191,15 @@ export interface PythonReference {
|
|
|
201
191
|
callable?: string;
|
|
202
192
|
call_pattern?: string;
|
|
203
193
|
}
|
|
204
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* A stable id derived from a title: ASCII runs become snake_case (unchanged
|
|
196
|
+
* legacy behavior). Titles with no ASCII content at all (e.g. pure Chinese)
|
|
197
|
+
* used to collapse onto the bare fallback, so distinct memories shared one
|
|
198
|
+
* id and every later create failed as "already exists". They now take
|
|
199
|
+
* `<fallback>_<sha256-8>` instead: distinct titles get distinct ids while
|
|
200
|
+
* identical titles still dedupe onto the same id. The fallback itself is
|
|
201
|
+
* assumed id-safe (callers pass entry kinds).
|
|
202
|
+
*/
|
|
205
203
|
export declare function slug(raw: string, fallback: string): string;
|
|
206
204
|
/** Fresh empty state with the current schema version. */
|
|
207
205
|
export declare function emptyHarnessState(): HarnessState;
|
package/lib/types.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* code here is an original implementation, written for the DeepSeek Harness
|
|
9
9
|
* plugin surface.
|
|
10
10
|
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
11
12
|
/**
|
|
12
13
|
* Memory-entry shape discipline (one fact per entry, typed like ZCode's
|
|
13
14
|
* memory frontmatter): every `memory` create must stamp one of these in
|
|
@@ -85,7 +86,15 @@ export function isArchived(entry) {
|
|
|
85
86
|
const value = entry.metadata[ARCHIVED_AT_KEY];
|
|
86
87
|
return typeof value === "string" && value.length > 0;
|
|
87
88
|
}
|
|
88
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* A stable id derived from a title: ASCII runs become snake_case (unchanged
|
|
91
|
+
* legacy behavior). Titles with no ASCII content at all (e.g. pure Chinese)
|
|
92
|
+
* used to collapse onto the bare fallback, so distinct memories shared one
|
|
93
|
+
* id and every later create failed as "already exists". They now take
|
|
94
|
+
* `<fallback>_<sha256-8>` instead: distinct titles get distinct ids while
|
|
95
|
+
* identical titles still dedupe onto the same id. The fallback itself is
|
|
96
|
+
* assumed id-safe (callers pass entry kinds).
|
|
97
|
+
*/
|
|
89
98
|
export function slug(raw, fallback) {
|
|
90
99
|
const normalized = raw
|
|
91
100
|
.trim()
|
|
@@ -93,7 +102,10 @@ export function slug(raw, fallback) {
|
|
|
93
102
|
.replace(/[^a-z0-9]+/g, "_")
|
|
94
103
|
.replace(/^_+|_+$/g, "")
|
|
95
104
|
.slice(0, 80);
|
|
96
|
-
|
|
105
|
+
if (normalized)
|
|
106
|
+
return normalized;
|
|
107
|
+
const digest = createHash("sha256").update(raw, "utf8").digest("hex").slice(0, 8);
|
|
108
|
+
return `${fallback}_${digest}`;
|
|
97
109
|
}
|
|
98
110
|
/** Fresh empty state with the current schema version. */
|
|
99
111
|
export function emptyHarnessState() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-continual-evolve",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Continual self-evolution plugin for DeepSeek Harness: versioned, auditable, rollback-safe harness state (prompt notes, memories, skills, subagent specs) refined from session trajectories.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,12 +48,15 @@
|
|
|
48
48
|
"build": "tsc -p tsconfig.json",
|
|
49
49
|
"prepare": "tsc -p tsconfig.json",
|
|
50
50
|
"dev": "tsc -p tsconfig.json --watch",
|
|
51
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.scripts.json --noEmit",
|
|
52
52
|
"test": "vitest run",
|
|
53
53
|
"test:coverage": "vitest run --coverage",
|
|
54
|
+
"coverage:gaps": "tsx scripts/coverage-gaps.ts",
|
|
54
55
|
"test:watch": "vitest",
|
|
55
56
|
"lint": "oxlint src test",
|
|
56
|
-
"clean": "rm -rf lib"
|
|
57
|
+
"clean": "rm -rf lib",
|
|
58
|
+
"check:docs": "tsx scripts/run-gates.ts docs",
|
|
59
|
+
"check:governance": "tsx scripts/verify-governance.ts"
|
|
57
60
|
},
|
|
58
61
|
"peerDependencies": {
|
|
59
62
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
"@types/node": "^22.10.0",
|
|
74
77
|
"@vitest/coverage-v8": "^3.2.0",
|
|
75
78
|
"oxlint": "^0.16.0",
|
|
79
|
+
"tsx": "^4.23.15",
|
|
76
80
|
"typescript": "^5.9.0",
|
|
77
81
|
"vitest": "^3.2.0"
|
|
78
82
|
}
|