@erclx/canon 5.30.0 → 5.32.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 +1 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +4 -16
- package/claude/skills/auto-ship/SKILL.md +13 -58
- package/claude/skills/auto-ship/references/draft-mark.md +2 -2
- package/claude/skills/auto-ship/references/failure-recovery.md +1 -5
- package/claude/skills/auto-ship/references/tier-failures.md +1 -1
- package/claude/skills/auto-ship/references/ui-checklist.md +2 -2
- package/claude/skills/auto-ship/references/verb-absent.md +3 -27
- package/claude/skills/backlog-triage/SKILL.md +1 -1
- package/claude/skills/build-in-slices/REQUIREMENT.md +1 -1
- package/claude/skills/build-in-slices/references/adopted.md +1 -1
- package/claude/skills/canon-cli/REQUIREMENT.md +4 -6
- package/claude/skills/canon-cli/SKILL.md +2 -2
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
- package/claude/skills/canon-feedback/SKILL.md +10 -13
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +13 -14
- package/claude/skills/canon-feedback-triage/SKILL.md +50 -29
- package/claude/skills/canvas/REQUIREMENT.md +3 -4
- package/claude/skills/canvas/SKILL.md +5 -5
- package/{standards → claude/skills/canvas/references}/canvas.md +12 -1
- package/claude/skills/ci-workflow/REQUIREMENT.md +0 -1
- package/claude/skills/context-fold/REQUIREMENT.md +8 -20
- package/claude/skills/context-fold/SKILL.md +21 -45
- package/claude/skills/context-fold/references/classify.md +7 -8
- package/claude/skills/context-fold/references/completion-report.md +2 -6
- package/claude/skills/context-fold/references/context-refresh.md +5 -15
- package/claude/skills/context-fold/references/promoted-pages.md +3 -3
- package/claude/skills/context-fold/references/receipt-sweep.md +6 -17
- package/claude/skills/context-fold/references/unusable-baseline.md +4 -6
- package/claude/skills/create-rule/REQUIREMENT.md +0 -1
- package/claude/skills/create-skill/REQUIREMENT.md +6 -4
- package/claude/skills/create-skill/SKILL.md +9 -8
- package/{standards → claude/skills/create-skill/references}/skill-paths.md +1 -0
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +0 -2
- package/claude/skills/design-taste/SKILL.md +2 -4
- package/claude/skills/design-taste/references/vocabulary.md +1 -1
- package/claude/skills/docs-sync/REQUIREMENT.md +3 -3
- package/claude/skills/docs-sync/SKILL.md +1 -1
- package/claude/skills/document-health/REQUIREMENT.md +2 -2
- package/claude/skills/document-health/SKILL.md +4 -5
- package/claude/skills/document-health/references/axes.md +4 -6
- package/claude/skills/draft-and-pick/REQUIREMENT.md +16 -14
- package/claude/skills/draft-and-pick/SKILL.md +40 -32
- package/claude/skills/draft-and-pick/references/live-arms.md +9 -9
- package/claude/skills/draft-doc/REQUIREMENT.md +5 -5
- package/claude/skills/draft-doc/SKILL.md +10 -11
- package/claude/skills/draft-doc/references/requirements.md +61 -0
- package/claude/skills/draft-doc/references/wiki-kind.md +53 -0
- package/claude/skills/draft-doc/references/wiki.md +50 -30
- package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
- package/claude/skills/draft-figure/SKILL.md +5 -5
- package/claude/skills/draft-figure/references/architecture-set.md +4 -4
- package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
- package/claude/skills/draft-figure/references/mermaid-path.md +55 -0
- package/claude/skills/draft-figure/references/mermaid.md +43 -34
- package/claude/skills/draft-identity/REQUIREMENT.md +1 -1
- package/claude/skills/draft-identity/SKILL.md +9 -7
- package/claude/skills/draft-ready/REQUIREMENT.md +1 -1
- package/claude/skills/draft-ready/SKILL.md +6 -6
- package/claude/skills/draft-ready/references/assembly.md +1 -1
- package/{standards → claude/skills/draft-ready/references}/ready.md +13 -5
- package/claude/skills/git-issue/SKILL.md +1 -1
- package/claude/skills/git-issue/references/issue.md +3 -3
- package/claude/skills/git-pr/SKILL.md +1 -1
- package/claude/skills/git-ship/REQUIREMENT.md +0 -2
- package/claude/skills/git-ship/SKILL.md +16 -24
- package/claude/skills/git-stage/SKILL.md +1 -1
- package/claude/skills/markdown-craft/REQUIREMENT.md +23 -0
- package/claude/skills/markdown-craft/SKILL.md +17 -0
- package/{standards → claude/skills/markdown-craft/references}/markdown.md +14 -7
- package/claude/skills/plan-feature/REQUIREMENT.md +2 -1
- package/claude/skills/plan-feature/SKILL.md +14 -8
- package/{standards → claude/skills/plan-feature/references}/plan.md +33 -21
- package/claude/skills/plan-groundwork/SKILL.md +5 -6
- package/{standards → claude/skills/plan-groundwork/references}/groundwork.md +17 -4
- package/claude/skills/plan-intake/SKILL.md +2 -3
- package/{standards → claude/skills/plan-intake/references}/intake.md +17 -2
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -1
- package/claude/skills/review-address/SKILL.md +2 -2
- package/claude/skills/review-address/references/post-review-findings.md +1 -1
- package/claude/skills/review-craft/REQUIREMENT.md +3 -3
- package/claude/skills/review-craft/SKILL.md +2 -2
- package/claude/skills/review-craft/references/ui.md +4 -4
- package/claude/skills/review-pr/REQUIREMENT.md +1 -1
- package/claude/skills/review-pr/SKILL.md +5 -7
- package/claude/skills/review-pr/references/post.md +2 -2
- package/claude/skills/review-ui/REQUIREMENT.md +0 -1
- package/claude/skills/review-ui/SKILL.md +1 -1
- package/claude/skills/review-ui/references/post.md +1 -1
- package/claude/skills/role-orchestrator/REQUIREMENT.md +0 -3
- package/claude/skills/role-orchestrator/SKILL.md +8 -8
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +4 -3
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +7 -10
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +0 -3
- package/claude/skills/role-orchestrator/references/orchestrator-resume.md +1 -1
- package/claude/skills/role-planner/SKILL.md +2 -2
- package/claude/skills/role-reviewer/SKILL.md +1 -1
- package/claude/skills/role-worker/SKILL.md +2 -2
- package/claude/skills/session-compact/REQUIREMENT.md +1 -4
- package/claude/skills/session-compact/SKILL.md +3 -13
- package/claude/skills/session-compact/references/handoff-note.md +2 -2
- package/claude/skills/session-map/REQUIREMENT.md +1 -9
- package/claude/skills/session-map/SKILL.md +8 -16
- package/{standards → claude/skills/session-map/references}/session.md +17 -11
- package/claude/skills/session-relay/REQUIREMENT.md +1 -1
- package/claude/skills/session-resume/REQUIREMENT.md +4 -6
- package/claude/skills/session-resume/SKILL.md +3 -9
- package/claude/skills/session-worktree/SKILL.md +1 -1
- package/claude/skills/session-worktree/references/main-root-writes.md +2 -2
- package/claude/skills/systematic-debugging/REQUIREMENT.md +0 -1
- package/claude/skills/target-check/REQUIREMENT.md +3 -3
- package/claude/skills/target-check/SKILL.md +1 -1
- package/claude/skills/target-check/references/domains.md +1 -1
- package/claude/skills/target-setup/REQUIREMENT.md +4 -4
- package/claude/skills/target-setup/SKILL.md +3 -3
- package/claude/skills/target-setup/references/detect.md +1 -1
- package/claude/skills/target-setup/references/indexes.md +1 -1
- package/claude/skills/task-board/SKILL.md +4 -4
- package/claude/skills/task-board/references/archive.md +1 -1
- package/{standards → claude/skills/task-board/references}/board.md +9 -0
- package/claude/skills/task-board/references/decline.md +1 -1
- package/{standards → claude/skills/task-board/references}/tasks.md +13 -8
- package/claude/skills/teach-workspace/SKILL.md +1 -1
- package/claude/skills/teach-workspace/references/revisit.md +1 -1
- package/{standards → claude/skills/teach-workspace/references}/teach.md +17 -5
- package/claude/skills/test-first/REQUIREMENT.md +1 -1
- package/claude/skills/ux-audit/REQUIREMENT.md +2 -2
- package/claude/skills/ux-audit/SKILL.md +2 -3
- package/claude/skills/write-human/REQUIREMENT.md +0 -1
- package/claude/skills/write-human/SKILL.md +2 -2
- package/docs/agents/audits.md +1 -1
- package/docs/agents/canvas.md +1 -1
- package/docs/agents/commands.md +6 -10
- package/docs/agents/context-audit-checks.md +10 -24
- package/docs/agents/context-audit.md +7 -15
- package/docs/agents/context-classify.md +5 -5
- package/docs/agents/counts.md +5 -5
- package/docs/agents/drift-surfaces.md +2 -2
- package/docs/agents/index.md +3 -4
- package/docs/agents/install-and-sync.md +6 -6
- package/docs/agents/intake.md +1 -1
- package/docs/agents/markdown-audit.md +1 -1
- package/docs/agents/records-stale.md +5 -5
- package/docs/agents/records-validate.md +1 -1
- package/docs/agents/records.md +2 -2
- package/docs/agents/restated.md +1 -1
- package/docs/agents/routing.md +1 -1
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/skills-audit.md +2 -2
- package/docs/agents/state-scoped-risk.md +2 -2
- package/docs/agents/superseded.md +2 -2
- package/docs/agents/tasks-plans.md +2 -2
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/tasks.md +1 -1
- package/docs/agents/teach.md +1 -1
- package/docs/agents/test-order.md +1 -1
- package/docs/agents/upstream.md +94 -0
- package/docs/target/migrations.md +24 -0
- package/docs/target/projects.md +5 -7
- package/docs/target/scenarios.md +2 -2
- package/docs/target/sync.md +3 -3
- package/docs/workflow/ai-workflow.md +7 -38
- package/docs/workflow/index.md +1 -1
- package/docs/workflow/operating-model.md +8 -8
- package/docs/workflow/parallel-features.md +7 -7
- package/docs/workflow/skill-map-on-request.md +13 -16
- package/docs/workflow/skill-map.md +20 -32
- package/docs/workflow/visual-design-workflow.md +11 -14
- package/governance/renames.toml +0 -23
- package/governance/rules/canon/605-worktrees.md +1 -1
- package/governance/rules/{standards → canon}/plan.md +5 -5
- package/governance/rules/canon/tasks.md +18 -0
- package/governance/rules/writing/markdown.md +23 -0
- package/governance/stacks/base.toml +1 -1
- package/package.json +1 -1
- package/scripts/core/install-check.sh +2 -2
- package/scripts/core/regen-web-previews.ts +1 -1
- package/src/audits/catalog.ts +5 -97
- package/src/claude/cases/authoring.ts +5 -18
- package/src/claude/cases/misc.ts +0 -17
- package/src/claude/cases/setup.ts +3 -20
- package/src/claude/cases/workflow.ts +0 -33
- package/src/claude/seeds.ts +1 -7
- package/src/claude/skills-audit.ts +1 -1
- package/src/claude/skills-reach.ts +1 -1
- package/src/cli/help.ts +3 -4
- package/src/cli.ts +2 -4
- package/src/commands/claude.ts +9 -15
- package/src/commands/context.ts +18 -280
- package/src/commands/feedback.ts +22 -91
- package/src/commands/gov.ts +2 -2
- package/src/commands/records.ts +24 -6
- package/src/commands/sync.ts +5 -3
- package/src/commands/upstream.ts +430 -0
- package/src/context/audit.ts +6 -15
- package/src/context/classify/extract.ts +6 -13
- package/src/context/classify/patterns.ts +2 -41
- package/src/context/classify/prompts.ts +2 -6
- package/src/context/classify/run.ts +3 -3
- package/src/context/folders.ts +7 -12
- package/src/context/gate.ts +11 -91
- package/src/context/requirements.ts +13 -4
- package/src/counts/scan.ts +3 -3
- package/src/design/components.ts +2 -2
- package/src/feedback/body.ts +0 -12
- package/src/gate/measures.ts +6 -57
- package/src/gate/stages.ts +0 -1
- package/src/gov/citations.ts +9 -3
- package/src/gov/restated.ts +3 -3
- package/src/markdown/structure.ts +3 -3
- package/src/migrate/record-layout.ts +3 -3
- package/src/records/backup.ts +1 -0
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +2 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/records/validate.ts +55 -0
- package/src/roots/record.ts +2 -1
- package/src/sandbox/scenario.ts +115 -11
- package/src/shipped/references.ts +3 -3
- package/src/standards/read.ts +52 -1
- package/src/sync/seeds-report.ts +1 -1
- package/src/tasks/answers.ts +14 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/stack.ts +176 -0
- package/src/tasks/validate.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/upstream/catalog.ts +112 -0
- package/src/upstream/cursor.ts +120 -0
- package/src/upstream/digest.ts +75 -0
- package/src/upstream/due.ts +128 -0
- package/src/upstream/releases.ts +170 -0
- package/standards/context.md +3 -5
- package/standards/design.md +2 -3
- package/standards/index.md +0 -23
- package/standards/publish.md +1 -1
- package/standards/rule.md +1 -1
- package/standards/skill.md +1 -1
- package/tooling/base/reference.md +1 -1
- package/tooling/claude/reference.md +6 -20
- package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +1 -1
- package/tooling/claude/seeds/.claude/hooks/records-index.sh +8 -20
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +3 -3
- package/tooling/claude/seeds/CLAUDE.md +1 -3
- package/tooling/claude/seeds/canon/REQUIREMENTS.md +2 -9
- package/tooling/web/configs/e2e/screenshot.ts +0 -1
- package/claude/skills/auto-ship/references/review-findings.md +0 -20
- package/claude/skills/bash-cli-script/REQUIREMENT.md +0 -43
- package/claude/skills/bash-cli-script/SKILL.md +0 -53
- package/claude/skills/bash-cli-script/references/template.md +0 -19
- package/claude/skills/canon-operator/REQUIREMENT.md +0 -62
- package/claude/skills/canon-operator/SKILL.md +0 -113
- package/claude/skills/canon-rollout/REQUIREMENT.md +0 -59
- package/claude/skills/canon-rollout/SKILL.md +0 -140
- package/claude/skills/canon-rollout/references/failure-recovery.md +0 -20
- package/claude/skills/context-fold/references/anchor-sweep.md +0 -58
- package/claude/skills/context-fold/references/wireframe-sweep.md +0 -45
- package/claude/skills/create-standard/REQUIREMENT.md +0 -33
- package/claude/skills/create-standard/SKILL.md +0 -31
- package/claude/skills/design-extract/REQUIREMENT.md +0 -42
- package/claude/skills/design-extract/SKILL.md +0 -108
- package/claude/skills/draft-and-pick/references/frame.html +0 -118
- package/claude/skills/draft-doc/references/wireframes.md +0 -48
- package/claude/skills/index-lookup/REQUIREMENT.md +0 -35
- package/claude/skills/index-lookup/SKILL.md +0 -44
- package/claude/skills/markdown-propose/REQUIREMENT.md +0 -47
- package/claude/skills/markdown-propose/SKILL.md +0 -122
- package/claude/skills/markdown-propose/references/format.md +0 -118
- package/claude/skills/memory-capture/REQUIREMENT.md +0 -50
- package/claude/skills/memory-capture/SKILL.md +0 -117
- package/claude/skills/memory-review/REQUIREMENT.md +0 -52
- package/claude/skills/memory-review/SKILL.md +0 -129
- package/claude/skills/memory-review/references/later-phases.md +0 -118
- package/claude/skills/memory-review/references/receipt-format.md +0 -64
- package/claude/skills/project-commands/REQUIREMENT.md +0 -42
- package/claude/skills/project-commands/SKILL.md +0 -87
- package/claude/skills/repo-metadata/REQUIREMENT.md +0 -37
- package/claude/skills/repo-metadata/SKILL.md +0 -54
- package/claude/skills/restate-plainly/REQUIREMENT.md +0 -41
- package/claude/skills/restate-plainly/SKILL.md +0 -41
- package/claude/skills/review-branch/REQUIREMENT.md +0 -40
- package/claude/skills/review-branch/SKILL.md +0 -137
- package/claude/skills/seed-sync/REQUIREMENT.md +0 -45
- package/claude/skills/seed-sync/SKILL.md +0 -158
- package/claude/skills/sketch-design/REQUIREMENT.md +0 -38
- package/claude/skills/sketch-design/SKILL.md +0 -94
- package/claude/skills/ux-walkthrough/REQUIREMENT.md +0 -56
- package/claude/skills/ux-walkthrough/SKILL.md +0 -53
- package/claude/skills/ux-walkthrough/references/builds.md +0 -15
- package/claude/skills/ux-walkthrough/references/candidate-pages.md +0 -11
- package/claude/skills/ux-walkthrough/references/measuring.md +0 -22
- package/claude/skills/ux-walkthrough/references/record.md +0 -36
- package/claude/skills/ux-walkthrough/references/relay.md +0 -25
- package/docs/agents/architecture-record.md +0 -36
- package/docs/agents/review-classification.md +0 -76
- package/governance/rules/canon/601-indexes.md +0 -9
- package/governance/rules/canon/603-memory.md +0 -11
- package/governance/rules/claude/565-behavior.md +0 -27
- package/governance/rules/claude/567-planning.md +0 -16
- package/governance/rules/claude/575-hooks.md +0 -16
- package/governance/rules/claude/576-settings.md +0 -13
- package/governance/rules/claude/592-claude-md.md +0 -19
- package/governance/rules/lang/120-bash.md +0 -18
- package/governance/rules/standards/architecture.md +0 -13
- package/governance/rules/standards/groundwork.md +0 -13
- package/governance/rules/standards/intake.md +0 -12
- package/governance/rules/standards/markdown.md +0 -16
- package/governance/rules/standards/memory.md +0 -12
- package/governance/rules/standards/ready.md +0 -13
- package/governance/rules/standards/requirements.md +0 -12
- package/governance/rules/standards/session.md +0 -13
- package/governance/rules/standards/tasks.md +0 -15
- package/governance/rules/standards/teach.md +0 -14
- package/governance/rules/standards/wiki.md +0 -13
- package/governance/rules/standards/wireframes.md +0 -13
- package/governance/rules/writing/800-prose.md +0 -15
- package/src/autoship/classify.ts +0 -75
- package/src/autoship/paths.ts +0 -50
- package/src/commands/autoship.ts +0 -129
- package/src/commands/repo.ts +0 -393
- package/src/context/architecture.ts +0 -654
- package/src/context/wireframe-states.ts +0 -238
- package/src/repo/metadata.ts +0 -206
- package/standards/architecture.md +0 -127
- package/standards/memory.md +0 -158
- package/standards/mermaid.md +0 -64
- package/standards/requirements.md +0 -79
- package/standards/wiki.md +0 -77
- package/standards/wireframes.md +0 -143
- package/tooling/claude/seeds/.claude/memory/index.md +0 -8
- package/tooling/claude/seeds/canon/ARCHITECTURE.md +0 -23
- package/tooling/claude/seeds/canon/wireframes/index.md +0 -8
- /package/{standards → claude/skills/create-skill/references}/skill-practice.md +0 -0
- /package/{standards → claude/skills/create-skill/references}/skill-requirement.md +0 -0
- /package/{standards → claude/skills/plan-feature/references}/plan-lifecycle.md +0 -0
- /package/{standards → claude/skills/plan-groundwork/references}/groundwork-spikes.md +0 -0
- /package/{standards → claude/skills/task-board/references}/tasks-decline.md +0 -0
- /package/{standards → claude/skills/teach-workspace/references}/glossary.md +0 -0
package/src/commands/feedback.ts
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import type { Command } from 'commander'
|
|
1
|
+
import { Option, type Command } from 'commander'
|
|
4
2
|
import {
|
|
5
3
|
deriveDomainLabels,
|
|
6
|
-
deriveSlug,
|
|
7
4
|
deriveTitle,
|
|
8
5
|
missingField,
|
|
9
6
|
missingFieldMessage,
|
|
10
7
|
} from '@/feedback/body'
|
|
11
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
12
|
-
import { creationRel } from '@/roots/record'
|
|
13
8
|
import { createGithubIssue } from '@/feedback/github'
|
|
14
9
|
import { issueFailureMessage } from '@/feedback/github-format'
|
|
15
|
-
import { frameError, frameSuccess
|
|
10
|
+
import { frameError, frameSuccess } from '@/cli/ui'
|
|
16
11
|
|
|
17
12
|
function readStdin(): Promise<string> {
|
|
18
13
|
return new Promise((resolveStream, rejectStream) => {
|
|
@@ -25,62 +20,17 @@ function readStdin(): Promise<string> {
|
|
|
25
20
|
})
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
function timestamp(): string {
|
|
29
|
-
const d = new Date()
|
|
30
|
-
const pad = (n: number): string => n.toString().padStart(2, '0')
|
|
31
|
-
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* A registry install ships no `.claude/`, so a local write would land inside
|
|
36
|
-
* `node_modules/` and report a project-relative path the operator cannot find.
|
|
37
|
-
* The toolkit source is the only root where local scratch reaches a maintainer.
|
|
38
|
-
*/
|
|
39
|
-
function isToolkitSource(): boolean {
|
|
40
|
-
return existsSync(join(PROJECT_ROOT, '.claude'))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* One producer, one record folder. Feedback is not a review, so it writes
|
|
45
|
-
* under a root folder of its own rather than inside `review/`, and `canon
|
|
46
|
-
* records push` backs it with no list edit, since it carries every `.canon/`
|
|
47
|
-
* entry it does not exclude.
|
|
48
|
-
*/
|
|
49
|
-
function writeLocal(body: string, mismatch: string | undefined): string {
|
|
50
|
-
// Resolved against the same root the write joins onto. `creationRel` reads
|
|
51
|
-
// whichever root already carries a `feedback/` folder and falls back to the
|
|
52
|
-
// creation root when neither does.
|
|
53
|
-
const relativeDir = creationRel(PROJECT_ROOT, 'feedback')
|
|
54
|
-
const feedbackDir = join(PROJECT_ROOT, relativeDir)
|
|
55
|
-
mkdirSync(feedbackDir, { recursive: true })
|
|
56
|
-
const filename = `feedback-${deriveSlug(body)}-${timestamp()}.md`
|
|
57
|
-
const filePath = join(feedbackDir, filename)
|
|
58
|
-
writeFileSync(filePath, `${body}\n`, 'utf8')
|
|
59
|
-
|
|
60
|
-
// Manual frame rather than `frameSuccess`, since the mismatch warning is a
|
|
61
|
-
// frame-interior line landing between the opener and the command title,
|
|
62
|
-
// matching the convention `#1587` set at its other six sites.
|
|
63
|
-
const { GREEN, GREY, NC, WHITE } = palette(process.stderr)
|
|
64
|
-
process.stderr.write(`${GREY}┌${NC}\n`)
|
|
65
|
-
if (mismatch !== undefined) logWarn(mismatch)
|
|
66
|
-
process.stderr.write(
|
|
67
|
-
`${GREY}│${NC} ${WHITE}canon feedback${NC}\n${GREY}│${NC}\n${GREY}│${NC} ${GREEN}✓${NC} ${join(relativeDir, filename)}\n${GREY}└${NC}\n`,
|
|
68
|
-
)
|
|
69
|
-
return filePath
|
|
70
|
-
}
|
|
71
|
-
|
|
72
23
|
export function register(program: Command): void {
|
|
73
24
|
program
|
|
74
25
|
.command('feedback')
|
|
75
26
|
.helpOption('-h, --help', 'Show this help message')
|
|
76
27
|
.description(
|
|
77
|
-
'
|
|
78
|
-
)
|
|
79
|
-
.option(
|
|
80
|
-
'--github',
|
|
81
|
-
'Open a GitHub issue on the toolkit repo instead of writing local scratch',
|
|
28
|
+
'Open a GitHub issue on the toolkit repo from a feedback block on stdin',
|
|
82
29
|
)
|
|
83
|
-
|
|
30
|
+
// A cached plugin still pipes `--github`, and commander rejects an unknown
|
|
31
|
+
// option, so the flag stays accepted and hidden for one release.
|
|
32
|
+
.addOption(new Option('--github').hideHelp())
|
|
33
|
+
.action(async () => {
|
|
84
34
|
if (process.stdin.isTTY) {
|
|
85
35
|
frameError(
|
|
86
36
|
'No feedback on stdin. Pipe a markdown block: pbpaste | canon feedback',
|
|
@@ -95,8 +45,8 @@ export function register(program: Command): void {
|
|
|
95
45
|
return
|
|
96
46
|
}
|
|
97
47
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
48
|
+
// A report missing its surface is no more useful in the tracker than
|
|
49
|
+
// anywhere else, so validation runs before any `gh` call.
|
|
100
50
|
const absent = missingField(body)
|
|
101
51
|
if (absent) {
|
|
102
52
|
frameError(missingFieldMessage(absent))
|
|
@@ -104,39 +54,20 @@ export function register(program: Command): void {
|
|
|
104
54
|
return
|
|
105
55
|
}
|
|
106
56
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
process.stdout.write(`${result.url}\n`)
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
const reason = issueFailureMessage(result)
|
|
119
|
-
if (!isToolkitSource()) {
|
|
120
|
-
frameError(reason)
|
|
121
|
-
process.exitCode = 1
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
const { NC, YELLOW } = palette(process.stderr)
|
|
125
|
-
process.stderr.write(
|
|
126
|
-
`${YELLOW}! ${reason} Wrote local scratch instead.${NC}\n`,
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (!isToolkitSource()) {
|
|
131
|
-
frameError(
|
|
132
|
-
'Local scratch needs the toolkit source. Re-run with --github to open an issue instead.',
|
|
133
|
-
)
|
|
134
|
-
process.exitCode = 1
|
|
57
|
+
const result = await createGithubIssue({
|
|
58
|
+
title: deriveTitle(body),
|
|
59
|
+
body,
|
|
60
|
+
labels: ['feedback', ...deriveDomainLabels(body)],
|
|
61
|
+
})
|
|
62
|
+
if (result.ok) {
|
|
63
|
+
frameSuccess('canon feedback', result.url)
|
|
64
|
+
process.stdout.write(`${result.url}\n`)
|
|
135
65
|
return
|
|
136
66
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
process.stdout.write(`${
|
|
67
|
+
// The block goes to stdout so the session can paste it where the issue
|
|
68
|
+
// would have gone, and the reason frames on stderr.
|
|
69
|
+
frameError(issueFailureMessage(result))
|
|
70
|
+
process.stdout.write(`${body}\n`)
|
|
71
|
+
process.exitCode = 1
|
|
141
72
|
})
|
|
142
73
|
}
|
package/src/commands/gov.ts
CHANGED
|
@@ -239,8 +239,8 @@ export function register(program: Command): void {
|
|
|
239
239
|
'after',
|
|
240
240
|
[
|
|
241
241
|
'',
|
|
242
|
-
'Measures the
|
|
243
|
-
'
|
|
242
|
+
'Measures the test-first order the test-first skill and auto-ship hold: the',
|
|
243
|
+
'test before the code. It reports and never gates, because pairing',
|
|
244
244
|
'a test to an implementation is a judgment.',
|
|
245
245
|
'',
|
|
246
246
|
'Coverage:',
|
package/src/commands/records.ts
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
plural,
|
|
58
58
|
} from '@/cli/ui'
|
|
59
59
|
import { currentWorktreeRoot, mainWorktreeRoot } from '@/git/worktree'
|
|
60
|
+
import { readLivePlans, stackFindings } from '@/tasks/stack'
|
|
60
61
|
|
|
61
62
|
/** Returned when a record carries a finding, which is the gating result. */
|
|
62
63
|
const EXIT_FINDINGS = 2
|
|
@@ -299,10 +300,10 @@ export function register(program: Command): void {
|
|
|
299
300
|
'Entries are ordered due first, then those citing a missing path, then the',
|
|
300
301
|
'longest unreviewed, then by name, so a review takes the first N as a batch.',
|
|
301
302
|
'',
|
|
302
|
-
'canonical: reads REQUIREMENTS.md
|
|
303
|
-
'
|
|
304
|
-
'
|
|
305
|
-
'
|
|
303
|
+
'canonical: reads REQUIREMENTS.md at whichever surface root carries it,',
|
|
304
|
+
'in the current worktree since it is tracked. It reports its reviewed date',
|
|
305
|
+
'and the release tags merged into HEAD after that day, or never when it',
|
|
306
|
+
'carries no date. --days does not apply.',
|
|
306
307
|
'',
|
|
307
308
|
'Exit codes:',
|
|
308
309
|
' 0 the reading completed, whatever it found',
|
|
@@ -644,7 +645,7 @@ async function runStaleCanonical(
|
|
|
644
645
|
logStep('Canonical')
|
|
645
646
|
|
|
646
647
|
if (outcome.docs.length === 0) {
|
|
647
|
-
logInfo('
|
|
648
|
+
logInfo('REQUIREMENTS.md does not exist')
|
|
648
649
|
} else {
|
|
649
650
|
for (const doc of outcome.docs) logInfo(describeCanonical(doc, outcome))
|
|
650
651
|
}
|
|
@@ -911,7 +912,24 @@ async function runValidate(
|
|
|
911
912
|
|
|
912
913
|
const root = opts.root ?? (await defaultRoot(kind))
|
|
913
914
|
|
|
914
|
-
return report(await
|
|
915
|
+
return report(await validateWithStacks(root, kind), emitJson, root)
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Appends the one plans finding that reads across files. It sits in the command
|
|
920
|
+
* layer because `src/records/` sits below `src/tasks/`, and `migrate` iterates
|
|
921
|
+
* `validateRecords` alone, so it never meets a finding with no safe repair.
|
|
922
|
+
*/
|
|
923
|
+
async function validateWithStacks(
|
|
924
|
+
root: string,
|
|
925
|
+
kind: RecordKind,
|
|
926
|
+
): Promise<ValidateOutcome> {
|
|
927
|
+
const outcome = await validateRecords(root, kind)
|
|
928
|
+
if (!outcome.ok || kind !== 'plans') return outcome
|
|
929
|
+
|
|
930
|
+
const stacked = stackFindings(await readLivePlans(root))
|
|
931
|
+
|
|
932
|
+
return { ...outcome, findings: [...outcome.findings, ...stacked] }
|
|
915
933
|
}
|
|
916
934
|
|
|
917
935
|
function report(
|
package/src/commands/sync.ts
CHANGED
|
@@ -282,7 +282,7 @@ function renderTooling(report: CheckReport): void {
|
|
|
282
282
|
/**
|
|
283
283
|
* Seeds print their own section because no sync command applies them. A `stale`
|
|
284
284
|
* seed is safe to take whole and a `drifted` one holds edits, which is the split
|
|
285
|
-
*
|
|
285
|
+
* that decides what needs a hand merge.
|
|
286
286
|
*/
|
|
287
287
|
function renderSeeds(report: CheckReport): void {
|
|
288
288
|
const notable = report.seeds.entries.filter(
|
|
@@ -300,7 +300,9 @@ function renderSeeds(report: CheckReport): void {
|
|
|
300
300
|
logWarn(`${entry.rel} (${entry.state})`)
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
logInfo(
|
|
303
|
+
logInfo(
|
|
304
|
+
'A stale seed is safe to take whole. Merge a drifted one by hand against the toolkit copy.',
|
|
305
|
+
)
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
/**
|
|
@@ -401,7 +403,7 @@ async function runSync(target: string): Promise<number> {
|
|
|
401
403
|
|
|
402
404
|
if (existsSync(join(resolved, '.claude'))) {
|
|
403
405
|
process.stderr.write(
|
|
404
|
-
`${GREY}Tip:
|
|
406
|
+
`${GREY}Tip: merge a drifted seed by hand against the toolkit copy, and take a stale one whole.${NC}\n`,
|
|
405
407
|
)
|
|
406
408
|
}
|
|
407
409
|
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { $ } from 'bun'
|
|
3
|
+
import type { Command } from 'commander'
|
|
4
|
+
import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
|
|
5
|
+
import { currentWorktreeRoot, mainWorktreeRoot } from '@/git/worktree'
|
|
6
|
+
import { buildCatalog, renderCatalog } from '@/upstream/catalog'
|
|
7
|
+
import {
|
|
8
|
+
advanceCursor,
|
|
9
|
+
compareVersions,
|
|
10
|
+
llmsDifference,
|
|
11
|
+
readCursor,
|
|
12
|
+
readLastLlms,
|
|
13
|
+
} from '@/upstream/cursor'
|
|
14
|
+
import { buildDigest } from '@/upstream/digest'
|
|
15
|
+
import {
|
|
16
|
+
decideDue,
|
|
17
|
+
passesVocabulary,
|
|
18
|
+
readDueCache,
|
|
19
|
+
writeDueCache,
|
|
20
|
+
type DueReason,
|
|
21
|
+
type Gap,
|
|
22
|
+
} from '@/upstream/due'
|
|
23
|
+
import {
|
|
24
|
+
githubPageFetcher,
|
|
25
|
+
grepHint,
|
|
26
|
+
keepLines,
|
|
27
|
+
walkReleases,
|
|
28
|
+
type FetchFailure,
|
|
29
|
+
} from '@/upstream/releases'
|
|
30
|
+
|
|
31
|
+
const LLMS_URL = 'https://code.claude.com/docs/llms.txt'
|
|
32
|
+
const VERSION = /^\d+(\.\d+)*$/
|
|
33
|
+
const READ_BUDGET_MS = 5000
|
|
34
|
+
|
|
35
|
+
interface RootOption {
|
|
36
|
+
readonly root?: string
|
|
37
|
+
readonly json?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface FetchOptions extends RootOption {
|
|
41
|
+
readonly since?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface AdvanceOptions extends RootOption {
|
|
45
|
+
readonly intake: string
|
|
46
|
+
readonly llms: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Refusal =
|
|
50
|
+
| 'no-cursor'
|
|
51
|
+
| 'bad-since'
|
|
52
|
+
| 'cursor-missing'
|
|
53
|
+
| FetchFailure
|
|
54
|
+
| 'older-than-cursor'
|
|
55
|
+
| 'invalid-version'
|
|
56
|
+
|
|
57
|
+
const REFUSALS: Record<Refusal, string> = {
|
|
58
|
+
'no-cursor':
|
|
59
|
+
'No cursor is stored, so there is no range to read. Pass --since <version> with the last version a digest covered.',
|
|
60
|
+
'bad-since': 'The --since value is not a dotted version such as 2.1.256.',
|
|
61
|
+
'cursor-missing':
|
|
62
|
+
'The releases feed ran out before reaching a release at or below the cursor, so the range cannot be bounded. Pass --since with a more recent version.',
|
|
63
|
+
'rate-limited':
|
|
64
|
+
'GitHub refused the request. Set GH_TOKEN or GITHUB_TOKEN to lift the hourly limit.',
|
|
65
|
+
network: 'The releases feed could not be reached.',
|
|
66
|
+
http: 'GitHub answered with an error for the releases feed.',
|
|
67
|
+
'older-than-cursor':
|
|
68
|
+
'The version is older than the stored cursor, and the cursor never moves back.',
|
|
69
|
+
'invalid-version': 'The version is not a dotted number such as 2.1.289.',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function register(program: Command): void {
|
|
73
|
+
const upstream = program
|
|
74
|
+
.command('upstream')
|
|
75
|
+
.description(
|
|
76
|
+
'Read Claude Code releases against the toolkit (fetch, catalog, advance)',
|
|
77
|
+
)
|
|
78
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
79
|
+
.addHelpText(
|
|
80
|
+
'after',
|
|
81
|
+
[
|
|
82
|
+
'',
|
|
83
|
+
'Ships in the published CLI as `canon gate` and `canon sandbox` do, but',
|
|
84
|
+
'only the toolkit repository has the skill that drives it, so a target',
|
|
85
|
+
'has no use for these verbs.',
|
|
86
|
+
'',
|
|
87
|
+
].join('\n'),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
upstream
|
|
91
|
+
.command('fetch')
|
|
92
|
+
.description(
|
|
93
|
+
'List the release lines since the cursor, with the mechanical noise dropped',
|
|
94
|
+
)
|
|
95
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
96
|
+
.option('--since <version>', 'Read from this version instead of the cursor')
|
|
97
|
+
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
98
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
99
|
+
.addHelpText(
|
|
100
|
+
'after',
|
|
101
|
+
[
|
|
102
|
+
'',
|
|
103
|
+
'Writes nothing. The cursor moves only through `upstream advance`, so a',
|
|
104
|
+
'run that dies midway reads the same range next time.',
|
|
105
|
+
'',
|
|
106
|
+
'Exit codes:',
|
|
107
|
+
' 0 the range was read, possibly empty',
|
|
108
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
109
|
+
'',
|
|
110
|
+
'Examples:',
|
|
111
|
+
' canon upstream fetch --json',
|
|
112
|
+
' canon upstream fetch --since 2.1.256 --json',
|
|
113
|
+
'',
|
|
114
|
+
].join('\n'),
|
|
115
|
+
)
|
|
116
|
+
.action(async (opts: FetchOptions) => {
|
|
117
|
+
process.exitCode = await runFetch(opts)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
upstream
|
|
121
|
+
.command('catalog')
|
|
122
|
+
.description("List the toolkit's own mechanisms, one line each")
|
|
123
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
124
|
+
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
125
|
+
.option('--json', 'Emit the sections as a JSON record instead of text')
|
|
126
|
+
.addHelpText(
|
|
127
|
+
'after',
|
|
128
|
+
[
|
|
129
|
+
'',
|
|
130
|
+
'Writes nothing. Skills, verbs, hooks, and gate stages are read from the',
|
|
131
|
+
'tree, so the list cannot drift from it. A row that exists but cannot be',
|
|
132
|
+
'described is named under Gaps.',
|
|
133
|
+
'',
|
|
134
|
+
'Examples:',
|
|
135
|
+
' canon upstream catalog',
|
|
136
|
+
' canon upstream catalog --json',
|
|
137
|
+
'',
|
|
138
|
+
].join('\n'),
|
|
139
|
+
)
|
|
140
|
+
.action(async (opts: RootOption) => {
|
|
141
|
+
process.exitCode = await runCatalog(opts)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
upstream
|
|
145
|
+
.command('due')
|
|
146
|
+
.description('Say whether a digest is due, from the cursor and the install')
|
|
147
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
148
|
+
.option('--root <path>', 'Repository to read, defaulting to the main root')
|
|
149
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
150
|
+
.addHelpText(
|
|
151
|
+
'after',
|
|
152
|
+
[
|
|
153
|
+
'',
|
|
154
|
+
'Due when the cursor is a week old and the installed version is ahead of',
|
|
155
|
+
'it, or sooner when an Added line in the gap names a canon surface. The',
|
|
156
|
+
'gap is read from GitHub at most once a day and cached under the scratch',
|
|
157
|
+
'folder, and a failed read is held for the day as well.',
|
|
158
|
+
'',
|
|
159
|
+
'Exit codes:',
|
|
160
|
+
' 0 answered, whether or not a digest is due',
|
|
161
|
+
'',
|
|
162
|
+
'Examples:',
|
|
163
|
+
' canon upstream due --json',
|
|
164
|
+
'',
|
|
165
|
+
].join('\n'),
|
|
166
|
+
)
|
|
167
|
+
.action(async (opts: RootOption) => {
|
|
168
|
+
process.exitCode = await runDue(opts)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
upstream
|
|
172
|
+
.command('advance')
|
|
173
|
+
.description('Move the cursor to a version once its digest is filed')
|
|
174
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
175
|
+
.argument('<version>', 'Last version the digest covered')
|
|
176
|
+
.requiredOption('--intake <slug>', 'Intake folder the digest was filed in')
|
|
177
|
+
.option('--no-llms', 'Keep the stored llms.txt rather than fetching it')
|
|
178
|
+
.option('--root <path>', 'Repository to write, defaulting to the cwd')
|
|
179
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
180
|
+
.addHelpText(
|
|
181
|
+
'after',
|
|
182
|
+
[
|
|
183
|
+
'',
|
|
184
|
+
'Writes the cursor and the current llms.txt under the upstream record',
|
|
185
|
+
'folder through a temp file and a rename. It refuses a version older than',
|
|
186
|
+
'the stored cursor.',
|
|
187
|
+
'',
|
|
188
|
+
'Exit codes:',
|
|
189
|
+
' 0 the cursor moved',
|
|
190
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
191
|
+
'',
|
|
192
|
+
'Examples:',
|
|
193
|
+
' canon upstream advance 2.1.289 --intake claude-code-2-1-257-to-2-1-289',
|
|
194
|
+
'',
|
|
195
|
+
].join('\n'),
|
|
196
|
+
)
|
|
197
|
+
.action(async (version: string, opts: AdvanceOptions) => {
|
|
198
|
+
process.exitCode = await runAdvance(version, opts)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function refuse(
|
|
203
|
+
reason: Refusal,
|
|
204
|
+
extra: Record<string, unknown>,
|
|
205
|
+
emitJson: boolean,
|
|
206
|
+
): number {
|
|
207
|
+
logStep('Refused')
|
|
208
|
+
logWarn(REFUSALS[reason])
|
|
209
|
+
outro()
|
|
210
|
+
|
|
211
|
+
if (emitJson) {
|
|
212
|
+
process.stdout.write(
|
|
213
|
+
`${JSON.stringify({ ...extra, reason, message: REFUSALS[reason] })}\n`,
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return 1
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function fetchLlms(): Promise<string | null> {
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(LLMS_URL)
|
|
223
|
+
|
|
224
|
+
return response.ok ? await response.text() : null
|
|
225
|
+
} catch {
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function runFetch(opts: FetchOptions): Promise<number> {
|
|
231
|
+
const emitJson = opts.json ?? false
|
|
232
|
+
const cursorRoot = opts.root ? resolve(opts.root) : await mainWorktreeRoot()
|
|
233
|
+
const readRoot = opts.root ? resolve(opts.root) : await currentWorktreeRoot()
|
|
234
|
+
|
|
235
|
+
intro('canon upstream fetch')
|
|
236
|
+
|
|
237
|
+
if (opts.since !== undefined && !VERSION.test(opts.since)) {
|
|
238
|
+
return refuse('bad-since', { since: opts.since }, emitJson)
|
|
239
|
+
}
|
|
240
|
+
const cursor = opts.since ?? readCursor(cursorRoot)?.version
|
|
241
|
+
if (!cursor) return refuse('no-cursor', {}, emitJson)
|
|
242
|
+
|
|
243
|
+
const digest = await buildDigest(cursor, githubPageFetcher(), (name) =>
|
|
244
|
+
grepHint(readRoot, name),
|
|
245
|
+
)
|
|
246
|
+
if (digest.kind === 'failed') {
|
|
247
|
+
return refuse(digest.reason, {}, emitJson)
|
|
248
|
+
}
|
|
249
|
+
if (digest.kind === 'cursor-missing') {
|
|
250
|
+
return refuse('cursor-missing', { cursor: digest.cursor }, emitJson)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const llms = await fetchLlms()
|
|
254
|
+
const difference =
|
|
255
|
+
llms === null ? null : llmsDifference(readLastLlms(cursorRoot), llms)
|
|
256
|
+
|
|
257
|
+
logStep('Range')
|
|
258
|
+
logInfo(
|
|
259
|
+
`${digest.from} to ${digest.to}, ${plural(digest.releases, 'release')}, ${plural(digest.lines.length, 'line')} kept`,
|
|
260
|
+
)
|
|
261
|
+
if (difference === null) logWarn('llms.txt could not be fetched')
|
|
262
|
+
outro()
|
|
263
|
+
|
|
264
|
+
if (!emitJson) {
|
|
265
|
+
for (const line of digest.lines) {
|
|
266
|
+
process.stdout.write(`${line.version}\t${line.text}\n`)
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
process.stdout.write(
|
|
270
|
+
`${JSON.stringify({
|
|
271
|
+
from: digest.from,
|
|
272
|
+
to: digest.to,
|
|
273
|
+
releases: digest.releases,
|
|
274
|
+
lines: digest.lines,
|
|
275
|
+
llms: difference,
|
|
276
|
+
})}\n`,
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return 0
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const DUE_TEXT: Record<DueReason, string> = {
|
|
284
|
+
'no-cursor': 'No digest has been run, so there is no cursor.',
|
|
285
|
+
week: 'A week has passed since the last digest.',
|
|
286
|
+
vocabulary: 'A release since the last digest added something canon may use.',
|
|
287
|
+
current: 'The installed version is at or below the cursor.',
|
|
288
|
+
recent:
|
|
289
|
+
'The last digest is under a week old and nothing in the gap stands out.',
|
|
290
|
+
'unknown-version': 'The installed version could not be read.',
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function installedVersion(): Promise<string | null> {
|
|
294
|
+
try {
|
|
295
|
+
const result = await $`claude --version`.quiet().nothrow()
|
|
296
|
+
const match = /^\d+(\.\d+)*/.exec(result.stdout.toString().trim())
|
|
297
|
+
|
|
298
|
+
return match ? match[0] : null
|
|
299
|
+
} catch {
|
|
300
|
+
return null
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// A failed read is stored as a null gap, so a rate limit hit by one session of
|
|
305
|
+
// a wave is not retried by the nineteen after it.
|
|
306
|
+
async function readGap(
|
|
307
|
+
root: string,
|
|
308
|
+
installed: string,
|
|
309
|
+
cursor: string,
|
|
310
|
+
now: Date,
|
|
311
|
+
): Promise<Gap | null> {
|
|
312
|
+
const cached = readDueCache(root, installed, cursor, now)
|
|
313
|
+
if (cached) return cached.gap
|
|
314
|
+
|
|
315
|
+
// The hook that calls this has a 10 second timeout, and a network that drops
|
|
316
|
+
// packets would otherwise hold every session start to it with nothing cached.
|
|
317
|
+
const walk = await walkReleases(
|
|
318
|
+
cursor,
|
|
319
|
+
githubPageFetcher(process.env, AbortSignal.timeout(READ_BUDGET_MS)),
|
|
320
|
+
)
|
|
321
|
+
const gap: Gap | null =
|
|
322
|
+
walk.kind === 'reached'
|
|
323
|
+
? {
|
|
324
|
+
releases: walk.releases.length,
|
|
325
|
+
lines: walk.releases
|
|
326
|
+
.flatMap(keepLines)
|
|
327
|
+
.map((line) => line.text)
|
|
328
|
+
.filter(passesVocabulary),
|
|
329
|
+
}
|
|
330
|
+
: null
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
await writeDueCache(root, {
|
|
334
|
+
checkedAt: now.toISOString(),
|
|
335
|
+
installed,
|
|
336
|
+
cursor,
|
|
337
|
+
gap,
|
|
338
|
+
})
|
|
339
|
+
} catch {
|
|
340
|
+
// A cache that cannot be written costs a request, not a wrong answer.
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return gap
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function runDue(opts: RootOption): Promise<number> {
|
|
347
|
+
const root = opts.root ? resolve(opts.root) : await mainWorktreeRoot()
|
|
348
|
+
const now = new Date()
|
|
349
|
+
const cursor = readCursor(root)
|
|
350
|
+
const installed = await installedVersion()
|
|
351
|
+
const isAhead =
|
|
352
|
+
cursor !== null &&
|
|
353
|
+
installed !== null &&
|
|
354
|
+
compareVersions(installed, cursor.version) > 0
|
|
355
|
+
const gap = isAhead
|
|
356
|
+
? await readGap(root, installed, cursor.version, now)
|
|
357
|
+
: null
|
|
358
|
+
const result = decideDue({ installed, cursor, gap, now })
|
|
359
|
+
|
|
360
|
+
intro('canon upstream due')
|
|
361
|
+
logStep(result.due ? 'Due' : 'Not due')
|
|
362
|
+
logInfo(DUE_TEXT[result.reason])
|
|
363
|
+
outro()
|
|
364
|
+
|
|
365
|
+
if (opts.json) {
|
|
366
|
+
process.stdout.write(
|
|
367
|
+
`${JSON.stringify({
|
|
368
|
+
...result,
|
|
369
|
+
installed,
|
|
370
|
+
cursor: cursor?.version ?? null,
|
|
371
|
+
})}\n`,
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return 0
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function runCatalog(opts: RootOption): Promise<number> {
|
|
379
|
+
const root = opts.root ? resolve(opts.root) : await currentWorktreeRoot()
|
|
380
|
+
const catalog = buildCatalog(root)
|
|
381
|
+
|
|
382
|
+
if (opts.json) {
|
|
383
|
+
process.stdout.write(`${JSON.stringify(catalog)}\n`)
|
|
384
|
+
} else {
|
|
385
|
+
process.stdout.write(`${renderCatalog(catalog)}\n`)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return 0
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function runAdvance(
|
|
392
|
+
version: string,
|
|
393
|
+
opts: AdvanceOptions,
|
|
394
|
+
): Promise<number> {
|
|
395
|
+
const emitJson = opts.json ?? false
|
|
396
|
+
const root = opts.root ? resolve(opts.root) : await mainWorktreeRoot()
|
|
397
|
+
|
|
398
|
+
intro('canon upstream advance')
|
|
399
|
+
|
|
400
|
+
const llms = opts.llms ? await fetchLlms() : null
|
|
401
|
+
const outcome = await advanceCursor(root, {
|
|
402
|
+
version,
|
|
403
|
+
intake: opts.intake,
|
|
404
|
+
today: new Date().toISOString().slice(0, 10),
|
|
405
|
+
llms,
|
|
406
|
+
})
|
|
407
|
+
if (outcome.kind === 'refused') {
|
|
408
|
+
return refuse(outcome.reason, { version }, emitJson)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
logStep('Cursor')
|
|
412
|
+
logInfo(`Moved to ${version}, filed as ${opts.intake}`)
|
|
413
|
+
if (opts.llms && llms === null) {
|
|
414
|
+
logWarn('llms.txt could not be fetched, so the stored copy is unchanged')
|
|
415
|
+
}
|
|
416
|
+
outro()
|
|
417
|
+
|
|
418
|
+
if (emitJson) {
|
|
419
|
+
process.stdout.write(
|
|
420
|
+
`${JSON.stringify({
|
|
421
|
+
version,
|
|
422
|
+
intake: opts.intake,
|
|
423
|
+
advanced: true,
|
|
424
|
+
llms: llms === null ? 'kept' : 'stored',
|
|
425
|
+
})}\n`,
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return 0
|
|
430
|
+
}
|