@massu/core 1.2.1 → 1.4.0-soak.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.
Files changed (61) hide show
  1. package/README.md +40 -0
  2. package/commands/README.md +137 -0
  3. package/commands/massu-deploy.python-docker.md +170 -0
  4. package/commands/massu-deploy.python-fly.md +189 -0
  5. package/commands/massu-deploy.python-launchd.md +144 -0
  6. package/commands/massu-deploy.python-systemd.md +163 -0
  7. package/commands/massu-deploy.python.md +200 -0
  8. package/commands/massu-scaffold-page.md +172 -59
  9. package/commands/massu-scaffold-page.swift.md +121 -0
  10. package/commands/massu-scaffold-router.python-django.md +153 -0
  11. package/commands/massu-scaffold-router.python-fastapi.md +145 -0
  12. package/commands/massu-scaffold-router.python.md +143 -0
  13. package/dist/cli.js +10170 -4138
  14. package/dist/hooks/auto-learning-pipeline.js +44 -6
  15. package/dist/hooks/classify-failure.js +44 -6
  16. package/dist/hooks/cost-tracker.js +44 -6
  17. package/dist/hooks/fix-detector.js +44 -6
  18. package/dist/hooks/incident-pipeline.js +44 -6
  19. package/dist/hooks/post-edit-context.js +44 -6
  20. package/dist/hooks/post-tool-use.js +44 -6
  21. package/dist/hooks/pre-compact.js +44 -6
  22. package/dist/hooks/pre-delete-check.js +44 -6
  23. package/dist/hooks/quality-event.js +44 -6
  24. package/dist/hooks/rule-enforcement-pipeline.js +44 -6
  25. package/dist/hooks/session-end.js +44 -6
  26. package/dist/hooks/session-start.js +4789 -410
  27. package/dist/hooks/user-prompt.js +44 -6
  28. package/package.json +10 -4
  29. package/src/cli.ts +28 -2
  30. package/src/commands/config-refresh.ts +88 -20
  31. package/src/commands/init.ts +130 -23
  32. package/src/commands/install-commands.ts +482 -42
  33. package/src/commands/refresh-log.ts +37 -0
  34. package/src/commands/show-template.ts +65 -0
  35. package/src/commands/template-engine.ts +262 -0
  36. package/src/commands/watch.ts +430 -0
  37. package/src/config.ts +69 -3
  38. package/src/detect/adapters/nextjs-trpc.ts +166 -0
  39. package/src/detect/adapters/parse-guard.ts +133 -0
  40. package/src/detect/adapters/python-django.ts +208 -0
  41. package/src/detect/adapters/python-fastapi.ts +223 -0
  42. package/src/detect/adapters/query-helpers.ts +170 -0
  43. package/src/detect/adapters/runner.ts +252 -0
  44. package/src/detect/adapters/swift-swiftui.ts +171 -0
  45. package/src/detect/adapters/tree-sitter-loader.ts +348 -0
  46. package/src/detect/adapters/types.ts +174 -0
  47. package/src/detect/codebase-introspector.ts +190 -0
  48. package/src/detect/index.ts +28 -2
  49. package/src/detect/regex-fallback.ts +449 -0
  50. package/src/hooks/session-start.ts +94 -3
  51. package/src/lib/gitToplevel.ts +22 -0
  52. package/src/lib/installLock.ts +179 -0
  53. package/src/lib/pidLiveness.ts +67 -0
  54. package/src/lsp/auto-detect.ts +89 -0
  55. package/src/lsp/client.ts +590 -0
  56. package/src/lsp/enrich.ts +127 -0
  57. package/src/lsp/types.ts +221 -0
  58. package/src/watch/daemon.ts +385 -0
  59. package/src/watch/lockfile-detector.ts +65 -0
  60. package/src/watch/paths.ts +279 -0
  61. package/src/watch/state.ts +178 -0
