@jiggai/recipes 0.2.25 → 0.3.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.
package/README.md CHANGED
@@ -11,7 +11,7 @@ If you like durable workflows: ClawRecipes is built around a **file-first team w
11
11
  ## Quickstart
12
12
  ### 1) Install
13
13
  #### Option A (preferred): install from npm
14
- Once published:
14
+ When published on npm:
15
15
 
16
16
  ```bash
17
17
  openclaw plugins install @jiggai/recipes
@@ -58,7 +58,8 @@ openclaw recipes dispatch \
58
58
  - `openclaw recipes list|show|status`
59
59
  - `openclaw recipes scaffold` (agent → `workspace-<agentId>` + writes workspace recipe `~/.openclaw/workspace/recipes/<agentId>.md` by default)
60
60
  - `openclaw recipes scaffold-team` (team → `workspace-<teamId>` + `roles/<role>/` + writes workspace recipe `~/.openclaw/workspace/recipes/<teamId>.md` by default)
61
- - `openclaw recipes install <idOrSlug> [--yes] [--global|--agent-id <id>|--team-id <id>]` (skills: global or scoped)
61
+ - `openclaw recipes install-skill <idOrSlug> [--yes] [--global|--agent-id <id>|--team-id <id>]` (skills: global or scoped)
62
+ - `openclaw recipes install <slug>` (marketplace recipe)
62
63
  - `openclaw recipes bind|unbind|bindings` (multi-agent routing)
63
64
  - `openclaw recipes dispatch ...` (request → inbox + ticket + assignment)
64
65
  - `openclaw recipes tickets|move-ticket|assign|take|handoff|complete` (file-first ticket workflow)
@@ -79,15 +80,26 @@ The plugin supports these config keys (with defaults):
79
80
  Config schema is defined in `openclaw.plugin.json`.
80
81
 
81
82
  ## Documentation
82
- Start here:
83
- - Installation: `docs/INSTALLATION.md`
84
- - Agents + skills: `docs/AGENTS_AND_SKILLS.md`
85
- - Tutorial (create a recipe): `docs/TUTORIAL_CREATE_RECIPE.md`
83
+ **For users:**
84
+ - [Installation](docs/INSTALLATION.md) — install the plugin
85
+ - [Agents & skills](docs/AGENTS_AND_SKILLS.md) — mental model, tool policies
86
+ - [Tutorial](docs/TUTORIAL_CREATE_RECIPE.md) — create your first recipe
87
+ - [Commands](docs/COMMANDS.md) — full command reference
88
+ - [Team workflow](docs/TEAM_WORKFLOW.md) — file-first workflow
89
+
90
+ **For contributors:**
91
+ - [Architecture](docs/ARCHITECTURE.md) — codebase structure
92
+ - [Contributing](CONTRIBUTING.md) — setup, tests, PR workflow
86
93
 
87
94
  ## Development
88
95
  ### Unit tests (vitest)
89
96
  Run:
90
97
  - `npm test`
98
+ - `npm run test:coverage` — coverage with CI thresholds (see `vitest.config.ts`)
99
+ - `npm run smell-check` — quality checks (ESLint, jscpd, pattern grep)
100
+
101
+ ### Pre-commit hooks
102
+ Husky runs on commit. Run `npm ci` first to install hooks.
91
103
 
92
104
  ### Scaffold smoke test (regression)
93
105
  A lightweight smoke check validates scaffold-team output contains the required testing workflow docs (ticket 0004).
@@ -98,6 +110,11 @@ Run:
98
110
  Notes:
99
111
  - Creates a temporary `workspace-smoke-<timestamp>-team` under `~/.openclaw/` and then deletes it.
100
112
  - Exits non-zero on mismatch.
113
+ - Requires OpenClaw and workspace config.
114
+
115
+ ### For contributors
116
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — codebase structure
117
+ - [CONTRIBUTING.md](CONTRIBUTING.md) — setup, commands, pre-commit, CI
101
118
 
102
119
  Reference:
103
120
  - Commands: `docs/COMMANDS.md`
@@ -139,8 +156,10 @@ Notes:
139
156
  - [Recipe format](https://github.com/JIGGAI/ClawRecipes/blob/main/docs/RECIPE_FORMAT.md): `docs/RECIPE_FORMAT.md`
140
157
  - [Team workflow](https://github.com/JIGGAI/ClawRecipes/blob/main/docs/TEAM_WORKFLOW.md): `docs/TEAM_WORKFLOW.md`
141
158
  - [Agents & Skills](https://github.com/JIGGAI/ClawRecipes/blob/main/docs/AGENTS_AND_SKILLS.md): `docs/AGENTS_AND_SKILLS.md`
159
+ - [Architecture](https://github.com/JIGGAI/ClawRecipes/blob/main/docs/ARCHITECTURE.md): `docs/ARCHITECTURE.md`
142
160
  - [Bundled](https://github.com/JIGGAI/ClawRecipes/blob/main/docs/BUNDLED_RECIPES.md): `docs/BUNDLED_RECIPES.md`
143
161
  - [Create Recipe Tutorial](https://github.com/JIGGAI/ClawRecipes/blob/main/docs/TUTORIAL_CREATE_RECIPE.md): `docs/TUTORIAL_CREATE_RECIPE.md`
162
+ - [Contributing](https://github.com/JIGGAI/ClawRecipes/blob/main/CONTRIBUTING.md): `CONTRIBUTING.md`
144
163
 
145
164
  ## Note
146
165
  ClawRecipes is meant to be *installed* and then used to build **agents + teams**.
@@ -149,3 +168,11 @@ Most users should focus on:
149
168
  - authoring recipes in their OpenClaw workspace (`<workspace>/recipes/*.md`)
150
169
  - scaffolding teams (`openclaw recipes scaffold-team ...`)
151
170
  - running the file-first workflow (dispatch → backlog → in-progress → testing → done)
171
+
172
+ ## Goals
173
+ - Release Clawmarket, https://github.com/JIGGAI/ClawMarket, public url https://clawkitchen.ai
174
+ - Release ClawKitchen, https://github.com/JIGGAI/ClawKitchen
175
+ - Merge at least 1 community pull request
176
+ - Daily shipping/pull requests of ClawRecipes features
177
+ - Improve recipes with more detailed agent files
178
+ - Add ability to install skills for agents through ClawKitchen
@@ -95,30 +95,26 @@ openclaw gateway restart
95
95
 
96
96
  > Tip: if you later re-run scaffold with `--apply-config`, the recipe’s tool policy may overwrite your manual edits. If you want a change to stick, encode it in the recipe.
97
97
 
98
- ## Installing skills (workspace-local)
99
- ClawRecipes favors **workspace-local** installs so each OpenClaw workspace is self-contained.
98
+ ## Installing skills
99
+ ClawRecipes favors **workspace-local** installs so each agent/team workspace is self-contained.
100
100
 
101
101
  ### Install a skill slug
102
102
  ```bash
103
- openclaw recipes install <skill-slug>
103
+ openclaw recipes install-skill <skill-slug>
104
104
  # or non-interactive:
105
- openclaw recipes install <skill-slug> --yes
105
+ openclaw recipes install-skill <skill-slug> --yes
106
106
  ```
107
107
 
108
- This runs ClawHub under the hood and installs into the **current OpenClaw workspace** skills dir:
109
- - `<workspace>/skills/<skill-slug>`
110
-
111
- Examples:
112
- - standalone agent workspace: `~/.openclaw/workspace-<agentId>/skills/<skill-slug>`
113
- - team workspace: `~/.openclaw/workspace-<teamId>/skills/<skill-slug>`
114
-
115
- > Note: in the new workspace policy, standalone agents live in `~/.openclaw/workspace-<agentId>` and teams live in `~/.openclaw/workspace-<teamId>`. Skill install targeting is still being refined during the experimental phase.
108
+ By default, installs **globally** into `~/.openclaw/skills/`. Use flags to target a specific workspace:
109
+ - `--global` — shared across all agents (default)
110
+ - `--agent-id <id>` — `~/.openclaw/workspace-<agentId>/skills/<skill-slug>`
111
+ - `--team-id <id>` — `~/.openclaw/workspace-<teamId>/skills/<skill-slug>`
116
112
 
117
113
  ### Install the skills required by a recipe
118
114
  If a recipe declares skills in `requiredSkills` or `optionalSkills`:
119
115
 
120
116
  ```bash
121
- openclaw recipes install <recipe-id>
117
+ openclaw recipes install-skill <recipe-id>
122
118
  ```
123
119
 
124
120
  That installs the recipe’s declared skills.
@@ -0,0 +1,109 @@
1
+ # Architecture
2
+
3
+ Overview of the ClawRecipes codebase for maintainers.
4
+
5
+ ## Overview
6
+
7
+ `index.ts` acts as thin CLI wiring: it registers commands via `api.registerCli` and delegates to handlers. Business logic lives in `src/handlers/` and `src/lib/`. The plugin exports `__internal` for test-only access to handlers and lib functions.
8
+
9
+ ## File structure
10
+
11
+ ```
12
+ ClawRecipes/
13
+ ├── index.ts # Entry point: CLI wiring, delegates to handlers
14
+ ├── src/
15
+ │ ├── handlers/ # One file per command group
16
+ │ │ ├── cron.ts # Cron reconciliation (used during scaffold)
17
+ │ │ ├── install.ts # install-skill, install (marketplace recipe)
18
+ │ │ ├── recipes.ts # list, show, status, bind, unbind, bindings
19
+ │ │ ├── scaffold.ts # scaffold (single agent)
20
+ │ │ ├── team.ts # scaffold-team, migrate-team, remove-team
21
+ │ │ └── tickets.ts # tickets, move-ticket, assign, take, handoff, dispatch, complete
22
+ │ └── lib/ # Shared logic
23
+ │ ├── recipes-config.ts # OpenClaw config load/write, bindings, agent snippets
24
+ │ ├── recipes.ts # Recipe listing, loading, workspace paths
25
+ │ ├── recipe-id.ts # Pick recipe id (auto-increment)
26
+ │ ├── scaffold-utils.ts # Shared scaffold logic
27
+ │ ├── ticket-workflow.ts # Ticket stages, move, assign, handoff
28
+ │ ├── ticket-finder.ts # Ticket lookup by id/number
29
+ │ ├── lanes.ts # Workflow stages (backlog, in-progress, testing, done)
30
+ │ ├── cleanup-workspaces.ts
31
+ │ ├── remove-team.ts # Team uninstall logic
32
+ │ └── ... # prompt, template, skill-install, fs-utils, etc.
33
+ ├── recipes/ # Bundled recipe markdown files
34
+ ├── scripts/ # Smell-check, scaffold smoke, etc.
35
+ └── tests/ # Vitest unit tests
36
+ ```
37
+
38
+ ## Handler-to-command map
39
+
40
+ | Handler | Commands |
41
+ |-----------|----------|
42
+ | recipes | list, show, status, bind, unbind, bindings |
43
+ | scaffold | scaffold |
44
+ | team | scaffold-team, migrate-team, remove-team |
45
+ | tickets | tickets, move-ticket, assign, take, handoff, dispatch, complete |
46
+ | install | install-skill (ClawHub skills), install (marketplace recipe), install-recipe (alias) |
47
+ | cron | Reconciled during scaffold (no standalone command) |
48
+
49
+ ## Shared scaffold flow
50
+
51
+ Both `scaffold` and `scaffold-team` use:
52
+
53
+ - `scaffoldAgentFromRecipe` — creates workspace, writes recipe-managed files, applies agent config
54
+ - `reconcileRecipeCronJobs` — when a recipe declares `cronJobs`, installs/updates cron jobs per `cronInstallation` config
55
+
56
+ Cron behavior applies to both commands when the recipe has `cronJobs` in frontmatter.
57
+
58
+ ## Data flow
59
+
60
+ ```mermaid
61
+ flowchart LR
62
+ subgraph cli [CLI]
63
+ Cmd[recipes list/show/status/...]
64
+ end
65
+ subgraph idx [index.ts]
66
+ IndexTS[registerCli]
67
+ end
68
+ subgraph handlers [Handlers]
69
+ HRecipes[recipes.ts]
70
+ HScaffold[scaffold.ts]
71
+ HTeam[team.ts]
72
+ HTickets[tickets.ts]
73
+ HInstall[install.ts]
74
+ end
75
+ subgraph lib [Lib]
76
+ LibRecipes[recipes-config]
77
+ LibScaffold[scaffold-utils]
78
+ LibTickets[ticket-workflow]
79
+ end
80
+ Cmd --> IndexTS
81
+ IndexTS --> HRecipes
82
+ IndexTS --> HScaffold
83
+ IndexTS --> HTeam
84
+ IndexTS --> HTickets
85
+ IndexTS --> HInstall
86
+ HRecipes --> LibRecipes
87
+ HScaffold --> LibScaffold
88
+ HTeam --> LibScaffold
89
+ HTickets --> LibTickets
90
+ ```
91
+
92
+ ## Key decisions
93
+
94
+ - **Tool policy preservation**: When a recipe omits `tools`, the scaffold preserves the existing agent's tool policy (rather than resetting it). See scaffold logic and tests.
95
+ - **`__internal` export**: Unit tests import handlers and lib helpers via `__internal`; these are not part of the public plugin API.
96
+
97
+ ## Quality automation
98
+
99
+ - **smell-check**: `npm run smell-check` runs:
100
+ - **ESLint**: `no-explicit-any`, `complexity`, `max-lines-per-function`, `max-params` (src/; index.ts exempt from complexity/lines)
101
+ - **jscpd**: Duplicate code detection (≥8 lines, ≥50 tokens)
102
+ - **Pattern grep**: `as any` in src/ (max 10), TODO/FIXME/XXX (max 20)
103
+ - **lint**: `npm run lint` / `npm run lint:fix`
104
+ - **tests**: `npm test` (vitest), `npm run test:coverage`
105
+ - **CI**: `.github/workflows/ci.yml` runs test:coverage, smell-check, npm audit
106
+
107
+ ---
108
+
109
+ If this doc is outdated, please submit a PR to update it.
@@ -19,8 +19,8 @@ openclaw recipes scaffold project-manager --agent-id pm --name "Project Manager"
19
19
  ```
20
20
 
21
21
  What it writes:
22
- - `agents/pm/SOUL.md`
23
- - `agents/pm/AGENTS.md`
22
+ - `workspace-pm/SOUL.md`
23
+ - `workspace-pm/AGENTS.md`
24
24
 
25
25
  Default tool policy (recipe-defined):
26
26
  - allows: `group:fs`, `group:web`, plus `cron` and `message`
@@ -272,18 +272,7 @@ openclaw recipes scaffold-team financial-planner-team --team-id financial-planne
272
272
  Roles:
273
273
  - lead, advisor, analyst, tax, insurance, ops
274
274
 
275
- ## 17) `stock-trader-team` (team)
276
- **Use when:** you want a trading workflow: research/signals/risk/journaling.
277
-
278
- Scaffold:
279
- ```bash
280
- openclaw recipes scaffold-team stock-trader-team --team-id stock-trader-team-team --apply-config
281
- ```
282
-
283
- Roles:
284
- - lead, researcher, signals, risk, journal, ops
285
-
286
- ## 18) `crypto-trader-team` (team)
275
+ ## 17) `crypto-trader-team` (team)
287
276
  **Use when:** you want a crypto trading workflow with onchain research.
288
277
 
289
278
  Scaffold:
package/docs/COMMANDS.md CHANGED
@@ -46,18 +46,13 @@ Options:
46
46
  - `--overwrite-recipe` (overwrite the generated workspace recipe file if it already exists)
47
47
  - `--overwrite` (overwrite recipe-managed files)
48
48
  - `--apply-config` (write/update `agents.list[]` in OpenClaw config)
49
+ - Cron: see Cron installation under scaffold-team.
49
50
 
50
51
  Also writes a workspace recipe file:
51
52
  - `~/.openclaw/workspace/recipes/<recipeId>.md`
52
53
 
53
54
  ## `scaffold-team <recipeId>`
54
55
 
55
- ### Cron installation config
56
- If a recipe declares `cronJobs`, scaffold will reconcile those jobs using the plugin config key:
57
- - `plugins.entries.recipes.config.cronInstallation`: `off | prompt | on`
58
- - `off`: never install/reconcile
59
- - `prompt` (default): prompt each run (default answer is **No**)
60
- - `on`: install/reconcile; new jobs follow `enabledByDefault`
61
56
  Scaffold a shared **team workspace** + multiple agents from a **team** recipe.
62
57
 
63
58
  ```bash
@@ -91,30 +86,55 @@ Standard folders:
91
86
  Also creates agent config entries under `agents.list[]` (when `--apply-config`), with agent ids:
92
87
  - `<teamId>-<role>`
93
88
 
94
- ## `install <idOrSlug> [--yes]`
89
+ ### Cron installation
90
+ If a recipe declares `cronJobs`, scaffold and scaffold-team reconcile those jobs using the plugin config key:
91
+ - `plugins.entries.recipes.config.cronInstallation`: `off | prompt | on`
92
+ - `off`: never install/reconcile
93
+ - `prompt` (default): prompt each run (default answer is **No**)
94
+ - `on`: install/reconcile; new jobs follow `enabledByDefault`
95
+
96
+ Applies to both `scaffold` and `scaffold-team` when the recipe declares `cronJobs`.
97
+
98
+ ## `install-skill <idOrSlug> [--yes]`
95
99
  Install skills from ClawHub (confirmation-gated).
96
100
 
97
- Default behavior: **global install** into `~/.openclaw/skills`.
101
+ Default: **global** into `~/.openclaw/skills`.
98
102
 
99
103
  ```bash
100
104
  # Global (shared across all agents)
101
- openclaw recipes install agentchat --yes
105
+ openclaw recipes install-skill agentchat --yes
102
106
 
103
107
  # Agent-scoped (into workspace-<agentId>/skills)
104
- openclaw recipes install agentchat --yes --agent-id dev
108
+ openclaw recipes install-skill agentchat --yes --agent-id dev
105
109
 
106
110
  # Team-scoped (into workspace-<teamId>/skills)
107
- openclaw recipes install agentchat --yes --team-id development-team-team
111
+ openclaw recipes install-skill agentchat --yes --team-id development-team-team
108
112
  ```
109
113
 
114
+ Options:
115
+ - `--yes` — skip confirmation
116
+ - `--global` — install into global skills (default when no scope flags)
117
+ - `--agent-id <id>` — install into agent workspace
118
+ - `--team-id <id>` — install into team workspace
119
+
110
120
  Behavior:
111
121
  - If `idOrSlug` matches a recipe id, installs that recipe’s `requiredSkills` + `optionalSkills`.
112
122
  - Otherwise treats it as a ClawHub skill slug.
113
- - Installs via:
114
- - `npx clawhub@latest --workdir <targetWorkspace> --dir skills install <slug>` (agent/team)
115
- - `npx clawhub@latest --workdir ~/.openclaw --dir skills install <slug>` (global)
116
- - Confirmation-gated unless `--yes`.
117
- - In non-interactive mode (no TTY), requires `--yes`.
123
+ - Installs via `npx clawhub@latest ...`. Confirmation-gated unless `--yes`. In non-interactive mode (no TTY), requires `--yes`.
124
+
125
+ ## `install <slug>`
126
+ Install a marketplace recipe into your workspace recipes dir (by slug).
127
+
128
+ ```bash
129
+ openclaw recipes install development-team
130
+ openclaw recipes install development-team --overwrite
131
+ ```
132
+
133
+ Options:
134
+ - `--registry-base <url>` — Marketplace API base URL (default: `https://clawkitchen.ai`)
135
+ - `--overwrite` — overwrite existing recipe file
136
+
137
+ Use `install-recipe` as an alias for this command.
118
138
 
119
139
  ## `bind`
120
140
  Add/update a multi-agent routing binding (writes `bindings[]` in `~/.openclaw/openclaw.json`).
@@ -166,10 +186,58 @@ Options:
166
186
  - `--mode move|copy`
167
187
  - `--overwrite` (merge into existing destination)
168
188
 
189
+ ## `remove-team`
190
+ Safe uninstall: remove a scaffolded team workspace, agents from config, and stamped cron jobs.
191
+
192
+ ```bash
193
+ openclaw recipes remove-team --team-id development-team-team --plan --json
194
+ openclaw recipes remove-team --team-id development-team-team --yes
195
+ ```
196
+
197
+ Options:
198
+ - `--team-id <teamId>` (required)
199
+ - `--plan` — print plan and exit without applying
200
+ - `--json` — output JSON
201
+ - `--yes` — skip confirmation (apply destructive changes)
202
+ - `--include-ambiguous` — also remove cron jobs that only loosely match the team (dangerous)
203
+
204
+ Notes:
205
+ - Confirmation-gated by default. Use `--yes` to apply without prompting.
206
+ - Cron cleanup removes only cron jobs stamped with `recipes.teamId=<teamId>`.
207
+ - Restart required after removal: `openclaw gateway restart`
208
+
169
209
  ## `dispatch`
170
- Convert a natural-language request into file-first execution artifacts.
210
+ Convert a natural-language request into file-first execution artifacts (inbox + backlog ticket + assignment stubs).
211
+
212
+ ```bash
213
+ openclaw recipes dispatch \
214
+ --team-id development-team-team \
215
+ --request "Add a customer-support team recipe" \
216
+ --owner lead
217
+ ```
218
+
219
+ Options:
220
+ - `--team-id <teamId>` (required)
221
+ - `--request <text>` (optional; prompts in TTY)
222
+ - `--owner dev|devops|lead|test` (default: `dev`)
223
+ - `--yes` (skip review prompt)
224
+
225
+ Creates:
226
+ - `workspace-<teamId>/inbox/<timestamp>-<slug>.md`
227
+ - `workspace-<teamId>/work/backlog/<NNNN>-<slug>.md`
228
+ - `workspace-<teamId>/work/assignments/<NNNN>-assigned-<owner>.md`
229
+
230
+ Ticket numbering:
231
+ - Scans `work/backlog`, `work/in-progress`, `work/testing`, `work/done` and uses max+1.
171
232
 
172
- ## `tickets`
233
+ Review-before-write:
234
+ - Prints a JSON plan and asks for confirmation unless `--yes`.
235
+
236
+ ## Ticket workflow commands
237
+
238
+ The following commands manage the file-first ticket flow (`work/backlog` → `in-progress` → `testing` → `done`).
239
+
240
+ ### `tickets`
173
241
  List tickets for a team across the standard workflow stages.
174
242
 
175
243
  ```bash
@@ -202,7 +270,7 @@ openclaw recipes cleanup-workspaces --prefix smoke- --prefix qa- --yes
202
270
  openclaw recipes cleanup-workspaces --json
203
271
  ```
204
272
 
205
- ## `move-ticket`
273
+ ### `move-ticket`
206
274
  Move a ticket file between workflow stages and update the ticket’s `Status:` field.
207
275
 
208
276
  ```bash
@@ -217,7 +285,7 @@ Stages:
217
285
  - `testing` → `Status: testing`
218
286
  - `done` → `Status: done` (optional `Completed:` timestamp)
219
287
 
220
- ## `assign`
288
+ ### `assign`
221
289
  Assign a ticket to an owner (updates `Owner:` and creates an assignment stub).
222
290
 
223
291
  ```bash
@@ -227,14 +295,14 @@ openclaw recipes assign --team-id <teamId> --ticket 0007 --owner lead
227
295
 
228
296
  Owners (current): `dev|devops|lead|test`.
229
297
 
230
- ## `take`
298
+ ### `take`
231
299
  Shortcut: assign + move to in-progress.
232
300
 
233
301
  ```bash
234
302
  openclaw recipes take --team-id <teamId> --ticket 0007 --owner dev
235
303
  ```
236
304
 
237
- ## `handoff`
305
+ ### `handoff`
238
306
  QA handoff in one step: move a ticket to `work/testing/`, set `Status: testing`, assign to a tester (default `test`), and write/update the assignment stub.
239
307
 
240
308
  ```bash
@@ -246,33 +314,9 @@ Notes:
246
314
  - Creates `work/testing/` if missing.
247
315
  - Idempotent: if the ticket is already in `work/testing/`, it won’t re-move it; it will ensure fields + assignment stub.
248
316
 
249
- ## `complete`
250
- Shortcut: move to done + ensure `Status: done` + add `Completed:` timestamp.
317
+ ### `complete`
318
+ Shortcut: move to done + ensure `Status: done` + add `Completed:` timestamp. No confirmation prompt.
251
319
 
252
320
  ```bash
253
321
  openclaw recipes complete --team-id <teamId> --ticket 0007
254
322
  ```
255
-
256
- ```bash
257
- openclaw recipes dispatch \
258
- --team-id development-team-team \
259
- --request "Add a customer-support team recipe" \
260
- --owner lead
261
- ```
262
-
263
- Options:
264
- - `--team-id <teamId>` (required)
265
- - `--request <text>` (optional; prompts in TTY)
266
- - `--owner dev|devops|lead|test` (default: `dev`)
267
- - `--yes` (skip review prompt)
268
-
269
- Creates (createOnly):
270
- - `workspace-<teamId>/inbox/<timestamp>-<slug>.md`
271
- - `workspace-<teamId>/work/backlog/<NNNN>-<slug>.md`
272
- - `workspace-<teamId>/work/assignments/<NNNN>-assigned-<owner>.md`
273
-
274
- Ticket numbering:
275
- - Scans `work/backlog`, `work/in-progress`, `work/testing`, `work/done` and uses max+1.
276
-
277
- Review-before-write:
278
- - Prints a JSON plan and asks for confirmation unless `--yes`.
@@ -7,11 +7,11 @@ This repo is an **OpenClaw plugin** (not a standalone CLI). OpenClaw loads it an
7
7
  ## Prerequisites
8
8
  - OpenClaw installed and working (`openclaw --version`)
9
9
  - Node.js available (OpenClaw uses Node to load plugins)
10
- - (For `recipes install`) you’ll need access to ClawHub (the command runs `npx clawhub@latest ...`).
10
+ - (For `recipes install-skill`) you’ll need access to ClawHub (the command runs `npx clawhub@latest ...`).
11
11
 
12
12
  ## Install
13
13
  ### Option A (preferred): install from npm
14
- Once published, you can install directly via npm:
14
+ When published on npm, you can install directly:
15
15
 
16
16
  ```bash
17
17
  openclaw plugins install @jiggai/recipes
@@ -34,7 +34,7 @@ openclaw gateway restart
34
34
  openclaw plugins list
35
35
  ```
36
36
 
37
- ### Option B: already cloned
37
+ ### Option C: already cloned
38
38
  ```bash
39
39
  openclaw plugins install --link ~/clawrecipes
40
40
  openclaw gateway restart
@@ -47,7 +47,7 @@ openclaw plugins list
47
47
  # look for id: recipes
48
48
  ```
49
49
 
50
- 4) Try a basic command:
50
+ Try a basic command:
51
51
  ```bash
52
52
  openclaw recipes list
53
53
  ```
@@ -77,7 +77,7 @@ openclaw gateway restart
77
77
  - Check: `openclaw plugins list`
78
78
  - Verify `openclaw.plugin.json` exists at repo root and has `id: "recipes"`.
79
79
 
80
- ### `recipes install` fails
80
+ ### `recipes install-skill` fails
81
81
  - Run `npx clawhub@latest --help` to confirm the CLI can run.
82
82
  - Ensure you are logged into ClawHub if required (`npx clawhub@latest login`).
83
83
  - Confirm the install scope you intended:
@@ -9,7 +9,7 @@ They can be:
9
9
  ## File locations
10
10
  Recipes are discovered from:
11
11
  - Built-in: `recipes/default/*.md` inside this plugin
12
- - Workspace-local: `<workspaceRoot>/recipes/*.md` (default)
12
+ - Workspace-local: `<openclawWorkspace>/recipes/*.md` (default). `<openclawWorkspace>` is your OpenClaw workspace root (typically `~/.openclaw/workspace` or the directory configured in OpenClaw)
13
13
 
14
14
  ## Frontmatter (common)
15
15
  ```yaml
@@ -32,7 +32,7 @@ These are **ClawHub skill slugs**.
32
32
 
33
33
  They’re used by:
34
34
  - `openclaw recipes status` (detect missing skills)
35
- - `openclaw recipes install <recipeId>` (install the listed skills)
35
+ - `openclaw recipes install-skill <recipeId>` (install the listed skills)
36
36
 
37
37
  ## Agent recipes
38
38
  Agent recipes use templates + files.
@@ -21,6 +21,9 @@ When you scaffold a team:
21
21
  ```
22
22
 
23
23
  ## The loop
24
+
25
+ CLI commands: `dispatch`, `tickets`, `move-ticket`, `assign`, `take`, `handoff`, `complete`. See [COMMANDS.md](COMMANDS.md) for full reference.
26
+
24
27
  1) **Intake**
25
28
  - New requests land in `inbox/`.
26
29
 
@@ -88,7 +91,7 @@ Then restart the gateway.
88
91
  Tip: use the `agents_list` tool to see what’s currently allowed.
89
92
 
90
93
  ## Dispatcher command
91
- The lead can convert a natural-language request into artifacts with:
94
+ The lead can convert a natural-language request into artifacts with [dispatch](COMMANDS.md#dispatch):
92
95
 
93
96
  ```bash
94
97
  openclaw recipes dispatch --team-id <teamId> --request "..." --owner dev
@@ -29,7 +29,7 @@ kind: team
29
29
  version: 0.1.0
30
30
  description: A tiny demo team
31
31
 
32
- # Optional: skill slugs to install with `openclaw recipes install my-first-team`
32
+ # Optional: skill slugs to install with `openclaw recipes install-skill my-first-team`
33
33
  requiredSkills: []
34
34
 
35
35
  team:
@@ -133,13 +133,12 @@ This will propose (or write, with `--yes`) three artifacts:
133
133
  Tickets move through lanes:
134
134
  - `work/backlog/` → `work/in-progress/` → `work/testing/` → `work/done/`
135
135
 
136
- - Move the ticket from `work/backlog/` `work/in-progress/`
137
- - Do the work
138
- - When ready for QA:
139
- - Move the ticket to `work/testing/`
140
- - Set `Owner: test` and add **Verification steps** to the ticket
141
- - After verification:
142
- - Move the ticket to `work/done/` and add a short completion report
136
+ You can move tickets manually (edit files) or use the CLI:
137
+ - `openclaw recipes take --team-id my-first-team-team --ticket 0001 --owner worker` — assign and move to in-progress
138
+ - `openclaw recipes handoff --team-id my-first-team-team --ticket 0001` — move to testing, assign to test
139
+ - `openclaw recipes complete --team-id my-first-team-team --ticket 0001` — move to done
140
+
141
+ See `docs/COMMANDS.md` for `move-ticket`, `assign`, and other ticket commands.
143
142
 
144
143
  ## Common mistakes
145
144
  - **Forgetting the `-team` suffix** on `--team-id` (required).
@@ -2,7 +2,7 @@
2
2
  "id": "recipes",
3
3
  "name": "Recipes",
4
4
  "description": "Markdown recipes that scaffold agents and teams (workspace-local).",
5
- "version": "0.2.25",
5
+ "version": "0.3.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jiggai/recipes",
3
- "version": "0.2.25",
3
+ "version": "0.3.0",
4
4
  "description": "ClawRecipes plugin for OpenClaw (markdown recipes -> scaffold agents/teams)",
5
5
  "main": "index.ts",
6
6
  "type": "commonjs",
@@ -268,6 +268,26 @@ templates:
268
268
  - Research briefs → outbox/research/
269
269
  - Metrics definitions/dashboards notes → shared-context/metrics/
270
270
 
271
+ files:
272
+ - path: SOUL.md
273
+ template: soul
274
+ mode: createOnly
275
+ - path: AGENTS.md
276
+ template: agents
277
+ mode: createOnly
278
+ - path: TOOLS.md
279
+ template: tools
280
+ mode: createOnly
281
+ - path: STATUS.md
282
+ template: status
283
+ mode: createOnly
284
+ - path: NOTES.md
285
+ template: notes
286
+ mode: createOnly
287
+ tools:
288
+ profile: "business"
289
+ allow: ["group:fs", "group:web"]
290
+ deny: ["exec"]
271
291
  ---
272
292
 
273
293
  # Business Team Recipe
@@ -7,17 +7,25 @@ kind: team
7
7
  cronJobs:
8
8
  - id: lead-triage-loop
9
9
  name: "Lead triage loop"
10
- schedule: "*/30 7-23 * * 1-5"
11
- timezone: "America/New_York"
12
- message: "Automated lead triage loop: triage inbox/tickets, assign work, and update notes/status.md. Anti-stuck: if lowest in-progress is HARD BLOCKED, advance the next unblocked ticket (or pull from backlog). If in-progress is stale (>12h no dated update), comment or move it back."
13
- enabledByDefault: true
10
+ schedule: "*/30 8-23 * * 1-5"
11
+ agentId: "{{teamId}}-lead"
12
+ channel: "last"
13
+ message: "Lead triage loop (automated). Goal: keep tickets moving. Steps: run ticket hygiene; inspect board; pull next actionable work into in-progress; assign owners; write updates to tickets (## Comments) and notes/status.md. If lowest-numbered in-progress is hard-blocked, advance the next unblocked ticket (or pull from backlog)."
14
+ enabledByDefault: false
14
15
  - id: execution-loop
