@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,84 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readField, readFrontmatter } from '@/indexes/frontmatter'
|
|
3
|
+
import { computeIndex } from '@/indexes/render'
|
|
4
|
+
import { INDEX_FILE } from '@/indexes/walk'
|
|
5
|
+
|
|
6
|
+
export const REGEN_ACTIONS = [
|
|
7
|
+
'written',
|
|
8
|
+
'would-write',
|
|
9
|
+
'unchanged',
|
|
10
|
+
'skipped',
|
|
11
|
+
'error',
|
|
12
|
+
] as const
|
|
13
|
+
|
|
14
|
+
export type RegenAction = (typeof REGEN_ACTIONS)[number]
|
|
15
|
+
|
|
16
|
+
export interface RegenResult {
|
|
17
|
+
readonly path: string
|
|
18
|
+
readonly action: RegenAction
|
|
19
|
+
readonly reason?: string
|
|
20
|
+
readonly errors?: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RegenOptions {
|
|
24
|
+
readonly dryRun: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Regenerates one folder's index and reports what happened.
|
|
29
|
+
*
|
|
30
|
+
* A folder marked `auto: false` is skipped rather than errored, which is how
|
|
31
|
+
* hand-maintained catalogs opt out of the walker.
|
|
32
|
+
*/
|
|
33
|
+
export async function regenOne(
|
|
34
|
+
dir: string,
|
|
35
|
+
options: RegenOptions,
|
|
36
|
+
): Promise<RegenResult> {
|
|
37
|
+
const path = `${dir}/${INDEX_FILE}`
|
|
38
|
+
|
|
39
|
+
if (!existsSync(path)) {
|
|
40
|
+
return { path, action: 'error', reason: 'no index.md' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const auto = readField(await readFrontmatter(path), 'auto')
|
|
44
|
+
if (auto === 'false') {
|
|
45
|
+
return { path, action: 'skipped', reason: 'auto:false' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rendered = await computeIndex(dir)
|
|
49
|
+
if (!rendered.ok) {
|
|
50
|
+
return {
|
|
51
|
+
path,
|
|
52
|
+
action: 'error',
|
|
53
|
+
reason: 'frontmatter',
|
|
54
|
+
errors: rendered.errors,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (rendered.content === (await Bun.file(path).text())) {
|
|
59
|
+
return { path, action: 'unchanged' }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (options.dryRun) {
|
|
63
|
+
return { path, action: 'would-write' }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await Bun.write(path, rendered.content)
|
|
67
|
+
return { path, action: 'written' }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Maps a run's results onto the documented exit codes: 0 clean, 1 error,
|
|
72
|
+
* 2 drift found under `--dry-run`. Skills branch on the JSON instead, but the
|
|
73
|
+
* codes are load-bearing for `bun run check`.
|
|
74
|
+
*/
|
|
75
|
+
export function exitCodeFor(
|
|
76
|
+
results: readonly RegenResult[],
|
|
77
|
+
options: RegenOptions,
|
|
78
|
+
): 0 | 1 | 2 {
|
|
79
|
+
if (results.some((result) => result.action === 'error')) return 1
|
|
80
|
+
if (options.dryRun && results.some((r) => r.action === 'would-write')) {
|
|
81
|
+
return 2
|
|
82
|
+
}
|
|
83
|
+
return 0
|
|
84
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { basename, dirname, resolve } from 'node:path'
|
|
2
|
+
import { readField, readFrontmatter } from '@/indexes/frontmatter'
|
|
3
|
+
import { INDEX_FILE, listIndexes } from '@/indexes/walk'
|
|
4
|
+
|
|
5
|
+
export interface IndexEntry {
|
|
6
|
+
readonly name: string
|
|
7
|
+
readonly title: string
|
|
8
|
+
readonly description: string
|
|
9
|
+
readonly category?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SubCatalog {
|
|
13
|
+
readonly name: string
|
|
14
|
+
readonly title: string
|
|
15
|
+
readonly subtitle: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type RenderResult =
|
|
19
|
+
| { readonly ok: true; readonly content: string }
|
|
20
|
+
| { readonly ok: false; readonly errors: string[] }
|
|
21
|
+
|
|
22
|
+
interface IndexHead {
|
|
23
|
+
readonly raw: string
|
|
24
|
+
readonly title: string
|
|
25
|
+
readonly subtitle: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Builds a folder's `index.md` from its own frontmatter plus every sibling's.
|
|
30
|
+
*
|
|
31
|
+
* Frontmatter is re-emitted verbatim rather than re-serialized, so key order
|
|
32
|
+
* and any `auto: false` marker survive a regeneration untouched.
|
|
33
|
+
*/
|
|
34
|
+
export async function computeIndex(dir: string): Promise<RenderResult> {
|
|
35
|
+
const indexPath = `${dir}/${INDEX_FILE}`
|
|
36
|
+
const head = await readIndexHead(indexPath)
|
|
37
|
+
if (!head.ok) return head
|
|
38
|
+
|
|
39
|
+
const entries = await collectEntries(dir)
|
|
40
|
+
if (!entries.ok) return entries
|
|
41
|
+
|
|
42
|
+
const subCatalogs = await collectSubCatalogs(dir)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
content: renderIndex(head.head, entries.entries, subCatalogs),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function readIndexHead(
|
|
51
|
+
indexPath: string,
|
|
52
|
+
): Promise<{ ok: true; head: IndexHead } | { ok: false; errors: string[] }> {
|
|
53
|
+
const frontmatter = await readFrontmatter(indexPath)
|
|
54
|
+
const title = readField(frontmatter, 'title')
|
|
55
|
+
const subtitle = readField(frontmatter, 'subtitle')
|
|
56
|
+
|
|
57
|
+
const errors: string[] = []
|
|
58
|
+
if (!title) errors.push(`missing frontmatter field "title" in ${indexPath}`)
|
|
59
|
+
if (!subtitle) {
|
|
60
|
+
errors.push(`missing frontmatter field "subtitle" in ${indexPath}`)
|
|
61
|
+
}
|
|
62
|
+
if (!frontmatter || !title || !subtitle) return { ok: false, errors }
|
|
63
|
+
|
|
64
|
+
return { ok: true, head: { raw: frontmatter.raw, title, subtitle } }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function collectEntries(
|
|
68
|
+
dir: string,
|
|
69
|
+
): Promise<
|
|
70
|
+
{ ok: true; entries: IndexEntry[] } | { ok: false; errors: string[] }
|
|
71
|
+
> {
|
|
72
|
+
const glob = new Bun.Glob('*.md')
|
|
73
|
+
const paths: string[] = []
|
|
74
|
+
|
|
75
|
+
for await (const rel of glob.scan({ cwd: dir, onlyFiles: true, dot: true })) {
|
|
76
|
+
if (rel === INDEX_FILE) continue
|
|
77
|
+
paths.push(resolve(dir, rel))
|
|
78
|
+
}
|
|
79
|
+
paths.sort()
|
|
80
|
+
|
|
81
|
+
const entries: IndexEntry[] = []
|
|
82
|
+
const errors: string[] = []
|
|
83
|
+
|
|
84
|
+
for (const path of paths) {
|
|
85
|
+
const frontmatter = await readFrontmatter(path)
|
|
86
|
+
const title = readField(frontmatter, 'title')
|
|
87
|
+
const description = readField(frontmatter, 'description')
|
|
88
|
+
|
|
89
|
+
if (!title) errors.push(`missing frontmatter field "title" in ${path}`)
|
|
90
|
+
if (!description) {
|
|
91
|
+
errors.push(`missing frontmatter field "description" in ${path}`)
|
|
92
|
+
}
|
|
93
|
+
if (!title || !description) continue
|
|
94
|
+
|
|
95
|
+
entries.push({
|
|
96
|
+
name: basename(path),
|
|
97
|
+
title,
|
|
98
|
+
description,
|
|
99
|
+
category: readField(frontmatter, 'category'),
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (errors.length > 0) {
|
|
104
|
+
errors.push(`${errors.length} missing frontmatter field(s) in ${dir}`)
|
|
105
|
+
return { ok: false, errors }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { ok: true, entries }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reuses the shared walker so nested discovery inherits the same prune and
|
|
113
|
+
* gitignore rules, then keeps only direct children of this folder.
|
|
114
|
+
*/
|
|
115
|
+
async function collectSubCatalogs(dir: string): Promise<SubCatalog[]> {
|
|
116
|
+
const nested = await listIndexes(dir)
|
|
117
|
+
const catalogs: SubCatalog[] = []
|
|
118
|
+
|
|
119
|
+
for (const indexPath of nested.sort()) {
|
|
120
|
+
const childDir = dirname(indexPath)
|
|
121
|
+
if (dirname(childDir) !== resolve(dir)) continue
|
|
122
|
+
|
|
123
|
+
const frontmatter = await readFrontmatter(indexPath)
|
|
124
|
+
const title = readField(frontmatter, 'title')
|
|
125
|
+
const subtitle = readField(frontmatter, 'subtitle')
|
|
126
|
+
|
|
127
|
+
if (!title || !subtitle) {
|
|
128
|
+
process.stderr.write(
|
|
129
|
+
`WARNING: skipping nested index ${childDir}/${INDEX_FILE} (missing title or subtitle)\n`,
|
|
130
|
+
)
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
catalogs.push({ name: basename(childDir), title, subtitle })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return catalogs
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function renderIndex(
|
|
141
|
+
head: IndexHead,
|
|
142
|
+
entries: IndexEntry[],
|
|
143
|
+
subCatalogs: SubCatalog[],
|
|
144
|
+
): string {
|
|
145
|
+
const hasCategories = entries.some((entry) => Boolean(entry.category))
|
|
146
|
+
|
|
147
|
+
let out = `${head.raw}\n\n# ${head.title}\n\n${head.subtitle}\n`
|
|
148
|
+
if (entries.length > 0 || subCatalogs.length > 0) out += '\n'
|
|
149
|
+
|
|
150
|
+
if (hasCategories) {
|
|
151
|
+
const categories = [
|
|
152
|
+
...new Set(entries.map((entry) => entry.category).filter(Boolean)),
|
|
153
|
+
].sort() as string[]
|
|
154
|
+
|
|
155
|
+
categories.forEach((category, position) => {
|
|
156
|
+
if (position > 0) out += '\n'
|
|
157
|
+
out += `## ${category}\n\n`
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
if (entry.category !== category) continue
|
|
160
|
+
out += formatEntry(entry)
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
} else {
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
out += formatEntry(entry)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (subCatalogs.length > 0) {
|
|
170
|
+
if (hasCategories) out += '\n## Sub-catalogs\n\n'
|
|
171
|
+
for (const catalog of subCatalogs) {
|
|
172
|
+
out += `- [${catalog.title}](${catalog.name}/${INDEX_FILE}): ${catalog.subtitle}\n`
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return out
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function formatEntry(entry: IndexEntry): string {
|
|
180
|
+
return `- [${entry.title}](${entry.name}): ${entry.description}\n`
|
|
181
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs'
|
|
2
|
+
import { dirname, resolve } from 'node:path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
|
|
5
|
+
const INDEX_FILE = 'index.md'
|
|
6
|
+
|
|
7
|
+
const PRUNED_SEGMENTS = ['node_modules', '.git']
|
|
8
|
+
|
|
9
|
+
function isPruned(relativePath: string): boolean {
|
|
10
|
+
return relativePath
|
|
11
|
+
.split('/')
|
|
12
|
+
.some((segment) => PRUNED_SEGMENTS.includes(segment))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lists every folder index under `root`.
|
|
17
|
+
*
|
|
18
|
+
* Vendored and git trees are pruned by path segment, since `Bun.Glob` has no
|
|
19
|
+
* exclude. Everything else is dropped via `git check-ignore`, so a project's
|
|
20
|
+
* own `.gitignore` governs what the walker sees. Outside a git repo the
|
|
21
|
+
* segment prune is the only filter that applies.
|
|
22
|
+
*/
|
|
23
|
+
export async function listIndexes(root: string): Promise<string[]> {
|
|
24
|
+
const glob = new Bun.Glob(`**/${INDEX_FILE}`)
|
|
25
|
+
const candidates: string[] = []
|
|
26
|
+
|
|
27
|
+
for await (const rel of glob.scan({
|
|
28
|
+
cwd: root,
|
|
29
|
+
onlyFiles: true,
|
|
30
|
+
dot: true,
|
|
31
|
+
})) {
|
|
32
|
+
if (isPruned(rel)) continue
|
|
33
|
+
candidates.push(resolve(root, rel))
|
|
34
|
+
}
|
|
35
|
+
candidates.sort()
|
|
36
|
+
|
|
37
|
+
if (candidates.length === 0) return []
|
|
38
|
+
|
|
39
|
+
const ignored = await listIgnored(root, candidates)
|
|
40
|
+
if (ignored.size === 0) return candidates
|
|
41
|
+
|
|
42
|
+
return candidates.filter((path) => !ignored.has(path))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function listIgnored(
|
|
46
|
+
root: string,
|
|
47
|
+
candidates: string[],
|
|
48
|
+
): Promise<Set<string>> {
|
|
49
|
+
const isRepo = await $`git -C ${root} rev-parse --git-dir`
|
|
50
|
+
.quiet()
|
|
51
|
+
.nothrow()
|
|
52
|
+
.then((result) => result.exitCode === 0)
|
|
53
|
+
|
|
54
|
+
if (!isRepo) return new Set()
|
|
55
|
+
|
|
56
|
+
const stdin = Buffer.from(`${candidates.join('\n')}\n`)
|
|
57
|
+
|
|
58
|
+
const result = await $`git -C ${root} check-ignore --stdin < ${stdin}`
|
|
59
|
+
.quiet()
|
|
60
|
+
.nothrow()
|
|
61
|
+
|
|
62
|
+
if (result.exitCode > 1) return new Set()
|
|
63
|
+
|
|
64
|
+
return new Set(
|
|
65
|
+
result
|
|
66
|
+
.text()
|
|
67
|
+
.split('\n')
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.map((path) => resolve(root, path)),
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reports whether git ignores `path`.
|
|
75
|
+
*
|
|
76
|
+
* Positional regen reaches an ignored folder, because `findIndexedAncestor`
|
|
77
|
+
* walks the filesystem and never consults git. Staging what it writes there
|
|
78
|
+
* always fails, so the caller checks this first and skips the attempt rather
|
|
79
|
+
* than warning on a no-op.
|
|
80
|
+
*/
|
|
81
|
+
export async function isIgnored(root: string, path: string): Promise<boolean> {
|
|
82
|
+
const ignored = await listIgnored(root, [resolve(path)])
|
|
83
|
+
return ignored.has(resolve(path))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Walks up from `path` to the nearest folder holding an index, stopping at
|
|
88
|
+
* `root`. Returns undefined when the path escapes the boundary or nothing
|
|
89
|
+
* indexed is found.
|
|
90
|
+
*/
|
|
91
|
+
export function findIndexedAncestor(
|
|
92
|
+
path: string,
|
|
93
|
+
root: string,
|
|
94
|
+
): string | undefined {
|
|
95
|
+
if (!existsSync(path)) return undefined
|
|
96
|
+
|
|
97
|
+
const rootAbs = resolve(root)
|
|
98
|
+
let dir = statSync(path).isDirectory()
|
|
99
|
+
? resolve(path)
|
|
100
|
+
: dirname(resolve(path))
|
|
101
|
+
|
|
102
|
+
while (dir === rootAbs || dir.startsWith(`${rootAbs}/`)) {
|
|
103
|
+
if (existsSync(`${dir}/${INDEX_FILE}`)) return dir
|
|
104
|
+
if (dir === rootAbs) return undefined
|
|
105
|
+
dir = dirname(dir)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return undefined
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { INDEX_FILE, PRUNED_SEGMENTS }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { DEFAULT_STACK, SKIPPABLE_DOMAINS } from '@/init/plan'
|
|
3
|
+
|
|
4
|
+
interface InitOptionSpec {
|
|
5
|
+
/** The option value key commander stores the parsed value under. */
|
|
6
|
+
readonly key: string
|
|
7
|
+
readonly flags: string
|
|
8
|
+
readonly description: string
|
|
9
|
+
readonly defaultValue?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The option surface of `aitk init`, declared once so the flag list and the
|
|
14
|
+
* provenance check cannot drift apart. Reading the key from the spec is what
|
|
15
|
+
* keeps a renamed flag from silently dropping out of `flagsProvided`.
|
|
16
|
+
*/
|
|
17
|
+
export const INIT_OPTIONS: readonly InitOptionSpec[] = [
|
|
18
|
+
{
|
|
19
|
+
key: 'stack',
|
|
20
|
+
flags: '--stack <name>',
|
|
21
|
+
description: 'Governance stack (e.g., base, astro, react)',
|
|
22
|
+
defaultValue: DEFAULT_STACK,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'add',
|
|
26
|
+
flags: '--add <rules>',
|
|
27
|
+
description: 'Comma-separated governance rules to layer on',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'snippets',
|
|
31
|
+
flags: '--snippets <category>',
|
|
32
|
+
description: "Snippets preset, category, or 'all'",
|
|
33
|
+
defaultValue: 'essentials',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'skip',
|
|
37
|
+
flags: '--skip <list>',
|
|
38
|
+
description: `Skip core domains: ${SKIPPABLE_DOMAINS.join(', ')}`,
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
/** Declares every init option on the command, defaults included. */
|
|
43
|
+
export function applyInitOptions(command: Command): Command {
|
|
44
|
+
for (const option of INIT_OPTIONS) {
|
|
45
|
+
if (option.defaultValue === undefined) {
|
|
46
|
+
command.option(option.flags, option.description)
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
command.option(option.flags, option.description, option.defaultValue)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return command
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the operator passed any flag, which is what makes the command
|
|
58
|
+
* scriptable by suppressing the confirmation prompt. `--snippets` and `--stack`
|
|
59
|
+
* both carry defaults, so presence has to be read from where the value came
|
|
60
|
+
* from rather than from the value itself.
|
|
61
|
+
*/
|
|
62
|
+
export function flagsProvided(cmd: Command): boolean {
|
|
63
|
+
return INIT_OPTIONS.some(
|
|
64
|
+
(option) => cmd.getOptionValueSource(option.key) === 'cli',
|
|
65
|
+
)
|
|
66
|
+
}
|
package/src/init/plan.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export const SKIPPABLE_DOMAINS = ['wiki', 'standards', 'governance'] as const
|
|
2
|
+
|
|
3
|
+
export type SkippableDomain = (typeof SKIPPABLE_DOMAINS)[number]
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The stack a caller gets without asking. Governance installs on every init so
|
|
7
|
+
* the standards that install alongside it arrive with the rules that route to
|
|
8
|
+
* them, and `--skip governance` is the one spelling for declining.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_STACK = 'base'
|
|
11
|
+
|
|
12
|
+
export interface SkipPlan {
|
|
13
|
+
readonly skipped: ReadonlySet<SkippableDomain>
|
|
14
|
+
/** Values that named nothing skippable. Warned about, never fatal. */
|
|
15
|
+
readonly unknown: readonly string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type PreviewLevel = 'info' | 'warn'
|
|
19
|
+
|
|
20
|
+
export interface PreviewLine {
|
|
21
|
+
readonly level: PreviewLevel
|
|
22
|
+
readonly text: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface InitPlan {
|
|
26
|
+
readonly preview: readonly PreviewLine[]
|
|
27
|
+
readonly total: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface InitFlags {
|
|
31
|
+
readonly stack?: string
|
|
32
|
+
readonly add?: string
|
|
33
|
+
readonly snippets: string
|
|
34
|
+
readonly skip: SkipPlan
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the stack the run will install. An empty `--stack` reads as absent
|
|
39
|
+
* rather than as a way to decline, so the flag carries a real name or nothing
|
|
40
|
+
* and `--skip governance` stays the only spelling for opting out.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveStack(stack: string | undefined): string {
|
|
43
|
+
return stack === undefined || stack === '' ? DEFAULT_STACK : stack
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reads the `--skip` list. An unrecognized value is reported and dropped rather
|
|
48
|
+
* than aborting, because the flag names optional domains and a typo should not
|
|
49
|
+
* cost the operator the whole init.
|
|
50
|
+
*/
|
|
51
|
+
export function parseSkip(csv: string | undefined): SkipPlan {
|
|
52
|
+
const skipped = new Set<SkippableDomain>()
|
|
53
|
+
const unknown: string[] = []
|
|
54
|
+
|
|
55
|
+
for (const raw of (csv ?? '').split(',')) {
|
|
56
|
+
const item = raw.trim()
|
|
57
|
+
if (item === '') continue
|
|
58
|
+
|
|
59
|
+
if (isSkippable(item)) skipped.add(item)
|
|
60
|
+
else unknown.push(item)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { skipped, unknown }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Builds the preview and the count from one pass, so the two cannot disagree.
|
|
68
|
+
* Every `info` line is a domain that will run, which is what makes the count a
|
|
69
|
+
* filter rather than a second tally kept in step by hand. A skipped domain
|
|
70
|
+
* prints nothing at all and declined governance prints a warning, so neither
|
|
71
|
+
* reaches the total.
|
|
72
|
+
*
|
|
73
|
+
* Governance is the one skip that costs another domain something, so its
|
|
74
|
+
* warning names the consequence rather than only the action. Declining
|
|
75
|
+
* standards too removes that consequence, and the warning drops it.
|
|
76
|
+
*
|
|
77
|
+
* The skip also drops `--add`, which names rules the caller asked for. Input
|
|
78
|
+
* that goes nowhere is reported for the same reason `parseSkip` reports an
|
|
79
|
+
* unrecognized value, so the warning names the flag rather than dropping it
|
|
80
|
+
* without a word.
|
|
81
|
+
*/
|
|
82
|
+
export function planInit(flags: InitFlags): InitPlan {
|
|
83
|
+
const preview: PreviewLine[] = [
|
|
84
|
+
{ level: 'info', text: 'base tooling (configs, seeds, deps, scripts)' },
|
|
85
|
+
{ level: 'info', text: 'claude (workflow docs, settings)' },
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
const stack = resolveStack(flags.stack)
|
|
89
|
+
|
|
90
|
+
if (flags.skip.skipped.has('governance')) {
|
|
91
|
+
const notes: string[] = []
|
|
92
|
+
if (flags.add !== undefined && flags.add !== '')
|
|
93
|
+
notes.push(`--add ${flags.add} not installed`)
|
|
94
|
+
if (!flags.skip.skipped.has('standards'))
|
|
95
|
+
notes.push('standards land without the rules that route to them')
|
|
96
|
+
|
|
97
|
+
const detail = notes.length === 0 ? '' : `, ${notes.join(', ')}`
|
|
98
|
+
preview.push({ level: 'warn', text: `governance (skipped${detail})` })
|
|
99
|
+
} else if (flags.add === undefined || flags.add === '') {
|
|
100
|
+
preview.push({ level: 'info', text: `governance (stack: ${stack})` })
|
|
101
|
+
} else {
|
|
102
|
+
preview.push({
|
|
103
|
+
level: 'info',
|
|
104
|
+
text: `governance (stack: ${stack}, extras: ${flags.add})`,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!flags.skip.skipped.has('standards')) {
|
|
109
|
+
preview.push({ level: 'info', text: 'standards (authoring conventions)' })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
preview.push({ level: 'info', text: `snippets (${flags.snippets})` })
|
|
113
|
+
|
|
114
|
+
if (!flags.skip.skipped.has('wiki')) {
|
|
115
|
+
preview.push({
|
|
116
|
+
level: 'info',
|
|
117
|
+
text: 'wiki (.claude/wiki/ with a stub index)',
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const total = preview.filter((line) => line.level === 'info').length
|
|
122
|
+
|
|
123
|
+
return { preview, total }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isSkippable(value: string): value is SkippableDomain {
|
|
127
|
+
return (SKIPPABLE_DOMAINS as readonly string[]).includes(value)
|
|
128
|
+
}
|
package/src/init/run.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { logInfo, logStep, logWarn } from '@/ui'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A domain either runs or announces why it did not. Governance under
|
|
5
|
+
* `--skip governance` is the second kind: it reports in sequence so the
|
|
6
|
+
* timeline still accounts for every core domain, without counting as a failure.
|
|
7
|
+
*/
|
|
8
|
+
export type DomainStep =
|
|
9
|
+
| { readonly kind: 'run'; readonly label: string; run(): Promise<boolean> }
|
|
10
|
+
| { readonly kind: 'skip'; readonly label: string; readonly notice: string }
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Runs each step in order and returns the labels that failed.
|
|
14
|
+
*
|
|
15
|
+
* Partial failure is a first-class outcome rather than an error path. An init
|
|
16
|
+
* that aborts on the first failing domain would look tidier and leave the
|
|
17
|
+
* operator with no idea which of the remaining domains would have worked, so
|
|
18
|
+
* the run continues and the caller reports the failures at the end.
|
|
19
|
+
*
|
|
20
|
+
* The sequence is deliberate. Base tooling seeds the files the later domains
|
|
21
|
+
* install alongside, so these are not independent operations to batch.
|
|
22
|
+
*/
|
|
23
|
+
export async function runDomains(
|
|
24
|
+
steps: readonly DomainStep[],
|
|
25
|
+
): Promise<string[]> {
|
|
26
|
+
const failed: string[] = []
|
|
27
|
+
|
|
28
|
+
for (const step of steps) {
|
|
29
|
+
logStep(step.label)
|
|
30
|
+
|
|
31
|
+
if (step.kind === 'skip') {
|
|
32
|
+
logWarn(step.notice)
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (await step.run()) {
|
|
37
|
+
logInfo('Done')
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
logWarn('Failed, run manually')
|
|
42
|
+
failed.push(step.label)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return failed
|
|
46
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type InitFlags, resolveStack } from '@/init/plan'
|
|
2
|
+
import type { DomainStep } from '@/init/run'
|
|
3
|
+
|
|
4
|
+
/** Builds the child-process invocation for one domain. */
|
|
5
|
+
export type RunFactory = (args: readonly string[]) => () => Promise<boolean>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Orders the domains an init installs. Base tooling seeds the files the later
|
|
9
|
+
* domains install alongside, so the sequence is part of the contract rather
|
|
10
|
+
* than an arbitrary listing.
|
|
11
|
+
*
|
|
12
|
+
* The caller supplies the run factory so the list can be read for its labels
|
|
13
|
+
* and kinds without spawning anything.
|
|
14
|
+
*/
|
|
15
|
+
export function buildSteps(
|
|
16
|
+
target: string,
|
|
17
|
+
resolved: string,
|
|
18
|
+
flags: InitFlags,
|
|
19
|
+
child: RunFactory,
|
|
20
|
+
): DomainStep[] {
|
|
21
|
+
const steps: DomainStep[] = [
|
|
22
|
+
{
|
|
23
|
+
kind: 'run',
|
|
24
|
+
label: 'Base tooling',
|
|
25
|
+
run: child(['tooling', 'sync', 'base', resolved]),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
kind: 'run',
|
|
29
|
+
label: 'Claude workflow',
|
|
30
|
+
run: child(['claude', 'init', resolved]),
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const stack = resolveStack(flags.stack)
|
|
35
|
+
|
|
36
|
+
if (flags.skip.skipped.has('governance')) {
|
|
37
|
+
const recovery = govArgs(stack, flags.add, target).join(' ')
|
|
38
|
+
steps.push({
|
|
39
|
+
kind: 'skip',
|
|
40
|
+
label: 'Governance',
|
|
41
|
+
notice: `Skipped: --skip governance. Run 'aitk ${recovery}' to install rules.`,
|
|
42
|
+
})
|
|
43
|
+
} else {
|
|
44
|
+
steps.push({
|
|
45
|
+
kind: 'run',
|
|
46
|
+
label: 'Governance',
|
|
47
|
+
run: child(govArgs(stack, flags.add, resolved)),
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!flags.skip.skipped.has('standards')) {
|
|
52
|
+
steps.push({
|
|
53
|
+
kind: 'run',
|
|
54
|
+
label: 'Standards',
|
|
55
|
+
run: child(['standards', 'install', resolved]),
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
steps.push({
|
|
60
|
+
kind: 'run',
|
|
61
|
+
label: 'Snippets',
|
|
62
|
+
run: child(['snippets', 'install', flags.snippets, resolved]),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if (!flags.skip.skipped.has('wiki')) {
|
|
66
|
+
steps.push({
|
|
67
|
+
kind: 'run',
|
|
68
|
+
label: 'Wiki',
|
|
69
|
+
run: child(['wiki', 'init', resolved]),
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return steps
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Builds the `gov install` argv. The run and the recovery command a skip prints
|
|
78
|
+
* come from here both, so the command a caller is told to paste installs what
|
|
79
|
+
* the run would have.
|
|
80
|
+
*/
|
|
81
|
+
function govArgs(
|
|
82
|
+
stack: string,
|
|
83
|
+
add: string | undefined,
|
|
84
|
+
path: string,
|
|
85
|
+
): string[] {
|
|
86
|
+
const args = ['gov', 'install', stack]
|
|
87
|
+
if (add !== undefined && add !== '') args.push('--add', add)
|
|
88
|
+
args.push(path)
|
|
89
|
+
|
|
90
|
+
return args
|
|
91
|
+
}
|