huskies: merge 1113 story [huskies-server repo] Convert static website to Next.js with static rendering
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
/** CLI reference — flags and subcommands for the huskies binary. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
/** Page metadata for the CLI reference. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'CLI Reference — Huskies Docs',
|
||||
description: 'Command-line reference for huskies, huskies init, and related subcommands.',
|
||||
}
|
||||
|
||||
/** Renders the CLI reference for huskies command-line flags. */
|
||||
export default function CliPage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="page-title">CLI Reference</h1>
|
||||
<p className="page-subtitle">
|
||||
Huskies ships as a single binary. Most interaction happens through the web UI or chat transports,
|
||||
but the CLI is used for initial setup and server control.
|
||||
</p>
|
||||
|
||||
<h2>huskies</h2>
|
||||
<p>Start the huskies server.</p>
|
||||
<pre>
|
||||
<code>huskies [OPTIONS]</code>
|
||||
</pre>
|
||||
|
||||
<h3>Options</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Flag</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>--port <PORT></td>
|
||||
<td>3000</td>
|
||||
<td>
|
||||
HTTP port to listen on. Set the <code>HUSKIES_PORT</code> environment variable as an
|
||||
alternative.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--project <PATH></td>
|
||||
<td>current dir</td>
|
||||
<td>
|
||||
Path to the project directory. Huskies looks for <code>.huskies/</code> here.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--help</td>
|
||||
<td>—</td>
|
||||
<td>Print help and exit.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--version</td>
|
||||
<td>—</td>
|
||||
<td>Print version and exit.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Examples</h3>
|
||||
<pre>
|
||||
<code>{`# Start on the default port
|
||||
huskies
|
||||
|
||||
# Start on a custom port
|
||||
huskies --port 3001
|
||||
|
||||
# Specify project directory explicitly
|
||||
huskies --project /path/to/project --port 3000
|
||||
|
||||
# Using environment variable
|
||||
HUSKIES_PORT=3002 huskies`}</code>
|
||||
</pre>
|
||||
|
||||
<div className="note">
|
||||
<strong>Multiple instances:</strong> Each worktree or project can run its own huskies instance on a
|
||||
different port. Use <code>HUSKIES_PORT</code> to avoid conflicts when running several instances
|
||||
simultaneously.
|
||||
</div>
|
||||
|
||||
<h2>huskies init</h2>
|
||||
<p>
|
||||
Initialise a project directory for use with huskies. Creates the <code>.huskies/</code> directory
|
||||
structure, default configuration files, and <code>.mcp.json</code>.
|
||||
</p>
|
||||
<pre>
|
||||
<code>huskies init [OPTIONS]</code>
|
||||
</pre>
|
||||
|
||||
<h3>Options</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Flag</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>--port <PORT></td>
|
||||
<td>3000</td>
|
||||
<td>
|
||||
Port written into <code>.mcp.json</code> for MCP tool discovery.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--project <PATH></td>
|
||||
<td>current dir</td>
|
||||
<td>Directory to initialise. Must be a git repository.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--help</td>
|
||||
<td>—</td>
|
||||
<td>Print help and exit.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>What it creates</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Path</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>.huskies/project.toml</code>
|
||||
</td>
|
||||
<td>Project-wide settings (QA mode, agent limits, timezone, etc.).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>.huskies/agents.toml</code>
|
||||
</td>
|
||||
<td>Agent definitions for coder, QA, and mergemaster roles.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>.huskies/work/1_backlog/</code>
|
||||
</td>
|
||||
<td>Pipeline stage directories (1 through 6).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>.huskies/specs/00_CONTEXT.md</code>
|
||||
</td>
|
||||
<td>Placeholder project context file for the setup wizard.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>.huskies/specs/tech/STACK.md</code>
|
||||
</td>
|
||||
<td>Placeholder tech stack file for the setup wizard.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>.mcp.json</code>
|
||||
</td>
|
||||
<td>
|
||||
MCP server config so Claude Code discovers huskies' tools automatically.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="note">
|
||||
<strong>Git required:</strong> The project directory must be a git repository. Run{' '}
|
||||
<code>git init</code> first if needed.
|
||||
</div>
|
||||
|
||||
<h2>huskies agent</h2>
|
||||
<p>
|
||||
Spawn a single agent process directly from the command line. This is the command the server uses
|
||||
internally when you run <code>start <number></code> in chat — you rarely need to invoke
|
||||
it manually.
|
||||
</p>
|
||||
<pre>
|
||||
<code>huskies agent [OPTIONS]</code>
|
||||
</pre>
|
||||
|
||||
<h3>Options</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Flag</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>--story <ID></td>
|
||||
<td>
|
||||
Story ID slug to work on (e.g. <code>42_story_add_login</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--agent <NAME></td>
|
||||
<td>
|
||||
Agent name from <code>agents.toml</code> to use (e.g. <code>coder-1</code>,{' '}
|
||||
<code>qa</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--worktree <PATH></td>
|
||||
<td>Path to the git worktree the agent should work in.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--port <PORT></td>
|
||||
<td>Huskies server port, so the agent can call MCP tools.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>--help</td>
|
||||
<td>Print help and exit.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Environment variables</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>HUSKIES_PORT</code>
|
||||
</td>
|
||||
<td>
|
||||
Server port. Overrides the <code>--port</code> flag.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ANTHROPIC_API_KEY</code>
|
||||
</td>
|
||||
<td>
|
||||
Anthropic API key for agent sessions. Can also be set via the web UI on first use.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>GITEA_TOKEN</code>
|
||||
</td>
|
||||
<td>
|
||||
Gitea API token used by the <code>script/release</code> script when publishing releases.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Gateway event-push protocol</h2>
|
||||
<p>
|
||||
Project nodes can push pipeline status events to the gateway in real time over a WebSocket
|
||||
connection. The gateway fans each event out to all connected local subscribers.
|
||||
</p>
|
||||
|
||||
<h3>Connecting</h3>
|
||||
<ol>
|
||||
<li>
|
||||
Obtain a one-time join token: <code>POST /gateway/tokens</code> →{' '}
|
||||
<code>{'{"token":"…"}'}</code>
|
||||
</li>
|
||||
<li>
|
||||
Open a WebSocket upgrade to{' '}
|
||||
<code>GET /gateway/events/push?token=TOKEN&project=PROJECT_NAME</code>
|
||||
</li>
|
||||
<li>
|
||||
The token is consumed on upgrade. The project name is attached to every event the server
|
||||
broadcasts downstream.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>Sending events</h3>
|
||||
<p>
|
||||
Each message must be a JSON-encoded <code>StoredEvent</code> frame:
|
||||
</p>
|
||||
<pre>
|
||||
<code>{`// Stage transition
|
||||
{"type":"stage_transition","story_id":"42_story_login","from_stage":"2_current","to_stage":"3_qa","timestamp_ms":1700000000000}
|
||||
|
||||
// Merge failure
|
||||
{"type":"merge_failure","story_id":"42_story_login","reason":"conflict in src/main.rs","timestamp_ms":1700000001000}
|
||||
|
||||
// Story blocked
|
||||
{"type":"story_blocked","story_id":"42_story_login","reason":"retry limit exceeded","timestamp_ms":1700000002000}`}</code>
|
||||
</pre>
|
||||
<p>
|
||||
The server does not send frames back. Any other frames received by the project node indicate an
|
||||
error or server restart — treat them as a disconnect signal.
|
||||
</p>
|
||||
|
||||
<h3>Reconnect with exponential back-off</h3>
|
||||
<p>
|
||||
Project nodes <strong>must</strong> reconnect on any disconnect. Use the following policy to avoid
|
||||
thundering herds after a gateway restart:
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Initial delay</td>
|
||||
<td>1 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Back-off multiplier</td>
|
||||
<td>2× per attempt</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum delay</td>
|
||||
<td>60 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jitter</td>
|
||||
<td>±10 % of the computed delay</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Pseudocode:</p>
|
||||
<pre>
|
||||
<code>{`delay = 1.0 // seconds
|
||||
max_delay = 60.0
|
||||
|
||||
loop:
|
||||
token = POST /gateway/tokens
|
||||
connect ws:/gateway/events/push?token=TOKEN&project=NAME
|
||||
while connected:
|
||||
send StoredEvent frames
|
||||
// disconnected — wait and retry
|
||||
jitter = delay * (random(0.9, 1.1))
|
||||
sleep(min(jitter, max_delay))
|
||||
delay = min(delay * 2, max_delay)`}</code>
|
||||
</pre>
|
||||
|
||||
<div className="note">
|
||||
<strong>New token per connection:</strong> Each WebSocket upgrade consumes the join token. Request a
|
||||
fresh token for every reconnect attempt.
|
||||
</div>
|
||||
|
||||
<h2>Building from source</h2>
|
||||
<h3>Standard release build</h3>
|
||||
<pre>
|
||||
<code>{`cargo build --release\n# Output: target/release/huskies`}</code>
|
||||
</pre>
|
||||
|
||||
<h3>Static Linux binary (musl)</h3>
|
||||
<p>
|
||||
Requires <code>cross</code>: <code>cargo install cross</code>.
|
||||
</p>
|
||||
<pre>
|
||||
<code>cross build --release --target x86_64-unknown-linux-musl</code>
|
||||
</pre>
|
||||
|
||||
<h3>Docker image</h3>
|
||||
<pre>
|
||||
<code>docker compose -f docker/docker-compose.yml build</code>
|
||||
</pre>
|
||||
|
||||
<h3>Release script</h3>
|
||||
<p>
|
||||
Builds macOS arm64 and Linux amd64 binaries, bumps the version, tags the repo, and publishes a
|
||||
Gitea release with changelog and binaries attached.
|
||||
</p>
|
||||
<pre>
|
||||
<code>script/release 0.8.0</code>
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/** Bot commands reference — full list of chat transport commands. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
/** Page metadata for the bot commands reference. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'Bot Commands — Huskies Docs',
|
||||
description:
|
||||
'Full reference of huskies bot commands available in Matrix, Slack, WhatsApp, Discord, and the web UI.',
|
||||
}
|
||||
|
||||
/** Renders the full bot commands reference page. */
|
||||
export default function CommandsPage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="page-title">Bot Commands</h1>
|
||||
<p className="page-subtitle">
|
||||
Commands available in every chat transport (Matrix, Slack, WhatsApp, Discord) and the built-in web
|
||||
UI. Commands are case-insensitive. Run <code>help</code> in any chat to see the list.
|
||||
</p>
|
||||
|
||||
<div className="note">
|
||||
<strong>How to invoke:</strong> In chat rooms, address the bot first (e.g.{' '}
|
||||
<code>@huskies start 42</code>) or enable ambient mode so it responds to all messages. In the web
|
||||
UI, type commands directly.
|
||||
</div>
|
||||
|
||||
<h2>Pipeline management</h2>
|
||||
<div className="cmd-grid">
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">status</div>
|
||||
<div className="cmd-desc">
|
||||
Show pipeline status and agent availability. Use <code>status <number></code> for a
|
||||
detailed triage dump on a specific story.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">start</div>
|
||||
<div className="cmd-desc">
|
||||
Start an agent on a story: <code>start <number></code>. To use the opus model:{' '}
|
||||
<code>start <number> opus</code>.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">move</div>
|
||||
<div className="cmd-desc">
|
||||
Move a work item to a pipeline stage: <code>move <number> <stage></code>. Stages:{' '}
|
||||
<code>backlog</code>, <code>current</code>, <code>qa</code>, <code>merge</code>,{' '}
|
||||
<code>done</code>.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">show</div>
|
||||
<div className="cmd-desc">
|
||||
Display the full text of a work item: <code>show <number></code>.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">delete</div>
|
||||
<div className="cmd-desc">
|
||||
Remove a work item from the pipeline: <code>delete <number></code>.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">unblock</div>
|
||||
<div className="cmd-desc">
|
||||
Reset a blocked story: <code>unblock <number></code>. Clears the blocked flag and resets
|
||||
the retry count.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">assign</div>
|
||||
<div className="cmd-desc">
|
||||
Pre-assign a model to a story before starting: <code>assign <number> <model></code>{' '}
|
||||
(e.g. <code>assign 42 opus</code>).
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">backlog</div>
|
||||
<div className="cmd-desc">
|
||||
Show all items in the backlog with dependency satisfaction status — which are ready to
|
||||
start and which are still waiting on other stories.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">depends</div>
|
||||
<div className="cmd-desc">
|
||||
Set story dependencies: <code>depends <number> [dep1 dep2 ...]</code>. Call with no deps
|
||||
to clear all dependencies.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">timer</div>
|
||||
<div className="cmd-desc">
|
||||
Schedule a deferred agent start: <code>timer <number> HH:MM</code>. List all timers:{' '}
|
||||
<code>timer list</code>. Cancel: <code>timer cancel <number></code>. Times are interpreted
|
||||
in the project timezone.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Worktrees</h2>
|
||||
<div className="cmd-grid">
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">rmtree</div>
|
||||
<div className="cmd-desc">
|
||||
Delete the worktree for a story without removing the story from the pipeline:{' '}
|
||||
<code>rmtree <number></code>. Useful for freeing disk space on a story that needs to be
|
||||
restarted.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Observability</h2>
|
||||
<div className="cmd-grid">
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">cost</div>
|
||||
<div className="cmd-desc">
|
||||
Show token spend: 24h total, top stories, breakdown by agent type, and all-time total.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">coverage</div>
|
||||
<div className="cmd-desc">
|
||||
Show test coverage from the cached baseline. Use <code>coverage run</code> to rerun the full
|
||||
test suite and regenerate the report.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">git</div>
|
||||
<div className="cmd-desc">
|
||||
Show git status for the main repository: current branch, uncommitted changes, and ahead/behind
|
||||
remote.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">htop</div>
|
||||
<div className="cmd-desc">
|
||||
Live system and agent process dashboard. Use <code>htop</code> to start,{' '}
|
||||
<code>htop 10m</code> to run for 10 minutes, <code>htop stop</code> to stop.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">run_tests</div>
|
||||
<div className="cmd-desc">
|
||||
Run the project's test suite (<code>script/test</code>) and show a pass/fail summary with
|
||||
output. Use <code>run_tests <number></code> to run tests inside a specific story's
|
||||
worktree instead of the project root.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">loc</div>
|
||||
<div className="cmd-desc">
|
||||
Show top source files by line count: <code>loc</code> (top 10), <code>loc <N></code> for
|
||||
N files, or <code>loc <filepath></code> for a specific file.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">overview</div>
|
||||
<div className="cmd-desc">
|
||||
Show an implementation summary for a merged story: <code>overview <number></code>.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">unreleased</div>
|
||||
<div className="cmd-desc">
|
||||
Show stories merged to master since the last release tag.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Server management</h2>
|
||||
<div className="cmd-grid">
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">rebuild</div>
|
||||
<div className="cmd-desc">Rebuild the huskies server binary and restart the process.</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">reset</div>
|
||||
<div className="cmd-desc">
|
||||
Clear the current Claude Code session and start a fresh context window.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Setup & configuration</h2>
|
||||
<div className="cmd-grid">
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">setup</div>
|
||||
<div className="cmd-desc">
|
||||
Show setup wizard progress. Drive the wizard from chat: <code>setup generate</code>,{' '}
|
||||
<code>setup confirm</code>, <code>setup skip</code>, <code>setup retry</code>.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">ambient</div>
|
||||
<div className="cmd-desc">
|
||||
Toggle ambient mode for the current room: <code>ambient on</code> or{' '}
|
||||
<code>ambient off</code>. In ambient mode the bot responds to all messages, not just addressed
|
||||
ones.
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="cmd-name">help</div>
|
||||
<div className="cmd-desc">Show the list of available commands.</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
/** Configuration reference — project.toml, agents.toml, and bot.toml docs. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
/** Page metadata for the configuration reference. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'Configuration — Huskies Docs',
|
||||
description: 'Reference for project.toml, agents.toml, and bot.toml configuration files.',
|
||||
}
|
||||
|
||||
/** Renders the configuration reference for all huskies TOML files. */
|
||||
export default function ConfigurationPage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="page-title">Configuration</h1>
|
||||
<p className="page-subtitle">
|
||||
Huskies is configured via three TOML files in your <code>.huskies/</code> directory. All files are
|
||||
created by <code>huskies init</code> with sensible defaults.
|
||||
</p>
|
||||
|
||||
<h2 id="project-toml">project.toml</h2>
|
||||
<p>
|
||||
Project-wide settings. Lives at <code>.huskies/project.toml</code>.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Type</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>default_qa</td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
<code>"server"</code>
|
||||
</td>
|
||||
<td>
|
||||
Default QA mode. One of <code>"server"</code> (automated gate run),{' '}
|
||||
<code>"agent"</code> (spawn a QA agent), or <code>"human"</code> (manual
|
||||
approval).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default_coder_model</td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
<code>"sonnet"</code>
|
||||
</td>
|
||||
<td>
|
||||
Default model for coder agents. Only agents matching this model are auto-assigned. Use{' '}
|
||||
<code>"opus"</code> on individual stories for complex tasks.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>max_coders</td>
|
||||
<td>integer</td>
|
||||
<td>
|
||||
<code>3</code>
|
||||
</td>
|
||||
<td>
|
||||
Maximum concurrent coder agents. Stories wait in <code>2_current/</code> when all slots are
|
||||
full.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>max_retries</td>
|
||||
<td>integer</td>
|
||||
<td>
|
||||
<code>3</code>
|
||||
</td>
|
||||
<td>
|
||||
Maximum retries per story per pipeline stage before marking it as blocked. Set to{' '}
|
||||
<code>0</code> to disable.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>base_branch</td>
|
||||
<td>string</td>
|
||||
<td>auto-detected</td>
|
||||
<td>
|
||||
Base branch for merges and agent prompts. When unset, huskies reads the current HEAD branch.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>rate_limit_notifications</td>
|
||||
<td>bool</td>
|
||||
<td>
|
||||
<code>false</code>
|
||||
</td>
|
||||
<td>
|
||||
Send chat notifications when API soft rate limits are hit. Hard blocks and story-blocked
|
||||
notifications are always sent.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>timezone</td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
<code>"UTC"</code>
|
||||
</td>
|
||||
<td>
|
||||
IANA timezone for timer scheduling (e.g. <code>"Europe/London"</code>,{' '}
|
||||
<code>"America/New_York"</code>). Timer HH:MM inputs are interpreted in this
|
||||
timezone.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Component setup</h3>
|
||||
<p>
|
||||
The <code>[[component]]</code> sections define how to build and verify each part of your project.
|
||||
The server runs setup commands before accepting a story's QA and teardown commands after
|
||||
merging.
|
||||
</p>
|
||||
<pre>
|
||||
<code>{`[[component]]
|
||||
name = "frontend"
|
||||
path = "frontend"
|
||||
setup = ["npm ci", "npm run build"]
|
||||
teardown = []
|
||||
|
||||
[[component]]
|
||||
name = "server"
|
||||
path = "."
|
||||
setup = ["mkdir -p frontend/dist", "cargo check"]
|
||||
teardown = []`}</code>
|
||||
</pre>
|
||||
|
||||
<h3>Story front matter overrides</h3>
|
||||
<p>Individual stories can override project defaults using YAML front matter:</p>
|
||||
<pre>
|
||||
<code>{`---
|
||||
name: "My Complex Story"
|
||||
qa: agent # override default_qa
|
||||
agent: opus # use the opus coder agent
|
||||
---`}</code>
|
||||
</pre>
|
||||
|
||||
<h2 id="agents-toml">agents.toml</h2>
|
||||
<p>
|
||||
Agent definitions. Lives at <code>.huskies/agents.toml</code>. Each <code>[[agent]]</code> block
|
||||
defines one agent slot.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>
|
||||
Unique identifier for this agent slot (e.g. <code>"coder-1"</code>,{' '}
|
||||
<code>"qa"</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>stage</td>
|
||||
<td>
|
||||
Pipeline stage this agent handles. One of <code>"coder"</code>,{' '}
|
||||
<code>"qa"</code>, or <code>"mergemaster"</code>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>role</td>
|
||||
<td>
|
||||
Human-readable description of the agent's responsibilities (shown in status output).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>model</td>
|
||||
<td>
|
||||
Claude model to use. One of <code>"sonnet"</code> (claude-sonnet-4-6) or{' '}
|
||||
<code>"opus"</code> (claude-opus-4-6).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>max_turns</td>
|
||||
<td>Maximum conversation turns before the agent is forcefully stopped.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>max_budget_usd</td>
|
||||
<td>Maximum API spend in USD before the agent is stopped.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>prompt</td>
|
||||
<td>
|
||||
The initial user-turn prompt sent to the agent. Supports template variables (see below).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>system_prompt</td>
|
||||
<td>The system prompt sent to the agent session.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Template variables</h3>
|
||||
<p>
|
||||
The following variables are interpolated into <code>prompt</code> and <code>system_prompt</code> at
|
||||
agent start time:
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{'{{story_id}}'}</td>
|
||||
<td>
|
||||
The story's ID slug (e.g. <code>42_story_add_login</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{'{{worktree_path}}'}</td>
|
||||
<td>Absolute path to the agent's git worktree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{'{{base_branch}}'}</td>
|
||||
<td>
|
||||
The base branch name from <code>project.toml</code>.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Example: adding an opus coder</h3>
|
||||
<pre>
|
||||
<code>{`[[agent]]
|
||||
name = "coder-opus"
|
||||
stage = "coder"
|
||||
role = "Senior engineer for complex tasks."
|
||||
model = "opus"
|
||||
max_turns = 80
|
||||
max_budget_usd = 20.00
|
||||
prompt = "You are working on story {{story_id}} ..."
|
||||
system_prompt = "You are a senior full-stack engineer ..."`}</code>
|
||||
</pre>
|
||||
<p>
|
||||
To use this agent for a specific story, add <code>agent: opus</code> to the story's front
|
||||
matter, or run <code>start <number> opus</code> in chat.
|
||||
</p>
|
||||
|
||||
<h2 id="agent-md">
|
||||
Project-local agent prompt (<code>.huskies/AGENT.md</code>)
|
||||
</h2>
|
||||
<p>
|
||||
Place a file at <code>.huskies/AGENT.md</code> in your project root to append project-specific
|
||||
guidance to every agent's initial prompt at spawn time.
|
||||
</p>
|
||||
|
||||
<h3>How it works</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Huskies reads <code>.huskies/AGENT.md</code> each time an agent is spawned — no caching, no
|
||||
restart required.
|
||||
</li>
|
||||
<li>
|
||||
The file content is appended <em>after</em> the baked-in agent prompt, so project guidance
|
||||
refines core instructions without overriding them.
|
||||
</li>
|
||||
<li>Applies to all agent roles: coder, QA, mergemaster, and supervisor.</li>
|
||||
<li>
|
||||
If the file is missing or empty, agents spawn normally — no warnings, no errors.
|
||||
</li>
|
||||
<li>
|
||||
When the file exists and is non-empty, a single <code>INFO</code> log line is emitted showing the
|
||||
file path and byte count.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Ordering</h3>
|
||||
<ol>
|
||||
<li>
|
||||
Baked-in agent prompt (from <code>agents.toml</code> or <code>project.toml</code>)
|
||||
</li>
|
||||
<li>
|
||||
Project-local content from <code>.huskies/AGENT.md</code>
|
||||
</li>
|
||||
<li>Resume context (only on agent restart after a gate failure)</li>
|
||||
</ol>
|
||||
|
||||
<h3>Example</h3>
|
||||
<pre>
|
||||
<code>{`# .huskies/AGENT.md
|
||||
|
||||
## Documentation
|
||||
Docs live in \`website/docs/*.html\`, not Markdown files.
|
||||
Edit the relevant .html file when a story asks for documentation.
|
||||
|
||||
## Quality gates
|
||||
Run \`cargo clippy -- -D warnings\` before committing. Zero warnings allowed.`}</code>
|
||||
</pre>
|
||||
<p>
|
||||
Edit the file at any time — the next agent spawn picks up the latest content automatically.
|
||||
</p>
|
||||
|
||||
<h2 id="bot-toml">bot.toml</h2>
|
||||
<p>
|
||||
Chat transport configuration. Lives at <code>.huskies/bot.toml</code>. This file is gitignored as
|
||||
it contains credentials. Copy the appropriate example file to get started:
|
||||
</p>
|
||||
<pre>
|
||||
<code>cp .huskies/bot.toml.matrix.example .huskies/bot.toml</code>
|
||||
</pre>
|
||||
<p>
|
||||
Only one transport can be active at a time. See the{' '}
|
||||
<a href="/docs/transports">Chat transports</a> guide for setup instructions for each platform.
|
||||
</p>
|
||||
|
||||
<h3>Common fields</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>enabled</td>
|
||||
<td>
|
||||
Set to <code>true</code> to activate the bot. Set to <code>false</code> to disable without
|
||||
removing the file.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>transport</td>
|
||||
<td>
|
||||
Transport type: <code>"matrix"</code>, <code>"whatsapp"</code>,{' '}
|
||||
<code>"slack"</code>, or <code>"discord"</code>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>display_name</td>
|
||||
<td>Optional. Bot display name in chat messages.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>history_size</td>
|
||||
<td>
|
||||
Optional. Maximum conversation turns to remember per room/user (default: 20).
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="gateway-aggregated-stream">Gateway: aggregated chat stream</h2>
|
||||
<p>
|
||||
When running <code>huskies --gateway</code>, you can configure a single bot that receives pipeline
|
||||
notifications from <strong>all</strong> registered projects. Events are prefixed with{' '}
|
||||
<code>[project-name]</code> so you can tell them apart in one shared room.
|
||||
</p>
|
||||
<p>
|
||||
The aggregated stream is configured entirely in the <strong>gateway's</strong>{' '}
|
||||
<code>.huskies/bot.toml</code> — no per-project bot config is required and no per-project
|
||||
files need to change when you add a new project to <code>projects.toml</code>.
|
||||
</p>
|
||||
|
||||
<h3>Enabling the aggregated stream</h3>
|
||||
<p>
|
||||
Add or edit <code><gateway-config-dir>/.huskies/bot.toml</code> and set{' '}
|
||||
<code>enabled = true</code>. The gateway bot will automatically poll every project listed in{' '}
|
||||
<code>projects.toml</code> and forward events to the configured rooms.
|
||||
</p>
|
||||
<pre>
|
||||
<code>{`# <gateway-config-dir>/.huskies/bot.toml
|
||||
enabled = true
|
||||
transport = "matrix"
|
||||
homeserver = "https://matrix.example.com"
|
||||
username = "@gateway-bot:example.com"
|
||||
password = "secret"
|
||||
room_ids = ["!gateway-room:example.com"]
|
||||
allowed_users = ["@you:example.com"]
|
||||
|
||||
# Gateway-specific: poll interval and on/off switch
|
||||
aggregated_notifications_poll_interval_secs = 5 # default
|
||||
aggregated_notifications_enabled = true # default`}</code>
|
||||
</pre>
|
||||
|
||||
<h3>Aggregated stream settings</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Type</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>aggregated_notifications_enabled</td>
|
||||
<td>bool</td>
|
||||
<td>
|
||||
<code>true</code>
|
||||
</td>
|
||||
<td>
|
||||
Set to <code>false</code> to disable the aggregated stream without disabling the gateway bot
|
||||
entirely. Per-project configs are never consulted.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>aggregated_notifications_poll_interval_secs</td>
|
||||
<td>integer</td>
|
||||
<td>
|
||||
<code>5</code>
|
||||
</td>
|
||||
<td>
|
||||
How often (in seconds) the gateway polls each project's <code>/api/events</code>{' '}
|
||||
endpoint. Lower values reduce notification latency.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>No-duplicate guarantee</h3>
|
||||
<p>
|
||||
Per-project bots and the gateway aggregated stream send to different rooms — they are
|
||||
independent. Events from a per-project bot go to that project's rooms; events from the gateway
|
||||
stream go to the gateway rooms. The same event will never appear twice in either room.
|
||||
</p>
|
||||
|
||||
<h3>Unreachable projects</h3>
|
||||
<p>
|
||||
If a per-project server is temporarily unreachable, the gateway logs a warning and skips that
|
||||
project for the current poll cycle. All other projects continue to deliver notifications normally.
|
||||
No configuration change is required — the poller retries on the next interval.
|
||||
</p>
|
||||
|
||||
<h3>Supported event types</h3>
|
||||
<p>
|
||||
The aggregated stream delivers the following event types, each prefixed with the project name:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Stage transitions</strong> — story created, agent started, QA requested, QA
|
||||
approved/rejected, merge succeeded (all pipeline stage moves)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Merge failures</strong> — merge failed with a reason
|
||||
</li>
|
||||
<li>
|
||||
<strong>Story blocked</strong> — story blocked after exceeding retry limit
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
/* Outer shell */
|
||||
.shell {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.shell { padding: 0 1.25rem; }
|
||||
}
|
||||
|
||||
/* Header (docs) */
|
||||
.shell header {
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.shell header nav a.active { color: var(--cyan); }
|
||||
|
||||
/* Docs layout */
|
||||
.docs-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
gap: 0;
|
||||
min-height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 2.5rem 0 2.5rem 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-family: var(--display);
|
||||
font-size: 0.6rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
padding: 0 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar nav a {
|
||||
display: block;
|
||||
padding: 0.4rem 1.5rem;
|
||||
font-size: 0.83rem;
|
||||
color: var(--text-secondary);
|
||||
transition: color 0.15s, background 0.15s;
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
|
||||
.sidebar nav a:hover {
|
||||
color: var(--text);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar nav a.active {
|
||||
color: var(--cyan);
|
||||
border-left-color: var(--cyan);
|
||||
background: var(--cyan-dim);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
.docs-main {
|
||||
padding: 2.5rem 3rem 4rem;
|
||||
max-width: 780px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.docs-layout { grid-template-columns: 1fr; }
|
||||
.sidebar {
|
||||
position: static;
|
||||
height: auto;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
.docs-main { padding: 2rem 0 3rem; }
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
.page-title {
|
||||
font-family: var(--display);
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 1rem;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 300;
|
||||
margin-bottom: 3rem;
|
||||
line-height: 1.7;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: var(--display);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 3rem 0 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border);
|
||||
scroll-margin-top: 2rem;
|
||||
}
|
||||
|
||||
h2:first-of-type {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: var(--display);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
margin: 1.8rem 0 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
p strong {
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: 1.4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
li strong {
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Code */
|
||||
code {
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
font-size: 0.8rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 0.1em 0.4em;
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 1.2rem 1.4rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0 1.5rem;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 0.82rem;
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0 1.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
font-family: var(--display);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
padding: 0.6rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.65rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
vertical-align: top;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
font-size: 0.8rem;
|
||||
color: var(--cyan);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr:last-child td { border-bottom: none; }
|
||||
|
||||
/* Callout / note box */
|
||||
.note {
|
||||
background: var(--cyan-dim);
|
||||
border: 1px solid rgba(34, 211, 238, 0.2);
|
||||
border-radius: 6px;
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1.2rem 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.note strong {
|
||||
color: var(--cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Step list */
|
||||
.step-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
counter-reset: steps;
|
||||
}
|
||||
|
||||
.step-list li {
|
||||
counter-increment: steps;
|
||||
display: grid;
|
||||
grid-template-columns: 40px 1fr;
|
||||
gap: 1rem;
|
||||
padding: 1.2rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.step-list li:first-child { border-top: 1px solid var(--border); }
|
||||
|
||||
.step-list li::before {
|
||||
content: counter(steps, decimal-leading-zero);
|
||||
font-family: var(--display);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-dim);
|
||||
padding-top: 0.15rem;
|
||||
}
|
||||
|
||||
/* Command cards */
|
||||
.cmd-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
background: var(--border);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin: 1rem 0 1.5rem;
|
||||
}
|
||||
|
||||
.cmd-row {
|
||||
display: grid;
|
||||
grid-template-columns: 160px 1fr;
|
||||
background: var(--surface);
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.cmd-row:hover { background: var(--surface-hover); }
|
||||
|
||||
.cmd-name {
|
||||
padding: 0.9rem 1.1rem;
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
font-size: 0.8rem;
|
||||
color: var(--cyan);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cmd-desc {
|
||||
padding: 0.9rem 1.1rem;
|
||||
font-size: 0.84rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Docs index cards */
|
||||
.doc-cards {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1px;
|
||||
background: var(--border);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.doc-card {
|
||||
background: var(--surface);
|
||||
padding: 1.6rem;
|
||||
transition: background 0.2s;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.doc-card:hover { background: var(--surface-hover); opacity: 1; }
|
||||
|
||||
.doc-card-title {
|
||||
font-family: var(--display);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.doc-card-desc {
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.doc-cards { grid-template-columns: 1fr; }
|
||||
.cmd-row { grid-template-columns: 130px 1fr; }
|
||||
}
|
||||
|
||||
/* Footer (docs) */
|
||||
.shell footer {
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-dim);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.shell footer a {
|
||||
color: var(--text-dim);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.shell footer a:hover { color: var(--text-secondary); }
|
||||
|
||||
/* Pipeline stages (used in pipeline page) */
|
||||
.pipeline-stages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
background: var(--border);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.stage-row {
|
||||
display: grid;
|
||||
grid-template-columns: 48px 140px 1fr;
|
||||
gap: 0;
|
||||
background: var(--surface);
|
||||
transition: background 0.2s;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.stage-row:hover { background: var(--surface-hover); }
|
||||
|
||||
.stage-num {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--display);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-dim);
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.stage-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem 1.1rem;
|
||||
font-family: var(--display);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.stage-desc {
|
||||
padding: 1rem 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.stage-row.active .stage-name { color: var(--cyan); }
|
||||
@@ -0,0 +1,48 @@
|
||||
/** Shared layout for all docs pages: header, sidebar, and footer. */
|
||||
import type { Metadata } from 'next'
|
||||
import DocsSidebar from '@/components/DocsSidebar'
|
||||
import './docs.css'
|
||||
|
||||
/** Default metadata for docs pages. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'Documentation — Huskies',
|
||||
}
|
||||
|
||||
/** Wraps all doc pages with the shared docs shell (header, sidebar, footer). */
|
||||
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<div className="shell">
|
||||
<header className="reveal r1">
|
||||
<a href="/" className="logo">
|
||||
huskies
|
||||
</a>
|
||||
<nav>
|
||||
<a href="/#how">How it works</a>
|
||||
<a href="/#features">Features</a>
|
||||
<a href="/docs" className="active">
|
||||
Docs
|
||||
</a>
|
||||
<a href="https://code.crashlabs.io/crashlabs/huskies">Source</a>
|
||||
<a href="https://code.crashlabs.io/crashlabs/huskies/releases">Releases</a>
|
||||
<a href="mailto:hello@huskies.dev" className="nav-cta">
|
||||
Get in touch
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div className="shell">
|
||||
<div className="docs-layout">
|
||||
<DocsSidebar />
|
||||
<main className="docs-main reveal r3">{children}</main>
|
||||
</div>
|
||||
|
||||
<footer className="reveal r3">
|
||||
<span>© 2026 Libby Labs Ltd.</span>
|
||||
<a href="/privacy">Privacy Policy</a>
|
||||
</footer>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/** Docs index — overview and card links to all documentation sections. */
|
||||
import type { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
|
||||
/** Page metadata for the docs index. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'Documentation — Huskies',
|
||||
description:
|
||||
'Huskies documentation: quickstart, configuration, bot commands, pipeline, and more.',
|
||||
}
|
||||
|
||||
/** Renders the documentation overview page with section cards. */
|
||||
export default function DocsIndexPage() {
|
||||
return (
|
||||
<>
|
||||
<p
|
||||
className="hero-kicker"
|
||||
style={{
|
||||
fontFamily: 'var(--display)',
|
||||
fontSize: '0.68rem',
|
||||
fontWeight: 600,
|
||||
letterSpacing: '0.18em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'var(--cyan)',
|
||||
marginBottom: '1rem',
|
||||
}}
|
||||
>
|
||||
Documentation
|
||||
</p>
|
||||
<h1 className="page-title">Huskies Docs</h1>
|
||||
<p className="page-subtitle">
|
||||
Everything you need to set up and run huskies — a story-driven development pipeline that turns
|
||||
coding agents into a disciplined team.
|
||||
</p>
|
||||
|
||||
<div className="doc-cards">
|
||||
<Link className="doc-card" href="/docs/quickstart">
|
||||
<div className="doc-card-title">Quickstart</div>
|
||||
<div className="doc-card-desc">
|
||||
Install huskies, run the server, create your first story, and watch an agent implement it.
|
||||
</div>
|
||||
</Link>
|
||||
<Link className="doc-card" href="/docs/configuration">
|
||||
<div className="doc-card-title">Configuration</div>
|
||||
<div className="doc-card-desc">
|
||||
Reference for <code>project.toml</code>, <code>agents.toml</code>, and <code>bot.toml</code>.
|
||||
</div>
|
||||
</Link>
|
||||
<Link className="doc-card" href="/docs/commands">
|
||||
<div className="doc-card-title">Bot commands</div>
|
||||
<div className="doc-card-desc">
|
||||
Full list of commands available in Matrix, Slack, WhatsApp, and the web UI.
|
||||
</div>
|
||||
</Link>
|
||||
<Link className="doc-card" href="/docs/pipeline">
|
||||
<div className="doc-card-title">Pipeline stages</div>
|
||||
<div className="doc-card-desc">
|
||||
How work items move from backlog through QA and merge to done.
|
||||
</div>
|
||||
</Link>
|
||||
<Link className="doc-card" href="/docs/transports">
|
||||
<div className="doc-card-title">Chat transports</div>
|
||||
<div className="doc-card-desc">
|
||||
Connect huskies to Matrix, WhatsApp, Slack, Discord, or the built-in web UI.
|
||||
</div>
|
||||
</Link>
|
||||
<Link className="doc-card" href="/docs/cli">
|
||||
<div className="doc-card-title">CLI reference</div>
|
||||
<div className="doc-card-desc">
|
||||
Command-line flags for <code>huskies</code>, <code>huskies init</code>, and{' '}
|
||||
<code>huskies agent</code>.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h2>What is huskies?</h2>
|
||||
<p>
|
||||
Huskies is a story-driven development server. You write stories (feature requests) with acceptance
|
||||
criteria; huskies spawns coding agents in isolated git worktrees, runs them through quality gates, and
|
||||
squash-merges the result to your main branch — all without you writing a line of code.
|
||||
</p>
|
||||
<p>
|
||||
It ships as a single Rust binary with an embedded React frontend. No separate database or build
|
||||
infrastructure required.
|
||||
</p>
|
||||
|
||||
<h2>How it works</h2>
|
||||
<ol className="step-list">
|
||||
<li>
|
||||
<div>
|
||||
<strong>Write a story.</strong> Describe the change with acceptance criteria via the web UI, a chat
|
||||
room (Matrix, WhatsApp, Slack), or by dropping a Markdown file in{' '}
|
||||
<code>.huskies/work/1_backlog/</code>.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<strong>Agent picks it up.</strong> Run <code>start <number></code> (or configure
|
||||
auto-start). A coder agent creates a feature branch, implements the code, and writes tests against
|
||||
your criteria.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<strong>Quality gates run.</strong> Linters, tests, and compilation checks run automatically when
|
||||
the agent exits. Nothing moves forward until everything passes.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<strong>QA review.</strong> A QA agent verifies each acceptance criterion, runs your test suite,
|
||||
and either approves or rejects with detailed findings.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<strong>Merge & land.</strong> A merge agent resolves conflicts and squash-merges to your main
|
||||
branch. The worktree is cleaned up automatically.
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2>Key concepts</h2>
|
||||
<p>
|
||||
<strong>Stories</strong> are Markdown files with YAML front matter. They live in{' '}
|
||||
<code>.huskies/work/</code> and move through pipeline stages as work progresses.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Agents</strong> are Claude Code sessions that run autonomously in git worktrees. Each story gets
|
||||
its own isolated worktree so multiple stories can be in flight simultaneously.
|
||||
</p>
|
||||
<p>
|
||||
<strong>MCP tools</strong> give Claude Code sessions programmatic access to the pipeline: creating
|
||||
stories, starting agents, checking status, recording test results.
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/** Pipeline stages reference — describes the six-stage story workflow. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
/** Page metadata for the pipeline stages guide. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'Pipeline Stages — Huskies Docs',
|
||||
description:
|
||||
'How work items move through the huskies pipeline: backlog, current, QA, merge, done.',
|
||||
}
|
||||
|
||||
/** Renders the pipeline stages reference page. */
|
||||
export default function PipelinePage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="page-title">Pipeline Stages</h1>
|
||||
<p className="page-subtitle">
|
||||
Work items move through six stages from idea to archive. Each stage is a directory under{' '}
|
||||
<code>.huskies/work/</code>. Moving a file between directories advances the story.
|
||||
</p>
|
||||
|
||||
<div className="pipeline-stages">
|
||||
<div className="stage-row">
|
||||
<div className="stage-num">1</div>
|
||||
<div className="stage-name">Backlog</div>
|
||||
<div className="stage-desc">
|
||||
<code>1_backlog/</code> — New work items awaiting prioritisation. Stories sit here until
|
||||
you decide to start them.
|
||||
</div>
|
||||
</div>
|
||||
<div className="stage-row active">
|
||||
<div className="stage-num">2</div>
|
||||
<div className="stage-name">Current</div>
|
||||
<div className="stage-desc">
|
||||
<code>2_current/</code> — Work in progress. Run <code>start <number></code> to
|
||||
assign a coder agent. Multiple stories can be in current simultaneously (up to{' '}
|
||||
<code>max_coders</code>).
|
||||
</div>
|
||||
</div>
|
||||
<div className="stage-row">
|
||||
<div className="stage-num">3</div>
|
||||
<div className="stage-name">QA</div>
|
||||
<div className="stage-desc">
|
||||
<code>3_qa/</code> — Quality review. The server automatically moves stories here when the
|
||||
coder agent passes all quality gates. A QA agent (or a human) verifies each acceptance criterion.
|
||||
</div>
|
||||
</div>
|
||||
<div className="stage-row">
|
||||
<div className="stage-num">4</div>
|
||||
<div className="stage-name">Merge</div>
|
||||
<div className="stage-desc">
|
||||
<code>4_merge/</code> — Ready to merge. Stories reach here after QA approval. Run{' '}
|
||||
<code>start <number></code> to trigger the mergemaster agent, which squash-merges to your
|
||||
base branch.
|
||||
</div>
|
||||
</div>
|
||||
<div className="stage-row">
|
||||
<div className="stage-num">5</div>
|
||||
<div className="stage-name">Done</div>
|
||||
<div className="stage-desc">
|
||||
<code>5_done/</code> — Merged and complete. The mergemaster moves stories here after a
|
||||
successful merge. Auto-swept to archive after 4 hours.
|
||||
</div>
|
||||
</div>
|
||||
<div className="stage-row">
|
||||
<div className="stage-num">6</div>
|
||||
<div className="stage-name">Archived</div>
|
||||
<div className="stage-desc">
|
||||
<code>6_archived/</code> — Long-term storage. Stories land here automatically from done.
|
||||
Use <code>overview <number></code> to see the implementation summary for any archived
|
||||
story.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Work item types</h2>
|
||||
<p>
|
||||
All work item types move through the same pipeline. They differ in naming convention and workflow:
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Filename pattern</th>
|
||||
<th>When to use</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>story</td>
|
||||
<td>
|
||||
<code>42_story_add_login.md</code>
|
||||
</td>
|
||||
<td>New functionality. Requires acceptance criteria and tests.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bug</td>
|
||||
<td>
|
||||
<code>43_bug_login_crashes.md</code>
|
||||
</td>
|
||||
<td>Defect in existing functionality. Write a failing test first.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>spike</td>
|
||||
<td>
|
||||
<code>44_spike_auth_options.md</code>
|
||||
</td>
|
||||
<td>Time-boxed research to reduce uncertainty. No production code.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>refactor</td>
|
||||
<td>
|
||||
<code>45_refactor_extract_auth.md</code>
|
||||
</td>
|
||||
<td>Code quality improvement. Behaviour must not change.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Story file format</h2>
|
||||
<p>Every work item is a Markdown file with YAML front matter:</p>
|
||||
<pre>
|
||||
<code>{`---
|
||||
name: "Short human-readable name"
|
||||
qa: agent # optional: override default_qa
|
||||
agent: opus # optional: request specific agent model
|
||||
---
|
||||
|
||||
# Story 42: Add login endpoint
|
||||
|
||||
## User Story
|
||||
As a user, I want to log in with email and password so that I can access my account.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] POST /auth/login accepts email and password
|
||||
- [ ] Returns a JWT token on success
|
||||
- [ ] Returns 401 on invalid credentials
|
||||
- [ ] Rate-limited to 5 attempts per minute per IP
|
||||
|
||||
## Out of Scope
|
||||
- OAuth / social login
|
||||
- Password reset flow`}</code>
|
||||
</pre>
|
||||
|
||||
<h2>Acceptance criteria tracking</h2>
|
||||
<p>
|
||||
Acceptance criteria use Markdown checkboxes (<code>- [ ]</code>). The QA agent reviews each criterion
|
||||
against the code diff and marks passing criteria as <code>- [x]</code> in the story file. Criteria
|
||||
that fail are noted in the QA report.
|
||||
</p>
|
||||
|
||||
<div className="note">
|
||||
<strong>Golden rule:</strong> No code is written until acceptance criteria are captured in the story.
|
||||
The agent reads the story file to understand what to build and what to test.
|
||||
</div>
|
||||
|
||||
<h2>Filesystem watcher</h2>
|
||||
<p>
|
||||
The server watches <code>.huskies/work/</code> for changes. When a file is created, moved, or
|
||||
modified, the watcher auto-commits with a deterministic message and broadcasts a WebSocket update to
|
||||
the frontend. This means:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
You can drag a story between stage folders in your IDE and it advances automatically.
|
||||
</li>
|
||||
<li>MCP tools only need to write or move files — the watcher handles git commits.</li>
|
||||
<li>The pipeline board updates in real time without a manual refresh.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Blocked stories</h2>
|
||||
<p>
|
||||
A story is marked <strong>blocked</strong> when it fails the same pipeline stage more than{' '}
|
||||
<code>max_retries</code> times (default: 3). Blocked stories require manual intervention:
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
Run <code>status <number></code> to see the failure log.
|
||||
</li>
|
||||
<li>Fix the underlying issue (update the story, fix a build problem, etc.).</li>
|
||||
<li>
|
||||
Run <code>unblock <number></code> to reset the retry counter.
|
||||
</li>
|
||||
<li>
|
||||
Run <code>start <number></code> to try again.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2>Dependencies</h2>
|
||||
<p>
|
||||
Stories can declare dependencies using the <code>depends</code> command. A story with unresolved
|
||||
dependencies waits in <code>2_current/</code> until all its dependencies have reached{' '}
|
||||
<code>5_done/</code>.
|
||||
</p>
|
||||
<pre>
|
||||
<code>{`depends 45 42 43 # story 45 waits for 42 and 43 to finish\ndepends 45 # clear all dependencies`}</code>
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/** Quickstart guide — Docker, binary, and source install instructions. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
/** Page metadata for the quickstart guide. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'Quickstart — Huskies Docs',
|
||||
description: 'Get huskies running in minutes: Docker setup, first story, first agent run.',
|
||||
}
|
||||
|
||||
/** Renders the quickstart installation and setup guide. */
|
||||
export default function QuickstartPage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="page-title">Quickstart</h1>
|
||||
<p className="page-subtitle">
|
||||
Get huskies running in your project in a few minutes. This guide covers Docker setup, running from a
|
||||
binary, your first story, and your first agent run.
|
||||
</p>
|
||||
|
||||
<h2>Option A: Docker (recommended)</h2>
|
||||
<p>
|
||||
The easiest way to run huskies is with Docker Compose. This requires Docker and a Claude API key.
|
||||
</p>
|
||||
<ol className="step-list">
|
||||
<li>
|
||||
<div>
|
||||
<strong>Get the compose file.</strong> Download <code>docker-compose.yml</code> from the{' '}
|
||||
<a href="https://code.crashlabs.io/crashlabs/huskies/releases">releases page</a> or copy it from
|
||||
the repository's <code>docker/</code> directory.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<strong>Set your API key.</strong> Create a <code>.env</code> file next to the compose file:
|
||||
<pre><code>ANTHROPIC_API_KEY=sk-ant-...</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<strong>Mount your project.</strong> Edit the compose file to mount your project directory:
|
||||
<pre><code>{`volumes:\n - /path/to/your/project:/workspace`}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<strong>Start the server.</strong>
|
||||
<pre><code>docker compose up</code></pre>
|
||||
Open <a href="http://localhost:3000">http://localhost:3000</a> to see the pipeline board.
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2>Option B: Binary</h2>
|
||||
<p>
|
||||
Download the pre-built binary for your platform from the{' '}
|
||||
<a href="https://code.crashlabs.io/crashlabs/huskies/releases">releases page</a> and place it
|
||||
somewhere on your <code>PATH</code>.
|
||||
</p>
|
||||
|
||||
<h3>macOS (Apple Silicon)</h3>
|
||||
<pre><code>{`curl -L https://code.crashlabs.io/crashlabs/huskies/releases/download/latest/huskies-aarch64-apple-darwin \\
|
||||
-o /usr/local/bin/huskies
|
||||
chmod +x /usr/local/bin/huskies`}</code></pre>
|
||||
|
||||
<h3>Linux (x86-64)</h3>
|
||||
<pre><code>{`curl -L https://code.crashlabs.io/crashlabs/huskies/releases/download/latest/huskies-x86_64-unknown-linux-musl \\
|
||||
-o /usr/local/bin/huskies
|
||||
chmod +x /usr/local/bin/huskies`}</code></pre>
|
||||
|
||||
<h2>Option C: Build from source</h2>
|
||||
<p>Requires Rust (stable), Node.js, and npm.</p>
|
||||
<pre><code>{`git clone https://code.crashlabs.io/crashlabs/huskies
|
||||
cd huskies
|
||||
cargo build --release
|
||||
# Binary is at target/release/huskies`}</code></pre>
|
||||
|
||||
<h2>Initialise your project</h2>
|
||||
<p>
|
||||
From your project directory, run the init command. This creates the <code>.huskies/</code> directory
|
||||
with the pipeline structure and configuration files.
|
||||
</p>
|
||||
<pre><code>{`cd /path/to/your/project\nhuskies init --port 3000`}</code></pre>
|
||||
<p>This creates:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>.huskies/project.toml</code> — project-wide settings
|
||||
</li>
|
||||
<li>
|
||||
<code>.huskies/agents.toml</code> — agent definitions (coder, QA, mergemaster)
|
||||
</li>
|
||||
<li>
|
||||
<code>.huskies/work/</code> — the 6-stage pipeline directory
|
||||
</li>
|
||||
<li>
|
||||
<code>.mcp.json</code> — MCP server config for Claude Code integration
|
||||
</li>
|
||||
<li>
|
||||
<code>.huskies/specs/</code> — placeholder spec files for your project context
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="note">
|
||||
<strong>Claude Code integration:</strong> The <code>.mcp.json</code> file automatically registers
|
||||
huskies' MCP tools with Claude Code. Open a Claude Code session in your project and it will
|
||||
discover tools like <code>create_story</code>, <code>start_agent</code>, and{' '}
|
||||
<code>get_pipeline_status</code> automatically.
|
||||
</div>
|
||||
|
||||
<h2>Start the server</h2>
|
||||
<pre><code>huskies --port 3000</code></pre>
|
||||
<p>
|
||||
Open <a href="http://localhost:3000">http://localhost:3000</a> to see the pipeline board, agent
|
||||
status, and chat interface.
|
||||
</p>
|
||||
|
||||
<h2>Run the setup wizard</h2>
|
||||
<p>
|
||||
Open a Claude Code session in your project directory (or use the web chat UI), and tell Claude:
|
||||
</p>
|
||||
<pre><code>help me set up this project with huskies</code></pre>
|
||||
<p>
|
||||
Claude will walk you through the setup wizard — generating project context (
|
||||
<code>specs/00_CONTEXT.md</code>), tech stack docs (<code>specs/tech/STACK.md</code>), and
|
||||
test/release scripts. Review each step and confirm or ask to retry.
|
||||
</p>
|
||||
|
||||
<h2>Create your first story</h2>
|
||||
<p>In the chat UI or via a chat transport, type:</p>
|
||||
<pre><code>I want to add a health check endpoint to the API</code></pre>
|
||||
<p>
|
||||
Claude will create a story file in <code>.huskies/work/1_backlog/</code> with a user story and
|
||||
acceptance criteria. Review it, then move it to current:
|
||||
</p>
|
||||
<pre><code>move <story-number> current</code></pre>
|
||||
|
||||
<h2>Start an agent</h2>
|
||||
<p>
|
||||
Once a story is in <code>2_current/</code>, start a coding agent:
|
||||
</p>
|
||||
<pre><code>start <story-number></code></pre>
|
||||
<p>
|
||||
The agent creates an isolated git worktree, implements the feature against the acceptance criteria,
|
||||
runs quality gates (clippy, tests, biome), and exits. The server automatically advances the story to
|
||||
QA if all gates pass.
|
||||
</p>
|
||||
|
||||
<h2>Review and merge</h2>
|
||||
<p>Once QA passes, the story moves to <code>4_merge/</code>. To merge:</p>
|
||||
<pre><code>start <story-number></code></pre>
|
||||
<p>
|
||||
The mergemaster agent resolves any conflicts and squash-merges to your main branch. The worktree is
|
||||
cleaned up automatically.
|
||||
</p>
|
||||
|
||||
<div className="note">
|
||||
<strong>Tip:</strong> Use <code>status</code> in the chat at any time to see the current pipeline
|
||||
state, active agents, and their progress.
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
/** Chat transports guide — Matrix, Slack, WhatsApp, Discord, and web UI setup. */
|
||||
import type { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
|
||||
/** Page metadata for the chat transports guide. */
|
||||
export const metadata: Metadata = {
|
||||
title: 'Chat Transports — Huskies Docs',
|
||||
description:
|
||||
'Connect huskies to Matrix, WhatsApp, Slack, Discord, or the built-in web UI.',
|
||||
}
|
||||
|
||||
/** Renders the chat transports setup guide for all supported platforms. */
|
||||
export default function TransportsPage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="page-title">Chat Transports</h1>
|
||||
<p className="page-subtitle">
|
||||
Huskies can be controlled via bot commands in any of five transports. Only one external transport
|
||||
can be active at a time. The web UI is always available regardless.
|
||||
</p>
|
||||
|
||||
<div className="note">
|
||||
<strong>Configuration:</strong> Copy the relevant example file to{' '}
|
||||
<code>.huskies/bot.toml</code> and fill in your credentials. The file is gitignored. Restart
|
||||
huskies after changes.
|
||||
</div>
|
||||
|
||||
<h2>Web UI</h2>
|
||||
<p>
|
||||
The built-in web interface is always available at <code>http://localhost:<port></code>. No
|
||||
configuration required. It provides:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Pipeline board showing all work items and their stages</li>
|
||||
<li>Agent status panel with live output streaming</li>
|
||||
<li>Chat interface for running commands and talking to Claude</li>
|
||||
<li>Coverage and cost dashboards</li>
|
||||
</ul>
|
||||
<p>
|
||||
No <code>bot.toml</code> is required for the web UI. If no transport is configured, huskies runs
|
||||
in web-only mode.
|
||||
</p>
|
||||
|
||||
<h2>Matrix</h2>
|
||||
<p>
|
||||
Matrix uses the Matrix Client-Server API with long-polling sync. No public webhook URL is required
|
||||
— the bot connects outbound to your homeserver.
|
||||
</p>
|
||||
|
||||
<h3>Setup</h3>
|
||||
<ol className="step-list">
|
||||
<li>
|
||||
<div>Register a Matrix account for the bot on your homeserver (e.g. <code>@huskies:example.com</code>).</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>Invite the bot account to the rooms you want it to monitor.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Copy the example config and fill in your credentials:
|
||||
<pre>
|
||||
<code>cp .huskies/bot.toml.matrix.example .huskies/bot.toml</code>
|
||||
</pre>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>bot.toml fields</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>homeserver</td>
|
||||
<td>
|
||||
Your Matrix homeserver URL (e.g. <code>https://matrix.example.com</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>username</td>
|
||||
<td>
|
||||
Bot account Matrix ID (e.g. <code>@huskies:example.com</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>password</td>
|
||||
<td>Bot account password.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>room_ids</td>
|
||||
<td>
|
||||
List of room IDs to listen in (e.g. <code>{`["!roomid:example.com"]`}</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>allowed_users</td>
|
||||
<td>
|
||||
Matrix IDs allowed to interact. Empty list means nobody — always set this.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ambient_rooms</td>
|
||||
<td>
|
||||
Rooms where the bot responds to all messages (not just addressed ones). Updated automatically
|
||||
by <code>ambient on/off</code>.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Slack</h2>
|
||||
<p>
|
||||
Slack uses event subscriptions over a webhook. You'll need a public HTTPS URL pointing to your
|
||||
huskies server.
|
||||
</p>
|
||||
|
||||
<h3>Setup</h3>
|
||||
<ol className="step-list">
|
||||
<li>
|
||||
<div>
|
||||
Create a Slack App at{' '}
|
||||
<a href="https://api.slack.com/apps">api.slack.com/apps</a>.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Add OAuth scopes: <code>chat:write</code>, <code>chat:update</code>.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Subscribe to bot events: <code>message.channels</code>, <code>message.groups</code>,{' '}
|
||||
<code>message.im</code>.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>Install the app to your workspace and copy the bot token.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Set your webhook URL in Event Subscriptions:{' '}
|
||||
<code>https://your-server/webhook/slack</code>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Copy the example config:
|
||||
<pre>
|
||||
<code>cp .huskies/bot.toml.slack.example .huskies/bot.toml</code>
|
||||
</pre>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>bot.toml fields</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>slack_bot_token</td>
|
||||
<td>
|
||||
OAuth bot token starting with <code>xoxb-</code>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>slack_signing_secret</td>
|
||||
<td>Signing secret from the app's Basic Information page.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>slack_channel_ids</td>
|
||||
<td>
|
||||
List of channel IDs to listen in (e.g. <code>{`["C01ABCDEF"]`}</code>).
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>WhatsApp (Meta Cloud API)</h2>
|
||||
<p>
|
||||
Connects huskies to WhatsApp Business via the Meta Cloud API. Requires a Meta Business account and
|
||||
a public webhook URL.
|
||||
</p>
|
||||
|
||||
<h3>Setup</h3>
|
||||
<ol className="step-list">
|
||||
<li>
|
||||
<div>
|
||||
Create a Meta Business App at{' '}
|
||||
<a href="https://developers.facebook.com">developers.facebook.com</a>.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>Add the WhatsApp product and get a Phone Number ID.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>Generate a permanent access token.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Register your webhook URL in Meta's dashboard:{' '}
|
||||
<code>https://your-server/webhook/whatsapp</code>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Copy the example config:
|
||||
<pre>
|
||||
<code>cp .huskies/bot.toml.whatsapp-meta.example .huskies/bot.toml</code>
|
||||
</pre>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>bot.toml fields</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>whatsapp_provider</td>
|
||||
<td>
|
||||
Set to <code>"meta"</code> for the Meta Cloud API.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>whatsapp_phone_number_id</td>
|
||||
<td>Phone Number ID from the Meta dashboard.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>whatsapp_access_token</td>
|
||||
<td>Permanent access token.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>whatsapp_verify_token</td>
|
||||
<td>
|
||||
Webhook verify token — must match what you set in Meta's dashboard.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>whatsapp_allowed_phones</td>
|
||||
<td>
|
||||
Optional. List of phone numbers allowed to interact (e.g.{' '}
|
||||
<code>{`["+15551234567"]`}</code>). When absent, all numbers are allowed.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>whatsapp_notification_template</td>
|
||||
<td>
|
||||
Optional. Name of the approved Meta message template for out-of-window notifications
|
||||
(default: <code>"pipeline_notification"</code>).
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>WhatsApp (Twilio)</h2>
|
||||
<p>
|
||||
An alternative WhatsApp integration using Twilio's WhatsApp API. Requires a Twilio account.
|
||||
</p>
|
||||
<pre>
|
||||
<code>cp .huskies/bot.toml.whatsapp-twilio.example .huskies/bot.toml</code>
|
||||
</pre>
|
||||
<p>
|
||||
Set <code>whatsapp_provider = "twilio"</code> and fill in your Twilio account SID, auth
|
||||
token, and phone numbers. The webhook URL is the same:{' '}
|
||||
<code>https://your-server/webhook/whatsapp</code>.
|
||||
</p>
|
||||
|
||||
<h2>Discord</h2>
|
||||
<p>
|
||||
Connects huskies to Discord using the Discord Gateway WebSocket. No public webhook URL required
|
||||
— the bot connects outbound.
|
||||
</p>
|
||||
|
||||
<h3>Setup</h3>
|
||||
<ol className="step-list">
|
||||
<li>
|
||||
<div>
|
||||
Create a Discord Application at{' '}
|
||||
<a href="https://discord.com/developers/applications">
|
||||
discord.com/developers/applications
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>Go to Bot, create a bot, and copy the token.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Enable <strong>Message Content Intent</strong> under Privileged Gateway Intents.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Go to OAuth2 → URL Generator, select the <code>bot</code> scope with permissions: Send
|
||||
Messages, Read Message History, Manage Messages.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>Use the generated URL to invite the bot to your server.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Right-click target channels → Copy Channel ID (requires Developer Mode enabled in Discord
|
||||
settings).
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Copy the example config:
|
||||
<pre>
|
||||
<code>cp .huskies/bot.toml.discord.example .huskies/bot.toml</code>
|
||||
</pre>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>bot.toml fields</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>discord_bot_token</td>
|
||||
<td>Bot token from the Discord developer portal.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>discord_channel_ids</td>
|
||||
<td>
|
||||
List of channel IDs to listen in (e.g.{' '}
|
||||
<code>{`["123456789012345678"]`}</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>discord_allowed_users</td>
|
||||
<td>
|
||||
Optional. Discord user IDs allowed to interact. When absent, all users in configured
|
||||
channels can interact.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="gateway-aggregated">Gateway: aggregated notifications</h2>
|
||||
<p>
|
||||
When using <code>huskies --gateway</code>, you can configure the gateway bot to receive
|
||||
notifications from <strong>all</strong> registered projects in a single room. Events are prefixed
|
||||
with <code>[project-name]</code>.
|
||||
</p>
|
||||
<p>
|
||||
No additional transport is required — the gateway aggregated stream works with any of the
|
||||
transports above. Configure the gateway's <code>.huskies/bot.toml</code> with your transport
|
||||
credentials and set <code>aggregated_notifications_enabled = true</code> (the default). See{' '}
|
||||
<Link href="/docs/configuration#gateway-aggregated-stream">
|
||||
Configuration → Gateway aggregated stream
|
||||
</Link>{' '}
|
||||
for the full reference.
|
||||
</p>
|
||||
<div className="note">
|
||||
<strong>No per-project changes needed:</strong> Adding a new project to{' '}
|
||||
<code>projects.toml</code> does not require editing per-project bot configs — the gateway
|
||||
picks it up automatically.
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user