15
16
  name: "Execution loop"
16
- schedule: "*/30 7-23 * * 1-5"
17
- timezone: "America/New_York"
18
- message: "Automated execution loop: make progress on in-progress tickets, keep changes small/safe, and update notes/status.md."
17
+ schedule: "*/30 8-23 * * 1-5"
18
+ agentId: "{{teamId}}-lead"
19
+ channel: "last"
20
+ message: "Execution loop (automated). Goal: make concrete progress on the lowest-numbered in-progress ticket. Run ticket hygiene; perform repo lint/build checks (avoid noisy tests unless defined); update ticket + notes/status.md. Send a message only when there is a material change."
21
+ enabledByDefault: false
22
+ - id: pr-watcher
23
+ name: "PR watcher"
24
+ schedule: "*/30 8-23 * * 1-5"
25
+ agentId: "{{teamId}}-lead"
26
+ channel: "last"
27
+ message: "PR watcher (automated). Goal: watch ticket-linked PR URLs; summarize failing checks; if merged, move tickets to done with a short completion report. Requires GitHub access/auth on the controller."
19
28
  enabledByDefault: false
20
- # pr-watcher omitted (enable only when a real PR integration exists)
21
29
  requiredSkills: []
22
30
  team:
23
31
  teamId: development-team
@@ -64,6 +72,7 @@ templates:
64
72
 
