@kkelly-offical/kkcode 0.1.2
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 -0
- package/README.md +445 -0
- package/package.json +46 -0
- package/src/agent/agent.mjs +170 -0
- package/src/agent/custom-agent-loader.mjs +158 -0
- package/src/agent/generator.mjs +115 -0
- package/src/agent/prompt/architect.txt +36 -0
- package/src/agent/prompt/build-fixer.txt +71 -0
- package/src/agent/prompt/build.txt +101 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +29 -0
- package/src/agent/prompt/guide.txt +40 -0
- package/src/agent/prompt/longagent.txt +178 -0
- package/src/agent/prompt/plan.txt +50 -0
- package/src/agent/prompt/researcher.txt +23 -0
- package/src/agent/prompt/reviewer.txt +44 -0
- package/src/agent/prompt/security-reviewer.txt +62 -0
- package/src/agent/prompt/tdd-guide.txt +84 -0
- package/src/agent/prompt/title.txt +8 -0
- package/src/command/custom-commands.mjs +57 -0
- package/src/commands/agent.mjs +71 -0
- package/src/commands/audit.mjs +77 -0
- package/src/commands/background.mjs +86 -0
- package/src/commands/chat.mjs +114 -0
- package/src/commands/command.mjs +41 -0
- package/src/commands/config.mjs +44 -0
- package/src/commands/doctor.mjs +148 -0
- package/src/commands/hook.mjs +29 -0
- package/src/commands/init.mjs +141 -0
- package/src/commands/longagent.mjs +100 -0
- package/src/commands/mcp.mjs +89 -0
- package/src/commands/permission.mjs +36 -0
- package/src/commands/prompt.mjs +42 -0
- package/src/commands/review.mjs +266 -0
- package/src/commands/rule.mjs +34 -0
- package/src/commands/session.mjs +235 -0
- package/src/commands/theme.mjs +98 -0
- package/src/commands/usage.mjs +91 -0
- package/src/config/defaults.mjs +195 -0
- package/src/config/import-config.mjs +76 -0
- package/src/config/load-config.mjs +76 -0
- package/src/config/schema.mjs +509 -0
- package/src/context.mjs +40 -0
- package/src/core/constants.mjs +46 -0
- package/src/core/errors.mjs +57 -0
- package/src/core/events.mjs +29 -0
- package/src/core/types.mjs +57 -0
- package/src/github/api.mjs +78 -0
- package/src/github/auth.mjs +286 -0
- package/src/github/flow.mjs +298 -0
- package/src/github/workspace.mjs +212 -0
- package/src/index.mjs +82 -0
- package/src/knowledge/api-design.txt +9 -0
- package/src/knowledge/cpp.txt +10 -0
- package/src/knowledge/docker.txt +10 -0
- package/src/knowledge/dotnet.txt +9 -0
- package/src/knowledge/electron.txt +10 -0
- package/src/knowledge/flutter.txt +10 -0
- package/src/knowledge/go.txt +9 -0
- package/src/knowledge/graphql.txt +10 -0
- package/src/knowledge/java.txt +9 -0
- package/src/knowledge/kotlin.txt +10 -0
- package/src/knowledge/loader.mjs +125 -0
- package/src/knowledge/next.txt +8 -0
- package/src/knowledge/node.txt +8 -0
- package/src/knowledge/nuxt.txt +9 -0
- package/src/knowledge/php.txt +10 -0
- package/src/knowledge/python.txt +10 -0
- package/src/knowledge/react-native.txt +10 -0
- package/src/knowledge/react.txt +9 -0
- package/src/knowledge/ruby.txt +11 -0
- package/src/knowledge/rust.txt +9 -0
- package/src/knowledge/svelte.txt +9 -0
- package/src/knowledge/swift.txt +10 -0
- package/src/knowledge/tailwind.txt +10 -0
- package/src/knowledge/testing.txt +8 -0
- package/src/knowledge/typescript.txt +8 -0
- package/src/knowledge/vue.txt +9 -0
- package/src/mcp/client-http.mjs +157 -0
- package/src/mcp/client-sse.mjs +286 -0
- package/src/mcp/client-stdio.mjs +451 -0
- package/src/mcp/registry.mjs +394 -0
- package/src/mcp/stdio-framing.mjs +127 -0
- package/src/orchestration/background-manager.mjs +358 -0
- package/src/orchestration/background-worker.mjs +245 -0
- package/src/orchestration/longagent-manager.mjs +116 -0
- package/src/orchestration/stage-scheduler.mjs +489 -0
- package/src/orchestration/subagent-router.mjs +62 -0
- package/src/orchestration/task-scheduler.mjs +74 -0
- package/src/permission/engine.mjs +92 -0
- package/src/permission/exec-policy.mjs +372 -0
- package/src/permission/prompt.mjs +39 -0
- package/src/permission/rules.mjs +120 -0
- package/src/permission/workspace-trust.mjs +44 -0
- package/src/plugin/builtin-hooks/console-warn.mjs +41 -0
- package/src/plugin/builtin-hooks/extract-patterns.mjs +75 -0
- package/src/plugin/builtin-hooks/post-edit-format.mjs +57 -0
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +61 -0
- package/src/plugin/builtin-hooks/strategic-compaction.mjs +38 -0
- package/src/plugin/hook-bus.mjs +154 -0
- package/src/provider/anthropic.mjs +389 -0
- package/src/provider/ollama.mjs +236 -0
- package/src/provider/openai-compatible.mjs +1 -0
- package/src/provider/openai.mjs +339 -0
- package/src/provider/retry-policy.mjs +68 -0
- package/src/provider/router.mjs +228 -0
- package/src/provider/sse.mjs +91 -0
- package/src/repl.mjs +2929 -0
- package/src/review/diff-parser.mjs +36 -0
- package/src/review/rejection-queue.mjs +62 -0
- package/src/review/review-store.mjs +21 -0
- package/src/review/risk-score.mjs +61 -0
- package/src/rules/load-rules.mjs +64 -0
- package/src/runtime.mjs +1 -0
- package/src/session/checkpoint.mjs +239 -0
- package/src/session/compaction.mjs +276 -0
- package/src/session/engine.mjs +225 -0
- package/src/session/instinct-manager.mjs +172 -0
- package/src/session/instruction-loader.mjs +25 -0
- package/src/session/longagent-plan.mjs +329 -0
- package/src/session/longagent-scaffold.mjs +100 -0
- package/src/session/longagent.mjs +1462 -0
- package/src/session/loop.mjs +905 -0
- package/src/session/memory-loader.mjs +75 -0
- package/src/session/project-context.mjs +367 -0
- package/src/session/prompt/anthropic.txt +151 -0
- package/src/session/prompt/beast.txt +37 -0
- package/src/session/prompt/max-steps.txt +6 -0
- package/src/session/prompt/plan.txt +9 -0
- package/src/session/prompt/qwen.txt +46 -0
- package/src/session/prompt-loader.mjs +18 -0
- package/src/session/recovery.mjs +52 -0
- package/src/session/store.mjs +503 -0
- package/src/session/system-prompt.mjs +260 -0
- package/src/session/task-validator.mjs +266 -0
- package/src/session/usability-gates.mjs +379 -0
- package/src/skill/builtin/backend-patterns.mjs +123 -0
- package/src/skill/builtin/commit.mjs +64 -0
- package/src/skill/builtin/debug.mjs +45 -0
- package/src/skill/builtin/frontend-patterns.mjs +120 -0
- package/src/skill/builtin/frontend.mjs +188 -0
- package/src/skill/builtin/init.mjs +220 -0
- package/src/skill/builtin/review.mjs +49 -0
- package/src/skill/builtin/security-checklist.mjs +80 -0
- package/src/skill/builtin/tdd.mjs +54 -0
- package/src/skill/generator.mjs +113 -0
- package/src/skill/registry.mjs +336 -0
- package/src/storage/audit-store.mjs +83 -0
- package/src/storage/event-log.mjs +82 -0
- package/src/storage/ghost-commit-store.mjs +235 -0
- package/src/storage/json-store.mjs +53 -0
- package/src/storage/paths.mjs +148 -0
- package/src/theme/color.mjs +64 -0
- package/src/theme/default-theme.mjs +29 -0
- package/src/theme/load-theme.mjs +71 -0
- package/src/theme/markdown.mjs +135 -0
- package/src/theme/schema.mjs +45 -0
- package/src/theme/status-bar.mjs +158 -0
- package/src/tool/audit-wrapper.mjs +38 -0
- package/src/tool/edit-transaction.mjs +126 -0
- package/src/tool/executor.mjs +109 -0
- package/src/tool/file-lock-manager.mjs +85 -0
- package/src/tool/git-auto.mjs +545 -0
- package/src/tool/git-full-auto.mjs +478 -0
- package/src/tool/image-util.mjs +276 -0
- package/src/tool/prompt/background_cancel.txt +1 -0
- package/src/tool/prompt/background_output.txt +1 -0
- package/src/tool/prompt/bash.txt +71 -0
- package/src/tool/prompt/codesearch.txt +18 -0
- package/src/tool/prompt/edit.txt +27 -0
- package/src/tool/prompt/enter_plan.txt +74 -0
- package/src/tool/prompt/exit_plan.txt +62 -0
- package/src/tool/prompt/glob.txt +33 -0
- package/src/tool/prompt/grep.txt +43 -0
- package/src/tool/prompt/list.txt +8 -0
- package/src/tool/prompt/multiedit.txt +20 -0
- package/src/tool/prompt/notebookedit.txt +21 -0
- package/src/tool/prompt/patch.txt +24 -0
- package/src/tool/prompt/question.txt +44 -0
- package/src/tool/prompt/read.txt +40 -0
- package/src/tool/prompt/task.txt +83 -0
- package/src/tool/prompt/todowrite.txt +117 -0
- package/src/tool/prompt/webfetch.txt +38 -0
- package/src/tool/prompt/websearch.txt +43 -0
- package/src/tool/prompt/write.txt +38 -0
- package/src/tool/prompt-loader.mjs +18 -0
- package/src/tool/question-prompt.mjs +86 -0
- package/src/tool/registry.mjs +1309 -0
- package/src/tool/task-tool.mjs +28 -0
- package/src/ui/activity-renderer.mjs +410 -0
- package/src/ui/repl-dashboard.mjs +357 -0
- package/src/usage/pricing.mjs +121 -0
- package/src/usage/usage-meter.mjs +113 -0
- package/src/util/git.mjs +496 -0
- package/src/util/template.mjs +10 -0
- package/src/util/yaml.mjs +100 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { access, readFile, writeFile, mkdir } from "node:fs/promises"
|
|
3
|
+
import { homedir } from "node:os"
|
|
4
|
+
import { spawn } from "node:child_process"
|
|
5
|
+
import { readReviewState } from "../review/review-store.mjs"
|
|
6
|
+
import { fsckSessionStore, getSession } from "./store.mjs"
|
|
7
|
+
import { EventBus } from "../core/events.mjs"
|
|
8
|
+
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
9
|
+
|
|
10
|
+
const DEFAULT_GATE_TIMEOUT_MS = 15 * 60 * 1000
|
|
11
|
+
const GATE_PREFS_FILE = path.join(homedir(), ".kkcode", "gate-preferences.json")
|
|
12
|
+
|
|
13
|
+
// --- Gate result cache (5-min TTL, only caches passing results) ---
|
|
14
|
+
const gateCache = new Map()
|
|
15
|
+
const GATE_CACHE_TTL_MS = 5 * 60 * 1000
|
|
16
|
+
|
|
17
|
+
function getCachedGate(key) {
|
|
18
|
+
const entry = gateCache.get(key)
|
|
19
|
+
if (!entry) return null
|
|
20
|
+
if (Date.now() - entry.ts > GATE_CACHE_TTL_MS) { gateCache.delete(key); return null }
|
|
21
|
+
return entry.result
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setCachedGate(key, result) {
|
|
25
|
+
if (result.status === "pass" || result.status === "not_applicable") {
|
|
26
|
+
gateCache.set(key, { result, ts: Date.now() })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function clearGateCache() { gateCache.clear() }
|
|
31
|
+
|
|
32
|
+
// --- Gate preference persistence ---
|
|
33
|
+
let cachedPrefs = null
|
|
34
|
+
|
|
35
|
+
async function loadGatePreferences() {
|
|
36
|
+
if (cachedPrefs) return cachedPrefs
|
|
37
|
+
try {
|
|
38
|
+
const raw = await readFile(GATE_PREFS_FILE, "utf8")
|
|
39
|
+
cachedPrefs = JSON.parse(raw)
|
|
40
|
+
return cachedPrefs
|
|
41
|
+
} catch {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function saveGatePreferences(prefs) {
|
|
47
|
+
cachedPrefs = prefs
|
|
48
|
+
await mkdir(path.dirname(GATE_PREFS_FILE), { recursive: true })
|
|
49
|
+
await writeFile(GATE_PREFS_FILE, JSON.stringify(prefs, null, 2), "utf8")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function hasGatePreferences() {
|
|
53
|
+
const prefs = await loadGatePreferences()
|
|
54
|
+
return prefs !== null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getGatePreferences() {
|
|
58
|
+
return loadGatePreferences()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildGatePromptText() {
|
|
62
|
+
return [
|
|
63
|
+
"[SYSTEM] LongAgent 质量门控配置",
|
|
64
|
+
"",
|
|
65
|
+
"LongAgent 完成后会运行以下质量检查门控,通过后才标记为完成:",
|
|
66
|
+
" 1. build — 运行 npm run build 检查构建是否通过",
|
|
67
|
+
" 2. test — 运行测试套件确保无回归",
|
|
68
|
+
" 3. review — 检查代码审查状态",
|
|
69
|
+
" 4. health — 检查会话存储健康状态",
|
|
70
|
+
" 5. budget — 检查 token 预算是否超限",
|
|
71
|
+
"",
|
|
72
|
+
"请选择要启用的门控(用逗号分隔,或输入 all/none):",
|
|
73
|
+
"例如: build,test 或 all 或 none",
|
|
74
|
+
"",
|
|
75
|
+
"提示:门控可以在配置文件中随时修改 (agent.longagent.usability_gates)"
|
|
76
|
+
].join("\n")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function parseGateSelection(answer) {
|
|
80
|
+
const text = String(answer || "").toLowerCase().trim()
|
|
81
|
+
const gates = ["build", "test", "review", "health", "budget"]
|
|
82
|
+
if (text === "all" || text === "全部" || text === "所有") {
|
|
83
|
+
return Object.fromEntries(gates.map(g => [g, true]))
|
|
84
|
+
}
|
|
85
|
+
if (text === "none" || text === "无" || text === "不需要" || text === "跳过") {
|
|
86
|
+
return Object.fromEntries(gates.map(g => [g, false]))
|
|
87
|
+
}
|
|
88
|
+
const selected = new Set(
|
|
89
|
+
text.split(/[,,\s]+/).map(s => s.trim()).filter(Boolean)
|
|
90
|
+
)
|
|
91
|
+
return Object.fromEntries(gates.map(g => [g, selected.has(g)]))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isEnabled(config, gateName) {
|
|
95
|
+
return config?.agent?.longagent?.usability_gates?.[gateName]?.enabled !== false
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function fileExists(file) {
|
|
99
|
+
try {
|
|
100
|
+
await access(file)
|
|
101
|
+
return true
|
|
102
|
+
} catch {
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function readPackageScripts(cwd) {
|
|
108
|
+
const pkgPath = path.join(cwd, "package.json")
|
|
109
|
+
const raw = await readFile(pkgPath, "utf8").catch(() => null)
|
|
110
|
+
if (!raw) return null
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(raw)
|
|
113
|
+
return parsed?.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {}
|
|
114
|
+
} catch {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function npmBin() {
|
|
120
|
+
return process.platform === "win32" ? "npm.cmd" : "npm"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function outputSnippet(result) {
|
|
124
|
+
const lines = `${result.stdout || ""}\n${result.stderr || ""}`
|
|
125
|
+
.split(/\r?\n/)
|
|
126
|
+
.map((line) => line.trim())
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
return lines.slice(-12).join(" | ")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function runCommand({ command, args, cwd, timeoutMs = DEFAULT_GATE_TIMEOUT_MS }) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
let done = false
|
|
134
|
+
let stdout = ""
|
|
135
|
+
let stderr = ""
|
|
136
|
+
let timedOut = false
|
|
137
|
+
|
|
138
|
+
const child = spawn(command, args, {
|
|
139
|
+
cwd,
|
|
140
|
+
windowsHide: true,
|
|
141
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
timedOut = true
|
|
146
|
+
child.kill()
|
|
147
|
+
}, timeoutMs)
|
|
148
|
+
|
|
149
|
+
child.stdout.on("data", (buf) => {
|
|
150
|
+
stdout += String(buf)
|
|
151
|
+
})
|
|
152
|
+
child.stderr.on("data", (buf) => {
|
|
153
|
+
stderr += String(buf)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
child.on("error", (error) => {
|
|
157
|
+
if (done) return
|
|
158
|
+
done = true
|
|
159
|
+
clearTimeout(timer)
|
|
160
|
+
resolve({
|
|
161
|
+
ok: false,
|
|
162
|
+
code: null,
|
|
163
|
+
stdout,
|
|
164
|
+
stderr: `${stderr}\n${error.message}`.trim(),
|
|
165
|
+
timedOut: false
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
child.on("close", (code) => {
|
|
170
|
+
if (done) return
|
|
171
|
+
done = true
|
|
172
|
+
clearTimeout(timer)
|
|
173
|
+
resolve({
|
|
174
|
+
ok: !timedOut && code === 0,
|
|
175
|
+
code,
|
|
176
|
+
stdout,
|
|
177
|
+
stderr,
|
|
178
|
+
timedOut
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function checkBuildGate({ cwd, config }) {
|
|
185
|
+
const cached = getCachedGate("build"); if (cached) return cached
|
|
186
|
+
if (!isEnabled(config, "build")) {
|
|
187
|
+
return { enabled: false, status: "disabled", reason: "build gate disabled" }
|
|
188
|
+
}
|
|
189
|
+
const scripts = await readPackageScripts(cwd)
|
|
190
|
+
if (!scripts) {
|
|
191
|
+
return { enabled: true, status: "not_applicable", reason: "package.json not found" }
|
|
192
|
+
}
|
|
193
|
+
if (!scripts.build) {
|
|
194
|
+
return { enabled: true, status: "not_applicable", reason: "build script not found" }
|
|
195
|
+
}
|
|
196
|
+
const result = await runCommand({
|
|
197
|
+
command: npmBin(),
|
|
198
|
+
args: ["run", "build", "--silent"],
|
|
199
|
+
cwd
|
|
200
|
+
})
|
|
201
|
+
if (result.ok) {
|
|
202
|
+
const r = { enabled: true, status: "pass", reason: "build succeeded" }
|
|
203
|
+
setCachedGate("build", r); return r
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
enabled: true,
|
|
207
|
+
status: "fail",
|
|
208
|
+
reason: result.timedOut ? "build timed out" : `build failed with code ${result.code}`,
|
|
209
|
+
output: outputSnippet(result)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function checkTestGate({ cwd, config }) {
|
|
214
|
+
const cached = getCachedGate("test"); if (cached) return cached
|
|
215
|
+
if (!isEnabled(config, "test")) {
|
|
216
|
+
return { enabled: false, status: "disabled", reason: "test gate disabled" }
|
|
217
|
+
}
|
|
218
|
+
const scripts = await readPackageScripts(cwd)
|
|
219
|
+
const hasTestDir = await fileExists(path.join(cwd, "test"))
|
|
220
|
+
const hasNodeTestDir = await fileExists(path.join(cwd, "tests"))
|
|
221
|
+
|
|
222
|
+
if (!scripts && !hasTestDir && !hasNodeTestDir) {
|
|
223
|
+
return { enabled: true, status: "not_applicable", reason: "no package.json or test directory" }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let result
|
|
227
|
+
if (scripts?.test) {
|
|
228
|
+
result = await runCommand({
|
|
229
|
+
command: npmBin(),
|
|
230
|
+
args: ["run", "test", "--silent"],
|
|
231
|
+
cwd
|
|
232
|
+
})
|
|
233
|
+
} else if (hasTestDir || hasNodeTestDir) {
|
|
234
|
+
result = await runCommand({
|
|
235
|
+
command: process.execPath,
|
|
236
|
+
args: ["--test"],
|
|
237
|
+
cwd
|
|
238
|
+
})
|
|
239
|
+
} else {
|
|
240
|
+
return { enabled: true, status: "not_applicable", reason: "test script not found" }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (result.ok) {
|
|
244
|
+
const r = { enabled: true, status: "pass", reason: "tests succeeded" }
|
|
245
|
+
setCachedGate("test", r); return r
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
enabled: true,
|
|
249
|
+
status: "fail",
|
|
250
|
+
reason: result.timedOut ? "tests timed out" : `tests failed with code ${result.code}`,
|
|
251
|
+
output: outputSnippet(result)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function checkReviewGate({ cwd, config, sessionId }) {
|
|
256
|
+
const cached = getCachedGate("review"); if (cached) return cached
|
|
257
|
+
if (!isEnabled(config, "review")) {
|
|
258
|
+
return { enabled: false, status: "disabled", reason: "review gate disabled" }
|
|
259
|
+
}
|
|
260
|
+
const state = await readReviewState(cwd)
|
|
261
|
+
if (!state.files.length) {
|
|
262
|
+
return { enabled: true, status: "not_applicable", reason: "no review file state" }
|
|
263
|
+
}
|
|
264
|
+
if (state.sessionId && sessionId && state.sessionId !== sessionId) {
|
|
265
|
+
return {
|
|
266
|
+
enabled: true,
|
|
267
|
+
status: "not_applicable",
|
|
268
|
+
reason: `review state belongs to other session (${state.sessionId})`
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const pending = state.files.filter((file) => file.status !== "approved")
|
|
272
|
+
if (pending.length > 0) {
|
|
273
|
+
return {
|
|
274
|
+
enabled: true,
|
|
275
|
+
status: "fail",
|
|
276
|
+
reason: `${pending.length} review item(s) not approved`,
|
|
277
|
+
output: pending.slice(0, 5).map((item) => item.path).join(", ")
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const r = { enabled: true, status: "pass", reason: "all review items approved" }
|
|
281
|
+
setCachedGate("review", r); return r
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function checkHealthGate({ config }) {
|
|
285
|
+
const cached = getCachedGate("health"); if (cached) return cached
|
|
286
|
+
if (!isEnabled(config, "health")) {
|
|
287
|
+
return { enabled: false, status: "disabled", reason: "health gate disabled" }
|
|
288
|
+
}
|
|
289
|
+
const report = await fsckSessionStore()
|
|
290
|
+
if (report.ok) {
|
|
291
|
+
const r = { enabled: true, status: "pass", reason: "session fsck passed" }
|
|
292
|
+
setCachedGate("health", r); return r
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
enabled: true,
|
|
296
|
+
status: "fail",
|
|
297
|
+
reason: "session fsck failed",
|
|
298
|
+
output: report.suggestions.join(" | ")
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function checkBudgetGate({ config, sessionId }) {
|
|
303
|
+
const cached = getCachedGate("budget"); if (cached) return cached
|
|
304
|
+
if (!isEnabled(config, "budget")) {
|
|
305
|
+
return { enabled: false, status: "disabled", reason: "budget gate disabled" }
|
|
306
|
+
}
|
|
307
|
+
const sessionData = await getSession(sessionId)
|
|
308
|
+
const budgetState = sessionData?.session?.budgetState || null
|
|
309
|
+
if (!budgetState) {
|
|
310
|
+
return { enabled: true, status: "pass", reason: "no budget restriction state" }
|
|
311
|
+
}
|
|
312
|
+
const strategy = config?.usage?.budget?.strategy || "warn"
|
|
313
|
+
if (budgetState.exceeded && strategy === "block") {
|
|
314
|
+
return {
|
|
315
|
+
enabled: true,
|
|
316
|
+
status: "fail",
|
|
317
|
+
reason: "budget exceeded with strategy=block",
|
|
318
|
+
output: (budgetState.warnings || []).join(" | ")
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if ((budgetState.warnings || []).length > 0) {
|
|
322
|
+
return {
|
|
323
|
+
enabled: true,
|
|
324
|
+
status: "warn",
|
|
325
|
+
reason: "budget warning",
|
|
326
|
+
output: budgetState.warnings.join(" | ")
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const r = { enabled: true, status: "pass", reason: "budget gate passed" }
|
|
330
|
+
setCachedGate("budget", r); return r
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function isPassingStatus(status) {
|
|
334
|
+
return status === "pass" || status === "not_applicable"
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export async function runUsabilityGates({
|
|
338
|
+
sessionId,
|
|
339
|
+
config,
|
|
340
|
+
cwd = process.cwd(),
|
|
341
|
+
iteration = 0
|
|
342
|
+
}) {
|
|
343
|
+
const [build, test, review, health, budget] = await Promise.all([
|
|
344
|
+
checkBuildGate({ cwd, config }),
|
|
345
|
+
checkTestGate({ cwd, config }),
|
|
346
|
+
checkReviewGate({ cwd, config, sessionId }),
|
|
347
|
+
checkHealthGate({ config }),
|
|
348
|
+
checkBudgetGate({ config, sessionId })
|
|
349
|
+
])
|
|
350
|
+
const checks = { build, test, review, health, budget }
|
|
351
|
+
|
|
352
|
+
for (const [gate, result] of Object.entries(checks)) {
|
|
353
|
+
await EventBus.emit({
|
|
354
|
+
type: EVENT_TYPES.LONGAGENT_GATE_CHECKED,
|
|
355
|
+
sessionId,
|
|
356
|
+
payload: {
|
|
357
|
+
gate,
|
|
358
|
+
status: result.status,
|
|
359
|
+
reason: result.reason,
|
|
360
|
+
iteration
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const failures = Object.entries(checks)
|
|
366
|
+
.filter(([, result]) => result.enabled !== false && !isPassingStatus(result.status))
|
|
367
|
+
.map(([gate, result]) => ({
|
|
368
|
+
gate,
|
|
369
|
+
status: result.status,
|
|
370
|
+
reason: result.reason,
|
|
371
|
+
output: result.output || ""
|
|
372
|
+
}))
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
allPass: failures.length === 0,
|
|
376
|
+
gates: checks,
|
|
377
|
+
failures
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export const name = "backend-patterns"
|
|
2
|
+
export const description = "Backend development patterns reference: API design, repository pattern, middleware, error handling, authentication"
|
|
3
|
+
|
|
4
|
+
export async function run(ctx) {
|
|
5
|
+
const topic = (ctx.args || "").trim().toLowerCase()
|
|
6
|
+
|
|
7
|
+
const sections = {
|
|
8
|
+
api: `## API Design Patterns
|
|
9
|
+
|
|
10
|
+
### RESTful Conventions
|
|
11
|
+
- GET /resources — list (with pagination: ?page=1&limit=20)
|
|
12
|
+
- GET /resources/:id — get single
|
|
13
|
+
- POST /resources — create (return 201 + Location header)
|
|
14
|
+
- PUT /resources/:id — full replace
|
|
15
|
+
- PATCH /resources/:id — partial update
|
|
16
|
+
- DELETE /resources/:id — remove (return 204)
|
|
17
|
+
|
|
18
|
+
### Response Format
|
|
19
|
+
\`\`\`json
|
|
20
|
+
{
|
|
21
|
+
"data": { ... },
|
|
22
|
+
"meta": { "page": 1, "total": 42, "limit": 20 },
|
|
23
|
+
"error": null
|
|
24
|
+
}
|
|
25
|
+
\`\`\`
|
|
26
|
+
|
|
27
|
+
### Error Responses
|
|
28
|
+
\`\`\`json
|
|
29
|
+
{
|
|
30
|
+
"error": {
|
|
31
|
+
"code": "VALIDATION_ERROR",
|
|
32
|
+
"message": "Email is required",
|
|
33
|
+
"details": [{ "field": "email", "rule": "required" }]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
\`\`\`
|
|
37
|
+
Status codes: 400 validation, 401 unauthenticated, 403 unauthorized, 404 not found, 409 conflict, 422 unprocessable, 429 rate limited, 500 server error`,
|
|
38
|
+
|
|
39
|
+
repository: `## Repository Pattern
|
|
40
|
+
|
|
41
|
+
Separate data access from business logic:
|
|
42
|
+
\`\`\`
|
|
43
|
+
Controller → Service → Repository → Database
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
- **Repository**: handles queries, CRUD, caching. Returns domain objects.
|
|
47
|
+
- **Service**: business rules, validation, orchestration. Calls repositories.
|
|
48
|
+
- **Controller**: HTTP concerns only. Parses request, calls service, formats response.
|
|
49
|
+
|
|
50
|
+
Benefits: testable (mock repository in service tests), swappable storage, clear boundaries.`,
|
|
51
|
+
|
|
52
|
+
middleware: `## Middleware Patterns
|
|
53
|
+
|
|
54
|
+
Execution order matters. Typical chain:
|
|
55
|
+
1. **CORS** — set access headers
|
|
56
|
+
2. **Request ID** — assign unique ID for tracing
|
|
57
|
+
3. **Logger** — log method, path, duration, status
|
|
58
|
+
4. **Rate limiter** — throttle by IP or API key
|
|
59
|
+
5. **Auth** — verify JWT/session, attach user to request
|
|
60
|
+
6. **Validation** — validate request body/params against schema
|
|
61
|
+
7. **Handler** — actual business logic
|
|
62
|
+
8. **Error handler** — catch errors, format response (always LAST)`,
|
|
63
|
+
|
|
64
|
+
auth: `## Authentication Patterns
|
|
65
|
+
|
|
66
|
+
### JWT (Stateless)
|
|
67
|
+
- Access token (short-lived: 15min) + Refresh token (long-lived: 7d)
|
|
68
|
+
- Store refresh in httpOnly cookie, access in memory (NOT localStorage)
|
|
69
|
+
- Rotate refresh tokens on use (one-time use)
|
|
70
|
+
|
|
71
|
+
### Session (Stateful)
|
|
72
|
+
- Server-side session store (Redis for multi-server)
|
|
73
|
+
- Session ID in httpOnly, Secure, SameSite=Strict cookie
|
|
74
|
+
- Regenerate session ID after login (prevent fixation)
|
|
75
|
+
|
|
76
|
+
### OAuth 2.0
|
|
77
|
+
- Always validate \`state\` parameter (CSRF protection)
|
|
78
|
+
- Use PKCE for public clients (SPAs, mobile)
|
|
79
|
+
- Exchange code for tokens server-side (never client-side)`,
|
|
80
|
+
|
|
81
|
+
error: `## Error Handling
|
|
82
|
+
|
|
83
|
+
### Layered Error Strategy
|
|
84
|
+
- **Repository**: throw typed errors (NotFoundError, ConflictError)
|
|
85
|
+
- **Service**: catch repo errors, add business context, re-throw
|
|
86
|
+
- **Controller/Middleware**: catch all, map to HTTP status, log, respond
|
|
87
|
+
|
|
88
|
+
### Custom Error Classes
|
|
89
|
+
\`\`\`javascript
|
|
90
|
+
class AppError extends Error {
|
|
91
|
+
constructor(message, code, statusCode = 500) {
|
|
92
|
+
super(message)
|
|
93
|
+
this.code = code
|
|
94
|
+
this.statusCode = statusCode
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
class NotFoundError extends AppError {
|
|
98
|
+
constructor(resource, id) {
|
|
99
|
+
super(\`\${resource} \${id} not found\`, "NOT_FOUND", 404)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
\`\`\`
|
|
103
|
+
|
|
104
|
+
### Never expose internals
|
|
105
|
+
- Log full stack trace server-side
|
|
106
|
+
- Return sanitized message to client
|
|
107
|
+
- Never leak database errors, file paths, or config values`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (topic && sections[topic]) {
|
|
111
|
+
return sections[topic]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Return overview with all sections
|
|
115
|
+
const overview = Object.values(sections).join("\n\n---\n\n")
|
|
116
|
+
return `# Backend Development Patterns
|
|
117
|
+
|
|
118
|
+
Use \`/backend-patterns <topic>\` for a specific section: api, repository, middleware, auth, error
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
${overview}`
|
|
123
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const name = "commit"
|
|
2
|
+
export const description = "Stage changes and create a git commit with a descriptive message using AI-powered git automation"
|
|
3
|
+
|
|
4
|
+
export async function run(args, context = {}) {
|
|
5
|
+
const hasGitAuto = context.config?.git_auto?.enabled !== false
|
|
6
|
+
|
|
7
|
+
return `Review the current git status and create a well-structured commit.
|
|
8
|
+
|
|
9
|
+
${hasGitAuto ? `🚀 Git Auto Mode Enabled
|
|
10
|
+
The AI can now use ghost commits (temporary snapshots) to safely manage changes before you finalize them.
|
|
11
|
+
` : `⚙️ Standard Mode
|
|
12
|
+
Consider enabling git_auto in your config for enhanced safety features.
|
|
13
|
+
`}
|
|
14
|
+
|
|
15
|
+
Steps:
|
|
16
|
+
1. Run \`git_info\` to understand the repository context.
|
|
17
|
+
2. Run \`git_status\` to see all changed, staged, and untracked files.
|
|
18
|
+
3. Review the changes to understand what needs to be committed.
|
|
19
|
+
|
|
20
|
+
${hasGitAuto ? `4. **IMPORTANT**: Before making any edits, create a ghost commit snapshot:
|
|
21
|
+
\`git_snapshot\` - Creates a temporary snapshot you can restore later
|
|
22
|
+
|
|
23
|
+
5. After reviewing, if you need to make changes, the AI will:
|
|
24
|
+
- First create an automatic snapshot (if git_auto.auto_snapshot is enabled)
|
|
25
|
+
- Apply changes using \`edit\`, \`write\`, or \`git_apply_patch\`
|
|
26
|
+
|
|
27
|
+
6. If you're not satisfied with the changes:
|
|
28
|
+
- Use \`git_list_snapshots\` to see available snapshots
|
|
29
|
+
- Use \`git_restore\` with the snapshot_id to revert
|
|
30
|
+
|
|
31
|
+
7. When satisfied with the changes, guide the user to manually run:
|
|
32
|
+
\`bash: git add <files> && git commit -m "<message>"\`
|
|
33
|
+
Note: AI is forbidden from running git commit directly for security.`
|
|
34
|
+
|
|
35
|
+
: `4. Stage the relevant files with manual git commands (AI cannot run git commit):
|
|
36
|
+
- AI can suggest: \`bash: git add <files>\`
|
|
37
|
+
- But user must manually run: \`git commit -m "<message>"\`
|
|
38
|
+
|
|
39
|
+
5. Note: AI is forbidden from executing git commit/push for security reasons.`}
|
|
40
|
+
|
|
41
|
+
Commit Message Format (Conventional Commits):
|
|
42
|
+
- feat: new feature or capability
|
|
43
|
+
- fix: bug fix
|
|
44
|
+
- refactor: code restructuring without behavior change
|
|
45
|
+
- docs: documentation changes
|
|
46
|
+
- style: formatting, whitespace, semicolons
|
|
47
|
+
- test: adding or updating tests
|
|
48
|
+
- chore: build process, dependencies, tooling
|
|
49
|
+
|
|
50
|
+
Format: <type>(<optional scope>): <short description>
|
|
51
|
+
Example: feat(auth): add JWT token refresh logic
|
|
52
|
+
|
|
53
|
+
Important:
|
|
54
|
+
- Keep the commit focused on a single logical change.
|
|
55
|
+
- If there are unrelated changes, create separate commits.
|
|
56
|
+
- The commit message subject should be under 72 characters.
|
|
57
|
+
- Use imperative mood: "add feature" not "added feature".
|
|
58
|
+
|
|
59
|
+
${hasGitAuto ? `Safety Features:
|
|
60
|
+
- Ghost commits are stored for 7 days then auto-cleaned
|
|
61
|
+
- Maximum 50 snapshots per repository
|
|
62
|
+
- Snapshots don't interfere with your normal git workflow
|
|
63
|
+
- Use \`git_cleanup\` to manually clean up expired snapshots` : ""}`
|
|
64
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const name = "debug"
|
|
2
|
+
export const description = "Diagnose and fix a bug or error (usage: /debug <error description or message>)"
|
|
3
|
+
|
|
4
|
+
export async function run(ctx) {
|
|
5
|
+
const issue = (ctx.args || "").trim()
|
|
6
|
+
|
|
7
|
+
if (!issue) {
|
|
8
|
+
return `Please describe the bug or paste the error message.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
/debug TypeError: Cannot read properties of undefined
|
|
12
|
+
/debug the login page shows a blank screen after submit
|
|
13
|
+
/debug test suite fails on CI but passes locally`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return `Debug this issue: ${issue}
|
|
17
|
+
|
|
18
|
+
Follow this systematic approach:
|
|
19
|
+
|
|
20
|
+
1. **Reproduce**
|
|
21
|
+
- Identify the minimal steps or command to trigger the issue.
|
|
22
|
+
- If an error message was given, search the codebase for the source: \`grep -r "<key phrase>" src/\`
|
|
23
|
+
- If a test fails, run it in isolation to confirm.
|
|
24
|
+
|
|
25
|
+
2. **Locate root cause**
|
|
26
|
+
- Trace the execution path from the error location backward.
|
|
27
|
+
- Read the relevant source files to understand the logic.
|
|
28
|
+
- Check recent changes: \`git log --oneline -10\` and \`git diff HEAD~3\` for potential regressions.
|
|
29
|
+
- Add diagnostic logging or assertions if the cause isn't obvious.
|
|
30
|
+
|
|
31
|
+
3. **Fix**
|
|
32
|
+
- Apply the minimal change that addresses the root cause.
|
|
33
|
+
- Do NOT refactor surrounding code or fix unrelated issues.
|
|
34
|
+
- Preserve existing behavior for all other code paths.
|
|
35
|
+
|
|
36
|
+
4. **Verify**
|
|
37
|
+
- Run the reproduction steps again to confirm the fix.
|
|
38
|
+
- Run related tests if they exist.
|
|
39
|
+
- Check for regressions in adjacent functionality.
|
|
40
|
+
|
|
41
|
+
5. **Output**
|
|
42
|
+
- State the root cause in one sentence.
|
|
43
|
+
- Show the exact change made (file, line, before/after).
|
|
44
|
+
- List verification steps performed.`
|
|
45
|
+
}
|