@erclx/canon 5.31.0 → 5.33.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/auto-ship/references/worktree-name.md +1 -1
- package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
- package/claude/skills/backlog-triage/SKILL.md +2 -2
- package/claude/skills/canon-cli/REQUIREMENT.md +19 -18
- package/claude/skills/canon-cli/SKILL.md +75 -32
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
- package/claude/skills/canon-feedback/SKILL.md +11 -14
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +13 -14
- package/claude/skills/canon-feedback-triage/SKILL.md +49 -30
- package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +5 -6
- package/claude/skills/{canvas → canvas-board}/SKILL.md +7 -7
- package/{standards → claude/skills/canvas-board/references}/canvas.md +12 -1
- package/{standards → claude/skills/canvas-board/references}/design.md +13 -5
- package/claude/skills/ci-workflow/REQUIREMENT.md +0 -1
- package/claude/skills/context-fold/REQUIREMENT.md +28 -48
- package/claude/skills/context-fold/SKILL.md +26 -95
- package/claude/skills/context-fold/references/classify.md +7 -8
- package/claude/skills/context-fold/references/completion-report.md +4 -8
- package/claude/skills/context-fold/references/context-refresh.md +7 -17
- package/{standards → claude/skills/context-fold/references}/context.md +19 -10
- package/claude/skills/context-fold/references/unusable-baseline.md +4 -8
- package/claude/skills/create-rule/REQUIREMENT.md +0 -1
- package/claude/skills/create-rule/SKILL.md +2 -2
- package/{standards → claude/skills/create-rule/references}/rule.md +12 -5
- package/claude/skills/create-skill/REQUIREMENT.md +6 -4
- package/claude/skills/create-skill/SKILL.md +10 -9
- package/{standards → claude/skills/create-skill/references}/skill-paths.md +1 -0
- package/{standards → claude/skills/create-skill/references}/skill.md +18 -15
- 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 +3 -5
- 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 +3 -3
- package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
- package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
- 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 +9 -10
- 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 +2 -2
- 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 +3 -3
- package/claude/skills/draft-identity/SKILL.md +10 -8
- package/claude/skills/draft-ready/REQUIREMENT.md +1 -1
- package/claude/skills/draft-ready/SKILL.md +7 -7
- 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-branch/SKILL.md +1 -1
- package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
- package/claude/skills/git-commit/SKILL.md +2 -2
- package/claude/skills/git-followup/REQUIREMENT.md +1 -1
- package/claude/skills/git-followup/SKILL.md +1 -1
- 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 +13 -13
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +38 -14
- package/claude/skills/git-pr/references/labels.md +5 -1
- package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
- package/{standards → claude/skills/git-pr/references}/publish.md +5 -3
- package/claude/skills/git-pr/references/task-number.md +1 -1
- package/claude/skills/git-ship/REQUIREMENT.md +0 -2
- package/claude/skills/git-ship/SKILL.md +5 -12
- package/claude/skills/git-split/SKILL.md +3 -3
- package/claude/skills/git-stage/SKILL.md +1 -1
- package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
- package/claude/skills/plan-feature/SKILL.md +2 -6
- package/{standards → claude/skills/plan-feature/references}/plan.md +9 -10
- 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 +2 -2
- package/claude/skills/review-pr/SKILL.md +8 -10
- package/claude/skills/review-pr/references/body-variants.md +2 -2
- package/claude/skills/review-pr/references/key-changes.md +3 -3
- package/claude/skills/review-pr/references/post.md +4 -2
- package/claude/skills/review-ui/REQUIREMENT.md +1 -2
- package/claude/skills/review-ui/SKILL.md +2 -2
- package/claude/skills/review-ui/references/post.md +1 -1
- package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -3
- package/claude/skills/role-orchestrator/SKILL.md +10 -10
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +3 -3
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +9 -9
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +23 -1
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -5
- package/claude/skills/role-orchestrator/references/orchestrator-resume.md +4 -1
- package/claude/skills/role-planner/REQUIREMENT.md +2 -1
- package/claude/skills/role-planner/SKILL.md +15 -4
- package/claude/skills/role-reviewer/SKILL.md +2 -2
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +11 -3
- 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 +9 -17
- 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 +6 -8
- package/claude/skills/session-resume/SKILL.md +3 -9
- package/claude/skills/session-worktree/SKILL.md +6 -6
- 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 +2 -2
- package/claude/skills/task-board/REQUIREMENT.md +7 -12
- package/claude/skills/task-board/SKILL.md +9 -21
- 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 +15 -10
- package/claude/skills/task-complete/REQUIREMENT.md +47 -0
- package/claude/skills/task-complete/SKILL.md +86 -0
- package/claude/skills/{task-board → task-complete}/references/archive.md +5 -5
- package/claude/skills/task-complete/references/receipt-sweep.md +32 -0
- package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
- package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
- package/claude/skills/teach-workspace/SKILL.md +9 -5
- package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
- package/claude/skills/teach-workspace/references/promotion-fold.md +29 -0
- package/claude/skills/teach-workspace/references/promotion.md +3 -3
- package/claude/skills/teach-workspace/references/revisit.md +1 -1
- package/{standards → claude/skills/teach-workspace/references}/teach.md +17 -5
- package/claude/skills/ui-checklist/SKILL.md +1 -1
- package/claude/skills/ux-audit/REQUIREMENT.md +2 -2
- package/claude/skills/ux-audit/SKILL.md +3 -4
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/claude/skills/write-human/REQUIREMENT.md +0 -1
- package/claude/skills/write-human/SKILL.md +1 -1
- package/docs/agents/audits.md +1 -1
- package/docs/agents/canvas.md +1 -1
- package/docs/agents/commands.md +4 -8
- package/docs/agents/context-audit-checks.md +11 -25
- 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 +5 -7
- package/docs/agents/indexes.md +1 -1
- package/docs/agents/install-and-sync.md +6 -11
- package/docs/agents/intake.md +1 -1
- package/docs/agents/key-changes.md +5 -5
- package/docs/agents/label-coverage.md +11 -0
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/records-push.md +2 -2
- package/docs/agents/records-stale.md +13 -48
- package/docs/agents/records-validate.md +4 -8
- package/docs/agents/records.md +6 -28
- package/docs/agents/restated.md +1 -1
- package/docs/agents/rule-citations.md +2 -2
- package/docs/agents/sandbox.md +4 -4
- 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 +1 -1
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/tasks.md +3 -3
- package/docs/agents/teach.md +1 -1
- package/docs/agents/upstream.md +26 -2
- package/docs/target/migrations.md +32 -0
- package/docs/target/projects.md +5 -7
- package/docs/target/scenarios.md +2 -2
- package/docs/target/sync.md +4 -4
- package/docs/workflow/ai-workflow.md +7 -38
- package/docs/workflow/index.md +1 -1
- package/docs/workflow/operating-model.md +10 -10
- package/docs/workflow/parallel-features.md +12 -12
- package/docs/workflow/skill-map-on-request.md +21 -24
- package/docs/workflow/skill-map.md +23 -34
- package/docs/workflow/visual-design-workflow.md +13 -16
- package/governance/renames.toml +0 -30
- package/governance/rules/canon/605-worktrees.md +1 -1
- package/governance/rules/canon/context.md +13 -0
- package/governance/rules/{standards → canon}/plan.md +4 -5
- package/governance/rules/canon/tasks.md +18 -0
- package/governance/rules/claude/skill.md +17 -0
- package/governance/stacks/base.toml +2 -4
- package/package.json +4 -4
- package/scripts/core/install-check.sh +4 -4
- package/scripts/core/regen-web-previews.ts +1 -1
- package/scripts/lib/ui.sh +0 -159
- package/src/audits/catalog.ts +5 -98
- package/src/claude/cases/authoring.ts +0 -18
- package/src/claude/cases/misc.ts +1 -14
- package/src/claude/cases/setup.ts +7 -20
- package/src/claude/cases/workflow.ts +6 -34
- package/src/claude/seeds.ts +1 -7
- package/src/claude/skills-audit.ts +3 -3
- package/src/cli/help.ts +4 -5
- package/src/cli/ui.ts +2 -3
- package/src/cli.ts +0 -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 -15
- package/src/commands/labels.ts +326 -7
- package/src/commands/migrate.ts +6 -1
- package/src/commands/pr.ts +1 -1
- package/src/commands/records.ts +17 -403
- package/src/commands/sandbox.ts +3 -6
- package/src/commands/sync.ts +5 -3
- package/src/commands/tasks.ts +3 -3
- package/src/commands/upstream.ts +135 -0
- package/src/context/audit.ts +10 -19
- 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 +8 -13
- package/src/context/gate.ts +11 -91
- package/src/context/requirements.ts +13 -4
- package/src/counts/scan.ts +3 -3
- package/src/design/budget.ts +1 -1
- package/src/design/components.ts +3 -3
- package/src/design/document.ts +1 -1
- package/src/design/regen.ts +1 -1
- package/src/design/render.ts +1 -1
- package/src/feedback/body.ts +0 -12
- package/src/gate/measures.ts +7 -58
- package/src/gate/sequencer.ts +56 -1
- package/src/gate/stages.ts +1 -7
- package/src/gov/citations.ts +9 -3
- package/src/gov/install.ts +2 -3
- package/src/gov/restated.ts +3 -3
- package/src/gov/stacks.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/labels/apply.ts +39 -0
- package/src/labels/format.ts +3 -3
- package/src/labels/phase.ts +3 -3
- package/src/labels/sections.ts +43 -0
- package/src/markdown/structure.ts +2 -2
- package/src/migrate/record-layout.ts +3 -3
- package/src/migrate/skill-names.ts +20 -1
- package/src/pr/paths.ts +4 -4
- package/src/records/backup.ts +16 -6
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +2 -1
- package/src/records/stale-canonical.ts +33 -3
- package/src/records/validate.ts +2 -188
- package/src/roots/record.ts +1 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/coverage.ts +2 -6
- package/src/sandbox/equivalence.ts +5 -44
- package/src/sandbox/expect.ts +4 -4
- package/src/sandbox/provision.ts +13 -36
- package/src/sandbox/scenario.ts +137 -55
- package/src/sandbox/tree.ts +2 -6
- package/src/shipped/references.ts +3 -3
- package/src/standards/read.ts +44 -0
- package/src/sync/git.ts +1 -1
- package/src/sync/seeds-report.ts +1 -1
- package/src/tasks/archive.ts +5 -5
- package/src/tasks/branch.ts +3 -3
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/record.ts +2 -2
- package/src/tasks/validate.ts +2 -2
- package/src/teach/workspace.ts +1 -1
- package/src/upstream/cursor.ts +1 -1
- package/src/upstream/due.ts +128 -0
- package/src/upstream/releases.ts +2 -1
- package/standards/index.md +0 -35
- package/standards/standard.md +1 -7
- package/tooling/base/reference.md +2 -2
- package/tooling/base/seeds/.github/pull_request_template.md +2 -2
- package/tooling/claude/reference.md +7 -21
- package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
- package/tooling/claude/seeds/.claude/hooks/records-index.sh +8 -20
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +3 -3
- package/tooling/claude/seeds/.claude/settings.json +9 -0
- 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/promoted-pages.md +0 -29
- package/claude/skills/context-fold/references/receipt-sweep.md +0 -43
- 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/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/603-memory.md +0 -11
- package/governance/rules/lang/120-bash.md +0 -18
- package/governance/rules/standards/architecture.md +0 -13
- package/governance/rules/standards/context.md +0 -14
- package/governance/rules/standards/design.md +0 -12
- package/governance/rules/standards/groundwork.md +0 -13
- package/governance/rules/standards/intake.md +0 -12
- package/governance/rules/standards/memory.md +0 -12
- package/governance/rules/standards/readme.md +0 -12
- package/governance/rules/standards/ready.md +0 -13
- package/governance/rules/standards/requirements.md +0 -12
- package/governance/rules/standards/rule.md +0 -13
- package/governance/rules/standards/session.md +0 -13
- package/governance/rules/standards/skill.md +0 -21
- package/governance/rules/standards/standard.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/scripts/config.sh +0 -11
- package/scripts/lib/gov.sh +0 -14
- package/scripts/lib/sandbox-fixtures.sh +0 -191
- package/scripts/lib/sandbox-git.sh +0 -125
- package/scripts/lib/sandbox-path.sh +0 -50
- package/scripts/sandbox-hook.sh +0 -99
- 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/gov/standard-rules.ts +0 -179
- package/src/records/migrate.ts +0 -78
- package/src/records/stale.ts +0 -243
- package/src/repo/metadata.ts +0 -206
- package/src/sandbox/hooks.ts +0 -128
- 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/draft-figure/references}/figures.md +0 -0
- /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
- /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
- /package/{standards → claude/skills/git-commit/references}/versioning.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/records/stale.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import { readdir, readFile } from 'node:fs/promises'
|
|
3
|
-
import { join, relative } from 'node:path'
|
|
4
|
-
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
5
|
-
import {
|
|
6
|
-
CREATION_ROOT,
|
|
7
|
-
RECORD_ROOTS,
|
|
8
|
-
recordDir,
|
|
9
|
-
SCRATCH,
|
|
10
|
-
spell,
|
|
11
|
-
} from '@/roots/record'
|
|
12
|
-
|
|
13
|
-
/** The window after which a reviewed entry is due again, in days. */
|
|
14
|
-
export const DEFAULT_REVIEW_DAYS = 30
|
|
15
|
-
|
|
16
|
-
const DAY_MS = 24 * 60 * 60 * 1000
|
|
17
|
-
|
|
18
|
-
const MEMORY_INDEX = 'index.md'
|
|
19
|
-
|
|
20
|
-
export interface StaleEntry {
|
|
21
|
-
/** The filename stem, which is what a review names an entry by. */
|
|
22
|
-
readonly name: string
|
|
23
|
-
readonly category: string | null
|
|
24
|
-
/** `YYYY-MM-DD`, or null when the entry carries no readable review date. */
|
|
25
|
-
readonly reviewed: string | null
|
|
26
|
-
/** The raw value of a `reviewed` field that is not a date, present only then. */
|
|
27
|
-
readonly invalidReviewed?: string
|
|
28
|
-
readonly due: boolean
|
|
29
|
-
/** Backticked paths the entry cites that the project root does not hold. */
|
|
30
|
-
readonly unresolved: readonly string[]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface StaleReport {
|
|
34
|
-
readonly ok: true
|
|
35
|
-
readonly root: string
|
|
36
|
-
/** The pen read, relative to the root. */
|
|
37
|
-
readonly folder: string
|
|
38
|
-
readonly days: number
|
|
39
|
-
readonly total: number
|
|
40
|
-
readonly due: number
|
|
41
|
-
readonly entries: readonly StaleEntry[]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const STALE_REFUSALS = ['no-folder'] as const
|
|
45
|
-
|
|
46
|
-
export type StaleRefusal = (typeof STALE_REFUSALS)[number]
|
|
47
|
-
|
|
48
|
-
export interface StaleRefused {
|
|
49
|
-
readonly ok: false
|
|
50
|
-
readonly reason: StaleRefusal
|
|
51
|
-
readonly message: string
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export type StaleOutcome = StaleReport | StaleRefused
|
|
55
|
-
|
|
56
|
-
const REVIEWED_FIELD = /^reviewed:[ \t]*(.*?)[ \t]*$/m
|
|
57
|
-
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/
|
|
58
|
-
const RAW_FRONTMATTER = /^---\n([\s\S]*?)\n---/
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Reads `reviewed` off the raw block rather than the parsed fields, for the
|
|
62
|
-
* reason `hasOpeningDate` in the validator gives: a YAML parser resolves a
|
|
63
|
-
* bare `YYYY-MM-DD` to a date on one schema and a string on another.
|
|
64
|
-
*
|
|
65
|
-
* A value that is not a calendar date reads as never reviewed and is carried
|
|
66
|
-
* back so the review can see it, rather than throwing over one hand edit.
|
|
67
|
-
*/
|
|
68
|
-
export function readReviewed(
|
|
69
|
-
text: string,
|
|
70
|
-
): Pick<StaleEntry, 'reviewed' | 'invalidReviewed'> {
|
|
71
|
-
const block = RAW_FRONTMATTER.exec(text)?.[1]
|
|
72
|
-
const match = block ? REVIEWED_FIELD.exec(block) : null
|
|
73
|
-
if (!match) return { reviewed: null }
|
|
74
|
-
|
|
75
|
-
const value = match[1].replace(/^(['"])(.*)\1$/, '$2')
|
|
76
|
-
const parsed = Date.parse(`${value}T00:00:00Z`)
|
|
77
|
-
const isDate =
|
|
78
|
-
ISO_DATE.test(value) &&
|
|
79
|
-
Number.isFinite(parsed) &&
|
|
80
|
-
new Date(parsed).toISOString().slice(0, 10) === value
|
|
81
|
-
|
|
82
|
-
return isDate
|
|
83
|
-
? { reviewed: value }
|
|
84
|
-
: { reviewed: null, invalidReviewed: value }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Tokens that name a session record, scratch at either root, or a checkout,
|
|
89
|
-
* none of which a tree commits. The scratch spellings come from `spell` so the
|
|
90
|
-
* legacy root's variant is stated in one place.
|
|
91
|
-
*/
|
|
92
|
-
const UNTRACKED_PREFIXES = [
|
|
93
|
-
...RECORD_ROOTS.map((at) => `${at}/${spell(at, SCRATCH)}/`),
|
|
94
|
-
`${CREATION_ROOT}/`,
|
|
95
|
-
'.claude/worktrees/',
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
const INLINE_CODE = /`([^`\n]+)`/g
|
|
99
|
-
const LINE_ANCHOR = /:\d+(?:[-:]\d+)*$/
|
|
100
|
-
const FILE_EXTENSION = /\.[A-Za-z0-9]+$/
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Reduces one backticked token to the repository path it cites, or undefined
|
|
104
|
-
* when it cites none.
|
|
105
|
-
*
|
|
106
|
-
* A path is a token carrying a `/` and ending in a file extension once any
|
|
107
|
-
* line or heading anchor is stripped. A placeholder, an elided path, a glob, a
|
|
108
|
-
* variable, a URL, and a path outside the project all look like paths and
|
|
109
|
-
* resolve against nothing in the tree, so each is dropped rather than reported.
|
|
110
|
-
*/
|
|
111
|
-
function toCitedPath(token: string): string | undefined {
|
|
112
|
-
if (/\s/.test(token) || token.includes('://')) return undefined
|
|
113
|
-
if (/[<*{$]/.test(token) || token.includes('...')) return undefined
|
|
114
|
-
if (/^(?:\/|~|\.\.\/)/.test(token)) return undefined
|
|
115
|
-
|
|
116
|
-
const path = token
|
|
117
|
-
.replace(/#.*$/, '')
|
|
118
|
-
.replace(LINE_ANCHOR, '')
|
|
119
|
-
.replace(/^\.\//, '')
|
|
120
|
-
|
|
121
|
-
if (!path.includes('/') || !FILE_EXTENSION.test(path)) return undefined
|
|
122
|
-
if (UNTRACKED_PREFIXES.some((prefix) => path.startsWith(prefix)))
|
|
123
|
-
return undefined
|
|
124
|
-
|
|
125
|
-
return path
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Every distinct repository path a body cites in inline code, in order. */
|
|
129
|
-
export function citedPaths(body: string): string[] {
|
|
130
|
-
const paths = new Set<string>()
|
|
131
|
-
|
|
132
|
-
for (const match of body.matchAll(INLINE_CODE)) {
|
|
133
|
-
const path = toCitedPath(match[1])
|
|
134
|
-
if (path) paths.add(path)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return [...paths]
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function isDue(reviewed: string | null, days: number, now: number): boolean {
|
|
141
|
-
if (reviewed === null) return true
|
|
142
|
-
return now - Date.parse(`${reviewed}T00:00:00`) > days * DAY_MS
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function readEntry(
|
|
146
|
-
root: string,
|
|
147
|
-
dir: string,
|
|
148
|
-
file: string,
|
|
149
|
-
days: number,
|
|
150
|
-
now: number,
|
|
151
|
-
): Promise<StaleEntry> {
|
|
152
|
-
const text = (await readFile(join(dir, file), 'utf8')).replaceAll(
|
|
153
|
-
'\r\n',
|
|
154
|
-
'\n',
|
|
155
|
-
)
|
|
156
|
-
const frontmatter = parseFrontmatter(text)
|
|
157
|
-
const review = readReviewed(text)
|
|
158
|
-
const body = text.slice(RAW_FRONTMATTER.exec(text)?.[0].length ?? 0)
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
name: file.replace(/\.md$/, ''),
|
|
162
|
-
category: readField(frontmatter, 'category') ?? null,
|
|
163
|
-
...review,
|
|
164
|
-
due: isDue(review.reviewed, days, now),
|
|
165
|
-
unresolved: citedPaths(body).filter(
|
|
166
|
-
(path) => !existsSync(join(root, path)),
|
|
167
|
-
),
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* The order a review takes a batch in: due entries first, and among them the
|
|
173
|
-
* ones citing a tree that moved, then the longest unreviewed, then by name so
|
|
174
|
-
* two runs over one pen agree.
|
|
175
|
-
*/
|
|
176
|
-
function compareEntries(left: StaleEntry, right: StaleEntry): number {
|
|
177
|
-
if (left.due !== right.due) return left.due ? -1 : 1
|
|
178
|
-
|
|
179
|
-
const leftMoved = left.unresolved.length > 0
|
|
180
|
-
const rightMoved = right.unresolved.length > 0
|
|
181
|
-
if (leftMoved !== rightMoved) return leftMoved ? -1 : 1
|
|
182
|
-
|
|
183
|
-
if (left.reviewed !== right.reviewed) {
|
|
184
|
-
if (left.reviewed === null) return -1
|
|
185
|
-
if (right.reviewed === null) return 1
|
|
186
|
-
return left.reviewed < right.reviewed ? -1 : 1
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return left.name.localeCompare(right.name)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Reports each memory entry's review state and the paths it cites that the
|
|
194
|
-
* project no longer holds, ordered so a caller takes the first N as a batch.
|
|
195
|
-
*
|
|
196
|
-
* It reads the top level of the pen alone. `review/` holds receipts and
|
|
197
|
-
* `archive/` holds retirements, and neither is an entry a review would queue.
|
|
198
|
-
* Paths resolve against the project root rather than the entry's folder, since
|
|
199
|
-
* an entry cites the tree it was written about. It reports and never writes.
|
|
200
|
-
*
|
|
201
|
-
* `now` is a parameter so a test can pin the window against fixture dates.
|
|
202
|
-
*/
|
|
203
|
-
export async function staleMemory(
|
|
204
|
-
root: string,
|
|
205
|
-
days: number = DEFAULT_REVIEW_DAYS,
|
|
206
|
-
now: number = Date.now(),
|
|
207
|
-
): Promise<StaleOutcome> {
|
|
208
|
-
const dir = recordDir(root, 'memory')
|
|
209
|
-
|
|
210
|
-
if (!existsSync(dir)) {
|
|
211
|
-
return {
|
|
212
|
-
ok: false,
|
|
213
|
-
reason: 'no-folder',
|
|
214
|
-
message: `No memory folder at ${dir}, so there are no entries to read.`,
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const listed = await readdir(dir, { withFileTypes: true })
|
|
219
|
-
const files = listed
|
|
220
|
-
.filter(
|
|
221
|
-
(entry) =>
|
|
222
|
-
entry.isFile() &&
|
|
223
|
-
entry.name.endsWith('.md') &&
|
|
224
|
-
entry.name !== MEMORY_INDEX,
|
|
225
|
-
)
|
|
226
|
-
.map((entry) => entry.name)
|
|
227
|
-
|
|
228
|
-
const entries = (
|
|
229
|
-
await Promise.all(
|
|
230
|
-
files.map((file) => readEntry(root, dir, file, days, now)),
|
|
231
|
-
)
|
|
232
|
-
).toSorted(compareEntries)
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
ok: true,
|
|
236
|
-
root,
|
|
237
|
-
folder: relative(root, dir),
|
|
238
|
-
days,
|
|
239
|
-
total: entries.length,
|
|
240
|
-
due: entries.filter((entry) => entry.due).length,
|
|
241
|
-
entries,
|
|
242
|
-
}
|
|
243
|
-
}
|
package/src/repo/metadata.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* What this run can compute from the tree alone. A field stays absent rather
|
|
6
|
-
* than empty when nothing local resolves it, so a caller never reads
|
|
7
|
-
* "nothing to propose" as "propose removing what is already there."
|
|
8
|
-
*/
|
|
9
|
-
export interface MetadataProposal {
|
|
10
|
-
readonly description?: string
|
|
11
|
-
readonly homepage?: string
|
|
12
|
-
readonly topics?: readonly string[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** What the remote already carries, read by the caller rather than here. */
|
|
16
|
-
export interface CurrentMetadata {
|
|
17
|
-
readonly description: string
|
|
18
|
-
readonly homepage: string
|
|
19
|
-
readonly topics: readonly string[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface MetadataDiff {
|
|
23
|
-
readonly description?: { readonly current: string; readonly proposed: string }
|
|
24
|
-
readonly homepage?: { readonly current: string; readonly proposed: string }
|
|
25
|
-
readonly topics?: {
|
|
26
|
-
readonly added: readonly string[]
|
|
27
|
-
readonly removed: readonly string[]
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** GitHub's own cap on the About field. */
|
|
32
|
-
const MAX_DESCRIPTION_LENGTH = 350
|
|
33
|
-
|
|
34
|
-
/** GitHub's own cap on topics per repository. */
|
|
35
|
-
const MAX_TOPICS = 20
|
|
36
|
-
|
|
37
|
-
/** GitHub's own shape for a topic: lowercase, alphanumeric, internal hyphens. */
|
|
38
|
-
const TOPIC_PATTERN = /^[a-z0-9][a-z0-9-]*$/
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Whether an already-trimmed, already-lowercased string is a shape GitHub
|
|
42
|
-
* accepts as a topic. Exported so a caller validating an operator-supplied
|
|
43
|
-
* topic list checks against the same rule this reader silently filters
|
|
44
|
-
* `package.json`'s `keywords` through, rather than reimplementing it against
|
|
45
|
-
* a looser test such as non-emptiness alone.
|
|
46
|
-
*/
|
|
47
|
-
export function isValidTopic(topic: string): boolean {
|
|
48
|
-
return TOPIC_PATTERN.test(topic)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* A bare image, or an image wrapped in a link, which is the shape a shields.io
|
|
53
|
-
* badge takes. The wrapped alternative goes first, since the bare form would
|
|
54
|
-
* otherwise match its inner image alone and leave the wrapping link behind.
|
|
55
|
-
*/
|
|
56
|
-
const BADGE_TOKEN = /\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)|!\[[^\]]*\]\([^)]*\)/g
|
|
57
|
-
|
|
58
|
-
/** True once every badge token is stripped and nothing remains. */
|
|
59
|
-
function isBadgeLine(line: string): boolean {
|
|
60
|
-
return line.replace(BADGE_TOKEN, '').trim() === ''
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function stripInlineMarkdown(line: string): string {
|
|
64
|
-
return line
|
|
65
|
-
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
66
|
-
.replace(/(\*\*|__)(.+?)\1/g, '$2')
|
|
67
|
-
.replace(/(\*|_|`)(.+?)\1/g, '$2')
|
|
68
|
-
.trim()
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The first prose line past the title and any badge row, stripped of inline
|
|
73
|
-
* markdown and capped at GitHub's About field length.
|
|
74
|
-
*
|
|
75
|
-
* `undefined` only when the file carries nothing past its title and badges,
|
|
76
|
-
* per the skill requirement's refusal boundary: a resolvable line that reads
|
|
77
|
-
* as a title rather than a sentence is still proposed, since rejecting it is
|
|
78
|
-
* a judgment for the person reading the proposal rather than for this reader.
|
|
79
|
-
*/
|
|
80
|
-
export function extractOpeningLine(readme: string): string | undefined {
|
|
81
|
-
for (const raw of readme.split('\n')) {
|
|
82
|
-
const line = raw.trim()
|
|
83
|
-
if (line === '' || line.startsWith('#') || isBadgeLine(line)) continue
|
|
84
|
-
|
|
85
|
-
const stripped = stripInlineMarkdown(line)
|
|
86
|
-
if (stripped === '') continue
|
|
87
|
-
|
|
88
|
-
return stripped.length > MAX_DESCRIPTION_LENGTH
|
|
89
|
-
? `${stripped.slice(0, MAX_DESCRIPTION_LENGTH - 1)}…`
|
|
90
|
-
: stripped
|
|
91
|
-
}
|
|
92
|
-
return undefined
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* `package.json`'s `keywords` read as candidate topics, the field the wider
|
|
97
|
-
* npm ecosystem already uses for this. Invalid entries are dropped silently
|
|
98
|
-
* rather than refused, since a manifest mixing free-text keywords with
|
|
99
|
-
* topic-shaped ones is ordinary and only the shaped half transfers.
|
|
100
|
-
*/
|
|
101
|
-
function readTopics(keywords: unknown): readonly string[] | undefined {
|
|
102
|
-
if (!Array.isArray(keywords)) return undefined
|
|
103
|
-
|
|
104
|
-
const topics = new Set<string>()
|
|
105
|
-
for (const entry of keywords) {
|
|
106
|
-
if (typeof entry !== 'string') continue
|
|
107
|
-
const topic = entry.trim().toLowerCase()
|
|
108
|
-
if (isValidTopic(topic)) topics.add(topic)
|
|
109
|
-
if (topics.size === MAX_TOPICS) break
|
|
110
|
-
}
|
|
111
|
-
return [...topics]
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
interface PackageFields {
|
|
115
|
-
readonly homepage?: unknown
|
|
116
|
-
readonly keywords?: unknown
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function readManifest(root: string): Promise<PackageFields | undefined> {
|
|
120
|
-
try {
|
|
121
|
-
const parsed: unknown = JSON.parse(
|
|
122
|
-
await readFile(join(root, 'package.json'), 'utf8'),
|
|
123
|
-
)
|
|
124
|
-
return (parsed ?? undefined) as PackageFields | undefined
|
|
125
|
-
} catch {
|
|
126
|
-
return undefined
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Computes what this tree can propose for its own repository metadata, with
|
|
132
|
-
* no network call: an About text from the README's opening line, and a
|
|
133
|
-
* homepage and a topic set from `package.json`, the one manifest every
|
|
134
|
-
* target project this ships to already carries.
|
|
135
|
-
*
|
|
136
|
-
* A field a target project declares nowhere stays absent rather than empty,
|
|
137
|
-
* so the caller comparing this against the remote never treats a field this
|
|
138
|
-
* reader has no opinion on as a proposal to clear it.
|
|
139
|
-
*/
|
|
140
|
-
export async function proposeMetadata(root: string): Promise<MetadataProposal> {
|
|
141
|
-
const [readme, manifest] = await Promise.all([
|
|
142
|
-
readFile(join(root, 'README.md'), 'utf8').catch(() => undefined),
|
|
143
|
-
readManifest(root),
|
|
144
|
-
])
|
|
145
|
-
|
|
146
|
-
const description =
|
|
147
|
-
readme === undefined ? undefined : extractOpeningLine(readme)
|
|
148
|
-
|
|
149
|
-
const homepage =
|
|
150
|
-
typeof manifest?.homepage === 'string' && manifest.homepage.trim() !== ''
|
|
151
|
-
? manifest.homepage.trim()
|
|
152
|
-
: undefined
|
|
153
|
-
|
|
154
|
-
const topics = readTopics(manifest?.keywords)
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
...(description !== undefined && { description }),
|
|
158
|
-
...(homepage !== undefined && { homepage }),
|
|
159
|
-
...(topics !== undefined && topics.length > 0 && { topics }),
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Compares a computed proposal against what the remote already carries.
|
|
165
|
-
*
|
|
166
|
-
* A field the proposal has no opinion on is never diffed, which is what
|
|
167
|
-
* keeps a target project with no `keywords` field from seeing every existing
|
|
168
|
-
* topic reported as a removal.
|
|
169
|
-
*/
|
|
170
|
-
export function compareMetadata(
|
|
171
|
-
current: CurrentMetadata,
|
|
172
|
-
proposed: MetadataProposal,
|
|
173
|
-
): MetadataDiff {
|
|
174
|
-
const diff: {
|
|
175
|
-
description?: { current: string; proposed: string }
|
|
176
|
-
homepage?: { current: string; proposed: string }
|
|
177
|
-
topics?: { added: readonly string[]; removed: readonly string[] }
|
|
178
|
-
} = {}
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
proposed.description !== undefined &&
|
|
182
|
-
proposed.description !== current.description
|
|
183
|
-
) {
|
|
184
|
-
diff.description = {
|
|
185
|
-
current: current.description,
|
|
186
|
-
proposed: proposed.description,
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
proposed.homepage !== undefined &&
|
|
192
|
-
proposed.homepage !== current.homepage
|
|
193
|
-
) {
|
|
194
|
-
diff.homepage = { current: current.homepage, proposed: proposed.homepage }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (proposed.topics !== undefined) {
|
|
198
|
-
const currentSet = new Set(current.topics)
|
|
199
|
-
const proposedSet = new Set(proposed.topics)
|
|
200
|
-
const added = proposed.topics.filter((topic) => !currentSet.has(topic))
|
|
201
|
-
const removed = current.topics.filter((topic) => !proposedSet.has(topic))
|
|
202
|
-
if (added.length > 0 || removed.length > 0) diff.topics = { added, removed }
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return diff
|
|
206
|
-
}
|
package/src/sandbox/hooks.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { PROJECT_ROOT } from '@/roots/project'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The bash entry a scenario's hooks run through. Scenarios and the library they
|
|
9
|
-
* call stay bash, so this contract is the whole of what the TypeScript harness
|
|
10
|
-
* knows about one: which hooks it declares and what `use_config` exports.
|
|
11
|
-
*/
|
|
12
|
-
const HOOK_SCRIPT = join(PROJECT_ROOT, 'scripts', 'sandbox-hook.sh')
|
|
13
|
-
|
|
14
|
-
const ANCHOR_RECORD = '@anchor='
|
|
15
|
-
|
|
16
|
-
export interface Probe {
|
|
17
|
-
readonly exports: Readonly<Record<string, string>>
|
|
18
|
-
readonly isAnchor: boolean
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type ProbeResult =
|
|
22
|
-
| ({ readonly ok: true } & Probe)
|
|
23
|
-
| { readonly ok: false; readonly status: number }
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Reads the NUL-separated `NAME=value` pairs the probe prints. NUL rather than a
|
|
27
|
-
* newline, since an exported value can hold one. The anchor record closes every
|
|
28
|
-
* well-formed probe, so output missing it is a probe that did not finish.
|
|
29
|
-
*/
|
|
30
|
-
export function parseProbe(stdout: string): Probe {
|
|
31
|
-
const { exports, anchor } = parsePairs(stdout)
|
|
32
|
-
if (anchor === undefined)
|
|
33
|
-
throw new Error('the probe printed no anchor record')
|
|
34
|
-
|
|
35
|
-
return { exports, isAnchor: anchor === '1' }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function parsePairs(text: string): {
|
|
39
|
-
exports: Record<string, string>
|
|
40
|
-
anchor: string | undefined
|
|
41
|
-
} {
|
|
42
|
-
const exports: Record<string, string> = {}
|
|
43
|
-
let anchor: string | undefined
|
|
44
|
-
|
|
45
|
-
for (const record of text.split('\0')) {
|
|
46
|
-
if (record.startsWith(ANCHOR_RECORD)) {
|
|
47
|
-
anchor = record.slice(ANCHOR_RECORD.length)
|
|
48
|
-
continue
|
|
49
|
-
}
|
|
50
|
-
const split = record.indexOf('=')
|
|
51
|
-
if (split > 0) exports[record.slice(0, split)] = record.slice(split + 1)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { exports, anchor }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Runs `use_config` and `use_anchor` in a child and returns what they exported.
|
|
59
|
-
* Stderr is inherited by default, so a hook that fails narrates into the same
|
|
60
|
-
* frame the harness writes. A non-zero status passes through rather than being
|
|
61
|
-
* flattened, since the harness exits with it.
|
|
62
|
-
*/
|
|
63
|
-
export function probeScenario(
|
|
64
|
-
file: string,
|
|
65
|
-
env: NodeJS.ProcessEnv,
|
|
66
|
-
stderr: 'inherit' | 'pipe' = 'inherit',
|
|
67
|
-
): ProbeResult {
|
|
68
|
-
const result = spawnSync('bash', [HOOK_SCRIPT, 'probe', file], {
|
|
69
|
-
env,
|
|
70
|
-
encoding: 'utf8',
|
|
71
|
-
stdio: ['ignore', 'pipe', stderr],
|
|
72
|
-
})
|
|
73
|
-
const status = result.status ?? 1
|
|
74
|
-
if (status !== 0) return { ok: false, status }
|
|
75
|
-
|
|
76
|
-
return { ok: true, ...parseProbe(result.stdout) }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* How `stage_setup` ended. The retired dispatcher ran it in its own shell, so
|
|
81
|
-
* an `exit` there ended provisioning through the frame-closing trap, and an
|
|
82
|
-
* `exec` replaced the harness outright, leaving its program's frame as the last
|
|
83
|
-
* one written. The harness reproduces both from this.
|
|
84
|
-
*/
|
|
85
|
-
export type StageEnding = 'returned' | 'exited' | 'replaced'
|
|
86
|
-
|
|
87
|
-
export interface StageResult {
|
|
88
|
-
readonly status: number
|
|
89
|
-
readonly ending: StageEnding
|
|
90
|
-
readonly exports: Readonly<Record<string, string>>
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const EXITED_RECORD = '@exited'
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Runs `stage_setup` inside the tree with every stream inherited, so a scenario
|
|
97
|
-
* prompting through `select_or_route_scenario` still reaches a terminal and its
|
|
98
|
-
* narration lands in order with the harness's own.
|
|
99
|
-
*
|
|
100
|
-
* The exports `stage_setup` changed come back through a file rather than a
|
|
101
|
-
* pipe. Stdout belongs to the scenario, and a background process a scenario
|
|
102
|
-
* starts would hold an extra pipe open past the child's exit.
|
|
103
|
-
*/
|
|
104
|
-
export function stageScenario(
|
|
105
|
-
file: string,
|
|
106
|
-
env: NodeJS.ProcessEnv,
|
|
107
|
-
stdio: 'inherit' | 'ignore' = 'inherit',
|
|
108
|
-
): StageResult {
|
|
109
|
-
const scratch = mkdtempSync(join(tmpdir(), 'canon-sandbox-hook-'))
|
|
110
|
-
const report = join(scratch, 'exports')
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
const result = spawnSync('bash', [HOOK_SCRIPT, 'stage', file, report], {
|
|
114
|
-
env,
|
|
115
|
-
stdio,
|
|
116
|
-
})
|
|
117
|
-
const status = result.status ?? 1
|
|
118
|
-
if (!existsSync(report)) return { status, ending: 'replaced', exports: {} }
|
|
119
|
-
|
|
120
|
-
const text = readFileSync(report, 'utf8')
|
|
121
|
-
if (text.split('\0').includes(EXITED_RECORD))
|
|
122
|
-
return { status, ending: 'exited', exports: {} }
|
|
123
|
-
|
|
124
|
-
return { status, ending: 'returned', exports: parsePairs(text).exports }
|
|
125
|
-
} finally {
|
|
126
|
-
rmSync(scratch, { recursive: true, force: true })
|
|
127
|
-
}
|
|
128
|
-
}
|