@erclx/canon 5.26.0 → 5.28.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 +2 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +3 -11
- package/claude/skills/auto-ship/references/review-findings.md +20 -0
- package/claude/skills/backlog-triage/SKILL.md +2 -0
- package/claude/skills/bash-cli-script/SKILL.md +2 -0
- package/claude/skills/build-in-slices/SKILL.md +2 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/canon-feedback/SKILL.md +2 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
- package/claude/skills/canon-operator/SKILL.md +2 -0
- package/claude/skills/canon-rollout/SKILL.md +3 -11
- package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
- package/claude/skills/canvas/SKILL.md +13 -0
- package/claude/skills/ci-workflow/SKILL.md +2 -0
- package/claude/skills/code-craft/SKILL.md +2 -0
- package/claude/skills/codebase-layout/SKILL.md +2 -0
- package/claude/skills/context-fold/SKILL.md +3 -17
- package/claude/skills/context-fold/references/completion-report.md +26 -0
- package/claude/skills/create-rule/SKILL.md +2 -0
- package/claude/skills/create-skill/SKILL.md +2 -0
- package/claude/skills/create-standard/SKILL.md +2 -0
- package/claude/skills/decision-escalate/SKILL.md +2 -0
- package/claude/skills/deploy-app/SKILL.md +2 -0
- package/claude/skills/deprecation-migration/SKILL.md +2 -0
- package/claude/skills/design-extract/SKILL.md +2 -0
- package/claude/skills/design-taste/SKILL.md +2 -0
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/SKILL.md +2 -0
- package/claude/skills/draft-and-pick/SKILL.md +2 -0
- package/claude/skills/draft-doc/SKILL.md +2 -0
- package/claude/skills/draft-doc/references/wiki.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +2 -0
- package/claude/skills/draft-identity/SKILL.md +2 -0
- package/claude/skills/draft-ready/SKILL.md +5 -1
- package/claude/skills/draft-screencast/SKILL.md +2 -0
- package/claude/skills/draft-slides/SKILL.md +2 -0
- package/claude/skills/git-branch/SKILL.md +2 -0
- package/claude/skills/git-commit/SKILL.md +2 -0
- package/claude/skills/git-followup/SKILL.md +2 -0
- package/claude/skills/git-issue/SKILL.md +2 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +7 -7
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +2 -0
- package/claude/skills/git-stage/SKILL.md +2 -0
- package/claude/skills/git-worktree/SKILL.md +2 -0
- package/claude/skills/index-lookup/SKILL.md +2 -0
- package/claude/skills/markdown-propose/SKILL.md +2 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/plan-feature/SKILL.md +2 -0
- package/claude/skills/plan-groundwork/SKILL.md +2 -0
- package/claude/skills/plan-intake/SKILL.md +2 -0
- package/claude/skills/plan-intake-answer/SKILL.md +2 -0
- package/claude/skills/project-commands/SKILL.md +2 -0
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +2 -0
- package/claude/skills/repo-metadata/SKILL.md +2 -0
- package/claude/skills/restate-plainly/SKILL.md +2 -0
- package/claude/skills/review-address/REQUIREMENT.md +4 -2
- package/claude/skills/review-address/SKILL.md +19 -14
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-address/references/rebase-conflicts.md +4 -2
- package/claude/skills/review-branch/SKILL.md +2 -0
- package/claude/skills/review-craft/SKILL.md +2 -0
- package/claude/skills/review-craft/references/ui.md +6 -6
- package/claude/skills/review-pr/SKILL.md +4 -8
- package/claude/skills/review-pr/references/body-variants.md +52 -0
- package/claude/skills/review-pr/references/key-changes.md +16 -0
- package/claude/skills/review-pr/references/post.md +15 -43
- package/claude/skills/review-ui/REQUIREMENT.md +2 -2
- package/claude/skills/review-ui/SKILL.md +16 -12
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +13 -3
- package/claude/skills/review-ui/references/tick.md +22 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +15 -15
- package/claude/skills/role-orchestrator/SKILL.md +31 -42
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +18 -16
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +13 -3
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +6 -34
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +5 -5
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +12 -23
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +6 -6
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-orchestrator/scripts/poll.ts +304 -10
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +4 -2
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +3 -1
- package/claude/skills/role-worker/SKILL.md +5 -2
- package/claude/skills/search-craft/SKILL.md +2 -0
- package/claude/skills/seed-sync/SKILL.md +2 -0
- package/claude/skills/session-compact/SKILL.md +2 -0
- package/claude/skills/session-map/SKILL.md +2 -0
- package/claude/skills/session-relay/SKILL.md +2 -0
- package/claude/skills/session-resume/SKILL.md +2 -0
- package/claude/skills/session-worktree/SKILL.md +3 -11
- package/claude/skills/session-worktree/references/install-command.md +20 -0
- package/claude/skills/sketch-design/SKILL.md +2 -0
- package/claude/skills/systematic-debugging/SKILL.md +2 -0
- package/claude/skills/target-check/SKILL.md +2 -0
- package/claude/skills/target-setup/SKILL.md +2 -0
- package/claude/skills/task-board/SKILL.md +2 -0
- package/claude/skills/teach-workspace/SKILL.md +5 -15
- package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
- package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
- package/claude/skills/teach-workspace/references/revisit.md +22 -0
- package/claude/skills/test-craft/SKILL.md +2 -0
- package/claude/skills/test-first/SKILL.md +2 -0
- package/claude/skills/ui-checklist/SKILL.md +3 -1
- package/claude/skills/ux-audit/SKILL.md +2 -0
- package/claude/skills/ux-measure/SKILL.md +2 -0
- package/claude/skills/ux-walkthrough/SKILL.md +2 -0
- package/claude/skills/video-craft/SKILL.md +2 -0
- package/claude/skills/write-human/SKILL.md +2 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -8
- package/docs/agents/capture.md +5 -5
- package/docs/agents/commands.md +2 -2
- package/docs/agents/gate.md +3 -1
- package/docs/agents/index.md +5 -4
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +47 -23
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/pr-tick.md +72 -0
- package/docs/agents/sandbox.md +30 -6
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +2 -1
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +18 -18
- package/docs/agents/tasks.md +1 -1
- package/docs/workflow/operating-model.md +15 -21
- package/docs/workflow/parallel-features.md +24 -3
- package/docs/workflow/skill-map.md +3 -1
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-hero.ts +1 -1
- package/scripts/lib/sandbox-path.sh +6 -162
- package/src/audits/baseline.ts +1 -1
- package/src/audits/run.ts +2 -2
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -1
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +201 -171
- package/src/canvas/client/inspector/color-inputs.tsx +139 -0
- package/src/canvas/client/inspector/color-picker.tsx +668 -0
- package/src/canvas/client/inspector/color-space.ts +240 -0
- package/src/canvas/client/inspector/field.tsx +66 -3
- package/src/canvas/client/inspector/fields.css +473 -6
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/token-select.tsx +109 -0
- package/src/canvas/client/inspector/values.ts +117 -3
- package/src/canvas/client/inspector.tsx +311 -15
- package/src/canvas/client/keys.ts +54 -0
- package/src/canvas/client/selection.tsx +199 -0
- package/src/canvas/client/shell.css +210 -12
- package/src/canvas/client/state.ts +165 -14
- package/src/canvas/client/surface.tsx +341 -39
- package/src/canvas/client/tools.tsx +64 -0
- package/src/canvas/content.ts +51 -2
- package/src/canvas/edit.ts +49 -6
- package/src/canvas/editing.ts +203 -0
- package/src/canvas/global.d.ts +15 -0
- package/src/canvas/server.ts +149 -12
- package/src/canvas/tokens.ts +60 -18
- package/src/census/count.ts +1 -1
- package/src/claude/seeds.ts +3 -3
- package/src/claude/skills-audit.ts +28 -0
- package/src/claude/skills-drift.ts +1 -1
- package/src/claude/skills-families.ts +36 -0
- package/src/claude/skills-list.ts +76 -15
- package/src/claude/skills-reach.ts +1 -1
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +180 -3
- package/src/commands/claude.ts +70 -12
- package/src/commands/design.ts +145 -5
- package/src/commands/docs.ts +1 -1
- package/src/commands/feedback.ts +2 -2
- package/src/commands/gate.ts +9 -2
- package/src/commands/gov.ts +3 -3
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +2 -2
- package/src/commands/pr.ts +288 -65
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +64 -15
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +1 -1
- package/src/commands/sync.ts +2 -2
- package/src/commands/tasks.ts +21 -4
- package/src/commands/teach.ts +1 -1
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +2 -2
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/comments/scan.ts +1 -1
- package/src/comments/trend.ts +1 -1
- package/src/context/architecture.ts +1 -1
- package/src/context/citations.ts +3 -3
- package/src/context/classify/extract.ts +3 -3
- package/src/context/folders.ts +2 -2
- package/src/context/requirements.ts +1 -1
- package/src/design/project-fonts.ts +392 -0
- package/src/docs/read.ts +1 -1
- package/src/exec.ts +1 -1
- package/src/feedback/github.ts +2 -2
- package/src/gate/measures.ts +48 -10
- package/src/gate/sequencer.ts +56 -4
- package/src/gate/stages.ts +17 -0
- package/src/gate/suite-lock.ts +214 -0
- package/src/{git-files.ts → git/files.ts} +2 -2
- package/src/{git-ignore.ts → git/ignore.ts} +1 -1
- package/src/{worktree.ts → git/worktree.ts} +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/install.ts +1 -1
- package/src/gov/superseded.ts +1 -1
- package/src/gov/test-order.ts +2 -2
- package/src/help.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/indexes/walk.ts +1 -1
- package/src/intake/folder.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/labels/map.ts +1 -1
- package/src/labels/phase.ts +3 -3
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/records.ts +11 -11
- package/src/migrate/scratch-evidence.ts +1 -1
- package/src/migrate/surface-roots.ts +6 -6
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +250 -59
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/pr/tick.ts +56 -0
- package/src/process/harness.ts +1 -1
- package/src/records/backup.ts +2 -2
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/records/size.ts +1 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/records/stale.ts +1 -1
- package/src/records/validate.ts +2 -2
- package/src/{project-root.ts → roots/project.ts} +1 -1
- package/src/{record-root.ts → roots/record.ts} +1 -1
- package/src/sandbox/coverage.ts +11 -12
- package/src/sandbox/equivalence.ts +57 -26
- package/src/sandbox/headless/dispatch.ts +264 -0
- package/src/sandbox/headless/run.ts +653 -0
- package/src/sandbox/headless/watch.ts +145 -0
- package/src/sandbox/hooks.ts +1 -1
- package/src/sandbox/provision.ts +90 -29
- package/src/sandbox/scenario.ts +588 -0
- package/src/sandbox/tree.ts +4 -8
- package/src/secrets/scan.ts +1 -1
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/live.ts +4 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +2 -2
- package/src/slides/decks.ts +1 -1
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/standards/read.ts +1 -1
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +3 -3
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/layout.ts +2 -2
- package/src/sync/reverse.ts +1 -1
- package/src/sync/stamp.ts +1 -1
- package/src/sync/target.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/targets/sweep.ts +2 -2
- package/src/{target.ts → targets/validate.ts} +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/trunk.ts +1 -1
- package/src/tasks/validate.ts +38 -14
- package/src/teach/nav.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/read.ts +1 -1
- package/src/tooling/scan.ts +1 -1
- package/src/tooling/subfolder.ts +1 -1
- package/src/version/installed.ts +1 -1
- package/src/version/skew.ts +1 -1
- package/src/web/readme-citations.ts +1 -1
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/board.md +3 -3
- package/standards/canvas.md +2 -1
- package/standards/plan.md +13 -7
- package/standards/skill.md +1 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/tsconfig.json +1 -1
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- /package/src/{client-commands.ts → gate/client-commands.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{legacy-path.ts → roots/legacy.ts} +0 -0
- /package/src/{surface-root.ts → roots/surface.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
linkSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
unlinkSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from 'node:fs'
|
|
9
|
+
import { dirname, join } from 'node:path'
|
|
10
|
+
import { liveness, type LivenessProbes, SYSTEM_PROBES } from '@/sessions/live'
|
|
11
|
+
import { stateDir } from '@/targets/registry'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Who holds a machine lock, as the file records it.
|
|
15
|
+
*
|
|
16
|
+
* The start time is what separates the holder from whatever inherits its pid
|
|
17
|
+
* after a crash, so a takeover never waits on a recycled number.
|
|
18
|
+
*/
|
|
19
|
+
export interface LockHolder {
|
|
20
|
+
readonly pid: number
|
|
21
|
+
readonly procStart: string | undefined
|
|
22
|
+
/** The worktree whose gate holds the lock, which the wait line names. */
|
|
23
|
+
readonly root: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SuiteLockOptions {
|
|
27
|
+
readonly path: string
|
|
28
|
+
readonly root: string
|
|
29
|
+
readonly pid?: number
|
|
30
|
+
readonly probes?: LivenessProbes
|
|
31
|
+
readonly pollMs?: number
|
|
32
|
+
readonly sleep?: (ms: number) => Promise<void>
|
|
33
|
+
/** Called once, when the first poll finds a live holder, so a wait is never silent. */
|
|
34
|
+
readonly onWait?: (holder: LockHolder) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface HeldLock {
|
|
38
|
+
readonly release: () => void
|
|
39
|
+
/** The holder this acquirer queued behind, absent when it never waited. */
|
|
40
|
+
readonly waitedOn?: LockHolder
|
|
41
|
+
readonly waitedMs: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type AcquireOutcome =
|
|
45
|
+
| { readonly acquired: true; readonly release: () => void }
|
|
46
|
+
| { readonly acquired: false; readonly holder: LockHolder | undefined }
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The file a named machine lock lives at, resolved at call time so a test run
|
|
50
|
+
* that redirects the state folder never touches the real one.
|
|
51
|
+
*/
|
|
52
|
+
export function machineLockPath(name: string): string {
|
|
53
|
+
return join(stateDir(), 'locks', `${name}.lock`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isHolder(value: unknown): value is LockHolder {
|
|
57
|
+
if (typeof value !== 'object' || value === null) return false
|
|
58
|
+
const fields = value as Record<string, unknown>
|
|
59
|
+
return typeof fields.pid === 'number' && typeof fields.root === 'string'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readText(path: string): string | undefined {
|
|
63
|
+
try {
|
|
64
|
+
return readFileSync(path, 'utf8')
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseHolder(text: string): LockHolder | undefined {
|
|
71
|
+
try {
|
|
72
|
+
const value: unknown = JSON.parse(text)
|
|
73
|
+
return isHolder(value) ? value : undefined
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isCode(error: unknown, code: string): boolean {
|
|
80
|
+
return (error as NodeJS.ErrnoException).code === code
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Publishes the holder's file whole or not at all.
|
|
85
|
+
*
|
|
86
|
+
* `link` refuses an existing name the way an exclusive open does, and the
|
|
87
|
+
* linked file is already written, so no reader meets an empty lock and mistakes
|
|
88
|
+
* a holder mid-write for a corrupt file it may take over.
|
|
89
|
+
*/
|
|
90
|
+
function publish(path: string, text: string, pid: number): boolean {
|
|
91
|
+
const staged = `${path}.${pid}.tmp`
|
|
92
|
+
writeFileSync(staged, text)
|
|
93
|
+
try {
|
|
94
|
+
linkSync(staged, path)
|
|
95
|
+
return true
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (isCode(error, 'EEXIST')) return false
|
|
98
|
+
throw error
|
|
99
|
+
} finally {
|
|
100
|
+
unlinkSync(staged)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Moves a dead holder's file aside, or puts back one that changed under it.
|
|
106
|
+
*
|
|
107
|
+
* Unlink then create is two calls, and two acquirers judging one stale file
|
|
108
|
+
* would both get through. A rename moves the file to a name only this acquirer
|
|
109
|
+
* uses, so only one of them gets it, and comparing what moved against what was
|
|
110
|
+
* judged catches the case where another acquirer already replaced it.
|
|
111
|
+
*
|
|
112
|
+
* One interleaving still gets through. A third acquirer publishing between the
|
|
113
|
+
* rename and the restore leaves the moved holder's file with nowhere to go, so
|
|
114
|
+
* two suites run at once. That costs one oversubscribed run and never a hang,
|
|
115
|
+
* and closing it needs a lock the kernel releases, which `node:fs` does not
|
|
116
|
+
* offer.
|
|
117
|
+
*/
|
|
118
|
+
function displace(path: string, judged: string, pid: number): void {
|
|
119
|
+
const aside = `${path}.${pid}.stale`
|
|
120
|
+
try {
|
|
121
|
+
renameSync(path, aside)
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (isCode(error, 'ENOENT')) return
|
|
124
|
+
throw error
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (readText(aside) !== judged) {
|
|
128
|
+
try {
|
|
129
|
+
linkSync(aside, path)
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (!isCode(error, 'EEXIST')) throw error
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
unlinkSync(aside)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* One attempt at the lock: take it free, take it over from a dead holder, or
|
|
139
|
+
* report who holds it.
|
|
140
|
+
*/
|
|
141
|
+
export function tryAcquire(options: SuiteLockOptions): AcquireOutcome {
|
|
142
|
+
const pid = options.pid ?? process.pid
|
|
143
|
+
const probes = options.probes ?? SYSTEM_PROBES
|
|
144
|
+
const own: LockHolder = {
|
|
145
|
+
pid,
|
|
146
|
+
procStart: probes.procStartOf(pid) ?? undefined,
|
|
147
|
+
root: options.root,
|
|
148
|
+
}
|
|
149
|
+
const text = JSON.stringify(own)
|
|
150
|
+
|
|
151
|
+
mkdirSync(dirname(options.path), { recursive: true })
|
|
152
|
+
|
|
153
|
+
if (publish(options.path, text, pid)) {
|
|
154
|
+
return { acquired: true, release: () => release(options.path, text) }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const judged = readText(options.path)
|
|
158
|
+
if (judged === undefined) return { acquired: false, holder: undefined }
|
|
159
|
+
|
|
160
|
+
const holder = parseHolder(judged)
|
|
161
|
+
if (holder !== undefined && liveness(holder, probes).alive) {
|
|
162
|
+
return { acquired: false, holder }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
displace(options.path, judged, pid)
|
|
166
|
+
|
|
167
|
+
if (publish(options.path, text, pid)) {
|
|
168
|
+
return { acquired: true, release: () => release(options.path, text) }
|
|
169
|
+
}
|
|
170
|
+
return { acquired: false, holder: parseHolder(readText(options.path) ?? '') }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Removes the file only while it is still this holder's own. */
|
|
174
|
+
function release(path: string, text: string): void {
|
|
175
|
+
if (readText(path) !== text) return
|
|
176
|
+
try {
|
|
177
|
+
unlinkSync(path)
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (!isCode(error, 'ENOENT')) throw error
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const wait = (ms: number) =>
|
|
184
|
+
new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Waits for the lock with no deadline while its holder lives. A deadline would
|
|
188
|
+
* turn a long queue back into the intermittent failure the lock removes, and a
|
|
189
|
+
* dead holder is taken over on the next poll rather than waited out.
|
|
190
|
+
*/
|
|
191
|
+
export async function acquireSuiteLock(
|
|
192
|
+
options: SuiteLockOptions,
|
|
193
|
+
): Promise<HeldLock> {
|
|
194
|
+
const sleep = options.sleep ?? wait
|
|
195
|
+
const pollMs = options.pollMs ?? 500
|
|
196
|
+
const startedAt = performance.now()
|
|
197
|
+
let waitedOn: LockHolder | undefined
|
|
198
|
+
|
|
199
|
+
for (;;) {
|
|
200
|
+
const outcome = tryAcquire(options)
|
|
201
|
+
if (outcome.acquired) {
|
|
202
|
+
return {
|
|
203
|
+
release: outcome.release,
|
|
204
|
+
waitedOn,
|
|
205
|
+
waitedMs: waitedOn === undefined ? 0 : performance.now() - startedAt,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (waitedOn === undefined && outcome.holder !== undefined) {
|
|
209
|
+
options.onWait?.(outcome.holder)
|
|
210
|
+
}
|
|
211
|
+
waitedOn = outcome.holder ?? waitedOn
|
|
212
|
+
await sleep(pollMs)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
|
-
import { baseCandidates, isMergeBase } from '@/git
|
|
3
|
-
import { gitEnv } from '@/git
|
|
2
|
+
import { baseCandidates, isMergeBase } from '@/git/base'
|
|
3
|
+
import { gitEnv } from '@/git/env'
|
|
4
4
|
|
|
5
5
|
/** Runs git under `root` with the resolution variables a hook exports stripped. */
|
|
6
6
|
async function git(
|
package/src/gov/citations.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises'
|
|
|
3
3
|
import { dirname, join, resolve } from 'node:path'
|
|
4
4
|
import { $ } from 'bun'
|
|
5
5
|
import { isMarked } from '@/sweep/exempt-marker'
|
|
6
|
-
import { gitEnv } from '@/git
|
|
6
|
+
import { gitEnv } from '@/git/env'
|
|
7
7
|
import { listRuleFiles } from '@/gov/payload'
|
|
8
8
|
import { parseFrontmatter } from '@/indexes/frontmatter'
|
|
9
9
|
import { findBacktickSpans } from '@/markdown/backticks'
|
package/src/gov/install.ts
CHANGED
package/src/gov/superseded.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { isBinary } from '@/sweep/binary'
|
|
4
4
|
import { isMarked } from '@/sweep/exempt-marker'
|
|
5
|
-
import { listRepositoryFiles } from '@/git
|
|
5
|
+
import { listRepositoryFiles } from '@/git/files'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The inline token exempting one line from this sweep, shaped on the
|
package/src/gov/test-order.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execaSync } from 'execa'
|
|
2
|
-
import { baseCandidates, isMergeBase } from '@/git
|
|
3
|
-
import { gitEnv } from '@/git
|
|
2
|
+
import { baseCandidates, isMergeBase } from '@/git/base'
|
|
3
|
+
import { gitEnv } from '@/git/env'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The extensions this check can pair. A test sits beside its subject under one
|
package/src/help.ts
CHANGED
|
@@ -105,7 +105,7 @@ const EXAMPLES = [
|
|
|
105
105
|
'canon canvas list --json',
|
|
106
106
|
'canon slides render',
|
|
107
107
|
'canon slides list --json',
|
|
108
|
-
'canon capture assets/captures/install.html --selector .window --out assets/
|
|
108
|
+
'canon capture assets/captures/install.html --selector .window --out assets/frames',
|
|
109
109
|
'canon serve .canon/teach',
|
|
110
110
|
'canon inventory focus --json',
|
|
111
111
|
'canon drive http://localhost:4173 run.json --json',
|
package/src/hooks/post-merge.ts
CHANGED
package/src/indexes/walk.ts
CHANGED
package/src/intake/folder.ts
CHANGED
package/src/labels/audit.ts
CHANGED
package/src/labels/map.ts
CHANGED
package/src/labels/phase.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { linesOutsideFences, maskCodeSpans } from '@/markdown/scan'
|
|
2
|
-
import { RECORD_ENTRIES, RECORD_ROOTS } from '@/record
|
|
3
|
-
import type { RecordRoot } from '@/record
|
|
2
|
+
import { RECORD_ENTRIES, RECORD_ROOTS } from '@/roots/record'
|
|
3
|
+
import type { RecordRoot } from '@/roots/record'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The two version namespaces `standards/versioning.md` keeps apart, and why a
|
|
@@ -114,7 +114,7 @@ const ROOT_READING: Record<RecordRoot, 'whole' | 'entries'> = {
|
|
|
114
114
|
* `.claude/` is tracked and holds `rules`, `skills`, `hooks`, and `context`,
|
|
115
115
|
* so a rule path resolves in any clone and is not a board reference, and only
|
|
116
116
|
* the entries the record move relocated are unreadable there. Reading the
|
|
117
|
-
* roots and the relocated entries from `src/record
|
|
117
|
+
* roots and the relocated entries from `src/roots/record.ts` is what makes a
|
|
118
118
|
* folder added there matched here without an edit, and the list above is what
|
|
119
119
|
* covers the one thing that module deliberately does not carry.
|
|
120
120
|
*
|
package/src/markdown/files.ts
CHANGED
package/src/migrate/apply.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
2
|
import { readFile, mkdir, rename, rmdir, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { dirname, join, relative } from 'node:path'
|
|
4
|
-
import { gitEnv } from '@/git
|
|
4
|
+
import { gitEnv } from '@/git/env'
|
|
5
5
|
import type { RenamePlan, RenameSource } from '@/migrate/plan'
|
|
6
6
|
import { FROM_ROOT as OLD_ROOT, type RecordsPlan } from '@/migrate/records'
|
|
7
7
|
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
slugOf,
|
|
41
41
|
} from '@/migrate/evidence-ordinal'
|
|
42
42
|
import { presentFolders } from '@/records/backup'
|
|
43
|
-
import { recordDir, SCRATCH, spell, type RecordRoot } from '@/record
|
|
43
|
+
import { recordDir, SCRATCH, spell, type RecordRoot } from '@/roots/record'
|
|
44
44
|
|
|
45
45
|
const CANON: RecordRoot = '.canon'
|
|
46
46
|
const CLAUDE: RecordRoot = '.claude'
|
package/src/migrate/records.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
RECORD_ENTRIES,
|
|
16
16
|
RECORD_ROOTS,
|
|
17
17
|
spell,
|
|
18
|
-
} from '@/record
|
|
18
|
+
} from '@/roots/record'
|
|
19
19
|
|
|
20
20
|
/** The root the entries below leave, exported so the writer can prune it. */
|
|
21
21
|
export const FROM_ROOT: RecordRoot = '.claude'
|
|
@@ -26,7 +26,7 @@ const TO_ROOT: RecordRoot = '.canon'
|
|
|
26
26
|
/**
|
|
27
27
|
* Every entry the move relocates, at the name `.claude/` gave it.
|
|
28
28
|
*
|
|
29
|
-
* The list is `RECORD_ENTRIES`, which `record
|
|
29
|
+
* The list is `RECORD_ENTRIES`, which `roots/record.ts` owns because a seed
|
|
30
30
|
* destination and a superseded-layout report ask the same question. Restating
|
|
31
31
|
* it here would let the sweep and the resolver disagree about what moved.
|
|
32
32
|
*/
|
|
@@ -76,12 +76,12 @@ function escape(value: string): string {
|
|
|
76
76
|
* paths a session actually opened. Rewriting either makes it testify to
|
|
77
77
|
* something that never happened.
|
|
78
78
|
*
|
|
79
|
-
* This module and `src/record
|
|
80
|
-
* root on purpose. Sweeping them turns every citation this expression is
|
|
81
|
-
* from into its own replacement, leaving a rewriter that maps `.canon/`
|
|
82
|
-
* `.canon/` and matches nothing. `src/surface
|
|
83
|
-
* early, for the same reason: it spells `.claude` as data the moment
|
|
84
|
-
* exists, and excluding it later would leave one release where a records
|
|
79
|
+
* This module and `src/roots/record.ts` are two of the sources that state the
|
|
80
|
+
* old root on purpose. Sweeping them turns every citation this expression is
|
|
81
|
+
* built from into its own replacement, leaving a rewriter that maps `.canon/`
|
|
82
|
+
* to `.canon/` and matches nothing. `src/roots/surface.ts` joins the list a
|
|
83
|
+
* release early, for the same reason: it spells `.claude` as data the moment
|
|
84
|
+
* it exists, and excluding it later would leave one release where a records
|
|
85
85
|
* migration in a target could rewrite the resolver that migration itself
|
|
86
86
|
* depends on.
|
|
87
87
|
*
|
|
@@ -112,8 +112,8 @@ const EXCLUDED_PREFIXES: readonly string[] = [
|
|
|
112
112
|
|
|
113
113
|
const EXCLUDED_PATHS: readonly string[] = [
|
|
114
114
|
'CHANGELOG.md',
|
|
115
|
-
'src/record
|
|
116
|
-
'src/surface
|
|
115
|
+
'src/roots/record.ts',
|
|
116
|
+
'src/roots/surface.ts',
|
|
117
117
|
]
|
|
118
118
|
|
|
119
119
|
const EXCLUDED_SUFFIXES: readonly string[] = ['.test.ts']
|
|
@@ -171,7 +171,7 @@ export const RECORD_ONLY_ROOTS: readonly RecordRoot[] = RECORD_ROOTS.filter(
|
|
|
171
171
|
* under `.claude/`, and a target's installed `.claude/rules/canon/core/035-tasks.md`
|
|
172
172
|
* is the file the sweep exists to repoint, so a bare `.claude/` prefix strands
|
|
173
173
|
* it silently. The old root is therefore entry-scoped, through `spell` so the
|
|
174
|
-
* one naming variant stays decided in `record
|
|
174
|
+
* one naming variant stays decided in `roots/record.ts`.
|
|
175
175
|
*
|
|
176
176
|
* Joined with a literal separator rather than through `join`, the way
|
|
177
177
|
* `EXCLUDED_PREFIXES` already is. These are matched against what `git ls-files`
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
slugOf,
|
|
35
35
|
} from '@/migrate/evidence-ordinal'
|
|
36
36
|
import { presentFolders } from '@/records/backup'
|
|
37
|
-
import { recordDir, SCRATCH } from '@/record
|
|
37
|
+
import { recordDir, SCRATCH } from '@/roots/record'
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Every folder this promotion moves, at the name it carries under scratch.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { join } from 'node:path'
|
|
12
|
-
import { SURFACE_ENTRIES, spell, type SurfaceRoot } from '@/surface
|
|
12
|
+
import { SURFACE_ENTRIES, spell, type SurfaceRoot } from '@/roots/surface'
|
|
13
13
|
|
|
14
14
|
/** The root the entries below leave. */
|
|
15
15
|
export const FROM_ROOT: SurfaceRoot = '.claude'
|
|
@@ -80,8 +80,8 @@ function escape(value: string): string {
|
|
|
80
80
|
*
|
|
81
81
|
* The changelog is release history, and an eval result is a transcript of
|
|
82
82
|
* paths a session actually opened; rewriting either makes it testify to
|
|
83
|
-
* something that never happened. `src/surface
|
|
84
|
-
* `src/record
|
|
83
|
+
* something that never happened. `src/roots/surface.ts` and
|
|
84
|
+
* `src/roots/record.ts` are the two sources that spell the old root as data on
|
|
85
85
|
* purpose, so sweeping either would turn the very literals this verb reads
|
|
86
86
|
* into their own replacement. A hook is the third such source, guarding on a
|
|
87
87
|
* `case` carrying an arm per root, so `.claude/hooks/` and the seed's copy of
|
|
@@ -102,8 +102,8 @@ const EXCLUDED_PREFIXES: readonly string[] = [
|
|
|
102
102
|
|
|
103
103
|
const EXCLUDED_PATHS: readonly string[] = [
|
|
104
104
|
'CHANGELOG.md',
|
|
105
|
-
'src/surface
|
|
106
|
-
'src/record
|
|
105
|
+
'src/roots/surface.ts',
|
|
106
|
+
'src/roots/record.ts',
|
|
107
107
|
]
|
|
108
108
|
|
|
109
109
|
export function isExcludedPath(path: string): boolean {
|
|
@@ -380,7 +380,7 @@ export interface SurfaceRootsPlan {
|
|
|
380
380
|
* moved entry, and whether its content cites one. Either alone is enough to
|
|
381
381
|
* produce an entry, and a file excluded from the citation rewrite still moves
|
|
382
382
|
* when its path itself is inside a moved entry, since the two entries excluded
|
|
383
|
-
* today, `src/surface
|
|
383
|
+
* today, `src/roots/surface.ts` and `src/roots/record.ts`, both sit outside
|
|
384
384
|
* every moved entry and never take that branch in practice.
|
|
385
385
|
*/
|
|
386
386
|
export function planSurfaceRootsMove(
|
package/src/pr/bijection.ts
CHANGED