@ultime/codecraft 1.4.0 → 1.6.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/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +3 -0
- package/README.md +78 -39
- package/SKILL.md +13 -9
- package/VERSION +1 -1
- package/adapters/README.md +3 -4
- package/codecraft.config.example.json +5 -1
- package/docs/00-constitution.md +5 -5
- package/docs/01-family-cards.md +2 -2
- package/docs/02-ai-contract.md +14 -3
- package/docs/04-gates.md +21 -6
- package/docs/06-architecture.md +5 -3
- package/docs/07-ai-prompts.md +16 -7
- package/docs/08-audit-contract.md +141 -0
- package/docs/09-glossary.md +22 -0
- package/evals/cases.json +110 -0
- package/evals/fixtures/a11-marker/packages/core/dist/tokens.css +4 -0
- package/evals/fixtures/a11-marker/packages/core/package.json +1 -0
- package/evals/fixtures/a11-marker/packages/core/src/index.ts +1 -0
- package/evals/fixtures/a17-boundaries/codecraft.config.json +5 -0
- package/evals/fixtures/a17-boundaries/src/pages/home.ts +1 -0
- package/evals/fixtures/a17-default/src/pages/home.ts +1 -0
- package/evals/fixtures/a17-generated-data/src/icons/icons.ts +2 -0
- package/evals/fixtures/a17-generated-data/src/pages/home.ts +3 -0
- package/evals/fixtures/a17-generated-header/src/pages/home.ts +5 -0
- package/evals/fixtures/a17-generated-header/src/types/project.ts +4 -0
- package/evals/fixtures/a17-local/src/pages/home.ts +4 -0
- package/evals/fixtures/a17-local/src/types/local.ts +3 -0
- package/evals/fixtures/a17-local/src/utils/format.ts +3 -0
- package/evals/fixtures/a2-declared/packages/core/package.json +6 -0
- package/evals/fixtures/a2-declared/packages/core/src/index.ts +3 -0
- package/evals/fixtures/a2-declared/packages/kit/package.json +1 -0
- package/evals/fixtures/a2-workspace/packages/core/package.json +1 -0
- package/evals/fixtures/a2-workspace/packages/core/src/index.ts +3 -0
- package/evals/fixtures/a2-workspace/packages/kit/package.json +1 -0
- package/evals/fixtures/a21-doc-paths/packages/ui/README.md +5 -0
- package/evals/fixtures/a21-doc-paths/packages/ui/package.json +1 -0
- package/evals/fixtures/a21-doc-paths/packages/ui/src/index.ts +1 -0
- package/evals/fixtures/a21-placeholders/.env +2 -0
- package/evals/fixtures/a21-placeholders/server/application.yml +9 -0
- package/evals/fixtures/a21-placeholders/src/pages/home.ts +1 -0
- package/evals/fixtures/a21-plaintext/application.yml +5 -0
- package/evals/fixtures/a21-plaintext/src/main/java/Ali.java +3 -0
- package/evals/fixtures/a21-plaintext/src/pages/home.ts +1 -0
- package/evals/fixtures/a6-clean/src/pages/handled.ts +8 -0
- package/evals/fixtures/a6-empty-catch/src/pages/empty.ts +3 -0
- package/evals/fixtures/ignore-active/codecraft.config.json +5 -0
- package/evals/fixtures/ignore-active/src/pages/home.ts +1 -0
- package/evals/fixtures/ignore-expired/codecraft.config.json +5 -0
- package/evals/fixtures/ignore-expired/src/pages/home.ts +1 -0
- package/evals/fixtures/library-a11/dist/tokens.css +3 -0
- package/evals/fixtures/library-a11/package.json +4 -0
- package/evals/fixtures/library-a11/src/index.ts +1 -0
- package/evals/fixtures/zero-files/package.json +4 -0
- package/evals/golden-tasks.md +14 -1
- package/evals/run.mjs +75 -0
- package/package.json +4 -2
- package/prompts/output.md +19 -4
- package/scenarios/architecture-report.md +88 -0
- package/scenarios/audit-existing.md +15 -8
- package/scenarios/new-project.md +1 -1
- package/scenarios/refactor-review.md +1 -1
- package/src/check.mjs +290 -40
- package/src/cli.mjs +158 -8
- package/src/gates.mjs +28 -1
- package/src/plan.mjs +2 -0
- package/templates/README.md +3 -1
- package/templates/architecture-report.md +97 -0
- package/adapters/claude/SKILL.md +0 -42
- package/adapters/trae/SKILL.md +0 -42
package/src/cli.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
3
4
|
import { check } from './check.mjs'
|
|
4
5
|
import { loadConfig, runGates } from './gates.mjs'
|
|
5
6
|
import { buildPlan, summarizePlan } from './plan.mjs'
|
|
@@ -22,12 +23,15 @@ Options:
|
|
|
22
23
|
--root <path> Project root (default: cwd)
|
|
23
24
|
--pack app|library|design-system|auto Rule pack (default: auto)
|
|
24
25
|
--config <path> Config file (default: codecraft.config.json)
|
|
26
|
+
--changed Only report findings in changed / untracked files
|
|
27
|
+
--since <ref> Only report findings changed since a git ref (e.g. origin/main)
|
|
28
|
+
--exclude <glob> Skip files when scanning (repeatable; e.g. "evals/fixtures/**")
|
|
25
29
|
--no-gates Skip gates in audit / plan
|
|
26
30
|
--with-gates Run configured gates in plan
|
|
27
31
|
--fail-fast Stop gates after the first failure
|
|
28
32
|
--name <name> Scaffold name (required with --apply)
|
|
29
33
|
--apply Write the scaffold; existing paths are skipped
|
|
30
|
-
--format md|json
|
|
34
|
+
--format md|json|sarif Output format (default: md)
|
|
31
35
|
--severity warn|error Reported severity (default: warn)
|
|
32
36
|
--ci Treat findings as errors
|
|
33
37
|
--help, -h
|
|
@@ -106,9 +110,28 @@ export function main(argv) {
|
|
|
106
110
|
return 0
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
const result = check(root, {
|
|
113
|
+
const result = check(root, {
|
|
114
|
+
pack,
|
|
115
|
+
boundaries: config.config.boundaries,
|
|
116
|
+
exclude: args.exclude,
|
|
117
|
+
})
|
|
118
|
+
let changed = null
|
|
119
|
+
let scoped = result.findings
|
|
120
|
+
if (args.changed || args.since) {
|
|
121
|
+
try {
|
|
122
|
+
changed = changedFiles(root, args.since)
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(`codecraft: ${error.message}`)
|
|
125
|
+
return 2
|
|
126
|
+
}
|
|
127
|
+
const files = new Set(changed)
|
|
128
|
+
scoped = scoped.filter((finding) => {
|
|
129
|
+
const file = finding.file.replace(/\\/g, '/')
|
|
130
|
+
return files.has(file) || [...files].some((candidate) => candidate.endsWith(`/${file}`))
|
|
131
|
+
})
|
|
132
|
+
}
|
|
110
133
|
const severity = args.ci ? 'error' : args.severity
|
|
111
|
-
const findings =
|
|
134
|
+
const findings = scoped.map((finding) => ({ ...finding, severity }))
|
|
112
135
|
const filtered = applyIgnores(findings, config.config.ignore ?? [])
|
|
113
136
|
const activeFindings = filtered.findings
|
|
114
137
|
const warnings = []
|
|
@@ -127,8 +150,11 @@ export function main(argv) {
|
|
|
127
150
|
root,
|
|
128
151
|
config: config.file,
|
|
129
152
|
severity,
|
|
130
|
-
rules: pack === 'app' ? ['A17', 'A21'] : ['A2', 'A11', 'A21'],
|
|
153
|
+
rules: pack === 'app' ? ['A6', 'A17', 'A21'] : ['A2', 'A6', 'A11', 'A21'],
|
|
131
154
|
files: result.files,
|
|
155
|
+
scope: args.changed || args.since ? 'changed' : 'full',
|
|
156
|
+
changed: changed?.length ?? null,
|
|
157
|
+
exclude: args.exclude,
|
|
132
158
|
findings: activeFindings.length,
|
|
133
159
|
ignored: filtered.ignored.length,
|
|
134
160
|
expired: filtered.expired.length,
|
|
@@ -161,6 +187,8 @@ export function main(argv) {
|
|
|
161
187
|
2,
|
|
162
188
|
),
|
|
163
189
|
)
|
|
190
|
+
} else if (args.format === 'sarif') {
|
|
191
|
+
console.log(formatSarif(meta, activeFindings))
|
|
164
192
|
} else if (command === 'plan') {
|
|
165
193
|
console.log(formatPlan(meta, activeFindings, plan, gates))
|
|
166
194
|
} else {
|
|
@@ -200,6 +228,9 @@ function parseArgs(argv) {
|
|
|
200
228
|
failFast: false,
|
|
201
229
|
name: '',
|
|
202
230
|
apply: false,
|
|
231
|
+
changed: false,
|
|
232
|
+
since: '',
|
|
233
|
+
exclude: [],
|
|
203
234
|
format: 'md',
|
|
204
235
|
severity: 'warn',
|
|
205
236
|
ci: false,
|
|
@@ -216,6 +247,13 @@ function parseArgs(argv) {
|
|
|
216
247
|
else if (arg === '--with-gates') args.withGates = true
|
|
217
248
|
else if (arg === '--fail-fast') args.failFast = true
|
|
218
249
|
else if (arg === '--apply') args.apply = true
|
|
250
|
+
else if (arg === '--changed') args.changed = true
|
|
251
|
+
else if (arg === '--since') args.since = argv[++i] ?? ''
|
|
252
|
+
else if (arg === '--exclude') {
|
|
253
|
+
const pattern = argv[++i] ?? ''
|
|
254
|
+
if (!pattern) fail('--exclude needs a path or glob (e.g. "evals/fixtures/**")')
|
|
255
|
+
args.exclude.push(pattern)
|
|
256
|
+
}
|
|
219
257
|
else if (arg === '--name') args.name = argv[++i] ?? ''
|
|
220
258
|
else if (arg === '--root') args.root = argv[++i] ?? '.'
|
|
221
259
|
else if (arg === '--pack') args.pack = argv[++i] ?? 'auto'
|
|
@@ -227,7 +265,9 @@ function parseArgs(argv) {
|
|
|
227
265
|
}
|
|
228
266
|
|
|
229
267
|
if (!['app', 'library', 'design-system', 'auto'].includes(args.pack)) fail(`invalid --pack: ${args.pack}`)
|
|
230
|
-
if (args.format !== 'md' && args.format !== 'json'
|
|
268
|
+
if (args.format !== 'md' && args.format !== 'json' && args.format !== 'sarif') {
|
|
269
|
+
fail(`invalid --format: ${args.format}`)
|
|
270
|
+
}
|
|
231
271
|
if (args.severity !== 'warn' && args.severity !== 'error') {
|
|
232
272
|
fail(`invalid --severity: ${args.severity}`)
|
|
233
273
|
}
|
|
@@ -235,6 +275,39 @@ function parseArgs(argv) {
|
|
|
235
275
|
return args
|
|
236
276
|
}
|
|
237
277
|
|
|
278
|
+
function changedFiles(root, since) {
|
|
279
|
+
const top = git(root, ['rev-parse', '--show-toplevel'])
|
|
280
|
+
if (top == null) {
|
|
281
|
+
throw new Error('not a git repository; --changed / --since need git (run inside a git repo or drop the flag)')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const lists = [git(root, ['diff', '--name-only', '--diff-filter=ACMRT', 'HEAD'])]
|
|
285
|
+
if (since) lists.push(git(root, ['diff', '--name-only', '--diff-filter=ACMRT', `${since}...HEAD`]))
|
|
286
|
+
lists.push(git(root, ['ls-files', '--others', '--exclude-standard']))
|
|
287
|
+
|
|
288
|
+
const realRoot = fs.realpathSync(root)
|
|
289
|
+
const files = new Set()
|
|
290
|
+
for (const list of lists) {
|
|
291
|
+
if (!list) continue
|
|
292
|
+
for (const line of list.split('\n')) {
|
|
293
|
+
const file = line.trim()
|
|
294
|
+
if (!file) continue
|
|
295
|
+
const relative = path.relative(realRoot, path.resolve(top, file)).split(path.sep).join('/')
|
|
296
|
+
if (relative && !relative.startsWith('..')) files.add(relative)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return [...files]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function git(root, args) {
|
|
303
|
+
const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' })
|
|
304
|
+
if (result.status !== 0) {
|
|
305
|
+
const message = (result.stderr || '').trim()
|
|
306
|
+
throw new Error(`git ${args.join(' ')} failed${message ? `: ${message}` : ''}`)
|
|
307
|
+
}
|
|
308
|
+
return result.stdout.trim()
|
|
309
|
+
}
|
|
310
|
+
|
|
238
311
|
export function detectPack(root) {
|
|
239
312
|
if (isDir(path.join(root, 'apps'))) return 'app'
|
|
240
313
|
if (hasAppMarkers(root)) return 'app'
|
|
@@ -256,13 +329,28 @@ const APP_FILE_MARKERS = [
|
|
|
256
329
|
'src/app.config.ts',
|
|
257
330
|
'taro.config.js',
|
|
258
331
|
'taro.config.ts',
|
|
332
|
+
'vue.config.js',
|
|
333
|
+
'vue.config.ts',
|
|
259
334
|
]
|
|
260
335
|
const APP_DEPS = ['nuxt', '@tarojs/taro', '@dcloudio/uni-app', 'uni-app', 'next']
|
|
336
|
+
/** 单页 Web 应用入口;库的 demo 页通常不具备「框架依赖 + 入口 + html」三件套。 */
|
|
337
|
+
const WEB_ENTRIES = [
|
|
338
|
+
'src/main.ts',
|
|
339
|
+
'src/main.js',
|
|
340
|
+
'src/main.tsx',
|
|
341
|
+
'src/main.jsx',
|
|
342
|
+
'src/index.ts',
|
|
343
|
+
'src/index.js',
|
|
344
|
+
'src/index.tsx',
|
|
345
|
+
'src/index.jsx',
|
|
346
|
+
]
|
|
347
|
+
const WEB_DEPS = ['vue', 'react', 'react-dom', 'react-scripts', 'svelte', 'solid-js', 'preact', '@angular/core']
|
|
261
348
|
|
|
262
349
|
function hasAppMarkers(root) {
|
|
263
350
|
if (APP_DIR_MARKERS.some((dir) => isDir(path.join(root, dir)))) return true
|
|
264
351
|
if (APP_FILE_MARKERS.some((file) => isFile(path.join(root, file)))) return true
|
|
265
352
|
if (hasUniAppFiles(root)) return true
|
|
353
|
+
if (hasWebEntry(root)) return true
|
|
266
354
|
return hasAppDependency(root)
|
|
267
355
|
}
|
|
268
356
|
|
|
@@ -282,6 +370,7 @@ function hasNestedApp(root) {
|
|
|
282
370
|
if (APP_DIR_MARKERS.some((dir) => isDir(path.join(child, dir)))) return true
|
|
283
371
|
if (APP_FILE_MARKERS.some((file) => isFile(path.join(child, file)))) return true
|
|
284
372
|
if (hasUniAppFiles(child)) return true
|
|
373
|
+
if (hasWebEntry(child)) return true
|
|
285
374
|
if (hasAppDependency(child)) return true
|
|
286
375
|
}
|
|
287
376
|
return false
|
|
@@ -294,14 +383,25 @@ function hasUniAppFiles(root) {
|
|
|
294
383
|
}
|
|
295
384
|
|
|
296
385
|
function hasAppDependency(root) {
|
|
386
|
+
return dependencyNames(root).some((dep) => APP_DEPS.includes(dep))
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** 框架依赖 + html + 入口;三者齐全才按单页应用处理,避免把带 demo 的库判成 app。 */
|
|
390
|
+
function hasWebEntry(root) {
|
|
391
|
+
const html = isFile(path.join(root, 'index.html')) || isFile(path.join(root, 'public', 'index.html'))
|
|
392
|
+
if (!html) return false
|
|
393
|
+
if (!dependencyNames(root).some((dep) => WEB_DEPS.includes(dep))) return false
|
|
394
|
+
return WEB_ENTRIES.some((file) => isFile(path.join(root, file)))
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function dependencyNames(root) {
|
|
297
398
|
let manifest
|
|
298
399
|
try {
|
|
299
400
|
manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
|
|
300
401
|
} catch {
|
|
301
|
-
return
|
|
402
|
+
return []
|
|
302
403
|
}
|
|
303
|
-
|
|
304
|
-
return APP_DEPS.some((dep) => dep in deps)
|
|
404
|
+
return Object.keys({ ...(manifest.dependencies ?? {}), ...(manifest.devDependencies ?? {}) })
|
|
305
405
|
}
|
|
306
406
|
|
|
307
407
|
function isFile(target) {
|
|
@@ -317,6 +417,55 @@ function fail(message) {
|
|
|
317
417
|
process.exit(2)
|
|
318
418
|
}
|
|
319
419
|
|
|
420
|
+
function formatSarif(meta, findings) {
|
|
421
|
+
const rules = meta.rules.map((id) => ({
|
|
422
|
+
id,
|
|
423
|
+
name: SARIF_RULES[id]?.name ?? id,
|
|
424
|
+
shortDescription: { text: SARIF_RULES[id]?.short ?? id },
|
|
425
|
+
}))
|
|
426
|
+
return JSON.stringify(
|
|
427
|
+
{
|
|
428
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
429
|
+
version: '2.1.0',
|
|
430
|
+
runs: [
|
|
431
|
+
{
|
|
432
|
+
tool: {
|
|
433
|
+
driver: {
|
|
434
|
+
name: 'codecraft',
|
|
435
|
+
informationUri: 'https://github.com/ultime-space/skills',
|
|
436
|
+
version: meta.version,
|
|
437
|
+
rules,
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
results: findings.map((finding) => ({
|
|
441
|
+
ruleId: finding.rule,
|
|
442
|
+
level: finding.severity === 'error' ? 'error' : 'warning',
|
|
443
|
+
message: { text: finding.message },
|
|
444
|
+
locations: [
|
|
445
|
+
{
|
|
446
|
+
physicalLocation: {
|
|
447
|
+
artifactLocation: { uri: finding.file.replace(/\\/g, '/') },
|
|
448
|
+
region: { startLine: finding.line },
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
})),
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
},
|
|
456
|
+
null,
|
|
457
|
+
2,
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const SARIF_RULES = {
|
|
462
|
+
A2: { name: 'DependencyDirection', short: 'Dependencies must point inward' },
|
|
463
|
+
A6: { name: 'EmptyCatch', short: 'Empty catch swallows failures' },
|
|
464
|
+
A11: { name: 'GeneratedReadOnly', short: 'Generated files must be read-only and marked' },
|
|
465
|
+
A17: { name: 'ExternalBoundary', short: 'Internal layers must not import external or generated types' },
|
|
466
|
+
A21: { name: 'SecretScan', short: 'No secrets or internal paths in repository' },
|
|
467
|
+
}
|
|
468
|
+
|
|
320
469
|
function formatReport(meta, findings, ignored, expired, gates) {
|
|
321
470
|
const lines = header(meta)
|
|
322
471
|
|
|
@@ -457,6 +606,7 @@ function header(meta) {
|
|
|
457
606
|
`- Pack: ${meta.pack}`,
|
|
458
607
|
`- Root: ${meta.root}`,
|
|
459
608
|
`- Config: ${meta.config ?? '(none)'}`,
|
|
609
|
+
`- Exclude: ${meta.exclude?.length ? meta.exclude.join(', ') : '(none)'}`,
|
|
460
610
|
`- Rules: ${meta.rules.join(' / ')}`,
|
|
461
611
|
`- Severity: ${meta.severity}`,
|
|
462
612
|
`- Files: ${meta.files}`,
|
package/src/gates.mjs
CHANGED
|
@@ -27,7 +27,34 @@ export function loadConfig(root, configPath) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
file,
|
|
32
|
+
config: {
|
|
33
|
+
...config,
|
|
34
|
+
ignore: validateIgnore(config.ignore, file),
|
|
35
|
+
...(config.boundaries ? { boundaries: validateBoundaries(config.boundaries, file) } : {}),
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateBoundaries(value, file) {
|
|
41
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
42
|
+
throw new Error(`invalid config ${file}: "boundaries" must be an object with external / generated arrays`)
|
|
43
|
+
}
|
|
44
|
+
const boundaries = {}
|
|
45
|
+
for (const key of ['external', 'generated']) {
|
|
46
|
+
if (value[key] == null) continue
|
|
47
|
+
if (!Array.isArray(value[key])) {
|
|
48
|
+
throw new Error(`invalid config ${file}: boundaries.${key} must be an array of import prefixes or globs`)
|
|
49
|
+
}
|
|
50
|
+
boundaries[key] = value[key].map((item) => {
|
|
51
|
+
if (typeof item !== 'string' || !item.trim()) {
|
|
52
|
+
throw new Error(`invalid config ${file}: boundaries.${key} entries must be non-empty strings`)
|
|
53
|
+
}
|
|
54
|
+
return item.trim()
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
return boundaries
|
|
31
58
|
}
|
|
32
59
|
|
|
33
60
|
export function runGates(root, gates, options = {}) {
|
package/src/plan.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const ACTIONS = {
|
|
2
2
|
A2: 'Declare the workspace dependency in package.json, or remove the import; if shared, move it to a lower-layer package.',
|
|
3
3
|
A4: 'Move the external import to a Wrapper/API module; expose an internal type to the caller.',
|
|
4
|
+
A6: 'Return a Result or handle the error; never swallow it in an empty catch.',
|
|
4
5
|
A11: "Regenerate the file from source and add a 'do not edit' marker; if it is not generated, move it out of generated paths.",
|
|
5
6
|
A17: 'Move the external import to a Wrapper/API module; expose an internal type to the caller.',
|
|
6
7
|
}
|
|
@@ -8,6 +9,7 @@ const ACTIONS = {
|
|
|
8
9
|
const VERIFY = {
|
|
9
10
|
A2: 'codecraft check --pack library',
|
|
10
11
|
A4: 'codecraft check --pack app',
|
|
12
|
+
A6: 'codecraft check',
|
|
11
13
|
A11: 'codecraft check --pack library',
|
|
12
14
|
A17: 'codecraft check --pack app',
|
|
13
15
|
}
|
package/templates/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|---|---|
|
|
7
7
|
| `app/` | 应用骨架:`README.md` + `package.json` |
|
|
8
8
|
| `library/` | 包骨架:`README.md` + `CHANGELOG.md` + `compat.md` + `package.json` |
|
|
9
|
+
| `architecture-report.md` | 代码架构分析报告模板(Agent 输出用,不是 init 骨架) |
|
|
9
10
|
|
|
10
11
|
- 目录结构由 `src/init.mjs` 定义,模板只提供文件内容。
|
|
11
|
-
-
|
|
12
|
+
- `architecture-report.md` 只被 `scenarios/architecture-report.md` 引用,不参与 `init`。
|
|
13
|
+
- 页面 / 组件 / Wrapper的单文件形态见 `docs/01-family-cards.md`,不单独出模板:不同框架的写法差异大于共性。
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# <项目 / 模块> 代码架构分析报告(Codecraft)
|
|
2
|
+
|
|
3
|
+
> 模式:Audit / Plan,只读 · 日期:YYYY-MM-DD · 仓库:`<repo>` · 分支:`<branch>` · HEAD:`<sha>`
|
|
4
|
+
> 范围:`<目录 / 包>` · 规则版本:Codecraft `<version>`
|
|
5
|
+
> 只读声明:未改代码、未起服务。未执行:`<typecheck / build / 单测>`。
|
|
6
|
+
> 边界:本文只讨论代码架构;业务能力、角色、业务流程、领域规则不在范围。
|
|
7
|
+
|
|
8
|
+
## 1. 结论与置信度
|
|
9
|
+
|
|
10
|
+
- 一句话结论:
|
|
11
|
+
- 置信度:High / Medium / Low(依据:静态 + 人工抽样 + 关键路径)
|
|
12
|
+
- 最高风险:
|
|
13
|
+
- 最需要先做的决定:
|
|
14
|
+
|
|
15
|
+
## 2. 范围与方法
|
|
16
|
+
|
|
17
|
+
| 项 | 内容 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| 范围 | |
|
|
20
|
+
| 已执行 | |
|
|
21
|
+
| 未执行 | |
|
|
22
|
+
| 覆盖边界 | |
|
|
23
|
+
| 判定口径 | |
|
|
24
|
+
|
|
25
|
+
## 3. 架构现状
|
|
26
|
+
|
|
27
|
+
### 3.1 系统与容器
|
|
28
|
+
|
|
29
|
+
| 单元 | 职责 | 运行时 / 部署 | 外部依赖 |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| | | | |
|
|
32
|
+
|
|
33
|
+
### 3.2 模块与依赖
|
|
34
|
+
|
|
35
|
+
| 模块 / 包 | 职责 | 允许依赖 | 禁止依赖 | 证据 |
|
|
36
|
+
|---|---|---|---|---|
|
|
37
|
+
| | | | | |
|
|
38
|
+
|
|
39
|
+
- 依赖方向:
|
|
40
|
+
- 边界与内外分离:
|
|
41
|
+
- 公共 API / 内部实现:
|
|
42
|
+
|
|
43
|
+
### 3.3 关键链路
|
|
44
|
+
|
|
45
|
+
| 链路 | 入口 | 关键节点 | 失败模式 | 证据 |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| 请求 | | | | |
|
|
48
|
+
| 状态 | | | | |
|
|
49
|
+
| 错误 | | | | |
|
|
50
|
+
| 数据 | | | | |
|
|
51
|
+
| 生成 | | | | |
|
|
52
|
+
|
|
53
|
+
## 4. A1–A21 判定
|
|
54
|
+
|
|
55
|
+
| ID | 判定 | 证据 | 局限 |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| A1 | pass / pass(静态)/ warn / fail / na | `path:line` 或命令 | |
|
|
58
|
+
| … | | | |
|
|
59
|
+
|
|
60
|
+
- 一致性检查:
|
|
61
|
+
- `codecraft check` 结果:`files / findings / 覆盖范围`
|
|
62
|
+
- A17 / A21 盲区:
|
|
63
|
+
|
|
64
|
+
## 5. 风险与技术债
|
|
65
|
+
|
|
66
|
+
| 优先级 | 问题 | 证据 | 影响 | 建议 |
|
|
67
|
+
|---|---|---|---|---|
|
|
68
|
+
| P0 | | | | |
|
|
69
|
+
| P1 | | | | |
|
|
70
|
+
| P2 | | | | |
|
|
71
|
+
| P3 | | | | |
|
|
72
|
+
|
|
73
|
+
## 6. 演进计划
|
|
74
|
+
|
|
75
|
+
| 阶段 | 内容 | 可回滚 | 验收 |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| 0 | | | |
|
|
78
|
+
| 1 | | | |
|
|
79
|
+
| 2 | | | |
|
|
80
|
+
|
|
81
|
+
- 不写时间估算。
|
|
82
|
+
- 每阶段一个可验收的垂直切片。
|
|
83
|
+
|
|
84
|
+
## 7. 未知项与待确认
|
|
85
|
+
|
|
86
|
+
| # | 问题 | 为什么无法确认 | 需要谁 / 什么证据 |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| 1 | | | |
|
|
89
|
+
|
|
90
|
+
## 8. 附录:证据与命令
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# 本次执行的检查
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- 相关文件:
|
|
97
|
+
- 相关文档:
|
package/adapters/claude/SKILL.md
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: codecraft
|
|
3
|
-
description: 面向 JS/TS 的代码规范与架构设计(Codecraft)。默认 Web 前端,Node 后端可复用结构规则;用于审计、架构设计、重构评审、规范检查与脚手架。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Codecraft(代码手艺)
|
|
7
|
-
|
|
8
|
-
> 方向:JS/TS(默认前端)。后端只复用结构规则;Page / Component 等前端族不套用到后端。
|
|
9
|
-
|
|
10
|
-
## 先读
|
|
11
|
-
|
|
12
|
-
1. 项目 `AGENTS.md` / `rules/`
|
|
13
|
-
2. `../../docs/00-constitution.md`
|
|
14
|
-
3. 只读相关族卡:`../../docs/01-family-cards.md`
|
|
15
|
-
4. 设计架构时:`../../docs/06-architecture.md`
|
|
16
|
-
5. 需要选型时:`../../docs/05-patterns.md`
|
|
17
|
-
6. 选场景:`../../scenarios/`
|
|
18
|
-
|
|
19
|
-
## 模式
|
|
20
|
-
|
|
21
|
-
- **Audit(审计)**:只读,出报告。
|
|
22
|
-
- **Plan(计划)**:只读,出架构 / 升级计划。
|
|
23
|
-
- **Apply(执行)**:改代码 + 跑门禁。
|
|
24
|
-
|
|
25
|
-
## 工作流
|
|
26
|
-
|
|
27
|
-
1. 判定任务属于哪个场景。
|
|
28
|
-
2. 只读相关族卡,不要一次加载全部。
|
|
29
|
-
3. 先出计划;Apply 前确认范围。
|
|
30
|
-
4. 跑项目验证命令;不能跑要说明原因。
|
|
31
|
-
5. 按 `../../prompts/output.md` 输出。
|
|
32
|
-
|
|
33
|
-
## 禁止
|
|
34
|
-
|
|
35
|
-
- Audit / Plan 模式改文件。
|
|
36
|
-
- 把业务逻辑放进 Transport 或 Page。
|
|
37
|
-
- 内部代码 import `external/` 或生成类型。
|
|
38
|
-
- 手改生成物。
|
|
39
|
-
- 写敏感信息;发现即停止、脱敏、提示轮换。
|
|
40
|
-
- 安全红线登记例外。
|
|
41
|
-
|
|
42
|
-
规则正文只在 `docs/`;本文件只做入口。
|
package/adapters/trae/SKILL.md
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: codecraft
|
|
3
|
-
description: 面向 JS/TS 的代码规范与架构设计(Codecraft)。默认 Web 前端,Node 后端可复用结构规则;用于审计、架构设计、重构评审、规范检查与脚手架。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Codecraft(代码手艺)
|
|
7
|
-
|
|
8
|
-
> 方向:JS/TS(默认前端)。后端只复用结构规则;Page / Component 等前端族不套用到后端。
|
|
9
|
-
|
|
10
|
-
## 先读
|
|
11
|
-
|
|
12
|
-
1. 项目 `AGENTS.md` / `rules/`
|
|
13
|
-
2. `../../docs/00-constitution.md`
|
|
14
|
-
3. 只读相关族卡:`../../docs/01-family-cards.md`
|
|
15
|
-
4. 设计架构时:`../../docs/06-architecture.md`
|
|
16
|
-
5. 需要选型时:`../../docs/05-patterns.md`
|
|
17
|
-
6. 选场景:`../../scenarios/`
|
|
18
|
-
|
|
19
|
-
## 模式
|
|
20
|
-
|
|
21
|
-
- **Audit(审计)**:只读,出报告。
|
|
22
|
-
- **Plan(计划)**:只读,出架构 / 升级计划。
|
|
23
|
-
- **Apply(执行)**:改代码 + 跑门禁。
|
|
24
|
-
|
|
25
|
-
## 工作流
|
|
26
|
-
|
|
27
|
-
1. 判定任务属于哪个场景。
|
|
28
|
-
2. 只读相关族卡,不要一次加载全部。
|
|
29
|
-
3. 先出计划;Apply 前确认范围。
|
|
30
|
-
4. 跑项目验证命令;不能跑要说明原因。
|
|
31
|
-
5. 按 `../../prompts/output.md` 输出。
|
|
32
|
-
|
|
33
|
-
## 禁止
|
|
34
|
-
|
|
35
|
-
- Audit / Plan 模式改文件。
|
|
36
|
-
- 把业务逻辑放进 Transport 或 Page。
|
|
37
|
-
- 内部代码 import `external/` 或生成类型。
|
|
38
|
-
- 手改生成物。
|
|
39
|
-
- 写敏感信息;发现即停止、脱敏、提示轮换。
|
|
40
|
-
- 安全红线登记例外。
|
|
41
|
-
|
|
42
|
-
规则正文只在 `docs/`;本文件只做入口。
|