@mestreyoda/fabrica 0.2.9 → 0.2.12

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/ARCHITECTURE.md CHANGED
@@ -63,17 +63,23 @@ Important invariants:
63
63
 
64
64
  ## Installation model
65
65
 
66
- The supported local installation is path-based:
66
+ Fabrica is distributed as a self-contained OpenClaw plugin package.
67
+
68
+ The supported operator path is:
67
69
 
68
70
  ```bash
69
- openclaw plugins install -l /path/to/fabrica
71
+ openclaw plugins install @mestreyoda/fabrica
70
72
  ```
71
73
 
72
- That means:
74
+ The installed extension must be loadable in isolation. Fabrica may depend on
75
+ OpenClaw only through the plugin host ABI and runtime objects passed by the
76
+ host. It must not require manual symlinks, local `npm install`, or host-global
77
+ module resolution to load.
73
78
 
74
- - the repository stays editable in place
75
- - the installed plugin points back to this directory
76
- - the loaded runtime should match the local build output in `dist/`
79
+ External credentials and routes such as GitHub auth, Telegram chat IDs, and
80
+ webhook secrets are operational configuration, not installation dependencies.
81
+ Fabrica's `doctor` and `setup` flows guide and validate that operational
82
+ configuration where applicable.
77
83
 
78
84
  ## Operational notes
79
85
 
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.2.12 - 2026-03-31
4
+
5
+ - Made the published plugin self-contained by replacing remaining runtime helper imports from `openclaw/plugin-sdk`.
6
+ - Added release gates for runtime-boundary and isolated installability verification, with fail-closed behavior, timeouts, and cleanup.
7
+ - Documented the real install contract and workspace-scoped operational setup flow in the package README and architecture notes.
8
+ - Aligned local deploy to the same contract by removing the extension `node_modules` symlink fallback.
9
+
10
+ ## 0.2.11 - 2026-03-31
11
+
12
+ - Unified reviewer completion around the canonical `Review result: APPROVE|REJECT` contract.
13
+ - Hardened dispatch identity and reduced heartbeat progression to repair-oriented behavior.
14
+ - Enforced canonical PR selection across reviewer/tester and queue-side flows.
15
+ - Preserved and hardened the Telegram three-plane model: DM bootstrap, project forum topics, and ops routing.
16
+ - Restored confidence lanes for release verification with explicit `test:unit`, `test:e2e`, and `test:hot-path`.
17
+ - Aligned plugin config governance and observability surfaces so runtime knobs are real and misleading signals no longer present themselves as authoritative.
package/README.md CHANGED
@@ -64,6 +64,9 @@ The heartbeat ticks every 60 seconds. On each tick, Fabrica alternates between a
64
64
  openclaw plugins install @mestreyoda/fabrica
65
65
  ```
66
66
 
67
+ That install should be enough for OpenClaw to load Fabrica immediately, without
68
+ manual remediation.
69
+
67
70
  ### Via GitHub clone
68
71
 
69
72
  ```bash
@@ -74,37 +77,53 @@ openclaw plugins install -l ~/fabrica
74
77
  After installation, verify the plugin loaded correctly:
75
78
 
76
79
  ```bash
77
- openclaw plugins list
78
- openclaw fabrica doctor
80
+ openclaw plugins inspect fabrica
79
81
  ```
80
82
 
83
+ ## Loadability vs operational readiness
84
+
85
+ - **Loadable:** the plugin installs and OpenClaw can load it immediately.
86
+ - **Operational:** Fabrica has the GitHub, Telegram, and optional webhook
87
+ configuration needed for your workflow.
88
+
89
+ `openclaw plugins inspect fabrica` is the loadability check after install.
90
+ `openclaw fabrica doctor` runs once the plugin is loaded and checks the
91
+ operational/workspace state, then tells you what is still missing.
92
+
81
93
  ## Quick start
82
94
 
83
- **1. Configure the plugin** in `~/.openclaw/openclaw.json`:
95
+ **1. Install Fabrica**:
84
96
 
