iterate-plugin 2.12.3 → 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.
- package/README.md +28 -13
- package/dist/approval-gate.js +16 -2
- package/dist/config-loader.js +5 -0
- package/dist/index.js +16 -6
- package/dist/session-hooks.js +36 -11
- package/dist/skill-prompt.js +3 -0
- package/dist/tools/decision-log.js +10 -1
- package/dist/tools/defense-events.js +260 -0
- package/dist/tools/defense-store.js +97 -0
- package/dist/tools/experience-bank.js +248 -0
- package/dist/tools/experience-store.js +132 -0
- package/dist/tools/quality-gate.js +180 -0
- package/dist/tools/quality-store.js +174 -0
- package/lib/client.js +318 -15
- package/package.json +6 -6
- package/src/approval-gate.ts +14 -2
- package/src/client/index.ts +288 -16
- package/src/config-loader.ts +5 -0
- package/src/index.ts +16 -6
- package/src/session-hooks.ts +33 -11
- package/src/skill-prompt.ts +3 -0
- package/src/tools/decision-log.ts +10 -1
- package/src/tools/defense-events.ts +295 -0
- package/src/tools/defense-store.ts +113 -0
- package/src/tools/experience-bank.ts +264 -0
- package/src/tools/experience-store.ts +160 -0
- package/src/tools/quality-gate.ts +193 -0
- package/src/tools/quality-store.ts +199 -0
- package/src/types.ts +118 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/quality-store.ts — quality gate storage layer.
|
|
3
|
+
*
|
|
4
|
+
* Provides read/write access to quality gate data stored in
|
|
5
|
+
* .iterate/quality-gate.json. Quality gate snapshots are generated
|
|
6
|
+
* from review results and validation outcomes.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
const QUALITY_GATE_FILE = 'quality-gate.json';
|
|
11
|
+
/** Default empty quality gate snapshot. */
|
|
12
|
+
function emptySnapshot() {
|
|
13
|
+
return {
|
|
14
|
+
timestamp: new Date().toISOString(),
|
|
15
|
+
overallStatus: 'pending',
|
|
16
|
+
overallScore: 0,
|
|
17
|
+
dimensions: [],
|
|
18
|
+
verificationPassRate: 0,
|
|
19
|
+
totalChecks: 0,
|
|
20
|
+
passedChecks: 0,
|
|
21
|
+
failedChecks: 0,
|
|
22
|
+
totalFindings: 0,
|
|
23
|
+
criticalCount: 0,
|
|
24
|
+
highCount: 0,
|
|
25
|
+
mediumCount: 0,
|
|
26
|
+
lowCount: 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Read the quality gate snapshot from disk. */
|
|
30
|
+
export function readQualityGate(projectRoot) {
|
|
31
|
+
const filePath = path.join(projectRoot, '.iterate', QUALITY_GATE_FILE);
|
|
32
|
+
try {
|
|
33
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
34
|
+
const parsed = JSON.parse(content);
|
|
35
|
+
if (parsed && typeof parsed === 'object') {
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// File not found or invalid JSON
|
|
41
|
+
}
|
|
42
|
+
return emptySnapshot();
|
|
43
|
+
}
|
|
44
|
+
/** Write the quality gate snapshot to disk. */
|
|
45
|
+
export function writeQualityGate(projectRoot, snapshot) {
|
|
46
|
+
const dirPath = path.join(projectRoot, '.iterate');
|
|
47
|
+
const filePath = path.join(dirPath, QUALITY_GATE_FILE);
|
|
48
|
+
try {
|
|
49
|
+
if (!fs.existsSync(dirPath)) {
|
|
50
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
fs.writeFileSync(filePath, JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Silently fail - quality gate is not critical
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Compute the convergence rate for a dimension.
|
|
60
|
+
*
|
|
61
|
+
* Convergence measures how much NEW-finding volume shrank across rounds:
|
|
62
|
+
* `(first - last) / first` from the dimension's per-round findings series,
|
|
63
|
+
* expressed as a 0-100 percentage, clamped. A series with no fresh findings
|
|
64
|
+
* (or a dimension never reporting a first-round reading) counts as fully
|
|
65
|
+
* converged (100). Returns 0 — no measurable improvement — when a reading
|
|
66
|
+
* exists but the series is empty or malformed.
|
|
67
|
+
*/
|
|
68
|
+
export function convergenceRateFor(series, currentCount) {
|
|
69
|
+
if (Array.isArray(series) && series.length > 0) {
|
|
70
|
+
const first = series.find((n) => typeof n === 'number' && Number.isFinite(n));
|
|
71
|
+
const last = [...series].reverse().find((n) => typeof n === 'number' && Number.isFinite(n));
|
|
72
|
+
if (first === undefined || last === undefined)
|
|
73
|
+
return currentCount === 0 ? 100 : 0;
|
|
74
|
+
if (first <= 0)
|
|
75
|
+
return currentCount === 0 ? 100 : 0;
|
|
76
|
+
const raw = ((first - Math.max(0, last)) / first) * 100;
|
|
77
|
+
return Math.max(0, Math.min(100, Math.round(raw)));
|
|
78
|
+
}
|
|
79
|
+
return currentCount === 0 ? 100 : 0;
|
|
80
|
+
}
|
|
81
|
+
/** Compute a quality gate snapshot from review data. */
|
|
82
|
+
export function computeQualityGate(opts) {
|
|
83
|
+
const { dimensions, findings, validationResults, findingsByRound, fixedByDimension } = opts;
|
|
84
|
+
// Count findings by severity
|
|
85
|
+
const criticalCount = findings.filter((f) => f.severity === 'critical').length;
|
|
86
|
+
const highCount = findings.filter((f) => f.severity === 'high').length;
|
|
87
|
+
const mediumCount = findings.filter((f) => f.severity === 'medium').length;
|
|
88
|
+
const lowCount = findings.filter((f) => f.severity === 'low').length;
|
|
89
|
+
const totalFindings = findings.length;
|
|
90
|
+
// Compute dimension scores
|
|
91
|
+
const dimensionStats = {};
|
|
92
|
+
for (const dim of dimensions) {
|
|
93
|
+
dimensionStats[dim] = { count: 0, critical: 0, high: 0, medium: 0, low: 0 };
|
|
94
|
+
}
|
|
95
|
+
for (const finding of findings) {
|
|
96
|
+
const stats = dimensionStats[finding.dimension];
|
|
97
|
+
if (stats) {
|
|
98
|
+
stats.count++;
|
|
99
|
+
if (finding.severity === 'critical')
|
|
100
|
+
stats.critical++;
|
|
101
|
+
else if (finding.severity === 'high')
|
|
102
|
+
stats.high++;
|
|
103
|
+
else if (finding.severity === 'medium')
|
|
104
|
+
stats.medium++;
|
|
105
|
+
else
|
|
106
|
+
stats.low++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Compute dimension-level quality gates
|
|
110
|
+
const dimensionGates = dimensions.map((dim) => {
|
|
111
|
+
const stats = dimensionStats[dim] || { count: 0, critical: 0, high: 0, medium: 0, low: 0 };
|
|
112
|
+
// Score: 100 - (critical*30 + high*15 + medium*5 + low*1), capped at 0
|
|
113
|
+
const penalty = stats.critical * 30 + stats.high * 15 + stats.medium * 5 + stats.low * 1;
|
|
114
|
+
const score = Math.max(0, 100 - penalty);
|
|
115
|
+
const status = score >= 80 ? 'pass' : score >= 50 ? 'warn' : 'fail';
|
|
116
|
+
const series = findingsByRound?.[dim];
|
|
117
|
+
const convergenceRate = convergenceRateFor(Array.isArray(series) ? series : undefined, stats.count);
|
|
118
|
+
return {
|
|
119
|
+
dimension: dim,
|
|
120
|
+
convergenceRate,
|
|
121
|
+
findingsCount: stats.count,
|
|
122
|
+
fixedCount: fixedByDimension?.[dim] ?? 0,
|
|
123
|
+
score,
|
|
124
|
+
status,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
// Compute verification pass rate
|
|
128
|
+
const totalChecks = validationResults?.length ?? 0;
|
|
129
|
+
const passedChecks = validationResults?.filter((r) => r.exitCode === 0).length ?? 0;
|
|
130
|
+
const failedChecks = totalChecks - passedChecks;
|
|
131
|
+
const verificationPassRate = totalChecks > 0 ? Math.round((passedChecks / totalChecks) * 100) : 0;
|
|
132
|
+
// Compute overall score (weighted average of dimension scores)
|
|
133
|
+
const overallScore = dimensionGates.length > 0
|
|
134
|
+
? Math.round(dimensionGates.reduce((sum, d) => sum + d.score, 0) / dimensionGates.length)
|
|
135
|
+
: 0;
|
|
136
|
+
// Determine overall status
|
|
137
|
+
const hasCritical = criticalCount > 0;
|
|
138
|
+
const hasHighFail = dimensionGates.some((d) => d.status === 'fail');
|
|
139
|
+
const verificationFails = totalChecks > 0 && failedChecks > 0;
|
|
140
|
+
let overallStatus = 'pass';
|
|
141
|
+
let failReason;
|
|
142
|
+
if (hasCritical) {
|
|
143
|
+
overallStatus = 'fail';
|
|
144
|
+
failReason = `${criticalCount} critical findings present`;
|
|
145
|
+
}
|
|
146
|
+
else if (hasHighFail) {
|
|
147
|
+
overallStatus = 'fail';
|
|
148
|
+
failReason = 'One or more dimensions failed quality gate';
|
|
149
|
+
}
|
|
150
|
+
else if (verificationFails) {
|
|
151
|
+
overallStatus = 'fail';
|
|
152
|
+
failReason = `${failedChecks} validation checks failed`;
|
|
153
|
+
}
|
|
154
|
+
else if (overallScore < 70) {
|
|
155
|
+
overallStatus = 'fail';
|
|
156
|
+
failReason = `Overall score ${overallScore} below threshold (70)`;
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
overallStatus,
|
|
161
|
+
overallScore,
|
|
162
|
+
dimensions: dimensionGates,
|
|
163
|
+
verificationPassRate,
|
|
164
|
+
totalChecks,
|
|
165
|
+
passedChecks,
|
|
166
|
+
failedChecks,
|
|
167
|
+
failReason,
|
|
168
|
+
totalFindings,
|
|
169
|
+
criticalCount,
|
|
170
|
+
highCount,
|
|
171
|
+
mediumCount,
|
|
172
|
+
lowCount,
|
|
173
|
+
};
|
|
174
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -1208,6 +1208,43 @@ var ITERATE_CSS = `
|
|
|
1208
1208
|
.iterate-chip-phase { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; color: var(--dsw-alias-label-primary); background: color-mix(in srgb, var(--dsw-alias-label-secondary) 14%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-label-secondary) 26%, transparent); }
|
|
1209
1209
|
.iterate-chip-phase[data-live] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
|
|
1210
1210
|
|
|
1211
|
+
/* v3.0: task_mode indicator chip */
|
|
1212
|
+
.iterate-chip-taskmode { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
1213
|
+
.iterate-chip-taskmode[data-mode="code"] { color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 28%, transparent); }
|
|
1214
|
+
.iterate-chip-taskmode[data-mode="iterate"] { color: #B45309; background: color-mix(in srgb, #B45309 12%, transparent); border: 1px solid color-mix(in srgb, #B45309 28%, transparent); }
|
|
1215
|
+
|
|
1216
|
+
/* v3.0: Quality gate status */
|
|
1217
|
+
.iterate-gate { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
1218
|
+
.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); }
|
|
1219
|
+
.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); }
|
|
1220
|
+
.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); }
|
|
1221
|
+
|
|
1222
|
+
/* v3.0: Experience bank hit highlight */
|
|
1223
|
+
.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); }
|
|
1224
|
+
.iterate-exp-hit b { font-weight: 600; }
|
|
1225
|
+
|
|
1226
|
+
/* v3.0: Defense event type badges */
|
|
1227
|
+
.iterate-defense-badge { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
1228
|
+
.iterate-defense-badge[data-type="precondition_failed"] { 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); }
|
|
1229
|
+
.iterate-defense-badge[data-type="rollback"] { 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); }
|
|
1230
|
+
.iterate-defense-badge[data-type="invariant_violated"] { 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); }
|
|
1231
|
+
.iterate-defense-badge[data-type="assumption_falsified"] { color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 28%, transparent); }
|
|
1232
|
+
|
|
1233
|
+
/* v3.0: Command buttons */
|
|
1234
|
+
.iterate-cmd { padding: 4px 10px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-size: 11px; cursor: pointer; font-weight: 500; }
|
|
1235
|
+
.iterate-cmd:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
1236
|
+
.iterate-cmd[data-primary] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
|
|
1237
|
+
.iterate-cmd[data-danger] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent); color: var(--dsw-alias-state-error-primary); }
|
|
1238
|
+
.iterate-cmd:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1239
|
+
|
|
1240
|
+
/* v3.0: Dimension score bar */
|
|
1241
|
+
.iterate-dim-score { display: flex; align-items: center; gap: 6px; font-size: 11px; }
|
|
1242
|
+
.iterate-dim-score-bar { width: 60px; height: 6px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }
|
|
1243
|
+
.iterate-dim-score-fill { height: 100%; border-radius: 3px; transition: width 300ms ease; }
|
|
1244
|
+
.iterate-dim-score-fill[data-status="pass"] { background: var(--dsw-alias-state-success-primary); }
|
|
1245
|
+
.iterate-dim-score-fill[data-status="warn"] { background: var(--dsw-alias-state-warn-primary); }
|
|
1246
|
+
.iterate-dim-score-fill[data-status="fail"] { background: var(--dsw-alias-state-error-primary); }
|
|
1247
|
+
|
|
1211
1248
|
/* Accessibility-switch toggle */
|
|
1212
1249
|
.iterate-switch { position: relative; width: 42px; height: 24px; border-radius: 999px; padding: 0; cursor: pointer; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); transition: background-color 160ms ease, border-color 160ms ease; }
|
|
1213
1250
|
.iterate-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
@@ -1493,6 +1530,14 @@ function ConvergenceDashboard(props) {
|
|
|
1493
1530
|
"data-live": transcript.active === true ? "" : void 0,
|
|
1494
1531
|
title: transcript.active === true ? `\u5F53\u524D\u5904\u4E8E\u300C${phase}\u300D\u9636\u6BB5\uFF08\u8FD0\u884C\u4E2D\uFF09` : `\u6700\u8FD1\u4E00\u6B21\u6267\u884C\u505C\u7559\u5728\u300C${phase}\u300D\u9636\u6BB5\uFF08\u5DF2\u7ED3\u675F\uFF09`
|
|
1495
1532
|
}, `${phase} \xB7 ${transcript.active === true ? "\u8FD0\u884C\u4E2D" : "\u5DF2\u7ED3\u675F"}`) : null;
|
|
1533
|
+
const taskModeTranscript = latestTranscript(session);
|
|
1534
|
+
const taskMode = taskModeTranscript?.taskMode;
|
|
1535
|
+
const taskModeChip = taskMode ? React.createElement("span", {
|
|
1536
|
+
className: "iterate-chip-taskmode",
|
|
1537
|
+
key: "taskmode",
|
|
1538
|
+
"data-mode": taskMode,
|
|
1539
|
+
title: taskMode === "code" ? "\u4EE3\u7801\u6A21\u5F0F\uFF1A\u76F4\u63A5\u4FEE\u6539\u6587\u4EF6" : "\u8FED\u4EE3\u6A21\u5F0F\uFF1A\u5BA1\u67E5-\u4FEE\u590D\u5FAA\u73AF"
|
|
1540
|
+
}, taskMode === "code" ? "\u25CF code" : "\u25CF iterate") : null;
|
|
1496
1541
|
const dimNames = Object.keys(dims);
|
|
1497
1542
|
const dimBadges = dimNames.slice(0, 6).map(
|
|
1498
1543
|
(dim) => React.createElement(
|
|
@@ -1557,6 +1602,7 @@ function ConvergenceDashboard(props) {
|
|
|
1557
1602
|
resumeChip,
|
|
1558
1603
|
imageChip,
|
|
1559
1604
|
liveChip,
|
|
1605
|
+
taskModeChip,
|
|
1560
1606
|
React.createElement(TrendChart, { points: trend.points }),
|
|
1561
1607
|
...dimBadges
|
|
1562
1608
|
);
|
|
@@ -1677,6 +1723,7 @@ function TriagePanel(props) {
|
|
|
1677
1723
|
const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: "" });
|
|
1678
1724
|
const [selected, setSelected] = React.useState(null);
|
|
1679
1725
|
const [selectAll, setSelectAll] = React.useState(false);
|
|
1726
|
+
const [cmdCopied, setCmdCopied] = React.useState(null);
|
|
1680
1727
|
React.useEffect(() => {
|
|
1681
1728
|
if (storage) storage.set(storageKey, JSON.stringify(verdicts));
|
|
1682
1729
|
}, [storageKey, verdicts]);
|
|
@@ -1740,6 +1787,33 @@ function TriagePanel(props) {
|
|
|
1740
1787
|
const ignored = collectIgnoredEntries(verdicts, findings);
|
|
1741
1788
|
const ignoredCount = ignored.length;
|
|
1742
1789
|
const counts = countVerdicts(verdicts);
|
|
1790
|
+
const doApproveArchitecturalFix = () => {
|
|
1791
|
+
const cmd = "\u8BF7\u8C03\u7528 `iterate_fix` \u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u8BBE\u7F6E is_architectural: true\uFF09";
|
|
1792
|
+
copyText(cmd).then((ok) => {
|
|
1793
|
+
if (ok) {
|
|
1794
|
+
setCmdCopied("approve-arch");
|
|
1795
|
+
setTimeout(() => setCmdCopied(null), 1600);
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
};
|
|
1799
|
+
const doTriggerNewRound = () => {
|
|
1800
|
+
const cmd = "\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5";
|
|
1801
|
+
copyText(cmd).then((ok) => {
|
|
1802
|
+
if (ok) {
|
|
1803
|
+
setCmdCopied("new-round");
|
|
1804
|
+
setTimeout(() => setCmdCopied(null), 1600);
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
};
|
|
1808
|
+
const doRollbackToCheckpoint = () => {
|
|
1809
|
+
const cmd = "\u8BF7\u8C03\u7528 `iterate_checkpoint` \u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9";
|
|
1810
|
+
copyText(cmd).then((ok) => {
|
|
1811
|
+
if (ok) {
|
|
1812
|
+
setCmdCopied("rollback");
|
|
1813
|
+
setTimeout(() => setCmdCopied(null), 1600);
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
};
|
|
1743
1817
|
const doCopyYaml = () => {
|
|
1744
1818
|
const yaml = toKnownIntentionalYaml(ignored);
|
|
1745
1819
|
if (!yaml) return;
|
|
@@ -1895,6 +1969,36 @@ function TriagePanel(props) {
|
|
|
1895
1969
|
}, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
|
|
1896
1970
|
)
|
|
1897
1971
|
),
|
|
1972
|
+
// v3.0: Command buttons for native actions (§8)
|
|
1973
|
+
React.createElement(
|
|
1974
|
+
"div",
|
|
1975
|
+
{ className: "iterate-triage-foot", style: { borderTop: "1px solid var(--dsw-alias-border-l1)" } },
|
|
1976
|
+
React.createElement("span", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" } }, "\u6307\u6325\u64CD\u4F5C"),
|
|
1977
|
+
React.createElement(
|
|
1978
|
+
"span",
|
|
1979
|
+
{ style: { display: "flex", gap: 6 } },
|
|
1980
|
+
React.createElement("button", {
|
|
1981
|
+
className: "iterate-cmd",
|
|
1982
|
+
"data-primary": "",
|
|
1983
|
+
"data-copied": cmdCopied === "approve-arch" ? "" : void 0,
|
|
1984
|
+
onClick: doApproveArchitecturalFix,
|
|
1985
|
+
title: "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF09"
|
|
1986
|
+
}, cmdCopied === "approve-arch" ? "\u5DF2\u590D\u5236" : "\u6279\u51C6\u67B6\u6784\u4FEE\u590D"),
|
|
1987
|
+
React.createElement("button", {
|
|
1988
|
+
className: "iterate-cmd",
|
|
1989
|
+
"data-copied": cmdCopied === "new-round" ? "" : void 0,
|
|
1990
|
+
onClick: doTriggerNewRound,
|
|
1991
|
+
title: "\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5"
|
|
1992
|
+
}, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : "\u89E6\u53D1\u65B0\u4E00\u8F6E"),
|
|
1993
|
+
React.createElement("button", {
|
|
1994
|
+
className: "iterate-cmd",
|
|
1995
|
+
"data-danger": "",
|
|
1996
|
+
"data-copied": cmdCopied === "rollback" ? "" : void 0,
|
|
1997
|
+
onClick: doRollbackToCheckpoint,
|
|
1998
|
+
title: "\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9"
|
|
1999
|
+
}, cmdCopied === "rollback" ? "\u5DF2\u590D\u5236" : "\u56DE\u6EDA\u68C0\u67E5\u70B9")
|
|
2000
|
+
)
|
|
2001
|
+
),
|
|
1898
2002
|
payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
|
|
1899
2003
|
);
|
|
1900
2004
|
}
|
|
@@ -1978,7 +2082,13 @@ function ProgressCapsule() {
|
|
|
1978
2082
|
};
|
|
1979
2083
|
}, []);
|
|
1980
2084
|
if (!info) return null;
|
|
1981
|
-
return React.createElement("div", {
|
|
2085
|
+
return React.createElement("div", {
|
|
2086
|
+
"data-iterate-root": "",
|
|
2087
|
+
className: "iterate-capsule",
|
|
2088
|
+
"data-ok": info.ok ? "" : void 0,
|
|
2089
|
+
role: "status",
|
|
2090
|
+
"aria-live": "polite"
|
|
2091
|
+
}, info.text);
|
|
1982
2092
|
}
|
|
1983
2093
|
function SettingsPanel(_props) {
|
|
1984
2094
|
const [enabled, setEnabled] = React.useState(themeEnabled);
|
|
@@ -2171,7 +2281,11 @@ var OBS_TABS = [
|
|
|
2171
2281
|
{ key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA" },
|
|
2172
2282
|
{ key: "f5", label: "\u65AD\u70B9\u6062\u590D" },
|
|
2173
2283
|
{ key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0" },
|
|
2174
|
-
{ key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" }
|
|
2284
|
+
{ key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" },
|
|
2285
|
+
// v3.0: Quality Command Center tabs
|
|
2286
|
+
{ key: "f8", label: "\u8D28\u91CF\u95E8\u7981" },
|
|
2287
|
+
{ key: "f9", label: "\u7ECF\u9A8C\u94F6\u884C" },
|
|
2288
|
+
{ key: "f10", label: "\u9632\u5FA1\u4E8B\u4EF6" }
|
|
2175
2289
|
];
|
|
2176
2290
|
var OBS_LIVE_META = {
|
|
2177
2291
|
read: { label: "\u9605\u8BFB", color: "var(--dsw-alias-brand-primary)" },
|
|
@@ -2240,6 +2354,9 @@ function ObservatoryPanel(props) {
|
|
|
2240
2354
|
const [f3Filter, setF3Filter] = React.useState(
|
|
2241
2355
|
{ severities: [], dimensions: [], search: "" }
|
|
2242
2356
|
);
|
|
2357
|
+
const [expSearch, setExpSearch] = React.useState("");
|
|
2358
|
+
const [expResults, setExpResults] = React.useState([]);
|
|
2359
|
+
const [defenseFilter, setDefenseFilter] = React.useState("");
|
|
2243
2360
|
const copyTimer = React.useRef(null);
|
|
2244
2361
|
React.useEffect(() => () => {
|
|
2245
2362
|
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
@@ -2266,6 +2383,7 @@ function ObservatoryPanel(props) {
|
|
|
2266
2383
|
});
|
|
2267
2384
|
};
|
|
2268
2385
|
const exportObservatory = () => {
|
|
2386
|
+
if (!manifest) return;
|
|
2269
2387
|
const json = serializeObservatoryExport(manifest, manifest.live || []);
|
|
2270
2388
|
const filename = `iterate-observatory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`;
|
|
2271
2389
|
const flash = () => {
|
|
@@ -2483,38 +2601,39 @@ function ObservatoryPanel(props) {
|
|
|
2483
2601
|
);
|
|
2484
2602
|
const findingBlocks = filtered.map((f, i) => {
|
|
2485
2603
|
const k = `f3-${i}`;
|
|
2486
|
-
const
|
|
2604
|
+
const finding = f;
|
|
2605
|
+
const loc = `${String(finding.file || "?")}${typeof finding.line === "number" && finding.line > 0 ? `:${finding.line}` : ""}`;
|
|
2487
2606
|
const entry = {
|
|
2488
|
-
file: String(
|
|
2489
|
-
...typeof
|
|
2490
|
-
dimension: String(
|
|
2491
|
-
reason: String(
|
|
2607
|
+
file: String(finding.file || ""),
|
|
2608
|
+
...typeof finding.line === "number" && finding.line > 0 ? { line: finding.line } : {},
|
|
2609
|
+
dimension: String(finding.dimension || ""),
|
|
2610
|
+
reason: String(finding.summary || "")
|
|
2492
2611
|
};
|
|
2493
2612
|
const knownInstruction = buildApplyInstruction([entry]);
|
|
2494
|
-
const fixInstruction = buildObsFixInstruction(
|
|
2613
|
+
const fixInstruction = buildObsFixInstruction(finding);
|
|
2495
2614
|
return React.createElement(
|
|
2496
2615
|
"div",
|
|
2497
2616
|
{ key: k, className: "iterate-obs-block" },
|
|
2498
2617
|
React.createElement(
|
|
2499
2618
|
"div",
|
|
2500
2619
|
{ className: "iterate-obs-block-head" },
|
|
2501
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(
|
|
2502
|
-
React.createElement("span", {}, severityLabel(
|
|
2620
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(finding.severity) } }),
|
|
2621
|
+
React.createElement("span", {}, severityLabel(finding.severity)),
|
|
2503
2622
|
React.createElement("span", { className: "iterate-obs-file" }, loc),
|
|
2504
|
-
React.createElement("span", {}, String(
|
|
2505
|
-
|
|
2623
|
+
React.createElement("span", {}, String(finding.dimension || "")),
|
|
2624
|
+
finding.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, "\u5DF2\u786E\u8BA4") : null
|
|
2506
2625
|
),
|
|
2507
2626
|
React.createElement(
|
|
2508
2627
|
"div",
|
|
2509
2628
|
{ className: "iterate-obs-block-body" },
|
|
2510
|
-
React.createElement("div", { className: "iterate-obs-msg" }, String(
|
|
2629
|
+
React.createElement("div", { className: "iterate-obs-msg" }, String(finding.summary || "")),
|
|
2511
2630
|
React.createElement(
|
|
2512
2631
|
"div",
|
|
2513
2632
|
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
2514
2633
|
React.createElement("button", {
|
|
2515
2634
|
className: "iterate-btn",
|
|
2516
2635
|
"data-copied": copiedKey === `${k}-fix` ? "" : void 0,
|
|
2517
|
-
onClick: () => copyInstruction(`${k}-fix`, String(
|
|
2636
|
+
onClick: () => copyInstruction(`${k}-fix`, String(finding.suggested_fix || "")),
|
|
2518
2637
|
title: "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C"
|
|
2519
2638
|
}, copiedKey === `${k}-fix` ? "\u5DF2\u590D\u5236" : "\u590D\u5236 suggested_fix"),
|
|
2520
2639
|
React.createElement("button", {
|
|
@@ -2793,6 +2912,183 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2793
2912
|
}
|
|
2794
2913
|
return React.createElement("div", {}, filterBar, ...rows);
|
|
2795
2914
|
};
|
|
2915
|
+
const renderQualityGate = () => {
|
|
2916
|
+
const qualityGateInstruction = "\u8BF7\u8C03\u7528 `iterate_quality_gate` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001";
|
|
2917
|
+
return React.createElement(
|
|
2918
|
+
"div",
|
|
2919
|
+
{},
|
|
2920
|
+
React.createElement(
|
|
2921
|
+
"div",
|
|
2922
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2923
|
+
React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
|
|
2924
|
+
React.createElement("button", {
|
|
2925
|
+
className: "iterate-btn",
|
|
2926
|
+
"data-primary": "",
|
|
2927
|
+
"data-copied": copiedKey === "qgate" ? "" : void 0,
|
|
2928
|
+
onClick: () => copyInstruction("qgate", qualityGateInstruction),
|
|
2929
|
+
title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4"
|
|
2930
|
+
}, copiedKey === "qgate" ? "\u5DF2\u590D\u5236" : "\u67E5\u8BE2\u95E8\u7981")
|
|
2931
|
+
),
|
|
2932
|
+
React.createElement(
|
|
2933
|
+
"div",
|
|
2934
|
+
{ className: "iterate-obs-block" },
|
|
2935
|
+
React.createElement(
|
|
2936
|
+
"div",
|
|
2937
|
+
{ className: "iterate-obs-block-head" },
|
|
2938
|
+
React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
|
|
2939
|
+
React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
|
|
2940
|
+
),
|
|
2941
|
+
React.createElement(
|
|
2942
|
+
"div",
|
|
2943
|
+
{ className: "iterate-obs-block-body" },
|
|
2944
|
+
React.createElement("div", { className: "iterate-obs-msg" }, "\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002"),
|
|
2945
|
+
React.createElement(
|
|
2946
|
+
"div",
|
|
2947
|
+
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
2948
|
+
React.createElement("button", {
|
|
2949
|
+
className: "iterate-btn",
|
|
2950
|
+
"data-copied": copiedKey === "qgate-instr" ? "" : void 0,
|
|
2951
|
+
onClick: () => copyInstruction("qgate-instr", qualityGateInstruction),
|
|
2952
|
+
title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
|
|
2953
|
+
}, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
|
|
2954
|
+
)
|
|
2955
|
+
)
|
|
2956
|
+
)
|
|
2957
|
+
);
|
|
2958
|
+
};
|
|
2959
|
+
const renderExperienceBank = () => {
|
|
2960
|
+
const expInstruction = expSearch ? `\u8BF7\u8C03\u7528 \`iterate_experience\` \u641C\u7D22\u7ECF\u9A8C\uFF1A
|
|
2961
|
+
|
|
2962
|
+
\`\`\`json
|
|
2963
|
+
${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
|
|
2964
|
+
\`\`\`` : "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C";
|
|
2965
|
+
const resultRows = expResults.map((entry, i) => {
|
|
2966
|
+
const e = entry;
|
|
2967
|
+
return React.createElement(
|
|
2968
|
+
"div",
|
|
2969
|
+
{ key: `exp-${i}`, className: "iterate-obs-row" },
|
|
2970
|
+
React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
|
|
2971
|
+
React.createElement("span", { className: "iterate-obs-msg" }, String(e.pattern || "")),
|
|
2972
|
+
React.createElement("span", { className: "iterate-exp-hit" }, `\u547D\u4E2D ${e.hitCount ?? 0} \u6B21`)
|
|
2973
|
+
);
|
|
2974
|
+
});
|
|
2975
|
+
return React.createElement(
|
|
2976
|
+
"div",
|
|
2977
|
+
{},
|
|
2978
|
+
React.createElement(
|
|
2979
|
+
"div",
|
|
2980
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2981
|
+
React.createElement("b", {}, "\u7ECF\u9A8C\u94F6\u884C"),
|
|
2982
|
+
React.createElement("button", {
|
|
2983
|
+
className: "iterate-btn",
|
|
2984
|
+
"data-primary": "",
|
|
2985
|
+
"data-copied": copiedKey === "exp-list" ? "" : void 0,
|
|
2986
|
+
onClick: () => copyInstruction("exp-list", "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C"),
|
|
2987
|
+
title: "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4"
|
|
2988
|
+
}, copiedKey === "exp-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u7ECF\u9A8C")
|
|
2989
|
+
),
|
|
2990
|
+
React.createElement(
|
|
2991
|
+
"div",
|
|
2992
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2993
|
+
React.createElement("input", {
|
|
2994
|
+
className: "iterate-filter-search",
|
|
2995
|
+
type: "search",
|
|
2996
|
+
placeholder: "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026",
|
|
2997
|
+
"aria-label": "\u641C\u7D22\u7ECF\u9A8C",
|
|
2998
|
+
value: expSearch,
|
|
2999
|
+
onChange: (e) => setExpSearch(e.target.value)
|
|
3000
|
+
}),
|
|
3001
|
+
React.createElement("button", {
|
|
3002
|
+
className: "iterate-btn",
|
|
3003
|
+
"data-copied": copiedKey === "exp-search" ? "" : void 0,
|
|
3004
|
+
disabled: !expSearch,
|
|
3005
|
+
onClick: () => copyInstruction("exp-search", expInstruction),
|
|
3006
|
+
title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
|
|
3007
|
+
}, copiedKey === "exp-search" ? "\u5DF2\u590D\u5236" : "\u641C\u7D22")
|
|
3008
|
+
),
|
|
3009
|
+
resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : React.createElement("div", { className: "iterate-obs-empty" }, "\u8F93\u5165\u5173\u952E\u8BCD\u641C\u7D22\u5386\u53F2\u7ECF\u9A8C\uFF0C\u6216\u70B9\u51FB\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u770B\u5168\u90E8")
|
|
3010
|
+
);
|
|
3011
|
+
};
|
|
3012
|
+
const renderDefenseEvents = () => {
|
|
3013
|
+
const defenseInstruction = defenseFilter ? `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A
|
|
3014
|
+
|
|
3015
|
+
\`\`\`json
|
|
3016
|
+
${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
|
|
3017
|
+
\`\`\`` : "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6";
|
|
3018
|
+
const typeOptions = [
|
|
3019
|
+
{ value: "", label: "\u5168\u90E8\u7C7B\u578B" },
|
|
3020
|
+
{ value: "precondition_failed", label: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25" },
|
|
3021
|
+
{ value: "rollback", label: "\u56DE\u6EDA" },
|
|
3022
|
+
{ value: "invariant_violated", label: "\u4E0D\u53D8\u91CF\u8FDD\u53CD" },
|
|
3023
|
+
{ value: "assumption_falsified", label: "\u5047\u8BBE\u88AB\u8BC1\u4F2A" }
|
|
3024
|
+
];
|
|
3025
|
+
return React.createElement(
|
|
3026
|
+
"div",
|
|
3027
|
+
{},
|
|
3028
|
+
React.createElement(
|
|
3029
|
+
"div",
|
|
3030
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
3031
|
+
React.createElement("b", {}, "\u9632\u5FA1\u4E8B\u4EF6"),
|
|
3032
|
+
React.createElement("button", {
|
|
3033
|
+
className: "iterate-btn",
|
|
3034
|
+
"data-primary": "",
|
|
3035
|
+
"data-copied": copiedKey === "defense-list" ? "" : void 0,
|
|
3036
|
+
onClick: () => copyInstruction("defense-list", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6"),
|
|
3037
|
+
title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4"
|
|
3038
|
+
}, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
|
|
3039
|
+
React.createElement("button", {
|
|
3040
|
+
className: "iterate-btn",
|
|
3041
|
+
"data-copied": copiedKey === "defense-counts" ? "" : void 0,
|
|
3042
|
+
onClick: () => copyInstruction("defense-counts", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1"),
|
|
3043
|
+
title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4"
|
|
3044
|
+
}, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1")
|
|
3045
|
+
),
|
|
3046
|
+
React.createElement(
|
|
3047
|
+
"div",
|
|
3048
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
3049
|
+
React.createElement(
|
|
3050
|
+
"select",
|
|
3051
|
+
{
|
|
3052
|
+
className: "iterate-filter-select",
|
|
3053
|
+
value: defenseFilter,
|
|
3054
|
+
"aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
|
|
3055
|
+
onChange: (e) => setDefenseFilter(e.target.value)
|
|
3056
|
+
},
|
|
3057
|
+
...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
|
|
3058
|
+
),
|
|
3059
|
+
defenseFilter ? React.createElement("button", {
|
|
3060
|
+
className: "iterate-filter-clear",
|
|
3061
|
+
onClick: () => setDefenseFilter(""),
|
|
3062
|
+
title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
|
|
3063
|
+
}, "\u6E05\u9664") : null
|
|
3064
|
+
),
|
|
3065
|
+
React.createElement(
|
|
3066
|
+
"div",
|
|
3067
|
+
{ className: "iterate-obs-block" },
|
|
3068
|
+
React.createElement(
|
|
3069
|
+
"div",
|
|
3070
|
+
{ className: "iterate-obs-block-head" },
|
|
3071
|
+
React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
|
|
3072
|
+
React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
|
|
3073
|
+
),
|
|
3074
|
+
React.createElement(
|
|
3075
|
+
"div",
|
|
3076
|
+
{ className: "iterate-obs-block-body" },
|
|
3077
|
+
React.createElement("div", { className: "iterate-obs-msg" }, "\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002"),
|
|
3078
|
+
React.createElement(
|
|
3079
|
+
"div",
|
|
3080
|
+
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
3081
|
+
React.createElement("button", {
|
|
3082
|
+
className: "iterate-btn",
|
|
3083
|
+
"data-copied": copiedKey === "defense-instr" ? "" : void 0,
|
|
3084
|
+
onClick: () => copyInstruction("defense-instr", defenseInstruction),
|
|
3085
|
+
title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
|
|
3086
|
+
}, copiedKey === "defense-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
|
|
3087
|
+
)
|
|
3088
|
+
)
|
|
3089
|
+
)
|
|
3090
|
+
);
|
|
3091
|
+
};
|
|
2796
3092
|
const renderLive = () => {
|
|
2797
3093
|
const liveEntries = manifest.live || [];
|
|
2798
3094
|
if (liveEntries.length === 0) {
|
|
@@ -2820,7 +3116,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2820
3116
|
const meta = OBS_LIVE_META[type] || { label: String(e.type || "?"), color: OBS_LIVE_FALLBACK_COLOR };
|
|
2821
3117
|
const tool = String(e.tool || "");
|
|
2822
3118
|
const target = String(e.target || "");
|
|
2823
|
-
const time = formatObsLiveTime(e.ts);
|
|
3119
|
+
const time = formatObsLiveTime(String(e.ts || ""));
|
|
2824
3120
|
return React.createElement(
|
|
2825
3121
|
"div",
|
|
2826
3122
|
{ key: `live-${i}`, className: "iterate-obs-row" },
|
|
@@ -2880,6 +3176,13 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2880
3176
|
return renderConsole();
|
|
2881
3177
|
case "f7":
|
|
2882
3178
|
return renderTimeline();
|
|
3179
|
+
// v3.0: Quality Command Center tabs
|
|
3180
|
+
case "f8":
|
|
3181
|
+
return renderQualityGate();
|
|
3182
|
+
case "f9":
|
|
3183
|
+
return renderExperienceBank();
|
|
3184
|
+
case "f10":
|
|
3185
|
+
return renderDefenseEvents();
|
|
2883
3186
|
default:
|
|
2884
3187
|
return null;
|
|
2885
3188
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "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
|
|
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": {
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@deepseek-ai/cordis": "4.0.2",
|
|
64
|
-
"@deepseek-ai/dsh-tools": "0.1.2-
|
|
65
|
-
"@deepseek-ai/dsh-util-values": "0.1.2-
|
|
64
|
+
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-util-values": "0.1.2-rc.1",
|
|
66
66
|
"js-yaml": "4.3.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@deepseek-ai/dsh-jobs": "^0.1.2-
|
|
70
|
-
"@deepseek-ai/dsh-session": "0.1.2-
|
|
69
|
+
"@deepseek-ai/dsh-jobs": "^0.1.2-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
71
71
|
"@types/js-yaml": "4.0.9",
|
|
72
72
|
"@types/node": "22.15.0",
|
|
73
73
|
"@types/react": "19.2.2",
|