@clemrig/factory 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,8 +33,8 @@ factory doctor
33
33
  index.ts an example workflow of the repository
34
34
  prompts/answer.md the prompt of its answer stage
35
35
  tsconfig.json editor settings only
36
- .gitignore node_modules/, runs/, .env
37
- issues/<slug>/ issue.md, spec.md, plan.md (committed)
36
+ .gitignore node_modules/, runs/, .env, issues/*/spec-answers.json*
37
+ issues/<slug>/ issue.md, spec.md, spec-page.json, plan.md (committed)
38
38
  runs/ one folder per run (ignored)
39
39
  .env optional secrets (ignored)
40
40
  .agents/skills/issue/ the issue skill, read by Codex and Pi
@@ -46,14 +46,14 @@ The global `factory` command uses the nearest `.factory/` holding a `package.jso
46
46
  ## From an idea to a pull request
47
47
 
48
48
  1. **Backlog.** In Claude Code, Codex or Pi, `/issue` asks for a title and a description, writes a draft outside the repository and saves it with `factory run issue-save --issue <slug> --kind issue --file <draft>`. The command checks the draft and writes `.factory/issues/<slug>/issue.md` itself: the agent chooses neither the path nor the format.
49
- 2. **Specifying.** `/issue` again: the agent reads the code, asks questions until it can fill every section without inventing anything, including the issues this one depends on, and proposes the spec; once the operator agrees, `--kind spec` writes `spec.md`. When a run refuses to start because of the spec, `/issue` fixes it from the problems the run listed.
50
- 3. Review the spec, commit it and push it on the main branch.
49
+ 2. **Specifying.** `/issue` again: the agent reads the code, asks questions until it can fill every section without inventing anything, including the issues this one depends on, and proposes the spec; once the operator agrees, `--kind spec` writes `spec.md`. The agent then presents it, with a mockup for each change that brings an interface, and `--kind page` keeps the presentation as `spec-page.json` (see The spec page). When a run refuses to start because of the spec, `/issue` fixes it from the problems the run listed.
50
+ 3. Validate the spec on its page in `factory ui` (the Spec link of its card): the message of the cards you send back goes to `/issue`, which updates the spec and presents it again. Then commit the spec and its presentation and push them on the main branch.
51
51
  4. **Executing.** `factory run implement --issue <slug>`: starting the run approves the spec. It runs without the operator; follow it in `factory ui`.
52
52
  5. **To Approve → Done.** Review and merge the pull request. After `git pull`, `plan.md` is on the main branch: the issue is done. Its folder stays.
53
53
 
54
54
  A failed run stops; past `prepare`, it leaves the repository on the branch `<slug>`, with the agent's changes, for inspection. When the cause has passed (a timeout, the budget, the network, a Git hook), or when the checks or the review stopped it at their maximum of corrections and you grant one more round, `factory resume <RUN_ID>` starts the stopped stage again (each resumption allows that loop one more correction), its agent in the same conversation: the repository must still be on `<slug>` at the commit where the run stopped (the plan's commit, or the starting commit before it), with its changes in progress and none in `.factory/`; a delivery that stopped after its commit resumes once `git reset --soft <plan commit>` puts the changes back in progress, as the refusal says, a run whose pull request is recorded in its state, stopped once its delivery was done, does not resume, and neither does, past `prepare`, a run started with an earlier version of the factory. Otherwise, clean up, fix what is needed (often the spec, committed and pushed again) and start a new run: it starts again from the main branch, recreates the branch and replaces the pull request's content. Requested changes on a pull request are fixed the same way, or by hand on the branch. An issue is abandoned by deleting its folder (and closing its pull request, if any).
55
55
 
56
- `issue-save` lists every problem of a draft's format at once, then, for a valid spec, its dependencies that are no issue, refuses a spec for an issue that has no `issue.md`, replaces the file if it exists and commits nothing; each save is a run. The skill tells the agent never to write in `.factory/issues/` directly, commit or run `implement`. Pi reads a project's skills once the project is trusted; Codex ignores a `SKILL.md` that is itself a link, hence the folder link.
56
+ `issue-save` lists every problem of a draft's format at once, then, for a valid spec, its dependencies that are no issue, and for a presentation, what does not present the saved spec; it refuses a spec for an issue that has no `issue.md` and a presentation for an issue whose `spec.md` is missing or invalid, replaces the file if it exists and commits nothing; each save is a run. The skill tells the agent never to write in `.factory/issues/` directly, commit or run `implement`. Pi reads a project's skills once the project is trusted; Codex ignores a `SKILL.md` that is itself a link, hence the folder link.
57
57
 
58
58
  ### Issues
59
59
 
@@ -63,6 +63,8 @@ An issue is a folder `.factory/issues/<slug>/` holding at least one of the files
63
63
  - `spec.md`: a `# ` title, then exactly `## Objective`, `## Scope`, `## Out of scope`, `## Acceptance criteria`, `## Constraints` and `## Dependencies`, in this order, with nothing before the first and none empty (`None.`). Acceptance criteria holds only `- AC<n>: <criterion>` items numbered from AC1 without gaps; an indented line continues an item. Dependencies holds only `- <slug>` items, each another issue of the repository that must be delivered first, or `None.`. The spec says what and why, not how; headings stay in English, the content may be in any language. `issue-save` and `implement` read it the same way; `issue-save` also refuses a dependency that is no issue of the repository.
64
64
  - `plan.md`: written by the run, in the language of the spec's content: the approach, what the code already does that the change must keep (Existing), steps that each name the criteria they serve, their files, the cases they must handle besides their main path and their tests, how it read the spec where the spec reads more than one way, and the risks.
65
65
 
66
+ Beside `spec.md`, `spec-page.json` keeps the presentation of the spec for its page, as `issue-save` checked it, with the spec it presents word for word (see The spec page), and `spec-answers.json` where the operator is on that page, forgotten when a new presentation is saved; Git ignores the latter. A repository initialized before factory 0.20.0 adds `issues/*/spec-answers.json*` to its `.factory/.gitignore`: otherwise the answers are changes in progress, and `implement` refuses to start.
67
+
66
68
  ### The `implement` run
67
69
 
68
70
  | Stage | What it does |
@@ -109,6 +111,10 @@ The `implement`, `review` and `describe` agents check a change that shows in a w
109
111
 
110
112
  Its title is the commit subject. Its body is short: a warning first when the operator delivered the change despite its review (see below), a caution when merging is a one-way door (reverting does not walk it back: a migration that transforms data, a published contract, an external effect), then the link to the change's review page with what there is to decide, and a table at a glance: the files changed with their lines, links to the spec at the starting commit and to `plan.md`, the run's ID; the door and the blast radius; the criteria met, the checks and the screenshots; how the review ended and what it left. The link is `FACTORY_UI_URL` followed by `review?run=<RUN_ID>`; without it, the body says to open `/review?run=<RUN_ID>` in `factory ui` where the run ran.
111
113
 
114
+ #### The spec page
115
+
116
+ `factory ui` serves, for each issue whose spec is presented, a page to validate it before any run, linked from the issue's card: a deck of cards, with the same piles, drags and keys as the review page below. The agent of `/issue` writes the presentation in plain words, in the spec's language: the need; a card per change, with a mockup when it brings an interface (a whole HTML document drawn at a given size, shown scaled to the card and at full size on a click, in a frame without script, network or anything but its own HTML and inline styles); a card per choice the spec made, with the options it weighed and the one it took, where taking another sends the card back; a card per group of acceptance criteria, each in plain words with how it will be checked; and what the spec leaves out. `issue-save` checks that it presents every criterion of the saved spec once and that each choice's options hold, and keeps the spec it presents: once the spec changes, the page says it is stale. The verdict is spec validated, with the command to run next, or changes requested, with a message made of the cards you sent back, your notes and the options you took instead, to give to the agent. Each card but the choices and the verdict opens the spec's own words. Your answers are kept in `.factory/issues/<slug>/spec-answers.json`, so that another device resumes them. Validating gates nothing yet: `implement` starts on any valid spec.
117
+
112
118
  #### The review page
113
119
 
114
120
  `factory ui` serves, for each run that delivered a described pull request, a page to review it from the run's data alone, linked from the pull request and from the issue's card. It is a deck of cards, one at a time, between two piles: send a card right when it suits you, left when it needs another look, by dragging it or with the keyboard (→, ←, D for its dossier, S to skip a check, Backspace to take it back). The cards to know come first: the mission and its danger (door, blast radius, rollback), what the review did (each round's criteria met and findings, the checks), what changes (each change, the size of the diff, why there is no screenshot when there is none), then, when the run took screenshots, what it looks like: each screenshot with its caption, at full size on a click, where ← and → go from one to the next. Then a card per decision: each the `describe` agent wrote by rule (a change beyond the spec, an existing test whose expectation changed, a reading of the spec beyond its letter), with its advice and what shows it (the spec's words beside the plan's reading, an excerpt of the diff, screenshots, a short list); one for the non-blocking findings the review left, to merge and note for later or not; one for what the operator delivered open. Then a card per check only a person can do (a real phone, real data, a sound), and the verdict: good to merge, with what to note and what you did not check, or changes requested, with a message made of the cards you sent back and your notes, to copy. Each card opens its dossier: the run's texts word for word, the review's evidence, links to the spec, the plan and the file in the pull request; the change's dossier also holds the diagrams, where to read the code, and every interpretation, deviation and assumption no card shows. Your answers are kept with the run (`answers.json` in its folder), so that another device resumes them.
@@ -144,7 +150,7 @@ The pull request is your approval: merge it, or close it, and the scout does not
144
150
  | To Approve | the last `implement` run completed, and `plan.md` is not on the main branch |
145
151
  | Done | `plan.md` is on the main branch, a merged cleanup's too |
146
152
 
147
- The files are read in the working tree, the main branch is the local branch `origin/HEAD` names, and runs are those of `.factory/runs/`. A card shows the issue's title (the `# ` line of `issue.md`; its slug without one), its slug, the dependencies of its spec not delivered yet (none once the issue is delivered), the newest pull request its `implement` runs recorded (a new run updates the same one), with a Review link to its review page when the run described it and, for each workflow shown on cards, its last run on the issue: its steps (done, current, failed or to come, each with the corrections that sent the run back into it), its duration and its estimated cost. `implement` shows six steps: prepare, plan, implement, verify, review and deliver (the description included). The title opens the newest of these runs in the runs dashboard. The most recently active issue comes first in its column; the page refreshes every 2 seconds. What may leave the board incomplete is listed above it: a `factory.ts` that does not load (`factory ui` loads it when it starts: restart it once fixed), an unreadable `issue.md` or run, an undelivered issue's `spec.md` that cannot be read or is invalid (its dependencies are then not shown), or a main branch Git cannot read (`origin/HEAD` missing: `git remote set-head origin --auto` sets it).
153
+ The files are read in the working tree, the main branch is the local branch `origin/HEAD` names, and runs are those of `.factory/runs/`. A card shows the issue's title (the `# ` line of `issue.md`; its slug without one), its slug, the dependencies of its spec not delivered yet (none once the issue is delivered), the newest pull request its `implement` runs recorded (a new run updates the same one), with a Review link to its review page when the run described it, a Spec link to its spec page when its spec is presented and, for each workflow shown on cards, its last run on the issue: its steps (done, current, failed or to come, each with the corrections that sent the run back into it), its duration and its estimated cost. `implement` shows six steps: prepare, plan, implement, verify, review and deliver (the description included). The title opens the newest of these runs in the runs dashboard. The most recently active issue comes first in its column; the page refreshes every 2 seconds. What may leave the board incomplete is listed above it: a `factory.ts` that does not load (`factory ui` loads it when it starts: restart it once fixed), an unreadable `issue.md` or run, an undelivered issue's `spec.md` that cannot be read or is invalid (its dependencies are then not shown), or a main branch Git cannot read (`origin/HEAD` missing: `git remote set-head origin --auto` sets it).
148
154
 
