@erclx/canon 5.42.0 → 5.44.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +3 -3
- package/claude/skills/backlog-triage/references/apply.md +6 -6
- package/claude/skills/canon-cli/SKILL.md +4 -4
- package/claude/skills/canvas-board/REQUIREMENT.md +1 -1
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/ci-workflow/REQUIREMENT.md +2 -1
- package/claude/skills/ci-workflow/SKILL.md +12 -2
- package/claude/skills/ci-workflow/references/workflows.md +12 -9
- package/claude/skills/context-fold/references/context.md +1 -1
- package/claude/skills/create-rule/SKILL.md +1 -0
- package/claude/skills/create-skill/REQUIREMENT.md +1 -1
- package/claude/skills/create-skill/SKILL.md +2 -2
- package/claude/skills/create-skill/references/skill-requirement.md +68 -9
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/design-identity/REQUIREMENT.md +40 -0
- package/claude/skills/design-identity/SKILL.md +138 -0
- package/claude/skills/{draft-identity → design-identity}/references/card-route.md +2 -2
- package/claude/skills/design-taste/REQUIREMENT.md +4 -4
- package/claude/skills/design-taste/SKILL.md +2 -2
- package/claude/skills/design-taste/references/kinds.md +1 -1
- package/claude/skills/design-taste/references/preflight.md +1 -1
- package/claude/skills/document-health/references/axes.md +2 -1
- package/claude/skills/draft-and-pick/REQUIREMENT.md +8 -69
- package/claude/skills/draft-and-pick/SKILL.md +3 -153
- package/claude/skills/draft-doc/REQUIREMENT.md +9 -30
- package/claude/skills/draft-doc/SKILL.md +4 -76
- package/claude/skills/draft-figure/REQUIREMENT.md +8 -42
- package/claude/skills/draft-figure/SKILL.md +3 -77
- package/claude/skills/draft-identity/REQUIREMENT.md +8 -23
- package/claude/skills/draft-identity/SKILL.md +3 -130
- package/claude/skills/draft-ready/REQUIREMENT.md +8 -37
- package/claude/skills/draft-ready/SKILL.md +3 -128
- package/claude/skills/draft-screencast/REQUIREMENT.md +8 -23
- package/claude/skills/draft-screencast/SKILL.md +3 -62
- package/claude/skills/draft-slides/REQUIREMENT.md +8 -27
- package/claude/skills/draft-slides/SKILL.md +3 -72
- package/claude/skills/draw-figure/REQUIREMENT.md +59 -0
- package/claude/skills/draw-figure/SKILL.md +85 -0
- package/claude/skills/{draft-figure → draw-figure}/references/architecture-set.md +2 -2
- package/claude/skills/{draft-figure → draw-figure}/references/hand-drawn.md +2 -2
- package/claude/skills/{draft-figure → draw-figure}/references/mermaid-path.md +2 -2
- package/claude/skills/{draft-figure → draw-figure}/references/mermaid.md +1 -1
- package/claude/skills/git-commit/references/commit.md +1 -5
- package/claude/skills/git-pr/references/pr.md +1 -0
- package/claude/skills/make-deck/REQUIREMENT.md +44 -0
- package/claude/skills/make-deck/SKILL.md +80 -0
- package/claude/skills/markdown-craft/REQUIREMENT.md +1 -1
- package/claude/skills/markdown-craft/SKILL.md +2 -2
- package/claude/skills/pick-by-eye/REQUIREMENT.md +86 -0
- package/claude/skills/pick-by-eye/SKILL.md +161 -0
- package/claude/skills/plan-feature/SKILL.md +3 -2
- package/claude/skills/plan-feature/references/plan.md +25 -11
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/plan-intake/SKILL.md +1 -1
- package/claude/skills/plan-ready/REQUIREMENT.md +54 -0
- package/claude/skills/plan-ready/SKILL.md +136 -0
- package/claude/skills/{draft-ready → plan-ready}/references/assembly.md +1 -1
- package/claude/skills/{draft-ready → plan-ready}/references/ready.md +1 -1
- package/claude/skills/pr-evidence/references/evidence.md +1 -1
- package/claude/skills/record-screencast/REQUIREMENT.md +3 -3
- package/claude/skills/record-screencast/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/REQUIREMENT.md +1 -1
- package/claude/skills/role-orchestrator/SKILL.md +2 -0
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +5 -2
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -0
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +18 -13
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +5 -4
- package/claude/skills/role-worker/REQUIREMENT.md +2 -0
- package/claude/skills/role-worker/SKILL.md +35 -2
- package/claude/skills/script-screencast/REQUIREMENT.md +40 -0
- package/claude/skills/script-screencast/SKILL.md +70 -0
- package/claude/skills/{draft-screencast → script-screencast}/references/beats-template.md +2 -2
- package/claude/skills/search-craft/REQUIREMENT.md +4 -4
- package/claude/skills/search-craft/SKILL.md +2 -2
- package/claude/skills/target-setup/SKILL.md +1 -0
- package/claude/skills/task-board/SKILL.md +11 -9
- package/claude/skills/task-board/references/board.md +15 -12
- package/claude/skills/teach-workspace/SKILL.md +1 -1
- package/claude/skills/teach-workspace/references/lesson-craft.md +2 -2
- package/claude/skills/ui-checklist/SKILL.md +2 -0
- package/claude/skills/write-doc/REQUIREMENT.md +46 -0
- package/claude/skills/write-doc/SKILL.md +83 -0
- package/claude/skills/{draft-doc → write-doc}/references/context.md +2 -2
- package/claude/skills/{draft-doc → write-doc}/references/docs.md +2 -2
- package/claude/skills/{draft-doc → write-doc}/references/readme.md +2 -2
- package/claude/skills/{draft-doc → write-doc}/references/wiki-kind.md +4 -4
- package/claude/skills/{draft-doc → write-doc}/references/wiki.md +1 -1
- package/docs/agents/audits.md +1 -0
- package/docs/agents/markdown-audit.md +4 -2
- package/docs/agents/migrate.md +13 -10
- package/docs/target/migrations.md +28 -4
- package/docs/target/projects.md +1 -1
- package/docs/workflow/parallel-features.md +3 -2
- package/docs/workflow/skill-map-on-request.md +18 -12
- package/docs/workflow/skill-map.md +13 -12
- package/docs/workflow/visual-design-workflow.md +2 -2
- package/governance/detect.toml +4 -0
- package/governance/ledger/framework/astro.md +73 -0
- package/governance/ledger/framework/fastapi.md +76 -0
- package/governance/ledger/framework/nextjs.md +86 -0
- package/governance/ledger/framework/react.md +83 -0
- package/governance/ledger/framework/shadcn.md +60 -0
- package/governance/ledger/framework/tailwind.md +55 -0
- package/governance/rules/canon/context.md +2 -1
- package/governance/rules/canon/plan.md +3 -5
- package/governance/rules/canon/scratch.md +3 -1
- package/governance/rules/canon/task-scope.md +2 -1
- package/governance/rules/canon/tasks.md +2 -1
- package/governance/rules/canon/worktrees.md +5 -2
- package/governance/rules/claude/output.md +7 -4
- package/governance/rules/claude/skill.md +6 -4
- package/governance/rules/framework/astro.md +12 -14
- package/governance/rules/framework/fastapi.md +20 -24
- package/governance/rules/framework/nextjs.md +19 -27
- package/governance/rules/framework/react.md +22 -33
- package/governance/rules/framework/shadcn.md +11 -15
- package/governance/rules/framework/tailwind.md +12 -14
- package/governance/rules/tooling/ci-workflow.md +6 -39
- package/governance/rules/ui/design-reference.md +37 -0
- package/governance/rules/writing/markdown.md +3 -2
- package/package.json +6 -6
- package/src/audits/catalog.ts +42 -0
- package/src/claude/cases/authoring.ts +15 -3
- package/src/claude/cases/misc.ts +12 -0
- package/src/claude/cases/workflow.ts +19 -1
- package/src/claude/settings.ts +8 -4
- package/src/claude/skills-audit.ts +113 -1
- package/src/commands/claude.ts +45 -12
- package/src/commands/demo.ts +1 -1
- package/src/commands/gov.ts +198 -0
- package/src/commands/markdown.ts +1 -1
- package/src/commands/migrate.ts +125 -2
- package/src/commands/pr.ts +34 -83
- package/src/commands/tasks.ts +383 -2
- package/src/commands/tooling.ts +3 -3
- package/src/commands/worktrees.ts +49 -0
- package/src/context/audit.ts +1 -1
- package/src/demo/beats.ts +1 -1
- package/src/gate/stages.ts +15 -0
- package/src/gov/audit.ts +441 -0
- package/src/listeners/belongs.ts +21 -0
- package/src/listeners/machine.ts +93 -0
- package/src/listeners/parse.ts +41 -0
- package/src/markdown/scope.ts +45 -35
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/session-defaults.ts +138 -0
- package/src/migrate/skill-names.ts +68 -21
- package/src/pr/frames.ts +14 -3
- package/src/pr/local.ts +4 -69
- package/src/records/validate.ts +31 -0
- package/src/sync/check.ts +15 -0
- package/src/tasks/board.ts +854 -0
- package/src/tasks/record.ts +20 -1
- package/src/tasks/validate.ts +206 -9
- package/src/tooling/stamp.ts +56 -12
- package/src/worktrees/stop.ts +92 -0
- package/tooling/astro/configs/card-src/pages/og-card.astro +1 -1
- package/tooling/astro/reference.md +2 -2
- package/tooling/base/configs/.github/workflows/phase-label-gate.yml +6 -2
- package/tooling/base/configs/commitlint.config.js +0 -20
- package/tooling/base/reference.md +1 -1
- package/tooling/base/seeds/.github/workflows/verify.yml +4 -1
- package/tooling/claude/reference.md +13 -7
- package/tooling/claude/seeds/.claude/hooks/board-validate.sh +65 -0
- package/tooling/claude/seeds/.claude/settings.json +8 -0
- package/tooling/claude/user/settings.template.json +0 -4
- package/tooling/cloudflare/configs/.github/workflows/deploy.yml +5 -5
- package/tooling/public/configs/.github/dependabot.yml +12 -0
- package/tooling/public/configs/.github/workflows/release-please.yml +30 -0
- package/tooling/public/manifest.toml +8 -0
- package/tooling/public/reference.md +53 -0
- package/tooling/public/seeds/.github/ISSUE_TEMPLATE/bug.yml +48 -0
- package/tooling/public/seeds/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/tooling/public/seeds/.github/ISSUE_TEMPLATE/feature.yml +16 -0
- package/tooling/public/seeds/.release-please-manifest.json +3 -0
- package/tooling/public/seeds/CONTRIBUTING.md +50 -0
- package/tooling/public/seeds/LICENSE +21 -0
- package/tooling/public/seeds/SECURITY.md +13 -0
- package/tooling/public/seeds/release-please-config.json +12 -0
- package/tooling/shell/configs/.github/workflows/shell.yml +4 -1
- package/tooling/web/configs/.github/workflows/readme-screenshot.yml +6 -3
- package/tooling/web/configs/.github/workflows/verify.yml +15 -12
- /package/claude/skills/{draft-figure → draw-figure}/references/figures.md +0 -0
- /package/claude/skills/{draft-slides → make-deck}/references/deck.md +0 -0
- /package/claude/skills/{draft-and-pick → pick-by-eye}/references/live-arms.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/agent-facing.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/application.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/cli.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/library.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/plugin.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/requirements.md +0 -0
package/src/commands/migrate.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
3
|
import type { Command } from 'commander'
|
|
4
4
|
import { listRepositoryFiles } from '@/git/files'
|
|
5
5
|
import { indexSourceRules } from '@/gov/adapter'
|
|
@@ -25,6 +25,11 @@ import {
|
|
|
25
25
|
type RuleLayoutPlan,
|
|
26
26
|
walkFlatRules,
|
|
27
27
|
} from '@/migrate/rule-layout'
|
|
28
|
+
import {
|
|
29
|
+
applySessionDefaults,
|
|
30
|
+
readSessionDefaults,
|
|
31
|
+
type SessionDefaultsRead,
|
|
32
|
+
} from '@/migrate/session-defaults'
|
|
28
33
|
import {
|
|
29
34
|
SKILL_CITATION_MAP,
|
|
30
35
|
SKILL_NAME_MAP,
|
|
@@ -50,7 +55,7 @@ import {
|
|
|
50
55
|
} from '@/migrate/scratch-evidence'
|
|
51
56
|
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
52
57
|
import { readStamp, stampedHashes } from '@/sync/stamp'
|
|
53
|
-
import { logError, logInfo, logStep, logWarn, plural } from '@/cli/ui'
|
|
58
|
+
import { logAdd, logError, logInfo, logStep, logWarn, plural } from '@/cli/ui'
|
|
54
59
|
|
|
55
60
|
interface SweepOptions {
|
|
56
61
|
readonly json?: boolean
|
|
@@ -959,6 +964,81 @@ function toRuleLayoutRecord(
|
|
|
959
964
|
}
|
|
960
965
|
}
|
|
961
966
|
|
|
967
|
+
interface SessionDefaultsOptions {
|
|
968
|
+
readonly json?: boolean
|
|
969
|
+
readonly write?: boolean
|
|
970
|
+
readonly root?: string
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Adds the seeded session defaults to a target settings file that predates
|
|
975
|
+
* them. A missing file is nothing to do, since `canon claude init` seeds the
|
|
976
|
+
* whole file with the defaults already in it.
|
|
977
|
+
*/
|
|
978
|
+
async function runSessionDefaults(
|
|
979
|
+
opts: SessionDefaultsOptions,
|
|
980
|
+
): Promise<number> {
|
|
981
|
+
const root = resolve(opts.root ?? process.cwd())
|
|
982
|
+
|
|
983
|
+
const mismatch = checkoutMismatchWarning(process.cwd())
|
|
984
|
+
if (mismatch !== undefined) logWarn(mismatch)
|
|
985
|
+
|
|
986
|
+
const read = await readSessionDefaults(root, PROJECT_ROOT)
|
|
987
|
+
|
|
988
|
+
if (opts.json) {
|
|
989
|
+
process.stdout.write(
|
|
990
|
+
`${JSON.stringify(toSessionDefaultsRecord(read, opts.write))}\n`,
|
|
991
|
+
)
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (read.kind === 'refused') {
|
|
995
|
+
logError(read.reason)
|
|
996
|
+
return 1
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (read.kind === 'absent') {
|
|
1000
|
+
logInfo(`No ${read.path}, so there is nothing to migrate.`)
|
|
1001
|
+
return 0
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
for (const key of read.plan.keys) {
|
|
1005
|
+
if (key.changed) logAdd(key.label)
|
|
1006
|
+
else logInfo(key.label)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (!read.plan.changed) {
|
|
1010
|
+
logInfo('Session defaults already present.')
|
|
1011
|
+
return 0
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (!opts.write) {
|
|
1015
|
+
logWarn('Nothing was written. Pass --write to apply this plan.')
|
|
1016
|
+
return 2
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
await applySessionDefaults(read)
|
|
1020
|
+
logStep(`Wrote ${read.path}.`)
|
|
1021
|
+
return 0
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function toSessionDefaultsRecord(
|
|
1025
|
+
read: SessionDefaultsRead,
|
|
1026
|
+
wrote: boolean | undefined,
|
|
1027
|
+
): unknown {
|
|
1028
|
+
if (read.kind === 'refused') {
|
|
1029
|
+
return { ok: false, wrote: false, reason: read.reason }
|
|
1030
|
+
}
|
|
1031
|
+
if (read.kind === 'absent') {
|
|
1032
|
+
return { ok: true, wrote: false, path: read.path, keys: [] }
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
ok: true,
|
|
1036
|
+
wrote: wrote === true && read.plan.changed,
|
|
1037
|
+
path: read.path,
|
|
1038
|
+
keys: read.plan.keys,
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
962
1042
|
export function register(program: Command): void {
|
|
963
1043
|
const migrate = program
|
|
964
1044
|
.command('migrate')
|
|
@@ -1255,6 +1335,10 @@ export function register(program: Command): void {
|
|
|
1255
1335
|
'',
|
|
1256
1336
|
`Rewrites ${Object.keys(SKILL_NAME_MAP).length} skill names and moves the folders that carry them.`,
|
|
1257
1337
|
`It also carries ${Object.keys(SKILL_CITATION_MAP).length} namespaced-only rows for a skill renamed from a single word.`,
|
|
1338
|
+
'The seven draft- skills move onto verb-first names, such as',
|
|
1339
|
+
// The old spellings below are the rename itself. canon-keep-retired
|
|
1340
|
+
'draft-doc to write-doc and draft-and-pick to pick-by-eye, and a',
|
|
1341
|
+
'pointer left under each old name is never moved.',
|
|
1258
1342
|
'It takes no scope. The skill folders it moves are authored in the',
|
|
1259
1343
|
'toolkit and no target holds a copy of that catalog, so in a target it',
|
|
1260
1344
|
'rewrites citations of a renamed skill and moves nothing.',
|
|
@@ -1322,4 +1406,43 @@ export function register(program: Command): void {
|
|
|
1322
1406
|
.action(async (opts: RuleLayoutOptions) => {
|
|
1323
1407
|
process.exitCode = await runRuleLayout(opts)
|
|
1324
1408
|
})
|
|
1409
|
+
|
|
1410
|
+
migrate
|
|
1411
|
+
.command('session-defaults')
|
|
1412
|
+
.description('Add the seeded session defaults to a target settings file')
|
|
1413
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
1414
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
1415
|
+
.option('--write', 'Apply the plan rather than reporting it')
|
|
1416
|
+
.option(
|
|
1417
|
+
'--root <path>',
|
|
1418
|
+
'Project root, defaulting to the working directory',
|
|
1419
|
+
)
|
|
1420
|
+
.addHelpText(
|
|
1421
|
+
'after',
|
|
1422
|
+
[
|
|
1423
|
+
'',
|
|
1424
|
+
'Adds Agent and Task to permissions.deny in .claude/settings.json,',
|
|
1425
|
+
'appending to the list without reordering it, and sets',
|
|
1426
|
+
'autoMemoryEnabled to false only when the key is absent. A deny entry',
|
|
1427
|
+
'is never removed and an existing autoMemoryEnabled is never changed.',
|
|
1428
|
+
'',
|
|
1429
|
+
'A missing settings file is nothing to do. A file that is not valid',
|
|
1430
|
+
'JSON, or whose permissions or deny has an unexpected shape, is',
|
|
1431
|
+
'refused by name and never partly written.',
|
|
1432
|
+
'',
|
|
1433
|
+
'Exit codes:',
|
|
1434
|
+
' 0 nothing to add, or --write applied the plan',
|
|
1435
|
+
' 1 refused: the toolkit root, or an unreadable settings file',
|
|
1436
|
+
' 2 a plan exists and --write was not passed',
|
|
1437
|
+
'',
|
|
1438
|
+
'Examples:',
|
|
1439
|
+
' canon migrate session-defaults',
|
|
1440
|
+
' canon migrate session-defaults --write',
|
|
1441
|
+
' canon migrate session-defaults --json',
|
|
1442
|
+
'',
|
|
1443
|
+
].join('\n'),
|
|
1444
|
+
)
|
|
1445
|
+
.action(async (opts: SessionDefaultsOptions) => {
|
|
1446
|
+
process.exitCode = await runSessionDefaults(opts)
|
|
1447
|
+
})
|
|
1325
1448
|
}
|
package/src/commands/pr.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, statSync } from 'node:fs'
|
|
2
|
+
import { readFile, realpath } from 'node:fs/promises'
|
|
3
3
|
import { basename, join, resolve } from 'node:path'
|
|
4
4
|
import { $ } from 'bun'
|
|
5
5
|
import type { Command } from 'commander'
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
pruneFramesWithLegacy,
|
|
58
58
|
pushFrame,
|
|
59
59
|
readFrame,
|
|
60
|
+
isDesignReference,
|
|
60
61
|
referenceSlug,
|
|
61
62
|
type TreeEntry,
|
|
62
63
|
} from '@/pr/frames'
|
|
@@ -72,15 +73,13 @@ import {
|
|
|
72
73
|
readBaseStanding,
|
|
73
74
|
readChangedPaths,
|
|
74
75
|
} from '@/pr/lift'
|
|
76
|
+
import { machineListenerReader } from '@/listeners/machine'
|
|
75
77
|
import { loadPaintedPatterns } from '@/pr/painted'
|
|
76
78
|
import {
|
|
77
79
|
dropAddressLine,
|
|
78
80
|
findLocalServer,
|
|
79
|
-
type Listener,
|
|
80
81
|
type LocalRefusal,
|
|
81
82
|
type LocalRunner,
|
|
82
|
-
parseLsofListeners,
|
|
83
|
-
parseProcNetTcp,
|
|
84
83
|
} from '@/pr/local'
|
|
85
84
|
import { KEY_CHANGES } from '@/pr/paths'
|
|
86
85
|
import {
|
|
@@ -224,9 +223,9 @@ const FRAMES_REFUSALS: Record<FramesCommandRefusal, string> = {
|
|
|
224
223
|
'no-number':
|
|
225
224
|
'--add and --drop write for one pull request, so name its number.',
|
|
226
225
|
'bad-box':
|
|
227
|
-
'--add takes --box <n>, a positive whole number naming the checklist box, or --reference <page>/<frame
|
|
226
|
+
'--add takes --box <n>, a positive whole number naming the checklist box, or --reference, as <page>/<frame> or a tracked canon/design/<surface>/<name> path, never both.',
|
|
228
227
|
'bad-reference':
|
|
229
|
-
'--reference takes <page>/<frame
|
|
228
|
+
'--reference takes <page>/<frame> or a tracked canon/design/<surface>/<name> path, each half opening on a letter or digit and holding only letters, digits, dots, underscores, and dashes, with no `--` in either, so the capture files under a name that reads back as the frame.',
|
|
230
229
|
'bad-head':
|
|
231
230
|
'--head takes a commit sha, 7 to 40 lowercase hex characters. A branch name or a path would file the frame under a folder that names no commit.',
|
|
232
231
|
'bad-pass':
|
|
@@ -1062,7 +1061,7 @@ export function register(program: Command): void {
|
|
|
1062
1061
|
.option('--box <n>', 'The checklist box the frame shows, with --add')
|
|
1063
1062
|
.option(
|
|
1064
1063
|
'--reference <address>',
|
|
1065
|
-
'The design reference the capture shows, as <page>/<frame
|
|
1064
|
+
'The design reference the capture shows, as <page>/<frame> or a tracked canon/design/<surface>/<name> path, with --add in place of --box',
|
|
1066
1065
|
)
|
|
1067
1066
|
.option(
|
|
1068
1067
|
'--head <sha>',
|
|
@@ -1089,7 +1088,9 @@ export function register(program: Command): void {
|
|
|
1089
1088
|
'so nothing is checked out and no other branch is touched. A frame lands',
|
|
1090
1089
|
'at pr-<number>/<short-head>/<pass>/box-<n>.png, or box-<n>-<theme>.png',
|
|
1091
1090
|
'with --theme, or ref-<page>--<frame>.png for a design reference capture',
|
|
1092
|
-
'passed as --reference <page>/<frame> in place of --box,
|
|
1091
|
+
'passed as --reference <page>/<frame> in place of --box, or',
|
|
1092
|
+
'ref-design--<surface>--<name>.png for a tracked canon/design/<surface>/<name>',
|
|
1093
|
+
'reference, whose --add must name that same file, and `link` names that',
|
|
1093
1094
|
`path on ${FRAMES_BRANCH}, so it survives a rewrite that drops other pull`,
|
|
1094
1095
|
'requests and resolves until its own pull request is dropped. The pass',
|
|
1095
1096
|
'segment keeps a second pass at one head and box from replacing an image',
|
|
@@ -1111,7 +1112,7 @@ export function register(program: Command): void {
|
|
|
1111
1112
|
' bad-mode not exactly one of --add, --drop, --prune',
|
|
1112
1113
|
' no-number --add or --drop named no pull request',
|
|
1113
1114
|
' bad-box --add with both --box and --reference, or neither',
|
|
1114
|
-
' bad-reference --reference is not <page>/<frame> in the safe name form',
|
|
1115
|
+
' bad-reference --reference is not <page>/<frame> or canon/design/<surface>/<name> in the safe name form, or the tracked path is no file',
|
|
1115
1116
|
' bad-head --head is not 7 to 40 lowercase hex characters',
|
|
1116
1117
|
' bad-pass --add without a compact UTC --pass stamp',
|
|
1117
1118
|
' bad-theme --theme is not dark or light, or no --add, or rides a --reference',
|
|
@@ -2730,81 +2731,10 @@ async function runPreviewCheck(
|
|
|
2730
2731
|
return reading.reason === 'fresh' ? 0 : 1
|
|
2731
2732
|
}
|
|
2732
2733
|
|
|
2733
|
-
|
|
2734
|
-
return readFile(path, 'utf8').catch(() => undefined)
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
/**
|
|
2738
|
-
* Every listening socket's owning pid, read from `/proc`. Only a process this
|
|
2739
|
-
* user owns exposes its `fd` table, which is also the only process whose
|
|
2740
|
-
* server this user's worktree could have started.
|
|
2741
|
-
*/
|
|
2742
|
-
async function readProcListeners(): Promise<Listener[] | undefined> {
|
|
2743
|
-
const tables = await Promise.all(
|
|
2744
|
-
['/proc/net/tcp', '/proc/net/tcp6'].map(readText),
|
|
2745
|
-
)
|
|
2746
|
-
if (tables.every((text) => text === undefined)) return undefined
|
|
2747
|
-
const byInode = new Map(
|
|
2748
|
-
tables.flatMap((text) =>
|
|
2749
|
-
text === undefined
|
|
2750
|
-
? []
|
|
2751
|
-
: parseProcNetTcp(text).map((s) => [s.inode, s.port] as const),
|
|
2752
|
-
),
|
|
2753
|
-
)
|
|
2754
|
-
|
|
2755
|
-
const processIds = (await readdir('/proc').catch(() => [])).filter((name) =>
|
|
2756
|
-
/^\d+$/.test(name),
|
|
2757
|
-
)
|
|
2758
|
-
const listeners: Listener[] = []
|
|
2759
|
-
for (const pid of processIds) {
|
|
2760
|
-
const fds = await readdir(`/proc/${pid}/fd`).catch(() => [])
|
|
2761
|
-
for (const fd of fds) {
|
|
2762
|
-
const target = await readlink(`/proc/${pid}/fd/${fd}`).catch(() => '')
|
|
2763
|
-
const inode = /^socket:\[(\d+)\]$/.exec(target)?.[1]
|
|
2764
|
-
const port = inode === undefined ? undefined : byInode.get(inode)
|
|
2765
|
-
if (port !== undefined) listeners.push({ pid: Number(pid), port })
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
return listeners
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
/**
|
|
2772
|
-
* The machine half of a detection: `/proc` wherever it exists, lsof on a
|
|
2773
|
-
* machine without it, and no reader anywhere else.
|
|
2774
|
-
*/
|
|
2734
|
+
/** The listener reads come from the machine, and the HTML probe from here. */
|
|
2775
2735
|
function machineLocalRunner(): LocalRunner {
|
|
2776
|
-
const hasLsof = Bun.which('lsof') !== null
|
|
2777
2736
|
return {
|
|
2778
|
-
|
|
2779
|
-
// lsof 4.95 drops every process whose kernel-truncated name holds an
|
|
2780
|
-
// unmatched `(`, which Next's `next-server (vX.Y.Z)` title cuts down to,
|
|
2781
|
-
// so `/proc` goes first despite costing several times what lsof does.
|
|
2782
|
-
if (existsSync('/proc/net/tcp')) {
|
|
2783
|
-
const listeners = await readProcListeners()
|
|
2784
|
-
if (listeners !== undefined) return listeners
|
|
2785
|
-
}
|
|
2786
|
-
if (hasLsof) {
|
|
2787
|
-
const result = await $`lsof -nP -iTCP -sTCP:LISTEN -Fpn`
|
|
2788
|
-
.quiet()
|
|
2789
|
-
.nothrow()
|
|
2790
|
-
// lsof exits 1 both when it matched nothing and when it could not
|
|
2791
|
-
// read some process, and the rows it did print are real either way.
|
|
2792
|
-
if (result.exitCode <= 1) return parseLsofListeners(result.text())
|
|
2793
|
-
}
|
|
2794
|
-
return undefined
|
|
2795
|
-
},
|
|
2796
|
-
async cwdOf(pid) {
|
|
2797
|
-
if (existsSync(`/proc/${pid}/cwd`)) {
|
|
2798
|
-
return readlink(`/proc/${pid}/cwd`).catch(() => undefined)
|
|
2799
|
-
}
|
|
2800
|
-
if (!hasLsof) return undefined
|
|
2801
|
-
const result = await $`lsof -a -p ${pid} -d cwd -Fn`.quiet().nothrow()
|
|
2802
|
-
const line = result
|
|
2803
|
-
.text()
|
|
2804
|
-
.split('\n')
|
|
2805
|
-
.find((row) => row.startsWith('n'))
|
|
2806
|
-
return line?.slice(1)
|
|
2807
|
-
},
|
|
2737
|
+
...machineListenerReader(),
|
|
2808
2738
|
// An HTML page rather than any answer, since a test runner's server or a
|
|
2809
2739
|
// reload socket in the same tree answers too, and a reviewer opening one
|
|
2810
2740
|
// gets a bare 404 instead of the branch.
|
|
@@ -3265,6 +3195,27 @@ async function runFrames(
|
|
|
3265
3195
|
const invalid = framesArgumentRefusal(number, opts)
|
|
3266
3196
|
if (invalid !== undefined) return refuseFrames(invalid)
|
|
3267
3197
|
|
|
3198
|
+
if (opts.reference !== undefined && isDesignReference(opts.reference)) {
|
|
3199
|
+
const tracked = resolve(
|
|
3200
|
+
root,
|
|
3201
|
+
opts.reference.endsWith('.png')
|
|
3202
|
+
? opts.reference
|
|
3203
|
+
: `${opts.reference}.png`,
|
|
3204
|
+
)
|
|
3205
|
+
const refuseTracked = (message: string) =>
|
|
3206
|
+
refuseWith('bad-reference', message, emitJson, root)
|
|
3207
|
+
if (!statSync(tracked, { throwIfNoEntry: false })?.isFile()) {
|
|
3208
|
+
return refuseTracked(
|
|
3209
|
+
`${opts.reference} is not a file under the repository, so no tracked reference can be filed from it.`,
|
|
3210
|
+
)
|
|
3211
|
+
}
|
|
3212
|
+
if (opts.add === undefined || resolve(opts.add) !== tracked) {
|
|
3213
|
+
return refuseTracked(
|
|
3214
|
+
`--add must name ${tracked}, since a tracked reference files its own image and a different one would disagree with its label.`,
|
|
3215
|
+
)
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3268
3219
|
const bytes =
|
|
3269
3220
|
opts.add === undefined ? undefined : await readFrame(resolve(opts.add))
|
|
3270
3221
|
if (opts.add !== undefined && bytes === undefined) {
|