@clemrig/factory 0.0.0-stage → 0.2.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -2
  3. package/dist/app/doctor.d.ts +18 -0
  4. package/dist/app/doctor.js +40 -0
  5. package/dist/app/doctor.js.map +1 -0
  6. package/dist/app/factory.d.ts +14 -0
  7. package/dist/app/factory.js +33 -0
  8. package/dist/app/factory.js.map +1 -0
  9. package/dist/app/init.d.ts +6 -0
  10. package/dist/app/init.js +15 -0
  11. package/dist/app/init.js.map +1 -0
  12. package/dist/app/options.d.ts +17 -0
  13. package/dist/app/options.js +78 -0
  14. package/dist/app/options.js.map +1 -0
  15. package/dist/app/run.d.ts +8 -0
  16. package/dist/app/run.js +29 -0
  17. package/dist/app/run.js.map +1 -0
  18. package/dist/app/runs.d.ts +4 -0
  19. package/dist/app/runs.js +6 -0
  20. package/dist/app/runs.js.map +1 -0
  21. package/dist/cli/application.d.ts +6 -0
  22. package/dist/cli/application.js +63 -0
  23. package/dist/cli/application.js.map +1 -0
  24. package/dist/cli/args.d.ts +12 -0
  25. package/dist/cli/args.js +45 -0
  26. package/dist/cli/args.js.map +1 -0
  27. package/dist/cli/doctor.d.ts +5 -0
  28. package/dist/cli/doctor.js +33 -0
  29. package/dist/cli/doctor.js.map +1 -0
  30. package/dist/cli/help.d.ts +1 -0
  31. package/dist/cli/help.js +17 -0
  32. package/dist/cli/help.js.map +1 -0
  33. package/dist/cli/init.d.ts +6 -0
  34. package/dist/cli/init.js +18 -0
  35. package/dist/cli/init.js.map +1 -0
  36. package/dist/cli/launcher.d.ts +5 -0
  37. package/dist/cli/launcher.js +55 -0
  38. package/dist/cli/launcher.js.map +1 -0
  39. package/dist/cli/reporter.d.ts +17 -0
  40. package/dist/cli/reporter.js +122 -0
  41. package/dist/cli/reporter.js.map +1 -0
  42. package/dist/cli/run.d.ts +5 -0
  43. package/dist/cli/run.js +52 -0
  44. package/dist/cli/run.js.map +1 -0
  45. package/dist/cli/status.d.ts +4 -0
  46. package/dist/cli/status.js +15 -0
  47. package/dist/cli/status.js.map +1 -0
  48. package/dist/cli/ui.d.ts +3 -0
  49. package/dist/cli/ui.js +25 -0
  50. package/dist/cli/ui.js.map +1 -0
  51. package/dist/index.d.ts +6 -0
  52. package/dist/index.js +7 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/main.d.ts +2 -0
  55. package/dist/main.js +4 -0
  56. package/dist/main.js.map +1 -0
  57. package/dist/system/environment.d.ts +2 -0
  58. package/dist/system/environment.js +13 -0
  59. package/dist/system/environment.js.map +1 -0
  60. package/dist/system/factory.d.ts +16 -0
  61. package/dist/system/factory.js +62 -0
  62. package/dist/system/factory.js.map +1 -0
  63. package/dist/system/templates.d.ts +8 -0
  64. package/dist/system/templates.js +30 -0
  65. package/dist/system/templates.js.map +1 -0
  66. package/package.json +42 -4
  67. package/templates/init/factory.ts +4 -0
  68. package/templates/init/gitignore +3 -0
  69. package/templates/init/tsconfig.json +13 -0
  70. package/templates/init/workflows/ask/index.ts +57 -0
  71. package/templates/init/workflows/ask/prompts/answer.md +7 -0