65
73
  Team: {{teamId}}
66
74
  Shared workspace: {{teamDir}}
75
+ Role: lead
67
76
 
68
77
  ## Guardrails (read → act → write)
69
78
 
@@ -72,24 +81,26 @@ templates:
72
81
  - `notes/plan.md`
73
82
  - `notes/status.md`
74
83
  - `shared-context/priorities.md`
75
- - the relevant ticket(s)
84
+ - the current ticket
85
+
86
+ During work (every loop):
87
+ 1) Run hygiene:
88
+ - `./scripts/ticket-hygiene.sh`
76
89
 
77
90
  After you act:
78
91
  1) Write back:
79
- - Update tickets with decisions/assignments.
80
- - Keep `notes/status.md` current (3–5 bullets per active ticket).
92
+ - Update the ticket with decisions/assignments and a dated entry under `## Comments`.
93
+ - Keep `notes/status.md` current (add 3–5 bullets per active ticket).
94
+ - Append detailed logs/output to `shared-context/agent-outputs/` (append-only).
81
95
 
82
96
  ## Curator model
83
97
 
84
- You are the curator of:
98
+ You curate:
85
99
  - `notes/plan.md`
86
100
  - `shared-context/priorities.md`
87
101
 
88
- Everyone else should append to:
102
+ Other agents should not edit curated files; they append to:
89
103
  - `shared-context/agent-outputs/` (append-only)
