@erclx/canon 4.0.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/LICENSE +21 -0
- package/README.md +129 -0
- package/claude/.claude-plugin/plugin.json +19 -0
- package/claude/skills/bash-cli-script/REQUIREMENT.md +42 -0
- package/claude/skills/bash-cli-script/SKILL.md +48 -0
- package/claude/skills/bash-cli-script/references/template.md +43 -0
- package/claude/skills/bash-script/REQUIREMENT.md +36 -0
- package/claude/skills/bash-script/SKILL.md +100 -0
- package/claude/skills/bash-script/references/patterns.md +349 -0
- package/claude/skills/canon-cli/REQUIREMENT.md +41 -0
- package/claude/skills/canon-cli/SKILL.md +103 -0
- package/claude/skills/canon-feedback-file/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-file/SKILL.md +80 -0
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +63 -0
- package/claude/skills/canon-operator/REQUIREMENT.md +61 -0
- package/claude/skills/canon-operator/SKILL.md +108 -0
- package/claude/skills/canon-rollout/REQUIREMENT.md +59 -0
- package/claude/skills/canon-rollout/SKILL.md +147 -0
- package/claude/skills/canon-screencast/REQUIREMENT.md +39 -0
- package/claude/skills/canon-screencast/SKILL.md +167 -0
- package/claude/skills/canon-slides-draft/REQUIREMENT.md +39 -0
- package/claude/skills/canon-slides-draft/SKILL.md +62 -0
- package/claude/skills/ci-workflow/REQUIREMENT.md +40 -0
- package/claude/skills/ci-workflow/SKILL.md +65 -0
- package/claude/skills/ci-workflow/references/workflows.md +98 -0
- package/claude/skills/claude-address-review/REQUIREMENT.md +57 -0
- package/claude/skills/claude-address-review/SKILL.md +212 -0
- package/claude/skills/claude-address-review/references/rebase-conflicts.md +39 -0
- package/claude/skills/claude-autoship/REQUIREMENT.md +50 -0
- package/claude/skills/claude-autoship/SKILL.md +207 -0
- package/claude/skills/claude-design-extract/REQUIREMENT.md +42 -0
- package/claude/skills/claude-design-extract/SKILL.md +102 -0
- package/claude/skills/claude-diagram/REQUIREMENT.md +45 -0
- package/claude/skills/claude-diagram/SKILL.md +177 -0
- package/claude/skills/claude-docs/REQUIREMENT.md +60 -0
- package/claude/skills/claude-docs/SKILL.md +287 -0
- package/claude/skills/claude-docs/references/anchor-sweep.md +58 -0
- package/claude/skills/claude-docs/references/wireframe-sweep.md +45 -0
- package/claude/skills/claude-feature/REQUIREMENT.md +36 -0
- package/claude/skills/claude-feature/SKILL.md +115 -0
- package/claude/skills/claude-groundwork/REQUIREMENT.md +48 -0
- package/claude/skills/claude-groundwork/SKILL.md +142 -0
- package/claude/skills/claude-intake/REQUIREMENT.md +49 -0
- package/claude/skills/claude-intake/SKILL.md +114 -0
- package/claude/skills/claude-intake-answer/REQUIREMENT.md +48 -0
- package/claude/skills/claude-intake-answer/SKILL.md +90 -0
- package/claude/skills/claude-markdown-propose/REQUIREMENT.md +48 -0
- package/claude/skills/claude-markdown-propose/SKILL.md +118 -0
- package/claude/skills/claude-markdown-propose/references/format.md +107 -0
- package/claude/skills/claude-memory-capture/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-capture/SKILL.md +101 -0
- package/claude/skills/claude-memory-review/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-review/SKILL.md +210 -0
- package/claude/skills/claude-memory-review/references/receipt-format.md +48 -0
- package/claude/skills/claude-orchestrate/REQUIREMENT.md +121 -0
- package/claude/skills/claude-orchestrate/SKILL.md +241 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +132 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +32 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +72 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +87 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +30 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +21 -0
- package/claude/skills/claude-orchestrate/scripts/poll.sh +373 -0
- package/claude/skills/claude-orchestrate/scripts/watch.sh +181 -0
- package/claude/skills/claude-pr-review/REQUIREMENT.md +47 -0
- package/claude/skills/claude-pr-review/SKILL.md +295 -0
- package/claude/skills/claude-review/REQUIREMENT.md +39 -0
- package/claude/skills/claude-review/SKILL.md +142 -0
- package/claude/skills/claude-seed-sync/REQUIREMENT.md +45 -0
- package/claude/skills/claude-seed-sync/SKILL.md +156 -0
- package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
- package/claude/skills/claude-standards-audit/SKILL.md +99 -0
- package/claude/skills/claude-tasks/REQUIREMENT.md +43 -0
- package/claude/skills/claude-tasks/SKILL.md +161 -0
- package/claude/skills/claude-teach/REQUIREMENT.md +56 -0
- package/claude/skills/claude-teach/SKILL.md +196 -0
- package/claude/skills/claude-teach/references/lesson-craft.md +59 -0
- package/claude/skills/claude-teach/references/pedagogy.md +67 -0
- package/claude/skills/claude-teach/references/promotion.md +54 -0
- package/claude/skills/claude-ui-test/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ui-test/SKILL.md +77 -0
- package/claude/skills/claude-ux-audit/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ux-audit/SKILL.md +79 -0
- package/claude/skills/claude-ux-measure/REQUIREMENT.md +48 -0
- package/claude/skills/claude-ux-measure/SKILL.md +122 -0
- package/claude/skills/claude-worker/REQUIREMENT.md +54 -0
- package/claude/skills/claude-worker/SKILL.md +96 -0
- package/claude/skills/claude-worktree/REQUIREMENT.md +58 -0
- package/claude/skills/claude-worktree/SKILL.md +136 -0
- package/claude/skills/create-rule/REQUIREMENT.md +45 -0
- package/claude/skills/create-rule/SKILL.md +68 -0
- package/claude/skills/create-skill/REQUIREMENT.md +38 -0
- package/claude/skills/create-skill/SKILL.md +32 -0
- package/claude/skills/create-snippet/REQUIREMENT.md +39 -0
- package/claude/skills/create-snippet/SKILL.md +30 -0
- package/claude/skills/create-standard/REQUIREMENT.md +35 -0
- package/claude/skills/create-standard/SKILL.md +29 -0
- package/claude/skills/decision-escalate/REQUIREMENT.md +45 -0
- package/claude/skills/decision-escalate/SKILL.md +79 -0
- package/claude/skills/docs-sync/REQUIREMENT.md +41 -0
- package/claude/skills/docs-sync/SKILL.md +95 -0
- package/claude/skills/git-branch/REQUIREMENT.md +38 -0
- package/claude/skills/git-branch/SKILL.md +60 -0
- package/claude/skills/git-commit/REQUIREMENT.md +36 -0
- package/claude/skills/git-commit/SKILL.md +49 -0
- package/claude/skills/git-followup/REQUIREMENT.md +43 -0
- package/claude/skills/git-followup/SKILL.md +48 -0
- package/claude/skills/git-issue/REQUIREMENT.md +38 -0
- package/claude/skills/git-issue/SKILL.md +65 -0
- package/claude/skills/git-pr/REQUIREMENT.md +50 -0
- package/claude/skills/git-pr/SKILL.md +164 -0
- package/claude/skills/git-pr/references/labels.md +95 -0
- package/claude/skills/git-ship/REQUIREMENT.md +42 -0
- package/claude/skills/git-ship/SKILL.md +55 -0
- package/claude/skills/git-split/REQUIREMENT.md +39 -0
- package/claude/skills/git-split/SKILL.md +162 -0
- package/claude/skills/git-stage/REQUIREMENT.md +39 -0
- package/claude/skills/git-stage/SKILL.md +73 -0
- package/claude/skills/git-worktree/REQUIREMENT.md +38 -0
- package/claude/skills/git-worktree/SKILL.md +130 -0
- package/claude/skills/migration-claude-md/REQUIREMENT.md +40 -0
- package/claude/skills/migration-claude-md/SKILL.md +76 -0
- package/claude/skills/migration-context/REQUIREMENT.md +36 -0
- package/claude/skills/migration-context/SKILL.md +95 -0
- package/claude/skills/migration-standards-drop/REQUIREMENT.md +55 -0
- package/claude/skills/migration-standards-drop/SKILL.md +113 -0
- package/claude/skills/migration-superseded/REQUIREMENT.md +44 -0
- package/claude/skills/migration-superseded/SKILL.md +115 -0
- package/claude/skills/project-commands/REQUIREMENT.md +42 -0
- package/claude/skills/project-commands/SKILL.md +85 -0
- package/claude/skills/restate-plainly/REQUIREMENT.md +41 -0
- package/claude/skills/restate-plainly/SKILL.md +39 -0
- package/claude/skills/session-map/REQUIREMENT.md +57 -0
- package/claude/skills/session-map/SKILL.md +70 -0
- package/claude/skills/session-resume/REQUIREMENT.md +49 -0
- package/claude/skills/session-resume/SKILL.md +51 -0
- package/claude/skills/setup-gov/REQUIREMENT.md +37 -0
- package/claude/skills/setup-gov/SKILL.md +77 -0
- package/claude/skills/setup-indexes/REQUIREMENT.md +45 -0
- package/claude/skills/setup-indexes/SKILL.md +153 -0
- package/claude/skills/setup-init/REQUIREMENT.md +45 -0
- package/claude/skills/setup-init/SKILL.md +127 -0
- package/claude/skills/setup-plugins/REQUIREMENT.md +42 -0
- package/claude/skills/setup-plugins/SKILL.md +81 -0
- package/claude/skills/setup-plugins/references/plugin-catalog.md +53 -0
- package/claude/skills/setup-verify/REQUIREMENT.md +39 -0
- package/claude/skills/setup-verify/SKILL.md +51 -0
- package/claude/skills/systematic-debugging/REQUIREMENT.md +41 -0
- package/claude/skills/systematic-debugging/SKILL.md +70 -0
- package/claude/skills/write-human/REQUIREMENT.md +46 -0
- package/claude/skills/write-human/SKILL.md +68 -0
- package/claude/skills/write-human/references/density.md +38 -0
- package/claude/skills/write-human/references/machine-tells.md +107 -0
- package/claude/skills/write-human/references/source-material.md +37 -0
- package/claude/skills/youtube-transcripts/REQUIREMENT.md +38 -0
- package/claude/skills/youtube-transcripts/SKILL.md +34 -0
- package/docs/agents/audits.md +98 -0
- package/docs/agents/capture.md +37 -0
- package/docs/agents/census.md +23 -0
- package/docs/agents/commands.md +146 -0
- package/docs/agents/comments.md +34 -0
- package/docs/agents/context-audit-checks.md +120 -0
- package/docs/agents/context-audit.md +83 -0
- package/docs/agents/counts.md +76 -0
- package/docs/agents/demo.md +86 -0
- package/docs/agents/docs.md +17 -0
- package/docs/agents/gate.md +84 -0
- package/docs/agents/index.md +47 -0
- package/docs/agents/indexes.md +35 -0
- package/docs/agents/install-and-sync.md +385 -0
- package/docs/agents/intake.md +81 -0
- package/docs/agents/key-changes.md +103 -0
- package/docs/agents/label-coverage.md +73 -0
- package/docs/agents/markdown-audit.md +197 -0
- package/docs/agents/output-shape.md +70 -0
- package/docs/agents/overview.md +26 -0
- package/docs/agents/records.md +170 -0
- package/docs/agents/restated.md +81 -0
- package/docs/agents/review-classification.md +77 -0
- package/docs/agents/routing.md +61 -0
- package/docs/agents/rule-citations.md +98 -0
- package/docs/agents/sandbox.md +71 -0
- package/docs/agents/scripting.md +149 -0
- package/docs/agents/sessions.md +120 -0
- package/docs/agents/skills-audit.md +94 -0
- package/docs/agents/skills-reach.md +64 -0
- package/docs/agents/standards-audit.md +38 -0
- package/docs/agents/state-scoped-risk.md +105 -0
- package/docs/agents/superseded.md +85 -0
- package/docs/agents/targets.md +83 -0
- package/docs/agents/tasks.md +200 -0
- package/docs/agents/teach.md +158 -0
- package/docs/agents/test-order.md +56 -0
- package/docs/agents/worktrees.md +62 -0
- package/docs/ai-workflow.md +317 -0
- package/docs/index.md +23 -0
- package/docs/operating-model.md +223 -0
- package/docs/target-projects.md +258 -0
- package/docs/visual-design-workflow.md +151 -0
- package/docs/zshrc-aliases.md +65 -0
- package/governance/rules/ci/700-ci-workflow.md +44 -0
- package/governance/rules/claude/500-prose.md +15 -0
- package/governance/rules/claude/501-markdown.md +14 -0
- package/governance/rules/claude/510-context.md +28 -0
- package/governance/rules/claude/511-indexes.md +14 -0
- package/governance/rules/claude/520-wireframes.md +12 -0
- package/governance/rules/claude/530-requirements.md +11 -0
- package/governance/rules/claude/540-architecture.md +11 -0
- package/governance/rules/claude/550-design.md +11 -0
- package/governance/rules/claude/555-tasks.md +12 -0
- package/governance/rules/claude/556-groundwork.md +11 -0
- package/governance/rules/claude/557-intake.md +11 -0
- package/governance/rules/claude/558-plan.md +22 -0
- package/governance/rules/claude/559-memory.md +11 -0
- package/governance/rules/claude/560-diagrams.md +18 -0
- package/governance/rules/claude/561-teach.md +13 -0
- package/governance/rules/claude/562-session.md +15 -0
- package/governance/rules/claude/570-skill.md +24 -0
- package/governance/rules/claude/575-hooks.md +17 -0
- package/governance/rules/claude/576-settings.md +14 -0
- package/governance/rules/claude/580-readme.md +11 -0
- package/governance/rules/claude/590-rule-authoring.md +12 -0
- package/governance/rules/claude/591-standard-authoring.md +12 -0
- package/governance/rules/claude/592-claude-md.md +19 -0
- package/governance/rules/core/000-constitution.md +30 -0
- package/governance/rules/core/005-behavior.md +27 -0
- package/governance/rules/core/010-testing.md +35 -0
- package/governance/rules/core/015-output.md +20 -0
- package/governance/rules/core/020-concurrency.md +22 -0
- package/governance/rules/core/025-indexes.md +9 -0
- package/governance/rules/core/030-error-handling.md +31 -0
- package/governance/rules/core/035-tasks.md +13 -0
- package/governance/rules/core/040-performance.md +20 -0
- package/governance/rules/core/045-memory.md +12 -0
- package/governance/rules/core/050-logging.md +20 -0
- package/governance/rules/core/055-scratch.md +9 -0
- package/governance/rules/core/060-naming.md +19 -0
- package/governance/rules/core/065-spelling.md +19 -0
- package/governance/rules/core/070-planning.md +18 -0
- package/governance/rules/core/075-dependencies.md +25 -0
- package/governance/rules/core/080-config-comments.md +22 -0
- package/governance/rules/core/085-worktrees.md +17 -0
- package/governance/rules/core/087-git.md +11 -0
- package/governance/rules/core/090-code-comments.md +39 -0
- package/governance/rules/framework/200-react.md +51 -0
- package/governance/rules/framework/210-astro.md +41 -0
- package/governance/rules/framework/220-fastapi.md +43 -0
- package/governance/rules/framework/230-nextjs.md +48 -0
- package/governance/rules/framework/250-tailwind.md +32 -0
- package/governance/rules/framework/260-shadcn.md +34 -0
- package/governance/rules/lang/100-typescript.md +40 -0
- package/governance/rules/lang/110-python.md +42 -0
- package/governance/rules/lang/120-bash.md +19 -0
- package/governance/rules/lib/300-testing-ts.md +39 -0
- package/governance/rules/lib/305-e2e-reliability.md +34 -0
- package/governance/rules/lib/306-test-scope.md +25 -0
- package/governance/rules/lib/310-zod.md +25 -0
- package/governance/rules/lib/320-tanstack-query.md +32 -0
- package/governance/rules/lib/330-testing-py.md +44 -0
- package/governance/rules/lib/340-pydantic.md +38 -0
- package/governance/rules/lib/350-security-web.md +32 -0
- package/governance/rules/lib/360-security-server.md +39 -0
- package/governance/rules/lib/370-database.md +35 -0
- package/governance/rules/snippets/505-at-references.md +9 -0
- package/governance/rules/ui/400-ui.md +36 -0
- package/governance/rules/ui/410-a11y.md +48 -0
- package/governance/rules/ui/420-forms.md +36 -0
- package/governance/rules/ui/430-ux-completeness.md +65 -0
- package/governance/rules/ui/440-surface-capture.md +34 -0
- package/governance/rules/ui/450-link-behavior.md +19 -0
- package/governance/stacks/astro.toml +2 -0
- package/governance/stacks/base.toml +8 -0
- package/governance/stacks/node-server.toml +2 -0
- package/governance/stacks/node.toml +2 -0
- package/governance/stacks/python-fastapi.toml +2 -0
- package/governance/stacks/python.toml +2 -0
- package/governance/stacks/react.toml +2 -0
- package/package.json +69 -0
- package/scripts/config.sh +11 -0
- package/scripts/core/bootstrap.sh +81 -0
- package/scripts/core/check-color-source.sh +41 -0
- package/scripts/core/check-ignore-parity.sh +162 -0
- package/scripts/core/check-plugin-boundary.sh +45 -0
- package/scripts/core/check-seed-independence.sh +59 -0
- package/scripts/core/check-skill-paths.sh +24 -0
- package/scripts/core/clean.sh +36 -0
- package/scripts/core/install-check.sh +101 -0
- package/scripts/core/list-seed-roots.sh +18 -0
- package/scripts/core/regen-claude-copies.sh +10 -0
- package/scripts/core/regen-hero.sh +217 -0
- package/scripts/core/regen-indexes.sh +10 -0
- package/scripts/core/regen-tooling-paths.sh +61 -0
- package/scripts/core/repair-bare-flag.sh +19 -0
- package/scripts/core/snapshot.sh +134 -0
- package/scripts/core/update.sh +35 -0
- package/scripts/docs/list.sh +165 -0
- package/scripts/lib/frontmatter.sh +30 -0
- package/scripts/lib/gov.sh +14 -0
- package/scripts/lib/sandbox-fixtures.sh +191 -0
- package/scripts/lib/sandbox-git.sh +125 -0
- package/scripts/lib/sandbox-path.sh +206 -0
- package/scripts/lib/tooling.sh +35 -0
- package/scripts/lib/ui.sh +266 -0
- package/scripts/lib/worktree.sh +20 -0
- package/scripts/manage-sandbox.sh +466 -0
- package/scripts/snippets/create.sh +156 -0
- package/scripts/standards/list.sh +115 -0
- package/scripts/tooling/create.sh +109 -0
- package/scripts/tooling/verify.sh +179 -0
- package/snippets/align.md +12 -0
- package/snippets/claude/decision-memo.md +39 -0
- package/snippets/claude/feature-recap.md +19 -0
- package/snippets/claude/figma-steps.md +24 -0
- package/snippets/compact-summary.md +5 -0
- package/snippets/decision-help.md +6 -0
- package/snippets/meta-prompt.md +14 -0
- package/snippets/research-prompt.md +7 -0
- package/snippets/session-notes.md +11 -0
- package/snippets/snippets.toml +5 -0
- package/snippets/step-by-step.md +10 -0
- package/snippets/web-research.md +21 -0
- package/src/audits/baseline.ts +201 -0
- package/src/audits/catalog.ts +876 -0
- package/src/audits/run.ts +204 -0
- package/src/autoship/classify.ts +75 -0
- package/src/autoship/paths.ts +51 -0
- package/src/binary.ts +16 -0
- package/src/browser/engine.ts +40 -0
- package/src/census/count.ts +113 -0
- package/src/claude/cases/all.ts +24 -0
- package/src/claude/cases/authoring.ts +53 -0
- package/src/claude/cases/claude-workflow.ts +158 -0
- package/src/claude/cases/git.ts +44 -0
- package/src/claude/cases/misc.ts +27 -0
- package/src/claude/cases/setup.ts +94 -0
- package/src/claude/gitignore.ts +51 -0
- package/src/claude/routing.ts +283 -0
- package/src/claude/seeds-list.ts +47 -0
- package/src/claude/seeds.ts +150 -0
- package/src/claude/settings.ts +151 -0
- package/src/claude/skills-audit.ts +228 -0
- package/src/claude/skills-drift.ts +156 -0
- package/src/claude/skills-list.ts +99 -0
- package/src/claude/skills-rank.ts +320 -0
- package/src/claude/skills-reach.ts +227 -0
- package/src/cli-run.ts +43 -0
- package/src/cli.ts +200 -0
- package/src/commands/audits.ts +350 -0
- package/src/commands/autoship.ts +129 -0
- package/src/commands/capture.ts +133 -0
- package/src/commands/census.ts +105 -0
- package/src/commands/claude.ts +1286 -0
- package/src/commands/comments.ts +240 -0
- package/src/commands/context.ts +857 -0
- package/src/commands/demo.ts +389 -0
- package/src/commands/deps.ts +173 -0
- package/src/commands/design.ts +36 -0
- package/src/commands/docs.ts +60 -0
- package/src/commands/feedback-format.ts +23 -0
- package/src/commands/feedback.ts +112 -0
- package/src/commands/gate.ts +189 -0
- package/src/commands/gov.ts +1265 -0
- package/src/commands/indexes.ts +184 -0
- package/src/commands/init.ts +113 -0
- package/src/commands/intake.ts +406 -0
- package/src/commands/inventory.ts +256 -0
- package/src/commands/labels.ts +361 -0
- package/src/commands/markdown.ts +544 -0
- package/src/commands/migrate.ts +175 -0
- package/src/commands/pass-through.ts +39 -0
- package/src/commands/pr.ts +411 -0
- package/src/commands/records.ts +728 -0
- package/src/commands/sandbox.ts +468 -0
- package/src/commands/secrets.ts +132 -0
- package/src/commands/serve.ts +159 -0
- package/src/commands/sessions.ts +408 -0
- package/src/commands/slides.ts +126 -0
- package/src/commands/snippets.ts +84 -0
- package/src/commands/standards.ts +247 -0
- package/src/commands/sync.ts +428 -0
- package/src/commands/targets.ts +319 -0
- package/src/commands/tasks.ts +743 -0
- package/src/commands/teach.ts +786 -0
- package/src/commands/tooling.ts +573 -0
- package/src/commands/transcripts.ts +44 -0
- package/src/commands/upgrade.ts +231 -0
- package/src/commands/wiki.ts +100 -0
- package/src/commands/worktrees.ts +191 -0
- package/src/comments/scan.ts +338 -0
- package/src/comments/trend.ts +207 -0
- package/src/comments/vocabulary.ts +85 -0
- package/src/context/architecture.ts +364 -0
- package/src/context/audit.ts +790 -0
- package/src/context/citations.ts +196 -0
- package/src/context/folders.ts +186 -0
- package/src/context/gate.ts +57 -0
- package/src/context/index-drift.ts +64 -0
- package/src/context/narration.ts +99 -0
- package/src/copy.ts +30 -0
- package/src/counts/catalogs.ts +96 -0
- package/src/counts/numbers.ts +79 -0
- package/src/counts/scan.ts +314 -0
- package/src/demo/beats.ts +135 -0
- package/src/demo/compile.ts +326 -0
- package/src/demo/container.ts +63 -0
- package/src/demo/cursors.ts +55 -0
- package/src/demo/drive.ts +357 -0
- package/src/demo/pointer.ts +178 -0
- package/src/demo/theme.ts +112 -0
- package/src/deps/audit.ts +153 -0
- package/src/design/parse.ts +116 -0
- package/src/design/render.ts +249 -0
- package/src/docs/read.ts +77 -0
- package/src/exec.ts +16 -0
- package/src/exempt-marker.ts +43 -0
- package/src/frontmatter.ts +13 -0
- package/src/gate/measures.ts +682 -0
- package/src/gate/sequencer.ts +386 -0
- package/src/gate/stages.ts +412 -0
- package/src/git-env.ts +36 -0
- package/src/git-files.ts +105 -0
- package/src/git-ignore.ts +46 -0
- package/src/github-format.ts +13 -0
- package/src/github.ts +24 -0
- package/src/gov/adapter.ts +103 -0
- package/src/gov/citations.ts +514 -0
- package/src/gov/consumed.ts +129 -0
- package/src/gov/install.ts +132 -0
- package/src/gov/list.ts +106 -0
- package/src/gov/payload.ts +39 -0
- package/src/gov/restated.ts +814 -0
- package/src/gov/stacks.ts +205 -0
- package/src/gov/superseded.ts +415 -0
- package/src/gov/test-order.ts +407 -0
- package/src/indexes/frontmatter.ts +46 -0
- package/src/indexes/regen.ts +84 -0
- package/src/indexes/render.ts +201 -0
- package/src/indexes/walk.ts +83 -0
- package/src/init/flags.ts +60 -0
- package/src/init/plan.ts +114 -0
- package/src/init/run.ts +46 -0
- package/src/init/steps.ts +77 -0
- package/src/intake/folder.ts +320 -0
- package/src/intake/items.ts +174 -0
- package/src/inventory/config.ts +117 -0
- package/src/inventory/group.ts +76 -0
- package/src/inventory/subjects.ts +114 -0
- package/src/inventory/walk.ts +129 -0
- package/src/labels/audit.ts +82 -0
- package/src/labels/coverage.ts +79 -0
- package/src/labels/map.ts +101 -0
- package/src/labels/phase.ts +95 -0
- package/src/markdown/bans.ts +94 -0
- package/src/markdown/files.ts +102 -0
- package/src/markdown/gate.ts +28 -0
- package/src/markdown/scan.ts +295 -0
- package/src/markdown/structure.ts +730 -0
- package/src/migrate/apply.ts +115 -0
- package/src/migrate/plan.ts +103 -0
- package/src/migrate/rename.ts +183 -0
- package/src/pr/bijection.ts +145 -0
- package/src/pr/paths.ts +335 -0
- package/src/process/harness.ts +167 -0
- package/src/project-root.ts +19 -0
- package/src/records/backup.ts +455 -0
- package/src/records/migrate.ts +78 -0
- package/src/records/size.ts +260 -0
- package/src/records/validate.ts +1162 -0
- package/src/sandbox/census.ts +228 -0
- package/src/sandbox/coverage.ts +115 -0
- package/src/sandbox/expect.ts +629 -0
- package/src/sandbox/tree.ts +47 -0
- package/src/secrets/marker.ts +30 -0
- package/src/secrets/patterns.ts +142 -0
- package/src/secrets/scan.ts +123 -0
- package/src/secrets/shipped.ts +111 -0
- package/src/seed-marker.ts +74 -0
- package/src/serve/static.ts +322 -0
- package/src/sessions/claim.ts +85 -0
- package/src/sessions/live.ts +79 -0
- package/src/sessions/registry.ts +137 -0
- package/src/sessions/resolve.ts +333 -0
- package/src/slides/layouts.ts +391 -0
- package/src/slides/open.ts +18 -0
- package/src/slides/parse.ts +84 -0
- package/src/slides/render.ts +88 -0
- package/src/slides/styles.ts +44 -0
- package/src/snippets/categories.ts +66 -0
- package/src/snippets/list.ts +32 -0
- package/src/snippets/presets.ts +50 -0
- package/src/standards/audit.ts +132 -0
- package/src/standards/read.ts +100 -0
- package/src/sync/check.ts +692 -0
- package/src/sync/engine.ts +576 -0
- package/src/sync/git.ts +225 -0
- package/src/sync/history.ts +123 -0
- package/src/sync/layout.ts +140 -0
- package/src/sync/reverse.ts +268 -0
- package/src/sync/seeds-report.ts +126 -0
- package/src/sync/stamp.ts +358 -0
- package/src/sync/target.ts +70 -0
- package/src/sync/workflow.ts +200 -0
- package/src/target.ts +43 -0
- package/src/targets/pulls.ts +250 -0
- package/src/targets/registry.ts +203 -0
- package/src/targets/resolve.ts +145 -0
- package/src/targets/sweep.ts +246 -0
- package/src/tasks/archive.ts +506 -0
- package/src/tasks/record.ts +311 -0
- package/src/tasks/trunk.ts +89 -0
- package/src/tasks/validate.ts +1001 -0
- package/src/teach/lesson.ts +180 -0
- package/src/teach/workspace.ts +842 -0
- package/src/tooling/gitignore.ts +122 -0
- package/src/tooling/inject.ts +193 -0
- package/src/tooling/list.ts +39 -0
- package/src/tooling/manifest.ts +176 -0
- package/src/tooling/package.ts +166 -0
- package/src/tooling/read.ts +65 -0
- package/src/tooling/scan.ts +147 -0
- package/src/tooling/stamp.ts +44 -0
- package/src/transcripts/fetch.ts +156 -0
- package/src/transcripts/metadata.ts +54 -0
- package/src/transcripts/vtt.ts +114 -0
- package/src/ui.ts +266 -0
- package/src/version/compare.ts +53 -0
- package/src/version/installed.ts +40 -0
- package/src/version/manager.ts +67 -0
- package/src/version/skew.ts +192 -0
- package/src/wiki/init.ts +85 -0
- package/src/worktree.ts +143 -0
- package/src/worktrees/reclaim.ts +306 -0
- package/standards/architecture.md +72 -0
- package/standards/branch.md +59 -0
- package/standards/commit.md +72 -0
- package/standards/context.md +151 -0
- package/standards/design.md +93 -0
- package/standards/diagrams.md +152 -0
- package/standards/glossary.md +75 -0
- package/standards/groundwork.md +211 -0
- package/standards/index.md +36 -0
- package/standards/intake.md +192 -0
- package/standards/issue.md +94 -0
- package/standards/markdown.md +137 -0
- package/standards/memory.md +144 -0
- package/standards/plan.md +172 -0
- package/standards/pr.md +139 -0
- package/standards/publish.md +51 -0
- package/standards/readme.md +208 -0
- package/standards/requirements.md +70 -0
- package/standards/rule.md +118 -0
- package/standards/session.md +109 -0
- package/standards/skill.md +300 -0
- package/standards/slug.md +39 -0
- package/standards/snippets.md +76 -0
- package/standards/standard.md +170 -0
- package/standards/tasks.md +254 -0
- package/standards/teach.md +153 -0
- package/standards/versioning.md +71 -0
- package/standards/wireframes.md +113 -0
- package/tooling/astro/configs/astro.config.mjs +31 -0
- package/tooling/astro/configs/eslint.config.js +79 -0
- package/tooling/astro/configs/playwright.config.ts +26 -0
- package/tooling/astro/configs/tsconfig.json +12 -0
- package/tooling/astro/configs/vitest.config.ts +22 -0
- package/tooling/astro/manifest.toml +34 -0
- package/tooling/astro/reference.md +60 -0
- package/tooling/base/configs/.editorconfig +5 -0
- package/tooling/base/configs/.github/pull_request_template.md +18 -0
- package/tooling/base/configs/.github/workflows/verify.yml +35 -0
- package/tooling/base/configs/.husky/commit-msg +1 -0
- package/tooling/base/configs/.husky/post-merge +61 -0
- package/tooling/base/configs/.husky/post-rewrite +21 -0
- package/tooling/base/configs/.husky/pre-commit +1 -0
- package/tooling/base/configs/.husky/pre-push +1 -0
- package/tooling/base/configs/.prettierrc +12 -0
- package/tooling/base/configs/.shellcheckrc +1 -0
- package/tooling/base/configs/.vscode/extensions.json +9 -0
- package/tooling/base/configs/.vscode/settings.json +3 -0
- package/tooling/base/configs/commitlint.config.js +11 -0
- package/tooling/base/configs/scripts/verify.sh +64 -0
- package/tooling/base/manifest.toml +30 -0
- package/tooling/base/reference.md +98 -0
- package/tooling/base/seeds/.claude/context/ci.md +33 -0
- package/tooling/base/seeds/.claude/context/development.md +37 -0
- package/tooling/base/seeds/.claude/context/index.md +11 -0
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/base/seeds/.lintstagedrc +8 -0
- package/tooling/base/seeds/.prettierignore +0 -0
- package/tooling/base/seeds/cspell.json +20 -0
- package/tooling/claude/manifest.toml +14 -0
- package/tooling/claude/reference.md +79 -0
- package/tooling/claude/seeds/.claude/ARCHITECTURE.md +13 -0
- package/tooling/claude/seeds/.claude/DESIGN.md +62 -0
- package/tooling/claude/seeds/.claude/REQUIREMENTS.md +18 -0
- package/tooling/claude/seeds/.claude/diagrams/index.md +8 -0
- package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +50 -0
- package/tooling/claude/seeds/.claude/hooks/memory-index.sh +68 -0
- package/tooling/claude/seeds/.claude/hooks/path-form.sh +57 -0
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +52 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +86 -0
- package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +71 -0
- package/tooling/claude/seeds/.claude/memory/index.md +8 -0
- package/tooling/claude/seeds/.claude/settings.json +47 -0
- package/tooling/claude/seeds/.claude/tasks/index.md +8 -0
- package/tooling/claude/seeds/.claude/wireframes/index.md +8 -0
- package/tooling/claude/seeds/CLAUDE.md +29 -0
- package/tooling/claude/user/settings.template.json +10 -0
- package/tooling/claude/user/statusline-command.sh +53 -0
- package/tooling/python/configs/.coveragerc +14 -0
- package/tooling/python/configs/.python-version +1 -0
- package/tooling/python/configs/mypy.ini +6 -0
- package/tooling/python/configs/pytest.ini +4 -0
- package/tooling/python/configs/ruff.toml +15 -0
- package/tooling/python/configs/scripts/verify.sh +77 -0
- package/tooling/python/manifest.toml +16 -0
- package/tooling/python/reference.md +66 -0
- package/tooling/python/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/python/seeds/tests/test_smoke.py +2 -0
- package/tooling/vite-react/configs/playwright.config.ts +26 -0
- package/tooling/vite-react/configs/tsconfig.json +35 -0
- package/tooling/vite-react/configs/vite.config.ts +24 -0
- package/tooling/vite-react/configs/vitest.config.ts +26 -0
- package/tooling/vite-react/manifest.toml +23 -0
- package/tooling/vite-react/reference.md +55 -0
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +1 -0
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +1 -0
- package/tooling/web/configs/.github/workflows/verify.yml +134 -0
- package/tooling/web/configs/.vscode/extensions.json +13 -0
- package/tooling/web/configs/.vscode/settings.json +10 -0
- package/tooling/web/configs/e2e/home.spec.ts +6 -0
- package/tooling/web/configs/e2e/screenshot.ts +53 -0
- package/tooling/web/configs/eslint.config.js +82 -0
- package/tooling/web/configs/scripts/screenshot.sh +28 -0
- package/tooling/web/configs/scripts/verify.sh +80 -0
- package/tooling/web/configs/scripts/worktree-port.sh +74 -0
- package/tooling/web/configs/src/test/setup.ts +8 -0
- package/tooling/web/manifest.toml +58 -0
- package/tooling/web/reference.md +115 -0
- package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { basename } from 'node:path'
|
|
2
|
+
import { $ } from 'bun'
|
|
3
|
+
import { gitEnv } from '@/git-env'
|
|
4
|
+
import {
|
|
5
|
+
type Confidence,
|
|
6
|
+
liveness,
|
|
7
|
+
type LivenessProbes,
|
|
8
|
+
SYSTEM_PROBES,
|
|
9
|
+
} from '@/sessions/live'
|
|
10
|
+
import { readRegistry, type SessionRecord } from '@/sessions/registry'
|
|
11
|
+
|
|
12
|
+
/** Why a row carries no branch, stated rather than left as an absent key. */
|
|
13
|
+
export type Unresolved =
|
|
14
|
+
| 'not-a-repository'
|
|
15
|
+
| 'detached-head'
|
|
16
|
+
| 'git-unavailable'
|
|
17
|
+
|
|
18
|
+
export interface ResolvedSession {
|
|
19
|
+
readonly name: string
|
|
20
|
+
readonly pid: number
|
|
21
|
+
readonly sessionId: string | null
|
|
22
|
+
readonly cwd: string
|
|
23
|
+
readonly kind: string
|
|
24
|
+
readonly status: string
|
|
25
|
+
readonly startedAt: string | null
|
|
26
|
+
/**
|
|
27
|
+
* The stamp the record carries beside `status`, kept alongside the dwell it
|
|
28
|
+
* computes below for a caller comparing two sessions rather than reading the
|
|
29
|
+
* age of one.
|
|
30
|
+
*/
|
|
31
|
+
readonly statusUpdatedAt: string | null
|
|
32
|
+
/**
|
|
33
|
+
* Elapsed milliseconds since `statusUpdatedAt`, falling back to the coarser
|
|
34
|
+
* `updatedAt` when the record predates the narrower field, computed once at
|
|
35
|
+
* read time so three callers do not each convert the raw stamp and each get
|
|
36
|
+
* the clock-skew case wrong. The one record ever measured carrying
|
|
37
|
+
* `status: "waiting"` has exactly this shape: no `statusUpdatedAt`, an
|
|
38
|
+
* `updatedAt` beside it, so a caller reading this field alone would report
|
|
39
|
+
* the single real instance as unmeasured rather than as stalled.
|
|
40
|
+
* Clamped at zero rather than reported negative: a record stamped
|
|
41
|
+
* by a clock running ahead of this one is a skew to absorb, not a session
|
|
42
|
+
* that has not started waiting yet.
|
|
43
|
+
*/
|
|
44
|
+
readonly statusDwellMs: number | null
|
|
45
|
+
/**
|
|
46
|
+
* The shared git directory every worktree of one repository resolves to,
|
|
47
|
+
* which is what identifies the repository a row belongs to. A branch name is
|
|
48
|
+
* unique inside one and says nothing across a machine.
|
|
49
|
+
*/
|
|
50
|
+
readonly repository: string | null
|
|
51
|
+
readonly worktree: string | null
|
|
52
|
+
readonly branch: string | null
|
|
53
|
+
/** Null exactly when `branch` is set. The two are written together. */
|
|
54
|
+
readonly unresolved: Unresolved | null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type SessionReport =
|
|
58
|
+
| { readonly kind: 'absent'; readonly dir: string }
|
|
59
|
+
| {
|
|
60
|
+
readonly kind: 'resolved'
|
|
61
|
+
readonly dir: string
|
|
62
|
+
/**
|
|
63
|
+
* The weakest confidence any surviving row was decided at, so one read
|
|
64
|
+
* tells a caller whether the roster can be trusted as an identity.
|
|
65
|
+
*/
|
|
66
|
+
readonly confidence: Confidence
|
|
67
|
+
readonly sessions: readonly ResolvedSession[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ResolveOptions {
|
|
71
|
+
readonly dir?: string
|
|
72
|
+
readonly probes?: LivenessProbes
|
|
73
|
+
readonly locate?: (cwd: string) => Promise<Located>
|
|
74
|
+
/** The instant the dwell is computed against. Defaults to `Date.now`. */
|
|
75
|
+
readonly now?: () => number
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Located {
|
|
79
|
+
readonly repository: string | null
|
|
80
|
+
readonly worktree: string | null
|
|
81
|
+
readonly branch: string | null
|
|
82
|
+
readonly unresolved: Unresolved | null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Asks git what a directory is checked out to.
|
|
87
|
+
*
|
|
88
|
+
* A detached HEAD and a directory outside any repository are separated here
|
|
89
|
+
* rather than collapsed into one empty answer, because the first is a session
|
|
90
|
+
* that has a worktree and no branch and the second has neither.
|
|
91
|
+
*/
|
|
92
|
+
async function locate(cwd: string): Promise<Located> {
|
|
93
|
+
const top = await $`git -C ${cwd} rev-parse --show-toplevel`
|
|
94
|
+
.env(gitEnv())
|
|
95
|
+
.quiet()
|
|
96
|
+
.nothrow()
|
|
97
|
+
|
|
98
|
+
if (top.exitCode !== 0) {
|
|
99
|
+
// git absent and git refusing the directory are both non-zero here. The
|
|
100
|
+
// first is the platform report the plan asked for and the second is an
|
|
101
|
+
// ordinary answer, so the distinguishing read is whether git ran at all.
|
|
102
|
+
const version = await $`git --version`.quiet().nothrow()
|
|
103
|
+
return {
|
|
104
|
+
repository: null,
|
|
105
|
+
worktree: null,
|
|
106
|
+
branch: null,
|
|
107
|
+
unresolved:
|
|
108
|
+
version.exitCode === 0 ? 'not-a-repository' : 'git-unavailable',
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const worktree = top.stdout.toString().trim()
|
|
113
|
+
const repository = await repositoryOf(cwd)
|
|
114
|
+
const head = await $`git -C ${cwd} branch --show-current`
|
|
115
|
+
.env(gitEnv())
|
|
116
|
+
.quiet()
|
|
117
|
+
.nothrow()
|
|
118
|
+
const branch = head.stdout.toString().trim()
|
|
119
|
+
|
|
120
|
+
if (head.exitCode !== 0 || branch.length === 0) {
|
|
121
|
+
return { repository, worktree, branch: null, unresolved: 'detached-head' }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { repository, worktree, branch, unresolved: null }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolves the shared git directory a working directory belongs to.
|
|
129
|
+
*
|
|
130
|
+
* The common directory is what a linked worktree and its main checkout agree
|
|
131
|
+
* on, so two rows in one repository match here while the toplevel would place
|
|
132
|
+
* every worktree in a repository of its own.
|
|
133
|
+
*/
|
|
134
|
+
export async function repositoryOf(cwd: string): Promise<string | null> {
|
|
135
|
+
const dir =
|
|
136
|
+
await $`git -C ${cwd} rev-parse --path-format=absolute --git-common-dir`
|
|
137
|
+
.env(gitEnv())
|
|
138
|
+
.quiet()
|
|
139
|
+
.nothrow()
|
|
140
|
+
|
|
141
|
+
if (dir.exitCode !== 0) return null
|
|
142
|
+
|
|
143
|
+
const resolved = dir.stdout.toString().trim()
|
|
144
|
+
return resolved.length > 0 ? resolved : null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Falls back to `updatedAt` when `statusUpdatedAt` is absent, since the
|
|
149
|
+
* narrower field is the newer of the two and a record predating it still
|
|
150
|
+
* carries the coarser one. The one record measured with `status: "waiting"`
|
|
151
|
+
* takes exactly this path: no `statusUpdatedAt`, an `updatedAt` beside it.
|
|
152
|
+
*/
|
|
153
|
+
function dwellMs(record: SessionRecord, now: number): number | null {
|
|
154
|
+
const stamp = record.statusUpdatedAt ?? record.updatedAt
|
|
155
|
+
return stamp === undefined ? null : Math.max(0, now - stamp)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* An absent field is rendered as an absence rather than as a value.
|
|
160
|
+
*
|
|
161
|
+
* A missing start time formatted from zero reads as a session launched in 1970
|
|
162
|
+
* and a missing identifier as an empty one, both of which a caller would take
|
|
163
|
+
* for data. Null says the record did not carry it, which is the same
|
|
164
|
+
* distinction the registry draws between an absent folder and an empty one.
|
|
165
|
+
*/
|
|
166
|
+
function present(
|
|
167
|
+
record: SessionRecord,
|
|
168
|
+
located: Located,
|
|
169
|
+
now: number,
|
|
170
|
+
): ResolvedSession {
|
|
171
|
+
return {
|
|
172
|
+
name: record.name,
|
|
173
|
+
pid: record.pid,
|
|
174
|
+
sessionId: record.sessionId ?? null,
|
|
175
|
+
cwd: record.cwd,
|
|
176
|
+
kind: record.kind ?? 'unknown',
|
|
177
|
+
status: record.status ?? 'unknown',
|
|
178
|
+
startedAt:
|
|
179
|
+
record.startedAt === undefined
|
|
180
|
+
? null
|
|
181
|
+
: new Date(record.startedAt).toISOString(),
|
|
182
|
+
statusUpdatedAt:
|
|
183
|
+
record.statusUpdatedAt === undefined
|
|
184
|
+
? null
|
|
185
|
+
: new Date(record.statusUpdatedAt).toISOString(),
|
|
186
|
+
statusDwellMs: dwellMs(record, now),
|
|
187
|
+
repository: located.repository,
|
|
188
|
+
worktree: located.worktree,
|
|
189
|
+
branch: located.branch,
|
|
190
|
+
unresolved: located.unresolved,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Resolves every live session to the worktree and branch it holds.
|
|
196
|
+
*
|
|
197
|
+
* The registry is the whole source. Each session writes its own working
|
|
198
|
+
* directory beside its own name, so a caller matching a listing row to a branch
|
|
199
|
+
* reads both from one record instead of ordering the roster by start time and
|
|
200
|
+
* hoping the order holds.
|
|
201
|
+
*
|
|
202
|
+
* A row whose branch cannot be read is kept and marked. Dropping it would leave
|
|
203
|
+
* a caller unable to tell a session that holds no branch from one the resolver
|
|
204
|
+
* never saw, and the second is the failure this replaces.
|
|
205
|
+
*/
|
|
206
|
+
export async function resolveSessions(
|
|
207
|
+
opts: ResolveOptions = {},
|
|
208
|
+
): Promise<SessionReport> {
|
|
209
|
+
const probes = opts.probes ?? SYSTEM_PROBES
|
|
210
|
+
const find = opts.locate ?? locate
|
|
211
|
+
const now = opts.now?.() ?? Date.now()
|
|
212
|
+
const registry = readRegistry(opts.dir)
|
|
213
|
+
|
|
214
|
+
if (registry.kind === 'absent') return { kind: 'absent', dir: registry.dir }
|
|
215
|
+
|
|
216
|
+
const live: SessionRecord[] = []
|
|
217
|
+
let confidence: Confidence = 'confirmed'
|
|
218
|
+
|
|
219
|
+
for (const record of registry.records) {
|
|
220
|
+
const state = liveness(record, probes)
|
|
221
|
+
if (!state.alive) continue
|
|
222
|
+
if (state.confidence === 'unverified') confidence = 'unverified'
|
|
223
|
+
live.push(record)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const sessions = await Promise.all(
|
|
227
|
+
live.map(async (record) => present(record, await find(record.cwd), now)),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return { kind: 'resolved', dir: registry.dir, confidence, sessions }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The environment slice the caller's own identity is read from. */
|
|
234
|
+
export type Env = Record<string, string | undefined>
|
|
235
|
+
|
|
236
|
+
/** Why the caller's own row could not be named, rather than an empty result. */
|
|
237
|
+
export type SelfUnresolved = 'no-identity' | 'no-row'
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* What the environment offers about the session making the call.
|
|
241
|
+
*
|
|
242
|
+
* Both fields are candidates rather than a pair, since a client sets them
|
|
243
|
+
* independently and a caller can arrive carrying either one alone.
|
|
244
|
+
*/
|
|
245
|
+
export interface SelfIdentity {
|
|
246
|
+
readonly sessionId: string | null
|
|
247
|
+
readonly pid: number | null
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export type SelfReport =
|
|
251
|
+
| { readonly kind: 'self'; readonly session: ResolvedSession }
|
|
252
|
+
| {
|
|
253
|
+
readonly kind: 'unresolved'
|
|
254
|
+
readonly reason: SelfUnresolved
|
|
255
|
+
readonly identity: SelfIdentity
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* A positive integer, which is what separates a pid from a socket named for
|
|
260
|
+
* something else. Signal zero addresses the caller's own process group, so a
|
|
261
|
+
* zero would match a row rather than failing to.
|
|
262
|
+
*/
|
|
263
|
+
function pidOf(value: string | undefined): number | null {
|
|
264
|
+
if (value === undefined) return null
|
|
265
|
+
const pid = Number(value.trim())
|
|
266
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Reads whatever the environment states about the calling session.
|
|
271
|
+
*
|
|
272
|
+
* Three identifier namespaces are in play and only two of them join to a
|
|
273
|
+
* roster row. `CLAUDE_CODE_SESSION_ID` is the roster's own `sessionId` and is
|
|
274
|
+
* read first because it survives a rename, where the name a session carries is
|
|
275
|
+
* derived from what it turned out to be doing and rotates while a build runs.
|
|
276
|
+
* `CLAUDE_CODE_HOST_SESSION_ID` is deliberately never read: it holds a
|
|
277
|
+
* `local_`-prefixed value from the harness namespace that matches no row, and
|
|
278
|
+
* it is the variable a reader searching the environment for a session id finds
|
|
279
|
+
* first.
|
|
280
|
+
*
|
|
281
|
+
* The socket path is the last rung because its basename is the caller's pid by
|
|
282
|
+
* a client convention rather than a published interface, so a client that
|
|
283
|
+
* moves it drops this rung while leaving the two above it standing.
|
|
284
|
+
*/
|
|
285
|
+
export function callerIdentity(env: Env = process.env): SelfIdentity {
|
|
286
|
+
const stated = env.CLAUDE_CODE_SESSION_ID?.trim()
|
|
287
|
+
const socket = env.CLAUDE_CODE_MESSAGING_SOCKET
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
sessionId: stated !== undefined && stated.length > 0 ? stated : null,
|
|
291
|
+
pid:
|
|
292
|
+
pidOf(env.CLAUDE_PID) ??
|
|
293
|
+
pidOf(
|
|
294
|
+
socket === undefined
|
|
295
|
+
? undefined
|
|
296
|
+
: basename(socket).replace(/\.sock$/, ''),
|
|
297
|
+
),
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Names which row of a roster belongs to the caller.
|
|
303
|
+
*
|
|
304
|
+
* The read that already returns every field marks none of them as the caller,
|
|
305
|
+
* so this performs the join rather than adding a source. An identity the
|
|
306
|
+
* environment does not carry and an identity no row matches are separated,
|
|
307
|
+
* because the first is a client that states nothing and the second is a
|
|
308
|
+
* session the roster cannot see. A session driving from Remote Control is the
|
|
309
|
+
* measured instance of the second: it is addressable on the message channel
|
|
310
|
+
* and holds no local process record for the roster to report.
|
|
311
|
+
*/
|
|
312
|
+
export function selfOf(
|
|
313
|
+
sessions: readonly ResolvedSession[],
|
|
314
|
+
identity: SelfIdentity,
|
|
315
|
+
): SelfReport {
|
|
316
|
+
if (identity.sessionId === null && identity.pid === null) {
|
|
317
|
+
return { kind: 'unresolved', reason: 'no-identity', identity }
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const byId =
|
|
321
|
+
identity.sessionId === null
|
|
322
|
+
? undefined
|
|
323
|
+
: sessions.find((session) => session.sessionId === identity.sessionId)
|
|
324
|
+
const byPid =
|
|
325
|
+
identity.pid === null
|
|
326
|
+
? undefined
|
|
327
|
+
: sessions.find((session) => session.pid === identity.pid)
|
|
328
|
+
const match = byId ?? byPid
|
|
329
|
+
|
|
330
|
+
return match === undefined
|
|
331
|
+
? { kind: 'unresolved', reason: 'no-row', identity }
|
|
332
|
+
: { kind: 'self', session: match }
|
|
333
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import type PptxGenJS from 'pptxgenjs'
|
|
2
|
+
import type { Slide } from '@/slides/parse'
|
|
3
|
+
import { FONTS, type Theme, TYPE } from '@/slides/styles'
|
|
4
|
+
|
|
5
|
+
type Deck = InstanceType<typeof PptxGenJS>
|
|
6
|
+
type PptxSlide = ReturnType<Deck['addSlide']>
|
|
7
|
+
|
|
8
|
+
export interface LayoutInfo {
|
|
9
|
+
name: string
|
|
10
|
+
description: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NavEntry {
|
|
14
|
+
title: string
|
|
15
|
+
slideNumber: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DeckNav {
|
|
19
|
+
deckTitle: string
|
|
20
|
+
tocSlide?: number
|
|
21
|
+
sections: NavEntry[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const LAYOUTS: LayoutInfo[] = [
|
|
25
|
+
{ name: 'title', description: 'Cover slide with deck title and subtitle' },
|
|
26
|
+
{ name: 'toc', description: 'Contents slide linking to each section' },
|
|
27
|
+
{ name: 'section', description: 'Section divider with one large heading' },
|
|
28
|
+
{ name: 'bullets', description: 'Title above a bullet list' },
|
|
29
|
+
{ name: 'two-column', description: 'Two labeled columns of bullets' },
|
|
30
|
+
{ name: 'stat-callout', description: 'Row of large stats with captions' },
|
|
31
|
+
{ name: 'grid', description: 'Two-by-two cards, each a term and detail' },
|
|
32
|
+
{ name: 'quote', description: 'Large pull quote with attribution' },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
const W = 13.333
|
|
36
|
+
const H = 7.5
|
|
37
|
+
const MX = 0.7
|
|
38
|
+
const CW = W - MX * 2
|
|
39
|
+
const BODY_Y = 1.7
|
|
40
|
+
|
|
41
|
+
const RENDERERS: Record<
|
|
42
|
+
string,
|
|
43
|
+
(s: PptxSlide, slide: Slide, theme: Theme) => void
|
|
44
|
+
> = {
|
|
45
|
+
title: renderTitle,
|
|
46
|
+
section: renderSection,
|
|
47
|
+
bullets: renderBullets,
|
|
48
|
+
'two-column': renderTwoColumn,
|
|
49
|
+
'stat-callout': renderStatCallout,
|
|
50
|
+
grid: renderGrid,
|
|
51
|
+
quote: renderQuote,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function renderDeckSlide(
|
|
55
|
+
s: PptxSlide,
|
|
56
|
+
slide: Slide,
|
|
57
|
+
theme: Theme,
|
|
58
|
+
): void {
|
|
59
|
+
const renderer = RENDERERS[slide.layout] ?? renderBullets
|
|
60
|
+
renderer(s, slide, theme)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function renderToc(
|
|
64
|
+
s: PptxSlide,
|
|
65
|
+
slide: Slide,
|
|
66
|
+
theme: Theme,
|
|
67
|
+
nav: DeckNav,
|
|
68
|
+
): void {
|
|
69
|
+
addTitle(s, slide.title || 'Contents', theme)
|
|
70
|
+
if (nav.sections.length === 0) return
|
|
71
|
+
s.addText(
|
|
72
|
+
nav.sections.map((entry) => ({
|
|
73
|
+
text: entry.title,
|
|
74
|
+
options: { breakLine: true, hyperlink: { slide: entry.slideNumber } },
|
|
75
|
+
})),
|
|
76
|
+
{
|
|
77
|
+
x: MX,
|
|
78
|
+
y: BODY_Y,
|
|
79
|
+
w: CW,
|
|
80
|
+
h: 5,
|
|
81
|
+
fontFace: FONTS.body,
|
|
82
|
+
fontSize: TYPE.body + 2,
|
|
83
|
+
color: theme.accent,
|
|
84
|
+
valign: 'top',
|
|
85
|
+
lineSpacingMultiple: 1.6,
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function addFooter(
|
|
91
|
+
s: PptxSlide,
|
|
92
|
+
slide: Slide,
|
|
93
|
+
theme: Theme,
|
|
94
|
+
nav: DeckNav,
|
|
95
|
+
): void {
|
|
96
|
+
if (slide.layout === 'title' || slide.layout === 'toc') return
|
|
97
|
+
if (nav.deckTitle) {
|
|
98
|
+
s.addText(nav.deckTitle, {
|
|
99
|
+
x: MX,
|
|
100
|
+
y: H - 0.5,
|
|
101
|
+
w: CW / 2,
|
|
102
|
+
h: 0.3,
|
|
103
|
+
fontFace: FONTS.body,
|
|
104
|
+
fontSize: TYPE.caption,
|
|
105
|
+
color: theme.muted,
|
|
106
|
+
valign: 'middle',
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
if (nav.tocSlide) {
|
|
110
|
+
s.addText('Contents', {
|
|
111
|
+
x: MX + CW / 2,
|
|
112
|
+
y: H - 0.5,
|
|
113
|
+
w: CW / 2,
|
|
114
|
+
h: 0.3,
|
|
115
|
+
fontFace: FONTS.body,
|
|
116
|
+
fontSize: TYPE.caption,
|
|
117
|
+
color: theme.muted,
|
|
118
|
+
align: 'right',
|
|
119
|
+
valign: 'middle',
|
|
120
|
+
hyperlink: { slide: nav.tocSlide },
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function renderTitle(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
126
|
+
s.addText(clean(slide.title), {
|
|
127
|
+
x: MX,
|
|
128
|
+
y: 2.6,
|
|
129
|
+
w: CW,
|
|
130
|
+
h: 1.4,
|
|
131
|
+
fontFace: FONTS.heading,
|
|
132
|
+
fontSize: TYPE.cover,
|
|
133
|
+
bold: true,
|
|
134
|
+
color: theme.ink,
|
|
135
|
+
valign: 'top',
|
|
136
|
+
})
|
|
137
|
+
const subtitle = slide.content.join(' ').trim()
|
|
138
|
+
if (subtitle) {
|
|
139
|
+
s.addText(clean(subtitle), {
|
|
140
|
+
x: MX,
|
|
141
|
+
y: 4.1,
|
|
142
|
+
w: CW,
|
|
143
|
+
h: 1,
|
|
144
|
+
fontFace: FONTS.body,
|
|
145
|
+
fontSize: TYPE.body + 4,
|
|
146
|
+
color: theme.muted,
|
|
147
|
+
valign: 'top',
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function renderSection(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
153
|
+
s.addText(clean(slide.title), {
|
|
154
|
+
x: MX,
|
|
155
|
+
y: 2.9,
|
|
156
|
+
w: CW,
|
|
157
|
+
h: 1.6,
|
|
158
|
+
fontFace: FONTS.heading,
|
|
159
|
+
fontSize: TYPE.section,
|
|
160
|
+
bold: true,
|
|
161
|
+
color: theme.accent,
|
|
162
|
+
valign: 'middle',
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function renderBullets(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
167
|
+
addTitle(s, slide.title, theme)
|
|
168
|
+
const items = bullets(slide.content)
|
|
169
|
+
if (items.length === 0) return
|
|
170
|
+
s.addText(
|
|
171
|
+
items.map((text) => ({
|
|
172
|
+
text,
|
|
173
|
+
options: { bullet: { code: '2022' }, breakLine: true },
|
|
174
|
+
})),
|
|
175
|
+
{
|
|
176
|
+
x: MX,
|
|
177
|
+
y: BODY_Y,
|
|
178
|
+
w: CW,
|
|
179
|
+
h: 5,
|
|
180
|
+
fontFace: FONTS.body,
|
|
181
|
+
fontSize: TYPE.body,
|
|
182
|
+
color: theme.ink,
|
|
183
|
+
valign: 'top',
|
|
184
|
+
lineSpacingMultiple: 1.3,
|
|
185
|
+
paraSpaceAfter: 10,
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function renderTwoColumn(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
191
|
+
addTitle(s, slide.title, theme)
|
|
192
|
+
const cols = columns(slide.content)
|
|
193
|
+
const gap = 0.6
|
|
194
|
+
const colWidth = (CW - gap) / 2
|
|
195
|
+
cols.slice(0, 2).forEach((col, index) => {
|
|
196
|
+
const x = MX + index * (colWidth + gap)
|
|
197
|
+
s.addText(col.heading, {
|
|
198
|
+
x,
|
|
199
|
+
y: BODY_Y,
|
|
200
|
+
w: colWidth,
|
|
201
|
+
h: 0.6,
|
|
202
|
+
fontFace: FONTS.heading,
|
|
203
|
+
fontSize: TYPE.body + 4,
|
|
204
|
+
bold: true,
|
|
205
|
+
color: theme.accent,
|
|
206
|
+
valign: 'top',
|
|
207
|
+
})
|
|
208
|
+
if (col.items.length === 0) return
|
|
209
|
+
s.addText(
|
|
210
|
+
col.items.map((text) => ({
|
|
211
|
+
text,
|
|
212
|
+
options: { bullet: { code: '2022' }, breakLine: true },
|
|
213
|
+
})),
|
|
214
|
+
{
|
|
215
|
+
x,
|
|
216
|
+
y: BODY_Y + 0.7,
|
|
217
|
+
w: colWidth,
|
|
218
|
+
h: 4.3,
|
|
219
|
+
fontFace: FONTS.body,
|
|
220
|
+
fontSize: TYPE.body,
|
|
221
|
+
color: theme.ink,
|
|
222
|
+
valign: 'top',
|
|
223
|
+
lineSpacingMultiple: 1.3,
|
|
224
|
+
paraSpaceAfter: 8,
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderStatCallout(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
231
|
+
addTitle(s, slide.title, theme)
|
|
232
|
+
const stats = pairs(slide.content).slice(0, 4)
|
|
233
|
+
if (stats.length === 0) return
|
|
234
|
+
const gap = 0.5
|
|
235
|
+
const cellWidth = (CW - gap * (stats.length - 1)) / stats.length
|
|
236
|
+
stats.forEach((stat, index) => {
|
|
237
|
+
const x = MX + index * (cellWidth + gap)
|
|
238
|
+
s.addText(stat.left, {
|
|
239
|
+
x,
|
|
240
|
+
y: 2.6,
|
|
241
|
+
w: cellWidth,
|
|
242
|
+
h: 1.3,
|
|
243
|
+
fontFace: FONTS.heading,
|
|
244
|
+
fontSize: TYPE.stat,
|
|
245
|
+
bold: true,
|
|
246
|
+
color: theme.accent,
|
|
247
|
+
align: 'left',
|
|
248
|
+
valign: 'top',
|
|
249
|
+
})
|
|
250
|
+
s.addText(stat.right, {
|
|
251
|
+
x,
|
|
252
|
+
y: 3.9,
|
|
253
|
+
w: cellWidth,
|
|
254
|
+
h: 1.4,
|
|
255
|
+
fontFace: FONTS.body,
|
|
256
|
+
fontSize: TYPE.body,
|
|
257
|
+
color: theme.muted,
|
|
258
|
+
align: 'left',
|
|
259
|
+
valign: 'top',
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function renderGrid(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
265
|
+
addTitle(s, slide.title, theme)
|
|
266
|
+
const cards = pairs(slide.content).slice(0, 4)
|
|
267
|
+
const gap = 0.5
|
|
268
|
+
const cardWidth = (CW - gap) / 2
|
|
269
|
+
const cardHeight = 2.1
|
|
270
|
+
cards.forEach((card, index) => {
|
|
271
|
+
const col = index % 2
|
|
272
|
+
const row = Math.floor(index / 2)
|
|
273
|
+
const x = MX + col * (cardWidth + gap)
|
|
274
|
+
const y = BODY_Y + row * (cardHeight + gap)
|
|
275
|
+
s.addShape('roundRect', {
|
|
276
|
+
x,
|
|
277
|
+
y,
|
|
278
|
+
w: cardWidth,
|
|
279
|
+
h: cardHeight,
|
|
280
|
+
fill: { color: theme.surface },
|
|
281
|
+
line: { type: 'none' },
|
|
282
|
+
rectRadius: 0.08,
|
|
283
|
+
})
|
|
284
|
+
s.addText(card.left, {
|
|
285
|
+
x: x + 0.3,
|
|
286
|
+
y: y + 0.25,
|
|
287
|
+
w: cardWidth - 0.6,
|
|
288
|
+
h: 0.6,
|
|
289
|
+
fontFace: FONTS.heading,
|
|
290
|
+
fontSize: TYPE.body + 4,
|
|
291
|
+
bold: true,
|
|
292
|
+
color: theme.ink,
|
|
293
|
+
valign: 'top',
|
|
294
|
+
})
|
|
295
|
+
s.addText(card.right, {
|
|
296
|
+
x: x + 0.3,
|
|
297
|
+
y: y + 0.95,
|
|
298
|
+
w: cardWidth - 0.6,
|
|
299
|
+
h: cardHeight - 1.2,
|
|
300
|
+
fontFace: FONTS.body,
|
|
301
|
+
fontSize: TYPE.body - 2,
|
|
302
|
+
color: theme.ink,
|
|
303
|
+
valign: 'top',
|
|
304
|
+
lineSpacingMultiple: 1.2,
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function renderQuote(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
310
|
+
const lines = slide.content
|
|
311
|
+
const attribution = lines.find((line) => /^(—|-)\s/.test(line.trim()))
|
|
312
|
+
const quote = lines
|
|
313
|
+
.filter((line) => line !== attribution)
|
|
314
|
+
.join(' ')
|
|
315
|
+
.trim()
|
|
316
|
+
s.addText(clean(quote), {
|
|
317
|
+
x: MX,
|
|
318
|
+
y: 2.2,
|
|
319
|
+
w: CW,
|
|
320
|
+
h: 2.6,
|
|
321
|
+
fontFace: FONTS.heading,
|
|
322
|
+
fontSize: TYPE.quote,
|
|
323
|
+
italic: true,
|
|
324
|
+
color: theme.ink,
|
|
325
|
+
valign: 'top',
|
|
326
|
+
lineSpacingMultiple: 1.25,
|
|
327
|
+
})
|
|
328
|
+
if (attribution) {
|
|
329
|
+
s.addText(clean(attribution.replace(/^(—|-)\s+/, '')), {
|
|
330
|
+
x: MX,
|
|
331
|
+
y: 5,
|
|
332
|
+
w: CW,
|
|
333
|
+
h: 0.6,
|
|
334
|
+
fontFace: FONTS.body,
|
|
335
|
+
fontSize: TYPE.body,
|
|
336
|
+
bold: true,
|
|
337
|
+
color: theme.accent,
|
|
338
|
+
valign: 'top',
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function addTitle(s: PptxSlide, title: string, theme: Theme): void {
|
|
344
|
+
if (!title) return
|
|
345
|
+
s.addText(clean(title), {
|
|
346
|
+
x: MX,
|
|
347
|
+
y: 0.5,
|
|
348
|
+
w: CW,
|
|
349
|
+
h: 0.9,
|
|
350
|
+
fontFace: FONTS.heading,
|
|
351
|
+
fontSize: TYPE.title,
|
|
352
|
+
bold: true,
|
|
353
|
+
color: theme.ink,
|
|
354
|
+
valign: 'top',
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function clean(text: string): string {
|
|
359
|
+
return text.replace(/\*\*/g, '').replace(/`/g, '').trim()
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function bullets(content: string[]): string[] {
|
|
363
|
+
return content
|
|
364
|
+
.filter((line) => line.trim().startsWith('-'))
|
|
365
|
+
.map((line) => clean(line.replace(/^\s*-\s+/, '')))
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function pairs(content: string[]): { left: string; right: string }[] {
|
|
369
|
+
return content
|
|
370
|
+
.filter((line) => line.trim().startsWith('-'))
|
|
371
|
+
.map((line) => line.replace(/^\s*-\s+/, ''))
|
|
372
|
+
.map((line) => {
|
|
373
|
+
const [left, ...rest] = line.split(/\s*:\s*/)
|
|
374
|
+
return { left: clean(left), right: clean(rest.join(': ')) }
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function columns(content: string[]): { heading: string; items: string[] }[] {
|
|
379
|
+
const cols: { heading: string; items: string[] }[] = []
|
|
380
|
+
for (const line of content) {
|
|
381
|
+
const heading = line.match(/^##\s+(.+)/)
|
|
382
|
+
if (heading) {
|
|
383
|
+
cols.push({ heading: clean(heading[1]), items: [] })
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
if (line.trim().startsWith('-') && cols.length > 0) {
|
|
387
|
+
cols[cols.length - 1].items.push(clean(line.replace(/^\s*-\s+/, '')))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return cols
|
|
391
|
+
}
|