@agent-relay/factory 0.0.0 → 0.1.1

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 (221) hide show
  1. package/README.md +240 -22
  2. package/bin/factory.mjs +7 -0
  3. package/dist/cli/fleet.d.ts +77 -0
  4. package/dist/cli/fleet.d.ts.map +1 -0
  5. package/dist/cli/fleet.js +500 -0
  6. package/dist/cli/fleet.js.map +1 -0
  7. package/dist/config/schema.d.ts +1313 -0
  8. package/dist/config/schema.d.ts.map +1 -0
  9. package/dist/config/schema.js +309 -0
  10. package/dist/config/schema.js.map +1 -0
  11. package/dist/constants/linear.d.ts +4 -0
  12. package/dist/constants/linear.d.ts.map +1 -0
  13. package/dist/constants/linear.js +15 -0
  14. package/dist/constants/linear.js.map +1 -0
  15. package/dist/constants/slack.d.ts +3 -0
  16. package/dist/constants/slack.d.ts.map +1 -0
  17. package/dist/constants/slack.js +3 -0
  18. package/dist/constants/slack.js.map +1 -0
  19. package/dist/dispatch/templates.d.ts +36 -0
  20. package/dist/dispatch/templates.d.ts.map +1 -0
  21. package/dist/dispatch/templates.js +110 -0
  22. package/dist/dispatch/templates.js.map +1 -0
  23. package/dist/fleet/create-fleet.d.ts +13 -0
  24. package/dist/fleet/create-fleet.d.ts.map +1 -0
  25. package/dist/fleet/create-fleet.js +14 -0
  26. package/dist/fleet/create-fleet.js.map +1 -0
  27. package/dist/fleet/internal-fleet-client.d.ts +85 -0
  28. package/dist/fleet/internal-fleet-client.d.ts.map +1 -0
  29. package/dist/fleet/internal-fleet-client.js +575 -0
  30. package/dist/fleet/internal-fleet-client.js.map +1 -0
  31. package/dist/fleet/relay-fleet-client.d.ts +28 -0
  32. package/dist/fleet/relay-fleet-client.d.ts.map +1 -0
  33. package/dist/fleet/relay-fleet-client.js +33 -0
  34. package/dist/fleet/relay-fleet-client.js.map +1 -0
  35. package/dist/github/index.d.ts +5 -0
  36. package/dist/github/index.d.ts.map +1 -0
  37. package/dist/github/index.js +3 -0
  38. package/dist/github/index.js.map +1 -0
  39. package/dist/github/merge-gate.d.ts +47 -0
  40. package/dist/github/merge-gate.d.ts.map +1 -0
  41. package/dist/github/merge-gate.js +147 -0
  42. package/dist/github/merge-gate.js.map +1 -0
  43. package/dist/github/probe-closer.d.ts +15 -0
  44. package/dist/github/probe-closer.d.ts.map +1 -0
  45. package/dist/github/probe-closer.js +67 -0
  46. package/dist/github/probe-closer.js.map +1 -0
  47. package/dist/index.d.ts +32 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +18 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/issue-key-match.d.ts +3 -0
  52. package/dist/issue-key-match.d.ts.map +1 -0
  53. package/dist/issue-key-match.js +22 -0
  54. package/dist/issue-key-match.js.map +1 -0
  55. package/dist/linear/state-resolver.d.ts +25 -0
  56. package/dist/linear/state-resolver.d.ts.map +1 -0
  57. package/dist/linear/state-resolver.js +221 -0
  58. package/dist/linear/state-resolver.js.map +1 -0
  59. package/dist/mount/local-mount-preflight.d.ts +8 -0
  60. package/dist/mount/local-mount-preflight.d.ts.map +1 -0
  61. package/dist/mount/local-mount-preflight.js +97 -0
  62. package/dist/mount/local-mount-preflight.js.map +1 -0
  63. package/dist/mount/relayfile-binary.d.ts +7 -0
  64. package/dist/mount/relayfile-binary.d.ts.map +1 -0
  65. package/dist/mount/relayfile-binary.js +143 -0
  66. package/dist/mount/relayfile-binary.js.map +1 -0
  67. package/dist/mount/relayfile-cloud-mount-client.d.ts +123 -0
  68. package/dist/mount/relayfile-cloud-mount-client.d.ts.map +1 -0
  69. package/dist/mount/relayfile-cloud-mount-client.js +427 -0
  70. package/dist/mount/relayfile-cloud-mount-client.js.map +1 -0
  71. package/dist/orchestrator/batch-tracker.d.ts +42 -0
  72. package/dist/orchestrator/batch-tracker.d.ts.map +1 -0
  73. package/dist/orchestrator/batch-tracker.js +125 -0
  74. package/dist/orchestrator/batch-tracker.js.map +1 -0
  75. package/dist/orchestrator/factory.d.ts +55 -0
  76. package/dist/orchestrator/factory.d.ts.map +1 -0
  77. package/dist/orchestrator/factory.js +4018 -0
  78. package/dist/orchestrator/factory.js.map +1 -0
  79. package/dist/orchestrator/index.d.ts +5 -0
  80. package/dist/orchestrator/index.d.ts.map +1 -0
  81. package/dist/orchestrator/index.js +4 -0
  82. package/dist/orchestrator/index.js.map +1 -0
  83. package/dist/orchestrator/process-identity.d.ts +23 -0
  84. package/dist/orchestrator/process-identity.d.ts.map +1 -0
  85. package/dist/orchestrator/process-identity.js +122 -0
  86. package/dist/orchestrator/process-identity.js.map +1 -0
  87. package/dist/orchestrator/reaper.d.ts +60 -0
  88. package/dist/orchestrator/reaper.d.ts.map +1 -0
  89. package/dist/orchestrator/reaper.js +293 -0
  90. package/dist/orchestrator/reaper.js.map +1 -0
  91. package/dist/ports/fleet.d.ts +95 -0
  92. package/dist/ports/fleet.d.ts.map +1 -0
  93. package/dist/ports/fleet.js +2 -0
  94. package/dist/ports/fleet.js.map +1 -0
  95. package/dist/ports/index.d.ts +5 -0
  96. package/dist/ports/index.d.ts.map +1 -0
  97. package/dist/ports/index.js +2 -0
  98. package/dist/ports/index.js.map +1 -0
  99. package/dist/ports/mount.d.ts +56 -0
  100. package/dist/ports/mount.d.ts.map +1 -0
  101. package/dist/ports/mount.js +2 -0
  102. package/dist/ports/mount.js.map +1 -0
  103. package/dist/ports/state.d.ts +61 -0
  104. package/dist/ports/state.d.ts.map +1 -0
  105. package/dist/ports/state.js +2 -0
  106. package/dist/ports/state.js.map +1 -0
  107. package/dist/ports/system.d.ts +15 -0
  108. package/dist/ports/system.d.ts.map +1 -0
  109. package/dist/ports/system.js +2 -0
  110. package/dist/ports/system.js.map +1 -0
  111. package/dist/ports/writeback.d.ts +25 -0
  112. package/dist/ports/writeback.d.ts.map +1 -0
  113. package/dist/ports/writeback.js +2 -0
  114. package/dist/ports/writeback.js.map +1 -0
  115. package/dist/safety/factory-scope.d.ts +16 -0
  116. package/dist/safety/factory-scope.d.ts.map +1 -0
  117. package/dist/safety/factory-scope.js +107 -0
  118. package/dist/safety/factory-scope.js.map +1 -0
  119. package/dist/state/in-memory-state-store.d.ts +31 -0
  120. package/dist/state/in-memory-state-store.d.ts.map +1 -0
  121. package/dist/state/in-memory-state-store.js +115 -0
  122. package/dist/state/in-memory-state-store.js.map +1 -0
  123. package/dist/state/index.d.ts +3 -0
  124. package/dist/state/index.d.ts.map +1 -0
  125. package/dist/state/index.js +2 -0
  126. package/dist/state/index.js.map +1 -0
  127. package/dist/state/store.d.ts +29 -0
  128. package/dist/state/store.d.ts.map +1 -0
  129. package/dist/state/store.js +26 -0
  130. package/dist/state/store.js.map +1 -0
  131. package/dist/subscriptions/common.d.ts +7 -0
  132. package/dist/subscriptions/common.d.ts.map +1 -0
  133. package/dist/subscriptions/common.js +59 -0
  134. package/dist/subscriptions/common.js.map +1 -0
  135. package/dist/subscriptions/event-client.d.ts +40 -0
  136. package/dist/subscriptions/event-client.d.ts.map +1 -0
  137. package/dist/subscriptions/event-client.js +234 -0
  138. package/dist/subscriptions/event-client.js.map +1 -0
  139. package/dist/subscriptions/globs.d.ts +5 -0
  140. package/dist/subscriptions/globs.d.ts.map +1 -0
  141. package/dist/subscriptions/globs.js +50 -0
  142. package/dist/subscriptions/globs.js.map +1 -0
  143. package/dist/subscriptions/index.d.ts +8 -0
  144. package/dist/subscriptions/index.d.ts.map +1 -0
  145. package/dist/subscriptions/index.js +5 -0
  146. package/dist/subscriptions/index.js.map +1 -0
  147. package/dist/subscriptions/linear-filter.d.ts +26 -0
  148. package/dist/subscriptions/linear-filter.d.ts.map +1 -0
  149. package/dist/subscriptions/linear-filter.js +143 -0
  150. package/dist/subscriptions/linear-filter.js.map +1 -0
  151. package/dist/subscriptions/specs.d.ts +44 -0
  152. package/dist/subscriptions/specs.d.ts.map +1 -0
  153. package/dist/subscriptions/specs.js +195 -0
  154. package/dist/subscriptions/specs.js.map +1 -0
  155. package/dist/testing/fakes.d.ts +114 -0
  156. package/dist/testing/fakes.d.ts.map +1 -0
  157. package/dist/testing/fakes.js +219 -0
  158. package/dist/testing/fakes.js.map +1 -0
  159. package/dist/testing/index.d.ts +2 -0
  160. package/dist/testing/index.d.ts.map +1 -0
  161. package/dist/testing/index.js +2 -0
  162. package/dist/testing/index.js.map +1 -0
  163. package/dist/triage/heuristic.d.ts +38 -0
  164. package/dist/triage/heuristic.d.ts.map +1 -0
  165. package/dist/triage/heuristic.js +262 -0
  166. package/dist/triage/heuristic.js.map +1 -0
  167. package/dist/triage/index.d.ts +7 -0
  168. package/dist/triage/index.d.ts.map +1 -0
  169. package/dist/triage/index.js +5 -0
  170. package/dist/triage/index.js.map +1 -0
  171. package/dist/triage/llm.d.ts +11 -0
  172. package/dist/triage/llm.d.ts.map +1 -0
  173. package/dist/triage/llm.js +46 -0
  174. package/dist/triage/llm.js.map +1 -0
  175. package/dist/triage/schema.d.ts +240 -0
  176. package/dist/triage/schema.d.ts.map +1 -0
  177. package/dist/triage/schema.js +34 -0
  178. package/dist/triage/schema.js.map +1 -0
  179. package/dist/triage/tiered.d.ts +8 -0
  180. package/dist/triage/tiered.d.ts.map +1 -0
  181. package/dist/triage/tiered.js +40 -0
  182. package/dist/triage/tiered.js.map +1 -0
  183. package/dist/types.d.ts +211 -0
  184. package/dist/types.d.ts.map +1 -0
  185. package/dist/types.js +2 -0
  186. package/dist/types.js.map +1 -0
  187. package/dist/webhook/handler.d.ts +22 -0
  188. package/dist/webhook/handler.d.ts.map +1 -0
  189. package/dist/webhook/handler.js +57 -0
  190. package/dist/webhook/handler.js.map +1 -0
  191. package/dist/webhook/index.d.ts +5 -0
  192. package/dist/webhook/index.d.ts.map +1 -0
  193. package/dist/webhook/index.js +3 -0
  194. package/dist/webhook/index.js.map +1 -0
  195. package/dist/webhook/registrar.d.ts +16 -0
  196. package/dist/webhook/registrar.d.ts.map +1 -0
  197. package/dist/webhook/registrar.js +50 -0
  198. package/dist/webhook/registrar.js.map +1 -0
  199. package/dist/writeback/github.d.ts +6 -0
  200. package/dist/writeback/github.d.ts.map +1 -0
  201. package/dist/writeback/github.js +48 -0
  202. package/dist/writeback/github.js.map +1 -0
  203. package/dist/writeback/index.d.ts +6 -0
  204. package/dist/writeback/index.d.ts.map +1 -0
  205. package/dist/writeback/index.js +4 -0
  206. package/dist/writeback/index.js.map +1 -0
  207. package/dist/writeback/linear.d.ts +41 -0
  208. package/dist/writeback/linear.d.ts.map +1 -0
  209. package/dist/writeback/linear.js +243 -0
  210. package/dist/writeback/linear.js.map +1 -0
  211. package/dist/writeback/shared.d.ts +7 -0
  212. package/dist/writeback/shared.d.ts.map +1 -0
  213. package/dist/writeback/shared.js +29 -0
  214. package/dist/writeback/shared.js.map +1 -0
  215. package/dist/writeback/slack.d.ts +16 -0
  216. package/dist/writeback/slack.d.ts.map +1 -0
  217. package/dist/writeback/slack.js +104 -0
  218. package/dist/writeback/slack.js.map +1 -0
  219. package/package.json +46 -9
  220. package/index.d.ts +0 -2
  221. package/index.js +0 -7
