diff --git a/docs/README.md b/docs/README.md index 86223215..3e067f40 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,7 +24,7 @@ The site is its own pnpm workspace (`docs/pnpm-workspace.yaml`), so its dependen ``` src/styles/tokens.css theme colors for light and dark; every color on the site comes from here src/styles/global.css base type, layout wrapper, pills, cards, buttons -src/data/ links, supported agents, and the feature cards +src/data/ links and supported agents src/components/ one component per section; Hero and DemoCard hold the interactive parts src/scripts/ circuit.ts (hero background) and demo.ts (the demo card loop) src/layouts/Base.astro page shell, fonts, meta tags @@ -39,7 +39,7 @@ Each page under `src/content/docs/docs/` is a Markdown file served at `/docs/ - {features.map((feature) => ( -
-
-

{feature.title}

- {pill.label} -
-

{feature.body}

- {feature.ref && {feature.ref}} -
- ))} - - - diff --git a/docs/src/components/Features.astro b/docs/src/components/Features.astro deleted file mode 100644 index 701c658a..00000000 --- a/docs/src/components/Features.astro +++ /dev/null @@ -1,46 +0,0 @@ ---- -import FeatureGrid from './FeatureGrid.astro'; -import { available, roadmap } from '../data/features'; -import { harnesses } from '../data/site'; ---- - -
-
-
-
- Features -

Works today

-
-
Available in version 0.3 on npm
-
- - -
-

Bring your own agent

-

osq drives the coding agent you already use. Swap it per project in one line of config.

-
- {harnesses.map((name) => {name})} -
-
- -
-
- Roadmap -

Coming in the rebuild

-
-
In progress osq is being rebuilt as a fast single binary
-
- -
-
- - diff --git a/docs/src/components/GetStarted.astro b/docs/src/components/GetStarted.astro index ca2a5543..c6d3ca1a 100644 --- a/docs/src/components/GetStarted.astro +++ b/docs/src/components/GetStarted.astro @@ -1,5 +1,5 @@ --- -import { links } from '../data/site'; +import { harnesses, links } from '../data/site'; ---
@@ -8,6 +8,10 @@ import { links } from '../data/site'; Get started

Running in a few minutes

osq needs Node.js 24 or newer and one supported coding agent. It adds a few folders to your repo and nothing else.

+
+ Works with + {harnesses.map((name) => {name})} +

Open source

osq is MIT licensed and developed in the open. Everything it knows about your project lives in plain files in your own repository, so you can read it, diff it, or walk away with it.