85
- ```json
86
- {
87
- "plugins": {
88
- "entries": {
89
- "fabrica": {
90
- "config": {
91
- "github": {
92
- "org": "<YOUR_GITHUB_ORG_OR_USER>"
93
- }
94
- }
95
- }
96
- }
97
- }
98
- }
97
+ ```bash
98
+ openclaw plugins install @mestreyoda/fabrica
99
+ ```
100
+
101
+ The plugin should load immediately after install, without manual remediation.
102
+
103
+ **2. Confirm loadability**:
104
+
105
+ ```bash
106
+ openclaw plugins inspect fabrica
107
+ ```
108
+
109
+ **3. Configure operational state for a workspace**:
110
+
111
+ ```bash
112
+ openclaw fabrica doctor workspace --workspace /path/to/workspace
113
+ openclaw fabrica setup --workspace /path/to/workspace --new-agent fabrica
99
114
  ```
100
115
 
101
- **2. Restart the gateway**:
116
+ Use `openclaw fabrica setup --agent <id>` if you already have an agent. GitHub,
117
+ Telegram, and webhook behavior are separate operational concerns, not
118
+ installation dependencies.
119
+
120
+ **4. Restart the gateway**:
102
121
 
103
122
  ```bash
104
123
  systemctl --user restart openclaw-gateway.service
105
124
  ```
106
125
 
107
- **3. Trigger a new project programmatically**:
126
+ **5. Trigger a new project programmatically**:
108
127
 
109
128
  ```bash
110
129
  cd ~/fabrica # GitHub clone install only
@@ -116,13 +135,13 @@ npx tsx scripts/genesis-trigger.ts "A CLI tool that counts words in a file" \
116
135
 
117
136
  Remove `--dry-run` to execute for real.
118
137
 
119
- **4. Watch the pipeline run**:
138
+ **6. Watch the pipeline run**:
120
139
 
121
140
  ```bash
122
141
  tail -f ~/.openclaw/workspace/logs/genesis.log
123
142
  ```
124
143
 
125
- **5. Check metrics**:
144
+ **7. Check metrics**:
126
145
 
127
146
  ```bash
128
147
  openclaw fabrica metrics
@@ -132,7 +151,7 @@ openclaw fabrica metrics
132
151
 
133
152
  ### Minimal (gh CLI only)
134
153
 
135
- This configuration uses `gh` CLI for all GitHub operations (no GitHub App needed):
154
+ This mode uses authenticated `gh` CLI for all GitHub operations. Worker models, levels, and workflow routing live in the project workflow files, not in `openclaw.json`.
136
155
 
137
156
  ```json
