Tags: systeminit/swamp
Tags
feat(doctor): add swamp doctor audit preflight diagnostic (swamp-club… …#156) (#1227) ## Summary Adds `swamp doctor audit [--tool <name>]` — a new preflight diagnostic that verifies the AI-tool audit integration configured in the repo is healthy end-to-end. Non-zero exit on any fail, safe to gate in CI. `doctor` is registered as a parent namespace so future diagnostics (`doctor datastore`, `doctor vault`, ...) plug in as siblings. Implements swamp-club#156. ## Five checks | Check | Scope | What it catches | |---|---|---| | `binary-on-path` | all 4 tools | AI tool binary missing from PATH | | `swamp-binary-on-path` | all 4 tools (+ Kiro baked-path) | swamp missing from PATH, or kiro hook's baked absolute path orphaned by `brew upgrade` | | `agent-config-loadable` | per-tool parser | config missing, malformed, or broken (e.g. Kiro `tools: ["*"]`) | | `default-agent-set` | Kiro only | `.kiro/settings/cli.json` not pointing `chat.defaultAgent` at `swamp` | | `recording-smoke-test` | all 4 tools | **upstream normalizer drift** — synthetic `postToolUse` payload piped through `swamp audit record --from-hook` must land as a row in today's audit JSONL | ## The headline verification Temporarily removing `"shell"` from `KIRO_SHELL_TOOL_NAMES` in `hook_input.ts`, recompiling, and running doctor against a freshly-init'd kiro repo produces: ``` ✗ recording-smoke-test synthetic kiro payload did NOT land in today's audit JSONL hint: The hook normalizer in src/domain/audit/hook_input.ts may have drifted from the upstream contract... 4 passed, 1 failed, 0 skipped — OVERALL: FAIL ``` Reverted before this commit. This is the load-bearing proof that the feature protects against future upstream drift instead of being theatre. ## Name override The issue body says "please NOT `doctor`". The team picked `doctor` anyway — the command is a namespace and will grow; the name fits that shape. Acknowledging here for reviewer context. ## Scope note on the three cited Kiro bugs Issue 156 cites three concrete bugs as motivation. Their status in this PR: - **`tools: ["*"]` in kiro agent config** → `agent-config-loadable` flags it with a hint; NOT fixed here. - **Missing `chat.defaultAgent: "swamp"`** → `default-agent-set` flags it; NOT fixed here. - **Hard-coded `tool_name === "execute_bash"` in Kiro normalizer** → **already fixed** upstream of this PR. `src/domain/audit/hook_input.ts:146-150` has `KIRO_SHELL_TOOL_NAMES = new Set(["execute_bash", "shell", "execute_cmd"])`. This PR's diagnostic catches the class of bug, it does not fix that specific one. ## Ancillary changes - **Extract `todaysAuditFilePath` helper.** `src/infrastructure/persistence/jsonl_audit_repository.ts` had two duplicate copies of the `commands-YYYY-MM-DD.jsonl` format string; moved to `src/domain/audit/audit_path.ts` and consumed by both the writer and the doctor smoke-test reader. Single source of truth for the filename format. - **Reserve `echo swamp-doctor-smoke-test` as a sentinel command prefix.** The smoke-test writes a real audit row with this prefix; the audit timeline filters it out of the default view. Opt in with `swamp audit --include-diagnostic`. - **Add `parseAiToolOrThrow`** in `src/cli/ai_tool_parser.ts` — central validator for `--tool` flags, so bogus values fail with a usage error listing valid names. - **Docs:** `design/audit.md`, `design/audit-doctor.md`, and a short "verify the audit integration" block in the `swamp-repo` skill. ## Follow-ups - UAT coverage tracked in [systeminit/swamp-uat#160](systeminit/swamp-uat#160) (CLI) and [systeminit/swamp-uat#161](systeminit/swamp-uat#161) (adversarial). - swamp-club has GitHub issues disabled — `content/manual/reference/doctor.md` and a "verify the integration" section for each `content/manual/how-to/use-swamp-with-*.md` need to be routed through the team's docs process. - POSIX-only PATH resolution — documented; Windows support deferred. ## Test Plan - [x] `deno check` — clean - [x] `deno lint` — clean - [x] `deno fmt --check` — clean - [x] `deno run test` — **4922 passed, 0 failed, 1 ignored** - [x] `deno run compile` — produces working binary - [x] Manual smoke-run on freshly-init'd kiro / claude / cursor / opencode repos — all pass - [x] Regression injection (remove `"shell"` from `KIRO_SHELL_TOOL_NAMES`, recompile, rerun) — smoke-test correctly FAILS on kiro - [x] Real-repo test against `~/code/kiro-audit` (existing audit history) — passes; sentinel filter confirmed with `swamp audit --include-diagnostic` - [x] CWD-independence adversarial test — ran from CWD=`/` with `--repo-dir /tmp/...`, synthetic row landed in the target repo, not the CWD 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
docs(skills): document markDirty in swamp-extension-datastore (#1226) ## Summary Pick up the \`markDirty()\` addition from #1225 in the skill that guides authors building custom datastore providers. - \`references/api.md\` — interface block includes \`markDirty(): Promise<void>\`, plus a section describing when it's load-bearing (fast-path implementations that cache a clean/dirty watermark) versus when a no-op is correct (sync services that unconditionally walk the cache). - \`references/examples.md\` — the working sync service example returns a no-op \`markDirty\` with a comment pointing at the fast-path pattern in \`design/datastores.md\`. No code changes, docs only — keeps guidance for extension authors in lock-step with the core interface change that already shipped. ## Test Plan - [x] \`deno fmt\` clean per the skill-files rule in CLAUDE.md. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
fix(datastore): signal cache writes to sync service fast path (#1225) ## Summary The s3/gcs zero-diff fast path in `DatastoreSyncService` maintains a local-dirty watermark that it flips during its own `pushFile`-equivalent. Swamp core writes into the cache directly from the persistence repositories (`UnifiedDataRepository`, `YamlOutputRepository`, `YamlWorkflowRunRepository`, `YamlEvaluatedDefinitionRepository`), which bypasses that hook — the next `pushChanged` short-circuits past the write and the data lives only in the local cache until eviction. Single writer setups against a low-traffic bucket are most exposed; high traffic masks the bug because unrelated remote mutations invalidate the sidecar. ### Fix - Add a required `markDirty()` method to `DatastoreSyncService` (mirrored in `packages/testing/datastore_types.ts`). - Every public mutation on the four datastore-tier repositories calls `notifyDirty()` before any cache write begins, so a crash mid-write still leaves the watermark dirty — per-method (not per-atomicWrite) keeps the repositories clean and the hook is idempotent anyway. - Single sync service instance threads through `requireInitializedRepoUnlocked` → `acquireModelLocks` and through the serve context (`OpenServerState`, `ConnectionContext`, `WebhookServiceDeps`), so an implementation that caches the sidecar flag in memory stays coherent across the `markDirty` hook and the pull/push fast-path read. The \`acquireModelLocks\` param is optional for backward compatibility; every caller in the repo now passes one. - Document the contract under the Zero-Diff Fast Path section in \`design/datastores.md\`. - Filesystem datastores wire no service; \`notifyDirty\` is a no-op for them. ### Extension follow-up The required interface change surfaces as a compile-time signal to extension authors (\`@swamp/s3-datastore\`, \`@swamp/gcs-datastore\`, swamp-club/166) that they need to implement \`markDirty()\` — trivial: it's the same primitive as their internal \`pushFile\` hook's dirty-flip. ## Test Plan - [x] New repository contract tests pin \`markDirty\` invocation on every public mutation, including the negative cases (reads, dry-run GC, no-op delete) where it must not fire. - [x] Existing \`datastore_sync_service_test.ts\` still passes; 12 stub sync services across tests updated to include the new field. - [x] \`testing_package_compat_test.ts\` structurally verifies the \`@systeminit/swamp-testing\` mirror still matches core. - [x] \`deno check\`, \`deno lint\`, \`deno fmt --check\`, \`deno run test\` (4854 passed, 0 failed, 1 ignored), \`deno run compile\` all clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
fix(extensions): complete legacy layout migration on repo upgrade (sw… …amp-club#160) (#1224) ## Summary Closes [swamp-club#160](https://swamp.club/lab/issues/160). `swamp repo upgrade` was silently deleting user working-tree files when it detected extensions tracked at a legacy on-disk layout. The two-phase migration added in #1186 renamed gen-1 paths to gen-2, then deleted the gen-2 files expecting the user to run `swamp extension install` afterward to restore them. No warning, no prompt, no automatic follow-up. The fix collapses the split between the two primitives that owned "make extension state correct": - `extensionInstall` now treats legacy-layout entries as needing re-pull (not just missing files), installs them into the per-extension subtree, and sweeps the original legacy paths only after a successful install. - `repo upgrade` invokes `extensionInstall` directly as its final step. - `migrateExtensionLayout` and its two phase methods are deleted (~280 LOC) along with `ExtensionLayoutMigrationSummary`, `pruneEmptyDirsUp`, and the `extensionMigration` result field. Net LOC: **−170** despite adding the install-pass wiring, a test seam, a shared factory, and several new tests. ## User-visible behavior - **`swamp repo upgrade` now completes the migration end-to-end.** No manual `swamp extension install` follow-up required for the common case. - **On install failure** (network down, extension yanked, auth missing), legacy files are preserved and the error names the failed extensions with a recovery command. - **`swamp extension install` also migrates legacy-layout repos.** Previously a silent no-op on legacy state — positive side effect of unifying the two primitives. ## Latent bug caught during verification Running an end-to-end manual test against `@swamp/aws/s3` surfaced a second bug: empty `_lib/` directories were being stranded after the sweep. Root cause: `resolveRepoDir` returns `"."` by default, which propagated through `join(".", "path") → "path"` (relative), and `cleanupEmptyParentDirs`'s `startsWith(stopAtDir)` check silently failed against relative paths. The factory now absolutizes `repoDir` up front so downstream filesystem helpers always see absolute paths. Added a regression test asserting nested parent dirs are pruned. This one wouldn't have been caught by unit tests alone — my initial unit tests used absolute `tmpDir` from `Deno.makeTempDir`, which masked the relative-path issue. Running against a real extension with a nested `_lib/` found it immediately. ## Test plan - [x] `deno check` — clean - [x] `deno lint` — clean (1081 files) - [x] `deno fmt` — clean (1199 files) - [x] `deno run test` — 4728 pass, 0 fail, 1 ignored (added 8 new tests) - [x] `deno run compile` — binary produced - [x] **Manual E2E happy path:** `@swamp/aws/s3` seeded as gen-1 legacy, `swamp repo upgrade` run; all 11 model files migrated to `.swamp/pulled-extensions/@swamp/aws/s3/models/…`, `filesChecksum` anchor matches original pull byte-for-byte (`a9378bf483…`), legacy paths (including nested `_lib/`) fully swept. - [x] **Manual E2E failure path:** lockfile referencing `@me/launchd-daemon` (not in registry); `swamp repo upgrade` exits non-zero with clear error naming the extension + recovery command; all 4 legacy files preserved on disk. - [x] **Manual E2E via `extension install`:** same legacy seed; `swamp extension install` also migrates successfully (positive side effect verified). ## Notes for reviewers - The `swamp extension install` behavior change (no-op → auto-migrate on legacy repos) is intentional; documented in the `swamp-repo` skill. - There is no `CHANGELOG.md` in this repo, so the user-visible release notes live in this PR description. - I skipped the integration test against a real/fake registry as out of scope for this PR; unit coverage + manual verification cover the critical paths. A follow-up would be reasonable. - Private/auth-gated extensions are not directly verified end-to-end — the factory uses the same `ExtensionApiClient` as `swamp extension install`, so coupling should hold, but worth spot-checking if a reviewer has a private extension in a test lockfile. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
fix(datastore): clarify --timeout scope in SyncTimeoutError message (#… …1223) ## Summary Follow-up to #1222 addressing the two actionable findings from review: ### 1. CLI UX Review — `--timeout` remedy was misleading for implicit syncs The timeout fires from `flushDatastoreSync()` after *every* command, not just `swamp datastore sync`. The previous message led with `• Rerun with --timeout <seconds>`, which would fail for a user whose timeout fired from e.g. `swamp model run` (the flag only exists on `swamp datastore sync`). **Fix:** reorder remedies so the env var (universal escape hatch) is listed first and the `--timeout` flag is explicitly scoped to `swamp datastore sync`: \`\`\` Datastore push to "@swamp/s3-datastore" timed out after 300000ms. Try one of: • Set SWAMP_DATASTORE_SYNC_TIMEOUT_MS for the duration of your shell session — applies to every command that triggers sync, including the implicit pull/push around write commands. • If the timeout fired on an explicit 'swamp datastore sync', rerun with --timeout <seconds> (e.g. --timeout 1800 for large one-off syncs). The flag is not available on other commands; use the env var above for those. • If you are on @swamp/s3-datastore or @swamp/gcs-datastore at scale, update to the latest extension — the fingerprint fast path short-circuits zero-diff syncs. • If a prior process crashed without releasing the lock, run 'swamp datastore lock release --force' (add --model <type>/<id> for a specific model lock). \`\`\` New scope-clarification test pins the contract: the message MUST name `swamp datastore sync` explicitly and MUST mention the env var covers implicit syncs. That prevents a future well-meaning reword from reintroducing the same trap. ### 2. Code Review + Adversarial Review — `CreateDatastoreSyncDepsOptions` not re-exported from barrel The new interface lived at the internal path `src/libswamp/datastores/sync.ts`. Any consumer outside the CLI wanting to type an options variable would have had to violate CLAUDE.md's barrel-import convention. One-line fix: add `type CreateDatastoreSyncDepsOptions` to the export from `src/libswamp/mod.ts`. ## What this does NOT change - The precedence chain (CLI flag → config → env var → default) is unchanged. - The structured fields of `SyncTimeoutError` (`label`, `direction`, `timeoutMs`, `cause`) are unchanged. - No behavior change for users who don't hit the timeout. ## Test plan - [x] `deno check` — clean - [x] `deno lint` — clean (1094 files) - [x] `deno fmt` — clean - [x] `deno run test` — 4843 passed / 0 failed - [x] `deno run compile` — binary built - [x] New test `SyncTimeoutError: --timeout remedy is scoped to 'swamp datastore sync'` passes ## Links - Primary PR: #1222 - Lab issue: https://swamp.club/lab/164 Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
feat(datastore): add --timeout flag and enrich SyncTimeoutError (swam… …p-club#164) (#1222) ## Summary Companion PR to the `@swamp/s3-datastore` fingerprint fast-path fix for [lab/164](https://swamp.club/lab/164) (minutes-slow `swamp datastore sync` at 4k-file scale, even on zero-diff). This PR adds the swamp-core ergonomics: a `--timeout` CLI flag for one-off sync overrides, and a richer `SyncTimeoutError` message so users get actionable next steps when the timeout does fire. Either PR may land first — the enriched error uses version-free wording, so there is no ordering dependency. ## What changed ### `--timeout <seconds>` flag on `swamp datastore sync` - Per-invocation override; wins over `CustomDatastoreConfig.syncTimeoutMs` and `SWAMP_DATASTORE_SYNC_TIMEOUT_MS`. - Validated at the CLI boundary: positive integer, rejected ≤0, capped at **21600s (6h)** — generous enough for initial seeding from a slow link, bulk recovery after an outage, or 100k-file migrations, while rejecting fat-finger misconfigs. The env var stays uncapped as the shell-session escape hatch. - Scoped to `swamp datastore sync` only. Write commands that trigger implicit sync keep their existing escape hatch (env var). Adding `--timeout` everywhere would inflate the CLI surface without commensurate benefit. - `--help` clearly describes precedence so users reach for the right knob. ### Enriched `SyncTimeoutError` message Before: \`\`\` Datastore push to "@swamp/s3-datastore" timed out after 300000ms. If a prior process crashed without releasing the lock, run 'swamp datastore lock release --force' (add --model <type>/<id> for a specific model lock). \`\`\` After: \`\`\` Datastore push to "@swamp/s3-datastore" timed out after 300000ms. Try one of: • Rerun with --timeout <seconds> (e.g. --timeout 1800 for large one-off syncs). • Set SWAMP_DATASTORE_SYNC_TIMEOUT_MS for the duration of your shell session. • If you are on @swamp/s3-datastore or @swamp/gcs-datastore at scale, update to the latest extension — the fingerprint fast path short-circuits zero-diff syncs. • If a prior process crashed without releasing the lock, run 'swamp datastore lock release --force' (add --model <type>/<id> for a specific model lock). \`\`\` Version-free wording keeps the message stable across extension releases. No changes to the error's structured fields (`label`, `direction`, `timeoutMs`, `cause`). ### Internal plumbing - `resolveSyncTimeoutMs(config, overrideMs?)` — new optional parameter at highest precedence. Non-positive overrides silently fall through to the next source. - `createDatastoreSyncDeps(repoDir, resolver, options?)` — new `CreateDatastoreSyncDepsOptions` interface with `syncTimeoutMsOverride`. ### Docs `design/datastores.md` — expanded "Sync Timeout" section with the 4-tier precedence chain and reference to the enriched error. New "Zero-Diff Fast Path (Extension Guidance)" subsection with backend-agnostic wording (fingerprint + dirty-flag sidecar pattern), so future extension authors have a breadcrumb. Notes that sync is not a content-integrity tool — use `DatastoreVerifier` for that. ## What this does NOT do - Does not fix the 5-minute zero-diff sync itself — that's the `@swamp/s3-datastore` fingerprint fast-path PR. This PR is the ergonomics layer. - Does not change default behavior for anyone who doesn't pass `--timeout` or hit the timeout. - Does not close the AbortSignal plumbing gap in the s3 extension. ## Test plan - [x] `deno check` — clean - [x] `deno lint` — clean (1091 files) - [x] `deno fmt` — clean - [x] `deno run test` — 4822 passed / 0 failed (19 new tests) - [x] `deno run compile` — binary built - [x] `./swamp datastore sync --help` — `--timeout` flag documented correctly - [ ] Reporter runs `swamp datastore sync --timeout 60` against their 4k-file repo and confirms: (a) validation rejects 0/negative/>21600, (b) enriched error message renders all four remedies, (c) flag override wins over env var when both are set. Tracked on lab/164 as the verification gate. ### New tests | File | What it covers | |---|---| | `src/domain/datastore/datastore_sync_service_test.ts` | Enriched message contents; version-free extension hint (regex guards against calendar-version and semver); structured field preservation; multi-line JSON round-trip. | | `src/domain/datastore/datastore_config_test.ts` | `resolveSyncTimeoutMs` precedence: override wins over config/env/default; non-positive override falls through; undefined preserves existing precedence. | | `src/cli/commands/datastore_sync_test.ts` | `parseTimeoutFlag` validation: seconds→ms conversion, upper-bound acceptance, zero/negative/over-cap/non-integer/non-number rejection. | ## Links - Lab issue: https://swamp.club/lab/164 - Companion PR: `@swamp/s3-datastore` fingerprint fast-path (in `systeminit/swamp-extensions`, being opened in parallel) - Follow-up issue: https://swamp.club/lab/166 (gcs mirror) Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
feat(drivers): finish repo-level defaultDriver — direct path + defini… …tion tier (swamp-club#165) (#1221) ## Summary Closes swamp-club#165. Completes the cleanup gaps left by #1219 so the repo-level `defaultDriver` priority chain now applies uniformly to both workflow and direct `swamp model method run` paths. Also fixes a concurrent-read race on the marker load and un-breaks the silently-dropped `definition.driver` tier. Three deliverables, one commit: - **Direct `swamp model method run` now honors `defaultDriver`.** `src/libswamp/models/run.ts` routes through `resolveDriverConfig` with cli/definition/repo tiers. Serve/WS (`src/serve/connection.ts`) and HTTP SSE (`src/serve/open/http.ts`) paths inherit for free. A new `loadRepoMarker` dep on `ModelMethodRunDeps` is built via the extracted `createRepoMarkerLoader` helper. - **Promise-memoize the marker read.** Previously `WorkflowExecutionService.loadRepoMarker` cached the resolved value — two concurrent callers both saw `undefined` and both hit the file. Now `this.markerPromise ??= markerRepo.read(...)`; `.swamp.yaml` is read at most once per service instance. The primitive is extracted to `src/infrastructure/persistence/repo_marker_loader.ts` and reused by all four factories (workflow service, CLI, serve/WS, HTTP SSE). - **Make the `definition` tier live.** `src/domain/workflows/execution_service.ts:523` used to read `ctx.driver ?? evaluatedDefinition.driver` — dead code, because `ctx.driver` was always populated by the old upstream `resolveDriverConfig` call (with `"raw"` as its fallback), so the RHS never fired and `definition.driver` was silently dropped. `StepExecutionContext` now carries unresolved `driverTiers: { cli, step, job, workflow, repo }` and final resolution happens inside `DefaultStepExecutor.executeModelMethod` where `evaluatedDefinition` is in scope. ## Behavior changes - `swamp model method run <m> <meth>` in a repo with `defaultDriver: docker` in `.swamp.yaml` now runs via docker without `--driver`. `--driver raw` override still wins. - Model definitions with `driver:` set at the definition level are now honored during workflow execution. **Internal blast radius: zero** — grep of `extensions/**/manifest.yaml` and `extensions/**/*.yaml` returned no hits. Third-party extensions that set `driver:` at the definition level will now apply it (this was the documented design from day one but was never wired). - Malformed `.swamp.yaml` now aborts every direct method run with a YAML parse error (previously direct runs didn't read the marker at all). Consistent with workflow behavior and documented in `design/execution-drivers.md`. ## Tests added - `src/infrastructure/persistence/repo_marker_loader_test.ts` — concurrency primitive: two simultaneous calls through a `Promise.withResolvers` latch, assert exactly one underlying `.read()` call. Covers null-result caching too. - `src/domain/models/method_execution_service_test.ts` — pre-flight check receives the resolved `driver`/`driverConfig` from MethodContext (propagation audit). - `src/libswamp/models/run_test.ts` — four direct-path resolution tests: defaultDriver from marker wins; CLI override wins; raw fallback; definition tier applies when no higher tier set. - `src/domain/workflows/execution_service_test.ts` — existing three workflow-path resolution tests updated to assert on the new `driverTiers` shape. - `integration/driver_resolution_test.ts` — end-to-end CLI smoke (no docker required): defaultDriver honored, CLI override wins, malformed YAML aborts loudly. ## Docs - `design/execution-drivers.md` — added a short note under "Resolution Priority" stating that the priority chain applies uniformly to workflow and direct paths and that malformed `.swamp.yaml` aborts at start-of-run. - **Follow-up docs gap (not in this PR):** the swamp-club manual reference at `content/manual/reference/repository-configuration.md` still does not document `defaultDriver` / `defaultDriverConfig` — pre-existing gap from #1219. `systeminit/swamp-club` has GitHub issues disabled; flagging here so the gap can be routed to the swamp-club lab. ## Out of scope (per issue body, separate follow-ups) - Named-fields refactor of `resolveDriverConfig` (positional `DriverSource | undefined` params). - `WorkflowExecutionService.execute()` option forwarding (currently drops `driver`). - Runtime schema validation of `RepoMarkerData` (the `parseYaml(...) as RepoMarkerData` cast). - UAT coverage for `--driver` / `defaultDriver` in `systeminit/swamp-uat`. ## Test plan - [x] `deno check` — passes - [x] `deno lint` — passes - [x] `deno fmt --check` — passes - [x] `deno run test` — 4823/4823 pass - [x] `deno run compile` — binary produced - [x] Local smoke: scratch repo with `defaultDriver: raw` → honored; `--driver raw` beats `defaultDriver: docker`; malformed YAML aborts with exit code 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
docs(skills): document bare-specifier vs scorer hermeticity (swamp-cl… …ub#161) (#1220) ## Summary Closes swamp-club#161. Following #1218 shipping `swamp extension quality`, authors now discover locally that the scorer strips the tarball's `deno.json` and writes a controlled one with no imports map — so bare `"zod"` imports fail with `deno doc --json failed: Import "zod" not a dependency` before factor scoring even begins. This PR teaches that pattern to authors through the two skills that previously led them into the trap. **Three files, +50/-7 lines:** - **`swamp-extension-quality/references/templates.md`** — the JSDoc entrypoint skeleton was showing bare `"zod"`; switched to `"npm:zod@4"` with a rationale bullet explaining hermeticity. An author who copies the template literally now produces an extension that scores. - **`swamp-extension-publish/references/publishing.md`** — the Import Rules had two contradictory bullets (`"npm:zod@4"` is "always required" vs. bare specifiers "can be used" with `deno.json`). Rewritten into one coherent paragraph that carries both load-bearing signals: hermeticity at score time AND zod-specific externalization (why zod in particular is called out, not merely a consequence of hermeticity). - **`swamp-extension-quality/SKILL.md`** — added a gotcha block under "Essential mechanics authors get wrong" that names the failure mode explicitly, calls out the Deno `no-import-prefix` default that activates when a `deno.json` with `imports` is present (which is every swamp repo's default state, creating a catch-22), and gives concrete mitigations: either global `"lint": { "rules": { "exclude": ["no-import-prefix"] } }` in `deno.json`, or per-import `// deno-lint-ignore no-import-prefix`. Also added a one-sentence cross-link from the new "Self-check your score locally" section and a symptom→fix row to "Common patterns" so a reader landing anywhere in the skill finds the fix. ## Test Plan Three-layer verification: - [x] `deno fmt --check` clean on all three files - [x] `deno check` clean - [x] `deno lint` clean (1088 files) - [x] `deno run test` — **4803 passed, 0 failed** - [x] Executable round-trip in `/tmp/swamp-issue-161-verify`: - Bare `"zod"` import → `swamp extension quality` throws the exact error documented in the gotcha (`deno doc --json failed: Warning Import "zod" not a dependency`) - Switch to `"npm:zod@4"` → scorer runs and reports 11/12 (91%) factor results - The test also surfaced the Deno `no-import-prefix` default rule firing as predicted, and the documented mitigation (`lint.rules.exclude`) unblocked the round-trip — confirming both halves of the gotcha match the tool's actual behaviour. ## Follow-up (not in this PR) The deadlock between the scorer (strips `deno.json`) and Deno's default lint rules (forbid `npm:` prefix when an `imports` map is present) is ultimately a swamp-club server-side concern. A future follow-up issue could make the analyzer honour the tarball's `files/deno.json` with appropriate hardening — the README/LICENSE promotion logic already does similar. Until then, the gotcha documents the author-side workaround. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
feat(drivers): repo-level defaultDriver in .swamp.yaml (swamp-club#15… …9) (#1219) ## Summary Closes swamp-club#159. Adds a repo-level `defaultDriver` (and `defaultDriverConfig`) to `.swamp.yaml` so a repo can declare a baseline execution driver once, without touching every workflow or passing `--driver` on every CLI invocation. New resolution chain for workflow steps: ``` cli > step > job > workflow > definition > repo > "raw" ``` - `RepoMarkerData` gains two optional fields (`defaultDriver`, `defaultDriverConfig`) — same shape as the existing `datastore` field. - `resolveDriverConfig` gains an optional 6th `repo` tier, positioned below `definition` and above the `"raw"` fallback. - `WorkflowExecutionService` loads `.swamp.yaml` once per `run()` (memoized on the instance so nested workflow calls don't re-read) and passes the repo tier into `resolveDriverConfig` at the step-context build site. - `design/execution-drivers.md` updated to document the new tier with an example. **Scope:** workflow execution only. Direct `swamp model method run` invocations go through a separate code path that doesn't call `resolveDriverConfig` today and will still default to `"raw"` when no CLI override is supplied. Flagged in the adversarial review as ADV-1; can be a follow-up if desired. ## Test Plan - [x] `deno check` — passes - [x] `deno lint` — passes - [x] `deno fmt --check` — passes - [x] `deno run test` — 4732 passed / 0 failed / 1 ignored - [x] `deno run compile` — produces a working binary New tests added: - 5 cases in `driver_resolution_test.ts` covering repo-tier precedence (wins over default, loses to workflow/job/step/cli, skipped when `driver` undefined but `driverConfig` set) - 1 roundtrip in `repo_marker_repository_test.ts` with `defaultDriver` + `defaultDriverConfig` - 3 service-level tests in `execution_service_test.ts` with a real `.swamp.yaml` on disk: default applied, CLI overrides, no default → raw Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
feat(extensions): add quality command and opportunistic package cache… … (swamp-club#163) (#1218) ## Summary Implements swamp-club#163 — a new `swamp extension quality <manifest>` CLI that scores an extension against the Swamp Club rubric locally, mirroring the server's scorecard pipeline so local results match the score the registry will compute after publish. - **New CLI command:** `swamp extension quality manifest.yaml [--json]`. Exits non-zero if any factor misses; suitable for CI self-checks. - **Server-mirrored scorer:** `extension_rubric_scorer.ts` mirrors [`analysis-factors.ts`](https://github.com/systeminit/swamp-club/blob/main/lib/domain/scorecard/analysis-factors.ts) and the rubric-v2 composition from [`score.ts`](https://github.com/systeminit/swamp-club/blob/main/lib/domain/scorecard/score.ts) — same slow-type code set, same JSDoc coverage shape, same README/LICENSE lookup in the extracted tarball, same 12-point weighting. - **Parity tests:** 24 tests marked `[parity]` port the upstream swamp-club test suite so logic drift is caught before release. - **Opportunistic package cache** at `.swamp/cache/packages/<hash>/`: keyed on source inputs (manifest + resolved file contents + deno config). `extension quality` writes the packaged tarball; `extension push` checks the cache before packaging and reuses the bytes on a hit. Any source change invalidates the entry by construction — authors who run quality before push don't pay twice, and the bytes scored equal the bytes uploaded. ### Opt-in by design `quality` is uniformly opt-in: push's behaviour is unchanged, no new flags, no mandatory gate. The `swamp-extension-publish` and `swamp-extension-quality` skills gain brief pointers at the new CLI between states 6 and 7 of the publish flow. ### Known caveat `repository-verified` is a structural check on the CLI side (HTTPS + allowlisted host). The server additionally performs an HTTP HEAD to confirm the repo is public — so a local "earned" can come back "missing" from the registry if the repo URL is malformed or private. The log renderer surfaces this caveat. Follow-up issue: narrow the gap (HTTP HEAD from the CLI, or a shared scoring package between this repo and swamp-club). ### Smoke test Ran against `systeminit/swamp-extensions/datastore/s3` — reports **12/12 points, 100%, all factors earned**, matching the registry. ## Test Plan - [x] 10 unit tests for `extension_package_cache.ts` (hit/miss, determinism, cache invalidation) - [x] 39 unit tests for `extension_rubric_scorer.ts` covering every factor and edge case - [x] 24 `[parity]` tests ported from swamp-club's `analysis-factors_test.ts` and `scorecard_test.ts` - [x] 6 integration tests in `integration/extension_quality_test.ts` covering the full CLI pipeline (package → cache → extract → deno doc → compose), including cache hit/miss, source mutation invalidating cache, log and JSON modes - [x] Smoke test against real `@si/s3-datastore` extension — 12/12 (100%), matches registry - [x] `deno check` clean - [x] `deno lint` clean - [x] `deno fmt --check` clean - [x] `deno run test` (4,802 passed, 1 pre-existing flaky unrelated to this change, 1 ignored) - [x] `deno run compile` succeeds ## Follow-ups (not in this PR) - File issue in `systeminit/swamp-uat` for CLI UAT coverage (`tests/cli/extension/quality/`) - File issue in `systeminit/swamp-club` to document the new command in `content/manual/how-to` - Consider a shared scoring package between `systeminit/swamp` and `systeminit/swamp-club` to eliminate the current ~100 lines of mirrored logic 🤖 Generated with [Claude Code](https://claude.com/claude-code)
PreviousNext