Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.13
bun-version: 1.4.2
Comment thread
amondnet marked this conversation as resolved.

- name: Install dependencies
run: bun install --frozen-lockfile
Expand All @@ -40,6 +40,9 @@ jobs:
- name: Lint
run: bun run lint

- name: Skill bundles are up to date
run: bun run build:skill:check

- name: Test (with coverage)
run: bun run test:coverage

Expand Down
6 changes: 5 additions & 1 deletion .please/docs/knowledge/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

- **`bun run lint` requires Node 22+ in CI** — the eslint binary uses `#!/usr/bin/env node`. The Bun setup action does not install Node. Add `actions/setup-node@v4` before `oven-sh/setup-bun@v2`. Older Node (≤20) lacks `Object.groupBy` used by `eslint-flat-config-utils`. Local dev is fine because Node 22+ is usually already installed.

- **Bun 1.3+ writes text-format `bun.lock`** (not legacy `bun.lockb`). `package.json` must pin `engines.bun: ">=1.3.0"`; lower Bun versions cannot read the new lockfile and will fail `bun install --frozen-lockfile`. Documented in `tech-stack.md` § Runtime.
- **Bun 1.3+ writes text-format `bun.lock`** (not legacy `bun.lockb`); lower Bun versions cannot read the new lockfile and will fail `bun install --frozen-lockfile`. That is the floor the lockfile needs — `engines.bun` sits higher (`>=1.4.2`), because the committed skill bundles are byte-compared against CI's Bun (see below). Documented in `tech-stack.md` § Runtime.

- **Committed skill bundles are Bun-version-coupled** — `bun run build:skill:check` byte-compares a fresh bundle against the committed one, so a Bun whose codegen differs from CI's pin fails the gate on an untouched source tree. Keep the local Bun and `ci.yml`'s `bun-version` on the same version; after bumping either, run `bun run build:skill` and commit the result, and raise `engines.bun` to match so a contributor on an older Bun is told before the gate tells them.

- **`import.meta.main` is not portable through a bundle** — Bun lowers it to a `__require` comparison that resolves only when some dependency happens to pull in the CJS interop helper. `detect.js` worked by accident (fast-xml-parser supplied it) while `docs.js` threw `ReferenceError: __require is not defined` under plain `node`. `scripts/build-skill.ts` pins it with `define: { 'import.meta.main': 'true' }`; a bundle is always the entrypoint.

- **`@pleaseai/eslint-config` includes formatting** — designed standalone, no Prettier. Auto-format applies: no semicolons, single quotes, sorted JSON keys. Adopt early or expect a cascade reformat across every committed file. Do not also install Prettier.

Expand Down
34 changes: 20 additions & 14 deletions .please/docs/knowledge/tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- **What**: JavaScript runtime for executing plugin scripts.
- **Why**: Fast startup (~10ms vs ~80ms for Node), built-in TypeScript without a bundler, native fetch/glob APIs.
- **Where**: `scripts/*.ts` files invoked by skills and slash commands.
- **Version target**: latest stable (≥1.3). The committed `bun.lock` uses Bun's text-format lockfile (default since 1.3); Bun 1.1.x cannot read it. `package.json` `engines.bun` enforces this floor.
- **Version target**: latest stable. The committed `bun.lock` uses Bun's text-format lockfile (default since 1.3); Bun 1.1.x cannot read it. `engines.bun` sits above that at `>=1.4.2`, matching `ci.yml`'s pin, because `build:skill:check` byte-compares the committed bundles against CI's codegen — an older Bun regenerates them differently and fails the gate on an untouched tree.

### Node fallback
- **Why**: Some users may not have Bun. Scripts should not rely on Bun-only globals (`Bun.file`, `Bun.serve`) unless the README documents a Bun requirement.
Expand All @@ -23,13 +23,21 @@

## Plugin Architecture

