Procházet zdrojové kódy

chore(bigpowers): adopt bigpowers lifecycle and conventions

Adopt the bigpowers spec-driven lifecycle alongside the existing
AGENTS.md workflow. Split domains: AGENTS.md stays the cluster/recipe/
DGX-ops source of truth (untouched); CLAUDE.md + CONVENTIONS.md + specs/
become the dev-workflow + planning source of truth.

New artifacts:
- CLAUDE.md (81 lines) — project, commands, architecture, conventions,
  never-list, agent rules. Fenced markers for skill-managed sections.
- CONVENTIONS.md (72 lines) — Always Green doctrine, Discovered Defects
  ladder, banned dismissive phrases, project hard rules (≤500 lines,
  make check, no #[allow], no Co-authored-by), cluster safety, shell
  quoting, i18n. Passes validate-agentic-ste.sh --strict.
- specs/tech-architecture/tech-stack.md (149 lines) — cold architectural
  scan via map-codebase: stack, data flow, module layout, conventions,
  8 active signals (debt hotspots, i18n thread-local bug, etc.).
- specs/state.yaml — solo-git mode, Sustain phase, active_flow null.
- specs/ tree scaffolded (product/, tech-architecture/, verifications/,
  epics/archive/, bugs/, adr/) with standard placeholder files.

AGENTS.md hard gates (≤500 lines, make check, no #[allow]) still bind
and are restated in CONVENTIONS.md. make check passes unchanged.

bigpowers helper scripts stay external (callable via absolute path
from ~/.pi/agent/npm/node_modules/bigpowers/scripts/); not copied into
the project repo.
Alexander Korolev před 2 měsíci
rodič
revize
3379f83f32

+ 81 - 0
CLAUDE.md

@@ -0,0 +1,81 @@
+# sm121-launcher — Claude Code
+
+Read CONVENTIONS.md before any git operation.
+
+<!-- BEGIN bigpowers:project -->
+## Project
+
+Rust CLI for launching vLLM/SGLang inference containers across DGX Spark clusters via SSH.
+Docker default. tmux alternative.
+
+Stack: Rust edition 2024. clap, tokio, russh, anyhow, tracing, fluent-i18n.
+
+## Commands
+
+| Action    | Command                                      |
+|-----------|----------------------------------------------|
+| Build     | `cargo build`                                |
+| Test      | `cargo test` (128 tests)                     |
+| Lint      | `cargo clippy` (`-D warnings`)               |
+| Preflight | `make check` (fmt→clippy→test→≤500 lines)    |
+| Dry-run   | `cargo run -- -d serve --recipe <name>`     |
+| CI        | `gh pr checks` (when a PR is open)           |
+
+## Architecture
+
+CLI dispatches subcommands: serve, stop, logs, health, doctor, exec, power, tmux, build.
+orchestrate/ ties cluster selection, before_serve hooks, docker launch, ssh exec.
+config/ loads config.toml + recipes. docker/ owns build, sync, command, compose, mods.
+ssh.rs wraps russh for per-node remote execution.
+See `specs/tech-architecture/tech-stack.md` for the full architectural map.
+
+## Conventions
+
+- Files MUST stay ≤500 lines. Extract submodules near the limit.
+- `make check` MUST pass before every commit. No exceptions.
+- NEVER add `#[allow(...)]` attributes. Refactor to fix clippy.
+- NEVER add `Co-authored-by` footers. Author solely as the user.
+- Use Conventional Commits: `feat(...)`, `fix(...)`, `docs(...)`.
+- Run `sm121 health` before `stop` or `power off`. The cluster serves live traffic.
+
+## Never
+
+- Never run `stop` or `power off` without `health` + user confirmation.
+- Never dismiss reproducible gate failures as pre-existing.
+- Never proceed on red Preflight. Invoke quick-fix or fix-bug first.
+- Never assume the cluster is idle. Verify with `health` first.
+<!-- END bigpowers:project -->
+
+<!-- BEGIN bigpowers:context-routing -->
+## Context Routing
+
+| Glob | Source of truth |
+|------|-----------------|
+| `recipes/**`, `docker-images/**`, `host-scripts/**` | `AGENTS.md` |
+| `src/**`, `locales/**` | `CONVENTIONS.md` + `specs/` |
+| Cluster/recipe/config format questions | `AGENTS.md` (cluster ops SoT) |
+| Dev workflow / planning / lifecycle | `specs/` (bigpowers SoT) |
+<!-- END bigpowers:context-routing -->
+
+<!-- BEGIN bigpowers:learned-preferences -->
+## Learned User Preferences
+
+- (none recorded yet)
+
+## Workspace Facts
+
+- Project root: `/Volumes/main/projects/git.kilork.org/kilork/sm121-launcher`
+- Remote: `git.kilork.org/kilork/sm121-launcher.git`
+- SSH user: `kilork` (from config.toml)
+- bigpowers scripts: `~/.pi/agent/npm/node_modules/bigpowers/scripts/`
+<!-- END bigpowers:learned-preferences -->
+
+## Agent Rules
+
+- **Workflow Mandate:** Use bigpowers skills for tasks. DO NOT write code directly.
+- **Always Green:** Preflight MUST pass before forward work.
+- Read `specs/` before writing code.
+- Write minimum code that solves the stated problem. Nothing extra.
+- Run tests after every change. Show evidence before declaring done.
+- One clarifying question beats a wrong assumption baked into 200 lines of code.
+- **Dual SoT:** `AGENTS.md` owns cluster/recipe ops. `CONVENTIONS.md` owns dev workflow.

+ 72 - 0
CONVENTIONS.md

@@ -0,0 +1,72 @@
+# sm121-launcher — Conventions
+
+Shared rules for all AI agents. Read before any git operation.
+
+## Always Green / Shift Left
+
+Gate failures cost more downstream. Fix them at the source.
+Preflight MUST pass before every commit. No exceptions.
+CI MUST pass before every merge. No exceptions.
+Red gates block all forward work. Invoke fix-or-log immediately.
+
+Preflight: `make check` (fmt → clippy `-D warnings` → test → ≤500 lines).
+CI: `gh pr checks` (when a PR is open).
+
+## Discovered Defects
+
+Found a bug unrelated to your task? Fix-or-log, then proceed.
+
+1. Run `quick-fix` for trivial data-only fixes.
+2. Run `fix-bug` for logic fixes needing investigation.
+3. Commit discovered fixes separately from feature work.
+4. Never silently fix a discovered defect inside a feature commit.
+
+## Banned Dismissive Phrases
+
+| Banned phrase | Action |
+|---------------|--------|
+| "pre-existing" | Shifts blame. Fix or log it. |
+| "unrelated to session" | Red gates are never unrelated. |
+| "not introduced by my changes" | Irrelevant if the gate is red. |
+| "out of scope" | Never ignore a red gate. |
+
+## Project Hard Rules
+
+- Files MUST stay ≤500 lines. `make check-lines` enforces this.
+- Extract submodules when a file approaches 500 lines.
+- NEVER add `#[allow(...)]` attributes. Refactor instead.
+- `#![deny(clippy::wildcard_imports)]` — no `use foo::*;`.
+- NEVER add `Co-authored-by` footers. Author solely as the user.
+- Use Conventional Commits: `feat(...)`, `fix(...)`, `docs(...)`.
+- Solve clippy warnings by refactoring. NEVER suppress with allow attributes.
+
+## Cluster Safety
+
+The cluster serves live traffic. A model runs at any time.
+Run `sm121 health` before `stop`, `power off`, or `power switch`.
+Confirm with the user that the cluster is idle before proceeding.
+Never assume the cluster is idle. Verify first.
+`logs`, `health`, `doctor`, and dry-runs (`-d`) are always safe.
+
+## Shell Quoting
+
+Use `shq()` for SSH command interpolation.
+Keep `shq()` output bare. NEVER wrap it in double quotes.
+Single quotes inside `"..."` become literal characters in the path.
+Use `printf` for path assembly inside SSH commands.
+See commit `0208fc1` for the bug this prevents.
+
+## Specs
+
+Write all planning output to `specs/`.
+Read `specs/` before writing code.
+`specs/tech-architecture/tech-stack.md` is the project long-term memory.
+`specs/state.yaml` tracks session state and active flow.
+
+## i18n
+
+`fluent-i18n` crate, 6 locales: en, ru, es, it, fr, de.
+Use `t!` macro with `{ "key" => value }` args.
+Pluralize via Fluent selectors: `{ $count -> [one] ... *[other] ... }`.
+`set_locale` is thread-local. Spawned tasks miss non-en locales.
+Pass counts as `u32` to match the `cluster-selected` pattern.

+ 3 - 0
specs/README.md

@@ -0,0 +1,3 @@
+# Specs
+
+All planning documents for this project.

+ 0 - 0
specs/bugs/registry.yaml


+ 0 - 0
specs/execution-status.yaml


+ 0 - 0
specs/planning-status.yaml


+ 0 - 0
specs/product/GLOSSARY_LATEST.yaml


+ 0 - 0
specs/product/SCOPE_LATEST.yaml


+ 0 - 0
specs/product/VISION_LATEST.yaml


+ 0 - 0
specs/release-plan.yaml


+ 31 - 0
specs/state.yaml

@@ -0,0 +1,31 @@
+# sm121-launcher — session state
+# Canonical integrate-mode signal: workflow_mode. Skills read this instead of
+# sniffing profiles. active_flow drives the lifecycle phase (see survey-context).
+# This file is the YAML cockpit entry point — update it as work progresses.
+
+workflow_mode: solo-git          # solo-git: land via scripts/land-branch.sh; team-pr: gh pr
+
+active_flow: null                # null = no active epic/bug flow (Sustain phase)
+active_epic_id: null
+active_story_id: null
+
+session:
+  goal: "bigpowers onboarding complete; ready for next task"
+  phase: Sustain                  # PMBOK phase: Discover|Design|Plan|Initiate|Execute|Verify|Bug|Review|Integrate|Sustain
+  started: 2026-07-27T11:18:35Z
+
+git:
+  branch: main
+  head: 283d2fa
+  clean: true                    # working tree clean before onboarding artifacts
+
+handoff:
+  next_skill: survey-context     # run survey-context to read this state + recommend next step
+  note: "Onboarding done. Next task triggers a flow (build_epic / fix_bug / plan-release)."
+
+metrics:
+  story_start: null              # ISO 8601 at story start — informational progress marker only
+
+# bigpowers helper scripts (callable via absolute path, not project-local):
+#   bash ~/.pi/agent/npm/node_modules/bigpowers/scripts/validate-specs-yaml.sh "$(pwd)/specs"
+#   bash ~/.pi/agent/npm/node_modules/bigpowers/scripts/validate-agentic-ste.sh --strict CLAUDE.md CONVENTIONS.md

+ 0 - 0
specs/tech-architecture/DESIGN_PLAN_LATEST.md


+ 0 - 0
specs/tech-architecture/IMPACT_LATEST.md


+ 0 - 0
specs/tech-architecture/REFACTOR_LATEST.md


+ 0 - 0
specs/tech-architecture/SECURITY_PLAN_LATEST.md


+ 0 - 0
specs/tech-architecture/TEST_PLAN_LATEST.md


+ 149 - 0
specs/tech-architecture/tech-stack.md

@@ -0,0 +1,149 @@
+# Project Context — sm121-launcher
+
+> Derived by `map-codebase` from a cold scan of the codebase. This is the
+> project's long-term memory: stack, architecture, conventions, and the
+> signals that influence planning. Refresh after significant changes.
+
+## Stack
+
+- **Language:** Rust, edition 2024 (strict typing, no `unsafe`)
+- **Binary:** `sm121` — CLI for launching vLLM/SGLang inference containers
+  across a DGX Spark cluster via SSH
+- **Core dependencies:**
+  - `clap` 4.6 (derive) — CLI argument parsing
+  - `tokio` 1 (full) — async runtime; all SSH/Docker ops are async
+  - `russh` 0.61 + `russh-keys` 0.50.0-beta.7 — SSH client (cluster ops)
+  - `anyhow` 1.0 — error handling (`?` + `.context()` + `bail!`)
+  - `serde` + `serde_json` + `toml` + `toml_edit` — config (TOML) + recipe files
+  - `tracing` + `tracing-subscriber` (env-filter) — structured logging
+  - `sha2` 0.11 — Docker image checksum labels
+  - `base64` 0.22 — file-content encoding for SSH upload
+  - `walkdir` 2 — recursive docker-dir checksumming
+  - `reqwest` 0.13 (rustls, no default-features) — Home Assistant REST API
+  - `fluent-i18n` 0.1.0 — localization (6 locales: en/ru/es/it/fr/de)
+  - `dirs` 6 — config-dir discovery
+
+## Architecture
+
+### Entry points
+- `src/main.rs` — `main()`: locale init, tracing init, dispatch on `Commands`
+- `src/cli.rs` — `Commands` enum (clap derive): `serve`, `stop`, `logs`,
+  `health`, `doctor`, `exec`, `power`, `tmux`, `init-host-keys`, `build`
+
+### Primary data flow (serve)
+```
+main.rs → cli dispatch → orchestrate/mod.rs::serve()
+  ├─ cluster_state.rs::select_cluster()   (auto: smallest with NCCL + required_nodes)
+  ├─ orchestrate/mod.rs                  (power-on via home_assistant.rs if --on)
+  ├─ before_serve.rs                     (cluster + recipe hooks, parallel SSH)
+  ├─ docker/build.rs::handle_build()      (ONLY if --build-node; checksum-gated)
+  ├─ docker/mod.rs::serve()
+  │    ├─ doctor.rs::try_repaired_image()
+  │    ├─ docker/stale.rs::warn_if_stale()  (checksum label vs dir, non-blocking)
+  │    ├─ docker/sync.rs                  (digest-based image sync to peers)
+  │    ├─ docker/command.rs               (build `docker run` cmd per node)
+  │    │    ├─ docker/compose.rs          (composed_model dir: symlinks + files)
+  │    │    ├─ docker/mods.rs             (entrypoint wrapping for [mods])
+  │    │    └─ resolve extra_mounts + path_refs
+  │    └─ ssh.rs::run_ssh() per node      (tokio::spawn, parallel)
+  └─ docker/logs.rs                      (follow logs from head node)
+```
+
+### Module layout
+- `config/` — `Config`, `Cluster`, `Node`, `Recipe`, `ComposedModel` types +
+  TOML loading (`config.toml` + `recipes/<name>/recipe.toml`)
+- `docker/` — container lifecycle: `build`, `sync`, `command`, `compose`,
+  `mods`, `health`, `logs`, `deploy`, `stale`, `tail`
+- `orchestrate/` — high-level serve/stop/power orchestration
+- `tmux/` — alternative launcher (tmux sessions instead of docker)
+- `ssh.rs` — russh wrapper: `run_ssh`, `run_ssh_with_retry`, `upload_script`,
+  `upload_dir`, `fetch_host_key`, `load_secret_key`
+- `power.rs`, `home_assistant.rs` — HA entity control (power on/off/switch)
+- `before_serve.rs`, `after_stop.rs` — hook execution (parallel across nodes)
+- `cluster_state.rs` — occupancy tracking + auto-cluster selection
+- `builtin_scripts.rs` — `include_str!` of `host-scripts/*.sh` (stop/restore
+  DGX services), referenced by alias from recipes
+- `host_keys.rs` — SSH host key pinning into `config.toml`
+- `doctor.rs` — `.repaired` image tag variant detection
+
+### Config discovery (main.rs:38-39)
+1. `~/.config/sm121/config.toml`
+2. `./config.toml` in cwd
+3. `<default_config_dir>/config.toml`
+
+### External artifacts (repo-rooted)
+- `recipes/<name>/recipe.toml` — 22 recipes
+- `docker-images/<dir>/` — 4 image build dirs (Dockerfile + build.sh +
+  versions.env)
+- `host-scripts/*.sh` — 2 built-in scripts (embedded at compile time)
+- `locales/{en,ru,es,it,fr,de}/main.ftl` — Fluent translations
+
+## Conventions (Observed)
+
+- **Error handling:** anyhow throughout. 47 `bail!` (validation), 21
+  `.context()` (error enrichment), 15 `anyhow!` (ad-hoc). Errors bubble with
+  context; no custom error types. SSH failures include command + output in
+  the error message.
+- **Observability:** `tracing` macros — 85 `info!`, 36 `warn!`, 1 `debug!`.
+  `tracing-subscriber` with env-filter (default `info`). No structured JSON
+  logging; human-readable with ANSI colors.
+- **Type safety:** strict Rust, no `unsafe`, no `#[allow(...)]` (clippy
+  warnings fixed by refactoring, never suppressed). `#![deny(clippy::wildcard_imports)]`.
+- **Testing:** 128 tests across 18 files. Inline `#[cfg(test)] mod tests` +
+  co-located `tests.rs` siblings (e.g. `config/tests.rs`, `docker/tests.rs`,
+  `orchestrate/tests.rs`). Async tests use `#[tokio::test]`.
+- **File-size discipline:** ≤500 lines per `.rs` file, enforced by
+  `make check-lines`. Submodules extracted when a file approaches the limit.
+- **Shell-quoting:** `shq()` wraps strings in single quotes for safe SSH
+  interpolation. A known footgun: `shq` output inside `"..."` makes the
+  single quotes literal (caused the composed-dir zero-symlinks bug, fixed
+  in `0208fc1`). Convention: keep `shq()` bare, quote only `$vars`.
+- **No TODO/FIXME markers** in the codebase (clean).
+- **Commit style:** Conventional Commits (`feat(...)`, `fix(...)`, no
+  `Co-authored-by` footers — P1 rule).
+
+## Signals / Active Considerations
+
+1. **Debt hotspots — 4 files at/near the 500-line limit:**
+   - `power.rs` (500), `docker/mod.rs` (499), `docker/command.rs` (499),
+     `docker/build.rs` (488)
+   - Any new feature touching these requires refactoring first (extract
+     submodules). This is the tightest constraint on where work can land.
+
+2. **i18n thread-local bug (systemic, pre-existing):** `fluent_i18n::set_locale`
+   is thread-local; `tokio::spawn` worker threads don't inherit the locale
+   set on main. 16 spawn sites affected. Non-en translations only render on
+   the main thread; en (fallback) renders everywhere. Fixing requires either
+   a process-global locale store or per-task `set_locale` calls.
+
+3. **i18n key drift:** en has 127 keys; other locales have 91 (36 keys
+   missing). Non-en translations are incomplete — newly added keys
+   (composed-dir-*, stale warnings) need translation.
+
+4. **Stop-flow cleanup gap:** `composed_model` dirs in `/tmp` aren't cleaned
+   if containers exited with `--rm` (stop_cluster only runs when running
+   containers exist). Harmless symlinks accumulate. Minor.
+
+5. **Docker image staleness:** `serve` without `--build-node` now WARNs on
+   checksum mismatch (`63b5518`), but proceeds. Stale images can still run
+   — the check is non-blocking by design (operator may want the current
+   image). Documented in AGENTS.md.
+
+6. **Cluster safety (operational, not code):** The cluster may be serving
+   live traffic at any time. `stop`/`power off`/`power switch` MUST be
+   preceded by `health` + user confirmation. A stray `stop` has killed
+   running agents mid-task before. Enforced by discipline, not code.
+
+7. **Two source-of-truth files (post-bigpowers adoption):** AGENTS.md
+   (cluster/recipe/DGX ops, the original SoT) now coexists with
+   CLAUDE.md + CONVENTIONS.md + specs/ (dev workflow, bigpowers). Split-
+   domains decision: AGENTS.md owns cluster/recipe technical reference;
+   CLAUDE.md/CONVENTIONS.md own dev workflow + hard gates. Both must stay
+   consistent on the gates that overlap (≤500 lines, make check, no
+   #[allow]).
+
+8. **Composed-model mechanism is new and feature-rich:** `[[composed_model]]`
+   with recursive symlink defaults, subdir globs, generated files, and
+   container-side absolute symlinks. Tested via `test-composed` recipe but
+   not yet exercised against a real multi-repo model build end-to-end on
+   the cluster (the NF4 Lloyd-Experts recipe awaits a build).