@ultime/codecraft 1.3.0 → 1.5.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 +31 -0
- package/CONTRIBUTING.md +3 -0
- package/README.md +51 -81
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/adapters/README.md +3 -4
- package/codecraft.config.example.json +5 -1
- package/docs/00-constitution.md +2 -2
- package/docs/02-ai-contract.md +12 -1
- package/docs/04-gates.md +21 -6
- package/docs/07-ai-prompts.md +9 -8
- package/docs/08-audit-contract.md +141 -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 +9 -5
- package/evals/run.mjs +75 -0
- package/package.json +6 -4
- package/prompts/output.md +15 -4
- package/scenarios/audit-existing.md +15 -8
- package/src/check.mjs +290 -40
- package/src/cli.mjs +159 -143
- package/src/gates.mjs +28 -1
- package/src/plan.mjs +2 -0
- package/adapters/claude/SKILL.md +0 -42
- package/adapters/trae/SKILL.md +0 -42
- package/src/install.mjs +0 -172
package/src/install.mjs
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
|
|
6
|
-
const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url))
|
|
7
|
-
const SKILL_FILES = ['SKILL.md']
|
|
8
|
-
const SKILL_DIRS = ['docs', 'scenarios', 'prompts']
|
|
9
|
-
|
|
10
|
-
const USER_AGENTS_FILE = path.join(os.homedir(), '.codecraft', 'agents.json')
|
|
11
|
-
const PROJECT_AGENTS_FILE = 'codecraft.agents.json'
|
|
12
|
-
|
|
13
|
-
const BUILTIN_AGENTS = {
|
|
14
|
-
generic: { project: '.agents/skills', global: '.agents/skills', markers: ['.agents'] },
|
|
15
|
-
claude: { project: '.claude/skills', global: '.claude/skills', markers: ['.claude', 'CLAUDE.md'] },
|
|
16
|
-
cursor: { project: '.cursor/skills', global: '.cursor/skills', markers: ['.cursor'] },
|
|
17
|
-
trae: { project: '.trae/skills', global: '.trae/skills', markers: ['.trae'] },
|
|
18
|
-
'trae-cn': { project: '.trae-cn/skills', global: '.trae-cn/skills', markers: ['.trae-cn'] },
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const AGENTS = Object.keys(BUILTIN_AGENTS)
|
|
22
|
-
|
|
23
|
-
export function loadAgents(root, options = {}) {
|
|
24
|
-
const registry = new Map(
|
|
25
|
-
Object.entries(BUILTIN_AGENTS).map(([name, definition]) => [name, { ...definition, source: 'builtin' }]),
|
|
26
|
-
)
|
|
27
|
-
mergeAgentFile(registry, options.userFile ?? USER_AGENTS_FILE, 'user')
|
|
28
|
-
mergeAgentFile(registry, options.projectFile ?? path.join(root, PROJECT_AGENTS_FILE), 'project')
|
|
29
|
-
return registry
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function listAgents(root, options = {}) {
|
|
33
|
-
const registry = loadAgents(root, options)
|
|
34
|
-
return [...registry.entries()].map(([name, definition]) => ({
|
|
35
|
-
name,
|
|
36
|
-
project: definition.project,
|
|
37
|
-
global: definition.global,
|
|
38
|
-
source: definition.source,
|
|
39
|
-
detected: {
|
|
40
|
-
project: isDetected(root, definition.markers),
|
|
41
|
-
global: isDetected(os.homedir(), definition.markers),
|
|
42
|
-
},
|
|
43
|
-
}))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function parseAgents(value, names = AGENTS) {
|
|
47
|
-
if (!value || value === 'auto') return []
|
|
48
|
-
const agents = [...new Set(value.split(',').map((item) => item.trim()).filter(Boolean))]
|
|
49
|
-
const invalid = agents.filter((agent) => !names.includes(agent))
|
|
50
|
-
if (invalid.length) {
|
|
51
|
-
throw new Error(`unknown agent: ${invalid.join(', ')}(可选:${names.join(', ')};用 --list-agents 查看)`)
|
|
52
|
-
}
|
|
53
|
-
return agents
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function buildInstallPlan(root, options = {}) {
|
|
57
|
-
const registry = options.registry ?? loadAgents(root, options)
|
|
58
|
-
const base = options.global ? os.homedir() : path.resolve(root)
|
|
59
|
-
const names = [...registry.keys()]
|
|
60
|
-
const explicit = options.agents ?? []
|
|
61
|
-
const invalid = explicit.filter((agent) => !registry.has(agent))
|
|
62
|
-
if (invalid.length) {
|
|
63
|
-
throw new Error(`unknown agent: ${invalid.join(', ')}(可选:${names.join(', ')};用 --list-agents 查看)`)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const agents = explicit.length ? explicit : detectAgents(base, registry, options.global)
|
|
67
|
-
const destinations = options.dest
|
|
68
|
-
? [{ agent: 'custom', dir: path.resolve(base, options.dest) }]
|
|
69
|
-
: agents.map((agent) => {
|
|
70
|
-
const definition = registry.get(agent)
|
|
71
|
-
return { agent, dir: path.join(base, options.global ? definition.global : definition.project) }
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
assertSource()
|
|
75
|
-
|
|
76
|
-
const byTarget = new Map()
|
|
77
|
-
for (const { agent, dir } of destinations) {
|
|
78
|
-
const target = path.join(dir, 'codecraft')
|
|
79
|
-
if (!byTarget.has(target)) byTarget.set(target, { target, agents: [] })
|
|
80
|
-
byTarget.get(target).agents.push(agent)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return [...byTarget.values()].map(({ target, agents: targetAgents }) => {
|
|
84
|
-
const exists = fs.existsSync(target)
|
|
85
|
-
return {
|
|
86
|
-
agent: targetAgents.join(','),
|
|
87
|
-
agents: targetAgents,
|
|
88
|
-
target,
|
|
89
|
-
exists,
|
|
90
|
-
action: exists ? 'exists' : 'create',
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function applyInstallPlan(plan, options = {}) {
|
|
96
|
-
const created = []
|
|
97
|
-
const skipped = []
|
|
98
|
-
const replaced = []
|
|
99
|
-
|
|
100
|
-
for (const entry of plan) {
|
|
101
|
-
if (entry.exists && !options.force) {
|
|
102
|
-
skipped.push(entry.target)
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
if (entry.exists) {
|
|
106
|
-
fs.rmSync(entry.target, { recursive: true, force: true })
|
|
107
|
-
replaced.push(entry.target)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
fs.mkdirSync(entry.target, { recursive: true })
|
|
111
|
-
for (const file of SKILL_FILES) {
|
|
112
|
-
fs.copyFileSync(path.join(PACKAGE_ROOT, file), path.join(entry.target, file))
|
|
113
|
-
}
|
|
114
|
-
for (const dir of SKILL_DIRS) {
|
|
115
|
-
fs.cpSync(path.join(PACKAGE_ROOT, dir), path.join(entry.target, dir), { recursive: true })
|
|
116
|
-
}
|
|
117
|
-
created.push(entry.target)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return { created, skipped, replaced }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function mergeAgentFile(registry, file, source) {
|
|
124
|
-
if (!fs.existsSync(file)) return
|
|
125
|
-
|
|
126
|
-
let parsed
|
|
127
|
-
try {
|
|
128
|
-
parsed = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
129
|
-
} catch (error) {
|
|
130
|
-
throw new Error(`invalid agents file ${file}: ${error.message} (fix the JSON syntax or remove the file)`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const agents = parsed?.agents
|
|
134
|
-
if (!agents || typeof agents !== 'object' || Array.isArray(agents)) {
|
|
135
|
-
throw new Error(`invalid agents file ${file}: expected { "agents": { "<name>": { project, global, markers } } }`)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
for (const [name, definition] of Object.entries(agents)) {
|
|
139
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
|
|
140
|
-
throw new Error(`invalid agents file ${file}: agent name "${name}" must be lowercase kebab-case`)
|
|
141
|
-
}
|
|
142
|
-
const project = typeof definition?.project === 'string' ? definition.project.trim() : ''
|
|
143
|
-
if (!project) {
|
|
144
|
-
throw new Error(`invalid agents file ${file}: agent "${name}" needs a non-empty "project" path`)
|
|
145
|
-
}
|
|
146
|
-
const global = typeof definition?.global === 'string' && definition.global.trim() ? definition.global.trim() : project
|
|
147
|
-
const markers = Array.isArray(definition?.markers)
|
|
148
|
-
? definition.markers.filter((marker) => typeof marker === 'string' && marker.trim())
|
|
149
|
-
: []
|
|
150
|
-
registry.set(name, { project, global, markers, source })
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function detectAgents(base, registry, global) {
|
|
155
|
-
const found = [...registry.entries()]
|
|
156
|
-
.filter(([, definition]) => isDetected(base, definition.markers))
|
|
157
|
-
.map(([name]) => name)
|
|
158
|
-
if (global) return found.length ? found : ['generic']
|
|
159
|
-
return [...new Set([...found, 'generic'])]
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function isDetected(base, markers) {
|
|
163
|
-
return markers.some((marker) => fs.existsSync(path.join(base, marker)))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function assertSource() {
|
|
167
|
-
for (const file of SKILL_FILES) {
|
|
168
|
-
if (!fs.existsSync(path.join(PACKAGE_ROOT, file))) {
|
|
169
|
-
throw new Error(`skill source is incomplete: missing ${file}`)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|