@erclx/aitk 0.8.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 +99 -0
- package/claude/.claude-plugin/plugin.json +19 -0
- package/claude/skills/bash-script/SKILL.md +96 -0
- package/claude/skills/bash-script/references/patterns.md +349 -0
- package/claude/skills/ci-workflow/SKILL.md +52 -0
- package/claude/skills/ci-workflow/references/workflows.md +74 -0
- package/claude/skills/claude-address-review/SKILL.md +107 -0
- package/claude/skills/claude-autoship/SKILL.md +137 -0
- package/claude/skills/claude-design-extract/SKILL.md +69 -0
- package/claude/skills/claude-design-propose/SKILL.md +75 -0
- package/claude/skills/claude-diagram/SKILL.md +126 -0
- package/claude/skills/claude-docs/SKILL.md +204 -0
- package/claude/skills/claude-feature/REQUIREMENT.md +35 -0
- package/claude/skills/claude-feature/SKILL.md +148 -0
- package/claude/skills/claude-groundwork/SKILL.md +148 -0
- package/claude/skills/claude-groundwork/references/folder-format.md +107 -0
- package/claude/skills/claude-memory-capture/SKILL.md +80 -0
- package/claude/skills/claude-memory-review/REQUIREMENT.md +37 -0
- package/claude/skills/claude-memory-review/SKILL.md +206 -0
- package/claude/skills/claude-orchestrate/SKILL.md +125 -0
- package/claude/skills/claude-pr-review/SKILL.md +150 -0
- package/claude/skills/claude-review/SKILL.md +136 -0
- package/claude/skills/claude-roadmap/SKILL.md +61 -0
- package/claude/skills/claude-roadmap/references/roadmap.md +52 -0
- package/claude/skills/claude-screencast/SKILL.md +160 -0
- package/claude/skills/claude-seed-sync/SKILL.md +141 -0
- package/claude/skills/claude-slides-draft/SKILL.md +62 -0
- package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
- package/claude/skills/claude-standards-audit/SKILL.md +79 -0
- package/claude/skills/claude-standards-audit/references/branch.md +49 -0
- package/claude/skills/claude-standards-audit/references/pr.md +124 -0
- package/claude/skills/claude-tasks/SKILL.md +123 -0
- package/claude/skills/claude-ui-test/SKILL.md +75 -0
- package/claude/skills/claude-ux-audit/SKILL.md +78 -0
- package/claude/skills/claude-worktree/SKILL.md +79 -0
- package/claude/skills/cli-script/SKILL.md +47 -0
- package/claude/skills/cli-script/references/template.md +43 -0
- package/claude/skills/create-rule/SKILL.md +68 -0
- package/claude/skills/create-skill/REQUIREMENT.md +30 -0
- package/claude/skills/create-skill/SKILL.md +24 -0
- package/claude/skills/create-snippet/SKILL.md +28 -0
- package/claude/skills/create-snippet/references/snippets.md +57 -0
- package/claude/skills/create-standard/SKILL.md +31 -0
- package/claude/skills/create-standard/references/standard.md +52 -0
- package/claude/skills/docs-sync/SKILL.md +87 -0
- package/claude/skills/git-branch/REQUIREMENT.md +38 -0
- package/claude/skills/git-branch/SKILL.md +60 -0
- package/claude/skills/git-branch/references/branch.md +49 -0
- package/claude/skills/git-commit/REQUIREMENT.md +33 -0
- package/claude/skills/git-commit/SKILL.md +51 -0
- package/claude/skills/git-commit/references/commit.md +63 -0
- package/claude/skills/git-followup/REQUIREMENT.md +38 -0
- package/claude/skills/git-followup/SKILL.md +35 -0
- package/claude/skills/git-issue/REQUIREMENT.md +38 -0
- package/claude/skills/git-issue/SKILL.md +69 -0
- package/claude/skills/git-issue/references/issue.md +84 -0
- package/claude/skills/git-pr/REQUIREMENT.md +37 -0
- package/claude/skills/git-pr/SKILL.md +119 -0
- package/claude/skills/git-pr/references/branch.md +49 -0
- package/claude/skills/git-pr/references/pr.md +124 -0
- package/claude/skills/git-ship/REQUIREMENT.md +34 -0
- package/claude/skills/git-ship/SKILL.md +39 -0
- package/claude/skills/git-split/REQUIREMENT.md +38 -0
- package/claude/skills/git-split/SKILL.md +160 -0
- package/claude/skills/git-split/references/branch.md +49 -0
- package/claude/skills/git-split/references/pr.md +124 -0
- package/claude/skills/git-stage/REQUIREMENT.md +36 -0
- package/claude/skills/git-stage/SKILL.md +74 -0
- package/claude/skills/git-stage/references/commit.md +63 -0
- package/claude/skills/git-worktree/REQUIREMENT.md +37 -0
- package/claude/skills/git-worktree/SKILL.md +120 -0
- package/claude/skills/migration-claude-md/SKILL.md +74 -0
- package/claude/skills/migration-context/SKILL.md +93 -0
- package/claude/skills/migration-standards/SKILL.md +75 -0
- package/claude/skills/project-commands/SKILL.md +73 -0
- package/claude/skills/session-resume/SKILL.md +36 -0
- package/claude/skills/setup-gov/SKILL.md +71 -0
- package/claude/skills/setup-indexes/SKILL.md +148 -0
- package/claude/skills/setup-init/SKILL.md +114 -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/SKILL.md +51 -0
- package/claude/skills/systematic-debugging/SKILL.md +70 -0
- package/claude/skills/toolkit-cli/SKILL.md +38 -0
- package/claude/skills/toolkit-feedback/SKILL.md +80 -0
- package/claude/skills/toolkit-operator/SKILL.md +45 -0
- package/claude/skills/toolkit-triage/SKILL.md +63 -0
- package/claude/skills/youtube-transcripts/SKILL.md +34 -0
- package/docs/agents.md +371 -0
- package/docs/ai-workflow.md +157 -0
- package/docs/index.md +20 -0
- package/docs/operating-model.md +106 -0
- package/docs/target-projects.md +183 -0
- package/docs/visual-design-workflow.md +136 -0
- package/docs/zshrc-aliases.md +65 -0
- package/governance/rules/claude/500-prose.md +12 -0
- package/governance/rules/claude/510-context.md +16 -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/560-diagrams.md +11 -0
- package/governance/rules/claude/570-skill.md +18 -0
- package/governance/rules/claude/580-readme.md +11 -0
- package/governance/rules/claude/590-rule-authoring.md +12 -0
- package/governance/rules/core/000-constitution.md +30 -0
- package/governance/rules/core/010-testing.md +35 -0
- package/governance/rules/core/020-concurrency.md +22 -0
- package/governance/rules/core/030-error-handling.md +31 -0
- package/governance/rules/core/040-performance.md +20 -0
- package/governance/rules/core/050-logging.md +20 -0
- package/governance/rules/core/060-naming.md +19 -0
- package/governance/rules/core/070-planning.md +13 -0
- package/governance/rules/core/080-config-comments.md +22 -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/lib/300-testing-ts.md +38 -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 +31 -0
- package/governance/rules/ui/400-ui.md +34 -0
- package/governance/rules/ui/410-a11y.md +44 -0
- package/governance/rules/ui/420-forms.md +34 -0
- package/governance/rules/ui/430-ux-completeness.md +62 -0
- package/governance/stacks/astro.toml +2 -0
- package/governance/stacks/base.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 +67 -0
- package/scripts/config.sh +11 -0
- package/scripts/core/bootstrap.sh +81 -0
- package/scripts/core/check-plugin-boundary.sh +45 -0
- package/scripts/core/check-skill-paths.sh +25 -0
- package/scripts/core/clean.sh +36 -0
- package/scripts/core/install-check.sh +93 -0
- package/scripts/core/regen-claude-copies.sh +26 -0
- package/scripts/core/regen-indexes.sh +10 -0
- package/scripts/core/regen-skill-references.sh +27 -0
- package/scripts/core/snapshot.sh +132 -0
- package/scripts/core/update.sh +35 -0
- package/scripts/core/verify.sh +201 -0
- package/scripts/docs/list.sh +129 -0
- package/scripts/gov/list.sh +234 -0
- package/scripts/lib/frontmatter.sh +30 -0
- package/scripts/lib/gov.sh +14 -0
- package/scripts/lib/sandbox-fixtures.sh +109 -0
- package/scripts/lib/sandbox-git.sh +73 -0
- package/scripts/lib/tooling.sh +24 -0
- package/scripts/lib/ui.sh +173 -0
- package/scripts/lib/worktree.sh +20 -0
- package/scripts/manage-sandbox.sh +453 -0
- package/scripts/snippets/create.sh +156 -0
- package/scripts/standards/list.sh +91 -0
- package/scripts/tooling/create.sh +109 -0
- package/scripts/tooling/ref.sh +141 -0
- package/scripts/tooling/verify.sh +179 -0
- package/snippets/claude/decision-memo.md +39 -0
- package/snippets/claude/feature-recap.md +19 -0
- package/snippets/claude/figma-steps.md +18 -0
- package/snippets/claude/orchestrator-resume.md +23 -0
- package/snippets/claude/orchestrator-sweep.md +10 -0
- package/snippets/compact-summary.md +5 -0
- package/snippets/create-snippet.md +7 -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 +6 -0
- package/snippets/step-by-step.md +10 -0
- package/snippets/web-research.md +21 -0
- package/src/claude/gitignore.ts +51 -0
- package/src/claude/seeds-list.ts +47 -0
- package/src/claude/seeds.ts +124 -0
- package/src/claude/settings.ts +151 -0
- package/src/cli-run.ts +43 -0
- package/src/cli.ts +111 -0
- package/src/commands/claude.ts +349 -0
- package/src/commands/design.ts +39 -0
- package/src/commands/docs.ts +59 -0
- package/src/commands/feedback-format.ts +23 -0
- package/src/commands/feedback.ts +103 -0
- package/src/commands/gov.ts +268 -0
- package/src/commands/indexes.ts +182 -0
- package/src/commands/init.ts +117 -0
- package/src/commands/pass-through.ts +39 -0
- package/src/commands/sandbox.ts +315 -0
- package/src/commands/slides.ts +106 -0
- package/src/commands/snippets.ts +236 -0
- package/src/commands/standards.ts +89 -0
- package/src/commands/sync.ts +211 -0
- package/src/commands/tasks.ts +185 -0
- package/src/commands/tooling.ts +432 -0
- package/src/commands/transcripts.ts +48 -0
- package/src/commands/wiki.ts +101 -0
- package/src/copy.ts +30 -0
- package/src/design/parse.ts +79 -0
- package/src/design/render.ts +170 -0
- package/src/docs/read.ts +59 -0
- package/src/exec.ts +17 -0
- package/src/frontmatter.ts +13 -0
- package/src/github-format.ts +13 -0
- package/src/github.ts +20 -0
- package/src/gov/adapter.ts +73 -0
- package/src/gov/install.ts +103 -0
- package/src/gov/payload.ts +39 -0
- package/src/gov/stacks.ts +124 -0
- package/src/indexes/frontmatter.ts +46 -0
- package/src/indexes/regen.ts +84 -0
- package/src/indexes/render.ts +181 -0
- package/src/indexes/walk.ts +111 -0
- package/src/init/flags.ts +66 -0
- package/src/init/plan.ts +128 -0
- package/src/init/run.ts +46 -0
- package/src/init/steps.ts +91 -0
- package/src/sandbox/coverage.ts +115 -0
- package/src/sandbox/expect.ts +412 -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/adapter.ts +34 -0
- package/src/snippets/categories.ts +59 -0
- package/src/snippets/install.ts +125 -0
- package/src/snippets/list.ts +32 -0
- package/src/snippets/presets.ts +50 -0
- package/src/standards/adapter.ts +51 -0
- package/src/standards/index-refresh.ts +44 -0
- package/src/standards/install.ts +52 -0
- package/src/sync/check.ts +269 -0
- package/src/sync/engine.ts +416 -0
- package/src/sync/git.ts +204 -0
- package/src/sync/stamp.ts +202 -0
- package/src/sync/target.ts +79 -0
- package/src/sync/workflow.ts +201 -0
- package/src/target.ts +43 -0
- package/src/tasks/archive.ts +304 -0
- package/src/tooling/gitignore.ts +122 -0
- package/src/tooling/inject.ts +214 -0
- package/src/tooling/list.ts +39 -0
- package/src/tooling/manifest.ts +178 -0
- package/src/tooling/package.ts +166 -0
- package/src/tooling/scan.ts +186 -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 +187 -0
- package/src/wiki/init.ts +85 -0
- package/standards/architecture.md +41 -0
- package/standards/bundled/branch.md +49 -0
- package/standards/bundled/commit.md +63 -0
- package/standards/bundled/issue.md +84 -0
- package/standards/bundled/pr.md +124 -0
- package/standards/bundled/roadmap.md +52 -0
- package/standards/bundled/snippets.md +57 -0
- package/standards/bundled/standard.md +52 -0
- package/standards/context.md +132 -0
- package/standards/design.md +31 -0
- package/standards/diagrams.md +55 -0
- package/standards/index.md +21 -0
- package/standards/prose.md +122 -0
- package/standards/readme.md +163 -0
- package/standards/requirements.md +61 -0
- package/standards/rule.md +79 -0
- package/standards/skill.md +257 -0
- package/standards/tasks.md +140 -0
- package/standards/versioning.md +47 -0
- package/standards/wireframes.md +67 -0
- package/tooling/astro/configs/astro.config.mjs +17 -0
- package/tooling/astro/configs/eslint.config.js +79 -0
- package/tooling/astro/configs/playwright.config.ts +25 -0
- package/tooling/astro/configs/tsconfig.json +12 -0
- package/tooling/astro/configs/vitest.config.ts +22 -0
- package/tooling/astro/manifest.toml +32 -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/clean.sh +45 -0
- package/tooling/base/configs/scripts/update.sh +49 -0
- package/tooling/base/configs/scripts/verify.sh +64 -0
- package/tooling/base/manifest.toml +32 -0
- package/tooling/base/reference.md +114 -0
- package/tooling/base/seeds/.claude/context/ci.md +27 -0
- package/tooling/base/seeds/.claude/context/development.md +32 -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 +11 -0
- package/tooling/claude/reference.md +62 -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/context/index.md +8 -0
- package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +43 -0
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +45 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +61 -0
- package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +60 -0
- package/tooling/claude/seeds/.claude/settings.json +39 -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 +87 -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 +25 -0
- package/tooling/vite-react/configs/tsconfig.json +35 -0
- package/tooling/vite-react/configs/vite.config.ts +14 -0
- package/tooling/vite-react/configs/vitest.config.ts +26 -0
- package/tooling/vite-react/manifest.toml +21 -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 +41 -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/src/test/setup.ts +8 -0
- package/tooling/web/manifest.toml +57 -0
- package/tooling/web/reference.md +95 -0
- package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
4
|
+
import type { Command } from 'commander'
|
|
5
|
+
import { registerPassThroughVerbs } from '@/commands/pass-through'
|
|
6
|
+
import { PROJECT_ROOT } from '@/exec'
|
|
7
|
+
import { createGovAdapter } from '@/gov/adapter'
|
|
8
|
+
import { hasStandards, installRules, lookupRules } from '@/gov/install'
|
|
9
|
+
import { buildRulesPayload, listRuleFiles } from '@/gov/payload'
|
|
10
|
+
import {
|
|
11
|
+
govStackExists,
|
|
12
|
+
listGovStacks,
|
|
13
|
+
mergeExtraRules,
|
|
14
|
+
resolveRules,
|
|
15
|
+
} from '@/gov/stacks'
|
|
16
|
+
import { recordStamp, runDomainSync } from '@/sync/engine'
|
|
17
|
+
import { resolveTarget } from '@/target'
|
|
18
|
+
import {
|
|
19
|
+
intro,
|
|
20
|
+
isNonInteractive,
|
|
21
|
+
logAdd,
|
|
22
|
+
logError,
|
|
23
|
+
logInfo,
|
|
24
|
+
logStep,
|
|
25
|
+
logWarn,
|
|
26
|
+
outro,
|
|
27
|
+
select,
|
|
28
|
+
} from '@/ui'
|
|
29
|
+
|
|
30
|
+
const GREEN = '\x1b[0;32m'
|
|
31
|
+
const NC = '\x1b[0m'
|
|
32
|
+
|
|
33
|
+
const PAYLOAD_REL = join('.claude', '.tmp', 'gov', 'rules.md')
|
|
34
|
+
const RULES_REL = join('.claude', 'rules')
|
|
35
|
+
|
|
36
|
+
const PASS_THROUGH_VERBS = ['list'] as const
|
|
37
|
+
|
|
38
|
+
interface InstallOptions {
|
|
39
|
+
readonly add?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function register(program: Command): void {
|
|
43
|
+
const gov = program
|
|
44
|
+
.command('gov')
|
|
45
|
+
.description('Governance commands (install, sync, build, list)')
|
|
46
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
47
|
+
|
|
48
|
+
gov
|
|
49
|
+
.command('install')
|
|
50
|
+
.description('Install a governance stack into .claude/rules/')
|
|
51
|
+
.argument('[stack]', 'Stack name (e.g. base, node, react)')
|
|
52
|
+
.argument('[target]', 'Target directory', '.')
|
|
53
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
54
|
+
.option('--add <rules>', 'Comma-separated rules to layer on the stack')
|
|
55
|
+
.addHelpText(
|
|
56
|
+
'after',
|
|
57
|
+
[
|
|
58
|
+
'',
|
|
59
|
+
'Examples:',
|
|
60
|
+
' aitk gov install react',
|
|
61
|
+
' aitk gov install node ../my-app',
|
|
62
|
+
' aitk gov install astro --add 200-react,260-shadcn ../my-app',
|
|
63
|
+
'',
|
|
64
|
+
].join('\n'),
|
|
65
|
+
)
|
|
66
|
+
.action(
|
|
67
|
+
async (
|
|
68
|
+
stack: string | undefined,
|
|
69
|
+
target: string,
|
|
70
|
+
opts: InstallOptions,
|
|
71
|
+
) => {
|
|
72
|
+
process.exitCode = await runInstall(stack, target, opts)
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
gov
|
|
77
|
+
.command('sync')
|
|
78
|
+
.description('Update rules already installed under .claude/rules/')
|
|
79
|
+
.argument('[target]', 'Target directory', '.')
|
|
80
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
81
|
+
.action(async (target: string) => {
|
|
82
|
+
process.exitCode = await runDomainSync(
|
|
83
|
+
createGovAdapter(PROJECT_ROOT),
|
|
84
|
+
target,
|
|
85
|
+
{ protectedRoot: PROJECT_ROOT },
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
gov
|
|
90
|
+
.command('build')
|
|
91
|
+
.description('Concatenate installed rules into .claude/.tmp/gov/rules.md')
|
|
92
|
+
.argument('[target]', 'Target directory', '.')
|
|
93
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
94
|
+
.action(async (target: string) => {
|
|
95
|
+
process.exitCode = await runBuild(target)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
registerPassThroughVerbs(gov, 'gov', PASS_THROUGH_VERBS)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Renders the target-relative path the prompt quotes, keeping the argument the
|
|
103
|
+
* caller typed rather than the absolute path it resolves to.
|
|
104
|
+
*/
|
|
105
|
+
function displayPath(target: string, rel: string): string {
|
|
106
|
+
const trimmed = target.replace(/\/$/, '').replace(/^\.\//, '')
|
|
107
|
+
return trimmed === '' || trimmed === '.' ? rel : `${trimmed}/${rel}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Refuses rather than picking. `select_option` returned `options[0]` under
|
|
112
|
+
* `AITK_NON_INTERACTIVE=1`, so a headless `aitk gov install` with no stack
|
|
113
|
+
* installed whichever stack sorted first, measured as 26 astro rules into an
|
|
114
|
+
* empty directory. Every documented agent path already passes the argument.
|
|
115
|
+
*/
|
|
116
|
+
async function chooseStack(root: string): Promise<string | number> {
|
|
117
|
+
const stacks = listGovStacks(root)
|
|
118
|
+
|
|
119
|
+
if (stacks.length === 0) {
|
|
120
|
+
logError('No stacks found in governance/stacks/')
|
|
121
|
+
outro()
|
|
122
|
+
return 1
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isNonInteractive()) {
|
|
126
|
+
logError(
|
|
127
|
+
`Stack argument is required in non-interactive mode. One of: ${stacks.join(', ')}.`,
|
|
128
|
+
)
|
|
129
|
+
outro()
|
|
130
|
+
return 1
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return select({
|
|
134
|
+
message: 'Select stack to install:',
|
|
135
|
+
options: stacks.map((stack) => ({ value: stack, label: stack })),
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function runInstall(
|
|
140
|
+
stack: string | undefined,
|
|
141
|
+
target: string,
|
|
142
|
+
opts: InstallOptions,
|
|
143
|
+
): Promise<number> {
|
|
144
|
+
intro('aitk gov install')
|
|
145
|
+
|
|
146
|
+
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
147
|
+
if (typeof resolved === 'number') return resolved
|
|
148
|
+
|
|
149
|
+
let selected = stack
|
|
150
|
+
if (selected === undefined) {
|
|
151
|
+
const choice = await chooseStack(PROJECT_ROOT)
|
|
152
|
+
if (typeof choice === 'number') return choice
|
|
153
|
+
selected = choice
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!govStackExists(PROJECT_ROOT, selected)) {
|
|
157
|
+
logError(`Stack not found: ${selected}`)
|
|
158
|
+
outro()
|
|
159
|
+
return 1
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const resolution = resolveRules(PROJECT_ROOT, selected)
|
|
163
|
+
if (!resolution.ok) {
|
|
164
|
+
logError(`Stack not found: ${resolution.missingStack}`)
|
|
165
|
+
outro()
|
|
166
|
+
return 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const add = opts.add ?? ''
|
|
170
|
+
const rules =
|
|
171
|
+
add === '' ? resolution.rules : mergeExtraRules(resolution.rules, add)
|
|
172
|
+
|
|
173
|
+
if (rules.length === 0) {
|
|
174
|
+
logWarn(`No rules defined for stack: ${selected}`)
|
|
175
|
+
outro()
|
|
176
|
+
return 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
logStep(
|
|
180
|
+
add === ''
|
|
181
|
+
? `Resolving stack: ${selected}`
|
|
182
|
+
: `Resolving stack: ${selected} + extras`,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const { found, missing } = lookupRules(PROJECT_ROOT, rules)
|
|
186
|
+
for (const rule of missing) logWarn(`${rule} (source not found, skipping)`)
|
|
187
|
+
for (const source of found) logInfo(source.rule)
|
|
188
|
+
|
|
189
|
+
const shouldInstall = await select({
|
|
190
|
+
message: `Install ${found.length} rules to ${displayPath(target, RULES_REL)}?`,
|
|
191
|
+
options: [
|
|
192
|
+
{ value: true, label: 'Yes' },
|
|
193
|
+
{ value: false, label: 'No' },
|
|
194
|
+
],
|
|
195
|
+
nonInteractiveDefault: true,
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
if (!shouldInstall) {
|
|
199
|
+
logWarn('Cancelled')
|
|
200
|
+
outro()
|
|
201
|
+
return 0
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
logStep('Installing rules')
|
|
205
|
+
for (const rel of await installRules(found, resolved)) logAdd(rel)
|
|
206
|
+
await recordStamp(createGovAdapter(PROJECT_ROOT), resolved, new Date())
|
|
207
|
+
|
|
208
|
+
if (!hasStandards(resolved)) {
|
|
209
|
+
logWarn(
|
|
210
|
+
`Rules reference .claude/standards/. Run 'aitk standards install ${target}' or 'aitk init' so the references resolve.`,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
outro()
|
|
215
|
+
process.stderr.write(`${GREEN}✓ Rules installed${NC}\n`)
|
|
216
|
+
return 0
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Emits the rules payload the `gov-*` skills read. The output lands in the
|
|
221
|
+
* target's scratch directory rather than stdout because the file is the
|
|
222
|
+
* contract, and the skills point at the path.
|
|
223
|
+
*/
|
|
224
|
+
async function runBuild(target: string): Promise<number> {
|
|
225
|
+
intro('aitk gov build')
|
|
226
|
+
|
|
227
|
+
const resolved = resolve(target)
|
|
228
|
+
const rulesDir = join(resolved, RULES_REL)
|
|
229
|
+
|
|
230
|
+
if (!existsSync(rulesDir)) {
|
|
231
|
+
logError(`No rules found at ${rulesDir}. Run \`aitk gov install\` first.`)
|
|
232
|
+
outro()
|
|
233
|
+
return 1
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const files = listRuleFiles(rulesDir)
|
|
237
|
+
logStep(`Reading ${RULES_REL} (${files.length} found)`)
|
|
238
|
+
for (const file of files) {
|
|
239
|
+
logInfo(basename(file))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const shouldBuild = await select({
|
|
243
|
+
message: `Build ${files.length} rules to ${PAYLOAD_REL}?`,
|
|
244
|
+
options: [
|
|
245
|
+
{ value: true, label: 'Yes' },
|
|
246
|
+
{ value: false, label: 'No' },
|
|
247
|
+
],
|
|
248
|
+
nonInteractiveDefault: true,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
if (!shouldBuild) {
|
|
252
|
+
logWarn('Cancelled')
|
|
253
|
+
outro()
|
|
254
|
+
return 0
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
logStep('Building rules payload')
|
|
258
|
+
const output = join(resolved, PAYLOAD_REL)
|
|
259
|
+
await mkdir(dirname(output), { recursive: true })
|
|
260
|
+
await writeFile(output, buildRulesPayload(files))
|
|
261
|
+
logAdd(PAYLOAD_REL)
|
|
262
|
+
|
|
263
|
+
outro()
|
|
264
|
+
process.stderr.write(
|
|
265
|
+
`${GREEN}✓ Rules built (${files.length} rules → ${PAYLOAD_REL})${NC}\n`,
|
|
266
|
+
)
|
|
267
|
+
return 0
|
|
268
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises'
|
|
2
|
+
import { dirname, resolve } from 'node:path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import type { Command } from 'commander'
|
|
5
|
+
import { exitCodeFor, type RegenResult, regenOne } from '@/indexes/regen'
|
|
6
|
+
import { findIndexedAncestor, isIgnored, listIndexes } from '@/indexes/walk'
|
|
7
|
+
import { intro, logAdd, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
8
|
+
|
|
9
|
+
interface RegenCommandOptions {
|
|
10
|
+
readonly dryRun?: boolean
|
|
11
|
+
readonly json?: boolean
|
|
12
|
+
readonly root?: string
|
|
13
|
+
readonly stage?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function register(program: Command): void {
|
|
17
|
+
const indexes = program
|
|
18
|
+
.command('indexes')
|
|
19
|
+
.description('Regenerate index.md files from sibling frontmatter')
|
|
20
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
21
|
+
|
|
22
|
+
indexes
|
|
23
|
+
.command('regen')
|
|
24
|
+
.description('Regenerate index.md files from sibling frontmatter')
|
|
25
|
+
.argument(
|
|
26
|
+
'[paths...]',
|
|
27
|
+
'Paths to resolve up to their nearest indexed folder',
|
|
28
|
+
)
|
|
29
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
30
|
+
.option('--dry-run', 'Report changes without writing')
|
|
31
|
+
.option('--json', 'Emit a machine-readable record per index on stdout')
|
|
32
|
+
.option('--root <path>', 'Walk-up boundary for positional paths')
|
|
33
|
+
.option('--no-stage', 'Skip the auto git add on modified indexes')
|
|
34
|
+
.addHelpText(
|
|
35
|
+
'after',
|
|
36
|
+
[
|
|
37
|
+
'',
|
|
38
|
+
'Exit codes:',
|
|
39
|
+
' 0 no drift, nothing to do',
|
|
40
|
+
' 1 frontmatter error or missing index.md',
|
|
41
|
+
' 2 --dry-run found folders that would change',
|
|
42
|
+
'',
|
|
43
|
+
'Examples:',
|
|
44
|
+
' aitk indexes regen',
|
|
45
|
+
' aitk indexes regen --dry-run',
|
|
46
|
+
' aitk indexes regen --json docs/',
|
|
47
|
+
'',
|
|
48
|
+
].join('\n'),
|
|
49
|
+
)
|
|
50
|
+
.action(async (paths: string[], opts: RegenCommandOptions) => {
|
|
51
|
+
process.exitCode = await runRegen(paths, opts)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runRegen(
|
|
56
|
+
paths: string[],
|
|
57
|
+
opts: RegenCommandOptions,
|
|
58
|
+
): Promise<number> {
|
|
59
|
+
const root = resolve(opts.root ?? process.cwd())
|
|
60
|
+
const dryRun = opts.dryRun ?? false
|
|
61
|
+
const emitJson = opts.json ?? false
|
|
62
|
+
|
|
63
|
+
if (!(await isDirectory(root))) {
|
|
64
|
+
if (emitJson) {
|
|
65
|
+
process.stdout.write(
|
|
66
|
+
`${JSON.stringify({ error: `root not a directory: ${root}` })}\n`,
|
|
67
|
+
)
|
|
68
|
+
return 1
|
|
69
|
+
}
|
|
70
|
+
process.stderr.write(`Root is not a directory: ${root}\n`)
|
|
71
|
+
return 1
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const dirs =
|
|
75
|
+
paths.length > 0
|
|
76
|
+
? collectFromPaths(paths, root)
|
|
77
|
+
: (await listIndexes(root)).map(dirname)
|
|
78
|
+
|
|
79
|
+
const shouldStage =
|
|
80
|
+
paths.length > 0 && opts.stage !== false && !dryRun && !emitJson
|
|
81
|
+
|
|
82
|
+
const results: RegenResult[] = []
|
|
83
|
+
|
|
84
|
+
if (!emitJson) {
|
|
85
|
+
intro('aitk indexes')
|
|
86
|
+
logStep(dryRun ? 'Indexes (dry-run)' : 'Indexes')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const dir of dirs) {
|
|
90
|
+
const result = await regenOne(dir, { dryRun })
|
|
91
|
+
results.push(result)
|
|
92
|
+
|
|
93
|
+
for (const message of result.errors ?? []) {
|
|
94
|
+
process.stderr.write(`ERROR: ${message}\n`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!emitJson) await reportResult(result, root, shouldStage)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (emitJson) {
|
|
101
|
+
process.stdout.write(
|
|
102
|
+
`${JSON.stringify({
|
|
103
|
+
root,
|
|
104
|
+
dryRun,
|
|
105
|
+
results: results.map(({ path, action, reason }) =>
|
|
106
|
+
reason ? { path, action, reason } : { path, action },
|
|
107
|
+
),
|
|
108
|
+
})}\n`,
|
|
109
|
+
)
|
|
110
|
+
} else {
|
|
111
|
+
outro()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return exitCodeFor(results, { dryRun })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function collectFromPaths(paths: string[], root: string): string[] {
|
|
118
|
+
const dirs: string[] = []
|
|
119
|
+
|
|
120
|
+
for (const path of paths) {
|
|
121
|
+
const dir = findIndexedAncestor(resolve(root, path), root)
|
|
122
|
+
if (!dir) {
|
|
123
|
+
process.stderr.write(
|
|
124
|
+
`WARN: ${path} has no index.md ancestor under ${root}\n`,
|
|
125
|
+
)
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
if (!dirs.includes(dir)) dirs.push(dir)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return dirs
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function reportResult(
|
|
135
|
+
result: RegenResult,
|
|
136
|
+
root: string,
|
|
137
|
+
shouldStage: boolean,
|
|
138
|
+
): Promise<void> {
|
|
139
|
+
const rel = result.path.startsWith(`${root}/`)
|
|
140
|
+
? result.path.slice(root.length + 1)
|
|
141
|
+
: result.path
|
|
142
|
+
|
|
143
|
+
switch (result.action) {
|
|
144
|
+
case 'written':
|
|
145
|
+
logAdd(rel)
|
|
146
|
+
if (shouldStage) await stage(result.path, rel, root)
|
|
147
|
+
break
|
|
148
|
+
case 'would-write':
|
|
149
|
+
logWarn(`${rel} would change`)
|
|
150
|
+
break
|
|
151
|
+
case 'unchanged':
|
|
152
|
+
logInfo(`${rel} unchanged`)
|
|
153
|
+
break
|
|
154
|
+
case 'skipped':
|
|
155
|
+
logInfo(`${rel} skipped (auto:false)`)
|
|
156
|
+
break
|
|
157
|
+
case 'error':
|
|
158
|
+
logWarn(`${rel} error`)
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function stage(path: string, rel: string, root: string): Promise<void> {
|
|
164
|
+
if (await isIgnored(root, path)) return
|
|
165
|
+
|
|
166
|
+
const staged = await $`git -C ${root} add -- ${path}`
|
|
167
|
+
.quiet()
|
|
168
|
+
.nothrow()
|
|
169
|
+
.then((result) => result.exitCode === 0)
|
|
170
|
+
|
|
171
|
+
if (staged) {
|
|
172
|
+
logInfo(`staged ${rel}`)
|
|
173
|
+
} else {
|
|
174
|
+
logWarn(`failed to stage ${rel}`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function isDirectory(path: string): Promise<boolean> {
|
|
179
|
+
return stat(path)
|
|
180
|
+
.then((info) => info.isDirectory())
|
|
181
|
+
.catch(() => false)
|
|
182
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { cliPath, cliRun } from '@/cli-run'
|
|
3
|
+
import { PROJECT_ROOT } from '@/exec'
|
|
4
|
+
import { applyInitOptions, flagsProvided } from '@/init/flags'
|
|
5
|
+
import { type InitFlags, parseSkip, planInit } from '@/init/plan'
|
|
6
|
+
import { runDomains } from '@/init/run'
|
|
7
|
+
import { buildSteps } from '@/init/steps'
|
|
8
|
+
import { resolveTarget } from '@/target'
|
|
9
|
+
import { intro, logInfo, logStep, logWarn, outro, select } from '@/ui'
|
|
10
|
+
|
|
11
|
+
const GREEN = '\x1b[0;32m'
|
|
12
|
+
const YELLOW = '\x1b[0;33m'
|
|
13
|
+
const NC = '\x1b[0m'
|
|
14
|
+
|
|
15
|
+
interface InitOptions {
|
|
16
|
+
/** Always present: the option falls back to `DEFAULT_STACK`. */
|
|
17
|
+
readonly stack: string
|
|
18
|
+
readonly add?: string
|
|
19
|
+
readonly snippets: string
|
|
20
|
+
readonly skip?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function register(program: Command): void {
|
|
24
|
+
const command = program
|
|
25
|
+
.command('init')
|
|
26
|
+
.description('Bootstrap a project with base tooling and toolkit domains')
|
|
27
|
+
.argument('[target]', 'Target directory', '.')
|
|
28
|
+
|
|
29
|
+
applyInitOptions(command)
|
|
30
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
31
|
+
.addHelpText(
|
|
32
|
+
'after',
|
|
33
|
+
[
|
|
34
|
+
'',
|
|
35
|
+
'Passing any option skips the confirmation prompt.',
|
|
36
|
+
'',
|
|
37
|
+
'Examples:',
|
|
38
|
+
' aitk init',
|
|
39
|
+
' aitk init ../my-app',
|
|
40
|
+
' aitk init --stack astro --add 260-shadcn ../my-app',
|
|
41
|
+
' aitk init --skip governance ../my-app',
|
|
42
|
+
'',
|
|
43
|
+
].join('\n'),
|
|
44
|
+
)
|
|
45
|
+
.action(async (target: string, options: InitOptions, cmd: Command) => {
|
|
46
|
+
process.exitCode = await runInit(target, options, flagsProvided(cmd))
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runInit(
|
|
51
|
+
target: string,
|
|
52
|
+
options: InitOptions,
|
|
53
|
+
skipPrompt: boolean,
|
|
54
|
+
): Promise<number> {
|
|
55
|
+
intro('aitk init')
|
|
56
|
+
|
|
57
|
+
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
58
|
+
if (typeof resolved === 'number') return resolved
|
|
59
|
+
|
|
60
|
+
const skip = parseSkip(options.skip)
|
|
61
|
+
for (const value of skip.unknown) {
|
|
62
|
+
logWarn(`Unknown --skip value: ${value} (ignoring)`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const flags: InitFlags = {
|
|
66
|
+
stack: options.stack,
|
|
67
|
+
add: options.add,
|
|
68
|
+
snippets: options.snippets,
|
|
69
|
+
skip,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const plan = planInit(flags)
|
|
73
|
+
|
|
74
|
+
logStep('Core domains')
|
|
75
|
+
for (const line of plan.preview) {
|
|
76
|
+
if (line.level === 'info') logInfo(line.text)
|
|
77
|
+
else logWarn(line.text)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!skipPrompt) {
|
|
81
|
+
const shouldInstall = await select({
|
|
82
|
+
message: `Install ${plan.total} domains to ${target}?`,
|
|
83
|
+
options: [
|
|
84
|
+
{ value: true, label: 'Yes' },
|
|
85
|
+
{ value: false, label: 'Cancel' },
|
|
86
|
+
],
|
|
87
|
+
nonInteractiveDefault: true,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
if (!shouldInstall) {
|
|
91
|
+
logWarn('Cancelled')
|
|
92
|
+
outro()
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const path = cliPath(PROJECT_ROOT)
|
|
98
|
+
const child = (args: readonly string[]): (() => Promise<boolean>) =>
|
|
99
|
+
cliRun(path, args, { nonInteractive: true, stdin: 'ignore' })
|
|
100
|
+
|
|
101
|
+
const failed = await runDomains(buildSteps(target, resolved, flags, child))
|
|
102
|
+
|
|
103
|
+
outro()
|
|
104
|
+
process.stderr.write('\n')
|
|
105
|
+
|
|
106
|
+
if (failed.length === 0) {
|
|
107
|
+
process.stderr.write(
|
|
108
|
+
`${GREEN}✓ Project initialized (${plan.total} domains)${NC}\n`,
|
|
109
|
+
)
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
process.stderr.write(
|
|
114
|
+
`${YELLOW}! Project initialized with ${failed.length} failure(s): ${failed.join(', ')}${NC}\n`,
|
|
115
|
+
)
|
|
116
|
+
return 1
|
|
117
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { execScript } from '@/exec'
|
|
3
|
+
import { intro } from '@/ui'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Registers the verbs a domain has not migrated yet, each forwarding to
|
|
7
|
+
* `scripts/<domain>/<verb>.sh`. Naming them instead of catching everything on
|
|
8
|
+
* the parent gives each verb its own `--help` and lets a migrated sibling sit
|
|
9
|
+
* alongside them.
|
|
10
|
+
*/
|
|
11
|
+
export function registerPassThroughVerbs(
|
|
12
|
+
parent: Command,
|
|
13
|
+
domain: string,
|
|
14
|
+
verbs: readonly string[],
|
|
15
|
+
): void {
|
|
16
|
+
for (const verb of verbs) {
|
|
17
|
+
parent
|
|
18
|
+
.command(verb)
|
|
19
|
+
.description(`Run the ${domain} ${verb} command`)
|
|
20
|
+
.allowUnknownOption()
|
|
21
|
+
.allowExcessArguments(true)
|
|
22
|
+
.passThroughOptions()
|
|
23
|
+
.helpOption(false)
|
|
24
|
+
.action(async (_opts: unknown, cmd: Command) => {
|
|
25
|
+
if (!isHelpRequest(cmd.args)) intro(`aitk ${domain}`)
|
|
26
|
+
await execScript(`${domain}/${verb}.sh`, cmd.args)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Commander resolves `--help` before an action runs, so a pass-through verb
|
|
33
|
+
* that keeps the built-in help option prints a one-line stub instead of the
|
|
34
|
+
* script's usage. Disabling it lets the flag reach the script, which owns the
|
|
35
|
+
* real flag surface, and this guard keeps the frame from opening around it.
|
|
36
|
+
*/
|
|
37
|
+
function isHelpRequest(args: readonly string[]): boolean {
|
|
38
|
+
return args.includes('-h') || args.includes('--help')
|
|
39
|
+
}
|