Skip to content

docs: add README with project logo#1

Merged
mahirl merged 1 commit intomainfrom
keeb/logo-readme
Jan 28, 2026
Merged

docs: add README with project logo#1
mahirl merged 1 commit intomainfrom
keeb/logo-readme

Conversation

@keeb
Copy link
Copy Markdown
Contributor

@keeb keeb commented Jan 28, 2026

No description provided.

Comment thread README.md

## License

MIT
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

@mahirl mahirl merged commit 93c01f1 into main Jan 28, 2026
@adamhjk adamhjk deleted the keeb/logo-readme branch January 29, 2026 06:36
stack72 added a commit that referenced this pull request Mar 17, 2026
… bundling failure

- Move bundleSourceFactory call inside the non-raw driver branch so raw
  executions never trigger self-contained bundling (HIGH #2)
- Switch to promise-based memoization to avoid duplicate concurrent builds
- Restore error handling: log a warning and rethrow on bundling failure,
  resetting the cached promise so subsequent calls can retry (HIGH #1)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
stack72 added a commit that referenced this pull request Mar 19, 2026
Agents now see "search before you build" as Rule #1 in every swamp-managed
CLAUDE.md, prompting them to check local types and community extensions
before creating custom models. Previously this guidance only appeared in
the swamp-extension-model skill, which agents loaded too late — after
already deciding to build from scratch.

Closes #757

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
stack72 added a commit that referenced this pull request Mar 19, 2026
Agents now see "search before you build" as Rule #1 in every swamp-managed
CLAUDE.md, prompting them to check local types and community extensions
before creating custom models. Previously this guidance only appeared in
the swamp-extension-model skill, which agents loaded too late — after
already deciding to build from scratch.

Closes #757

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
fix: use correct command `swamp extension pull` instead of `resolve`

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
stack72 added a commit that referenced this pull request Mar 19, 2026
Agents now see "search before you build" as Rule #1 in every swamp-managed
CLAUDE.md, prompting them to check local types and community extensions
before creating custom models. Previously this guidance only appeared in
the swamp-extension-model skill, which agents loaded too late — after
already deciding to build from scratch.

Closes #757

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
fix: use correct command `swamp extension pull` instead of `resolve`

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
stack72 added a commit that referenced this pull request Mar 19, 2026
feat: search community extensions before creating custom models

## Summary

- Rewrites Rule #1 in the CLAUDE.md template from "Extension models for
service integrations" to **"Search before you build"**, with explicit
steps: search local types → search community extensions (`swamp
extension pull`) → install if found → only then create custom
- Adds a community search step to the `swamp-model` skill workflow
- Adds Scenario 5 ("Community Extension Found") to the
`swamp-extension-model` skill showing the happy path where a community
extension exists

## User Impact

Previously, agents only saw guidance to "create an extension model" in
the CLAUDE.md Rules section. The `swamp-extension-model` skill
documented the search-first flow, but agents only loaded that skill
*after* deciding to create a model — too late. Now agents see the
search-first guidance immediately in Rule #1, prompting them to run
`swamp extension search` before building anything custom.

## Test Plan

- [x] New unit test: `RepoService.init generates CLAUDE.md with
extension search guidance` — asserts generated CLAUDE.md contains
`extension search`, `Search before you build`, and `swamp model type
search`
- [x] All 78 repo service tests pass
- [x] Full test suite passes
- [x] `deno check`, `deno lint`, `deno fmt` all pass
- [x] `deno run compile` — binary recompiled
- [x] **Manual verification**: Asked an agent to "create me a model that
I can use to create an aws ec2 instance" — the agent correctly ran
`swamp model type search "ec2"` followed by `swamp extension search "ec2
instance"` before attempting to create anything, then found a community
extension and used `swamp extension pull` to install it

Closes #757

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
johnrwatson pushed a commit that referenced this pull request Apr 7, 2026
## Summary

Adds a GitHub Action that auto-moves every new issue filed against
`systeminit/swamp` into the swamp.club lab, posts a comment on the
GitHub issue linking to the new lab home, and closes the GitHub issue.
Also migrates the existing `@swamp/issue-lifecycle` extension client to
the numeric lab issue id contract introduced by
systeminit/swamp-club#364 and #369.

## User-visible behaviour change

When anyone files an issue against `systeminit/swamp`:

1. The issue body (with an `Automoved by swampadmin from GitHub issue
#<n>` footer appended) is POSTed to
`https://swamp.club/api/v1/lab/issues/ensure`.
2. A comment is posted on the GitHub issue that reads:
> Thank you for opening an issue. This issue is now managed in the
Claude lab.
   > 
   > https://swamp.club/lab/<number>
3. The GitHub issue is closed.

If the swamp-club call fails for any reason, the action fails and **the
GitHub issue stays open** — there is no failure mode in which we close a
GitHub issue without confirming the lab counterpart exists. See "Why the
close is gated" below.

The previous behaviour (welcome comment + `needs-triage` label, with a
maintainer skip) is removed. Every new issue gets moved unconditionally
— issues filed by maintainers go through the same path as community
issues, by design.

## Contract change in swamp-club#369

systeminit/swamp-club#364 introduced a sequential, human-friendly
`number` on every lab issue (allocated by an atomic Mongo counter) and
migrated the UI route from `/lab/{uuid}` to `/lab/{number}`.

systeminit/swamp-club#369 finished the job: it collapsed
`/api/v1/lab/issues/{uuid}` and `/api/v1/lab/issues/by-number/{n}` into
a single `/api/v1/lab/issues/{number}` tree, so every endpoint that used
to take a UUID now takes the sequential number. The internal UUID is
still the Mongo foreign key (comments, events, lifecycle entries
reference it) but **it never appears in a URL**.

Endpoints affected, all of which this client now hits via the new shape:

| Endpoint | Method |
|---|---|
| `/api/v1/lab/issues/{number}` | `PATCH` (used by `transitionStatus`) |
| `/api/v1/lab/issues/{number}/lifecycle` | `POST` (used by
`postLifecycleEntry`) |
| `/api/v1/lab/issues/ensure` | `POST` (unchanged — still no path id) |

This client previously cached the UUID it received from `/ensure` and
interpolated it into the lifecycle/PATCH paths. After #369, those calls
would 400 with `\"Invalid issue number\"` because the server-side
`parseLabIssueNumberParam` validator (`^\d+$` + `Number.isSafeInteger`)
rejects UUIDs. The migration in this PR is necessary to keep the
issue-lifecycle integration working at all.

### Tracking issue
This PR closes the client-side half of the work I tracked in
systeminit/swamp-club#367, which I filed when I first noticed the
contract drift. The server-side half shipped as #369 (already merged).

## Safe-integer validation rationale

swamp-club#369 added a strict validator `lib/lab-issue-number-param.ts`
that uses `Number.isSafeInteger` instead of `Number.isInteger`. The PR
description explains why: during live testing,
`Number.parseInt(\"7d03f113-80a5-...\", 10)` silently truncated a UUID
to `7` and routed a request meant for one issue onto issue #7,
**clobbering its title**. The strict validator catches that hazard.

This client doesn't parse strings (it consumes a JSON number from
`/ensure` and emits it back into a URL path), so the parseInt hazard is
not directly reachable. But mirroring the server's safe-integer bound is
still the right call for two reasons:

1. **Contract symmetry.** If the server ever returns a value the client
would happily emit but the next request would 400 on, we'd have a
confusing failure mode. Validating with the same predicate the server
uses means \"if the client accepts it, the server accepts it.\"
2. **Defence against upstream contract drift.** The client previously
asserted the response shape with a TypeScript `as` cast —
runtime-trivial. The new code reads `data?.issue?.number` defensively
(typed as `unknown`) and runs it through the same `Number.isSafeInteger`
+ positive check. A malformed response can no longer throw a `TypeError`
before we hit our validator, and a numerically-suspicious response (NaN,
Infinity, fractions, anything past 2^53-1) is rejected with a structured
log line.

## Why the GitHub Action close is gated on a valid lab number

The action's only safety property is: **a GitHub issue must never be
closed unless its lab counterpart exists.** If we close before
confirming, an issue can disappear from GitHub without ever being filed
in the lab — losing the report.

The flow is therefore strictly sequential and short-circuits on every
error:

1. POST to `/api/v1/lab/issues/ensure`. If non-2xx →
`core.setFailed(...)` and `return` (issue stays open).
2. Parse JSON. If `data.issue.number` is missing or not a safe positive
integer → `core.setFailed(...)` and `return` (issue stays open).
3. Post the auto-responder comment with the lab URL.
4. **Only then** close the GitHub issue.

`core.setFailed` does not stop execution by itself in
`actions/github-script`, so each guard is paired with an explicit
`return`. Steps 3 and 4 are unreachable unless we have a confirmed
`labIssueNumber`.

## Files changed

### `.github/workflows/auto-response.yml`

Repurposed end-to-end:
- Removed the maintainer-permission probe and the maintainer-only skip
path.
- Removed the `needs-triage` label step and the welcome-comment step.
- Single new step (`actions/github-script@v7`, Node 20 fetch) that:
- Reads the issue title, body, and author from `context.payload.issue`.
- Builds the lab body: original body + `\n\n---\nAutomoved by swampadmin
from GitHub issue #<n>` footer (handles empty bodies).
- POSTs to `https://swamp.club/api/v1/lab/issues/ensure` with `{
githubRepoFullName, githubIssueNumber, title, body, type: \"feature\",
githubAuthorLogin }`, 15s `AbortSignal.timeout`.
- Validates the response: defensive `data?.issue?.number` access,
`Number.isSafeInteger`, `> 0`. Mirrors swamp-club's
`parseLabIssueNumberParam`.
- Posts a comment on the GitHub issue: `\"Thank you for opening an
issue. This issue is now managed in the Claude
lab.\\n\\nhttps://swamp.club/lab/<number>\"`.
  - Closes the GitHub issue via `issues.update({ state: \"closed\" })`.
- Reads the API key from the repo secret `SWAMP_API_KEY` (already
configured).
- Trigger is unchanged: `on.issues.types: [opened]`. Permissions are
unchanged: `issues: write`, `contents: read`.

### `extensions/models/_lib/swamp_club.ts`

- Renamed `private issueId: string | null` → `private labIssueNumber:
number | null`. The cached value is now the sequential lab id, not the
UUID.
- `ensureIssue` now returns `Promise<number | null>` (was
`Promise<string | null>`).
- Reads `data?.issue?.number` with `unknown` typing (was `data.issue.id`
with a `{ id: string }` cast).
- Validates with `typeof === \"number\"` + `Number.isSafeInteger` +
positive (was a UUID regex). Mirrors swamp-club's
`parseLabIssueNumberParam`.
- Logs `\"swamp-club returned invalid lab issue number: {number}\"` on
validation failure (was `\"...invalid issue ID...\"`).
- `postLifecycleEntry` now builds
`${baseUrl}/api/v1/lab/issues/${this.labIssueNumber}/lifecycle` and
gates on `this.labIssueNumber === null` (was `!this.issueId`).
- `transitionStatus` now builds
`${baseUrl}/api/v1/lab/issues/${this.labIssueNumber}` and gates on
`this.labIssueNumber === null` (was `!this.issueId`).
- New public `labUrl()` helper: returns
`${baseUrl}/lab/${labIssueNumber}` if the issue has been ensured, or
`null` otherwise. Lets callers display the public lab URL without
re-fetching anything.
- Updated `createSwampClubClient` doc comment: \"The lab issue number is
resolved lazily — no extra arg needed.\" (was \"The issue ID is resolved
lazily — no swampClubIssueId arg needed.\")

### `extensions/models/issue_lifecycle.ts`

- `ensureSwampClub`: renamed local `id` → `labIssueNumber`, replaced `if
(!id)` with `if (labIssueNumber === null)`. The strict null check guards
against a future `0` being misread as missing — the server validates `>
0`, so `0` is currently unreachable, but `=== null` is the right pattern
for a `number | null` return type either way.
- Updated the doc comment from \"the issueId cache is lost\" to \"the
lab issue number cache is lost.\"
- The other `ensureIssue` call site (`triage_started` lifecycle entry,
line ~435) discards the return value, so no change needed.

### `extensions/models/README.md`

- Added a paragraph in the \"Swamp Club Integration / How it works\"
section:
> Each lab issue is assigned a sequential, human-friendly number (`#1`,
`#2`, ...) that is used in every lab URL — both the dashboard and the
API. After the model has run against an issue you can find it at
`https://swamp.club/lab/<number>`.

## Test plan

### Local verification
- [x] `deno fmt --check` — clean (1114 files)
- [x] `deno lint` — clean (1008 files)
- [x] `deno run test` — **4193 passed, 0 failed** (3m30s)
- [x] `deno check extensions/models/_lib/swamp_club.ts
extensions/models/issue_lifecycle.ts` — clean

### Client behaviour (manual / by-inspection)

- [x] `ensureIssue` happy path: server returns `{ issue: { number: 42,
... }, created: true }` → cache populated, `42` returned.
- [x] `ensureIssue` cache hit: second call short-circuits via
`this.labIssueNumber !== null` and returns the cached number without
re-fetching.
- [x] `ensureIssue` rejects malformed responses without throwing:
missing `issue` key, missing `number` key, `number: \"42\"` (string),
`number: 0`, `number: -1`, `number: 1.5`, `number: NaN`, `number: 2 **
53` (past safe integer). Each path logs a structured warning and returns
`null`.
- [x] `postLifecycleEntry` and `transitionStatus` no-op when
`labIssueNumber === null` (i.e. before `ensureIssue` succeeds).
- [x] `labUrl()` returns `null` before `ensureIssue`, then
`${baseUrl}/lab/{number}` after.

### GitHub Action

- [ ] **Smoke test:** file a throwaway issue against `systeminit/swamp`
after this PR merges. Expect:
  - The issue closes within seconds of being filed.
- A comment appears with the auto-responder text and a
`https://swamp.club/lab/<n>` link.
- The lab issue exists at that URL with the original body plus the
`Automoved by swampadmin from GitHub issue #<n>` footer.
- [ ] **Failure-mode test:** temporarily revoke the `SWAMP_API_KEY`
secret value (or point at an unreachable host) and file a throwaway
issue. Expect:
  - The action run fails with a `core.setFailed` message.
  - The GitHub issue stays **open**.
  - No auto-responder comment is posted.
  - Restore the secret afterwards.
- [ ] **Validator test:** if a future swamp-club regression returns a
non-number `id` field, expect the action to fail with `\"swamp.club
ensure returned no lab issue number: ...\"` and the issue to stay open.

I have not pre-merged the smoke tests because they require the workflow
to be on `main` to fire on real `issues: opened` events. Suggest doing
them as the first action after merge with throwaway issues.

## Related

- systeminit/swamp-club#367 — issue I filed to track migrating the lab
API endpoints from UUID to sequential number (closed by #369).
- systeminit/swamp-club#369 — the merged server-side PR that shipped the
contract this client now consumes. Adds `parseLabIssueNumberParam` and
caught a real data-corruption hazard with `Number.parseInt`.
- systeminit/swamp-club#364 — the original PR that introduced the
sequential `number` field on lab issues and migrated the UI route to
`/lab/{number}`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants