iterate-plugin 3.6.0 → 3.7.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/lib/parse.js CHANGED
@@ -207,9 +207,15 @@ export function countSessionImages(session) {
207
207
 
208
208
  /**
209
209
  * Check whether `obj` is a valid ReviewReport-like object.
210
- * The minimum requirement: an object with `convergence` (object),
210
+ * The minimum requirement: an object with `convergence` (a RECORD),
211
211
  * `findings` (array), and `rounds` (array).
212
212
  *
213
+ * The convergence guard is also the TranscriptManifest discriminator: a
214
+ * manifest carries `convergence` / `rounds` / `findings` too, but its
215
+ * convergence is the per-round count SERIES (an array). Since
216
+ * `typeof [] === 'object'` in JS, only an explicit array rejection keeps the
217
+ * two shapes apart — see `isTranscriptManifest`.
218
+ *
213
219
  * @param {unknown} obj
214
220
  * @returns {obj is Record<string, unknown>}
215
221
  */
@@ -217,12 +223,8 @@ export function isReviewReport(obj) {
217
223
  if (!obj || typeof obj !== 'object') return false
218
224
  const o = /** @type {Record<string, unknown>} */ (obj)
219
225
  const convergence = safeGet(o, 'convergence')
220
- return (
221
- typeof convergence === 'object' &&
222
- convergence !== null &&
223
- Array.isArray(safeGet(o, 'findings')) &&
224
- Array.isArray(safeGet(o, 'rounds'))
225
- )
226
+ if (!convergence || typeof convergence !== 'object' || Array.isArray(convergence)) return false
227
+ return Array.isArray(safeGet(o, 'findings')) && Array.isArray(safeGet(o, 'rounds'))
226
228
  }
227
229
 
228
230
  /**
@@ -585,7 +587,13 @@ export function scanSessionForTranscript(session) {
585
587
  }
586
588
  }
587
589
 
588
- return null
590
+ // Fallback: a host snapshot without the tool-call envelope (a restored
591
+ // transcript, a rehydrated turn, a shell that surfaces artifacts directly)
592
+ // may still carry a manifest somewhere in the tree. Deep-scan for it so the
593
+ // dashboard and the observatory keep filling instead of degrading to the
594
+ // first-run empty state. The manifest is returned RAW — callers normalize.
595
+ const loose = findFirstInObject(session, isTranscriptManifest)
596
+ return loose ? /** @type {Record<string, unknown>} */ (loose) : null
589
597
  }
590
598
 
591
599
  /**
@@ -975,10 +983,16 @@ export function normalizeQualityGateSnapshot(raw) {
975
983
  */
976
984
  export function scanSessionForQualityGate(session) {
977
985
  const node = latestToolResultNode(session, 'iterate_quality_gate', findQualityGateInObject)
978
- if (node === null) return null
979
- const found = findQualityGateInObject(node)
980
- if (!found) return null
981
- return normalizeQualityGateSnapshot(found)
986
+ if (node !== null) {
987
+ const found = findQualityGateInObject(node)
988
+ if (found) return normalizeQualityGateSnapshot(found)
989
+ }
990
+ // Fallback: a host snapshot without the tool-call envelope (a restored
991
+ // transcript, a rehydrated turn, a shell that surfaces artifacts directly)
992
+ // still carries the snapshot somewhere in the tree — deep-scan for it so
993
+ // the panel fills instead of claiming "not pulled yet".
994
+ const loose = findFirstInObject(session, isQualityGateSnapshot)
995
+ return loose ? normalizeQualityGateSnapshot(loose) : null
982
996
  }
983
997
 
984
998
  /**
@@ -1034,10 +1048,13 @@ export function normalizeExperienceBankResult(raw) {
1034
1048
  */
1035
1049
  export function scanSessionForExperienceBank(session) {
1036
1050
  const node = latestToolResultNode(session, 'iterate_experience', findExperienceResultInObject)
1037
- if (node === null) return null
1038
- const found = findExperienceResultInObject(node)
1039
- if (!found) return null
1040
- return normalizeExperienceBankResult(found)
1051
+ if (node !== null) {
1052
+ const found = findExperienceResultInObject(node)
1053
+ if (found) return normalizeExperienceBankResult(found)
1054
+ }
1055
+ // Same deep-scan fallback as the quality-gate scanner (see above).
1056
+ const loose = findFirstInObject(session, isExperienceBankResult)
1057
+ return loose ? normalizeExperienceBankResult(loose) : null
1041
1058
  }
