# Claude Code — Dev Guide for singularity-forge

See [AGENTS.md](AGENTS.md) for SF planning conventions and the promote-only state rule.
The foundational product contract is [ADR-0000: SF Is a Purpose-to-Software Compiler](docs/adr/0000-purpose-to-software-compiler.md).

## Build pipeline (MUST READ before editing extension source)

Source TypeScript files under `src/resources/extensions/sf/` are **not loaded
directly at runtime**. The loader (`src/loader.ts`) resolves extension entry
points from `dist/resources/extensions/sf/` (compiled `.js`) and copies them
to `~/.sf/agent/extensions/sf/` via `initResources`. Editing a `.ts` source
file has **no effect** until you recompile:

```bash
npm run copy-resources   # tsc --project tsconfig.resources.json + file copy
```

This clears and rebuilds `dist/resources/` in one shot. Expect ~60–90 s on
first run; subsequent runs reuse tsc's incremental cache if you keep one.

The `dist-redirect.mjs` resolver (used by tests and `dev-cli.js`) only
redirects `.js → .ts` for imports whose `parentURL` is inside `/src/`. Files
loaded from `~/.sf/agent/extensions/sf/` (compiled JS) are **not** redirected.

## Running tests

**Use vitest — no pre-compilation step needed.**

```bash
# Run a specific test file (fast, no coverage overhead):
npx vitest run src/resources/extensions/sf/tests/<name>.test.ts --config vitest.config.ts

# Run the full SF extension test suite:
npm run test:unit

# Run only tests affected by recent changes (fast feedback loop):
npx vitest run --changed --config vitest.config.ts

# Watch mode for active development:
npx vitest --config vitest.config.ts
```

**Do not use Python for one-off JSON/hash work.** The resource fingerprint in
`~/.sf/agent/managed-resources.json` is computed by Node's SHA-256 — Python's
`hashlib` produces a different result for the same files, which breaks the
fast-path check in `initResources` and causes a 30-60 s full resync on every
launch. Use `node -e` (or `jq`) for any shell-level JSON/hash operations in
this repo.

## Key directories

| Path | Purpose |
|------|---------|
| `src/resources/extensions/sf/` | Extension TypeScript source (edit here) |
| `dist/resources/extensions/sf/` | Compiled output (rebuilt by `copy-resources`) |
| `~/.sf/agent/extensions/sf/` | Installed copy (synced from dist on startup) |
| `src/resources/extensions/sf/prompts/` | Prompt templates (`.md`) |
| `src/resources/extensions/sf/tests/dist-redirect.mjs` | Module resolver hook for tests |

## Template variables

When adding a new `{{variable}}` to a prompt template in `prompts/`, you must:

1. Pass it in every `loadPrompt("template-name", { ..., newVar })` call site
   (`auto-prompts.ts` is the main one for execute-task).
2. Add it (with a sensible placeholder value) to any test that calls
   `loadPrompt("template-name", {...})` — see
   `src/resources/extensions/sf/tests/plan-slice-prompt.test.ts`.
3. Run `npm run copy-resources` to land the change in dist.

`loadPrompt` throws at runtime if any `{{var}}` in the template has no
corresponding key in the vars object — this is intentional to catch
template/code drift early.
