> ## Documentation Index
> Fetch the complete documentation index at: https://acai.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Acai.sh

> A toolkit for spec-driven software development. Stop writing prompts, start writing specs. Ship better quality software and minimize slop.

export const AnimatedTerminal = () => {
  const [messages, setMessages] = useState([]);
  const [inputText, setInputText] = useState("");
  const [stepIndex, setStepIndex] = useState(0);
  const [phase, setPhase] = useState("idle");
  const chatRef = useRef(null);
  const timeoutsRef = useRef([]);
  const STEPS = [{
    type: "user-typing",
    content: "I made changes to the spec - @animated-terminal.feature.yaml - please run `acai skill` to learn spec-driven development."
  }, {
    type: "tool-command",
    content: "acai skill",
    output: ["Skill loaded: acai", "Summary: specs first, then code, then status updates.", "Guidance: use ACIDs to keep implementation and review aligned."]
  }, {
    type: "agent-message",
    content: "I will read the spec to see what changed, then re-align the code to spec."
  }, {
    type: "tool-command",
    content: "acai feature animated-terminal",
    output: ["Feature: animated-terminal", "Status: 9 implemented, 1 remaining.", "Next focus: STORY.3-1"]
  }, {
    type: "agent-message",
    content: "I see the new requirement, animated-terminal.STORY.3-1, which adds a demonstration of the `set-status` command. I'll align the code now."
  }, {
    type: "system-status",
    content: "📝 Writing code..."
  }, {
    type: "agent-message",
    content: "Now I will make sure all requirements are well tested."
  }, {
    type: "system-status",
    content: "📝 Writing code..."
  }, {
    type: "agent-message",
    content: "Done. The animation component and ACID references are now aligned with the spec."
  }, {
    type: "user-typing",
    content: "Nice job. I'm testing it now. It looks acceptable. Mark all requirements as accepted."
  }, {
    type: "tool-command",
    content: 'acai set-status \'{"animated-terminal.STORY.3-1":"accepted"}\'',
    output: ["Requirements updated: 1"]
  }, {
    type: "agent-message",
    content: "Done. All requirements are now accepted. What should we work on next?"
  }];
  const clearAllTimeouts = () => {
    timeoutsRef.current.forEach(clearTimeout);
    timeoutsRef.current = [];
  };
  const addTimeout = (fn, delay) => {
    const id = setTimeout(fn, delay);
    timeoutsRef.current.push(id);
    return id;
  };
  useEffect(() => {
    return () => clearAllTimeouts();
  }, []);
  useEffect(() => {
    if (chatRef.current) {
      const {scrollTop, scrollHeight, clientHeight} = chatRef.current;
      const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
      if (isNearBottom) {
        chatRef.current.scrollTop = scrollHeight;
      }
    }
  }, [messages]);
  useEffect(() => {
    if (stepIndex >= STEPS.length) {
      setPhase("completed");
      return;
    }
    const step = STEPS[stepIndex];
    if (step.type === "user-typing") {
      setPhase("typing");
      let currentText = "";
      const fullText = step.content;
      let charIdx = 0;
      const typeChar = () => {
        if (charIdx < fullText.length) {
          currentText += fullText[charIdx];
          setInputText(currentText);
          charIdx++;
          addTimeout(typeChar, 45);
        } else {
          addTimeout(() => {
            setMessages(prev => [...prev, {
              type: "user",
              content: fullText
            }]);
            setInputText("");
            setStepIndex(prev => prev + 1);
          }, 600);
        }
      };
      addTimeout(typeChar, 250);
    } else if (step.type === "agent-message") {
      setPhase("agent-thinking");
      addTimeout(() => {
        setMessages(prev => [...prev, {
          type: "agent",
          content: step.content
        }]);
        addTimeout(() => {
          setStepIndex(prev => prev + 1);
        }, 1700);
      }, 700);
    } else if (step.type === "tool-command") {
      setPhase("tool-running");
      addTimeout(() => {
        const id = Math.random().toString(36).substr(2, 9);
        setMessages(prev => [...prev, {
          type: "tool",
          content: step.content,
          status: "running",
          id
        }]);
        addTimeout(() => {
          setMessages(prev => prev.map(m => m.id === id ? {
            ...m,
            status: "done",
            output: step.output
          } : m));
          addTimeout(() => {
            setStepIndex(prev => prev + 1);
          }, 900);
        }, 1400);
      }, 500);
    } else if (step.type === "system-status") {
      setPhase("system-working");
      addTimeout(() => {
        setMessages(prev => [...prev, {
          type: "system",
          content: step.content
        }]);
        addTimeout(() => {
          setStepIndex(prev => prev + 1);
        }, 2200);
      }, 600);
    }
  }, [stepIndex]);
  const renderMessage = (msg, idx) => {
    if (msg.type === "user") {
      return <div key={idx} className="mb-4">
                    <div className="flex items-start">
                        <span className="text-[#58a6ff] mr-2 font-bold opacity-70">
                            ➜
                        </span>
                        <span className="text-[#e6edf3] font-mono leading-relaxed">
                            {msg.content}
                        </span>
                    </div>
                </div>;
    }
    if (msg.type === "agent") {
      return <div key={idx} className="mb-4">
                    <div className="text-[#e6edf3] font-mono leading-relaxed whitespace-pre-wrap">
                        {msg.content}
                    </div>
                </div>;
    }
    if (msg.type === "system") {
      return <div key={idx} className="mb-4 ml-2 italic text-[#484f58] font-mono text-xs animate-pulse">
                    {msg.content}
                </div>;
    }
    if (msg.type === "tool") {
      return <div key={idx} className="mb-4 ml-2 pl-3 border-l-2 border-[#30363d] bg-[#161b22]/50 py-2 rounded-r">
                    <div className="flex items-center text-xs font-mono">
                        <span className={msg.status === "running" ? "text-[#febc2e] mr-2 animate-pulse" : "text-[#28c840] mr-2"}>
                            {msg.status === "running" ? "⚡" : "✓"}
                        </span>
                        <span className="text-[#ffb11f] font-bold">acai </span>
                        <span className="text-[#e6edf3] ml-1">
                            {msg.content.replace("acai ", "")}
                        </span>
                        {msg.status === "running" && <span className="ml-2 text-[#484f58] italic text-[10px]">
                                running...
                            </span>}
                    </div>
                    {msg.status === "done" && msg.output && <div className="mt-2 text-[11px] text-[#8b949e] font-mono whitespace-pre-line leading-relaxed border-t border-[#30363d]/50 pt-1">
                            {msg.output.join("\n")}
                        </div>}
                </div>;
    }
    return null;
  };
  return <div className="w-full max-w-2xl mx-auto my-8 overflow-hidden rounded-xl border border-[#30363d] shadow-2xl bg-[#0d1117] font-mono text-sm h-[420px] flex flex-col" style={{
    height: "420px",
    minHeight: "420px",
    contain: "layout paint"
  }}>
            <style>{`
        @keyframes cursorBlink {
          0%, 100% { opacity: 1; }
          50% { opacity: 0; }
        }
        .animate-cursor {
          animation: cursorBlink 1s infinite;
        }
        .chat-history::-webkit-scrollbar {
          width: 8px;
        }
        .chat-history::-webkit-scrollbar-track {
          background: transparent;
        }
        .chat-history::-webkit-scrollbar-thumb {
          background: #30363d;
          border-radius: 4px;
        }
        .chat-history::-webkit-scrollbar-thumb:hover {
          background: #484f58;
        }
      `}</style>

            {}
            <div className="flex items-center px-4 py-3 bg-[#161b22] border-b border-[#30363d] select-none">
                <div className="flex gap-2 mr-4">
                    <div className="w-3 h-3 rounded-full bg-[#ff5f57]" />
                    <div className="w-3 h-3 rounded-full bg-[#febc2e]" />
                    <div className="w-3 h-3 rounded-full bg-[#28c840]" />
                </div>
                <div className="text-xs text-[#8b949e] font-medium flex-grow text-center pr-12">
                    acai — OpenCode Zen
                </div>
            </div>

            {}
            <div ref={chatRef} className="chat-history flex-grow overflow-y-auto p-4 scroll-smooth">
                {messages.map((msg, i) => renderMessage(msg, i))}
            </div>

            {}
            <div className="bg-[#161b22] px-4 py-1 border-t border-[#30363d] flex justify-between items-center text-[10px] text-[#8b949e]">
                <div className="flex items-center gap-3">
                    <span className="flex items-center gap-1">
                        <span className="w-2 h-2 rounded-full bg-[#58a6ff]" />
                        Agent: taskmaster
                    </span>
                    <span>Model: Big Pickle</span>
                </div>
            </div>

            {}
            <div className="bg-[#0d1117] p-3 border-t border-[#30363d] border-l-[3px] border-l-[#58a6ff]">
                <div className="flex items-center font-mono">
                    <span className="text-[#e6edf3] break-all">
                        {inputText}
                    </span>
                    {}
                    <span className="w-2 h-4 bg-[#58a6ff] ml-1 animate-cursor" />
                </div>
            </div>

            {}
            <div className="bg-[#0d1117] px-4 py-2 flex justify-end gap-4 text-[10px] text-[#484f58] uppercase tracking-wider font-bold">
                <span>tab agents</span>
                <span>ctrl+p commands</span>
            </div>
        </div>;
};

