df0a0cef20
ci / reno-work MCP — per-function unit tests (push) Successful in 14s
Retire the two remaining launchd crons' DUTIES into headless MCP tools so
the online coordinator owns them end-to-end (no local cron needed).
scan_site_visits (signals.py) — PORT of calendar-reminders-daily (§1+§1b):
scan the PRIMARY GCal for /site visit/i (yesterday→+30d), per client
idempotently surface 做报价 + 给 Ryan cabinet-plan reminders and ensure a CRM
project. Deterministic x_sync_uuid ledger; dry-safe.
Parity fixes on the validator's two majors:
- CalendarClient.book now takes attendees + calendar_id → reminders land on
Sylvia's PRIMARY calendar and actually invite her (sendUpdates=all), as the
cron did, instead of the silent shared calendar with no attendee.
- reminder ledger rows are x_source='gcal': still the dedup anchor
(find_event_by_uuid matches on uuid) but excluded from the owned-task
digest, so they're Sylvia's prompts — the coordinator's actionable items
are the quote-stage tasks create_project spawns (no 5-task pileup).
- calendar_action verb aligned to add_calendar_event (matches set_start_date).
append_sales_sheet (sheet.py) — REPLACES email-to-sheet.cjs's sheet WRITE with
a headless Sheets API v4 append to the 邮件客人 tab (owner KEEPS the sheet).
Transposed insert-at-B, rows 4..16, ONE atomic batchUpdate (can't half-write a
column). Content-idempotent on (date,name,address); dry-safe; degrades to an
append_sales_sheet agent_action until the net-new SHEETS_* OAuth cred (needs
auth/spreadsheets scope) is provisioned — inert, never a bad write.
31 tools; 129 tests pass; ci stdio floor 30→31.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
1.8 KiB
YAML
49 lines
1.8 KiB
YAML
# ci.yml — per-function CI for the reno-work work-MCP.
|
|
#
|
|
# Complements .github/workflows/enterskill-gate.yml (structural manifest lint):
|
|
# this job unit-tests every one of the 12 MCP tools with NO live Odoo and NO
|
|
# network — mcp/test_server.py stubs the module-level _odoo with a recording
|
|
# fake and asserts the exact Odoo JSON-2 payload shape (writes) / search domain
|
|
# (reads), plus the MCP stdio protocol. Live writes are impossible in CI
|
|
# (RENO_WORK_DRY_RUN gating + the stub), so it is safe and hermetic.
|
|
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
mcp-unit:
|
|
name: reno-work MCP — per-function unit tests
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Install pytest
|
|
run: pip install --quiet pytest
|
|
|
|
- name: Unit-test every MCP tool (no live Odoo)
|
|
run: pytest mcp/ -v
|
|
|
|
- name: stdio protocol smoke (real process — initialize + tools/list)
|
|
run: |
|
|
out=$(printf '%s\n%s\n' \
|
|
'{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
|
|
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
|
| python3 mcp/server.py)
|
|
echo "$out"
|
|
echo "$out" | grep -q '"name": "reno-work"' || { echo "FAIL: no reno-work serverInfo"; exit 1; }
|
|
echo "$out" | python3 -c "import sys,json; \
|
|
rows=[json.loads(l) for l in sys.stdin if l.strip()]; \
|
|
tools=[r for r in rows if r.get('id')==2][0]['result']['tools']; \
|
|
assert len(tools)>=31, f'expected >=31 tools, got {len(tools)}'; \
|
|
print(f'stdio smoke OK: {len(tools)} tools')"
|