149
155
  ## Adapt the factory
150
156
 
@@ -323,7 +329,7 @@ The run executes in the foreground. The terminal first shows the launch (flow's
323
329
 
324
330
  **Resume.** `factory resume RUN_ID` runs a failed run again in the foreground, under the same ID, at the stage where it stopped (with `--accept-review`, an `implement` or `tidy` run its review stopped by asking for changes is delivered as the review left it: see The pull request): its input, data, corrections, cost and agents' conversations are kept, the stage's journal starts with why the run had stopped (`Resumed after: …`), and its agent is told so and asked to continue rather than start over. The workflow is the one `factory.ts` defines now, its prompts are read again, and its runtimes are checked as for `factory run`. The budget is the one given with `--max-cost-usd`, else the run's own, else the workflow's; a run that already spent it does not resume without a higher one. The run's budget still counts its whole cost, but calls that announced no cost before the resumption, such as a call stopped by its timeout, no longer fail it; their cost counts only if the runtime includes it in the total it later announces for the resumed session, and the run's cost stays marked partial. The run's duration leaves out the time it spent stopped. Only a failed run resumes, and by one command at a time: a run left `running` after a second Ctrl-C does not, and a workflow may refuse to resume from the state of the repository, as `implement` does. A resumed stage starts over: what it did before it failed must be safe to do again. A run stopped by a correction maximum resumes at the stage that asked for it, which may then take one more correction: the checks may pass after a fix by hand, and a gate judging the same answer sends it back once more.
325
331
 
326
- `factory status` lists the repository's runs or shows one; while `factory.ts` cannot load, `status` and `ui` say so and still show the runs with the default rendering. `factory ui` serves on `127.0.0.1` (port chosen by the system unless `--port` sets it, URL printed; Ctrl-C stops it) the Kanban on `/`, each delivered run's review page at `/review?run=<RUN_ID>` (see The review page; it also answers the host of `FACTORY_UI_URL`), and, under `/runs/`, a read-only dashboard of the repository's runs, with a Board link back to the Kanban: the runs in a rail grouped by day, named by their issue, filtered and searched; for a run its figures and links above three tabs: Timeline with its launches on one bar with the pauses between them and why each stopped, each opening what it ran with and what changed since the one before, its stage executions in lanes over its active time with its rounds and corrections, then the selected execution's panel (Overview with the session each call ran in as its runtime confirmed it, Input with the correction it received and what flow sent each agent call, loaded on demand: the workflow's prompt, the instructions, the context and the output schema, the whole request and the command, Output with the agent's answer, the correction sent back or not and each call's answer when it made several, Activity with its calls, tool calls, journal and warnings in one timeline), Cost with its cost by session and over time, Result with its result, its links and its data as a tree. The address keeps the run, its tab, the execution chosen and the execution's tab. It follows a running run as it goes. To watch on another machine, forward the same port through SSH: `ssh -L 4321:127.0.0.1:4321 machine`, run `factory ui --port 4321` there, and open `http://localhost:4321/`. `factory doctor` checks Node, loads the workflows, reads their prompts, lists each workflow's options and checks each runtime's executable, version and connection, without any provider request.
332
+ `factory status` lists the repository's runs or shows one; while `factory.ts` cannot load, `status` and `ui` say so and still show the runs with the default rendering. `factory ui` serves on `127.0.0.1` (port chosen by the system unless `--port` sets it, URL printed; Ctrl-C stops it) the Kanban on `/`, each presented spec's page at `/spec?issue=<slug>` (see The spec page), each delivered run's review page at `/review?run=<RUN_ID>` (see The review page; it also answers the host of `FACTORY_UI_URL`), and, under `/runs/`, a read-only dashboard of the repository's runs, with a Board link back to the Kanban: the runs in a rail grouped by day, named by their issue, filtered and searched; for a run its figures and links above three tabs: Timeline with its launches on one bar with the pauses between them and why each stopped, each opening what it ran with and what changed since the one before, its stage executions in lanes over its active time with its rounds and corrections, then the selected execution's panel (Overview with the session each call ran in as its runtime confirmed it, Input with the correction it received and what flow sent each agent call, loaded on demand: the workflow's prompt, the instructions, the context and the output schema, the whole request and the command, Output with the agent's answer, the correction sent back or not and each call's answer when it made several, Activity with its calls, tool calls, journal and warnings in one timeline), Cost with its cost by session and over time, Result with its result, its links and its data as a tree. The address keeps the run, its tab, the execution chosen and the execution's tab. It follows a running run as it goes. To watch on another machine, forward the same port through SSH: `ssh -L 4321:127.0.0.1:4321 machine`, run `factory ui --port 4321` there, and open `http://localhost:4321/`. `factory doctor` checks Node, loads the workflows, reads their prompts, lists each workflow's options and checks each runtime's executable, version and connection, without any provider request.
327
333
 
328
334
  ## Data
329
335
 
@@ -338,7 +344,7 @@ Tokens and costs are the figures each runtime announces: Claude Code announces a
338
344
  - The repository is used in place: one `implement` run at a time, since it needs a clean tree and stops if another run moved the repository during its starting checks; after a failure, the operator cleans up, or leaves the branch as it is to resume the run. The repository's Git hooks run on the run's commits, and a failing hook fails the run.
339
345
  - The factory's own agents (`plan`, `implement`, `review`, `describe`) cannot run on Codex, since `maxCostUsd` is required and Codex announces no cost; the repository's own workflows and the `issue` skill can use Codex.
340
346
  - Runtimes are not sandboxes: the implementing agent's tools can run any command its profile allows, bounded only by its prompt, the repository's agent instructions and the run's checks.
341
- - The review page lives on the machine of the run: elsewhere, the pull request shows only its header. It is served without sign-in: anyone who reaches `factory ui`, on that machine or through `FACTORY_UI_URL`, can read it and change its answers. The factory does not check the Mermaid syntax: the page shows the text of a diagram it cannot draw. Mermaid, built into the page, brings `@clemrig/factory-ui` to about 5.5 MB.
347
+ - The review page lives on the machine of the run: elsewhere, the pull request shows only its header. A spec page shows the working tree of the machine that serves it: a spec written elsewhere is validated there once pulled. Both are served without sign-in: anyone who reaches `factory ui`, on that machine or through `FACTORY_UI_URL`, can read them and change their answers. Validating a spec does not gate `implement` yet. The factory does not check the Mermaid syntax: the page shows the text of a diagram it cannot draw. Mermaid, built into the page, brings `@clemrig/factory-ui` to about 5.5 MB.
342
348
  - Screenshots show the new state only, never the previous one; no videos.
343
349
  - `tidy` has no scheduler of its own: cron, launchd or a CI runner starts it. Its scout reads at most 200 recent commits and the latest 100 open and 100 closed pull requests, with the first 100 files of each open one, as `gh` lists them, and a defect it finds is only named in the cleanup's issue.
344
350
  - Not in this version: changing the factory's prompts, correction maximums, pull request format or columns from a repository; columns added by a workflow; backlog priority; actions or drag-and-drop on the board; resuming a run left `running` after its process was killed, detached runs, logs `--follow` and stop; local settings or profile overrides; graph drawing in the dashboard; runs or issues of several repositories; `factory new`; a shared stage library; a test harness for workflows; CI, cron or Git hook triggers; notifications; Windows.
package/dist/app/board.js CHANGED
@@ -66,6 +66,7 @@ export async function readBoard(factory, loadError) {
66
66
  slug: folder.slug,
67
67
  title: (folder.issue && issueTitle(folder.issue)) || folder.slug,
68
68
  hasSpec: folder.hasSpec,
69
+ hasSpecPage: folder.hasSpecPage,
69
70
  // A delivered issue's spec may predate today's format: it waits for nothing.
70
71
  dependencies: folder.hasSpec && !isDelivered
71
72
  ? (await dependenciesOf(factory.dir, folder.slug, problems)).map((slug) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/app/board.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/app/board.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type SpecAnswers, type SpecPage } from "../domain/spec-page.js";
2
+ import type { Factory } from "../system/factory.js";
3
+ /**
4
+ * The page of issue `slug`'s spec, from its presentation; `undefined` when
5
+ * its spec is not presented, an unknown issue included.
6
+ */
7
+ export declare function readSpecPage(factory: Factory, slug: string): Promise<SpecPage | undefined>;
8
+ /** Where the operator is in issue `slug`'s spec validation; `undefined` before the first answer, or when what is kept no longer reads. */
9
+ export declare function readSpecAnswers(factory: Factory, slug: string): Promise<SpecAnswers | undefined>;
10
+ /** Keeps the answers the page sent for issue `slug`'s spec; returns why they are refused, if they are. */
11
+ export declare function saveSpecAnswers(factory: Factory, slug: string, answers: unknown): Promise<string | undefined>;
@@ -0,0 +1,37 @@
1
+ import { z } from "@clemrig/flow";
2
+ import { isSlug } from "../domain/issue.js";
3
+ import { presentedSpec, specAnswers, specPage, } from "../domain/spec-page.js";
4
+ import { keepSpecAnswers, readArtifact, readSpecAnswers as readKeptAnswers, readSpecPage as readPresented, } from "../system/issues.js";
5
+ /**
6
+ * The page of issue `slug`'s spec, from its presentation; `undefined` when
7
+ * its spec is not presented, an unknown issue included.
8
+ */
9
+ export async function readSpecPage(factory, slug) {
10
+ if (!isSlug(slug))
11
+ return undefined;
12
+ const kept = await readPresented(factory.dir, slug);
13
+ if (kept === undefined)
14
+ return undefined;
15
+ const parsed = presentedSpec.safeParse(kept);
16
+ if (!parsed.success)
17
+ throw new Error(`The presentation of ${slug}'s spec no longer reads:\n${z.prettifyError(parsed.error)}`);
18
+ return specPage(slug, parsed.data, await readArtifact(factory.dir, slug, "spec"));
19
+ }
20
+ /** Where the operator is in issue `slug`'s spec validation; `undefined` before the first answer, or when what is kept no longer reads. */
21
+ export async function readSpecAnswers(factory, slug) {
22
+ if (!(await readSpecPage(factory, slug)))
23
+ return undefined;
24
+ const kept = specAnswers.safeParse(await readKeptAnswers(factory.dir, slug));
25
+ return kept.success ? kept.data : undefined;
26
+ }
27
+ /** Keeps the answers the page sent for issue `slug`'s spec; returns why they are refused, if they are. */
28
+ export async function saveSpecAnswers(factory, slug, answers) {
29
+ if (!(await readSpecPage(factory, slug)))
30
+ return `Issue ${slug} has no presented spec`;
31
+ const parsed = specAnswers.safeParse(answers);
32
+ if (!parsed.success)
33
+ return `Invalid answers:\n${z.prettifyError(parsed.error)}`;
34
+ await keepSpecAnswers(factory.dir, slug, parsed.data);
35
+ return undefined;
36
+ }
37
+ //# sourceMappingURL=spec-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-page.js","sourceRoot":"","sources":["../../src/app/spec-page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EACL,aAAa,EAGb,WAAW,EACX,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,eAAe,EACf,YAAY,EACZ,eAAe,IAAI,eAAe,EAClC,YAAY,IAAI,aAAa,GAC9B,MAAM,qBAAqB,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAgB,EAChB,IAAY;IAEZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACpD,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO;QACjB,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,6BAA6B,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACxF,CAAC;IACJ,OAAO,QAAQ,CACb,IAAI,EACJ,MAAM,CAAC,IAAI,EACX,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAC9C,CAAC;AACJ,CAAC;AAED,0IAA0I;AAC1I,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAgB,EAChB,IAAY;IAEZ,IAAI,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3D,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC;AAED,0GAA0G;AAC1G,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAgB,EAChB,IAAY,EACZ,OAAgB;IAEhB,IAAI,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACtC,OAAO,SAAS,IAAI,wBAAwB,CAAC;IAC/C,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,OAAO;QACjB,OAAO,qBAAqB,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC9D,MAAM,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -1,2 +1,5 @@
1
- /** Checks a draft written by an agent, then writes it as the issue's issue.md or spec.md. */
1
+ /**
2
+ * Checks a draft written by an agent, then writes it as the issue's
3
+ * issue.md or spec.md, or as the presentation of its spec (`page`).
4
+ */
2
5
  export declare const issueSave: import("@clemrig/flow").Workflow;
@@ -1,15 +1,16 @@
1
1
  import { defineWorkflow, END, node, step, z } from "@clemrig/flow";
2
2
  import { isSlug, parseIssue } from "../../domain/issue.js";
3
3
  import { parseSpec } from "../../domain/spec.js";
4
- import { hasArtifact, isIssue, readDraft, writeArtifact, } from "../../system/issues.js";
4
+ import { parsePresentation } from "../../domain/spec-page.js";
5
+ import { forgetSpecAnswers, hasArtifact, isIssue, readArtifact, readDraft, writeArtifact, writeSpecPage, } from "../../system/issues.js";
5
6
  const input = z.object({
6
7
  issue: z
7
8
  .string()
8
9
  .refine(isSlug, "Expected lowercase words separated by hyphens"),
9
- kind: z.enum(["issue", "spec"]),
10
+ kind: z.enum(["issue", "spec", "page"]),
10
11
  file: z.string().min(1),
11
12
  });
12
- /** What is wrong with a draft: its format, and for a spec, dependencies that are no issue of the repository. */
13
+ /** What is wrong with a draft of issue.md or spec.md: its format, and for a spec, dependencies that are no issue of the repository. */
13
14
  async function draftProblems(dir, { issue, kind }, text) {
14
15
  if (kind === "issue") {
15
16
  const parsed = parseIssue(text);
@@ -24,7 +25,35 @@ async function draftProblems(dir, { issue, kind }, text) {
24
25
  problems.push(`Dependencies: there is no issue ${dependency} in .factory/issues/`);
25
26
  return problems;
26
27
  }
27
- /** Checks a draft written by an agent, then writes it as the issue's issue.md or spec.md. */
28
+ /**
29
+ * Checks the presentation of issue `issue`'s spec an agent drafted, then
30
+ * keeps it with the spec it presents, word for word, as `spec-page.json`;
31
+ * the operator's answers on the previous one are forgotten.
32
+ */
33
+ async function savePresentation(dir, issue, file) {
34
+ const spec = await readArtifact(dir, issue, "spec");
35
+ if (spec === undefined)
36
+ throw new Error(`Issue ${issue} has no spec.md: save it first`);
37
+ const parsedSpec = parseSpec(spec, issue);
38
+ if (!parsedSpec.ok)
39
+ throw new Error(`Issue ${issue}'s spec.md is invalid: save a valid spec first`);
40
+ const parsed = parsePresentation(await readDraft(file), parsedSpec.value);
41
+ if (!parsed.ok)
42
+ throw new Error([
43
+ `Invalid presentation draft ${file}:`,
44
+ ...parsed.problems.map((problem) => `- ${problem}`),
45
+ ].join("\n"));
46
+ const path = await writeSpecPage(dir, issue, {
47
+ spec,
48
+ presentation: parsed.value,
49
+ });
50
+ await forgetSpecAnswers(dir, issue);
51
+ return path;
52
+ }
53
+ /**
54
+ * Checks a draft written by an agent, then writes it as the issue's
55
+ * issue.md or spec.md, or as the presentation of its spec (`page`).
56
+ */
28
57
  export const issueSave = defineWorkflow({
29
58
  name: "issue-save",
30
59
  input,
@@ -32,6 +61,8 @@ export const issueSave = defineWorkflow({
32
61
  nodes: {
33
62
  save: node(step(async ({ dir, state }) => {
34
63
  const { issue, kind, file } = state.input;
64
+ if (kind === "page")
65
+ return { path: await savePresentation(dir, issue, file) };
35
66
  if (kind === "spec" && !(await hasArtifact(dir, issue, "issue")))
36
67
  throw new Error(`Issue ${issue} does not exist: save its issue.md first`);
37
68
  const text = await readDraft(file);
@@ -1 +1 @@
1
- {"version":3,"file":"issue-save.js","sourceRoot":"","sources":["../../../src/app/workflows/issue-save.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"issue-save.js","sourceRoot":"","sources":["../../../src/app/workflows/issue-save.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,OAAO,EACP,YAAY,EACZ,SAAS,EACT,aAAa,EACb,aAAa,GACd,MAAM,wBAAwB,CAAC;AAEhC,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACrB,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,MAAM,CAAC,MAAM,EAAE,+CAA+C,CAAC;IAClE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACxB,CAAC,CAAC;AAGH,uIAAuI;AACvI,KAAK,UAAU,aAAa,CAC1B,GAAW,EACX,EAAE,KAAK,EAAE,IAAI,EAA6C,EAC1D,IAAY;IAEZ,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC1C,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC;IACvC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,KAAK,CAAC,YAAY;QAChD,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YACnC,QAAQ,CAAC,IAAI,CACX,mCAAmC,UAAU,sBAAsB,CACpE,CAAC;IACN,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,gBAAgB,CAC7B,GAAW,EACX,KAAa,EACb,IAAY;IAEZ,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACpD,IAAI,IAAI,KAAK,SAAS;QACpB,MAAM,IAAI,KAAK,CAAC,SAAS,KAAK,gCAAgC,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,EAAE;QAChB,MAAM,IAAI,KAAK,CACb,SAAS,KAAK,gDAAgD,CAC/D,CAAC;IACJ,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC1E,IAAI,CAAC,MAAM,CAAC,EAAE;QACZ,MAAM,IAAI,KAAK,CACb;YACE,8BAA8B,IAAI,GAAG;YACrC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC;SACpD,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACJ,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE;QAC3C,IAAI;QACJ,YAAY,EAAE,MAAM,CAAC,KAAK;KAC3B,CAAC,CAAC;IACH,MAAM,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,cAAc,CAAC;IACtC,IAAI,EAAE,YAAY;IAClB,KAAK;IACL,OAAO,EAAE,MAAM;IACf,KAAK,EAAE;QACL,IAAI,EAAE,IAAI,CACR,IAAI,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;YAC5B,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,KAAc,CAAC;YACnD,IAAI,IAAI,KAAK,MAAM;gBACjB,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;YAC5D,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;gBAC9D,MAAM,IAAI,KAAK,CACb,SAAS,KAAK,0CAA0C,CACzD,CAAC;YACJ,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;YACnC,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;YACjE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;gBACrB,MAAM,IAAI,KAAK,CACb;oBACE,WAAW,IAAI,aAAa,IAAI,GAAG;oBACnC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC;iBAC7C,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;YACJ,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QAC/D,CAAC,CAAC,EACF,EAAE,EAAE,EAAE,GAAG,EAAE,CACZ;KACF;IACD,qDAAqD;IACrD,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAC1B,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;CAC9D,CAAC,CAAC"}
package/dist/cli/ui.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { Factory } from "../app/factory.js";
2
2
  /**
3
3
  * `factory ui`: serves the Kanban of the repository's issues, the dashboard
4
- * of its runs under `/runs/` and each delivered run's review page, until
5
- * Ctrl-C, on `port` or one the system chooses. It also answers the host of
6
- * `FACTORY_UI_URL`, which a proxy such as `tailscale serve` keeps.
4
+ * of its runs under `/runs/`, each presented spec's page and each delivered
5
+ * run's review page, until Ctrl-C, on `port` or one the system chooses. It
6
+ * also answers the host of `FACTORY_UI_URL`, which a proxy such as
7
+ * `tailscale serve` keeps.
7
8
  */
8
9
  export declare function ui(factory: Factory, port?: number, loadError?: string): Promise<number>;
package/dist/cli/ui.js CHANGED
@@ -5,11 +5,13 @@ import { readBoard } from "../app/board.js";
5
5
  import { uiAddress } from "../app/factory.js";
6
6
  import { readReviewAnswers, readReviewPage, readReviewScreenshot, saveReviewAnswers, } from "../app/review.js";
7
7
  import { presentResult } from "../app/runs.js";
8
+ import { readSpecAnswers, readSpecPage, saveSpecAnswers, } from "../app/spec-page.js";
8
9
  /**
9
10
  * `factory ui`: serves the Kanban of the repository's issues, the dashboard
10
- * of its runs under `/runs/` and each delivered run's review page, until
11
- * Ctrl-C, on `port` or one the system chooses. It also answers the host of
12
- * `FACTORY_UI_URL`, which a proxy such as `tailscale serve` keeps.
11
+ * of its runs under `/runs/`, each presented spec's page and each delivered
12
+ * run's review page, until Ctrl-C, on `port` or one the system chooses. It
13
+ * also answers the host of `FACTORY_UI_URL`, which a proxy such as
14
+ * `tailscale serve` keeps.
13
15
  */
14
16
  export async function ui(factory, port, loadError) {
15
17
  const label = basename(factory.dir);
@@ -34,9 +36,14 @@ export async function ui(factory, port, loadError) {
34
36
  saveAnswers: (run, answers) => saveReviewAnswers(factory, run, answers),
35
37
  screenshot: (run, file) => readReviewScreenshot(factory, run, file),
36
38
  },
39
+ spec: {
40
+ page: (issue) => readSpecPage(factory, issue),
41
+ answers: (issue) => readSpecAnswers(factory, issue),
42
+ saveAnswers: (issue, answers) => saveSpecAnswers(factory, issue, answers),
43
+ },
37
44
  ...(address ? { hosts: [address.host] } : {}),
38
45
  }, port);
39
- process.stdout.write(`factory ui: ${page.url} (runs: ${page.url}runs/; review pages: ${address?.href ?? page.url}review?run=<id>; Ctrl-C to stop)\n`);
46
+ process.stdout.write(`factory ui: ${page.url} (runs: ${page.url}runs/; spec pages: ${address?.href ?? page.url}spec?issue=<slug>; review pages: ${address?.href ?? page.url}review?run=<id>; Ctrl-C to stop)\n`);
40
47
  await new Promise((resolve) => {
41
48
  const stop = () => {
42
49
  process.off("SIGINT", stop);
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,8 @@ export interface IssueFacts {
8
8
  /** From `issue.md`; the slug when it has no readable title. */
9
9
  title: string;
10
10
  hasSpec: boolean;
11
+ /** Its spec is presented, for the operator to validate it on its page. */
12
+ hasSpecPage: boolean;
11
13
  /** The issues its spec depends on, and whether each is delivered: its `plan.md` on the main branch, whatever the working tree holds. */
12
14
  dependencies: {
13
15
  slug: string;
@@ -49,6 +51,8 @@ export interface IssueCard {
49
51
  pullRequest?: string;
50
52
  /** The run that recorded it, when it has a review page: one that described its change. */
51
53
  review?: string;
54
+ /** Its spec has a page to validate it. */
55
+ spec?: boolean;
52
56
  /** The last run of each shown workflow, newest first. */
53
57
  runs: RunProgress[];
54
58
  }
@@ -94,6 +94,7 @@ export function boardOf(issues, runs, shown, problems) {
94
94
  ...(pullRequest ? { pullRequest } : {}),
95
95
  // Runs delivered by an earlier factory have no description, nor its page.
96
96
  ...(delivered?.data.description ? { review: delivered.id } : {}),
97
+ ...(issue.hasSpecPage ? { spec: true } : {}),
97
98
  runs: cardRuns,
98
99
  },
99
100
  };
@@ -1 +1 @@
1
- {"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/domain/board.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/domain/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,YAAY,EAAqB,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAExD,4DAA4D;AAC5D,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,SAAS;IACT,YAAY;IACZ,WAAW;IACX,SAAS;IACT,YAAY;IACZ,MAAM;CACE,CAAC;AAyEX,6GAA6G;AAC7G,MAAM,UAAU,QAAQ,CACtB,KAAiB,EACjB,YAAkD;IAElD,IAAI,KAAK,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC;IACnC,IAAI,YAAY,EAAE,MAAM,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAC3D,IAAI,YAAY,EAAE,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CACrB,EAAE,KAAK,EAAE,WAAW,EAAgD,EACpE,GAAmD;IAEnD,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7C,IAAI;QACJ,KAAK,EACH,GAAG,CAAC,MAAM,KAAK,WAAW,IAAI,KAAK,GAAG,EAAE;YACtC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC;gBACpB,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS;oBACxB,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,QAAQ;QAClB,OAAO,EAAE,KAAK;aACX,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;aAC5D,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC;KAC9C,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,UAAU,CACjB,GAAa,EACb,QAAsD;IAEtD,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3D,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;QAC7B,UAAU;QACV,OAAO;QACP,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CACrB,MAAoB,EACpB,IAAgB,EAChB,KAAsB,EACtB,QAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,CAAC,QAAgB,EAAE,EAAE,CAChC,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QACnD,0EAA0E;QAC1E,gDAAgD;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC;QAC1E,MAAM,WAAW,GAAG,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAAG,KAAK;aACnB,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YACpB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAChC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,CAAC,CAAC;aACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;aAC9D,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;aACnE,IAAI,EAAE;aACN,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACV,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;YACjC,QAAQ,EAAE,QAAQ,IAAI,KAAK,CAAC,SAAS;YACrC,IAAI,EAAE;gBACJ,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,4EAA4E;gBAC5E,QAAQ,EAAE,KAAK,CAAC,SAAS;oBACvB,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,KAAK,CAAC,YAAY;yBACf,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;yBACrC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;gBAC9B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvC,0EAA0E;gBAC1E,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChE,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,IAAI,EAAE,QAAQ;aACf;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI;YACJ,KAAK,EAAE,MAAM;iBACV,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC;iBACvC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;iBACpD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;QACH,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,132 @@
1
+ import { z } from "@clemrig/flow";
2
+ import type { Parsed } from "./parsed.js";
3
+ import { type Spec } from "./spec.js";
4
+ /** Above this size, a mockup is refused: a sketch of an interface fits well below it. */
5
+ export declare const maxMockupBytes: number;
6
+ /**
7
+ * How the agent that wrote a spec with the operator presents it, for the
8
+ * operator to validate it on the spec page: in plain words, in the spec's
9
+ * language, each change with a mockup when it brings an interface.
10
+ */
11
+ export declare const specPresentation: z.ZodObject<{
12
+ need: z.ZodString;
13
+ changes: z.ZodArray<z.ZodObject<{
14
+ title: z.ZodString;
15
+ text: z.ZodString;
16
+ mockup: z.ZodOptional<z.ZodObject<{
17
+ caption: z.ZodString;
18
+ html: z.ZodString;
19
+ width: z.ZodNumber;
20
+ height: z.ZodNumber;
21
+ }, z.core.$strict>>;
22
+ }, z.core.$strict>>;
23
+ decisions: z.ZodArray<z.ZodObject<{
24
+ id: z.ZodString;
25
+ title: z.ZodString;
26
+ why: z.ZodString;
27
+ options: z.ZodArray<z.ZodObject<{
28
+ id: z.ZodString;
29
+ title: z.ZodString;
30
+ text: z.ZodString;
31
+ }, z.core.$strict>>;
32
+ chosen: z.ZodString;
33
+ }, z.core.$strict>>;
34
+ criteria: z.ZodArray<z.ZodObject<{
35
+ title: z.ZodString;
36
+ criteria: z.ZodArray<z.ZodObject<{
37
+ id: z.ZodString;
38
+ plain: z.ZodString;
39
+ check: z.ZodString;
40
+ }, z.core.$strict>>;
41
+ }, z.core.$strict>>;
42
+ outOfScope: z.ZodArray<z.ZodString>;
43
+ }, z.core.$strict>;
44
+ export type SpecPresentation = z.output<typeof specPresentation>;
45
+ /** What the factory keeps of a presented spec: the presentation, and the spec it presents, word for word. */
46
+ export declare const presentedSpec: z.ZodObject<{
47
+ spec: z.ZodString;
48
+ presentation: z.ZodObject<{
49
+ need: z.ZodString;
50
+ changes: z.ZodArray<z.ZodObject<{
51
+ title: z.ZodString;
52
+ text: z.ZodString;
53
+ mockup: z.ZodOptional<z.ZodObject<{
54
+ caption: z.ZodString;
55
+ html: z.ZodString;
56
+ width: z.ZodNumber;
57
+ height: z.ZodNumber;
58
+ }, z.core.$strict>>;
59
+ }, z.core.$strict>>;
60
+ decisions: z.ZodArray<z.ZodObject<{
61
+ id: z.ZodString;
62
+ title: z.ZodString;
63
+ why: z.ZodString;
64
+ options: z.ZodArray<z.ZodObject<{
65
+ id: z.ZodString;
66
+ title: z.ZodString;
67
+ text: z.ZodString;
68
+ }, z.core.$strict>>;
69
+ chosen: z.ZodString;
70
+ }, z.core.$strict>>;
71
+ criteria: z.ZodArray<z.ZodObject<{
72
+ title: z.ZodString;
73
+ criteria: z.ZodArray<z.ZodObject<{
74
+ id: z.ZodString;
75
+ plain: z.ZodString;
76
+ check: z.ZodString;
77
+ }, z.core.$strict>>;
78
+ }, z.core.$strict>>;
79
+ outOfScope: z.ZodArray<z.ZodString>;
80
+ }, z.core.$strict>;
81
+ }, z.core.$strict>;
82
+ export type PresentedSpec = z.output<typeof presentedSpec>;
83
+ /** What is wrong with a presentation of `spec`: criteria not presented once each, choices whose options do not hold. */
84
+ export declare function presentationProblems(presentation: SpecPresentation, spec: Spec): string[];
85
+ /** A presentation an agent drafted, checked against the spec it presents. */
86
+ export declare function parsePresentation(draft: string, spec: Spec): Parsed<SpecPresentation>;
87
+ /** What the spec page shows of a presented spec; `@clemrig/factory-ui` draws it. */
88
+ export interface SpecPage {
89
+ issue: string;
90
+ title: string;
91
+ stale: boolean;
92
+ need: string;
93
+ changes: SpecPresentation["changes"];
94
+ decisions: SpecPresentation["decisions"];
95
+ criteria: {
96
+ title: string;
97
+ criteria: {
98
+ id: string;
99
+ text: string;
100
+ plain: string;
101
+ check: string;
102
+ }[];
103
+ }[];
104
+ outOfScope: string[];
105
+ spec: {
106
+ objective: string;
107
+ scope: string;
108
+ outOfScope: string;
109
+ constraints: string;
110
+ };
111
+ }
112
+ /**
113
+ * The page of issue `slug`'s presented spec, in the words of the spec it
114
+ * presents; stale when `current`, the spec now, says something else.
115
+ * `undefined` when the spec it presents no longer reads.
116
+ */
117
+ export declare function specPage(slug: string, { spec: text, presentation }: PresentedSpec, current: string | undefined): SpecPage | undefined;
118
+ /** Where a person is in a spec's validation, as the page sends it: the review's answers, and the options taken. */
119
+ export declare const specAnswers: z.ZodObject<{
120
+ answers: z.ZodRecord<z.ZodString, z.ZodEnum<{
121
+ no: "no";
122
+ ok: "ok";
123
+ skip: "skip";
124
+ }>>;
125
+ notes: z.ZodRecord<z.ZodString, z.ZodString>;
126
+ seen: z.ZodArray<z.ZodString>;
127
+ position: z.ZodNumber;
128
+ startedAt: z.ZodNullable<z.ZodNumber>;
129
+ endedAt: z.ZodNullable<z.ZodNumber>;
130
+ choices: z.ZodRecord<z.ZodString, z.ZodString>;
131
+ }, z.core.$strict>;
132
+ export type SpecAnswers = z.output<typeof specAnswers>;
@@ -0,0 +1,155 @@
1
+ import { z } from "@clemrig/flow";
2
+ import { reviewAnswers } from "./review-page.js";
3
+ import { parseSpec } from "./spec.js";
4
+ /** An id a card is kept under: a choice's, an option's. */
5
+ const id = z
6
+ .string()
7
+ .regex(/^[a-z][a-z0-9-]{0,39}$/, "Expected a lowercase letter, then lowercase letters, digits or hyphens: 40 characters at most");
8
+ const text = (max) => z.string().trim().min(1).max(max);
9
+ /** Above this size, a mockup is refused: a sketch of an interface fits well below it. */
10
+ export const maxMockupBytes = 256 * 1024;
11
+ const mockup = z.strictObject({
12
+ caption: text(300),
13
+ html: z
14
+ .string()
15
+ .min(1)
16
+ .refine((html) => new TextEncoder().encode(html).length <= maxMockupBytes, `At most ${maxMockupBytes / 1024} KiB`),
17
+ width: z.number().int().min(320).max(1600),
18
+ height: z.number().int().min(200).max(4000),
19
+ });
20
+ /**
21
+ * How the agent that wrote a spec with the operator presents it, for the
22
+ * operator to validate it on the spec page: in plain words, in the spec's
23
+ * language, each change with a mockup when it brings an interface.
24
+ */
25
+ export const specPresentation = z.strictObject({
26
+ need: text(4000),
27
+ changes: z
28
+ .array(z.strictObject({
29
+ title: text(200),
30
+ text: text(4000),
31
+ mockup: mockup.optional(),
32
+ }))
33
+ .min(1)
34
+ .max(20),
35
+ decisions: z
36
+ .array(z.strictObject({
37
+ id,
38
+ title: text(300),
39
+ why: text(2000),
40
+ options: z
41
+ .array(z.strictObject({ id, title: text(200), text: text(2000) }))
42
+ .min(2)
43
+ .max(5),
44
+ chosen: id,
45
+ }))
46
+ .max(20),
47
+ criteria: z
48
+ .array(z.strictObject({
49
+ title: text(200),
50
+ criteria: z
51
+ .array(z.strictObject({
52
+ id: z.string().regex(/^AC[1-9]\d*$/, "Expected AC<n>"),
53
+ plain: text(1000),
54
+ check: text(500),
55
+ }))
56
+ .min(1),
57
+ }))
58
+ .min(1)
59
+ .max(20),
60
+ outOfScope: z.array(text(500)).max(30),
61
+ });
62
+ /** What the factory keeps of a presented spec: the presentation, and the spec it presents, word for word. */
63
+ export const presentedSpec = z.strictObject({
64
+ spec: z.string(),
65
+ presentation: specPresentation,
66
+ });
67
+ /** What is wrong with a presentation of `spec`: criteria not presented once each, choices whose options do not hold. */
68
+ export function presentationProblems(presentation, spec) {
69
+ const problems = [];
70
+ const presented = presentation.criteria.flatMap(({ criteria }) => criteria.map(({ id }) => id));
71
+ const known = new Set(spec.criteria.map(({ id }) => id));
72
+ for (const criterion of spec.criteria)
73
+ if (!presented.includes(criterion.id))
74
+ problems.push(`criteria: ${criterion.id} is not presented`);
75
+ for (const criterion of new Set(presented)) {
76
+ if (!known.has(criterion))
77
+ problems.push(`criteria: ${criterion} is not a criterion of the spec`);
78
+ if (presented.indexOf(criterion) !== presented.lastIndexOf(criterion))
79
+ problems.push(`criteria: ${criterion} is presented more than once`);
80
+ }
81
+ const decisions = presentation.decisions.map(({ id }) => id);
82
+ presentation.decisions.forEach((decision, index) => {
83
+ const where = `decisions[${index}]`;
84
+ if (decisions.indexOf(decision.id) !== index)
85
+ problems.push(`${where}: ${decision.id} is the id of another choice`);
86
+ const options = decision.options.map(({ id }) => id);
87
+ for (const option of new Set(options))
88
+ if (options.indexOf(option) !== options.lastIndexOf(option))
89
+ problems.push(`${where}: option ${option} is listed twice`);
90
+ if (!options.includes(decision.chosen))
91
+ problems.push(`${where}.chosen: ${decision.chosen} is not an option`);
92
+ });
93
+ return problems;
94
+ }
95
+ /** A presentation an agent drafted, checked against the spec it presents. */
96
+ export function parsePresentation(draft, spec) {
97
+ let value;
98
+ try {
99
+ value = JSON.parse(draft);
100
+ }
101
+ catch (error) {
102
+ return {
103
+ ok: false,
104
+ problems: [`Not JSON: ${error instanceof Error ? error.message : error}`],
105
+ };
106
+ }
107
+ const parsed = specPresentation.safeParse(value);
108
+ if (!parsed.success)
109
+ return { ok: false, problems: [z.prettifyError(parsed.error)] };
110
+ const problems = presentationProblems(parsed.data, spec);
111
+ return problems.length > 0
112
+ ? { ok: false, problems }
113
+ : { ok: true, value: parsed.data };
114
+ }
115
+ /**
116
+ * The page of issue `slug`'s presented spec, in the words of the spec it
117
+ * presents; stale when `current`, the spec now, says something else.
118
+ * `undefined` when the spec it presents no longer reads.
119
+ */
120
+ export function specPage(slug, { spec: text, presentation }, current) {
121
+ const parsed = parseSpec(text, slug);
122
+ if (!parsed.ok)
123
+ return undefined;
124
+ const spec = parsed.value;
125
+ const words = new Map(spec.criteria.map(({ id, text }) => [id, text]));
126
+ return {
127
+ issue: slug,
128
+ title: spec.title,
129
+ stale: current !== text,
130
+ need: presentation.need,
131
+ changes: presentation.changes,
132
+ decisions: presentation.decisions,
133
+ criteria: presentation.criteria.map((group) => ({
134
+ title: group.title,
135
+ criteria: group.criteria.map((criterion) => ({
136
+ id: criterion.id,
137
+ text: words.get(criterion.id) ?? "",
138
+ plain: criterion.plain,
139
+ check: criterion.check,
140
+ })),
141
+ })),
142
+ outOfScope: presentation.outOfScope,
143
+ spec: {
144
+ objective: spec.objective,
145
+ scope: spec.scope,
146
+ outOfScope: spec.outOfScope,
147
+ constraints: spec.constraints,
148
+ },
149
+ };
150
+ }
151
+ /** Where a person is in a spec's validation, as the page sends it: the review's answers, and the options taken. */
152
+ export const specAnswers = reviewAnswers.extend({
153
+ choices: z.record(id, id),
154
+ });
155
+ //# sourceMappingURL=spec-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-page.js","sourceRoot":"","sources":["../../src/domain/spec-page.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,8 @@ export interface IssueFolder {
13
13
  /** Why its `issue.md` cannot be read. */
14
14
  unreadable?: string;
15
15
  hasSpec: boolean;
16
+ /** Its spec is presented: it has a spec page. */
17
+ hasSpecPage: boolean;
16
18
  /** When one of its files last changed, as an ISO date. */
17
19
  changedAt: string;
18
20
  }
@@ -26,4 +28,12 @@ export declare function isIssue(repository: string, slug: string): Promise<boole
26
28
  export declare function readArtifact(repository: string, slug: string, kind: ArtifactKind): Promise<string | undefined>;
27
29
  /** A draft handed over by an agent, wherever it wrote it. */
28
30
  export declare function readDraft(path: string): Promise<string>;
31
+ /** The issue's presented spec, as kept; nothing when its spec is not presented. */
32
+ export declare function readSpecPage(repository: string, slug: string): Promise<unknown | undefined>;
33
+ export declare function writeSpecPage(repository: string, slug: string, value: unknown): Promise<string>;
34
+ /** Where the operator is in the issue's spec validation; nothing before the first answer. */
35
+ export declare function readSpecAnswers(repository: string, slug: string): Promise<unknown | undefined>;
36
+ export declare function keepSpecAnswers(repository: string, slug: string, answers: unknown): Promise<void>;
37
+ /** Forgets where the operator was in the issue's spec validation: a new presentation starts it over. */
38
+ export declare function forgetSpecAnswers(repository: string, slug: string): Promise<void>;
29
39
  export {};
@@ -1,4 +1,5 @@
1
- import { access, mkdir, readdir, readFile, stat, writeFile, } from "node:fs/promises";
1
+ import { randomUUID } from "node:crypto";
2
+ import { access, mkdir, readdir, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
2
3
  import { dirname, join } from "node:path";
3
4
  /** An issue's files: a folder with none of them is not an issue. */
4
5
  const artifactKinds = ["issue", "spec", "plan"];
@@ -8,6 +9,14 @@ export const issuesPath = join(".factory", "issues");
8
9
  export function artifactPath(slug, kind) {
9
10
  return join(issuesPath, slug, `${kind}.md`);
10
11
  }
12
+ /** `.factory/issues/<slug>/spec-page.json`: the presentation of its spec, kept with it. */
13
+ function specPagePath(slug) {
14
+ return join(issuesPath, slug, "spec-page.json");
15
+ }
16
+ /** `.factory/issues/<slug>/spec-answers.json`: where the operator is in its validation, which Git ignores. */
17
+ function specAnswersPath(slug) {
18
+ return join(issuesPath, slug, "spec-answers.json");
19
+ }
11
20
  /** Every folder of `.factory/issues/`; none before the first issue. */
12
21
  export async function listIssueFolders(repository) {
13
22
  const root = join(repository, issuesPath);
@@ -41,6 +50,7 @@ export async function listIssueFolders(repository) {
41
50
  ? { issue: issue.text }
42
51
  : { unreadable: issue.error }),
43
52
  hasSpec: files.includes("spec.md"),
53
+ hasSpecPage: files.includes("spec-page.json"),
44
54
  changedAt: new Date(Math.max(...changes)).toISOString(),
45
55
  });
46
56
  }
@@ -72,4 +82,50 @@ export async function readArtifact(repository, slug, kind) {
72
82
  export function readDraft(path) {
73
83
  return readFile(path, "utf8");
74
84
  }
85
+ /** A JSON file of the repository, or nothing when there is none. */
86
+ async function readJson(repository, path) {
87
+ let text;
88
+ try {
89
+ text = await readFile(join(repository, path), "utf8");
90
+ }
91
+ catch (error) {
92
+ if (error.code === "ENOENT")
93
+ return undefined;
94
+ throw error;
95
+ }
96
+ try {
97
+ return JSON.parse(text);
98
+ }
99
+ catch {
100
+ throw new Error(`${path} is not JSON`);
101
+ }
102
+ }
103
+ /** Writes `value` as the JSON file `path` of the repository, replacing it whole. */
104
+ async function writeJson(repository, path, value) {
105
+ const target = join(repository, path);
106
+ await mkdir(dirname(target), { recursive: true });
107
+ const temporary = `${target}.${randomUUID()}.tmp`;
108
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`);
109
+ await rename(temporary, target);
110
+ return path;
111
+ }
112
+ /** The issue's presented spec, as kept; nothing when its spec is not presented. */
113
+ export function readSpecPage(repository, slug) {
114
+ return readJson(repository, specPagePath(slug));
115
+ }
116
+ export function writeSpecPage(repository, slug, value) {
117
+ return writeJson(repository, specPagePath(slug), value);
118
+ }
119
+ /** Where the operator is in the issue's spec validation; nothing before the first answer. */
120
+ export async function readSpecAnswers(repository, slug) {
121
+ // Answers that no longer read start the validation over.
122
+ return readJson(repository, specAnswersPath(slug)).catch(() => undefined);
123
+ }
124
+ export async function keepSpecAnswers(repository, slug, answers) {
125
+ await writeJson(repository, specAnswersPath(slug), answers);
126
+ }
127
+ /** Forgets where the operator was in the issue's spec validation: a new presentation starts it over. */
128
+ export async function forgetSpecAnswers(repository, slug) {
129
+ await rm(join(repository, specAnswersPath(slug)), { force: true });
130
+ }
75
131
  //# sourceMappingURL=issues.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"issues.js","sourceRoot":"","sources":["../../src/system/issues.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"issues.js","sourceRoot":"","sources":["../../src/system/issues.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clemrig/factory",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Take a repository's issues from spec to pull request with agents, watch them on a local Kanban, and run the repository's own workflows",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "prepack": "npm run build"
40
40
  },
41
41
  "dependencies": {
42
- "@clemrig/factory-ui": "0.4.0",
42
+ "@clemrig/factory-ui": "0.5.0",
43
43
  "@clemrig/flow": "0.11.0",
44
44
  "@clemrig/flow-ui": "0.12.0"
45
45
  }
@@ -1,3 +1,4 @@
1
1
  node_modules/
2
2
  runs/
3
3
  .env
4
+ issues/*/spec-answers.json*
@@ -26,11 +26,52 @@ description of the need. Once the operator agrees, save the issue (see Save).
26
26
  issue brings. List the issues of `.factory/issues/` that look related, with whether each is delivered (its
27
27
  `plan.md` is on the main branch), and record the operator's answer in Dependencies.
28
28
  4. Describe what and why, never how: the run that implements the spec plans the technical work itself.
29
+ Where you chose between real alternatives, write the one you recommend: the presentation shows the others.
29
30
  5. Propose the complete spec and wait for the operator's agreement, then save it (see Save).
31
+ 6. Present it for the operator to validate it on its page (see Present).
30
32
 
31
33
  Other issues in `.factory/issues/` are history, not the current truth: rely on the code and the docs, and read
32
34
  them only to find the issues this one depends on.
33
35
 
36
+ ## Present
37
+
38
+ The spec page of `factory ui` (`spec?issue=<slug>`, linked from the issue's card) shows the spec as a deck of
39
+ cards the operator answers one by one. Write its presentation, in the spec's language and in plain words, as a
40
+ JSON draft outside the repository, then save it with `--kind page` (see Save):
41
+
42
+ ```json
43
+ {
44
+ "need": "<the problem and why it matters, for someone who has not read the spec>",
45
+ "changes": [{ "title": "<a few words>", "text": "<what changes, for whom, why>", "mockup": <optional> }],
46
+ "decisions": [{
47
+ "id": "<id>", "title": "<the question>", "why": "<what the answer changes>",
48
+ "options": [{ "id": "<id>", "title": "<a few words>", "text": "<what it does, what it costs>" }],
49
+ "chosen": "<the id of the option the spec takes>"
50
+ }],
51
+ "criteria": [{ "title": "<what they check together>", "criteria": [
52
+ { "id": "AC1", "plain": "<the criterion in plain words>", "check": "<how it will be checked, on what>" }
53
+ ] }],
54
+ "outOfScope": ["<what the spec leaves out, in plain words>"]
55
+ }
56
+ ```
57
+
58
+ - One change per part of the scope a person would judge on its own; `text` in a few sentences, `**bold**` and
59
+ `` `code` `` allowed.
60
+ - A mockup only for a change that brings or changes an interface:
61
+ `{ "caption": "…", "html": "<a whole HTML document>", "width": <320–1600>, "height": <200–4000> }`. It is
62
+ shown in a frame without script, network or anything but its own HTML and inline styles: no `<script>`, no
63
+ link to a stylesheet, a font or an image (draw them, or use `data:` images). Reproduce the application's look
64
+ (read its styles: colors, fonts, components) and mark what is new; keep it under 256 KiB.
65
+ - A decision for each choice where the spec took one of real alternatives (2 to 5 options), the one it took as
66
+ `chosen`; none when there was nothing to choose. An `<id>` starts with a lowercase letter, then lowercase
67
+ letters, digits or hyphens, 40 characters at most.
68
+ - Every acceptance criterion of the spec exactly once, grouped by what a person checks together.
69
+
70
+ `factory run issue-save` checks the draft against the saved spec and keeps it with the spec it presents: once
71
+ the spec changes, the page says it is stale, so save a new presentation after saving a new spec. When the
72
+ operator brings back the page's message (the cards they sent back, their notes, the options they took instead),
73
+ update the spec with them, save it, then present it again.
74
+
34
75
  ## Fix a refused spec
35
76
 
36
77
  When `factory run implement` refuses to start because of the spec (a missing section such as Dependencies, a
@@ -86,14 +127,15 @@ delivered is either delivered first or removed from the spec, as the operator de
86
127
 
87
128
  1. Write the draft to a new file outside the repository, for example in a directory created by `mktemp -d`.
88
129
  2. From the repository, run
89
- `factory run issue-save --issue <slug> --kind issue --file <absolute path>` for `issue.md`, or
90
- `--kind spec` for `spec.md`.
91
- 3. `Saved .factory/issues/<slug>/<kind>.md` means it is done. Otherwise the command lists every problem: fix
92
- the draft and run it again. It refuses a spec whose issue does not exist yet, and a
93
- dependency that is no issue of the repository.
130
+ `factory run issue-save --issue <slug> --kind issue --file <absolute path>` for `issue.md`,
131
+ `--kind spec` for `spec.md`, or `--kind page` for the spec's presentation (`spec-page.json`).
132
+ 3. `Saved .factory/issues/<slug>/<file>` means it is done. Otherwise the command lists every problem: fix
133
+ the draft and run it again. It refuses a spec whose issue does not exist yet, a
134
+ dependency that is no issue of the repository, and a presentation that does not present the saved spec.
94
135
 
95
136
  ## Boundaries
96
137
 
97
138
  Never write in `.factory/issues/` directly, never commit, and never run `factory run implement`. When the spec
98
- is saved, tell the operator the next steps: review it, commit it and push it on the main branch, then run
99
- `factory run implement --issue <slug>`.
139
+ and its presentation are saved, tell the operator the next steps: validate it on its page in `factory ui` (the
140
+ Spec link of the issue's card), commit the spec and its `spec-page.json` and push them on the main branch, then
141
+ run `factory run implement --issue <slug>`.