iterate-plugin 2.12.3 → 3.2.1
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 +28 -13
- package/dist/approval-gate.js +16 -2
- package/dist/config-loader.js +5 -0
- package/dist/index.js +16 -6
- package/dist/session-hooks.js +36 -11
- package/dist/skill-prompt.js +3 -0
- package/dist/tools/decision-log.js +10 -1
- package/dist/tools/defense-events.js +260 -0
- package/dist/tools/defense-store.js +97 -0
- package/dist/tools/experience-bank.js +248 -0
- package/dist/tools/experience-store.js +132 -0
- package/dist/tools/quality-gate.js +180 -0
- package/dist/tools/quality-store.js +174 -0
- package/lib/client.js +318 -15
- package/package.json +6 -6
- package/src/approval-gate.ts +14 -2
- package/src/client/index.ts +288 -16
- package/src/config-loader.ts +5 -0
- package/src/index.ts +16 -6
- package/src/session-hooks.ts +33 -11
- package/src/skill-prompt.ts +3 -0
- package/src/tools/decision-log.ts +10 -1
- package/src/tools/defense-events.ts +295 -0
- package/src/tools/defense-store.ts +113 -0
- package/src/tools/experience-bank.ts +264 -0
- package/src/tools/experience-store.ts +160 -0
- package/src/tools/quality-gate.ts +193 -0
- package/src/tools/quality-store.ts +199 -0
- package/src/types.ts +118 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# iterate-plugin for DeepSeek Harness (dsh)
|
|
2
2
|
|
|
3
|
-
> dsh 桌面端的 iterate
|
|
4
|
-
> The iterate ecosystem's
|
|
3
|
+
> dsh 桌面端的 iterate 质量指挥中心 + 经验银行插件(v3.1)。把 iterate 生态的同一套 review/fix loop 直接搬进 dsh 界面,新增质量门禁、经验银行、防御事件流与原生指挥操作。
|
|
4
|
+
> The iterate ecosystem's quality command center + experience bank plugin for dsh (v3.1). Natively embedded inside the DeepSeek Harness (dsh) desktop client with quality gates, experience bank, defense events stream, and native command buttons.
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
7
7
|
<a href="README.md"><strong>English</strong></a> ·
|
|
@@ -61,7 +61,9 @@ dsh plugin --profile web add iterate-plugin
|
|
|
61
61
|
|
|
62
62
|
`iterate-plugin` is the [iterate](https://github.com/jingzhao-l/iterate-skill) integration for the [DeepSeek Harness (dsh)](https://github.com/deepseek-ai/deepseek-harness) desktop client. It brings iterate's review loop (review → triage → fix → validate → converge) directly into the dsh UI, offering **autonomous closed-loop code iteration** (normal mode) and **dry-run read-only multi-round review**.
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
**v3.1 Quality Command Center**: The plugin has been upgraded from a "passive observation panel" to an "active command center + knowledge base". New features include quality gate view, experience bank, defense events stream, native command buttons, and task_mode indicator.
|
|
65
|
+
|
|
66
|
+
Besides 17 pure-function tools, it ships a **build-free Web UI layer** (convergence dashboard, triage panel, stats card, observatory panel with 10 tabs, theme skin, etc.) that plugs straight into dsh's existing UI slots. Configuration (`iterate.config.yaml` and the review dimensions) is identical across the other two components of the iterate ecosystem ([skill](https://github.com/jingzhao-l/iterate-skill) / [headless engine](https://github.com/jingzhao-l/iterate-harness)) — zero migration cost.
|
|
65
67
|
|
|
66
68
|
## Features
|
|
67
69
|
|
|
@@ -81,23 +83,28 @@ Besides 13 pure-function tools, it ships a **build-free Web UI layer** (triage p
|
|
|
81
83
|
| Fix atomic findings only, keep architectural for later | ❌ | ✅ |
|
|
82
84
|
| Breakpoint save / resume (long iterations) | ✅ | ✅ |
|
|
83
85
|
|
|
84
|
-
### Tool layer
|
|
86
|
+
### Tool layer (v3.1: 17 tools)
|
|
85
87
|
|
|
86
|
-
- **
|
|
88
|
+
- **17 registered tools** (14 original + 3 v3.1 quality command center tools):
|
|
89
|
+
- Original: `iterate_config` / `iterate_validate` / `iterate_decision_log` / `iterate_context` / `iterate_review` / `iterate_triage` / `iterate_fix` / `iterate_diff` / `iterate_rollback` / `iterate_checkpoint` / `iterate_status` / `iterate_history` / `iterate_prune` / `iterate_transcript`
|
|
90
|
+
- v3.1/v3.2: `iterate_experience` (list/search/get/**add**) / `iterate_quality_gate` (read/**compute**) / `iterate_defense_events` (list/counts/**record**)
|
|
87
91
|
- **Findings triage loop**: review → UI triage (y/n/a) → `iterate_triage` writes back `known_intentional` → auto-filtered next round
|
|
88
92
|
- **Structured fix system**: each fix backs up first, writes a registry entry, records the diff; a failed validation can be reverted with `iterate_rollback`
|
|
89
93
|
- **Breakpoint resume**: checkpoints saved at the start of each round; interrupted long iterations can resume
|
|
90
94
|
- **History audit**: `iterate_history` reads the decision log (filtered by type / time / count) and the fix registry summary to audit run process and fix details
|
|
91
95
|
- **Runtime cleanup**: `iterate_prune` removes stale decision-log entries, stale checkpoints, orphaned fix backups and empty rounds; dry-run by default (report-only), real cleanup requires `dryRun:false`, and every cleanup is logged
|
|
92
96
|
- **Config read / write**: `iterate_config` supports validated, backed-up, rollback-capable partial writes
|
|
97
|
+
- **v3.1/v3.2 Experience Bank**: `iterate_experience` queries historical fixes and patterns with search/filter/adopt, and can persist new verified fixes (`add`) — re-adding the same pattern+dimension bumps its hit count instead of duplicating it
|
|
98
|
+
- **v3.1/v3.2 Quality Gate**: `iterate_quality_gate` reads quality gate status with dimension convergence rates and PASS/FAIL, and can recompute + persist a fresh certificate (`compute`) from this round's findings/validation results (real convergence from `findingsByRound`)
|
|
99
|
+
- **v3.1/v3.2 Defense Events**: `iterate_defense_events` queries defense events (precondition failures, rollbacks, invariant violations, assumption falsifications) and can `record` new ones; readable labels follow the project language (en/zh)
|
|
93
100
|
|
|
94
|
-
### UI layer (build-free client slots)
|
|
101
|
+
### UI layer (build-free client slots, v3.1: 10 tabs)
|
|
95
102
|
|
|
96
103
|
| UI component | Mounted slot | Function |
|
|
97
104
|
| --- | --- | --- |
|
|
98
|
-
| ConvergenceDashboard | `conversation.input.dock` | Live round progress bar, severity stats, dimension badges, trend mini-chart above the input; normal mode also shows fix-count badges; plus a live workflow-phase chip (current phase + running/stopped) |
|
|
99
|
-
| ObservatoryPanel | `conversation.input.dock` |
|
|
100
|
-
| TriagePanel | `conversation.chat.turnTail` | Per-finding y/n/a triage, filtering, batch (incl. select-all), keyboard shortcuts, localStorage persistence, copy-YAML / apply-instruction |
|
|
105
|
+
| ConvergenceDashboard | `conversation.input.dock` | Live round progress bar, severity stats, dimension badges, trend mini-chart above the input; normal mode also shows fix-count badges; plus a live workflow-phase chip (current phase + running/stopped); **v3.1: task_mode indicator (code/iterate)** |
|
|
106
|
+
| ObservatoryPanel | `conversation.input.dock` | **Ten-tab** runtime observatory below the input: live activity stream (type filter), review threads (expand/collapse all), convergence trend, finding locations (severity/dimension/search filter), fixes + rollback, checkpoint resume, decision timeline (type/round filter + search); **v3.1: Quality Gate (F8)**, **Experience Bank (F9)**, **Defense Events (F10)**; one-click export of all observatory data to JSON (download, copy fallback) |
|
|
107
|
+
| TriagePanel | `conversation.chat.turnTail` | Per-finding y/n/a triage, filtering, batch (incl. select-all), keyboard shortcuts, localStorage persistence, copy-YAML / apply-instruction; **v3.1: Native command buttons (approve architectural fix, trigger new round, rollback to checkpoint)** |
|
|
101
108
|
| StatsCard | `conversation.chat.turnTail` | When no findings remain: convergence stats, round history table, trend chart, completion summary |
|
|
102
109
|
| iterate theme skin | `theme.overrideTokens` | Warm-amber 13-dsw-token override, light/dark modes, togglable in settings |
|
|
103
110
|
| ProgressCapsule | `shell.overlay` | Popup notification on each round completion / convergence (incl. convergence confirm) |
|
|
@@ -212,7 +219,7 @@ validation:
|
|
|
212
219
|
|
|
213
220
|
> The config can be read and **validated-partially-written** via `iterate_config` (auto backup, auto rollback on write failure).
|
|
214
221
|
|
|
215
|
-
## Registered tools (
|
|
222
|
+
## Registered tools (v3.1: 17)
|
|
216
223
|
|
|
217
224
|
| Tool | Function |
|
|
218
225
|
| --- | --- |
|
|
@@ -229,6 +236,10 @@ validation:
|
|
|
229
236
|
| `iterate_status` | Summarize current iteration state: mode, current/last round, fixes applied, remaining architectural, decision-log entry count, whether a checkpoint exists |
|
|
230
237
|
| `iterate_history` | Read iteration history (read-only): decision-log entries (filter by `type` / `since` / `limit`, default latest 50, cap 200) + fix-registry summary (per-round fixed/failed counts). For auditing the run, tracing logs, and inventorying fixes |
|
|
231
238
|
| `iterate_prune` | Clean runtime artifacts: stale decision-log entries (by `retainDays`, default 30), stale checkpoints, orphaned fix backups, empty rounds. Dry-run by default (report-only); real cleanup with `dryRun:false`, each cleanup logged |
|
|
239
|
+
| `iterate_transcript` | Runtime observatory: persist review transcripts, threads, fixes, and nudge directions to `.iterate/transcript.json` for the client observatory |
|
|
240
|
+
| `iterate_experience` | **v3.1/v3.2** Query the experience bank (list/search/get), or `add` a new verified fix: re-adding the same pattern+dimension bumps its hit count instead of duplicating it. Persists to `.iterate/experience.json` |
|
|
241
|
+
| `iterate_quality_gate` | **v3.1/v3.2** Read the quality certificate (`read`), or recompute + persist a fresh one (`compute`) from findings, validation results, `findingsByRound`, and `fixedByDimension`. Real per-dimension convergence rates |
|
|
242
|
+
| `iterate_defense_events` | **v3.1/v3.2** Query defense events (list/counts), or `record` a new one. Human-readable labels follow the project language (en/zh) |
|
|
232
243
|
|
|
233
244
|
## Runtime artifact layout
|
|
234
245
|
|
|
@@ -240,6 +251,9 @@ All runtime state lives under `.iterate/` at the project root (can be excluded v
|
|
|
240
251
|
checkpoint.json # iteration breakpoint (resume)
|
|
241
252
|
transcript.json # runtime-observatory manifest (per-reviewer threads, trend, fixes, timeline, nudge)
|
|
242
253
|
transcript-live.ndjson # append-only near-real-time reviewer-activity feed (read/fix/rollback/validate…), byte-capped
|
|
254
|
+
experience.json # v3.1: experience bank (historical fixes and patterns, accumulated across sessions)
|
|
255
|
+
quality-gate.json # v3.1: quality gate snapshot (dimension convergence, verification pass rates, PASS/FAIL)
|
|
256
|
+
defense-events.json # v3.1: defense events stream (precondition failures, rollbacks, invariant violations, assumption falsifications)
|
|
243
257
|
fixes/
|
|
244
258
|
registry.json # fix registry (list of FixRecords, grouped by round)
|
|
245
259
|
<fix-id>_<ts>.bak # original file backup before each fix
|
|
@@ -249,11 +263,12 @@ All runtime state lives under `.iterate/` at the project root (can be excluded v
|
|
|
249
263
|
|
|
250
264
|
The plugin follows dsh's "everything-is-a-plugin" architecture:
|
|
251
265
|
|
|
252
|
-
- **Does exactly two things**: injects the system prompt teaching the model the iterate workflow + registers
|
|
266
|
+
- **Does exactly two things**: injects the system prompt teaching the model the iterate workflow + registers 17 pure-function tools
|
|
253
267
|
- **All orchestration runs through dsh native `workflow` + `agent` + `parallel`**
|
|
254
268
|
- **Core logic is entirely pure functions** (dedupe / filter / sort / converge / meta-audit / diff computation / history filtering / cleanup reporting) — unit-testable, no I/O
|
|
255
269
|
- **Security model**: file writes confined to the resolved project root (path-traversal protection); always back up before writing, roll back on failure; config writes also back up + roll back; `iterate_prune` is dry-run by default and only clears artifacts under `.iterate/` with every cleanup logged; `iterate_fix` caps content length and `iterate_triage` caps entry count to fend off abnormal oversized payloads
|
|
256
270
|
- **Build-free UI**: `lib/client.js` uses a `React.createElement` tree + injected `<style>` tags, all colors via `--dsw-*` tokens, degrading gracefully when a service is missing
|
|
271
|
+
- **v3.1 Quality Command Center**: extends the plugin from "passive observation panel" to "active command center + knowledge base" with quality gates, experience bank, defense events, and native command buttons
|
|
257
272
|
- Follows the iterate skill's design principles: deterministic convergence, auditable, least privilege
|
|
258
273
|
|
|
259
274
|
## Running the tests
|
|
@@ -267,8 +282,8 @@ npm test
|
|
|
267
282
|
|
|
268
283
|
All tests pass:
|
|
269
284
|
|
|
270
|
-
- **
|
|
271
|
-
- Coverage: dedupe, filter, sort, multi-round convergence, meta-review audit, path safety, timeout clamping, config read/write + rollback, triage merge, diff computation, checkpoint validation, fix registry, history read + filter, prune cleanup report + dry-run semantics, UI pure functions (select-all key, runtime status guide).
|
|
285
|
+
- **426 unit tests green**, type-check clean
|
|
286
|
+
- Coverage: dedupe, filter, sort, multi-round convergence, meta-review audit, path safety, timeout clamping, config read/write + rollback, triage merge, diff computation, checkpoint validation, fix registry, history read + filter, prune cleanup report + dry-run semantics, UI pure functions (select-all key, runtime status guide), **v3.1: experience bank, quality gate, defense events**.
|
|
272
287
|
|
|
273
288
|
## ⚠️ Disclaimer
|
|
274
289
|
|
package/dist/approval-gate.js
CHANGED
|
@@ -46,12 +46,26 @@ function describe(toolName, arguments0) {
|
|
|
46
46
|
* deployments, and for any non-iterate tool.
|
|
47
47
|
*/
|
|
48
48
|
export function decideApproval(execution, policy) {
|
|
49
|
-
|
|
49
|
+
// Defensive reads: a hostile/proxied execution object must degrade to "not
|
|
50
|
+
// our tool" (allow) rather than throw inside the gate.
|
|
51
|
+
let name = '';
|
|
52
|
+
try {
|
|
53
|
+
name = typeof execution?.name === 'string' ? execution.name : '';
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
name = '';
|
|
57
|
+
}
|
|
50
58
|
if (!name)
|
|
51
59
|
return { kind: 'allow' };
|
|
52
60
|
if (!DESTRUCTIVE_TOOLS.has(name))
|
|
53
61
|
return { kind: 'allow' };
|
|
54
|
-
|
|
62
|
+
let rawArgs;
|
|
63
|
+
try {
|
|
64
|
+
rawArgs = execution.arguments;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
rawArgs = undefined;
|
|
68
|
+
}
|
|
55
69
|
const args = rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
56
70
|
? rawArgs
|
|
57
71
|
: {};
|
package/dist/config-loader.js
CHANGED
|
@@ -191,6 +191,11 @@ export function validateConfig(config) {
|
|
|
191
191
|
*/
|
|
192
192
|
export function resolveProjectRoot(input, sessionCwd) {
|
|
193
193
|
const raw = (input ?? '').trim();
|
|
194
|
+
// A NUL byte can never name a real path and makes `resolve()` (and every
|
|
195
|
+
// downstream fs call) throw — treat it as unsafe input, not a throw path.
|
|
196
|
+
if (raw.includes('\0')) {
|
|
197
|
+
return { ok: false, reason: 'Refusing project root containing NUL bytes.' };
|
|
198
|
+
}
|
|
194
199
|
const root = raw ? resolve(raw) : resolve(effectiveCwd(sessionCwd));
|
|
195
200
|
if (!root || root === sep) {
|
|
196
201
|
return { ok: false, reason: 'Refusing filesystem root as project root.' };
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* iterate-plugin — dsh plugin for the iterate autonomous closed-loop workflow
|
|
3
3
|
*
|
|
4
4
|
* Architecture:
|
|
5
|
-
* - The plugin registers
|
|
6
|
-
*
|
|
5
|
+
* - The plugin registers 17 tools (14 original + 3 v3.0 quality command center tools)
|
|
6
|
+
* Original: config, validate, decision-log, context, review, triage, fix, diff,
|
|
7
|
+
* rollback, checkpoint, status, history, prune, transcript
|
|
8
|
+
* v3.0: experience, quality_gate, defense_events
|
|
7
9
|
* - The plugin injects a system prompt section teaching the iterate workflow pattern
|
|
8
10
|
* - The model (prompted by the skill) writes a workflow script using dsh's `workflow` tool
|
|
9
11
|
* - The workflow script uses `agent()` / `parallel()` / `phase()` / `log()` to orchestrate
|
|
10
|
-
* - Subagents use the
|
|
11
|
-
* review, triage, apply/rollback/fixing, checkpoint, status, history, prune, transcript
|
|
12
|
+
* - Subagents use the 17 tools to do real work (read config, run validation, log decisions,
|
|
13
|
+
* review, triage, apply/rollback/fixing, checkpoint, status, history, prune, transcript,
|
|
14
|
+
* query experience bank, check quality gates, query defense events)
|
|
12
15
|
* - A `tools/pre-execute` hook gates destructive iterate calls behind human approval
|
|
13
16
|
* (F8 observatory approval policy: ask / deny / allow).
|
|
14
17
|
*
|
|
@@ -20,7 +23,7 @@
|
|
|
20
23
|
*
|
|
21
24
|
* Key files:
|
|
22
25
|
* - src/index.ts — Plugin entry: register tools + inject skill prompt
|
|
23
|
-
* - src/tools/ —
|
|
26
|
+
* - src/tools/ — 17 tool implementations (14 original + 3 v3.0)
|
|
24
27
|
* - src/config-loader.ts — YAML config loading
|
|
25
28
|
* - src/types.ts — Shared types
|
|
26
29
|
*/
|
|
@@ -35,13 +38,16 @@ import { registerCheckpointTool, registerStatusTool } from "./tools/checkpoint.j
|
|
|
35
38
|
import { registerHistoryTool } from "./tools/history.js";
|
|
36
39
|
import { registerPruneTool } from "./tools/prune.js";
|
|
37
40
|
import { registerTranscriptTool } from "./tools/transcript.js";
|
|
41
|
+
import { registerExperienceBankTool } from "./tools/experience-bank.js";
|
|
42
|
+
import { registerQualityGateTool } from "./tools/quality-gate.js";
|
|
43
|
+
import { registerDefenseEventsTool } from "./tools/defense-events.js";
|
|
38
44
|
import { registerSessionHooks } from "./session-hooks.js";
|
|
39
45
|
import { registerLiveCapture } from "./live.js";
|
|
40
46
|
import { ITERATE_SKILL_PROMPT } from "./skill-prompt.js";
|
|
41
47
|
export const name = 'iterate-plugin';
|
|
42
48
|
export const inject = ['tools', 'systemPrompt'];
|
|
43
49
|
export function apply(ctx) {
|
|
44
|
-
// 1. Register the 14
|
|
50
|
+
// 1. Register the 17 tools (14 original + 3 v3.0)
|
|
45
51
|
registerConfigTool(ctx);
|
|
46
52
|
registerValidateTool(ctx);
|
|
47
53
|
registerDecisionLogTool(ctx);
|
|
@@ -56,6 +62,10 @@ export function apply(ctx) {
|
|
|
56
62
|
registerHistoryTool(ctx);
|
|
57
63
|
registerPruneTool(ctx);
|
|
58
64
|
registerTranscriptTool(ctx);
|
|
65
|
+
// v3.0: Quality Command Center tools
|
|
66
|
+
registerExperienceBankTool(ctx);
|
|
67
|
+
registerQualityGateTool(ctx);
|
|
68
|
+
registerDefenseEventsTool(ctx);
|
|
59
69
|
// 2. Wire the observatory approval gate onto dsh's tools/pre-execute waterfall,
|
|
60
70
|
// and the live reviewer-activity feed onto tools/result.
|
|
61
71
|
registerSessionHooks(ctx);
|
package/dist/session-hooks.js
CHANGED
|
@@ -32,17 +32,36 @@ import { decideApproval, isDestructiveIterateTool } from "./approval-gate.js";
|
|
|
32
32
|
* Returns a dsh `PreToolDecision` so the caller can short-circuit the caller.
|
|
33
33
|
*/
|
|
34
34
|
export function gateDecision(exec) {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
35
|
+
// Defensively read the tool name: an exec handed to the waterfall is an
|
|
36
|
+
// ordinary object, but a hostile/proxied exec must degrade to "not our tool"
|
|
37
|
+
// (allow) instead of throwing before classification. The gate only ever
|
|
38
|
+
// inspects iterate tools, so an unreadable name also must not alter
|
|
39
|
+
// unrelated tooling.
|
|
40
|
+
let name = '';
|
|
41
|
+
try {
|
|
42
|
+
name = exec?.name ?? '';
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
name = '';
|
|
46
|
+
}
|
|
47
|
+
if (!isDestructiveIterateTool(name))
|
|
38
48
|
return { kind: 'allow' };
|
|
39
49
|
// Resolve the project root (use the call's own `path` arg, else the agent's
|
|
40
50
|
// session cwd) to read the effective observatory policy.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
let argPath;
|
|
52
|
+
let sessionCwd;
|
|
53
|
+
try {
|
|
54
|
+
const args = exec?.arguments;
|
|
55
|
+
if (args && typeof args === 'object' && !Array.isArray(args)) {
|
|
56
|
+
const p = args.path;
|
|
57
|
+
if (typeof p === 'string')
|
|
58
|
+
argPath = p;
|
|
59
|
+
}
|
|
60
|
+
sessionCwd = exec?.agent?.session?.header?.cwd;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// hostile/proxied exec — fall through with both undefined (defaults to ask)
|
|
64
|
+
}
|
|
46
65
|
const resolved = resolveProjectRoot(argPath, sessionCwd);
|
|
47
66
|
let policy = 'ask';
|
|
48
67
|
if (resolved.ok) {
|
|
@@ -67,13 +86,19 @@ export function gateDecision(exec) {
|
|
|
67
86
|
*/
|
|
68
87
|
export function registerSessionHooks(ctx) {
|
|
69
88
|
ctx.on('tools/pre-execute', (exec, next) => {
|
|
70
|
-
//
|
|
89
|
+
// Fail-safe: a throwing gate must never fail OPEN. Degrade to `ask` so a
|
|
90
|
+
// destructive call still routes through human consent instead of running
|
|
91
|
+
// via `next()`'s allow default (matches the header's documented contract).
|
|
71
92
|
let decision;
|
|
72
93
|
try {
|
|
73
94
|
decision = gateDecision(exec);
|
|
74
95
|
}
|
|
75
|
-
catch {
|
|
76
|
-
|
|
96
|
+
catch (err) {
|
|
97
|
+
console.warn('[iterate] approval gate failed; degrading to ask.', err);
|
|
98
|
+
return Promise.resolve({
|
|
99
|
+
kind: 'ask',
|
|
100
|
+
reason: 'iterate approval gate unavailable — require consent',
|
|
101
|
+
});
|
|
77
102
|
}
|
|
78
103
|
if (decision.kind === 'ask') {
|
|
79
104
|
// Delegate the actual human-consent prompt + audit to dsh's approval
|
package/dist/skill-prompt.js
CHANGED
|
@@ -23,6 +23,9 @@ You have the iterate plugin installed, which registers these tools:
|
|
|
23
23
|
- \`iterate_history\` — inspect the runtime state in detail: decision-log entries and applied fixes (optionally scoped to a round or a fixed file)
|
|
24
24
|
- \`iterate_prune\` — remove stale runtime artifacts (\`.iterate/\` entries). Defaults to a read-only dry-run that reports what WOULD be removed; pass \`dryRun:false\` to actually prune.
|
|
25
25
|
- \`iterate_transcript\` — runtime observatory file (\`.iterate/transcript.json\`). \`read\` fetches the persisted manifest including any steering \`nudge\` for this run's reviewers; \`capture\` (call once after the final report) persists the per-reviewer threads, convergence trend, findings, fixes, checkpoint, and timeline so the client observatory panel reflects the run; \`nudge\` sets/clears steering text the next round's reviewers read. Purely local, never touches source files.
|
|
26
|
+
- \`iterate_experience\` — experience bank (\`.iterate/experience.json\`): \`list\`/\`search\`/\`get\` recall verified fixes and patterns from past sessions (read the bank before fixing so proven fixes are applied first); \`add\` records a new verified fix — re-adding the same pattern+dimension bumps its hit count instead of duplicating it.
|
|
27
|
+
- \`iterate_quality_gate\` — quality certificate: \`read\` loads the persisted dimension convergence rates / verification pass rate / PASS-FAIL status; \`compute\` recomputes a fresh snapshot from this round's findings + validation results (supply \`findingsByRound\` for real convergence) and persists it to \`.iterate/quality-gate.json\`.
|
|
28
|
+
- \`iterate_defense_events\` — defense event stream (\`.iterate/defense-events.json\`): \`list\`/\`counts\` review precondition failures, rollbacks, invariant violations, and falsified assumptions; \`record\` logs a new event when a defense fires. Human-readable labels follow the project \`language\` (en/zh).
|
|
26
29
|
|
|
27
30
|
### When to use
|
|
28
31
|
When the user asks to review or iterate on the project (e.g. "review this project", "iterate on error handling", "check the codebase for issues", "dry-run review", "反复审查"), run an iterate **workflow** by calling the \`workflow\` tool.
|
|
@@ -57,7 +57,7 @@ export function appendDecisionEntry(projectRoot, entry) {
|
|
|
57
57
|
appendFileSync(filePath, line, 'utf-8');
|
|
58
58
|
}
|
|
59
59
|
catch (err) {
|
|
60
|
-
return { count:
|
|
60
|
+
return { count: 0, path: join(projectRoot, LOG_DIR, LOG_FILE), error: `failed to append decision log: ${String(err)}` };
|
|
61
61
|
}
|
|
62
62
|
// Count entries
|
|
63
63
|
let count = 0;
|
|
@@ -202,6 +202,15 @@ export function registerDecisionLogTool(ctx) {
|
|
|
202
202
|
data,
|
|
203
203
|
};
|
|
204
204
|
const result = appendDecisionEntry(projectRoot, entry);
|
|
205
|
+
if (result.error) {
|
|
206
|
+
return {
|
|
207
|
+
operation: 'append',
|
|
208
|
+
success: false,
|
|
209
|
+
entryCount: 0,
|
|
210
|
+
logPath: result.path,
|
|
211
|
+
error: result.error,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
205
214
|
return {
|
|
206
215
|
operation: 'append',
|
|
207
216
|
success: true,
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/defense-events.ts — defense event stream query & record tool.
|
|
3
|
+
*
|
|
4
|
+
* iterate_defense_events — browse/search defense events from the current
|
|
5
|
+
* iteration, or record a new one.
|
|
6
|
+
*
|
|
7
|
+
* Defense events include: precondition failures, rollbacks, invariant violations,
|
|
8
|
+
* and assumption falsifications. Read operations give visibility into defensive
|
|
9
|
+
* actions; "record" persists a new event to .iterate/defense-events.json.
|
|
10
|
+
*/
|
|
11
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
12
|
+
import { resolveProjectRootForExec, loadEffectiveConfig } from "../config-loader.js";
|
|
13
|
+
import { readDefenseEvents, writeDefenseEvents, addDefenseEvent } from "./defense-store.js";
|
|
14
|
+
const DEFAULT_LIMIT = 50;
|
|
15
|
+
const MAX_LIMIT = 100;
|
|
16
|
+
const EVENT_TYPES = [
|
|
17
|
+
'precondition_failed',
|
|
18
|
+
'rollback',
|
|
19
|
+
'invariant_violated',
|
|
20
|
+
'assumption_falsified',
|
|
21
|
+
];
|
|
22
|
+
/** Clamp a caller-supplied limit to a sane range. */
|
|
23
|
+
function clampLimit(limit) {
|
|
24
|
+
if (typeof limit !== 'number' || !Number.isInteger(limit) || limit <= 0) {
|
|
25
|
+
return DEFAULT_LIMIT;
|
|
26
|
+
}
|
|
27
|
+
return Math.min(limit, MAX_LIMIT);
|
|
28
|
+
}
|
|
29
|
+
/** Bilingual, config-driven human-readable labels for defense event types. */
|
|
30
|
+
const EVENT_TYPE_LABELS = {
|
|
31
|
+
precondition_failed: { zh: '前置校验失败', en: 'precondition failed' },
|
|
32
|
+
rollback: { zh: '回滚', en: 'rollback' },
|
|
33
|
+
invariant_violated: { zh: '不变量违反', en: 'invariant violated' },
|
|
34
|
+
assumption_falsified: { zh: '假设被证伪', en: 'assumption falsified' },
|
|
35
|
+
};
|
|
36
|
+
/** Label for a defense event type in the requested language (fallback: English). */
|
|
37
|
+
function labelFor(type, language) {
|
|
38
|
+
const labels = EVENT_TYPE_LABELS[type];
|
|
39
|
+
return labels ? labels[language] : type;
|
|
40
|
+
}
|
|
41
|
+
/** Validate arguments for the record operation. */
|
|
42
|
+
function validateRecordInput(args) {
|
|
43
|
+
const errors = [];
|
|
44
|
+
if (typeof args.type !== 'string' || !EVENT_TYPES.includes(args.type)) {
|
|
45
|
+
errors.push(`type must be one of: ${EVENT_TYPES.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
if (typeof args.round !== 'number' || !Number.isInteger(args.round) || args.round < 1) {
|
|
48
|
+
errors.push('round must be a positive integer');
|
|
49
|
+
}
|
|
50
|
+
if (typeof args.description !== 'string' || !args.description.trim()) {
|
|
51
|
+
errors.push('description is required');
|
|
52
|
+
}
|
|
53
|
+
if (typeof args.defense !== 'string' || !args.defense.trim()) {
|
|
54
|
+
errors.push('defense is required');
|
|
55
|
+
}
|
|
56
|
+
if (typeof args.outcome !== 'string' || !args.outcome.trim()) {
|
|
57
|
+
errors.push('outcome is required');
|
|
58
|
+
}
|
|
59
|
+
const severity = args.severity;
|
|
60
|
+
if (severity !== 'critical' && severity !== 'high' && severity !== 'medium' && severity !== 'low') {
|
|
61
|
+
errors.push('severity must be one of critical, high, medium, low');
|
|
62
|
+
}
|
|
63
|
+
return errors;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Register the `iterate_defense_events` tool.
|
|
67
|
+
* Queries defense events from the current iteration.
|
|
68
|
+
*/
|
|
69
|
+
export function registerDefenseEventsTool(ctx) {
|
|
70
|
+
ctx.tools.register(defineTool({
|
|
71
|
+
name: 'iterate_defense_events',
|
|
72
|
+
description: 'Query or record defense events: precondition failures, rollbacks, invariant violations, ' +
|
|
73
|
+
'and assumption falsifications. ' +
|
|
74
|
+
'List/counts return events with descriptions, outcomes, and summary counts; ' +
|
|
75
|
+
'"record" persists a new event to .iterate/defense-events.json. ' +
|
|
76
|
+
'Use it to review defensive actions taken, or to log one when a defense fires.',
|
|
77
|
+
parameters: {
|
|
78
|
+
operation: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'Operation: list (browse all), counts (summary by type), record (log a new event). Default: list.',
|
|
81
|
+
enum: ['list', 'counts', 'record'],
|
|
82
|
+
},
|
|
83
|
+
type: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: 'Event type (filter for list; required for record): precondition_failed, rollback, invariant_violated, assumption_falsified.',
|
|
86
|
+
},
|
|
87
|
+
round: {
|
|
88
|
+
type: 'integer',
|
|
89
|
+
description: 'Round number (filter for list; required for record).',
|
|
90
|
+
},
|
|
91
|
+
severity: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Severity (filter for list; required for record): critical, high, medium, low.',
|
|
94
|
+
},
|
|
95
|
+
description: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'What was being checked (required for record).',
|
|
98
|
+
},
|
|
99
|
+
defense: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
description: 'The defense that was triggered (required for record).',
|
|
102
|
+
},
|
|
103
|
+
outcome: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Outcome: what was protected against (required for record).',
|
|
106
|
+
},
|
|
107
|
+
file: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'Optional file/location context (record).',
|
|
110
|
+
},
|
|
111
|
+
line: {
|
|
112
|
+
type: 'integer',
|
|
113
|
+
description: 'Optional line number context (record).',
|
|
114
|
+
},
|
|
115
|
+
language: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
description: 'Label language for readable output: en (default) or zh. Falls back to the project config language.',
|
|
118
|
+
enum: ['en', 'zh'],
|
|
119
|
+
},
|
|
120
|
+
limit: {
|
|
121
|
+
type: 'integer',
|
|
122
|
+
description: `Max events to return (default: ${DEFAULT_LIMIT}, cap: ${MAX_LIMIT}).`,
|
|
123
|
+
},
|
|
124
|
+
path: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: 'Project root directory (default: current working directory).',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
output: {
|
|
130
|
+
schema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
properties: {
|
|
134
|
+
ok: { type: 'boolean', required: true },
|
|
135
|
+
kind: { type: 'string' },
|
|
136
|
+
operation: { type: 'string' },
|
|
137
|
+
count: { type: 'integer' },
|
|
138
|
+
events: { type: 'json' },
|
|
139
|
+
counts: { type: 'json' },
|
|
140
|
+
event: { type: 'json' },
|
|
141
|
+
language: { type: 'string' },
|
|
142
|
+
errors: { type: 'json' },
|
|
143
|
+
error: { type: 'string' },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
render: (_args, value) => {
|
|
147
|
+
if (!value.ok)
|
|
148
|
+
return [{ type: 'text', text: `defense events query failed: ${value.error}` }];
|
|
149
|
+
const language = value.language === 'zh' ? 'zh' : 'en';
|
|
150
|
+
if (value.operation === 'counts' && value.counts) {
|
|
151
|
+
const counts = value.counts;
|
|
152
|
+
const lines = [
|
|
153
|
+
'Defense Event Summary:',
|
|
154
|
+
...EVENT_TYPES.map((type) => ` ${labelFor(type, language)}: ${counts[type] ?? 0}`),
|
|
155
|
+
` Total: ${EVENT_TYPES.reduce((sum, type) => sum + (counts[type] ?? 0), 0)}`,
|
|
156
|
+
];
|
|
157
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
158
|
+
}
|
|
159
|
+
if (value.operation === 'record' && value.event) {
|
|
160
|
+
const e = value.event;
|
|
161
|
+
return [{ type: 'text', text: [
|
|
162
|
+
`Recorded defense event: ${e.id}`,
|
|
163
|
+
` Round ${e.round} - ${labelFor(e.type, language)} (${e.severity})`,
|
|
164
|
+
` Check: ${e.description}`,
|
|
165
|
+
` Defense: ${e.defense}`,
|
|
166
|
+
` Outcome: ${e.outcome}`,
|
|
167
|
+
e.file ? ` File: ${e.file}${e.line ? `:${e.line}` : ''}` : '',
|
|
168
|
+
].filter(Boolean).join('\n') }];
|
|
169
|
+
}
|
|
170
|
+
const events = value.events ?? [];
|
|
171
|
+
if (events.length === 0) {
|
|
172
|
+
return [{ type: 'text', text: 'No defense events recorded.' }];
|
|
173
|
+
}
|
|
174
|
+
const lines = [
|
|
175
|
+
`Defense Events (${value.count} total):`,
|
|
176
|
+
'',
|
|
177
|
+
...events.map((e) => {
|
|
178
|
+
const typeLabel = labelFor(e.type, language);
|
|
179
|
+
return `[${e.id}] Round ${e.round} - ${typeLabel}\n ${e.description}\n Outcome: ${e.outcome}`;
|
|
180
|
+
}),
|
|
181
|
+
];
|
|
182
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
async execute(args, exec) {
|
|
186
|
+
const resolved = resolveProjectRootForExec(exec, args.path);
|
|
187
|
+
if (!resolved.ok)
|
|
188
|
+
return { ok: false, kind: 'defense_events', error: resolved.reason };
|
|
189
|
+
const projectRoot = resolved.root;
|
|
190
|
+
const configLang = loadEffectiveConfig(projectRoot).config.language;
|
|
191
|
+
const language = args.language === 'zh' || args.language === 'en' ? args.language : configLang;
|
|
192
|
+
const operation = typeof args.operation === 'string' ? args.operation : 'list';
|
|
193
|
+
const limit = clampLimit(args.limit);
|
|
194
|
+
if (operation === 'record') {
|
|
195
|
+
const errors = validateRecordInput(args);
|
|
196
|
+
if (errors.length > 0) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
kind: 'defense_events',
|
|
200
|
+
operation: 'record',
|
|
201
|
+
errors: errors,
|
|
202
|
+
error: `Invalid defense event: ${errors.join('; ')}`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const stream = readDefenseEvents(projectRoot);
|
|
206
|
+
const next = addDefenseEvent(stream, {
|
|
207
|
+
round: args.round,
|
|
208
|
+
type: args.type,
|
|
209
|
+
description: args.description,
|
|
210
|
+
defense: args.defense,
|
|
211
|
+
outcome: args.outcome,
|
|
212
|
+
severity: args.severity,
|
|
213
|
+
...(typeof args.file === 'string' && args.file.length > 0 ? { file: args.file } : {}),
|
|
214
|
+
...(typeof args.line === 'number' ? { line: args.line } : {}),
|
|
215
|
+
});
|
|
216
|
+
writeDefenseEvents(projectRoot, next);
|
|
217
|
+
const event = next.events[next.events.length - 1];
|
|
218
|
+
return {
|
|
219
|
+
ok: true,
|
|
220
|
+
kind: 'defense_events',
|
|
221
|
+
operation: 'record',
|
|
222
|
+
language,
|
|
223
|
+
event: event,
|
|
224
|
+
counts: next.counts,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const stream = readDefenseEvents(projectRoot);
|
|
228
|
+
if (operation === 'counts') {
|
|
229
|
+
return {
|
|
230
|
+
ok: true,
|
|
231
|
+
kind: 'defense_events',
|
|
232
|
+
operation: 'counts',
|
|
233
|
+
language,
|
|
234
|
+
counts: stream.counts,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Filter events
|
|
238
|
+
let events = stream.events;
|
|
239
|
+
if (typeof args.type === 'string' && args.type) {
|
|
240
|
+
events = events.filter((e) => e.type === args.type);
|
|
241
|
+
}
|
|
242
|
+
if (typeof args.round === 'number') {
|
|
243
|
+
events = events.filter((e) => e.round === args.round);
|
|
244
|
+
}
|
|
245
|
+
if (typeof args.severity === 'string' && args.severity) {
|
|
246
|
+
events = events.filter((e) => e.severity === args.severity);
|
|
247
|
+
}
|
|
248
|
+
// Sort by timestamp descending (newest first)
|
|
249
|
+
events.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
250
|
+
return {
|
|
251
|
+
ok: true,
|
|
252
|
+
kind: 'defense_events',
|
|
253
|
+
operation: 'list',
|
|
254
|
+
language,
|
|
255
|
+
count: Math.min(events.length, limit),
|
|
256
|
+
events: events.slice(0, limit),
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
}));
|
|
260
|
+
}
|