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.
- package/README.md +28 -12
- package/README.zh-CN.md +2 -1
- package/dist/approval-gate.js +16 -2
- package/dist/config-loader.js +5 -0
- package/dist/git-scope.js +61 -7
- 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 +662 -103
- package/lib/parse.js +93 -0
- package/package.json +7 -6
- package/src/approval-gate.ts +14 -2
- package/src/client/index.ts +542 -49
- package/src/config-loader.ts +5 -0
- package/src/git-scope.ts +48 -7
- package/src/index.ts +16 -6
- package/src/session-hooks.ts +33 -11
- package/src/skill-prompt.ts +3 -0
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/config.ts +1 -1
- package/src/tools/decision-log.ts +11 -2
- 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/fix.ts +1 -1
- package/src/tools/history.ts +1 -1
- package/src/tools/prune.ts +1 -1
- package/src/tools/quality-gate.ts +193 -0
- package/src/tools/quality-store.ts +199 -0
- package/src/tools/review.ts +1 -1
- package/src/tools/transcript.ts +1 -1
- package/src/tools/triage.ts +1 -1
- package/src/types.ts +118 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/quality-gate.ts — quality gate query & write tool.
|
|
3
|
+
*
|
|
4
|
+
* iterate_quality_gate — query the persisted quality certificate, or compute
|
|
5
|
+
* and persist a new one from review/validation data.
|
|
6
|
+
*
|
|
7
|
+
* Provides a machine-readable quality certificate for the current iteration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
11
|
+
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
|
|
12
|
+
import { resolveProjectRootForExec } from '../config-loader.ts'
|
|
13
|
+
import { readQualityGate, writeQualityGate, computeQualityGate } from './quality-store.ts'
|
|
14
|
+
import type { QualityGateSnapshot } from '../types.ts'
|
|
15
|
+
|
|
16
|
+
/** Validate a single finding object; returns true when well-formed. */
|
|
17
|
+
function isValidFinding(raw: unknown): raw is { dimension: string; severity: string; file: string; line?: number } {
|
|
18
|
+
if (!raw || typeof raw !== 'object') return false
|
|
19
|
+
const f = raw as Record<string, unknown>
|
|
20
|
+
return (
|
|
21
|
+
typeof f.dimension === 'string' &&
|
|
22
|
+
typeof f.severity === 'string' &&
|
|
23
|
+
typeof f.file === 'string' &&
|
|
24
|
+
(f.line === undefined || typeof f.line === 'number')
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Validate a single validation result; returns true when well-formed. */
|
|
29
|
+
function isValidValidationResult(raw: unknown): raw is { command: string; exitCode: number } {
|
|
30
|
+
if (!raw || typeof raw !== 'object') return false
|
|
31
|
+
const r = raw as Record<string, unknown>
|
|
32
|
+
return typeof r.command === 'string' && typeof r.exitCode === 'number' && Number.isFinite(r.exitCode)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Sanitize a caller-supplied per-dimension number map. */
|
|
36
|
+
function sanitizeNumberMap(raw: unknown): Record<string, number> | undefined {
|
|
37
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined
|
|
38
|
+
const out: Record<string, number> = {}
|
|
39
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
40
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0) out[key] = value
|
|
41
|
+
}
|
|
42
|
+
return Object.keys(out).length > 0 ? out : undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Sanitize a caller-supplied per-dimension round series map. */
|
|
46
|
+
function sanitizeRoundSeries(raw: unknown): Record<string, number[]> | undefined {
|
|
47
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined
|
|
48
|
+
const out: Record<string, number[]> = {}
|
|
49
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
const series = value
|
|
52
|
+
.filter((n): n is number => typeof n === 'number' && Number.isFinite(n) && n >= 0)
|
|
53
|
+
if (series.length > 0) out[key] = series
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return Object.keys(out).length > 0 ? out : undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register the `iterate_quality_gate` tool.
|
|
61
|
+
* Reads the persisted quality certificate, or computes + persists a new one.
|
|
62
|
+
*/
|
|
63
|
+
export function registerQualityGateTool(ctx: { tools: { register: (def: ReturnType<typeof defineTool>) => void } }): void {
|
|
64
|
+
ctx.tools.register(
|
|
65
|
+
defineTool({
|
|
66
|
+
name: 'iterate_quality_gate',
|
|
67
|
+
description:
|
|
68
|
+
'Query or write the quality gate status: dimension convergence rates, verification pass rates, ' +
|
|
69
|
+
'and overall PASS/FAIL status. ' +
|
|
70
|
+
'Operation "read" (default) returns the persisted machine-readable quality certificate. ' +
|
|
71
|
+
'Operation "compute" computes a fresh snapshot from this round\'s findings/validation results, ' +
|
|
72
|
+
'persists it to .iterate/quality-gate.json, and returns it.',
|
|
73
|
+
parameters: {
|
|
74
|
+
operation: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'Operation: read (load persisted certificate) or compute (recompute + persist). Default: read.',
|
|
77
|
+
enum: ['read', 'compute'],
|
|
78
|
+
},
|
|
79
|
+
dimensions: {
|
|
80
|
+
type: 'array',
|
|
81
|
+
items: { type: 'string' },
|
|
82
|
+
description: 'Dimensions to gate (required for compute).',
|
|
83
|
+
},
|
|
84
|
+
findings: {
|
|
85
|
+
type: 'json',
|
|
86
|
+
description:
|
|
87
|
+
'Findings array (required for compute). Each item: { dimension, severity (critical|high|medium|low), file, line? }.',
|
|
88
|
+
},
|
|
89
|
+
validationResults: {
|
|
90
|
+
type: 'json',
|
|
91
|
+
description: 'Validation results array (optional for compute). Each item: { command, exitCode }.',
|
|
92
|
+
},
|
|
93
|
+
findingsByRound: {
|
|
94
|
+
type: 'json',
|
|
95
|
+
description:
|
|
96
|
+
'Optional per-dimension NEW-finding counts across rounds (latest last) — used to compute real convergence rates. ' +
|
|
97
|
+
'Example: { "correctness": [5, 2, 0] }.',
|
|
98
|
+
},
|
|
99
|
+
fixedByDimension: {
|
|
100
|
+
type: 'json',
|
|
101
|
+
description: 'Optional per-dimension count of fixed findings, e.g. { "correctness": 3 }.',
|
|
102
|
+
},
|
|
103
|
+
path: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Project root directory (default: current working directory).',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
output: {
|
|
110
|
+
schema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
additionalProperties: false,
|
|
113
|
+
properties: {
|
|
114
|
+
ok: { type: 'boolean', required: true },
|
|
115
|
+
kind: { type: 'string' },
|
|
116
|
+
operation: { type: 'string' },
|
|
117
|
+
snapshot: { type: 'json' },
|
|
118
|
+
error: { type: 'string' },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
render: (_args, value) => {
|
|
122
|
+
if (!value.ok) return [{ type: 'text', text: `quality gate query failed: ${value.error}` }]
|
|
123
|
+
const operation = typeof value.operation === 'string' ? value.operation : 'read'
|
|
124
|
+
const snapshot = value.snapshot as unknown as QualityGateSnapshot
|
|
125
|
+
if (!snapshot) return [{ type: 'text', text: 'No quality gate data available.' }]
|
|
126
|
+
|
|
127
|
+
const statusEmoji = snapshot.overallStatus === 'pass' ? '✓' : snapshot.overallStatus === 'fail' ? '✗' : '○'
|
|
128
|
+
const lines = [
|
|
129
|
+
`${statusEmoji} Quality Gate: ${snapshot.overallStatus.toUpperCase()} (score: ${snapshot.overallScore})`,
|
|
130
|
+
`Verification: ${snapshot.passedChecks}/${snapshot.totalChecks} passed (${snapshot.verificationPassRate}%)`,
|
|
131
|
+
`Findings: ${snapshot.totalFindings} total (${snapshot.criticalCount} critical, ${snapshot.highCount} high, ${snapshot.mediumCount} medium, ${snapshot.lowCount} low)`,
|
|
132
|
+
'',
|
|
133
|
+
'Dimension Breakdown:',
|
|
134
|
+
...snapshot.dimensions.map((d) => {
|
|
135
|
+
const dimStatus = d.status === 'pass' ? '✓' : d.status === 'warn' ? '!' : '✗'
|
|
136
|
+
return ` ${dimStatus} ${d.dimension}: score=${d.score}, convergence=${d.convergenceRate}%, findings=${d.findingsCount}, fixed=${d.fixedCount}`
|
|
137
|
+
}),
|
|
138
|
+
]
|
|
139
|
+
if (snapshot.failReason) {
|
|
140
|
+
lines.push('', `Fail Reason: ${snapshot.failReason}`)
|
|
141
|
+
}
|
|
142
|
+
if (operation === 'compute') {
|
|
143
|
+
lines.push('', 'Quality gate snapshot computed and persisted.')
|
|
144
|
+
}
|
|
145
|
+
return [{ type: 'text', text: lines.join('\n') }]
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
async execute(args, exec) {
|
|
150
|
+
const resolved = resolveProjectRootForExec(exec, args.path)
|
|
151
|
+
if (!resolved.ok) return { ok: false, kind: 'quality_gate', error: resolved.reason }
|
|
152
|
+
const projectRoot = resolved.root
|
|
153
|
+
|
|
154
|
+
const operation = typeof args.operation === 'string' ? args.operation : 'read'
|
|
155
|
+
|
|
156
|
+
if (operation === 'compute') {
|
|
157
|
+
const dimensions = Array.isArray(args.dimensions)
|
|
158
|
+
? args.dimensions.filter((d): d is string => typeof d === 'string' && d.length > 0)
|
|
159
|
+
: []
|
|
160
|
+
const findings = Array.isArray(args.findings) ? args.findings.filter(isValidFinding) : []
|
|
161
|
+
const validationResults = Array.isArray(args.validationResults)
|
|
162
|
+
? args.validationResults.filter(isValidValidationResult)
|
|
163
|
+
: undefined
|
|
164
|
+
const findingsByRound = sanitizeRoundSeries(args.findingsByRound)
|
|
165
|
+
const fixedByDimension = sanitizeNumberMap(args.fixedByDimension)
|
|
166
|
+
|
|
167
|
+
const snapshot = computeQualityGate({
|
|
168
|
+
dimensions,
|
|
169
|
+
findings,
|
|
170
|
+
validationResults,
|
|
171
|
+
findingsByRound,
|
|
172
|
+
fixedByDimension,
|
|
173
|
+
})
|
|
174
|
+
writeQualityGate(projectRoot, snapshot)
|
|
175
|
+
return {
|
|
176
|
+
ok: true,
|
|
177
|
+
kind: 'quality_gate',
|
|
178
|
+
operation: 'compute',
|
|
179
|
+
snapshot: snapshot as unknown as JsonValue,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const snapshot = readQualityGate(projectRoot)
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
kind: 'quality_gate',
|
|
187
|
+
operation: 'read',
|
|
188
|
+
snapshot: snapshot as unknown as JsonValue,
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
|
|
9
|
+
import * as fs from 'node:fs'
|
|
10
|
+
import * as path from 'node:path'
|
|
11
|
+
import type { QualityGateSnapshot, QualityGateDimension } from '../types.ts'
|
|
12
|
+
|
|
13
|
+
const QUALITY_GATE_FILE = 'quality-gate.json'
|
|
14
|
+
|
|
15
|
+
/** Default empty quality gate snapshot. */
|
|
16
|
+
function emptySnapshot(): QualityGateSnapshot {
|
|
17
|
+
return {
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
overallStatus: 'pending',
|
|
20
|
+
overallScore: 0,
|
|
21
|
+
dimensions: [],
|
|
22
|
+
verificationPassRate: 0,
|
|
23
|
+
totalChecks: 0,
|
|
24
|
+
passedChecks: 0,
|
|
25
|
+
failedChecks: 0,
|
|
26
|
+
totalFindings: 0,
|
|
27
|
+
criticalCount: 0,
|
|
28
|
+
highCount: 0,
|
|
29
|
+
mediumCount: 0,
|
|
30
|
+
lowCount: 0,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Read the quality gate snapshot from disk. */
|
|
35
|
+
export function readQualityGate(projectRoot: string): QualityGateSnapshot {
|
|
36
|
+
const filePath = path.join(projectRoot, '.iterate', QUALITY_GATE_FILE)
|
|
37
|
+
try {
|
|
38
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
39
|
+
const parsed = JSON.parse(content) as QualityGateSnapshot
|
|
40
|
+
if (parsed && typeof parsed === 'object') {
|
|
41
|
+
return parsed
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// File not found or invalid JSON
|
|
45
|
+
}
|
|
46
|
+
return emptySnapshot()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Write the quality gate snapshot to disk. */
|
|
50
|
+
export function writeQualityGate(projectRoot: string, snapshot: QualityGateSnapshot): void {
|
|
51
|
+
const dirPath = path.join(projectRoot, '.iterate')
|
|
52
|
+
const filePath = path.join(dirPath, QUALITY_GATE_FILE)
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
if (!fs.existsSync(dirPath)) {
|
|
56
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
57
|
+
}
|
|
58
|
+
fs.writeFileSync(filePath, JSON.stringify(snapshot, null, 2), 'utf-8')
|
|
59
|
+
} catch {
|
|
60
|
+
// Silently fail - quality gate is not critical
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Compute the convergence rate for a dimension.
|
|
66
|
+
*
|
|
67
|
+
* Convergence measures how much NEW-finding volume shrank across rounds:
|
|
68
|
+
* `(first - last) / first` from the dimension's per-round findings series,
|
|
69
|
+
* expressed as a 0-100 percentage, clamped. A series with no fresh findings
|
|
70
|
+
* (or a dimension never reporting a first-round reading) counts as fully
|
|
71
|
+
* converged (100). Returns 0 — no measurable improvement — when a reading
|
|
72
|
+
* exists but the series is empty or malformed.
|
|
73
|
+
*/
|
|
74
|
+
export function convergenceRateFor(series: number[] | undefined, currentCount: number): number {
|
|
75
|
+
if (Array.isArray(series) && series.length > 0) {
|
|
76
|
+
const first = series.find((n) => typeof n === 'number' && Number.isFinite(n))
|
|
77
|
+
const last = [...series].reverse().find((n) => typeof n === 'number' && Number.isFinite(n))
|
|
78
|
+
if (first === undefined || last === undefined) return currentCount === 0 ? 100 : 0
|
|
79
|
+
if (first <= 0) return currentCount === 0 ? 100 : 0
|
|
80
|
+
const raw = ((first - Math.max(0, last)) / first) * 100
|
|
81
|
+
return Math.max(0, Math.min(100, Math.round(raw)))
|
|
82
|
+
}
|
|
83
|
+
return currentCount === 0 ? 100 : 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Compute a quality gate snapshot from review data. */
|
|
87
|
+
export function computeQualityGate(opts: {
|
|
88
|
+
dimensions: string[]
|
|
89
|
+
findings: Array<{
|
|
90
|
+
dimension: string
|
|
91
|
+
severity: string
|
|
92
|
+
file: string
|
|
93
|
+
line?: number
|
|
94
|
+
}>
|
|
95
|
+
validationResults?: Array<{
|
|
96
|
+
command: string
|
|
97
|
+
exitCode: number
|
|
98
|
+
}>
|
|
99
|
+
/** Per-dimension sequence of NEW-finding counts across rounds, newest last. */
|
|
100
|
+
findingsByRound?: Record<string, number[]>
|
|
101
|
+
/** Per-dimension count of findings already fixed this iteration. */
|
|
102
|
+
fixedByDimension?: Record<string, number>
|
|
103
|
+
}): QualityGateSnapshot {
|
|
104
|
+
const { dimensions, findings, validationResults, findingsByRound, fixedByDimension } = opts
|
|
105
|
+
|
|
106
|
+
// Count findings by severity
|
|
107
|
+
const criticalCount = findings.filter((f) => f.severity === 'critical').length
|
|
108
|
+
const highCount = findings.filter((f) => f.severity === 'high').length
|
|
109
|
+
const mediumCount = findings.filter((f) => f.severity === 'medium').length
|
|
110
|
+
const lowCount = findings.filter((f) => f.severity === 'low').length
|
|
111
|
+
const totalFindings = findings.length
|
|
112
|
+
|
|
113
|
+
// Compute dimension scores
|
|
114
|
+
const dimensionStats: Record<string, { count: number; critical: number; high: number; medium: number; low: number }> = {}
|
|
115
|
+
for (const dim of dimensions) {
|
|
116
|
+
dimensionStats[dim] = { count: 0, critical: 0, high: 0, medium: 0, low: 0 }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const finding of findings) {
|
|
120
|
+
const stats = dimensionStats[finding.dimension]
|
|
121
|
+
if (stats) {
|
|
122
|
+
stats.count++
|
|
123
|
+
if (finding.severity === 'critical') stats.critical++
|
|
124
|
+
else if (finding.severity === 'high') stats.high++
|
|
125
|
+
else if (finding.severity === 'medium') stats.medium++
|
|
126
|
+
else stats.low++
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Compute dimension-level quality gates
|
|
131
|
+
const dimensionGates: QualityGateDimension[] = dimensions.map((dim) => {
|
|
132
|
+
const stats = dimensionStats[dim] || { count: 0, critical: 0, high: 0, medium: 0, low: 0 }
|
|
133
|
+
// Score: 100 - (critical*30 + high*15 + medium*5 + low*1), capped at 0
|
|
134
|
+
const penalty = stats.critical * 30 + stats.high * 15 + stats.medium * 5 + stats.low * 1
|
|
135
|
+
const score = Math.max(0, 100 - penalty)
|
|
136
|
+
const status: 'pass' | 'warn' | 'fail' = score >= 80 ? 'pass' : score >= 50 ? 'warn' : 'fail'
|
|
137
|
+
const series = findingsByRound?.[dim]
|
|
138
|
+
const convergenceRate = convergenceRateFor(Array.isArray(series) ? series : undefined, stats.count)
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
dimension: dim,
|
|
142
|
+
convergenceRate,
|
|
143
|
+
findingsCount: stats.count,
|
|
144
|
+
fixedCount: fixedByDimension?.[dim] ?? 0,
|
|
145
|
+
score,
|
|
146
|
+
status,
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Compute verification pass rate
|
|
151
|
+
const totalChecks = validationResults?.length ?? 0
|
|
152
|
+
const passedChecks = validationResults?.filter((r) => r.exitCode === 0).length ?? 0
|
|
153
|
+
const failedChecks = totalChecks - passedChecks
|
|
154
|
+
const verificationPassRate = totalChecks > 0 ? Math.round((passedChecks / totalChecks) * 100) : 0
|
|
155
|
+
|
|
156
|
+
// Compute overall score (weighted average of dimension scores)
|
|
157
|
+
const overallScore = dimensionGates.length > 0
|
|
158
|
+
? Math.round(dimensionGates.reduce((sum, d) => sum + d.score, 0) / dimensionGates.length)
|
|
159
|
+
: 0
|
|
160
|
+
|
|
161
|
+
// Determine overall status
|
|
162
|
+
const hasCritical = criticalCount > 0
|
|
163
|
+
const hasHighFail = dimensionGates.some((d) => d.status === 'fail')
|
|
164
|
+
const verificationFails = totalChecks > 0 && failedChecks > 0
|
|
165
|
+
|
|
166
|
+
let overallStatus: 'pass' | 'fail' | 'pending' = 'pass'
|
|
167
|
+
let failReason: string | undefined
|
|
168
|
+
|
|
169
|
+
if (hasCritical) {
|
|
170
|
+
overallStatus = 'fail'
|
|
171
|
+
failReason = `${criticalCount} critical findings present`
|
|
172
|
+
} else if (hasHighFail) {
|
|
173
|
+
overallStatus = 'fail'
|
|
174
|
+
failReason = 'One or more dimensions failed quality gate'
|
|
175
|
+
} else if (verificationFails) {
|
|
176
|
+
overallStatus = 'fail'
|
|
177
|
+
failReason = `${failedChecks} validation checks failed`
|
|
178
|
+
} else if (overallScore < 70) {
|
|
179
|
+
overallStatus = 'fail'
|
|
180
|
+
failReason = `Overall score ${overallScore} below threshold (70)`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
timestamp: new Date().toISOString(),
|
|
185
|
+
overallStatus,
|
|
186
|
+
overallScore,
|
|
187
|
+
dimensions: dimensionGates,
|
|
188
|
+
verificationPassRate,
|
|
189
|
+
totalChecks,
|
|
190
|
+
passedChecks,
|
|
191
|
+
failedChecks,
|
|
192
|
+
failReason,
|
|
193
|
+
totalFindings,
|
|
194
|
+
criticalCount,
|
|
195
|
+
highCount,
|
|
196
|
+
mediumCount,
|
|
197
|
+
lowCount,
|
|
198
|
+
}
|
|
199
|
+
}
|
package/src/tools/review.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
2
|
-
import type { JsonValue } from '@deepseek-ai/dsh-
|
|
2
|
+
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
|
|
3
3
|
import { loadEffectiveConfig, resolveProjectRootForExec } from '../config-loader.ts'
|
|
4
4
|
import { runWithJob } from '../jobs.ts'
|
|
5
5
|
import {
|
package/src/tools/transcript.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
22
22
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
23
23
|
import { existsSync } from 'node:fs'
|
|
24
24
|
import { dirname } from 'node:path'
|
|
25
|
-
import type { JsonValue } from '@deepseek-ai/dsh-
|
|
25
|
+
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
|
|
26
26
|
import {
|
|
27
27
|
loadEffectiveConfig,
|
|
28
28
|
resolveProjectRootForExec,
|
package/src/tools/triage.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
4
|
-
import type { JsonValue } from '@deepseek-ai/dsh-
|
|
4
|
+
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
|
|
5
5
|
import yaml from 'js-yaml'
|
|
6
6
|
import { resolveProjectRootForExec } from '../config-loader.ts'
|
|
7
7
|
import type { KnownIntentional } from '../types.ts'
|
package/src/types.ts
CHANGED
|
@@ -230,6 +230,20 @@ export interface IterationStatus {
|
|
|
230
230
|
resumeCount: number
|
|
231
231
|
checkpoint: IterationCheckpoint | null
|
|
232
232
|
lastUpdated: string | null
|
|
233
|
+
/** v3.0: Quality gate snapshot */
|
|
234
|
+
qualityGate?: QualityGateSnapshot
|
|
235
|
+
/** v3.0: Experience bank summary */
|
|
236
|
+
experienceBank?: {
|
|
237
|
+
totalEntries: number
|
|
238
|
+
totalHits: number
|
|
239
|
+
}
|
|
240
|
+
/** v3.0: Defense events summary */
|
|
241
|
+
defenseEvents?: {
|
|
242
|
+
totalEvents: number
|
|
243
|
+
counts: Record<DefenseEventType, number>
|
|
244
|
+
}
|
|
245
|
+
/** v3.0: task_mode from harness */
|
|
246
|
+
taskMode?: 'code' | 'iterate' | null
|
|
233
247
|
}
|
|
234
248
|
|
|
235
249
|
/** ─── Runtime observatory (transcript) ───────────────────────────────────── */
|
|
@@ -331,4 +345,108 @@ export interface TranscriptManifest {
|
|
|
331
345
|
active: boolean
|
|
332
346
|
policy: 'ask' | 'deny' | 'allow'
|
|
333
347
|
}
|
|
348
|
+
/** v3.0: task_mode indicator from harness status */
|
|
349
|
+
taskMode?: 'code' | 'iterate' | null
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ─── v3.0: Quality Gate ──────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
/** A single dimension's quality gate status. */
|
|
355
|
+
export interface QualityGateDimension {
|
|
356
|
+
dimension: string
|
|
357
|
+
convergenceRate: number
|
|
358
|
+
findingsCount: number
|
|
359
|
+
fixedCount: number
|
|
360
|
+
/** 0-100 score based on findings severity and count */
|
|
361
|
+
score: number
|
|
362
|
+
status: 'pass' | 'warn' | 'fail'
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Quality gate snapshot for the current iteration. */
|
|
366
|
+
export interface QualityGateSnapshot {
|
|
367
|
+
timestamp: string
|
|
368
|
+
overallStatus: 'pass' | 'fail' | 'pending'
|
|
369
|
+
overallScore: number
|
|
370
|
+
dimensions: QualityGateDimension[]
|
|
371
|
+
verificationPassRate: number
|
|
372
|
+
totalChecks: number
|
|
373
|
+
passedChecks: number
|
|
374
|
+
failedChecks: number
|
|
375
|
+
/** Reason for overall FAIL status, if applicable */
|
|
376
|
+
failReason?: string
|
|
377
|
+
/** Total findings across all dimensions */
|
|
378
|
+
totalFindings: number
|
|
379
|
+
/** Findings by severity */
|
|
380
|
+
criticalCount: number
|
|
381
|
+
highCount: number
|
|
382
|
+
mediumCount: number
|
|
383
|
+
lowCount: number
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ─── v3.0: Experience Bank ───────────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
/** A single experience entry in the experience bank. */
|
|
389
|
+
export interface ExperienceEntry {
|
|
390
|
+
id: string
|
|
391
|
+
timestamp: string
|
|
392
|
+
dimension: string
|
|
393
|
+
pattern: string
|
|
394
|
+
description: string
|
|
395
|
+
/** The fix that was applied and verified */
|
|
396
|
+
verifiedFix: string
|
|
397
|
+
/** Files involved in this experience */
|
|
398
|
+
files: string[]
|
|
399
|
+
/** How many times this pattern has been encountered */
|
|
400
|
+
hitCount: number
|
|
401
|
+
/** Last time this experience was hit */
|
|
402
|
+
lastHitAt?: string
|
|
403
|
+
/** Tags for categorization */
|
|
404
|
+
tags: string[]
|
|
405
|
+
/** Related finding summary */
|
|
406
|
+
findingSummary: string
|
|
407
|
+
/** Severity of the original finding */
|
|
408
|
+
severity: 'critical' | 'high' | 'medium' | 'low'
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Experience bank state for the project. */
|
|
412
|
+
export interface ExperienceBank {
|
|
413
|
+
entries: ExperienceEntry[]
|
|
414
|
+
lastUpdated: string
|
|
415
|
+
totalHits: number
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ─── v3.0: Defense Events ────────────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
/** Defense event types */
|
|
421
|
+
export type DefenseEventType =
|
|
422
|
+
| 'precondition_failed'
|
|
423
|
+
| 'rollback'
|
|
424
|
+
| 'invariant_violated'
|
|
425
|
+
| 'assumption_falsified'
|
|
426
|
+
|
|
427
|
+
/** A single defense event recorded during iteration. */
|
|
428
|
+
export interface DefenseEvent {
|
|
429
|
+
id: string
|
|
430
|
+
timestamp: string
|
|
431
|
+
round: number
|
|
432
|
+
type: DefenseEventType
|
|
433
|
+
/** What was being checked */
|
|
434
|
+
description: string
|
|
435
|
+
/** The defense that was triggered */
|
|
436
|
+
defense: string
|
|
437
|
+
/** Outcome: what was protected against */
|
|
438
|
+
outcome: string
|
|
439
|
+
/** Optional file/location context */
|
|
440
|
+
file?: string
|
|
441
|
+
line?: number
|
|
442
|
+
/** Severity of the event */
|
|
443
|
+
severity: 'critical' | 'high' | 'medium' | 'low'
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Defense events stream for the current iteration. */
|
|
447
|
+
export interface DefenseEventStream {
|
|
448
|
+
events: DefenseEvent[]
|
|
449
|
+
lastUpdated: string
|
|
450
|
+
/** Summary counts by type */
|
|
451
|
+
counts: Record<DefenseEventType, number>
|
|
334
452
|
}
|