90
- - `shared-context/feedback/`
91
-
92
- Your job is to periodically distill those inputs into the curated files.
93
104
 
94
105
  ## File-first workflow (tickets)
95
106
 
@@ -101,29 +112,9 @@ templates:
101
112
  - `work/in-progress/` — tickets currently being executed
102
113
  - `work/testing/` — tickets awaiting QA verification
103
114
  - `work/done/` — completed tickets + completion notes
104
- - `notes/plan.md` — current plan / priorities (curated)
105
- - `notes/status.md` current status snapshot
106
- - `shared-context/` shared context + append-only outputs
107
-
108
- ### Ticket numbering (critical)
109
- - Backlog tickets MUST be named `0001-...md`, `0002-...md`, etc.
110
- - The developer pulls the lowest-numbered ticket assigned to them.
111
-
112
- ### Ticket format
113
- See `TICKETS.md` in the team root. Every ticket should include:
114
- - Context
115
- - Requirements
116
- - Acceptance criteria
117
- - Owner (dev/devops)
118
- - Status
119
-
120
- ### Your responsibilities
121
- - For every new request in `inbox/`, create a normalized ticket in `work/backlog/`.
122
- - Curate `notes/plan.md` and `shared-context/priorities.md`.
123
- - Keep `notes/status.md` updated.
124
- - When work is ready for QA, move the ticket to `work/testing/` and assign it to the tester.
125
- - Only after QA verification, move the ticket to `work/done/` (or use `openclaw recipes complete`).
126
- - When a completion appears in `work/done/`, write a short summary into `outbox/`.
115
+
116
+ Handoff to QA (keeps assignment stubs consistent):
117
+ - `openclaw recipes handoff --team-id {{teamId}} --ticket <NNNN> --yes`
127
118
 
