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.
97 lines • 5.39 kB
JavaScript
/**
* The root `tickets/` directory (#629): a plain repo convention where The Framework
* keeps its human-facing roadmap files, rather than hiding them in a proprietary
* `.the-framework/` dir. It sits beside conventions like the `knowledge-base/` docs. Since
* #682 moved the backlog out to a root `TODO_AGENTS.md`, this directory holds only
* ticket files (`<DATE>_<SLUG>.md`).
*/
export const TICKETS_DIR = 'tickets';
/**
* The flat, durable backlog/roadmap file — the confirmed-task queue (the "AI task queue"
* the repo-context (#683) fragment names). Lives at the repo root as `TODO_AGENTS.md` (#682):
* moved out of `tickets/` so that directory holds only tickets. This is the file an agent
* drains and the dashboard surfaces — the only backlog kind since the session-scoped
* `TODO_<slug>.agent.md` files were retired (#1369).
*/
export const FLAT_TODO_FILE = 'TODO_AGENTS.md';
// The format specs for {@link TICKETS_DIR} and {@link FLAT_TODO_FILE} (#684/#880) are no longer
// named here as a path. They used to be the two `node_modules/@gemstack/the-framework/prompts/*.md`
// pointers the repo-context (#683) fragment handed the agent, which only resolve when the framework is a
// root dependency of the repo it works on — so the agent could not open them and both files drifted
// from the format (#1163). The spec content now travels in the system channel itself; see
// `CONTEXT_FORMATS` in system-prompt.ts. The priority sections that spec describes need no parser
// support: `parseTodoEntries` skips headings and returns entries in file order, so a
// priority-sorted file drains in priority order.
/**
* Which `## Priority N` section a ticket's own `priority:` key earns it, translating the ticket
* format's words into the backlog format's numbers (#1164).
*
* The two formats were specified separately and never mapped onto each other, so a ticket put on
* the queue had no ranked place to land. The scale's ends are reserved by `todo_format.md`
* itself: 10 is "rarely used, critical production bugs", which nothing queued by a click should
* claim, and 0 is "only if capacity", which is a decision about the ticket rather than a
* translation of it. An unmarked ticket sits in the middle, which is what the ticket format
* saying `priority:` is optional has to mean.
*
* A number is taken at its word, because that is what the ticket format specifies
* (`Priority: 10-0`) and what every ticket writes — the word spellings (`urgent`/`high`/`low`)
* once mapped here were never part of the format and are no longer read. Out-of-range and
* fractional values are not clamped into something plausible: they are not a priority on this
* scale, and inventing one hides the typo.
*/
export function todoPriorityForTicket(priority) {
const written = priority?.trim();
if (written !== undefined && /^\d+$/.test(written)) {
const value = Number(written);
if (value >= 0 && value <= 10)
return value;
}
return 5;
}
/**
* The ticket a queue entry came from, or `undefined` for an entry that is just text (#1117).
*
* Queueing a ticket writes the entry as a markdown link back to it (#1164), so the identity the
* queue used to drop is already on the line — this is the read side of that write. Only a link
* into {@link TICKETS_DIR} counts, and only one whose target stays inside it: the result names a
* file a reader will go and open, so an entry linking anywhere else is treated as plain text
* rather than followed. A relative segment, an absolute path, a URL, or a nested directory all
* fail that test.
*/
export function ticketFromQueueEntry(entry) {
const target = /\]\(([^)\s]+)\)/.exec(entry)?.[1];
return target && isTicketPath(target) ? target : undefined;
}
/**
* Whether a string names a ticket file: `tickets/<name>.md`, and nothing else (#1117).
*
* The one gate for both ends of the link — what a queue entry is read as, and what the `--ticket`
* flag is allowed to record — because the result is a path the dashboard renders and a reader
* opens. A relative segment, an absolute path, a URL, a dotfile, or anything nested deeper all
* fail it, so no caller has to think about the difference.
*/
export function isTicketPath(path) {
if (!path.startsWith(`${TICKETS_DIR}/`))
return false;
const file = path.slice(TICKETS_DIR.length + 1);
return file.endsWith('.md') && !file.includes('/') && !file.startsWith('.');
}
/**
* The GitHub issue a ticket tracks, as a `#42` reference, or `undefined` when it tracks none.
*
* Read off the ticket's `GitHub: [#42](…/issues/42)` header line (`prompts/ticketing_format.md`).
* The number comes from the URL when there is one — the label is display text, the URL is the
* identity — with the label's own `#42` as the fallback for a hand-written line.
*
* This is what lets a merge close the ticket's issue (#1334): the reference rides the PR title
* as `(fix #42)`, and GitHub's squash subject inherits the title.
*/
export function ticketIssueRef(md) {
const line = md.split('\n').find(l => l.trim().toLowerCase().startsWith('github:'));
if (!line)
return undefined;
const url = /\((?:[^)]*\/)?(?:issues|pull)\/(\d+)\)/.exec(line);
const match = url ?? /#(\d+)/.exec(line);
return match ? `#${match[1]}` : undefined;
}
//# sourceMappingURL=tickets.js.map