@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,315 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import type { Command } from 'commander'
|
|
4
|
+
import { PROJECT_ROOT, execScript } from '@/exec'
|
|
5
|
+
import {
|
|
6
|
+
collectCoverage,
|
|
7
|
+
coveragePercent,
|
|
8
|
+
type CoverageReport,
|
|
9
|
+
} from '@/sandbox/coverage'
|
|
10
|
+
import {
|
|
11
|
+
expectFilePath,
|
|
12
|
+
parseTarget,
|
|
13
|
+
resolveVerdict,
|
|
14
|
+
verdictExitCode,
|
|
15
|
+
type RunEnvelope,
|
|
16
|
+
type Verdict,
|
|
17
|
+
} from '@/sandbox/expect'
|
|
18
|
+
import {
|
|
19
|
+
intro,
|
|
20
|
+
logError,
|
|
21
|
+
logInfo,
|
|
22
|
+
logRemove,
|
|
23
|
+
logStep,
|
|
24
|
+
logWarn,
|
|
25
|
+
outro,
|
|
26
|
+
select,
|
|
27
|
+
} from '@/ui'
|
|
28
|
+
|
|
29
|
+
const SANDBOX_DIR = join(PROJECT_ROOT, 'scripts', 'sandbox')
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Holds fixture content for scenarios rather than scenarios of its own.
|
|
33
|
+
* Twin of the `-not -name fixtures` filter in `scripts/manage-sandbox.sh`.
|
|
34
|
+
*/
|
|
35
|
+
const FIXTURES_DIR = 'fixtures'
|
|
36
|
+
|
|
37
|
+
/** A run that reported nothing counts as clean, so only real signals fail. */
|
|
38
|
+
const CLEAN_ENVELOPE: RunEnvelope = { isError: false, turns: 0, denials: 0 }
|
|
39
|
+
|
|
40
|
+
interface CheckOptions {
|
|
41
|
+
readonly envelope?: string
|
|
42
|
+
readonly writes?: string
|
|
43
|
+
readonly json?: boolean
|
|
44
|
+
readonly strict?: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface CoverageOptions {
|
|
48
|
+
readonly json?: boolean
|
|
49
|
+
readonly strict?: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getCategories(): string[] {
|
|
53
|
+
return readdirSync(SANDBOX_DIR, { withFileTypes: true })
|
|
54
|
+
.filter((d) => d.isDirectory() && d.name !== FIXTURES_DIR)
|
|
55
|
+
.map((d) => d.name)
|
|
56
|
+
.sort()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getCommands(category: string): string[] {
|
|
60
|
+
return readdirSync(join(SANDBOX_DIR, category), { withFileTypes: true })
|
|
61
|
+
.filter((f) => f.isFile() && f.name.endsWith('.sh'))
|
|
62
|
+
.map((f) => f.name.replace(/\.sh$/, ''))
|
|
63
|
+
.sort()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function interactivePicker(): Promise<string> {
|
|
67
|
+
intro('aitk sandbox')
|
|
68
|
+
|
|
69
|
+
const categories = getCategories()
|
|
70
|
+
const category = await select({
|
|
71
|
+
message: 'Select category:',
|
|
72
|
+
options: categories.map((c) => ({ value: c, label: c })),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const commands = getCommands(category)
|
|
76
|
+
const command = await select({
|
|
77
|
+
message: 'Select command:',
|
|
78
|
+
options: commands.map((c) => ({ value: c, label: c })),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return `${category}:${command}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reads the `claude -p --output-format json` envelope. Returns undefined when no
|
|
86
|
+
* file was given, so the turn ceiling reports as skipped rather than passing on a
|
|
87
|
+
* fabricated zero. A file that exists but does not parse falls back to clean,
|
|
88
|
+
* since decision 8 lets the envelope fail a run but never pass one.
|
|
89
|
+
*/
|
|
90
|
+
function readEnvelope(path: string | undefined): RunEnvelope | undefined {
|
|
91
|
+
if (path === undefined) return undefined
|
|
92
|
+
if (!existsSync(path)) return CLEAN_ENVELOPE
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as Record<
|
|
96
|
+
string,
|
|
97
|
+
unknown
|
|
98
|
+
>
|
|
99
|
+
const denials = parsed.permission_denials
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
isError: parsed.is_error === true,
|
|
103
|
+
turns: typeof parsed.num_turns === 'number' ? parsed.num_turns : 0,
|
|
104
|
+
denials: Array.isArray(denials) ? denials.length : 0,
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
return CLEAN_ENVELOPE
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Undefined when no file was given, which is not the same as a run that wrote
|
|
113
|
+
* nothing. The write-scope assertion needs that distinction: an empty list is a
|
|
114
|
+
* finding, an absent list is a gap in what the caller supplied.
|
|
115
|
+
*/
|
|
116
|
+
function readWrites(path: string | undefined): string[] | undefined {
|
|
117
|
+
if (path === undefined) return undefined
|
|
118
|
+
if (!existsSync(path)) return []
|
|
119
|
+
|
|
120
|
+
return readFileSync(path, 'utf8')
|
|
121
|
+
.split('\n')
|
|
122
|
+
.map((line) => line.trim())
|
|
123
|
+
.filter((line) => line !== '')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The unchecked count rides the summary line in every verdict, including a pass.
|
|
128
|
+
* It is the number the rollout is measured by, and a count that surfaces only on
|
|
129
|
+
* arms that happen to carry prose is a count nobody drains.
|
|
130
|
+
*/
|
|
131
|
+
function reportVerdict(verdict: Verdict): void {
|
|
132
|
+
if (verdict.results.length > 0) {
|
|
133
|
+
logStep('Checking expectations')
|
|
134
|
+
for (const result of verdict.results) {
|
|
135
|
+
if (result.ok) logInfo(result.message)
|
|
136
|
+
else logRemove(result.message)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (verdict.manual.length > 0) {
|
|
141
|
+
logStep('Not checked (needs a reader)')
|
|
142
|
+
for (const line of verdict.manual) logWarn(line)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (verdict.skipped.length > 0) {
|
|
146
|
+
logStep('Not checked (no data supplied)')
|
|
147
|
+
for (const line of verdict.skipped) logWarn(line)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
logStep(`Verdict: ${verdict.state.toUpperCase()}`)
|
|
151
|
+
if (verdict.note !== undefined) logWarn(verdict.note)
|
|
152
|
+
|
|
153
|
+
const summary = `${verdict.asserted} asserted, ${verdict.failed} failed, ${verdict.unchecked} unchecked`
|
|
154
|
+
if (verdict.state === 'unchecked') logWarn(summary)
|
|
155
|
+
else logInfo(summary)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Lists the undeclared scenarios by name rather than counting them. A percentage
|
|
160
|
+
* alone is a number nobody acts on, while a name is the next thing to arm.
|
|
161
|
+
*/
|
|
162
|
+
function reportCoverage(report: CoverageReport): void {
|
|
163
|
+
const armed = report.scenarios.filter((s) => s.armed.length > 0)
|
|
164
|
+
const bare = report.scenarios.filter((s) => s.armed.length === 0)
|
|
165
|
+
|
|
166
|
+
if (armed.length > 0) {
|
|
167
|
+
logStep('Declares expectations')
|
|
168
|
+
for (const scenario of armed) {
|
|
169
|
+
logInfo(
|
|
170
|
+
`${scenario.category}:${scenario.command} (${scenario.armed.join(', ')})`,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (bare.length > 0) {
|
|
176
|
+
logStep('Provisions only, asserts nothing')
|
|
177
|
+
for (const scenario of bare)
|
|
178
|
+
logWarn(`${scenario.category}:${scenario.command}`)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
logStep('Coverage')
|
|
182
|
+
const summary =
|
|
183
|
+
`${report.armedScenarios}/${report.totalScenarios} scenarios declared ` +
|
|
184
|
+
`(${coveragePercent(report)}%), ${report.armedArms} armed arms`
|
|
185
|
+
if (bare.length > 0) logWarn(summary)
|
|
186
|
+
else logInfo(summary)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function runCoverage(options: CoverageOptions): void {
|
|
190
|
+
intro('aitk sandbox coverage')
|
|
191
|
+
|
|
192
|
+
const report = collectCoverage(PROJECT_ROOT)
|
|
193
|
+
reportCoverage(report)
|
|
194
|
+
if (options.json === true) process.stdout.write(`${JSON.stringify(report)}\n`)
|
|
195
|
+
|
|
196
|
+
outro()
|
|
197
|
+
process.exitCode =
|
|
198
|
+
options.strict === true && report.armedScenarios < report.totalScenarios
|
|
199
|
+
? 1
|
|
200
|
+
: 0
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function runCheck(
|
|
204
|
+
target: string,
|
|
205
|
+
arm: string | undefined,
|
|
206
|
+
options: CheckOptions,
|
|
207
|
+
): void {
|
|
208
|
+
intro('aitk sandbox check')
|
|
209
|
+
|
|
210
|
+
const parsed = parseTarget(target)
|
|
211
|
+
if (parsed === undefined) {
|
|
212
|
+
logError('Invalid target. Use <category>:<command>, e.g. claude:docs.')
|
|
213
|
+
outro()
|
|
214
|
+
process.exitCode = 1
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// A sandbox that was never provisioned fails every path assertion, reading as a
|
|
219
|
+
// skill that did nothing rather than a caller that ran check too early. The
|
|
220
|
+
// whole point of a verdict is that it means what it says.
|
|
221
|
+
const sandboxDir = join(PROJECT_ROOT, '.sandbox')
|
|
222
|
+
if (!existsSync(sandboxDir)) {
|
|
223
|
+
logError(`No sandbox at ${sandboxDir}. Provision one with aitk sandbox.`)
|
|
224
|
+
outro()
|
|
225
|
+
process.exitCode = 1
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const verdict = resolveVerdict(
|
|
230
|
+
expectFilePath(PROJECT_ROOT, parsed.category, parsed.command, arm ?? ''),
|
|
231
|
+
{
|
|
232
|
+
sandboxDir,
|
|
233
|
+
writes: readWrites(options.writes),
|
|
234
|
+
envelope: readEnvelope(options.envelope),
|
|
235
|
+
},
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
// The report always renders on stderr. `--json` adds the machine copy on
|
|
239
|
+
// stdout rather than replacing the frame, per the stream contract in
|
|
240
|
+
// `docs/agents.md`, so one invocation serves a human and a caller at once.
|
|
241
|
+
reportVerdict(verdict)
|
|
242
|
+
if (options.json === true)
|
|
243
|
+
process.stdout.write(`${JSON.stringify(verdict)}\n`)
|
|
244
|
+
|
|
245
|
+
outro()
|
|
246
|
+
process.exitCode = verdictExitCode(verdict.state, options.strict === true)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function register(program: Command): void {
|
|
250
|
+
const sandbox = program
|
|
251
|
+
.command('sandbox')
|
|
252
|
+
.description('Provision and run sandbox scenarios')
|
|
253
|
+
.allowUnknownOption()
|
|
254
|
+
.allowExcessArguments(true)
|
|
255
|
+
.passThroughOptions()
|
|
256
|
+
.action(async (_opts: unknown, cmd: Command) => {
|
|
257
|
+
const args = cmd.args
|
|
258
|
+
|
|
259
|
+
if (args.length === 0) {
|
|
260
|
+
const resolved = await interactivePicker()
|
|
261
|
+
await execScript('manage-sandbox.sh', ['--no-header', resolved])
|
|
262
|
+
} else {
|
|
263
|
+
await execScript('manage-sandbox.sh', args)
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
sandbox
|
|
268
|
+
.command('check')
|
|
269
|
+
.description('Check a provisioned sandbox against a scenario expectation')
|
|
270
|
+
.argument('<target>', 'Scenario as <category>:<command>, e.g. claude:docs')
|
|
271
|
+
.argument('[arm]', 'Named scenario arm, e.g. drift')
|
|
272
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
273
|
+
.option('--envelope <file>', 'Run envelope JSON from claude -p')
|
|
274
|
+
.option('--writes <file>', 'Newline-delimited paths the session wrote')
|
|
275
|
+
.option('--json', 'Emit the verdict as JSON on stdout')
|
|
276
|
+
.option('--strict', 'Exit non-zero when the arm declares no expectation')
|
|
277
|
+
.addHelpText(
|
|
278
|
+
'after',
|
|
279
|
+
[
|
|
280
|
+
'',
|
|
281
|
+
'Examples:',
|
|
282
|
+
' aitk sandbox check claude:docs drift',
|
|
283
|
+
' aitk sandbox check claude:docs drift --envelope run.json --json',
|
|
284
|
+
'',
|
|
285
|
+
'Exit codes: 0 on pass or unchecked, 1 on failure.',
|
|
286
|
+
'With --strict, unchecked exits 1 as well.',
|
|
287
|
+
].join('\n'),
|
|
288
|
+
)
|
|
289
|
+
.action(
|
|
290
|
+
(target: string, arm: string | undefined, options: CheckOptions) => {
|
|
291
|
+
runCheck(target, arm, options)
|
|
292
|
+
},
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
sandbox
|
|
296
|
+
.command('coverage')
|
|
297
|
+
.description('Report which scenarios declare expectations')
|
|
298
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
299
|
+
.option('--json', 'Emit the report as JSON on stdout')
|
|
300
|
+
.option('--strict', 'Exit non-zero while any scenario declares nothing')
|
|
301
|
+
.addHelpText(
|
|
302
|
+
'after',
|
|
303
|
+
[
|
|
304
|
+
'',
|
|
305
|
+
'Examples:',
|
|
306
|
+
' aitk sandbox coverage',
|
|
307
|
+
' aitk sandbox coverage --json',
|
|
308
|
+
'',
|
|
309
|
+
'Exit codes: 0 always, unless --strict and a scenario declares nothing.',
|
|
310
|
+
].join('\n'),
|
|
311
|
+
)
|
|
312
|
+
.action((options: CoverageOptions) => {
|
|
313
|
+
runCoverage(options)
|
|
314
|
+
})
|
|
315
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import type { Command } from 'commander'
|
|
4
|
+
import { LAYOUTS } from '@/slides/layouts'
|
|
5
|
+
import { openDeck } from '@/slides/open'
|
|
6
|
+
import { renderSlidesDoc } from '@/slides/render'
|
|
7
|
+
import type { Variant } from '@/slides/styles'
|
|
8
|
+
import { intro, outro } from '@/ui'
|
|
9
|
+
|
|
10
|
+
const GREY = '\x1b[0;90m'
|
|
11
|
+
const GREEN = '\x1b[0;32m'
|
|
12
|
+
const RED = '\x1b[0;31m'
|
|
13
|
+
const NC = '\x1b[0m'
|
|
14
|
+
|
|
15
|
+
export function register(program: Command): void {
|
|
16
|
+
const slides = program
|
|
17
|
+
.command('slides')
|
|
18
|
+
.description('Slide deck commands (render, list)')
|
|
19
|
+
|
|
20
|
+
slides
|
|
21
|
+
.command('render')
|
|
22
|
+
.description('Render a SLIDES.md source into a PowerPoint deck')
|
|
23
|
+
.option('-s, --source <path>', 'Source SLIDES.md path', '.claude/SLIDES.md')
|
|
24
|
+
.option('-o, --out <path>', 'Output directory', '.claude/review/slides')
|
|
25
|
+
.option('-v, --variant <variant>', 'Override variant (light or dark)')
|
|
26
|
+
.option(
|
|
27
|
+
'-m, --mirror <path>',
|
|
28
|
+
'Copy the deck to this directory after writing',
|
|
29
|
+
)
|
|
30
|
+
.option('--open', 'Open the deck after writing')
|
|
31
|
+
.action(
|
|
32
|
+
async (opts: {
|
|
33
|
+
source: string
|
|
34
|
+
out: string
|
|
35
|
+
variant?: string
|
|
36
|
+
mirror?: string
|
|
37
|
+
open?: boolean
|
|
38
|
+
}) => {
|
|
39
|
+
const sourcePath = resolve(process.cwd(), opts.source)
|
|
40
|
+
const outDir = resolve(process.cwd(), opts.out)
|
|
41
|
+
if (!existsSync(sourcePath)) {
|
|
42
|
+
fail(`${opts.source} not found`)
|
|
43
|
+
}
|
|
44
|
+
const variant = parseVariant(opts.variant)
|
|
45
|
+
const mirror = resolveMirror(opts.mirror)
|
|
46
|
+
intro('Render slides')
|
|
47
|
+
const result = await renderSlidesDoc(sourcePath, outDir, {
|
|
48
|
+
variant,
|
|
49
|
+
mirror,
|
|
50
|
+
})
|
|
51
|
+
process.stderr.write(
|
|
52
|
+
`${GREY}│${NC} ${GREEN}✓${NC} ${result.slideCount} slides\n${GREY}│${NC} ${GREEN}✓${NC} ${result.pptxPath}\n`,
|
|
53
|
+
)
|
|
54
|
+
if (result.mirrorPath) {
|
|
55
|
+
process.stderr.write(
|
|
56
|
+
`${GREY}│${NC} ${GREEN}✓${NC} mirrored to ${result.mirrorPath}\n`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
if (opts.open) {
|
|
60
|
+
const target = result.mirrorPath ?? result.pptxPath
|
|
61
|
+
const opened = await openDeck(target)
|
|
62
|
+
const mark = opened ? `${GREEN}✓${NC}` : `${RED}✗${NC}`
|
|
63
|
+
process.stderr.write(
|
|
64
|
+
`${GREY}│${NC} ${mark} ${opened ? 'opened' : 'could not open'} ${target}\n`,
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
outro()
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
slides
|
|
72
|
+
.command('list')
|
|
73
|
+
.description('List the available slide layouts')
|
|
74
|
+
.option('--json', 'Output the layout catalog as JSON')
|
|
75
|
+
.action((opts: { json?: boolean }) => {
|
|
76
|
+
if (opts.json) {
|
|
77
|
+
process.stdout.write(`${JSON.stringify(LAYOUTS)}\n`)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
intro('Slide layouts')
|
|
81
|
+
for (const layout of LAYOUTS) {
|
|
82
|
+
process.stderr.write(
|
|
83
|
+
`${GREY}│${NC} ${GREEN}✓${NC} ${layout.name} ${GREY}${layout.description}${NC}\n`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
outro()
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseVariant(value: string | undefined): Variant | undefined {
|
|
91
|
+
if (value === undefined) return undefined
|
|
92
|
+
if (value === 'light' || value === 'dark') return value
|
|
93
|
+
fail(`Invalid variant "${value}". Use light or dark.`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveMirror(value: string | undefined): string | undefined {
|
|
97
|
+
const mirror = value ?? process.env.AITK_SLIDES_MIRROR
|
|
98
|
+
return mirror ? resolve(process.cwd(), mirror) : undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function fail(message: string): never {
|
|
102
|
+
process.stderr.write(
|
|
103
|
+
`${GREY}┌${NC}\n${GREY}│${NC} ${RED}✗${NC} ${message}\n${GREY}└${NC}\n`,
|
|
104
|
+
)
|
|
105
|
+
process.exit(1)
|
|
106
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { registerPassThroughVerbs } from '@/commands/pass-through'
|
|
3
|
+
import { PROJECT_ROOT } from '@/exec'
|
|
4
|
+
import { createSnippetsAdapter } from '@/snippets/adapter'
|
|
5
|
+
import { BASE_CATEGORY } from '@/snippets/categories'
|
|
6
|
+
import {
|
|
7
|
+
ALL_CATEGORY,
|
|
8
|
+
installSnippets,
|
|
9
|
+
installableCategories,
|
|
10
|
+
resolveSnippets,
|
|
11
|
+
} from '@/snippets/install'
|
|
12
|
+
import { buildSnippetsCatalog } from '@/snippets/list'
|
|
13
|
+
import { recordStamp, runDomainSync } from '@/sync/engine'
|
|
14
|
+
import { resolveTarget } from '@/target'
|
|
15
|
+
import {
|
|
16
|
+
intro,
|
|
17
|
+
isNonInteractive,
|
|
18
|
+
logAdd,
|
|
19
|
+
logError,
|
|
20
|
+
logInfo,
|
|
21
|
+
logStep,
|
|
22
|
+
logWarn,
|
|
23
|
+
outro,
|
|
24
|
+
select,
|
|
25
|
+
} from '@/ui'
|
|
26
|
+
|
|
27
|
+
const GREEN = '\x1b[0;32m'
|
|
28
|
+
const NC = '\x1b[0m'
|
|
29
|
+
|
|
30
|
+
const SNIPPETS_REL = '.claude/snippets'
|
|
31
|
+
|
|
32
|
+
const PASS_THROUGH_VERBS = ['create'] as const
|
|
33
|
+
|
|
34
|
+
interface ListOptions {
|
|
35
|
+
readonly categories?: boolean
|
|
36
|
+
readonly entries?: boolean
|
|
37
|
+
readonly json?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function register(program: Command): void {
|
|
41
|
+
const snippets = program
|
|
42
|
+
.command('snippets')
|
|
43
|
+
.description('Snippets commands (install, sync, create, list)')
|
|
44
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
45
|
+
|
|
46
|
+
snippets
|
|
47
|
+
.command('install')
|
|
48
|
+
.description('Install snippets into .claude/snippets/')
|
|
49
|
+
.argument('[category]', "Preset, folder, or 'all'")
|
|
50
|
+
.argument('[target]', 'Target directory', '.')
|
|
51
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
52
|
+
.addHelpText(
|
|
53
|
+
'after',
|
|
54
|
+
[
|
|
55
|
+
'',
|
|
56
|
+
'Examples:',
|
|
57
|
+
' aitk snippets install essentials',
|
|
58
|
+
' aitk snippets install all',
|
|
59
|
+
' aitk snippets install claude ../my-app',
|
|
60
|
+
'',
|
|
61
|
+
].join('\n'),
|
|
62
|
+
)
|
|
63
|
+
.action(async (category: string | undefined, target: string) => {
|
|
64
|
+
process.exitCode = await runInstall(category, target)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
snippets
|
|
68
|
+
.command('sync')
|
|
69
|
+
.description('Update snippets already installed under .claude/snippets/')
|
|
70
|
+
.argument('[target]', 'Target directory', '.')
|
|
71
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
72
|
+
.action(async (target: string) => {
|
|
73
|
+
process.exitCode = await runDomainSync(
|
|
74
|
+
createSnippetsAdapter(PROJECT_ROOT),
|
|
75
|
+
target,
|
|
76
|
+
{ protectedRoot: PROJECT_ROOT },
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
snippets
|
|
81
|
+
.command('list')
|
|
82
|
+
.description('List snippet presets, categories, and entries')
|
|
83
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
84
|
+
.option('--categories', 'Only list presets and category names')
|
|
85
|
+
.option('--entries', 'Only list entry slugs grouped by category')
|
|
86
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
87
|
+
.action((opts: ListOptions) => {
|
|
88
|
+
process.exitCode = runList(opts)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
registerPassThroughVerbs(snippets, 'snippets', PASS_THROUGH_VERBS)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Refuses rather than picking, the same judgment `aitk gov install` applies to
|
|
96
|
+
* its stack picker. Headless, `select_option` returned the first option, which
|
|
97
|
+
* here was `all`, so an agent omitting the argument installed every category.
|
|
98
|
+
*/
|
|
99
|
+
async function chooseCategory(root: string): Promise<string | number> {
|
|
100
|
+
const categories = installableCategories(root)
|
|
101
|
+
|
|
102
|
+
if (categories.length === 0) {
|
|
103
|
+
logError('No categories found in snippets source.')
|
|
104
|
+
outro()
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isNonInteractive()) {
|
|
109
|
+
logError(
|
|
110
|
+
`Category argument is required in non-interactive mode. One of: ${[ALL_CATEGORY, ...categories].join(', ')}.`,
|
|
111
|
+
)
|
|
112
|
+
outro()
|
|
113
|
+
return 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return select({
|
|
117
|
+
message: 'Select category to install:',
|
|
118
|
+
options: [ALL_CATEGORY, ...categories].map((name) => ({
|
|
119
|
+
value: name,
|
|
120
|
+
label: name,
|
|
121
|
+
})),
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function runInstall(
|
|
126
|
+
category: string | undefined,
|
|
127
|
+
target: string,
|
|
128
|
+
): Promise<number> {
|
|
129
|
+
intro('aitk snippets install')
|
|
130
|
+
|
|
131
|
+
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
132
|
+
if (typeof resolved === 'number') return resolved
|
|
133
|
+
|
|
134
|
+
let selected = category
|
|
135
|
+
if (selected === undefined) {
|
|
136
|
+
const choice = await chooseCategory(PROJECT_ROOT)
|
|
137
|
+
if (typeof choice === 'number') return choice
|
|
138
|
+
selected = choice
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const resolution = resolveSnippets(PROJECT_ROOT, selected)
|
|
142
|
+
if (!resolution.ok) {
|
|
143
|
+
logError(`Category not found: ${resolution.unknownCategory}`)
|
|
144
|
+
outro()
|
|
145
|
+
return 1
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
logStep(resolution.step)
|
|
149
|
+
for (const slug of resolution.missing) {
|
|
150
|
+
logWarn(`${slug} (source not found, skipping)`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (resolution.files.length === 0) {
|
|
154
|
+
logWarn(`No snippets found for category: ${selected}`)
|
|
155
|
+
outro()
|
|
156
|
+
return 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const file of resolution.files) logInfo(file.relPath)
|
|
160
|
+
|
|
161
|
+
const display = target.replace(/\/$/, '')
|
|
162
|
+
const shouldInstall = await select({
|
|
163
|
+
message: `Install ${resolution.files.length} snippets to ${display}/${SNIPPETS_REL}?`,
|
|
164
|
+
options: [
|
|
165
|
+
{ value: true, label: 'Yes' },
|
|
166
|
+
{ value: false, label: 'No' },
|
|
167
|
+
],
|
|
168
|
+
nonInteractiveDefault: true,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (!shouldInstall) {
|
|
172
|
+
logWarn('Cancelled')
|
|
173
|
+
outro()
|
|
174
|
+
return 0
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
logStep('Installing snippets')
|
|
178
|
+
for (const rel of await installSnippets(resolution.files, resolved)) {
|
|
179
|
+
logAdd(rel)
|
|
180
|
+
}
|
|
181
|
+
await recordStamp(createSnippetsAdapter(PROJECT_ROOT), resolved, new Date())
|
|
182
|
+
|
|
183
|
+
outro()
|
|
184
|
+
process.stderr.write(`${GREEN}✓ Snippets installed${NC}\n`)
|
|
185
|
+
return 0
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* `JSON.stringify` replaces a `printf` template that interpolated names into a
|
|
190
|
+
* JSON string literal unescaped, so a slug or category carrying a quote emitted
|
|
191
|
+
* output no consuming skill could parse.
|
|
192
|
+
*
|
|
193
|
+
* The frame opens after the `--json` return, since the bash verb only ever
|
|
194
|
+
* emitted section headers and the pass-through above it owned the `┌`.
|
|
195
|
+
*/
|
|
196
|
+
function runList(opts: ListOptions): number {
|
|
197
|
+
const catalog = buildSnippetsCatalog(PROJECT_ROOT)
|
|
198
|
+
|
|
199
|
+
if (opts.json) {
|
|
200
|
+
process.stdout.write(`${JSON.stringify(catalog)}\n`)
|
|
201
|
+
return 0
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
intro('aitk snippets list')
|
|
205
|
+
|
|
206
|
+
const showCategories = opts.entries !== true
|
|
207
|
+
const showEntries = opts.categories !== true
|
|
208
|
+
|
|
209
|
+
if (showCategories) {
|
|
210
|
+
if (catalog.presets.length > 0) {
|
|
211
|
+
logStep('Presets')
|
|
212
|
+
for (const preset of catalog.presets) {
|
|
213
|
+
logInfo(`${preset.name} (${preset.slugs.length} entries)`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
logStep('Categories')
|
|
218
|
+
for (const category of catalog.categories) {
|
|
219
|
+
logInfo(`${category.name} (${category.entries.length} entries)`)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (showEntries) {
|
|
224
|
+
logStep('Entries')
|
|
225
|
+
for (const category of catalog.categories) {
|
|
226
|
+
for (const entry of category.entries) {
|
|
227
|
+
logInfo(
|
|
228
|
+
category.name === BASE_CATEGORY ? entry : `${category.name}/${entry}`,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
outro()
|
|
235
|
+
return 0
|
|
236
|
+
}
|