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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ All notable changes to atom land here. Format: [Keep a Changelog](https://keepac

No changes pending. v0.3 candidate list at `docs/planning/v0.3.md`.

## [0.3.0] — 2026-06-05

Opens the v0.3 line with the first candidate to ship ahead of scope-lock: a global Claude skill that runs atom's bootstrap from inside any session. The five CLIs align to 0.3.0; `bin/atom-update-check` stays at 0.1.0.

### Added

- **`/atom-new` — bootstrap a project from inside Claude** (v0.3 #23). A global Claude Code skill that drives the Mode 1 bootstrap conversationally from any directory or session — no `cd ~/.atom/atom`, no terminal round-trip. New top-level `skills/` directory holds **global user-facing** skills (distinct from `scaffold/.claude/skills/`, which are copied *into* bootstrapped projects). The skill is a **thin launcher**: it locates the installed atom (`$ATOM_SOURCE_DIR` → `$ATOM_INSTALL` → `~/.atom/atom/`), reads `AGENTS.md` Mode 1 + the docs it references at invoke time, and runs that flow against a target directory it asks the user to confirm. It deliberately does *not* restate the bootstrap steps, so it can't drift from `AGENTS.md` — the single source of truth. Scoped to Mode 1 only; Modes 2 (maintain atom) and 3 (build atom features) are dev-facing and stay out. Fails gracefully with the `install.sh` one-liner when no atom checkout is found.
- **Global-skill linking in `install.sh` and `atom upgrade`.** Both symlink each subdirectory of `skills/` into `~/.claude/skills/` (e.g. `~/.claude/skills/atom-new` → `~/.atom/atom/skills/atom-new`). Because it's a symlink into the git checkout, `atom upgrade`'s `git pull` refreshes skill content with no copy step — no new distribution channel, consistent with git-not-npm. Idempotent: re-runs repoint stale symlinks. Safe: a real (non-symlink) file or directory the user owns at the target name is left untouched and the skip is reported, never silently clobbered. `atom upgrade` gained an exported `linkGlobalSkills(installDir)` that runs after the CLI re-install (covers users who installed before global skills existed). Best-effort in both paths — a link failure never fails the install or upgrade.

### Tests

- **Test 21 in `scripts/test-atom-setup.sh`** (14 assertions): the skill ships and is shaped as a discoverable thin launcher (frontmatter `name`/`description`, references `AGENTS.md` Mode 1, has the not-installed fallback); both install paths wire the symlink and protect user-owned dirs; and a hermetic functional pass exercising `linkGlobalSkills` directly across fresh-link / idempotent-repoint / user-owned-dir-skip, using a scratch `HOME` so the runner's real `~/.claude` is never touched. Suite total 164 → 178. Added a `bin/atom` dependency pre-flight (Test 20 and 21 both load `picocolors` via `upgrade.js`).
- **Fixed two CI-only test failures** (pre-existing, environment-specific). `14.15` asserted on `<bold>name</bold> is ready`, but picocolors auto-enables ANSI codes when `$CI` is set (GitHub Actions), splitting the string — pinned `NO_COLOR=1` in the harness so output is plain text everywhere. `20.7` double-quoted backticks in its grep pattern, producing `` \` `` which GNU grep (Ubuntu CI) treats as a buffer-start anchor that never matches mid-pattern, while BSD grep (macOS) took it literally — single-quoted the pattern so backticks stay literal. Verified against GNU grep 3.12. The wizard-e2e CI job, red since 2026-05-14, is green again.

## [0.2.1] — 2026-05-14

Bundled release for Wave 2 (user-facing features) + Wave 3 (distribution architecture). All five CLIs aligned to 0.2.1; new `bin/atom-update-check` ships at 0.1.0.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ To keep atom itself up to date later, run `atom upgrade`.
> [!TIP]
> Want zero questions? Run `atom-setup new my-project --bare` and you're done in under 5 seconds. All flags pass through (`--minimal`, `--full`, `--dry-run`, `--resume`, etc.).

> [!TIP]
> Live in Claude Code? Install puts an **`/atom-new`** skill on your machine. Type `/atom-new` in any session — Claude walks you through the same bootstrap conversationally, no trip back to the terminal. The wizard above is still there when you want the fast, no-questions path.

### Don't trust curl-pipe-bash? Install manually.

```bash
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.1
0.3.0
2 changes: 1 addition & 1 deletion bin/atom-setup/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@atom/atom-setup",
"version": "0.2.1",
"version": "0.3.0",
"description": "Interactive wizard that transforms a cloned atom checkout into a personalized project.",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion bin/atom/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@atom/atom",
"version": "0.2.1",
"version": "0.3.0",
"description": "Top-level help dispatcher for atom's tooling. Lists every CLI in one place.",
"type": "module",
"bin": {
Expand Down
69 changes: 68 additions & 1 deletion bin/atom/src/upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@
// Refuses to upgrade if the install dir's git tree is dirty — the
// user has been editing it and a pull might lose work.

import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
import {
existsSync,
lstatSync,
mkdirSync,
readFileSync,
readdirSync,
realpathSync,
rmSync,
statSync,
symlinkSync,
} from 'node:fs';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';
import { spawnSync } from 'node:child_process';
Expand Down Expand Up @@ -130,9 +140,66 @@ export async function runUpgrade(args) {
}
}

linkGlobalSkills(installDir);

console.log(`\n ${color.green('✓')} now on atom ${color.bold(upstreamVersion)}`);
}

// Symlink each skill in <install>/skills/ into ~/.claude/skills/ so it's
// invocable from any Claude session. Mirrors the install.sh block; this
// path covers users who installed before global skills existed and
// repoints stale links after the git pull. Best-effort: a failure here
// never fails the upgrade (CLIs are already updated by this point).
export function linkGlobalSkills(installDir) {
const skillsSrc = join(installDir, 'skills');
if (!existsSync(skillsSrc)) return;

const claudeSkillsDir = join(homedir(), '.claude', 'skills');
let entries;
try {
entries = readdirSync(skillsSrc, { withFileTypes: true });
} catch {
return;
}

for (const entry of entries) {
if (!entry.isDirectory()) continue;
const name = entry.name;
const target = join(skillsSrc, name);
const dest = join(claudeSkillsDir, name);
try {
mkdirSync(claudeSkillsDir, { recursive: true });
const link = lstatExisting(dest);
if (link === 'symlink') {
rmSync(dest);
symlinkSync(target, dest);
console.log(` ${color.dim('→')} skill ${name} ${color.dim('linked')}`);
} else if (link === 'other') {
// A real file/dir the user owns — never clobber it.
console.log(
` ${color.dim('→')} skill ${name} ${color.dim(`skipped (~/.claude/skills/${name} exists, not a link)`)}`,
);
} else {
symlinkSync(target, dest);
console.log(` ${color.dim('→')} skill ${name} ${color.dim('linked')}`);
}
} catch {
// Non-fatal: the CLI upgrade already succeeded.
console.log(` ${color.dim('→')} skill ${name} ${color.dim('(link skipped)')}`);
}
}
}

// Returns 'symlink' if dest is a symlink, 'other' if it's a real
// file/dir, null if it doesn't exist.
function lstatExisting(dest) {
try {
return lstatSync(dest).isSymbolicLink() ? 'symlink' : 'other';
} catch {
return null;
}
}

function findInstallDir() {
// 1. env override
const env = process.env.ATOM_INSTALL;
Expand Down
2 changes: 1 addition & 1 deletion bin/learnings/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@atom/learnings",
"version": "0.2.1",
"version": "0.3.0",
"description": "Your local playbook of generalized patterns. Lives at ~/.atom/learnings, optionally synced to a private GitHub repo. Auto-copied into every new atom-bootstrapped project.",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion bin/model-race/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@atom/model-race",
"version": "0.2.1",
"version": "0.3.0",
"description": "Run the same feature spec through multiple AI models in parallel via Git worktrees. Compare, score, merge the winner.",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion bin/nucleus/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@atom/nucleus",
"version": "0.2.1",
"version": "0.3.0",
"description": "Cross-project learning store. Captures session learnings into ~/.nucleus, syncs across machines via GitHub.",
"type": "module",
"bin": {
Expand Down
81 changes: 76 additions & 5 deletions docs/planning/v0.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,73 @@ doctor command answers them in one paste.

---

### #23 `/atom-new` bootstrap skill

> **Status: built** (shipped ahead of v0.3 scope-lock, see `[Unreleased]`
> in `CHANGELOG.md`). Lives in `skills/atom-new/`; linked into
> `~/.claude/skills/` by `install.sh` + `atom upgrade`. Locked decisions
> below held: conversational, Mode 1 only, thin launcher, symlink
> distribution. Open questions resolved during build: named `atom-new`;
> target dir is confirmed per-invocation; not-installed path points at
> `install.sh`. **Released in 0.3.0 (2026-06-05)** — opened the v0.3
> line ahead of full scope-lock.

**What**: ship a global Claude Code skill (`/atom-new`) that drives the
Mode 1 bootstrap flow conversationally from **any** directory or
session — no `cd ~/.atom/atom`, no terminal round-trip. The skill is a
**thin launcher**: it locates the installed atom (`~/.atom/atom/`),
reads `AGENTS.md` Mode 1 + `docs/` from there, and executes the
conversational bootstrap against the current working dir. It does *not*
restate the steps — `AGENTS.md` stays the single source of truth.

**Why**: today, to bootstrap you have to *be inside atom* — either
`cd ~/.atom/atom` so `CLAUDE.md → AGENTS.md` auto-loads, or run the
`atom-setup` wizard in a terminal. Both mean leaving wherever you
actually are. A global skill removes that friction, and — the real
unlock — its description makes Claude *proactively offer it* when a user
says "let's start a new project" in any session. You stop having to
remember atom exists. The `atom-setup` wizard already serves the
terminal-first path; this is the in-Claude conversational counterpart.

**Scope (locked)**: **Mode 1 only.** Modes 2 (maintain atom) and 3
(build atom features) are dev-facing — they only make sense inside the
`atom-dev` repo. Folding them into a global skill makes it ambiguous
("start a project... or maintain the template?") for zero benefit. One
focused skill = the bootstrap flow.

**Flow (locked)**: **conversational**, not a wizard shell-out. The
guided dialogue is atom's differentiator *inside* Claude; the
deterministic 10-section wizard already exists for terminal lovers.

**Distribution**: piggyback on existing machinery, no new channel. The
skill installs to `~/.claude/skills/atom-new/` as one more global
symlink that `install.sh` creates and `atom upgrade` refreshes —
consistent with the dev/use globals separation
(`atom_dev_use_separation_state` memory) and the git-not-npm decision
(`atom_distribution_decision` memory).

**Open questions**:
- Name: `/atom-new` vs. `/new-project` vs. `/atom`. `/atom` is
ambiguous against the `atom` CLI; lean `/atom-new`.
- Where does the new project get created? The wizard handles target
dir; the skill must ask (cwd vs. a named subdir) since it can fire
from anywhere.
- Thin-launcher contract: the skill reads `AGENTS.md` Mode 1 from the
install at invoke time. Confirm it fails gracefully (points to
`install.sh`) when atom isn't installed.
- Discovery tuning: the skill `description:` is what makes Claude
proactively offer it. Get the trigger phrasing right ("start a new
project", "bootstrap from a template") without over-firing.
- Compatibility: Claude Code only. Other AI tools keep the
`cd` + AGENTS.md path and the `atom-setup` wizard.

**Effort**: small. A thin `SKILL.md` (~20–40 lines pointing at the
installed `AGENTS.md`/`docs`) + one symlink wired into `install.sh` and
`atom upgrade`. The risk is *not* effort — it's the fork-the-instructions
trap, which the thin-launcher design kills.

---

## Tier 2 — interesting, requires deliberate scoping

These could fit atom but only if v0.3 explicitly decides atom is
Expand Down Expand Up @@ -347,16 +414,20 @@ it — the embedding column belongs in the DB.
material already exists; the work is curation + voice rewrite.
Almost certainly yes if the maintainer is comfortable shipping the
distilled patterns as opinionated defaults.
4. **Decide on #18 (cost tracking)**. The §1 wizard question forces
4. **Decide on #23 (`/atom-new` skill)**. Standalone, cheap (thin
launcher + one symlink), reinforces proactive discovery. Almost
certainly yes — the only real risk is the fork-the-instructions
trap, which the thin-launcher design already mitigates.
5. **Decide on #18 (cost tracking)**. The §1 wizard question forces
the issue: ship the tracker, or drop the question.
5. **Decide on #16 (SQLite)**. Architectural; gate on whether #20
6. **Decide on #16 (SQLite)**. Architectural; gate on whether #20
(semantic) is in scope and on whether scale demands it yet.
6. **Decide on #17 (research)**. Lowest urgency; defer to v0.4 if
7. **Decide on #17 (research)**. Lowest urgency; defer to v0.4 if
v0.3 already fills up.
7. **Ship #22 Phase A now (out-of-band v0.2.x)**. WSL2 callout is a
8. **Ship #22 Phase A now (out-of-band v0.2.x)**. WSL2 callout is a
~1hr stopgap independent of the v0.3 wave. Phase B (native port) is
demand-gated — re-evaluate after Phase A surfaces signal.
8. **#19, #20** ride along with whichever wave fits.
9. **#19, #20** ride along with whichever wave fits.

---

Expand Down
28 changes: 28 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,41 @@ if [ ${#MISSING[@]} -gt 0 ]; then
exit 1
fi

# ─── global Claude skills ───────────────────────────────────────────
# Symlink each skill in $TARGET/skills/ into ~/.claude/skills/ so it's
# invocable from any Claude session. Symlinks (not copies) into the git
# checkout mean `atom upgrade`'s git pull refreshes them for free.
# Best-effort: a skill link failing must not fail the whole install.
SKILLS_SRC="$TARGET/skills"
CLAUDE_SKILLS_DIR="$HOME/.claude/skills"
if [ -d "$SKILLS_SRC" ]; then
for skill_path in "$SKILLS_SRC"/*/; do
[ -d "$skill_path" ] || continue
skill_name="$(basename "$skill_path")"
dest="$CLAUDE_SKILLS_DIR/$skill_name"
mkdir -p "$CLAUDE_SKILLS_DIR"
if [ -L "$dest" ]; then
# Existing symlink — repoint it (idempotent across reinstalls).
ln -sfn "${skill_path%/}" "$dest"
printf " → skill %-12s linked\n" "$skill_name"
elif [ -e "$dest" ]; then
# A real file/dir the user owns — never clobber it.
printf " → skill %-12s skipped (\$HOME/.claude/skills/%s exists, not a link)\n" "$skill_name" "$skill_name"
else
ln -sfn "${skill_path%/}" "$dest"
printf " → skill %-12s linked\n" "$skill_name"
fi
done
fi

# ─── success ────────────────────────────────────────────────────────
echo ""
echo "✓ atom installed. Run \`atom-setup new <project-name>\` to start your first project."
echo ""
echo "Quick reference:"
echo " atom --help see every atom command"
echo " atom-setup new my-project scaffold a new project"
echo " /atom-new bootstrap a project from inside Claude"
echo " atom upgrade update atom in place"
echo " nucleus init one-time setup for your session memory"
echo ""
Loading
Loading