@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTING.md +3 -0
  3. package/README.md +51 -81
  4. package/SKILL.md +2 -2
  5. package/VERSION +1 -1
  6. package/adapters/README.md +3 -4
  7. package/codecraft.config.example.json +5 -1
  8. package/docs/00-constitution.md +2 -2
  9. package/docs/02-ai-contract.md +12 -1
  10. package/docs/04-gates.md +21 -6
  11. package/docs/07-ai-prompts.md +9 -8
  12. package/docs/08-audit-contract.md +141 -0
  13. package/evals/cases.json +110 -0
  14. package/evals/fixtures/a11-marker/packages/core/dist/tokens.css +4 -0
  15. package/evals/fixtures/a11-marker/packages/core/package.json +1 -0
  16. package/evals/fixtures/a11-marker/packages/core/src/index.ts +1 -0
  17. package/evals/fixtures/a17-boundaries/codecraft.config.json +5 -0
  18. package/evals/fixtures/a17-boundaries/src/pages/home.ts +1 -0
  19. package/evals/fixtures/a17-default/src/pages/home.ts +1 -0
  20. package/evals/fixtures/a17-generated-data/src/icons/icons.ts +2 -0
  21. package/evals/fixtures/a17-generated-data/src/pages/home.ts +3 -0
  22. package/evals/fixtures/a17-generated-header/src/pages/home.ts +5 -0
  23. package/evals/fixtures/a17-generated-header/src/types/project.ts +4 -0
  24. package/evals/fixtures/a17-local/src/pages/home.ts +4 -0
  25. package/evals/fixtures/a17-local/src/types/local.ts +3 -0
  26. package/evals/fixtures/a17-local/src/utils/format.ts +3 -0
  27. package/evals/fixtures/a2-declared/packages/core/package.json +6 -0
  28. package/evals/fixtures/a2-declared/packages/core/src/index.ts +3 -0
  29. package/evals/fixtures/a2-declared/packages/kit/package.json +1 -0
  30. package/evals/fixtures/a2-workspace/packages/core/package.json +1 -0
  31. package/evals/fixtures/a2-workspace/packages/core/src/index.ts +3 -0
  32. package/evals/fixtures/a2-workspace/packages/kit/package.json +1 -0
  33. package/evals/fixtures/a21-doc-paths/packages/ui/README.md +5 -0
  34. package/evals/fixtures/a21-doc-paths/packages/ui/package.json +1 -0
  35. package/evals/fixtures/a21-doc-paths/packages/ui/src/index.ts +1 -0
  36. package/evals/fixtures/a21-placeholders/.env +2 -0
  37. package/evals/fixtures/a21-placeholders/server/application.yml +9 -0
  38. package/evals/fixtures/a21-placeholders/src/pages/home.ts +1 -0
  39. package/evals/fixtures/a21-plaintext/application.yml +5 -0
  40. package/evals/fixtures/a21-plaintext/src/main/java/Ali.java +3 -0
  41. package/evals/fixtures/a21-plaintext/src/pages/home.ts +1 -0
  42. package/evals/fixtures/a6-clean/src/pages/handled.ts +8 -0
  43. package/evals/fixtures/a6-empty-catch/src/pages/empty.ts +3 -0
  44. package/evals/fixtures/ignore-active/codecraft.config.json +5 -0
  45. package/evals/fixtures/ignore-active/src/pages/home.ts +1 -0
  46. package/evals/fixtures/ignore-expired/codecraft.config.json +5 -0
  47. package/evals/fixtures/ignore-expired/src/pages/home.ts +1 -0
  48. package/evals/fixtures/library-a11/dist/tokens.css +3 -0
  49. package/evals/fixtures/library-a11/package.json +4 -0
  50. package/evals/fixtures/library-a11/src/index.ts +1 -0
  51. package/evals/fixtures/zero-files/package.json +4 -0
  52. package/evals/golden-tasks.md +9 -5
  53. package/evals/run.mjs +75 -0
  54. package/package.json +6 -4
  55. package/prompts/output.md +15 -4
  56. package/scenarios/audit-existing.md +15 -8
  57. package/src/check.mjs +290 -40
  58. package/src/cli.mjs +159 -143
  59. package/src/gates.mjs +28 -1
  60. package/src/plan.mjs +2 -0
  61. package/adapters/claude/SKILL.md +0 -42
  62. package/adapters/trae/SKILL.md +0 -42
  63. 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
- }