@clemrig/factory 0.3.0 → 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.
Files changed (90) hide show
  1. package/README.md +111 -21
  2. package/dist/app/board.d.ts +7 -0
  3. package/dist/app/board.js +47 -0
  4. package/dist/app/board.js.map +1 -0
  5. package/dist/app/define.d.ts +27 -0
  6. package/dist/app/define.js +68 -0
  7. package/dist/app/define.js.map +1 -0
  8. package/dist/app/doctor.js +5 -4
  9. package/dist/app/doctor.js.map +1 -1
  10. package/dist/app/factory.js +10 -2
  11. package/dist/app/factory.js.map +1 -1
  12. package/dist/app/run.js +1 -1
  13. package/dist/app/run.js.map +1 -1
  14. package/dist/app/workflows/implement.d.ts +8 -0
  15. package/dist/app/workflows/implement.js +268 -0
  16. package/dist/app/workflows/implement.js.map +1 -0
  17. package/dist/app/workflows/issue-save.d.ts +2 -0
  18. package/dist/app/workflows/issue-save.js +35 -0
  19. package/dist/app/workflows/issue-save.js.map +1 -0
  20. package/dist/cli/application.js +1 -1
  21. package/dist/cli/application.js.map +1 -1
  22. package/dist/cli/help.d.ts +1 -1
  23. package/dist/cli/help.js +5 -3
  24. package/dist/cli/help.js.map +1 -1
  25. package/dist/cli/init.d.ts +2 -2
  26. package/dist/cli/init.js +4 -4
  27. package/dist/cli/init.js.map +1 -1
  28. package/dist/cli/ui.d.ts +5 -2
  29. package/dist/cli/ui.js +20 -7
  30. package/dist/cli/ui.js.map +1 -1
  31. package/dist/domain/board.d.ts +65 -0
  32. package/dist/domain/board.js +95 -0
  33. package/dist/domain/board.js.map +1 -0
  34. package/dist/domain/definition.d.ts +15 -0
  35. package/dist/domain/definition.js +28 -0
  36. package/dist/domain/definition.js.map +1 -0
  37. package/dist/domain/delivery.d.ts +9 -0
  38. package/dist/domain/delivery.js +10 -0
  39. package/dist/domain/delivery.js.map +1 -0
  40. package/dist/domain/issue.d.ts +11 -0
  41. package/dist/domain/issue.js +22 -0
  42. package/dist/domain/issue.js.map +1 -0
  43. package/dist/domain/parsed.d.ts +8 -0
  44. package/dist/domain/parsed.js +2 -0
  45. package/dist/domain/parsed.js.map +1 -0
  46. package/dist/domain/plan.d.ts +24 -0
  47. package/dist/domain/plan.js +49 -0
  48. package/dist/domain/plan.js.map +1 -0
  49. package/dist/domain/pull-request.d.ts +17 -0
  50. package/dist/domain/pull-request.js +32 -0
  51. package/dist/domain/pull-request.js.map +1 -0
  52. package/dist/domain/review.d.ts +36 -0
  53. package/dist/domain/review.js +45 -0
  54. package/dist/domain/review.js.map +1 -0
  55. package/dist/domain/spec.d.ts +18 -0
  56. package/dist/domain/spec.js +118 -0
  57. package/dist/domain/spec.js.map +1 -0
  58. package/dist/domain/verification.d.ts +16 -0
  59. package/dist/domain/verification.js +11 -0
  60. package/dist/domain/verification.js.map +1 -0
  61. package/dist/index.d.ts +8 -4
  62. package/dist/index.js +5 -3
  63. package/dist/index.js.map +1 -1
  64. package/dist/system/checks.d.ts +32 -0
  65. package/dist/system/checks.js +49 -0
  66. package/dist/system/checks.js.map +1 -0
  67. package/dist/system/factory.d.ts +7 -5
  68. package/dist/system/factory.js +19 -19
  69. package/dist/system/factory.js.map +1 -1
  70. package/dist/system/git.d.ts +25 -0
  71. package/dist/system/git.js +73 -0
  72. package/dist/system/git.js.map +1 -0
  73. package/dist/system/github.d.ts +8 -0
  74. package/dist/system/github.js +52 -0
  75. package/dist/system/github.js.map +1 -0
  76. package/dist/system/issues.d.ts +24 -0
  77. package/dist/system/issues.js +66 -0
  78. package/dist/system/issues.js.map +1 -0
  79. package/dist/system/lock.d.ts +8 -0
  80. package/dist/system/lock.js +35 -0
  81. package/dist/system/lock.js.map +1 -0
  82. package/dist/system/templates.d.ts +2 -1
  83. package/dist/system/templates.js +69 -3
  84. package/dist/system/templates.js.map +1 -1
  85. package/package.json +6 -4
  86. package/prompts/implement/implement.md +21 -0
  87. package/prompts/implement/plan.md +20 -0
  88. package/prompts/implement/review.md +17 -0
  89. package/templates/init/factory.ts +37 -2
  90. package/templates/skills/issue/SKILL.md +82 -0
