@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.
Files changed (163) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +452 -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/config/defaults.mjs +280 -260
  29. package/src/config/import-config.mjs +1 -1
  30. package/src/config/load-config.mjs +61 -4
  31. package/src/config/schema.mjs +591 -574
  32. package/src/context.mjs +4 -1
  33. package/src/core/constants.mjs +97 -91
  34. package/src/core/types.mjs +1 -1
  35. package/src/github/api.mjs +78 -78
  36. package/src/github/auth.mjs +294 -286
  37. package/src/github/flow.mjs +298 -298
  38. package/src/github/workspace.mjs +225 -212
  39. package/src/index.mjs +84 -82
  40. package/src/knowledge/frontend-aesthetics.txt +38 -38
  41. package/src/mcp/client-http.mjs +139 -141
  42. package/src/mcp/client-sse.mjs +297 -288
  43. package/src/mcp/client-stdio.mjs +534 -533
  44. package/src/mcp/constants.mjs +2 -2
  45. package/src/mcp/registry.mjs +498 -479
  46. package/src/mcp/stdio-framing.mjs +135 -133
  47. package/src/mcp/tool-result.mjs +24 -24
  48. package/src/observability/edit-diagnostics.mjs +449 -0
  49. package/src/observability/index.mjs +42 -42
  50. package/src/observability/metrics.mjs +165 -137
  51. package/src/observability/tracer.mjs +137 -137
  52. package/src/onboarding.mjs +209 -0
  53. package/src/orchestration/background-manager.mjs +567 -372
  54. package/src/orchestration/background-worker.mjs +419 -305
  55. package/src/orchestration/interruption-reason.mjs +21 -0
  56. package/src/orchestration/longagent-manager.mjs +197 -171
  57. package/src/orchestration/stage-scheduler.mjs +733 -728
  58. package/src/orchestration/subagent-router.mjs +7 -1
  59. package/src/orchestration/task-scheduler.mjs +219 -7
  60. package/src/permission/engine.mjs +1 -1
  61. package/src/permission/exec-policy.mjs +370 -370
  62. package/src/permission/file-edit-policy.mjs +108 -0
  63. package/src/permission/prompt.mjs +1 -1
  64. package/src/permission/rules.mjs +116 -7
  65. package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
  66. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
  67. package/src/plugin/hook-bus.mjs +19 -5
  68. package/src/plugin/manifest-loader.mjs +222 -0
  69. package/src/provider/anthropic.mjs +396 -390
  70. package/src/provider/ollama.mjs +7 -1
  71. package/src/provider/openai.mjs +382 -340
  72. package/src/provider/retry-policy.mjs +74 -68
  73. package/src/provider/router.mjs +242 -241
  74. package/src/provider/sse.mjs +104 -104
  75. package/src/provider/wizard.mjs +556 -0
  76. package/src/repl/capability-facade.mjs +30 -0
  77. package/src/repl/command-surface.mjs +23 -0
  78. package/src/repl/controller-entry.mjs +40 -0
  79. package/src/repl/core-shell.mjs +208 -0
  80. package/src/repl/dialog-router.mjs +87 -0
  81. package/src/repl/input-engine.mjs +76 -0
  82. package/src/repl/keymap.mjs +7 -0
  83. package/src/repl/operator-surface.mjs +15 -0
  84. package/src/repl/permission-flow.mjs +49 -0
  85. package/src/repl/runtime-facade.mjs +36 -0
  86. package/src/repl/slash-router.mjs +62 -0
  87. package/src/repl/state-store.mjs +29 -0
  88. package/src/repl/turn-controller.mjs +58 -0
  89. package/src/repl/verification.mjs +23 -0
  90. package/src/repl.mjs +3368 -2981
  91. package/src/rules/load-rules.mjs +3 -3
  92. package/src/runtime.mjs +1 -1
  93. package/src/session/agent-transaction.mjs +86 -0
  94. package/src/session/checkpoint.mjs +302 -302
  95. package/src/session/compaction.mjs +298 -298
  96. package/src/session/engine.mjs +417 -232
  97. package/src/session/longagent-4stage.mjs +467 -460
  98. package/src/session/longagent-hybrid.mjs +1344 -1097
  99. package/src/session/longagent-plan.mjs +376 -365
  100. package/src/session/longagent-project-memory.mjs +53 -53
  101. package/src/session/longagent-scaffold.mjs +291 -291
  102. package/src/session/longagent-task-bus.mjs +138 -54
  103. package/src/session/longagent-utils.mjs +828 -472
  104. package/src/session/longagent.mjs +911 -900
  105. package/src/session/loop.mjs +1005 -930
  106. package/src/session/prompt/agent.txt +25 -25
  107. package/src/session/prompt/anthropic.txt +150 -150
  108. package/src/session/prompt/beast.txt +1 -1
  109. package/src/session/prompt/plan.txt +31 -31
  110. package/src/session/prompt/qwen.txt +46 -46
  111. package/src/session/recovery.mjs +21 -0
  112. package/src/session/rollback.mjs +196 -195
  113. package/src/session/routing-observability.mjs +72 -0
  114. package/src/session/runtime-state.mjs +47 -0
  115. package/src/session/store.mjs +523 -519
  116. package/src/session/system-prompt.mjs +308 -273
  117. package/src/session/task-validator.mjs +267 -267
  118. package/src/session/usability-gates.mjs +2 -2
  119. package/src/skill/builtin/commit.mjs +64 -64
  120. package/src/skill/builtin/design.mjs +76 -76
  121. package/src/skill/generator.mjs +18 -2
  122. package/src/skill/registry.mjs +642 -390
  123. package/src/storage/audit-store.mjs +18 -11
  124. package/src/storage/event-log.mjs +7 -1
  125. package/src/storage/ghost-commit-store.mjs +243 -245
  126. package/src/storage/paths.mjs +13 -0
  127. package/src/theme/default-theme.mjs +1 -1
  128. package/src/theme/markdown.mjs +4 -0
  129. package/src/theme/schema.mjs +1 -1
  130. package/src/theme/status-bar.mjs +162 -158
  131. package/src/tool/audit-wrapper.mjs +18 -2
  132. package/src/tool/edit-transaction.mjs +23 -0
  133. package/src/tool/executor.mjs +26 -1
  134. package/src/tool/file-read-state.mjs +65 -0
  135. package/src/tool/git-auto.mjs +526 -526
  136. package/src/tool/git-full-auto.mjs +487 -478
  137. package/src/tool/mutation-guard.mjs +54 -0
  138. package/src/tool/prompt/edit.txt +3 -3
  139. package/src/tool/prompt/multiedit.txt +1 -0
  140. package/src/tool/prompt/notebookedit.txt +2 -1
  141. package/src/tool/prompt/patch.txt +25 -24
  142. package/src/tool/prompt/read.txt +3 -3
  143. package/src/tool/prompt/sysinfo.txt +29 -0
  144. package/src/tool/prompt/task.txt +66 -4
  145. package/src/tool/prompt/write.txt +2 -2
  146. package/src/tool/question-prompt.mjs +99 -93
  147. package/src/tool/registry.mjs +1701 -1343
  148. package/src/tool/task-tool.mjs +14 -6
  149. package/src/ui/activity-renderer.mjs +667 -664
  150. package/src/ui/repl-background-panel.mjs +7 -0
  151. package/src/ui/repl-capability-panel.mjs +9 -0
  152. package/src/ui/repl-dashboard.mjs +54 -4
  153. package/src/ui/repl-help.mjs +110 -0
  154. package/src/ui/repl-operator-panel.mjs +12 -0
  155. package/src/ui/repl-route-feedback.mjs +35 -0
  156. package/src/ui/repl-status-view.mjs +76 -0
  157. package/src/ui/repl-task-panel.mjs +5 -0
  158. package/src/ui/repl-transcript-panel.mjs +56 -0
  159. package/src/ui/repl-turn-summary.mjs +135 -0
  160. package/src/usage/pricing.mjs +122 -121
  161. package/src/usage/usage-meter.mjs +1 -0
  162. package/src/util/git.mjs +562 -519
  163. package/src/util/template.mjs +6 -1
@@ -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()