iterate-plugin 3.7.0 → 3.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 +43 -8
- package/README.zh-CN.md +26 -5
- package/dist/file-lock.js +75 -14
- package/dist/git-scope.js +22 -2
- package/dist/index.js +9 -4
- package/dist/report-render.js +452 -0
- package/dist/session-record.js +246 -0
- package/dist/skill-prompt.js +4 -3
- package/dist/tools/checkpoint.js +24 -17
- package/dist/tools/config.js +42 -33
- package/dist/tools/decision-log.js +13 -93
- package/dist/tools/report.js +239 -0
- package/dist/tools/transcript.js +260 -53
- package/dist/tools/triage.js +52 -45
- package/dist/transcript.js +2 -2
- package/lib/client.js +486 -22
- package/lib/parse.js +29 -6
- package/package.json +38 -13
- package/src/client/i18n.ts +6 -0
- package/src/client/index.ts +66 -4
- package/src/file-lock.ts +84 -14
- package/src/git-scope.ts +21 -2
- package/src/index.ts +9 -4
- package/src/report-render.ts +578 -0
- package/src/session-record.ts +333 -0
- package/src/skill-prompt.ts +4 -3
- package/src/tools/checkpoint.ts +23 -16
- package/src/tools/config.ts +40 -31
- package/src/tools/decision-log.ts +12 -80
- package/src/tools/report.ts +244 -0
- package/src/tools/transcript.ts +271 -52
- package/src/tools/triage.ts +47 -40
- package/src/transcript.ts +2 -2
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Both modes share:
|
|
|
107
107
|
### UI layer (build-free client slots, v3.7: 11 tabs, bilingual)
|
|
108
108
|
|
|
109
109
|
- **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) and a **task_mode indicator (code / iterate)**, now backed by the persisted transcript (`iterate_status` / `iterate_transcript` emit `taskMode`). While a run is active the dashboard switches to a running state (round + phase) instead of the static empty state, and the start buttons are hidden.
|
|
110
|
-
- **ObservatoryPanel** (`conversation.input.dock`) — an **eleven-tab** runtime observatory below the input: **live activity stream** (default tab, type filter) + **F1** review threads (expand / collapse all), **F2** convergence trend (incl. the per-round comparison table: findings raised / fixed / severity mix / validation outcomes), **F3** finding locations (severity / dimension / search filter), **F4** fixes + rollback, **F5** checkpoint resume, **F6** run console, **F7** decision timeline (type / round filter + search), **F8** Quality Gate, **F9** Experience Bank, **F10** Defense Events. Number-key shortcuts: `1`–`9` jump to F1–F9, `0` to F10, `-` back to the live stream. **F8/F9/F10 are live** — they scan the session stream for the latest `iterate_quality_gate` / `iterate_experience` / `iterate_defense_events` results and render real data (PASS/FAIL chip + per-dimension score bars, experience entries with adopt buttons + client-side search, defense type-count chips + filtered event stream); when the session stream has none they distinguish "no data on disk" from "not pulled yet" and offer a **拉取磁盘快照 (pull disk snapshot)** instruction that has the model call the read-side tools and echo the `.iterate/` artifacts back into the stream. One-click JSON export of all observatory data — manifest + live + `qualityGate` / `experienceBank` / `defenseEvents` / `report` (download, copy fallback). **v3.5.3: reset actions surfaced** — F5 copies an `iterate_checkpoint clear` instruction, F8 an `iterate_quality_gate clear` instruction, F10 an `iterate_defense_events clear` instruction.
|
|
110
|
+
- **ObservatoryPanel** (`conversation.input.dock`) — an **eleven-tab** runtime observatory below the input: **live activity stream** (default tab, type filter) + **F1** review threads (expand / collapse all), **F2** convergence trend (incl. the per-round comparison table: findings raised / fixed / severity mix / validation outcomes), **F3** finding locations (severity / dimension / search filter), **F4** fixes + rollback, **F5** checkpoint resume, **F6** run console, **F7** decision timeline (type / round filter + search), **F8** Quality Gate, **F9** Experience Bank, **F10** Defense Events. Number-key shortcuts: `1`–`9` jump to F1–F9, `0` to F10, `-` back to the live stream. **F8/F9/F10 are live** — they scan the session stream for the latest `iterate_quality_gate` / `iterate_experience` / `iterate_defense_events` results and render real data (PASS/FAIL chip + per-dimension score bars, experience entries with adopt buttons + client-side search, defense type-count chips + filtered event stream); when the session stream has none they distinguish "no data on disk" from "not pulled yet" and offer a **拉取磁盘快照 (pull disk snapshot)** instruction that has the model call the read-side tools and echo the `.iterate/` artifacts back into the stream. One-click JSON export of all observatory data — manifest + live + `qualityGate` / `experienceBank` / `defenseEvents` / `report` (download, copy fallback). **v3.9: two report-export buttons** (导出报告 / 导出 SARIF) render the *same shared data model* as the `iterate_report` tool — a PR-pasteable Markdown run report and a SARIF 2.1.0 log for CI / GitHub Code Scanning. **v3.5.3: reset actions surfaced** — F5 copies an `iterate_checkpoint clear` instruction, F8 an `iterate_quality_gate clear` instruction, F10 an `iterate_defense_events clear` instruction.
|
|
111
111
|
- **TriagePanel** (`conversation.chat.turnTail`) — per-finding triage with `y` (keep → fix), `n` (skip), `a` (known-intentional) keyboard shortcuts and `↑`/`↓` navigation (focus-guarded: only active while the panel has focus), filtering, batch ops (incl. select-all), localStorage persistence. **Write-back closed loop**: the primary button copies an `iterate_triage apply` instruction for the marked entries (the tool validates, dedupes, backs up, rolls back on failure); a secondary button copies the raw `known_intentional` YAML; **回读已写入条目** copies an `iterate_triage list` instruction to confirm what actually landed. Copy payloads are aligned to the target tool schemas. **v3.2: native command buttons** (approve architectural fix, trigger new round, rollback to checkpoint). **v3.3: §8 指派修复 (assign)** — one click copies an `iterate_fix` instruction carrying every in-scope finding (respecting the batch toggle).
|
|
112
112
|
- **StatsCard** (`conversation.chat.turnTail`) — convergence stats, round history, trend chart and completion summary when no findings remain.
|
|
113
113
|
- **iterate theme skin** (`theme.overrideTokens`) — a warm-amber 13-token `--dsw-*` override, light/dark modes, togglable in settings.
|
|
@@ -303,10 +303,11 @@ validation:
|
|
|
303
303
|
- `iterate_status` — summarize current iteration state: mode, current/last round, fixes applied, remaining architectural, decision-log entry count, whether a checkpoint exists; **v3.4: also surfaces the persisted quality-gate snapshot, experience-bank summary, and defense-events summary** (`qualityGate` / `experienceBank` / `defenseEvents`)
|
|
304
304
|
- `iterate_history` — read iteration history (read-only): decision-log entries (filter by `type` / `since` / `round` / fixed `file` / `limit`, default latest 50, cap 200) + fix-registry summary (per-round fixed/failed counts, scoped by the same round/file filters; a `file`-scoped summary recomputes counts from the kept records). For auditing the run, tracing logs, and inventorying fixes
|
|
305
305
|
- `iterate_prune` — clean runtime artifacts: stale decision-log entries (by `retainDays`, default 30), stale checkpoints, orphaned fix backups, empty rounds, stray temp files (crashed atomic writes). Dry-run by default (report-only); real cleanup with `dryRun:false`, each cleanup logged
|
|
306
|
-
- `iterate_transcript` — runtime observatory: persist review transcripts, threads, fixes, and nudge directions to `.iterate/transcript.json` for the client observatory
|
|
306
|
+
- `iterate_transcript` — runtime observatory: persist review transcripts, threads, fixes, and nudge directions to `.iterate/transcript.json` for the client observatory. **v3.9: `read` recovers a summary manifest from the session log's run record when the file is lost/corrupt (`recovered:true`); `capture` links the run to the dsh session (`sessionRecord`); `recall` lists this session's run records (newest first)**
|
|
307
307
|
- `iterate_experience` — **v3.2** query the experience bank (list / search / get), `add` a new verified fix (re-adding the same pattern+dimension bumps its hit count instead of duplicating it), or **v3.5 remove a stale/incorrect entry by `id`** so bad experiences never resurface. Persists to `.iterate/experience.json`
|
|
308
308
|
- `iterate_quality_gate` — **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. **v3.5: `clear` removes the persisted certificate to reset a stale gate before a fresh iteration**
|
|
309
309
|
- `iterate_defense_events` — **v3.2** query defense events (list / counts), `record` a new one, or **v3.5.3 `clear` the persisted stream** so a fresh iteration starts without stale defensive data. Human-readable labels follow the project language (en / zh)
|
|
310
|
+
- `iterate_report` — **v3.9** export the run as a portable report. `format:"markdown"` (default) renders a PR-pasteable report (run header, quality gate, findings, fixes, validations, defense events, experience bank); `format:"sarif"` renders a SARIF 2.1.0 log for CI / GitHub Code Scanning (one rule per review dimension, severity → error/warning/note, file+line locations, suggested fix attached). `write:true` persists it to `.iterate/report.md` / `.iterate/report.sarif`; `lang` (zh | en) selects the report language. Read-only apart from the optional report file
|
|
310
311
|
|
|
311
312
|
---
|
|
312
313
|
|
|
@@ -342,11 +343,17 @@ All runtime state lives under `.iterate/` at the project root (can be excluded v
|
|
|
342
343
|
`0.1.2-rc.1`, `0.1.3-alpha.1`, `0.1.5-alpha.1`, `0.1.5-alpha.2`,
|
|
343
344
|
`0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`,
|
|
344
345
|
`0.1.7-alpha.1`, `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, `0.2.0-rc.1`,
|
|
345
|
-
`0.2.0-rc.2`, `0.2.1-alpha.1`, `0.2.1-alpha.2
|
|
346
|
-
`0.2.1-alpha.2`
|
|
346
|
+
`0.2.0-rc.2`, `0.2.1-alpha.1`, `0.2.1-alpha.2`, `0.2.1-alpha.3`,
|
|
347
|
+
`0.2.1-alpha.4`, `0.2.1-alpha.5`, `0.2.1-rc.1`, `0.2.1-rc.2` (24 releases,
|
|
348
|
+
`0.1.1-rc.1` …
|
|
349
|
+
`0.2.1-rc.2`).
|
|
347
350
|
The plugin is built against `@deepseek-ai/dsh-tools` /
|
|
348
|
-
`@deepseek-ai/dsh-util-values` `0.2.1-
|
|
349
|
-
(tool registration, client slots + theme, bundle patch
|
|
351
|
+
`@deepseek-ai/dsh-util-values` `0.2.1-rc.2` and uses only public contracts
|
|
352
|
+
(tool registration, client slots + theme, bundle patch, and — since v3.9 —
|
|
353
|
+
the session state-record API for session-linked run records).
|
|
354
|
+
It also declares `engines.dsh` (`>=0.1.1-rc.1`) so the dsh ≥ 0.2.1-rc.2
|
|
355
|
+
runtime evaluates compatibility from that single range (earlier runtimes fall
|
|
356
|
+
back to the `@deepseek-ai/dsh-*` peer versions listed in `package.json`).
|
|
350
357
|
- **Disposable-Profile evidence (real, `dsh` CLI `0.1.1-rc.1`)**: on a temp
|
|
351
358
|
`$DSH_HOME`, `dsh plugin --profile <p> add <this-repo>` installed the bundle in
|
|
352
359
|
~449 ms; `dsh --profile <p> --dump-config` composed the `iterate-plugin` bundle
|
|
@@ -384,8 +391,36 @@ following capabilities; elevated capability means a DSH Profile install stays
|
|
|
384
391
|
|
|
385
392
|
### Dependencies
|
|
386
393
|
|
|
387
|
-
Runtime
|
|
388
|
-
`@deepseek-ai/
|
|
394
|
+
Runtime dependency: `js-yaml` (pinned exact version). The three DSH host
|
|
395
|
+
modules the plugin is built against — `@deepseek-ai/cordis`,
|
|
396
|
+
`@deepseek-ai/dsh-tools`, `@deepseek-ai/dsh-util-values` — are declared as
|
|
397
|
+
**optional `peerDependencies`** so the host's own copies are used instead of a
|
|
398
|
+
second one bundled with the plugin (no duplicate module instances; `dsh`
|
|
399
|
+
supplies them). They stay pinned in `devDependencies` for local builds and
|
|
400
|
+
tests, in lockstep with the newest `dsh.compatibility.dshReleases` entry.
|
|
401
|
+
|
|
402
|
+
#### Upstream adoption (v3.8.0 → dsh `0.2.1-alpha.4`)
|
|
403
|
+
|
|
404
|
+
- **Adopted — the `engines.dsh` compatibility contract.** As of `0.2.1-alpha.4`
|
|
405
|
+
the dsh runtime decides a third-party plugin's compatibility **from
|
|
406
|
+
`engines.dsh` when it is declared** (a semver range, prereleases included),
|
|
407
|
+
ignoring `peerDependencies`; when it is absent the runtime falls back to
|
|
408
|
+
checking the exact `@deepseek-ai/dsh-*` peer versions. The plugin now declares
|
|
409
|
+
`engines.dsh: ">=0.1.1-rc.1"` — the same lower bound as the oldest
|
|
410
|
+
`dshReleases` entry — so a current runtime loads it without flagging the
|
|
411
|
+
exact-pinned peer list, while every host-specific capability stays
|
|
412
|
+
feature-detected (a missing `WorkingDirectoryService`, `ask.displayReason`,
|
|
413
|
+
`ctx.jobs`, etc. still degrade to the safe fallback). Earlier runtimes keep
|
|
414
|
+
using the `@deepseek-ai/dsh-*` peer versions, which are extended through
|
|
415
|
+
`0.2.1-alpha.4`.
|
|
416
|
+
- **`0.2.1-alpha.3` / `0.2.1-alpha.4` are otherwise host-level releases** — Web
|
|
417
|
+
sign-in, secret/token masking, minimal-mode image reading, `/compact`, plugin
|
|
418
|
+
hot-reload, custom plugin index sources, and per-model reasoning-level UI. None
|
|
419
|
+
expose a tool/agent/session API this plugin consumes (`defineTool`, `JsonValue`,
|
|
420
|
+
`exec.*`, `tools/pre-execute`, `presentCall`), so no further adoption is
|
|
421
|
+
required this cycle.
|
|
422
|
+
- All five `@deepseek-ai/*` packages are pinned to the exact `0.2.1-alpha.4`
|
|
423
|
+
(`@deepseek-ai/cordis` stays `4.0.5-alpha.2`).
|
|
389
424
|
|
|
390
425
|
#### Upstream adoption (v3.7.0 → dsh `0.2.1-alpha.2`)
|
|
391
426
|
|
package/README.zh-CN.md
CHANGED
|
@@ -106,7 +106,7 @@ dsh plugin --profile web add iterate-plugin
|
|
|
106
106
|
### UI 层(客户端免构建槽位,v3.7:11 个页签,双语)
|
|
107
107
|
|
|
108
108
|
- **收敛看板 `ConvergenceDashboard`**(`conversation.input.dock`)— 输入框上方实时显示轮次进度条、严重度统计、维度徽章、趋势迷你图;normal 模式另显示修复计数徽章;还有运行阶段芯片(当前工作流阶段 + 运行中/已结束)与 **task_mode 指示器(code / iterate)**。运行期间看板切换为"运行中"状态(显示轮次与阶段),并隐藏启动按钮。
|
|
109
|
-
- **运行时观测台 `ObservatoryPanel`**(`conversation.input.dock`)— 输入框下方**十一个页签**运行时观测台:**实时活动流**(默认页,支持按活动类型筛选)+ **F1** 审查线程(全部展开/全部收起)、**F2** 收敛趋势(含跨轮对比表:每轮 findings / fixed / 严重度分布 / 验证结果)、**F3** 发现定位(按严重度/维度/关键词筛选)、**F4** 修复与回滚、**F5** 断点恢复、**F6** 运行控制台、**F7** 决策时间线(按类型/轮次筛选与关键词搜索)、**F8** 质量门禁、**F9** 经验银行、**F10** 防御事件。数字键快捷键:`1`–`9` 跳到 F1–F9,`0` 到 F10,`-` 回实时活动流。**F8/F9/F10 为实时数据**——扫描会话流中最近一次 `iterate_quality_gate` / `iterate_experience` / `iterate_defense_events` 结果并渲染真实数据(PASS/FAIL 徽章 + 分维度评分条、经验条目(含采纳按钮 + 客户端搜索)、防御类型计数芯片 + 筛选后的事件流);会话流中没有数据时区分"磁盘无数据"与"尚未拉取",并提供**拉取磁盘快照**指令(让模型调用只读工具、把 `.iterate/` 产物回显进会话流)。一键导出全部观测数据为 JSON——manifest + live + `qualityGate` / `experienceBank` / `defenseEvents` / `report`(优先下载,失败回退复制)。**v3.5.3 新增重置操作**:F5 复制 `iterate_checkpoint clear`、F8 复制 `iterate_quality_gate clear`、F10 复制 `iterate_defense_events clear` 指令。
|
|
109
|
+
- **运行时观测台 `ObservatoryPanel`**(`conversation.input.dock`)— 输入框下方**十一个页签**运行时观测台:**实时活动流**(默认页,支持按活动类型筛选)+ **F1** 审查线程(全部展开/全部收起)、**F2** 收敛趋势(含跨轮对比表:每轮 findings / fixed / 严重度分布 / 验证结果)、**F3** 发现定位(按严重度/维度/关键词筛选)、**F4** 修复与回滚、**F5** 断点恢复、**F6** 运行控制台、**F7** 决策时间线(按类型/轮次筛选与关键词搜索)、**F8** 质量门禁、**F9** 经验银行、**F10** 防御事件。数字键快捷键:`1`–`9` 跳到 F1–F9,`0` 到 F10,`-` 回实时活动流。**F8/F9/F10 为实时数据**——扫描会话流中最近一次 `iterate_quality_gate` / `iterate_experience` / `iterate_defense_events` 结果并渲染真实数据(PASS/FAIL 徽章 + 分维度评分条、经验条目(含采纳按钮 + 客户端搜索)、防御类型计数芯片 + 筛选后的事件流);会话流中没有数据时区分"磁盘无数据"与"尚未拉取",并提供**拉取磁盘快照**指令(让模型调用只读工具、把 `.iterate/` 产物回显进会话流)。一键导出全部观测数据为 JSON——manifest + live + `qualityGate` / `experienceBank` / `defenseEvents` / `report`(优先下载,失败回退复制)。**v3.9 新增两个报告导出按钮**(导出报告 / 导出 SARIF),与 `iterate_report` 工具共用同一份数据模型——分别产出可直接粘贴进 PR 的 Markdown 运行报告,与供 CI / GitHub Code Scanning 消费的 SARIF 2.1.0 日志。**v3.5.3 新增重置操作**:F5 复制 `iterate_checkpoint clear`、F8 复制 `iterate_quality_gate clear`、F10 复制 `iterate_defense_events clear` 指令。
|
|
110
110
|
- **Findings 分诊面板 `TriagePanel`**(`conversation.chat.turnTail`)— 逐条分诊,键盘快捷键 `y`(保留→修复)、`n`(跳过)、`a`(已知有意)与 `↑`/`↓` 移动(焦点守卫:仅面板内焦点时生效),支持筛选、批量(含一键全选所有 findings)、localStorage 持久化。**写回闭环**:主按钮复制已标记条目的 `iterate_triage apply` 指令(工具负责校验、去重、备份、失败回滚);次按钮复制裸 `known_intentional` YAML;**回读已写入条目**复制 `iterate_triage list` 指令核对实际落盘内容。复制载荷与目标工具 schema 对齐。**v3.2:原生命令按钮**(批准架构修复、触发新一轮、回滚到断点)。
|
|
111
111
|
- **收敛统计卡片 `StatsCard`**(`conversation.chat.turnTail`)— 无 findings 时显示收敛统计、历史轮次、趋势图、完成摘要。
|
|
112
112
|
- **iterate 主题皮肤**(`theme.overrideTokens`)— 暖琥珀配色的 13 个 `--dsw-*` token 覆盖,明暗双模式,可在设置页开关。
|
|
@@ -302,10 +302,11 @@ validation:
|
|
|
302
302
|
- `iterate_status` — 汇总当前迭代状态:模式、当前轮/总轮、已修复数、剩余 architectural、决策日志条数、是否存在 checkpoint;**v3.4:同时返回持久化的质量门禁快照、经验银行摘要与防御事件摘要**(`qualityGate` / `experienceBank` / `defenseEvents`)
|
|
303
303
|
- `iterate_history` — 读取迭代历史(只读):决策日志条目(可按 `type` / `since` / `round` / 修复文件 `file` / `limit` 过滤,默认取最新 50 条,上限 200 条)+ 修复注册表汇总(各轮 fixed/failed 计数,按同一 round/file 过滤收窄;`file` 过滤时按保留的记录重算计数)。用于审查运行过程、审计日志、盘点修复
|
|
304
304
|
- `iterate_prune` — 清理运行时产物:过期决策日志条目(按 `retainDays`,默认 30 天)、陈旧断点、孤儿修复备份、空轮次、崩溃原子写入残留的临时文件。默认 dry-run 只报告不删除;`dryRun:false` 才真正清理,每次清理写入决策日志
|
|
305
|
-
- `iterate_transcript` — 运行时观测台:把审查转录、线程、修复与 nudge 指令持久化到 `.iterate/transcript.json
|
|
305
|
+
- `iterate_transcript` — 运行时观测台:把审查转录、线程、修复与 nudge 指令持久化到 `.iterate/transcript.json`,供客户端观测台读取。**v3.9:`read` 在文件丢失/损坏时从会话日志的运行记录恢复出一份摘要 manifest(`recovered:true`);`capture` 把本次运行关联到 dsh 会话(`sessionRecord`);`recall` 列出本会话的运行记录(最新在前)**
|
|
306
306
|
- `iterate_experience` — **v3.2** 查询经验银行(list / search / get),或 `add` 一条新的已验证修复:重复添加同一 pattern + dimension 累加命中次数而非重复写入。**v3.5 新增 `remove`:按 `id` 删除过时或错误的经验条目**。持久化到 `.iterate/experience.json`
|
|
307
307
|
- `iterate_quality_gate` — **v3.2** 读取质量凭证(`read`),或基于 findings、验证结果、`findingsByRound` 与 `fixedByDimension` 重新计算并持久化一份新凭证(`compute`)。真实的逐维度收敛率。**v3.5 新增 `clear`:删除持久化的质量凭证**,用于新一轮迭代前重置陈旧的门禁
|
|
308
308
|
- `iterate_defense_events` — **v3.2** 查询防御事件(list / counts),`record` 记录一条新事件,或 **v3.5.3 `clear` 清除持久化事件流**,让新一轮迭代不带陈旧防御数据起跑。可读标签跟随项目语言(en / zh)
|
|
309
|
+
- `iterate_report` — **v3.9** 把本次运行导出为可移植报告。`format:"markdown"`(默认)产出可直接粘贴进 PR 的报告(运行头部、质量门禁、发现、修复、验证、防御事件、经验银行);`format:"sarif"` 产出 SARIF 2.1.0 日志供 CI / GitHub Code Scanning 消费(每个审查维度一条规则,严重度 → error/warning/note,携带文件+行号位置与建议修复)。`write:true` 持久化到 `.iterate/report.md` / `.iterate/report.sarif`;`lang`(zh | en)选择报告语言。除可选的报告文件外均为只读
|
|
309
310
|
|
|
310
311
|
---
|
|
311
312
|
|
|
@@ -334,7 +335,7 @@ validation:
|
|
|
334
335
|
### Node.js / DSH 兼容范围
|
|
335
336
|
|
|
336
337
|
- **Node.js**:`>=20`(`package.json` 的 `engines.node`)。
|
|
337
|
-
- **DSH**:`package.json` 的 `dsh.compatibility.dshReleases` 字段是发布兼容性的**唯一真源**(本 README 逐字镜像该字段;两者需同步更新)。以下官方版本声明为 `compatible`:`0.1.1-rc.1`、`0.1.2-alpha.4`、`0.1.2-alpha.5`、`0.1.2-rc.1`、`0.1.3-alpha.1`、`0.1.5-alpha.1`、`0.1.5-alpha.2`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-alpha.1`、`0.2.1-alpha.2`(共
|
|
338
|
+
- **DSH**:`package.json` 的 `dsh.compatibility.dshReleases` 字段是发布兼容性的**唯一真源**(本 README 逐字镜像该字段;两者需同步更新)。以下官方版本声明为 `compatible`:`0.1.1-rc.1`、`0.1.2-alpha.4`、`0.1.2-alpha.5`、`0.1.2-rc.1`、`0.1.3-alpha.1`、`0.1.5-alpha.1`、`0.1.5-alpha.2`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-alpha.1`、`0.2.1-alpha.2`、`0.2.1-alpha.3`、`0.2.1-alpha.4`、`0.2.1-alpha.5`、`0.2.1-rc.1`、`0.2.1-rc.2`(共 24 个版本,`0.1.1-rc.1` … `0.2.1-rc.2`)。插件基于 `@deepseek-ai/dsh-tools` / `@deepseek-ai/dsh-util-values` `0.2.1-rc.2` 构建,只使用公开契约(工具注册、客户端 slots + theme、bundle patch,以及 v3.9 起的会话状态记录接口)。插件同时声明 `engines.dsh`(`>=0.1.1-rc.1`),使 dsh ≥ 0.2.1-rc.2 运行时只依据该范围判断兼容性(更早的运行时仍回退校验 `package.json` 中列出的 `@deepseek-ai/dsh-*` peer 版本)。
|
|
338
339
|
- **一次性 Profile 证据(真实,`dsh` CLI `0.1.1-rc.1`)**:在临时 `$DSH_HOME` 下
|
|
339
340
|
`dsh plugin --profile <p> add <本仓库>` 约 449ms 完成安装;`dsh --profile <p>
|
|
340
341
|
--dump-config` 正确合成 `iterate-plugin` bundle patch;`dsh plugin --profile <p>
|
|
@@ -364,8 +365,28 @@ validation:
|
|
|
364
365
|
|
|
365
366
|
### 依赖
|
|
366
367
|
|
|
367
|
-
|
|
368
|
-
`@deepseek-ai/dsh-util-values
|
|
368
|
+
运行时依赖:`js-yaml`(精确锁版)。本插件构建所依赖的三个 DSH 宿主模块——
|
|
369
|
+
`@deepseek-ai/cordis`、`@deepseek-ai/dsh-tools`、`@deepseek-ai/dsh-util-values`
|
|
370
|
+
——已声明为**可选 `peerDependencies`**,直接复用宿主自带的那一份,避免插件内
|
|
371
|
+
再打包一份造成双模块实例(由 `dsh` 提供);同时在 `devDependencies` 中精确锁版,
|
|
372
|
+
供本地构建与测试,并与 `dsh.compatibility.dshReleases` 的最新条目保持一致。
|
|
373
|
+
|
|
374
|
+
#### 上游采纳记录(v3.8.0 → dsh `0.2.1-alpha.4`)
|
|
375
|
+
|
|
376
|
+
- **已采纳——`engines.dsh` 兼容契约。** 自 `0.2.1-alpha.4` 起,dsh 运行时**在声明了
|
|
377
|
+
`engines.dsh` 时只依据该字段**(一个含预发布版本的 semver 范围)判断第三方插件兼容性,
|
|
378
|
+
忽略 `peerDependencies`;未声明时才回退逐个校验精确的 `@deepseek-ai/dsh-*` peer 版本。
|
|
379
|
+
插件现已声明 `engines.dsh: ">=0.1.1-rc.1"`(与最早的 `dshReleases` 条目同下界),
|
|
380
|
+
使当前运行时加载时不再因精确锁版的 peer 列表被判不兼容;而所有宿主相关能力依旧
|
|
381
|
+
特性探测(缺少 `WorkingDirectoryService`、`ask.displayReason`、`ctx.jobs` 等一律
|
|
382
|
+
降级到安全回退)。更早的运行时仍走 `@deepseek-ai/dsh-*` peer 版本,已扩展至
|
|
383
|
+
`0.2.1-alpha.4`。
|
|
384
|
+
- **`0.2.1-alpha.3` / `0.2.1-alpha.4` 其余均为宿主级变更**——Web 登录、密钥/令牌脱敏、
|
|
385
|
+
极简模式读图、`/compact`、插件热重载、自定义插件索引源、按模型配置推理等级 UI。
|
|
386
|
+
都未暴露本插件消费的 tool/agent/session API(`defineTool`、`JsonValue`、`exec.*`、
|
|
387
|
+
`tools/pre-execute`、`presentCall`),本轮无需进一步采纳。
|
|
388
|
+
- 五个 `@deepseek-ai/*` 包全部精确锁到 `0.2.1-alpha.4`
|
|
389
|
+
(`@deepseek-ai/cordis` 保持 `4.0.5-alpha.2`)。
|
|
369
390
|
|
|
370
391
|
#### 上游采纳记录(v3.7.0 → dsh `0.2.1-alpha.2`)
|
|
371
392
|
|
package/dist/file-lock.js
CHANGED
|
@@ -26,10 +26,17 @@
|
|
|
26
26
|
* `withProjectLock` is therefore NOT reentrant and must not be nested for the
|
|
27
27
|
* same (projectRoot, name).
|
|
28
28
|
*/
|
|
29
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeSync, } from 'node:fs';
|
|
29
|
+
import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeSync, } from 'node:fs';
|
|
30
30
|
import { join } from 'node:path';
|
|
31
31
|
const LOCK_DIR = '.iterate';
|
|
32
|
-
|
|
32
|
+
// Bounded wait before degrading to "proceed unlocked". `acquireProjectLock`
|
|
33
|
+
// spins on `Atomics.wait`, which BLOCKS the single plugin thread for the whole
|
|
34
|
+
// budget — so a long default wedges unrelated tool calls and the UI heartbeat
|
|
35
|
+
// whenever a sibling process holds the lock. Healthy holders release in
|
|
36
|
+
// microseconds, so 1000ms is very generous; anything slower is treated as
|
|
37
|
+
// contention and degrades to the historical unlocked behavior rather than
|
|
38
|
+
// stalling the event loop for five seconds.
|
|
39
|
+
const DEFAULT_WAIT_MS = 1000;
|
|
33
40
|
const DEFAULT_STALE_MS = 10000;
|
|
34
41
|
const POLL_MS = 25;
|
|
35
42
|
/** True when a pid refers to a live process (or we lack permission to tell). */
|
|
@@ -60,14 +67,17 @@ function lockFileName(name) {
|
|
|
60
67
|
return `.${name}.lock`;
|
|
61
68
|
}
|
|
62
69
|
/**
|
|
63
|
-
* Acquire an advisory lock named `name` inside `<projectRoot>/.iterate
|
|
70
|
+
* Acquire an advisory lock named `name` inside `<projectRoot>/.iterate/`,
|
|
71
|
+
* reporting whether it was actually taken.
|
|
64
72
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* created or the wait times out — callers then proceed
|
|
68
|
-
* historical best-effort behavior, rather than failing
|
|
73
|
+
* The returned release function is always callable, idempotent, and never
|
|
74
|
+
* throws. `acquired:false` (with a no-op release) is returned when the lock
|
|
75
|
+
* directory cannot be created or the wait times out — callers then proceed
|
|
76
|
+
* unlocked, which is the historical best-effort behavior, rather than failing
|
|
77
|
+
* the operation. Callers that MUST hold the mutex (e.g. a log rewrite that
|
|
78
|
+
* renames the file) branch on `acquired` and refuse to run unlocked.
|
|
69
79
|
*/
|
|
70
|
-
export function
|
|
80
|
+
export function acquireProjectLockChecked(projectRoot, name, options = {}) {
|
|
71
81
|
const waitMs = Number.isFinite(options.waitMs) && (options.waitMs ?? 0) >= 0
|
|
72
82
|
? options.waitMs
|
|
73
83
|
: DEFAULT_WAIT_MS;
|
|
@@ -80,7 +90,7 @@ export function acquireProjectLock(projectRoot, name, options = {}) {
|
|
|
80
90
|
mkdirSync(dir, { recursive: true });
|
|
81
91
|
}
|
|
82
92
|
catch {
|
|
83
|
-
return () => { };
|
|
93
|
+
return { acquired: false, release: () => { } };
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
const lockPath = join(dir, lockFileName(name));
|
|
@@ -92,7 +102,7 @@ export function acquireProjectLock(projectRoot, name, options = {}) {
|
|
|
92
102
|
}
|
|
93
103
|
catch (err) {
|
|
94
104
|
if (err.code !== 'EEXIST')
|
|
95
|
-
return () => { };
|
|
105
|
+
return { acquired: false, release: () => { } };
|
|
96
106
|
// Lock exists — steal it when the owner is gone or the lock is ancient.
|
|
97
107
|
let stale = false;
|
|
98
108
|
try {
|
|
@@ -118,11 +128,21 @@ export function acquireProjectLock(projectRoot, name, options = {}) {
|
|
|
118
128
|
continue;
|
|
119
129
|
}
|
|
120
130
|
if (Date.now() - start > waitMs)
|
|
121
|
-
return () => { };
|
|
131
|
+
return { acquired: false, release: () => { } };
|
|
122
132
|
sleepSync(POLL_MS);
|
|
123
133
|
continue;
|
|
124
134
|
}
|
|
125
|
-
// Owned the lock:
|
|
135
|
+
// Owned the lock: capture the created inode (for the ownership-checked
|
|
136
|
+
// release), stamp the holder pid, then release once on return.
|
|
137
|
+
let ino = 0;
|
|
138
|
+
try {
|
|
139
|
+
ino = fstatSync(fd).ino;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// inode unavailable on this filesystem — the release falls back to the
|
|
143
|
+
// pid-stamp check only.
|
|
144
|
+
ino = 0;
|
|
145
|
+
}
|
|
126
146
|
try {
|
|
127
147
|
writeSync(fd, String(process.pid));
|
|
128
148
|
}
|
|
@@ -135,9 +155,20 @@ export function acquireProjectLock(projectRoot, name, options = {}) {
|
|
|
135
155
|
catch {
|
|
136
156
|
// best-effort
|
|
137
157
|
}
|
|
138
|
-
return makeLockRelease(lockPath);
|
|
158
|
+
return { acquired: true, release: makeLockRelease(lockPath, ino) };
|
|
139
159
|
}
|
|
140
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Acquire an advisory lock named `name` inside `<projectRoot>/.iterate/`.
|
|
163
|
+
*
|
|
164
|
+
* Returns a release function: always callable, idempotent, never throws.
|
|
165
|
+
* A no-op (but callable) release is returned when the lock directory cannot be
|
|
166
|
+
* created or the wait times out — callers then proceed unlocked, which is the
|
|
167
|
+
* historical best-effort behavior, rather than failing the operation.
|
|
168
|
+
*/
|
|
169
|
+
export function acquireProjectLock(projectRoot, name, options = {}) {
|
|
170
|
+
return acquireProjectLockChecked(projectRoot, name, options).release;
|
|
171
|
+
}
|
|
141
172
|
/**
|
|
142
173
|
* Build the release function for a lock file this process just acquired.
|
|
143
174
|
*
|
|
@@ -151,7 +182,25 @@ export function acquireProjectLock(projectRoot, name, options = {}) {
|
|
|
151
182
|
*
|
|
152
183
|
* Always callable, idempotent, and never throws.
|
|
153
184
|
*/
|
|
154
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Build the release function for a lock file this process just acquired.
|
|
187
|
+
*
|
|
188
|
+
* Advisory locks here are STOLEN when the recorded owner dies or the mtime
|
|
189
|
+
* goes ancient: the steal unlinks our file and creates a new one stamped with
|
|
190
|
+
* the STEALER's pid. An unguarded release would then unlink the new holder's
|
|
191
|
+
* lock — turning our release into a double-release that admits a third process
|
|
192
|
+
* into a "held" critical section. So the release re-reads the stamp and only
|
|
193
|
+
* unlinks a file that is still ours: same pid (or unstamped) AND, when we know
|
|
194
|
+
* the inode we created, the SAME inode still at the path. The inode check closes
|
|
195
|
+
* the read-then-unlink window where the stamp check alone could race a steal
|
|
196
|
+
* (our pid is alive, but the file was already replaced between the two calls).
|
|
197
|
+
*
|
|
198
|
+
* Always callable, idempotent, and never throws.
|
|
199
|
+
*
|
|
200
|
+
* @param lockPath - the lock file path.
|
|
201
|
+
* @param ino - the inode captured at acquisition (0 = unknown; skip the check).
|
|
202
|
+
*/
|
|
203
|
+
export function makeLockRelease(lockPath, ino) {
|
|
155
204
|
let released = false;
|
|
156
205
|
return () => {
|
|
157
206
|
if (released)
|
|
@@ -169,6 +218,18 @@ export function makeLockRelease(lockPath) {
|
|
|
169
218
|
// it is no longer ours to delete.
|
|
170
219
|
if (raw !== '' && Number(raw) !== process.pid)
|
|
171
220
|
return;
|
|
221
|
+
// The path must still refer to the file WE created. A stealer unlinked our
|
|
222
|
+
// file and created a fresh one; that replacement carries a different inode,
|
|
223
|
+
// so unlinking by path would delete the NEW holder's lock.
|
|
224
|
+
if (typeof ino === 'number' && ino !== 0) {
|
|
225
|
+
try {
|
|
226
|
+
if (statSync(lockPath).ino !== ino)
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return; // vanished mid-release — nothing of ours left
|
|
231
|
+
}
|
|
232
|
+
}
|
|
172
233
|
try {
|
|
173
234
|
unlinkSync(lockPath);
|
|
174
235
|
}
|
package/dist/git-scope.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* git repo.
|
|
22
22
|
*/
|
|
23
23
|
import { execFile } from 'node:child_process';
|
|
24
|
-
import {
|
|
24
|
+
import { lstatSync, realpathSync } from 'node:fs';
|
|
25
25
|
import { join, sep } from 'node:path';
|
|
26
26
|
/**
|
|
27
27
|
* Parse `git diff --name-only -z` stdout into a list of relative paths.
|
|
@@ -112,6 +112,15 @@ export function parseChangedFiles(stdout) {
|
|
|
112
112
|
* Keep only entries that resolve to an existing regular file under `root`.
|
|
113
113
|
* Traversal-safe: rejects absolute paths and any relative path that would
|
|
114
114
|
* escape `root` via `..` (resolved against the root before stat).
|
|
115
|
+
*
|
|
116
|
+
* Symlink handling: `statSync` (and `existsSync`) FOLLOW symlinks, so a git
|
|
117
|
+
* entry that is a link to a file OUTSIDE the project would pass a path-only
|
|
118
|
+
* containment check and enter the changed-only scope. Such a file is then
|
|
119
|
+
* rejected by the evidence gate (realpath out of bounds → `file_not_found`),
|
|
120
|
+
* so every finding on it would fail closed as `EVIDENCE_VIOLATION` forever —
|
|
121
|
+
* a permanent false positive for repos with dotfile or shared-config links.
|
|
122
|
+
* `lstatSync` distinguishes the link itself; a link whose REAL target escapes
|
|
123
|
+
* `root` is dropped here instead.
|
|
115
124
|
*/
|
|
116
125
|
export function filterExistingFiles(root, files) {
|
|
117
126
|
const out = [];
|
|
@@ -123,7 +132,18 @@ export function filterExistingFiles(root, files) {
|
|
|
123
132
|
if (!candidate.startsWith(rootPrefix) && candidate !== root)
|
|
124
133
|
continue;
|
|
125
134
|
try {
|
|
126
|
-
|
|
135
|
+
const st = lstatSync(candidate);
|
|
136
|
+
if (st.isFile()) {
|
|
137
|
+
out.push(rel);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!st.isSymbolicLink())
|
|
141
|
+
continue;
|
|
142
|
+
// A link whose real target stays inside the project is a legitimate,
|
|
143
|
+
// reviewable file; one that escapes is excluded from scope (fail-closed
|
|
144
|
+
// at the source instead of as a permanent evidence violation).
|
|
145
|
+
const real = realpathSync(candidate);
|
|
146
|
+
if (real === root || real.startsWith(rootPrefix))
|
|
127
147
|
out.push(rel);
|
|
128
148
|
}
|
|
129
149
|
catch {
|
package/dist/index.js
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
* iterate-plugin — dsh plugin for the iterate autonomous closed-loop workflow
|
|
3
3
|
*
|
|
4
4
|
* Architecture:
|
|
5
|
-
* - The plugin registers
|
|
5
|
+
* - The plugin registers 18 tools (14 original + 3 v3.0 quality command center
|
|
6
|
+
* tools + 1 v3.9 report export)
|
|
6
7
|
* Original: config, validate, decision-log, context, review, triage, fix, diff,
|
|
7
8
|
* rollback, checkpoint, status, history, prune, transcript
|
|
8
9
|
* v3.0: experience, quality_gate, defense_events
|
|
10
|
+
* v3.9: report
|
|
9
11
|
* - The plugin injects a system prompt section teaching the iterate workflow pattern
|
|
10
12
|
* - The model (prompted by the skill) writes a workflow script using dsh's `workflow` tool
|
|
11
13
|
* - The workflow script uses `agent()` / `parallel()` / `phase()` / `log()` to orchestrate
|
|
12
|
-
* - Subagents use the
|
|
14
|
+
* - Subagents use the 18 tools to do real work (read config, run validation, log decisions,
|
|
13
15
|
* review, triage, apply/rollback/fixing, checkpoint, status, history, prune, transcript,
|
|
14
|
-
* query experience bank, check quality gates, query defense events)
|
|
16
|
+
* query experience bank, check quality gates, query defense events, export a report)
|
|
15
17
|
* - A `tools/pre-execute` hook gates destructive iterate calls behind human approval
|
|
16
18
|
* (F8 observatory approval policy: ask / deny / allow).
|
|
17
19
|
*
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
*
|
|
24
26
|
* Key files:
|
|
25
27
|
* - src/index.ts — Plugin entry: register tools + inject skill prompt
|
|
26
|
-
* - src/tools/ —
|
|
28
|
+
* - src/tools/ — 18 tool implementations (14 original + 3 v3.0 + 1 v3.9)
|
|
27
29
|
* - src/config-loader.ts — YAML config loading
|
|
28
30
|
* - src/types.ts — Shared types
|
|
29
31
|
*/
|
|
@@ -41,6 +43,7 @@ import { registerTranscriptTool } from "./tools/transcript.js";
|
|
|
41
43
|
import { registerExperienceBankTool } from "./tools/experience-bank.js";
|
|
42
44
|
import { registerQualityGateTool } from "./tools/quality-gate.js";
|
|
43
45
|
import { registerDefenseEventsTool } from "./tools/defense-events.js";
|
|
46
|
+
import { registerReportTool } from "./tools/report.js";
|
|
44
47
|
import { registerSessionHooks } from "./session-hooks.js";
|
|
45
48
|
import { registerLiveCapture } from "./live.js";
|
|
46
49
|
import { installWorkingDirectoryContext } from "./working-directory.js";
|
|
@@ -87,6 +90,8 @@ export function apply(ctx) {
|
|
|
87
90
|
['iterate_experience', () => registerExperienceBankTool(ctx)],
|
|
88
91
|
['iterate_quality_gate', () => registerQualityGateTool(ctx)],
|
|
89
92
|
['iterate_defense_events', () => registerDefenseEventsTool(ctx)],
|
|
93
|
+
// v3.9: PR-ready / CI-ready report export
|
|
94
|
+
['iterate_report', () => registerReportTool(ctx)],
|
|
90
95
|
];
|
|
91
96
|
for (const [toolName, register] of toolRegistrations) {
|
|
92
97
|
try {
|