<Callout icon="grape" iconType="regular" color="#930d7a">
  **ACAI** - **A**cceptance **C**riteria for **AI**
</Callout>

<AnimatedTerminal />

***

## What is it?

Open-source tools to assist with spec-driven software development.

* A **simple spec format** called `feature.yaml`, that keeps your requirements organized and traceable.
* A **CLI** for you or your LLM to push and pull specs, requirements, coverage, status and comments.
* A **server and dashboard** to facilitate QA, code review, and collaboration.
* A **process and convention** for identifying spec requirements from within your code comments and tests, to create a searchable, greppable connection between the two.

***

## Very Quick Start

Acai can be incrementally adopted into any existing project.

```sh theme={null}
npx @acai.sh/cli --help
```

<Steps>
  <Step title="Specify">
    Write requirements and acceptance criteria in `feature.yaml` spec files.
  </Step>

  <Step title="Push">
    Run `acai push` to extract the specs and push them to an Acai Server.
  </Step>

  <Step title="Ship">
    Give your agents access to the cli so they can implement, review, self-assign and share status updates.
  </Step>
</Steps>

<Prompt
  description={`Dear Claude,

I hope this email finds you well.  

Please run \`npx @acai.sh/cli skill\`.

This will teach you everything you need to know about our process for spec-driven development. Then, proceed to plan and implement the features specified in the spec I wrote.

Love,  
[your-name]
`}
>
  Dear Claude,
  I hope this email finds you well.

  Please run \`npx @acai.sh/cli skill\`.

  This will teach you everything you need to know about our process for spec-driven development. Then, proceed to plan and implement the features specified in the spec I wrote.

  Love,\
  \[your-name]
</Prompt>

This unlocks a spec-oriented workflow that **can replace GitHub and Linear tickets**. You, your agent swarm, and your product manager can stay in-sync while rapidly iterating on specs and their implementations.

***

<Frame caption="See implementation status at a glance">
  <img src="https://mintcdn.com/acai/LaJ29xrIxyCm5Sr_/images/desktop-feature-impl-view.png?fit=max&auto=format&n=LaJ29xrIxyCm5Sr_&q=85&s=d39e3b8b4643b0dbe281efa19c454146" alt="Dashboard screenshot showing progress on implementation of a feature spec" width="2400" height="1600" data-path="images/desktop-feature-impl-view.png" />
</Frame>

***

# Key Features

### Go beyond test coverage

Have all requirements been implemented?\
Do all implementations have tests?\
Have humans QA'd and accepted the implementation?

### Collaborative & multiplayer

Invite your team to a shared Acai dashboard, or create access tokens for AI agents and CLI access to the server.

<Frame caption="Useful tools for collaboration between humans or agents">
  <img src="https://mintcdn.com/acai/VyQCNCTaoVIJrncU/images/comment-example.png?fit=max&auto=format&n=VyQCNCTaoVIJrncU&q=85&s=3b2236b085718c37f130fe0c0ba18529" alt="Example of a comment and a rejected requirement status" width="608" height="393" data-path="images/comment-example.png" />
</Frame>

Track progress from spec, to implementation, to agent review, to human acceptance:

`No status` -> `Assigned` -> `Completed` -> `Accepted`

```md Agent Code Reviewer theme={null}
I've reviewed profile.MENU.2 and profile.AUTH.1
I see no issues. Unit tests are satisfactory.
I am marking the requirement as `COMPLETED`...
I am merging the changes to the feature branch...
```

### Any git workflow

Acai works with monorepos and polyrepos or any combination.

* Monorepos containing many products with many specs and many branches (`main`, `dev` etc.)
* Polyrepos where a feature may touch several branches on several git repos (frontend, backend, microservice etc.)

<Frame caption="Acai handles the complex journey of a feature and it's spec, even across repos and branches">
  <img src="https://mintcdn.com/acai/LaJ29xrIxyCm5Sr_/images/desktop-feature-view.png?fit=max&auto=format&n=LaJ29xrIxyCm5Sr_&q=85&s=4186752eb6f5df88ca91f1175c767f12" alt="Example dashboard showing details about a specific feature requirement" width="2400" height="1600" data-path="images/desktop-feature-view.png" />
</Frame>

### Grab context

Acai introduces the ACID system for referencing your spec from anywhere in your repo. This makes it easy to see exactly where a requirement has been implemented or tested, so you (or your agent) can improve test coverage, QA, and react to ever-changing requirements.

```sh theme={null}
acai feature my-feature-name --json --include-refs
```

### Review code more effectively

Stop trying to read huge diffs from top to bottom. Instead, use the acai dashboard and jump straight to the functions, tests or comments that reference each requirement in your spec. Start by reviewing the requirements that matter most; auth, security, performance, and user happiness.

***

## Simple example

At the top of the page you can see a fun terminal animation we created as a demo of Acai in action. Below you can see the spec, code, and tests for that animation.

<CodeGroup>
  ```yaml feature.yaml theme={null}
  feature:
      name: animated-terminal
      product: docs
      description: |
          A mock terminal animation for our docs site.
          It demonstrates how an AI agent uses the acai toolkit to work on a software project.
          🥚 In the example, it is self-referentially building itself, from this very same spec.

  components:
      FRAME:
          name: Terminal frame and container
          description: UI container that looks like a terminal window on a Mac with a TUI instance running inside it
          requirements:
              1: Renders scrollable mock chat history
              1-1: Does not auto-scroll unless user is already at the bottom of scroll container
              2: Renders mock text input
              2-1: Does not accept actual user inputs (no typing, no buttons)

      STORY:
          name: Rough storyboard for the animation
          requirements:
              1: Animation starts with user typing "I've made some changes to the spec @animated-terminal.feature.yaml - please run `acai skill` to learn spec-driven development."
              2: Shows the agent run `acai skill`. Agent says "Got it. I will look to see what changed, then re-align the code to spec."
              3: Concludes with user typing "Nice job. I'm looking at it right now. It's flawless. Go ahead and mark all requirements as accepted."
              3-1: Agent runs `acai set-status` and marks all requirements as accepted.
              4: The animation stops after the final step and does not loop.

  constraints:
      DEV:
          requirements:
              1: Must be a single, self-contained standalone react component (a single export, with no imports)
  ```

  ```tsx animated-terminal.jsx theme={null}
  // animated-terminal.DEV.1 - No imports
  export const AnimatedTerminal = () => {
    const chatRef = useRef(null);

    useEffect(() => {
      // animated-terminal.FRAME.1-1 - autoscroll
      const el = chatRef.current;
      if (el) {
        // Use a threshold to detect if the user is "near" the bottom
        const isNearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 100;
        if (isNearBottom) el.scrollTop = el.scrollHeight;
      }
    }, [messages]);

    return (
      {/* animated-terminal.FRAME.1 */}
      <div ref={chatRef} className="chat-history flex-grow overflow-y-auto p-4 scroll-smooth">
        {messages.map((msg, i) => renderMessage(msg, i))}
      </div>
    );
  };
  ```

  ```ts animated-terminal.test.ts theme={null}
  // When we tag tests with requirement IDs, the acai.sh dashboard automatically tracks coverage!
  describe("Constraints", () => {
      // animated-terminal.DEV.1 - Dev constraint
      it("self-contained standalone component (no imports)", () => {});
  });

  describe("UI rendering", () => {
      // animated-terminal.FRAME.1
      it("renders scrollable mock chat history", () => {});
      // animated-terminal.FRAME.1-1
      it("auto-scrolls ONLY if user is at bottom threshold", () => {});
      // animated-terminal.FRAME.2
      it("renders mock text input", () => {});
      // animated-terminal.FRAME.2-1
      it("ignores user typing and button interactions", () => {});
  });
  ```
</CodeGroup>

As shown above, acai gives each requirement an ID that is unique, stable, and readable. These IDs should be referenced liberally (in tests, comments, and other specs), and are extracted from code with a single command.

The idea is when your spec changes, your code must change too.

## Why acai?

<AccordionGroup>
  <Accordion title="Greppable, traceable">
    LLM agents love Acai IDs. They make it easy to pull in context, find references, and trace the *intent* of an implementation or test.
  </Accordion>

  <Accordion title="Stable, durable">
    Stop losing progress when agents go off the rails. Your spec is the source
    of truth, even as complexity grows and requirements evolve.
  </Accordion>

  <Accordion title="Rapidly iterate">
    Quickly prototype, try throwaway implementations, and solidify the spec
    before touching production code. The result is less time prompting, and a
    better end result.
  </Accordion>

  <Accordion title="No tedium">
    In the past, this level of specification rigour was painful for human teams. Today, for an LLM with access to Acai, it's painless.
  </Accordion>
</AccordionGroup>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="bot" href="/quickstart">
    Get up and running. Write and implement your first spec.
  </Card>

  <Card title="Writing Specs" icon="pen-tool" href="/spec-driven-development">
    Introduction to acai specs, and best practices for spec-first software.
  </Card>
</CardGroup>