1042
1059
 
1043
1060
  /**
@@ -1098,10 +1115,13 @@ export function normalizeDefenseEventsResult(raw) {
1098
1115
  */
1099
1116
  export function scanSessionForDefenseEvents(session) {
1100
1117
  const node = latestToolResultNode(session, 'iterate_defense_events', findDefenseResultInObject)
1101
- if (node === null) return null
1102
- const found = findDefenseResultInObject(node)
1103
- if (!found) return null
1104
- return normalizeDefenseEventsResult(found)
1118
+ if (node !== null) {
1119
+ const found = findDefenseResultInObject(node)
1120
+ if (found) return normalizeDefenseEventsResult(found)
1121
+ }
1122
+ // Same deep-scan fallback as the quality-gate scanner (see above).
1123
+ const loose = findFirstInObject(session, isDefenseEventsResult)
1124
+ return loose ? normalizeDefenseEventsResult(loose) : null
1105
1125
  }
1106
1126
 
1107
1127
  // ─── Run-summary / meta-review verdict detection ─────────────────────────────
@@ -1963,10 +1983,16 @@ export function toolCalledInSession(session, toolName) {
1963
1983
  *
1964
1984
  * @param {boolean} pulled whether the backing tool was called this session
1965
1985
  * @param {string} topic what is missing (e.g. "质量门禁证书")
1986
+ * @param {'zh'|'en'} [lang] UI language (the caller localizes `topic` itself)
1966
1987
  * @returns {string}
1967
1988
  */
1968
- export function diskEmptyStateText(pulled, topic) {
1969
- const subject = topic || '数据'
1989
+ export function diskEmptyStateText(pulled, topic, lang = 'zh') {
1990
+ const subject = topic || (lang === 'en' ? 'data' : '数据')
1991
+ if (lang === 'en') {
1992
+ return pulled
1993
+ ? `Pulled from disk: ${subject} has no records yet (the disk is empty).`
1994
+ : `Not pulled from disk yet: ${subject} lives under the project's .iterate/ — copy the "Pull disk snapshot" instruction back into the session and it will fill in.`
1995
+ }
1970
1996
  return pulled
1971
1997
  ? `已从磁盘拉取:${subject}暂无记录(磁盘无数据)。`
1972
1998
  : `尚未从磁盘拉取${subject}:数据存放在项目 .iterate/ 下,点「拉取磁盘快照」复制指令发回会话即可回填。`
@@ -2223,27 +2249,40 @@ export function trendMax(points) {
2223
2249
  //
2224
2250
  // Every value the iterate loop can report as `stoppedReason` — including the
2225
2251
  // schema-retry batch (schema_invalid / no_usable_reviewer_output / inconclusive)
2226
- // — maps to a concrete Chinese label. Unknown values still get a readable
2227
- // default instead of a bare "已结束". Kept in parse.js so both the client
2228
- // observatory badge and Node unit tests share one source of truth.
2252
+ // — maps to a concrete label in zh and en. Unknown values still get a readable
2253
+ // default instead of a bare "已结束" / "Done". Kept in parse.js so both the
2254
+ // client observatory badge and Node unit tests share one source of truth.
2229
2255
 
2230
2256
  /**
2231
- * Chinese badge label for a run's `stoppedReason`, or null/undefined-safe.
2257
+ * Badge label for a run's `stoppedReason`, or null/undefined-safe.
2258
+ *
2259
+ * @param {unknown} reason
2260
+ * @param {'zh'|'en'} [lang] UI language (default zh, the model-facing default)
2232
2261
  *
2233
2262
  * @param {string | null | undefined} reason
2234
2263
  * @returns {string}
2235
2264
  */
2236
- export function stoppedReasonLabel(reason) {
2237
- switch (reason) {
2238
- case 'converged': return '已结束 · 已收敛(无新发现)'
2239
- case 'max_rounds_reached': return '已结束 · 达到轮数上限'
2240
- case 'aborted_by_validation': return '已结束 · 验证失败后回滚停止'
2241
- case 'aborted_by_config': return '已结束 · 验证命令不在白名单(配置需修复)'
2242
- case 'inconclusive': return '已结束 · 轮次结论不明(审查输出无效)'
2243
- case 'schema_invalid': return '已结束 · 审查输出连续 schema 校验失败'
2244
- case 'no_usable_reviewer_output': return '已结束 · 无可用审查输出'
2245
- default: return reason ? `已结束 · ${reason}` : '已结束'
2246
- }
2265
+ export function stoppedReasonLabel(reason, lang = 'zh') {
2266
+ const zh = {
2267
+ converged: '已结束 · 已收敛(无新发现)',
2268
+ max_rounds_reached: '已结束 · 达到轮数上限',
2269
+ aborted_by_validation: '已结束 · 验证失败后回滚停止',
2270
+ aborted_by_config: '已结束 · 验证命令不在白名单(配置需修复)',
2271
+ inconclusive: '已结束 · 轮次结论不明(审查输出无效)',
2272
+ schema_invalid: '已结束 · 审查输出连续 schema 校验失败',
2273
+ no_usable_reviewer_output: '已结束 · 无可用审查输出',
2274
+ }
2275
+ const en = {
2276
+ converged: 'Done \u00b7 converged (no new findings)',
2277
+ max_rounds_reached: 'Done \u00b7 reached the round limit',
2278
+ aborted_by_validation: 'Done \u00b7 rolled back after a validation failure',
2279
+ aborted_by_config: 'Done \u00b7 validation command not allow-listed (config needs fixing)',
2280
+ inconclusive: 'Done \u00b7 inconclusive rounds (invalid review output)',
2281
+ schema_invalid: 'Done \u00b7 repeated schema validation failures in review output',
2282
+ no_usable_reviewer_output: 'Done \u00b7 no usable review output',
2283
+ }
2284
+ if (lang === 'en') return en[reason] ?? (reason ? `Done \u00b7 ${reason}` : 'Done')
2285
+ return zh[reason] ?? (reason ? `已结束 · ${reason}` : '已结束')
2247
2286
  }
2248
2287
 
2249
2288
  // ─── Completion notification ────────────────────────────────────────────────
@@ -2252,48 +2291,93 @@ export function stoppedReasonLabel(reason) {
2252
2291
  * One-line completion summary for notifications ("已收敛 / 已达最大轮数").
2253
2292
  *
2254
2293
  * @param {Record<string, unknown>} report
2294
+ * @param {'zh'|'en'} [lang] UI language
2255
2295
  * @returns {string}
2256
2296
  */
2257
- export function buildCompletionSummary(report) {
2297
+ export function buildCompletionSummary(report, lang = 'zh') {
2258
2298
  const conv = /** @type {Record<string, unknown>} */ (report.convergence ?? {})
2259
2299
  const rounds = getCurrentRound(report)
2260
2300
  const total = getTotalRounds(report)
2261
2301
  const stats = severityStats(report)
2262
2302
  const converged = conv.converged === true
2263
- const reason = converged ? '已收敛' : `已达最大轮数 ${total}`
2264
2303
  const totalFindings = stats.critical + stats.high + stats.medium + stats.low
2304
+ if (lang === 'en') {
2305
+ const reason = converged ? 'converged' : `reached the round limit ${total}`
2306
+ return `iterate review complete \u00b7 ${rounds}/${total} rounds \u00b7 ${totalFindings} findings \u00b7 ${reason}`
2307
+ }
2308
+ const reason = converged ? '已收敛' : `已达最大轮数 ${total}`
2265
2309
  return `iterate 评审完成 · ${rounds}/${total} 轮 · ${totalFindings} 项发现 · ${reason}`
2266
2310
  }
2267
2311
 
2268
2312
  // ─── Config edit guidance ───────────────────────────────────────────────────
2269
2313
 
2270
2314
  /**
2271
- * Editable config fields (key + label + hint), used by the settings guide.
2272
- * @type {Array<{ key: string, label: string, hint: string }>}
2315
+ * Editable config fields, used by the settings picker (rendered in the UI) and
2316
+ * by the copy-paste guide / instruction builders (model-facing, always zh).
2317
+ * `label`/`hint` are `{ zh, en }` pairs — the UI localizes them with
2318
+ * `field.label[getLang()]`, while the guide builders below read `.zh`.
2319
+ * @type {Array<{ key: string, label: { zh: string, en: string }, hint: { zh: string, en: string } }>}
2273
2320
  */
2274
2321
  export const CONFIG_EDIT_FIELDS = [
2275
- { key: 'goal', label: '目标', hint: '一句话描述本次迭代目标(字符串)' },
2276
- { key: 'dimensions', label: '审查维度', hint: '数组,如 ["correctness","security"]' },
2277
- { key: 'max_rounds', label: '最大轮数', hint: '正整数' },
2278
- { key: 'review.scope', label: '审查范围', hint: '"full" 或 "changed-only"' },
2279
- { key: 'reasoning_effort', label: '审查推理强度', hint: '"low" / "medium" / "high",缺省跟随模型默认' },
2322
+ {
2323
+ key: 'goal',
2324
+ label: { zh: '目标', en: 'Goal' },
2325
+ hint: { zh: '一句话描述本次迭代目标(字符串)', en: "One-sentence description of this iteration's goal (a string)" },
2326
+ },
2327
+ {
2328
+ key: 'dimensions',
2329
+ label: { zh: '审查维度', en: 'Review dimensions' },
2330
+ hint: { zh: '数组,如 ["correctness","security"]', en: 'Array, e.g. ["correctness","security"]' },
2331
+ },
2332
+ {
2333
+ key: 'max_rounds',
2334
+ label: { zh: '最大轮数', en: 'Max rounds' },
2335
+ hint: { zh: '正整数', en: 'Positive integer' },
2336
+ },
2337
+ {
2338
+ key: 'review.scope',
2339
+ label: { zh: '审查范围', en: 'Review scope' },
2340
+ hint: { zh: '"full" 或 "changed-only"', en: '"full" or "changed-only"' },
2341
+ },
2342
+ {
2343
+ key: 'reasoning_effort',
2344
+ label: { zh: '审查推理强度', en: 'Review reasoning effort' },
2345
+ hint: { zh: '"low" / "medium" / "high",缺省跟随模型默认', en: '"low" / "medium" / "high"; omitted = follow the model default' },
2346
+ },
2280
2347
  {
2281
2348
  key: 'validation.commands',
2282
- label: '验证命令',
2283
- hint: '数组,每项是一条白名单命令字符串,如 ["npm test","npm run lint"];每轮验证逐条执行。留空数组 = 不验证(本轮迭代不受任何测试保护)',
2349
+ label: { zh: '验证命令', en: 'Validation commands' },
2350
+ hint: {
2351
+ zh: '数组,每项是一条白名单命令字符串,如 ["npm test","npm run lint"];每轮验证逐条执行。留空数组 = 不验证(本轮迭代不受任何测试保护)',
2352
+ en: 'Array of allow-listed command strings, e.g. ["npm test","npm run lint"]; run one by one every round. An empty array = no verification (this iteration runs with no test protection)',
2353
+ },
2284
2354
  },
2285
2355
  {
2286
2356
  key: 'language',
2287
- label: '界面语言',
2288
- hint: '"zh" 或 "en"(默认 "en"):控制审批理由、防御事件标签等面向人文案的语言',
2357
+ label: { zh: '界面语言', en: 'Interface language' },
2358
+ hint: {
2359
+ zh: '"zh" 或 "en"(默认 "en"):控制审批理由、防御事件标签等面向人文案的语言',
2360
+ en: '"zh" or "en" (default "en"): language of human-facing copy such as approval rationales and defense-event labels',
2361
+ },
2289
2362
  },
2290
2363
  {
2291
2364
  key: 'personalization.known_intentional',
2292
- label: '已知有意问题',
2293
- hint: '数组,每项 {file, line?, dimension, reason};命中条目在评审中被跳过。可用分诊面板的「回读已写入条目」核对',
2365
+ label: { zh: '已知有意问题', en: 'Known intentional issues' },
2366
+ hint: {
2367
+ zh: '数组,每项 {file, line?, dimension, reason};命中条目在评审中被跳过。可用分诊面板的「回读已写入条目」核对',
2368
+ en: 'Array of {file, line?, dimension, reason}; findings hitting an entry are skipped in review. Verify via the triage panel\'s "read back written entries"',
2369
+ },
2370
+ },
2371
+ {
2372
+ key: 'atomic.max_lines',
2373
+ label: { zh: '原子修复上限行数', en: 'Atomic fix line cap' },
2374
+ hint: { zh: '正整数', en: 'Positive integer' },
2375
+ },
2376
+ {
2377
+ key: 'git.push_per_round',
2378
+ label: { zh: '每轮推送', en: 'Push per round' },
2379
+ hint: { zh: 'true / false', en: 'true / false' },
2294
2380
  },
2295
- { key: 'atomic.max_lines', label: '原子修复上限行数', hint: '正整数' },
2296
- { key: 'git.push_per_round', label: '每轮推送', hint: 'true / false' },
2297
2381
  ]
2298
2382
 
2299
2383
  /**
@@ -2312,7 +2396,7 @@ export function buildConfigEditGuide() {
2312
2396
  ' 请先给 validation.commands 配上至少一条真实命令(如 npm test)。',
2313
2397
  '',
2314
2398
  '可编辑字段:',
2315
- ...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}(${f.label}):${f.hint}`),
2399
+ ...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}(${f.label.zh}):${f.hint.zh}`),
2316
2400
  '',
2317
2401
  '让模型帮你改:',
2318
2402
  '1. 调用 iterate_config({ operation: "read" }) 查看当前配置;',
@@ -2344,7 +2428,7 @@ export const CONFIG_VALUE_PLACEHOLDER = '<由模型填写:该字段的新值>'
2344
2428
  * Look up an editable config field by its dotted key (gap #8).
2345
2429
  *
2346
2430
  * @param {string} key
2347
- * @returns {{ key: string, label: string, hint: string } | null}
2431
+ * @returns {{ key: string, label: { zh: string, en: string }, hint: { zh: string, en: string } } | null}
2348
2432
  */
2349
2433
  export function configFieldByKey(key) {
2350
2434
  if (typeof key !== 'string' || !key) return null
@@ -2363,8 +2447,9 @@ export function configFieldByKey(key) {
2363
2447
  */
2364
2448
  export function buildConfigFieldInstruction(key, value) {
2365
2449
  const field = configFieldByKey(key)
2366
- const label = field ? field.label : key
2367
- const hint = field ? field.hint : '见 iterate.config.yaml'
2450
+ // Model-facing instruction text: always zh, like every other builder here.
2451
+ const label = field ? field.label.zh : key
2452
+ const hint = field ? field.hint.zh : '见 iterate.config.yaml'
2368
2453
  const updates = { [key]: value === undefined ? CONFIG_VALUE_PLACEHOLDER : value }
2369
2454
  // Delegate the payload to `buildConfigEditInstruction` so the multi-field
2370
2455
  // builder stays a live code path (it used to be test-only dead code).
@@ -2379,6 +2464,116 @@ export function buildConfigFieldInstruction(key, value) {
2379
2464
  ].join('\n')
2380
2465
  }
2381
2466
 
2467
+ // ─── Config read-back (write → read-back loop) ──────────────────────────────
2468
+ //
2469
+ // The triage panel got a read-back instruction (#7); the config editor only
2470
+ // generated writes, so there was no way to SEE what the file actually says
2471
+ // after pasting an instruction. These three pieces close that loop: an
2472
+ // instruction the settings card copies, a session scanner that picks the
2473
+ // echoed result back up, and a dotted-path resolver that surfaces the value
2474
+ // of the field the user currently has selected.
2475
+
2476
+ /**
2477
+ * Copy-paste instruction that makes the model call `iterate_config` read and
2478
+ * echo the full result (including `config`) into the session, where the
2479
+ * settings card can display it.
2480
+ *
2481
+ * @returns {string}
2482
+ */
2483
+ export function buildConfigReadInstruction() {
2484
+ return (
2485
+ '请调用 `iterate_config` 回读当前生效配置,并把完整结果(含 config 字段)原样回显,' +
2486
+ '供我核对界面上展示的当前值:\n\n' +
2487
+ '```json\n' +
2488
+ `${JSON.stringify({ operation: 'read' }, null, 2)}\n` +
2489
+ '```'
2490
+ )
2491
+ }
2492
+
2493
+ /**
2494
+ * Top-level keys of an iterate config, derived from the editable field list
2495
+ * so a newly editable section is covered by the predicate automatically.
2496
+ * @type {string[]}
2497
+ */
2498
+ const CONFIG_TOP_LEVEL_KEYS = [...new Set(CONFIG_EDIT_FIELDS.map((f) => f.key.split('.')[0]))]
2499
+
2500
+ /**
2501
+ * Is `v` an effective iterate config? Requires several known top-level keys:
2502
+ * `loadEffectiveConfig` always merges the built-in defaults, so a real config
2503
+ * is complete — while a stray `{ goal: ... }` record elsewhere in the
2504
+ * transcript cannot impersonate one.
2505
+ *
2506
+ * @param {unknown} v
2507
+ * @returns {boolean}
2508
+ */
2509
+ export function isEffectiveConfig(v) {
2510
+ if (!v || typeof v !== 'object' || Array.isArray(v)) return false
2511
+ const rec = /** @type {Record<string, unknown>} */ (v)
2512
+ let hits = 0
2513
+ for (const key of CONFIG_TOP_LEVEL_KEYS) {
2514
+ if (Object.hasOwn(rec, key)) hits += 1
2515
+ if (hits >= 3) return true
2516
+ }
2517
+ return false
2518
+ }
2519
+
2520
+ /**
2521
+ * Locate the config inside one `iterate_config` tool result. Reads and writes
2522
+ * both report it under `config`; a section read (`data`) is deliberately not
2523
+ * accepted because it is only a slice.
2524
+ *
2525
+ * @param {unknown} v
2526
+ * @returns {Record<string, unknown> | null}
2527
+ */
2528
+ export function findConfigInResult(v) {
2529
+ if (!v || typeof v !== 'object' || Array.isArray(v)) return null
2530
+ const rec = /** @type {Record<string, unknown>} */ (v)
2531
+ const cfg = safeGet(rec, 'config')
2532
+ if (isEffectiveConfig(cfg)) return /** @type {Record<string, unknown>} */ (cfg)
2533
+ return isEffectiveConfig(v) ? rec : null
2534
+ }
2535
+
2536
+ /**
2537
+ * Latest effective config echoed into the session: the newest full
2538
+ * `iterate_config` result (read or post-write), falling back to a deep scan
2539
+ * for a config that reached the stream without the tool envelope (restored
2540
+ * transcript, host that surfaces artifacts directly).
2541
+ *
2542
+ * @param {unknown} session
2543
+ * @returns {Record<string, unknown> | null}
2544
+ */
2545
+ export function scanSessionForConfigRead(session) {
2546
+ const node = latestToolResultNode(session, 'iterate_config', findConfigInResult)
2547
+ if (node !== null) {
2548
+ const cfg = findConfigInResult(node)
2549
+ if (cfg) return cfg
2550
+ }
2551
+ const loose = findFirstInObject(session, isEffectiveConfig)
2552
+ return loose ? /** @type {Record<string, unknown>} */ (loose) : null
2553
+ }
2554
+
2555
+ /**
2556
+ * Resolve a dotted editable-field key (`validation.commands`, `review.scope`)
2557
+ * against a read-back config.
2558
+ *
2559
+ * @param {Record<string, unknown> | null | undefined} config
2560
+ * @param {string} key dotted field key from CONFIG_EDIT_FIELDS
2561
+ * @returns {{ resolved: boolean, value: unknown }} `value` is undefined when unresolved
2562
+ */
2563
+ export function configValueByPath(config, key) {
2564
+ if (!config || typeof config !== 'object' || typeof key !== 'string' || !key) {
2565
+ return { resolved: false, value: undefined }
2566
+ }
2567
+ let node = /** @type {unknown} */ (config)
2568
+ for (const part of key.split('.')) {
2569
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return { resolved: false, value: undefined }
2570
+ const rec = /** @type {Record<string, unknown>} */ (node)
2571
+ if (!Object.hasOwn(rec, part)) return { resolved: false, value: undefined }
2572
+ node = rec[part]
2573
+ }
2574
+ return { resolved: true, value: node }
2575
+ }
2576
+
2382
2577
  /**
2383
2578
  * Keyboard shortcut → triage verdict mapping (used by the triage panel).
2384
2579
  * @type {Record<string, 'keep' | 'skip' | 'ignore'>}
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "iterate-plugin",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness with quality command center and experience bank (v3.3). Features: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus dry-run pure-review mode, quality gate compute/persist, writable experience bank, defense events stream (record + bilingual labels), and native command buttons — with a live quality command center (F8/F9/F10 render real session data), §8 assign-fix instruction, and task_mode indicator wired end to end.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/jingzhao-l/iterate-plugin.git"
9
+ "url": "https://github.com/jingzhao-l/iterate-skill.git",
10
+ "directory": "harness/iterate-plugin"
10
11
  },
11
- "homepage": "https://github.com/jingzhao-l/iterate-plugin",
12
+ "homepage": "https://github.com/jingzhao-l/iterate-skill/tree/main/harness/iterate-plugin",
12
13
  "bugs": {
13
- "url": "https://github.com/jingzhao-l/iterate-plugin/issues"
14
+ "url": "https://github.com/jingzhao-l/iterate-skill/issues"
14
15
  },
15
16
  "keywords": [
16
17
  "dsh-plugin",
@@ -45,7 +46,8 @@
45
46
  "0.1.7-rc.2": "compatible",
46
47
  "0.2.0-rc.1": "compatible",
47
48
  "0.2.0-rc.2": "compatible",
48
- "0.2.1-alpha.1": "compatible"
49
+ "0.2.1-alpha.1": "compatible",
50
+ "0.2.1-alpha.2": "compatible"
49
51
  }
50
52
  },
51
53
  "client": {
@@ -76,25 +78,28 @@
76
78
  "build": "tsc -p tsconfig.build.json",
77
79
  "build:client": "node scripts/build-client.mjs",
78
80
  "prepublishOnly": "npm run build && npm run build:client",
79
- "typecheck": "tsc --noEmit",
81
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && tsc --noEmit -p tsconfig.client.json",
80
82
  "typecheck:client": "tsc --noEmit -p tsconfig.client.json",
81
83
  "test": "tsx --test test/*.test.ts",
82
84
  "test:validate": "tsx --test test/validate.test.ts"
83
85
  },
84
86
  "dependencies": {
85
87
  "@deepseek-ai/cordis": "4.0.5-alpha.1",
86
- "@deepseek-ai/dsh-tools": "0.2.1-alpha.1",
87
- "@deepseek-ai/dsh-util-values": "0.2.1-alpha.1",
88
+ "@deepseek-ai/dsh-tools": "0.2.1-alpha.2",
89
+ "@deepseek-ai/dsh-util-values": "0.2.1-alpha.2",
88
90
  "js-yaml": "4.3.2"
89
91
  },
90
92
  "devDependencies": {
91
- "@deepseek-ai/dsh-agent": "0.2.1-alpha.1",
92
- "@deepseek-ai/dsh-jobs": "0.2.1-alpha.1",
93
- "@deepseek-ai/dsh-session": "0.2.1-alpha.1",
93
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.2",
94
+ "@deepseek-ai/dsh-jobs": "0.2.1-alpha.2",
95
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.2",
94
96
  "@types/js-yaml": "4.0.9",
95
97
  "@types/node": "22.15.0",
96
98
  "@types/react": "19.2.2",
97
99
  "esbuild": "0.25.12",
100
+ "jsdom": "26.1.0",
101
+ "react": "19.2.2",
102
+ "react-dom": "19.2.2",
98
103
  "tsx": "4.20.3",
99
104
  "typescript": "5.9.3"
100
105
  },