@clemrig/factory 0.20.0 → 0.22.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 +30 -17
- package/dist/app/prototype.d.ts +15 -0
- package/dist/app/prototype.js +65 -0
- package/dist/app/prototype.js.map +1 -0
- package/dist/app/review.js +3 -2
- package/dist/app/review.js.map +1 -1
- package/dist/app/spec-page.d.ts +15 -2
- package/dist/app/spec-page.js +45 -10
- package/dist/app/spec-page.js.map +1 -1
- package/dist/app/workflows/issue-save.js +63 -20
- package/dist/app/workflows/issue-save.js.map +1 -1
- package/dist/app/workflows/stages/agents.d.ts +5 -0
- package/dist/app/workflows/stages/agents.js +5 -0
- package/dist/app/workflows/stages/agents.js.map +1 -1
- package/dist/app/workflows/stages/deliver.js +11 -3
- package/dist/app/workflows/stages/deliver.js.map +1 -1
- package/dist/app/workflows/stages/implement.js +8 -2
- package/dist/app/workflows/stages/implement.js.map +1 -1
- package/dist/app/workflows/stages/plan.js +2 -0
- package/dist/app/workflows/stages/plan.js.map +1 -1
- package/dist/app/workflows/stages/prepare.js +15 -1
- package/dist/app/workflows/stages/prepare.js.map +1 -1
- package/dist/app/workflows/stages/review.js +9 -2
- package/dist/app/workflows/stages/review.js.map +1 -1
- package/dist/app/workflows/stages/run.d.ts +14 -1
- package/dist/app/workflows/stages/run.js.map +1 -1
- package/dist/cli/ui.js +3 -2
- package/dist/cli/ui.js.map +1 -1
- package/dist/domain/pull-request.d.ts +9 -2
- package/dist/domain/pull-request.js +12 -2
- package/dist/domain/pull-request.js.map +1 -1
- package/dist/domain/spec-page.d.ts +73 -17
- package/dist/domain/spec-page.js +93 -6
- package/dist/domain/spec-page.js.map +1 -1
- package/dist/domain/spec.d.ts +13 -1
- package/dist/domain/spec.js +60 -8
- package/dist/domain/spec.js.map +1 -1
- package/dist/domain/tidy.d.ts +2 -1
- package/dist/domain/tidy.js +5 -1
- package/dist/domain/tidy.js.map +1 -1
- package/dist/system/git.d.ts +4 -0
- package/dist/system/git.js +13 -0
- package/dist/system/git.js.map +1 -1
- package/dist/system/images.d.ts +16 -0
- package/dist/system/images.js +51 -0
- package/dist/system/images.js.map +1 -0
- package/dist/system/issues.d.ts +18 -1
- package/dist/system/issues.js +63 -3
- package/dist/system/issues.js.map +1 -1
- package/dist/system/run-files.d.ts +0 -8
- package/dist/system/run-files.js +1 -28
- package/dist/system/run-files.js.map +1 -1
- package/package.json +4 -4
- package/prompts/implement.md +11 -1
- package/prompts/plan.md +18 -6
- package/prompts/review.md +13 -4
- package/templates/init/factory.ts +18 -0
- package/templates/skills/issue/SKILL.md +48 -18
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** An image file's bytes and media type. */
|
|
2
|
+
export interface Image {
|
|
3
|
+
body: Buffer;
|
|
4
|
+
type: string;
|
|
5
|
+
}
|
|
6
|
+
/** The media type of a PNG, JPEG, GIF or WebP image, from its first bytes; `undefined` for anything else. */
|
|
7
|
+
export declare function imageType(start: Buffer): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* The image at `path`, with its media type: `undefined` when it is no image
|
|
10
|
+
* file of its own there, a link included, or is over `maxBytes`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readImage(path: string, maxBytes: number): Promise<Image | undefined>;
|
|
13
|
+
/** An image an agent hands over with a draft, by its absolute path; otherwise why it is none. */
|
|
14
|
+
export declare function readDraftImage(path: string, maxBytes: number): Promise<Image | string>;
|
|
15
|
+
/** Image `file` of `folder`, named alone: a path that leaves the folder reads nothing. */
|
|
16
|
+
export declare function readImageIn(folder: string, file: string, maxBytes: number): Promise<Image | undefined>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, isAbsolute, join } from "node:path";
|
|
3
|
+
/** The media type of a PNG, JPEG, GIF or WebP image, from its first bytes; `undefined` for anything else. */
|
|
4
|
+
export function imageType(start) {
|
|
5
|
+
const ascii = (from, to) => start.subarray(from, to).toString("latin1");
|
|
6
|
+
if (ascii(0, 8) === "\x89PNG\r\n\x1a\n")
|
|
7
|
+
return "image/png";
|
|
8
|
+
if (ascii(0, 3) === "\xff\xd8\xff")
|
|
9
|
+
return "image/jpeg";
|
|
10
|
+
if (ascii(0, 4) === "GIF8")
|
|
11
|
+
return "image/gif";
|
|
12
|
+
if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP")
|
|
13
|
+
return "image/webp";
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
/** The image at `path`, with its media type, or why it is none: no file of its own there, a link included, or over `maxBytes`. */
|
|
17
|
+
async function inspectImage(path, maxBytes) {
|
|
18
|
+
const found = await lstat(path).catch(() => undefined);
|
|
19
|
+
if (!found)
|
|
20
|
+
return "does not exist";
|
|
21
|
+
if (found.isSymbolicLink())
|
|
22
|
+
return "is a link: give the image file itself";
|
|
23
|
+
if (!found.isFile())
|
|
24
|
+
return "is not a file";
|
|
25
|
+
if (found.size > maxBytes)
|
|
26
|
+
return `has ${found.size} bytes, over ${maxBytes / 1024 / 1024} MiB`;
|
|
27
|
+
const body = await readFile(path);
|
|
28
|
+
const type = imageType(body);
|
|
29
|
+
return type ? { body, type } : "is not a PNG, JPEG, GIF or WebP image";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The image at `path`, with its media type: `undefined` when it is no image
|
|
33
|
+
* file of its own there, a link included, or is over `maxBytes`.
|
|
34
|
+
*/
|
|
35
|
+
export async function readImage(path, maxBytes) {
|
|
36
|
+
const image = await inspectImage(path, maxBytes);
|
|
37
|
+
return typeof image === "string" ? undefined : image;
|
|
38
|
+
}
|
|
39
|
+
/** An image an agent hands over with a draft, by its absolute path; otherwise why it is none. */
|
|
40
|
+
export async function readDraftImage(path, maxBytes) {
|
|
41
|
+
return isAbsolute(path)
|
|
42
|
+
? inspectImage(path, maxBytes)
|
|
43
|
+
: "is not an absolute path";
|
|
44
|
+
}
|
|
45
|
+
/** Image `file` of `folder`, named alone: a path that leaves the folder reads nothing. */
|
|
46
|
+
export async function readImageIn(folder, file, maxBytes) {
|
|
47
|
+
if (basename(file) !== file)
|
|
48
|
+
return undefined;
|
|
49
|
+
return readImage(join(folder, file), maxBytes);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=images.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../src/system/images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQvD,6GAA6G;AAC7G,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE,CACzC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,mBAAmB;QAAE,OAAO,WAAW,CAAC;IAC5D,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,cAAc;QAAE,OAAO,YAAY,CAAC;IACxD,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM;QAAE,OAAO,WAAW,CAAC;IAC/C,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM;QAAE,OAAO,YAAY,CAAC;IAC3E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,kIAAkI;AAClI,KAAK,UAAU,YAAY,CACzB,IAAY,EACZ,QAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,gBAAgB,CAAC;IACpC,IAAI,KAAK,CAAC,cAAc,EAAE;QAAE,OAAO,uCAAuC,CAAC;IAC3E,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;QAAE,OAAO,eAAe,CAAC;IAC5C,IAAI,KAAK,CAAC,IAAI,GAAG,QAAQ;QACvB,OAAO,OAAO,KAAK,CAAC,IAAI,gBAAgB,QAAQ,GAAG,IAAI,GAAG,IAAI,MAAM,CAAC;IACvE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,uCAAuC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAY,EACZ,QAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACvD,CAAC;AAED,iGAAiG;AACjG,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,QAAgB;IAEhB,OAAO,UAAU,CAAC,IAAI,CAAC;QACrB,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC;QAC9B,CAAC,CAAC,yBAAyB,CAAC;AAChC,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,IAAY,EACZ,QAAgB;IAEhB,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC9C,OAAO,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC"}
|
package/dist/system/issues.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Image } from "./images.js";
|
|
1
2
|
/** An issue's files: a folder with none of them is not an issue. */
|
|
2
3
|
declare const artifactKinds: readonly ["issue", "spec", "plan"];
|
|
3
4
|
export type ArtifactKind = (typeof artifactKinds)[number];
|
|
@@ -30,7 +31,23 @@ export declare function readArtifact(repository: string, slug: string, kind: Art
|
|
|
30
31
|
export declare function readDraft(path: string): Promise<string>;
|
|
31
32
|
/** The issue's presented spec, as kept; nothing when its spec is not presented. */
|
|
32
33
|
export declare function readSpecPage(repository: string, slug: string): Promise<unknown | undefined>;
|
|
33
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Keeps the presentation of the issue's spec, `value`, and the captures it
|
|
36
|
+
* names, which replace the previous ones whole. Both are written apart, then
|
|
37
|
+
* renamed in: a failure leaves the previous presentation as it was, with
|
|
38
|
+
* nothing Git would see as a change in progress. Returns where they are.
|
|
39
|
+
*/
|
|
40
|
+
export declare function writeSpecPage(repository: string, slug: string, value: unknown, captures: {
|
|
41
|
+
name: string;
|
|
42
|
+
body: Uint8Array;
|
|
43
|
+
}[]): Promise<{
|
|
44
|
+
page: string;
|
|
45
|
+
captures?: string;
|
|
46
|
+
}>;
|
|
47
|
+
/** Where capture `file` of the issue's spec page is, absolute. */
|
|
48
|
+
export declare function specCapturePath(repository: string, slug: string, file: string): string;
|
|
49
|
+
/** Capture `file` of the issue's spec page; `undefined` when there is no such image. */
|
|
50
|
+
export declare function readSpecCapture(repository: string, slug: string, file: string, maxBytes: number): Promise<Image | undefined>;
|
|
34
51
|
/** Where the operator is in the issue's spec validation; nothing before the first answer. */
|
|
35
52
|
export declare function readSpecAnswers(repository: string, slug: string): Promise<unknown | undefined>;
|
|
36
53
|
export declare function keepSpecAnswers(repository: string, slug: string, answers: unknown): Promise<void>;
|
package/dist/system/issues.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { access, mkdir, readdir, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { readImageIn } from "./images.js";
|
|
4
5
|
/** An issue's files: a folder with none of them is not an issue. */
|
|
5
6
|
const artifactKinds = ["issue", "spec", "plan"];
|
|
6
7
|
/** Where the issues are, relative to the repository. */
|
|
@@ -13,6 +14,10 @@ export function artifactPath(slug, kind) {
|
|
|
13
14
|
function specPagePath(slug) {
|
|
14
15
|
return join(issuesPath, slug, "spec-page.json");
|
|
15
16
|
}
|
|
17
|
+
/** `.factory/issues/<slug>/spec-page/`: the captures its spec's presentation shows, kept with it. */
|
|
18
|
+
function capturesPath(slug) {
|
|
19
|
+
return join(issuesPath, slug, "spec-page");
|
|
20
|
+
}
|
|
16
21
|
/** `.factory/issues/<slug>/spec-answers.json`: where the operator is in its validation, which Git ignores. */
|
|
17
22
|
function specAnswersPath(slug) {
|
|
18
23
|
return join(issuesPath, slug, "spec-answers.json");
|
|
@@ -100,12 +105,13 @@ async function readJson(repository, path) {
|
|
|
100
105
|
throw new Error(`${path} is not JSON`);
|
|
101
106
|
}
|
|
102
107
|
}
|
|
108
|
+
const jsonText = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
103
109
|
/** Writes `value` as the JSON file `path` of the repository, replacing it whole. */
|
|
104
110
|
async function writeJson(repository, path, value) {
|
|
105
111
|
const target = join(repository, path);
|
|
106
112
|
await mkdir(dirname(target), { recursive: true });
|
|
107
113
|
const temporary = `${target}.${randomUUID()}.tmp`;
|
|
108
|
-
await writeFile(temporary,
|
|
114
|
+
await writeFile(temporary, jsonText(value));
|
|
109
115
|
await rename(temporary, target);
|
|
110
116
|
return path;
|
|
111
117
|
}
|
|
@@ -113,8 +119,62 @@ async function writeJson(repository, path, value) {
|
|
|
113
119
|
export function readSpecPage(repository, slug) {
|
|
114
120
|
return readJson(repository, specPagePath(slug));
|
|
115
121
|
}
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Keeps the presentation of the issue's spec, `value`, and the captures it
|
|
124
|
+
* names, which replace the previous ones whole. Both are written apart, then
|
|
125
|
+
* renamed in: a failure leaves the previous presentation as it was, with
|
|
126
|
+
* nothing Git would see as a change in progress. Returns where they are.
|
|
127
|
+
*/
|
|
128
|
+
export async function writeSpecPage(repository, slug, value, captures) {
|
|
129
|
+
const page = join(repository, specPagePath(slug));
|
|
130
|
+
const folder = join(repository, capturesPath(slug));
|
|
131
|
+
const id = randomUUID();
|
|
132
|
+
const written = { page: `${page}.${id}.tmp`, folder: `${folder}.${id}.tmp` };
|
|
133
|
+
const previous = `${folder}.${id}.old`;
|
|
134
|
+
const discard = () => Promise.all([written.page, written.folder].map((path) => rm(path, { recursive: true, force: true })));
|
|
135
|
+
try {
|
|
136
|
+
await mkdir(dirname(page), { recursive: true });
|
|
137
|
+
if (captures.length > 0) {
|
|
138
|
+
await mkdir(written.folder);
|
|
139
|
+
for (const { name, body } of captures)
|
|
140
|
+
await writeFile(join(written.folder, name), body);
|
|
141
|
+
}
|
|
142
|
+
await writeFile(written.page, jsonText(value));
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
await discard();
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
const moved = await rename(folder, previous).then(() => true, (error) => {
|
|
149
|
+
if (error.code === "ENOENT")
|
|
150
|
+
return false;
|
|
151
|
+
throw error;
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
if (captures.length > 0)
|
|
155
|
+
await rename(written.folder, folder);
|
|
156
|
+
await rename(written.page, page);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
await rm(folder, { recursive: true, force: true });
|
|
160
|
+
if (moved)
|
|
161
|
+
await rename(previous, folder);
|
|
162
|
+
await discard();
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
await rm(previous, { recursive: true, force: true });
|
|
166
|
+
return {
|
|
167
|
+
page: specPagePath(slug),
|
|
168
|
+
...(captures.length > 0 ? { captures: `${capturesPath(slug)}/` } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Where capture `file` of the issue's spec page is, absolute. */
|
|
172
|
+
export function specCapturePath(repository, slug, file) {
|
|
173
|
+
return join(repository, capturesPath(slug), file);
|
|
174
|
+
}
|
|
175
|
+
/** Capture `file` of the issue's spec page; `undefined` when there is no such image. */
|
|
176
|
+
export function readSpecCapture(repository, slug, file, maxBytes) {
|
|
177
|
+
return readImageIn(join(repository, capturesPath(slug)), file, maxBytes);
|
|
118
178
|
}
|
|
119
179
|
/** Where the operator is in the issue's spec validation; nothing before the first answer. */
|
|
120
180
|
export async function readSpecAnswers(repository, slug) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issues.js","sourceRoot":"","sources":["../../src/system/issues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,EACR,MAAM,EACN,EAAE,EACF,IAAI,EACJ,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"issues.js","sourceRoot":"","sources":["../../src/system/issues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,EACR,MAAM,EACN,EAAE,EACF,IAAI,EACJ,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAc,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,oEAAoE;AACpE,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAU,CAAC;AAGzD,wDAAwD;AACxD,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAErD,6GAA6G;AAC7G,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAkB;IAC3D,OAAO,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,2FAA2F;AAC3F,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAClD,CAAC;AAED,qGAAqG;AACrG,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC7C,CAAC;AAED,8GAA8G;AAC9G,SAAS,eAAe,CAAC,IAAY;IACnC,OAAO,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,mBAAmB,CAAC,CAAC;AACrD,CAAC;AAgBD,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAkB;IAElB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAChE,CAAC,KAA4B,EAAE,EAAE;QAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACvC,MAAM,KAAK,CAAC;IACd,CAAC,CACF,CAAC;IACF,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,sEAAsE;QACtE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC;YAAE,SAAS;QAC1E,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CACrC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO;QACxB,iEAAiE;QACjE,CAAC,KAA4B,EAAE,EAAE;YAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,CAAC,CAAC;YACtC,MAAM,KAAK,CAAC;QACd,CAAC,CACF,CACF,CACF,CAAC;QACF,4DAA4D;QAC5D,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CACpE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EACpB,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC;YACnB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC,CACH,CAAC;QACF,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,CAAC,MAAM,IAAI,KAAK;gBACjB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;gBACvB,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YAChC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;YAClC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAC7C,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE;SACxD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAAkB,EAClB,IAAY,EACZ,IAAkB,EAClB,IAAY;IAEZ,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAAkB,EAClB,IAAY,EACZ,IAAkB;IAElB,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAC5D,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACZ,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,UAAkB,EAClB,IAAY;IAEZ,KAAK,MAAM,IAAI,IAAI,aAAa;QAC9B,IAAI,MAAM,WAAW,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IAC7D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAkB,EAClB,IAAY,EACZ,IAAkB;IAElB,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACnE,OAAO,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAChC,CAAC;AAED,oEAAoE;AACpE,KAAK,UAAU,QAAQ,CACrB,UAAkB,EAClB,IAAY;IAEZ,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACzE,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAC;IACzC,CAAC;AACH,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAE3E,oFAAoF;AACpF,KAAK,UAAU,SAAS,CACtB,UAAkB,EAClB,IAAY,EACZ,KAAc;IAEd,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,UAAU,EAAE,MAAM,CAAC;IAClD,MAAM,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,IAAY;IAEZ,OAAO,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAAkB,EAClB,IAAY,EACZ,KAAc,EACd,QAA8C;IAE9C,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;IAC7E,MAAM,QAAQ,GAAG,GAAG,MAAM,IAAI,EAAE,MAAM,CAAC;IACvC,MAAM,OAAO,GAAG,GAAG,EAAE,CACnB,OAAO,CAAC,GAAG,CACT,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1C,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAC3C,CACF,CAAC;IACJ,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC5B,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,QAAQ;gBACnC,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;QACtD,CAAC;QACD,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,IAAI,CAC/C,GAAG,EAAE,CAAC,IAAI,EACV,CAAC,KAA4B,EAAE,EAAE;QAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,KAAK,CAAC;IACd,CAAC,CACF,CAAC;IACF,IAAI,CAAC;QACH,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,IAAI,KAAK;YAAE,MAAM,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,OAAO,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,OAAO;QACL,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC;QACxB,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvE,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAC7B,UAAkB,EAClB,IAAY,EACZ,IAAY;IAEZ,OAAO,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAC7B,UAAkB,EAClB,IAAY,EACZ,IAAY,EACZ,QAAgB;IAEhB,OAAO,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC3E,CAAC;AAED,6FAA6F;AAC7F,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAAkB,EAClB,IAAY;IAEZ,yDAAyD;IACzD,OAAO,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAAkB,EAClB,IAAY,EACZ,OAAgB;IAEhB,MAAM,SAAS,CAAC,UAAU,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAkB,EAClB,IAAY;IAEZ,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC"}
|
|
@@ -17,14 +17,6 @@ export declare function keepCheckOutputs(runDir: string, passed: PassedCheck[]):
|
|
|
17
17
|
export declare function keepFailedCheck(runDir: string, name: string, output: string): Promise<string>;
|
|
18
18
|
/** The run's folder where the describing agent puts its screenshots, created empty. */
|
|
19
19
|
export declare function screenshotsFolder(runDir: string): Promise<string>;
|
|
20
|
-
/**
|
|
21
|
-
* Screenshot `file` of `folder`, with its media type: `undefined` when it is
|
|
22
|
-
* no image file of its own there, a link included, or is over `maxBytes`.
|
|
23
|
-
*/
|
|
24
|
-
export declare function readScreenshot(folder: string, file: string, maxBytes: number): Promise<{
|
|
25
|
-
body: Buffer;
|
|
26
|
-
type: string;
|
|
27
|
-
} | undefined>;
|
|
28
20
|
/** The answers of a run's review page, as kept: `undefined` before the first, or when they are no JSON. */
|
|
29
21
|
export declare function readAnswers(runDir: string): Promise<unknown>;
|
|
30
22
|
/** Keeps the answers of a run's review page, replacing the file whole. */
|
package/dist/system/run-files.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { lstat, mkdir, open, readdir, readFile, realpath, rename, writeFile, } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { imageType } from "./images.js";
|
|
4
5
|
/** Keeps `text` in the run's directory, at `path` within it; returns where, absolute. */
|
|
5
6
|
export async function keepInRun(runDir, path, text) {
|
|
6
7
|
const file = join(runDir, path);
|
|
@@ -38,34 +39,6 @@ export async function screenshotsFolder(runDir) {
|
|
|
38
39
|
await mkdir(folder, { recursive: true });
|
|
39
40
|
return folder;
|
|
40
41
|
}
|
|
41
|
-
/** The media type of a PNG, JPEG, GIF or WebP image, from its first bytes; `undefined` for anything else. */
|
|
42
|
-
function imageType(start) {
|
|
43
|
-
const ascii = (from, to) => start.subarray(from, to).toString("latin1");
|
|
44
|
-
if (ascii(0, 8) === "\x89PNG\r\n\x1a\n")
|
|
45
|
-
return "image/png";
|
|
46
|
-
if (ascii(0, 3) === "\xff\xd8\xff")
|
|
47
|
-
return "image/jpeg";
|
|
48
|
-
if (ascii(0, 4) === "GIF8")
|
|
49
|
-
return "image/gif";
|
|
50
|
-
if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP")
|
|
51
|
-
return "image/webp";
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Screenshot `file` of `folder`, with its media type: `undefined` when it is
|
|
56
|
-
* no image file of its own there, a link included, or is over `maxBytes`.
|
|
57
|
-
*/
|
|
58
|
-
export async function readScreenshot(folder, file, maxBytes) {
|
|
59
|
-
if (basename(file) !== file)
|
|
60
|
-
return undefined;
|
|
61
|
-
const path = join(folder, file);
|
|
62
|
-
const found = await lstat(path).catch(() => undefined);
|
|
63
|
-
if (!found?.isFile() || found.size > maxBytes)
|
|
64
|
-
return undefined;
|
|
65
|
-
const body = await readFile(path);
|
|
66
|
-
const type = imageType(body);
|
|
67
|
-
return type ? { body, type } : undefined;
|
|
68
|
-
}
|
|
69
42
|
/** The answers of a run's review page, as kept: `undefined` before the first, or when they are no JSON. */
|
|
70
43
|
export async function readAnswers(runDir) {
|
|
71
44
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-files.js","sourceRoot":"","sources":["../../src/system/run-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,KAAK,EACL,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"run-files.js","sourceRoot":"","sources":["../../src/system/run-files.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.
|
|
3
|
+
"version": "0.22.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,8 +39,8 @@
|
|
|
39
39
|
"prepack": "npm run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@clemrig/factory-ui": "0.
|
|
43
|
-
"@clemrig/flow": "0.
|
|
44
|
-
"@clemrig/flow-ui": "0.
|
|
42
|
+
"@clemrig/factory-ui": "0.6.0",
|
|
43
|
+
"@clemrig/flow": "0.12.0",
|
|
44
|
+
"@clemrig/flow-ui": "0.13.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/prompts/implement.md
CHANGED
|
@@ -3,7 +3,8 @@ content, even when the request, the code or these instructions use another; keep
|
|
|
3
3
|
they are.
|
|
4
4
|
|
|
5
5
|
First read the repository's agent instructions: `AGENTS.md` at the root and in each directory you touch.
|
|
6
|
-
Follow their conventions and validation guidance. Ignore `.factory
|
|
6
|
+
Follow their conventions and validation guidance. Ignore `.factory/`, except the files the request names, and never
|
|
7
|
+
change it.
|
|
7
8
|
|
|
8
9
|
The request gives the spec, approved and unchanging, and the plan. The plan is your guide, not a script: its
|
|
9
10
|
`existing` says what the code already does and must keep doing, and each step's `cases` the situations it must
|
|
@@ -11,6 +12,15 @@ handle and test besides its main path. When the code shows a better way than a s
|
|
|
11
12
|
in `deviations`. Implement the spec through the plan, tests included, and run the relevant checks yourself before
|
|
12
13
|
answering.
|
|
13
14
|
|
|
15
|
+
When the request gives `prototype`, the spec draws on a throwaway prototype the operator validated. Read it by its
|
|
16
|
+
`commit`, which is in this repository (its `branch` may have moved since, or exist only on origin):
|
|
17
|
+
`git diff HEAD...<commit>` shows what it changed, `git show <commit>:<path>` a file as it was there; its `captures`
|
|
18
|
+
are screenshots of its screens the operator validated. Build what the spec asks as it shows it, with the same
|
|
19
|
+
screens, texts, behavior and existing components, where the spec and the plan do not settle them, and compare what
|
|
20
|
+
you build with its captures in the browser; where they differ, the spec and the plan win. Its code was written
|
|
21
|
+
without the repository's checks: write the change to the repository's standards, tests included, rather than copying
|
|
22
|
+
it, and bring nothing it does beyond the spec.
|
|
23
|
+
|
|
14
24
|
When the change shows in a web interface, check it in a browser before answering. When the request gives `app`, the
|
|
15
25
|
application already runs this working tree at `app.url`, with data of its own that you may change freely and that is
|
|
16
26
|
removed once you answer; its output is in the file `app.logs`, and your commands run with its variables, so that
|
package/prompts/plan.md
CHANGED
|
@@ -2,9 +2,9 @@ You plan the implementation of an approved spec in this repository. You change n
|
|
|
2
2
|
answer in the language of the spec's content, except `commit`, in English, even when the request, the code or
|
|
3
3
|
these instructions use another; keep code, paths and identifiers as they are.
|
|
4
4
|
|
|
5
|
-
Before planning, read the repository's agent instructions: `AGENTS.md` at the root and in each directory the
|
|
6
|
-
|
|
7
|
-
truth, and `.factory/runs/` holds agents' outputs.
|
|
5
|
+
Before planning, read the repository's agent instructions: `AGENTS.md` at the root and in each directory the change
|
|
6
|
+
will touch, and follow them. Ignore `.factory/`, except the files the request names: the other issues there are
|
|
7
|
+
history, not the current truth, and `.factory/runs/` holds agents' outputs.
|
|
8
8
|
|
|
9
9
|
The request gives the issue's slug and its spec. The spec is approved and does not change: plan what it asks,
|
|
10
10
|
nothing more. When the spec reads more than one way, choose the reading most consistent with the code and record it
|
|
@@ -12,6 +12,16 @@ in `interpretations`. When its letter would give a behavior its objective would
|
|
|
12
12
|
same and record the conflict in `interpretations`: only whoever approved the spec can depart from it, and the
|
|
13
13
|
review holds the change to its letter.
|
|
14
14
|
|
|
15
|
+
When the request gives `prototype`, the spec draws on a throwaway prototype the operator validated. Read it by its
|
|
16
|
+
`commit`, which is in this repository (its `branch` may have moved since, or exist only on origin):
|
|
17
|
+
`git diff HEAD...<commit>` shows what it changed, `git show <commit>:<path>` a file as it was there, and its notes,
|
|
18
|
+
when it has some (a `PROTOTYPE.md`, say), what was validated. Its `captures` are screenshots of its screens the
|
|
19
|
+
operator validated on the spec's page, files you can open. It shows how the operator saw what the spec asks: its
|
|
20
|
+
screens, texts and behavior, and the existing components it reuses; plan the spec that way where the spec does not
|
|
21
|
+
settle it. The spec still decides: where the prototype differs from it, plan the spec and record the difference in
|
|
22
|
+
`interpretations`; nothing the prototype does beyond the spec is approved. Its code was written without the
|
|
23
|
+
repository's checks: the plan builds what it shows, not its code as it is.
|
|
24
|
+
|
|
15
25
|
## Explore first
|
|
16
26
|
|
|
17
27
|
Before writing a step, find out how the code works where the spec changes it. For each area the spec touches,
|
|
@@ -22,9 +32,11 @@ such as an application, a layer or a feature, establish from the code itself, no
|
|
|
22
32
|
- the conventions the change must follow: texts and their plural forms, errors, API shapes, migrations;
|
|
23
33
|
- the tests and tools that prove such behavior in this repository.
|
|
24
34
|
|
|
25
|
-
When the spec touches several areas and you can launch sub-agents, give each, five at most, one area: the spec,
|
|
26
|
-
the questions above for that area
|
|
27
|
-
|
|
35
|
+
When the spec touches several areas and you can launch sub-agents, give each, five at most, one area: the spec, the
|
|
36
|
+
request's `prototype` when it gives one, the questions above for that area and the areas the others take, so that
|
|
37
|
+
each reads its own. Tell each to report facts with the file and symbol they come from, and what it looked for without
|
|
38
|
+
finding it; to run the searches and reads that do not depend on each other together, in the same turn; to change
|
|
39
|
+
nothing and to launch no agent itself. Check against the code the facts the plan depends on.
|
|
28
40
|
|
|
29
41
|
## The plan
|
|
30
42
|
|
package/prompts/review.md
CHANGED
|
@@ -12,6 +12,13 @@ The request gives the spec, the change and the repository's checks, which all pa
|
|
|
12
12
|
`diff`; a large one (`large` is true) with `diffFile`, the path of its diff: read the parts you review from it.
|
|
13
13
|
- `checks` gives each check's command, its duration and `outputFile`, where the run kept what it wrote.
|
|
14
14
|
|
|
15
|
+
When the request gives `prototype`, the spec was written from a throwaway prototype the operator validated. Read it
|
|
16
|
+
by its `commit`, which is in this repository (its `branch` may have moved since, or exist only on origin):
|
|
17
|
+
`git diff HEAD...<commit>` shows what it changed, `git show <commit>:<path>` a file as it was there; its `captures`
|
|
18
|
+
are screenshots of its screens the operator validated. Read them to understand what the spec's words describe, the
|
|
19
|
+
screens, texts and behavior they name. The spec remains the measure: what the prototype does beyond the spec is not
|
|
20
|
+
required.
|
|
21
|
+
|
|
15
22
|
Do not take anything on trust: verify each acceptance criterion in the code and the tests, reading files beyond the
|
|
16
23
|
diff when needed. Look for unmet criteria, defects, regressions, missing or weak tests, security problems and
|
|
17
24
|
departures from the repository's conventions. Follow every path the change must handle, failures, interruptions,
|
|
@@ -49,10 +56,12 @@ split the whole review between them for your first answer, five at most, and rev
|
|
|
49
56
|
keep while you coordinate the others gets less attention than theirs. Each takes one part of the change, such as an
|
|
50
57
|
application, a layer or a feature, with its checks in a browser when it shows in a web interface; one more, always,
|
|
51
58
|
takes the paths that cross the parts: failures, interruptions, concurrent requests and recoveries. Give each the
|
|
52
|
-
spec, its part, the files concerned, where to read the diff, and the request's `app` and `browser` when
|
|
53
|
-
them, with the rules above on servers and data, and tell it to report its findings with their evidence,
|
|
54
|
-
as above, to look for each defect's other occurrences, to
|
|
55
|
-
|
|
59
|
+
spec, its part, the files concerned, where to read the diff, and the request's `prototype`, `app` and `browser` when
|
|
60
|
+
it gives them, with the rules above on servers and data, and tell it to report its findings with their evidence,
|
|
61
|
+
established as above, to look for each defect's other occurrences, to run the reads, searches and inspection commands
|
|
62
|
+
that do not depend on each other together, in the same turn, but tests and browser checks one at a time, to change
|
|
63
|
+
nothing and to launch no agent itself.
|
|
64
|
+
Check their reports, then give one answer for the whole change.
|
|
56
65
|
|
|
57
66
|
Answer with:
|
|
58
67
|
|
|
@@ -12,6 +12,24 @@ const opus = {
|
|
|
12
12
|
const planner = {
|
|
13
13
|
...opus,
|
|
14
14
|
tools: ["Read", "Grep", "Glob", "Agent"],
|
|
15
|
+
agents: {
|
|
16
|
+
// In place of Claude Code's: one area of the code, read only and reported
|
|
17
|
+
// as facts, on the planner's model and effort; `thinking` would give it
|
|
18
|
+
// its own effort.
|
|
19
|
+
Explore: {
|
|
20
|
+
description:
|
|
21
|
+
"Read-only explorer of one area of the code. Give it the area, the questions to answer about it and the areas other agents take; it answers with facts, each with the file and symbol it comes from.",
|
|
22
|
+
prompt: [
|
|
23
|
+
"You explore one area of this repository for an agent that plans a change, and answer the questions it asks about that area. You change nothing: you only search and read.",
|
|
24
|
+
"Establish each answer from the code itself, not from names or documentation alone: read the whole of what an answer depends on, not only the lines a search matches. Stay in your area; outside it, read only what an answer depends on, such as a function your area calls, and leave the areas other agents take to them.",
|
|
25
|
+
"Run the searches and reads that do not depend on each other together, in the same turn.",
|
|
26
|
+
"Answer with facts, each with the file and symbol it comes from, then what you looked for and did not find, and what you could not establish. Propose no design and no code: the planning agent decides.",
|
|
27
|
+
].join("\n\n"),
|
|
28
|
+
tools: ["Read", "Grep", "Glob"],
|
|
29
|
+
// As Claude Code's Explore: the planner's request gives what it needs.
|
|
30
|
+
omitClaudeMd: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
15
33
|
timeoutMs: 30 * minutes,
|
|
16
34
|
} satisfies AgentProfile;
|
|
17
35
|
// Reads the change and runs what checks it: tests, the application in a
|
|
@@ -19,16 +19,25 @@ description of the need. Once the operator agrees, save the issue (see Save).
|
|
|
19
19
|
|
|
20
20
|
1. Read the repository's agent instructions (`AGENTS.md` at the root and in the directories concerned) and the
|
|
21
21
|
relevant code. Change nothing.
|
|
22
|
-
2.
|
|
22
|
+
2. When the operator built a prototype for this need (they say so, or the issue names one), it is what they
|
|
23
|
+
validated: write the spec from it, not from what you would design, and ask only about what it leaves open.
|
|
24
|
+
- It must be committed, its notes included (a `PROTOTYPE.md`, say), on a branch named with a slash, such as
|
|
25
|
+
`prototype/<slug>`, and pushed: if it is not, ask the operator to do it, or to let you do it there. The spec
|
|
26
|
+
names that branch and the full id of the commit they validated (see Format).
|
|
27
|
+
- Read it at that commit, before asking anything: its notes, what it changed (`git diff HEAD...<commit>`) and
|
|
28
|
+
its screens. To run it, add a working tree of that commit outside the repository
|
|
29
|
+
(`git worktree add --detach <folder> <commit>`, removed once done) and start it as the repository's
|
|
30
|
+
instructions say, or ask the operator where it already runs; never switch branches in the repository.
|
|
31
|
+
3. Ask the operator questions, a few at a time, until you can fill every section without inventing anything.
|
|
23
32
|
Do not ask what the code or the docs already answer. For each question, give its practical consequence
|
|
24
33
|
and your recommendation.
|
|
25
|
-
|
|
34
|
+
4. Ask whether the issue relies on another issue that must be delivered first: its spec assumes behavior that
|
|
26
35
|
issue brings. List the issues of `.factory/issues/` that look related, with whether each is delivered (its
|
|
27
36
|
`plan.md` is on the main branch), and record the operator's answer in Dependencies.
|
|
28
|
-
|
|
37
|
+
5. Describe what and why, never how: the run that implements the spec plans the technical work itself.
|
|
29
38
|
Where you chose between real alternatives, write the one you recommend: the presentation shows the others.
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
6. Propose the complete spec and wait for the operator's agreement, then save it (see Save).
|
|
40
|
+
7. Present it for the operator to validate it on its page (see Present).
|
|
32
41
|
|
|
33
42
|
Other issues in `.factory/issues/` are history, not the current truth: rely on the code and the docs, and read
|
|
34
43
|
them only to find the issues this one depends on.
|
|
@@ -57,11 +66,19 @@ JSON draft outside the repository, then save it with `--kind page` (see Save):
|
|
|
57
66
|
|
|
58
67
|
- One change per part of the scope a person would judge on its own; `text` in a few sentences, `**bold**` and
|
|
59
68
|
`` `code` `` allowed.
|
|
60
|
-
- A mockup only for a change that brings or changes an interface
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
- A mockup only for a change that brings or changes an interface. When the spec's prototype shows it, capture it,
|
|
70
|
+
never draw it: the operator validated that screen. Run the prototype at the spec's commit (see Specify), open the
|
|
71
|
+
state the change describes in a browser (`.factory/node_modules/.bin/playwright-cli`, which `factory init`
|
|
72
|
+
installs: run it with `--help` first, or the browser tools you have) and save a screenshot (PNG, JPEG or WebP,
|
|
73
|
+
2 MiB at most) beside the draft, outside the repository; give its absolute path,
|
|
74
|
+
`{ "caption": "…", "image": "<path>" }`, and `issue-save` keeps the image beside the spec. When you cannot run
|
|
75
|
+
the prototype or take the screenshot, ask the operator for it. Capture again whenever the spec names another
|
|
76
|
+
commit. Only an interface the prototype does not show is drawn:
|
|
77
|
+
`{ "caption": "…", "html": "<a whole HTML document>", "width": <320–1600>, "height": <200–4000> }`, shown in a
|
|
78
|
+
frame without script, network or anything but its own HTML and inline styles: no `<script>`, no link to a
|
|
79
|
+
stylesheet, a font or an image (draw them, or use `data:` images); under 256 KiB. Reproduce the application's
|
|
80
|
+
existing pages and components from its code and styles and mark what is new; invent no component, text or state
|
|
81
|
+
that neither the application nor the spec has.
|
|
65
82
|
- A decision for each choice where the spec took one of real alternatives (2 to 5 options), the one it took as
|
|
66
83
|
`chosen`; none when there was nothing to choose. An `<id>` starts with a lowercase letter, then lowercase
|
|
67
84
|
letters, digits or hyphens, 40 characters at most.
|
|
@@ -112,15 +129,25 @@ delivered is either delivered first or removed from the spec, as the operator de
|
|
|
112
129
|
|
|
113
130
|
## Dependencies
|
|
114
131
|
- <slug of an issue that must be delivered first>
|
|
132
|
+
|
|
133
|
+
## Prototype
|
|
134
|
+
- Branch: <the prototype's branch>
|
|
135
|
+
- Commit: <the full id of the commit the operator validated>
|
|
115
136
|
```
|
|
116
137
|
|
|
117
|
-
- Exactly these six sections, in this order, with nothing between the title and the first one
|
|
138
|
+
- Exactly these six sections, in this order, with nothing between the title and the first one; then Prototype,
|
|
139
|
+
only when the spec draws on a prototype, and left out otherwise.
|
|
118
140
|
- No section is empty: write `None.` when there is nothing to say.
|
|
119
141
|
- Acceptance criteria contains only `- AC<n>: <criterion>` items, numbered from AC1 without gaps; an indented
|
|
120
142
|
line continues the criterion above it. Each criterion is a fact that a user, an API client or an operator
|
|
121
143
|
can check, not an implementation detail.
|
|
122
144
|
- Dependencies contains only `- <slug>` items, each an issue of `.factory/issues/` other than this one, or
|
|
123
145
|
`None.`: the run refuses to start until each of them is delivered.
|
|
146
|
+
- Prototype contains `- Branch: <branch>` then `- Commit: <full commit id>`: the commit the operator validated,
|
|
147
|
+
as `git rev-parse` prints it, on a branch named with a slash, which no run takes, and which origin holds. The
|
|
148
|
+
runs read the prototype at that commit and refuse to start without it: keep the branch until the issue is
|
|
149
|
+
delivered, and never rewrite it (commits added on top are fine). To draw on another commit, have the operator
|
|
150
|
+
validate it, then save the spec and its presentation again.
|
|
124
151
|
- Headings stay in English; the content may be in any language.
|
|
125
152
|
|
|
126
153
|
## Save
|
|
@@ -129,13 +156,16 @@ delivered is either delivered first or removed from the spec, as the operator de
|
|
|
129
156
|
2. From the repository, run
|
|
130
157
|
`factory run issue-save --issue <slug> --kind issue --file <absolute path>` for `issue.md`,
|
|
131
158
|
`--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
|
-
|
|
134
|
-
|
|
159
|
+
3. `Saved .factory/issues/<slug>/<file>` means it is done. Otherwise the command lists every problem: fix the
|
|
160
|
+
draft and run it again. It refuses a spec whose issue does not exist yet, a dependency that is no issue of the
|
|
161
|
+
repository, a prototype origin does not hold, and a presentation that does not present the saved spec or whose
|
|
162
|
+
capture is no image. Each saved file is named, the captures folder included.
|
|
135
163
|
|
|
136
164
|
## Boundaries
|
|
137
165
|
|
|
138
|
-
Never write in `.factory/issues/` directly, never commit
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
166
|
+
Never write in `.factory/issues/` directly, never commit in the repository (a prototype is committed on its own
|
|
167
|
+
branch, by the operator or with their leave), and never run `factory run implement`. When the spec and its
|
|
168
|
+
presentation are saved, tell the operator the next steps: validate it on its page in `factory ui` (the Spec link
|
|
169
|
+
of the issue's card), commit the spec, its `spec-page.json` and its captures (`spec-page/`) and push them on the
|
|
170
|
+
main branch, then run `factory run implement --issue <slug>`. A prototype's branch stays until the issue is
|
|
171
|
+
delivered; its pull request then says whether it can be removed.
|