@erclx/canon 5.63.0 → 5.65.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 +26 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/context-fold/REQUIREMENT.md +2 -3
- package/claude/skills/context-fold/SKILL.md +1 -11
- package/claude/skills/context-fold/references/completion-report.md +8 -9
- package/claude/skills/create-skill/references/skill-practice.md +16 -1
- package/claude/skills/design-taste/references/adopted.md +17 -97
- package/claude/skills/design-taste/references/tells.md +2 -2
- package/claude/skills/document-health/REQUIREMENT.md +1 -1
- package/claude/skills/document-health/SKILL.md +3 -14
- package/claude/skills/document-health/references/axes.md +14 -16
- package/claude/skills/test-craft/references/e2e.md +1 -1
- package/docs/agents/audits.md +2 -6
- package/docs/agents/context-audit.md +1 -1
- package/governance/ledger/skills/api-design/SKILL.md +62 -0
- package/governance/ledger/skills/code-craft/SKILL.md +53 -0
- package/governance/ledger/skills/codebase-layout/SKILL.md +64 -0
- package/governance/ledger/skills/codebase-layout/frontend.md +62 -0
- package/governance/ledger/skills/codebase-layout/tests.md +26 -0
- package/governance/ledger/skills/deprecation-migration/SKILL.md +68 -0
- package/governance/ledger/skills/design-taste/SKILL.md +64 -0
- package/governance/ledger/skills/design-taste/craft.md +66 -0
- package/governance/ledger/skills/design-taste/kinds.md +58 -0
- package/governance/ledger/skills/design-taste/systems.md +42 -0
- package/governance/ledger/skills/design-taste/tells.md +103 -0
- package/governance/ledger/skills/review-craft/SKILL.md +61 -0
- package/governance/ledger/skills/review-craft/security.md +34 -0
- package/governance/ledger/skills/teach-craft/SKILL.md +33 -0
- package/governance/ledger/skills/teach-craft/lesson-craft.md +47 -0
- package/governance/ledger/skills/teach-craft/pedagogy.md +77 -0
- package/governance/ledger/skills/test-craft/SKILL.md +67 -0
- package/governance/ledger/skills/test-craft/component.md +49 -0
- package/governance/ledger/skills/test-craft/e2e.md +55 -0
- package/governance/ledger/skills/test-craft/unit.md +54 -0
- package/package.json +3 -3
- package/src/audits/catalog.ts +0 -52
- package/src/claude/skills-audit.ts +124 -41
- package/src/cli/retired.ts +8 -0
- package/src/commands/claude.ts +11 -2
- package/src/commands/context.ts +0 -425
- package/src/commands/sandbox.ts +247 -2
- package/src/context/citations.ts +9 -3
- package/src/gate/measures.ts +4 -1
- package/src/gate/stages.ts +25 -0
- package/src/git/files.ts +8 -3
- package/src/markdown/gated.ts +1 -1
- package/src/markdown/scope.ts +0 -1
- package/src/migrate/records.ts +6 -0
- package/src/sandbox/replay/expected.ts +137 -0
- package/src/sandbox/replay/run.ts +323 -0
- package/src/sandbox/replay/verdict.ts +102 -0
- package/tooling/astro/configs/playwright.config.ts +2 -2
- package/tooling/nextjs/configs/playwright.config.ts +2 -2
- package/tooling/vite-react/configs/playwright.config.ts +2 -2
- package/claude/skills/context-fold/references/classify.md +0 -87
- package/src/context/classify/extract.ts +0 -443
- package/src/context/classify/ollama.ts +0 -172
- package/src/context/classify/patterns.ts +0 -75
- package/src/context/classify/prompts.ts +0 -74
- package/src/context/classify/run.ts +0 -348
- package/src/context/classify/settings.ts +0 -196
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
chmodSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
statSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from 'node:fs'
|
|
9
|
-
import { dirname, join } from 'node:path'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Ollama is the only backend. The groundwork decision explicitly deferred a
|
|
13
|
-
* Haiku or Claude API backend, so this union has one member rather than
|
|
14
|
-
* carrying an unused second case a switch would have to handle.
|
|
15
|
-
*/
|
|
16
|
-
export type ClassifierBackend = 'ollama'
|
|
17
|
-
|
|
18
|
-
/** Where `set` writes and `classify`/`classifier show` read the project setting. */
|
|
19
|
-
export const CLASSIFIER_CONFIG_REL = join('canon', 'config', 'classifier.toml')
|
|
20
|
-
|
|
21
|
-
export type SettingSource = 'flag' | 'env' | 'file' | 'default'
|
|
22
|
-
|
|
23
|
-
export interface ClassifierFlags {
|
|
24
|
-
readonly backend?: string
|
|
25
|
-
readonly model?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface FileSettings {
|
|
29
|
-
readonly backend?: string
|
|
30
|
-
readonly model?: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function readFileSettings(root: string): FileSettings {
|
|
34
|
-
const path = join(root, CLASSIFIER_CONFIG_REL)
|
|
35
|
-
if (!existsSync(path)) return {}
|
|
36
|
-
|
|
37
|
-
let parsed: Record<string, unknown>
|
|
38
|
-
try {
|
|
39
|
-
parsed = Bun.TOML.parse(readFileSync(path, 'utf8')) as Record<
|
|
40
|
-
string,
|
|
41
|
-
unknown
|
|
42
|
-
>
|
|
43
|
-
} catch {
|
|
44
|
-
return {}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const table = parsed.classifier
|
|
48
|
-
if (typeof table !== 'object' || table === null) return {}
|
|
49
|
-
const { backend, model } = table as Record<string, unknown>
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
backend: typeof backend === 'string' ? backend : undefined,
|
|
53
|
-
model: typeof model === 'string' ? model : undefined,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isBackend(value: string): value is ClassifierBackend {
|
|
58
|
-
return value === 'ollama'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Resolves the backend through the stated precedence: flag, then the
|
|
63
|
-
* `CANON_CLASSIFIER_BACKEND` environment variable, then the project config
|
|
64
|
-
* file, then off. `off` is a value in its own right rather than an absence,
|
|
65
|
-
* since a project's config file setting `backend = "off"` states the same
|
|
66
|
-
* fact the missing-file default does, and the source line should say which
|
|
67
|
-
* one decided it.
|
|
68
|
-
*
|
|
69
|
-
* An unrecognized value at any tier is read the same as an absent one, and
|
|
70
|
-
* falls through to the next tier rather than refusing. That keeps a typo'd
|
|
71
|
-
* environment variable from silently making the whole precedence chain
|
|
72
|
-
* unreachable underneath it.
|
|
73
|
-
*/
|
|
74
|
-
export function resolveBackend(
|
|
75
|
-
root: string,
|
|
76
|
-
flags: ClassifierFlags,
|
|
77
|
-
): {
|
|
78
|
-
readonly value: ClassifierBackend | 'off'
|
|
79
|
-
readonly source: SettingSource
|
|
80
|
-
} {
|
|
81
|
-
if (flags.backend !== undefined) {
|
|
82
|
-
if (flags.backend === 'off') return { value: 'off', source: 'flag' }
|
|
83
|
-
if (isBackend(flags.backend))
|
|
84
|
-
return { value: flags.backend, source: 'flag' }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const envBackend = process.env.CANON_CLASSIFIER_BACKEND
|
|
88
|
-
if (envBackend !== undefined) {
|
|
89
|
-
if (envBackend === 'off') return { value: 'off', source: 'env' }
|
|
90
|
-
if (isBackend(envBackend)) return { value: envBackend, source: 'env' }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const fileBackend = readFileSettings(root).backend
|
|
94
|
-
if (fileBackend !== undefined) {
|
|
95
|
-
if (fileBackend === 'off') return { value: 'off', source: 'file' }
|
|
96
|
-
if (isBackend(fileBackend)) return { value: fileBackend, source: 'file' }
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { value: 'off', source: 'default' }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Resolves the model name through the same precedence as the backend, kept as
|
|
104
|
-
* a separate function since a caller can name a backend without a model (the
|
|
105
|
-
* `no-model` state `resolveClassifier` reports) or a model without a backend
|
|
106
|
-
* (which resolves nothing, since there is nothing to run it against).
|
|
107
|
-
*/
|
|
108
|
-
export function resolveModel(
|
|
109
|
-
root: string,
|
|
110
|
-
flags: ClassifierFlags,
|
|
111
|
-
): { readonly value: string; readonly source: SettingSource } | undefined {
|
|
112
|
-
if (flags.model !== undefined && flags.model !== '') {
|
|
113
|
-
return { value: flags.model, source: 'flag' }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const envModel = process.env.CANON_CLASSIFIER_MODEL
|
|
117
|
-
if (envModel !== undefined && envModel !== '') {
|
|
118
|
-
return { value: envModel, source: 'env' }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const fileModel = readFileSettings(root).model
|
|
122
|
-
if (fileModel !== undefined && fileModel !== '') {
|
|
123
|
-
return { value: fileModel, source: 'file' }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return undefined
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export type ClassifierResolution =
|
|
130
|
-
| { readonly kind: 'off'; readonly source: SettingSource }
|
|
131
|
-
| {
|
|
132
|
-
readonly kind: 'no-model'
|
|
133
|
-
readonly backend: ClassifierBackend
|
|
134
|
-
readonly source: SettingSource
|
|
135
|
-
}
|
|
136
|
-
| {
|
|
137
|
-
readonly kind: 'configured'
|
|
138
|
-
readonly backend: ClassifierBackend
|
|
139
|
-
readonly model: string
|
|
140
|
-
/** The source that decided the backend, which decided the model layer runs at all. */
|
|
141
|
-
readonly source: SettingSource
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Combines the two precedence reads into the one answer `classify` and
|
|
146
|
-
* `classifier show` both need: whether the model layer runs, and why.
|
|
147
|
-
*
|
|
148
|
-
* No default model name exists on purpose. The groundwork decision reads: a
|
|
149
|
-
* model name on one machine means nothing on another, so `show` reports the
|
|
150
|
-
* gap and `classify` warns and falls back to the regex layer rather than
|
|
151
|
-
* guessing a name that may not be pulled.
|
|
152
|
-
*/
|
|
153
|
-
export function resolveClassifier(
|
|
154
|
-
root: string,
|
|
155
|
-
flags: ClassifierFlags = {},
|
|
156
|
-
): ClassifierResolution {
|
|
157
|
-
const backend = resolveBackend(root, flags)
|
|
158
|
-
if (backend.value === 'off') return { kind: 'off', source: backend.source }
|
|
159
|
-
|
|
160
|
-
const model = resolveModel(root, flags)
|
|
161
|
-
if (model === undefined) {
|
|
162
|
-
return { kind: 'no-model', backend: backend.value, source: backend.source }
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
kind: 'configured',
|
|
167
|
-
backend: backend.value,
|
|
168
|
-
model: model.value,
|
|
169
|
-
source: backend.source,
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Writes the project's classifier setting, creating `canon/config/` when a
|
|
175
|
-
* project does not carry it yet. `pr-labels.toml` is the one precedent for a
|
|
176
|
-
* `canon/config/` file and it is hand-authored and read-only to the CLI, so
|
|
177
|
-
* this is the first write path into that folder and the first TOML file this
|
|
178
|
-
* CLI generates rather than parses.
|
|
179
|
-
*
|
|
180
|
-
* Preserves an existing file's mode per the operator-file convention, so a
|
|
181
|
-
* destination an operator locked down keeps its permissions across a rewrite.
|
|
182
|
-
*/
|
|
183
|
-
export function writeClassifierConfig(
|
|
184
|
-
root: string,
|
|
185
|
-
backend: ClassifierBackend | 'off',
|
|
186
|
-
model?: string,
|
|
187
|
-
): void {
|
|
188
|
-
const path = join(root, CLASSIFIER_CONFIG_REL)
|
|
189
|
-
const mode = existsSync(path) ? statSync(path).mode : undefined
|
|
190
|
-
|
|
191
|
-
const modelLine = model ? `model = "${model}"\n` : ''
|
|
192
|
-
mkdirSync(dirname(path), { recursive: true })
|
|
193
|
-
writeFileSync(path, `[classifier]\nbackend = "${backend}"\n${modelLine}`)
|
|
194
|
-
|
|
195
|
-
if (mode !== undefined) chmodSync(path, mode)
|
|
196
|
-
}
|