@@ -37,6 +41,8 @@ pnpm osq land 042 .start { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 48px; align-items: start; } .intro { display: grid; gap: 16px; } .lede { font-size: 16px; } + .agents { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 14px; color: var(--muted); } + .agents .pill { font-size: 13px; padding: 6px 12px; font-weight: 500; } .oss { display: grid; gap: 12px; margin-top: 8px; } .oss p { color: var(--muted); font-size: 15px; } pre { margin: 0; background: var(--code-bg); color: var(--code-fg); font-family: var(--mono); font-size: 13.5px; line-height: 1.7; padding: 20px; border-radius: 8px; overflow-x: auto; } diff --git a/docs/src/components/HowItWorks.astro b/docs/src/components/HowItWorks.astro index 3a3e5160..c6cf40ae 100644 --- a/docs/src/components/HowItWorks.astro +++ b/docs/src/components/HowItWorks.astro @@ -2,9 +2,9 @@ import Ledger from './Ledger.astro'; const steps = [ - { who: 'Anyone', you: true, title: 'Write a brief', body: 'A business goal in plain words, or a ticket copied from your project management backlog.' }, - { who: 'An engineer and a strong model', you: true, title: 'Spec it', body: 'Turns the brief into a spec AI can build: small tasks, each with the files it may touch and the test that proves it.' }, - { who: 'An engineer', you: true, title: 'Approve', body: 'A one-screen summary flags anything risky. Nothing runs before an engineer says yes.' }, + { who: 'Anyone', you: true, title: 'Write a brief', body: 'What you want and why, in plain words. No technical detail: a business goal or a backlog ticket is enough.' }, + { who: 'An engineer and a strong model', you: true, title: 'Spec it', body: 'Turns the brief into a detailed technical spec: small tasks, each with the files it may touch and the test that proves it.' }, + { who: 'An engineer', you: true, title: 'Approve', body: 'Every change in the spec is color-coded by risk, so the risky ones stand out. Nothing runs before an engineer says yes.' }, { who: 'Coding agents', title: 'Build', body: 'One fresh agent per task. A failed task retries with the reason, or comes back to you.' }, { who: 'osq', title: 'Verify and land', body: 'Re-tests the whole change, updates the specs, and merges it into your main branch.' }, ]; @@ -69,9 +69,25 @@ const leaves = [38.8, 41, 43.2].map((x) => box(x - 0.6, x + 0.6, -5, -5, 8.4, 9. const gate = { back: path([58, -5, 0], [58, -5, 15]), front: path([58, 5, 0], [58, 5, 15]), beam: path([58, -5.5, 15], [58, 5.5, 15]) }; // the bar sits low across the belt and lifts straight up to the top beam to let the box through const barrier = path([58, -5, 6.6], [58, 5, 6.6]); -// 4. two robot arms, one from behind the belt and one from the front -const armBack = { base: box(75, 79, -11, -7, 0, 3), d: path([77, -9, 3], [77, -9, 13], [80, -4, 17], [82, -0.5, 12]), joints: [[77, -9, 13], [80, -4, 17]] as V[], hand: [82, -0.5, 12] as V }; -const armFront = { base: box(86, 90, 7, 11, 0, 3), d: path([88, 9, 3], [88, 9, 12], [86, 4, 16], [83, 0.5, 11.5]), joints: [[88, 9, 12], [86, 4, 16]] as V[], hand: [83, 0.5, 11.5] as V }; +// 4. a lamp behind the belt whose head reaches over the box and rains code down into it +const LIT = 18.3; +const lamp = { + base: box(80.5, 85.5, -12.5, -6, 0, 1.4), + post: box(82.3, 83.7, -10.2, -8.8, 1.4, 19), + head: box(81.2, 84.8, -10.8, 1.6, 19, 20.8), + shade: box(81.6, 84.4, -3.4, 1.2, LIT, 19), +}; +// a sheet of light from the shade to the box's top, as wide on screen as the box +const beam = pts([81.7, 1.3, LIT], [84.3, -1.3, LIT], [84.3, -1.3, BELT + 2.6], [81.7, 1.3, BELT + 2.6]); +const fall = (LIT - BELT - 2.6) * S; +// columns of falling bits, in px from the shade's centre; each column loops on its own clock +const rain = [ + { x: -13, d: 1.1, delay: -0.2, bits: '10110' }, + { x: -6.5, d: 0.8, delay: -0.6, bits: '01101' }, + { x: 0, d: 1.0, delay: -0.1, bits: '11010' }, + { x: 6.5, d: 0.9, delay: -0.5, bits: '00111' }, + { x: 13, d: 1.2, delay: -0.8, bits: '10011' }, +]; // 5. the scanner arch and the crate that is main const scanner = { back: box(105, 107, -6, -4, 0, 13), front: box(105, 107, 4, 6, 0, 13), top: box(105, 107, -6, 6, 13, 15) }; const scanPlane = pts([106, -3.6, 12.6], [106, 3.6, 12.6], [106, 3.6, BELT + 0.2], [106, -3.6, BELT + 0.2]); @@ -80,7 +96,7 @@ const landed = [137, 141.5].map((x) => box(x - 1.3, x + 1.3, -4.7, -2.1, 8, 10.6 const crateLabel = p([140, 6, 3.2]); const retry = (() => { const [ax, ay] = p([106, 0, 17]); - const [bx, by] = p([84, 0, 21]); + const [bx, by] = p([84, 0, 23.5]); return { d: `M${ax.toFixed(1)} ${ay.toFixed(1)} C${(ax - 10).toFixed(1)} ${(ay - 70).toFixed(1)} ${(bx + 40).toFixed(1)} ${(by - 60).toFixed(1)} ${bx.toFixed(1)} ${by.toFixed(1)}`, label: [(ax + bx) / 2 + 10, Math.min(ay, by) - 52] }; })(); const humans = [ @@ -90,7 +106,7 @@ const badges = [ { v: [3.7, 5, 17] as V, you: true }, { v: [31, -8.5, 22] as V, you: true }, { v: [58, 0, 21] as V, you: true }, - { v: [83, 0, 24] as V, you: false }, + { v: [83, 0, 28] as V, you: false }, { v: [106, 0, 20] as V, you: false }, ]; --- @@ -103,7 +119,7 @@ const badges = [
- An isometric factory line. People plan a brief around a table and send it down a pipe, where it lands on the belt as a task box, an engineer and a model turn it into a spec of small tasks, an engineer opens the gate, two robot arms build each task, and a scanner checks every one before it lands in main. A failed task loops back to the arms. + An isometric factory line. People plan a brief around a table and send it down a pipe, where it lands on the belt as a task box, an engineer and a model turn it into a spec of small tasks, an engineer opens the gate, a lamp rains code down into each task to build it, and a scanner checks every one before it lands in main. A failed task loops back to the lamp. @@ -128,14 +144,13 @@ const badges = [ {leaves.map((pt) => )} - - - - - - {armBack.joints.map((v) => )} - - + {[lamp.base, lamp.post].map((b) => ( + + + + + + ))} @@ -220,15 +235,23 @@ const badges = [ - - - - - - - {armFront.joints.map((v) => )} - + + + + + + {rain.map((c) => ( + + {[...c.bits].map((b, i) => {b})} + + ))} + + + + + + @@ -327,11 +350,12 @@ const badges = [ .b3 { animation-name: talk3; } .barrier { fill: none; stroke: var(--accent); stroke-width: 5; stroke-linecap: round; animation: gate 16s ease-in-out infinite; } .lamp { animation: lamp 16s linear infinite; } - .limb { fill: none; stroke: var(--muted); stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; } - .joint { fill: var(--surface); stroke: var(--accent); stroke-width: 2; } - .grip { fill: none; stroke: var(--fg); stroke-width: 2.5; stroke-linecap: round; } - .arm { animation: work 16s ease-in-out infinite; } - .a2 { animation-name: work2; } + .shade { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 1; animation: shade 16s linear infinite; } + .beam { fill: var(--accent); opacity: 0; animation: beam 16s linear infinite; } + .rain { opacity: 0; animation: rain 16s linear infinite; } + .col { animation: fall 1s linear infinite; } + .col text { fill: var(--accent); font: 500 10px var(--mono); text-anchor: middle; } + .col text.lead { fill: var(--fg); } .scan { fill: var(--green-fg); opacity: 0.12; stroke: var(--green-fg); stroke-width: 1.5; animation: scan 16s linear infinite; } .retry { fill: none; stroke: var(--red-fg); stroke-width: 1.5; stroke-dasharray: 4 5; opacity: 0.8; animation: flow 1.2s linear infinite; } .note { fill: var(--muted); font: 13px var(--sans); } @@ -379,8 +403,12 @@ const badges = [ @keyframes leaf { 0%, 19% { opacity: 0; } 22%, 96% { opacity: 1; } 100% { opacity: 0; } } @keyframes gate { 0%, 41% { transform: translateY(0); } 44%, 51% { transform: translateY(-52px); } 55%, 100% { transform: translateY(0); } } @keyframes lamp { 0%, 41.9% { fill: var(--red-fg); } 42%, 52.9% { fill: var(--green-fg); } 53%, 100% { fill: var(--red-fg); } } - @keyframes work { 0%, 51% { transform: translateY(0); } 54% { transform: translateY(8px); } 57% { transform: translateY(0); } 60% { transform: translateY(8px); } 63% { transform: translateY(0); } 100% { transform: translateY(0); } } - @keyframes work2 { 0%, 52.5% { transform: translateY(0); } 55.5% { transform: translateY(8px); } 58.5% { transform: translateY(0); } 61.5% { transform: translateY(8px); } 64.5%, 100% { transform: translateY(0); } } + /* the lamp lights while the box sits under it, 51-66% */ + @keyframes shade { 0%, 50% { fill: var(--accent-soft); } 52%, 64% { fill: var(--accent); } 66%, 100% { fill: var(--accent-soft); } } + @keyframes beam { 0%, 50% { opacity: 0; } 52%, 64% { opacity: 0.1; } 66%, 100% { opacity: 0; } } + @keyframes rain { 0%, 51% { opacity: 0; } 52.5%, 64% { opacity: 1; } 65.5%, 100% { opacity: 0; } } + /* each column starts just above the shade and falls through the box's lid */ + @keyframes fall { from { translate: 0 -46px; } to { translate: 0 80px; } } @keyframes scan { 0%, 71% { opacity: 0.08; } 73%, 78% { opacity: 0.35; } 80%, 100% { opacity: 0.08; } } ol { list-style: none; margin: 24px 0 0; padding: 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 24px; } diff --git a/docs/src/components/Ledger.astro b/docs/src/components/Ledger.astro index 10a980e4..0a16cb5c 100644 --- a/docs/src/components/Ledger.astro +++ b/docs/src/components/Ledger.astro @@ -9,6 +9,8 @@ const feeds = columns.map((x) => { const mid = 500 + (x - 500) * 0.06; return x === 500 ? 'M500 0 V120' : `M${x} 0 V36 L${mid} 96 V120`; }); +// when each step happens in the factory's 16s loop (see HowItWorks), so a step's record drops as it acts +const fires = [0.02, 0.2, 0.44, 0.58, 0.88].map((t) => `${(t * 16).toFixed(2)}s`); ---
@@ -16,7 +18,7 @@ const feeds = columns.map((x) => { {feeds.map((d, i) => ( - + ))} @@ -53,7 +55,7 @@ const feeds = columns.map((x) => { .ledger { margin-top: 8px; display: flex; flex-direction: column; align-items: center; } .feeds { display: block; width: 100%; height: 96px; } .feed { fill: none; stroke: color-mix(in srgb, var(--accent) 35%, transparent); stroke-width: 1.5; } - .pulse { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 8 92; animation: down 3s linear infinite; } + .pulse { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 8 92; opacity: 0; animation: down 16s linear infinite; } .db { width: 64px; margin-top: -2px; } .db svg { display: block; width: 100%; height: auto; } .side, .cap { fill: var(--surface); stroke: var(--accent); stroke-width: 2; } @@ -66,7 +68,8 @@ const feeds = columns.map((x) => { h4 { margin: 0; font-family: var(--display); font-size: 16px; font-weight: 600; } .item p { font-size: 14px; color: var(--muted); } code { font-family: var(--mono); font-size: 12px; color: var(--muted); } - @keyframes down { from { stroke-dashoffset: 8; } to { stroke-dashoffset: -92; } } + /* one trip down in the first 10% of the loop, then the line rests until its step comes round again */ + @keyframes down { 0% { stroke-dashoffset: 8; opacity: 1; } 10% { stroke-dashoffset: -92; opacity: 1; } 10.1%, 100% { stroke-dashoffset: -92; opacity: 0; } } /* the feeds line up with five columns; once the steps wrap there is nothing to line up with */ @media (max-width: 900px) { .feeds { display: none; } .ledger { margin-top: 40px; } } @media (max-width: 560px) { .cards { grid-template-columns: minmax(0, 1fr); } } diff --git a/docs/src/components/Nav.astro b/docs/src/components/Nav.astro index a9475e63..b9bfda7d 100644 --- a/docs/src/components/Nav.astro +++ b/docs/src/components/Nav.astro @@ -9,8 +9,7 @@ import { links } from '../data/site'; @@ -26,5 +25,4 @@ import { links } from '../data/site'; ul { display: flex; gap: 20px; list-style: none; margin: 0; padding: 0; font-family: var(--display); font-size: 14px; flex-wrap: wrap; } a:not(.logo) { color: var(--muted); } @media (max-width: 760px) { ul { gap: 14px; } } - @media (max-width: 520px) { .hide-sm { display: none; } } diff --git a/docs/src/content/docs/docs/index.md b/docs/src/content/docs/docs/index.md index b5ac67a0..4b11ad8f 100644 --- a/docs/src/content/docs/docs/index.md +++ b/docs/src/content/docs/docs/index.md @@ -17,6 +17,30 @@ Nothing runs that you didn't approve, and nothing is marked done that osq didn't Run `osq` with no command at any point to see what needs you. +## What you get + +- **Approval before anything runs.** Every plan waits for you. Before you approve, osq shows the goal, each task, and flags like a shared file or a removed requirement. +- **Tests decide, not the agent.** osq runs each task's tests in its own process after the agent exits. Only a pass marks the task done, and the whole change must stay green after every task. +- **Agents stay in their lane.** Each task lists the files it may change. An agent that edits anything else, touches git, or quietly changes a test fails the task. +- **Retries that learn.** A failed task gets one more try with the exact failure in its prompt. If it fails the same way twice, osq stops and asks you to adjust the plan. +- **Each change on its own branch.** Changes run in their own git worktree. When you're happy, `osq land` merges it into your default branch. osq never rewrites history. +- **Approve from your phone.** Run osq on a server with `osq server start` and it keeps working while you're away. A dashboard shows what needs you, and approve, retry and land are one tap. +- **Specs that stay true.** Your project keeps living specs of what it does. They only change when an approved change lands, so they always describe the code you have. +- **A full audit trail.** Every spawn, test run, retry and decision is an append-only file in your repo. `osq report` and `osq query` answer why a task failed last month. +- **A second opinion.** An optional reviewer on a different model checks the finished change against its specs before it lands. +- **Bring your own agent.** osq drives Claude Code, Codex CLI, OpenCode, Pi or Antigravity. Swap it per project in one line of config. + +## Coming in the rebuild + +osq is being rebuilt as a fast single binary. These land with it: + +- **Many changes at once.** A queue that runs as many agents as there is ready work, and explains why anything is waiting. +- **Agents set up in a UI.** Pick the agent and model for each role, from planner to reviewer, without editing config by hand. +- **Spend limits.** Hard caps per agent, model and day, so a runaway task can't eat your budget. +- **Choose how much is automatic.** Decide per step whether you approve, approve only when green, or let osq go ahead. Core checks stay on. +- **One install.** A single download with no Node.js needed, for laptops and servers alike. +- **Self-improving process.** osq studies its own history and proposes better plans and checks, always as a change you approve. + ## Where things live Everything osq knows lives in plain files in your repository: diff --git a/docs/src/data/features.ts b/docs/src/data/features.ts deleted file mode 100644 index 22dee209..00000000 --- a/docs/src/data/features.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Feature cards. `available` lists only what ships in the current npm release; - * `roadmap` is what the Rust rebuild adds. Move a card across when it ships, never before. - */ -export interface Feature { - title: string; - body: string; - /** the command, file or term a reader can look up, shown in mono */ - ref?: string; -} - -export const available: Feature[] = [ - { - title: 'Approval before anything runs', - body: 'Every plan waits for you. Before you approve, osq shows the goal, each task, and flags like a shared file or a removed requirement.', - ref: 'osq approve', - }, - { - title: 'Tests decide, not the agent', - body: "osq runs each task's tests in its own process after the agent exits. Only a pass marks the task done, and the whole change must stay green after every task.", - ref: '.run/done/ · .run/dead/', - }, - { - title: 'Agents stay in their lane', - body: 'Each task lists the files it may change. An agent that edits anything else, touches git, or quietly changes a test fails the task.', - ref: 'scope · tests.modify', - }, - { - title: 'Retries that learn', - body: 'A failed task gets one more try with the exact failure in its prompt. If it fails the same way twice, osq stops and asks you to adjust the plan.', - ref: 'stuck · steering', - }, - { - title: 'Each change on its own branch', - body: "Changes run in their own git worktree. When you're happy, one command merges it into your default branch. osq never rewrites history.", - ref: 'osq land', - }, - { - title: 'Approve from your phone', - body: "Run osq on a server and it keeps working while you're away. A dashboard shows what needs you, and approve, retry and land are one tap.", - ref: 'osq server start', - }, - { - title: 'Specs that stay true', - body: 'Your project keeps living specs of what it does. They only change when an approved change lands, so they always describe the code you have.', - ref: 'openspec/specs/', - }, - { - title: 'A full audit trail', - body: 'Every spawn, test run, retry and decision is an append-only file in your repo. Ask why a task failed last month with a single query.', - ref: 'osq report · osq query', - }, - { - title: 'A second opinion', - body: 'An optional reviewer on a different model checks the finished change against its specs before it lands.', - ref: 'validator', - }, -]; - -export const roadmap: Feature[] = [ - { title: 'Many changes at once', body: 'A queue that runs as many agents as there is ready work, and explains why anything is waiting.' }, - { title: 'Agents set up in a UI', body: 'Pick the agent and model for each role, from planner to reviewer, without editing config by hand.' }, - { title: 'Spend limits', body: "Hard caps per agent, model and day, so a runaway task can't eat your budget." }, - { title: 'Choose how much is automatic', body: 'Decide per step whether you approve, approve only when green, or let osq go ahead. Core checks stay on.' }, - { title: 'One install', body: 'A single download with no Node.js needed, for laptops and servers alike.' }, - { title: 'Self-improving process', body: 'osq studies its own history and proposes better plans and checks, always as a change you approve.' }, -]; diff --git a/docs/src/pages/index.astro b/docs/src/pages/index.astro index e9850ee8..0c7fbbbd 100644 --- a/docs/src/pages/index.astro +++ b/docs/src/pages/index.astro @@ -4,7 +4,6 @@ import Hero from '../components/Hero.astro'; import Why from '../components/Why.astro'; import HowItWorks from '../components/HowItWorks.astro'; import SystemMap from '../components/SystemMap.astro'; -import Features from '../components/Features.astro'; import GetStarted from '../components/GetStarted.astro'; --- @@ -16,6 +15,5 @@ import GetStarted from '../components/GetStarted.astro'; -