@erclx/canon 4.23.0 → 4.23.1
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/claude/.claude-plugin/plugin.json +1 -1
- package/docs/agents/commands.md +1 -1
- package/package.json +1 -1
- package/src/commands/standards.ts +6 -6
- package/src/gate/measures.ts +1 -1
- package/src/git-base.ts +19 -0
- package/src/git-files.ts +4 -15
- package/src/gov/test-order.ts +10 -21
package/docs/agents/commands.md
CHANGED
|
@@ -56,7 +56,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
|
|
|
56
56
|
| `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift |
|
|
57
57
|
| `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, and report the structural checkpoints |
|
|
58
58
|
| `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
|
|
59
|
-
| `canon standards audit` | Report the corpus against the
|
|
59
|
+
| `canon standards audit` | Report the corpus against the success-criterion gate `standards/standard.md` states, failing only on a standard new to the branch (`--json`, `--arrivals-only`) |
|
|
60
60
|
| `canon claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD`, and the installed version against the newest published (`--json`) |
|
|
61
61
|
| `canon claude skills reach` | Report the bodies in either skill corpus citing a toolkit path no target project receives, exiting 2 on an unqualified one |
|
|
62
62
|
| `canon claude skills rank` | Score either skill corpus's descriptions against a case corpus by TF-IDF cosine similarity, reporting rank-one and top-three (`--cases <path>`) |
|
package/package.json
CHANGED
|
@@ -62,7 +62,7 @@ export function register(program: Command): void {
|
|
|
62
62
|
standards
|
|
63
63
|
.command('audit')
|
|
64
64
|
.description(
|
|
65
|
-
'Report the corpus against the
|
|
65
|
+
'Report the corpus against the success-criterion gate standards/standard.md states',
|
|
66
66
|
)
|
|
67
67
|
.argument('[path]', 'Project root, defaulting to the current directory')
|
|
68
68
|
.helpOption('-h, --help', 'Show this help message')
|
|
@@ -78,7 +78,7 @@ export function register(program: Command): void {
|
|
|
78
78
|
'Exit codes:',
|
|
79
79
|
' 0 the audit completed with every arriving standard carrying the section',
|
|
80
80
|
' 1 refused, with the reason on stderr',
|
|
81
|
-
' 2 a standard new to this branch carries no
|
|
81
|
+
' 2 a standard new to this branch carries no success-criterion section',
|
|
82
82
|
'',
|
|
83
83
|
'A standard already in the corpus without the section is a known gap',
|
|
84
84
|
'standards/standard.md names, not a violation, so only an arrival fails.',
|
|
@@ -204,8 +204,8 @@ function reportArrivalGate(
|
|
|
204
204
|
intro('canon standards audit')
|
|
205
205
|
logError(
|
|
206
206
|
missing.length === 1
|
|
207
|
-
? '1 standard new to this branch carries no
|
|
208
|
-
: `${missing.length} standards new to this branch carry no
|
|
207
|
+
? '1 standard new to this branch carries no success-criterion section'
|
|
208
|
+
: `${missing.length} standards new to this branch carry no success-criterion section`,
|
|
209
209
|
)
|
|
210
210
|
pipeOutput(missing.join('\n'))
|
|
211
211
|
outro()
|
|
@@ -217,7 +217,7 @@ function reportCorpus(
|
|
|
217
217
|
logStep('Corpus')
|
|
218
218
|
logInfo(`${plural(audit.standards.length, 'standard')} at standards/`)
|
|
219
219
|
logInfo(
|
|
220
|
-
`${plural(audit.withCriterion.length, 'standard')} carrying
|
|
220
|
+
`${plural(audit.withCriterion.length, 'standard')} carrying a success criterion`,
|
|
221
221
|
)
|
|
222
222
|
|
|
223
223
|
logStep('Known gaps')
|
|
@@ -241,7 +241,7 @@ function reportCorpus(
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
logError(
|
|
244
|
-
`${plural(audit.arrivalsWithoutCriterion.length, 'standard')} arrived carrying no
|
|
244
|
+
`${plural(audit.arrivalsWithoutCriterion.length, 'standard')} arrived carrying no success-criterion section`,
|
|
245
245
|
)
|
|
246
246
|
pipeOutput(audit.arrivalsWithoutCriterion.join('\n'))
|
|
247
247
|
}
|
package/src/gate/measures.ts
CHANGED
|
@@ -435,7 +435,7 @@ export const standardCriteria: Measure = async (ctx) => {
|
|
|
435
435
|
emissions: [output(run.all)],
|
|
436
436
|
failure:
|
|
437
437
|
run.exitCode === 2
|
|
438
|
-
? 'A standard new to this branch carries no
|
|
438
|
+
? 'A standard new to this branch carries no success-criterion section. Run bun src/cli.ts standards audit.'
|
|
439
439
|
: 'canon standards audit could not read which standards arrived on this branch. Run bun src/cli.ts standards audit --json to see why.',
|
|
440
440
|
}
|
|
441
441
|
}
|
package/src/git-base.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preferred first, matching `src/tasks/trunk.ts`. A local `main` trailing the
|
|
3
|
+
* remote pulls other people's merged commits into the range, so a check
|
|
4
|
+
* reading it decides against files the branch never touched.
|
|
5
|
+
*/
|
|
6
|
+
export const TRUNK_REFS = ['origin/main', 'main'] as const
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The refs to try, in order, for the far side of a branch range: a named ref
|
|
10
|
+
* alone, or the trunk list when none was named.
|
|
11
|
+
*/
|
|
12
|
+
export function baseCandidates(ref: string | undefined): readonly string[] {
|
|
13
|
+
return ref !== undefined ? [ref] : TRUNK_REFS
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Whether a `merge-base` result names a commit rather than an empty read. */
|
|
17
|
+
export function isMergeBase(value: string | undefined): value is string {
|
|
18
|
+
return value !== undefined && value !== ''
|
|
19
|
+
}
|
package/src/git-files.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
|
+
import { baseCandidates, isMergeBase } from '@/git-base'
|
|
2
3
|
import { gitEnv } from '@/git-env'
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* Preferred first, matching `src/gov/test-order.ts` and `src/tasks/trunk.ts`. A
|
|
6
|
-
* local `main` trailing the remote pulls other people's merged commits into the
|
|
7
|
-
* range, so a check reading it decides against files the branch never touched.
|
|
8
|
-
*/
|
|
9
|
-
const TRUNK_REFS = ['origin/main', 'main'] as const
|
|
10
|
-
|
|
11
5
|
/** Runs git under `root` with the resolution variables a hook exports stripped. */
|
|
12
6
|
async function git(
|
|
13
7
|
root: string,
|
|
@@ -37,14 +31,9 @@ export async function resolveBaseRef(
|
|
|
37
31
|
root: string,
|
|
38
32
|
ref?: string,
|
|
39
33
|
): Promise<string | undefined> {
|
|
40
|
-
|
|
41
|
-
const merged = await git(root, ['merge-base', 'HEAD',
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const trunk of TRUNK_REFS) {
|
|
46
|
-
const merged = await git(root, ['merge-base', 'HEAD', trunk])
|
|
47
|
-
if (merged !== undefined && merged !== '') return merged
|
|
34
|
+
for (const candidate of baseCandidates(ref)) {
|
|
35
|
+
const merged = await git(root, ['merge-base', 'HEAD', candidate])
|
|
36
|
+
if (isMergeBase(merged)) return merged
|
|
48
37
|
}
|
|
49
38
|
|
|
50
39
|
return undefined
|
package/src/gov/test-order.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { execaSync } from 'execa'
|
|
2
|
+
import { baseCandidates, isMergeBase } from '@/git-base'
|
|
2
3
|
import { gitEnv } from '@/git-env'
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* Preferred first, matching `src/tasks/trunk.ts`. A clone with no remote still
|
|
6
|
-
* answers off its local trunk, and a local `main` trailing the remote widens
|
|
7
|
-
* the range rather than narrowing it, which over-reports instead of hiding.
|
|
8
|
-
*/
|
|
9
|
-
const TRUNK_REFS = ['origin/main', 'main'] as const
|
|
10
|
-
|
|
11
5
|
/**
|
|
12
6
|
* The extensions this check can pair. A test sits beside its subject under one
|
|
13
7
|
* name throughout this corpus, so the weakest assumption available is also the
|
|
@@ -349,22 +343,17 @@ function resolveBase(
|
|
|
349
343
|
ref: string | undefined,
|
|
350
344
|
head: string,
|
|
351
345
|
): string | { kind: 'unreadable'; reason: TestOrderRefusal; message: string } {
|
|
352
|
-
|
|
353
|
-
const merged = git(root, ['merge-base', head,
|
|
354
|
-
if (merged
|
|
355
|
-
return {
|
|
356
|
-
kind: 'unreadable',
|
|
357
|
-
reason: 'bad-base',
|
|
358
|
-
message: `Ref ${ref} shares no history with HEAD in ${root}. Pass a ref this branch was taken from.`,
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return merged
|
|
346
|
+
for (const candidate of baseCandidates(ref)) {
|
|
347
|
+
const merged = git(root, ['merge-base', head, candidate])
|
|
348
|
+
if (isMergeBase(merged)) return merged
|
|
362
349
|
}
|
|
363
350
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
351
|
+
if (ref !== undefined) {
|
|
352
|
+
return {
|
|
353
|
+
kind: 'unreadable',
|
|
354
|
+
reason: 'bad-base',
|
|
355
|
+
message: `Ref ${ref} shares no history with HEAD in ${root}. Pass a ref this branch was taken from.`,
|
|
356
|
+
}
|
|
368
357
|
}
|
|
369
358
|
|
|
370
359
|
const rootCommits = git(root, ['rev-list', '--max-parents=0', head])
|