@@ -0,0 +1,163 @@
1
+ ---
2
+ name: massu-deploy
3
+ description: "Deploy a Python service supervised by systemd (Linux) — restart the user unit, poll health, tail journalctl, diff before/after, rollback if unhealthy"
4
+ allowed-tools: Bash(*), Read(*), Grep(*), Glob(*)
5
+ ---
6
+
7
+ # Massu Deploy: Python Service — systemd (Linux)
8
+
9
+ Restarts a Python service running under a `systemd --user` unit on Linux. Use this variant when your `massu.config.yaml` declares `config.python.service_label` and your host is Linux.
10
+
11
+ ## Workflow Position
12
+
13
+ ```
14
+ /massu-push -> /massu-deploy (systemd variant)
15
+ ```
16
+
17
+ This command restarts a production service. **If the service handles financial, transactional, or otherwise consequential state, real data is at risk** — pre-flight checks are mandatory.
18
+
19
+ ---
20
+
21
+ ## NON-NEGOTIABLE RULES
22
+
23
+ - **Never deploy with uncommitted changes** — push first via `/massu-push`
24
+ - **Never deploy with failing tests** — test suite must be green before this runs
25
+ - **Always restart-and-probe** — file-saved ≠ process-running-the-fix
26
+ - **Never kill processes without identifying them first** — confirm the unit name before sending any signal
27
+
28
+ ---
29
+
30
+ ## Pre-flight
31
+
32
+ ```bash
33
+ # 1. Branch + working tree clean
34
+ test -z "$(git status --porcelain)" || { echo "DIRTY — commit/stash first"; exit 1; }
35
+
36
+ # 2. Tests green
37
+ pytest -x 2>&1 | tail -10
38
+
39
+ # 3. Confirm the unit is active
40
+ systemctl --user status {{config.python.service_label | default("<service-label>")}} --no-pager | head -20
41
+
42
+ # 4. Capture current health for diff
43
+ curl -sS http://localhost:8000/health | python3 -m json.tool \
44
+ > /tmp/{{config.python.service_label | default("service")}}-health-before.json
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Approval Gate
50
+
51
+ ```
52
+ ===============================================================================
53
+ APPROVAL REQUIRED — SYSTEMD RESTART
54
+ ===============================================================================
55
+
56
+ Service unit : {{config.python.service_label | default("<service-label>")}}
57
+ Supervisor : systemd --user (Linux)
58
+ Pre-flight : PASS
59
+
60
+ This will:
61
+ 1. systemctl --user restart {{config.python.service_label | default("<service-label>")}}
62
+ 2. Poll /health every 2s (max 60s) until 200
63
+ 3. Smoke /health + any critical endpoint
64
+ 4. Diff before/after health JSON
65
+ 5. On failure: print rollback command
66
+
67
+ Reply "approve" or "abort".
68
+ ===============================================================================
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Restart
74
+
75
+ ```bash
76
+ systemctl --user restart {{config.python.service_label | default("<service-label>")}}
77
+ ```
78
+
79
+ If the unit is system-level (not user-level), drop `--user` and run with `sudo`.
80
+
81
+ ---
82
+
83
+ ## Poll Health
84
+
85
+ ```bash
86
+ for i in $(seq 1 30); do
87
+ sleep 2
88
+ status=$(curl -sS -o /dev/null -w "%{http_code}" http://localhost:8000/health || echo "000")
89
+ [ "$status" = "200" ] && { echo "READY after ${i} polls"; break; }
90
+ echo "poll ${i}: ${status}"
91
+ done
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Smoke + Diff
97
+
98
+ ```bash
99
+ curl -sS http://localhost:8000/health | python3 -m json.tool \
100
+ > /tmp/{{config.python.service_label | default("service")}}-health-after.json
101
+
102
+ diff \
103
+ /tmp/{{config.python.service_label | default("service")}}-health-before.json \
104
+ /tmp/{{config.python.service_label | default("service")}}-health-after.json \
105
+ | head -50
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Tail Startup Logs
111
+
112
+ ```bash
113
+ # Recent errors from the unit (2-minute window)
114
+ journalctl --user -u {{config.python.service_label | default("<service-label>")}} \
115
+ --since "2 minutes ago" --no-pager | grep -iE "error|warn" | head -20
116
+
117
+ # Follow live (Ctrl-C to stop)
118
+ journalctl --user -u {{config.python.service_label | default("<service-label>")}} -f --no-pager
119
+
120
+ # Full boot for this unit
121
+ journalctl --user -u {{config.python.service_label | default("<service-label>")}} \
122
+ -b --no-pager | tail -100
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Rollback
128
+
129
+ If `/health` does not return 200 within 60s, or any smoke check fails:
130
+
131
+ ```bash
132
+ git revert HEAD --no-edit
133
+ systemctl --user restart {{config.python.service_label | default("<service-label>")}}
134
+ ```
135
+
136
+ Then page yourself or your on-call — an unhealthy production service is an incident.
137
+
138
+ ---
139
+
140
+ ## Useful systemd Diagnostics
141
+
142
+ ```bash
143
+ # Check if the unit file needs a daemon-reload after editing the .service file
144
+ systemctl --user daemon-reload
145
+
146
+ # Show the resolved unit definition (useful to verify ExecStart path)
147
+ systemctl --user cat {{config.python.service_label | default("<service-label>")}}
148
+
149
+ # List recent restarts / crash history
150
+ systemctl --user show {{config.python.service_label | default("<service-label>")}} \
151
+ --property=NRestarts,ActiveState,SubState
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Audit Log
157
+
158
+ ```bash
159
+ echo "$(date -u +%FT%TZ) deploy surface=systemd sha=$(git rev-parse HEAD) unit={{config.python.service_label | default("<service-label>")}} actor=$(whoami)" \
160
+ >> data/audit/deploys.log
161
+ ```
162
+
163
+ Done. Report: unit name, sha, restart time, health status, any warnings.
@@ -0,0 +1,200 @@
1
+ ---
2
+ name: massu-deploy
3
+ description: "Deploy a Python service (FastAPI / asgi) to production. Default target is a long-running process supervised by launchd, systemd, pm2, or docker — NOT Vercel. Asks which surface before acting."
4
+ allowed-tools: Bash(*), Read(*), Grep(*), Glob(*)
5
+ ---
6
+
7
+ > **Shared rules apply.** Read `${paths.commands}/_shared-preamble.md` before proceeding.
8
+
9
+ # Massu Deploy: Python Service Production Deploy
10
+
11
+ ## Workflow Position
12
+
13
+ ```
14
+ /massu-create-plan -> /massu-plan -> /massu-loop -> /massu-commit -> /massu-push -> /massu-deploy
15
+ (CREATE) (AUDIT) (IMPLEMENT) (COMMIT) (PUSH) (DEPLOY)
16
+ ```
17
+
18
+ This command deploys to production. **If the service is doing anything financial, transactional, or otherwise consequential, real money / data integrity is at risk** — pre-flight checks are mandatory.
19
+
20
+ ---
21
+
22
+ ## CRITICAL — Deployment Surfaces
23
+
24
+ A non-Vercel Python project usually has multiple deploy surfaces. Ask the user which one(s) before proceeding:
25
+
26
+ | Surface | What "deploy" means | Authority |
27
+ |---------|---------------------|-----------|
28
+ | **Service** (default) | Restart the supervised process — `launchctl kickstart`, `systemctl restart`, `pm2 restart`, or `docker compose up -d` | The actual production brain |
29
+ | **Static / docs** | rsync / S3 sync (if applicable) | Static site only |
30
+ | **Worker(s)** | Restart any background workers (celery, rq, custom asyncio task runners) | Background processing |
31
+
32
+ **There is NO assumption of Vercel.** Do NOT run `vercel --prod` from this template. If the user wants a Vercel deploy of a separate sub-project, route them to a different surface or scaffold a dedicated script.
33
+
34
+ ---
35
+
36
+ ## NON-NEGOTIABLE RULES
37
+
38
+ - **Never deploy with uncommitted changes** — push first via `/massu-push`
39
+ - **Never deploy with failing tests** — `pytest` (or your project's test runner) must be green
40
+ - **Always restart-and-probe** — file-saved ≠ process-running-the-fix. After restart, hit a health endpoint and diff before/after
41
+ - **Never kill processes without identifying them first** — get the PID, log line, or supervisor label before sending any signal
42
+ - **Live-trading or live-billing flag flips are a separate concern** — this command does not flip `auto_*_mode`, `*_live`, `production_*` flags. Use the dedicated approval flow
43
+
44
+ ---
45
+
46
+ ## START NOW
47
+
48
+ ### Step 0: Ask the user
49
+
50
+ ```
51
+ ===============================================================================
52
+ PYTHON DEPLOY — Which surface?
53
+ ===============================================================================
54
+
55
+ service Restart the supervised Python service (default)
56
+ workers Restart background workers (celery / rq / custom)
57
+ all service + workers
58
+ static rsync / S3 sync (only if applicable)
59
+
60
+ Which? [service / workers / all / static]
61
+ ===============================================================================
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Path A: Service (default)
67
+
68
+ Substitute `${supervisor}` with whatever your project actually uses (`launchd`, `systemd`, `pm2`, `docker`). Substitute `${service_label}` with the actual label / unit name from `massu.config.yaml` (`deploy.python.service_label`) or from your project's process manager.
69
+
70
+ ### Pre-flight
71
+
72
+ ```bash
73
+ # 1. Branch + working tree clean
74
+ test -z "$(git status --porcelain)" || { echo "DIRTY — commit/stash first"; exit 1; }
75
+
76
+ # 2. Tests green (impacted area only — full suite is for /massu-push)
77
+ ${paths.python_test_command:-pytest} -x 2>&1 | tail -10
78
+
79
+ # 3. Identify the running process (NEVER blind-kill)
80
+ # launchd: launchctl list | grep ${service_label}
81
+ # systemd: systemctl status ${service_label}
82
+ # pm2: pm2 jlist | jq '.[] | select(.name=="${service_label}")'
83
+ # docker: docker compose ps ${service_label}
84
+
85
+ # 4. Capture current health for diff
86
+ curl -sS ${health_url:-http://localhost:8000/health} | python3 -m json.tool > /tmp/${service_label}-health-before.json
87
+ ```
88
+
89
+ ### Approval gate
90
+
91
+ ```
92
+ ===============================================================================
93
+ APPROVAL REQUIRED — RESTART ${service_label}
94
+ ===============================================================================
95
+
96
+ Pre-flight: PASS
97
+ Branch: <current>
98
+ HEAD: <sha>
99
+ Last service uptime: <etime>
100
+
101
+ This will:
102
+ 1. Restart ${service_label} via ${supervisor}
103
+ 2. Wait 5s, poll /health until 200 (max 60s)
104
+ 3. Smoke: /health, /api/feature-flags (if applicable), one critical endpoint
105
+ 4. Diff before/after health responses
106
+ 5. If any check fails: print rollback (`git revert HEAD` + restart)
107
+
108
+ Reply "approve" or "abort".
109
+ ===============================================================================
110
+ ```
111
+
112
+ ### Restart + verify
113
+
114
+ Pick the line that matches your supervisor:
115
+
116
+ ```bash
117
+ # launchd
118
+ launchctl kickstart -k gui/$(id -u)/${service_label}
119
+
120
+ # systemd (user)
121
+ systemctl --user restart ${service_label}
122
+
123
+ # pm2
124
+ pm2 restart ${service_label}
125
+
126
+ # docker compose
127
+ docker compose up -d --force-recreate ${service_label}
128
+ ```
129
+
130
+ Then probe:
131
+
132
+ ```bash
133
+ # Poll until ready (60s budget)
134
+ for i in $(seq 1 30); do
135
+ sleep 2
136
+ status=$(curl -sS -o /dev/null -w "%{http_code}" ${health_url:-http://localhost:8000/health} || echo "000")
137
+ [ "$status" = "200" ] && { echo "READY after ${i} polls"; break; }
138
+ done
139
+
140
+ # Smoke + diff
141
+ curl -sS ${health_url:-http://localhost:8000/health} | python3 -m json.tool > /tmp/${service_label}-health-after.json
142
+ diff /tmp/${service_label}-health-before.json /tmp/${service_label}-health-after.json | head -50
143
+
144
+ # Tail the supervisor log for startup errors
145
+ # launchd:
146
+ log show --predicate 'subsystem == "${service_label}"' --last 2m --info 2>/dev/null | grep -iE "error|warn" | head -20
147
+ # systemd:
148
+ # journalctl --user -u ${service_label} --since "2 minutes ago" | grep -iE "error|warn" | head -20
149
+ # pm2:
150
+ # pm2 logs ${service_label} --lines 100 --nostream | grep -iE "error|warn" | head -20
151
+ ```
152
+
153
+ ### Rollback
154
+
155
+ If `/health` does not return 200 within 60s, or smoke fails:
156
+
157
+ ```bash
158
+ git revert HEAD --no-edit && <restart-command-from-above>
159
+ ```
160
+
161
+ Then page yourself or your on-call: an unhealthy production service is an incident.
162
+
163
+ ---
164
+
165
+ ## Path B: Workers
166
+
167
+ ```bash
168
+ # Restart background workers (celery / rq / custom asyncio runners)
169
+ # Substitute the supervisor label list for your project's worker names.
170
+ for label in ${worker_labels}; do
171
+ echo "restarting $label..."
172
+ # launchd: launchctl kickstart -k gui/$(id -u)/$label
173
+ # systemd: systemctl --user restart $label
174
+ # pm2: pm2 restart $label
175
+ done
176
+
177
+ # Verify each worker is processing again — push a no-op job if you have one,
178
+ # or read the queue depth before/after.
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Path C: Static (only if applicable)
184
+
185
+ ```bash
186
+ # Build static site, rsync, or aws s3 sync — fill in your project's pipeline.
187
+ # This is intentionally NOT a default; only run if your project has a static deploy target.
188
+ ```
189
+
190
+ ---
191
+
192
+ ## After ANY surface deploys
193
+
194
+ Save the deployment sha and timestamp to your project's audit log so the boundary is visible:
195
+
196
+ ```bash
197
+ echo "$(date -u +%FT%TZ) deploy surface=<surface> sha=$(git rev-parse HEAD) actor=$(whoami)" >> ${paths.audit_log:-data/audit/deploys.log}
198
+ ```
199
+
200
+ Done. Report: surface, sha, time, smoke results, any warnings.
@@ -1,24 +1,40 @@
1
1
  ---
2
2
  name: massu-scaffold-page
3
- description: "When user wants to create a new page, route, or section in the app scaffolds the page component, layout, loading/error states, and tRPC integration"
3
+ description: "When user wants to create a new page, screen, or view asks which framework target (Next.js, SwiftUI, FastAPI templates, Rust web), then scaffolds component / view / handler with project conventions"
4
4
  allowed-tools: Bash(*), Read(*), Write(*), Edit(*), Grep(*), Glob(*)
5
5
  ---
6
6
 
7
- # Scaffold New Page
7
+ # Scaffold New Page / View
8
8
 
9
- Creates a complete Next.js App Router page following all project patterns.
9
+ This default template is **framework-agnostic**. It asks the user (or, when invoked by an agent, infers from `massu.config.yaml`) which target to scaffold, then dispatches to one of the embedded patterns below.
10
10
 
11
- ## What Gets Created
11
+ > **If your project ships a stack-specific variant** of this template (e.g., `massu-scaffold-page.swift.md`), the variant is preferred and this default is not installed. See `${paths.commands}/README.md` for the variant-resolution rules.
12
12
 
13
- | File | Purpose |
14
- |------|---------|
15
- | `page.tsx` | Main page component with Suspense boundary |
16
- | `loading.tsx` | Skeleton loading state |
17
- | `error.tsx` | Error boundary with retry |
13
+ ## Step 1 Pick a target
18
14
 
19
- ## Template
15
+ Ask the user (or read `framework.primary` / `framework.languages` from the consumer's `massu.config.yaml`):
16
+
17
+ | Stack | Path A: web | Path B: native / mobile | Path C: backend rendered |
18
+ |-------|-------------|-------------------------|--------------------------|
19
+ | TypeScript / Next.js | `app/<route>/page.tsx` + loading + error | — | — |
20
+ | Swift / SwiftUI | — | `Features/<feature>/Views/<Name>View.swift` (+ ViewModel + Response) | — |
21
+ | Python / FastAPI | — | — | Jinja template + handler in `routers/<name>.py` |
22
+ | Rust / Axum | — | — | `src/handlers/<name>.rs` returning `Html` or JSON |
23
+
24
+ If the user is unsure, default to whatever `framework.primary` points at.
25
+
26
+ ---
27
+
28
+ ## Pattern 1 — Next.js App Router page
29
+
30
+ ### What gets created
31
+
32
+ - `${paths.web_source}/app/<route>/page.tsx`
33
+ - `${paths.web_source}/app/<route>/loading.tsx`
34
+ - `${paths.web_source}/app/<route>/error.tsx`
35
+
36
+ ### `page.tsx`
20
37
 
21
- ### page.tsx
22
38
  ```tsx
23
39
  import { Suspense } from 'react';
24
40
  import { PageContent } from './page-content';
@@ -26,82 +42,179 @@ import Loading from './loading';
26
42
 
27
43
  export default function Page() {
28
44
  return (
29
- <div className="page-container">
30
- <Suspense fallback={<Loading />}>
31
- <PageContent />
32
- </Suspense>
33
- </div>
45
+ <Suspense fallback={<Loading />}>
46
+ <PageContent />
47
+ </Suspense>
34
48
  );
35
49
  }
36
50
  ```
37
51
 
38
- ### page-content.tsx (with params)
52
+ ### `page-content.tsx`
53
+
39
54
  ```tsx
40
55
  'use client';
41
56
 
42
- import { use } from 'react';
43
- import { api } from '@/trpc/react';
44
-
45
- export function PageContent({ params }: { params: Promise<{ id: string }> }) {
46
- const { id } = use(params);
47
- const { data, isLoading } = api.routerName.getById.useQuery({ id });
48
-
49
- if (isLoading) return <Loading />;
50
-
51
- return (
52
- <>
53
- <PageHeader title="Page Title" />
54
- {/* Page content */}
55
- </>
56
- );
57
+ import { useEffect, useState } from 'react';
58
+
59
+ export function PageContent() {
60
+ const [data, setData] = useState<unknown>(null);
61
+ const [error, setError] = useState<string | null>(null);
62
+
63
+ useEffect(() => {
64
+ // Substitute the URL with your project's API base — tRPC client, REST URL, etc.
65
+ fetch(`${process.env.NEXT_PUBLIC_API_BASE ?? '/api'}/<endpoint>`, {
66
+ credentials: 'include',
67
+ })
68
+ .then(async (r) => {
69
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
70
+ setData(await r.json());
71
+ })
72
+ .catch((e: Error) => setError(e.message));
73
+ }, []);
74
+
75
+ if (error) throw new Error(error);
76
+ if (!data) return null;
77
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
57
78
  }
58
79
  ```
59
80
 
60
- ### loading.tsx
61
- ```tsx
62
- import { Skeleton } from '@/components/ui/skeleton';
81
+ ### `loading.tsx` / `error.tsx`
63
82
 
83
+ ```tsx
84
+ // loading.tsx — show a skeleton, NOT a flash of empty state.
64
85
  export default function Loading() {
65
- return (
66
- <div className="page-container">
67
- <Skeleton className="h-8 w-48 mb-6" />
68
- <Skeleton className="h-64 w-full" />
69
- </div>
70
- );
86
+ return <div className="page-skeleton" />;
71
87
  }
72
88
  ```
73
89
 
74
- ### error.tsx
75
90
  ```tsx
91
+ // error.tsx — must be a client component. Provides a retry handle to the user.
76
92
  'use client';
77
-
78
93
  export default function Error({ error, reset }: { error: Error; reset: () => void }) {
79
94
  return (
80
- <div className="page-container">
81
- <h2>Something went wrong</h2>
82
- <p className="text-muted-foreground">{error.message}</p>
95
+ <div role="alert">
96
+ <p>Something went wrong: {error.message}</p>
83
97
  <button onClick={reset}>Try again</button>
84
98
  </div>
85
99
  );
86
100
  }
87
101
  ```
88
102
 
89
- ## Gotchas
103
+ ### Verification
104
+
105
+ ```bash
106
+ npm run build 2>&1 | tail -20 # or pnpm/yarn equivalent
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Pattern 2 — SwiftUI iOS / visionOS view
112
+
113
+ (Full version available as the `.swift.md` variant of this template.)
114
+
115
+ ### What gets created
116
+
117
+ - `${paths.swift_source}/Features/<feature>/Views/<Name>View.swift`
118
+ - `${paths.swift_source}/Features/<feature>/ViewModels/<Name>ViewModel.swift`
119
+ - `${paths.swift_source}/Features/<feature>/Models/<Name>Response.swift`
120
+
121
+ ### Sketch
122
+
123
+ ```swift
124
+ struct <Name>View: View {
125
+ @StateObject private var viewModel = <Name>ViewModel()
126
+ var body: some View {
127
+ Group {
128
+ if viewModel.isLoading { ProgressView() }
129
+ else if let err = viewModel.error { ErrorState(message: err) { Task { await viewModel.load() } } }
130
+ else { content }
131
+ }
132
+ .task { await viewModel.load() }
133
+ }
134
+ private var content: some View { /* real content */ EmptyView() }
135
+ }
136
+
137
+ @MainActor
138
+ final class <Name>ViewModel: ObservableObject {
139
+ @Published var data: <Name>Response?
140
+ @Published var isLoading = false
141
+ @Published var error: String?
142
+ func load() async { /* fetch and decode */ }
143
+ }
144
+ ```
145
+
146
+ **Critical**: With `JSONDecoder.keyDecodingStrategy = .convertFromSnakeCase`, mismatched property names decode to nil silently. Hand-verify every Decodable property against a real API response.
147
+
148
+ ### Verification
149
+
150
+ ```bash
151
+ xcodebuild -scheme <Target>_iOS -destination 'generic/platform=iOS Simulator' build | tail -20
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Pattern 3 — FastAPI Jinja-rendered page
90
157
 
91
- - **ALWAYS use `page-container`** class on root div — never `container mx-auto`
92
- - **Suspense boundaries** are REQUIRED for pages using `use(params)` or `useSearchParams()`
93
- - **protectedProcedure** for ALL tRPC queries that need auth
94
- - **No `sm:page-container`** — only exception is mobile chat layouts
95
- - **Breadcrumbs**: Add to PageHeader if page is nested (e.g., `/crm/contacts/[id]`)
158
+ ### What gets created
96
159
 
97
- ## Process
160
+ - `${paths.python_source}/routers/<name>.py` (Jinja-rendering handler)
161
+ - `${paths.python_templates}/<name>.html`
98
162
 
99
- 1. Ask user: What URL path? What data does it fetch?
100
- 2. Determine route segment: `src/app/(app)/[section]/[subsection]/`
101
- 3. Create all 3 files (page.tsx, loading.tsx, error.tsx)
102
- 4. If data-fetching: verify tRPC router exists, add query
103
- 5. Run `npx tsc --noEmit` to verify types
163
+ ### Sketch
164
+
165
+ ```python
166
+ from fastapi import APIRouter, Depends, Request
167
+ from fastapi.responses import HTMLResponse
168
+ from fastapi.templating import Jinja2Templates
169
+
170
+ from ._shared import get_current_user
171
+
172
+ router = APIRouter()
173
+ templates = Jinja2Templates(directory="${paths.python_templates}")
174
+
175
+ @router.get("/<route>", response_class=HTMLResponse)
176
+ async def render(request: Request, user: dict = Depends(get_current_user)):
177
+ return templates.TemplateResponse("<name>.html", {"request": request, "user": user})
178
+ ```
179
+
180
+ **Critical**: this pattern serves HTML from FastAPI directly; for pure-API endpoints, use `/massu-scaffold-router` instead.
181
+
182
+ ---
183
+
184
+ ## Pattern 4 — Axum (Rust) HTML handler
185
+
186
+ ### What gets created
187
+
188
+ - `${paths.rust_source}/handlers/<name>.rs`
189
+ - Wire-up in `src/main.rs` or `src/router.rs` via `.route("/<path>", get(<name>::handler))`.
190
+
191
+ ### Sketch
192
+
193
+ ```rust
194
+ use axum::{response::Html, routing::get, Router};
195
+
196
+ pub fn router() -> Router {
197
+ Router::new().route("/<path>", get(handler))
198
+ }
199
+
200
+ async fn handler() -> Html<&'static str> {
201
+ Html("<!doctype html><meta charset='utf-8'><title>Page</title><h1>Hello</h1>")
202
+ }
203
+ ```
204
+
205
+ ### Verification
206
+
207
+ ```bash
208
+ cargo build 2>&1 | tail -10 && cargo test --quiet
209
+ ```
210
+
211
+ ---
104
212
 
105
213
  ## START NOW
106
214
 
107
- Ask the user what page to create and where it should live in the route hierarchy.
215
+ Ask the user (in this order):
216
+
217
+ 1. **Which target?** TS / Next.js · Swift / SwiftUI · Python / FastAPI · Rust / Axum. Default to `framework.primary` from `massu.config.yaml` if the user is unsure.
218
+ 2. What's the URL path / feature name?
219
+ 3. What does the page render, and which API endpoint feeds it?
220
+ 4. Any auth requirements? (logged-in only · role-gated · biometric-gated for sensitive actions)