@@ -0,0 +1,62 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { isWorkflow } from "@clemrig/flow";
5
+ /** A factory without workflows, to read runs when `factory.ts` cannot load. */
6
+ export function emptyFactory(factoryDir) {
7
+ return {
8
+ dir: dirname(factoryDir),
9
+ factoryDir,
10
+ runsDir: join(factoryDir, "runs"),
11
+ workflows: new Map(),
12
+ };
13
+ }
14
+ /** Imports `.factory/factory.ts`, whose default export lists the repository's workflows. */
15
+ export async function loadFactory(factoryDir) {
16
+ const module = (await import(pathToFileURL(join(factoryDir, "factory.ts")).href));
17
+ const exported = module.default;
18
+ if (!Array.isArray(exported))
19
+ throw new Error("factory.ts must export default an array of defineWorkflow(…) results");
20
+ const workflows = new Map();
21
+ for (const workflow of exported) {
22
+ if (!isWorkflow(workflow))
23
+ throw new Error("factory.ts must export default an array of defineWorkflow(…) results");
24
+ if (workflows.has(workflow.name))
25
+ throw new Error(`Duplicate workflow ${workflow.name} in factory.ts`);
26
+ workflows.set(workflow.name, workflow);
27
+ }
28
+ return { ...emptyFactory(factoryDir), workflows };
29
+ }
30
+ export function workflowOf(factory, name) {
31
+ const workflow = factory.workflows.get(name);
32
+ if (!workflow)
33
+ throw new Error(`Unknown workflow ${name}; workflows: ${[...factory.workflows.keys()].join(", ") || "none"}`);
34
+ return workflow;
35
+ }
36
+ /** Reads `workflows/<workflow>/prompts/<name>.md` for each agent stage of the workflow, at every run. */
37
+ export async function loadPrompts(factoryDir, workflow) {
38
+ const prompts = {};
39
+ const problems = [];
40
+ for (const { prompt: name } of Object.values(workflow.agents)) {
41
+ if (Object.hasOwn(prompts, name))
42
+ continue;
43
+ const file = join(factoryDir, "workflows", workflow.name, "prompts", `${name}.md`);
44
+ let text;
45
+ try {
46
+ text = await readFile(file, "utf8");
47
+ }
48
+ catch (error) {
49
+ if (error.code !== "ENOENT")
50
+ throw error;
51
+ problems.push(`Missing prompt: ${file}`);
52
+ continue;
53
+ }
54
+ if (!text.trim())
55
+ problems.push(`Empty prompt: ${file}`);
56
+ prompts[name] = text.trimEnd();
57
+ }
58
+ if (problems.length)
59
+ throw new Error(problems.join("; "));
60
+ return prompts;
61
+ }
62
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/system/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Creates `.factory/` in `cwd` from the package's templates, its `package.json`
3
+ * pinned to this factory version. An existing `.factory/` is refused untouched.
4
+ */
5
+ export declare function createFromTemplates(cwd: string): Promise<{
6
+ factoryDir: string;
7
+ version: string;
8
+ }>;
@@ -0,0 +1,30 @@
1
+ import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /** Both `src/system/` and `dist/system/` sit two levels below the package root. */
4
+ const root = new URL("../../", import.meta.url);
5
+ /**
6
+ * Creates `.factory/` in `cwd` from the package's templates, its `package.json`
7
+ * pinned to this factory version. An existing `.factory/` is refused untouched.
8
+ */
9
+ export async function createFromTemplates(cwd) {
10
+ const factoryDir = join(cwd, ".factory");
11
+ try {
12
+ await mkdir(factoryDir);
13
+ }
14
+ catch (error) {
15
+ if (error.code === "EEXIST")
16
+ throw new Error(`${factoryDir} already exists`);
17
+ throw error;
18
+ }
19
+ const { version } = JSON.parse(await readFile(new URL("package.json", root), "utf8"));
20
+ await cp(new URL("templates/init/", root), factoryDir, { recursive: true });
21
+ // npm pack drops files named .gitignore: the template has no dot.
22
+ await rename(join(factoryDir, "gitignore"), join(factoryDir, ".gitignore"));
23
+ await writeFile(join(factoryDir, "package.json"), `${JSON.stringify({
24
+ private: true,
25
+ type: "module",
26
+ dependencies: { "@clemrig/factory": version },
27
+ }, null, 2)}\n`);
28
+ return { factoryDir, version };
29
+ }
30
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/system/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,mFAAmF;AACnF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAW;IAEX,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YACpD,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,iBAAiB,CAAC,CAAC;QAClD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAC5B,MAAM,QAAQ,CAAC,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAC/B,CAAC;IACzB,MAAM,EAAE,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,IAAI,CAAC,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5E,kEAAkE;IAClE,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;IAC5E,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EAChC,GAAG,IAAI,CAAC,SAAS,CACf;QACE,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,EAAE,kBAAkB,EAAE,OAAO,EAAE;KAC9C,EACD,IAAI,EACJ,CAAC,CACF,IAAI,CACN,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@clemrig/factory",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Write agent workflows in TypeScript inside any repository, run them in the terminal and watch them in a local dashboard",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/clementchesneau/clemrig-factory-v2.git",
9
+ "directory": "packages/factory"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "factory": "dist/main.js"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./cli": {
20
+ "default": "./dist/cli/application.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "templates"
26
+ ],
27
+ "engines": {
28
+ "node": ">=24"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "scripts": {
34
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "vitest run",
37
+ "test:coverage": "vitest run --coverage",
38
+ "prepack": "npm run build"
39
+ },
40
+ "dependencies": {
41
+ "@clemrig/flow": "0.1.0",
42
+ "@clemrig/flow-ui": "0.1.0"
43
+ }
44
+ }
@@ -0,0 +1,4 @@
1
+ import ask from "./workflows/ask/index.ts";
2
+
3
+ /** The workflows of this repository, run with `factory run <name>`. */
4
+ export default [ask];
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ runs/
3
+ .env
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "allowImportingTsExtensions": true,
9
+ "verbatimModuleSyntax": true,
10
+ "erasableSyntaxOnly": true,
11
+ "skipLibCheck": true
12
+ }
13
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ agent,
3
+ defineWorkflow,
4
+ END,
5
+ node,
6
+ pick,
7
+ retry,
8
+ runProcess,
9
+ schemaGate,
10
+ step,
11
+ z,
12
+ } from "@clemrig/factory";
13
+
14
+ const answer = z.strictObject({ answer: z.string().min(1) });
15
+
16
+ /** Answers a question about this repository from its recent commits. */
17
+ export default defineWorkflow({
18
+ name: "ask",
19
+ input: z.object({ question: z.string().min(1) }),
20
+ initial: "commits",
21
+ nodes: {
22
+ // CODE: deterministic work in the repository.
23
+ commits: node(
24
+ step(async ({ dir }) => {
25
+ const log = await runProcess(
26
+ ["git", "log", "--oneline", "-n", "10"],
27
+ dir,
28
+ 30_000,
29
+ );
30
+ if (log.exitCode !== 0)
31
+ throw new Error(`git log failed: ${log.stderr.trim()}`);
32
+ return { commits: log.stdout.trim().split("\n").filter(Boolean) };
33
+ }),
34
+ { ok: "answer" },
35
+ ),
36
+ // AGENT: prompts/answer.md, with the question and the commits as context.
37
+ // Read-only by default: its tools can read the repository, not change it.
38
+ answer: node(
39
+ agent({
40
+ profile: {
41
+ runtime: "claude-code",
42
+ model: "claude-sonnet-5-5",
43
+ thinking: "low",
44
+ tools: ["Read", "Grep", "Glob"],
45
+ },
46
+ schema: answer,
47
+ context: pick("input", "commits"),
48
+ }),
49
+ { ok: "check" },
50
+ ),
51
+ // GATE: an invalid answer goes back to the agent with feedback, at most 3 times.
52
+ check: node(schemaGate({ schema: answer, into: "answer" }), {
53
+ ok: END,
54
+ invalid: retry("answer", 3),
55
+ }),
56
+ },
57
+ });
@@ -0,0 +1,7 @@
1
+ You answer the operator's question about this repository.
2
+
3
+ - The context holds the question (`input.question`) and the ten most recent commits (`commits`).
4
+ - Read files with your tools when the commits are not enough.
5
+ - Answer in a few sentences, in the language of `input.question`, whatever language the rest of the context suggests.
6
+ - When the context holds `feedback`, your previous answer was refused: correct what it describes.
7
+ - Return `{"answer": "…"}`.