@clemrig/factory 0.2.1 → 0.4.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 +127 -37
- package/dist/app/board.d.ts +7 -0
- package/dist/app/board.js +47 -0
- package/dist/app/board.js.map +1 -0
- package/dist/app/define.d.ts +27 -0
- package/dist/app/define.js +68 -0
- package/dist/app/define.js.map +1 -0
- package/dist/app/doctor.js +5 -4
- package/dist/app/doctor.js.map +1 -1
- package/dist/app/factory.js +10 -2
- package/dist/app/factory.js.map +1 -1
- package/dist/app/run.js +1 -1
- package/dist/app/run.js.map +1 -1
- package/dist/app/workflows/implement.d.ts +8 -0
- package/dist/app/workflows/implement.js +268 -0
- package/dist/app/workflows/implement.js.map +1 -0
- package/dist/app/workflows/issue-save.d.ts +2 -0
- package/dist/app/workflows/issue-save.js +35 -0
- package/dist/app/workflows/issue-save.js.map +1 -0
- package/dist/cli/application.js +1 -1
- package/dist/cli/application.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +5 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/init.d.ts +2 -2
- package/dist/cli/init.js +4 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/reporter.js +42 -15
- package/dist/cli/reporter.js.map +1 -1
- package/dist/cli/ui.d.ts +5 -2
- package/dist/cli/ui.js +20 -7
- package/dist/cli/ui.js.map +1 -1
- package/dist/domain/board.d.ts +65 -0
- package/dist/domain/board.js +95 -0
- package/dist/domain/board.js.map +1 -0
- package/dist/domain/definition.d.ts +15 -0
- package/dist/domain/definition.js +28 -0
- package/dist/domain/definition.js.map +1 -0
- package/dist/domain/delivery.d.ts +9 -0
- package/dist/domain/delivery.js +10 -0
- package/dist/domain/delivery.js.map +1 -0
- package/dist/domain/issue.d.ts +11 -0
- package/dist/domain/issue.js +22 -0
- package/dist/domain/issue.js.map +1 -0
- package/dist/domain/parsed.d.ts +8 -0
- package/dist/domain/parsed.js +2 -0
- package/dist/domain/parsed.js.map +1 -0
- package/dist/domain/plan.d.ts +24 -0
- package/dist/domain/plan.js +49 -0
- package/dist/domain/plan.js.map +1 -0
- package/dist/domain/pull-request.d.ts +17 -0
- package/dist/domain/pull-request.js +32 -0
- package/dist/domain/pull-request.js.map +1 -0
- package/dist/domain/review.d.ts +36 -0
- package/dist/domain/review.js +45 -0
- package/dist/domain/review.js.map +1 -0
- package/dist/domain/spec.d.ts +18 -0
- package/dist/domain/spec.js +118 -0
- package/dist/domain/spec.js.map +1 -0
- package/dist/domain/verification.d.ts +16 -0
- package/dist/domain/verification.js +11 -0
- package/dist/domain/verification.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/system/checks.d.ts +32 -0
- package/dist/system/checks.js +49 -0
- package/dist/system/checks.js.map +1 -0
- package/dist/system/factory.d.ts +7 -5
- package/dist/system/factory.js +19 -19
- package/dist/system/factory.js.map +1 -1
- package/dist/system/git.d.ts +25 -0
- package/dist/system/git.js +73 -0
- package/dist/system/git.js.map +1 -0
- package/dist/system/github.d.ts +8 -0
- package/dist/system/github.js +52 -0
- package/dist/system/github.js.map +1 -0
- package/dist/system/issues.d.ts +24 -0
- package/dist/system/issues.js +66 -0
- package/dist/system/issues.js.map +1 -0
- package/dist/system/lock.d.ts +8 -0
- package/dist/system/lock.js +35 -0
- package/dist/system/lock.js.map +1 -0
- package/dist/system/templates.d.ts +2 -1
- package/dist/system/templates.js +69 -3
- package/dist/system/templates.js.map +1 -1
- package/package.json +6 -4
- package/prompts/implement/implement.md +21 -0
- package/prompts/implement/plan.md +20 -0
- package/prompts/implement/review.md +17 -0
- package/templates/init/factory.ts +37 -2
- package/templates/init/workflows/ask/index.ts +2 -1
- package/templates/skills/issue/SKILL.md +82 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { access, mkdir, readdir, readFile, stat, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
/** Where the issues are, relative to the repository. */
|
|
4
|
+
export const issuesPath = join(".factory", "issues");
|
|
5
|
+
/** `.factory/issues/<slug>/<kind>.md`, relative to the repository: the only place that names these paths. */
|
|
6
|
+
export function artifactPath(slug, kind) {
|
|
7
|
+
return join(issuesPath, slug, `${kind}.md`);
|
|
8
|
+
}
|
|
9
|
+
/** Every folder of `.factory/issues/`; none before the first issue. */
|
|
10
|
+
export async function listIssueFolders(repository) {
|
|
11
|
+
const root = join(repository, issuesPath);
|
|
12
|
+
const entries = await readdir(root, { withFileTypes: true }).catch((error) => {
|
|
13
|
+
if (error.code === "ENOENT")
|
|
14
|
+
return [];
|
|
15
|
+
throw error;
|
|
16
|
+
});
|
|
17
|
+
const folders = [];
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
if (!entry.isDirectory())
|
|
20
|
+
continue;
|
|
21
|
+
const files = await readdir(join(root, entry.name));
|
|
22
|
+
// A folder without its files is not an issue (an abandoned one, say).
|
|
23
|
+
if (!["issue.md", "spec.md", "plan.md"].some((file) => files.includes(file)))
|
|
24
|
+
continue;
|
|
25
|
+
const changes = await Promise.all([".", ...files].map((file) => stat(join(root, entry.name, file)).then(({ mtimeMs }) => mtimeMs,
|
|
26
|
+
// An editor's temporary file may go between the listing and now.
|
|
27
|
+
(error) => {
|
|
28
|
+
if (error.code === "ENOENT")
|
|
29
|
+
return 0;
|
|
30
|
+
throw error;
|
|
31
|
+
})));
|
|
32
|
+
// One unreadable issue.md leaves the other issues readable.
|
|
33
|
+
const issue = await readArtifact(repository, entry.name, "issue").then((text) => ({ text }), (error) => ({
|
|
34
|
+
error: error instanceof Error ? error.message : String(error),
|
|
35
|
+
}));
|
|
36
|
+
folders.push({
|
|
37
|
+
slug: entry.name,
|
|
38
|
+
...("text" in issue
|
|
39
|
+
? { issue: issue.text }
|
|
40
|
+
: { unreadable: issue.error }),
|
|
41
|
+
hasSpec: files.includes("spec.md"),
|
|
42
|
+
changedAt: new Date(Math.max(...changes)).toISOString(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return folders;
|
|
46
|
+
}
|
|
47
|
+
export async function writeArtifact(repository, slug, kind, text) {
|
|
48
|
+
const path = artifactPath(slug, kind);
|
|
49
|
+
await mkdir(dirname(join(repository, path)), { recursive: true });
|
|
50
|
+
await writeFile(join(repository, path), text);
|
|
51
|
+
return path;
|
|
52
|
+
}
|
|
53
|
+
export async function hasArtifact(repository, slug, kind) {
|
|
54
|
+
return access(join(repository, artifactPath(slug, kind))).then(() => true, () => false);
|
|
55
|
+
}
|
|
56
|
+
/** The artifact's text, or nothing when the issue has none. */
|
|
57
|
+
export async function readArtifact(repository, slug, kind) {
|
|
58
|
+
if (!(await hasArtifact(repository, slug, kind)))
|
|
59
|
+
return undefined;
|
|
60
|
+
return readFile(join(repository, artifactPath(slug, kind)), "utf8");
|
|
61
|
+
}
|
|
62
|
+
/** A draft handed over by an agent, wherever it wrote it. */
|
|
63
|
+
export function readDraft(path) {
|
|
64
|
+
return readFile(path, "utf8");
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues.js","sourceRoot":"","sources":["../../src/system/issues.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `work` while holding the lock file `path`, created atomically: two
|
|
3
|
+
* processes never hold it together. A holder is awaited up to `waitMs`; a lock
|
|
4
|
+
* still there after that, left by a process that stopped, is named in the error.
|
|
5
|
+
*/
|
|
6
|
+
export declare function exclusively<T>(path: string, work: () => Promise<T>, waitMs?: number): Promise<T>;
|
|
7
|
+
/** Runs `work` alone among the factory's runs of this working tree: the lock lives in its Git directory. */
|
|
8
|
+
export declare function withTreeLock<T>(dir: string, work: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { gitDir } from "./git.js";
|
|
4
|
+
/**
|
|
5
|
+
* Runs `work` while holding the lock file `path`, created atomically: two
|
|
6
|
+
* processes never hold it together. A holder is awaited up to `waitMs`; a lock
|
|
7
|
+
* still there after that, left by a process that stopped, is named in the error.
|
|
8
|
+
*/
|
|
9
|
+
export async function exclusively(path, work, waitMs = 10_000) {
|
|
10
|
+
const until = Date.now() + waitMs;
|
|
11
|
+
for (;;) {
|
|
12
|
+
try {
|
|
13
|
+
await writeFile(path, `${process.pid}\n`, { flag: "wx" });
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error.code !== "EEXIST")
|
|
18
|
+
throw error;
|
|
19
|
+
if (Date.now() >= until)
|
|
20
|
+
throw new Error(`${path} has been held for ${Math.round(waitMs / 1000)} s: if no implement run is starting, remove it`);
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return await work();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
await rm(path, { force: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Runs `work` alone among the factory's runs of this working tree: the lock lives in its Git directory. */
|
|
32
|
+
export async function withTreeLock(dir, work) {
|
|
33
|
+
return exclusively(join(await gitDir(dir), "factory-implement.lock"), work);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/system/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,IAAsB,EACtB,MAAM,GAAG,MAAM;IAEf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IAClC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,MAAM;QACR,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;YACpE,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK;gBACrB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,sBAAsB,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,gDAAgD,CACvG,CAAC;YACJ,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,IAAsB;IAEtB,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,EAAE,wBAAwB,CAAC,EAAE,IAAI,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates `.factory/` in `cwd` from the package's templates, its `package.json`
|
|
3
|
-
* pinned to this factory version
|
|
3
|
+
* pinned to this factory version, and the `issue` skill. An existing
|
|
4
|
+
* `.factory/` or skill is refused, with nothing written.
|
|
4
5
|
*/
|
|
5
6
|
export declare function createFromTemplates(cwd: string): Promise<{
|
|
6
7
|
factoryDir: string;
|
package/dist/system/templates.js
CHANGED
|
@@ -1,13 +1,70 @@
|
|
|
1
|
-
import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { cp, lstat, mkdir, readFile, readlink, rename, symlink, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
/** Both `src/system/` and `dist/system/` sit two levels below the package root. */
|
|
4
4
|
const root = new URL("../../", import.meta.url);
|
|
5
|
+
/** The `issue` skill: read by Pi and Codex there, by Claude Code through a folder link. */
|
|
6
|
+
const skillDir = join(".agents", "skills", "issue");
|
|
7
|
+
async function exists(path) {
|
|
8
|
+
return lstat(path).then(() => true, (error) => {
|
|
9
|
+
if (error.code === "ENOENT")
|
|
10
|
+
return false;
|
|
11
|
+
throw error;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Where a path really leads: through every link on the way, dangling ones
|
|
16
|
+
* included, and through what does not exist yet.
|
|
17
|
+
*/
|
|
18
|
+
async function location(path, links = 0) {
|
|
19
|
+
if (links > 40)
|
|
20
|
+
throw new Error(`Too many links on the way to ${path}`);
|
|
21
|
+
const parent = dirname(path);
|
|
22
|
+
const here = parent === path
|
|
23
|
+
? path
|
|
24
|
+
: join(await location(parent, links), basename(path));
|
|
25
|
+
const stats = await lstat(here).catch((error) => {
|
|
26
|
+
if (error.code === "ENOENT")
|
|
27
|
+
return undefined;
|
|
28
|
+
throw error;
|
|
29
|
+
});
|
|
30
|
+
if (!stats?.isSymbolicLink())
|
|
31
|
+
return here;
|
|
32
|
+
return location(resolve(dirname(here), await readlink(here)), links + 1);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Where the skill goes, and Claude Code's link to it, both where the paths
|
|
36
|
+
* really lead: `.agents`, `.claude` and their `skills` may be links, even to
|
|
37
|
+
* folders that do not exist yet. No link when `.claude/skills` leads to
|
|
38
|
+
* `.agents/skills`, which then holds the skill. The link's target is relative
|
|
39
|
+
* to the folder it lands in.
|
|
40
|
+
*/
|
|
41
|
+
async function skillPlaces(cwd) {
|
|
42
|
+
const agents = await location(join(resolve(cwd), ".agents", "skills"));
|
|
43
|
+
const claude = await location(join(resolve(cwd), ".claude", "skills"));
|
|
44
|
+
const skill = { path: join(agents, "issue"), shown: join(cwd, skillDir) };
|
|
45
|
+
if (claude === agents)
|
|
46
|
+
return { skill };
|
|
47
|
+
return {
|
|
48
|
+
skill,
|
|
49
|
+
link: {
|
|
50
|
+
path: join(claude, "issue"),
|
|
51
|
+
target: relative(claude, skill.path),
|
|
52
|
+
shown: join(cwd, ".claude", "skills", "issue"),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
5
56
|
/**
|
|
6
57
|
* Creates `.factory/` in `cwd` from the package's templates, its `package.json`
|
|
7
|
-
* pinned to this factory version
|
|
58
|
+
* pinned to this factory version, and the `issue` skill. An existing
|
|
59
|
+
* `.factory/` or skill is refused, with nothing written.
|
|
8
60
|
*/
|
|
9
61
|
export async function createFromTemplates(cwd) {
|
|
10
62
|
const factoryDir = join(cwd, ".factory");
|
|
63
|
+
// Everything is decided before anything is written.
|
|
64
|
+
const { skill, link } = await skillPlaces(cwd);
|
|
65
|
+
for (const { path, shown } of [skill, ...(link ? [link] : [])])
|
|
66
|
+
if (await exists(path))
|
|
67
|
+
throw new Error(`${shown} already exists`);
|
|
11
68
|
try {
|
|
12
69
|
await mkdir(factoryDir);
|
|
13
70
|
}
|
|
@@ -25,6 +82,15 @@ export async function createFromTemplates(cwd) {
|
|
|
25
82
|
type: "module",
|
|
26
83
|
dependencies: { "@clemrig/factory": version },
|
|
27
84
|
}, null, 2)}\n`);
|
|
85
|
+
await mkdir(dirname(skill.path), { recursive: true });
|
|
86
|
+
await cp(new URL("templates/skills/issue/", root), skill.path, {
|
|
87
|
+
recursive: true,
|
|
88
|
+
});
|
|
89
|
+
if (link) {
|
|
90
|
+
await mkdir(dirname(link.path), { recursive: true });
|
|
91
|
+
// Codex ignores a SKILL.md that is itself a link: the link is the folder's.
|
|
92
|
+
await symlink(link.target, link.path);
|
|
93
|
+
}
|
|
28
94
|
return { factoryDir, version };
|
|
29
95
|
}
|
|
30
96
|
//# sourceMappingURL=templates.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/system/templates.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/system/templates.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clemrig/factory",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
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": {
|
|
7
7
|
"type": "git",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
+
"prompts",
|
|
25
26
|
"templates"
|
|
26
27
|
],
|
|
27
28
|
"engines": {
|
|
@@ -38,7 +39,8 @@
|
|
|
38
39
|
"prepack": "npm run build"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@clemrig/
|
|
42
|
-
"@clemrig/flow
|
|
42
|
+
"@clemrig/factory-ui": "0.1.0",
|
|
43
|
+
"@clemrig/flow": "0.4.0",
|
|
44
|
+
"@clemrig/flow-ui": "0.2.0"
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
You implement an approved plan in this repository.
|
|
2
|
+
|
|
3
|
+
First read the repository's agent instructions: `AGENTS.md` at the root and in each directory you touch.
|
|
4
|
+
Follow their conventions and validation guidance. Ignore `.factory/` and never change it.
|
|
5
|
+
|
|
6
|
+
The request gives the spec, approved and unchanging, and the plan. Implement the plan, tests included, and run
|
|
7
|
+
the relevant checks yourself before answering.
|
|
8
|
+
|
|
9
|
+
Never commit, switch branches or push: the run commits, pushes and opens the pull request itself, and it stops
|
|
10
|
+
if you commit or switch branches.
|
|
11
|
+
|
|
12
|
+
When the request contains `feedback`, it reports what failed after your previous answer, a check or the
|
|
13
|
+
review: fix that, then answer again.
|
|
14
|
+
|
|
15
|
+
Answer with:
|
|
16
|
+
|
|
17
|
+
- `summary`: what you changed, in a few sentences.
|
|
18
|
+
- `criteria`: each acceptance criterion once, with the evidence that it is met (tests, files).
|
|
19
|
+
- `deviations`: where you departed from the plan and why; empty if nowhere.
|
|
20
|
+
|
|
21
|
+
Write in English.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
You plan the implementation of an approved spec in this repository. You change nothing.
|
|
2
|
+
|
|
3
|
+
Before planning, read the repository's agent instructions: `AGENTS.md` at the root and in each directory the
|
|
4
|
+
change will touch, and follow them. Ignore `.factory/`: the other issues there are history, not the current
|
|
5
|
+
truth, and `.factory/runs/` holds agents' outputs.
|
|
6
|
+
|
|
7
|
+
The request gives the issue's slug and its spec. The spec is approved and does not change: plan what it asks,
|
|
8
|
+
nothing more. When something is unclear, choose the interpretation most consistent with the code and record it
|
|
9
|
+
in `risks`.
|
|
10
|
+
|
|
11
|
+
Answer with a plan of verifiable steps:
|
|
12
|
+
|
|
13
|
+
- `summary`: the approach, in a few sentences.
|
|
14
|
+
- `steps`: each names the acceptance criteria it serves (`AC1`…), the files it creates or changes, and the
|
|
15
|
+
tests that prove it. Every acceptance criterion is served by at least one step.
|
|
16
|
+
- `commit`: the conventional commit subject of the whole change, for example
|
|
17
|
+
`feat(api): export watchlists as CSV`, at most 72 characters.
|
|
18
|
+
- `risks`: what could go wrong and what you assumed; empty if nothing.
|
|
19
|
+
|
|
20
|
+
Write in English.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
You review a change against its approved spec, independently. You change nothing.
|
|
2
|
+
|
|
3
|
+
Read the repository's agent instructions: `AGENTS.md` at the root and in the directories the change touches.
|
|
4
|
+
Ignore `.factory/`: it holds other issues' history and agents' outputs, which you must not rely on.
|
|
5
|
+
|
|
6
|
+
The request gives the spec, the diff of the change, and the repository's checks, which all passed. Do not take
|
|
7
|
+
anything on trust: verify each acceptance criterion in the code and the tests, reading files beyond the diff
|
|
8
|
+
when needed. Look for unmet criteria, defects, regressions, missing or weak tests, security problems and
|
|
9
|
+
departures from the repository's conventions.
|
|
10
|
+
|
|
11
|
+
Answer with:
|
|
12
|
+
|
|
13
|
+
- `criteria`: each acceptance criterion once, `met` or `not_met`, with its evidence (file, test, behavior).
|
|
14
|
+
- `findings`: `blocking` for an unmet criterion, a defect or a regression; `minor` for an improvement that is
|
|
15
|
+
not required. Give the file when relevant.
|
|
16
|
+
|
|
17
|
+
The change is approved only when every criterion is met and no finding is blocking. Write in English.
|
|
@@ -1,4 +1,39 @@
|
|
|
1
|
+
import { type AgentProfile, defineFactory } from "@clemrig/factory";
|
|
1
2
|
import ask from "./workflows/ask/index.ts";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
const minutes = 60_000;
|
|
5
|
+
const opus = {
|
|
6
|
+
runtime: "claude-code",
|
|
7
|
+
model: "claude-opus-5-5",
|
|
8
|
+
thinking: "high",
|
|
9
|
+
} as const;
|
|
10
|
+
const reader = {
|
|
11
|
+
...opus,
|
|
12
|
+
tools: ["Read", "Grep", "Glob"],
|
|
13
|
+
timeoutMs: 20 * minutes,
|
|
14
|
+
} satisfies AgentProfile;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This repository's factory: `factory run issue-save` and `factory run
|
|
18
|
+
* implement --issue <slug>` take an issue from its spec to a pull request.
|
|
19
|
+
*/
|
|
20
|
+
export default defineFactory({
|
|
21
|
+
// The repository's checks, run in order, without a shell: before the run on
|
|
22
|
+
// the main branch, then after each implementation. Replace this example.
|
|
23
|
+
verify: [{ name: "test", command: ["npm", "test"], timeoutMs: 10 * minutes }],
|
|
24
|
+
// The budget of an implement run, in USD; --max-cost-usd changes it for one run.
|
|
25
|
+
maxCostUsd: 25,
|
|
26
|
+
profiles: {
|
|
27
|
+
plan: reader,
|
|
28
|
+
implement: {
|
|
29
|
+
...opus,
|
|
30
|
+
tools: ["Read", "Grep", "Glob", "Edit", "Write", "Bash"],
|
|
31
|
+
// The listed tools run; anything else is refused instead of waiting for a prompt.
|
|
32
|
+
permissionMode: "dontAsk",
|
|
33
|
+
timeoutMs: 60 * minutes,
|
|
34
|
+
},
|
|
35
|
+
review: reader,
|
|
36
|
+
},
|
|
37
|
+
// This repository's own workflows, run with `factory run <name>`.
|
|
38
|
+
workflows: [ask],
|
|
39
|
+
});
|
|
@@ -34,7 +34,8 @@ export default defineWorkflow({
|
|
|
34
34
|
{ ok: "answer" },
|
|
35
35
|
),
|
|
36
36
|
// AGENT: prompts/answer.md, with the question and the commits as context.
|
|
37
|
-
//
|
|
37
|
+
// Claude Code runs with this machine's configuration; `tools` limits it to
|
|
38
|
+
// reading the repository.
|
|
38
39
|
answer: node(
|
|
39
40
|
agent({
|
|
40
41
|
profile: {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issue
|
|
3
|
+
description: "Create or specify a factory issue in .factory/issues/<slug>/: capture an idea as issue.md, then clarify it with the operator into a spec.md ready for factory run implement. Use when the operator wants to add an issue, write or refine a spec, or prepare work for the factory."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Issue
|
|
7
|
+
|
|
8
|
+
You move an issue through the first two columns of the factory's Kanban: **Backlog** (its `issue.md` exists)
|
|
9
|
+
and **Specifying** (its `spec.md` exists). You never write these files yourself: `factory run issue-save` checks
|
|
10
|
+
a draft and writes it at the right place. Talk with the operator in their language.
|
|
11
|
+
|
|
12
|
+
## Find or create the issue
|
|
13
|
+
|
|
14
|
+
Issues live in `.factory/issues/<slug>/`; read them to find the one the operator means. For a new issue,
|
|
15
|
+
propose a slug (lowercase words separated by hyphens; it also names the issue's branch), a title and a short
|
|
16
|
+
description of the need. Once the operator agrees, save the issue (see Save).
|
|
17
|
+
|
|
18
|
+
## Specify
|
|
19
|
+
|
|
20
|
+
1. Read the repository's agent instructions (`AGENTS.md` at the root and in the directories concerned) and the
|
|
21
|
+
relevant code. Change nothing.
|
|
22
|
+
2. Ask the operator questions, a few at a time, until you can fill every section without inventing anything.
|
|
23
|
+
Do not ask what the code or the docs already answer. For each question, give its practical consequence
|
|
24
|
+
and your recommendation.
|
|
25
|
+
3. Describe what and why, never how: the run that implements the spec plans the technical work itself.
|
|
26
|
+
4. Propose the complete spec and wait for the operator's agreement, then save it (see Save).
|
|
27
|
+
|
|
28
|
+
Other issues in `.factory/issues/` are history, not the current truth: rely on the code and the docs.
|
|
29
|
+
|
|
30
|
+
## Format
|
|
31
|
+
|
|
32
|
+
`issue.md`:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
# <Title>
|
|
36
|
+
|
|
37
|
+
<Description of the need>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`spec.md`:
|
|
41
|
+
|
|
42
|
+
```markdown
|
|
43
|
+
# <Title>
|
|
44
|
+
|
|
45
|
+
## Objective
|
|
46
|
+
<The problem and why it matters>
|
|
47
|
+
|
|
48
|
+
## Scope
|
|
49
|
+
<What the change includes>
|
|
50
|
+
|
|
51
|
+
## Out of scope
|
|
52
|
+
<What it explicitly excludes>
|
|
53
|
+
|
|
54
|
+
## Acceptance criteria
|
|
55
|
+
- AC1: <A fact observable from outside>
|
|
56
|
+
- AC2: <…>
|
|
57
|
+
|
|
58
|
+
## Constraints
|
|
59
|
+
<Limits imposed on the solution>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Exactly these five sections, in this order, with nothing between the title and the first one.
|
|
63
|
+
- No section is empty: write `None.` when there is nothing to say.
|
|
64
|
+
- Acceptance criteria contains only `- AC<n>: <criterion>` items, numbered from AC1 without gaps; an indented
|
|
65
|
+
line continues the criterion above it. Each criterion is a fact that a user, an API client or an operator
|
|
66
|
+
can check, not an implementation detail.
|
|
67
|
+
- Headings stay in English; the content may be in any language.
|
|
68
|
+
|
|
69
|
+
## Save
|
|
70
|
+
|
|
71
|
+
1. Write the draft to a new file outside the repository, for example in a directory created by `mktemp -d`.
|
|
72
|
+
2. From the repository, run
|
|
73
|
+
`factory run issue-save --issue <slug> --kind issue --file <absolute path>` for `issue.md`, or
|
|
74
|
+
`--kind spec` for `spec.md`.
|
|
75
|
+
3. `Saved .factory/issues/<slug>/<kind>.md` means it is done. Otherwise the command lists every problem: fix
|
|
76
|
+
the draft and run it again. It refuses a spec whose issue does not exist yet.
|
|
77
|
+
|
|
78
|
+
## Boundaries
|
|
79
|
+
|
|
80
|
+
Never write in `.factory/issues/` directly, never commit, and never run `factory run implement`. When the spec
|
|
81
|
+
is saved, tell the operator the next steps: review it, commit it and push it on the main branch, then run
|
|
82
|
+
`factory run implement --issue <slug>`.
|