Skip to content
Closed
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
170 changes: 170 additions & 0 deletions docs/superpowers/plans/2026-09-18-skill-materialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Skill Materialization Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add opt-in `--save` delivery that materializes a fetched skill into an immutable temporary file and returns a compact instruction, without changing existing callers.

**Architecture:** Add a focused materializer that owns validation, canonicalization, hashing, collision resolution, and atomic directory publication. Keep registry transport and default output unchanged; make `skills get --save` opt into materialization and its reader-aware handoff output.

**Tech Stack:** TypeScript, Node.js standard library, Zod, Vitest

**Spec:** `docs/superpowers/specs/2026-09-18-skill-materialization.md`

## Global Constraints

- Do not use TypeScript type assertions or coercions.
- Return public CLI failures as `CliError` values with actionable messages.
- Do not edit generated artifacts.
- Use test-driven development and preserve the compact/nondecorated piped output contract.
- Without `--save`, preserve the current raw Markdown and `data.document` JSON contracts byte-for-byte.
- Treat `--save=false` as an explicit opt-out.
- Never overwrite a published skill artifact; reuse an exact match or extend the hash prefix for a conflicting path.
- Do not create a commit unless the user explicitly asks for one.

---

### Task 1: Immutable skill materializer

**Files:**
- Create: `src/skill-materializer.ts`
- Create: `src/skill-materializer.test.ts`

**Interfaces:**
- Consumes: a requested skill name, fetched Markdown string, and optional `{ rootDirectory, now }` dependencies.
- Produces: `materializeSkill(name, document, deps): MaterializedSkill`, where `MaterializedSkill` contains `name`, `path`, `sha256`, `bytes`, `lines`, and `instruction`.

- [ ] **Step 1: Write failing tests for canonical materialization**

Cover the literal 12-character-prefix directory shape, canonical bytes, full
SHA-256 metadata, byte/line counts, file permissions, instruction string, and
absence of staging directories after success using a temporary test root and
fixed clock.

- [ ] **Step 2: Run the focused test and verify the expected missing-module failure**

Run: `pnpm vitest run src/skill-materializer.test.ts`

- [ ] **Step 3: Implement minimal validation and materialization**

Use `node:crypto`, `node:fs`, `node:os`, and `node:path`. Validate the name,
frontmatter name, and EOF sentinel with a Zod string schema. Canonicalize CRLF
to LF plus one trailing newline, create private parent directories, write
`SKILL.md` completely inside a uniquely named private sibling staging
directory, then atomically rename the staging directory to the final version
directory. Map filesystem failures to actionable `CliError` values and clean
the staging directory on every unsuccessful publication.

- [ ] **Step 4: Run the focused test and verify it passes**

Run: `pnpm vitest run src/skill-materializer.test.ts`

- [ ] **Step 5: Add failing validation tests, then implement actionable failures**

Cover invalid requested names, mismatched frontmatter names, missing
sentinels, and mismatched sentinels. Each failure must be an `upstream_error`
or `usage_error` `CliError` and must leave no final skill file.

- [ ] **Step 6: Add failing collision tests**

Prepublish an exact canonical document at the fixed timestamp and 12-character
prefix, then assert a second materialization returns that path without
changing the existing file metadata. Prepublish different bytes at the same
candidate path, then assert materialization leaves that artifact untouched
and publishes under the same timestamp with a 16-character prefix. Also
assert no staging directories remain after either outcome.

- [ ] **Step 7: Implement deterministic collision resolution**

Before publication, compare an existing candidate's exact bytes with the
canonical document and reuse an exact match. For different bytes, try digest
prefix lengths `12, 16, 20, ..., 64`. During a publication race, inspect the
candidate after a failed directory rename: reuse it if the bytes match or
continue to the next prefix if they differ. If the 64-character candidate is
also occupied by different bytes, return an actionable `CliError` without
modifying any existing artifact.

- [ ] **Step 8: Run the focused materializer tests and verify they pass**

Run: `pnpm vitest run src/skill-materializer.test.ts`

### Task 2: Command and help contract

**Files:**
- Modify: `src/args.ts`
- Modify: `src/args.test.ts`
- Modify: `src/skills-commands.ts`
- Modify: `src/skills-commands.test.ts`
- Modify: `src/catalog.ts`
- Modify: `src/catalog.test.ts`
- Modify: `src/help.ts`
- Modify: `src/help.test.ts`

**Interfaces:**
- Consumes: `materializeSkill` from Task 1.
- Produces: the existing raw/JSON document output without `--save`; one-line text output with `--save`; and `{ data: MaterializedSkill }` with `--save --json`.

- [ ] **Step 1: Write failing parsing and flag-validation tests for `--save`**

Add `save` as an optional boolean global option that is accepted only by
`skills get`. Assert that bare `--save` and `--save=true` opt in,
`--save=false` opts out, and other commands reject it.

- [ ] **Step 2: Write failing command tests for the opt-in output matrix**

Preserve the existing assertions for raw Markdown and
`{ data: { name, document } }` without `--save`. Add assertions that
`--save` emits only the materializer instruction at both a TTY and through a
pipe, invokes the materializer exactly once, and that `--save --json` emits
the same materialization fields without `document`.

- [ ] **Step 3: Run command, argument, catalog, and help tests and verify contract failures**

Run: `pnpm vitest run src/args.test.ts src/skills-commands.test.ts src/catalog.test.ts src/help.test.ts`

