Update BuildEase demo skill (session 2026-09-21 edits)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DywdYrFTrWa4h1T3ZykPBX
This commit is contained in:
@@ -17,6 +17,11 @@ Editing this repo means editing instructions that another Claude instance will f
|
|||||||
ln -s "$PWD" ~/.claude/skills/buildease-demo
|
ln -s "$PWD" ~/.claude/skills/buildease-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or check the repo out at that path directly — either layout works. What matters is that
|
||||||
|
**exactly one copy exists**: a clone alongside a checkout means you edit one and Claude
|
||||||
|
loads the other, with nothing to signal the drift. The substrate is symlinked for that
|
||||||
|
reason.
|
||||||
|
|
||||||
Claude Code discovers skills from the `name:`/`description:` frontmatter in `SKILL.md`. Changing `name:` changes how the skill is invoked; changing `description:` changes when it triggers — both are the routing surface, so treat them as API.
|
Claude Code discovers skills from the `name:`/`description:` frontmatter in `SKILL.md`. Changing `name:` changes how the skill is invoked; changing `description:` changes when it triggers — both are the routing surface, so treat them as API.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -31,7 +36,9 @@ Claude Code discovers skills from the `name:`/`description:` frontmatter in `SKI
|
|||||||
| Purge chain — take-off / progress-statement teardown order | *Purge test residue* |
|
| Purge chain — take-off / progress-statement teardown order | *Purge test residue* |
|
||||||
| Follow-through framing | Phase C |
|
| Follow-through framing | Phase C |
|
||||||
|
|
||||||
**The boundary is the point of this repo.** Anything needed that isn't one of those five is a gap in `callista-odoo-demo` — fix it *there*, so the next vertical inherits it. Do not work around it here, and never reimplement the substrate's `scripts/`, `templates/`, or `demo_config.json` handling.
|
**The boundary is the point of this repo.** New *mechanism* needed — a step that doesn't exist, an override those five don't offer — is a gap in `callista-odoo-demo`: fix it *there*, so the next vertical inherits it. Do not work around it here, and never reimplement the substrate's `scripts/`, `templates/`, or `demo_config.json` handling.
|
||||||
|
|
||||||
|
Construction detail that merely hangs off an existing named step (settings that default off, what a plausible staged number looks like, which chain *Test heroes* exercises) is **not** a gap. It belongs here, under *Step-local notes*, tagged with the step it applies at.
|
||||||
|
|
||||||
This repo was previously a full three-phase skill. The generic two-thirds (discovery brief from any source, the two-agent parallel build, the follow-through doc set) moved into the substrate on 2026-07-31 because Callista runs it for plain Odoo implementations too, and that content was stranded behind a construction trigger.
|
This repo was previously a full three-phase skill. The generic two-thirds (discovery brief from any source, the two-agent parallel build, the follow-through doc set) moved into the substrate on 2026-07-31 because Callista runs it for plain Odoo implementations too, and that content was stranded behind a construction trigger.
|
||||||
|
|
||||||
@@ -48,7 +55,7 @@ This repo was previously a full three-phase skill. The generic two-thirds (disco
|
|||||||
### Editing rules that carry weight
|
### Editing rules that carry weight
|
||||||
|
|
||||||
- **Reference substrate steps by name** (*Test heroes*, *Purge test residue*), never by number. Numbered references are how an upstream renumbering silently misroutes this skill with nothing to detect it.
|
- **Reference substrate steps by name** (*Test heroes*, *Purge test residue*), never by number. Numbered references are how an upstream renumbering silently misroutes this skill with nothing to detect it.
|
||||||
- **The purge ordering** (transient wizard rows → statements → invoices → timesheets → tasks → projects → sale orders → take-offs → partner last) is ordered because Odoo blocks deletion otherwise. Do not reorder or summarise. The take-off-without-a-sale-order trap in particular is non-obvious and cost a debugging round.
|
- **The purge ordering** (transient wizard rows → invoices → statements → timesheets → tasks → projects → sale orders → take-offs → partner last) is ordered because Odoo blocks deletion otherwise. Invoices go *before* statements — an `invoiced` `sale.progress` refuses `action_cancel` until its invoice is cancelled — so do not "helpfully" reorder them back. Do not reorder or summarise. The take-off-without-a-sale-order trap in particular is non-obvious and cost a debugging round.
|
||||||
- **Not installing `buildease_demo_data`** is the reason this overlay exists at all; the app-level dependencies that bundle used to drag in must still be routed from the pain table.
|
- **Not installing `buildease_demo_data`** is the reason this overlay exists at all; the app-level dependencies that bundle used to drag in must still be routed from the pain table.
|
||||||
- `reference/buildease.md` drifts when mev changes. When updating it, keep the two things that make it useful: the pain→module table keyed on *the client's own words*, and the gotchas (missing `custom_product_*` deps, Odoo 19 only, settings that default off).
|
- `reference/buildease.md` drifts when mev changes. When updating it, keep the two things that make it useful: the pain→module table keyed on *the client's own words*, and the gotchas (missing `custom_product_*` deps, Odoo 19 only, settings that default off).
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ that are specific to construction, and nothing else.
|
|||||||
|
|
||||||
**Requires** `callista-odoo-demo` installed at `~/.claude/skills/callista-odoo-demo/`
|
**Requires** `callista-odoo-demo` installed at `~/.claude/skills/callista-odoo-demo/`
|
||||||
(Callista's fork of Smetools/odoo-demo-architect). If it is missing, say so and stop —
|
(Callista's fork of Smetools/odoo-demo-architect). If it is missing, say so and stop —
|
||||||
do not reimplement its XML-RPC connector, data builder, or pipeline here.
|
do not reimplement its JSON-2 connector, data builder, or pipeline here.
|
||||||
|
|
||||||
> Do **not** substitute an `odoo-demo-architect` install for it. That is upstream, without
|
> Do **not** substitute an `odoo-demo-architect` install for it. That is upstream, without
|
||||||
> Phase A's source-agnostic discovery, without Phase C, and without the extension points
|
> Phase A's source-agnostic discovery, without Phase C, and without the extension points
|
||||||
@@ -97,9 +97,11 @@ a debugging round:
|
|||||||
- **Guard each step separately and loop until a pass keeps nothing.** One `try` around
|
- **Guard each step separately and loop until a pass keeps nothing.** One `try` around
|
||||||
draft → cancel → unlink lets a raise on an already-draft invoice skip the unlink beneath
|
draft → cancel → unlink lets a raise on an already-draft invoice skip the unlink beneath
|
||||||
it, and the purge then reports success over residue.
|
it, and the purge then reports success over residue.
|
||||||
- **Button methods return an unmarshallable action dict** over XML-RPC (`Fault` mentioning
|
- **Button methods return an action dict, not `True`.** Over JSON-2 it comes back as plain
|
||||||
`Marshal`/`dumps`). The transition already committed — catch it and move on. Retrying
|
JSON, so a returned dict is not a failure and not proof of success — read the state back.
|
||||||
fails on a state guard and reads like a real error.
|
(On a legacy XML-RPC connection the same dict raises a `Fault` mentioning `Marshal`/`dumps`
|
||||||
|
*after* the transition committed: catch it and move on, never retry, because the retry
|
||||||
|
fails on a state guard and reads like a real error.)
|
||||||
- **Deleting the take-off deletes its sale order** — `quantity.take.off.unlink()` unlinks
|
- **Deleting the take-off deletes its sale order** — `quantity.take.off.unlink()` unlinks
|
||||||
`sale_order_ids` first. The take-off is the handle for both.
|
`sale_order_ids` first. The take-off is the handle for both.
|
||||||
- A take-off with **no** sale order still cancels and deletes normally: call `cancel()`, which
|
- A take-off with **no** sale order still cancels and deletes normally: call `cancel()`, which
|
||||||
@@ -125,26 +127,31 @@ mev repo**, so anything under `buildease_custom_product*` is not a "just install
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## BuildEase-specific checks
|
## Step-local notes
|
||||||
|
|
||||||
- After installing, **walk Settings once**. `res.config.settings` and `res.company` are
|
Domain detail hung off substrate steps that need no overlay — they change no mechanism, so
|
||||||
extended by 8 modules each and several BuildEase behaviours default to off.
|
they are not gaps in `callista-odoo-demo` (see its [Extension points](../callista-odoo-demo/SKILL.md#extension-points)).
|
||||||
- At the staged-numbers sanity check: **booked hours well under ordered hours**. Over 100%
|
|
||||||
makes the progress statement look wrong on screen even when every assertion passed.
|
- *At **Fork the slow half**, agent 1:* after installing, **walk Settings once**.
|
||||||
- The spine of every BuildEase demo is QTO → sale order → project → progress statement →
|
`res.config.settings` and `res.company` are extended by 8 modules each and several
|
||||||
invoice. If that chain breaks mid-demo you lose the deal, so it is the chain **Test
|
BuildEase behaviours default to off.
|
||||||
heroes** must exercise end to end.
|
- *At **Sanity-check the staged numbers**:* **booked hours well under ordered hours**. Over
|
||||||
- `quantity.take.off` inherits `mail.thread` + `mail.activity.mixin` — chatter and
|
100% makes the progress statement look wrong on screen even when every assertion passed.
|
||||||
activities work for free. Cheap demo beats.
|
- *At **Test heroes**:* the spine of every BuildEase demo is QTO → sale order → project →
|
||||||
|
progress statement → invoice. If that chain breaks mid-demo you lose the deal, so it is
|
||||||
|
the chain the tests must exercise end to end.
|
||||||
|
- *At **Build heroes**:* `quantity.take.off` inherits `mail.thread` + `mail.activity.mixin`
|
||||||
|
— chatter and activities work for free. Cheap demo beats.
|
||||||
|
|
||||||
## Guardrails
|
## Guardrails
|
||||||
|
|
||||||
- Never claim a BuildEase feature works without having seen it work in the demo DB.
|
- Never claim a BuildEase feature works without having seen it work in the demo DB.
|
||||||
- Never invent a pain quote. `(inferred)` is fine; a fabricated quote read back to the
|
- Never invent a pain quote. `(inferred)` is fine; a fabricated quote read back to the
|
||||||
client in the demo is not.
|
client in the demo is not.
|
||||||
- If something you need is not one of the five overlays above, that is a gap in
|
- If something you need is new **mechanism** — a step that doesn't exist, an override the
|
||||||
`callista-odoo-demo` — fix it there, so the next vertical gets it free. Do not work
|
five overlays don't offer — that is a gap in `callista-odoo-demo`. Fix it there, so the
|
||||||
around it here.
|
next vertical gets it free; do not work around it here. Construction detail that hangs
|
||||||
|
off an existing named step is not a gap: it goes under *Step-local notes*.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ and each statement gets its own copy carrying **that period's** index. This is w
|
|||||||
Three traps, each of which cost a debugging round:
|
Three traps, each of which cost a debugging round:
|
||||||
|
|
||||||
- **The rows are generated by an `@api.onchange`.** The list is `create="0" delete="0"` in
|
- **The rows are generated by an `@api.onchange`.** The list is `create="0" delete="0"` in
|
||||||
the form, so there is no button and no create path — and `write()` over XML-RPC does not
|
the form, so there is no button and no create path — and `write()` over the external API
|
||||||
fire onchanges, so nothing appears. Every field on `formula.input` is a plain **stored**
|
does not fire onchanges, so nothing appears. Every field on `formula.input` is a plain **stored**
|
||||||
field (readonly in the UI only), so write them directly, `weight_parameter` included.
|
field (readonly in the UI only), so write them directly, `weight_parameter` included.
|
||||||
- **Set `is_revision = True` on the take-off first.** `revision_scheme_id` is
|
- **Set `is_revision = True` on the take-off first.** `revision_scheme_id` is
|
||||||
`invisible="not is_revision"`, and `revision_formula_ids` computes off the *inputs*, not
|
`invisible="not is_revision"`, and `revision_formula_ids` computes off the *inputs*, not
|
||||||
@@ -210,12 +210,12 @@ need to delete the order separately, only to have cancelled it.
|
|||||||
**A take-off with no sale order *can* be cancelled and deleted.** `cancel()` writes
|
**A take-off with no sale order *can* be cancelled and deleted.** `cancel()` writes
|
||||||
`state = "cancel"` directly and then calls `action_cancel()` on `sale_order_id` — an empty
|
`state = "cancel"` directly and then calls `action_cancel()` on `sale_order_id` — an empty
|
||||||
recordset when there is no order, and in Odoo 19 `sale.order.action_cancel` is
|
recordset when there is no order, and in Odoo 19 `sale.order.action_cancel` is
|
||||||
`self.write({'state': 'cancel'})` with no `ensure_one()`, so it is a clean no-op. Over
|
`self.write({'state': 'cancel'})` with no `ensure_one()`, so it is a clean no-op. Through
|
||||||
XML-RPC:
|
the substrate's `x()` helper:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
x("quantity.take.off", "cancel", [qto_id]) # works with or without a sale order
|
x("quantity.take.off", "cancel", qto_ids) # works with or without a sale order
|
||||||
x("quantity.take.off", "unlink", [qto_id]) # now passes the state == 'cancel' check
|
x("quantity.take.off", "unlink", qto_ids) # now passes the state == 'cancel' check
|
||||||
```
|
```
|
||||||
|
|
||||||
This supersedes the older "convert it to a quotation first, then cancel that" workaround,
|
This supersedes the older "convert it to a quotation first, then cancel that" workaround,
|
||||||
@@ -239,11 +239,13 @@ already-draft invoice, and the `unlink` beneath it never runs. Then run the whol
|
|||||||
loop until a pass keeps nothing — cascades free records that the previous pass could not
|
loop until a pass keeps nothing — cascades free records that the previous pass could not
|
||||||
touch.
|
touch.
|
||||||
|
|
||||||
**Button methods return an action dict Odoo cannot marshal over XML-RPC.**
|
**Button methods return an action dict, not `True`.** `action_cancel`, `action_approved`,
|
||||||
`action_cancel`, `action_approved`, `action_price_revision`, `create_invoices` and friends
|
`action_price_revision`, `create_invoices` and friends all hand back a window action. Over
|
||||||
all raise a `Fault` whose text mentions `Marshal`/`dumps`. **The method already ran and
|
JSON-2 that serialises fine, so the dict is neither an error nor proof the transition
|
||||||
committed** — only the response failed. Catch that specific fault and continue; retrying
|
landed — read the state back. On a legacy XML-RPC connection the same dict raises a `Fault`
|
||||||
re-runs the transition and will fail the second time on a state guard.
|
whose text mentions `Marshal`/`dumps`: **the method already ran and committed**, only the
|
||||||
|
response failed, so catch that specific fault and continue. Never retry — the second run
|
||||||
|
hits a state guard and reads like a real error.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user