@shawnstack/quickforge 1.1.0 → 1.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/README.md +1 -1
- package/bin/quickforge.mjs +72 -7
- package/dist/assets/{anthropic-By-wpU1w.js → anthropic-DLvtwHL2.js} +2 -2
- package/dist/assets/{azure-openai-responses-C8spS__i.js → azure-openai-responses-D68z7hLN.js} +1 -1
- package/dist/assets/css-utils-rkE68RDy.js +1 -0
- package/dist/assets/{google-DiIcyajo.js → google-B_sSaRBM.js} +1 -1
- package/dist/assets/{google-gemini-cli-BXZFGMXD.js → google-gemini-cli-CYqGXjGi.js} +1 -1
- package/dist/assets/google-shared-XhYUKiGZ.js +11 -0
- package/dist/assets/{google-vertex-D93MV5Cx.js → google-vertex-DSMuB4YB.js} +1 -1
- package/dist/assets/icons-BsZ9PlYY.js +1 -0
- package/dist/assets/index-BqFfVQJM.css +3 -0
- package/dist/assets/index-DoraECXN.js +3187 -0
- package/dist/assets/lit-vendor-1dsGB-Iy.js +2 -0
- package/dist/assets/{mistral-BAJNGYqd.js → mistral-BZngRB4x.js} +2 -2
- package/dist/assets/{openai-codex-responses-BHHCy65K.js → openai-codex-responses-Niu7xDYK.js} +1 -1
- package/dist/assets/openai-completions-B2bhb9k0.js +5 -0
- package/dist/assets/{openai-responses-CP9-AyAD.js → openai-responses-CDYDv8yL.js} +1 -1
- package/dist/assets/{openai-responses-shared-_z7sua8J.js → openai-responses-shared-BIKPTpEQ.js} +1 -1
- package/dist/assets/react-vendor-Ds3ovY0w.js +9 -0
- package/dist/assets/rolldown-runtime-CkqCuyE9.js +1 -0
- package/dist/index.html +7 -3
- package/package.json +14 -13
- package/server/agent-manager.mjs +1053 -0
- package/server/conversation-compaction.mjs +302 -0
- package/server/custom-commands.mjs +344 -0
- package/server/index.mjs +322 -32
- package/server/project-config.mjs +80 -31
- package/server/reasoning-cache.mjs +51 -0
- package/server/restart-supervisor.mjs +38 -0
- package/server/routes/agent.mjs +323 -0
- package/server/routes/backup.mjs +250 -0
- package/server/routes/instructions.mjs +6 -17
- package/server/routes/project.mjs +46 -10
- package/server/routes/scheduled-tasks.mjs +424 -0
- package/server/routes/shared-conversation.mjs +404 -0
- package/server/routes/shares.mjs +84 -0
- package/server/routes/skills.mjs +145 -0
- package/server/routes/static.mjs +4 -3
- package/server/routes/storage.mjs +58 -10
- package/server/routes/system.mjs +35 -0
- package/server/routes/tools.mjs +53 -2
- package/server/session-utils.mjs +102 -0
- package/server/share-store.mjs +468 -0
- package/server/skills.mjs +539 -0
- package/server/storage.mjs +247 -6
- package/server/system-prompt.mjs +67 -0
- package/server/tools/definitions.mjs +120 -0
- package/server/tools/index.mjs +167 -46
- package/server/utils/logger.mjs +34 -0
- package/server/utils/network.mjs +38 -0
- package/server/utils/platform.mjs +30 -0
- package/server/utils/response.mjs +8 -1
- package/skills/ai-context-package/SKILL.md +104 -0
- package/skills/ai-context-package/skill.json +9 -0
- package/skills/code-review/SKILL.md +23 -0
- package/skills/code-review/skill.json +9 -0
- package/skills/frontend-react/SKILL.md +22 -0
- package/skills/frontend-react/skill.json +9 -0
- package/skills/quickforge-project/SKILL.md +22 -0
- package/skills/quickforge-project/skill.json +9 -0
- package/dist/assets/chunk-62oNxeRG.js +0 -1
- package/dist/assets/confirm-dialog-4mZt9XEq.js +0 -1
- package/dist/assets/google-shared-CXUHW-9O.js +0 -11
- package/dist/assets/index-Bq6VHkyY.js +0 -3048
- package/dist/assets/index-D7uXa1RT.css +0 -3
- package/dist/assets/openai-completions-BtZAvOiJ.js +0 -5
- package/dist/assets/prompt-dialog-BGMKszUz.js +0 -1
- /package/dist/assets/{github-copilot-headers-C0toI16e.js → github-copilot-headers-CrI0CIJ7.js} +0 -0
- /package/dist/assets/{hash-fDQBJsbb.js → hash-Bt1aVMQ3.js} +0 -0
- /package/dist/assets/{headers-Drkm68SQ.js → headers-5EYI0_pl.js} +0 -0
- /package/dist/assets/{openai-CuiHR4mv.js → openai-Cn7eGqwa.js} +0 -0
- /package/dist/assets/{transform-messages-BFwlToJ0.js → transform-messages-CV4kCtBB.js} +0 -0
package/server/tools/index.mjs
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { spawn } from 'node:child_process'
|
|
4
|
-
import { resolveWorkspacePath, toWorkspaceRelative, assertSafeWorkspacePath, truncateText, splitLines,
|
|
4
|
+
import { resolveWorkspacePath, toWorkspaceRelative, assertSafeWorkspacePath, truncateText, splitLines, walkFiles } from '../utils/workspace.mjs'
|
|
5
5
|
import { readProjectConfig, getActiveProject } from '../project-config.mjs'
|
|
6
|
+
import {
|
|
7
|
+
formatSkillActivation,
|
|
8
|
+
loadSelectedGlobalSkills,
|
|
9
|
+
loadSelectedProjectSkills,
|
|
10
|
+
mergeSkills,
|
|
11
|
+
readSkillResource,
|
|
12
|
+
} from '../skills.mjs'
|
|
6
13
|
import { getWorkspaceRoot, getToolWorkspaceRoot } from '../utils/workspace.mjs'
|
|
7
14
|
|
|
15
|
+
// --- get_project_info ---
|
|
16
|
+
export async function toolGetProjectInfo(_params, context) {
|
|
17
|
+
const config = context?.project ? null : await readProjectConfig()
|
|
18
|
+
const project = context?.project || getActiveProject(config)
|
|
19
|
+
const workspaceRoot = context?.workspaceRoot || project?.path || getWorkspaceRoot()
|
|
20
|
+
|
|
21
|
+
if (!project) {
|
|
22
|
+
return {
|
|
23
|
+
content: 'No active project is configured.',
|
|
24
|
+
details: { project: null, workspaceRoot },
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
content: [`Project: ${project.name}`, `Path: ${workspaceRoot}`, `ID: ${project.id}`].join('\n'),
|
|
30
|
+
details: { project, workspaceRoot },
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
8
34
|
// --- list_dir ---
|
|
9
35
|
export async function toolListDir(params, context) {
|
|
10
36
|
const dir = resolveWorkspacePath(params?.path || '.', context)
|
|
@@ -54,6 +80,31 @@ export async function toolReadFile(params, context) {
|
|
|
54
80
|
}
|
|
55
81
|
|
|
56
82
|
// --- grep_files ---
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Process items with bounded concurrency. Returns results in input order.
|
|
86
|
+
* @template T, R
|
|
87
|
+
* @param {T[]} items
|
|
88
|
+
* @param {(item: T, index: number) => Promise<R>} fn
|
|
89
|
+
* @param {number} concurrency
|
|
90
|
+
* @returns {Promise<R[]>}
|
|
91
|
+
*/
|
|
92
|
+
async function poolMap(items, fn, concurrency = 20) {
|
|
93
|
+
const results = new Array(items.length)
|
|
94
|
+
let cursor = 0
|
|
95
|
+
|
|
96
|
+
async function worker() {
|
|
97
|
+
while (cursor < items.length) {
|
|
98
|
+
const index = cursor++
|
|
99
|
+
results[index] = await fn(items[index], index)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker())
|
|
104
|
+
await Promise.all(workers)
|
|
105
|
+
return results
|
|
106
|
+
}
|
|
107
|
+
|
|
57
108
|
export async function toolGrepFiles(params, context) {
|
|
58
109
|
const root = resolveWorkspacePath(params?.path || '.', context)
|
|
59
110
|
assertSafeWorkspacePath(root, context)
|
|
@@ -67,24 +118,62 @@ export async function toolGrepFiles(params, context) {
|
|
|
67
118
|
|
|
68
119
|
const limit = Math.min(1000, Math.max(1, Number(params?.limit || 200)))
|
|
69
120
|
const flags = params?.caseSensitive ? 'g' : 'gi'
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
121
|
+
let matcher
|
|
122
|
+
try {
|
|
123
|
+
matcher = params?.regex
|
|
124
|
+
? new RegExp(query, flags)
|
|
125
|
+
: new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags)
|
|
126
|
+
} catch {
|
|
127
|
+
const error = new Error('Invalid regular expression')
|
|
128
|
+
error.statusCode = 400
|
|
129
|
+
throw error
|
|
130
|
+
}
|
|
73
131
|
|
|
74
132
|
const files = await walkFiles(root, [], context)
|
|
75
133
|
const matches = []
|
|
76
134
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
135
|
+
// Stat and filter files in parallel, then grep in parallel batches
|
|
136
|
+
const candidateResults = await poolMap(files, async (file) => {
|
|
137
|
+
try {
|
|
138
|
+
const stat = await fs.stat(file)
|
|
139
|
+
if (stat.size > 1024 * 1024) return { file, skip: true }
|
|
140
|
+
return { file, skip: false }
|
|
141
|
+
} catch {
|
|
142
|
+
return { file, skip: true }
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const candidates = candidateResults.filter((r) => !r.skip).map((r) => r.file)
|
|
147
|
+
|
|
148
|
+
// Grep with bounded concurrency — short-circuit when limit reached
|
|
149
|
+
let matchCount = 0
|
|
150
|
+
for (let batchStart = 0; batchStart < candidates.length && matchCount < limit; batchStart += 20) {
|
|
151
|
+
const batch = candidates.slice(batchStart, batchStart + 20)
|
|
152
|
+
const batchMatches = await Promise.all(
|
|
153
|
+
batch.map(async (file) => {
|
|
154
|
+
if (matchCount >= limit) return []
|
|
155
|
+
try {
|
|
156
|
+
const text = await fs.readFile(file, 'utf8')
|
|
157
|
+
const lines = splitLines(text)
|
|
158
|
+
const fileMatches = []
|
|
159
|
+
for (let index = 0; index < lines.length && (matchCount + fileMatches.length) < limit; index++) {
|
|
160
|
+
matcher.lastIndex = 0
|
|
161
|
+
if (matcher.test(lines[index])) {
|
|
162
|
+
fileMatches.push(`${toWorkspaceRelative(file, context)}:${index + 1}: ${lines[index]}`)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return fileMatches
|
|
166
|
+
} catch {
|
|
167
|
+
return []
|
|
168
|
+
}
|
|
169
|
+
}),
|
|
170
|
+
)
|
|
171
|
+
for (const fm of batchMatches) {
|
|
172
|
+
if (matchCount >= limit) break
|
|
173
|
+
for (const m of fm) {
|
|
174
|
+
if (matchCount >= limit) break
|
|
175
|
+
matches.push(m)
|
|
176
|
+
matchCount++
|
|
88
177
|
}
|
|
89
178
|
}
|
|
90
179
|
}
|
|
@@ -144,6 +233,59 @@ export async function toolEditFile(params, context) {
|
|
|
144
233
|
}
|
|
145
234
|
}
|
|
146
235
|
|
|
236
|
+
// --- run_command ---
|
|
237
|
+
function activeSkillsForContext(context) {
|
|
238
|
+
return mergeSkills(context?.globalSkills, context?.projectSkills)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function activeSkillByName(context, name) {
|
|
242
|
+
const skillName = String(name || '')
|
|
243
|
+
return activeSkillsForContext(context).find((skill) => skill.name === skillName)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function loadSkillToolContext(config = {}) {
|
|
247
|
+
const globalSkills = await loadSelectedGlobalSkills(config.globalSkillNames)
|
|
248
|
+
const projectSkills = config.workspaceRoot
|
|
249
|
+
? await loadSelectedProjectSkills(config.projectSkillNames, config.workspaceRoot)
|
|
250
|
+
: []
|
|
251
|
+
return { globalSkills, projectSkills }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// --- activate_skill ---
|
|
255
|
+
export async function toolActivateSkill(params, context) {
|
|
256
|
+
const skill = activeSkillByName(context, params?.name)
|
|
257
|
+
if (!skill) {
|
|
258
|
+
const error = new Error(`Unknown or disabled skill: ${params?.name || ''}`)
|
|
259
|
+
error.statusCode = 404
|
|
260
|
+
throw error
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
content: truncateText(await formatSkillActivation(skill)),
|
|
265
|
+
details: {
|
|
266
|
+
skill: skill.name,
|
|
267
|
+
source: skill.source,
|
|
268
|
+
directory: skill.rootDir,
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// --- read_skill_resource ---
|
|
274
|
+
export async function toolReadSkillResource(params, context) {
|
|
275
|
+
const skill = activeSkillByName(context, params?.skill)
|
|
276
|
+
if (!skill) {
|
|
277
|
+
const error = new Error(`Unknown or disabled skill: ${params?.skill || ''}`)
|
|
278
|
+
error.statusCode = 404
|
|
279
|
+
throw error
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const result = await readSkillResource(skill, params?.path, params)
|
|
283
|
+
return {
|
|
284
|
+
content: truncateText(result.content),
|
|
285
|
+
details: result.details,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
147
289
|
// --- run_command ---
|
|
148
290
|
export async function toolRunCommand(params, context) {
|
|
149
291
|
const command = String(params?.command || '')
|
|
@@ -191,40 +333,17 @@ export async function toolRunCommand(params, context) {
|
|
|
191
333
|
].join('\n')
|
|
192
334
|
resolve({ content: truncateText(content), details: { command, project: context?.project, cwd: getToolWorkspaceRoot(context), code, signal, timedOut } })
|
|
193
335
|
})
|
|
336
|
+
child.on('error', (err) => {
|
|
337
|
+
clearTimeout(timer)
|
|
338
|
+
resolve({
|
|
339
|
+
isError: true,
|
|
340
|
+
content: truncateText(`Error running command: ${err.message}`),
|
|
341
|
+
details: { command, project: context?.project, error: err.message },
|
|
342
|
+
})
|
|
343
|
+
})
|
|
194
344
|
})
|
|
195
345
|
}
|
|
196
346
|
|
|
197
|
-
// --- get_project_info ---
|
|
198
|
-
export async function toolGetProjectInfo(_params, context) {
|
|
199
|
-
if (context?.project) {
|
|
200
|
-
return {
|
|
201
|
-
content: `Project: ${context.project.name}\nRoot: ${context.project.path}`,
|
|
202
|
-
details: { project: context.project, workspaceRoot: context.workspaceRoot },
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const config = await readProjectConfig()
|
|
207
|
-
const project = getActiveProject(config)
|
|
208
|
-
return {
|
|
209
|
-
content: `Active project: ${project.name}\nRoot: ${project.path}`,
|
|
210
|
-
details: { project, workspaceRoot: getWorkspaceRoot() },
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Helper for grep
|
|
215
|
-
async function walkFiles(root, files = [], context) {
|
|
216
|
-
const entries = await fs.readdir(root, { withFileTypes: true })
|
|
217
|
-
for (const entry of entries) {
|
|
218
|
-
const fullPath = path.join(root, entry.name)
|
|
219
|
-
if (entry.isDirectory()) {
|
|
220
|
-
if (!shouldSkipSearchDir(entry.name)) await walkFiles(fullPath, files, context)
|
|
221
|
-
} else if (entry.isFile() && shouldSearchFile(entry.name) && !isSensitiveWorkspacePath(fullPath, context)) {
|
|
222
|
-
files.push(fullPath)
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return files
|
|
226
|
-
}
|
|
227
|
-
|
|
228
347
|
export const toolHandlers = {
|
|
229
348
|
get_project_info: toolGetProjectInfo,
|
|
230
349
|
list_dir: toolListDir,
|
|
@@ -233,4 +352,6 @@ export const toolHandlers = {
|
|
|
233
352
|
write_file: toolWriteFile,
|
|
234
353
|
edit_file: toolEditFile,
|
|
235
354
|
run_command: toolRunCommand,
|
|
355
|
+
activate_skill: toolActivateSkill,
|
|
356
|
+
read_skill_resource: toolReadSkillResource,
|
|
236
357
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { logsDir } from '../storage.mjs'
|
|
4
|
+
|
|
5
|
+
function timestamp() {
|
|
6
|
+
return new Date().toISOString()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function logFile() {
|
|
10
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
11
|
+
return path.join(logsDir, `server-${date}.log`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatArgs(args) {
|
|
15
|
+
return args.map((a) =>
|
|
16
|
+
typeof a === 'string' ? a : a instanceof Error ? a.stack : JSON.stringify(a),
|
|
17
|
+
).join(' ')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeLog(level, ...args) {
|
|
21
|
+
const line = `${timestamp()} [${level}] ${formatArgs(args)}\n`
|
|
22
|
+
process.stderr.write(line)
|
|
23
|
+
try {
|
|
24
|
+
fs.appendFileSync(logFile(), line)
|
|
25
|
+
} catch {
|
|
26
|
+
// ignore write errors
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const logger = {
|
|
31
|
+
info: (...args) => writeLog('INFO', ...args),
|
|
32
|
+
warn: (...args) => writeLog('WARN', ...args),
|
|
33
|
+
error: (...args) => writeLog('ERROR', ...args),
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os from 'node:os'
|
|
2
|
+
|
|
3
|
+
export function isPrivateIpv4(hostname) {
|
|
4
|
+
if (!/^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname || '')) return false
|
|
5
|
+
const parts = hostname.split('.').map((part) => Number(part))
|
|
6
|
+
if (parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false
|
|
7
|
+
const [a, b] = parts
|
|
8
|
+
return a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isLoopbackAddress(address) {
|
|
12
|
+
if (!address) return false
|
|
13
|
+
const normalized = address.replace(/^::ffff:/, '')
|
|
14
|
+
return normalized === '127.0.0.1' || normalized === '::1' || normalized === 'localhost'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getLanIpv4Addresses() {
|
|
18
|
+
const result = []
|
|
19
|
+
const seen = new Set()
|
|
20
|
+
const interfaces = os.networkInterfaces()
|
|
21
|
+
|
|
22
|
+
for (const entries of Object.values(interfaces)) {
|
|
23
|
+
for (const entry of entries || []) {
|
|
24
|
+
if (entry.family !== 'IPv4' || entry.internal) continue
|
|
25
|
+
if (!isPrivateIpv4(entry.address)) continue
|
|
26
|
+
if (seen.has(entry.address)) continue
|
|
27
|
+
seen.add(entry.address)
|
|
28
|
+
result.push(entry.address)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getLanUrls(port, protocol = 'http') {
|
|
36
|
+
const safePort = Number(port)
|
|
37
|
+
return getLanIpv4Addresses().map((address) => `${protocol}://${address}${safePort ? `:${safePort}` : ''}`)
|
|
38
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import os from 'node:os'
|
|
4
5
|
|
|
@@ -121,6 +122,35 @@ try {
|
|
|
121
122
|
throw error
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
export async function openPathInFileManager(targetPath) {
|
|
126
|
+
const resolved = path.resolve(String(targetPath || ''))
|
|
127
|
+
const stat = await fs.stat(resolved).catch(() => null)
|
|
128
|
+
if (!stat || !stat.isDirectory()) {
|
|
129
|
+
const error = new Error(`Directory does not exist: ${resolved}`)
|
|
130
|
+
error.statusCode = 400
|
|
131
|
+
throw error
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const command = process.platform === 'win32' ? 'explorer.exe' : process.platform === 'darwin' ? 'open' : 'xdg-open'
|
|
135
|
+
const args = [resolved]
|
|
136
|
+
await new Promise((resolve, reject) => {
|
|
137
|
+
const child = spawn(command, args, {
|
|
138
|
+
detached: true,
|
|
139
|
+
stdio: 'ignore',
|
|
140
|
+
windowsHide: false,
|
|
141
|
+
shell: false,
|
|
142
|
+
})
|
|
143
|
+
child.once('error', (error) => {
|
|
144
|
+
error.statusCode = 500
|
|
145
|
+
reject(error)
|
|
146
|
+
})
|
|
147
|
+
child.once('spawn', () => {
|
|
148
|
+
child.unref()
|
|
149
|
+
resolve()
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
export function openBrowser(url) {
|
|
125
155
|
if (process.env.QUICKFORGE_NO_OPEN === '1') return
|
|
126
156
|
|
|
@@ -27,7 +27,14 @@ export async function readJsonBody(req, maxBodyBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
|
27
27
|
chunks.push(chunk)
|
|
28
28
|
}
|
|
29
29
|
const text = Buffer.concat(chunks).toString('utf8')
|
|
30
|
-
|
|
30
|
+
if (!text) return null
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(text.trimStart())
|
|
33
|
+
} catch {
|
|
34
|
+
const error = new Error('Invalid JSON request body')
|
|
35
|
+
error.statusCode = 400
|
|
36
|
+
throw error
|
|
37
|
+
}
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export function decodeSegment(value) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-context-package
|
|
3
|
+
description: Use this skill when the user wants to prepare, fill, review, or enforce an AI task context package before coding, including task scope, constraints, validation commands, stop conditions, and expected output format.
|
|
4
|
+
metadata:
|
|
5
|
+
displayName: AI Context Package
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
tags: context, planning, template
|
|
8
|
+
---
|
|
9
|
+
# AI Context Package Skill
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
Produce a clear, bounded context package that helps an AI coding assistant understand the project, task goal, accepted scope, constraints, validation commands, and expected delivery format.
|
|
14
|
+
|
|
15
|
+
## Template
|
|
16
|
+
|
|
17
|
+
```md
|
|
18
|
+
# AI 上下文包:{TASK_NAME}
|
|
19
|
+
|
|
20
|
+
## 1. 项目概况
|
|
21
|
+
- 项目名称:{PROJECT_NAME}
|
|
22
|
+
- 项目定位:{PROJECT_SUMMARY}
|
|
23
|
+
- 技术栈:{TECH_STACK}
|
|
24
|
+
- 当前环境:{ENV}
|
|
25
|
+
|
|
26
|
+
## 2. 相关文档
|
|
27
|
+
- 项目知识地图:{PROJECT_KNOWLEDGE_DOC_PATH}
|
|
28
|
+
- 技术栈学习地图:{TECH_STACK_DOC_PATH}
|
|
29
|
+
- 模块设计文档:{MODULE_DOC_PATH}
|
|
30
|
+
- 解决方案文档:{SOLUTION_DOC_PATH}
|
|
31
|
+
- 接口文档/产品文档:{REQUIREMENT_DOC_PATH}
|
|
32
|
+
|
|
33
|
+
## 3. 任务目标
|
|
34
|
+
{BUSINESS_GOAL}
|
|
35
|
+
|
|
36
|
+
## 4. 验收标准
|
|
37
|
+
{ACCEPTANCE_CRITERIA}
|
|
38
|
+
|
|
39
|
+
## 5. 变更范围
|
|
40
|
+
允许修改:
|
|
41
|
+
{ALLOWED_FILES_OR_MODULES}
|
|
42
|
+
|
|
43
|
+
禁止修改:
|
|
44
|
+
{FORBIDDEN_FILES_OR_MODULES}
|
|
45
|
+
|
|
46
|
+
## 6. 相关代码入口
|
|
47
|
+
| 类型 | 路径 | 说明 |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| 入口/API | | |
|
|
50
|
+
| Service | | |
|
|
51
|
+
| DAO/Repository | | |
|
|
52
|
+
| 配置 | | |
|
|
53
|
+
| 测试 | | |
|
|
54
|
+
|
|
55
|
+
## 7. 已确定方案
|
|
56
|
+
{SOLUTION_SUMMARY}
|
|
57
|
+
|
|
58
|
+
## 8. 约束条件
|
|
59
|
+
- 不做无关重构。
|
|
60
|
+
- 不引入新依赖,除非明确说明。
|
|
61
|
+
- 不改变公共接口契约,除非有兼容方案。
|
|
62
|
+
- 不修改数据库结构,除非有迁移和回滚。
|
|
63
|
+
- 不泄露密钥和敏感数据。
|
|
64
|
+
- {OTHER_CONSTRAINTS}
|
|
65
|
+
|
|
66
|
+
## 9. 验证方式
|
|
67
|
+
- 测试命令:{TEST_COMMAND}
|
|
68
|
+
- 构建命令:{BUILD_COMMAND}
|
|
69
|
+
- 手工验证步骤:{MANUAL_VALIDATION}
|
|
70
|
+
|
|
71
|
+
## 10. 停止条件
|
|
72
|
+
遇到以下情况先停止,不要继续改代码:
|
|
73
|
+
- 代码事实与方案文档冲突。
|
|
74
|
+
- 需要扩大修改范围。
|
|
75
|
+
- 涉及数据库破坏性变更。
|
|
76
|
+
- 涉及权限扩大或敏感数据。
|
|
77
|
+
- 测试命令不可用且无法判断影响。
|
|
78
|
+
- {OTHER_STOP_CONDITIONS}
|
|
79
|
+
|
|
80
|
+
## 11. 期望 AI 输出
|
|
81
|
+
- 修改前理解。
|
|
82
|
+
- 修改文件清单。
|
|
83
|
+
- 每个文件变更说明。
|
|
84
|
+
- 测试/构建结果。
|
|
85
|
+
- 风险点。
|
|
86
|
+
- 回滚方式。
|
|
87
|
+
- 未完成项。
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Workflow
|
|
91
|
+
|
|
92
|
+
1. If the user provides only the template, treat it as a reusable task-context skill/template.
|
|
93
|
+
2. If fields are missing, ask only for the fields needed to proceed, or fill safe placeholders like `待补充` when the user wants a draft.
|
|
94
|
+
3. Before coding, restate the task understanding and explicitly list allowed and forbidden change scope.
|
|
95
|
+
4. Inspect the relevant docs/code entries before changing files.
|
|
96
|
+
5. Stop and ask for confirmation when any stop condition is met.
|
|
97
|
+
6. After changes, report using the expected AI output sections.
|
|
98
|
+
|
|
99
|
+
## Output Rules
|
|
100
|
+
|
|
101
|
+
- Keep the context package structured and copyable as Markdown.
|
|
102
|
+
- Do not invent requirements. Mark unknown values as `待补充`.
|
|
103
|
+
- Prefer concrete file/module paths over broad descriptions.
|
|
104
|
+
- Keep validation commands explicit.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-context-package",
|
|
3
|
+
"displayName": "AI Context Package",
|
|
4
|
+
"description": "Create a structured task context package for AI coding work, including goals, docs, scope, constraints, validation, and expected output.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"tags": ["context", "planning", "task", "requirements", "ai"],
|
|
7
|
+
"triggers": ["上下文包", "AI 上下文", "context package", "任务上下文", "验收标准", "变更范围"],
|
|
8
|
+
"entry": "SKILL.md"
|
|
9
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: Use this skill when the user asks for code review, PR review, bug finding, quality checks, security/performance/maintainability review, or wants feedback on code changes.
|
|
4
|
+
metadata:
|
|
5
|
+
displayName: Code Review
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
tags: code, review, quality
|
|
8
|
+
---
|
|
9
|
+
# Code Review Skill
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
1. Understand the requested change and inspect the relevant files before judging.
|
|
14
|
+
2. Focus on correctness, regressions, security, data loss, performance, and maintainability.
|
|
15
|
+
3. Prefer concrete findings over generic advice.
|
|
16
|
+
4. If proposing changes, keep them small and directly related to the review.
|
|
17
|
+
5. Verify with targeted tests, lint, typecheck, build, or a focused command when practical.
|
|
18
|
+
|
|
19
|
+
## Output
|
|
20
|
+
|
|
21
|
+
- Start with the highest-risk findings.
|
|
22
|
+
- For each finding include the file/location, risk, why it matters, and a concise fix.
|
|
23
|
+
- If no major issues are found, say so clearly and mention any checks performed.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "code-review",
|
|
3
|
+
"displayName": "Code Review",
|
|
4
|
+
"description": "Review code changes for correctness, bugs, security, performance, and maintainability.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"tags": ["code", "review", "quality"],
|
|
7
|
+
"triggers": ["code review", "review code", "pr review", "代码审查", "检查代码"],
|
|
8
|
+
"entry": "SKILL.md"
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-react
|
|
3
|
+
description: Use this skill for React, TypeScript, frontend UI, layout, component work, accessibility, styling, or browser interaction changes.
|
|
4
|
+
metadata:
|
|
5
|
+
displayName: React Frontend
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
tags: react, frontend, typescript, ui
|
|
8
|
+
---
|
|
9
|
+
# React Frontend Skill
|
|
10
|
+
|
|
11
|
+
## Guidelines
|
|
12
|
+
|
|
13
|
+
- Match the existing component style and design language.
|
|
14
|
+
- Prefer simple controlled state and small components over broad rewrites.
|
|
15
|
+
- Keep accessibility in mind: labels, aria attributes, keyboard escape/submit behavior, and focus states.
|
|
16
|
+
- Preserve existing behavior unless the user explicitly asks to change it.
|
|
17
|
+
- Avoid adding new dependencies for basic UI interactions.
|
|
18
|
+
- After changes, run typecheck/build or the smallest relevant verification command.
|
|
19
|
+
|
|
20
|
+
## Output
|
|
21
|
+
|
|
22
|
+
Briefly summarize what changed and what verification was run.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frontend-react",
|
|
3
|
+
"displayName": "React Frontend",
|
|
4
|
+
"description": "Build and modify React UI with TypeScript, accessible interactions, and minimal focused changes.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"tags": ["react", "frontend", "typescript", "ui"],
|
|
7
|
+
"triggers": ["react", "frontend", "ui", "组件", "前端", "界面"],
|
|
8
|
+
"entry": "SKILL.md"
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: quickforge-project
|
|
3
|
+
description: Use this skill when modifying the QuickForge application, including its React/Vite frontend, local Node.js backend, storage, project chats, workspace tools, skills, or local-only safety behavior.
|
|
4
|
+
metadata:
|
|
5
|
+
displayName: QuickForge Project
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
tags: quickforge, project, local-agent
|
|
8
|
+
---
|
|
9
|
+
# QuickForge Project Skill
|
|
10
|
+
|
|
11
|
+
## Project Rules
|
|
12
|
+
|
|
13
|
+
- Make surgical changes only; avoid broad refactors.
|
|
14
|
+
- Keep server API changes small and local to `server/routes` or dedicated modules.
|
|
15
|
+
- Keep frontend state changes explicit and close to the component/hook that owns them.
|
|
16
|
+
- Preserve local-only safety assumptions for workspace access.
|
|
17
|
+
- For project-scoped features, persist configuration in the project config where possible.
|
|
18
|
+
- Verify with `npm run build` or a targeted command before finishing.
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
21
|
+
|
|
22
|
+
QuickForge has a React/Vite frontend and a local Node.js backend. Project chats can use workspace tools when YOLO mode is enabled.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quickforge-project",
|
|
3
|
+
"displayName": "QuickForge Project",
|
|
4
|
+
"description": "Work within QuickForge conventions: local server, project chats, YOLO workspace tools, and minimal surgical changes.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"tags": ["quickforge", "project", "workspace"],
|
|
7
|
+
"triggers": ["quickforge", "项目", "本项目"],
|
|
8
|
+
"entry": "SKILL.md"
|
|
9
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,a)=>(a=n==null?{}:e(i(n)),c(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),u=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.")});export{l as i,s as n,u as r,o as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./chunk-62oNxeRG.js";import{f as t,m as n,n as r,p as i,r as a,t as o}from"./index-Bq6VHkyY.js";var s=e(n(),1),c=i(),l=t(),u=r();function d({options:e,onResolve:t}){let n=(0,s.useRef)(null);return(0,s.useEffect)(()=>{n.current?.focus();let e=e=>{e.key===`Escape`&&t(!1)};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[t]),(0,c.createPortal)((0,u.jsx)(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/50`,onClick:e=>{e.target===e.currentTarget&&t(!1)},children:(0,u.jsxs)(`div`,{className:a(`w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg`,`mx-4`),children:[(0,u.jsx)(`h2`,{className:`text-base font-semibold text-foreground`,children:e.title}),(0,u.jsx)(`p`,{className:`mt-2 text-sm text-muted-foreground`,children:e.description}),(0,u.jsxs)(`div`,{className:`mt-5 flex justify-end gap-2`,children:[(0,u.jsx)(o,{ref:n,variant:`outline`,size:`sm`,onClick:()=>t(!1),children:e.cancelLabel??`Cancel`}),(0,u.jsx)(o,{variant:`destructive`,size:`sm`,onClick:()=>t(!0),children:e.confirmLabel??`Delete`})]})]})}),document.body)}function f(e){return new Promise(t=>{let n=document.createElement(`div`);document.body.appendChild(n);let r=(0,l.createRoot)(n);function i(){r.unmount(),setTimeout(()=>n.remove(),0)}function a(e){i(),t(e)}r.render((0,u.jsx)(d,{options:e,onResolve:a}))})}export{f as showConfirm};
|