@mestreyoda/fabrica 0.2.7 → 0.2.11

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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.2.11 - 2026-03-31
4
+
5
+ - Unified reviewer completion around the canonical `Review result: APPROVE|REJECT` contract.
6
+ - Hardened dispatch identity and reduced heartbeat progression to repair-oriented behavior.
7
+ - Enforced canonical PR selection across reviewer/tester and queue-side flows.
8
+ - Preserved and hardened the Telegram three-plane model: DM bootstrap, project forum topics, and ops routing.
9
+ - Restored confidence lanes for release verification with explicit `test:unit`, `test:e2e`, and `test:hot-path`.
10
+ - 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
@@ -80,24 +80,22 @@ openclaw fabrica doctor
80
80
 
81
81
  ## Quick start
82
82
 
83
- **1. Configure the plugin** in `~/.openclaw/openclaw.json`:
83
+ **1. Enable the plugin** in `~/.openclaw/openclaw.json`:
84
84
 
85
85
  ```json
86
86
  {
87
87
  "plugins": {
88
88
  "entries": {
89
89
  "fabrica": {
90
- "config": {
91
- "github": {
92
- "org": "<YOUR_GITHUB_ORG_OR_USER>"
93
- }
94
- }
90
+ "enabled": true
95
91
  }
96
92
  }
97
93
  }
98
94
  }
99
95
  ```
100
96
 
97
+ For polling-first GitHub usage via authenticated `gh`, no extra plugin config is required. Fabrica's plugin config is only for control-plane settings such as heartbeat cadence, Telegram routing, notifications, and provider/webhook auth. Worker models, levels, and workflow routing live in the workspace files and can be generated or updated with `openclaw fabrica setup`.
98
+
101
99
  **2. Restart the gateway**:
102
100
 
103
101
  ```bash
@@ -132,7 +130,7 @@ openclaw fabrica metrics
132
130
 
133
131
  ### Minimal (gh CLI only)
134
132
 
135
- This configuration uses `gh` CLI for all GitHub operations (no GitHub App needed):
133
+ 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
134
 
137
135
  ```json
