@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,432 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { Command } from 'commander'
|
|
3
|
+
import { execScript, PROJECT_ROOT } from '@/exec'
|
|
4
|
+
import {
|
|
5
|
+
applyReferences,
|
|
6
|
+
injectConfigs,
|
|
7
|
+
injectGitignore,
|
|
8
|
+
injectManifest,
|
|
9
|
+
injectSeeds,
|
|
10
|
+
pruneGitignore,
|
|
11
|
+
} from '@/tooling/inject'
|
|
12
|
+
import { buildStackSummaries, describeStack } from '@/tooling/list'
|
|
13
|
+
import {
|
|
14
|
+
isStackExcluded,
|
|
15
|
+
listStacks,
|
|
16
|
+
type Manifest,
|
|
17
|
+
resolveChain,
|
|
18
|
+
stackExists,
|
|
19
|
+
} from '@/tooling/manifest'
|
|
20
|
+
import { scan, type ScanResult } from '@/tooling/scan'
|
|
21
|
+
import { intro, logAdd, logInfo, logStep, logWarn, outro, select } from '@/ui'
|
|
22
|
+
|
|
23
|
+
const GREEN = '\x1b[0;32m'
|
|
24
|
+
const NC = '\x1b[0m'
|
|
25
|
+
|
|
26
|
+
const PASS_THROUGH_VERBS = ['ref', 'create', 'verify'] as const
|
|
27
|
+
|
|
28
|
+
interface SyncOptions {
|
|
29
|
+
readonly ref?: boolean
|
|
30
|
+
readonly skip?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface InjectOptions {
|
|
34
|
+
readonly configs?: boolean
|
|
35
|
+
readonly seeds?: boolean
|
|
36
|
+
readonly manifest?: boolean
|
|
37
|
+
readonly gitignore?: boolean
|
|
38
|
+
readonly nested?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface PruneOptions {
|
|
42
|
+
readonly nested?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ListOptions {
|
|
46
|
+
readonly json?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Prepared =
|
|
50
|
+
| { readonly ok: true; readonly chain: Manifest[]; readonly target: string }
|
|
51
|
+
| { readonly ok: false; readonly error: string }
|
|
52
|
+
|
|
53
|
+
export function register(program: Command): void {
|
|
54
|
+
const tooling = program
|
|
55
|
+
.command('tooling')
|
|
56
|
+
.description('Manage tooling stacks (sync, ref, create)')
|
|
57
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
58
|
+
|
|
59
|
+
tooling
|
|
60
|
+
.command('sync')
|
|
61
|
+
.description('Sync configs, seeds, deps, scripts, and gitignore entries')
|
|
62
|
+
.argument('[stack]', 'Tooling stack name (e.g. base, vite-react)')
|
|
63
|
+
.argument('[target]', 'Target directory', '.')
|
|
64
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
65
|
+
.option('--no-ref', 'Skip dropping reference docs')
|
|
66
|
+
.option('--skip <stack>', 'Drop a layer from the extends chain')
|
|
67
|
+
.action(
|
|
68
|
+
async (stack: string | undefined, target: string, opts: SyncOptions) => {
|
|
69
|
+
process.exitCode = await runSync(stack, target, opts)
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
tooling
|
|
74
|
+
.command('inject')
|
|
75
|
+
.description('Apply one stack to a target without scanning or prompting')
|
|
76
|
+
.argument('<stack>', 'Tooling stack name')
|
|
77
|
+
.argument('[target]', 'Target directory', '.')
|
|
78
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
79
|
+
.option('--configs', 'Copy stack configs')
|
|
80
|
+
.option('--seeds', 'Merge stack seeds')
|
|
81
|
+
.option('--manifest', 'Install deps, apply scripts, merge gitignore')
|
|
82
|
+
.option('--gitignore', 'Merge gitignore entries only')
|
|
83
|
+
.option('--nested', 'Suppress the frame when a caller already opened one')
|
|
84
|
+
.action(async (stack: string, target: string, opts: InjectOptions) => {
|
|
85
|
+
process.exitCode = await runInject(stack, target, opts)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
tooling
|
|
89
|
+
.command('prune-gitignore')
|
|
90
|
+
.description('Remove managed gitignore entries no longer in the manifest')
|
|
91
|
+
.argument('<stack>', 'Tooling stack name')
|
|
92
|
+
.argument('[target]', 'Target directory', '.')
|
|
93
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
94
|
+
.option('--nested', 'Suppress the frame when a caller already opened one')
|
|
95
|
+
.action(async (stack: string, target: string, opts: PruneOptions) => {
|
|
96
|
+
process.exitCode = await runPrune(stack, target, opts)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
tooling
|
|
100
|
+
.command('list')
|
|
101
|
+
.description('List installable tooling stacks')
|
|
102
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
103
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
104
|
+
.action((opts: ListOptions) => {
|
|
105
|
+
process.exitCode = runList(opts)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
for (const verb of PASS_THROUGH_VERBS) {
|
|
109
|
+
tooling
|
|
110
|
+
.command(verb)
|
|
111
|
+
.description(`Run the tooling ${verb} command`)
|
|
112
|
+
.allowUnknownOption()
|
|
113
|
+
.allowExcessArguments(true)
|
|
114
|
+
.passThroughOptions()
|
|
115
|
+
.helpOption(false)
|
|
116
|
+
.action(async (_opts: unknown, cmd: Command) => {
|
|
117
|
+
await execScript(`tooling/${verb}.sh`, cmd.args)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `JSON.stringify` replaces a `printf` that interpolated manifest fields into a
|
|
124
|
+
* JSON string literal unescaped, so a stack name or description carrying a
|
|
125
|
+
* quote emitted output a consuming skill could not parse.
|
|
126
|
+
*
|
|
127
|
+
* The frame opens after the `--json` return. The bash emitted a closing `└`
|
|
128
|
+
* from its EXIT trap with no `┌` above it, because the hand-rolled
|
|
129
|
+
* pass-through loop below skips the `intro` the shared helper carries.
|
|
130
|
+
*/
|
|
131
|
+
function runList(opts: ListOptions): number {
|
|
132
|
+
const stacks = buildStackSummaries(PROJECT_ROOT)
|
|
133
|
+
|
|
134
|
+
if (opts.json) {
|
|
135
|
+
process.stdout.write(`${JSON.stringify({ stacks })}\n`)
|
|
136
|
+
return 0
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
intro('aitk tooling list')
|
|
140
|
+
logStep('Stacks')
|
|
141
|
+
for (const summary of stacks) logInfo(describeStack(summary))
|
|
142
|
+
outro()
|
|
143
|
+
return 0
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolves the chain and rejects the same inputs the bash rejected, so a bad
|
|
148
|
+
* stack name fails before anything touches the target.
|
|
149
|
+
*
|
|
150
|
+
* The excluded-stack guard is deliberately not here. It lived in the sync
|
|
151
|
+
* entry point, so `aitk claude` can still drive injection for the `claude`
|
|
152
|
+
* stack the way it drove `merge_gitignore` before.
|
|
153
|
+
*/
|
|
154
|
+
function prepare(stack: string, target: string, skip?: string): Prepared {
|
|
155
|
+
if (!stackExists(PROJECT_ROOT, stack)) {
|
|
156
|
+
return { ok: false, error: `Stack not found: ${stack}` }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (skip !== undefined) {
|
|
160
|
+
if (skip === stack) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
error: `Cannot --skip the stack being synced: ${skip}`,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!stackExists(PROJECT_ROOT, skip)) {
|
|
167
|
+
return { ok: false, error: `Stack to skip not found: ${skip}` }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const resolved = resolve(target)
|
|
172
|
+
if (resolved === PROJECT_ROOT) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
error:
|
|
176
|
+
'Cannot run against toolkit root. Files here are the source of truth.',
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
chain: resolveChain(PROJECT_ROOT, stack, { skipStack: skip }),
|
|
183
|
+
target: resolved,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function runSync(
|
|
188
|
+
stack: string | undefined,
|
|
189
|
+
target: string,
|
|
190
|
+
opts: SyncOptions,
|
|
191
|
+
): Promise<number> {
|
|
192
|
+
intro('aitk tooling sync')
|
|
193
|
+
|
|
194
|
+
const selected = stack ?? (await promptForStack())
|
|
195
|
+
if (selected === undefined) {
|
|
196
|
+
logWarn('No tooling stacks found')
|
|
197
|
+
outro()
|
|
198
|
+
return 1
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (isStackExcluded(selected)) {
|
|
202
|
+
logWarn(
|
|
203
|
+
'Claude is managed by `aitk claude`, not `aitk tooling`. Run `aitk claude sync` instead.',
|
|
204
|
+
)
|
|
205
|
+
outro()
|
|
206
|
+
return 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const prepared = prepare(selected, target, opts.skip)
|
|
210
|
+
if (!prepared.ok) {
|
|
211
|
+
logWarn(prepared.error)
|
|
212
|
+
outro()
|
|
213
|
+
return 1
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const includeReferences = opts.ref !== false
|
|
217
|
+
const result = scan(prepared.chain, prepared.target, { includeReferences })
|
|
218
|
+
|
|
219
|
+
report(result, includeReferences)
|
|
220
|
+
|
|
221
|
+
if (result.totalChanges === 0) {
|
|
222
|
+
outro()
|
|
223
|
+
process.stderr.write(`${GREEN}✓ Everything up to date${NC}\n`)
|
|
224
|
+
return 0
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const shouldApply = await select({
|
|
228
|
+
message: `Apply ${result.totalChanges} changes (${summarize(result)})?`,
|
|
229
|
+
options: [
|
|
230
|
+
{ value: true, label: 'Apply all' },
|
|
231
|
+
{ value: false, label: 'Cancel' },
|
|
232
|
+
],
|
|
233
|
+
nonInteractiveDefault: true,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
if (!shouldApply) {
|
|
237
|
+
logWarn('Sync cancelled')
|
|
238
|
+
outro()
|
|
239
|
+
return 0
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (result.configs.some((entry) => entry.state !== 'matching')) {
|
|
243
|
+
await injectConfigs(prepared.chain, prepared.target)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await injectSeeds(prepared.chain, prepared.target)
|
|
247
|
+
await injectManifest(prepared.chain, prepared.target)
|
|
248
|
+
|
|
249
|
+
const pending = result.references
|
|
250
|
+
.filter((entry) => entry.state === 'pending')
|
|
251
|
+
.map((entry) => entry.stack)
|
|
252
|
+
|
|
253
|
+
if (pending.length > 0) {
|
|
254
|
+
logStep('Applying references')
|
|
255
|
+
await applyReferences(prepared.chain, prepared.target, pending)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
outro()
|
|
259
|
+
process.stderr.write(`${GREEN}✓ Tooling sync complete${NC}\n`)
|
|
260
|
+
return 0
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function runInject(
|
|
264
|
+
stack: string,
|
|
265
|
+
target: string,
|
|
266
|
+
opts: InjectOptions,
|
|
267
|
+
): Promise<number> {
|
|
268
|
+
const framed = opts.nested !== true
|
|
269
|
+
if (framed) intro('aitk tooling inject')
|
|
270
|
+
|
|
271
|
+
const prepared = prepare(stack, target)
|
|
272
|
+
if (!prepared.ok) {
|
|
273
|
+
logWarn(prepared.error)
|
|
274
|
+
if (framed) outro()
|
|
275
|
+
return 1
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const applyAll =
|
|
279
|
+
!opts.configs && !opts.seeds && !opts.manifest && !opts.gitignore
|
|
280
|
+
|
|
281
|
+
if (opts.configs || applyAll) {
|
|
282
|
+
await injectConfigs(prepared.chain, prepared.target)
|
|
283
|
+
}
|
|
284
|
+
if (opts.seeds || applyAll) {
|
|
285
|
+
await injectSeeds(prepared.chain, prepared.target)
|
|
286
|
+
}
|
|
287
|
+
if (opts.manifest || applyAll) {
|
|
288
|
+
await injectManifest(prepared.chain, prepared.target)
|
|
289
|
+
} else if (opts.gitignore) {
|
|
290
|
+
await injectGitignore(prepared.chain, prepared.target)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (framed) outro()
|
|
294
|
+
return 0
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Emits the number of pruned entries on stdout so a caller can branch on it
|
|
299
|
+
* without parsing the timeline, replacing the bash nameref return.
|
|
300
|
+
*/
|
|
301
|
+
async function runPrune(
|
|
302
|
+
stack: string,
|
|
303
|
+
target: string,
|
|
304
|
+
opts: PruneOptions,
|
|
305
|
+
): Promise<number> {
|
|
306
|
+
const framed = opts.nested !== true
|
|
307
|
+
if (framed) intro('aitk tooling prune-gitignore')
|
|
308
|
+
|
|
309
|
+
const prepared = prepare(stack, target)
|
|
310
|
+
if (!prepared.ok) {
|
|
311
|
+
logWarn(prepared.error)
|
|
312
|
+
if (framed) outro()
|
|
313
|
+
return 1
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const removed = await pruneGitignore(prepared.chain, prepared.target)
|
|
317
|
+
if (framed) outro()
|
|
318
|
+
|
|
319
|
+
process.stdout.write(`${removed.length}\n`)
|
|
320
|
+
return 0
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function promptForStack(): Promise<string | undefined> {
|
|
324
|
+
const stacks = listStacks(PROJECT_ROOT)
|
|
325
|
+
if (stacks.length === 0) return undefined
|
|
326
|
+
|
|
327
|
+
return select({
|
|
328
|
+
message: 'Select tooling stack:',
|
|
329
|
+
options: stacks.map((name) => ({ value: name, label: name })),
|
|
330
|
+
nonInteractiveDefault: true,
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function report(result: ScanResult, includeReferences: boolean): void {
|
|
335
|
+
logStep('Scanning configs')
|
|
336
|
+
for (const entry of result.configs) {
|
|
337
|
+
if (entry.state === 'matching') logInfo(entry.rel)
|
|
338
|
+
}
|
|
339
|
+
for (const entry of result.configs) {
|
|
340
|
+
if (entry.state === 'drifted') logWarn(entry.rel)
|
|
341
|
+
}
|
|
342
|
+
for (const entry of result.configs) {
|
|
343
|
+
if (entry.state === 'new') logAdd(entry.rel)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
logStep('Scanning seeds')
|
|
347
|
+
for (const entry of result.seeds) {
|
|
348
|
+
if (entry.state === 'present') logInfo(entry.rel)
|
|
349
|
+
}
|
|
350
|
+
for (const entry of result.seeds) {
|
|
351
|
+
if (entry.state === 'missing') logAdd(entry.rel)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (result.hasPackageJson) {
|
|
355
|
+
reportPackage(result)
|
|
356
|
+
} else {
|
|
357
|
+
logWarn(
|
|
358
|
+
"Skipped scripts and deps: no package.json found (run 'bun init' to enable)",
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
logStep('Scanning gitignore')
|
|
363
|
+
for (const entry of result.gitignore) {
|
|
364
|
+
if (entry.state === 'present') logInfo(entry.entry)
|
|
365
|
+
}
|
|
366
|
+
for (const entry of result.gitignore) {
|
|
367
|
+
if (entry.state === 'missing') logAdd(entry.entry)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (!includeReferences) return
|
|
371
|
+
|
|
372
|
+
logStep('Scanning references')
|
|
373
|
+
for (const entry of result.references) {
|
|
374
|
+
if (entry.state === 'matching') logInfo(`.claude/tooling/${entry.stack}.md`)
|
|
375
|
+
}
|
|
376
|
+
for (const entry of result.references) {
|
|
377
|
+
if (entry.state === 'pending') logAdd(`.claude/tooling/${entry.stack}.md`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function reportPackage(result: ScanResult): void {
|
|
382
|
+
if (result.scripts.length > 0) {
|
|
383
|
+
logStep('Scanning scripts')
|
|
384
|
+
for (const entry of result.scripts) {
|
|
385
|
+
if (entry.state === 'matching') logInfo(entry.key)
|
|
386
|
+
}
|
|
387
|
+
for (const entry of result.scripts) {
|
|
388
|
+
if (entry.state === 'drifted') logWarn(entry.key)
|
|
389
|
+
}
|
|
390
|
+
for (const entry of result.scripts) {
|
|
391
|
+
if (entry.state === 'missing') logAdd(entry.key)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (result.deps.length > 0) {
|
|
396
|
+
logStep('Scanning dependencies')
|
|
397
|
+
for (const entry of result.deps) {
|
|
398
|
+
if (entry.state === 'present') logInfo(entry.name)
|
|
399
|
+
}
|
|
400
|
+
for (const entry of result.deps) {
|
|
401
|
+
if (entry.state === 'missing') logAdd(entry.spec)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function summarize(result: ScanResult): string {
|
|
407
|
+
const parts: string[] = []
|
|
408
|
+
const add = (count: number, label: string): void => {
|
|
409
|
+
if (count > 0) parts.push(`${count} ${label}`)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
add(
|
|
413
|
+
result.configs.filter((entry) => entry.state !== 'matching').length,
|
|
414
|
+
'configs',
|
|
415
|
+
)
|
|
416
|
+
add(result.seeds.filter((entry) => entry.state === 'missing').length, 'seeds')
|
|
417
|
+
add(
|
|
418
|
+
result.scripts.filter((entry) => entry.state !== 'matching').length,
|
|
419
|
+
'scripts',
|
|
420
|
+
)
|
|
421
|
+
add(result.deps.filter((entry) => entry.state === 'missing').length, 'deps')
|
|
422
|
+
add(
|
|
423
|
+
result.gitignore.filter((entry) => entry.state === 'missing').length,
|
|
424
|
+
'gitignore',
|
|
425
|
+
)
|
|
426
|
+
add(
|
|
427
|
+
result.references.filter((entry) => entry.state === 'pending').length,
|
|
428
|
+
'refs',
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
return parts.join(', ')
|
|
432
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { Command } from 'commander'
|
|
3
|
+
import { ensureYtDlp, fetchOne } from '@/transcripts/fetch'
|
|
4
|
+
|
|
5
|
+
const GREY = '\x1b[0;90m'
|
|
6
|
+
const WHITE = '\x1b[1;37m'
|
|
7
|
+
const RED = '\x1b[0;31m'
|
|
8
|
+
const GREEN = '\x1b[0;32m'
|
|
9
|
+
const NC = '\x1b[0m'
|
|
10
|
+
|
|
11
|
+
interface TranscriptOptions {
|
|
12
|
+
out: string
|
|
13
|
+
keepTimestamps?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function register(program: Command): void {
|
|
17
|
+
program
|
|
18
|
+
.command('transcripts <url>')
|
|
19
|
+
.description('Fetch a YouTube transcript with metadata frontmatter')
|
|
20
|
+
.option('-o, --out <path>', 'Output directory', 'transcripts')
|
|
21
|
+
.option(
|
|
22
|
+
'--keep-timestamps',
|
|
23
|
+
'Prefix each line with [mm:ss] instead of prose',
|
|
24
|
+
)
|
|
25
|
+
.action(async (url: string, opts: TranscriptOptions) => {
|
|
26
|
+
const outDir = resolve(process.cwd(), opts.out)
|
|
27
|
+
process.stderr.write(
|
|
28
|
+
`${GREY}┌${NC}\n${GREY}│${NC} ${WHITE}aitk transcripts${NC}\n`,
|
|
29
|
+
)
|
|
30
|
+
try {
|
|
31
|
+
ensureYtDlp()
|
|
32
|
+
const target = await fetchOne(url, {
|
|
33
|
+
outDir,
|
|
34
|
+
keepTimestamps: opts.keepTimestamps ?? false,
|
|
35
|
+
})
|
|
36
|
+
process.stdout.write(`${target}\n`)
|
|
37
|
+
process.stderr.write(
|
|
38
|
+
`${GREY}│${NC} ${GREEN}✓${NC} ${target}\n${GREY}└${NC}\n`,
|
|
39
|
+
)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
`${GREY}│${NC} ${RED}✗${NC} ${message}\n${GREY}└${NC}\n`,
|
|
44
|
+
)
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { Command } from 'commander'
|
|
3
|
+
import { PROJECT_ROOT } from '@/exec'
|
|
4
|
+
import {
|
|
5
|
+
intro,
|
|
6
|
+
logAdd,
|
|
7
|
+
logError,
|
|
8
|
+
logInfo,
|
|
9
|
+
logStep,
|
|
10
|
+
logWarn,
|
|
11
|
+
outro,
|
|
12
|
+
select,
|
|
13
|
+
} from '@/ui'
|
|
14
|
+
import {
|
|
15
|
+
applyWikiInit,
|
|
16
|
+
isWikiTarget,
|
|
17
|
+
LEGACY_WIKI_DIR_REL,
|
|
18
|
+
planWikiInit,
|
|
19
|
+
WIKI_DIR_REL,
|
|
20
|
+
WIKI_INDEX_REL,
|
|
21
|
+
} from '@/wiki/init'
|
|
22
|
+
|
|
23
|
+
const GREEN = '\x1b[0;32m'
|
|
24
|
+
const NC = '\x1b[0m'
|
|
25
|
+
|
|
26
|
+
export function register(program: Command): void {
|
|
27
|
+
const wiki = program
|
|
28
|
+
.command('wiki')
|
|
29
|
+
.description('Wiki commands (init)')
|
|
30
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
31
|
+
|
|
32
|
+
wiki
|
|
33
|
+
.command('init')
|
|
34
|
+
.description('Scaffold .claude/wiki/ with a stub index.md')
|
|
35
|
+
.argument('[target]', 'Target directory', '.')
|
|
36
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
37
|
+
.action(async (target: string) => {
|
|
38
|
+
process.exitCode = await runInit(target)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function runInit(target: string): Promise<number> {
|
|
43
|
+
intro('aitk wiki')
|
|
44
|
+
|
|
45
|
+
const resolved = resolve(target)
|
|
46
|
+
|
|
47
|
+
if (!isWikiTarget(resolved)) {
|
|
48
|
+
logError(`Target directory not found: ${target}`)
|
|
49
|
+
outro()
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (resolved === PROJECT_ROOT) {
|
|
54
|
+
logError(
|
|
55
|
+
'Cannot run against toolkit root. Files here are the source of truth.',
|
|
56
|
+
)
|
|
57
|
+
outro()
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const plan = planWikiInit(resolved)
|
|
62
|
+
|
|
63
|
+
logStep(`Scanning ${WIKI_DIR_REL}`)
|
|
64
|
+
if (plan.changes.includes('dir')) logAdd(WIKI_DIR_REL)
|
|
65
|
+
if (plan.hasIndex) logInfo(WIKI_INDEX_REL)
|
|
66
|
+
else logAdd(WIKI_INDEX_REL)
|
|
67
|
+
|
|
68
|
+
if (plan.hasLegacyWiki) {
|
|
69
|
+
logWarn(`${LEGACY_WIKI_DIR_REL} found at the target root, not migrated`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const count = plan.changes.length
|
|
73
|
+
if (count === 0) {
|
|
74
|
+
outro()
|
|
75
|
+
process.stderr.write(`${GREEN}✓ Wiki already initialized${NC}\n`)
|
|
76
|
+
return 0
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const shouldApply = await select({
|
|
80
|
+
message: `Apply ${count} change(s)?`,
|
|
81
|
+
options: [
|
|
82
|
+
{ value: true, label: 'Apply all' },
|
|
83
|
+
{ value: false, label: 'Cancel' },
|
|
84
|
+
],
|
|
85
|
+
nonInteractiveDefault: true,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (!shouldApply) {
|
|
89
|
+
logWarn('Cancelled')
|
|
90
|
+
outro()
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
logStep('Applying changes')
|
|
95
|
+
await applyWikiInit(resolved, plan)
|
|
96
|
+
if (plan.changes.includes('index')) logAdd(WIKI_INDEX_REL)
|
|
97
|
+
|
|
98
|
+
outro()
|
|
99
|
+
process.stderr.write(`${GREEN}✓ Wiki ready${NC}\n`)
|
|
100
|
+
return 0
|
|
101
|
+
}
|
package/src/copy.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { chmod, copyFile, mkdir, stat } from 'node:fs/promises'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Copies with `cp` semantics. Two behaviors matter and neither is the default
|
|
6
|
+
* in the obvious alternatives. `Bun.write` drops the executable bit, which
|
|
7
|
+
* would land husky hooks and `scripts/verify.sh` non-executable. Plain
|
|
8
|
+
* `copyFile` imposes the source mode on an existing destination, so a file the
|
|
9
|
+
* target deliberately restricted to 600, or an earlier stack set executable,
|
|
10
|
+
* comes back at the source's mode. `cp` leaves an existing destination's mode
|
|
11
|
+
* alone.
|
|
12
|
+
*
|
|
13
|
+
* Every domain that writes into a target routes through here. The tooling
|
|
14
|
+
* inject path found the trap first, and the sync engine inherits it for every
|
|
15
|
+
* adapter.
|
|
16
|
+
*/
|
|
17
|
+
export async function copyPreservingMode(
|
|
18
|
+
src: string,
|
|
19
|
+
dest: string,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
await mkdir(dirname(dest), { recursive: true })
|
|
22
|
+
|
|
23
|
+
const existingMode = await stat(dest)
|
|
24
|
+
.then((info) => info.mode)
|
|
25
|
+
.catch(() => undefined)
|
|
26
|
+
|
|
27
|
+
await copyFile(src, dest)
|
|
28
|
+
|
|
29
|
+
if (existingMode !== undefined) await chmod(dest, existingMode)
|
|
30
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
export type Row = Record<string, string>
|
|
4
|
+
|
|
5
|
+
export interface DesignDoc {
|
|
6
|
+
personality: string
|
|
7
|
+
color: Row[]
|
|
8
|
+
typography: Row[]
|
|
9
|
+
spacing: Row[]
|
|
10
|
+
borders: Row[]
|
|
11
|
+
motion: string
|
|
12
|
+
iconography: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function parseDesignDoc(path: string): DesignDoc {
|
|
16
|
+
const raw = readFileSync(path, 'utf8')
|
|
17
|
+
const sections = splitSections(raw)
|
|
18
|
+
return {
|
|
19
|
+
personality: prose(sections['Personality']),
|
|
20
|
+
color: table(sections['Color']),
|
|
21
|
+
typography: table(sections['Typography']),
|
|
22
|
+
spacing: table(sections['Spacing']),
|
|
23
|
+
borders: table(sections['Borders']),
|
|
24
|
+
motion: prose(sections['Motion']),
|
|
25
|
+
iconography: prose(sections['Iconography']),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function splitSections(raw: string): Record<string, string> {
|
|
30
|
+
const out: Record<string, string> = {}
|
|
31
|
+
const lines = raw.split('\n')
|
|
32
|
+
let current: string | null = null
|
|
33
|
+
let buffer: string[] = []
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
const match = line.match(/^##\s+(.+?)\s*$/)
|
|
36
|
+
if (match) {
|
|
37
|
+
if (current) out[current] = buffer.join('\n')
|
|
38
|
+
current = match[1]
|
|
39
|
+
buffer = []
|
|
40
|
+
} else if (current) {
|
|
41
|
+
buffer.push(line)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (current) out[current] = buffer.join('\n')
|
|
45
|
+
return out
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function prose(body: string | undefined): string {
|
|
49
|
+
if (!body) return ''
|
|
50
|
+
return body
|
|
51
|
+
.split('\n')
|
|
52
|
+
.filter((l) => l.trim() && !l.startsWith('|') && !l.startsWith('<'))
|
|
53
|
+
.join('\n')
|
|
54
|
+
.trim()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function table(body: string | undefined): Row[] {
|
|
58
|
+
if (!body) return []
|
|
59
|
+
const rows = body.split('\n').filter((l) => l.trim().startsWith('|'))
|
|
60
|
+
if (rows.length < 2) return []
|
|
61
|
+
const headers = splitRow(rows[0])
|
|
62
|
+
const data = rows.slice(2)
|
|
63
|
+
return data.map((line) => {
|
|
64
|
+
const cells = splitRow(line)
|
|
65
|
+
const row: Row = {}
|
|
66
|
+
headers.forEach((h, i) => {
|
|
67
|
+
row[h] = (cells[i] ?? '').trim()
|
|
68
|
+
})
|
|
69
|
+
return row
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function splitRow(line: string): string[] {
|
|
74
|
+
return line
|
|
75
|
+
.replace(/^\s*\|/, '')
|
|
76
|
+
.replace(/\|\s*$/, '')
|
|
77
|
+
.split('|')
|
|
78
|
+
.map((c) => c.trim().replace(/\s*\?\s*verify\s*$/, ''))
|
|
79
|
+
}
|