iterate-plugin 3.10.0 → 3.12.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 +14 -13
- package/README.zh-CN.md +12 -11
- package/dist/index.js +8 -5
- package/dist/report-render.js +33 -2
- package/dist/tools/preflight.js +239 -0
- package/dist/tools/report.js +20 -8
- package/lib/client.js +359 -9
- package/lib/parse.js +148 -2
- package/package.json +22 -20
- package/src/client/i18n.ts +43 -1
- package/src/client/index.ts +216 -4
- package/src/index.ts +8 -5
- package/src/report-render.ts +69 -3
- package/src/tools/preflight.ts +289 -0
- package/src/tools/report.ts +19 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.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",
|
|
@@ -52,7 +52,9 @@
|
|
|
52
52
|
"0.2.1-alpha.5": "compatible",
|
|
53
53
|
"0.2.1-rc.1": "compatible",
|
|
54
54
|
"0.2.1-rc.2": "compatible",
|
|
55
|
-
"0.2.1-rc.3": "compatible"
|
|
55
|
+
"0.2.1-rc.3": "compatible",
|
|
56
|
+
"0.2.1-rc.4": "compatible",
|
|
57
|
+
"0.2.2-alpha.2": "compatible"
|
|
56
58
|
}
|
|
57
59
|
},
|
|
58
60
|
"client": {
|
|
@@ -93,10 +95,10 @@
|
|
|
93
95
|
"js-yaml": "4.3.2"
|
|
94
96
|
},
|
|
95
97
|
"peerDependencies": {
|
|
96
|
-
"@deepseek-ai/cordis": "^4.0.5-alpha.2",
|
|
97
|
-
"@deepseek-ai/dsh-session": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.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 || 0.2.1-rc.3",
|
|
98
|
-
"@deepseek-ai/dsh-tools": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.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 || 0.2.1-rc.3",
|
|
99
|
-
"@deepseek-ai/dsh-util-values": "0.1.2-alpha.5 || 0.1.2-rc.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 || 0.2.1-rc.3"
|
|
98
|
+
"@deepseek-ai/cordis": "^4.0.5-alpha.2 || ~4.0.6-alpha.1",
|
|
99
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.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 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.2-alpha.2",
|
|
100
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.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 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.2-alpha.2",
|
|
101
|
+
"@deepseek-ai/dsh-util-values": "0.1.2-alpha.5 || 0.1.2-rc.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 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.2-alpha.2"
|
|
100
102
|
},
|
|
101
103
|
"peerDependenciesMeta": {
|
|
102
104
|
"@deepseek-ai/cordis": {
|
|
@@ -113,20 +115,20 @@
|
|
|
113
115
|
}
|
|
114
116
|
},
|
|
115
117
|
"devDependencies": {
|
|
116
|
-
"@deepseek-ai/cordis": "4.0.
|
|
117
|
-
"@deepseek-ai/dsh-agent": "0.2.
|
|
118
|
-
"@deepseek-ai/dsh-jobs": "0.2.
|
|
119
|
-
"@deepseek-ai/dsh-llm": "0.2.
|
|
120
|
-
"@deepseek-ai/dsh-ptc-runtime": "0.2.
|
|
121
|
-
"@deepseek-ai/dsh-sandbox": "0.2.
|
|
122
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.
|
|
123
|
-
"@deepseek-ai/dsh-scope": "0.2.
|
|
124
|
-
"@deepseek-ai/dsh-session": "0.2.
|
|
125
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.
|
|
126
|
-
"@deepseek-ai/dsh-tools": "0.2.
|
|
127
|
-
"@deepseek-ai/dsh-user-approval": "0.2.
|
|
128
|
-
"@deepseek-ai/dsh-util-values": "0.2.
|
|
129
|
-
"@deepseek-ai/dsh-working-directory": "0.2.
|
|
118
|
+
"@deepseek-ai/cordis": "4.0.6-alpha.1",
|
|
119
|
+
"@deepseek-ai/dsh-agent": "0.2.2-alpha.2",
|
|
120
|
+
"@deepseek-ai/dsh-jobs": "0.2.2-alpha.2",
|
|
121
|
+
"@deepseek-ai/dsh-llm": "0.2.2-alpha.2",
|
|
122
|
+
"@deepseek-ai/dsh-ptc-runtime": "0.2.2-alpha.2",
|
|
123
|
+
"@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2",
|
|
124
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.2-alpha.2",
|
|
125
|
+
"@deepseek-ai/dsh-scope": "0.2.2-alpha.2",
|
|
126
|
+
"@deepseek-ai/dsh-session": "0.2.2-alpha.2",
|
|
127
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2",
|
|
128
|
+
"@deepseek-ai/dsh-tools": "0.2.2-alpha.2",
|
|
129
|
+
"@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2",
|
|
130
|
+
"@deepseek-ai/dsh-util-values": "0.2.2-alpha.2",
|
|
131
|
+
"@deepseek-ai/dsh-working-directory": "0.2.2-alpha.2",
|
|
130
132
|
"@types/js-yaml": "4.0.9",
|
|
131
133
|
"@types/node": "22.15.0",
|
|
132
134
|
"@types/react": "19.2.2",
|
package/src/client/i18n.ts
CHANGED
|
@@ -259,6 +259,9 @@ export const EN: Record<string, string> = {
|
|
|
259
259
|
'导出 SARIF': 'Export SARIF',
|
|
260
260
|
'导出 SARIF 2.1.0(供 CI / GitHub Code Scanning 消费)':
|
|
261
261
|
'Export SARIF 2.1.0 for CI / GitHub Code Scanning',
|
|
262
|
+
'导出 JSON 报告': 'Export JSON report',
|
|
263
|
+
'导出完整运行模型的 JSON(供 CI 仪表盘 / PR 机器人消费,无损格式)':
|
|
264
|
+
'Export the complete run model as JSON (lossless, for CI dashboards / PR bots)',
|
|
262
265
|
'观测台页签(快捷键 1-9 / 0 / -)': 'Observatory tabs (keys 1-9 / 0 / -)',
|
|
263
266
|
'{a0}(按 {a1} 切换)': '{a0} (press {a1})',
|
|
264
267
|
'{a0}(按 - 切换)': '{a0} (press -)',
|
|
@@ -443,11 +446,50 @@ export const EN: Record<string, string> = {
|
|
|
443
446
|
'防线:{a0}': 'Defense: {a0}',
|
|
444
447
|
没有匹配该类型的防御事件: 'No defense events of this type',
|
|
445
448
|
|
|
449
|
+
// ── F11 preflight ────────────────────────────────────────────────────────
|
|
450
|
+
开跑预检: 'Preflight',
|
|
451
|
+
待预检: 'Not run yet',
|
|
452
|
+
未就绪: 'Not ready',
|
|
453
|
+
'复制 iterate_preflight 预检指令(含 JSON 参数)':
|
|
454
|
+
'Copy the iterate_preflight readiness instruction (JSON arguments included)',
|
|
455
|
+
运行预检: 'Run preflight',
|
|
456
|
+
开跑预检清单: 'Preflight checklist',
|
|
457
|
+
预检结果: 'Preflight result',
|
|
458
|
+
'开跑前先预检:确认验证命令、Git、工作区、陈旧门禁与中断断点都已就绪,避免开跑后才发现环境问题。':
|
|
459
|
+
'Run preflight first: confirm validation commands, git, the working tree, a stale gate and an interrupted checkpoint are ready, so you do not discover an environment problem mid-run.',
|
|
460
|
+
复制预检指令: 'Copy preflight instruction',
|
|
461
|
+
验证命令: 'Validation commands',
|
|
462
|
+
'已配置 {a0} 条验证命令': '{a0} validation command(s) configured',
|
|
463
|
+
'未配置 validation.commands —— 本轮无法证明修复,属阻断项':
|
|
464
|
+
'validation.commands is not configured — the run cannot prove fixes (blocking)',
|
|
465
|
+
'Git 仓库': 'Git repository',
|
|
466
|
+
'已检测到 Git 仓库': 'Git repository detected',
|
|
467
|
+
'不是 Git 仓库 —— changed-only 范围与 iterate_diff 需要它':
|
|
468
|
+
'Not a git repository — changed-only scope and iterate_diff need one',
|
|
469
|
+
工作区: 'Working tree',
|
|
470
|
+
'未提交改动:未知': 'Uncommitted changes: unknown',
|
|
471
|
+
'{a0} 个未提交/未跟踪文件 —— 建议先 commit 或 stash':
|
|
472
|
+
'{a0} uncommitted/untracked file(s) — commit or stash first',
|
|
473
|
+
工作区干净: 'Working tree clean',
|
|
474
|
+
陈旧门禁: 'Stale gate',
|
|
475
|
+
'磁盘上残留 FAIL 质量门禁 —— 开新轮前先清除':
|
|
476
|
+
'A stale FAIL quality gate is persisted — clear it before a fresh run',
|
|
477
|
+
'质量门禁:{a0}': 'Quality gate: {a0}',
|
|
478
|
+
无持久化质量门禁: 'No persisted quality gate',
|
|
479
|
+
中断断点: 'Interrupted checkpoint',
|
|
480
|
+
'存在中断断点(已恢复 {a0} 次)—— 恢复或清除后再开跑':
|
|
481
|
+
'An interrupted checkpoint is waiting (resumed {a0}x) — resume or clear it first',
|
|
482
|
+
无中断断点: 'No interrupted checkpoint',
|
|
483
|
+
配置验证命令: 'Configure validation',
|
|
484
|
+
恢复断点: 'Resume checkpoint',
|
|
485
|
+
'阻断 {a0} · 警告 {a1}': 'Blocking {a0} · warnings {a1}',
|
|
486
|
+
'预检失败:{a0}': 'Preflight failed: {a0}',
|
|
487
|
+
'预检只读,不会修改配置或文件。': 'Preflight is read-only; it never modifies config or files.',
|
|
488
|
+
|
|
446
489
|
// ── copy feedback ────────────────────────────────────────────────────────
|
|
447
490
|
已复制: 'Copied',
|
|
448
491
|
复制失败: 'Copy failed',
|
|
449
492
|
}
|
|
450
|
-
|
|
451
493
|
let current: Lang = 'zh'
|
|
452
494
|
let storage: LangStorage | null = null
|
|
453
495
|
const listeners = new Set<(lang: Lang) => void>()
|
package/src/client/index.ts
CHANGED
|
@@ -75,6 +75,8 @@ import {
|
|
|
75
75
|
scanSessionForQualityGate,
|
|
76
76
|
scanSessionForExperienceBank,
|
|
77
77
|
scanSessionForDefenseEvents,
|
|
78
|
+
scanSessionForPreflight,
|
|
79
|
+
buildPreflightInstruction,
|
|
78
80
|
filterLiveEntries,
|
|
79
81
|
filterTimelineEntries,
|
|
80
82
|
serializeObservatoryExport,
|
|
@@ -113,6 +115,7 @@ import {
|
|
|
113
115
|
import {
|
|
114
116
|
renderRunReportMarkdown, // Markdown run report (PR-pasteable)
|
|
115
117
|
renderRunReportSarif, // SARIF 2.1.0 log (CI / Code Scanning)
|
|
118
|
+
renderRunReportJson, // full normalized run model (CI dashboards / PR bots)
|
|
116
119
|
runReportFilename, // deterministic download filename
|
|
117
120
|
} from '../report-render.ts'
|
|
118
121
|
import {
|
|
@@ -335,6 +338,24 @@ interface ObsQualityGate {
|
|
|
335
338
|
dimensions?: ObsQualityGateDimension[]
|
|
336
339
|
}
|
|
337
340
|
|
|
341
|
+
/** One readiness check from `iterate_preflight` (F11). `detail` is structured
|
|
342
|
+
* facts the panel localizes into a sentence. */
|
|
343
|
+
interface ObsPreflightCheck {
|
|
344
|
+
id?: string
|
|
345
|
+
status?: string
|
|
346
|
+
detail?: Record<string, unknown>
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Latest `iterate_preflight` report scanned out of the session stream. */
|
|
350
|
+
interface ObsPreflight {
|
|
351
|
+
ok?: boolean
|
|
352
|
+
ready?: boolean
|
|
353
|
+
blocking?: number
|
|
354
|
+
warnings?: number
|
|
355
|
+
error?: string | null
|
|
356
|
+
checks?: ObsPreflightCheck[]
|
|
357
|
+
}
|
|
358
|
+
|
|
338
359
|
interface ObsExperienceEntry {
|
|
339
360
|
id?: string
|
|
340
361
|
timestamp?: string
|
|
@@ -582,6 +603,7 @@ const ITERATE_CSS = `
|
|
|
582
603
|
.iterate-gate[data-status="pass"] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
|
|
583
604
|
.iterate-gate[data-status="fail"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
|
|
584
605
|
.iterate-gate[data-status="pending"] { color: var(--dsw-alias-label-secondary); background: color-mix(in srgb, var(--dsw-alias-label-secondary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-label-secondary) 28%, transparent); }
|
|
606
|
+
.iterate-gate[data-status="warn"] { color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 28%, transparent); }
|
|
585
607
|
|
|
586
608
|
/* v3.0: Experience bank hit highlight */
|
|
587
609
|
.iterate-exp-hit { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); font-size: 11px; color: var(--dsw-alias-state-success-primary); }
|
|
@@ -2178,6 +2200,8 @@ const OBS_TABS = [
|
|
|
2178
2200
|
{ key: 'f8', label: { zh: '质量门禁', en: 'Quality gate' }, hotkey: '8' },
|
|
2179
2201
|
{ key: 'f9', label: { zh: '经验银行', en: 'Experience bank' }, hotkey: '9' },
|
|
2180
2202
|
{ key: 'f10', label: { zh: '防御事件', en: 'Defense events' }, hotkey: '0' },
|
|
2203
|
+
// v3.12: pre-run readiness checklist (validation/git/dirty-tree/stale-gate/checkpoint)
|
|
2204
|
+
{ key: 'f11', label: { zh: '开跑预检', en: 'Preflight' }, hotkey: '' },
|
|
2181
2205
|
] as const
|
|
2182
2206
|
|
|
2183
2207
|
/** Digit → tab id. `1-9` map to F1–F9, `0` to F10, `-` back to the live feed
|
|
@@ -2266,6 +2290,12 @@ function latestDefenseEvents(session: unknown): ObsDefenseResult | null {
|
|
|
2266
2290
|
return scanSessionForDefenseEvents(session) as ObsDefenseResult | null
|
|
2267
2291
|
}
|
|
2268
2292
|
|
|
2293
|
+
/** Find the latest preflight report inside a session snapshot. */
|
|
2294
|
+
function latestPreflight(session: unknown): ObsPreflight | null {
|
|
2295
|
+
if (!session) return null
|
|
2296
|
+
return scanSessionForPreflight(session) as ObsPreflight | null
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2269
2299
|
/** Build a human-pasteable `iterate_fix` instruction for one finding.
|
|
2270
2300
|
* Gap #10: delegates to the shared builder so the payload always names the
|
|
2271
2301
|
* four required `iterate_fix` params (file / content / finding / round) and
|
|
@@ -2294,6 +2324,7 @@ function ObservatoryPanel(props: SlotProps) {
|
|
|
2294
2324
|
const qualityGate = latestQualityGate(session)
|
|
2295
2325
|
const experienceBank = latestExperienceBank(session)
|
|
2296
2326
|
const defenseEvents = latestDefenseEvents(session)
|
|
2327
|
+
const preflight = latestPreflight(session)
|
|
2297
2328
|
|
|
2298
2329
|
const [open, setOpen] = React.useState(false)
|
|
2299
2330
|
const [tab, setTab] = React.useState('live')
|
|
@@ -2419,12 +2450,14 @@ function ObservatoryPanel(props: SlotProps) {
|
|
|
2419
2450
|
* SARIF 2.1.0 (CI / GitHub Code Scanning). Rendered from the SAME shared
|
|
2420
2451
|
* data model as the `iterate_report` tool, so the button and the tool can
|
|
2421
2452
|
* never disagree. Download-first with a copy fallback, like `exportObservatory`. */
|
|
2422
|
-
const exportReport = (format: 'markdown' | 'sarif') => {
|
|
2453
|
+
const exportReport = (format: 'markdown' | 'sarif' | 'json') => {
|
|
2423
2454
|
if (!manifest) return
|
|
2424
2455
|
const extra = { qualityGate, experienceBank, defenseEvents }
|
|
2425
2456
|
const content = format === 'sarif'
|
|
2426
2457
|
? JSON.stringify(renderRunReportSarif(manifest, extra), null, 2)
|
|
2427
|
-
:
|
|
2458
|
+
: format === 'json'
|
|
2459
|
+
? JSON.stringify(renderRunReportJson(manifest, extra), null, 2)
|
|
2460
|
+
: renderRunReportMarkdown(manifest, extra, { lang: getLang() })
|
|
2428
2461
|
const filename = runReportFilename(format)
|
|
2429
2462
|
const key = `report-${format}`
|
|
2430
2463
|
const flash = () => {
|
|
@@ -3353,6 +3386,176 @@ function ObservatoryPanel(props: SlotProps) {
|
|
|
3353
3386
|
)
|
|
3354
3387
|
}
|
|
3355
3388
|
|
|
3389
|
+
// ── F11: Preflight readiness ──────────────────────────────────────────────
|
|
3390
|
+
// v3.12: renders the latest iterate_preflight checklist scanned from the
|
|
3391
|
+
// session stream. Each warn/fail row offers the exact fix instruction
|
|
3392
|
+
// (configure validation, clear a stale gate, resume an interrupted
|
|
3393
|
+
// checkpoint) so the environment is repaired BEFORE a round is spent on it.
|
|
3394
|
+
const renderPreflight = () => {
|
|
3395
|
+
const preflightInstruction = buildPreflightInstruction()
|
|
3396
|
+
const report = preflight
|
|
3397
|
+
const hasReport = report !== null && Array.isArray(report.checks)
|
|
3398
|
+
const ready = report !== null && report.ready === true
|
|
3399
|
+
const blocking = Number(report && report.blocking != null ? report.blocking : 0)
|
|
3400
|
+
const warnings = Number(report && report.warnings != null ? report.warnings : 0)
|
|
3401
|
+
const statusLabel = !hasReport ? t('待预检') : ready ? t('就绪') : t('未就绪')
|
|
3402
|
+
const statusTone = !hasReport ? 'pending' : ready ? 'pass' : 'fail'
|
|
3403
|
+
const headerBar = React.createElement('div', { className: 'iterate-obs-bar', style: { marginBottom: 8 } },
|
|
3404
|
+
React.createElement('b', {}, t('开跑预检')),
|
|
3405
|
+
React.createElement('span', { className: 'iterate-gate', 'data-status': statusTone }, statusLabel),
|
|
3406
|
+
React.createElement('button', {
|
|
3407
|
+
className: 'iterate-btn', 'data-primary': '', 'data-copied': copiedKey === 'preflight' ? '' : undefined,
|
|
3408
|
+
onClick: () => copyInstruction('preflight', preflightInstruction),
|
|
3409
|
+
title: t('复制 iterate_preflight 预检指令(含 JSON 参数)'),
|
|
3410
|
+
}, copiedKey === 'preflight' ? t('已复制') : t('运行预检')),
|
|
3411
|
+
)
|
|
3412
|
+
|
|
3413
|
+
if (!hasReport) {
|
|
3414
|
+
return React.createElement('div', {},
|
|
3415
|
+
headerBar,
|
|
3416
|
+
React.createElement('div', { className: 'iterate-obs-block' },
|
|
3417
|
+
React.createElement('div', { className: 'iterate-obs-block-head' },
|
|
3418
|
+
React.createElement('span', {}, t('开跑预检清单')),
|
|
3419
|
+
React.createElement('span', { className: 'iterate-obs-badge' }, 'v3.12'),
|
|
3420
|
+
),
|
|
3421
|
+
React.createElement('div', { className: 'iterate-obs-block-body' },
|
|
3422
|
+
React.createElement('div', { className: 'iterate-obs-msg' },
|
|
3423
|
+
// Gap #4: separate "pulled and empty" from "never pulled".
|
|
3424
|
+
diskEmptyStateText(toolCalledInSession(session, 'iterate_preflight'), t('预检结果'), getLang()),
|
|
3425
|
+
),
|
|
3426
|
+
React.createElement('div', { className: 'iterate-obs-msg', style: { marginTop: 6 } },
|
|
3427
|
+
t('开跑前先预检:确认验证命令、Git、工作区、陈旧门禁与中断断点都已就绪,避免开跑后才发现环境问题。'),
|
|
3428
|
+
),
|
|
3429
|
+
React.createElement('div', { className: 'iterate-obs-bar', style: { marginTop: 8 } },
|
|
3430
|
+
React.createElement('button', {
|
|
3431
|
+
className: 'iterate-btn', 'data-copied': copiedKey === 'preflight-instr' ? '' : undefined,
|
|
3432
|
+
onClick: () => copyInstruction('preflight-instr', preflightInstruction),
|
|
3433
|
+
title: t('复制预检指令'),
|
|
3434
|
+
}, copiedKey === 'preflight-instr' ? t('已复制') : t('复制预检指令')),
|
|
3435
|
+
),
|
|
3436
|
+
),
|
|
3437
|
+
),
|
|
3438
|
+
)
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
// A tool-level failure (bad path, root refused) carries no usable checks —
|
|
3442
|
+
// surface the error instead of a misleading "ready, 0 blocking" summary.
|
|
3443
|
+
if (report && report.ok === false) {
|
|
3444
|
+
return React.createElement('div', {},
|
|
3445
|
+
headerBar,
|
|
3446
|
+
React.createElement('div', { className: 'iterate-obs-block' },
|
|
3447
|
+
React.createElement('div', { className: 'iterate-obs-block-head' },
|
|
3448
|
+
React.createElement('span', {}, t('开跑预检清单')),
|
|
3449
|
+
React.createElement('span', { className: 'iterate-obs-badge' }, 'v3.12'),
|
|
3450
|
+
),
|
|
3451
|
+
React.createElement('div', { className: 'iterate-obs-block-body' },
|
|
3452
|
+
React.createElement('div', { className: 'iterate-obs-msg' },
|
|
3453
|
+
tf('预检失败:{a0}', { a0: String(report.error || '') }),
|
|
3454
|
+
),
|
|
3455
|
+
),
|
|
3456
|
+
),
|
|
3457
|
+
)
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
// Localized label + detail per check id. Detail is structured facts from
|
|
3461
|
+
// the tool, so the panel — not the tool — owns the wording/language.
|
|
3462
|
+
const CHECK_META: Record<string, { label: string; detail: (d: Record<string, unknown>) => string }> = {
|
|
3463
|
+
validation: {
|
|
3464
|
+
label: t('验证命令'),
|
|
3465
|
+
detail: (d) => {
|
|
3466
|
+
const n = Number(d.commandCount ?? 0)
|
|
3467
|
+
return n > 0
|
|
3468
|
+
? tf('已配置 {a0} 条验证命令', { a0: n })
|
|
3469
|
+
: t('未配置 validation.commands —— 本轮无法证明修复,属阻断项')
|
|
3470
|
+
},
|
|
3471
|
+
},
|
|
3472
|
+
git: {
|
|
3473
|
+
label: t('Git 仓库'),
|
|
3474
|
+
detail: (d) => (d.isRepo === true ? t('已检测到 Git 仓库') : t('不是 Git 仓库 —— changed-only 范围与 iterate_diff 需要它')),
|
|
3475
|
+
},
|
|
3476
|
+
'dirty-tree': {
|
|
3477
|
+
label: t('工作区'),
|
|
3478
|
+
detail: (d) => {
|
|
3479
|
+
const n = d.changedFiles
|
|
3480
|
+
if (n === null || n === undefined) return t('未提交改动:未知')
|
|
3481
|
+
const num = Number(n)
|
|
3482
|
+
return num > 0
|
|
3483
|
+
? tf('{a0} 个未提交/未跟踪文件 —— 建议先 commit 或 stash', { a0: num })
|
|
3484
|
+
: t('工作区干净')
|
|
3485
|
+
},
|
|
3486
|
+
},
|
|
3487
|
+
'stale-gate': {
|
|
3488
|
+
label: t('陈旧门禁'),
|
|
3489
|
+
detail: (d) => {
|
|
3490
|
+
const s = d.overallStatus
|
|
3491
|
+
if (s === 'fail') return t('磁盘上残留 FAIL 质量门禁 —— 开新轮前先清除')
|
|
3492
|
+
return s ? tf('质量门禁:{a0}', { a0: String(s) }) : t('无持久化质量门禁')
|
|
3493
|
+
},
|
|
3494
|
+
},
|
|
3495
|
+
'interrupted-checkpoint': {
|
|
3496
|
+
label: t('中断断点'),
|
|
3497
|
+
detail: (d) => {
|
|
3498
|
+
if (d.interrupted === true) {
|
|
3499
|
+
const rc = Number(d.resumeCount ?? 0)
|
|
3500
|
+
return tf('存在中断断点(已恢复 {a0} 次)—— 恢复或清除后再开跑', { a0: rc })
|
|
3501
|
+
}
|
|
3502
|
+
return t('无中断断点')
|
|
3503
|
+
},
|
|
3504
|
+
},
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
// Repair action per check id; null = guidance only (no tool can fix it).
|
|
3508
|
+
const fixFor = (id: string): { key: string; label: string; instruction: string } | null => {
|
|
3509
|
+
switch (id) {
|
|
3510
|
+
case 'validation':
|
|
3511
|
+
return { key: 'preflight-fix-validation', label: t('配置验证命令'), instruction: buildConfigFieldInstruction('validation.commands') }
|
|
3512
|
+
case 'stale-gate':
|
|
3513
|
+
return { key: 'preflight-fix-gate', label: t('清除门禁'), instruction: buildQualityGateClearInstruction() }
|
|
3514
|
+
case 'interrupted-checkpoint':
|
|
3515
|
+
return { key: 'preflight-fix-checkpoint', label: t('恢复断点'), instruction: buildCheckpointResumeInstruction() }
|
|
3516
|
+
default:
|
|
3517
|
+
return null
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
const rows = (report.checks || []).map((c, i) => {
|
|
3522
|
+
const id = String(c.id || '')
|
|
3523
|
+
const status = String(c.status || 'warn')
|
|
3524
|
+
const meta = CHECK_META[id]
|
|
3525
|
+
const label = meta ? meta.label : id || '?'
|
|
3526
|
+
const detail = meta ? meta.detail(c.detail || {}) : ''
|
|
3527
|
+
const fix = status !== 'pass' ? fixFor(id) : null
|
|
3528
|
+
return React.createElement('div', { key: `pf-${i}`, className: 'iterate-obs-row' },
|
|
3529
|
+
React.createElement('span', { className: 'iterate-gate', 'data-status': status }, status.toUpperCase()),
|
|
3530
|
+
React.createElement('b', {}, label),
|
|
3531
|
+
React.createElement('span', { className: 'iterate-obs-msg' }, detail),
|
|
3532
|
+
fix
|
|
3533
|
+
? React.createElement('button', {
|
|
3534
|
+
className: 'iterate-btn', 'data-copied': copiedKey === fix.key ? '' : undefined,
|
|
3535
|
+
onClick: () => copyInstruction(fix.key, fix.instruction),
|
|
3536
|
+
title: fix.instruction,
|
|
3537
|
+
}, copiedKey === fix.key ? t('已复制') : fix.label)
|
|
3538
|
+
: null,
|
|
3539
|
+
)
|
|
3540
|
+
})
|
|
3541
|
+
|
|
3542
|
+
return React.createElement('div', {},
|
|
3543
|
+
headerBar,
|
|
3544
|
+
React.createElement('div', { className: 'iterate-obs-block' },
|
|
3545
|
+
React.createElement('div', { className: 'iterate-obs-block-head' },
|
|
3546
|
+
React.createElement('span', {}, t('开跑预检清单')),
|
|
3547
|
+
React.createElement('span', { className: 'iterate-obs-head-meta' },
|
|
3548
|
+
tf('阻断 {a0} · 警告 {a1}', { a0: blocking, a1: warnings }),
|
|
3549
|
+
),
|
|
3550
|
+
),
|
|
3551
|
+
React.createElement('div', { className: 'iterate-obs-block-body' },
|
|
3552
|
+
React.createElement('div', { className: 'iterate-obs-msg' }, t('预检只读,不会修改配置或文件。')),
|
|
3553
|
+
...rows,
|
|
3554
|
+
),
|
|
3555
|
+
),
|
|
3556
|
+
)
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3356
3559
|
// ── Live: real-time secondary-subagent activity stream (newest first) ─────
|
|
3357
3560
|
const renderLive = () => {
|
|
3358
3561
|
const liveEntries = manifest.live || []
|
|
@@ -3437,6 +3640,7 @@ function ObservatoryPanel(props: SlotProps) {
|
|
|
3437
3640
|
case 'f8': return renderQualityGate()
|
|
3438
3641
|
case 'f9': return renderExperienceBank()
|
|
3439
3642
|
case 'f10': return renderDefenseEvents()
|
|
3643
|
+
case 'f11': return renderPreflight()
|
|
3440
3644
|
default: return null
|
|
3441
3645
|
}
|
|
3442
3646
|
}
|
|
@@ -3481,8 +3685,8 @@ function ObservatoryPanel(props: SlotProps) {
|
|
|
3481
3685
|
title: t('将观测台全部数据导出为 JSON(优先下载,失败则复制)'),
|
|
3482
3686
|
}, copiedKey === 'export' ? t('已导出') : t('导出 JSON')),
|
|
3483
3687
|
// v3.9: portable report export — Markdown for a PR body, SARIF for
|
|
3484
|
-
// CI / GitHub Code Scanning
|
|
3485
|
-
// the `iterate_report` tool.
|
|
3688
|
+
// CI / GitHub Code Scanning, JSON for CI dashboards / PR bots.
|
|
3689
|
+
// Rendered from the same data model as the `iterate_report` tool.
|
|
3486
3690
|
React.createElement('button', {
|
|
3487
3691
|
className: 'iterate-btn', 'data-copied': copiedKey === 'report-markdown' ? '' : undefined,
|
|
3488
3692
|
onClick: () => exportReport('markdown'),
|
|
@@ -3493,6 +3697,14 @@ function ObservatoryPanel(props: SlotProps) {
|
|
|
3493
3697
|
onClick: () => exportReport('sarif'),
|
|
3494
3698
|
title: t('导出 SARIF 2.1.0(供 CI / GitHub Code Scanning 消费)'),
|
|
3495
3699
|
}, copiedKey === 'report-sarif' ? t('已导出') : t('导出 SARIF')),
|
|
3700
|
+
// v3.11: lossless JSON export — the complete normalized run model
|
|
3701
|
+
// (findings + fixes + validations + quality gate + experience bank
|
|
3702
|
+
// + defense events) for CI dashboards and PR bots.
|
|
3703
|
+
React.createElement('button', {
|
|
3704
|
+
className: 'iterate-btn', 'data-copied': copiedKey === 'report-json' ? '' : undefined,
|
|
3705
|
+
onClick: () => exportReport('json'),
|
|
3706
|
+
title: t('导出完整运行模型的 JSON(供 CI 仪表盘 / PR 机器人消费,无损格式)'),
|
|
3707
|
+
}, copiedKey === 'report-json' ? t('已导出') : t('导出 JSON 报告')),
|
|
3496
3708
|
// Gap #4: `.iterate/` artifacts are unreachable from the browser;
|
|
3497
3709
|
// this copies the one instruction that makes the model echo them
|
|
3498
3710
|
// into the session stream (which is what every panel scans).
|
package/src/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ import { registerExperienceBankTool } from './tools/experience-bank.ts'
|
|
|
46
46
|
import { registerQualityGateTool } from './tools/quality-gate.ts'
|
|
47
47
|
import { registerDefenseEventsTool } from './tools/defense-events.ts'
|
|
48
48
|
import { registerReportTool } from './tools/report.ts'
|
|
49
|
+
import { registerPreflightTool } from './tools/preflight.ts'
|
|
49
50
|
import { registerSessionHooks } from './session-hooks.ts'
|
|
50
51
|
import { registerLiveCapture } from './live.ts'
|
|
51
52
|
import { installWorkingDirectoryContext } from './working-directory.ts'
|
|
@@ -71,11 +72,11 @@ export function apply(ctx: Context): void {
|
|
|
71
72
|
registerSessionHooks(ctx)
|
|
72
73
|
registerLiveCapture(ctx)
|
|
73
74
|
|
|
74
|
-
// 1. Register the
|
|
75
|
-
// isolated: one failing tool must not take down the other
|
|
76
|
-
// above, or the prompt below (a partial plugin that still
|
|
77
|
-
// teaches the workflow beats an aborted `apply`). Failures are
|
|
78
|
-
// never swallowed silently.
|
|
75
|
+
// 1. Register the 19 tools (14 original + 3 v3.0 + report + preflight). Each
|
|
76
|
+
// registration is isolated: one failing tool must not take down the other
|
|
77
|
+
// 18, the gate above, or the prompt below (a partial plugin that still
|
|
78
|
+
// gates and teaches the workflow beats an aborted `apply`). Failures are
|
|
79
|
+
// logged, never swallowed silently.
|
|
79
80
|
const toolRegistrations: Array<[string, () => void]> = [
|
|
80
81
|
['iterate_config', () => registerConfigTool(ctx)],
|
|
81
82
|
['iterate_validate', () => registerValidateTool(ctx)],
|
|
@@ -97,6 +98,8 @@ export function apply(ctx: Context): void {
|
|
|
97
98
|
['iterate_defense_events', () => registerDefenseEventsTool(ctx)],
|
|
98
99
|
// v3.9: PR-ready / CI-ready report export
|
|
99
100
|
['iterate_report', () => registerReportTool(ctx)],
|
|
101
|
+
// v3.12: pre-run readiness check (validation/git/stale-gate/interrupted)
|
|
102
|
+
['iterate_preflight', () => registerPreflightTool(ctx)],
|
|
100
103
|
]
|
|
101
104
|
for (const [toolName, register] of toolRegistrations) {
|
|
102
105
|
try {
|
package/src/report-render.ts
CHANGED
|
@@ -20,10 +20,15 @@
|
|
|
20
20
|
* - **sarif** — SARIF 2.1.0 for CI / GitHub Code Scanning (ruleId =
|
|
21
21
|
* review dimension, severity → error | warning | note, file+line
|
|
22
22
|
* locations, suggested fix attached).
|
|
23
|
+
* - **json** — the complete normalized run model (see {@link RunReportData})
|
|
24
|
+
* as a machine-readable JSON document. This is the ONLY format that carries
|
|
25
|
+
* the full model (findings + fixes + validations + quality gate + experience
|
|
26
|
+
* bank + defense events) without lossy projection, so CI dashboards and PR
|
|
27
|
+
* bots can consume the exact same facts the human report shows.
|
|
23
28
|
*/
|
|
24
29
|
|
|
25
30
|
/** Report formats accepted by the tool and the export buttons. */
|
|
26
|
-
export const REPORT_FORMATS = ['markdown', 'sarif'] as const
|
|
31
|
+
export const REPORT_FORMATS = ['markdown', 'sarif', 'json'] as const
|
|
27
32
|
|
|
28
33
|
/** Severity ordering (lowest index = most severe). */
|
|
29
34
|
const SEVERITY_ORDER = ['critical', 'high', 'medium', 'low'] as const
|
|
@@ -563,6 +568,66 @@ export function renderRunReportSarif(
|
|
|
563
568
|
}
|
|
564
569
|
}
|
|
565
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Render the complete normalized run model as a JSON document.
|
|
573
|
+
*
|
|
574
|
+
* This is the lossless format: unlike Markdown (human projection) and SARIF
|
|
575
|
+
* (findings-only code-scanning log), the JSON carries EVERY fact the run
|
|
576
|
+
* recorded — findings, fixes, validations, convergence, the quality gate, the
|
|
577
|
+
* experience bank, and defense events — as structured data. CI dashboards and
|
|
578
|
+
* PR bots can consume it without re-parsing a human report. A missing or
|
|
579
|
+
* partial transcript still yields a valid document (empty arrays, null stores),
|
|
580
|
+
* so an export never fails because a run was interrupted.
|
|
581
|
+
*/
|
|
582
|
+
export interface RunReportJson {
|
|
583
|
+
$schema: 'iterate-run-report/1'
|
|
584
|
+
project: string
|
|
585
|
+
goal: string
|
|
586
|
+
mode: string | null
|
|
587
|
+
taskMode: string | null
|
|
588
|
+
round: number
|
|
589
|
+
maxRounds: number
|
|
590
|
+
active: boolean
|
|
591
|
+
stoppedReason: string | null
|
|
592
|
+
updatedAt: string
|
|
593
|
+
convergence: number[]
|
|
594
|
+
severityCounts: { critical: number; high: number; medium: number; low: number }
|
|
595
|
+
findings: ReportFinding[]
|
|
596
|
+
fixes: ReportFix[]
|
|
597
|
+
validations: ReportValidation[]
|
|
598
|
+
qualityGate: Record<string, unknown> | null
|
|
599
|
+
experienceBank: Record<string, unknown> | null
|
|
600
|
+
defenseEvents: Record<string, unknown> | null
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Render a JSON document from the same normalized data. */
|
|
604
|
+
export function renderRunReportJson(
|
|
605
|
+
manifest: unknown,
|
|
606
|
+
extra?: { qualityGate?: unknown; experienceBank?: unknown; defenseEvents?: unknown } | null,
|
|
607
|
+
): RunReportJson {
|
|
608
|
+
const data = buildRunReportData(manifest, extra)
|
|
609
|
+
return {
|
|
610
|
+
$schema: 'iterate-run-report/1',
|
|
611
|
+
project: data.project,
|
|
612
|
+
goal: data.goal,
|
|
613
|
+
mode: data.mode,
|
|
614
|
+
taskMode: data.taskMode,
|
|
615
|
+
round: data.round,
|
|
616
|
+
maxRounds: data.maxRounds,
|
|
617
|
+
active: data.active,
|
|
618
|
+
stoppedReason: data.stoppedReason,
|
|
619
|
+
updatedAt: data.updatedAt,
|
|
620
|
+
convergence: data.convergence,
|
|
621
|
+
severityCounts: data.severityCounts,
|
|
622
|
+
findings: data.findings,
|
|
623
|
+
fixes: data.fixes,
|
|
624
|
+
validations: data.validations,
|
|
625
|
+
qualityGate: data.qualityGate,
|
|
626
|
+
experienceBank: data.experienceBank,
|
|
627
|
+
defenseEvents: data.defenseEvents,
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
566
631
|
/**
|
|
567
632
|
* Render a report in the requested format. Unknown formats fall back to
|
|
568
633
|
* Markdown (the tool's default).
|
|
@@ -572,8 +637,9 @@ export function renderRunReport(
|
|
|
572
637
|
manifest: unknown,
|
|
573
638
|
extra?: { qualityGate?: unknown; experienceBank?: unknown; defenseEvents?: unknown } | null,
|
|
574
639
|
opts?: ReportRenderOptions,
|
|
575
|
-
): string | SarifLog {
|
|
640
|
+
): string | SarifLog | RunReportJson {
|
|
576
641
|
if (format === 'sarif') return renderRunReportSarif(manifest, extra, opts)
|
|
642
|
+
if (format === 'json') return renderRunReportJson(manifest, extra)
|
|
577
643
|
return renderRunReportMarkdown(manifest, extra, opts)
|
|
578
644
|
}
|
|
579
645
|
|
|
@@ -582,7 +648,7 @@ export function renderRunReport(
|
|
|
582
648
|
* `iterate-report-2026-10-18T01-02-03-000Z.md`.
|
|
583
649
|
*/
|
|
584
650
|
export function runReportFilename(format: string, opts?: { now?: string }): string {
|
|
585
|
-
const ext = format === 'sarif' ? 'sarif' : 'md'
|
|
651
|
+
const ext = format === 'sarif' ? 'sarif' : format === 'json' ? 'json' : 'md'
|
|
586
652
|
const stamp =
|
|
587
653
|
opts?.now && opts.now ? opts.now.replace(/[:.]/g, '-') : new Date().toISOString().replace(/[:.]/g, '-')
|
|
588
654
|
return `iterate-report-${stamp}.${ext}`
|