138
136
  {
@@ -140,13 +138,49 @@ This configuration uses `gh` CLI for all GitHub operations (no GitHub App needed
140
138
  "entries": {
141
139
  "fabrica": {
142
140
  "config": {
143
- "github": {
144
- "org": "<YOUR_GITHUB_ORG_OR_USER>"
141
+ "work_heartbeat": {
142
+ "enabled": true,
143
+ "intervalSeconds": 60,
144
+ "maxPickupsPerTick": 4
145
145
  },
146
- "workers": {
147
- "developer": { "model": "gpt-4o", "level": "medior" },
148
- "reviewer": { "model": "gpt-4o", "level": "senior" },
149
- "tester": { "model": "gpt-4o", "level": "medior" }
146
+ "projectExecution": "parallel",
147
+ "notifications": {
148
+ "workerStart": true,
149
+ "workerComplete": true
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ Optional GitHub App/webhook settings also live under `plugins.entries.fabrica.config.providers.github`.
159
+
160
+ ### With GitHub App / webhook config
161
+
162
+ Use plugin config when you want explicit webhook behavior or provider auth profiles:
163
+
164
+ ```json
165
+ {
166
+ "plugins": {
167
+ "entries": {
168
+ "fabrica": {
169
+ "config": {
170
+ "providers": {
171
+ "github": {
172
+ "defaultAuthProfile": "app",
173
+ "webhookMode": "optional",
174
+ "webhookPath": "/plugins/fabrica/github/webhook",
175
+ "webhookSecretEnv": "FABRICA_GITHUB_WEBHOOK_SECRET",
176
+ "authProfiles": {
177
+ "app": {
178
+ "mode": "github-app",
179
+ "appIdEnv": "FABRICA_GITHUB_APP_ID",
180
+ "privateKeyPathEnv": "FABRICA_GITHUB_APP_KEY_PATH"
181
+ }
182
+ }
183
+ }
150
184
  }
151
185
  }
152
186
  }
@@ -157,7 +191,7 @@ This configuration uses `gh` CLI for all GitHub operations (no GitHub App needed
157
191
 
158
192
  ### With Telegram
159
193
 
160
- Telegram enables DM-based project bootstrap and per-project forum topic notifications.
194
+ Telegram enables DM-based project bootstrap, per-project forum topics, and a separate ops chat for heartbeat and cron notifications.
161
195
 
162
196
  ```json
163
197
  {
@@ -177,6 +211,7 @@ Telegram enables DM-based project bootstrap and per-project forum topic notifica
177
211
  "telegram": {
178
212
  "bootstrapDmEnabled": true,
179
213
  "projectsForumChatId": "<YOUR_PROJECTS_FORUM_CHAT_ID>",
214
+ "projectsForumAccountId": "<OPTIONAL_TELEGRAM_ACCOUNT_ID>",
180
215
  "opsChatId": "<YOUR_OPS_CHAT_ID>"
181
216
  }
182
217
  }
@@ -186,7 +221,7 @@ Telegram enables DM-based project bootstrap and per-project forum topic notifica
186
221
  }
187
222
  ```
188
223
 
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.
224
+ 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
225
 
191
226
  ## Programmatic genesis
192
227
 
@@ -227,6 +262,8 @@ Run tests:
227
262
 
228
263
  ```bash
229
264
  npm test
265
+ npm run test:all
266
+ npm run test:hot-path
230
267
  ```
231
268
 
232
269
  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,55 +85,37 @@ 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
 
94
- When you are done, submit your review using **Method A** if the tools are available, or **Method B** otherwise.
94
+ After writing your review, you **MUST** output your final decision on a dedicated line in **exactly** one of these two formats:
95
95
 
96
- ### Method A — Fabrica tools (preferred)
96
+ ```
97
+ Review result: APPROVE
98
+ ```
99
+ ```
100
+ Review result: REJECT
101
+ ```
97
102
 
98
- 1. Call `review_submit` to write the review artifact to the PR:
99
- - **Approve:** `review_submit({ channelId: "<project slug>", issueId: <issue number>, result: "approve", body: "<what you checked>" })`
100
- - **Reject:** `review_submit({ channelId: "<project slug>", issueId: <issue number>, result: "reject", body: "<specific issues>" })`
101
- - Capture the returned `artifactId` and `artifactType`.
102
- 2. Then call `work_finish`:
103
- - **Approve:** `work_finish({ role: "reviewer", result: "approve", channelId: "<project slug>", summary: "<what you checked>", reviewArtifactId: <artifactId>, reviewArtifactType: "<artifactType>" })`
104
- - **Reject:** `work_finish({ role: "reviewer", result: "reject", channelId: "<project slug>", summary: "<specific issues>", reviewArtifactId: <artifactId>, reviewArtifactType: "<artifactType>" })`
105
- - **Blocked:** `work_finish({ role: "reviewer", result: "blocked", channelId: "<project slug>", summary: "<what you need>" })`
103
+ The Fabrica orchestrator reads your session output and advances the pipeline automatically based on this line. **You do not need to call any tool or run any CLI command.** Just output the line above and your work is done.
106
104
 
107
- > **IMPORTANT:** The `channelId` parameter accepts the project slug (e.g., "gestao-notas").
108
- > Extract it from the "Project: <name>" line in your task message. Do NOT use the numeric
109
- > channel ID — use the project slug to avoid resolution errors when channels are shared.
105
+ - Output `Review result: APPROVE` if all quality gates pass and the code is ready to proceed.
106
+ - Output `Review result: REJECT` if any blocking issue was found that the developer must fix.
110
107
 
111
- ### Method B GitHub CLI fallback (use only if `review_submit` / `work_finish` are unavailable)
108
+ > **IMPORTANT:** The decision line must appear in your response text, not inside a code block. It is case-insensitive but must follow the `Review result: APPROVE/REJECT` format exactly.
112
109
 
113
- Extract from your task message:
114
- - `OWNER/REPO` from the `Repo:` line
115
- - `PR_NUMBER` from the PR URL in the diff header or the `Branch:` line
116
- - `ISSUE_NUMBER` from the `Issue:` field
110
+ ### Optional: submit PR comment (best-effort)
117
111
 
118
- **Approve:**
119
- ```bash
120
- gh pr review PR_NUMBER --repo OWNER/REPO --approve -b "$(cat <<'EOF'
121
- <your full review body here>
122
- EOF
123
- )"
124
- ```
112
+ If `gh` is available and the PR author is not the same GitHub account you are logged in as, you may optionally leave a PR comment for visibility:
125
113
 
126
- **Reject (request changes):**
127
- ```bash
128
- gh pr review PR_NUMBER --repo OWNER/REPO --request-changes -b "$(cat <<'EOF'
129
- <specific issues and how to fix them>
130
- EOF
131
- )"
114
+ ```
115
+ gh pr comment <PR_NUMBER> --repo <OWNER/REPO> --body "<summary of your findings>"
132
116
  ```
133
117
 
134
- After submitting via `gh pr review`, the Fabrica heartbeat will detect the PR review state and advance the pipeline automatically. **Do NOT manually edit issue labels.**
135
-
136
- **Never call `task_comment` for review findings.** Your review must be posted on the PR itself.
118
+ This is informational only the orchestrator does not require it to advance the pipeline.
137
119
 
138
120
  ## Tools You Should NOT Use
139
121
 
@@ -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