Files
ai-coding-skills/docs/DEVELOPMENT.md
T
2026-05-03 21:09:22 -05:00

13 KiB

Development Guide — ai-coding-skills

This document covers prerequisites, how to run checks locally, the M1 quality tooling, the workspace policy, and the transitional pnpm run check contract.

Prerequisites

Tool Minimum version Install
Node.js 20 fnm install 22 or nvm install 22
pnpm 10 npm install -g pnpm
rg (ripgrep) any brew install ripgrep / apt-get install ripgrep
shellcheck any brew install shellcheck / apt-get install shellcheck
stat (BSD or GNU) any pre-installed on macOS; GNU variant on Linux
Python 3 3.8+ pre-installed on most systems

shellcheck is required. The pnpm run lint script will exit 2 with a clear error message if shellcheck is not on PATH. Every contributor must install it before running any quality checks.

Quick start

# Install dependencies (workspace-aware, no nested package.json modifications)
pnpm install

# Run the full quality suite
pnpm run check

Individual checks

Command What it does
pnpm run sync:pi Mirror pi skill variants into pi-package/skills/
pnpm run verify:pi Assert pi resource and workflow invariants
pnpm run verify:reviewers Assert reviewer-runtime skill invariants
pnpm run test:installer Run root-level Node.js unit tests (22 tests)
pnpm run lint ESLint on root scripts + shellcheck on all .sh files
pnpm run lint:fix Auto-fix ESLint + Prettier (do not run on pre-existing code until M2)
pnpm run typecheck TypeScript tsc --noEmit in workspace packages
pnpm run test Run all tests (root + workspace packages)
pnpm run verify:docs markdownlint + offline link-check + docs-flow verifier
pnpm run verify:docs:online Same as verify:docs but with full external link checking
pnpm run verify:generated Assert generated output freshness (stub; fleshed out in M3)
pnpm run check Aggregate: run every gate above and report a summary

Quality tooling (added in M1)

ESLint

Root-level flat config in eslint.config.mjs. Covers scripts/**/*.mjs and scripts/**/*.js. Uses @eslint/js recommended rules and Node.js globals. Nested workspace packages are responsible for their own ESLint configuration.

Prettier

Config in .prettierrc.json (print-width 100, LF line endings). Ignore file at .prettierignore excludes generated agent-variant directories and pnpm-lock.yaml.

markdownlint

Config in .markdownlint.jsonc (rules) and .markdownlint-cli2.jsonc (file globs and ignores). Key overrides vs defaults:

  • MD013 line-length relaxed to 120 chars (code blocks and tables excluded).
  • MD033 (inline HTML) disabled.
  • MD034 (bare URLs) disabled.
  • MD041 (first-line heading) disabled.
  • MD060 (table column style) disabled.

Run pnpm run verify:docs to lint all README.md, docs/*.md, and skills/**/SKILL.md files (node_modules excluded automatically).

Two configs:

  • markdown-link-check.jsonoffline mode (default): ignores all http:// and https:// links. Safe for local dev and CI.
  • markdown-link-check.online.jsononline mode: checks external links with a 10 s timeout, 2 retries, and retry-on-429. Use --online flag on scripts/lib/run-link-check.mjs.

pnpm run verify:docs uses the offline config by default.

shellcheck

Wrapper script at scripts/lib/run-shellcheck.mjs. Discovers every *.sh file under scripts/ and skills/ (excluding node_modules and generated agent-variant directories) and runs shellcheck on each.

Installation:

The wrapper exits with code 2 (not 1) when shellcheck is missing, so CI can distinguish "shellcheck absent" from "shellcheck found violations".

Cross-platform shell support (M2)

All shell scripts under scripts/ and skills/reviewer-runtime/ that are exercised by verify:pi, verify:reviewers, sync:pi, and test:installer must work on both macOS (BSD userland) and Ubuntu/Debian (GNU userland) without modification.

BSD vs GNU differences encountered

