iterate-plugin 2.12.2 → 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.
Files changed (41) hide show
  1. package/README.md +28 -12
  2. package/README.zh-CN.md +2 -1
  3. package/dist/approval-gate.js +16 -2
  4. package/dist/config-loader.js +5 -0
  5. package/dist/git-scope.js +61 -7
  6. package/dist/index.js +16 -6
  7. package/dist/session-hooks.js +36 -11
  8. package/dist/skill-prompt.js +3 -0
  9. package/dist/tools/decision-log.js +10 -1
  10. package/dist/tools/defense-events.js +260 -0
  11. package/dist/tools/defense-store.js +97 -0
  12. package/dist/tools/experience-bank.js +248 -0
  13. package/dist/tools/experience-store.js +132 -0
  14. package/dist/tools/quality-gate.js +180 -0
  15. package/dist/tools/quality-store.js +174 -0
  16. package/lib/client.js +662 -103
  17. package/lib/parse.js +93 -0
  18. package/package.json +7 -6
  19. package/src/approval-gate.ts +14 -2
  20. package/src/client/index.ts +542 -49
  21. package/src/config-loader.ts +5 -0
  22. package/src/git-scope.ts +48 -7
  23. package/src/index.ts +16 -6
  24. package/src/session-hooks.ts +33 -11
  25. package/src/skill-prompt.ts +3 -0
  26. package/src/tools/checkpoint.ts +1 -1
  27. package/src/tools/config.ts +1 -1
  28. package/src/tools/decision-log.ts +11 -2
  29. package/src/tools/defense-events.ts +295 -0
  30. package/src/tools/defense-store.ts +113 -0
  31. package/src/tools/experience-bank.ts +264 -0
  32. package/src/tools/experience-store.ts +160 -0
  33. package/src/tools/fix.ts +1 -1
  34. package/src/tools/history.ts +1 -1
  35. package/src/tools/prune.ts +1 -1
  36. package/src/tools/quality-gate.ts +193 -0
  37. package/src/tools/quality-store.ts +199 -0
  38. package/src/tools/review.ts +1 -1
  39. package/src/tools/transcript.ts +1 -1
  40. package/src/tools/triage.ts +1 -1
  41. package/src/types.ts +118 -0
package/lib/parse.js CHANGED
@@ -1427,4 +1427,97 @@ export function buildRuntimeStatusGuide() {
1427
1427
  '清理状态:让模型调用 iterate_prune(默认 dry-run,只报告不删除,显式 dryRun:false 才真正清理)。',
1428
1428
  ]
1429
1429
  return lines.join('\n')