138
157
  {
@@ -140,13 +159,49 @@ This configuration uses `gh` CLI for all GitHub operations (no GitHub App needed
140
159
  "entries": {
141
160
  "fabrica": {
142
161
  "config": {
143
- "github": {
144
- "org": "<YOUR_GITHUB_ORG_OR_USER>"
162
+ "work_heartbeat": {
163
+ "enabled": true,
164
+ "intervalSeconds": 60,
165
+ "maxPickupsPerTick": 4
145
166
  },
146
- "workers": {
147
- "developer": { "model": "gpt-4o", "level": "medior" },
148
- "reviewer": { "model": "gpt-4o", "level": "senior" },
149
- "tester": { "model": "gpt-4o", "level": "medior" }
167
+ "projectExecution": "parallel",
168
+ "notifications": {
169
+ "workerStart": true,
170
+ "workerComplete": true
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ Optional GitHub App/webhook settings also live under `plugins.entries.fabrica.config.providers.github`.
180
+
181
+ ### With GitHub App / webhook config
182
+
183
+ Use plugin config when you want explicit webhook behavior or provider auth profiles:
184
+
185
+ ```json
186
+ {
187
+ "plugins": {
188
+ "entries": {
189
+ "fabrica": {
190
+ "config": {
191
+ "providers": {
192
+ "github": {
193
+ "defaultAuthProfile": "app",
194
+ "webhookMode": "optional",
195
+ "webhookPath": "/plugins/fabrica/github/webhook",
196
+ "webhookSecretEnv": "FABRICA_GITHUB_WEBHOOK_SECRET",
197
+ "authProfiles": {
198
+ "app": {
199
+ "mode": "github-app",
200
+ "appIdEnv": "FABRICA_GITHUB_APP_ID",
201
+ "privateKeyPathEnv": "FABRICA_GITHUB_APP_KEY_PATH"
202
+ }
203
+ }
204
+ }
150
205
  }
151
206
  }
152
207
  }
@@ -157,7 +212,7 @@ This configuration uses `gh` CLI for all GitHub operations (no GitHub App needed
157
212
 
158
213
  ### With Telegram
159
214
 
160
- Telegram enables DM-based project bootstrap and per-project forum topic notifications.
215
+ Telegram enables DM-based project bootstrap, per-project forum topics, and a separate ops chat for heartbeat and cron notifications.
161
216
 
162
217
  ```json
163
218
  {
@@ -177,6 +232,7 @@ Telegram enables DM-based project bootstrap and per-project forum topic notifica
177
232
  "telegram": {
178
233
  "bootstrapDmEnabled": true,
179
234
  "projectsForumChatId": "<YOUR_PROJECTS_FORUM_CHAT_ID>",
235
+ "projectsForumAccountId": "<OPTIONAL_TELEGRAM_ACCOUNT_ID>",
180
236
  "opsChatId": "<YOUR_OPS_CHAT_ID>"
181
237
  }
182
238
  }
@@ -186,7 +242,7 @@ Telegram enables DM-based project bootstrap and per-project forum topic notifica
186
242
  }
187
243
  ```
188
244
 
189
- With Telegram enabled, send a project idea to the bot in a DM. Fabrica will ask clarifying questions, provision the GitHub repo, and create a dedicated forum topic for the project. All subsequent worker updates, review results, and the final merge notification appear in that topic.
245
+ With Telegram enabled, send a project idea to the bot in a DM. Fabrica will ask clarifying questions, provision the GitHub repo, create a dedicated forum topic for the project, and keep ops-only notifications on the separate `opsChatId` route.
190
246
 
191
247
  ## Programmatic genesis
192
248
 
@@ -227,6 +283,8 @@ Run tests:
227
283
 
228
284
  ```bash
229
285
  npm test
286
+ npm run test:all
287
+ npm run test:hot-path
230
288
  ```
231
289
 
232
290
  Build:
@@ -96,7 +96,7 @@ Brief summary of what needs to be implemented and why.
96
96
  ### How to Create
97
97
 
98
98
  1. Call `task_create` with:
99
- - `projectSlug`: same as your task message
99
+ - `projectSlug`: the value from the `Channel:` line in your task message (`task_create` still expects this field name)
100
100
  - `title`: clear, actionable title (e.g. "Implement SQLite session persistence")
101
101
  - `description`: use the format above — detailed enough for a developer to start immediately
102
102
 
@@ -105,13 +105,13 @@ Brief summary of what needs to be implemented and why.
105
105
 
106
106
  **Example:**
107
107
  ```
108
- task_create({ projectSlug: "my-app", title: "Implement SQLite session persistence", description: "From research #42\n\n## Overview\nReplace in-memory Map with SQLite...\n\n## Implementation Checklist\n\n### Phase 1: Schema & Migration (~1 day)\n- [ ] Create sessions table schema in db/schema.sql\n- [ ] Add migration logic in db/migrate.ts\n..." })
108
+ task_create({ projectSlug: "<project slug from the 'Channel:' line in the task message>", title: "Implement SQLite session persistence", description: "From research #42\n\n## Overview\nReplace in-memory Map with SQLite...\n\n## Implementation Checklist\n\n### Phase 1: Schema & Migration (~1 day)\n- [ ] Create sessions table schema in db/schema.sql\n- [ ] Add migration logic in db/migrate.ts\n..." })
109
109
  // → returns issue id: 43, url: "https://github.com/.../43"
110
110
 
111
111
  work_finish({
112
112
  role: "architect",
113
113
  result: "done",
114
- projectSlug: "my-app",
114
+ channelId: "my-app",
115
115
  summary: "Recommended SQLite approach. Created task #43.",
116
116
  createdTasks: [
117
117
  { id: 43, title: "Implement SQLite session persistence", url: "https://github.com/.../43" }
@@ -136,10 +136,10 @@ The task is created in Planning state — the operator reviews and moves it to t
136
136
 
137
137
  When you are done, **call `work_finish` yourself** — do not just announce in text.
138
138
 
139
- - **Done:** `work_finish({ role: "architect", result: "done", projectSlug: "<from task message>", summary: "<recommendation + created task numbers>", createdTasks: [{ id, title, url }] })`
140
- - **Blocked:** `work_finish({ role: "architect", result: "blocked", projectSlug: "<from task message>", summary: "<what you need>" })`
139
+ - **Done:** `work_finish({ role: "architect", result: "done", channelId: "<project slug from the 'Channel:' line in the task message>", summary: "<recommendation + created task numbers>", createdTasks: [{ id, title, url }] })`
140
+ - **Blocked:** `work_finish({ role: "architect", result: "blocked", channelId: "<project slug from the 'Channel:' line in the task message>", summary: "<what you need>" })`
141
141
 
142
- The `projectSlug` is included in your task message. Your session is persistent — you may be called back for refinements.
142
+ The project slug is included on the `Channel:` line in your task message. Your session is persistent — you may be called back for refinements.
143
143
 
144
144
  ## Tools You Should NOT Use
145
145
 
@@ -73,7 +73,7 @@ Do **not** treat the task envelope (`Repo:`, `Project:`, `Channel:`, branch hint
73
73
 
74
74
  - Read the PR diff carefully
75
75
  - Check the code against the review checklist
76
- - Submit your review using **one of the two methods below** (prefer `review_submit` if available)
76
+ - Output your decision in the format described in **Completing Your Task** below
77
77
 
78
78
  ## Conventions
79
79
 
@@ -85,9 +85,9 @@ Do **not** treat the task envelope (`Repo:`, `Project:`, `Channel:`, branch hint
85
85
 
86
86
  ## Filing Follow-Up Issues
87
87
 
88
- If you discover unrelated bugs or needed improvements, call `task_create`:
88
+ If you discover unrelated bugs or needed improvements, call `task_create` with the project slug from the `Channel:` line in your task message:
89
89
 
90
- `task_create({ projectSlug: "<from task message>", title: "Bug: ...", description: "..." })`
90
+ `task_create({ projectSlug: "<project slug from the 'Channel:' line in the task message>", title: "Bug: ...", description: "..." })`
91
91
 
92
92
  ## Completing Your Task
93
93
 
@@ -124,13 +124,14 @@ Use `task_comment` to post your findings in this format:
124
124
 
125
125
  ### 6. Call work_finish
126
126
 
127
- - **Pass:** `work_finish({ role: "tester", result: "pass", channelId: "<project slug from 'Project:' field in task message>", summary: "<brief summary>" })`
128
- - **Fail:** `work_finish({ role: "tester", result: "fail", channelId: "<project slug from 'Project:' field in task message>", summary: "<specific failures>" })`
129
- - **Refine:** `work_finish({ role: "tester", result: "refine", channelId: "<project slug from 'Project:' field in task message>", summary: "<what needs human input>" })`
130
- - **Blocked:** `work_finish({ role: "tester", result: "blocked", channelId: "<project slug from 'Project:' field in task message>", summary: "<what you need>" })`
127
+ - **Pass:** `work_finish({ role: "tester", result: "pass", channelId: "<project slug from the 'Channel:' line in the task message>", summary: "<brief summary>" })`
128
+ - **Fail:** `work_finish({ role: "tester", result: "fail", channelId: "<project slug from the 'Channel:' line in the task message>", summary: "<specific failures>" })`
129
+ - **Fail Infra:** `work_finish({ role: "tester", result: "fail_infra", channelId: "<project slug from the 'Channel:' line in the task message>", summary: "<toolchain or environment failure that prevented QA>" })`
130
+ - **Refine:** `work_finish({ role: "tester", result: "refine", channelId: "<project slug from the 'Channel:' line in the task message>", summary: "<what needs human input>" })`
131
+ - **Blocked:** `work_finish({ role: "tester", result: "blocked", channelId: "<project slug from the 'Channel:' line in the task message>", summary: "<what you need>" })`
131
132
 
132
133
  > **IMPORTANT:** The `channelId` parameter accepts the project slug (e.g., "gestao-notas").
133
- > Extract it from the "Project: <name>" line in your task message. Do NOT use the numeric
134
+ > Extract it from the "Channel: <slug>" line in your task message. Do NOT use the numeric
134
135
  > channel ID — use the project slug to avoid resolution errors when channels are shared.
135
136
 
136
137
  ## Conventions
@@ -140,9 +141,9 @@ Use `task_comment` to post your findings in this format:
140
141
 
141
142
  ## Filing Follow-Up Issues
142
143
 
143
- If you discover unrelated bugs or needed improvements during your work, call `task_create`:
144
+ If you discover unrelated bugs or needed improvements during your work, call `task_create` with the project slug from the `Channel:` line in your task message:
144
145
 
145
- `task_create({ projectSlug: "<from task message>", title: "Bug: ...", description: "..." })`
146
+ `task_create({ projectSlug: "<project slug from the 'Channel:' line in the task message>", title: "Bug: ...", description: "..." })`
146
147
 
147
148
  ## Tools You Should NOT Use
148
149