@@ -0,0 +1,11 @@
1
+ /** The correction sent to the implementing agent: the failed check, its cause and the end of its output. */
2
+ export function failureFeedback(check, cause, output) {
3
+ const header = `Verification failed: ${check.name} (\`${check.command.join(" ")}\`): ${cause}.`;
4
+ const tail = output.trim().split("\n").slice(-100).join("\n").slice(-8000);
5
+ return tail ? `${header} End of its output:\n${tail}` : header;
6
+ }
7
+ /** A duration as the terminal and the pull request show it. */
8
+ export function seconds(durationMs) {
9
+ return `${Math.round(durationMs / 1000)} s`;
10
+ }
11
+ //# sourceMappingURL=verification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification.js","sourceRoot":"","sources":["../../src/domain/verification.ts"],"names":[],"mappings":"AAcA,4GAA4G;AAC5G,MAAM,UAAU,eAAe,CAC7B,KAAoB,EACpB,KAAa,EACb,MAAc;IAEd,MAAM,MAAM,GAAG,wBAAwB,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC;IAChG,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,wBAAwB,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AACjE,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,OAAO,CAAC,UAAkB;IACxC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;AAC9C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  /**
2
- * Public API for writing workflows. A repository's `.factory/` imports only this
3
- * module; it re-exports the engine's, `z` included, so that workflows, the
4
- * factory and the engine share one Zod instance.
2
+ * Public API of a repository's `.factory/`: `defineFactory`, which adapts the
3
+ * factory's workflows to the repository, and the engine's API to write its own
4
+ * workflows, `z` included, so that workflows, the factory and the engine share
5
+ * one Zod instance.
5
6
  */
6
- export { type AgentProfile, agent, type Check, code, defineWorkflow, END, gate, type Log, node, ProcessInterruptedError, pick, type RunState, retry, runProcess, type Stage, type StageContext, type StageResult, schemaGate, step, type WorkflowDefinition, z, } from "@clemrig/flow";
7
+ export { type AgentProfile, agent, type Check, code, defineWorkflow, END, gate, type Log, node, ProcessInterruptedError, pick, type RunState, retry, runProcess, type Stage, type StageContext, type StageResult, schemaGate, step, type WorkflowDefinition, type WorkflowStep, z, } from "@clemrig/flow";
8
+ export { type DefinedFactory, defineFactory } from "./app/define.js";
9
+ export type { FactoryDefinition } from "./domain/definition.js";
10
+ export type { VerifyCommand } from "./domain/verification.js";
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Public API for writing workflows. A repository's `.factory/` imports only this
3
- * module; it re-exports the engine's, `z` included, so that workflows, the
4
- * factory and the engine share one Zod instance.
2
+ * Public API of a repository's `.factory/`: `defineFactory`, which adapts the
3
+ * factory's workflows to the repository, and the engine's API to write its own
4
+ * workflows, `z` included, so that workflows, the factory and the engine share
5
+ * one Zod instance.
5
6
  */
