theslopmachine 0.3.0
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/MANUAL.md +63 -0
- package/README.md +23 -0
- package/RELEASE.md +81 -0
- package/assets/agents/developer.md +294 -0
- package/assets/agents/slopmachine.md +510 -0
- package/assets/skills/beads-operations/SKILL.md +75 -0
- package/assets/skills/clarification-gate/SKILL.md +51 -0
- package/assets/skills/developer-session-lifecycle/SKILL.md +75 -0
- package/assets/skills/final-evaluation-orchestration/SKILL.md +75 -0
- package/assets/skills/frontend-design/SKILL.md +41 -0
- package/assets/skills/get-overlays/SKILL.md +157 -0
- package/assets/skills/planning-gate/SKILL.md +68 -0
- package/assets/skills/submission-packaging/SKILL.md +268 -0
- package/assets/skills/verification-gates/SKILL.md +106 -0
- package/assets/slopmachine/backend-evaluation-prompt.md +275 -0
- package/assets/slopmachine/beads-init.js +428 -0
- package/assets/slopmachine/document-completeness.md +45 -0
- package/assets/slopmachine/engineering-results.md +59 -0
- package/assets/slopmachine/frontend-evaluation-prompt.md +304 -0
- package/assets/slopmachine/implementation-comparison.md +36 -0
- package/assets/slopmachine/quality-document.md +108 -0
- package/assets/slopmachine/templates/AGENTS.md +114 -0
- package/assets/slopmachine/utils/convert_ai_session.py +1837 -0
- package/assets/slopmachine/utils/strip_session_parent.py +66 -0
- package/bin/slopmachine.js +9 -0
- package/package.json +25 -0
- package/src/cli.js +32 -0
- package/src/constants.js +77 -0
- package/src/init.js +179 -0
- package/src/install.js +330 -0
- package/src/utils.js +162 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { spawn } from 'node:child_process'
|
|
6
|
+
|
|
7
|
+
const targetInput = process.argv[2] || '.'
|
|
8
|
+
const target = path.resolve(process.cwd(), targetInput)
|
|
9
|
+
|
|
10
|
+
function log(message) {
|
|
11
|
+
console.log(`[beads-init] ${message}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function die(message) {
|
|
15
|
+
console.error(`[beads-init] ERROR: ${message}`)
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function run(command, args, options = {}) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const child = spawn(command, args, {
|
|
22
|
+
cwd: options.cwd,
|
|
23
|
+
env: options.env || process.env,
|
|
24
|
+
stdio: options.stdio || 'pipe',
|
|
25
|
+
shell: false,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
let stdout = ''
|
|
29
|
+
let stderr = ''
|
|
30
|
+
|
|
31
|
+
if (child.stdout) child.stdout.on('data', (chunk) => { stdout += chunk.toString() })
|
|
32
|
+
if (child.stderr) child.stderr.on('data', (chunk) => { stderr += chunk.toString() })
|
|
33
|
+
|
|
34
|
+
if (options.input !== undefined && child.stdin) {
|
|
35
|
+
child.stdin.write(options.input)
|
|
36
|
+
child.stdin.end()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
child.on('error', reject)
|
|
40
|
+
child.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }))
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function commandExists(command) {
|
|
45
|
+
const checker = process.platform === 'win32' ? 'where' : 'which'
|
|
46
|
+
const result = await run(checker, [command])
|
|
47
|
+
return result.code === 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function pathExists(targetPath) {
|
|
51
|
+
try {
|
|
52
|
+
await fs.access(targetPath)
|
|
53
|
+
return true
|
|
54
|
+
} catch {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function runBd(args, options = {}) {
|
|
60
|
+
return run('bd', args, { cwd: target, ...options })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runBdNoninteractive(args) {
|
|
64
|
+
return run('bd', args, {
|
|
65
|
+
cwd: target,
|
|
66
|
+
env: { ...process.env, CI: '1' },
|
|
67
|
+
input: '',
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function supportsInitFlag(flag) {
|
|
72
|
+
const help = await run('bd', ['init', '--help'])
|
|
73
|
+
return `${help.stdout}${help.stderr}`.includes(flag)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function chooseNoninteractiveInitModeFlag(isGitRepo) {
|
|
77
|
+
if (!isGitRepo) return null
|
|
78
|
+
const help = await run('bd', ['init', '--help'])
|
|
79
|
+
const text = `${help.stdout}${help.stderr}`
|
|
80
|
+
if (text.includes('--setup-exclude')) return '--setup-exclude'
|
|
81
|
+
if (text.includes('--stealth')) return '--stealth'
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function configureGitMergeDriver() {
|
|
86
|
+
const attributesFile = path.join(target, '.git', 'info', 'attributes')
|
|
87
|
+
const attributesLine = '.beads/*.jsonl merge=beads'
|
|
88
|
+
|
|
89
|
+
log('Configuring local git merge driver for Beads')
|
|
90
|
+
let result = await run('git', ['-C', target, 'config', 'merge.beads.driver', 'bd merge %A %O %A %B'])
|
|
91
|
+
if (result.code !== 0) die(result.stderr || 'Failed configuring git merge driver')
|
|
92
|
+
|
|
93
|
+
await fs.mkdir(path.dirname(attributesFile), { recursive: true })
|
|
94
|
+
if (!(await pathExists(attributesFile))) {
|
|
95
|
+
await fs.writeFile(attributesFile, '', 'utf8')
|
|
96
|
+
}
|
|
97
|
+
const content = await fs.readFile(attributesFile, 'utf8')
|
|
98
|
+
const lines = new Set(content.split(/\r?\n/))
|
|
99
|
+
if (!lines.has(attributesLine)) {
|
|
100
|
+
const prefix = content.endsWith('\n') || content.length === 0 ? '' : '\n'
|
|
101
|
+
await fs.appendFile(attributesFile, `${prefix}${attributesLine}\n`, 'utf8')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function configureGitRoleMaintainer() {
|
|
106
|
+
log('Setting local Beads role to maintainer')
|
|
107
|
+
const result = await run('git', ['-C', target, 'config', 'beads.role', 'maintainer'])
|
|
108
|
+
if (result.code !== 0) die(result.stderr || 'Failed setting beads.role maintainer')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseDoctorJson(raw) {
|
|
112
|
+
if (!raw.trim()) {
|
|
113
|
+
return { errors: -1, warnings: 0, known: 0, warnMissingJsonl: 0, warnDoltDirty: 0, warnSyncBranch: 0 }
|
|
114
|
+
}
|
|
115
|
+
let data
|
|
116
|
+
try {
|
|
117
|
+
data = JSON.parse(raw)
|
|
118
|
+
} catch {
|
|
119
|
+
return { errors: -1, warnings: 0, known: 0, warnMissingJsonl: 0, warnDoltDirty: 0, warnSyncBranch: 0 }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const checks = Array.isArray(data.checks) ? data.checks : []
|
|
123
|
+
let errors = 0
|
|
124
|
+
let warnings = 0
|
|
125
|
+
let known = 0
|
|
126
|
+
let warnMissingJsonl = 0
|
|
127
|
+
let warnDoltDirty = 0
|
|
128
|
+
let warnSyncBranch = 0
|
|
129
|
+
|
|
130
|
+
for (const check of checks) {
|
|
131
|
+
const name = `${check.name || ''}`.trim()
|
|
132
|
+
const status = check.status
|
|
133
|
+
const text = `${check.message || ''} ${check.detail || ''}`.toLowerCase()
|
|
134
|
+
|
|
135
|
+
if (status === 'error') {
|
|
136
|
+
errors += 1
|
|
137
|
+
if (text.includes('database not found: beads')) known = 1
|
|
138
|
+
if (name === 'Dolt Schema' && ['issues', 'dependencies', 'config', 'labels', 'events'].every((term) => text.includes(term))) {
|
|
139
|
+
known = 1
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (status === 'warning') {
|
|
144
|
+
warnings += 1
|
|
145
|
+
if (name === 'Dolt-JSONL Sync' && (text.includes('could not read jsonl file') || text.includes('no such file or directory'))) {
|
|
146
|
+
warnMissingJsonl = 1
|
|
147
|
+
}
|
|
148
|
+
if (name === 'Dolt Status' || name === 'Dolt Locks') {
|
|
149
|
+
warnDoltDirty = 1
|
|
150
|
+
}
|
|
151
|
+
if (name === 'Sync Branch Config' && text.includes('not configured')) {
|
|
152
|
+
warnSyncBranch = 1
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { errors, warnings, known, warnMissingJsonl, warnDoltDirty, warnSyncBranch }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function getActiveDbName() {
|
|
161
|
+
const metadataPath = path.join(target, '.beads', 'metadata.json')
|
|
162
|
+
if (await pathExists(metadataPath)) {
|
|
163
|
+
try {
|
|
164
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'))
|
|
165
|
+
if (metadata.dolt_database) {
|
|
166
|
+
return metadata.dolt_database
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
// ignore and fall through
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const showDatabases = await runBd(['sql', '-q', 'SHOW DATABASES;'])
|
|
174
|
+
const names = `${showDatabases.stdout}${showDatabases.stderr}`
|
|
175
|
+
.split(/\r?\n/)
|
|
176
|
+
.map((line) => line.trim())
|
|
177
|
+
.filter((line) => line && !/^[-]+$/.test(line))
|
|
178
|
+
.filter((line) => !['database', 'information_schema', '(2 rows)', '(1 row)'].includes(line.toLowerCase()))
|
|
179
|
+
.filter((line) => !(line.startsWith('(') && line.endsWith('rows)')))
|
|
180
|
+
|
|
181
|
+
return names.find((name) => !['beads', 'information_schema'].includes(name)) || ''
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function knownDoltFix(sourceDb) {
|
|
185
|
+
if (!sourceDb) return false
|
|
186
|
+
log(`Applying known Dolt doctor fix using source DB '${sourceDb}'`)
|
|
187
|
+
let result = await runBd(['sql', '-q', 'CREATE DATABASE IF NOT EXISTS beads;'])
|
|
188
|
+
if (result.code !== 0) return false
|
|
189
|
+
for (const table of ['issues', 'dependencies', 'config', 'labels', 'events']) {
|
|
190
|
+
result = await runBd(['sql', '-q', `CREATE TABLE IF NOT EXISTS beads.${table} LIKE \`${sourceDb}\`.${table};`])
|
|
191
|
+
if (result.code !== 0) return false
|
|
192
|
+
}
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function ensureBeadsJsonl() {
|
|
197
|
+
const jsonlPath = path.join(target, '.beads', 'beads.jsonl')
|
|
198
|
+
if (await pathExists(jsonlPath)) return true
|
|
199
|
+
log('Ensuring .beads/beads.jsonl exists')
|
|
200
|
+
const exportResult = await runBd(['export', '--force', '-o', '.beads/beads.jsonl'])
|
|
201
|
+
if (exportResult.code === 0) return true
|
|
202
|
+
await fs.writeFile(jsonlPath, '', 'utf8')
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function getPendingDoltSchemaCount() {
|
|
207
|
+
const result = await runBd(['--json', 'sql', 'SELECT COUNT(*) AS pending FROM beads.dolt_status;'])
|
|
208
|
+
if (result.code !== 0 || !result.stdout.trim()) return 0
|
|
209
|
+
try {
|
|
210
|
+
const data = JSON.parse(result.stdout)
|
|
211
|
+
if (Array.isArray(data) && data[0] && typeof data[0] === 'object') {
|
|
212
|
+
return Number(data[0].pending || 0)
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
return 0
|
|
216
|
+
}
|
|
217
|
+
return 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function commitPendingDoltSchema(activeDb) {
|
|
221
|
+
const pending = await getPendingDoltSchemaCount()
|
|
222
|
+
if (pending <= 0) return true
|
|
223
|
+
log("Committing pending Dolt schema changes in fallback 'beads' DB")
|
|
224
|
+
|
|
225
|
+
let switched = false
|
|
226
|
+
if (activeDb && activeDb !== 'beads') {
|
|
227
|
+
const setResult = await runBd(['dolt', 'set', 'database', 'beads'])
|
|
228
|
+
if (setResult.code !== 0) return false
|
|
229
|
+
switched = true
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const commitResult = await runBd(['vc', 'commit', '-m', 'Auto-commit initial fallback schema tables for bd doctor'])
|
|
233
|
+
|
|
234
|
+
if (switched) {
|
|
235
|
+
await runBd(['dolt', 'set', 'database', activeDb])
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (commitResult.code === 0) return true
|
|
239
|
+
const lower = `${commitResult.stdout}${commitResult.stderr}`.toLowerCase()
|
|
240
|
+
if (lower.includes('nothing to commit') || lower.includes('no changes') || lower.includes('clean working set')) {
|
|
241
|
+
return true
|
|
242
|
+
}
|
|
243
|
+
console.error(commitResult.stdout || commitResult.stderr)
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function configureSyncBranchReconciled() {
|
|
248
|
+
const syncBranchName = 'beads-sync'
|
|
249
|
+
log(`Reconciling sync.branch (${syncBranchName})`)
|
|
250
|
+
|
|
251
|
+
let result = await runBd(['config', 'set', 'sync.branch', syncBranchName])
|
|
252
|
+
if (result.code !== 0) return false
|
|
253
|
+
|
|
254
|
+
result = await runBdNoninteractive(['migrate', 'sync', syncBranchName, '--yes'])
|
|
255
|
+
if (result.code !== 0) {
|
|
256
|
+
result = await runBdNoninteractive(['migrate', 'sync', syncBranchName])
|
|
257
|
+
if (result.code !== 0) {
|
|
258
|
+
if (`${result.stdout}${result.stderr}`.trim()) {
|
|
259
|
+
console.error(`${result.stdout}${result.stderr}`.trim())
|
|
260
|
+
}
|
|
261
|
+
log("WARNING: 'bd migrate sync' failed; validating config directly")
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const configResult = await runBd(['config', 'get', 'sync.branch'])
|
|
266
|
+
const combined = `${configResult.stdout}${configResult.stderr}`
|
|
267
|
+
if (!combined.includes(syncBranchName)) {
|
|
268
|
+
if (combined.trim()) console.error(combined.trim())
|
|
269
|
+
return false
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
log(`Verified sync.branch: ${combined.trim()}`)
|
|
273
|
+
return true
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function installGitHooksNonfatal() {
|
|
277
|
+
log('Installing Beads git hooks')
|
|
278
|
+
const result = await runBd(['hooks', 'install'])
|
|
279
|
+
if (result.code === 0) {
|
|
280
|
+
if (`${result.stdout}`.trim()) {
|
|
281
|
+
console.log(result.stdout.trim())
|
|
282
|
+
}
|
|
283
|
+
return true
|
|
284
|
+
}
|
|
285
|
+
if (`${result.stdout}${result.stderr}`.trim()) {
|
|
286
|
+
console.error(`${result.stdout}${result.stderr}`.trim())
|
|
287
|
+
}
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function main() {
|
|
292
|
+
if (!(await commandExists('bd'))) {
|
|
293
|
+
die("'bd' is not installed or not in PATH. Install Beads first.")
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!(await pathExists(target))) {
|
|
297
|
+
die(`Target directory '${targetInput}' does not exist or is not accessible.`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const gitCheck = await run('git', ['-C', target, 'rev-parse', '--is-inside-work-tree'])
|
|
301
|
+
const isGitRepo = gitCheck.code === 0
|
|
302
|
+
|
|
303
|
+
log(`Target: ${target}`)
|
|
304
|
+
log(`Mode: ${isGitRepo ? 'git repository' : 'non-git directory'}`)
|
|
305
|
+
|
|
306
|
+
const beadsDir = path.join(target, '.beads')
|
|
307
|
+
if (!(await pathExists(beadsDir))) {
|
|
308
|
+
if (isGitRepo) {
|
|
309
|
+
await configureGitRoleMaintainer()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const initArgs = ['init', '--quiet']
|
|
313
|
+
const initModeFlag = await chooseNoninteractiveInitModeFlag(isGitRepo)
|
|
314
|
+
if (initModeFlag) initArgs.push(initModeFlag)
|
|
315
|
+
if (await supportsInitFlag('--skip-hooks')) initArgs.push('--skip-hooks')
|
|
316
|
+
|
|
317
|
+
log(`Running non-interactive 'bd ${initArgs.join(' ')}' (CI=1, stdin=/dev/null)`)
|
|
318
|
+
const initResult = await runBdNoninteractive(initArgs)
|
|
319
|
+
if (initResult.code !== 0) {
|
|
320
|
+
console.error(`${initResult.stdout}${initResult.stderr}`.trim())
|
|
321
|
+
die("'bd init' failed. Review output above.")
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (isGitRepo) {
|
|
325
|
+
await configureGitMergeDriver()
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (initResult.stdout.trim()) {
|
|
329
|
+
console.log(initResult.stdout.trim())
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
log('Found existing .beads; skipping init to avoid destructive re-initialization')
|
|
333
|
+
if (isGitRepo) {
|
|
334
|
+
await configureGitMergeDriver()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let hooksInstallStatus = 'not-applicable'
|
|
339
|
+
if (isGitRepo) {
|
|
340
|
+
if (await installGitHooksNonfatal()) {
|
|
341
|
+
hooksInstallStatus = 'installed'
|
|
342
|
+
} else {
|
|
343
|
+
hooksInstallStatus = 'failed'
|
|
344
|
+
log("WARNING: 'bd hooks install' failed; continuing to doctor")
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!(await configureSyncBranchReconciled())) {
|
|
348
|
+
die('Failed to reconcile sync.branch to beads-sync')
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const doctorBefore = await runBd(['doctor'])
|
|
353
|
+
const doctorJson = await runBd(['doctor', '--json'])
|
|
354
|
+
const parsedBefore = parseDoctorJson(doctorJson.stdout)
|
|
355
|
+
if (parsedBefore.errors === -1) {
|
|
356
|
+
console.log(doctorBefore.stdout)
|
|
357
|
+
die("Unable to parse 'bd doctor --json'. Run 'bd doctor --verbose' manually.")
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (parsedBefore.errors > 0 && parsedBefore.known === 1) {
|
|
361
|
+
const sourceDb = await getActiveDbName()
|
|
362
|
+
if (!sourceDb) {
|
|
363
|
+
console.log(doctorBefore.stdout)
|
|
364
|
+
die('Detected known Dolt issue but could not determine active DB name from .beads/metadata.json or SHOW DATABASES.')
|
|
365
|
+
}
|
|
366
|
+
if (!(await knownDoltFix(sourceDb))) {
|
|
367
|
+
console.log(doctorBefore.stdout)
|
|
368
|
+
die('Detected known Dolt issue, but auto-fix failed while creating fallback schema.')
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const doctorPostFix = await runBd(['doctor', '--json'])
|
|
373
|
+
const parsedPostFix = parseDoctorJson(doctorPostFix.stdout)
|
|
374
|
+
if (parsedPostFix.warnings === -1 || parsedPostFix.known === -1) {
|
|
375
|
+
console.log(doctorBefore.stdout)
|
|
376
|
+
die("Unable to parse post-fix 'bd doctor --json'.")
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (parsedPostFix.warnMissingJsonl === 1) {
|
|
380
|
+
if (!(await ensureBeadsJsonl())) {
|
|
381
|
+
die('Failed to ensure .beads/beads.jsonl exists')
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (parsedPostFix.warnDoltDirty === 1) {
|
|
386
|
+
const activeDb = await getActiveDbName()
|
|
387
|
+
if (!(await commitPendingDoltSchema(activeDb))) {
|
|
388
|
+
die('Failed to auto-commit pending Dolt schema changes')
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (parsedPostFix.warnSyncBranch === 1) {
|
|
393
|
+
if (!(await configureSyncBranchReconciled())) {
|
|
394
|
+
die('Failed to configure sync.branch')
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const doctorAfter = await runBd(['doctor'])
|
|
399
|
+
const doctorAfterJson = await runBd(['doctor', '--json'])
|
|
400
|
+
const parsedAfter = parseDoctorJson(doctorAfterJson.stdout)
|
|
401
|
+
|
|
402
|
+
if (doctorAfter.stdout.trim()) {
|
|
403
|
+
console.log(doctorAfter.stdout.trim())
|
|
404
|
+
}
|
|
405
|
+
if (parsedAfter.errors === -1) {
|
|
406
|
+
die("Initialization finished, but doctor JSON parse failed. Run: bd doctor --verbose")
|
|
407
|
+
}
|
|
408
|
+
if (parsedAfter.warnings === -1 || parsedAfter.known === -1) {
|
|
409
|
+
die("Initialization finished, but post-init warning parse failed. Run: bd doctor --verbose")
|
|
410
|
+
}
|
|
411
|
+
if (parsedAfter.errors > 0) {
|
|
412
|
+
die(`'bd doctor' still reports ${parsedAfter.errors} error(s). Run 'bd doctor --verbose' and inspect .beads/metadata.json.`)
|
|
413
|
+
}
|
|
414
|
+
if (isGitRepo && parsedAfter.warnSyncBranch === 1) {
|
|
415
|
+
die("'bd doctor' still reports Sync Branch Config warning: sync-branch not configured.")
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
log('Success: bd initialized and doctor reports zero errors')
|
|
419
|
+
if (hooksInstallStatus === 'installed') {
|
|
420
|
+
log('Hooks: Beads git hooks installed')
|
|
421
|
+
} else if (hooksInstallStatus === 'failed') {
|
|
422
|
+
log('Hooks: WARNING - installation failed (non-fatal)')
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
main().catch((error) => {
|
|
427
|
+
die(error instanceof Error ? error.message : String(error))
|
|
428
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## 5.1 Document Completeness
|
|
2
|
+
|
|
3
|
+
| Document Type | File Path | Completeness | Description |
|
|
4
|
+
| :---- | :---- | :---- | :---- |
|
|
5
|
+
| **User Instructions** | README.md | ✅ Complete | Includes functional features, installation steps, and usage guides |
|
|
6
|
+
| **Testing Instructions** | TESTING.md | ✅ Complete | Test environment isolation and execution methods |
|
|
7
|
+
| **Data Persistence Instructions** | DATA\_PERSISTENCE.md | ✅ Complete | Docker volume mounting and data protection |
|
|
8
|
+
|
|
9
|
+
5.2 Code Completeness
|
|
10
|
+
|
|
11
|
+
| Module | Implementation Status | Description |
|
|
12
|
+
| :---- | :---- | :---- |
|
|
13
|
+
| **Configuration Management** | ✅ Complete | complaint\_system/settings.py |
|
|
14
|
+
| **URL Routing** | ✅ Complete | complaint\_system/urls.py, main/urls.py |
|
|
15
|
+
| **Data Models** | ✅ Complete | main/models.py (FoodVote, Suggestion) |
|
|
16
|
+
| **View Functions** | ✅ Complete | main/views.py (5 view functions) |
|
|
17
|
+
| **Backend Management** | ✅ Complete | main/admin.py |
|
|
18
|
+
| **Template Files** | ✅ Complete | main/templates/ (4 templates) |
|
|
19
|
+
| **Test Suite** | ✅ Complete | tests/ (41 test cases) |
|
|
20
|
+
| **Dependency Config** | ✅ Complete | requirements.txt |
|
|
21
|
+
| **Docker Config** | ✅ Complete | Dockerfile, docker-compose.yml |
|
|
22
|
+
| **Startup Script** | ✅ Complete | docker-entrypoint.sh |
|
|
23
|
+
| **Test Data** | ✅ Complete | add\_test\_data.py |
|
|
24
|
+
|
|
25
|
+
5.3 Deployment Completeness
|
|
26
|
+
|
|
27
|
+
| Deployment Method | Implementation Status | Description |
|
|
28
|
+
| :---- | :---- | :---- |
|
|
29
|
+
| **Local Execution** | ✅ Complete | requirements.txt \+ python manage.py runserver |
|
|
30
|
+
| **Docker Deployment** | ✅ Complete | docker compose up one-click startup |
|
|
31
|
+
| **Data Persistence** | ✅ Complete | Volume mounting for media\_data and db\_data |
|
|
32
|
+
| **Auto-Initialization** | ✅ Complete | Automatic database migration, admin creation, and test data addition |
|
|
33
|
+
|
|
34
|
+
5.4 Delivery Completeness Rating
|
|
35
|
+
|
|
36
|
+
**Rating: 10/10**
|
|
37
|
+
|
|
38
|
+
**Strengths:**
|
|
39
|
+
|
|
40
|
+
* **Complete Documentation**: README and other documents are comprehensive
|
|
41
|
+
* **Complete Code**: All functional modules are implemented with nothing missing
|
|
42
|
+
* **Runnability**: Supports both local execution and Docker one-click deployment
|
|
43
|
+
* **Test Coverage**: 41 test cases with a completely isolated test environment
|
|
44
|
+
* **Automation**: Docker startup automatically completes all initialization tasks
|
|
45
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
**Engineering Details and Professionalism \- Self-Test Results**
|
|
2
|
+
|
|
3
|
+
* **Coding Standards**
|
|
4
|
+
* **Error Handling**
|
|
5
|
+
* **Input Validation and Message Prompts**
|
|
6
|
+
* **Error Handling**
|
|
7
|
+
* **File Upload Security**
|
|
8
|
+
|
|
9
|
+
**Image Upload Configuration**
|
|
10
|
+
|
|
11
|
+
image \= models.ImageField(upload\_to='suggestions/', blank=True, null=True)
|
|
12
|
+
|
|
13
|
+
**Security Features:**
|
|
14
|
+
|
|
15
|
+
* ✅ **CSRF Protection**: All POST requests require a CSRF token
|
|
16
|
+
* ✅ **XSS Protection**: Django templates automatically escape HTML
|
|
17
|
+
* ✅ **SQL Injection Protection**: Uses Django ORM with parameterized queries
|
|
18
|
+
* ✅ **Clickjacking Protection**: X-Frame-Options middleware
|
|
19
|
+
* ✅ **File Uploads**: Uses ImageField to validate image formats
|
|
20
|
+
|
|
21
|
+
**Test Integrity**
|
|
22
|
+
|
|
23
|
+
**4.4.1 Test Case Coverage**
|
|
24
|
+
|
|
25
|
+
**Test Coverage:**
|
|
26
|
+
|
|
27
|
+
* ✅ **Model Testing**: 19 test cases (FoodVote, Suggestion, User models)
|
|
28
|
+
* ✅ **View Testing**: 14 test cases (Home page, voting, suggestion list, word cloud generation)
|
|
29
|
+
* ✅ **Integration Testing**: 8 test cases (End-to-end process, Chinese language support)
|
|
30
|
+
* ✅ **Total**: 41 test cases all passed
|
|
31
|
+
|
|
32
|
+
**Test Isolation Mechanism:**
|
|
33
|
+
|
|
34
|
+
* ✅ Uses in-memory database (:memory:), automatically cleared after testing
|
|
35
|
+
* ✅ Automatic transaction rollback after each test function runs
|
|
36
|
+
* ✅ Test data does not pollute the production database (db.sqlite3)
|
|
37
|
+
* ✅ Automatic data initialization scripts in startup scripts are disabled
|
|
38
|
+
|
|
39
|
+
**Test Screenshots**
|
|
40
|
+
|
|
41
|
+
**Test Success Screenshots**
|
|
42
|
+
|
|
43
|
+
**Engineering Details Rating**
|
|
44
|
+
|
|
45
|
+
**Strengths:**
|
|
46
|
+
|
|
47
|
+
* **Coding Standards**: Follows PEP 8, clear naming, complete Chinese comments
|
|
48
|
+
* **Error Handling**: Layered processing, user-friendly prompts, comprehensive exception capturing
|
|
49
|
+
* **Security**: Django built-in security features, CSRF/XSS/SQL injection protection
|
|
50
|
+
* **Test Integrity**: 41 test cases, completely isolated test environment
|
|
51
|
+
* **Maintainability**: Modular design, clear responsibilities, easy to extend
|
|
52
|
+
|
|
53
|
+
**Room for Improvement:**
|
|
54
|
+
|
|
55
|
+
* Consider adding logging functionality
|
|
56
|
+
* Consider adding API documentation (e.g., using Swagger)
|
|
57
|
+
|
|
58
|
+
Would you like me to help you draft the logging configuration or the Swagger integration mentioned in the improvement section?
|
|
59
|
+
|