AI-Augmented Software Engineering · 7-Week Program
Getting started with Claude Code: setup, mental models, and deep codebase investigation.
29 March 2025
4 April 2025
Mini Project 1 — Week 1 Capstone
Hono's layered module architecture. Dependencies flow downward — no circular imports exist.
flowchart TB
subgraph Layer1["Layer 1 — Utilities"]
u1["utils/url.ts"]
u2["utils/html.ts"]
u3["utils/cookie.ts"]
u4["utils/jwt"]
u5["utils/encode.ts"]
u6["utils/body.ts"]
end
subgraph Layer2["Layer 2 — Routers"]
r1["router.ts"]
r2["RegExpRouter"]
r3["TrieRouter"]
r4["SmartRouter"]
r5["LinearRouter"]
r6["PatternRouter"]
end
subgraph Layer3["Layer 3 — Core"]
c1["types.ts"]
c2["request.ts"]
c3["context.ts"]
c4["http-exception.ts"]
end
subgraph Layer4["Layer 4 — Engine"]
e1["compose.ts"]
e2["hono-base.ts"]
e3["hono.ts"]
end
subgraph Layer5["Layer 5 — Helpers"]
h1["cookie"]
h2["html"]
h3["streaming"]
h4["ssg"]
h5["websocket"]
h6["conninfo"]
end
subgraph Layer6["Layer 6 — Middleware"]
m1["jwt"]
m2["cors"]
m3["csrf"]
m4["logger"]
m5["basic-auth"]
m6["bearer-auth"]
m7["compress"]
m8["secure-headers"]
end
subgraph Layer7["Layer 7 — Adapters"]
a1["Bun"]
a2["Deno"]
a3["CF Workers"]
a4["AWS Lambda"]
a5["Vercel"]
a6["Netlify"]
end
subgraph Layer8["Layer 8 — Client"]
cl1["client.ts"]
end
Layer1 --> Layer2
Layer1 --> Layer3
Layer2 --> Layer4
Layer3 --> Layer4
Layer4 --> Layer5
Layer4 --> Layer6
Layer5 --> Layer6
Layer4 --> Layer7
Layer5 --> Layer7
Layer4 --> Layer8
style Layer1 fill:#8d99ae,color:#fff
style Layer2 fill:#ff9f1c,color:#fff
style Layer3 fill:#4361ee,color:#fff
style Layer4 fill:#4361ee,color:#fff
style Layer5 fill:#f15bb5,color:#fff
style Layer6 fill:#2ec4b6,color:#fff
style Layer7 fill:#9b5de5,color:#fff
style Layer8 fill:#fee440,color:#000
How a GET request flows through Hono: client → adapter → router → middleware → handler → response.
sequenceDiagram
participant Client
participant Adapter as Platform Adapter
participant Hono as Hono / HonoBase
participant Router as SmartRouter
participant Compose as compose.ts
participant MW as Middleware Stack
participant Handler as Route Handler
participant Ctx as Context
Client->>Adapter: HTTP GET /api/users
Adapter->>Hono: fetch(request, env)
Hono->>Ctx: new Context(req, env)
Hono->>Router: router.match(GET, /api/users)
alt RegExpRouter handles pattern
Router-->>Hono: [[handler, paramIndexMap], paramStash]
else Fallback
Router->>Router: TrieRouter.match()
Router-->>Hono: [[handler, params]]
end
Hono->>Compose: compose(middleware, onError, onNotFound)
loop For each middleware
Compose->>MW: middleware[i](context, next)
MW->>Compose: await next()
end
Compose->>Handler: handler(context)
Handler->>Ctx: c.json(users)
loop Unwind middleware stack
MW->>MW: Post-processing
end
Compose-->>Hono: context.res
alt Error thrown
alt instanceof Error and onError
Compose->>Hono: onError(err, context)
else
Compose->>Compose: re-throw
end
end
Hono-->>Adapter: Response
Adapter-->>Client: HTTP 200 JSON
State transitions of the Context object during request processing.
stateDiagram-v2
[*] --> Created: new Context(req, env)
Created --> HeadersSet: c.header()
Created --> StatusSet: c.status()
HeadersSet --> HeadersSet: more headers
HeadersSet --> StatusSet: c.status()
StatusSet --> HeadersSet: c.header()
StatusSet --> Finalized: c.json / c.text / c.html
HeadersSet --> Finalized: c.json / c.text / c.html
Created --> Finalized: c.json / c.text / c.html
Created --> Redirected: c.redirect()
Redirected --> Finalized: 301/302 Response
Finalized --> ResponseSent: return to adapter
Created --> ErrorState: throw Error
Finalized --> ErrorState: post-processing throws
ErrorState --> ErrorHandled: onError()
ErrorHandled --> Finalized: overwrites res
ErrorState --> Unhandled: no onError
Unhandled --> [*]: propagates
Created --> NotFound: no route
NotFound --> Finalized: 404
ResponseSent --> [*]: delivered
Interactive force-directed graph of all 184 modules and 470 import relationships. Drag nodes, zoom, and search.
Findings from AI-assisted source code review of honojs/hono v4.12.9.
| # | Severity | File | Finding | Status |
|---|---|---|---|---|
| 1 | MEDIUM | compose.ts:53 | Error handling only catches Error instances. Non-Error throws bypass onError and propagate uncaught. | By Design |
| 2 | LOW | compose.ts:34 | next() called multiple times throws generic Error with no middleware context. | Confirmed |
| 3 | MEDIUM | smart-router/ | SmartRouter silently swallows UnsupportedPathError from RegExpRouter on fallback. | By Design |
| 4 | LOW | hono-base.ts | Default errorHandler returns plain text with no request ID for production debugging. | Enhancement |
| 5 | HIGH | utils/jwt/ | JWT implements crypto from scratch via Web Crypto API. Custom crypto carries higher risk. | Risk |
| 6 | MEDIUM | middleware/jwt/ | JWT middleware depends on internal custom crypto. Edge cases depend on custom code correctness. | Risk |
| 7 | LOW | package.json | Home directory package.json had two concatenated JSON objects (malformed). Broke npm system-wide. | Fixed |
| 8 | LOW | .gitignore | Originally only had node_modules/. Missing .env, dist/, .claude/, *.log. | Fixed |
Mastering prompt engineering and spec-driven development with Claude Code.
4–7 April 2026
7–11 April 2026
URL Shortener built spec-first with full requirement traceability
Four-part spec-driven development pipeline: prompts → spec → implementation → tests.
URL shortening and redirect flow — POST /shorten and GET /:code lifecycle.
sequenceDiagram
participant Client
participant API as Hono API
participant Validator as URL Validator
participant Store as Data Store
participant Analytics as Analytics Recorder
rect rgb(22, 27, 34)
Note over Client,Analytics: POST /shorten Flow
Client->>API: POST /shorten { url, expiresAt? }
API->>API: Check rate limit (60 req/min per IP)
alt Rate limit exceeded
API-->>Client: 429 Too Many Requests + Retry-After
end
API->>Validator: Validate URL format, length, scheme
alt Invalid URL
Validator-->>API: Validation failed
API-->>Client: 400 { error: { code: 400, message } }
end
Validator->>Validator: Check blocklist
alt Blocklisted domain
Validator-->>API: Domain blocked
API-->>Client: 403 { error: { code: 403, message } }
end
API->>Store: Check for existing URL
alt Duplicate URL
Store-->>API: Existing record found
API-->>Client: 409 { existing shortUrl }
end
API->>API: Generate unique short code (6-8 chars)
API->>Store: Save { shortCode, originalUrl, createdAt, expiresAt }
Store-->>API: Saved
API-->>Client: 201 { shortUrl, originalUrl, shortCode, createdAt, expiresAt }
end
rect rgb(22, 27, 34)
Note over Client,Analytics: GET /:code Redirect Flow
Client->>API: GET /:code
API->>Store: Lookup by shortCode
alt Not found
Store-->>API: null
API-->>Client: 404 { error: { code: 404, message } }
end
Store-->>API: URL record
API->>API: Check expiresAt
alt Expired
API-->>Client: 410 { error: { code: 410, message: "URL has expired" } }
end
alt Deleted
API-->>Client: 410 { error: { code: 410, message: "URL has been deleted" } }
end
API->>Analytics: Record event { timestamp, referrer, ipHash }
Analytics-->>API: Recorded
API-->>Client: 301 Location: originalUrl
end
Entity relationships: urls and analytics_events tables.
erDiagram
urls {
string id PK "UUID primary key"
string original_url "Original long URL (max 2048 chars)"
string short_code UK "Unique 6-8 alphanumeric code"
datetime created_at "Creation timestamp"
datetime expires_at "Optional expiration timestamp"
boolean is_active "false if deleted"
}
analytics_events {
string id PK "UUID primary key"
string url_id FK "References urls.id"
datetime accessed_at "Timestamp of the redirect"
string referrer "HTTP Referer header value"
string ip_hash "SHA-256 hash of client IP"
}
urls ||--o{ analytics_events : "has"
URL state transitions: active → expired → deleted.
stateDiagram-v2
[*] --> Active : POST shorten creates URL
Active --> Expired : expires_at reached
Active --> Deleted : DELETE called
Expired --> Deleted : cleanup or DELETE called
Deleted --> [*]
Requirement → code → test → status mapping for all REQ-SHORT requirements.
| REQ-ID | Description | Code File | Test File | Status |
|---|---|---|---|---|
| REQ-SHORT-001 | Shorten valid URL into unique short code | routes/shorten.ts, lib/shortener.ts, lib/store.ts | shorten.test.ts | PASS |
| REQ-SHORT-002 | Redirect short URL to original | routes/redirect.ts | redirect.test.ts | PASS |
| REQ-SHORT-003 | Track analytics per redirect | routes/redirect.ts, routes/analytics.ts, lib/store.ts | analytics.test.ts, redirect.test.ts | PASS |
| REQ-SHORT-004 | Optional URL expiration (410 on expired) | routes/shorten.ts, routes/redirect.ts | expiry.test.ts, redirect.test.ts | PASS |
| REQ-SHORT-005 | Input validation, blocklist, duplicates | lib/validator.ts, routes/shorten.ts | validator.test.ts, shorten.test.ts | PASS |
| REQ-SHORT-006 | Delete short URL (soft delete) | routes/delete.ts | — | PARTIAL |
| REQ-SHORT-007 | Consistent error response envelope | routes/*.ts, index.ts | shorten.test.ts | PASS |
Total: 7 requirements • Fully covered: 6 • Partially covered: 1 • Not covered: 0
Building a governed AI-augmented development pipeline: slash commands, governance hooks, audit logging, and measured ROI.
12 April 2026
18 April 2026
A drop-in .claude/ governance layer with measured ROI
Four-part governance pipeline: workflow analysis → slash commands → safety hooks → measured ROI.
Four-stage pipeline with hard gates — any stage failure aborts the entire run.
flowchart LR
subgraph invoke["Developer invokes /ship"]
A["Stage 1\n/review"]
end
A -->|PASS| B["Stage 2\n/test-gen"]
A -->|"FAIL\n(error findings)"| X1["Pipeline\nAborted"]
B -->|PASS| C["Stage 3\n/commit"]
B -->|"FAIL\n(tests fail)"| X2["Pipeline\nAborted"]
C -->|SUCCESS| D["Stage 4\ngh pr create"]
C -->|"FAIL\n(hook block)"| X3["Pipeline\nAborted"]
D --> E["PR Created\nwith review findings\n+ coverage data\n+ governance metadata"]
style A fill:#58a6ff,color:#fff,stroke:#1f6feb
style B fill:#bc8cff,color:#fff,stroke:#8b5cf6
style C fill:#f778ba,color:#fff,stroke:#db61a2
style D fill:#3fb950,color:#fff,stroke:#238636
style E fill:#238636,color:#fff,stroke:#196c2e
style X1 fill:#da3633,color:#fff,stroke:#b62324
style X2 fill:#da3633,color:#fff,stroke:#b62324
style X3 fill:#da3633,color:#fff,stroke:#b62324
PreToolUse hooks run before execution (prevention), PostToolUse hooks run after (logging).
sequenceDiagram
participant Dev as Developer
participant CC as Claude Code
participant Pre as PreToolUse Hooks
participant Tool as Tool Execution
participant Post as PostToolUse Hooks
participant Audit as Audit Logs
Dev->>CC: Prompt (e.g. "delete /tmp/test")
rect rgb(22, 27, 34)
Note over CC,Pre: PreToolUse Phase
CC->>Pre: validate-bash.py
alt Command is dangerous
Pre-->>CC: exit 1 + stderr BLOCKED
Pre->>Audit: Append to blocked.log
CC-->>Dev: BLOCKED: rm -rf
else Command is safe
Pre-->>CC: exit 0 (allow)
end
end
rect rgb(22, 27, 34)
Note over CC,Tool: Tool Execution
CC->>Tool: Execute command
Tool-->>CC: Result
end
rect rgb(22, 27, 34)
Note over CC,Post: PostToolUse Phase
CC->>Post: audit-log.sh
Post->>Audit: Append JSONL to audit.jsonl
Post-->>CC: exit 0
end
CC-->>Dev: Result
Four-tier override chain — Enterprise policies cannot be weakened by lower tiers.
flowchart TD
E["Enterprise\nsettings.json\n(org-wide, admin-managed)"]
P["Project\n.claude/settings.json\n(checked into repo)"]
U["User\n~/.claude/settings.json\n(personal preferences)"]
L["Local\n.claude/settings.local.json\n(gitignored)"]
E -->|overrides| P
P -->|overrides| U
U -->|overrides| L
style E fill:#da3633,color:#fff,stroke:#b62324
style P fill:#58a6ff,color:#fff,stroke:#1f6feb
style U fill:#bc8cff,color:#fff,stroke:#8b5cf6
style L fill:#484f58,color:#fff,stroke:#30363d
Every action flows through hooks into JSONL logs, queryable with jq for compliance.
flowchart LR
A1["Tool Actions\n(Bash, Edit, Write)"]
A2["User Prompts"]
A3["Session End"]
A4["Blocked Actions"]
H1["audit-log.sh"]
H2["prompt-log.sh"]
H3["session-summary.sh"]
H4["PreToolUse hooks"]
S1["audit.jsonl"]
S2["prompts.jsonl"]
S3["costs.jsonl"]
S4["blocked.log"]
R["Compliance Report"]
A1 --> H1 --> S1
A2 --> H2 --> S2
A3 --> H3 --> S3
A4 --> H4 --> S4
S1 --> R
S2 --> R
S3 --> R
S4 --> R
style A1 fill:#58a6ff,color:#fff,stroke:#1f6feb
style A2 fill:#bc8cff,color:#fff,stroke:#8b5cf6
style A3 fill:#f778ba,color:#fff,stroke:#db61a2
style A4 fill:#da3633,color:#fff,stroke:#b62324
style S1 fill:#161b22,color:#e6edf3,stroke:#30363d
style S2 fill:#161b22,color:#e6edf3,stroke:#30363d
style S3 fill:#161b22,color:#e6edf3,stroke:#30363d
style S4 fill:#161b22,color:#e6edf3,stroke:#30363d
style R fill:#3fb950,color:#fff,stroke:#238636
Connecting Claude Code to live business data via MCP — a real engineering operations tool with audit logging, governed tool calls, and chained workflows.
19 April 2026
25 April 2026
Claude Code wired to live business data via MCP — answers ops questions in under 60 seconds
Five-task build: an audit logger underpins typed GitHub MCP wrappers, two chained workflows, and the documentation/test layer that ties governance back to the code.
CLI → BaseWorkflow.run → the two workflows → tool wrappers → MCPClient. Every tool call is timed and audit-logged before returning to the workflow.
flowchart TD
CLI["main.py CLI
--workflow ..."] --> BW[BaseWorkflow.run]
BW --> WF1["MorningBriefWorkflow
WF-01"]
BW --> WF2["IncidentTriageWorkflow
WF-02"]
WF1 --> GT[github_tools wrappers]
WF1 --> DT["db_tools
(lab stub)"]
WF2 --> GT
WF2 --> DT
GT -->|mcp.call| MCP[MCPClient]
MCP -->|mcp_servers| GH[(GitHub MCP)]
MCP -->|mcp_servers| PG[(PostgreSQL MCP)]
GT --> AL[AuditLogger]
AL --> LOG[("~/.fintrack/audit.log
JSONL")]
style CLI fill:#58a6ff,color:#fff,stroke:#1f6feb
style BW fill:#bc8cff,color:#fff,stroke:#8b5cf6
style WF1 fill:#f778ba,color:#fff,stroke:#db61a2
style WF2 fill:#f778ba,color:#fff,stroke:#db61a2
style GT fill:#3fb950,color:#fff,stroke:#238636
style DT fill:#484f58,color:#fff,stroke:#30363d
style MCP fill:#bc8cff,color:#fff,stroke:#8b5cf6
style GH fill:#161b22,color:#e6edf3,stroke:#30363d
style PG fill:#161b22,color:#e6edf3,stroke:#30363d
style AL fill:#238636,color:#fff,stroke:#196c2e
style LOG fill:#161b22,color:#e6edf3,stroke:#30363d
Three MCP calls chained, each audit-logged, then Claude synthesises a four-section markdown report. Empty data sources fall through to "No data returned from <source>".
sequenceDiagram
participant Eng as Engineer
participant CLI as main.py
participant WF as MorningBriefWorkflow
participant GT as github_tools
participant DT as db_tools
participant MCP as MCPClient
participant GH as GitHub MCP
participant AL as AuditLogger
participant Claude as Claude (mcp.ask)
Eng->>CLI: --workflow morning-brief
CLI->>WF: execute()
rect rgb(22, 27, 34)
Note over WF,AL: Tool call 1 of 3
WF->>GT: get_open_prs(repo)
GT->>MCP: mcp.call("github_pull_requests")
MCP->>GH: tool call
GH-->>MCP: PRs JSON
MCP-->>GT: result
GT->>AL: log(success, duration_ms)
GT-->>WF: filtered PRs
end
rect rgb(22, 27, 34)
Note over WF,AL: Tool call 2 of 3
WF->>GT: get_priority_issues(repo)
GT->>MCP: mcp.call("github_issues")
MCP->>GH: tool call
GH-->>MCP: issues JSON
MCP-->>GT: result
GT->>AL: log(success, duration_ms)
GT-->>WF: P0/P1 issues
end
rect rgb(22, 27, 34)
Note over WF,AL: Tool call 3 of 3
WF->>DT: get_overnight_alerts()
DT-->>WF: alerts list
end
WF->>Claude: ask(prompt + 3 data sets)
Claude-->>WF: 4-section markdown
WF-->>CLI: result
CLI-->>Eng: morning brief
Four MCP calls in their own try/except so one failure cannot abort the workflow. Strict JSON parsing on Claude's response with a seven-key schema check; any parse or schema failure returns ESCALATE_FALLBACK.
sequenceDiagram
participant Eng as On-call
participant WF as IncidentTriageWorkflow
participant DT as db_tools
participant GT as github_tools
participant Claude as Claude
Eng->>WF: execute(service="payments")
rect rgb(22, 27, 34)
Note over WF,GT: Per-call try/except — graceful degradation
WF->>DT: get_error_rate(window=30)
DT-->>WF: 30-min baseline (or {} on error)
WF->>GT: search_recent_commits(hours=4)
GT-->>WF: commits (or [] on error)
WF->>GT: get_priority_issues(labels=["bug",svc])
GT-->>WF: bugs (or [] on error)
WF->>DT: get_error_rate(window=5)
DT-->>WF: 5-min snapshot (or {} on error)
end
WF->>Claude: ask(prompt with 5 placeholders)
Claude-->>WF: response (raw)
alt Valid JSON + 7 required keys present
WF-->>Eng: IncidentReport with escalate flag
else Invalid JSON or missing key
WF-->>Eng: ESCALATE_FALLBACK (escalate=true)
end
Per-call timing, SHA-256 input hashing, append-only JSONL, never raises. Six fields per entry: timestamp, workflow, tool, input_hash, status, duration_ms.
flowchart LR
A["github_tools call
e.g. get_open_prs()"]
A --> S["start =
perf_counter()"]
S --> CALL["mcp.call(tool, input)"]
CALL -->|success| H1["SHA-256 of
sorted-key JSON input"]
CALL -->|exception| H2["SHA-256 of input
(same hashing)"]
H1 --> E1["Build entry
status=success"]
H2 --> E2["Build entry
status=error"]
E1 --> WRITE["Append JSONL line
~/.fintrack/audit.log"]
E2 --> WRITE
WRITE -->|OK| OUT["return result / []"]
WRITE -->|OSError, ValueError, TypeError| STDERR["print warning
to stderr
NEVER raise"]
STDERR --> OUT
style A fill:#58a6ff,color:#fff,stroke:#1f6feb
style S fill:#484f58,color:#fff,stroke:#30363d
style CALL fill:#bc8cff,color:#fff,stroke:#8b5cf6
style H1 fill:#3fb950,color:#fff,stroke:#238636
style H2 fill:#da3633,color:#fff,stroke:#b62324
style E1 fill:#3fb950,color:#fff,stroke:#238636
style E2 fill:#da3633,color:#fff,stroke:#b62324
style WRITE fill:#238636,color:#fff,stroke:#196c2e
style STDERR fill:#da3633,color:#fff,stroke:#b62324
style OUT fill:#161b22,color:#e6edf3,stroke:#30363d
Read-only is the default tier. Write access requires a separate registry entry, manager sign-off, and elevated audit retention — per Architecture Rule 4 in fintrack-mcp-intel/CLAUDE.md.
flowchart TD
REGISTRY["Approved Server Registry
fintrack-mcp-intel/CLAUDE.md Section 2"]
subgraph readonly["READ-ONLY TIER (default)"]
GH["github-mcp
issues:read · pull_requests:read
contents:read · metadata:read"]
PG["pg-mcp
role: readonly
analytics replica"]
JIRA["jira-mcp (placeholder)
issues:read · sprints:read"]
end
subgraph writetier["WRITE TIER (gated)"]
SLACK["slack-mcp (placeholder)
post to single channel only
requires manager sign-off
elevated audit retention"]
end
GATE{{"Architecture Rule 4
read-only by default
write requires sign-off"}}
REGISTRY --> readonly
REGISTRY --> writetier
writetier -.->|enforced by| GATE
style readonly fill:#0d1117,color:#e6edf3,stroke:#3fb950
style writetier fill:#0d1117,color:#e6edf3,stroke:#da3633
style GH fill:#3fb950,color:#fff,stroke:#238636
style PG fill:#3fb950,color:#fff,stroke:#238636
style JIRA fill:#484f58,color:#fff,stroke:#30363d
style SLACK fill:#da3633,color:#fff,stroke:#b62324
style REGISTRY fill:#58a6ff,color:#fff,stroke:#1f6feb
style GATE fill:#bc8cff,color:#fff,stroke:#8b5cf6
Building a 2-agent code-review pipeline on a Python e-commerce backend, then packaging both agent prompts as versioned, parameterised Skills.
26 April 2025
2 May 2025
Two specialised review agents and two production Skills built on a deliberately-flawed e-commerce backend
Five-part build: an Architect agent and Security agent run in separate sessions, then both prompts are packaged as parameterised Skills with examples, tests, and a 100-point self-review.
Two agents in two separate Claude Code sessions. CLAUDE.md and the target source feed both; design.md is the only hand-off contract from Architect to Security; each agent's prompt is then frozen as a v1.0.0 Skill.
flowchart TD
IN1[CLAUDE.md] --> A1[Architect Agent
Session A]
IN2[processor.py] --> A1
A1 --> D[design.md]
IN1 --> A2[Security Agent
Session B]
IN2 --> A2
D --> A2
A2 --> F[findings.json]
A1 -.packaged as.-> S1[Skill: architecture-review v1.0.0]
A2 -.packaged as.-> S2[Skill: security-audit v1.0.0]
S1 --> LIB[(skills/
library)]
S2 --> LIB
Senior architect role, scope-locked to one module, with five DO-NOT exclusions to keep it out of security / tests / refactors. Output is a four-section markdown design doc plus a 2-sentence summary.
sequenceDiagram
participant Caller
participant Architect as Architect Agent
(Session A)
Caller->>Architect: ROLE + SCOPE + EXCLUSIONS
+ INPUT (CLAUDE.md, processor.py)
+ OUTPUT FORMAT
Note over Architect: stays in lane:
no security
no tests
no refactors
Architect->>Architect: analyze boundary
+ API surface
+ data flow
+ dependencies
Architect-->>Caller: design.md
(4 sections + SUMMARY)
OWASP Top 10 (2021) specialist. Receives design.md as read-only context; produces ONLY a JSON object with severity-ordered, file:line-anchored findings. No prose preamble, no schema drift.
sequenceDiagram
participant Caller
participant Security as Security Agent
(Session B)
Caller->>Security: ROLE + SCOPE + EXCLUSIONS
+ INPUT (CLAUDE.md, design.md, processor.py)
+ OUTPUT FORMAT (JSON schema)
Note over Security: design.md is
context only
(do not critique)
Security->>Security: each finding =
file:line + severity
+ OWASP category
+ impact + recommendation
Security-->>Caller: findings.json
(12 findings, severity-ordered)
skills/README.md is the index; each Skill has a SKILL.md with full 5-part structure plus Limitations, Tests, Changelog; each Skill ships an examples/ folder with typical / edge / minimal outputs.
flowchart LR
R[skills/README.md
Skill Index]
R --> AR[architecture-review/
SKILL.md v1.0.0]
R --> SA[security-audit/
SKILL.md v1.0.0]
AR --> AREX[examples/
typical.md / edge.md / minimal.md]
SA --> SAEX[examples/
typical.json / edge.json / minimal.json]
Building a complete CI/CD pipeline for HealthTrack: GitHub Actions with lint, test, coverage, security, and AI-Skill review jobs; a multi-stage production Dockerfile with non-root user and HEALTHCHECK; and a docker-compose stack with api, postgres, and redis health-checked.
3 May 2026
9 May 2026
Full GitHub Actions pipeline + multi-stage Dockerfile + 3-service compose stack
Four-part build for the HealthTrack patient-vitals API: a 4-job GitHub Actions pipeline with an AI-Skill CRITICAL gate, a multi-stage non-root Dockerfile, a 3-service compose stack with health-gated startup, and an end-to-end test that proves a real PR triggers the full pipeline.
Four jobs with explicit needs: ordering form a dependency DAG. The AI-Skill job parses the Security Audit Skill output and exits non-zero on any CRITICAL finding, blocking merge. Coverage gate at 80% and CRITICAL gate are independent — either can fail the run.
flowchart LR
PR([PR opened / push to main]) --> LINT[lint
flake8 + black]
LINT --> TEST[test
pytest --cov ≥ 80%]
LINT --> SEC[security
bandit + safety]
TEST --> AI[ai-skills
Security Audit Skill
on app/vitals.py]
SEC --> AI
AI -->|no CRITICAL| GREEN([green check
merge ready])
AI -->|CRITICAL found| BLOCK([red X
merge BLOCKED])
Stage 1 (python:3.11) installs deps into /install. Stage 2 (python:3.11-slim) copies only that prefix, drops to non-root appuser UID 10001, then runs gunicorn with a urllib HEALTHCHECK. Final image: 236 MB — the builder stage and its build toolchain never reach the registry.
flowchart TB
subgraph builder[Stage 1: builder — python:3.11]
direction TB
B1[Full python:3.11] --> B2[pip install --prefix=/install
-r requirements.txt]
end
subgraph runtime[Stage 2: runtime — python:3.11-slim]
direction TB
R1[python:3.11-slim] --> R2[ENV PYTHONDONTWRITEBYTECODE=1
PYTHONUNBUFFERED=1]
R2 --> R3[useradd appuser UID 10001]
R3 --> R4[COPY /install → /usr/local]
R4 --> R5[USER appuser]
R5 --> R6[EXPOSE 5000]
R6 --> R7[HEALTHCHECK urllib /health]
R7 --> R8[CMD gunicorn app:create_app]
end
B2 -. COPY --from=builder .-> R4
docker-compose.yml declares api + postgres:15-alpine + redis:7-alpine. api uses depends_on with condition: service_healthy, so it never starts until both backing stores answer their first healthcheck. The sequence below traces a developer push through the CI pipeline to the merge gate.
sequenceDiagram
participant Dev as Developer
participant GH as GitHub
participant CI as ci.yml
participant Skill as Security Audit Skill
participant Gate as Merge gate
Dev->>GH: git push + gh pr create
GH->>CI: trigger (pull_request)
CI->>CI: lint
par
CI->>CI: test — pytest cov ≥ 80%
and
CI->>CI: security — bandit + safety
end
CI->>Skill: security-audit --scope app/vitals.py
Skill-->>CI: JSON findings
alt no CRITICAL
CI->>Gate: exit 0
Gate-->>Dev: green check — merge ready
else CRITICAL found
CI->>Gate: exit 1
Gate-->>Dev: red X — merge BLOCKED
end
100-point rubric mapped to where each item lives in the submission and a self-graded result. validate_ci.py reports “All checks passed” across all 4 parts (Sessions A+B+C). The +10 bonus is split: PR-template customisation lands here; the AI-Skill summary in PR comments lands in Session D once the end-to-end CI run is captured.
| # | Rubric item | Where it lives | Result |
|---|---|---|---|
| 1 | CI workflow at .github/workflows/ci.yml; triggers on PR + push to main | week-6/healthtrack-api/.github/workflows/ci.yml | PASS · 15/15 |
| 2 | 4 jobs with needs: ordering; coverage ≥ 80%; CRITICAL gate | ci.yml jobs lint → {test, security} → ai-skills | PASS · 25/25 |
| 3 | Multi-stage Dockerfile; non-root; HEALTHCHECK; EXPOSE; Python env vars | week-6/healthtrack-api/Dockerfile | PASS · 20/20 |
| 4 | 3-service compose; healthchecks; named volumes; depends_on: condition: service_healthy | week-6/healthtrack-api/docker-compose.yml | PASS · 15/15 |
| 5 | .env.example committed; .env gitignored; no real secrets in git log | week-6/healthtrack-api/.env.example + .gitignore | PASS · 10/10 |
| 6 | /health with database + cache sub-checks; 503 on degradation | app/__init__.py /health route + 3 mocked tests | PASS · 10/10 |
| 7 | REPORT.md with concrete-evidence reflection answers | week-6/healthtrack-api/REPORT.md | PASS · 5/5 |
| B1 | PR template — Performance / Migrations / Feature Flags | .github/pull_request_template.md | BONUS · +5 |
| B2 | AI-Skill review summary in PR comments (real CI run) | Session D end-to-end test | pending Session D |
Structured capstone project integrating all skills from the program.
9 May 2025
18 May 2026
Production-grade Flask REST API built end-to-end via the AI-Augmented SDLC
Seven-phase capstone extending a notification service scaffold with REQ-007 (stats endpoint), three test suites, a 6-stage CI pipeline with coverage gate, Docker deployment, two ADRs, and a pre-commit governance hook. Integrates techniques from all prior weeks: spec-driven development (W2), governance hooks (W3), audit middleware (W4), structured prompts (W5), and CI/CD + Docker (W6).
Every request flows through the audit middleware before reaching the blueprint route. The notification service validates input, delegates to the email provider, and records history. The audit logger captures the response on the way out.
flowchart TD
Client([Client]) --> AuditIn[audit_logger.before_request]
AuditIn --> Route[Blueprint Route]
Route --> Validate[NotificationService.validate]
Validate -->|valid| Send[NotificationService.send]
Validate -->|invalid| Err400([400 Bad Request])
Send --> Provider[email_provider]
Provider -->|success| Record[_record_history]
Provider -->|failure| Retry[Retry Logic]
Retry -->|retries left| Provider
Retry -->|exhausted| Err500([500 + retry_history])
Record --> AuditOut[audit_logger.after_request]
Err400 --> AuditOut
Err500 --> AuditOut
AuditOut --> Client
When the email provider fails, the service retries up to MAX_RETRY_ATTEMPTS times with exponential backoff. Each attempt is logged. On exhaustion, a RuntimeError is raised with retry_history attached as an attribute (see ADR-003).
stateDiagram-v2
[*] --> Attempt1
Attempt1 --> Success : provider returns message_id
Attempt1 --> Attempt2 : provider raises exception
Attempt2 --> Success : provider returns message_id
Attempt2 --> Attempt3 : provider raises exception
Attempt3 --> Success : provider returns message_id
Attempt3 --> Exhausted : provider raises exception
Success --> AuditSuccess : record_history status=success
AuditSuccess --> [*]
Exhausted --> AuditFailed : record_history status=failed
AuditFailed --> RaiseError : RuntimeError + retry_history
RaiseError --> [*]
GET /api/notifications/stats computes metrics on-demand from the in-memory notification history (ADR-002). A single pass over the bounded list aggregates by_type, by_status, and tracks last_sent. Null-type records are mapped to “unknown” via a defensive guard.
flowchart LR
History[_notification_history\nlist max 100] --> Iterate[get_stats iterates]
Iterate --> ByType[by_type counter\nemail/sms/alert]
Iterate --> ByStatus[by_status counter\nsuccess/failed]
Iterate --> LastSent[max timestamp\nlast_sent]
ByType --> Result[return dict\ntotal + by_type + by_status + last_sent]
ByStatus --> Result
LastSent --> Result
Six-stage GitHub Actions pipeline: lint gates everything, then unit tests (with coverage gate), integration tests, regression tests, and security audit run in parallel. Docker build is the final stage — it builds the image, starts the container, and curls /api/health to prove the deployment works.
flowchart TD
Trigger([push / PR]) --> Lint[lint\nflake8 src/ tests/]
Lint --> Unit[unit-tests\npytest --cov-fail-under=80]
Lint --> Integration[integration-tests\npytest tests/integration/]
Lint --> Regression[regression-tests\npytest tests/regression/]
Lint --> Security[security\npip-audit]
Unit --> Docker[docker-build\nbuild + run + curl /api/health]
Integration --> Docker
Regression --> Docker
Security --> Docker
Docker --> Ready([ready to merge])
The pre-commit hook fires on every commit touching week-7/capstone-py/. It runs flake8, then unit tests, then writes a JSON audit entry to .audit-log.jsonl. Any failure blocks the commit. The hook is installed at the repo root and adapted to cd into the scaffold directory first.
flowchart TD
Commit([git commit]) --> Check{Files in\nweek-7/capstone-py?}
Check -->|no| Skip([Skip hook])
Check -->|yes| Flake[flake8 src/ tests/]
Flake -->|pass| Tests[pytest tests/unit/]
Flake -->|fail| Block([COMMIT BLOCKED])
Tests -->|pass| Audit[Write .audit-log.jsonl]
Tests -->|fail| Block
Audit --> Approve([Commit approved])