1430
+ }
1431
+
1432
+ // ─── Runtime-observatory UI pure helpers ─────────────────────────────────────
1433
+
1434
+ /**
1435
+ * Filter the live reviewer-activity feed by activity type. An empty/unknown
1436
+ * `type` matches everything; entries are returned in their original (newest
1437
+ * first) order. Purely defensive: non-array input yields [].
1438
+ *
1439
+ * @param {unknown} entries
1440
+ * @param {unknown} type
1441
+ * @returns {Array<Record<string, unknown>>}
1442
+ */
1443
+ export function filterLiveEntries(entries, type) {
1444
+ const list = Array.isArray(entries) ? entries : []
1445
+ const t = typeof type === 'string' ? type.trim() : ''
1446
+ if (!t) return list.slice()
1447
+ return list.filter((e) => e && typeof e === 'object' && String(e.type ?? '') === t)
1448
+ }
1449
+
1450
+ /**
1451
+ * Filter decision-timeline entries by type / round / free-text search, then
1452
+ * sort newest first by timestamp string (timeline entries are not guaranteed
1453
+ * to be reverse-ordered in the manifest).
1454
+ *
1455
+ * - `type`: exact `entry.type` match when non-empty.
1456
+ * - `round`: exact `entry.round` string match when non-empty.
1457
+ * - `search`: case-insensitive substring over type + round + JSON data.
1458
+ *
1459
+ * @param {unknown} entries
1460
+ * @param {{ type?: unknown, round?: unknown, search?: unknown } | null | undefined} opts
1461
+ * @returns {Array<Record<string, unknown>>}
1462
+ */
1463
+ export function filterTimelineEntries(entries, opts) {
1464
+ const list = Array.isArray(entries) ? entries : []
1465
+ const o = opts && typeof opts === 'object' ? opts : {}
1466
+ const type = typeof o.type === 'string' ? o.type : ''
1467
+ const round = typeof o.round === 'string' ? o.round : ''
1468
+ const q = typeof o.search === 'string' ? o.search.trim().toLowerCase() : ''
1469
+ const filtered = list.filter((t) => {
1470
+ if (!t || typeof t !== 'object') return false
1471
+ if (type && String(t.type ?? '') !== type) return false
1472
+ if (round && String(t.round ?? '') !== round) return false
1473
+ if (q) {
1474
+ const hay = [String(t.type ?? ''), String(t.round ?? ''), JSON.stringify(t.data ?? {})].join(' ').toLowerCase()
1475
+ if (hay.indexOf(q) < 0) return false
1476
+ }
1477
+ return true
1478
+ })
1479
+ return filtered
1480
+ .slice()
1481
+ .sort((a, b) => String(b.timestamp ?? '').localeCompare(String(a.timestamp ?? '')))
1482
+ }
1483
+
1484
+ /**
1485
+ * Serialize the full observatory state (manifest + live feed) into a JSON
1486
+ * string the client can copy/export. Always includes an `exportedAt` stamp and
1487
+ * guards against non-serializable / oversized payloads by falling back to the
1488
+ * manifest only.
1489
+ *
1490
+ * @param {unknown} manifest
1491
+ * @param {unknown} live
1492
+ * @returns {string}
1493
+ */
1494
+ export function serializeObservatoryExport(manifest, live) {
1495
+ const payload = {
1496
+ exportedAt: new Date().toISOString(),
1497
+ manifest: manifest && typeof manifest === 'object' ? manifest : null,
1498
+ live: Array.isArray(live) ? live : [],
1499
+ }
1500
+ try {
1501
+ return JSON.stringify(payload, null, 2)
1502
+ } catch {
1503
+ // A cyclic / non-serializable manifest must not crash the copy action.
1504
+ return JSON.stringify({ exportedAt: payload.exportedAt, manifest: null, live: [] }, null, 2)
1505
+ }
1506
+ }
1507
+
1508
+ /**
1509
+ * Latest non-empty workflow phase name (plan / review / fix / validate /
1510
+ * report …) from the transcript manifest's phase list. The last recorded
1511
+ * phase is the one the run is currently in (or last finished).
1512
+ *
1513
+ * @param {unknown} phases
1514
+ * @returns {string}
1515
+ */
1516
+ export function latestPhase(phases) {
1517
+ const list = Array.isArray(phases) ? phases : []
1518
+ let latest = ''
1519
+ for (const p of list) {
1520
+ if (typeof p === 'string' && p.trim()) latest = p.trim()
1521
+ }
1522
+ return latest
1430
1523
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iterate-plugin",
3
- "version": "2.12.2",
4
- "description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus a dry-run pure-review mode with multi-round convergence and a meta-review that audits the report and emits a final review report.",
3
+ "version": "3.2.1",
4
+ "description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness with quality command center and experience bank (v3.2). 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.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -60,13 +60,14 @@
60
60
  "test:validate": "tsx --test test/validate.test.ts"
61
61
  },
62
62
  "dependencies": {
63
- "@deepseek-ai/cordis": "4.0.1",
64
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
63
+ "@deepseek-ai/cordis": "4.0.2",
64
+ "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
65
+ "@deepseek-ai/dsh-util-values": "0.1.2-rc.1",
65
66
  "js-yaml": "4.3.1"
66
67
  },
67
68
  "devDependencies": {
68
- "@deepseek-ai/dsh-jobs": "^0.1.1-rc.2",
69
- "@deepseek-ai/dsh-session": "0.1.1-rc.2",
69
+ "@deepseek-ai/dsh-jobs": "^0.1.2-rc.1",
70
+ "@deepseek-ai/dsh-session": "0.1.2-rc.1",
70
71
  "@types/js-yaml": "4.0.9",
71
72
  "@types/node": "22.15.0",
72
73
  "@types/react": "19.2.2",
@@ -65,11 +65,23 @@ export function decideApproval(
65
65
  execution: ToolExecutionLike,
66
66
  policy: 'ask' | 'deny' | 'allow',
67
67
  ): ApprovalDecision {
68
- const name = typeof execution?.name === 'string' ? execution.name : ''
68
+ // Defensive reads: a hostile/proxied execution object must degrade to "not
69
+ // our tool" (allow) rather than throw inside the gate.
70
+ let name = ''
71
+ try {
72
+ name = typeof execution?.name === 'string' ? execution.name : ''
73
+ } catch {
74
+ name = ''
75
+ }
69
76
  if (!name) return { kind: 'allow' }
70
77
  if (!DESTRUCTIVE_TOOLS.has(name)) return { kind: 'allow' }
71
78
 
72
- const rawArgs = execution.arguments
79
+ let rawArgs: unknown
80
+ try {
81
+ rawArgs = execution.arguments
82
+ } catch {
83
+ rawArgs = undefined
84
+ }
73
85
  const args: Record<string, unknown> =
74
86
  rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
75
87
  ? (rawArgs as Record<string, unknown>)