Feature macOS (BSD) Linux (GNU) Portable form
stat permissions stat -f '%Lp' <path> stat -c '%a' <path> portable_stat_perms helper
herestrings (<<<) supported (bash) supported (bash) OK (scripts use #!/usr/bin/env bash)
find -E (extended regex) macOS-only not available use grep or POSIX -name
sed -i '' macOS form use sed -i on Linux detect or avoid in-place sed
date -j (date arithmetic) macOS-only use date -d on Linux Node helper or date +%s
readlink -f not on macOS by default GNU standard realpath or cd && pwd

scripts/lib/portable.sh

A shared helper that abstracts the two known BSD/GNU divergences hit in this repo:

# Source from any script that needs portable stat
# shellcheck source=lib/portable.sh
source "$(dirname "${BASH_SOURCE[0]}")/lib/portable.sh"

# Returns octal permission string: e.g. "755"
portable_stat_perms "$path"

The shellcheck wrapper passes -x --source-path=SCRIPTDIR so source directives resolve relative to the script file, not the working directory.

How to run the Ubuntu smoke test locally

# Requires Docker
docker run --rm \
  -v "$PWD:/w" \
  -w /w \
  node:20-bookworm \
  bash -lc 'apt-get update -q && apt-get install -y -q shellcheck ripgrep python3 \
            && corepack enable \
            && pnpm install --frozen-lockfile \
            && pnpm run check'

This runs the full pnpm run check suite (lint, typecheck, test, verify:pi, verify:reviewers, verify:docs, verify:generated) inside a clean Debian Bookworm container with Node 20.

Transitional check contract (M1)

pnpm run check may exit non-zero in M1. This is expected. The contract is:

pnpm run check exits non-zero only on issues recorded in docs/CLEANUP-BASELINE.md. Previously-green checks remain green. No violations are introduced by M1's own changes.

M2 update: verify:docs is now green. lint still fails on the same 2 pre-existing ESLint violations and 7 pre-existing shellcheck findings documented in docs/CLEANUP-BASELINE.md. Those will be fixed in a dedicated cleanup pass. No violations were introduced by M2.

M3 update: pnpm run check is now fully green. All pre-existing lint violations have been fixed (2 ESLint errors, 7 shellcheck findings). The verify:generated check is now a real implementation (was a stub in M2).

pnpm workspace policy (updated in M3)

The pnpm-workspace.yaml at the repo root uses a positive-include policy introduced in M3. There are no negative-glob exclusions.

Canonical source packages (never generated):

  • skills/atlassian/shared/scripts — shared Atlassian TypeScript runtime
  • skills/web-automation/shared — shared web-automation runtime template

Generated agent-variant packages (uniquely named, positively included):

  • skills/atlassian/{claude-code,codex,cursor,opencode,pi}/scripts → names @ai-coding-skills/atlassian-{claude-code,codex,cursor,opencode,pi}
  • skills/web-automation/{claude-code,codex,cursor,opencode,pi}/scripts → names @ai-coding-skills/web-automation-{claude-code,codex,cursor,opencode,pi}
  • pi-package/skills/atlassian/scripts → name @ai-coding-skills/atlassian-pi-mirror
  • pi-package/skills/web-automation/scripts → name @ai-coding-skills/web-automation-pi-mirror

Why unique names matter:

Each package in a pnpm workspace must have a distinct name field. In M1 all generated agent-variant packages shared the same non-unique name as their canonical source package. In M3 every package receives a scoped unique name of the form @ai-coding-skills/<skill>-<agent>, enabling pnpm to include them alongside canonical source packages without conflicts. The pi-package mirrors use the -mirror suffix to distinguish them from the pi agent variants.

All generated packages are "private": true and are never published to any registry.

After pnpm install, git status should show zero modifications to any package.json file under any generated directory. If it does not, the workspace config or generator is broken.

How to interpret the baseline report

See docs/CLEANUP-BASELINE.md for the full as-is capture. When a CI run fails on a check, compare the output against the baseline:

  • If the failure matches a baseline entry → it is a known pre-existing issue.
  • If the failure does not appear in the baseline → it is a regression introduced by recent changes and must be fixed before merge.

How variants are generated (M3)

Every agent-variant directory under skills/<skill>/<agent>/ and every pi-package/skills/<skill>/ mirror is generator-owned. Do not edit files inside these directories directly — edit the canonical source instead and run pnpm run sync:pi to regenerate.

Canonical sources (non-generated)

These directories are never generated and are never inside a generated root. They are the single source of truth for all content.

Skill Canonical source
atlassian SKILL.md: skills/atlassian/_source/<agent>/SKILL.md; scripts: skills/atlassian/shared/scripts/
web-automation SKILL.md: skills/web-automation/_source/<agent>/SKILL.md; scripts: skills/web-automation/shared/
create-plan skills/create-plan/_source/<agent>/ (SKILL.md + templates)
do-task skills/do-task/_source/<agent>/ (SKILL.md + templates)
implement-plan skills/implement-plan/_source/<agent>/SKILL.md
reviewer-runtime (base) skills/reviewer-runtime/run-review.sh, notify-telegram.sh

The _source/ directories and shared/ directories sit under the skill root outside every generated root. Stale-file detection (verify:generated) never traverses them.

Generated roots (authoritative list)

All paths are relative to the repo root.

skills/atlassian/{claude-code,codex,cursor,opencode,pi}/
skills/web-automation/{claude-code,codex,cursor,opencode,pi}/
skills/create-plan/{claude-code,codex,cursor,opencode,pi}/
skills/do-task/{claude-code,codex,cursor,opencode,pi}/
skills/implement-plan/{claude-code,codex,cursor,opencode,pi}/
skills/reviewer-runtime/pi/
pi-package/skills/{atlassian,create-plan,do-task,implement-plan,web-automation}/

Contributor workflow

  1. Edit the canonical source in the appropriate _source/<agent>/, shared/, or base skills/reviewer-runtime/ directory.

  2. Run the generator:

    pnpm run sync:pi        # regenerates all 31 generated roots
    
  3. Verify no drift:

    pnpm run verify:generated   # must exit 0
    
  4. Stage both the canonical source AND the generated output in the same commit. Never commit a canonical change without regenerating.

File-type-aware header policy

Every generated file (except JSON and pnpm-lock.yaml) receives a header identifying it as generated and pointing to the canonical source. Headers are inserted so they do not break parsers or tools:

File type Header form Placement
Markdown (with YAML front matter) <!-- ⚠️ GENERATED FILE ... --> After closing ---
Markdown (no front matter) <!-- ⚠️ GENERATED FILE ... --> Top of file
Shell script # ⚠️ GENERATED FILE ... After #! shebang
TypeScript / JavaScript // ⚠️ GENERATED FILE ... After #! shebang if present, else top
YAML (non-lockfile) # ⚠️ GENERATED FILE ... Top of file
JSON (no header — recorded in .generated-manifest.json)
pnpm-lock.yaml (no header — managed by pnpm)
JSONC // ⚠️ GENERATED FILE ... Top of file

.generated-manifest.json contract

Each generated root contains a .generated-manifest.json that:

  • Has a $schema marker and a generator field for identification.
  • Lists every other generator-owned path in that root (relative path, file mode, SHA-256 hash, kind).
  • Does NOT list itself (non-self-referential).
  • Is scoped to exactly one generated root — there is no manifest at skills/<skill>/.
  • Is verified structurally (not byte-for-byte) by verify:generated.

Package metadata (M3 change)

Each generated agent-variant package.json carries:

  • A unique private name in the form @ai-coding-skills/<skill>-<agent> (e.g. @ai-coding-skills/atlassian-claude-code).
  • "private": true to prevent accidental npm publication.

The pi-package mirrors (pi-package/skills/atlassian/scripts and pi-package/skills/web-automation/scripts) use the -mirror agent suffix (@ai-coding-skills/atlassian-pi-mirror, @ai-coding-skills/web-automation-pi-mirror) to distinguish them from the pi agent variants in the workspace.

This replaces the previous non-unique name fields (atlassian-skill-scripts, web-automation-scripts). These packages are private and are never published. See CHANGELOG.md for the full rename list.