@kkelly-offical/kkcode 0.1.7 → 0.2.3-preview.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 (166) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +474 -387
  3. package/package.json +50 -46
  4. package/src/agent/agent.mjs +228 -220
  5. package/src/agent/custom-agent-loader.mjs +6 -3
  6. package/src/agent/generator.mjs +2 -2
  7. package/src/agent/prompt/assistant.txt +12 -0
  8. package/src/agent/prompt/bug-hunter.txt +89 -89
  9. package/src/agent/prompt/frontend-designer.txt +58 -58
  10. package/src/agent/prompt/guide.txt +1 -1
  11. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
  12. package/src/agent/prompt/longagent-coding-agent.txt +37 -37
  13. package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
  14. package/src/agent/prompt/longagent-preview-agent.txt +63 -63
  15. package/src/command/custom-commands.mjs +2 -2
  16. package/src/commands/agent.mjs +1 -1
  17. package/src/commands/background.mjs +145 -4
  18. package/src/commands/chat.mjs +117 -76
  19. package/src/commands/config.mjs +148 -1
  20. package/src/commands/doctor.mjs +30 -6
  21. package/src/commands/init.mjs +32 -6
  22. package/src/commands/longagent.mjs +117 -0
  23. package/src/commands/mcp.mjs +275 -43
  24. package/src/commands/permission.mjs +1 -1
  25. package/src/commands/session.mjs +195 -140
  26. package/src/commands/skill.mjs +63 -0
  27. package/src/commands/theme.mjs +1 -1
  28. package/src/commands/update.mjs +32 -0
  29. package/src/config/defaults.mjs +289 -260
  30. package/src/config/import-config.mjs +1 -1
  31. package/src/config/load-config.mjs +61 -4
  32. package/src/config/schema.mjs +604 -574
  33. package/src/context.mjs +4 -1
  34. package/src/core/constants.mjs +97 -91
  35. package/src/core/types.mjs +1 -1
  36. package/src/github/api.mjs +78 -78
  37. package/src/github/auth.mjs +294 -286
  38. package/src/github/flow.mjs +298 -298
  39. package/src/github/workspace.mjs +225 -212
  40. package/src/index.mjs +87 -82
  41. package/src/knowledge/frontend-aesthetics.txt +38 -38
  42. package/src/mcp/client-http.mjs +139 -141
  43. package/src/mcp/client-sse.mjs +297 -288
  44. package/src/mcp/client-stdio.mjs +534 -533
  45. package/src/mcp/constants.mjs +4 -2
  46. package/src/mcp/registry.mjs +498 -479
  47. package/src/mcp/stdio-framing.mjs +135 -133
  48. package/src/mcp/tool-result.mjs +24 -24
  49. package/src/observability/edit-diagnostics.mjs +449 -0
  50. package/src/observability/index.mjs +42 -42
  51. package/src/observability/metrics.mjs +165 -137
  52. package/src/observability/tracer.mjs +137 -137
  53. package/src/onboarding.mjs +209 -0
  54. package/src/orchestration/background-manager.mjs +567 -372
  55. package/src/orchestration/background-worker.mjs +419 -305
  56. package/src/orchestration/interruption-reason.mjs +21 -0
  57. package/src/orchestration/longagent-manager.mjs +197 -171
  58. package/src/orchestration/stage-scheduler.mjs +733 -728
  59. package/src/orchestration/subagent-router.mjs +7 -1
  60. package/src/orchestration/task-scheduler.mjs +219 -7
  61. package/src/permission/engine.mjs +1 -1
  62. package/src/permission/exec-policy.mjs +370 -370
  63. package/src/permission/file-edit-policy.mjs +108 -0
  64. package/src/permission/prompt.mjs +1 -1
  65. package/src/permission/rules.mjs +116 -7
  66. package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
  67. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
  68. package/src/plugin/hook-bus.mjs +19 -5
  69. package/src/plugin/manifest-loader.mjs +222 -0
  70. package/src/provider/anthropic.mjs +396 -390
  71. package/src/provider/ollama.mjs +7 -1
  72. package/src/provider/openai.mjs +382 -340
  73. package/src/provider/retry-policy.mjs +74 -68
  74. package/src/provider/router.mjs +242 -241
  75. package/src/provider/sse.mjs +104 -104
  76. package/src/provider/wizard.mjs +556 -0
  77. package/src/repl/capability-facade.mjs +30 -0
  78. package/src/repl/command-surface.mjs +23 -0
  79. package/src/repl/controller-entry.mjs +40 -0
  80. package/src/repl/core-shell.mjs +208 -0
  81. package/src/repl/dialog-router.mjs +87 -0
  82. package/src/repl/input-engine.mjs +76 -0
  83. package/src/repl/keymap.mjs +7 -0
  84. package/src/repl/operator-surface.mjs +15 -0
  85. package/src/repl/permission-flow.mjs +49 -0
  86. package/src/repl/runtime-facade.mjs +36 -0
  87. package/src/repl/slash-router.mjs +62 -0
  88. package/src/repl/state-store.mjs +29 -0
  89. package/src/repl/turn-controller.mjs +58 -0
  90. package/src/repl/verification.mjs +23 -0
  91. package/src/repl.mjs +3371 -2981
  92. package/src/rules/load-rules.mjs +3 -3
  93. package/src/runtime.mjs +1 -1
  94. package/src/session/agent-transaction.mjs +86 -0
  95. package/src/session/checkpoint.mjs +302 -302
  96. package/src/session/compaction.mjs +298 -298
  97. package/src/session/engine.mjs +417 -232
  98. package/src/session/longagent-4stage.mjs +467 -460
  99. package/src/session/longagent-hybrid.mjs +1344 -1097
  100. package/src/session/longagent-plan.mjs +376 -365
  101. package/src/session/longagent-project-memory.mjs +53 -53
  102. package/src/session/longagent-scaffold.mjs +291 -291
  103. package/src/session/longagent-task-bus.mjs +138 -54
  104. package/src/session/longagent-utils.mjs +828 -472
  105. package/src/session/longagent.mjs +911 -900
  106. package/src/session/loop.mjs +1005 -930
  107. package/src/session/prompt/agent.txt +25 -25
  108. package/src/session/prompt/anthropic.txt +150 -150
  109. package/src/session/prompt/beast.txt +1 -1
  110. package/src/session/prompt/plan.txt +31 -31
  111. package/src/session/prompt/qwen.txt +46 -46
  112. package/src/session/recovery.mjs +21 -0
  113. package/src/session/rollback.mjs +196 -195
  114. package/src/session/routing-observability.mjs +72 -0
  115. package/src/session/runtime-state.mjs +47 -0
  116. package/src/session/store.mjs +523 -519
  117. package/src/session/system-prompt.mjs +308 -273
  118. package/src/session/task-validator.mjs +267 -267
  119. package/src/session/usability-gates.mjs +2 -2
  120. package/src/skill/builtin/commit.mjs +64 -64
  121. package/src/skill/builtin/design.mjs +76 -76
  122. package/src/skill/generator.mjs +18 -2
  123. package/src/skill/registry.mjs +642 -390
  124. package/src/storage/audit-store.mjs +18 -11
  125. package/src/storage/event-log.mjs +7 -1
  126. package/src/storage/ghost-commit-store.mjs +243 -245
  127. package/src/storage/paths.mjs +17 -0
  128. package/src/theme/default-theme.mjs +1 -1
  129. package/src/theme/markdown.mjs +4 -0
  130. package/src/theme/schema.mjs +1 -1
  131. package/src/theme/status-bar.mjs +162 -158
  132. package/src/tool/audit-wrapper.mjs +18 -2
  133. package/src/tool/edit-transaction.mjs +23 -0
  134. package/src/tool/executor.mjs +26 -1
  135. package/src/tool/file-read-state.mjs +65 -0
  136. package/src/tool/git-auto.mjs +526 -526
  137. package/src/tool/git-full-auto.mjs +487 -478
  138. package/src/tool/mutation-guard.mjs +54 -0
  139. package/src/tool/prompt/edit.txt +3 -3
  140. package/src/tool/prompt/multiedit.txt +1 -0
  141. package/src/tool/prompt/notebookedit.txt +2 -1
  142. package/src/tool/prompt/patch.txt +25 -24
  143. package/src/tool/prompt/read.txt +3 -3
  144. package/src/tool/prompt/sysinfo.txt +29 -0
  145. package/src/tool/prompt/task.txt +66 -4
  146. package/src/tool/prompt/write.txt +2 -2
  147. package/src/tool/question-prompt.mjs +99 -93
  148. package/src/tool/registry.mjs +1701 -1343
  149. package/src/tool/task-tool.mjs +14 -6
  150. package/src/ui/activity-renderer.mjs +667 -664
  151. package/src/ui/repl-background-panel.mjs +7 -0
  152. package/src/ui/repl-capability-panel.mjs +9 -0
  153. package/src/ui/repl-dashboard.mjs +54 -4
  154. package/src/ui/repl-help.mjs +110 -0
  155. package/src/ui/repl-operator-panel.mjs +12 -0
  156. package/src/ui/repl-route-feedback.mjs +35 -0
  157. package/src/ui/repl-status-view.mjs +76 -0
  158. package/src/ui/repl-task-panel.mjs +5 -0
  159. package/src/ui/repl-transcript-panel.mjs +56 -0
  160. package/src/ui/repl-turn-summary.mjs +135 -0
  161. package/src/update/checker.mjs +184 -0
  162. package/src/usage/pricing.mjs +122 -121
  163. package/src/usage/usage-meter.mjs +1 -0
  164. package/src/util/git.mjs +562 -519
  165. package/src/util/template.mjs +6 -1
  166. package/src/version.mjs +3 -0
