iterate-plugin 2.12.2 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +28 -12
  2. package/README.zh-CN.md +2 -1
  3. package/dist/approval-gate.js +16 -2
  4. package/dist/config-loader.js +5 -0
  5. package/dist/git-scope.js +61 -7
  6. package/dist/index.js +16 -6
  7. package/dist/session-hooks.js +36 -11
  8. package/dist/skill-prompt.js +3 -0
  9. package/dist/tools/decision-log.js +10 -1
  10. package/dist/tools/defense-events.js +260 -0
  11. package/dist/tools/defense-store.js +97 -0
  12. package/dist/tools/experience-bank.js +248 -0
  13. package/dist/tools/experience-store.js +132 -0
  14. package/dist/tools/quality-gate.js +180 -0
  15. package/dist/tools/quality-store.js +174 -0
  16. package/lib/client.js +662 -103
  17. package/lib/parse.js +93 -0
  18. package/package.json +7 -6
  19. package/src/approval-gate.ts +14 -2
  20. package/src/client/index.ts +542 -49
  21. package/src/config-loader.ts +5 -0
  22. package/src/git-scope.ts +48 -7
  23. package/src/index.ts +16 -6
  24. package/src/session-hooks.ts +33 -11
  25. package/src/skill-prompt.ts +3 -0
  26. package/src/tools/checkpoint.ts +1 -1
  27. package/src/tools/config.ts +1 -1
  28. package/src/tools/decision-log.ts +11 -2
  29. package/src/tools/defense-events.ts +295 -0
  30. package/src/tools/defense-store.ts +113 -0
  31. package/src/tools/experience-bank.ts +264 -0
  32. package/src/tools/experience-store.ts +160 -0
  33. package/src/tools/fix.ts +1 -1
  34. package/src/tools/history.ts +1 -1
  35. package/src/tools/prune.ts +1 -1
  36. package/src/tools/quality-gate.ts +193 -0
  37. package/src/tools/quality-store.ts +199 -0
  38. package/src/tools/review.ts +1 -1
  39. package/src/tools/transcript.ts +1 -1
  40. package/src/tools/triage.ts +1 -1
  41. package/src/types.ts +118 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * src/tools/defense-store.ts — defense event storage layer.