### Claude Code plugin conventions
- Manifest at `.claude-plugin/plugin.json` (only file in that dir).
- Components (`skills/`, `commands/`, `scripts/`) at plugin root.
- Path references use `${CLAUDE_PLUGIN_ROOT}` inside skills and scripts.
### Two install channels
The skill ships both as a Claude Code plugin and as a standalone skill installed
with `npx skills` ([vercel-labs/skills](https://github.com/vercel-labs/skills)).
The second channel copies **only** the `skills/spring-docs/` directory, so it has
no plugin root, no `node_modules`, and no dependency install. Everything the
skill executes at runtime therefore lives inside its own directory.

### Slash commands
Each slash command is a thin Markdown file in `commands/` that references a skill or invokes a script. Commands are entry points only — logic lives in skills/scripts.
- Manifest at `.claude-plugin/plugin.json` (only file in that dir).
- TypeScript sources stay at the plugin root (`scripts/`) where tests, typecheck
and lint reach them.
- `bun run build:skill` bundles them into `skills/spring-docs/scripts/*.mjs` —
dependency-free, committed, and run with `node`.
- Skill content references scripts through `${CLAUDE_SKILL_DIR}`, which resolves
at the personal, project **and** plugin level. `${CLAUDE_PLUGIN_ROOT}` is
substituted only in plugin skills, so it cannot be used here.

### Skills
Skills (`skills/spring-installer/SKILL.md`) describe behavior and reference scripts. Auto-invoked by Claude Code when the conversation matches the skill description.
Expand Down Expand Up @@ -71,12 +79,10 @@ Skills (`skills/spring-installer/SKILL.md`) describe behavior and reference scri

## Logging

### `consola`
- **What**: structured CLI logger (https://github.com/unjs/consola).
- **Why**: project-wide consistent log levels, `--verbose` / `--silent` toggling, prompt helpers, child loggers per module — all out of the box. Replaces the `log()` helper originally proposed in `ARCHITECTURE.md`. Tiny footprint, no native deps, ESM-first.
- **Where**: orchestration scripts (`scripts/*.ts`); the I/O-free library layer (`scripts/lib/*`) must remain logger-free.
- **Usage**: import the project-level instance from a single helper (e.g. `scripts/logger.ts` once introduced — outside the I/O-free `scripts/lib/` boundary), so log level / format are applied consistently. Tests should not import consola directly.
- **Note**: this entry supersedes the "no logger library" line in `ARCHITECTURE.md`; that file will be revised in the `arch-md-v2` track.
Scripts print JSON on stdout and nothing else — there is no logger library.
`consola` was adopted during the scaffold track and removed once the skill
bundles had to stay dependency-free (TD-001); a logger would be inlined into
every bundle for output the skill's callers parse as JSON anyway.

## Distribution

Expand Down Expand Up @@ -125,7 +131,7 @@ bun run scripts/fetch.ts framework 6.2.1 --output /tmp/spring-framework-6.2.1

## Out of Stack

- **No bundler** — `bun` runs `.ts` directly.
- **No bundler for the plugin channel** — `bun` runs `.ts` directly. `Bun.build` is used for one thing only: the committed skill bundles the standalone channel needs (see § Two install channels).
- **No frontend framework** — there is no UI; all output is terminal/files.
- **No database** — caches use the filesystem under `~/.cache/pleaseai-spring/`.
- **No long-running server** — every command is a one-shot invocation.
2 changes: 1 addition & 1 deletion .please/docs/tracks/tech-debt-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

| ID | Source Track | Description | Priority | Created |
|----|------------|-------------|----------|---------|
| TD-001 | plugin-scaffold-20260428 | `consola@3.4.2` adopted as runtime dep but no call site yet. First feature track must consume it or remove it. | low | 2026-04-29 |
| TD-002 | plugin-scaffold-20260428 | `README.md` "## Development" still references `bun run scripts/fetch.ts` and prebuilt-pipeline artifacts that don't exist (spec forbade rewriting). Reconcile after `arch-md-v2-20260428` lands. | medium | 2026-04-29 |
| TD-003 | plugin-scaffold-20260428 | `@pleaseai/eslint-config@0.0.1` is the only published version; source repo shows 0.0.3. Bump pin when later versions publish. | low | 2026-04-29 |
| TD-004 | plugin-scaffold-20260428 | CI uses `actions/setup-node@v4` only because eslint shells out to Node. If linting moves to a Bun-native path, drop the Node setup step. | low | 2026-04-29 |
Expand All @@ -18,3 +17,4 @@

| ID | Source Track | Description | Resolved In | Date |
|----|------------|-------------|-------------|------|
| TD-001 | plugin-scaffold-20260428 | `consola@3.4.2` adopted as runtime dep but no call site yet. Removed — zero call sites, and it would have been inlined into the dependency-free skill bundles. | spring-docs-skill | 2026-09-13 |
32 changes: 18 additions & 14 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
> Agent-first architecture document for `@pleaseai/spring` — Claude Code plugin
> for Spring ecosystem documentation.

> **Status**: Target architecture. The repository currently contains only
> `README.md`, `LICENSE`, and project metadata; the modules described below
> are the planned implementation. As code lands, this document is updated
> to reflect actual structure (not aspirational design).
> **Status**: Target architecture, **partly superseded**. Detection
> (`scripts/detect.ts`), documentation resolution (`scripts/docs.ts`) and the
> `spring-docs` skill are implemented. They do not install documentation into
> `.claude/skills/spring-*/` and do not annotate the project's `CLAUDE.md`:
> an archive is unpacked once into `~/.cache/pleaseai-spring/docs/<tag>/` and
> callers are handed the path (see README, "Why not install the docs into the
> project"). The install/resolve/convert pipeline described below is still the
> unrevised earlier design; it is rewritten when the remaining stages land.

## System Overview

Expand Down Expand Up @@ -38,11 +42,11 @@ Dependencies flow downward only. Lower layers must not import upper layers.

```
┌──────────────────────────────────────────────────┐
│ Interface Layer │ Slash commands (commands/*.md)
│ ───────────────── │ Plugin manifest (.claude-plugin/plugin.json)
│ Interface Layer │ Plugin manifest (.claude-plugin/plugin.json)
│ ───────────────── │
├──────────────────────────────────────────────────┤
│ Skill Layer │ skills/spring-installer/SKILL.md
│ ─────────── │ (Bridges commands → scripts; Claude-invoked)
│ ─────────── │ (Bridges Claude → scripts; Claude-invoked)
├──────────────────────────────────────────────────┤
│ Orchestration Layer │ scripts/install.ts (top-level pipeline)
│ ─────────────────── │
Expand All @@ -62,13 +66,11 @@ Dependencies flow downward only. Lower layers must not import upper layers.

- **Library layer has no I/O**: pure functions for parsing and conversion. All reads/writes happen one layer up. This keeps `scripts/lib/*` trivially unit-testable with fixture inputs.
- **Domain layer functions are independently runnable**: `bun run scripts/fetch.ts framework 6.2.1 --output /tmp/...` works without first running detect or resolve. Each stage is a usable CLI on its own.
- **Slash commands hold no logic**: `commands/install.md` just delegates; behavior lives in the skill and scripts.

## Entry Points

For understanding **the install pipeline** (most common starting point):

- `commands/install.md` — Slash command entry. Maps `/spring:install` to the installer skill.
- `skills/spring-installer/SKILL.md` — Skill description that Claude Code auto-invokes; orchestrates calls to scripts.
- `scripts/install.ts` — Top-level pipeline: detect → resolve → acquire → install → annotate `CLAUDE.md`.

Expand All @@ -92,8 +94,7 @@ For understanding **what gets written to the user's project**:
| Module | Purpose | Key Files | Depends On | Depended By |
| --------------------------- | ------------------------------------------------------ | ----------------------------------------------- | --------------------------------------- | ---------------------------- |
| `.claude-plugin/` | Plugin manifest (Claude Code convention). | `plugin.json` | — | Claude Code runtime |
| `commands/` | Slash command entry points (thin Markdown). | `install.md`, `list.md`, `update.md`, `remove.md`, `add.md` | `skills/spring-installer/` | Claude Code runtime |
| `skills/spring-installer/` | Skill that orchestrates scripts; Claude-invoked. | `SKILL.md` | `scripts/install.ts` | `commands/` |
| `skills/spring-installer/` | Skill that orchestrates scripts; Claude-invoked. | `SKILL.md` | `scripts/install.ts` | Claude Code runtime |
| `scripts/` (orchestration) | Pipeline driver; one `.ts` per stage. | `install.ts`, `detect.ts`, `resolve.ts`, `fetch.ts` | `scripts/lib/`, network, filesystem | `skills/spring-installer/` |
| `scripts/lib/` | Pure helpers (parsing, conversion rules, schemas). | `antora-rules.ts`, `manifest.ts` | — | `scripts/*.ts` |
| `prebuilt/` | Catalog mapping `{component, version}` → release URL. | `catalog.json` | — | `scripts/fetch.ts` |
Expand All @@ -110,10 +111,13 @@ For understanding **what gets written to the user's project**:
These constraints must hold across all changes. Violations are blocking review issues.

**Plugin layout follows Claude Code conventions strictly.**
The manifest lives at `.claude-plugin/plugin.json` and is the **only** file in that directory. All other components (`skills/`, `commands/`, `scripts/`) sit at the plugin root. Path references inside skills/scripts use `${CLAUDE_PLUGIN_ROOT}`. *Why*: Claude Code's plugin loader assumes this layout — deviating breaks discovery.
The manifest lives at `.claude-plugin/plugin.json` and is the **only** file in that directory. All other components (`skills/`, `scripts/`) sit at the plugin root. *Why*: Claude Code's plugin loader assumes this layout — deviating breaks discovery.

**Commands hold no logic.**
A `commands/*.md` file is a thin entry point that names a skill or invokes a script. Behavior lives in the skill or in `scripts/`. *Why*: keeps commands inspectable and lets the same logic be reached from scripts/tests without going through the slash-command path.
**The skill directory is self-contained.**
`skills/spring-docs/` carries everything it executes: `SKILL.md` plus the dependency-free bundles under its own `scripts/`. Skill content addresses them through `${CLAUDE_SKILL_DIR}`, never `${CLAUDE_PLUGIN_ROOT}`. *Why*: the skill ships through two channels. As a plugin it gets a plugin root and an automatic dependency install; installed standalone with `npx skills` it gets neither, because only the skill directory is copied. `${CLAUDE_SKILL_DIR}` resolves in both, `${CLAUDE_PLUGIN_ROOT}` is substituted only in plugin skills, and a bare relative path resolves against the user's project — where these scripts do not exist.

**Bundles are generated, never edited.**
`skills/spring-docs/scripts/*.mjs` is output from `bun run build:skill`; the TypeScript sources under `scripts/` are the only editable form. CI fails on a stale bundle (`bun run build:skill:check`). *Why*: `npx skills` copies straight from the repository, so no build step runs between the source and the installed skill — the artifact has to be committed, and a committed artifact drifts unless something checks it.

**Library layer is I/O-free.**
Modules under `scripts/lib/` (e.g., `antora-rules.ts`, `manifest.ts`) accept inputs and return outputs — no `fetch`, no `fs`, no `process.env`. *Why*: lets the bulk of the conversion logic be tested with fixture inputs and run safely in any environment.
Expand Down
Loading
Loading