@@ -1,267 +1,267 @@
1
- import { exec as execCb, execFile as execFileCb } from "node:child_process"
2
- import { promisify } from "node:util"
3
- import { access, readFile } from "node:fs/promises"
4
- import path from "node:path"
5
-
6
- const exec = promisify(execCb)
7
- const execFile = promisify(execFileCb)
8
-
9
- async function fileExists(p) {
10
- try { await access(p); return true } catch { return false }
11
- }
12
-
13
- export class TaskValidator {
14
- constructor({ cwd, configState }) {
15
- this.cwd = cwd
16
- this.configState = configState
17
- }
18
-
19
- async checkTodoCompletion(todoState) {
20
- if (!todoState || !Array.isArray(todoState)) {
21
- return {
22
- passed: true,
23
- message: "No todo list found"
24
- }
25
- }
26
-
27
- const incomplete = todoState.filter(t => t.status !== "completed")
28
- if (incomplete.length === 0) {
29
- return {
30
- passed: true,
31
- message: "All todo items completed"
32
- }
33
- }
34
-
35
- const items = incomplete.map(t => `- ${t.content}`).join("\n")
36
- return {
37
- passed: false,
38
- message: `Incomplete todo items:\n${items}`
39
- }
40
- }
41
-
42
- async checkJavaScriptSyntax() {
43
- const jsFiles = await this.findFilesByExtension(["js", "mjs", "cjs"])
44
- if (jsFiles.length === 0) {
45
- return {
46
- passed: true,
47
- message: "No JavaScript files to check"
48
- }
49
- }
50
-
51
- const errors = []
52
- for (const file of jsFiles.slice(0, 20)) {
53
- try {
54
- await execFile("node", ["--check", file], { cwd: this.cwd, timeout: 10000 })
55
- } catch (error) {
56
- errors.push(`${file}: ${(error.stderr || error.message || "").trim()}`)
57
- }
58
- }
59
-
60
- return {
61
- passed: errors.length === 0,
62
- message: errors.length === 0 ? "JavaScript syntax check passed" : `JavaScript syntax errors:\n${errors.join("\n")}`
63
- }
64
- }
65
-
66
- async checkTypeScript() {
67
- const tsconfigPath = path.join(this.cwd, "tsconfig.json")
68
- if (!(await fileExists(tsconfigPath))) {
69
- return {
70
- passed: true,
71
- message: "No tsconfig.json found"
72
- }
73
- }
74
-
75
- try {
76
- await exec("npx tsc --noEmit", {
77
- cwd: this.cwd,
78
- timeout: 30000
79
- })
80
- return {
81
- passed: true,
82
- message: "TypeScript check passed"
83
- }
84
- } catch (error) {
85
- const output = (error.stdout || error.stderr || "").trim()
86
- return {
87
- passed: false,
88
- message: `TypeScript errors:\n${output.slice(0, 2000)}`
89
- }
90
- }
91
- }
92
-
93
- async checkPythonSyntax() {
94
- const pyFiles = await this.findFilesByExtension(["py"])
95
- if (pyFiles.length === 0) {
96
- return {
97
- passed: true,
98
- message: "No Python files to check"
99
- }
100
- }
101
-
102
- const errors = []
103
- for (const file of pyFiles.slice(0, 20)) {
104
- try {
105
- await execFile("python", ["-m", "py_compile", file], { cwd: this.cwd, timeout: 10000 })
106
- } catch (error) {
107
- errors.push(`${file}: ${(error.stderr || error.message || "").trim()}`)
108
- }
109
- }
110
-
111
- return {
112
- passed: errors.length === 0,
113
- message: errors.length === 0 ? "Python syntax check passed" : `Python syntax errors:\n${errors.join("\n")}`
114
- }
115
- }
116
-
117
- async runTests() {
118
- const packageJsonPath = path.join(this.cwd, "package.json")
119
- if (!(await fileExists(packageJsonPath))) {
120
- return {
121
- passed: true,
122
- message: "No package.json found"
123
- }
124
- }
125
-
126
- try {
127
- const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"))
128
- const hasTestScript = packageJson.scripts?.test
129
- if (!hasTestScript) {
130
- return {
131
- passed: true,
132
- message: "No test script found"
133
- }
134
- }
135
-
136
- await exec("npm test", {
137
- cwd: this.cwd,
138
- timeout: 120000
139
- })
140
- return {
141
- passed: true,
142
- message: "Tests passed"
143
- }
144
- } catch (error) {
145
- const output = (error.stdout || error.stderr || "").trim()
146
- return {
147
- passed: false,
148
- message: `Test failures:\n${output.slice(0, 2000)}`
149
- }
150
- }
151
- }
152
-
153
- async findFilesByExtension(extensions) {
154
- const files = []
155
- for (const ext of extensions) {
156
- try {
157
- const matches = await this.globPattern(`**/*.${ext}`)
158
- files.push(...matches)
159
- } catch {
160
- }
161
- }
162
- return files
163
- }
164
-
165
- async globPattern(pattern) {
166
- try {
167
- const { Glob } = await import("glob")
168
- const g = new Glob(pattern, {
169
- cwd: this.cwd,
170
- ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"]
171
- })
172
- const matches = []
173
- for await (const match of g) {
174
- matches.push(path.join(this.cwd, match))
175
- }
176
- return matches
177
- } catch {
178
- return []
179
- }
180
- }
181
-
182
- async checkBuild() {
183
- const packageJsonPath = path.join(this.cwd, "package.json")
184
- if (!(await fileExists(packageJsonPath))) {
185
- return { passed: true, message: "No package.json found", severity: "skip" }
186
- }
187
- try {
188
- const pkg = JSON.parse(await readFile(packageJsonPath, "utf8"))
189
- if (!pkg.scripts?.build) {
190
- return { passed: true, message: "No build script", severity: "skip" }
191
- }
192
- await exec("npm run build --silent", { cwd: this.cwd, timeout: 60000 })
193
- return { passed: true, message: "Build succeeded", severity: "pass" }
194
- } catch (error) {
195
- const output = (error.stdout || error.stderr || "").trim()
196
- return { passed: false, message: `Build failed:\n${output.slice(0, 1500)}`, severity: "critical" }
197
- }
198
- }
199
-
200
- async checkLint() {
201
- const packageJsonPath = path.join(this.cwd, "package.json")
202
- if (!(await fileExists(packageJsonPath))) {
203
- return { passed: true, message: "No package.json found", severity: "skip" }
204
- }
205
- try {
206
- const pkg = JSON.parse(await readFile(packageJsonPath, "utf8"))
207
- if (!pkg.scripts?.lint) {
208
- return { passed: true, message: "No lint script", severity: "skip" }
209
- }
210
- await exec("npm run lint --silent", { cwd: this.cwd, timeout: 30000 })
211
- return { passed: true, message: "Lint passed", severity: "pass" }
212
- } catch (error) {
213
- const output = (error.stdout || error.stderr || "").trim()
214
- return { passed: false, message: `Lint issues:\n${output.slice(0, 1500)}`, severity: "warning" }
215
- }
216
- }
217
-
218
- async validate({ todoState, level = "standard" }) {
219
- const results = []
220
-
221
- const todoResult = await this.checkTodoCompletion(todoState)
222
- results.push({ name: "Todo", ...todoResult, severity: todoResult.passed ? "pass" : "critical" })
223
-
224
- const jsResult = await this.checkJavaScriptSyntax()
225
- results.push({ name: "JS Syntax", ...jsResult, severity: jsResult.passed ? "pass" : "critical" })
226
-
227
- if (level !== "quick") {
228
- const tsResult = await this.checkTypeScript()
229
- results.push({ name: "TypeScript", ...tsResult, severity: tsResult.passed ? "pass" : "critical" })
230
-
231
- const buildResult = await this.checkBuild()
232
- results.push({ name: "Build", ...buildResult })
233
-
234
- const testResult = await this.runTests()
235
- results.push({ name: "Tests", ...testResult, severity: testResult.passed ? "pass" : "critical" })
236
- }
237
-
238
- if (level === "strict") {
239
- const lintResult = await this.checkLint()
240
- results.push({ name: "Lint", ...lintResult })
241
-
242
- const pyResult = await this.checkPythonSyntax()
243
- results.push({ name: "Python", ...pyResult, severity: pyResult.passed ? "pass" : "warning" })
244
- }
245
-
246
- const critical = results.filter(r => !r.passed && r.severity === "critical").length
247
- const warnings = results.filter(r => !r.passed && r.severity === "warning").length
248
- const verdict = critical > 0 ? "BLOCK" : warnings > 0 ? "WARNING" : "APPROVE"
249
- const allPassed = verdict !== "BLOCK"
250
-
251
- const lines = [
252
- "VERIFICATION REPORT",
253
- "===================",
254
- ...results.map(r => `${r.passed ? "PASS" : "FAIL"} ${r.name}: ${r.message}`),
255
- "",
256
- `VERDICT: ${verdict}`,
257
- `CRITICAL: ${critical} WARNING: ${warnings}`,
258
- allPassed ? "Ready to proceed." : "Must fix critical issues before proceeding."
259
- ]
260
-
261
- return { passed: allPassed, verdict, results, message: lines.join("\n") }
262
- }
263
- }
264
-
265
- export async function createValidator({ cwd, configState }) {
266
- return new TaskValidator({ cwd, configState })
267
- }
1
+ import { exec as execCb, execFile as execFileCb } from "node:child_process"
2
+ import { promisify } from "node:util"
3
+ import { access, readFile } from "node:fs/promises"
4
+ import path from "node:path"
5
+
6
+ const exec = promisify(execCb)
7
+ const execFile = promisify(execFileCb)
8
+
9
+ async function fileExists(p) {
10
+ try { await access(p); return true } catch { return false }
11
+ }
12
+
13
+ export class TaskValidator {
14
+ constructor({ cwd, configState }) {
15
+ this.cwd = cwd
16
+ this.configState = configState
17
+ }
18
+
19
+ async checkTodoCompletion(todoState) {
20
+ if (!todoState || !Array.isArray(todoState)) {
21
+ return {
22
+ passed: true,
23
+ message: "No todo list found"
24
+ }
25
+ }
26
+
27
+ const incomplete = todoState.filter(t => t.status !== "completed")
28
+ if (incomplete.length === 0) {
29
+ return {
30
+ passed: true,
31
+ message: "All todo items completed"
32
+ }
33
+ }
34
+
35
+ const items = incomplete.map(t => `- ${t.content}`).join("\n")
36
+ return {
37
+ passed: false,
38
+ message: `Incomplete todo items:\n${items}`
39
+ }
40
+ }
41
+
42
+ async checkJavaScriptSyntax() {
43
+ const jsFiles = await this.findFilesByExtension(["js", "mjs", "cjs"])
44
+ if (jsFiles.length === 0) {
45
+ return {
46
+ passed: true,
47
+ message: "No JavaScript files to check"
48
+ }
49
+ }
50
+
51
+ const errors = []
52
+ for (const file of jsFiles.slice(0, 20)) {
53
+ try {
54
+ await execFile("node", ["--check", file], { cwd: this.cwd, timeout: 10000 })
55
+ } catch (error) {
56
+ errors.push(`${file}: ${(error.stderr || error.message || "").trim()}`)
57
+ }
58
+ }
59
+
60
+ return {
61
+ passed: errors.length === 0,
62
+ message: errors.length === 0 ? "JavaScript syntax check passed" : `JavaScript syntax errors:\n${errors.join("\n")}`
63
+ }
64
+ }
65
+
66
+ async checkTypeScript() {
67
+ const tsconfigPath = path.join(this.cwd, "tsconfig.json")
68
+ if (!(await fileExists(tsconfigPath))) {
69
+ return {
70
+ passed: true,
71
+ message: "No tsconfig.json found"
72
+ }
73
+ }
74
+
75
+ try {
76
+ await exec("npx tsc --noEmit", {
77
+ cwd: this.cwd,
78
+ timeout: 30000
79
+ })
80
+ return {
81
+ passed: true,
82
+ message: "TypeScript check passed"
83
+ }
84
+ } catch (error) {
85
+ const output = (error.stdout || error.stderr || "").trim()
86
+ return {
87
+ passed: false,
88
+ message: `TypeScript errors:\n${output.slice(0, 2000)}`
89
+ }
90
+ }
91
+ }
92
+
93
+ async checkPythonSyntax() {
94
+ const pyFiles = await this.findFilesByExtension(["py"])
95
+ if (pyFiles.length === 0) {
96
+ return {
97
+ passed: true,
98
+ message: "No Python files to check"
99
+ }
100
+ }
101
+
102
+ const errors = []
103
+ for (const file of pyFiles.slice(0, 20)) {
104
+ try {
105
+ await execFile("python", ["-m", "py_compile", file], { cwd: this.cwd, timeout: 10000 })
106
+ } catch (error) {
107
+ errors.push(`${file}: ${(error.stderr || error.message || "").trim()}`)
108
+ }
109
+ }
110
+
111
+ return {
112
+ passed: errors.length === 0,
113
+ message: errors.length === 0 ? "Python syntax check passed" : `Python syntax errors:\n${errors.join("\n")}`
114
+ }
115
+ }
116
+
117
+ async runTests() {
118
+ const packageJsonPath = path.join(this.cwd, "package.json")
119
+ if (!(await fileExists(packageJsonPath))) {
120
+ return {
121
+ passed: true,
122
+ message: "No package.json found"
123
+ }
124
+ }
125
+
126
+ try {
127
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"))
128
+ const hasTestScript = packageJson.scripts?.test
129
+ if (!hasTestScript) {
130
+ return {
131
+ passed: true,
132
+ message: "No test script found"
133
+ }
134
+ }
135
+
136
+ await exec("npm test", {
137
+ cwd: this.cwd,
138
+ timeout: 120000
139
+ })
140
+ return {
141
+ passed: true,
142
+ message: "Tests passed"
143
+ }
144
+ } catch (error) {
145
+ const output = (error.stdout || error.stderr || "").trim()
146
+ return {
147
+ passed: false,
148
+ message: `Test failures:\n${output.slice(0, 2000)}`
149
+ }
150
+ }
151
+ }
152
+
153
+ async findFilesByExtension(extensions) {
154
+ const files = []
155
+ for (const ext of extensions) {
156
+ try {
157
+ const matches = await this.globPattern(`**/*.${ext}`)
158
+ files.push(...matches)
159
+ } catch {
160
+ }
161
+ }
162
+ return files
163
+ }
164
+
165
+ async globPattern(pattern) {
166
+ try {
167
+ const { Glob } = await import("glob")
168
+ const g = new Glob(pattern, {
169
+ cwd: this.cwd,
170
+ ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"]
171
+ })
172
+ const matches = []
173
+ for await (const match of g) {
174
+ matches.push(path.join(this.cwd, match))
175
+ }
176
+ return matches
177
+ } catch {
178
+ return []
179
+ }
180
+ }
181
+
182
+ async checkBuild() {
183
+ const packageJsonPath = path.join(this.cwd, "package.json")
184
+ if (!(await fileExists(packageJsonPath))) {
185
+ return { passed: true, message: "No package.json found", severity: "skip" }
186
+ }
187
+ try {
188
+ const pkg = JSON.parse(await readFile(packageJsonPath, "utf8"))
189
+ if (!pkg.scripts?.build) {
190
+ return { passed: true, message: "No build script", severity: "skip" }
191
+ }
192
+ await exec("npm run build --silent", { cwd: this.cwd, timeout: 60000 })
193
+ return { passed: true, message: "Build succeeded", severity: "pass" }
194
+ } catch (error) {
195
+ const output = (error.stdout || error.stderr || "").trim()
196
+ return { passed: false, message: `Build failed:\n${output.slice(0, 1500)}`, severity: "critical" }
197
+ }
198
+ }
199
+
200
+ async checkLint() {
201
+ const packageJsonPath = path.join(this.cwd, "package.json")
202
+ if (!(await fileExists(packageJsonPath))) {
203
+ return { passed: true, message: "No package.json found", severity: "skip" }
204
+ }
205
+ try {
206
+ const pkg = JSON.parse(await readFile(packageJsonPath, "utf8"))
207
+ if (!pkg.scripts?.lint) {
208
+ return { passed: true, message: "No lint script", severity: "skip" }
209
+ }
210
+ await exec("npm run lint --silent", { cwd: this.cwd, timeout: 30000 })
211
+ return { passed: true, message: "Lint passed", severity: "pass" }
212
+ } catch (error) {
213
+ const output = (error.stdout || error.stderr || "").trim()
214
+ return { passed: false, message: `Lint issues:\n${output.slice(0, 1500)}`, severity: "warning" }
215
+ }
216
+ }
217
+
218
+ async validate({ todoState, level = "standard" }) {
219
+ const results = []
220
+
221
+ const todoResult = await this.checkTodoCompletion(todoState)
222
+ results.push({ name: "Todo", ...todoResult, severity: todoResult.passed ? "pass" : "critical" })
223
+
224
+ const jsResult = await this.checkJavaScriptSyntax()
225
+ results.push({ name: "JS Syntax", ...jsResult, severity: jsResult.passed ? "pass" : "critical" })
226
+
227
+ if (level !== "quick") {
228
+ const tsResult = await this.checkTypeScript()
229
+ results.push({ name: "TypeScript", ...tsResult, severity: tsResult.passed ? "pass" : "critical" })
230
+
231
+ const buildResult = await this.checkBuild()
232
+ results.push({ name: "Build", ...buildResult })
233
+
234
+ const testResult = await this.runTests()
235
+ results.push({ name: "Tests", ...testResult, severity: testResult.passed ? "pass" : "critical" })
236
+ }
237
+
238
+ if (level === "strict") {
239
+ const lintResult = await this.checkLint()
240
+ results.push({ name: "Lint", ...lintResult })
241
+
242
+ const pyResult = await this.checkPythonSyntax()
243
+ results.push({ name: "Python", ...pyResult, severity: pyResult.passed ? "pass" : "warning" })
244
+ }
245
+
246
+ const critical = results.filter(r => !r.passed && r.severity === "critical").length
247
+ const warnings = results.filter(r => !r.passed && r.severity === "warning").length
248
+ const verdict = critical > 0 ? "BLOCK" : warnings > 0 ? "WARNING" : "APPROVE"
249
+ const allPassed = verdict !== "BLOCK"
250
+
251
+ const lines = [
252
+ "VERIFICATION REPORT",
253
+ "===================",
254
+ ...results.map(r => `${r.passed ? "PASS" : "FAIL"} ${r.name}: ${r.message}`),
255
+ "",
256
+ `VERDICT: ${verdict}`,
257
+ `CRITICAL: ${critical} WARNING: ${warnings}`,
258
+ allPassed ? "Ready to proceed." : "Must fix critical issues before proceeding."
259
+ ]
260
+
261
+ return { passed: allPassed, verdict, results, message: lines.join("\n") }
262
+ }
263
+ }
264
+
265
+ export async function createValidator({ cwd, configState }) {
266
+ return new TaskValidator({ cwd, configState })
267
+ }
@@ -1,14 +1,14 @@
1
1
  import path from "node:path"
2
2
  import { access, readFile, writeFile, mkdir } from "node:fs/promises"
3
- import { homedir } from "node:os"
4
3
  import { spawn } from "node:child_process"
5
4
  import { readReviewState } from "../review/review-store.mjs"
6
5
  import { fsckSessionStore, getSession } from "./store.mjs"
7
6
  import { EventBus } from "../core/events.mjs"
8
7
  import { EVENT_TYPES } from "../core/constants.mjs"
8
+ import { userRootDir } from "../storage/paths.mjs"
9
9
 
10
10
  const DEFAULT_GATE_TIMEOUT_MS = 15 * 60 * 1000
11
- const GATE_PREFS_FILE = path.join(homedir(), ".kkcode", "gate-preferences.json")
11
+ const GATE_PREFS_FILE = path.join(userRootDir(), "gate-preferences.json")
12
12
 
13
13
  // --- Gate result cache (5-min TTL, only caches passing results) ---
14
14
  const gateCache = new Map()