mit einem Klick
03-design-draft
// Drafts the outbound integration's design document at .integration-pipeline/integration-design.md in Status Proposed.
// Drafts the outbound integration's design document at .integration-pipeline/integration-design.md in Status Proposed.
Implements the feature per the architecture doc using nhcore-first, NeoHaskell style.
Reads build and test logs, identifies the root cause, and edits implementation files to fix it.
Iterates build and test until both are green, then captures hlint warnings without gating.
Addresses every blocker finding from phases 12 and 13 and re-runs build and tests.
Use when implementing a new NeoHaskell feature end-to-end — from ADR draft through PR merge — and the work needs the structured 17-phase pipeline with ADR, security and performance reviews (each grounded against feature complexity and the Jess persona), outside-in test design, build loop, and PR/CI handling. Triggers on 'run feature pipeline', 'feature pipeline', 'start pipeline', 'implement feature', 'new NeoHaskell feature', 'implement issue
NeoHaskell code implementation guide. Use when implementing features, writing tests, build/test loops, or any task requiring NeoHaskell code. Handles pipeline phases 7-9 (tests, implementation, build loop), 12-13 (fix reviews, final build), and 16 (fix bot comments).
| name | 03-design-draft |
| description | Drafts the outbound integration's design document at .integration-pipeline/integration-design.md in Status Proposed. |
| kind | leaf |
| executor | opus |
| model | claude-opus-4-7 |
Produces .integration-pipeline/integration-design.md matching the integration design template, with Status: Proposed. The design doc is the input to every later phase (security, perf, devex, architecture, test spec).
This pipeline never writes to docs/decisions/. Repo-level ADRs are reserved for repo-level architecture; an outbound integration is scoped to be portable to a separate repo, and an ADR slot here would orphan if/when that move happens. The design doc therefore lives in the per-checkout .integration-pipeline/ directory and travels with the integration's source.
integration_name — string, from pipeline.py get integration_name.issue_number — string, from pipeline.py get issue_number (may be empty).module_name — string, Pascal-case suffix (e.g. Stripe), from pipeline.py get module_name.module_path — string, from pipeline.py get module_path (default integrations/Integration/<module_name>.hs).design_path — string, from pipeline.py get design_path (always .integration-pipeline/integration-design.md).integration_name and design_path non-empty; design_path starts with .integration-pipeline/.design_path already exists with content — re-runs after maintainer feedback go through pipeline.py reset or an explicit overwrite gesture.Status: Proposed.pipeline.py status shows phase 3 awaiting approval.Assumptions:
.integration-pipeline/ directory exists (phase 1 created it).integrations/Integration/<module_name>/ and integrations/Integration/<module_name>.hs.If any assumption fails (style guide unclear, design template missing), refuse and ask.
The draft must contain every section listed below. Wording mirrors the NeoHaskell ADR template so reviewers familiar with feature ADRs see no surprise, but the doc is not an ADR and must not claim a docs/decisions/ number.
# <integration_name> integration design — title.Status: Proposed — single line, on its own.## Context — what external system this integrates with, why now, what user need it serves, the link to issue #<issue_number> if present.## Decision drivers — bulleted list of forces driving the design (deadline, security, perf budget, Jess affordance).## Considered options — at least two alternatives, with one-paragraph trade-off each. If the integration is HTTP request/response (no streaming, no long-lived socket, no daemon, no non-HTTP transport), one of the considered options MUST be "wrap Integration.Http with a smart-constructor + Request/Response/Internal layout" (the Brevo / OpenRouter / Oura pattern), and that option MUST win the trade-off unless the design names a concrete blocker (streaming response, persistent connection, scheduled poller, non-HTTP wire). "For flexibility" or "for performance" is not a valid blocker — name the technical reason.## Decision outcome — the chosen option, restated in one paragraph. If the chosen option is anything other than "wrap Integration.Http", ## Decision drivers must contain a line naming the specific blocker (streaming / persistent connection / daemon / non-HTTP wire). See ../references/nhcore-context.md "Framework-provided defaults" for the canonical wrap-Http layout.## Trust boundary — what crosses the network, what secrets are needed, where they are sourced, what error modes the caller sees. Secrets MUST be sourced from the user's project Config.hs via the Config.field @(Redacted Text) "<integrationName>ApiKey" |> Config.required |> Config.envVar "<UPPER_SNAKE>" |> Config.secret DSL, threaded through the integration's send-style smart constructor with a (?config :: config, HasField "<fieldName>" config (Redacted Text)) constraint, and carried as Redacted Text end-to-end. The integration MUST NOT read environment variables itself (no System.Environment.getEnv, no Env.lookupEnv, no "${VAR}" literal-expansion pattern), MUST NOT accept raw Text keys in its public API, and MUST have exactly one Redacted.unwrap call site — the line inside <Module>.Internal that constructs the outbound Http.ApiKey / Http.Bearer / Http.Basic header tuple. See ../references/security-methodology.md §8a for the rule and ../references/nhcore-context.md "Framework-provided defaults" for the canonical user-side declaration.## Public API — every public function, type, and error the integration exports, with full signatures and a one-line doc-by-example for each. This is the surface the DevEx review (phase 6) inspects.## Out of scope — adjacent concerns explicitly excluded (e.g. inbound webhooks if this is request/response only).## Consequences — positive and negative downstream effects.## Portability note — one short paragraph confirming that this integration is built to be portable to a separate repo and that no design artefact has been written to docs/decisions/ or docs/architecture/.Compute the target path from pipeline.py get design_path. Refuse if the file exists and is non-empty.
Draft each section — keep code examples short, name only the public API the integration introduces, reference #<issue_number> once at the top of ## Context.
Apply the NeoHaskell style to every code example: pipes, do-blocks, case ... of, Task/Result, no let..in, no where, no $, no single-letter type params. Refuse if the chosen API forces any of these.
Apply the Jess persona check (../references/jess-persona.md): every public function reachable from the ## Public API block should be usable in 15 minutes from autocomplete alone. Refuse the design if it is not.
Apply the secret-sourcing check. If the integration needs any secret (API key, OAuth token, webhook signing secret, bearer), verify the draft (a) declares the canonical Config.field @(Redacted Text) "<integrationName>ApiKey" |> Config.required |> Config.envVar "<UPPER_SNAKE>" |> Config.secret snippet in ## Trust boundary, (b) types the send smart constructor with a (?config :: config, HasField "<fieldName>" config (Redacted Text)) constraint, (c) carries the value as Redacted Text through the Request record, (d) names exactly one Redacted.unwrap site (inside <Module>.Internal.toHttpRequest or equivalent). Refuse the draft if any of these are missing or if the draft proposes any of: System.Environment.getEnv, Env.lookupEnv, "${VAR}" literal-expansion, or a raw Text secret in the public API. See ../references/security-methodology.md §8a.
Apply the wrap-Http default check. If the integration's outbound shape is HTTP request/response over HTTPS — no WebSocket, no Server-Sent Events, no long-lived TCP/gRPC connection, no background polling daemon, no scheduled timer, no non-HTTP wire protocol — the draft MUST propose wrapping Integration.Http (the Brevo / OpenRouter / Oura pattern: <Module>.hs re-export shell + <Module>/Request.hs + <Module>/Response.hs + <Module>/Internal.hs with toHttpRequest and a ToAction (Request command) instance). Refuse the draft if (a) it hand-rolls an HTTP client, (b) it reaches for http-client / wreq / req / servant-client / etc. directly, or (c) ## Considered options does not include a wrap-Http alternative. Departures are allowed only when ## Decision drivers names a concrete blocker (streaming response, persistent connection, scheduled poller, non-HTTP wire) — "for flexibility" or "for performance" without a measured cost is not a valid blocker. See ../references/nhcore-context.md "Framework-provided defaults".
Verify the ## Portability note section is present and does not reference docs/decisions/ or docs/architecture/ as a target.
Write the file with Status: Proposed.
Run python3 .claude/skills/integration-pipeline-preview/scripts/pipeline.py complete 3.
pipeline.py get design_path (default .integration-pipeline/integration-design.md) exists, Status: Proposed, all 11 sections present.waiting_for_approval for phase 3.docs/decisions/... or docs/architecture/... → refuse and explain: outbound integrations are scoped to be portable; the repo-level ADR slots would orphan.## Portability note would be missing or would reference docs/decisions/ / docs/architecture/ → refuse the draft.System.Environment.getEnv, Env.lookupEnv, embeds "${VAR}" literals expected to be expanded by the integration or by Integration.Http, or otherwise sources secrets outside the user's project Config.hs — refuse the draft and rewrite to use the Config.field @(Redacted Text) ... |> Config.required |> Config.envVar "..." |> Config.secret DSL on the user side, with the integration consuming the value via a (?config :: config, HasField "<fieldName>" config (Redacted Text)) constraint. The exception is the legacy pattern used by Integration.OpenRouter, Integration.Oura, and Integration.Http.Auth's ${VAR} expansion — those are pre-existing tech debt and may be cited only as work to be migrated, never as templates to copy.Text secret in the public API of the integration — i.e. any function or record field on the integration's public surface that takes a plain Text for an API key, OAuth token, webhook signing secret, or bearer — refuse the draft and rewrite to either (a) source the secret from ?config.<field> (preferred), or (b) accept Redacted Text only.Redacted.unwrap site in the integration — refuse and require a single audited unwrap inside the request-builder (<Module>.Internal.toHttpRequest-style function) that constructs the outbound header tuple.Integration.Http — refuse. ~95% of outbound integrations are HTTPS request/response and MUST follow the canonical <Module>.hs + <Module>/Request.hs + <Module>/Response.hs + <Module>/Internal.hs (with toHttpRequest and ToAction (Request command)) layout used by Integration.Brevo, Integration.OpenRouter, Integration.Oura. Refuse if the design (a) hand-rolls an HTTP client, (b) imports http-client / wreq / req / servant-client directly, or (c) omits a wrap-Http alternative from ## Considered options. The only valid departures are concrete, named blockers in ## Decision drivers: WebSocket / SSE / chunked-streaming response, persistent TCP/gRPC connection, background polling daemon, scheduled timer, or non-HTTP wire (raw TCP / UDP / IPC / file-system notify). "For flexibility" or "for performance" without a measured cost is not a valid blocker.