framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
93 lines • 5.51 kB
TypeScript
import { type StoreFs } from './store/index.js';
/**
* The committed conversations (#908): the human turns and the agent's replies of a run, kept in
* the Git repo so a clone carries the chat and not just the fact a run happened (#857).
*
* Deliberately not the verbose transcript — #857 leaves the tool-call-level log to the model
* provider, which is also the standing policy in run-store.ts. What lands here is what a person
* would reread: what was asked, and what came back.
*
* One file per run rather than one shared file, because run worktrees are live concurrently and
* each auto-commits its own pending work on teardown; a shared file would be a merge conflict
* every time two runs chatted at once. The run id is the join key back to the `- run:` field
* LOGS.md records (#898), so the committed session list and the committed chat line up.
*
* Pure core over the same {@link StoreFs} seam as logs.ts.
*/
/** The directory, under `.the-framework/`, that holds one markdown file per conversation. */
export declare const CONVERSATIONS_DIR = "conversations";
/**
* The `.the-framework/.gitignore` that keeps run state transient while committing the DB. The
* conversations rules need both entries: the `*` rule makes git skip the directory without ever
* descending into it, so un-ignoring the files alone would never be reached.
*/
export declare const CONVERSATIONS_GITIGNORE = "!conversations/\n!conversations/**\n";
/** Who said it. The transport is {@link ConversationMessage.via}, not this. */
export type ConversationRole = 'user' | 'agent';
/** One turn in a conversation. */
export interface ConversationMessage {
/** ISO timestamp. */
at: string;
role: ConversationRole;
/**
* The surface the turn came through — `dashboard`, `discord`, … Recorded so a conversation
* read back shows where it happened, while this module stays free of any transport.
*/
via: string;
/** The message. Multi-line and kept that way; only line-leading markers are escaped. */
text: string;
}
/**
* Whether a transport name is safe to record. Exported because #917 lets a surface name itself
* over the control channel, so the name now arrives from outside this module: a `via` carrying
* the heading separator, a newline or a `#` would forge structure in a file whose entries are
* line-parsed, exactly the way #897 forged a LOGS.md title. Checked at the boundary rather than
* trusted, and the same predicate {@link parseConversation} reads back with.
*/
export declare function isSafeVia(via: unknown): via is string;
/**
* Escape a message body so it cannot forge structure (#897's threat model, applied to a
* transcript). LOGS.md collapses free text to one line, which is right for a line-parsed record
* and wrong here: a multi-paragraph reply has to stay readable in a `git diff`. So the text stays
* as written and only a line's leading `#` or `\` is escaped, which is enough — an entry is only
* ever started by a line beginning `## `. Reversed by {@link unescapeBody}.
*/
export declare function escapeBody(text: string): string;
/** Reverse {@link escapeBody}. */
export declare function unescapeBody(text: string): string;
/** The directory holding every conversation under `cwd`. */
export declare function conversationsDir(cwd: string): string;
/**
* One conversation's path. `undefined` for an unsafe run id: the id reaches this from a run
* store and, once #680 lands, indirectly from a chat surface, so it is checked rather than
* trusted into a path.
*/
export declare function conversationPath(cwd: string, runId: string): string | undefined;
/** Markdown for one message, starting at `## ` (no file header, no blank lines around it). */
export declare function renderMessage(message: ConversationMessage): string;
/**
* Parse every message out of the markdown, in file order (append order, so oldest-first — a
* transcript reads forwards, unlike the newest-first project log). Forgiving: a malformed or
* torn message is skipped, never thrown.
*/
export declare function parseConversation(md: string): ConversationMessage[];
/**
* Make sure `.the-framework/.gitignore` un-ignores the conversations dir, returning whether it
* wrote. Done lazily on append rather than only at install time: the seeded ignore file is
* written once and only when absent (`install.ts`), so every repo activated before this feature
* still carries the old three-line allow-list and would silently drop its own conversations.
*
* Only ours is upgraded — a file we do not recognize is left alone rather than appended to.
*/
export declare function ensureConversationsIgnored(cwd: string, fs?: StoreFs): Promise<boolean>;
/**
* Append one message to `.the-framework/conversations/<runId>.md`, creating the dir, the
* one-time file header, and the ignore rule when absent. A raw write (may reject); the caller
* decides best-effort. A no-op for an unsafe run id.
*/
export declare function appendMessage(cwd: string, runId: string, message: ConversationMessage, fs?: StoreFs): Promise<void>;
/** Read one conversation, oldest-first. Missing file (or unsafe id) yields `[]`. */
export declare function readConversation(cwd: string, runId: string, fs?: StoreFs): Promise<ConversationMessage[]>;
/** The run ids that have a committed conversation, sorted. Missing dir yields `[]`. */
export declare function listConversations(cwd: string, fs?: StoreFs): Promise<string[]>;
//# sourceMappingURL=conversations.d.ts.map