128
119
  dev.soul: |
129
120
  # SOUL.md
@@ -134,26 +125,32 @@ templates:
134
125
  dev.agents: |
135
126
  # AGENTS.md
136
127
 
137
- Team: {teamId}
138
- Shared workspace: {teamDir}
128
+ Team: {{teamId}}
129
+ Shared workspace: {{teamDir}}
139
130
  Role: dev
140
131
 
141
132
  ## Guardrails (read → act → write)
142
- Before you act:
143
- 1) Read:
144
- - `notes/plan.md`
145
- - `notes/status.md`
146
- - relevant ticket(s) in `work/in-progress/`
147
- - any relevant shared context under `shared-context/`
148
133
 
149
- After you act:
150
- 1) Write back:
151
- - Put outputs in the agreed folder (usually `outbox/` or a ticket file).
152
- - Update the ticket with what you did and where the artifact is.
134
+ Before you change anything, read:
135
+ - `notes/plan.md`
136
+ - `notes/status.md`
137
+ - `shared-context/priorities.md`
138
+ - the current ticket
153
139
 
154
- ## Workflow
155
- - Prefer a pull model: wait for a clear task from the lead, or propose a scoped task.
156
- - Keep work small and reversible.
140
+ Quick hygiene around stage moves:
141
+ - `./scripts/ticket-hygiene-dev.sh`
142
+
143
+ After you act, write back:
144
+ - Update the ticket with what you did + verification steps.
145
+ - Check/respond in the ticket’s `## Comments` section (especially if you were pinged).
146
+ - Add 3–5 bullets to `notes/status.md`.
147
+ - Append detailed logs/output to `shared-context/agent-outputs/` (append-only).
148
+
149
+ ## Pull system
150
+
151
+ - Continue any ticket already assigned to you in `work/in-progress/`.
152
+ - Otherwise pull the lowest-numbered assigned ticket from `work/backlog/` and move it to `work/in-progress/`.
153
+ - Keep changes small and reversible.
157
154
  devops.soul: |