package/README.md CHANGED
@@ -1,30 +1,248 @@
1
1
  # @agent-relay/factory
2
2
 
3
- Autonomous Linear-issue triage PR factory. **This is currently a placeholder
4
- reservation** the real package (orchestrator, triage engine, GitHub merge-gate,
5
- fleet client, StateStore, config) is extracted here from `pear/packages/factory-sdk`
6
- by the **p4** workflow in the factory build-out stack.
3
+ The autonomous issue factory: a loop that discovers Linear issues, triages them,
4
+ dispatches agents to implement fixes, opens PRs, drives them to completion through
5
+ a merge gate, and closes the issues all under a hard safety scope.
7
6
 
8
- See the planning + epic under [`planning/`](./planning/) (especially
9
- `factory-cloud-watches-local-node-linear-issue.md`).
7
+ The package publishes the **`factory`** CLI (`bin/factory.mjs`). From a repo
8
+ checkout, build first and invoke the CLI through the package bin:
10
9
 
11
- ## Publishing
10
+ ```bash
11
+ npm ci
12
+ npm run build
13
+
14
+ node bin/factory.mjs factory <action> --config <cfg>
15
+ ```
12
16
 
13
- Releases use npm **OIDC trusted-publisher provenance** (no `NPM_TOKEN`), via
14
- `.github/workflows/publish.yml` — `npm publish --provenance` under
15
- `id-token: write`. Register this repo/workflow as a trusted publisher for
16
- `@agent-relay/factory` on npmjs.com before the first CI publish.
17
+ Once installed from npm, the ergonomic form is:
17
18
 
