@bastani/atomic 0.5.0-4 → 0.5.0-5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,7 @@ Atomic is an open-source **multi-agent harness** that orchestrates **Claude Code
31
31
  - [Workflow SDK — Build Your Own Harness](#workflow-sdk--build-your-own-harness)
32
32
  - [Builder API](#builder-api)
33
33
  - [Session Context (`ctx`)](#session-context-ctx)
34
+ - [Session Options (`SessionRunOptions`)](#session-options-sessionrunoptions)
34
35
  - [Saving Transcripts](#saving-transcripts)
35
36
  - [Provider Helpers](#provider-helpers)
36
37
  - [Key Rules](#key-rules)
@@ -44,6 +45,8 @@ Atomic is an open-source **multi-agent harness** that orchestrates **Claude Code
44
45
  - [Why Research → Plan → Implement → Verify Works](#why-research--plan--implement--verify-works)
45
46
  - [Commands Reference](#commands-reference)
46
47
  - [CLI Commands](#cli-commands)
48
+ - [Global Flags](#global-flags)
49
+ - [`atomic init` Flags](#atomic-init-flags)
47
50
  - [`atomic chat` Flags](#atomic-chat-flags)
48
51
  - [`atomic workflow` Flags](#atomic-workflow-flags)
49
52
  - [Atomic-Provided Skills (invokable from any agent chat)](#atomic-provided-skills-invokable-from-any-agent-chat)
@@ -275,6 +278,8 @@ export default defineWorkflow({
275
278
  | **Native TypeScript control flow** | Use `for`, `if/else`, `Promise.all()`, `try/catch` — no framework DSL needed |
276
279
  | **Session return values** | Session callbacks can return data: `const h = await ctx.session(...); h.result` |
277
280
  | **Transcript passing** | Access prior session output via handle (`s.transcript(handle)`) or name (`s.transcript("name")`) |
281
+ | **Nested sub-sessions** | Call `s.session()` inside a session callback to spawn child sessions — visible as nested nodes in the graph |
282
+ | **Dependency tracking** | Use `dependsOn: ["name"]` to declare session ordering — the runtime waits and the graph shows the edges |
278
283
  | **Provider-agnostic** | Write raw SDK code for Claude, Copilot, or OpenCode inside each session callback |
279
284
  | **Live graph visualization** | Sessions appear in the TUI graph as they're spawned — loops and conditionals are visible in real time |
280
285
 
@@ -320,6 +325,24 @@ Use your workflow-creator skill to create a workflow that plans, implements, and
320
325
  | `s.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
321
326
  | `s.session(opts, fn)` | `Promise<SessionHandle<T>>` | Spawn a nested sub-session (child in the graph) |
322
327
 
328
+ #### Session Options (`SessionRunOptions`)
329
+
330
+ | Property | Type | Description |
331
+ | ------------- | ---------- | ----------------------------------------------------------------------------- |
332
+ | `name` | `string` | Unique session name within the workflow run |
333
+ | `description` | `string?` | Human-readable description shown in the graph |
334
+ | `dependsOn` | `string[]?`| Names of sessions that must complete before this one starts (creates graph edges) |
335
+
336
+ `dependsOn` is useful when spawning sessions with `Promise.all()` — it lets the runtime enforce ordering while still allowing parallel spawning of independent sessions:
337
+
338
+ ```ts
339
+ await Promise.all([
340
+ ctx.session({ name: "migrate-db" }, async (s) => { /* ... */ }),
341
+ ctx.session({ name: "seed-data", dependsOn: ["migrate-db"] }, async (s) => { /* ... */ }),
342
+ ctx.session({ name: "gen-types", dependsOn: ["migrate-db"] }, async (s) => { /* ... */ }),
343
+ ]);
344
+ ```
345
+
323
346
  #### Saving Transcripts
324
347
 
325
348
  Each provider saves transcripts differently:
@@ -334,13 +357,35 @@ Each provider saves transcripts differently:
334
357
 
335
358
  | Export | Purpose |
336
359
  | --------------------------------- | --------------------------------------------------- |
337
- | `createClaudeSession({ paneId })` | Start a Claude TUI in a tmux pane |
338
- | `claudeQuery({ paneId, prompt })` | Send a prompt to Claude and wait for the response |
339
- | `clearClaudeSession(paneId)` | Clear the current Claude session |
360
+ | `createClaudeSession(options)` | Start a Claude TUI in a tmux pane |
361
+ | `claudeQuery(options)` | Send a prompt to Claude and wait for the response |
362
+ | `clearClaudeSession(paneId)` | Free memory for a killed/finished Claude session |
340
363
  | `validateClaudeWorkflow()` | Validate a Claude workflow source before run |
341
364
  | `validateCopilotWorkflow()` | Validate a Copilot workflow source before run |
342
365
  | `validateOpenCodeWorkflow()` | Validate an OpenCode workflow source before run |
343
366
 
367
+ `createClaudeSession` accepts:
368
+
369
+ | Option | Type | Default | Description |
370
+ | ----------------- | ---------- | ----------------------------------------------------- | ---------------------------------- |
371
+ | `paneId` | `string` | — | tmux pane ID (required) |
372
+ | `chatFlags` | `string[]` | `["--dangerously-skip-permissions"]` | CLI flags passed to `claude` |
373
+ | `readyTimeoutMs` | `number` | `30000` | Timeout waiting for TUI readiness |
374
+
375
+ `claudeQuery` accepts:
376
+
377
+ | Option | Type | Default | Description |
378
+ | ----------------- | -------- | -------- | ------------------------------------------------ |
379
+ | `paneId` | `string` | — | tmux pane ID (required) |
380
+ | `prompt` | `string` | — | The prompt to send (required) |
381
+ | `timeoutMs` | `number` | `300000` | Response timeout (5 min) |
382
+ | `pollIntervalMs` | `number` | `2000` | Polling interval for output stabilization |
383
+ | `submitPresses` | `number` | `1` | C-m presses per submit round |
384
+ | `maxSubmitRounds` | `number` | `6` | Max retry rounds for delivery confirmation |
385
+ | `readyTimeoutMs` | `number` | `30000` | Pane readiness timeout before sending |
386
+
387
+ Returns `{ output: string; delivered: boolean }` — `delivered` confirms the prompt was accepted by the agent.
388
+
344
389
  #### Key Rules
345
390
 
346
391
  1. Every workflow file must use `export default` with `.run()` and `.compile()`
@@ -419,8 +464,10 @@ The [Ralph Wiggum Method](https://ghuntley.com/ralph/) enables **multi-hour auto
419
464
  **How Ralph works:**
420
465
 
421
466
  1. **Task Decomposition** — A `planner` sub-agent breaks your spec into a structured task list with dependency tracking, stored in a SQLite database with WAL mode for parallel access
422
- 2. **Worker Loop** — Dispatches `worker` sub-agents for ready tasks, executing up to 100 iterations with concurrent execution of independent tasks
423
- 3. **Review & Debug** — A `reviewer` sub-agent audits the implementation; if issues are found, a `debugger` sub-agent generates a report that feeds back to the planner on the next iteration
467
+ 2. **Orchestration** — An `orchestrator` sub-agent retrieves the task list, validates the dependency graph, and dispatches `worker` sub-agents for ready tasks with concurrent execution of independent tasks
468
+ 3. **Review & Debug** — A `reviewer` sub-agent audits the implementation with structured JSON output; if actionable findings exist (P0–P2 severity), a `debugger` sub-agent investigates root causes and produces a markdown report that feeds back to the planner on the next iteration
469
+
470
+ **Loop configuration:** Ralph runs up to **10 iterations** and exits early after **2 consecutive clean reviews** (zero actionable findings). P3 (minor) findings are filtered as non-actionable.
424
471
 
425
472
  ```bash
426
473
  # From a prompt
@@ -610,7 +657,7 @@ During `atomic workflow` execution, Atomic renders a live orchestrator panel bui
610
657
  - **Session graph** — Nodes for each `.session()` call with status (pending, running, completed, failed) and edges for sequential / parallel dependencies
611
658
  - **Task list tracking** — Ralph's decomposed task list with dependency arrows, updated in real time as workers complete tasks
612
659
  - **Pane previews** — Thumbnail of each tmux pane so you can see what every agent is doing without switching contexts
613
- - **Transcript passing visibility** — Highlights `ctx.save()` / `ctx.transcript()` handoffs as they happen between sessions
660
+ - **Transcript passing visibility** — Highlights `s.save()` / `s.transcript()` handoffs as they happen between sessions
614
661
 
615
662
  During `atomic chat`, there is no Atomic-owned TUI — `atomic chat -a <agent>` spawns the native agent CLI inside a tmux/psmux session, so all chat features (streaming, `@` mentions, `/slash-commands`, model selection, theme switching, keyboard shortcuts) come from the agent CLI itself. Atomic's role in chat mode is to handle config sync, tmux session management, and argument passthrough.
616
663
 
@@ -668,6 +715,29 @@ This is also why the cycle is iterative. Research and specs become persistent co
668
715
  | `atomic workflow` | Run a multi-session agent workflow with the Atomic orchestrator panel |
669
716
  | `atomic config set <k> <v>` | Set configuration values (currently supports `telemetry`) |
670
717
 
718
+ #### Global Flags
719
+
720
+ These flags are available on all commands:
721
+
722
+ | Flag | Description |
723
+ | --------------- | -------------------------------------------- |
724
+ | `-y, --yes` | Auto-confirm all prompts (non-interactive) |
725
+ | `--no-banner` | Skip ASCII banner display |
726
+ | `-v, --version` | Show version number |
727
+
728
+ #### `atomic init` Flags
729
+
730
+ | Flag | Description |
731
+ | -------------------- | ---------------------------------------------- |
732
+ | `-a, --agent <name>` | Pre-select agent: `claude`, `opencode`, `copilot` |
733
+ | `-s, --scm <name>` | Pre-select SCM: `github`, `sapling` |
734
+
735
+ ```bash
736
+ atomic init # Interactive setup
737
+ atomic init -a claude -s github # Pre-select agent and SCM
738
+ atomic init --yes # Auto-confirm all prompts
739
+ ```
740
+
671
741
  #### `atomic chat` Flags
672
742
 
673
743
  | Flag | Description |
@@ -760,7 +830,7 @@ Created automatically during `atomic init`. Resolution order:
760
830
  <summary>Install a specific version</summary>
761
831
 
762
832
  ```bash
763
- bun install -g @bastani/atomic@0.5.0-1 # replace with desired version
833
+ bun install -g @bastani/atomic@0.5.0-4 # replace with desired version
764
834
  ```
765
835
 
766
836
  List all published versions with `npm view @bastani/atomic versions`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.5.0-4",
3
+ "version": "0.5.0-5",
4
4
  "description": "Configuration management CLI and SDK for coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli.ts CHANGED
@@ -30,7 +30,6 @@ export function createProgram() {
30
30
  .version(VERSION, "-v, --version", "Show version number")
31
31
 
32
32
  // Global options available to all commands
33
- .option("-f, --force", "Overwrite all config files")
34
33
  .option("-y, --yes", "Auto-confirm all prompts (non-interactive mode)")
35
34
  .option("--no-banner", "Skip ASCII banner display")
36
35
 
@@ -68,7 +67,6 @@ export function createProgram() {
68
67
  showBanner: globalOpts.banner !== false,
69
68
  preSelectedAgent: localOpts.agent as AgentKey | undefined,
70
69
  preSelectedScm: localOpts.scm as SourceControlType | undefined,
71
- force: globalOpts.force,
72
70
  yes: globalOpts.yes,
73
71
  });
74
72
  });
@@ -167,8 +167,6 @@ interface InitOptions {
167
167
  /** Pre-selected source control type (skip SCM selection prompt) */
168
168
  preSelectedScm?: SourceControlType;
169
169
  configNotFoundMessage?: string;
170
- /** Force overwrite of preserved files (bypass preservation/merge logic) */
171
- force?: boolean;
172
170
  /** Auto-confirm all prompts (non-interactive mode for CI/testing) */
173
171
  yes?: boolean;
174
172
  /**
@@ -330,10 +328,7 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
330
328
  const targetFolder = join(targetDir, agent.folder);
331
329
  const folderExists = await pathExists(targetFolder);
332
330
 
333
- // --force bypasses update confirmation prompts.
334
- const shouldForce = options.force ?? false;
335
-
336
- if (folderExists && !shouldForce && !autoConfirm) {
331
+ if (folderExists && !autoConfirm) {
337
332
  const update = await confirm({
338
333
  message: `${agent.folder} already exists. Update source control skills?`,
339
334
  initialValue: true,
@@ -1,6 +1,6 @@
1
1
  // ─── React Contexts & Hooks ───────────────────────
2
2
 
3
- import { createContext, useContext, useState, useEffect } from "react";
3
+ import { createContext, useContext, useSyncExternalStore } from "react";
4
4
  import type { PanelStore } from "./orchestrator-panel-store.ts";
5
5
  import type { GraphTheme } from "./graph-theme.ts";
6
6
 
@@ -20,7 +20,16 @@ export function useGraphTheme(): GraphTheme {
20
20
  return ctx;
21
21
  }
22
22
 
23
- export function useStoreSubscription(store: PanelStore): void {
24
- const [, forceRender] = useState(0);
25
- useEffect(() => store.subscribe(() => forceRender((c) => c + 1)), [store]);
23
+ /**
24
+ * Subscribe to the store and return its current version.
25
+ *
26
+ * Uses `useSyncExternalStore` so the subscription is active from the
27
+ * very first render — no `useEffect` timing gap that could cause a
28
+ * missed `addSession` update.
29
+ */
30
+ export function useStoreVersion(store: PanelStore): number {
31
+ return useSyncExternalStore(
32
+ store.subscribe,
33
+ () => store.version,
34
+ );
26
35
  }
@@ -22,7 +22,7 @@ import { tmuxRun } from "../runtime/tmux.ts";
22
22
  import {
23
23
  useStore,
24
24
  useGraphTheme,
25
- useStoreSubscription,
25
+ useStoreVersion,
26
26
  TmuxSessionContext,
27
27
  } from "./orchestrator-panel-contexts.ts";
28
28
  import { computeLayout } from "./layout.ts";
@@ -51,10 +51,10 @@ export function SessionGraphPanel() {
51
51
  useRenderer();
52
52
  const { width: termW, height: termH } = useTerminalDimensions();
53
53
 
54
- useStoreSubscription(store);
54
+ const storeVersion = useStoreVersion(store);
55
55
 
56
56
  // Compute layout from current session data
57
- const layout = useMemo(() => computeLayout(store.sessions), [store.version]);
57
+ const layout = useMemo(() => computeLayout(store.sessions), [storeVersion]);
58
58
  const nodeList = useMemo(() => Object.values(layout.map), [layout]);
59
59
 
60
60
  const connectors = useMemo(() => {
@@ -82,7 +82,7 @@ export function SessionGraphPanel() {
82
82
  if (store.sessions.length > 0 && !layout.map[focusedId]) {
83
83
  setFocusedId(store.sessions[0]!.name);
84
84
  }
85
- }, [store.version]);
85
+ }, [storeVersion]);
86
86
 
87
87
  // Pulse animation for running nodes — paused when nothing is running
88
88
  const hasRunning = store.sessions.some((s) => s.status === "running");
@@ -99,11 +99,10 @@ export function SessionGraphPanel() {
99
99
  // Live timer refresh — re-render every second while any session is running
100
100
  const [, setTick] = useState(0);
101
101
  useEffect(() => {
102
- const hasRunning = store.sessions.some((s) => s.status === "running");
103
102
  if (!hasRunning) return;
104
103
  const id = setInterval(() => setTick((t) => t + 1), 1000);
105
104
  return () => clearInterval(id);
106
- }, [store.version]);
105
+ }, [hasRunning]);
107
106
 
108
107
  // Attach flash message
109
108
  const [attachMsg, setAttachMsg] = useState("");