PLAN — frappe-stack¶
Phased implementation plan. Each phase has an exit checkpoint. Do not start phase N+1 until phase N's checkpoint signs off.
0. Decision register (must close before Phase 1)¶
| ID | Decision | Default | Owner | Status |
|---|---|---|---|---|
| D-01 | Sync direction | B+ hybrid (staging interactive, prod git-only) | Product | confirmed 2026-05-01 |
| D-02 | Plugin namespace | /frappe-stack: (auto-derived from plugin name; Claude Code does not allow a separate prefix field — /fs- was researched-out 2026-05-03) |
Product | confirmed 2026-05-03 |
| D-03 | mgrant-stack access | work from public README until collaborator granted | Eng | confirmed 2026-05-03 |
| D-04 | Frappe target version | v15+ only | Eng | confirmed 2026-05-03 |
| D-05 | Plugin marketplace | private, install via direct git URL | Eng | confirmed 2026-05-03 |
| D-06 | Test framework for stack_core |
frappe.tests.utils.FrappeTestCase (per global frappe-testing rules) |
Eng | confirmed 2026-05-03 |
| D-07 | A/B in workflows — scope for v0.1 | binary split only (no n-arm); deterministic by hash(doc.name) |
Product | confirmed 2026-05-03 |
| D-08 | Audit log retention | append-only, no purge; archive after 365 days | Compliance | confirmed 2026-05-03 |
| D-09 | infra/ (Docker, CI, pre-commit) | deferred to post-v0.1 | Product | confirmed |
| D-10 | Plugin-only architecture (drop apps/stack_core/); use stock Frappe REST instead of custom server-side endpoints |
plugin-only | Product | confirmed 2026-05-05 |
1. Architecture overview¶
┌──────────────────────── Claude Code (PM's machine) ────────────────────────┐
│ frappe-stack plugin │
│ │
│ .claude-plugin/ skills/ agents/ commands/ hooks/ │
│ ├── manifest.json ├── platform/ ├── engineer ├── fs-init ├──── │
│ ├── README ├── building/ ├── reviewer ├── fs-build │
│ ├── process/ ├── tester ├── fs-promote │
│ └── builder- ├── deployer ├── fs-pull/push │
│ protocol/ └── … └── … │
│ │
│ │ │
│ │ HTTPS + API key │
└────────┼───────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────── Frappe site (staging) ──────────────┐ ┌── GitHub ──┐
│ apps/stack_core/ │ │ config- │
│ ├── api/ ←─────── plugin calls │ │ repo │
│ ├── doctype/ │ │ │
│ │ ├── stack_blueprint (versioned config) │ │ ┌──────┐ │
│ │ ├── stack_workflow_def │ │ │ PRs │ │
│ │ ├── experiment_assignment │ │ │ │ │
│ │ └── stack_audit_log (append-only) │ ←─→│ │ main │ │
│ ├── guardrails/ (schema, fieldtype, reserved) │ │ └──────┘ │
│ ├── git_bridge/ (export-fixtures + commit/PR) │ │ │
│ └── hooks.py │ └────────────┘
└─────────────────────────────────────────────────────────┘ │
│ merge
▼
┌─── Frappe site (production) ───┐
│ bench migrate (auto on merge) │
└────────────────────────────────┘
2. Phase 1 — Plugin manifest + Builder Protocol scaffolding (1–2 days)¶
Deliverables:
- .claude-plugin/plugin.json (manifest — name, version, author, repo) ✓ done 2026-05-03
- .claude-plugin/marketplace.json (marketplace listing — required for private install) ✓ done 2026-05-03
- .claude-plugin/README.md (install instructions: register marketplace + add plugin) ✓ done 2026-05-03
- CLAUDE.md for the plugin itself (this repo's working memory) ✓ done Phase 0
- HEARTBEAT.md template that gets stamped on every phase transition ✓ done Phase 0
- Empty skill/agent/command/hook directories with .gitkeep ✓ done 2026-05-03
- apps/stack_core/.gitkeep placeholder for the support app ✓ done 2026-05-03
- LICENSE (MIT, matching frappe_dhwani_base) ✓ done 2026-05-03
Checkpoint: plugin installs in Claude Code without errors; /frappe-stack:* commands appear (even if empty). All artifacts written; smoke-test in a clean Claude Code session pending.
3. Phase 2 — stack_core Frappe support app (3–5 days)¶
Deliverables:
- apps/stack_core/ skeleton via bench new-app
- DocTypes:
- Stack Blueprint — fields: name, version, type (doctype/workflow/dashboard/report/config), payload (JSON), status, applied_at, applied_by, git_commit_sha
- Stack Workflow Def — extends Blueprint, adds split-state fields
- Experiment Assignment — fields: doc_reference, blueprint, arm, assigned_at, outcome, outcome_at
- Stack Audit Log — fields: actor, action, blueprint, before_json, after_json, timestamp, ip
- API endpoints (all @frappe.whitelist(), all permission-checked):
- POST /api/method/stack_core.api.build_doctype
- POST /api/method/stack_core.api.build_workflow
- POST /api/method/stack_core.api.build_config
- POST /api/method/stack_core.api.export_fixtures (returns JSON of all blueprints)
- GET /api/method/stack_core.api.diff_against_git
- Guardrail validators:
- guardrails/schema_validator.py (JSON-Schema for each blueprint type)
- guardrails/fieldtype_whitelist.py (Code/Password/Attach gated by role)
- guardrails/reserved_names.py (block User, Role, DocType, …)
- guardrails/workflow_validator.py (terminal state, role on every state, no orphans)
- Tests (per frappe-testing.md):
- One test_<doctype>.py per DocType
- API tests with valid + invalid + permission-denied paths
- Negative tests for every guardrail (refuses bad input)
Checkpoint: all tests pass via bench --site test_site run-tests --app stack_core --coverage. Coverage ≥ 80%.
4. Phase 3 — High-value skills (3–5 days)¶
Order by PM impact. Each skill = skills/<category>/<name>/SKILL.md + supporting refs.
| Order | Skill | Why first |
|---|---|---|
| 1 | building/designing-forms |
DocType creation = most common PM ask |
| 2 | building/modeling-workflows |
Approval flows = second-most-common |
| 3 | process/git-roundtrip |
The differentiator — without this, we're just an API wrapper |
| 4 | platform/frappe-platform |
4-layer stack + config-vs-code decision tree (port from mgrant-stack pattern) |
| 5 | platform/frappe-permissions |
Required reading for every other skill |
| 6 | platform/frappe-patterns |
Index of patterns from sunandan89/mgrant-frappe-patterns |
| 7+ | rest (composing-reports, building-dashboards, wiring-integrations, designing-experiments, writing-specs, running-qa, promoting-changes, managing-tickets, builder-protocol) |
as needed |
Each skill must:
- Trigger on PM-natural phrases (e.g., "I need a form for…", not "create a DocType")
- Reference the global ~/.claude/rules/frappe/ files explicitly
- Include a worked example end-to-end
- List anti-patterns it refuses
Checkpoint: PM can run /fs-build doctype Beneficiary and the engineer agent uses designing-forms to walk them through fields, validation, permissions; emits a blueprint JSON; calls stack_core API; sees DocType live on staging.
5. Phase 4 — Agents (2–3 days)¶
Order:
| Order | Agent | Wraps |
|---|---|---|
| 1 | engineer |
The default — turns PM intent into blueprint JSON |
| 2 | reviewer |
Calls global frappe-reviewer agent + applies ~/.claude/rules/frappe/ rules |
| 3 | tester |
Generates test_<doctype>.py + Playwright tests per frappe-testing.md |
| 4 | deployer |
Runs /fs-promote, opens PR, monitors merge → migrate |
| 5 | analyst |
Read-only — runs reports, queries dashboards, summarizes A/B results |
| 6 | migrator |
Schema migration safety — uses global database-reviewer |
| 7 | documenter |
Auto-updates docs/ from blueprints |
| 8 | orchestrator |
Routes multi-step asks across the others |
Each agent: short description, action triggers, minimum tools, model selection per ~/.claude/rules/common/performance.md.
Checkpoint: /fs-build doctype … end-to-end uses engineer → reviewer → tester (parallel where independent), produces a working DocType + tests + audit log row + git commit.
6. Phase 5 — Slash commands (2 days)¶
| Command | Action |
|---|---|
/fs-init |
Bootstrap: install stack_core on a site, configure API key, register staging+prod sites in plugin config |
/fs-build <type> <name> |
Interactive build via engineer agent |
/fs-pull |
Fetch site state → write fixtures locally → commit |
/fs-push |
Apply local fixtures → staging site (refuses prod) |
/fs-diff |
Show drift between site and git |
/fs-promote |
Open PR from staging snapshot to prod-config repo |
/fs-experiment <action> |
Define / monitor / promote A/B in workflows |
/fs-review |
Pre-promote: runs reviewer + tester, surfaces issues |
/fs-ship |
Tag release after successful prod migrate |
Checkpoint: all commands have --help, all refuse cleanly on missing config, all complete a smoke test scenario documented in docs/walkthroughs/.
7. Phase 6 — Safety hooks (1 day)¶
hooks/ ships JSON definitions for Claude Code:
block-direct-prod-api.json— PreToolUse on Bash/WebFetch: any URL matching the configured prod host is blocked.enforce-blueprint-schema.json— PreToolUse onmcp__stack_core__*(or Bash curl to stack_core): validate JSON-Schema before send.audit-every-mutation.json— PostToolUse: append to local.frappe-stack/audit.jsonl.block-ignore-permissions.json— PreToolUse on Edit/Write: scan generated Python forignore_permissions=True, refuse.block-credential-leak.json— PreToolUse on Edit/Write: scan for AKIA / Bearer / api_key= patterns, refuse.block-force-push.json— PreToolUse on Bash: refusegit push --forceto protected branches.block-bench-drop-site.json— PreToolUse on Bash: refusebench drop-site.
Checkpoint: every guardrail in SECURITY.md §2.1 has a working hook, with a regression test that proves the hook fires.
8. Phase 7 — Git roundtrip engine (3–5 days)¶
apps/stack_core/git_bridge/:
exporter.py— wrapsbench export-fixtureswith per-blueprint granularity (fixes ambiguity around app-level fixtures, addresses Frappe issue #34915 partially).committer.py— runs in a configurable git working directory (the config repo, not the Frappe app source). Stages, commits with structured message, pushes to a feature branch.pr_opener.py— usesghCLI (or GitHub REST API) to open PR with auto-generated body: blueprint diff, audit log excerpt, test results.differ.py— compares site fixtures vs config-repomainHEAD, outputs structured diff.applier.py— opposite direction: pulls config repo, applies fixtures viabench migrate, idempotent.
Special-cases:
- App-level fixtures vs site-specific overrides (per Frappe issue #36398) — config repo has both fixtures/app/ (shipped with app) and fixtures/site/<sitename>/ (site overrides applied post-migrate).
- Conflict resolution when site state and git state both changed: surface as /fs-diff output, refuse /fs-promote until resolved.
Checkpoint: round-trip drill — make a change on staging, run /fs-pull, verify git diff matches; revert site, run /fs-push, verify site state matches git; run /fs-promote, verify PR opens with correct content; merge PR, verify prod migrates idempotently.
9. Phase 8 — A/B experiments in workflows (3–4 days)¶
Schema additions to Stack Workflow Def:
{
"states": [
{ "name": "Initial Review", "type": "split", "traffic_split": { "arm_a": 50, "arm_b": 50 },
"next_states": { "arm_a": "Manager Approval", "arm_b": "Auto-Approval" } }
]
}
Implementation:
- New Experiment Assignment row created on workflow entry into a split state.
- Assignment is deterministic: hash(doc.name + experiment_id) % 100 < traffic_split.arm_a → arm_a, else arm_b.
- Document gets a Custom Field experiment_arm (linked to assignment row).
- Subsequent next_states lookup keys off experiment_arm.
- Outcome captured when document reaches a terminal state — Experiment Assignment.outcome updated.
- Dashboard skill: building/designing-experiments ships a default Frappe Dashboard config showing per-arm conversion rate, mean cycle time, current sample size.
- /fs-experiment promote arm_a → mutates blueprint to remove split, fixes path to arm_a's, opens PR.
Checkpoint: end-to-end test: define experiment, simulate 100 documents, verify ~50/50 split, verify outcomes captured, verify dashboard reads correct numbers, verify promotion strips losing arm.
10. Phase 9 — Polish + docs + v0.1 release (2 days)¶
docs/walkthroughs/— 4 PM-facing screen-recorded walkthroughs:- First DocType
- First workflow with approvals
- First A/B experiment
- First promotion to prod
docs/operators/— DevOps runbook for installingstack_core+ configuring sites + rotating keysCHANGELOG.md— start tracking- Tag
v0.1.0
11. Effort summary¶
| Phase | Estimate (orig) | Actual |
|---|---|---|
| 0 — decisions closed | 0.5 d | done 2026-05-01 |
| 1 — manifest + protocol | 1–2 d | done 2026-05-03 |
2 — stack_core |
3–5 d | done 2026-05-03 (artifacts only; tests not executed — no bench) |
| 3 — skills | 3–5 d | done 2026-05-03 |
| 4 — agents | 2–3 d | done 2026-05-03 |
| 5 — commands | 2 d | done 2026-05-03 |
| 6 — hooks | 1 d | done 2026-05-03 |
| 7 — git roundtrip | 3–5 d | done 2026-05-03 (folded into Phase 2 commit) |
| 8 — A/B experiments | 3–4 d | done 2026-05-03 (integrated across Phases ⅔/5) |
| 9 — release polish | 2 d | done 2026-05-03 (walkthroughs + operator runbooks + CHANGELOG) |
All artifact work landed locally in commits across 2026-05-01 to 2026-05-03. Pending: bench smoke-test, plugin install smoke-test, push to origin.
12. Risks¶
| Risk | Likelihood | Mitigation |
|---|---|---|
| Frappe issue #34915 (fixture sync) bites us mid-Phase 7 | Medium | Phase 7 includes per-blueprint export to sidestep it |
| App fixtures vs site overrides clash on real sites | High | Two-tier fixtures/app/ + fixtures/site/<sitename>/ from day 1 |
| PM creates a DocType with a name that already exists in a different module | High | Reserved-name check + namespace prefix enforcement |
| API key leak on PM laptop | Medium | Plugin stores in OS keychain, not in plain config |
gh CLI not installed on PM machine |
Low | Fallback to GitHub REST API in pr_opener.py |
| mgrant-stack patterns we copy turn out to be private/proprietary | Medium | Only the structural pattern is borrowed; all skills/agents are written from scratch against public docs |