3
+ *
4
+ * Provides read/write access to defense events stored in
5
+ * .iterate/defense-events.json. Events are accumulated during iteration.
6
+ */
7
+
8
+ import * as fs from 'node:fs'
9
+ import * as path from 'node:path'
10
+ import type { DefenseEvent, DefenseEventStream, DefenseEventType } from '../types.ts'
11
+
12
+ const DEFENSE_EVENTS_FILE = 'defense-events.json'
13
+
14
+ /** Valid defense event types (must stay in sync with DefenseEventType). */
15
+ const VALID_EVENT_TYPES: ReadonlySet<DefenseEventType> = new Set<DefenseEventType>([
16
+ 'precondition_failed',
17
+ 'rollback',
18
+ 'invariant_violated',
19
+ 'assumption_falsified',
20
+ ])
21
+
22
+ /**
23
+ * Bump the count for an event type. Unknown types (malformed JSON on disk,
24
+ * or a caller passing an untyped value) are ignored rather than crashing or
25
+ * creating garbage keys in the counts object.
26
+ */
27
+ function bumpCount(counts: Record<DefenseEventType, number>, type: unknown): void {
28
+ if (typeof type === 'string' && VALID_EVENT_TYPES.has(type as DefenseEventType)) {
29
+ counts[type as DefenseEventType]++
30
+ }
31
+ }
32
+
33
+ /** Default empty defense event stream. */
34
+ function emptyStream(): DefenseEventStream {
35
+ return {
36
+ events: [],
37
+ lastUpdated: new Date().toISOString(),
38
+ counts: {
39
+ precondition_failed: 0,
40
+ rollback: 0,
41
+ invariant_violated: 0,
42
+ assumption_falsified: 0,
43
+ },
44
+ }
45
+ }
46
+
47
+ /** Read the defense events stream from disk. */
48
+ export function readDefenseEvents(projectRoot: string): DefenseEventStream {
49
+ const filePath = path.join(projectRoot, '.iterate', DEFENSE_EVENTS_FILE)
50
+ try {
51
+ const content = fs.readFileSync(filePath, 'utf-8')
52
+ const parsed = JSON.parse(content) as DefenseEventStream
53
+ if (parsed && Array.isArray(parsed.events)) {
54
+ return parsed
55
+ }
56
+ } catch {
57
+ // File not found or invalid JSON
58
+ }
59
+ return emptyStream()
60
+ }
61
+
62
+ /** Write the defense events stream to disk. */
63
+ export function writeDefenseEvents(projectRoot: string, stream: DefenseEventStream): void {
64
+ const dirPath = path.join(projectRoot, '.iterate')
65
+ const filePath = path.join(dirPath, DEFENSE_EVENTS_FILE)
66
+
67
+ try {
68
+ if (!fs.existsSync(dirPath)) {
69
+ fs.mkdirSync(dirPath, { recursive: true })
70
+ }
71
+ fs.writeFileSync(filePath, JSON.stringify(stream, null, 2), 'utf-8')
72
+ } catch {
73
+ // Silently fail - defense events are not critical
74
+ }
75
+ }
76
+
77
+ /** Add a defense event to the stream. */
78
+ export function addDefenseEvent(
79
+ stream: DefenseEventStream,
80
+ event: Omit<DefenseEvent, 'id' | 'timestamp'>
81
+ ): DefenseEventStream {
82
+ const id = `def-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
83
+ const newEvent: DefenseEvent = {
84
+ id,
85
+ timestamp: new Date().toISOString(),
86
+ ...event,
87
+ }
88
+
89
+ const newCounts = { ...stream.counts }
90
+ bumpCount(newCounts, event.type)
91
+
92
+ return {
93
+ events: [...stream.events, newEvent],
94
+ lastUpdated: new Date().toISOString(),
95
+ counts: newCounts,
96
+ }
97
+ }
98
+
99
+ /** Compute counts from events array (for consistency). */
100
+ export function computeCounts(events: DefenseEvent[]): Record<DefenseEventType, number> {
101
+ const counts: Record<DefenseEventType, number> = {
102
+ precondition_failed: 0,
103
+ rollback: 0,
104
+ invariant_violated: 0,
105
+ assumption_falsified: 0,
106
+ }
107
+
108
+ for (const event of events) {
109
+ bumpCount(counts, event.type)
110
+ }
111
+
112
+ return counts
113
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * src/tools/experience-bank.ts — experience bank query tool.
3
+ *
4
+ * iterate_experience — browse, search, query, and add project experience entries.
5
+ *
6
+ * Experiences are accumulated across sessions and stored in .iterate/experience.json.
7
+ */
8
+
9
+ import { defineTool } from '@deepseek-ai/dsh-tools'
10
+ import type { JsonValue } from '@deepseek-ai/dsh-util-values'
11
+ import { resolveProjectRootForExec } from '../config-loader.ts'
12
+ import { readExperienceBank, writeExperienceBank, searchExperienceEntries, upsertExperience } from './experience-store.ts'
13
+ import type { ExperienceEntryInput } from './experience-store.ts'
14
+ import type { ExperienceEntry } from '../types.ts'
15
+
16
+ const DEFAULT_LIMIT = 50
17
+ const MAX_LIMIT = 100
18
+
19
+ /** Clamp a caller-supplied limit to a sane range. */
20
+ function clampLimit(limit: number | undefined): number {
21
+ if (typeof limit !== 'number' || !Number.isInteger(limit) || limit <= 0) {
22
+ return DEFAULT_LIMIT
23
+ }
24
+ return Math.min(limit, MAX_LIMIT)
25
+ }
26
+
27
+ /** Validate a caller-supplied experience entry object. Returns error strings. */
28
+ function validateExperienceInput(raw: unknown): string[] {
29
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
30
+ return ['entry must be a JSON object']
31
+ }
32
+ const e = raw as Record<string, unknown>
33
+ const errors: string[] = []
34
+ if (typeof e.pattern !== 'string' || !e.pattern.trim()) errors.push('.pattern is required')
35
+ if (typeof e.dimension !== 'string' || !e.dimension.trim()) errors.push('.dimension is required')
36
+ if (typeof e.description !== 'string' || !e.description.trim()) errors.push('.description is required')
37
+ if (typeof e.verifiedFix !== 'string' || !e.verifiedFix.trim()) errors.push('.verifiedFix is required')
38
+ if (typeof e.findingSummary !== 'string' || !e.findingSummary.trim()) errors.push('.findingSummary is required')
39
+ const severity = e.severity
40
+ if (severity !== 'critical' && severity !== 'high' && severity !== 'medium' && severity !== 'low') {
41
+ errors.push('.severity must be one of critical, high, medium, low')
42
+ }
43
+ if (!Array.isArray(e.files) || !e.files.every((f) => typeof f === 'string' && f.length > 0)) {
44
+ errors.push('.files must be an array of non-empty strings')
45
+ }
46
+ if (!Array.isArray(e.tags) || !e.tags.every((t) => typeof t === 'string')) {
47
+ errors.push('.tags must be an array of strings')
48
+ }
49
+ return errors
50
+ }
51
+
52
+ /** Normalize a validated raw entry into the store input shape. */
53
+ function normalizeExperienceInput(raw: Record<string, unknown>): ExperienceEntryInput {
54
+ return {
55
+ ...(typeof raw.id === 'string' && raw.id.length > 0 ? { id: raw.id } : {}),
56
+ pattern: raw.pattern as string,
57
+ description: raw.description as string,
58
+ verifiedFix: raw.verifiedFix as string,
59
+ dimension: raw.dimension as string,
60
+ findingSummary: raw.findingSummary as string,
61
+ severity: raw.severity as ExperienceEntryInput['severity'],
62
+ files: raw.files as string[],
63
+ tags: raw.tags as string[],
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Register the `iterate_experience` tool.
69
+ * Queries the experience bank for historical fixes and patterns.
70
+ */
71
+ export function registerExperienceBankTool(ctx: { tools: { register: (def: ReturnType<typeof defineTool>) => void } }): void {
72
+ ctx.tools.register(
73
+ defineTool({
74
+ name: 'iterate_experience',
75
+ description:
76
+ 'Query or extend the experience bank: browse/search historical fixes and patterns, ' +
77
+ 'or record a new verified fix (operation:"add"). ' +
78
+ 'List/search/get return matching entries with hit counts, verified fixes, and related context. ' +
79
+ '"add" upserts an experience entry into .iterate/experience.json — a repeat of the same ' +
80
+ 'pattern+dimension increments its hit count instead of duplicating it. ' +
81
+ 'Use it to remember fixes that worked so future rounds apply them first.',
82
+ parameters: {
83
+ operation: {
84
+ type: 'string',
85
+ description: 'Operation: list (browse all), search (by query), get (by id), add (add a new experience). Default: list.',
86
+ enum: ['list', 'search', 'get', 'add'],
87
+ },
88
+ query: {
89
+ type: 'string',
90
+ description: 'Search query (for search operation). Matches against pattern, description, files, tags.',
91
+ },
92
+ dimension: {
93
+ type: 'string',
94
+ description: 'Filter by dimension (e.g., correctness, security, performance).',
95
+ },
96
+ tags: {
97
+ type: 'array',
98
+ items: { type: 'string' },
99
+ description: 'Filter by tags (AND logic).',
100
+ },
101
+ id: {
102
+ type: 'string',
103
+ description: 'Experience ID (for get operation, or to update a specific entry via add).',
104
+ },
105
+ entry: {
106
+ type: 'json',
107
+ description:
108
+ 'Experience entry object (required for add). Fields: id (optional), pattern, dimension, description, ' +
109
+ 'verifiedFix, findingSummary, severity (critical|high|medium|low), files (string[]), tags (string[]).',
110
+ },
111
+ limit: {
112
+ type: 'integer',
113
+ description: `Max entries to return (default: ${DEFAULT_LIMIT}, cap: ${MAX_LIMIT}).`,
114
+ },
115
+ path: {
116
+ type: 'string',
117
+ description: 'Project root directory (default: current working directory).',
118
+ },
119
+ },
120
+
121
+ output: {
122
+ schema: {
123
+ type: 'object',
124
+ additionalProperties: false,
125
+ properties: {
126
+ ok: { type: 'boolean', required: true },
127
+ kind: { type: 'string' },
128
+ operation: { type: 'string' },
129
+ count: { type: 'integer' },
130
+ entries: { type: 'json' },
131
+ entry: { type: 'json' },
132
+ totalHits: { type: 'integer' },
133
+ added: { type: 'boolean' },
134
+ errors: { type: 'json' },
135
+ error: { type: 'string' },
136
+ },
137
+ },
138
+ render: (_args, value) => {
139
+ if (!value.ok) return [{ type: 'text', text: `experience query failed: ${value.error}` }]
140
+ if (value.operation === 'add' && value.entry) {
141
+ const entry = value.entry as unknown as ExperienceEntry
142
+ return [{ type: 'text', text: [
143
+ value.added
144
+ ? `Recorded new experience: ${entry.id}`
145
+ : `Experience already known (hit ${entry.hitCount}): ${entry.id}`,
146
+ `Pattern: ${entry.pattern}`,
147
+ `Dimension: ${entry.dimension}`,
148
+ `Description: ${entry.description}`,
149
+ `Fix: ${entry.verifiedFix}`,
150
+ `Files: ${entry.files.join(', ')}`,
151
+ `Tags: ${entry.tags.join(', ')}`,
152
+ ].join('\n') }]
153
+ }
154
+ if (value.operation === 'get' && value.entry) {
155
+ const entry = value.entry as unknown as ExperienceEntry
156
+ return [{ type: 'text', text: [
157
+ `Experience: ${entry.id}`,
158
+ `Pattern: ${entry.pattern}`,
159
+ `Description: ${entry.description}`,
160
+ `Fix: ${entry.verifiedFix}`,
161
+ `Files: ${entry.files.join(', ')}`,
162
+ `Hits: ${entry.hitCount}`,
163
+ `Tags: ${entry.tags.join(', ')}`,
164
+ ].join('\n') }]
165
+ }
166
+ const entries = (value.entries as unknown as ExperienceEntry[] | undefined) ?? []
167
+ const lines = [
168
+ `Found ${value.count} experience(s) (total hits: ${value.totalHits})`,
169
+ '',
170
+ ...entries.map((e) => `[${e.id}] ${e.pattern} (hits: ${e.hitCount}) - ${e.description}`),
171
+ ]
172
+ return [{ type: 'text', text: lines.join('\n') }]
173
+ },
174
+ },
175
+
176
+ async execute(args, exec) {
177
+ const resolved = resolveProjectRootForExec(exec, args.path)
178
+ if (!resolved.ok) return { ok: false, kind: 'experience', error: resolved.reason }
179
+ const projectRoot = resolved.root
180
+
181
+ const operation = typeof args.operation === 'string' ? args.operation : 'list'
182
+ const limit = clampLimit(args.limit as number | undefined)
183
+
184
+ if (operation === 'add') {
185
+ const raw = args.entry
186
+ const errors = validateExperienceInput(raw)
187
+ if (errors.length > 0) {
188
+ return {
189
+ ok: false,
190
+ kind: 'experience',
191
+ operation: 'add',
192
+ errors: errors as unknown as JsonValue,
193
+ error: `Invalid experience entry: ${errors.join('; ')}`,
194
+ }
195
+ }
196
+ const bank = readExperienceBank(projectRoot)
197
+ const { bank: next, added, entryId } = upsertExperience(bank, normalizeExperienceInput(raw as Record<string, unknown>))
198
+ writeExperienceBank(projectRoot, next)
199
+ const entry = next.entries.find((e) => e.id === entryId)
200
+ return {
201
+ ok: true,
202
+ kind: 'experience',
203
+ operation: 'add',
204
+ added,
205
+ count: next.entries.length,
206
+ entry: entry as unknown as JsonValue,
207
+ totalHits: next.totalHits,
208
+ }
209
+ }
210
+
211
+ const bank = readExperienceBank(projectRoot)
212
+
213
+ if (operation === 'get' && typeof args.id === 'string') {
214
+ const entry = bank.entries.find((e) => e.id === args.id)
215
+ if (!entry) {
216
+ return { ok: false, kind: 'experience', error: `Experience not found: ${args.id}` }
217
+ }
218
+ return {
219
+ ok: true,
220
+ kind: 'experience',
221
+ operation: 'get',
222
+ count: 1,
223
+ entry: entry as unknown as JsonValue,
224
+ totalHits: bank.totalHits,
225
+ }
226
+ }
227
+
228
+ if (operation === 'search' && typeof args.query === 'string') {
229
+ const entries = searchExperienceEntries(bank.entries, args.query, {
230
+ dimension: typeof args.dimension === 'string' ? args.dimension : undefined,
231
+ tags: Array.isArray(args.tags) ? args.tags : undefined,
232
+ }).slice(0, limit)
233
+
234
+ return {
235
+ ok: true,
236
+ kind: 'experience',
237
+ operation: 'search',
238
+ count: entries.length,
239
+ entries: entries as unknown as JsonValue,
240
+ totalHits: bank.totalHits,
241
+ }
242
+ }
243
+
244
+ // Default: list with optional filters
245
+ let entries = bank.entries
246
+ if (typeof args.dimension === 'string' && args.dimension) {
247
+ entries = entries.filter((e) => e.dimension === args.dimension)
248
+ }
249
+ if (Array.isArray(args.tags) && args.tags.length > 0) {
250
+ entries = entries.filter((e) => args.tags!.every((t: string) => e.tags.includes(t)))
251
+ }
252
+
253
+ return {
254
+ ok: true,
255
+ kind: 'experience',
256
+ operation: 'list',
257
+ count: Math.min(entries.length, limit),
258
+ entries: entries.slice(0, limit) as unknown as JsonValue,
259
+ totalHits: bank.totalHits,
260
+ }
261
+ },
262
+ }),
263
+ )
264
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * src/tools/experience-store.ts — experience bank storage layer.
3
+ *
4
+ * Provides read/write access to the experience bank stored in
5
+ * .iterate/experience.json. Experiences are accumulated across sessions.
6
+ */
7
+
8
+ import * as fs from 'node:fs'
9
+ import * as path from 'node:path'
10
+ import type { ExperienceBank, ExperienceEntry } from '../types.ts'
11
+
12
+ const EXPERIENCE_FILE = 'experience.json'
13
+
14
+ /** Default empty experience bank. */
15
+ function emptyBank(): ExperienceBank {
16
+ return {
17
+ entries: [],
18
+ lastUpdated: new Date().toISOString(),
19
+ totalHits: 0,
20
+ }
21
+ }
22
+
23
+ /** Read the experience bank from disk. Returns empty bank if not found. */
24
+ export function readExperienceBank(projectRoot: string): ExperienceBank {
25
+ const filePath = path.join(projectRoot, '.iterate', EXPERIENCE_FILE)
26
+ try {
27
+ const content = fs.readFileSync(filePath, 'utf-8')
28
+ const parsed = JSON.parse(content) as ExperienceBank
29
+ if (parsed && Array.isArray(parsed.entries)) {
30
+ return parsed
31
+ }
32
+ } catch {
33
+ // File not found or invalid JSON
34
+ }
35
+ return emptyBank()
36
+ }
37
+
38
+ /** Write the experience bank to disk. */
39
+ export function writeExperienceBank(projectRoot: string, bank: ExperienceBank): void {
40
+ const dirPath = path.join(projectRoot, '.iterate')
41
+ const filePath = path.join(dirPath, EXPERIENCE_FILE)
42
+
43
+ try {
44
+ if (!fs.existsSync(dirPath)) {
45
+ fs.mkdirSync(dirPath, { recursive: true })
46
+ }
47
+ fs.writeFileSync(filePath, JSON.stringify(bank, null, 2), 'utf-8')
48
+ } catch {
49
+ // Silently fail - experience bank is not critical
50
+ }
51
+ }
52
+
53
+ /** Search experience entries by query string. */
54
+ export function searchExperienceEntries(
55
+ entries: ExperienceEntry[],
56
+ query: string,
57
+ opts: { dimension?: string; tags?: string[] } = {},
58
+ ): ExperienceEntry[] {
59
+ const lowerQuery = query.toLowerCase()
60
+
61
+ return entries.filter((entry) => {
62
+ // Dimension filter
63
+ if (opts.dimension && entry.dimension !== opts.dimension) {
64
+ return false
65
+ }
66
+
67
+ // Tags filter (AND logic)
68
+ if (opts.tags && opts.tags.length > 0) {
69
+ if (!opts.tags.every((t) => entry.tags.includes(t))) {
70
+ return false
71
+ }
72
+ }
73
+
74
+ // Text search across multiple fields
75
+ if (query) {
76
+ const searchableText = [
77
+ entry.pattern,
78
+ entry.description,
79
+ entry.verifiedFix,
80
+ entry.findingSummary,
81
+ entry.dimension,
82
+ ...entry.files,
83
+ ...entry.tags,
84
+ ].join(' ').toLowerCase()
85
+
86
+ if (!searchableText.includes(lowerQuery)) {
87
+ return false
88
+ }
89
+ }
90
+
91
+ return true
92
+ })
93
+ }
94
+
95
+ /** Fields the caller may supply when adding/updating an experience entry. */
96
+ export type ExperienceEntryInput = Omit<
97
+ ExperienceEntry,
98
+ 'id' | 'timestamp' | 'hitCount' | 'lastHitAt'
99
+ > & { id?: string }
100
+
101
+ /**
102
+ * Add or update an experience entry.
103
+ *
104
+ * An entry with an `id` that already exists, OR a new entry whose
105
+ * `pattern`+`dimension` pair matches an existing entry, is treated as a HIT:
106
+ * the matching entry's hitCount is incremented (lastHitAt refreshed) so
107
+ * repeated encounters of the same pattern do not create duplicates. Otherwise
108
+ * a fresh entry is appended with hitCount 1. Never mutates the input bank.
109
+ *
110
+ * Returns the resulting bank plus whether a NEW entry was created and the id
111
+ * of the affected entry.
112
+ */
113
+ export function upsertExperience(
114
+ bank: ExperienceBank,
115
+ entry: ExperienceEntryInput
116
+ ): { bank: ExperienceBank; added: boolean; entryId: string } {
117
+ const lastUpdated = new Date().toISOString()
118
+ const existing = entry.id
119
+ ? bank.entries.find((e) => e.id === entry.id)
120
+ : bank.entries.find((e) => e.pattern === entry.pattern && e.dimension === entry.dimension)
121
+
122
+ if (existing) {
123
+ const updated: ExperienceEntry = {
124
+ ...existing,
125
+ hitCount: (existing.hitCount ?? 0) + 1,
126
+ lastHitAt: lastUpdated,
127
+ }
128
+ return {
129
+ bank: {
130
+ ...bank,
131
+ entries: bank.entries.map((e) => (e.id === existing.id ? updated : e)),
132
+ lastUpdated,
133
+ totalHits: (bank.totalHits ?? 0) + 1,
134
+ },
135
+ added: false,
136
+ entryId: existing.id,
137
+ }
138
+ }
139
+
140
+ // Add new entry
141
+ const id = entry.id || `exp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
142
+ const newEntry: ExperienceEntry = {
143
+ id,
144
+ timestamp: lastUpdated,
145
+ hitCount: 1,
146
+ lastHitAt: lastUpdated,
147
+ ...entry,
148
+ }
149
+
150
+ return {
151
+ bank: {
152
+ ...bank,
153
+ entries: [...bank.entries, newEntry],
154
+ lastUpdated,
155
+ totalHits: (bank.totalHits ?? 0) + 1,
156
+ },
157
+ added: true,
158
+ entryId: id,
159
+ }
160
+ }
package/src/tools/fix.ts CHANGED
@@ -20,7 +20,7 @@
20
20
  import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