- [ ] **Step 4: Wire opt-in materialization into `runSkillsGet`**

Add `save` to the parseable global options and the `skills get` catalog flag
set. Inject the materializer through command dependencies for focused tests.
Always fetch once; without `--save`, retain the existing output branches.
With `--save`, materialize once, emit the instruction plus one newline, and
use the existing JSON formatter when `--json` is also enabled.

- [ ] **Step 5: Update public catalog and help copy**

Preserve the description of the default raw and `data.document` contracts.
Explain that `--save` stores a complete skill and prints the instruction
needed to use it, and that combining it with `--json` returns structured
materialization metadata.

- [ ] **Step 6: Run focused tests and verify they pass**

Run: `pnpm vitest run src/skill-materializer.test.ts src/args.test.ts src/skills-commands.test.ts src/catalog.test.ts src/help.test.ts`

### Task 3: Full verification

**Files:**
- Verify all modified and created files.

**Interfaces:**
- Consumes: completed Tasks 1 and 2.
- Produces: evidence that the public CLI remains type-safe and its full test suite passes.

- [ ] **Step 1: Run the TypeScript check**

Run: `pnpm test:typescript`

- [ ] **Step 2: Run the complete package test suite**

Run: `pnpm test`

- [ ] **Step 3: Run the package build**

Run: `pnpm build`

- [ ] **Step 4: Review the diff against the design**

Confirm the final output is minimal, the file is immutable and complete, no
generated files changed, `--save` always fetches before materializing, and
omitting `--save` retains every existing output and filesystem behavior.
Confirm exact collisions reuse without rewriting, different-byte collisions
extend the digest prefix, and no code path overwrites a published artifact.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internal agent plans published

Medium Severity

These Superpowers plan and spec files are agent scratch for an unimplemented --save skill-materialization feature. They ship in the npm tarball via docs/**/* and expose internal delivery-experiment and agent-tooling context, which violates the public-hygiene and no-local-dev-artifacts rules.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: Bugbot review guide — @amplitude/developer-cli

Reviewed by Cursor Bugbot for commit 8cf3f52. Configure here.

78 changes: 78 additions & 0 deletions docs/superpowers/specs/2026-09-18-skill-materialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Skill Materialization Design

## Goal

Make `amp skills get <name>` reliable for agents even when a skill is larger
than a shell tool's inline-output limit, while preserving the existing command
behavior during an opt-in evaluation period.

## Command contract

- Without `--save`, `amp skills get <name>` preserves its current behavior:
raw Markdown goes to stdout, while `--json` returns the document in
`data.document`.
- With `--save`, every invocation fetches the current skill, validates and
canonicalizes it, and publishes or reuses an immutable artifact beneath the
operating system temporary directory:
`<tmp>/amp/skills/<name>/<UTC timestamp>-<SHA-256 prefix>/SKILL.md`.
- `--save` prints one instruction:
`Read and follow the complete \`<name>\` skill at \`<path>\` for the current task.`
- `--save --json` returns structured materialization metadata instead of
embedding the skill document.
- `--save=false` is equivalent to omitting `--save`.

## Validation and identity

- Skill names use lowercase letters, digits, and internal hyphens.
- The Markdown begins with frontmatter whose `name` equals the requested name.
- The final nonblank line is `<!-- END AMP SKILL: <name> -->`.
- Documents are canonicalized to LF line endings and exactly one final newline.
- The directory uses a UTC second-resolution timestamp and at least the first
12 hexadecimal characters of the canonical document's SHA-256 digest.
- The full canonical bytes, not only the shortened hash in the path, determine
whether an existing artifact is identical and reusable.
- JSON exposes the full digest, byte count, and line count for diagnostics.

These checks apply to the `--save` path during the evaluation period. The EOF
sentinel is an internal delivery check. The normal instruction does not ask
agents to verify metadata or perform a second consumption workflow.

## Filesystem behavior

- Without `--save`, the command performs no filesystem writes.
- Parent directories and the final saved skill file are private to the current
user.
- The CLI writes `SKILL.md` completely inside a uniquely named sibling staging
directory, then atomically renames that directory to the final version path.
The final path is never used as a staging location and a published artifact
is never overwritten.
- If another invocation has already published the same timestamp, hash prefix,
and exact canonical bytes, the CLI removes its staging directory and reuses
the existing path without rewriting `SKILL.md`.
- If a candidate path exists with different bytes, the CLI extends the digest
prefix four hexadecimal characters at a time until it finds an unused path.
If all 64 characters are exhausted, it fails without changing any published
artifact.
- A failed publication removes its private staging directory. It determines a
collision by inspecting the final path after the failed rename rather than
relying on a platform-specific filesystem error code.
- No mutable `latest` pointer is created. Running the command again always
fetches and materializes the current registry response, so an earlier path
cannot silently stand in for a later retrieval.
- Lifecycle cleanup is delegated to the operating system temporary directory.

## Rollout

- The website prompt opts into the experiment with
`amp skills get integrating-amplitude --region us --save`.
- Existing users, scripts, redirects, and JSON consumers remain unchanged until
they add `--save`.
- Making saved delivery the default is a separate decision informed by agent
completion quality, unnecessary tool-call count, and compatibility results.

## Skill authoring

Entry-point `SKILL.md` files should remain focused on routing, invariants, and
the workflow. Large SDK-specific material should be progressively disclosed
through supporting files or child skills. A practical internal target is at
most 20 KB or 300 lines for the entry file.
Loading
Loading