iterate-plugin 3.9.2 → 3.11.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.
@@ -113,6 +113,7 @@ import {
113
113
  import {
114
114
  renderRunReportMarkdown, // Markdown run report (PR-pasteable)
115
115
  renderRunReportSarif, // SARIF 2.1.0 log (CI / Code Scanning)
116
+ renderRunReportJson, // full normalized run model (CI dashboards / PR bots)
116
117
  runReportFilename, // deterministic download filename
117
118
  } from '../report-render.ts'
118
119
  import {
@@ -2419,12 +2420,14 @@ function ObservatoryPanel(props: SlotProps) {
2419
2420
  * SARIF 2.1.0 (CI / GitHub Code Scanning). Rendered from the SAME shared
2420
2421
  * data model as the `iterate_report` tool, so the button and the tool can
2421
2422
  * never disagree. Download-first with a copy fallback, like `exportObservatory`. */
2422
- const exportReport = (format: 'markdown' | 'sarif') => {
2423
+ const exportReport = (format: 'markdown' | 'sarif' | 'json') => {
2423
2424
  if (!manifest) return
2424
2425
  const extra = { qualityGate, experienceBank, defenseEvents }
2425
2426
  const content = format === 'sarif'
2426
2427
  ? JSON.stringify(renderRunReportSarif(manifest, extra), null, 2)
2427
- : renderRunReportMarkdown(manifest, extra, { lang: getLang() })
2428
+ : format === 'json'
2429
+ ? JSON.stringify(renderRunReportJson(manifest, extra), null, 2)
2430
+ : renderRunReportMarkdown(manifest, extra, { lang: getLang() })
2428
2431
  const filename = runReportFilename(format)
2429
2432
  const key = `report-${format}`
2430
2433
  const flash = () => {
@@ -3481,8 +3484,8 @@ function ObservatoryPanel(props: SlotProps) {
3481
3484
  title: t('将观测台全部数据导出为 JSON(优先下载,失败则复制)'),
3482
3485
  }, copiedKey === 'export' ? t('已导出') : t('导出 JSON')),
3483
3486
  // v3.9: portable report export — Markdown for a PR body, SARIF for
3484
- // CI / GitHub Code Scanning. Rendered from the same data model as
3485
- // the `iterate_report` tool.
3487
+ // CI / GitHub Code Scanning, JSON for CI dashboards / PR bots.
3488
+ // Rendered from the same data model as the `iterate_report` tool.
3486
3489
  React.createElement('button', {
3487
3490
  className: 'iterate-btn', 'data-copied': copiedKey === 'report-markdown' ? '' : undefined,
3488
3491
  onClick: () => exportReport('markdown'),
@@ -3493,6 +3496,14 @@ function ObservatoryPanel(props: SlotProps) {
3493
3496
  onClick: () => exportReport('sarif'),
3494
3497
  title: t('导出 SARIF 2.1.0(供 CI / GitHub Code Scanning 消费)'),
3495
3498
  }, copiedKey === 'report-sarif' ? t('已导出') : t('导出 SARIF')),
3499
+ // v3.11: lossless JSON export — the complete normalized run model
3500
+ // (findings + fixes + validations + quality gate + experience bank
3501
+ // + defense events) for CI dashboards and PR bots.
3502
+ React.createElement('button', {
3503
+ className: 'iterate-btn', 'data-copied': copiedKey === 'report-json' ? '' : undefined,
3504
+ onClick: () => exportReport('json'),
3505
+ title: t('导出完整运行模型的 JSON(供 CI 仪表盘 / PR 机器人消费,无损格式)'),
3506
+ }, copiedKey === 'report-json' ? t('已导出') : t('导出 JSON 报告')),
3496
3507
  // Gap #4: `.iterate/` artifacts are unreachable from the browser;
3497
3508
  // this copies the one instruction that makes the model echo them
3498
3509
  // into the session stream (which is what every panel scans).
@@ -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}`