158
155
  # SOUL.md
159
156
 
@@ -241,26 +238,30 @@ templates:
241
238
  test.agents: |
242
239
  # AGENTS.md
243
240
 
244
- Team: {teamId}
245
- Shared workspace: {teamDir}
241
+ Team: {{teamId}}
242
+ Shared workspace: {{teamDir}}
246
243
  Role: test
247
244
 
248
245
  ## Guardrails (read → act → write)
249
- Before you act:
250
- 1) Read:
251
- - `notes/plan.md`
252
- - `notes/status.md`
253
- - relevant ticket(s) in `work/in-progress/`
254
- - any relevant shared context under `shared-context/`
255
246
 
256
- After you act:
257
- 1) Write back:
258
- - Put outputs in the agreed folder (usually `outbox/` or a ticket file).
259
- - Update the ticket with what you did and where the artifact is.
247
+ Before you verify anything, read:
248
+ - `notes/plan.md`
249
+ - `notes/status.md`
250
+ - `shared-context/priorities.md`
251
+ - the ticket in `work/testing/`
260
252
 
261
- ## Workflow
262
- - Prefer a pull model: wait for a clear task from the lead, or propose a scoped task.
263
- - Keep work small and reversible.
253
+ After you verify, write back:
254
+ - Record QA verification (preferred): create `work/testing/<ticket>.testing-verified.md`.
255
+ - Alternative: add a clear `## QA verification` section in the ticket.
256
+ - If failing: write a concise bug note, then move the ticket back to `work/in-progress/` and assign to the right owner.
257
+ - Check/respond in the ticket’s `## Comments` section.
258
+ - Add 3–5 bullets to `notes/status.md`.
259
+ - Append detailed logs/output to `shared-context/agent-outputs/` (append-only).
260
+
261
+ ## Testing lane workflow
262
+
263
+ - Work from `work/testing/`.
264
+ - Follow the ticket’s “How to test” steps and validate acceptance criteria.
264
265
  test.tools: |
265
266
  # TOOLS.md
266
267
 
@@ -9,6 +9,36 @@ import { toolsInvoke, type ToolTextResult } from "../toolsInvoke";
9
9
 
10
10
  export type CronInstallMode = "off" | "prompt" | "on";
11
11
 
