@erclx/canon 5.33.0 → 5.35.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/README.md +3 -5
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/REQUIREMENT.md +2 -3
- package/claude/skills/api-design/SKILL.md +2 -3
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/auto-ship/references/draft-mark.md +1 -1
- package/claude/skills/canon-cli/SKILL.md +10 -11
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
- package/claude/skills/canon-feedback/SKILL.md +10 -28
- package/claude/skills/canon-feedback/references/template.md +53 -0
- package/claude/skills/canvas-board/SKILL.md +4 -1
- package/claude/skills/canvas-board/references/canvas.md +26 -3
- package/claude/skills/code-craft/REQUIREMENT.md +1 -1
- package/claude/skills/code-craft/SKILL.md +0 -1
- package/claude/skills/create-rule/SKILL.md +1 -1
- package/claude/skills/create-rule/references/rule.md +3 -1
- package/claude/skills/create-skill/SKILL.md +2 -1
- package/claude/skills/create-skill/references/skill-paths.md +12 -15
- package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
- package/claude/skills/create-skill/references/skill.md +10 -4
- package/claude/skills/draft-and-pick/SKILL.md +1 -1
- package/claude/skills/git-commit/references/versioning.md +13 -13
- package/claude/skills/git-issue/references/issue.md +4 -1
- package/claude/skills/git-pr/SKILL.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/review-address/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +2 -2
- package/claude/skills/review-craft/references/ui.md +1 -1
- package/claude/skills/review-pr/SKILL.md +3 -3
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/review-ui/REQUIREMENT.md +1 -1
- package/claude/skills/review-ui/SKILL.md +8 -6
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +52 -16
- package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
- package/claude/skills/role-orchestrator/SKILL.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +11 -2
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
- package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
- package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
- package/claude/skills/role-reviewer/SKILL.md +17 -10
- package/claude/skills/role-worker/REQUIREMENT.md +1 -1
- package/claude/skills/role-worker/SKILL.md +6 -6
- package/claude/skills/session-compact/references/handoff-note.md +1 -1
- package/claude/skills/target-setup/SKILL.md +12 -6
- package/claude/skills/target-setup/references/detect.md +22 -5
- package/claude/skills/test-craft/REQUIREMENT.md +2 -3
- package/claude/skills/test-craft/SKILL.md +15 -3
- package/docs/agents/audits.md +1 -1
- package/docs/agents/canvas.md +5 -1
- package/docs/agents/commands.md +3 -5
- package/docs/agents/counts.md +5 -6
- package/docs/agents/drift-surfaces.md +15 -19
- package/docs/agents/gov-detect.md +47 -0
- package/docs/agents/index.md +6 -4
- package/docs/agents/install-and-sync.md +11 -31
- package/docs/agents/markdown-audit.md +10 -12
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/pr-evidence-layout.md +70 -0
- package/docs/agents/pr-evidence.md +8 -9
- package/docs/agents/pr-frames.md +7 -2
- package/docs/agents/pr-lift.md +132 -0
- package/docs/agents/records-stale.md +1 -1
- package/docs/agents/records-validate.md +11 -23
- package/docs/agents/records.md +1 -1
- package/docs/agents/routing.md +1 -1
- package/docs/agents/rule-citations.md +11 -14
- package/docs/agents/scripting.md +4 -21
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/teach.md +1 -1
- package/docs/target/index.md +1 -0
- package/docs/target/migrations.md +4 -0
- package/docs/target/projects.md +11 -11
- package/docs/target/scenarios.md +5 -6
- package/docs/target/standards-moved.md +32 -0
- package/docs/target/sync.md +5 -5
- package/docs/workflow/operating-model.md +16 -18
- package/docs/workflow/skill-map.md +1 -1
- package/governance/detect.toml +197 -0
- package/governance/renames.toml +0 -1
- package/governance/rules/code/000-code.md +1 -13
- package/governance/rules/code/010-testing.md +2 -30
- package/governance/rules/ui/400-ui.md +1 -1
- package/governance/rules/ui/480-canvas.md +13 -0
- package/governance/stacks/base.toml +15 -8
- package/governance/stacks/go.toml +2 -0
- package/governance/stacks/node.toml +1 -1
- package/governance/stacks/php.toml +2 -0
- package/governance/stacks/python.toml +1 -1
- package/package.json +1 -4
- package/scripts/core/install-check.sh +2 -3
- package/scripts/core/regen-hero.ts +0 -6
- package/scripts/lib/ui.sh +0 -9
- package/src/audits/catalog.ts +0 -9
- package/src/canvas/arrange.ts +66 -0
- package/src/canvas/capture.ts +20 -6
- package/src/canvas/content.ts +39 -0
- package/src/canvas/fit.ts +238 -0
- package/src/canvas/tokens.ts +28 -10
- package/src/capture/frame-values.ts +2 -32
- package/src/claude/routing.ts +1 -1
- package/src/claude/skills-reach.ts +4 -6
- package/src/cli/help.ts +3 -4
- package/src/cli.ts +0 -2
- package/src/commands/canvas.ts +179 -1
- package/src/commands/gov.ts +149 -10
- package/src/commands/init.ts +1 -1
- package/src/commands/labels.ts +4 -44
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +376 -1
- package/src/commands/records.ts +4 -20
- package/src/commands/tooling.ts +2 -4
- package/src/counts/catalogs.ts +0 -6
- package/src/feedback/body.ts +26 -5
- package/src/gate/boundaries.ts +2 -2
- package/src/gate/measures.ts +64 -72
- package/src/gate/sequencer.ts +51 -2
- package/src/gate/stages.ts +3 -22
- package/src/gov/citations.ts +11 -43
- package/src/gov/detect.ts +301 -0
- package/src/gov/list.ts +23 -2
- package/src/gov/stacks.ts +9 -0
- package/src/markdown/bans.ts +1 -1
- package/src/migrate/scratch-evidence.ts +2 -2
- package/src/pr/evidence.ts +218 -45
- package/src/pr/frames.ts +23 -3
- package/src/pr/lift.ts +418 -0
- package/src/pr/rest.ts +3 -0
- package/src/pr/review-scope.ts +76 -0
- package/src/records/validate.ts +6 -194
- package/src/sandbox/equivalence.ts +14 -7
- package/src/sandbox/provision.ts +0 -5
- package/src/secrets/shipped.ts +1 -2
- package/src/serve/static.ts +1 -2
- package/src/shipped/references.ts +29 -80
- package/src/sync/check.ts +18 -14
- package/src/tooling/inject.ts +6 -37
- package/src/tooling/read.ts +2 -3
- package/src/tooling/scan.ts +4 -17
- package/src/tooling/subfolder.ts +2 -66
- package/tooling/base/configs/scripts/verify.json +1 -1
- package/tooling/base/configs/scripts/verify.ts +1 -12
- package/tooling/base/manifest.toml +1 -2
- package/tooling/base/reference.md +10 -16
- package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
- package/tooling/base/seeds/.lintstagedrc +1 -2
- package/tooling/base/seeds/canon/context/ci.md +0 -1
- package/tooling/base/seeds/canon/context/development.md +1 -1
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
- package/tooling/go/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +2 -2
- package/tooling/web/configs/.github/workflows/verify.yml +0 -3
- package/tooling/web/manifest.toml +0 -1
- package/tooling/web/reference.md +2 -4
- package/docs/agents/standards-audit.md +0 -40
- package/governance/rules/tooling/765-spelling.md +0 -19
- package/governance/stacks/astro.toml +0 -2
- package/governance/stacks/nextjs.toml +0 -2
- package/governance/stacks/node-server.toml +0 -2
- package/governance/stacks/python-fastapi.toml +0 -2
- package/governance/stacks/react.toml +0 -2
- package/src/commands/standards.ts +0 -278
- package/src/labels/spelling.ts +0 -120
- package/src/standards/audit.ts +0 -146
- package/src/standards/list.ts +0 -33
- package/src/standards/read.ts +0 -177
- package/src/standards/scope.ts +0 -56
- package/standards/index.md +0 -10
- package/tooling/base/configs/.vscode/extensions.json +0 -9
- package/tooling/base/configs/.vscode/settings.json +0 -3
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
- package/tooling/base/seeds/cspell.json +0 -20
- package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
- package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
- package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
- package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
- package/tooling/web/configs/.vscode/extensions.json +0 -13
- package/tooling/web/configs/.vscode/settings.json +0 -10
- package/tooling/web/seeds/.cspell/tech-stack.txt +0 -21
package/src/gate/measures.ts
CHANGED
|
@@ -104,28 +104,6 @@ const info = (text: string): Emission => ({ kind: 'info', text })
|
|
|
104
104
|
const warn = (text: string): Emission => ({ kind: 'warn', text })
|
|
105
105
|
const output = (text: string): Emission => ({ kind: 'output', text })
|
|
106
106
|
|
|
107
|
-
/**
|
|
108
|
-
* Rules no stack reaches, sorted the way `canon gov list` emits them.
|
|
109
|
-
* `260-shadcn` and `320-tanstack-query` are opt-in libraries a project may not
|
|
110
|
-
* want.
|
|
111
|
-
*
|
|
112
|
-
* `130-go`, `140-php`, `335-testing-go`, and `336-testing-php` are opt-in by
|
|
113
|
-
* `--add` until a Go or PHP stack ships, which `target-setup` reaches by
|
|
114
|
-
* matching a detected language against each rule's description.
|
|
115
|
-
*
|
|
116
|
-
* All are recorded here rather than in a config file: the list is what a
|
|
117
|
-
* reader compares a new arrival against, and a config file would absorb the
|
|
118
|
-
* arrival silently.
|
|
119
|
-
*/
|
|
120
|
-
export const GOV_EXPECTED_UNREFERENCED = [
|
|
121
|
-
'130-go',
|
|
122
|
-
'140-php',
|
|
123
|
-
'260-shadcn',
|
|
124
|
-
'320-tanstack-query',
|
|
125
|
-
'335-testing-go',
|
|
126
|
-
'336-testing-php',
|
|
127
|
-
]
|
|
128
|
-
|
|
129
107
|
/**
|
|
130
108
|
* Scenarios declaring no expectation, taken from `canon sandbox coverage`
|
|
131
109
|
* against a clean tree. Raising it is a deliberate edit that says which
|
|
@@ -170,16 +148,19 @@ function parseJson(payload: string): unknown {
|
|
|
170
148
|
}
|
|
171
149
|
|
|
172
150
|
/**
|
|
173
|
-
* A rule no stack
|
|
174
|
-
*
|
|
175
|
-
*
|
|
151
|
+
* A rule no stack reaches arrives at a project only through `canon gov
|
|
152
|
+
* detect`, so each one needs a `governance/detect.toml` entry or nothing ever
|
|
153
|
+
* proposes it. A key naming a retired rule fails too, since it would hide from
|
|
154
|
+
* the rename sweep that keys the file by basename.
|
|
176
155
|
*
|
|
177
|
-
* The
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
156
|
+
* The check replaced a fixed list of expected unreferenced rules. Once the
|
|
157
|
+
* stacks are thin, that list is most of the catalog and would be rewritten on
|
|
158
|
+
* every new rule, which is a list nobody reads.
|
|
159
|
+
*
|
|
160
|
+
* The catalog is parsed here rather than piped through `bun --eval`, whose
|
|
161
|
+
* interpreter exits 0 when its script throws while stdin is a pipe, so a
|
|
162
|
+
* payload that is not JSON would print nothing and read as every rule
|
|
163
|
+
* reached. Parsing in process keeps the two states distinguishable.
|
|
183
164
|
*/
|
|
184
165
|
export const unreferencedRules: Measure = async (ctx) => {
|
|
185
166
|
const { exitCode, stdout } = await ctx.cli(['gov', 'list', '--json'])
|
|
@@ -190,34 +171,67 @@ export const unreferencedRules: Measure = async (ctx) => {
|
|
|
190
171
|
}
|
|
191
172
|
}
|
|
192
173
|
|
|
193
|
-
const record = parseJson(stdout) as
|
|
194
|
-
|
|
174
|
+
const record = parseJson(stdout) as
|
|
175
|
+
| { unreferenced?: unknown; rules?: unknown; staleDetect?: unknown }
|
|
176
|
+
| undefined
|
|
177
|
+
if (
|
|
178
|
+
!Array.isArray(record?.unreferenced) ||
|
|
179
|
+
!Array.isArray(record?.rules) ||
|
|
180
|
+
!Array.isArray(record?.staleDetect)
|
|
181
|
+
) {
|
|
195
182
|
return {
|
|
196
183
|
emissions: [],
|
|
197
184
|
unmeasured:
|
|
198
|
-
'The governance catalog carried no readable unreferenced list.',
|
|
185
|
+
'The governance catalog carried no readable unreferenced, rules, or staleDetect list.',
|
|
199
186
|
}
|
|
200
187
|
}
|
|
201
188
|
|
|
189
|
+
const detected = new Set(
|
|
190
|
+
record.rules
|
|
191
|
+
.filter(
|
|
192
|
+
(rule): rule is { name: string; detect: unknown } =>
|
|
193
|
+
typeof rule === 'object' &&
|
|
194
|
+
rule !== null &&
|
|
195
|
+
typeof (rule as { name?: unknown }).name === 'string',
|
|
196
|
+
)
|
|
197
|
+
.filter((rule) => rule.detect !== null && rule.detect !== undefined)
|
|
198
|
+
.map((rule) => rule.name),
|
|
199
|
+
)
|
|
202
200
|
const unreferenced = record.unreferenced.map(String)
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
const undetected = unreferenced.filter((rule) => !detected.has(rule))
|
|
202
|
+
const stale = record.staleDetect.map(String)
|
|
203
|
+
|
|
204
|
+
const emissions: Emission[] = []
|
|
205
|
+
if (undetected.length > 0) {
|
|
206
|
+
emissions.push(
|
|
207
|
+
warn(
|
|
208
|
+
`Reached by no stack and proposed by nothing: ${undetected.join(' ')}`,
|
|
209
|
+
),
|
|
210
|
+
warn(
|
|
211
|
+
'Name each in a stack, or add a table to governance/detect.toml with the deps or files that mark a project as wanting it.',
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
if (stale.length > 0) {
|
|
216
|
+
emissions.push(
|
|
217
|
+
warn(`governance/detect.toml names no live rule: ${stale.join(' ')}`),
|
|
218
|
+
warn('Rename the key to the rule it became, or delete the table.'),
|
|
219
|
+
)
|
|
205
220
|
}
|
|
206
221
|
|
|
207
|
-
|
|
208
|
-
if (listed === GOV_EXPECTED_UNREFERENCED.join(' ')) {
|
|
222
|
+
if (emissions.length > 0) {
|
|
209
223
|
return {
|
|
210
|
-
emissions
|
|
211
|
-
|
|
212
|
-
],
|
|
224
|
+
emissions,
|
|
225
|
+
failure: 'An opt-in rule has no route to a project',
|
|
213
226
|
}
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
return {
|
|
217
230
|
emissions: [
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
info(
|
|
232
|
+
unreferenced.length === 0
|
|
233
|
+
? 'Every rule is reached by a stack'
|
|
234
|
+
: `${plural(unreferenced.length, 'rule')} reached by no stack, each proposed by governance/detect.toml`,
|
|
221
235
|
),
|
|
222
236
|
],
|
|
223
237
|
}
|
|
@@ -491,7 +505,7 @@ export const documentCeiling: Measure = async (ctx) => {
|
|
|
491
505
|
* Whether a path's stem, its basename up to the first `.`, repeats its
|
|
492
506
|
* immediate parent folder, either whole or as a `<folder>-` prefix.
|
|
493
507
|
*
|
|
494
|
-
* Exact rather than singular-aware, so `
|
|
508
|
+
* Exact rather than singular-aware, so `skills/skill.md` passes. A false
|
|
495
509
|
* positive on a gate stage teaches a contributor to route around it, and a
|
|
496
510
|
* widened match is cheap to add once a real echo slips through.
|
|
497
511
|
*/
|
|
@@ -757,28 +771,6 @@ export function seedEntryCount(payload: string): number {
|
|
|
757
771
|
)
|
|
758
772
|
}
|
|
759
773
|
|
|
760
|
-
/**
|
|
761
|
-
* Scoped to arrival rather than the corpus, since `standards/standard.md`
|
|
762
|
-
* forbids writing a criterion into an existing standard outside the change that
|
|
763
|
-
* exercises it. Gating the known gaps would fail every push until someone
|
|
764
|
-
* closed them all, which is the sweep that rule exists to prevent.
|
|
765
|
-
*/
|
|
766
|
-
export const standardCriteria: Measure = async (ctx) => {
|
|
767
|
-
const run = await ctx.cli(['standards', 'audit', '--arrivals-only'])
|
|
768
|
-
|
|
769
|
-
if (run.exitCode === 0) {
|
|
770
|
-
return { emissions: [info('Arriving standards carry a success criterion')] }
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
return {
|
|
774
|
-
emissions: [output(run.all)],
|
|
775
|
-
failure:
|
|
776
|
-
run.exitCode === 2
|
|
777
|
-
? 'A standard new to this branch carries no success-criterion section. Run bun src/cli.ts standards audit.'
|
|
778
|
-
: 'canon standards audit could not read which standards arrived on this branch. Run bun src/cli.ts standards audit --json to see why.',
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
774
|
/**
|
|
783
775
|
* Every file in the gated corpus, walked from `ctx.root` rather than from the
|
|
784
776
|
* changed set.
|
|
@@ -790,7 +782,7 @@ export const standardCriteria: Measure = async (ctx) => {
|
|
|
790
782
|
* nothing it would notice.
|
|
791
783
|
*
|
|
792
784
|
* `dot` is what reaches the seeds, which is half of what `tooling/` ships. Every
|
|
793
|
-
* seeded `.claude/` tree, `.
|
|
785
|
+
* seeded `.claude/` tree, `.github/` workflow, and `.husky/` hook sits behind a
|
|
794
786
|
* dotted segment, so the default scan walks `tooling/` and returns none of the
|
|
795
787
|
* files a scaffolded project actually receives.
|
|
796
788
|
*/
|
|
@@ -827,8 +819,8 @@ function describeShippedReference(reference: ShippedReference): string {
|
|
|
827
819
|
return 'a pull request number that resolves elsewhere for a reader in a target'
|
|
828
820
|
case 'docs-path':
|
|
829
821
|
return 'a path into this repository that a registry install never carries'
|
|
830
|
-
case '
|
|
831
|
-
return 'a
|
|
822
|
+
case 'retired-verb':
|
|
823
|
+
return 'a call to canon standards, a verb no installed CLI carries'
|
|
832
824
|
case 'phase-label':
|
|
833
825
|
return 'a phase label that names a board no target holds'
|
|
834
826
|
case 'rule-path':
|
|
@@ -889,8 +881,8 @@ export const shippedReferences: Measure = async (ctx) => {
|
|
|
889
881
|
),
|
|
890
882
|
failure:
|
|
891
883
|
found.length === 1
|
|
892
|
-
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>,
|
|
893
|
-
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>,
|
|
884
|
+
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, cite the owner skill's reference in place of a canon standards call, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning canon/context/ entry, state the fact a shipped skill body's cited rule enforces instead of its path, or mark the line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`
|
|
885
|
+
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, cite the owner skill's reference in place of a canon standards call, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning canon/context/ entry, state the fact a shipped skill body's cited rule enforces instead of its path, or mark each line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`,
|
|
894
886
|
}
|
|
895
887
|
}
|
|
896
888
|
|
package/src/gate/sequencer.ts
CHANGED
|
@@ -50,6 +50,45 @@ export interface LoadReading {
|
|
|
50
50
|
/** The 1-minute load average. */
|
|
51
51
|
readonly loadAverage: number
|
|
52
52
|
readonly cores: number
|
|
53
|
+
/**
|
|
54
|
+
* The `some avg10` stall share per resource from `/proc/pressure`, which the
|
|
55
|
+
* load average folds into one number. Absent where the kernel has no PSI.
|
|
56
|
+
*/
|
|
57
|
+
readonly pressure?: PressureReading
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PressureReading {
|
|
61
|
+
readonly cpu: number
|
|
62
|
+
readonly memory: number
|
|
63
|
+
readonly io: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reads the `some avg10` percentage from one `/proc/pressure/<resource>` file.
|
|
68
|
+
* A file that does not parse answers `undefined`, which drops the clause.
|
|
69
|
+
*/
|
|
70
|
+
export function parsePressure(text: string): number | undefined {
|
|
71
|
+
const match = /^some\b.*\bavg10=(\d+(?:\.\d+)?)\b/m.exec(text)
|
|
72
|
+
if (match === null) return undefined
|
|
73
|
+
const value = Number.parseFloat(match[1])
|
|
74
|
+
return Number.isFinite(value) ? value : undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readPressure(): PressureReading | undefined {
|
|
78
|
+
try {
|
|
79
|
+
const read = (name: string) =>
|
|
80
|
+
parsePressure(readFileSync(`/proc/pressure/${name}`, 'utf8'))
|
|
81
|
+
const cpu = read('cpu')
|
|
82
|
+
const memory = read('memory')
|
|
83
|
+
const io = read('io')
|
|
84
|
+
if (cpu === undefined || memory === undefined || io === undefined) {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
return { cpu, memory, io }
|
|
88
|
+
} catch {
|
|
89
|
+
// PSI off or absent: the files may exist and still fail with EOPNOTSUPP.
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
53
92
|
}
|
|
54
93
|
|
|
55
94
|
/**
|
|
@@ -62,7 +101,12 @@ export function readProcLoad(): LoadReading | undefined {
|
|
|
62
101
|
const first = readFileSync('/proc/loadavg', 'utf8').split(' ')[0]
|
|
63
102
|
const loadAverage = Number.parseFloat(first)
|
|
64
103
|
if (!Number.isFinite(loadAverage)) return undefined
|
|
65
|
-
|
|
104
|
+
const pressure = readPressure()
|
|
105
|
+
return {
|
|
106
|
+
loadAverage,
|
|
107
|
+
cores: availableParallelism(),
|
|
108
|
+
...(pressure === undefined ? {} : { pressure }),
|
|
109
|
+
}
|
|
66
110
|
} catch {
|
|
67
111
|
return undefined
|
|
68
112
|
}
|
|
@@ -252,7 +296,12 @@ function withLoadLine(
|
|
|
252
296
|
const reading = (ctx.readLoad ?? readProcLoad)()
|
|
253
297
|
if (reading === undefined) return outcome
|
|
254
298
|
|
|
255
|
-
const
|
|
299
|
+
const { pressure } = reading
|
|
300
|
+
const stalls =
|
|
301
|
+
pressure === undefined
|
|
302
|
+
? ''
|
|
303
|
+
: `, pressure cpu ${pressure.cpu.toFixed(1)}% memory ${pressure.memory.toFixed(1)}% io ${pressure.io.toFixed(1)}% over 10s,`
|
|
304
|
+
const text = `Load average ${reading.loadAverage} on ${reading.cores} cores${stalls} when the ${stage.label} stage failed`
|
|
256
305
|
return {
|
|
257
306
|
...outcome,
|
|
258
307
|
emissions: [...outcome.emissions, { kind: 'info', text }],
|
package/src/gate/stages.ts
CHANGED
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
skillFamily,
|
|
26
26
|
skillPracticeShape,
|
|
27
27
|
skillProvenance,
|
|
28
|
-
standardCriteria,
|
|
29
28
|
unreferencedRules,
|
|
30
29
|
visualPathGlobs,
|
|
31
30
|
} from '@/gate/measures'
|
|
@@ -328,10 +327,9 @@ export const STAGES: readonly Stage[] = [
|
|
|
328
327
|
success: 'Capability seeding clean',
|
|
329
328
|
},
|
|
330
329
|
{
|
|
331
|
-
// A stack
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
// which `canon gov install` never reaches on its own.
|
|
330
|
+
// A rule no stack names reaches a target only when `canon gov detect`
|
|
331
|
+
// proposes it, so every such rule needs a governance/detect.toml entry,
|
|
332
|
+
// and every key there has to name a live rule.
|
|
335
333
|
id: 'unreferenced-rules',
|
|
336
334
|
label: 'Unreferenced rules',
|
|
337
335
|
checks: [{ kind: 'measure', measure: unreferencedRules }],
|
|
@@ -503,11 +501,6 @@ export const STAGES: readonly Stage[] = [
|
|
|
503
501
|
label: 'Skill family',
|
|
504
502
|
checks: [{ kind: 'measure', measure: skillFamily }],
|
|
505
503
|
},
|
|
506
|
-
{
|
|
507
|
-
id: 'standard-criteria',
|
|
508
|
-
label: 'Standard success criteria',
|
|
509
|
-
checks: [{ kind: 'measure', measure: standardCriteria }],
|
|
510
|
-
},
|
|
511
504
|
{
|
|
512
505
|
id: 'sandbox-coverage',
|
|
513
506
|
label: 'Sandbox coverage',
|
|
@@ -523,18 +516,6 @@ export const STAGES: readonly Stage[] = [
|
|
|
523
516
|
label: 'Plugin manifests',
|
|
524
517
|
checks: [{ kind: 'measure', measure: pluginManifests }],
|
|
525
518
|
},
|
|
526
|
-
{
|
|
527
|
-
id: 'spelling',
|
|
528
|
-
label: 'Spelling',
|
|
529
|
-
checks: [
|
|
530
|
-
{
|
|
531
|
-
kind: 'command',
|
|
532
|
-
argv: ['bun', 'run', 'check:spell'],
|
|
533
|
-
failure: 'Spell check failed',
|
|
534
|
-
},
|
|
535
|
-
],
|
|
536
|
-
success: 'Spell check passed',
|
|
537
|
-
},
|
|
538
519
|
{
|
|
539
520
|
id: 'shell',
|
|
540
521
|
label: 'Shell',
|
package/src/gov/citations.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { isMarked } from '@/sweep/exempt-marker'
|
|
|
6
6
|
import { gitEnv } from '@/git/env'
|
|
7
7
|
import { listRuleFiles } from '@/gov/payload'
|
|
8
8
|
import { parseFrontmatter } from '@/indexes/frontmatter'
|
|
9
|
-
import { movedStandardPath } from '@/standards/read'
|
|
10
9
|
import { findBacktickSpans } from '@/markdown/backticks'
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -42,14 +41,14 @@ export const RULE_DIRS: readonly string[] = [
|
|
|
42
41
|
export const GLOB_CORPUS = join('internal', 'rules')
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
|
-
* How the citation was written, kept on the finding because the
|
|
44
|
+
* How the citation was written, kept on the finding because the two resolve
|
|
46
45
|
* against different roots and a reader repairing one needs to know which.
|
|
47
46
|
*
|
|
48
|
-
* `
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* `path` is a backticked repository path. `sibling` is a rule naming another
|
|
48
|
+
* rule by filename alone, which resolves inside the folder the citing rule
|
|
49
|
+
* sits in.
|
|
51
50
|
*/
|
|
52
|
-
export type CitationForm = 'path' | '
|
|
51
|
+
export type CitationForm = 'path' | 'sibling'
|
|
53
52
|
|
|
54
53
|
/**
|
|
55
54
|
* What the sweep decided about one citation.
|
|
@@ -116,13 +115,6 @@ const FENCE = /^\s*(?:```|~~~)/
|
|
|
116
115
|
|
|
117
116
|
const FRONTMATTER_DELIMITER = /^---\s*$/
|
|
118
117
|
|
|
119
|
-
/**
|
|
120
|
-
* The verb form, with the name captured. A leading letter or digit is required,
|
|
121
|
-
* which is what leaves `canon standards <name>` unmatched: that line teaches the
|
|
122
|
-
* form rather than citing a standard, and it is the only one in either corpus.
|
|
123
|
-
*/
|
|
124
|
-
const STANDARD_CALL = /canon standards ([A-Za-z0-9][A-Za-z0-9._-]*)/g
|
|
125
|
-
|
|
126
118
|
/** A rule filename, which is how a rule names a sibling with no folder around it. */
|
|
127
119
|
const SIBLING_RULE = /^\d{3}-[a-z0-9-]+\.md$/
|
|
128
120
|
|
|
@@ -146,8 +138,8 @@ function isNotRepositoryPath(span: string): boolean {
|
|
|
146
138
|
* This is what separates a citation from the folder and module conventions the
|
|
147
139
|
* corpus is full of. `next/font`, `try/except`, `react-hooks/set-state-in-effect`,
|
|
148
140
|
* and `oven-sh/setup-bun@v2` all carry a slash and name no file, and a trailing
|
|
149
|
-
* slash is a folder rather than a document. The cost is that
|
|
150
|
-
*
|
|
141
|
+
* slash is a folder rather than a document. The cost is that a real folder path
|
|
142
|
+
* goes unchecked, which is the bound the report states.
|
|
151
143
|
*/
|
|
152
144
|
function namesAFile(span: string): boolean {
|
|
153
145
|
if (span.endsWith('/')) return false
|
|
@@ -160,10 +152,10 @@ function namesAFile(span: string): boolean {
|
|
|
160
152
|
* sweep can resolve.
|
|
161
153
|
*
|
|
162
154
|
* The whole span is classified rather than a trailing pattern inside it.
|
|
163
|
-
* Extracting
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
155
|
+
* Extracting a trailing `<folder>/<name>.md` out of a longer path and resolving
|
|
156
|
+
* that against another root manufactures a dead citation out of a file that
|
|
157
|
+
* exists, which a session measuring this corpus did before the check was
|
|
158
|
+
* written.
|
|
167
159
|
*/
|
|
168
160
|
export function classifySpan(span: string): CitationForm | undefined {
|
|
169
161
|
if (isNotRepositoryPath(span)) return undefined
|
|
@@ -176,15 +168,6 @@ export function classifySpan(span: string): CitationForm | undefined {
|
|
|
176
168
|
* Where a citation could answer, which is wherever the reader's own tools look
|
|
177
169
|
* and nowhere else.
|
|
178
170
|
*
|
|
179
|
-
* A standard name takes the authoring root alone, matching `standardRoots` in
|
|
180
|
-
* `@/standards/read`, which reads `standards/` at the working root and then the
|
|
181
|
-
* package corpus, plus the skill reference a retired name moved to. This verb
|
|
182
|
-
* refuses a tree holding no rule corpus, so it runs only where those two roots
|
|
183
|
-
* are one directory. `internal/standards/` is
|
|
184
|
-
* deliberately absent: `canon standards <name>` never reaches it, so admitting it
|
|
185
|
-
* here would pass a citation that refuses for the session opening it, which is a
|
|
186
|
-
* gate failing open.
|
|
187
|
-
*
|
|
188
171
|
* A sibling resolves inside the folder the citing rule sits in, since that is
|
|
189
172
|
* the only place a bare rule filename means anything.
|
|
190
173
|
*/
|
|
@@ -193,11 +176,6 @@ function candidatesFor(
|
|
|
193
176
|
cited: string,
|
|
194
177
|
ruleFile: string,
|
|
195
178
|
): string[] {
|
|
196
|
-
if (form === 'standard') {
|
|
197
|
-
const moved = movedStandardPath(cited)
|
|
198
|
-
|
|
199
|
-
return [join('standards', `${cited}.md`), ...(moved ? [moved] : [])]
|
|
200
|
-
}
|
|
201
179
|
if (form === 'sibling') return [join(dirname(ruleFile), cited)]
|
|
202
180
|
return [cited]
|
|
203
181
|
}
|
|
@@ -244,16 +222,6 @@ export function collectCitations(text: string): RawCitation[] {
|
|
|
244
222
|
: trimmed
|
|
245
223
|
const marked = isMarked(lines, index, CITATION_MARKER)
|
|
246
224
|
|
|
247
|
-
for (const match of line.matchAll(STANDARD_CALL)) {
|
|
248
|
-
found.push({
|
|
249
|
-
line: index + 1,
|
|
250
|
-
form: 'standard',
|
|
251
|
-
cited: match[1] ?? '',
|
|
252
|
-
preview,
|
|
253
|
-
marked,
|
|
254
|
-
})
|
|
255
|
-
}
|
|
256
|
-
|
|
257
225
|
// A backticked span is the only carrier a rule writes a citation in. No
|
|
258
226
|
// rule in either corpus uses a markdown link, and matching running prose
|
|
259
227
|
// would report every sentence that happens to name a file.
|