From 0871d141b15e800c5269a1754f5fa89e67f5bba9 Mon Sep 17 00:00:00 2001 From: Suleiman Shahbari Date: Sat, 18 Jul 2026 18:30:56 +0300 Subject: [PATCH] feat(framework): ship the ticket-format spec in the package, not materialized (#684) Per Rom's #674 call, the ticketing_format.md spec ships inside the package (added to the files allowlist) and the run-start context fragment references it by its node_modules path, so the format versions with the package instead of being materialized into each repo. Removes materializeTicketingFormat + its install call and export. Also adds the revised #684 optional priority: and topics: fields to the ticket body, and lowercases the [optional: ...] hint. Refs #684, #674 --- .changeset/ticketing-format-spec.md | 2 +- packages/framework/package.json | 3 ++- .../framework/prompts/ticketing_format.md | 7 +++-- packages/framework/src/index.ts | 1 - packages/framework/src/install.ts | 6 ++--- packages/framework/src/system-prompt.ts | 4 +-- packages/framework/src/tickets.test.ts | 27 +++++++------------ packages/framework/src/tickets.ts | 25 +++++------------ packages/framework/src/todo-loop.ts | 2 +- 9 files changed, 29 insertions(+), 48 deletions(-) diff --git a/.changeset/ticketing-format-spec.md b/.changeset/ticketing-format-spec.md index beab3a25..73c4f430 100644 --- a/.changeset/ticketing-format-spec.md +++ b/.changeset/ticketing-format-spec.md @@ -2,4 +2,4 @@ '@gemstack/framework': minor --- -Add the ticket-format spec (#684): a static `.the-framework/ticketing-format.md` describing the `tickets/_.md` and `.spike.md` file shapes, materialized on install beside the quality presets so an agent can open it by path. +Add the ticket-format spec (#684): `ticketing_format.md` describes the `tickets/_.md` and `.spike.md` file shapes (including the optional `priority:` and `topics:` fields). Per #674 it ships inside the package and the run-start context fragment references it by its `node_modules` path, so the format versions with the package rather than being materialized into each repo. diff --git a/packages/framework/package.json b/packages/framework/package.json index d21dead4..592a9960 100644 --- a/packages/framework/package.json +++ b/packages/framework/package.json @@ -28,7 +28,8 @@ "node": ">=22.12.0" }, "files": [ - "dist" + "dist", + "prompts/ticketing_format.md" ], "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/framework/prompts/ticketing_format.md b/packages/framework/prompts/ticketing_format.md index c4e034c7..5f878342 100644 --- a/packages/framework/prompts/ticketing_format.md +++ b/packages/framework/prompts/ticketing_format.md @@ -6,6 +6,9 @@ DATE: yyyy-mm-dd SLUG: succinct kebab-case slug of the ticket title Body: ```md +priority: low/medium/high/urgent [optional] +topics: [list-of-topics] [optional] + # Ticket title ## TLDR @@ -16,7 +19,7 @@ Body: ... -[Optional: more info (any heading and format you want)] +[optional: more info (any heading and format you want)] ``` ## tickets/_.spike.md @@ -35,7 +38,7 @@ Body: ... -[Optional: more info (any heading and format you want)] +[optional: more info (any heading and format you want)] ``` Typical spike content: diff --git a/packages/framework/src/index.ts b/packages/framework/src/index.ts index ffef1388..6ac50f19 100644 --- a/packages/framework/src/index.ts +++ b/packages/framework/src/index.ts @@ -276,7 +276,6 @@ export { LEGACY_TODO_FILE, TICKETS_DIR, TICKETING_FORMAT_FILE, - materializeTicketingFormat, DEFAULT_MAX_TODO_ITEMS, type TodoBacklog, type TodoLoopOptions, diff --git a/packages/framework/src/install.ts b/packages/framework/src/install.ts index 32213465..3f844fd2 100644 --- a/packages/framework/src/install.ts +++ b/packages/framework/src/install.ts @@ -3,7 +3,6 @@ import { nodeGitRunner, type GitRunner } from './project.js' import { logsPath, LOGS_HEADER, THE_FRAMEWORK_DIR, gitignorePath, LOGS_GITIGNORE } from './logs.js' import { nodeStoreFs, type StoreFs } from './store/index.js' import { materializePresets } from './presets.js' -import { materializeTicketingFormat } from './tickets.js' /** * Install/activate a repo for The Framework (#391): create the @@ -64,9 +63,8 @@ export async function installProject(cwd: string, deps: InstallDeps = {}): Promi // of git (only LOGS.md is committed), so they are regenerated on install and track the // installed framework version rather than going stale in the repo's history. await materializePresets(cwd, fs) - // The ticket-format spec rides along the same way (#684): gitignored, refreshed on - // install, so the #683 context pointer to it resolves to a real file. - await materializeTicketingFormat(cwd, fs) + // The ticket-format spec is NOT materialized (#674): it ships inside the package and the + // #683 context fragment points at its node_modules path, so it versions with the package. await git(['add', '-A'], cwd) await git(['commit', '-m', '[The Framework] install The Framework'], cwd) diff --git a/packages/framework/src/system-prompt.ts b/packages/framework/src/system-prompt.ts index 286eea71..5f460b9c 100644 --- a/packages/framework/src/system-prompt.ts +++ b/packages/framework/src/system-prompt.ts @@ -114,7 +114,7 @@ export const BUSINESS_KNOWLEDGE_DOCS: readonly ContextDoc[] = [DECISIONS_DOC, KN * {@link systemPromptBlock} renders as the `Context:` bullets. A superset of * {@link BUSINESS_KNOWLEDGE_DOCS}: it adds `GOAL.md` and the two roadmap/queue pointers the * agent reads but does *not* fold knowledge back into — `tickets/**.md` (the potential work, - * whose file shape is `.the-framework/ticketing-format.md`, #684) and the `TODO_AGENTS.md` + * whose file shape is the packaged `ticketing_format.md` spec, #684/#674) and the `TODO_AGENTS.md` * task queue. Repo-root paths, because that is the agent's cwd. README is left out: a repo's * own `README.md` already covers the overview. The ticket-format path is inlined rather than * imported from `tickets.ts`: this module must stay free of `node:fs` (it renders in the @@ -124,7 +124,7 @@ export const CONTEXT_DOCS: readonly ContextDoc[] = [ DECISIONS_DOC, { path: 'GOAL.md', comment: 'the goal of the project (long-term direction, scope, non-scope, ...)' }, KNOWLEDGE_BASE_DOC, - { path: 'tickets/**.md', comment: 'things to potentially work on; format: .the-framework/ticketing-format.md' }, + { path: 'tickets/**.md', comment: 'things to potentially work on; format: node_modules/@gemstack/framework/prompts/ticketing_format.md' }, { path: 'TODO_AGENTS.md', comment: 'the AI task queue' }, ] diff --git a/packages/framework/src/tickets.test.ts b/packages/framework/src/tickets.test.ts index 2177c68e..7788e8f9 100644 --- a/packages/framework/src/tickets.test.ts +++ b/packages/framework/src/tickets.test.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'node:assert' import { test } from 'node:test' -import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises' +import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { @@ -11,7 +11,6 @@ import { TICKETS_DIR, TICKETING_FORMAT_FILE, findFlatTodo, - materializeTicketingFormat, } from './tickets.js' import { TICKETING_FORMAT } from './prompts.generated.js' @@ -23,21 +22,15 @@ test('the flat backlog lives at the root TODO_AGENTS.md, with the legacy locatio assert.equal(LEGACY_TODO_FILE, 'TODO.md') }) -test('the ticket-format spec materializes under .the-framework, not tickets/ (#684)', async () => { - // It is framework-authored, so it lives beside the presets and never masquerades as a ticket. - assert.equal(TICKETING_FORMAT_FILE, '.the-framework/ticketing-format.md') - - const cwd = await mkdtemp(join(tmpdir(), 'framework-ticket-format-')) - try { - await materializeTicketingFormat(cwd) - const written = await readFile(join(cwd, TICKETING_FORMAT_FILE), 'utf8') - assert.equal(written, TICKETING_FORMAT) - // The spec teaches both the ticket and spike file shapes. - assert.ok(written.includes('tickets/_.md')) - assert.ok(written.includes('tickets/_.spike.md')) - } finally { - await rm(cwd, { recursive: true, force: true }) - } +test('the ticket-format spec ships in the package (not materialized), with priority/topics (#684/#674)', () => { + // Per Rom's #674 call it is not written into the repo; it ships inside the package and the + // context fragment reads it by its node_modules path, so the format versions with the package. + assert.equal(TICKETING_FORMAT_FILE, 'node_modules/@gemstack/framework/prompts/ticketing_format.md') + // The spec teaches both file shapes and the revised #684 optional priority/topics fields. + assert.ok(TICKETING_FORMAT.includes('tickets/_.md')) + assert.ok(TICKETING_FORMAT.includes('tickets/_.spike.md')) + assert.ok(TICKETING_FORMAT.includes('priority: low/medium/high/urgent')) + assert.ok(TICKETING_FORMAT.includes('topics:')) }) test('findFlatTodo prefers TODO_AGENTS.md, then legacy tickets/TODO.md, then root TODO.md, else undefined (#682)', async () => { diff --git a/packages/framework/src/tickets.ts b/packages/framework/src/tickets.ts index 710b1744..734aafff 100644 --- a/packages/framework/src/tickets.ts +++ b/packages/framework/src/tickets.ts @@ -1,8 +1,5 @@ import { stat } from 'node:fs/promises' import { join } from 'node:path' -import { TICKETING_FORMAT } from './prompts.generated.js' -import { THE_FRAMEWORK_DIR } from './logs.js' -import { nodeStoreFs, type StoreFs } from './store/index.js' /** * The root `tickets/` directory (#629): a plain repo convention where The Framework @@ -15,23 +12,13 @@ export const TICKETS_DIR = 'tickets' /** * The ticket-format spec (#684): the static reference an agent opens to learn the - * `tickets/_.md` (and `.spike.md`) file shape. Materialized under - * `.the-framework/` beside the presets — not under `tickets/` — because it is - * framework-authored and rides with the installed version, so it must not look like - * a ticket. The #683 context fragment points `tickets/**.md` at this path. + * `tickets/_.md` (and `.spike.md`) file shape. Per Rom's #674 call it ships + * *inside the installed package* rather than being materialized into the repo, so a future + * breaking change to the format rides with the package version instead of going stale in a + * committed file. The package ships `prompts/ticketing_format.md` (see the `files` allowlist), + * so an agent reads it at this cwd-relative path; the #683 context fragment points here. */ -export const TICKETING_FORMAT_FILE = `${THE_FRAMEWORK_DIR}/ticketing-format.md` - -/** - * Write the ticket-format spec to `/.the-framework/ticketing-format.md` so the - * #683 context pointer resolves to a real file (#684). Mirrors {@link materializePresets}: - * gitignored, overwritten on install, tracks the installed framework version rather than - * going stale in the repo. Creates `.the-framework/` if it is missing. - */ -export async function materializeTicketingFormat(cwd: string, fs: StoreFs = nodeStoreFs()): Promise { - await fs.mkdir(join(cwd, THE_FRAMEWORK_DIR)) - await fs.write(join(cwd, TICKETING_FORMAT_FILE), TICKETING_FORMAT) -} +export const TICKETING_FORMAT_FILE = 'node_modules/@gemstack/framework/prompts/ticketing_format.md' /** * The flat, durable backlog/roadmap file — the confirmed-task queue (the "AI task queue" diff --git a/packages/framework/src/todo-loop.ts b/packages/framework/src/todo-loop.ts index 12dfb746..97d0e863 100644 --- a/packages/framework/src/todo-loop.ts +++ b/packages/framework/src/todo-loop.ts @@ -6,7 +6,7 @@ import { requestChoices, runAwaitRounds } from './run.js' import { FLAT_TODO_FILE, findFlatTodo } from './tickets.js' import { createTurnSignalEmitter } from './turn-gate.js' -export { FLAT_TODO_FILE, LEGACY_HYPHEN_TODO_FILE, LEGACY_TICKETS_TODO_FILE, LEGACY_TODO_FILE, TICKETS_DIR, TICKETING_FORMAT_FILE, materializeTicketingFormat } from './tickets.js' +export { FLAT_TODO_FILE, LEGACY_HYPHEN_TODO_FILE, LEGACY_TICKETS_TODO_FILE, LEGACY_TODO_FILE, TICKETS_DIR, TICKETING_FORMAT_FILE } from './tickets.js' /** * The backlog loop (#323): once the main work settles, consume the agent's own