12
+ function interpolateTemplate(input: string | undefined, vars: Record<string, string>): string | undefined {
13
+ if (input == null) return undefined;
14
+ let out = String(input);
15
+ for (const [k, v] of Object.entries(vars)) {
16
+ out = out.replaceAll(`{{${k}}}`, v);
17
+ }
18
+ return out;
19
+ }
20
+
21
+ function applyCronJobVars(
22
+ scope: CronReconcileScope,
23
+ j: { id: string; name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; enabledByDefault?: boolean },
24
+ ): typeof j {
25
+ const vars: Record<string, string> = {
26
+ recipeId: scope.recipeId,
27
+ ...(scope.kind === "team" ? { teamId: scope.teamId } : { agentId: scope.agentId }),
28
+ };
29
+ return {
30
+ ...j,
31
+ name: interpolateTemplate(j.name, vars),
32
+ schedule: interpolateTemplate(j.schedule, vars),
33
+ timezone: interpolateTemplate(j.timezone, vars),
34
+ channel: interpolateTemplate(j.channel, vars),
35
+ to: interpolateTemplate(j.to, vars),
36
+ agentId: interpolateTemplate(j.agentId, vars),
37
+ description: interpolateTemplate(j.description, vars),
38
+ message: interpolateTemplate(j.message, vars),
39
+ };
40
+ }
41
+
12
42
  type OpenClawCronJob = {
13
43
  id: string;
14
44
  name?: string;
@@ -145,15 +175,29 @@ async function resolveCronUserOptIn(
145
175
  mode: CronInstallMode,
146
176
  recipeId: string,
147
177
  desiredCount: number
148
- ): Promise<{ userOptIn: boolean } | { return: { ok: true; changed: false; note: string; desiredCount: number } }> {
178
+ ): Promise<
179
+ | { userOptIn: boolean; enableInstalled: boolean }
180
+ | { return: { ok: true; changed: false; note: string; desiredCount: number } }
181
+ > {
149
182
  if (mode === "off") return { return: { ok: true, changed: false, note: "cron-installation-off" as const, desiredCount } };
150
- if (mode === "on") return { userOptIn: true };
183
+ if (mode === "on") return { userOptIn: true, enableInstalled: true };
184
+
185
+ // mode === "prompt"
186
+ // In non-interactive runs we still reconcile (create/update) cron jobs, but always DISABLED.
187
+ // This keeps scaffold idempotent and avoids silently skipping cron job stamping.
188
+ if (!process.stdin.isTTY) {
189
+ console.error(
190
+ `Non-interactive mode: cronInstallation=prompt; reconciling ${desiredCount} cron job(s) as disabled (no prompt).`
191
+ );
192
+ return { userOptIn: false, enableInstalled: false };
193
+ }
151
194
 
152
195
  const header = `Recipe ${recipeId} defines ${desiredCount} cron job(s).\nThese run automatically on a schedule. Install them?`;
153
196
  const userOptIn = await promptYesNo(header);
154
197
  if (!userOptIn) return { return: { ok: true, changed: false, note: "cron-installation-declined" as const, desiredCount } };
155
- if (!process.stdin.isTTY) console.error("Non-interactive mode: defaulting cron install to disabled.");
156
- return { userOptIn };
198
+
199
+ const enableInstalled = await promptYesNo("Enable the installed cron jobs now? (You can always enable later)");
200
+ return { userOptIn, enableInstalled };
157
201
  }
158
202
 
159
203
  async function createNewCronJob(opts: {
@@ -183,12 +227,13 @@ async function updateExistingCronJob(opts: {
183
227
  prevSpecHash: string | undefined;
184
228
  specHash: string;
185
229
  userOptIn: boolean;
230
+ enableInstalled: boolean;
186
231
  key: string;
187
232
  now: number;
188
233
  state: Awaited<ReturnType<typeof loadCronMappingState>>;
189
234
  results: CronReconcileResult[];
190
235
  }) {
191
- const { api, j, name, existing, prevSpecHash, specHash, userOptIn, key, now, state, results } = opts;
236
+ const { api, j, name, existing, prevSpecHash, specHash, userOptIn, enableInstalled, key, now, state, results } = opts;
192
237
  if (prevSpecHash !== specHash) {
193
238
  await cronUpdate(api, existing.id, buildCronJobPatch(j, name));
194
239
  results.push({ action: "updated", key, installedCronId: existing.id });
@@ -199,6 +244,11 @@ async function updateExistingCronJob(opts: {
199
244
  await cronUpdate(api, existing.id, { enabled: false });
200
245
  results.push({ action: "disabled", key, installedCronId: existing.id });
201
246
  }
247
+
248
+ if (userOptIn && enableInstalled && !existing.enabled) {
249
+ await cronUpdate(api, existing.id, { enabled: true });
250
+ results.push({ action: "updated", key, installedCronId: existing.id });
251
+ }
202
252
  state.entries[key] = { installedCronId: existing.id, specHash, updatedAtMs: now, orphaned: false };
203
253
  }
204
254
 
@@ -212,31 +262,47 @@ async function reconcileOneCronJob(
212
262
  results: CronReconcileResult[];
213
263
  },
214
264
  j: (ReturnType<typeof normalizeCronJobs>)[number],
215
- userOptIn: boolean
265
+ userOptIn: boolean,
266
+ enableInstalled: boolean
216
267
  ) {
217
268
  const { api, scope, state, byId, now, results } = ctx;
218
- const key = cronKey(scope, j.id);
219
- const name = j.name ?? `${scope.kind === "team" ? scope.teamId : scope.agentId} • ${scope.recipeId} • ${j.id}`;
269
+ const jj = applyCronJobVars(scope, j);
270
+ const key = cronKey(scope, jj.id);
271
+ const name =
272
+ jj.name ?? `${scope.kind === "team" ? scope.teamId : scope.agentId} • ${scope.recipeId} • ${jj.id}`;
220
273
  const specHash = hashSpec({
221
- schedule: j.schedule,
222
- message: j.message,
223
- timezone: j.timezone ?? "",
224
- channel: j.channel ?? "last",
225
- to: j.to ?? "",
226
- agentId: j.agentId ?? "",
274
+ schedule: jj.schedule,
275
+ message: jj.message,
276
+ timezone: jj.timezone ?? "",
277
+ channel: jj.channel ?? "last",
278
+ to: jj.to ?? "",
279
+ agentId: jj.agentId ?? "",
227
280
  name,
228
- description: j.description ?? "",
281
+ description: jj.description ?? "",
229
282
  });
230
283
 
231
284
  const prev = state.entries[key];
232
285
  const existing = prev?.installedCronId ? byId.get(prev.installedCronId) : undefined;
233
- const wantEnabled = userOptIn ? Boolean(j.enabledByDefault) : false;
286
+ const wantEnabled = userOptIn ? (enableInstalled ? true : Boolean(jj.enabledByDefault)) : false;
234
287
 
235
288
  if (!existing) {
236
- await createNewCronJob({ api, scope, j, wantEnabled, key, specHash, now, state, results });
289
+ await createNewCronJob({ api, scope, j: jj, wantEnabled, key, specHash, now, state, results });
237
290
  return;
238
291
  }
239
- await updateExistingCronJob({ api, j, name, existing, prevSpecHash: prev?.specHash, specHash, userOptIn, key, now, state, results });
292
+ await updateExistingCronJob({
293
+ api,
294
+ j: jj,
295
+ name,
296
+ existing,
297
+ prevSpecHash: prev?.specHash,
298
+ specHash,
299
+ userOptIn,
300
+ enableInstalled,
301
+ key,
302
+ now,
303
+ state,
304
+ results,
305
+ });
240
306
  }
241
307
 
242
308
  async function reconcileDesiredCronJobs(opts: {
@@ -244,6 +310,7 @@ async function reconcileDesiredCronJobs(opts: {
244
310
  scope: CronReconcileScope;
245
311
  desired: ReturnType<typeof normalizeCronJobs>;
246
312
  userOptIn: boolean;
313
+ enableInstalled: boolean;
247
314
  state: Awaited<ReturnType<typeof loadCronMappingState>>;
248
315
  byId: Map<string, OpenClawCronJob>;
249
316
  now: number;
@@ -258,7 +325,7 @@ async function reconcileDesiredCronJobs(opts: {
258
325
  results: opts.results,
259
326
  };
260
327
  for (const j of opts.desired) {
261
- await reconcileOneCronJob(ctx, j, opts.userOptIn);
328
+ await reconcileOneCronJob(ctx, j, opts.userOptIn, opts.enableInstalled);
262
329
  }
263
330
  }
264
331
 
@@ -290,7 +357,16 @@ export async function reconcileRecipeCronJobs(opts: {
290
357
  const desiredIds = new Set(desired.map((j) => j.id));
291
358
  const results: CronReconcileResult[] = [];
292
359
 
293
- await reconcileDesiredCronJobs({ ...opts, desired, userOptIn: optIn.userOptIn, state, byId, now, results });
360
+ await reconcileDesiredCronJobs({
361
+ ...opts,
362
+ desired,
363
+ userOptIn: optIn.userOptIn,
364
+ enableInstalled: optIn.enableInstalled,
365
+ state,
366
+ byId,
367
+ now,
368
+ results,
369
+ });
294
370
  await disableOrphanedCronJobs({
295
371
  api: opts.api,
296
372
  state,
@@ -1,31 +0,0 @@
1
- # Cleanup TODO
2
-
3
- ## Infrastructure
4
-
5
- - [x] Add @vitest/coverage-v8, test:coverage script
6
- - [x] Create TEST_COVERAGE_PROGRESS.md, CODE_SMELLS_TRACKER.md
7
- - [x] Run baseline coverage, populate progress doc
8
- - [x] Update TEST_COVERAGE_PROGRESS.md after each coverage batch
9
-
10
- ## Code Smells
11
-
12
- - [x] Extract fileExists to src/lib/fs-utils
13
- - [x] Extract stableStringify to src/lib/stable-stringify
14
- - [x] Unify parseFrontmatter/normalizeCronJobs (use recipe-frontmatter)
15
- - [x] Replace magic numbers in toolsInvoke
16
-
17
- ## Test Coverage
18
-
19
- - [x] lanes.ts
20
- - [x] ticket-finder.ts
21
- - [x] cleanup-workspaces (expand)
22
- - [x] remove-team (expand)
23
- - [x] marketplaceFetch (with fetch mock)
24
- - [x] toolsInvoke (with fetch mock)
25
- - [x] Extracted config, template, cron-utils, agent-config
26
- - [x] index.ts handlers (via extraction or integration)
27
-
28
- ## Final
29
-
30
- - [x] Set coverage thresholds (phased: 25% baseline; target 95%)
31
- - [x] Verify npm run test:coverage passes
@@ -1,42 +0,0 @@
1
- # Code Smells Tracker
2
-
3
- | # | Smell | Status | Priority | Notes |
4
- |---|-------|--------|----------|-------|
5
- | 1 | fileExists duplicated (index, ticket-finder, ticket-workflow, lanes, remove-team) | done | high | Extracted to src/lib/fs-utils |
6
- | 2 | stableStringify duplicated (index, bindings) | done | high | Extracted to src/lib/stable-stringify |
7
- | 3 | parseFrontmatter/normalizeCronJobs duplicated (index, recipe-frontmatter) | done | high | Unified; recipe-frontmatter extended with message/task/prompt |
8
- | 4 | Magic numbers in toolsInvoke (30_000, 150*attempt) | done | medium | Extracted as TOOLS_INVOKE_TIMEOUT_MS, RETRY_DELAY_BASE_MS |
9
- | 5 | index.ts god file (~2500 lines) | done | low | Extracted handlers to src/handlers/; index now ~670 lines (Phase 3) |
10
- | 6 | Duplicate ticket-finding logic (ticket-workflow vs ticket-finder) | done | medium | ticket-workflow now delegates to ticket-finder |
11
- | 7 | Duplicated ticket regex patterns | done | low | TICKET_FILENAME_REGEX in ticket-finder; tickets uses it |
12
- | 8 | Hardcoded "recipes" in scaffold | done | low | scaffold uses cfg.workspaceRecipesDir |
13
- | 9 | Lane path duplication in tickets | done | low | tickets uses ticketStageDir from lanes |
14
- | 10 | Magic numbers (1000, 18789, 0000) | done | low | MAX_RECIPE_ID_AUTO_INCREMENT, GATEWAY_DEFAULT_PORT, DEFAULT_TICKET_NUMBER in constants |
15
- | 11 | Overlapping TicketLane/TicketStage types | done | low | TicketLane = Exclude<TicketStage, "assignments"> in lanes; ticket-finder re-exports |
16
- | 12 | Config load/write duplicated | done | medium | loadOpenClawConfig, writeOpenClawConfig in recipes-config; all callers use them |
17
- | 13 | nextTicketNumber inline in dispatch | done | low | Extracted computeNextTicketNumber to ticket-finder |
18
- | 14 | bindings.ts vs recipes-config duplication | done | low | bindings re-exports from recipes-config; bindings.test uses recipes-config |
19
- | 15 | Silent catch in handleDispatch | done | low | Documented: non-critical enqueueSystemEvent, nudgeQueued reflects skip |
20
- | 16 | Duplicate pickRecipeId (scaffold vs team) | done | medium | Extracted to src/lib/recipe-id |
21
- | 17 | Hardcoded "recipes" in team | done | low | team uses cfg.workspaceRecipesDir |
22
- | 18 | as any in recipe-frontmatter normalizeCronJobs | done | low | CronJobInput type |
23
- | 19 | as any in cron.ts (scope, created response) | done | low | Proper types; CronAddResponse, CronReconcileResult |
24
- | 20 | getCfg trivial wrapper in recipes | done | very low | Inlined getRecipesConfig |
25
- | 21 | results: any[] in cron | done | low | CronReconcileResult union type |
26
-
27
- ## Automated smell detection
28
-
29
- Run `npm run smell-check` to:
30
- - **ESLint**: `no-explicit-any`, `complexity`, `max-lines-per-function`, `max-params` (src/; index.ts exempt from complexity/lines)
31
- - **jscpd**: Duplicate code detection (≥8 lines, ≥50 tokens)
32
- - **Pattern grep**: `as any` in src/ (max 10), TODO/FIXME/XXX (max 20)
33
-
34
- Scripts: `npm run lint`, `npm run lint:fix`, `npm run jscpd`, `npm run smell-check`
35
-
36
- ## Resolution log
37
-
38
- - **Smell 5**: Index handler extraction (Phase 2–3) moved ~1200 lines to src/handlers/{cron,recipes,scaffold,team,tickets,install}.ts. index.ts now thin CLI wiring only.
39
- - **Smells 6–11**: Additional cleanup: consolidated ticket-finding, extracted regex/constants, used config for recipes dir, ticketStageDir, magic-number constants, unified lane types.
40
- - **Smells 12–15**: Config helpers (load/write), computeNextTicketNumber, bindings consolidation, silent-catch documentation.
41
- - **Smells 16–21**: pickRecipeId extracted to recipe-id; team uses workspaceRecipesDir; CronJobInput type; cron types (CronAddResponse, CronReconcileResult); getCfg inlined; config double-lookup comment.
42
- - **Post-cleanup complete** (Feb 2026): All 21 smells resolved; smell-check passes with 0 warnings. Additional quality improvements: pre-commit hooks (husky + lint-staged), CI coverage enforcement, tsconfig.json, JSDoc for public APIs.
@@ -1,23 +0,0 @@
1
- # Remaining Code Smells — TODO
2
-
3
- ## Type safety (`as any` / `any`) — DONE
4
-
5
- - [x] **cron.ts** — Replaced with OpenClawCronJob, CronJobPatch, OpenClawPluginApi
6
- - [x] **team.ts** — AgentScaffoldResult, MigrateStep types
7
- - [x] **recipes-config.ts** — OpenClawConfigMutable
8
- - [x] **remove-team.ts** — Record<string, unknown>
9
- - [ ] **index.ts** — ~16 options: any remain (partial: install, scaffold use typed opts)
10
- - [x] **Misc** — recipes, tickets, agent-config, config, cron-utils, marketplaceFetch, toolsInvoke, lanes, cleanup-workspaces, recipe-frontmatter
11
-
12
- ## Duplicate code (jscpd) — DONE
13
-
14
- - [x] **ticket-workflow.ts** — patchTicketFields extracted
15
- - [x] **tickets.ts** — dryRunTicketMove extracted
16
- - [x] **index.ts** — runInstallRecipe, logScaffoldResult extracted
17
-
18
- ## Remaining (deferred)
19
-
20
- - [ ] **Complexity** — reconcileRecipeCronJobs (55), handleScaffoldTeam (27), etc.
21
- - [ ] **Long functions** — handleScaffoldTeam (163), reconcileRecipeCronJobs (139)
22
- - [ ] **scaffold.ts ↔ team.ts** — pickRecipeId already shared; further unification optional
23
- - [ ] **Console in src/lib** — Logging abstraction
@@ -1,37 +0,0 @@
1
- # Test Coverage Progress
2
-
3
- Target: 95% line coverage.
4
-
5
- ## Baseline (before cleanup)
6
-
7
- | File | % Stmts | % Branch | % Funcs | % Lines | Notes |
8
- |------|---------|----------|---------|---------|-------|
9
- | index.ts | 4.16 | 61.53 | 18.42 | 4.16 | God file; most logic untested |
10
- | src/marketplaceFetch.ts | 0 | 100 | 100 | 0 | Needs fetch mock |
11
- | src/toolsInvoke.ts | 0 | 100 | 0 | 0 | Needs fetch mock |
12
- | src/lib/bindings.ts | 91.42 | 66.66 | 100 | 91.42 | Lines 50-52 uncovered |
13
- | src/lib/cleanup-workspaces.ts | 78.51 | 61.9 | 100 | 78.51 | |
14
- | src/lib/lanes.ts | 60 | 83.33 | 66.66 | 60 | |
15
- | src/lib/recipe-frontmatter.ts | 93.54 | 70.83 | 100 | 93.54 | |
16
- | src/lib/remove-team.ts | 81.51 | 70.58 | 63.63 | 81.51 | |
17
- | src/lib/scaffold-templates.ts | 100 | 100 | 100 | 100 | |
18
- | src/lib/shared-context.ts | 100 | 85.71 | 100 | 100 | |
19
- | src/lib/ticket-finder.ts | 0 | 0 | 0 | 0 | Not yet exercised by tests |
20
- | src/lib/ticket-workflow.ts | 96.52 | 37.5 | 100 | 96.52 | |
21
- | **All files** | **18.25** | **58.85** | **48.83** | **18.25** | |
22
-
23
- ## Gaps to address
24
-
25
- - index.ts: extract logic to src/lib and test there; add integration tests for command handlers
26
- - ticket-finder.ts: add tests
27
- - marketplaceFetch, toolsInvoke: mock fetch, add tests
28
- - lanes.ts, cleanup-workspaces, remove-team: expand tests
29
-
30
- ## Updates
31
-
32
- - **Baseline**: Initial coverage run before cleanup work.
33
- - **Post-cleanup**: 25% overall; src/lib at 91.53%, src/ at 98.36%. index.ts remains low (3.91%) — extraction moved logic to lib. Thresholds set at 25% baseline; target 95% as further index extraction proceeds.
34
- - **Index handler extraction (Phase 2–3)**: Handler logic moved to src/handlers/; exercised via __internal in integration tests.
35
- - **Post-extraction baseline** (npm run test:coverage): 56.55% overall; src/ at 98.36%; src/handlers at 66.44%; src/lib at 94.63%; index.ts at 9.62% (thin CLI wiring).
36
- - **Comprehensive coverage plan** (Phase 1–3): cron-handler, install-handler, prompt, workspace, scaffold, team, tickets, recipes tests added. Thresholds raised to 60% lines/statements, 90% functions. index.ts remains thin wiring; logic exercised via __internal.
37
- - **CI coverage enforcement** (Feb 2026): CI now runs `npm run test:coverage`; build fails if thresholds are not met. Thresholds: lines 60%, statements 60%, functions 90%, branches 65%.