21
21
  import { join, sep } from 'node:path'
22
22
  import { defineTool } from '@deepseek-ai/dsh-tools'
23
- import type { JsonValue } from '@deepseek-ai/dsh-session'
23
+ import type { JsonValue } from '@deepseek-ai/dsh-util-values'
24
24
  import { loadEffectiveConfig, resolveProjectRootForExec } from '../config-loader.ts'
25
25
  import { runWithJob } from '../jobs.ts'
26
26
  import { countTouchedMethods } from '../method-scope.ts'
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { defineTool } from '@deepseek-ai/dsh-tools'
12
- import type { JsonValue } from '@deepseek-ai/dsh-session'
12
+ import type { JsonValue } from '@deepseek-ai/dsh-util-values'
13
13
  import { resolveProjectRootForExec } from '../config-loader.ts'
14
14
  import { readDecisionEntries } from './decision-log.ts'
15
15
  import { readRegistry } from './fix.ts'
@@ -21,7 +21,7 @@
21
21
  import { existsSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from 'node:fs'
22
22
  import { join } from 'node:path'
23
23
  import { defineTool } from '@deepseek-ai/dsh-tools'
24
- import type { JsonValue } from '@deepseek-ai/dsh-session'
24
+ import type { JsonValue } from '@deepseek-ai/dsh-util-values'
25
25
  import { resolveProjectRootForExec } from '../config-loader.ts'
26
26
  import { readDecisionEntries, appendDecisionEntry } from './decision-log.ts'
27
27
  import { readRegistry, removeRecord, recomputeRoundCounts } from './fix.ts'