@kkelly-offical/kkcode 0.1.7 → 0.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/LICENSE +674 -674
- package/README.md +452 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +228 -220
- package/src/agent/custom-agent-loader.mjs +6 -3
- package/src/agent/generator.mjs +2 -2
- package/src/agent/prompt/assistant.txt +12 -0
- package/src/agent/prompt/bug-hunter.txt +89 -89
- package/src/agent/prompt/frontend-designer.txt +58 -58
- package/src/agent/prompt/guide.txt +1 -1
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
- package/src/agent/prompt/longagent-coding-agent.txt +37 -37
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
- package/src/agent/prompt/longagent-preview-agent.txt +63 -63
- package/src/command/custom-commands.mjs +2 -2
- package/src/commands/agent.mjs +1 -1
- package/src/commands/background.mjs +145 -4
- package/src/commands/chat.mjs +117 -76
- package/src/commands/config.mjs +148 -1
- package/src/commands/doctor.mjs +30 -6
- package/src/commands/init.mjs +32 -6
- package/src/commands/longagent.mjs +117 -0
- package/src/commands/mcp.mjs +275 -43
- package/src/commands/permission.mjs +1 -1
- package/src/commands/session.mjs +195 -140
- package/src/commands/skill.mjs +63 -0
- package/src/commands/theme.mjs +1 -1
- package/src/config/defaults.mjs +280 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +591 -574
- package/src/context.mjs +4 -1
- package/src/core/constants.mjs +97 -91
- package/src/core/types.mjs +1 -1
- package/src/github/api.mjs +78 -78
- package/src/github/auth.mjs +294 -286
- package/src/github/flow.mjs +298 -298
- package/src/github/workspace.mjs +225 -212
- package/src/index.mjs +84 -82
- package/src/knowledge/frontend-aesthetics.txt +38 -38
- package/src/mcp/client-http.mjs +139 -141
- package/src/mcp/client-sse.mjs +297 -288
- package/src/mcp/client-stdio.mjs +534 -533
- package/src/mcp/constants.mjs +2 -2
- package/src/mcp/registry.mjs +498 -479
- package/src/mcp/stdio-framing.mjs +135 -133
- package/src/mcp/tool-result.mjs +24 -24
- package/src/observability/edit-diagnostics.mjs +449 -0
- package/src/observability/index.mjs +42 -42
- package/src/observability/metrics.mjs +165 -137
- package/src/observability/tracer.mjs +137 -137
- package/src/onboarding.mjs +209 -0
- package/src/orchestration/background-manager.mjs +567 -372
- package/src/orchestration/background-worker.mjs +419 -305
- package/src/orchestration/interruption-reason.mjs +21 -0
- package/src/orchestration/longagent-manager.mjs +197 -171
- package/src/orchestration/stage-scheduler.mjs +733 -728
- package/src/orchestration/subagent-router.mjs +7 -1
- package/src/orchestration/task-scheduler.mjs +219 -7
- package/src/permission/engine.mjs +1 -1
- package/src/permission/exec-policy.mjs +370 -370
- package/src/permission/file-edit-policy.mjs +108 -0
- package/src/permission/prompt.mjs +1 -1
- package/src/permission/rules.mjs +116 -7
- package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
- package/src/plugin/hook-bus.mjs +19 -5
- package/src/plugin/manifest-loader.mjs +222 -0
- package/src/provider/anthropic.mjs +396 -390
- package/src/provider/ollama.mjs +7 -1
- package/src/provider/openai.mjs +382 -340
- package/src/provider/retry-policy.mjs +74 -68
- package/src/provider/router.mjs +242 -241
- package/src/provider/sse.mjs +104 -104
- package/src/provider/wizard.mjs +556 -0
- package/src/repl/capability-facade.mjs +30 -0
- package/src/repl/command-surface.mjs +23 -0
- package/src/repl/controller-entry.mjs +40 -0
- package/src/repl/core-shell.mjs +208 -0
- package/src/repl/dialog-router.mjs +87 -0
- package/src/repl/input-engine.mjs +76 -0
- package/src/repl/keymap.mjs +7 -0
- package/src/repl/operator-surface.mjs +15 -0
- package/src/repl/permission-flow.mjs +49 -0
- package/src/repl/runtime-facade.mjs +36 -0
- package/src/repl/slash-router.mjs +62 -0
- package/src/repl/state-store.mjs +29 -0
- package/src/repl/turn-controller.mjs +58 -0
- package/src/repl/verification.mjs +23 -0
- package/src/repl.mjs +3368 -2981
- package/src/rules/load-rules.mjs +3 -3
- package/src/runtime.mjs +1 -1
- package/src/session/agent-transaction.mjs +86 -0
- package/src/session/checkpoint.mjs +302 -302
- package/src/session/compaction.mjs +298 -298
- package/src/session/engine.mjs +417 -232
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1097
- package/src/session/longagent-plan.mjs +376 -365
- package/src/session/longagent-project-memory.mjs +53 -53
- package/src/session/longagent-scaffold.mjs +291 -291
- package/src/session/longagent-task-bus.mjs +138 -54
- package/src/session/longagent-utils.mjs +828 -472
- package/src/session/longagent.mjs +911 -900
- package/src/session/loop.mjs +1005 -930
- package/src/session/prompt/agent.txt +25 -25
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +31 -31
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +196 -195
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -519
- package/src/session/system-prompt.mjs +308 -273
- package/src/session/task-validator.mjs +267 -267
- package/src/session/usability-gates.mjs +2 -2
- package/src/skill/builtin/commit.mjs +64 -64
- package/src/skill/builtin/design.mjs +76 -76
- package/src/skill/generator.mjs +18 -2
- package/src/skill/registry.mjs +642 -390
- package/src/storage/audit-store.mjs +18 -11
- package/src/storage/event-log.mjs +7 -1
- package/src/storage/ghost-commit-store.mjs +243 -245
- package/src/storage/paths.mjs +13 -0
- package/src/theme/default-theme.mjs +1 -1
- package/src/theme/markdown.mjs +4 -0
- package/src/theme/schema.mjs +1 -1
- package/src/theme/status-bar.mjs +162 -158
- package/src/tool/audit-wrapper.mjs +18 -2
- package/src/tool/edit-transaction.mjs +23 -0
- package/src/tool/executor.mjs +26 -1
- package/src/tool/file-read-state.mjs +65 -0
- package/src/tool/git-auto.mjs +526 -526
- package/src/tool/git-full-auto.mjs +487 -478
- package/src/tool/mutation-guard.mjs +54 -0
- package/src/tool/prompt/edit.txt +3 -3
- package/src/tool/prompt/multiedit.txt +1 -0
- package/src/tool/prompt/notebookedit.txt +2 -1
- package/src/tool/prompt/patch.txt +25 -24
- package/src/tool/prompt/read.txt +3 -3
- package/src/tool/prompt/sysinfo.txt +29 -0
- package/src/tool/prompt/task.txt +66 -4
- package/src/tool/prompt/write.txt +2 -2
- package/src/tool/question-prompt.mjs +99 -93
- package/src/tool/registry.mjs +1701 -1343
- package/src/tool/task-tool.mjs +14 -6
- package/src/ui/activity-renderer.mjs +667 -664
- package/src/ui/repl-background-panel.mjs +7 -0
- package/src/ui/repl-capability-panel.mjs +9 -0
- package/src/ui/repl-dashboard.mjs +54 -4
- package/src/ui/repl-help.mjs +110 -0
- package/src/ui/repl-operator-panel.mjs +12 -0
- package/src/ui/repl-route-feedback.mjs +35 -0
- package/src/ui/repl-status-view.mjs +76 -0
- package/src/ui/repl-task-panel.mjs +5 -0
- package/src/ui/repl-transcript-panel.mjs +56 -0
- package/src/ui/repl-turn-summary.mjs +135 -0
- package/src/usage/pricing.mjs +122 -121
- package/src/usage/usage-meter.mjs +1 -0
- package/src/util/git.mjs +562 -519
- package/src/util/template.mjs +6 -1
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { access } from "node:fs/promises"
|
|
2
|
+
import { execFile as execFileCb } from "node:child_process"
|
|
3
|
+
import { promisify } from "node:util"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
|
|
6
|
+
const execFile = promisify(execFileCb)
|
|
7
|
+
|
|
8
|
+
const MUTATION_TOOLS = new Set(["write", "edit", "patch", "multiedit", "notebookedit"])
|
|
9
|
+
const JS_SYNTAX_EXTENSIONS = new Set([".js", ".mjs", ".cjs"])
|
|
10
|
+
const TS_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"])
|
|
11
|
+
|
|
12
|
+
export const EDIT_DIAGNOSTICS_CONTRACT = "kkcode/edit-diagnostics@1"
|
|
13
|
+
export const MUTATION_OBSERVABILITY_CONTRACT = "kkcode/mutation-observability@1"
|
|
14
|
+
|
|
15
|
+
function toArray(value) {
|
|
16
|
+
return Array.isArray(value) ? value : []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function uniq(items) {
|
|
20
|
+
return [...new Set(items.filter(Boolean))]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function pluralize(count, noun) {
|
|
24
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function relativeFile(cwd, filePath) {
|
|
28
|
+
if (!filePath) return ""
|
|
29
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath)
|
|
30
|
+
const relative = path.relative(cwd, absolute)
|
|
31
|
+
return relative && !relative.startsWith("..") ? relative : filePath
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function diagnosticFingerprint(diagnostic = {}) {
|
|
35
|
+
return [
|
|
36
|
+
diagnostic.provider || "",
|
|
37
|
+
diagnostic.file || "",
|
|
38
|
+
diagnostic.code || "",
|
|
39
|
+
diagnostic.severity || "",
|
|
40
|
+
diagnostic.line ?? "",
|
|
41
|
+
diagnostic.column ?? "",
|
|
42
|
+
diagnostic.message || ""
|
|
43
|
+
].join("|")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeDiagnostic(diagnostic = {}, cwd = process.cwd()) {
|
|
47
|
+
const file = diagnostic.file ? relativeFile(cwd, diagnostic.file) : ""
|
|
48
|
+
return {
|
|
49
|
+
provider: String(diagnostic.provider || "unknown"),
|
|
50
|
+
file,
|
|
51
|
+
severity: String(diagnostic.severity || "error"),
|
|
52
|
+
code: diagnostic.code ? String(diagnostic.code) : null,
|
|
53
|
+
message: String(diagnostic.message || "").trim(),
|
|
54
|
+
line: Number.isFinite(diagnostic.line) ? Number(diagnostic.line) : null,
|
|
55
|
+
column: Number.isFinite(diagnostic.column) ? Number(diagnostic.column) : null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sortDiagnostics(diagnostics = []) {
|
|
60
|
+
return [...diagnostics].sort((left, right) => {
|
|
61
|
+
const byFile = String(left.file || "").localeCompare(String(right.file || ""))
|
|
62
|
+
if (byFile !== 0) return byFile
|
|
63
|
+
const byLine = Number(left.line || 0) - Number(right.line || 0)
|
|
64
|
+
if (byLine !== 0) return byLine
|
|
65
|
+
const byColumn = Number(left.column || 0) - Number(right.column || 0)
|
|
66
|
+
if (byColumn !== 0) return byColumn
|
|
67
|
+
return String(left.message || "").localeCompare(String(right.message || ""))
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function summarizeDiagnosticsDelta({ baseline = [], current = [], delta, available, reason = "" }) {
|
|
72
|
+
if (!available) {
|
|
73
|
+
return {
|
|
74
|
+
status: "unavailable",
|
|
75
|
+
text: reason ? `diagnostics unavailable (${reason})` : "diagnostics unavailable"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const addedCount = delta.added.length
|
|
80
|
+
const resolvedCount = delta.resolved.length
|
|
81
|
+
const persistedCount = delta.persisted.length
|
|
82
|
+
const currentCount = current.length
|
|
83
|
+
const baselineCount = baseline.length
|
|
84
|
+
|
|
85
|
+
if (addedCount === 0 && resolvedCount === 0 && currentCount === 0) {
|
|
86
|
+
return {
|
|
87
|
+
status: "clean",
|
|
88
|
+
text: baselineCount === 0
|
|
89
|
+
? "clean (no diagnostics before or after)"
|
|
90
|
+
: "clean (all prior diagnostics resolved)"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (addedCount > 0 && resolvedCount > 0) {
|
|
95
|
+
return {
|
|
96
|
+
status: "mixed",
|
|
97
|
+
text: `introduced ${pluralize(addedCount, "diagnostic")}, resolved ${pluralize(resolvedCount, "diagnostic")}, ${pluralize(persistedCount, "diagnostic")} still present`
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (addedCount > 0) {
|
|
102
|
+
return {
|
|
103
|
+
status: "regressed",
|
|
104
|
+
text: `introduced ${pluralize(addedCount, "diagnostic")}; ${pluralize(persistedCount, "diagnostic")} still present`
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (resolvedCount > 0) {
|
|
109
|
+
return {
|
|
110
|
+
status: "improved",
|
|
111
|
+
text: currentCount === 0
|
|
112
|
+
? `resolved ${pluralize(resolvedCount, "diagnostic")}; workspace is clean`
|
|
113
|
+
: `resolved ${pluralize(resolvedCount, "diagnostic")}; ${pluralize(currentCount, "diagnostic")} remain`
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
status: "unchanged",
|
|
119
|
+
text: currentCount === 0
|
|
120
|
+
? "clean (no diagnostics changed)"
|
|
121
|
+
: `no diagnostic changes (${pluralize(currentCount, "diagnostic")} remain)`
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function diffDiagnostics(baseline = [], current = []) {
|
|
126
|
+
const baselineMap = new Map(baseline.map((item) => [diagnosticFingerprint(item), item]))
|
|
127
|
+
const currentMap = new Map(current.map((item) => [diagnosticFingerprint(item), item]))
|
|
128
|
+
|
|
129
|
+
const added = []
|
|
130
|
+
const persisted = []
|
|
131
|
+
const resolved = []
|
|
132
|
+
|
|
133
|
+
for (const [key, diagnostic] of currentMap) {
|
|
134
|
+
if (baselineMap.has(key)) persisted.push(diagnostic)
|
|
135
|
+
else added.push(diagnostic)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const [key, diagnostic] of baselineMap) {
|
|
139
|
+
if (!currentMap.has(key)) resolved.push(diagnostic)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
added: sortDiagnostics(added),
|
|
144
|
+
persisted: sortDiagnostics(persisted),
|
|
145
|
+
resolved: sortDiagnostics(resolved),
|
|
146
|
+
unchanged: added.length === 0 && resolved.length === 0
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function exists(target) {
|
|
151
|
+
try {
|
|
152
|
+
await access(target)
|
|
153
|
+
return true
|
|
154
|
+
} catch {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createProviderStatus({ name, available, checkedFiles = [], diagnostics = [], reason = "" }) {
|
|
160
|
+
return {
|
|
161
|
+
name,
|
|
162
|
+
available,
|
|
163
|
+
checkedFiles: uniq(checkedFiles),
|
|
164
|
+
diagnostics: diagnostics.length,
|
|
165
|
+
...(reason ? { reason } : {})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function collectNodeSyntaxDiagnostics({ cwd, files }) {
|
|
170
|
+
const checkedFiles = uniq(files.filter((file) => JS_SYNTAX_EXTENSIONS.has(path.extname(file).toLowerCase())))
|
|
171
|
+
if (checkedFiles.length === 0) {
|
|
172
|
+
return {
|
|
173
|
+
diagnostics: [],
|
|
174
|
+
providers: [createProviderStatus({ name: "node-syntax", available: false, reason: "no JavaScript syntax-checkable files" })]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const diagnostics = []
|
|
179
|
+
for (const file of checkedFiles) {
|
|
180
|
+
const absolute = path.resolve(cwd, file)
|
|
181
|
+
try {
|
|
182
|
+
await execFile(process.execPath, ["--check", absolute], {
|
|
183
|
+
cwd,
|
|
184
|
+
timeout: 15000,
|
|
185
|
+
encoding: "utf8"
|
|
186
|
+
})
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const output = String(error?.stderr || error?.stdout || error?.message || "").trim()
|
|
189
|
+
const lineMatch = output.match(/:(\d+)\s*\n/) || output.match(/:(\d+)\s*$/m)
|
|
190
|
+
diagnostics.push(normalizeDiagnostic({
|
|
191
|
+
provider: "node-syntax",
|
|
192
|
+
file,
|
|
193
|
+
severity: "error",
|
|
194
|
+
code: "node-check",
|
|
195
|
+
message: output || "JavaScript syntax error",
|
|
196
|
+
line: lineMatch ? Number(lineMatch[1]) : null,
|
|
197
|
+
column: null
|
|
198
|
+
}, cwd))
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
diagnostics,
|
|
204
|
+
providers: [createProviderStatus({
|
|
205
|
+
name: "node-syntax",
|
|
206
|
+
available: true,
|
|
207
|
+
checkedFiles,
|
|
208
|
+
diagnostics
|
|
209
|
+
})]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseTypeScriptDiagnostics(output, cwd) {
|
|
214
|
+
const diagnostics = []
|
|
215
|
+
const lines = String(output || "").split(/\r?\n/)
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
const match = line.match(/^(.*)\((\d+),(\d+)\): error (TS\d+): (.+)$/)
|
|
218
|
+
|| line.match(/^(.*):(\d+):(\d+) - error (TS\d+): (.+)$/)
|
|
219
|
+
if (!match) continue
|
|
220
|
+
diagnostics.push(normalizeDiagnostic({
|
|
221
|
+
provider: "typescript-project",
|
|
222
|
+
file: match[1],
|
|
223
|
+
severity: "error",
|
|
224
|
+
code: match[4],
|
|
225
|
+
message: match[5],
|
|
226
|
+
line: Number(match[2]),
|
|
227
|
+
column: Number(match[3])
|
|
228
|
+
}, cwd))
|
|
229
|
+
}
|
|
230
|
+
return diagnostics
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function collectTypeScriptDiagnostics({ cwd, files }) {
|
|
234
|
+
const checkedFiles = uniq(files.filter((file) => TS_EXTENSIONS.has(path.extname(file).toLowerCase())))
|
|
235
|
+
if (checkedFiles.length === 0) {
|
|
236
|
+
return {
|
|
237
|
+
diagnostics: [],
|
|
238
|
+
providers: [createProviderStatus({ name: "typescript-project", available: false, reason: "no TypeScript files in edit set" })]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json")
|
|
243
|
+
if (!(await exists(tsconfigPath))) {
|
|
244
|
+
return {
|
|
245
|
+
diagnostics: [],
|
|
246
|
+
providers: [createProviderStatus({ name: "typescript-project", available: false, checkedFiles, reason: "missing tsconfig.json" })]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await execFile("npx", ["tsc", "--noEmit", "--pretty", "false"], {
|
|
252
|
+
cwd,
|
|
253
|
+
timeout: 20000,
|
|
254
|
+
encoding: "utf8"
|
|
255
|
+
})
|
|
256
|
+
return {
|
|
257
|
+
diagnostics: [],
|
|
258
|
+
providers: [createProviderStatus({ name: "typescript-project", available: true, checkedFiles, diagnostics: [] })]
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const output = String(error?.stdout || error?.stderr || error?.message || "").trim()
|
|
262
|
+
const diagnostics = parseTypeScriptDiagnostics(output, cwd)
|
|
263
|
+
return {
|
|
264
|
+
diagnostics,
|
|
265
|
+
providers: [createProviderStatus({
|
|
266
|
+
name: "typescript-project",
|
|
267
|
+
available: true,
|
|
268
|
+
checkedFiles,
|
|
269
|
+
diagnostics,
|
|
270
|
+
reason: diagnostics.length === 0 ? "typecheck failed without parseable diagnostics" : ""
|
|
271
|
+
})]
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function collectDiagnosticsSnapshot({ cwd = process.cwd(), files = [] } = {}) {
|
|
277
|
+
const normalizedFiles = uniq(files.map((file) => relativeFile(cwd, file)))
|
|
278
|
+
const nodeSyntax = await collectNodeSyntaxDiagnostics({ cwd, files: normalizedFiles })
|
|
279
|
+
const typescript = await collectTypeScriptDiagnostics({ cwd, files: normalizedFiles })
|
|
280
|
+
const diagnostics = sortDiagnostics([
|
|
281
|
+
...toArray(nodeSyntax.diagnostics),
|
|
282
|
+
...toArray(typescript.diagnostics)
|
|
283
|
+
])
|
|
284
|
+
const providers = [...toArray(nodeSyntax.providers), ...toArray(typescript.providers)]
|
|
285
|
+
const available = providers.some((provider) => provider.available)
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
files: normalizedFiles,
|
|
289
|
+
diagnostics,
|
|
290
|
+
providers,
|
|
291
|
+
available
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function buildEditDiagnosticsReport({ cwd = process.cwd(), files = [], baseline = {}, current = {}, reason = "" } = {}) {
|
|
296
|
+
const baselineDiagnostics = sortDiagnostics(toArray(baseline.diagnostics || []).map((item) => normalizeDiagnostic(item, cwd)))
|
|
297
|
+
const currentDiagnostics = sortDiagnostics(toArray(current.diagnostics || []).map((item) => normalizeDiagnostic(item, cwd)))
|
|
298
|
+
const delta = diffDiagnostics(baselineDiagnostics, currentDiagnostics)
|
|
299
|
+
const available = Boolean(baseline.available || current.available || baselineDiagnostics.length || currentDiagnostics.length)
|
|
300
|
+
const summary = summarizeDiagnosticsDelta({
|
|
301
|
+
baseline: baselineDiagnostics,
|
|
302
|
+
current: currentDiagnostics,
|
|
303
|
+
delta,
|
|
304
|
+
available,
|
|
305
|
+
reason
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
contract: EDIT_DIAGNOSTICS_CONTRACT,
|
|
310
|
+
files: uniq(files.map((file) => relativeFile(cwd, file))),
|
|
311
|
+
available,
|
|
312
|
+
baseline: {
|
|
313
|
+
count: baselineDiagnostics.length,
|
|
314
|
+
diagnostics: baselineDiagnostics,
|
|
315
|
+
providers: toArray(baseline.providers)
|
|
316
|
+
},
|
|
317
|
+
current: {
|
|
318
|
+
count: currentDiagnostics.length,
|
|
319
|
+
diagnostics: currentDiagnostics,
|
|
320
|
+
providers: toArray(current.providers)
|
|
321
|
+
},
|
|
322
|
+
delta,
|
|
323
|
+
summary
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function normalizeMutationChanges(metadata = {}) {
|
|
328
|
+
if (metadata?.observability?.contract === MUTATION_OBSERVABILITY_CONTRACT) {
|
|
329
|
+
return toArray(metadata.observability.changes)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const fromMutations = toArray(metadata.mutations).map((item) => ({
|
|
333
|
+
path: String(item?.filePath || item?.path || "").trim(),
|
|
334
|
+
operation: String(item?.operation || "multiedit"),
|
|
335
|
+
addedLines: Math.max(0, Number(item?.addedLines || 0)),
|
|
336
|
+
removedLines: Math.max(0, Number(item?.removedLines || 0))
|
|
337
|
+
}))
|
|
338
|
+
|
|
339
|
+
const fromMutation = metadata.mutation
|
|
340
|
+
? [{
|
|
341
|
+
path: String(metadata.mutation.filePath || metadata.mutation.path || "").trim(),
|
|
342
|
+
operation: String(metadata.mutation.operation || "mutation"),
|
|
343
|
+
addedLines: Math.max(0, Number(metadata.mutation.addedLines || 0)),
|
|
344
|
+
removedLines: Math.max(0, Number(metadata.mutation.removedLines || 0))
|
|
345
|
+
}]
|
|
346
|
+
: []
|
|
347
|
+
|
|
348
|
+
const fromFileChanges = toArray(metadata.fileChanges).map((item) => ({
|
|
349
|
+
path: String(item?.path || "").trim(),
|
|
350
|
+
operation: String(item?.tool || item?.operation || "mutation"),
|
|
351
|
+
addedLines: Math.max(0, Number(item?.addedLines || 0)),
|
|
352
|
+
removedLines: Math.max(0, Number(item?.removedLines || 0))
|
|
353
|
+
}))
|
|
354
|
+
|
|
355
|
+
return uniq([...fromMutations, ...fromMutation, ...fromFileChanges]
|
|
356
|
+
.filter((item) => item.path)
|
|
357
|
+
.map((item) => JSON.stringify(item))).map((item) => JSON.parse(item))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function buildMutationObservability(metadata = {}) {
|
|
361
|
+
const changes = normalizeMutationChanges(metadata)
|
|
362
|
+
if (changes.length === 0) {
|
|
363
|
+
return {
|
|
364
|
+
contract: MUTATION_OBSERVABILITY_CONTRACT,
|
|
365
|
+
changes: [],
|
|
366
|
+
totals: {
|
|
367
|
+
filesChanged: 0,
|
|
368
|
+
addedLines: 0,
|
|
369
|
+
removedLines: 0
|
|
370
|
+
},
|
|
371
|
+
operations: [],
|
|
372
|
+
summary: "no file mutations recorded"
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const totals = changes.reduce((acc, item) => ({
|
|
377
|
+
filesChanged: acc.filesChanged + 1,
|
|
378
|
+
addedLines: acc.addedLines + Math.max(0, Number(item.addedLines || 0)),
|
|
379
|
+
removedLines: acc.removedLines + Math.max(0, Number(item.removedLines || 0))
|
|
380
|
+
}), { filesChanged: 0, addedLines: 0, removedLines: 0 })
|
|
381
|
+
const operations = uniq(changes.map((item) => item.operation))
|
|
382
|
+
const operationText = operations.length === 1
|
|
383
|
+
? `via ${operations[0]}`
|
|
384
|
+
: `across ${pluralize(operations.length, "operation")}`
|
|
385
|
+
const summary = `${pluralize(totals.filesChanged, "file")} changed ${operationText} (+${totals.addedLines}/-${totals.removedLines})`
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
contract: MUTATION_OBSERVABILITY_CONTRACT,
|
|
389
|
+
changes,
|
|
390
|
+
totals,
|
|
391
|
+
operations,
|
|
392
|
+
summary
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function extractTouchedFiles({ args = {}, metadata = {} } = {}) {
|
|
397
|
+
const files = []
|
|
398
|
+
if (args?.path) files.push(String(args.path))
|
|
399
|
+
for (const change of toArray(args?.changes)) {
|
|
400
|
+
if (change?.path) files.push(String(change.path))
|
|
401
|
+
}
|
|
402
|
+
for (const change of toArray(metadata?.fileChanges)) {
|
|
403
|
+
if (change?.path) files.push(String(change.path))
|
|
404
|
+
}
|
|
405
|
+
if (metadata?.mutation?.filePath) files.push(String(metadata.mutation.filePath))
|
|
406
|
+
for (const change of toArray(metadata?.mutations)) {
|
|
407
|
+
if (change?.filePath) files.push(String(change.filePath))
|
|
408
|
+
}
|
|
409
|
+
return uniq(files)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function isMutationTool(toolName) {
|
|
413
|
+
return MUTATION_TOOLS.has(String(toolName || ""))
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function isDiagnosticsEligibleFile(filePath) {
|
|
417
|
+
const extension = path.extname(String(filePath || "")).toLowerCase()
|
|
418
|
+
return JS_SYNTAX_EXTENSIONS.has(extension) || TS_EXTENSIONS.has(extension)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function extractEditFeedbackFromToolEvents(toolEvents = []) {
|
|
422
|
+
return toArray(toolEvents)
|
|
423
|
+
.filter((event) => isMutationTool(event?.name))
|
|
424
|
+
.map((event) => {
|
|
425
|
+
const metadata = event?.metadata && typeof event.metadata === "object" ? event.metadata : {}
|
|
426
|
+
const observability = metadata.observability?.contract === MUTATION_OBSERVABILITY_CONTRACT
|
|
427
|
+
? metadata.observability
|
|
428
|
+
: buildMutationObservability(metadata)
|
|
429
|
+
const diagnostics = metadata.diagnostics?.contract === EDIT_DIAGNOSTICS_CONTRACT
|
|
430
|
+
? metadata.diagnostics
|
|
431
|
+
: null
|
|
432
|
+
const files = uniq([
|
|
433
|
+
...extractTouchedFiles({ args: event?.args, metadata }),
|
|
434
|
+
...toArray(observability?.changes).map((item) => item.path),
|
|
435
|
+
...toArray(diagnostics?.files)
|
|
436
|
+
])
|
|
437
|
+
|
|
438
|
+
if (!observability.changes.length && !diagnostics) return null
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
tool: String(event?.name || ""),
|
|
442
|
+
status: String(event?.status || ""),
|
|
443
|
+
files,
|
|
444
|
+
observability,
|
|
445
|
+
diagnostics
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
.filter(Boolean)
|
|
449
|
+
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { createMetricsCollector } from "./metrics.mjs"
|
|
2
|
-
import { createTracer } from "./tracer.mjs"
|
|
3
|
-
|
|
4
|
-
let metrics = null
|
|
5
|
-
let tracer = null
|
|
6
|
-
let unsubscribes = []
|
|
7
|
-
|
|
8
|
-
export function initialize(eventBus) {
|
|
9
|
-
if (metrics) return // idempotent
|
|
10
|
-
|
|
11
|
-
metrics = createMetricsCollector()
|
|
12
|
-
tracer = createTracer()
|
|
13
|
-
|
|
14
|
-
unsubscribes.push(
|
|
15
|
-
eventBus.registerSink(async (event) => {
|
|
16
|
-
metrics.handleEvent(event)
|
|
17
|
-
tracer.handleEvent(event)
|
|
18
|
-
})
|
|
19
|
-
)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function shutdown() {
|
|
23
|
-
for (const unsub of unsubscribes) unsub()
|
|
24
|
-
unsubscribes = []
|
|
25
|
-
metrics = null
|
|
26
|
-
tracer = null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function getMetrics() {
|
|
30
|
-
return metrics ? metrics.getSnapshot() : null
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function getTraces() {
|
|
34
|
-
return tracer ? tracer.getTraces() : []
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function exportReport() {
|
|
38
|
-
return {
|
|
39
|
-
metrics: metrics ? metrics.getSnapshot() : null,
|
|
40
|
-
traces: tracer ? tracer.exportTraces("json") : "[]"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
import { createMetricsCollector } from "./metrics.mjs"
|
|
2
|
+
import { createTracer } from "./tracer.mjs"
|
|
3
|
+
|
|
4
|
+
let metrics = null
|
|
5
|
+
let tracer = null
|
|
6
|
+
let unsubscribes = []
|
|
7
|
+
|
|
8
|
+
export function initialize(eventBus) {
|
|
9
|
+
if (metrics) return // idempotent
|
|
10
|
+
|
|
11
|
+
metrics = createMetricsCollector()
|
|
12
|
+
tracer = createTracer()
|
|
13
|
+
|
|
14
|
+
unsubscribes.push(
|
|
15
|
+
eventBus.registerSink(async (event) => {
|
|
16
|
+
metrics.handleEvent(event)
|
|
17
|
+
tracer.handleEvent(event)
|
|
18
|
+
})
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function shutdown() {
|
|
23
|
+
for (const unsub of unsubscribes) unsub()
|
|
24
|
+
unsubscribes = []
|
|
25
|
+
metrics = null
|
|
26
|
+
tracer = null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getMetrics() {
|
|
30
|
+
return metrics ? metrics.getSnapshot() : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getTraces() {
|
|
34
|
+
return tracer ? tracer.getTraces() : []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function exportReport() {
|
|
38
|
+
return {
|
|
39
|
+
metrics: metrics ? metrics.getSnapshot() : null,
|
|
40
|
+
traces: tracer ? tracer.exportTraces("json") : "[]"
|
|
41
|
+
}
|
|
42
|
+
}
|