6
7
  export { agent, code, defineWorkflow, END, gate, node, ProcessInterruptedError, pick, retry, runProcess, schemaGate, step, z, } from "@clemrig/flow";
8
+ export { defineFactory } from "./app/define.js";
7
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAEL,KAAK,EAEL,IAAI,EACJ,cAAc,EACd,GAAG,EACH,IAAI,EAEJ,IAAI,EACJ,uBAAuB,EACvB,IAAI,EAEJ,KAAK,EACL,UAAU,EAIV,UAAU,EACV,IAAI,EAEJ,CAAC,GACF,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,EAEL,IAAI,EACJ,cAAc,EACd,GAAG,EACH,IAAI,EAEJ,IAAI,EACJ,uBAAuB,EACvB,IAAI,EAEJ,KAAK,EACL,UAAU,EAIV,UAAU,EACV,IAAI,EAGJ,CAAC,GACF,MAAM,eAAe,CAAC;AACvB,OAAO,EAAuB,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { CheckResult, VerifyCommand } from "../domain/verification.js";
2
+ export type CheckOutcome = {
3
+ ok: true;
4
+ durationMs: number;
5
+ } | {
6
+ ok: false;
7
+ cause: string;
8
+ output: string;
9
+ };
10
+ /** Runs one of the repository's checks; a timeout or a command that cannot start fails it, Ctrl-C stops the run. */
11
+ export declare function runCheck(dir: string, check: VerifyCommand): Promise<CheckOutcome>;
12
+ export type ChecksOutcome = {
13
+ ok: true;
14
+ passed: CheckResult[];
15
+ } | {
16
+ ok: false;
17
+ passed: CheckResult[];
18
+ check: VerifyCommand;
19
+ cause: string;
20
+ output: string;
21
+ };
22
+ /** A check that starts, or passes with its duration. */
23
+ export type CheckProgress = {
24
+ check: VerifyCommand;
25
+ status: "started";
26
+ } | {
27
+ check: VerifyCommand;
28
+ status: "passed";
29
+ durationMs: number;
30
+ };
31
+ /** Runs the checks in order, stopping at the first failure. */
32
+ export declare function runChecks(dir: string, checks: VerifyCommand[], progress?: (event: CheckProgress) => void): Promise<ChecksOutcome>;
@@ -0,0 +1,49 @@
1
+ import { ProcessInterruptedError, ProcessTimeoutError, runProcess, } from "@clemrig/flow";
2
+ /** Runs one of the repository's checks; a timeout or a command that cannot start fails it, Ctrl-C stops the run. */
3
+ export async function runCheck(dir, check) {
4
+ const started = Date.now();
5
+ try {
6
+ const result = await runProcess(check.command, dir, check.timeoutMs);
7
+ if (result.exitCode === 0)
8
+ return { ok: true, durationMs: Date.now() - started };
9
+ return {
10
+ ok: false,
11
+ cause: `exit code ${result.exitCode}`,
12
+ output: result.stdout + result.stderr,
13
+ };
14
+ }
15
+ catch (error) {
16
+ if (error instanceof ProcessInterruptedError)
17
+ throw error;
18
+ return {
19
+ ok: false,
20
+ cause: error instanceof Error ? error.message : String(error),
21
+ // What a command wrote before its timeout explains it.
22
+ output: error instanceof ProcessTimeoutError ? error.stdout + error.stderr : "",
23
+ };
24
+ }
25
+ }
26
+ /** Runs the checks in order, stopping at the first failure. */
27
+ export async function runChecks(dir, checks, progress) {
28
+ const passed = [];
29
+ for (const check of checks) {
30
+ progress?.({ check, status: "started" });
31
+ const outcome = await runCheck(dir, check);
32
+ if (!outcome.ok)
33
+ return {
34
+ ok: false,
35
+ passed,
36
+ check,
37
+ cause: outcome.cause,
38
+ output: outcome.output,
39
+ };
40
+ progress?.({ check, status: "passed", durationMs: outcome.durationMs });
41
+ passed.push({
42
+ name: check.name,
43
+ command: check.command,
44
+ durationMs: outcome.durationMs,
45
+ });
46
+ }
47
+ return { ok: true, passed };
48
+ }
49
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/system/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,UAAU,GACX,MAAM,eAAe,CAAC;AAOvB,oHAAoH;AACpH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,GAAW,EACX,KAAoB;IAEpB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;YACvB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;QACxD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,aAAa,MAAM,CAAC,QAAQ,EAAE;YACrC,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;SACtC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,uBAAuB;YAAE,MAAM,KAAK,CAAC;QAC1D,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAC7D,uDAAuD;YACvD,MAAM,EACJ,KAAK,YAAY,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;SAC1E,CAAC;IACJ,CAAC;AACH,CAAC;AAiBD,+DAA+D;AAC/D,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAAW,EACX,MAAuB,EACvB,QAAyC;IAEzC,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,OAAO,CAAC,EAAE;YACb,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM;gBACN,KAAK;gBACL,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC;QACJ,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,UAAU,EAAE,OAAO,CAAC,UAAU;SAC/B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type Workflow } from "@clemrig/flow";
1
+ import type { Workflow } from "@clemrig/flow";
2
2
  /** A repository's `.factory/`: its workflows, where they work and where runs are kept. */
