singularity-forge/CLAUDE.md
Mikael Hugo e50f2c0af1
Some checks are pending
sf self-deploy / build, test, and publish server image (push) Waiting to run
sf self-deploy / deploy test and probe (push) Blocked by required conditions
sf self-deploy / promote prod (push) Blocked by required conditions
chore: align workflow + docs with k3s-only deploy path
Followup to the dead-docker delete: remove `docker:vega:*` package.json
scripts, the projects-view upgrade button, and the docker-compose-vega
sections of sf-self-deploy.md. Self-deploy workflow stays k3s-only
(build → push → deploy-test → deploy-prod via kubectl set image).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 01:04:05 +02:00

6.2 KiB
Raw Blame History

Claude Code — Dev Guide for singularity-forge

See 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.

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:

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

This clears and rebuilds dist/resources/ in one shot. Expect ~6090 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.

# 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.

Lint: test-import-drift guard

Problem: Test files with itemized import { foo } from "module" and many named imports (≥6) carry a maintenance trap — adding a new describe(...) block that uses a module function without updating the import list causes ReferenceError at vitest run-time, not at lint time. Biome's ESM lint does not catch used identifier not declared.

Guard: npm run check:test-imports runs scripts/check-test-imports.mjs which statically scans all *.test.{js,mjs,ts} files for this anti-pattern. It flags files that have ≥6 itemized imports AND reference a camelCase identifier not in the import list. False positives for test locals, vitest globals, Node builtins, and keyword-like words are filtered.

The check is NOT integrated into npm run lint by default (too broad for the current threshold) but runs as npm run check:test-imports. Add it to the lint script if the threshold is later lowered.

Convention: Test files whose subject is the public surface of a SF module (migration tests, integration tests over a module's full API) should use import * as <Namespace> from "<module>" instead of itemized imports when ≥6 named members are needed. This avoids the maintenance trap entirely. The check script targets files with ≥6 itemized imports + an undeclared camelCase identifier. Known false-positive categories are filtered (test locals, vitest globals, Node builtins, keyword-like words, boolean flags), but some legitimate cases may still appear in complex test files — use judgement when triaging output.

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.

Running the SF server in this repo

Use the server surface for dogfooding and browser/RPC control. Do not start the TUI as the default way to exercise autonomous mode.

# source/dev server, with resource redirect and restart support
npm run sf:server -- --port 4000 --host 127.0.0.1

# built server, after npm run build:core or npm run build
npm run sf:server:dist -- --port 4000 --host 127.0.0.1

If the server is already running, prefer sf headless ... control commands rather than starting a second writer. Feedback add/resolve commands are forwarded to the active server and queued there so CLI control does not hang behind an autonomous unit.

For remote local-network access, bind an additional trusted interface such as a Tailscale address. Do not bind 0.0.0.0 for the dev server unless an explicit fronting proxy/firewall decision is in place.

Before assuming a source edit is live, rebuild the relevant output:

npm run build:core

Then restart the server. Stale dist/ or stale ~/.sf/agent/extensions/sf/ copies can make fixed source look broken.

Self-deploy is documented in docs/specs/sf-self-deploy.md. The short version: Forgejo builds the source-pinned server image, generates dist/sf-release-manifest.json, rolls test, probes /api/healthz, /api/ready, and /api/version, then promotes the same image to prod. On vega, k3s owns the production server on port 4000. It is one shared webserver with repo switching underneath it; upgrades happen by rolling the verified image through the sf-server-test deployment before sf-server.