18
- **Reserve the name now (manual, no provenance — that's fine for a reservation):**
19
19
  ```bash
20
- cd factory
21
- npm publish --access public # publishes 0.0.0, reserves @agent-relay/factory
20
+ factory factory <action> [options]
21
+ ```
22
+
23
+ > **Heads-up:** every `factory` action requires `--config <path>` (see
24
+ > [Configuration](#configuration)). Commands fail fast with
25
+ > `factory commands require --config <path>` if it's missing.
26
+
27
+ ---
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ # One-shot batch — discover + triage + dispatch one cycle, then exit.
33
+ # Add --dry-run to plan without writing or spawning anything.
34
+ factory factory run-once --config ./factory.config.json --dry-run
35
+
36
+ # Live daemon — subscription-driven; runs until SIGINT/SIGTERM.
37
+ factory factory start --mode live --config ./factory.config.json
38
+ ```
39
+
40
+ ## Commands
41
+
42
+ | Command | What it does |
43
+ |---|---|
44
+ | `factory run-once` | One discovery→triage→dispatch cycle, then exit. Honors `--dry-run`. |
45
+ | `factory loop` | Bounded multi-iteration loop (`loop.maxIterations`), then exit. |
46
+ | `factory start --mode live` | Long-lived daemon. Subscription-driven; writes + refreshes a loop heartbeat. The production entrypoint. |
47
+ | `factory status` | Print the in-memory factory status as JSON. |
48
+ | `factory loop-status` | Read the heartbeat file and report liveness (stale vs. alive). |
49
+ | `factory kill-loop` | Send SIGTERM to the daemon PID recorded in the heartbeat. |
50
+ | `factory reap-orphans` | Crash backstop: reap orphaned agent pairs whose heartbeat is stale. Run as a scheduled job. |
51
+ | `factory triage <KEY\|path>` | Triage a single issue and print the decision. |
52
+ | `factory dispatch <KEY\|path>` | Triage + dispatch a single issue. Honors `--dry-run`. |
53
+ | `factory close-probe <PR#> --repo <owner/repo> --issue <KEY>` | Manually close a synthetic E2E probe PR. |
54
+
55
+ Global options (accepted anywhere in the args): `--config <path>`,
56
+ `--dry-run`, `--backend <internal\|relay>`.
57
+
58
+ ## The 2-process production model
59
+
60
+ The factory runs as **two** coordinated processes (see issue #321 §4):
61
+
62
+ 1. **Live daemon** — `factory factory start --mode live --config <live>`. Drives the
63
+ loop and refreshes a heartbeat file.
64
+ 2. **External reaper** — a *scheduled* `factory factory reap-orphans --config <live>`
65
+ that acts as a crash backstop, cleaning up orphaned agent pairs if the daemon dies.
66
+
67
+ > **HARD precondition:** the reaper **must** use the **same `--config`** as the
68
+ > live daemon. A mismatched config reaps nothing *and* leaves the backstop broken
69
+ > (the coupling is load-bearing — see §7 of issue #321).
70
+
71
+ ### Other operating preconditions (issue #321 §7)
72
+
73
+ - **`gh`-authenticated environment.** The gh-resolver is completion-load-bearing
74
+ while the cloud GitHub→mount PR-sync is degraded; a `gh` auth drop halts completion.
75
+ - Run the real binary path (`factory` / `node bin/factory.mjs`), not a shim.
76
+
77
+ ## Configuration
78
+
79
+ Pass a JSON file via `--config`. The full schema (with every default) lives in
80
+ [`src/config/schema.ts`](src/config/schema.ts), validated by Zod at load time —
81
+ an invalid config fails fast with a field-level error.
82
+
83
+ The file is either a bare config object, or a `{ "factoryConfig": { … } }`
84
+ wrapper (the wrapper form also allows a sibling `fixtureFiles` map — see
85
+ [Fixture mode](#fixture-mode-offline-testing)).
86
+
87
+ ### Minimal config
88
+
89
+ Only two fields are required — `workspaceId` and `repos.byLabel`. Everything else
90
+ has a default:
91
+
92
+ ```json
93
+ {
94
+ "workspaceId": "your-workspace-id",
95
+ "repos": {
96
+ "byLabel": { "pear": "AgentWorkforce/pear" },
97
+ "default": "AgentWorkforce/pear"
98
+ }
99
+ }
22
100
  ```
23
- Then the p4 extraction lands the real code and the **first real release is 0.1.0**
24
- via the publish workflow (`workflow_dispatch` → `custom_version: 0.1.0`), with
25
- provenance.
26
-
27
- > When p4 extracts `factory-sdk` into this repo, it must **preserve** this
28
- > `package.json`'s `repository` + `publishConfig` fields and
29
- > `.github/workflows/publish.yml`, and replace the placeholder `index.js` /
30
- > `index.d.ts` with the real package entrypoints.
101
+
102
+ ### Complete annotated config
103
+
104
+ A realistic live config, showing the fields you'll actually want to set (comments
105
+ are illustrative strip them; JSON has no comments):
106
+
107
+ ```jsonc
108
+ {
109
+ // relayfile cloud mount workspace id — the workspace whose /linear and /slack
110
+ // trees the factory reads. Same id the Pear app uses for this workspace.
111
+ "workspaceId": "ws_abc123",
112
+
113
+ // Which Linear issues to pull. Empty arrays = no filter on that dimension.
114
+ // Values are Linear team keys / project names / label names / assignee ids.
115
+ "subscription": {
116
+ "teams": ["AR"],
117
+ "labels": ["pear"],
118
+ "projects": [],
119
+ "assignees": []
120
+ },
121
+
122
+ // Issue → repo routing. Precedence (first match wins):
123
+ // byLabel → byProject → keywordRules (regex on title/desc) → default
124
+ // No match and no default → the issue is escalated, never dispatched.
125
+ "repos": {
126
+ "byLabel": { "pear": "AgentWorkforce/pear", "cloud": "AgentWorkforce/cloud" },
127
+ "byProject": { "Pear": "AgentWorkforce/pear" },
128
+ "keywordRules": [{ "pattern": "relayfile|mount", "repo": "AgentWorkforce/relayfile" }],
129
+ // Where each repo is checked out locally for the agent to work in.
130
+ "clonePaths": { "AgentWorkforce/pear": "/Users/you/Projects/pear" },
131
+ "default": "AgentWorkforce/pear"
132
+ },
133
+
134
+ // SAFETY GATE — an issue is only dispatched if BOTH hold (see below):
135
+ "safety": {
136
+ "requireTitlePrefix": "[factory-e2e]",
137
+ "requireTeamKey": "AR"
138
+ },
139
+
140
+ "batchSize": 5, // max issues dispatched per cycle
141
+ "dispatch": { "maxAttempts": 2, "errorCooldownMs": 60000 },
142
+ "mergePolicy": "never", // see mergePolicy note below
143
+ "models": { // optional per-role model overrides
144
+ "implementer": "claude-opus-4-8",
145
+ "reviewer": "claude-opus-4-8",
146
+ "triage": "claude-haiku-4-5-20251001"
147
+ },
148
+ "slack": { "channel": "C0B902XR6PN" }, // optional threaded status updates
149
+ "loop": {
150
+ "heartbeatPath": "/tmp/factory-run/factory-loop-heartbeat.json",
151
+ "registryPath": "/tmp/factory-run/factory-loop-registry.json",
152
+ "heartbeatStaleMs": 60000
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### Where the values come from
158
+
159
+ - **`workspaceId`** *(required)* — the relayfile cloud mount workspace id for this
160
+ workspace (the same one the Pear app mounts `/linear` and `/slack` under).
161
+ - **`repos.byLabel`** *(required)* — map a Linear label → `owner/repo`. The other
162
+ routing maps are optional; **precedence is `byLabel` → `byProject` →
163
+ `keywordRules` → `default`**, else the issue is escalated (never dispatched).
164
+ - **`repos.clonePaths`** — `owner/repo` → local working-tree path. Without an entry
165
+ the agent has nowhere to apply changes for that repo, so set it for every repo
166
+ you actually dispatch to.
167
+ - **`stateIds`** — the Linear workflow-state UUIDs for `readyForAgent`,
168
+ `agentImplementing`, `done`, `inPlanning`, and optionally `humanReview`.
169
+ Defaults cover the **AR team's** core states (see
170
+ [`src/constants/linear.ts`](src/constants/linear.ts)). `humanReview` is
171
+ intentionally omitted from the schema default so legacy configs fall back to
172
+ direct-to-Done behavior unless they opt into a review state. If you run against
173
+ a different Linear team, override the state UUIDs with that team's values (read
174
+ them from the Linear API / the issue JSON's `state.id`).
175
+ - **`safety`** — the scope gate (below). Defaults `[factory-e2e]` + team `AR`;
176
+ GitHub issue mirrors created by the factory use `[factory]` and are accepted
177
+ by the same gate.
178
+
179
+ ### The safety gate (what actually gets dispatched)
180
+
181
+ `isInFactoryScope` ([`src/safety/factory-scope.ts`](src/safety/factory-scope.ts))
182
+ dispatches an issue only when **both** are true:
183
+
184
+ 1. The issue **title starts with `safety.requireTitlePrefix`** — exactly
185
+ `[factory-e2e]`, or `[factory-e2e] <rest>` by default. Factory-created
186
+ GitHub mirrors titled `[factory] <GitHub title>` are also accepted. Anything
187
+ else is out of scope.
188
+ 2. The issue's **team key equals `safety.requireTeamKey`** (`AR`).
189
+
190
+ This is why `factory run-once` may pull issues but dispatch none — they're real
191
+ issues that fall outside the gate. Loosen the gate deliberately; it's the primary
192
+ guardrail against the factory acting on issues it shouldn't.
193
+
194
+ > **⚠️ `[factory-e2e]` is the synthetic self-test prefix — its PRs auto-close.**
195
+ > The default `requireTitlePrefix` is `[factory-e2e]`, which is *also* the marker
196
+ > the factory uses to identify synthetic E2E soak issues. A `[factory-e2e]`-titled
197
+ > issue's PR is **auto-closed, never merged** (`#isSyntheticProbeIssue`) — great
198
+ > for self-test soaks, wrong for real work. **For real issues you want to keep,
199
+ > set `requireTitlePrefix` to `[factory]`** and title issues `[factory] <task>`;
200
+ > reserve `[factory-e2e]` for the soak. The synthetic check matches `[factory-e2e]`
201
+ > exactly, so `[factory]` issues are not auto-closed. (Note: `[factory-e2e]` does
202
+ > **not** satisfy a `[factory]` gate — the prefixes differ at the `]`/`-`, so don't
203
+ > mix them.)
204
+
205
+ ### Ingesting GitHub issues (label `factory`)
206
+
207
+ The factory also picks up **GitHub issues labeled `factory`** (case-insensitive),
208
+ read from the mounted `/github/repos/**` tree (pushed in by the GitHub→relayfile
209
+ sync — no API polling). For each, it creates one Linear mirror titled
210
+ `[factory] <GitHub title>` in **Ready for Agent**, routes it via `repos.byLabel`,
211
+ then dispatches it like any other Linear issue. Mirrors are deduped (one per
212
+ GitHub issue) and set to `done` when the GitHub issue closes.
213
+
214
+ So to hand the factory a GitHub issue you **just add the `factory` label** — you
215
+ do **not** put `[factory]` in the GitHub title (the factory adds that to the
216
+ Linear mirror). This is the cross-repo path: label, say, a `relay` repo issue
217
+ `factory` and it flows in, routed to `AgentWorkforce/relay`.
218
+
219
+ | Source | What you do | Result |
220
+ |---|---|---|
221
+ | Linear (direct) | title `[factory] <task>`, team AR, label = repo, Ready for Agent | dispatched directly |
222
+ | GitHub (any configured repo) | add the **`factory` label** | mirrored to a `[factory]` Linear issue, then dispatched |
223
+
224
+ ### Other notable fields (defaults in parentheses)
225
+
226
+ - `mergePolicy` (`never`) — `never` keeps PRs open; `on-green-with-review` enables
227
+ autonomous merge on green + approved review. **Stays `never` until the flip is
228
+ thrown** (issue #321 §6).
229
+ - `loop.heartbeatPath` / `loop.registryPath` / `loop.heartbeatStaleMs` (`60000`) —
230
+ daemon/reaper coupling (the reaper must point at the same paths via the same
231
+ `--config`).
232
+ - `batchSize` (`5`), `dispatch.maxAttempts` (`2`), `dispatch.errorCooldownMs`
233
+ (`60000`), `models.{implementer,reviewer,triage}`, `slack.channel`,
234
+ `subscription.*`, `liveSubscription.*`, `stateIds.*`.
235
+
236
+ ### Fixture mode (offline testing)
237
+
238
+ If the config (or a `{ "factoryConfig": …, "fixtureFiles": … }` wrapper) includes a
239
+ `fixtureFiles` map, the CLI swaps in fake fleet + mount clients backed by those files —
240
+ no cloud, no real agents. See [`test/fixtures/factory.config.json`](test/fixtures/factory.config.json).
241
+
242
+ ## Notes
243
+
244
+ - The daemon is headless by design. Pear consumes this package and may expose its
245
+ own `pear factory ...` passthrough, but this repository's published CLI is
246
+ `factory`.
247
+ - Build output lives in `dist/`; `npm pack --dry-run` should include only
248
+ `dist/`, `bin/factory.mjs`, `package.json`, and `README.md`.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath, pathToFileURL } from 'node:url'
4
+ const here = dirname(fileURLToPath(import.meta.url))
5
+ const entry = join(here, '..', 'dist', 'cli', 'fleet.js')
6
+ const mod = await import(pathToFileURL(entry).href)
7
+ await mod.main(process.argv.slice(2))
@@ -0,0 +1,77 @@
1
+ import { createFactory, createFleet, type Capability, type Factory, type FactoryConfig, type FleetBackend, type FleetClient, type GhRunner, type FactoryStateResolution, type MountClient, type ProbeCloser, type RelayfileCloudMountClientConfig, type ResolvedFactoryWorkspace } from '../index';
2
+ interface FleetCliDeps {
3
+ fleet?: FleetClient;
4
+ mount?: MountClient;
5
+ createFactory?: typeof createFactory;
6
+ createFleet?: typeof createFleet;
7
+ cloudMountFromConfig?: (config?: RelayfileCloudMountClientConfig) => Promise<MountClient>;
8
+ resolveWorkspace?: () => Promise<ResolvedFactoryWorkspace>;
9
+ resolveStates?: (mount: MountClient, config: FactoryConfig) => Promise<FactoryStateResolution>;
10
+ ensureLocalMount?: (workspaceId: string, startDir: string, options?: {
11
+ acceptableWorkspaceIds?: readonly string[];
12
+ }) => Promise<void>;
13
+ waitForStopSignal?: () => Promise<number | void>;
14
+ stdout?: Pick<NodeJS.WriteStream, 'write'>;
15
+ stderr?: Pick<NodeJS.WriteStream, 'write'>;
16
+ probeCloser?: ProbeCloser;
17
+ probePrGhRunner?: GhRunner;
18
+ now?: () => number;
19
+ stopSignalProcessLike?: Pick<NodeJS.Process, 'once' | 'off'>;
20
+ daemonExit?: (code: number) => void;
21
+ flushDaemonOutput?: () => Promise<void>;
22
+ }
23
+ interface GlobalOptions {
24
+ backend: FleetBackend;
25
+ config?: string;
26
+ dryRun: boolean;
27
+ }
28
+ type ParsedCommand = {
29
+ kind: 'spawn';
30
+ input: {
31
+ capability: Capability;
32
+ name?: string;
33
+ node?: 'self' | string;
34
+ task?: string;
35
+ model?: string;
36
+ sessionRef?: string;
37
+ cwd?: string;
38
+ };
39
+ } | {
40
+ kind: 'roster';
41
+ } | {
42
+ kind: 'release';
43
+ name: string;
44
+ reason?: string;
45
+ } | {
46
+ kind: 'factory';
47
+ action: 'run-once' | 'loop' | 'status' | 'loop-status' | 'kill-loop' | 'reap-orphans';
48
+ } | {
49
+ kind: 'factory';
50
+ action: 'start';
51
+ mode?: 'live';
52
+ } | {
53
+ kind: 'factory-triage';
54
+ issue: string;
55
+ } | {
56
+ kind: 'factory-dispatch';
57
+ issue: string;
58
+ } | {
59
+ kind: 'factory-close-probe';
60
+ prNumber: number;
61
+ repo: string;
62
+ issue: string;
63
+ };
64
+ export declare function runFleetCli(argv: string[], deps?: FleetCliDeps): Promise<number>;
65
+ export declare function parseFleetCommand(args: string[]): ParsedCommand;
66
+ export declare function parseGlobalOptions(argv: string[]): {
67
+ globals: GlobalOptions;
68
+ args: string[];
69
+ };
70
+ export declare function resolveBrokerConnectionPath(startCwd?: string): string | undefined;
71
+ export declare function installFactoryStopSignalHandlers(factory: Factory, opts?: {
72
+ exit?: (code: number) => void;
73
+ processLike?: Pick<NodeJS.Process, 'once' | 'off'>;
74
+ }): () => void;
75
+ export declare function main(argv?: string[]): Promise<void>;
76
+ export {};
77
+ //# sourceMappingURL=fleet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../src/cli/fleet.ts"],"names":[],"mappings":"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"}