3
3
  export interface Factory {
4
4
  /** The repository: the directory that contains `.factory/`. */
@@ -6,11 +6,13 @@ export interface Factory {
6
6
  factoryDir: string;
7
7
  runsDir: string;
8
8
  workflows: Map<string, Workflow>;
9
+ /** The factory's own workflows, whose prompts come with this package. */
10
+ packaged: Set<string>;
9
11
  }
10
12
  /** A factory without workflows, to read runs when `factory.ts` cannot load. */
11
13
  export declare function emptyFactory(factoryDir: string): Factory;
12
- /** Imports `.factory/factory.ts`, whose default export lists the repository's workflows. */
13
- export declare function loadFactory(factoryDir: string): Promise<Factory>;
14
+ /** Imports `.factory/factory.ts` and returns its default export, unchecked. */
15
+ export declare function importFactory(factoryDir: string): Promise<unknown>;
14
16
  export declare function workflowOf(factory: Factory, name: string): Workflow;
15
- /** Reads `workflows/<workflow>/prompts/<name>.md` for each agent stage of the workflow, at every run. */
16
- export declare function loadPrompts(factoryDir: string, workflow: Workflow): Promise<Record<string, string>>;
17
+ /** Reads `<name>.md` for each agent stage of the workflow, at every run. */
18
+ export declare function loadPrompts(factory: Factory, workflow: Workflow): Promise<Record<string, string>>;
@@ -1,7 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
- import { pathToFileURL } from "node:url";
4
- import { isWorkflow } from "@clemrig/flow";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
4
  /** A factory without workflows, to read runs when `factory.ts` cannot load. */
6
5
  export function emptyFactory(factoryDir) {
7
6
  return {
@@ -9,23 +8,13 @@ export function emptyFactory(factoryDir) {
9
8
  factoryDir,
10
9
  runsDir: join(factoryDir, "runs"),
11
10
  workflows: new Map(),
11
+ packaged: new Set(),
12
12
  };
13
13
  }
14
- /** Imports `.factory/factory.ts`, whose default export lists the repository's workflows. */
15
- export async function loadFactory(factoryDir) {
14
+ /** Imports `.factory/factory.ts` and returns its default export, unchecked. */
15
+ export async function importFactory(factoryDir) {
16
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 };
17
+ return module.default;
29
18
  }
30
19
  export function workflowOf(factory, name) {
31
20
  const workflow = factory.workflows.get(name);
@@ -33,14 +22,25 @@ export function workflowOf(factory, name) {
33
22
  throw new Error(`Unknown workflow ${name}; workflows: ${[...factory.workflows.keys()].join(", ") || "none"}`);
34
23
  return workflow;
35
24
  }
36
- /** Reads `workflows/<workflow>/prompts/<name>.md` for each agent stage of the workflow, at every run. */
37
- export async function loadPrompts(factoryDir, workflow) {
25
+ /** Both `src/system/` and `dist/system/` sit two levels below the package root. */
26
+ const packageRoot = fileURLToPath(new URL("../../", import.meta.url));
27
+ /**
28
+ * Where a workflow's prompts are: `prompts/<workflow>/` of this package for the
29
+ * factory's own, `.factory/workflows/<workflow>/prompts/` for the repository's.
30
+ */
31
+ function promptsDir(factory, workflow) {
32
+ return factory.packaged.has(workflow.name)
33
+ ? join(packageRoot, "prompts", workflow.name)
34
+ : join(factory.factoryDir, "workflows", workflow.name, "prompts");
35
+ }
36
+ /** Reads `<name>.md` for each agent stage of the workflow, at every run. */
37
+ export async function loadPrompts(factory, workflow) {
38
38
  const prompts = {};
39
39
  const problems = [];
40
40
  for (const { prompt: name } of Object.values(workflow.agents)) {
41
41
  if (Object.hasOwn(prompts, name))
42
42
  continue;
43
- const file = join(factoryDir, "workflows", workflow.name, "prompts", `${name}.md`);
43
+ const file = join(promptsDir(factory, workflow), `${name}.md`);
44
44
  let text;
45
45
  try {
46
46
  text = await readFile(file, "utf8");
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/system/factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/system/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /** A branch's full reference: a tag of the same name never answers for it. */
2
+ export declare function branchRef(branch: string): string;
3
+ /** The full reference of `origin`'s copy of a branch. */
4
+ export declare function remoteRef(branch: string): string;
5
+ /** The branch `origin/HEAD` points to, read from its full reference: a tag never shortens it. */
6
+ export declare function defaultBranch(dir: string): Promise<string>;
7
+ /** The repository's Git directory, where Git keeps what belongs to this working tree. */
8
+ export declare function gitDir(dir: string): Promise<string>;
9
+ /** The files under `path` in a branch or commit, relative to the repository. */
10
+ export declare function filesIn(dir: string, ref: string, path: string): Promise<string[]>;
11
+ export declare function currentBranch(dir: string): Promise<string>;
12
+ /** Modified, staged or untracked files, in the whole tree or under a path. */
13
+ export declare function hasChanges(dir: string, path?: string): Promise<boolean>;
14
+ export declare function fetchBranch(dir: string, branch: string): Promise<void>;
15
+ export declare function commitOf(dir: string, ref: string): Promise<string>;
16
+ /** Switches to a branch; `from` creates it at that commit, replacing a branch of that name. */
17
+ export declare function switchTo(dir: string, branch: string, from?: string): Promise<void>;
18
+ /** Commits the given paths, or every change; returns the new commit. */
19
+ export declare function commit(dir: string, message: string, paths?: string[]): Promise<string>;
20
+ /** Every change since a commit, new files included: stages them all, then diffs the index. */
21
+ export declare function stagedDiff(dir: string, since: string): Promise<{
22
+ text: string;
23
+ truncated: boolean;
24
+ }>;
25
+ export declare function forcePush(dir: string, branch: string): Promise<void>;
@@ -0,0 +1,73 @@
1
+ import { runProcess } from "@clemrig/flow";
2
+ /** Runs git in the repository; a failure carries git's own message. */
3
+ async function git(dir, args, timeoutMs = 60_000) {
4
+ const result = await runProcess(["git", ...args], dir, timeoutMs);
5
+ if (result.exitCode !== 0)
6
+ throw new Error(`git ${args.join(" ")} failed: ${result.stderr.trim() || result.stdout.trim()}`);
7
+ return result;
8
+ }
9
+ async function output(dir, args) {
10
+ return (await git(dir, args)).stdout.trim();
11
+ }
12
+ /** A branch's full reference: a tag of the same name never answers for it. */
13
+ export function branchRef(branch) {
14
+ return `refs/heads/${branch}`;
15
+ }
16
+ /** The full reference of `origin`'s copy of a branch. */
17
+ export function remoteRef(branch) {
18
+ return `refs/remotes/origin/${branch}`;
19
+ }
20
+ /** The branch `origin/HEAD` points to, read from its full reference: a tag never shortens it. */
21
+ export async function defaultBranch(dir) {
22
+ return (await output(dir, ["symbolic-ref", "refs/remotes/origin/HEAD"])).replace(/^refs\/remotes\/origin\//, "");
23
+ }
24
+ /** The repository's Git directory, where Git keeps what belongs to this working tree. */
25
+ export function gitDir(dir) {
26
+ return output(dir, ["rev-parse", "--absolute-git-dir"]);
27
+ }
28
+ /** The files under `path` in a branch or commit, relative to the repository. */
29
+ export async function filesIn(dir, ref, path) {
30
+ return (await output(dir, ["ls-tree", "-r", "--name-only", ref, "--", path]))
31
+ .split("\n")
32
+ .filter(Boolean);
33
+ }
34
+ export function currentBranch(dir) {
35
+ return output(dir, ["branch", "--show-current"]);
36
+ }
37
+ /** Modified, staged or untracked files, in the whole tree or under a path. */
38
+ export async function hasChanges(dir, path) {
39
+ return ((await output(dir, [
40
+ "status",
41
+ "--porcelain",
42
+ // Every untracked file, whatever status.showUntrackedFiles says.
43
+ "--untracked-files=all",
44
+ ...(path ? ["--", path] : []),
45
+ ])) !== "");
46
+ }
47
+ export async function fetchBranch(dir, branch) {
48
+ await git(dir, ["fetch", "origin", `+${branchRef(branch)}:${remoteRef(branch)}`], 120_000);
49
+ }
50
+ export function commitOf(dir, ref) {
51
+ return output(dir, ["rev-parse", ref]);
52
+ }
53
+ /** Switches to a branch; `from` creates it at that commit, replacing a branch of that name. */
54
+ export async function switchTo(dir, branch, from) {
55
+ await git(dir, ["switch", ...(from ? ["-C", branch, from] : [branch])]);
56
+ }
57
+ /** Commits the given paths, or every change; returns the new commit. */
58
+ export async function commit(dir, message, paths) {
59
+ await git(dir, ["add", ...(paths ? ["--", ...paths] : ["-A"])]);
60
+ await git(dir, ["commit", "-m", message, ...(paths ? ["--", ...paths] : [])]);
61
+ return commitOf(dir, "HEAD");
62
+ }
63
+ /** Every change since a commit, new files included: stages them all, then diffs the index. */
64
+ export async function stagedDiff(dir, since) {
65
+ await git(dir, ["add", "-A"]);
66
+ const result = await git(dir, ["diff", "--cached", since]);
67
+ return { text: result.stdout, truncated: result.stdoutTruncated };
68
+ }
69
+ export async function forcePush(dir, branch) {
70
+ const ref = branchRef(branch);
71
+ await git(dir, ["push", "--force", "-u", "origin", `${ref}:${ref}`], 120_000);
72
+ }
73
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/system/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare function isAuthenticated(dir: string): Promise<boolean>;
2
+ /** Updates the branch's open pull request, or opens one; returns its URL. */
3
+ export declare function openPullRequest(dir: string, pullRequest: {
4
+ base: string;
5
+ head: string;
6
+ title: string;
7
+ body: string;
8
+ }): Promise<string>;
@@ -0,0 +1,52 @@
1
+ import { runProcess } from "@clemrig/flow";
2
+ /** Runs gh in the repository; its output is never journaled, as it may describe the account. */
3
+ async function gh(dir, args, stdin) {
4
+ const result = await runProcess(["gh", ...args], dir, 60_000, undefined, stdin);
5
+ if (result.exitCode !== 0)
6
+ throw new Error(`gh ${args.slice(0, 2).join(" ")} failed: ${result.stderr.trim()}`);
7
+ return result.stdout.trim();
8
+ }
9
+ export async function isAuthenticated(dir) {
10
+ const result = await runProcess(["gh", "auth", "status"], dir, 60_000);
11
+ return result.exitCode === 0;
12
+ }
13
+ /** Updates the branch's open pull request, or opens one; returns its URL. */
14
+ export async function openPullRequest(dir, pullRequest) {
15
+ const { base, head, title, body } = pullRequest;
16
+ const open = JSON.parse(await gh(dir, [
17
+ "pr",
18
+ "list",
19
+ "--head",
20
+ head,
21
+ "--state",
22
+ "open",
23
+ "--json",
24
+ "number,url",
25
+ ]));
26
+ const existing = open[0];
27
+ if (existing) {
28
+ await gh(dir, [
29
+ "pr",
30
+ "edit",
31
+ String(existing.number),
32
+ "--title",
33
+ title,
34
+ "--body-file",
35
+ "-",
36
+ ], body);
37
+ return existing.url;
38
+ }
39
+ return gh(dir, [
40
+ "pr",
41
+ "create",
42
+ "--base",
43
+ base,
44
+ "--head",
45
+ head,
46
+ "--title",
47
+ title,
48
+ "--body-file",
49
+ "-",
50
+ ], body);
51
+ }
52
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/system/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,gGAAgG;AAChG,KAAK,UAAU,EAAE,CACf,GAAW,EACX,IAAc,EACd,KAAc;IAEd,MAAM,MAAM,GAAG,MAAM,UAAU,CAC7B,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,EACf,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,CACN,CAAC;IACF,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CACnE,CAAC;IACJ,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACvE,OAAO,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,WAAwE;IAExE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CACrB,MAAM,EAAE,CAAC,GAAG,EAAE;QACZ,IAAI;QACJ,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,SAAS;QACT,MAAM;QACN,QAAQ;QACR,YAAY;KACb,CAAC,CACkC,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,EAAE,CACN,GAAG,EACH;YACE,IAAI;YACJ,MAAM;YACN,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACvB,SAAS;YACT,KAAK;YACL,aAAa;YACb,GAAG;SACJ,EACD,IAAI,CACL,CAAC;QACF,OAAO,QAAQ,CAAC,GAAG,CAAC;IACtB,CAAC;IACD,OAAO,EAAE,CACP,GAAG,EACH;QACE,IAAI;QACJ,QAAQ;QACR,QAAQ;QACR,IAAI;QACJ,QAAQ;QACR,IAAI;QACJ,SAAS;QACT,KAAK;QACL,aAAa;QACb,GAAG;KACJ,EACD,IAAI,CACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ export type ArtifactKind = "issue" | "spec" | "plan";
2
+ /** Where the issues are, relative to the repository. */
3
+ export declare const issuesPath: string;
4
+ /** `.factory/issues/<slug>/<kind>.md`, relative to the repository: the only place that names these paths. */
5
+ export declare function artifactPath(slug: string, kind: ArtifactKind): string;
6
+ /** An issue's folder as it is in the working tree. */
7
+ export interface IssueFolder {
8
+ slug: string;
9
+ /** Its `issue.md`, when it has one. */
10
+ issue?: string;
11
+ /** Why its `issue.md` cannot be read. */
12
+ unreadable?: string;
13
+ hasSpec: boolean;
14
+ /** When one of its files last changed, as an ISO date. */
15
+ changedAt: string;
16
+ }
17
+ /** Every folder of `.factory/issues/`; none before the first issue. */
18
+ export declare function listIssueFolders(repository: string): Promise<IssueFolder[]>;
19
+ export declare function writeArtifact(repository: string, slug: string, kind: ArtifactKind, text: string): Promise<string>;
20
+ export declare function hasArtifact(repository: string, slug: string, kind: ArtifactKind): Promise<boolean>;
21
+ /** The artifact's text, or nothing when the issue has none. */
22
+ export declare function readArtifact(repository: string, slug: string, kind: ArtifactKind): Promise<string | undefined>;
23
+ /** A draft handed over by an agent, wherever it wrote it. */
24
+ export declare function readDraft(path: string): Promise<string>;
@@ -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. An existing `.factory/` is refused untouched.
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;