diff --git a/.config/hk.pkl b/.config/hk.pkl index 67d80021..064581b1 100644 --- a/.config/hk.pkl +++ b/.config/hk.pkl @@ -1,5 +1,5 @@ -amends "package://github.com/jdx/hk/releases/download/v1.46.0/hk@1.46.0#/Config.pkl" -import "package://github.com/jdx/hk/releases/download/v1.46.0/hk@1.46.0#/Builtins.pkl" +amends "package://github.com/jdx/hk/releases/download/v2.4.0/hk@2.4.0#/Config.pkl" +import "package://github.com/jdx/hk/releases/download/v2.4.0/hk@2.4.0#/Builtins.pkl" local format = new Step { check = "mise run format:check" @@ -34,9 +34,15 @@ local fastSteps = new Mapping { ["typecheck"] = typecheck } +local skillsLock = new Step { + check = "scripts/sync-skills.sh --check" + fix = "scripts/sync-skills.sh" +} + local checkSteps = new Mapping { ...fastSteps ["fallow"] = fallow + ["skills-lock"] = skillsLock } hooks { diff --git a/.config/mise/conf.d/_config.toml b/.config/mise/conf.d/_config.toml index 43174020..b6634f4a 100644 --- a/.config/mise/conf.d/_config.toml +++ b/.config/mise/conf.d/_config.toml @@ -6,6 +6,7 @@ node = "lts" jq = "latest" vp = "latest" hk = "latest" +apm = "0.33.0" [task_config] dir = "{{ config_root }}" @@ -24,3 +25,10 @@ run = "nub install" [shell_alias] mr = "mise run" + +# Agent skills materialized by APM (see apm.yml); output is gitignored. +[deps.skills] +auto = true +sources = ["apm.yml", "apm.lock.yaml", "pnpm-lock.yaml", "apps/*/package.json", "packages/*/package.json", "site/package.json"] +outputs = [".agents/skills"] +run = "scripts/sync-skills.sh" diff --git a/.config/mise/conf.d/tasks-quality.toml b/.config/mise/conf.d/tasks-quality.toml index ff33fff7..088cb2f4 100644 --- a/.config/mise/conf.d/tasks-quality.toml +++ b/.config/mise/conf.d/tasks-quality.toml @@ -47,6 +47,10 @@ description = "Type check all TypeScript projects" alias = "tc" run = ["mise run //apps/demo:typecheck", "mise run //packages/create-karkas:typecheck"] +[tasks."lint:skills-lock"] +description = "Check APM skill refs and lockfile are in sync" +run = "scripts/sync-skills.sh --check" + [tasks."lint:steiger"] description = "Check demo FSD architecture with steiger" run = "mise run //apps/demo:lint:steiger" @@ -100,6 +104,7 @@ env = { VITE_CONNECT_LOGGER = "false" } run = [ { task = "prepare" }, { task = "format:check" }, + { task = "lint:skills-lock" }, { task = "lint" }, { task = "test:ci" }, { tasks = ["typecheck", "lint:steiger", "lint:fallow"] }, diff --git a/.config/mise/conf.d/tasks-skills.toml b/.config/mise/conf.d/tasks-skills.toml new file mode 100644 index 00000000..f683c5b0 --- /dev/null +++ b/.config/mise/conf.d/tasks-skills.toml @@ -0,0 +1,3 @@ +[tasks."skills:update"] +description = "Bump APM skill dependencies to latest refs and re-materialize" +run = "apm install --update --only apm" diff --git a/.config/skill-deps.txt b/.config/skill-deps.txt new file mode 100644 index 00000000..2167b5d7 --- /dev/null +++ b/.config/skill-deps.txt @@ -0,0 +1 @@ +@reatom/core=1001.3.0 diff --git a/.gitignore b/.gitignore index 7914e40f..0bcd4ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,9 @@ progress*.txt # vitest browser-mode failure screenshots __screenshots__/ .vitest-attachments/ + +# APM dependencies +apm_modules/ + +# APM-materialized agent skills (generated from apm.yml, do not edit) +.agents/skills/ diff --git a/apm.lock.yaml b/apm.lock.yaml new file mode 100644 index 00000000..87c91bb4 --- /dev/null +++ b/apm.lock.yaml @@ -0,0 +1,239 @@ +lockfile_version: '1' +apm_version: 0.33.0 +dependencies: +- repo_url: apphane-dev/kahraman + name: kahraman + host: github.com + resolved_commit: fb984ed4c2f3b7bf77414d03526f6353a8f894f5 + resolved_ref: v0.3.1 + version: unknown + package_type: skill_bundle + deployed_files: + - .agents/skills/kahraman-storybook-testing + - .agents/skills/kahraman-storybook-testing/SKILL.md + deployed_file_hashes: + .agents/skills/kahraman-storybook-testing/SKILL.md: sha256:92883384858f07732f62e04b679f7c0d3750beb2c5eac26a4b92da0c7d87c3eb + content_hash: sha256:e271e09b53f348ac1e86e3a4bd9906433a1295ad5d2f043470940f101c6a7220 +- repo_url: cursor/plugins + name: typescript-best-practices + host: github.com + resolved_commit: 23e4138daa01c42d4969f7a5465f82704e64f798 + resolved_ref: main + version: unknown + virtual_path: pstack/skills/typescript-best-practices + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/typescript-best-practices + - .agents/skills/typescript-best-practices/SKILL.md + - .agents/skills/typescript-best-practices/references/patterns.md + deployed_file_hashes: + .agents/skills/typescript-best-practices/SKILL.md: sha256:28f9e61710e205f6f3c5476f333483cff0e988f6a5b639e2faad536344b40c5e + .agents/skills/typescript-best-practices/references/patterns.md: sha256:faa1362055374452efccac1b629677c61acfb0bcc6c4e3311836b5715638ee18 + content_hash: sha256:59b3d20cf0939379ac9120ab9fb94076ad3aec99177308c699c720f475bcbd2c +- repo_url: cursor/plugins + name: unslop + host: github.com + resolved_commit: 23e4138daa01c42d4969f7a5465f82704e64f798 + resolved_ref: main + version: unknown + virtual_path: pstack/skills/unslop + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/unslop + - .agents/skills/unslop/SKILL.md + deployed_file_hashes: + .agents/skills/unslop/SKILL.md: sha256:195411d320b5b328f9f642baf59757ed19aaf0931c0838740e0aca273d538dc1 + content_hash: sha256:6018ca8c329ba87c6b4298207f58d6b773fe23e44ff52a00a9a4d160c19bedc0 +- repo_url: reatom/reatom + name: reatom + host: github.com + resolved_commit: b48778e3bf3a80d85b176157b094c2470d0ab86e + resolved_ref: v1001 + version: unknown + package_type: skill_bundle + deployed_files: + - .agents/skills/reatom + - .agents/skills/reatom-async + - .agents/skills/reatom-async/REFERENCE.md + - .agents/skills/reatom-async/SKILL.md + - .agents/skills/reatom-jsx + - .agents/skills/reatom-jsx/REFERENCE.md + - .agents/skills/reatom-jsx/SKILL.md + - .agents/skills/reatom-review + - .agents/skills/reatom-review/SKILL.md + - .agents/skills/reatom/REFERENCE.md + - .agents/skills/reatom/SKILL.md + deployed_file_hashes: + .agents/skills/reatom-async/REFERENCE.md: sha256:8cdbdd44a17ec47d4ab4ef36186d502c838517df020c6c14044733d188476951 + .agents/skills/reatom-async/SKILL.md: sha256:a6acfcc8886475923b93e280564193fbfb97be2385df65c758b1d1eec2c7505d + .agents/skills/reatom-jsx/REFERENCE.md: sha256:55f87fcefed94fd6b63e5ebfd11dd4c6aac1f7fd61b06d73cca25f212306401c + .agents/skills/reatom-jsx/SKILL.md: sha256:e26740b0cc7a3f1ddd0556e0bde67c27381bd0ae50a25619d603fe2a2873a6c2 + .agents/skills/reatom-review/SKILL.md: sha256:219e3e15521b23d5e88629461c6891fdf95b0b01fbe94620939a6040fa5c6520 + .agents/skills/reatom/REFERENCE.md: sha256:8dd96cc5bfb697a1848377f98a8a227901cc4144b767293c609822e8191b1b69 + .agents/skills/reatom/SKILL.md: sha256:2954dab61ae3495ade98ea222164af4e20ae8973c1f0fdcf2875708e78c2da27 + content_hash: sha256:ac7b61894281e986ec77f58dfe6bbeef9bb12602a3c4e461769303c42d336b29 +deployments: +- kind: project-relative + target: agent-skills + value: .agents/skills/kahraman-storybook-testing + runtime: null + scope: project + owners: + - apphane-dev/kahraman + active_owner: apphane-dev/kahraman + content_hash: null +- kind: project-relative + target: agent-skills + value: .agents/skills/kahraman-storybook-testing/SKILL.md + runtime: null + scope: project + owners: + - apphane-dev/kahraman + active_owner: apphane-dev/kahraman + content_hash: sha256:92883384858f07732f62e04b679f7c0d3750beb2c5eac26a4b92da0c7d87c3eb +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: null +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-async + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: null +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-async/REFERENCE.md + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: sha256:8cdbdd44a17ec47d4ab4ef36186d502c838517df020c6c14044733d188476951 +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-async/SKILL.md + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: sha256:a6acfcc8886475923b93e280564193fbfb97be2385df65c758b1d1eec2c7505d +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-jsx + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: null +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-jsx/REFERENCE.md + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: sha256:55f87fcefed94fd6b63e5ebfd11dd4c6aac1f7fd61b06d73cca25f212306401c +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-jsx/SKILL.md + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: sha256:e26740b0cc7a3f1ddd0556e0bde67c27381bd0ae50a25619d603fe2a2873a6c2 +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-review + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: null +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom-review/SKILL.md + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: sha256:219e3e15521b23d5e88629461c6891fdf95b0b01fbe94620939a6040fa5c6520 +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom/REFERENCE.md + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: sha256:8dd96cc5bfb697a1848377f98a8a227901cc4144b767293c609822e8191b1b69 +- kind: project-relative + target: agent-skills + value: .agents/skills/reatom/SKILL.md + runtime: null + scope: project + owners: + - reatom/reatom + active_owner: reatom/reatom + content_hash: sha256:2954dab61ae3495ade98ea222164af4e20ae8973c1f0fdcf2875708e78c2da27 +- kind: project-relative + target: agent-skills + value: .agents/skills/typescript-best-practices + runtime: null + scope: project + owners: + - cursor/plugins/pstack/skills/typescript-best-practices + active_owner: cursor/plugins/pstack/skills/typescript-best-practices + content_hash: null +- kind: project-relative + target: agent-skills + value: .agents/skills/typescript-best-practices/SKILL.md + runtime: null + scope: project + owners: + - cursor/plugins/pstack/skills/typescript-best-practices + active_owner: cursor/plugins/pstack/skills/typescript-best-practices + content_hash: sha256:28f9e61710e205f6f3c5476f333483cff0e988f6a5b639e2faad536344b40c5e +- kind: project-relative + target: agent-skills + value: .agents/skills/typescript-best-practices/references/patterns.md + runtime: null + scope: project + owners: + - cursor/plugins/pstack/skills/typescript-best-practices + active_owner: cursor/plugins/pstack/skills/typescript-best-practices + content_hash: sha256:faa1362055374452efccac1b629677c61acfb0bcc6c4e3311836b5715638ee18 +- kind: project-relative + target: agent-skills + value: .agents/skills/unslop + runtime: null + scope: project + owners: + - cursor/plugins/pstack/skills/unslop + active_owner: cursor/plugins/pstack/skills/unslop + content_hash: null +- kind: project-relative + target: agent-skills + value: .agents/skills/unslop/SKILL.md + runtime: null + scope: project + owners: + - cursor/plugins/pstack/skills/unslop + active_owner: cursor/plugins/pstack/skills/unslop + content_hash: sha256:195411d320b5b328f9f642baf59757ed19aaf0931c0838740e0aca273d538dc1 diff --git a/apm.yml b/apm.yml new file mode 100644 index 00000000..416a9bab --- /dev/null +++ b/apm.yml @@ -0,0 +1,16 @@ +name: karkas +version: 1.0.0 +dependencies: + apm: + - reatom/reatom#v1001 + - apphane-dev/kahraman#v0.3.1 + - cursor/plugins/pstack/skills/unslop#main + - cursor/plugins/pstack/skills/typescript-best-practices#main +targets: + - agent-skills +lifecycle: + post-install: + - type: command + description: Allow agents to auto-invoke unslop and TypeScript guide + bash: ./scripts/patch-skills.sh + timeoutSec: 10 diff --git a/docs/tooling.md b/docs/tooling.md index 94aecf3d..3ad405ef 100644 --- a/docs/tooling.md +++ b/docs/tooling.md @@ -86,3 +86,33 @@ decision. - `sharp`: denied; no current workflow requires its native install path. Review newly requested scripts with `nub ignored-builds` before changing these decisions. + +## Agent skills + +Agent skills are declared in `apm.yml` (Microsoft APM) and materialized into +`.agents/skills/`, which is gitignored. mise runs the install automatically as +the `skills` dependency provider whenever `apm.yml` changes: + +```sh +mise deps install skills # run manually +mise deps --explain skills # inspect the freshness decision +``` + +`apm.lock.yaml` pins resolved git commits and content hashes and is committed. +The `apm` CLI is pinned in `_config.toml` (0.33.0+ is required to install +skill-bundle packages such as `reatom/reatom`). + +Skill deps are coupled to the npm packages that ship them: +`scripts/sync-skills.sh` syncs `apm.yml` refs from the installed +`node_modules` versions (kahraman tracks its exact release tag; reatom +re-resolves the v1001 branch head when `@reatom/core` moves, recorded in +`.config/skill-deps.txt`). It runs automatically from the mise `skills` +dependency provider whenever package manifests or the lockfile change, and the +hk `skills-lock` check (in `hk check` / pre-push and CI) fails on drift. +`mise run skills:update` still bumps refs to the latest upstream tags manually. + +Two fetched skills (unslop, typescript-best-practices) have +`disable-model-invocation: true` upstream; `scripts/patch-skills.sh` strips it +via an `apm.yml` post-install lifecycle hook so agents can auto-apply them. +Fresh clones must run `apm lifecycle trust` once to enable the hook (apm will +warn and skip it until then). diff --git a/scripts/patch-skills.sh b/scripts/patch-skills.sh new file mode 100755 index 00000000..9e8f7b3f --- /dev/null +++ b/scripts/patch-skills.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Re-applied after every `apm install` (see lifecycle in apm.yml). +# Strips `disable-model-invocation` so agents can apply these skills +# automatically, matching the machine-wide copies. +set -euo pipefail +for skill in unslop typescript-best-practices; do + f=".agents/skills/$skill/SKILL.md" + [ -f "$f" ] && sed -i '/^disable-model-invocation:/d' "$f" +done diff --git a/scripts/sync-skills.sh b/scripts/sync-skills.sh new file mode 100755 index 00000000..c245419a --- /dev/null +++ b/scripts/sync-skills.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Couple agent-skill deps in apm.yml to the npm packages that ship them. +# +# kahraman -> apphane-dev/kahraman#v (release tags match npm) +# @reatom/core -> reatom/reatom#v1001 (no per-release tags; the +# lockfile pins the branch-head commit and the recorded npm +# version in .config/skill-deps.txt forces a re-resolve +# whenever the installed npm version moves) +# +# fix mode (default): rewrite refs from node_modules, then `apm install`. +# check mode (--check): fail if refs would change or the lockfile is stale. +set -euo pipefail +cd "$(dirname "$0")/.." +STATE=".config/skill-deps.txt" + +MODE="${1:-fix}" +changed="" + +require_version() { # + node -p "require('./node_modules/$1/package.json').version" 2>/dev/null || + { echo "✗ $1 not installed — run 'nub install' first" >&2; exit 1; } +} + +fail_check() { + echo "✗ $* — run 'scripts/sync-skills.sh' (or 'mise deps install skills') and commit" >&2 + exit 1 +} + +sync_tag() { # + local pkg="$1" prefix="$2" ver ref + ver="$(require_version "$pkg")" + ref="${prefix}#v${ver}" + if ! grep -qF "$ref" apm.yml; then + [[ "$MODE" == "--check" ]] && + fail_check "$pkg@$ver installed but apm.yml pins $(grep -oF "${prefix}#v[0-9A-Za-z._-]*" apm.yml || echo 'nothing')" + sed -i "s|${prefix}#v[0-9A-Za-z._-]*|${ref}|" apm.yml + changed="$changed $pkg" + fi +} + +sync_branch() { # + local pkg="$1" prefix="$2" branch="$3" ver recorded + ver="$(require_version "$pkg")" + recorded="" + if [ -f "$STATE" ]; then + recorded="$(sed -n "s|^${pkg}=||p" "$STATE" | tail -1)" + fi + if [[ "$recorded" != "$ver" ]]; then + [[ "$MODE" == "--check" ]] && + fail_check "$pkg@$ver installed but apm.yml was resolved for ${recorded:-}" + sed -i "s|${prefix}#[0-9A-Za-z._-]*|${prefix}#${branch}|" apm.yml + if grep -q "^${pkg}=" "$STATE" 2>/dev/null; then + sed -i "s|^${pkg}=.*|${pkg}=${ver}|" "$STATE" + else + echo "${pkg}=${ver}" >> "$STATE" + fi + changed="$changed $pkg" + fi +} + +sync_tag kahraman apphane-dev/kahraman +sync_branch @reatom/core reatom/reatom v1001 + +if [[ "$MODE" == "--check" ]]; then + if ! git diff --exit-code --quiet -- apm.yml apm.lock.yaml; then + fail_check "uncommitted changes in apm.yml/apm.lock.yaml" + fi + hold="$(mktemp)" + cp apm.lock.yaml "$hold" + apm install --only apm >/dev/null + if ! diff -q "$hold" apm.lock.yaml >/dev/null; then + echo "✗ lockfile out of sync with apm.yml — re-materialized in the working tree; commit the changes" >&2 + git diff --stat -- apm.lock.yaml >&2 + exit 1 + fi + rm -f "$hold" + exit 0 +fi + +[[ -n "$changed" ]] && echo "skill deps synced from npm:$changed" +apm install --only apm