@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.
- package/README.md +240 -22
- package/bin/factory.mjs +7 -0
- package/dist/cli/fleet.d.ts +77 -0
- package/dist/cli/fleet.d.ts.map +1 -0
- package/dist/cli/fleet.js +500 -0
- package/dist/cli/fleet.js.map +1 -0
- package/dist/config/schema.d.ts +1313 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +309 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/constants/linear.d.ts +4 -0
- package/dist/constants/linear.d.ts.map +1 -0
- package/dist/constants/linear.js +15 -0
- package/dist/constants/linear.js.map +1 -0
- package/dist/constants/slack.d.ts +3 -0
- package/dist/constants/slack.d.ts.map +1 -0
- package/dist/constants/slack.js +3 -0
- package/dist/constants/slack.js.map +1 -0
- package/dist/dispatch/templates.d.ts +36 -0
- package/dist/dispatch/templates.d.ts.map +1 -0
- package/dist/dispatch/templates.js +110 -0
- package/dist/dispatch/templates.js.map +1 -0
- package/dist/fleet/create-fleet.d.ts +13 -0
- package/dist/fleet/create-fleet.d.ts.map +1 -0
- package/dist/fleet/create-fleet.js +14 -0
- package/dist/fleet/create-fleet.js.map +1 -0
- package/dist/fleet/internal-fleet-client.d.ts +85 -0
- package/dist/fleet/internal-fleet-client.d.ts.map +1 -0
- package/dist/fleet/internal-fleet-client.js +575 -0
- package/dist/fleet/internal-fleet-client.js.map +1 -0
- package/dist/fleet/relay-fleet-client.d.ts +28 -0
- package/dist/fleet/relay-fleet-client.d.ts.map +1 -0
- package/dist/fleet/relay-fleet-client.js +33 -0
- package/dist/fleet/relay-fleet-client.js.map +1 -0
- package/dist/github/index.d.ts +5 -0
- package/dist/github/index.d.ts.map +1 -0
- package/dist/github/index.js +3 -0
- package/dist/github/index.js.map +1 -0
- package/dist/github/merge-gate.d.ts +47 -0
- package/dist/github/merge-gate.d.ts.map +1 -0
- package/dist/github/merge-gate.js +147 -0
- package/dist/github/merge-gate.js.map +1 -0
- package/dist/github/probe-closer.d.ts +15 -0
- package/dist/github/probe-closer.d.ts.map +1 -0
- package/dist/github/probe-closer.js +67 -0
- package/dist/github/probe-closer.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/issue-key-match.d.ts +3 -0
- package/dist/issue-key-match.d.ts.map +1 -0
- package/dist/issue-key-match.js +22 -0
- package/dist/issue-key-match.js.map +1 -0
- package/dist/linear/state-resolver.d.ts +25 -0
- package/dist/linear/state-resolver.d.ts.map +1 -0
- package/dist/linear/state-resolver.js +221 -0
- package/dist/linear/state-resolver.js.map +1 -0
- package/dist/mount/local-mount-preflight.d.ts +8 -0
- package/dist/mount/local-mount-preflight.d.ts.map +1 -0
- package/dist/mount/local-mount-preflight.js +97 -0
- package/dist/mount/local-mount-preflight.js.map +1 -0
- package/dist/mount/relayfile-binary.d.ts +7 -0
- package/dist/mount/relayfile-binary.d.ts.map +1 -0
- package/dist/mount/relayfile-binary.js +143 -0
- package/dist/mount/relayfile-binary.js.map +1 -0
- package/dist/mount/relayfile-cloud-mount-client.d.ts +123 -0
- package/dist/mount/relayfile-cloud-mount-client.d.ts.map +1 -0
- package/dist/mount/relayfile-cloud-mount-client.js +427 -0
- package/dist/mount/relayfile-cloud-mount-client.js.map +1 -0
- package/dist/orchestrator/batch-tracker.d.ts +42 -0
- package/dist/orchestrator/batch-tracker.d.ts.map +1 -0
- package/dist/orchestrator/batch-tracker.js +125 -0
- package/dist/orchestrator/batch-tracker.js.map +1 -0
- package/dist/orchestrator/factory.d.ts +55 -0
- package/dist/orchestrator/factory.d.ts.map +1 -0
- package/dist/orchestrator/factory.js +4018 -0
- package/dist/orchestrator/factory.js.map +1 -0
- package/dist/orchestrator/index.d.ts +5 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/index.js +4 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/process-identity.d.ts +23 -0
- package/dist/orchestrator/process-identity.d.ts.map +1 -0
- package/dist/orchestrator/process-identity.js +122 -0
- package/dist/orchestrator/process-identity.js.map +1 -0
- package/dist/orchestrator/reaper.d.ts +60 -0
- package/dist/orchestrator/reaper.d.ts.map +1 -0
- package/dist/orchestrator/reaper.js +293 -0
- package/dist/orchestrator/reaper.js.map +1 -0
- package/dist/ports/fleet.d.ts +95 -0
- package/dist/ports/fleet.d.ts.map +1 -0
- package/dist/ports/fleet.js +2 -0
- package/dist/ports/fleet.js.map +1 -0
- package/dist/ports/index.d.ts +5 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/ports/mount.d.ts +56 -0
- package/dist/ports/mount.d.ts.map +1 -0
- package/dist/ports/mount.js +2 -0
- package/dist/ports/mount.js.map +1 -0
- package/dist/ports/state.d.ts +61 -0
- package/dist/ports/state.d.ts.map +1 -0
- package/dist/ports/state.js +2 -0
- package/dist/ports/state.js.map +1 -0
- package/dist/ports/system.d.ts +15 -0
- package/dist/ports/system.d.ts.map +1 -0
- package/dist/ports/system.js +2 -0
- package/dist/ports/system.js.map +1 -0
- package/dist/ports/writeback.d.ts +25 -0
- package/dist/ports/writeback.d.ts.map +1 -0
- package/dist/ports/writeback.js +2 -0
- package/dist/ports/writeback.js.map +1 -0
- package/dist/safety/factory-scope.d.ts +16 -0
- package/dist/safety/factory-scope.d.ts.map +1 -0
- package/dist/safety/factory-scope.js +107 -0
- package/dist/safety/factory-scope.js.map +1 -0
- package/dist/state/in-memory-state-store.d.ts +31 -0
- package/dist/state/in-memory-state-store.d.ts.map +1 -0
- package/dist/state/in-memory-state-store.js +115 -0
- package/dist/state/in-memory-state-store.js.map +1 -0
- package/dist/state/index.d.ts +3 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +2 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/store.d.ts +29 -0
- package/dist/state/store.d.ts.map +1 -0
- package/dist/state/store.js +26 -0
- package/dist/state/store.js.map +1 -0
- package/dist/subscriptions/common.d.ts +7 -0
- package/dist/subscriptions/common.d.ts.map +1 -0
- package/dist/subscriptions/common.js +59 -0
- package/dist/subscriptions/common.js.map +1 -0
- package/dist/subscriptions/event-client.d.ts +40 -0
- package/dist/subscriptions/event-client.d.ts.map +1 -0
- package/dist/subscriptions/event-client.js +234 -0
- package/dist/subscriptions/event-client.js.map +1 -0
- package/dist/subscriptions/globs.d.ts +5 -0
- package/dist/subscriptions/globs.d.ts.map +1 -0
- package/dist/subscriptions/globs.js +50 -0
- package/dist/subscriptions/globs.js.map +1 -0
- package/dist/subscriptions/index.d.ts +8 -0
- package/dist/subscriptions/index.d.ts.map +1 -0
- package/dist/subscriptions/index.js +5 -0
- package/dist/subscriptions/index.js.map +1 -0
- package/dist/subscriptions/linear-filter.d.ts +26 -0
- package/dist/subscriptions/linear-filter.d.ts.map +1 -0
- package/dist/subscriptions/linear-filter.js +143 -0
- package/dist/subscriptions/linear-filter.js.map +1 -0
- package/dist/subscriptions/specs.d.ts +44 -0
- package/dist/subscriptions/specs.d.ts.map +1 -0
- package/dist/subscriptions/specs.js +195 -0
- package/dist/subscriptions/specs.js.map +1 -0
- package/dist/testing/fakes.d.ts +114 -0
- package/dist/testing/fakes.d.ts.map +1 -0
- package/dist/testing/fakes.js +219 -0
- package/dist/testing/fakes.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/triage/heuristic.d.ts +38 -0
- package/dist/triage/heuristic.d.ts.map +1 -0
- package/dist/triage/heuristic.js +262 -0
- package/dist/triage/heuristic.js.map +1 -0
- package/dist/triage/index.d.ts +7 -0
- package/dist/triage/index.d.ts.map +1 -0
- package/dist/triage/index.js +5 -0
- package/dist/triage/index.js.map +1 -0
- package/dist/triage/llm.d.ts +11 -0
- package/dist/triage/llm.d.ts.map +1 -0
- package/dist/triage/llm.js +46 -0
- package/dist/triage/llm.js.map +1 -0
- package/dist/triage/schema.d.ts +240 -0
- package/dist/triage/schema.d.ts.map +1 -0
- package/dist/triage/schema.js +34 -0
- package/dist/triage/schema.js.map +1 -0
- package/dist/triage/tiered.d.ts +8 -0
- package/dist/triage/tiered.d.ts.map +1 -0
- package/dist/triage/tiered.js +40 -0
- package/dist/triage/tiered.js.map +1 -0
- package/dist/types.d.ts +211 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook/handler.d.ts +22 -0
- package/dist/webhook/handler.d.ts.map +1 -0
- package/dist/webhook/handler.js +57 -0
- package/dist/webhook/handler.js.map +1 -0
- package/dist/webhook/index.d.ts +5 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +3 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/registrar.d.ts +16 -0
- package/dist/webhook/registrar.d.ts.map +1 -0
- package/dist/webhook/registrar.js +50 -0
- package/dist/webhook/registrar.js.map +1 -0
- package/dist/writeback/github.d.ts +6 -0
- package/dist/writeback/github.d.ts.map +1 -0
- package/dist/writeback/github.js +48 -0
- package/dist/writeback/github.js.map +1 -0
- package/dist/writeback/index.d.ts +6 -0
- package/dist/writeback/index.d.ts.map +1 -0
- package/dist/writeback/index.js +4 -0
- package/dist/writeback/index.js.map +1 -0
- package/dist/writeback/linear.d.ts +41 -0
- package/dist/writeback/linear.d.ts.map +1 -0
- package/dist/writeback/linear.js +243 -0
- package/dist/writeback/linear.js.map +1 -0
- package/dist/writeback/shared.d.ts +7 -0
- package/dist/writeback/shared.d.ts.map +1 -0
- package/dist/writeback/shared.js +29 -0
- package/dist/writeback/shared.js.map +1 -0
- package/dist/writeback/slack.d.ts +16 -0
- package/dist/writeback/slack.d.ts.map +1 -0
- package/dist/writeback/slack.js +104 -0
- package/dist/writeback/slack.js.map +1 -0
- package/package.json +46 -9
- package/index.d.ts +0 -2
- package/index.js +0 -7
package/README.md
CHANGED
|
@@ -1,30 +1,248 @@
|
|
|
1
1
|
# @agent-relay/factory
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
10
|
+
```bash
|
|
11
|
+
npm ci
|
|
12
|
+
npm run build
|
|
13
|
+
|
|
14
|
+
node bin/factory.mjs factory <action> --config <cfg>
|
|
15
|
+
```
|
|
12
16
|
|
|
13
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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`.
|
package/bin/factory.mjs
ADDED
|
@@ -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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"}
|