Skip to content

Push DuckTap toward v0.6 quality#16

Open
zanni098 wants to merge 1 commit into
mainfrom
codex/v06-quality-push
Open

Push DuckTap toward v0.6 quality#16
zanni098 wants to merge 1 commit into
mainfrom
codex/v06-quality-push

Conversation

@zanni098

@zanni098 zanni098 commented May 26, 2026

Copy link
Copy Markdown
Owner

Summary

Pushes DuckTap from the v0.2.2 surface toward v0.6.0 with quality-focused product upgrades:

  • first-class GraphQL introspection discovery
  • generated CLI local data lake: --save, data query, data search
  • TypeScript, Go, and Rust generator scaffolds, included in the default target set
  • ducktap publish dry-run artifact zip + manifest including all generated outputs
  • scorecard expanded with agent_native and local_data dimensions
  • richer dashboard UI with version/status rail, all-target selector, status API, and refreshed README screenshot
  • README, roadmap, comparison matrix, changelog, and package version updated to 0.6.0

Verification

  • python -m pytest -q -> 51 passed
  • ruff check src tests -> all checks passed
  • ducktap press tests/fixtures/petstore.yaml --out ./out-v06-smoke --name petstore-v06 -> generated six targets
  • ducktap shipcheck petstore-v06 --out-dir ./out-v06-smoke -> all checks OK
  • ducktap scorecard ./out-v06-smoke/petstore-v06.apispec.json --out-dir ./out-v06-smoke --fail-under 80 -> 94/A
  • ducktap publish petstore-v06 --out-dir ./out-v06-smoke --dist-dir ./dist-v06-smoke --dry-run -> archive + manifest created
  • Browser-smoked ducktap ui at http://127.0.0.1:8765 and refreshed docs/img/ducktap-ui.png

Comparison Note

A fresh local go test ./... on mvanhorn/cli-printing-press still shows Windows-sensitive failures in internal/authdoctor and internal/browsersniff before the run stalled in later packages; the run was interrupted after the repeated failure signal was captured.

Summary by CodeRabbit

  • New Features

    • GraphQL introspection discovery support
    • Multi-language CLI scaffolds (TypeScript, Go, Rust)
    • Local data lake: save, query, and search API responses
    • publish command with dry-run packaging and manifest generation
    • Enhanced scorecard with agent-native and local-data dimensions
    • Dashboard target selector and status API
  • Changed

    • Expanded default generation targets
    • Improved generated CLI documentation and capabilities

Review Change Stack

@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
duck-tap Ready Ready Preview, Comment May 26, 2026 7:03am

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

DuckTap releases v0.6.0 with GraphQL introspection discovery, TypeScript/Go/Rust CLI scaffolding, local SQLite data persistence (save/query/search), a publish command for packaging artifacts, enhanced scorecard dimensions, and WebUI target selection with status API. Version bumps, documentation, and comprehensive tests validate the expanded feature ecosystem.

Changes

DuckTap v0.6.0 Release

Layer / File(s) Summary
Version & Documentation Updates
pyproject.toml, src/ducktap/__init__.py, CHANGELOG.md, README.md, docs/COMPARISON.md, docs/ROADMAP.md
Version bumped to 0.6.0 with changelog entry and comprehensive documentation updates across README (artifacts/commands/scaffolds/data lake), comparison guide (DuckTap vs Printing Press positioning), and roadmap (condensed per-version status and known gaps).
GraphQL Introspection Discovery
src/ducktap/discovery/graphql.py, src/ducktap/core/plugins.py
New GraphQLDiscoverer converts GraphQL endpoints to APISpec by POSTing introspection queries, unwrapping types, extracting query fields as POST operations with body parameters, and registering as a built-in plugin for automatic discovery of GraphQL sources.
Multi-Language CLI Scaffolds
src/ducktap/generator/language_scaffolds.py, src/ducktap/generator/templates/cli/README.md.j2
Implements TypeScriptCLIGenerator, GoCLIGenerator, and RustCLIGenerator that scaffold runnable projects with language-specific build files, agent-context JSON export support, and README instructions. Shared helpers normalize operations manifest and embed JSON safely.
Core Infrastructure: DEFAULT_TARGETS & APISpec Export
src/ducktap/core/pipeline.py, src/ducktap/cli.py
Defines DEFAULT_TARGETS constant and updates press command to use it. Adds _plainify() helper for safe JSON serialization and writes ${spec.name}.apispec.json artifact before generator execution for downstream packaging/validation use.
Local Data Lake: Mirror Storage Enhancements
src/ducktap/generator/templates/cli/mirror.py.j2
Extends Mirror SQLite class with new records table (distinct from TTL cache), and implements save_records() bulk-insert, query(sql) for SELECT-only SQL execution, and search(text, limit) for full-text LIKE search over stored JSON responses.
CLI Data Persistence: --save & data Commands
src/ducktap/generator/templates/cli/main.py.j2, src/ducktap/generator/templates/cli/commands.py.j2
Adds --save global flag and Click context wiring, conditionally initializes Mirror when cache or save is enabled, implements data query (SQL execution) and data search (full-text search) command group, and persists successful API responses when save target is set.
Scorecard Enhancements: Agent-Native & Local Data Dimensions
src/ducktap/verify/scorecard.py
Rebalances existing dimension weights and adds two new heuristic-based dimensions: agent_native scans main.py/commands.py for agent-readiness tokens, and local_data scans mirror.py/commands.py for data/query-related tokens, both influencing final overall grade.
Publish Packaging & Release Command
src/ducktap/cli.py
New publish CLI command validates generated artifact directories, optionally computes scorecard from existing APISpec, runs shipcheck validation, zips artifacts with per-language structure into *-ducktap-artifacts.zip, writes *-publish-manifest.json including scorecard/shipcheck results, and supports --dry-run mode.
WebUI: Version Awareness, Targets, and API Status
src/ducktap/webui/app.py
Adds version badge and selectable targets grid to dashboard, imports DEFAULT_TARGETS and builds dynamic target cards. Introduces GET /api/status endpoint returning version/catalog/targets metadata. Updates POST /generate to accept optional targets list and passes them into press().
Comprehensive Test Coverage
tests/test_graphql_discovery.py, tests/test_multilang_generators.py, tests/test_publish.py, tests/test_generated_cli_runtime.py, tests/test_plugins.py, tests/test_webui.py, tests/test_e2e.py
Validates GraphQL discovery type mapping, multi-language scaffold generation and Go test execution, publish manifest/ZIP structure, CLI save/query/search round-trips, expanded plugin registrations, WebUI target selection/status API, and scorecard dimension presence.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A version leap from two-dot-two,
With GraphQL and scaffolds shiny and new,
A local lake stores each API call,
And publishing? It handles them all! 📦

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Push DuckTap toward v0.6 quality' clearly summarizes the main change: upgrading DuckTap from v0.2.2 to v0.6.0 with quality-focused product and feature additions. It is concise, specific, and directly relates to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/v06-quality-push

Comment @coderabbitai help to get the list of available commands and usage tips.

@zanni098
zanni098 marked this pull request as ready for review May 26, 2026 07:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ducktap/core/pipeline.py (1)

48-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add graphql to automatic discovery priority.

Line 48 omits graphql, which prevents automatic GraphQL discovery despite built-in registration. This breaks the “first-class discovery” path unless users force --from graphql.

Proposed fix
-    for name in ("openapi", "har", "browser-sniff"):
+    for name in ("openapi", "har", "graphql", "browser-sniff"):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ducktap/core/pipeline.py` around lines 48 - 49, The discovery priority
tuple in the for-loop (for name in ("openapi", "har", "browser-sniff"): ...)
omits the built-in "graphql" provider so GraphQL never gets auto-discovered;
update that tuple to include "graphql" (e.g., ("openapi", "har", "graphql",
"browser-sniff")) so the loop that does d = discs.get(name) will check and
select the GraphQL disco handler during automatic discovery.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/COMPARISON.md`:
- Around line 55-56: Update the phrase in the sentence mentioning "Printing
Press" to use a hyphenated compound modifier for clarity; change "Claude Code
focused experience" to "Claude Code-focused experience" (or "Claude-focused Code
experience") so the sentence reads e.g. "Pick Printing Press if you want the
most mature Claude Code-focused experience today, a single Go binary, and the
larger built-in catalog." Ensure the hyphenated form replaces the existing
dangling modifier around the "Claude Code" phrase.

In `@docs/ROADMAP.md`:
- Around line 37-39: Update the ROADMAP entry so the GraphQL landing version is
consistent: choose the correct release (either v0.3.0 or v0.6.0), then replace
the mismatched occurrence of "landed in v0.6.0" or "landed in v0.3.0" so both
checklist items/readme lines that mention GraphQL show the same version string
("v0.3.0" or "v0.6.0") and ensure the surrounding checklist text (the three
GraphQL bullets) remains semantically identical.

In `@src/ducktap/discovery/graphql.py`:
- Around line 62-63: The code currently ignores GraphQL introspection errors in
the JSON payload; update the logic around response, schema and fields (the block
using response.json().get("data", {}).get("__schema", {}) and fields =
((schema.get("queryType") or {}).get("fields") or [])) to first check
response.json().get("errors") and, if present and non-empty, raise a discovery
failure (e.g., raise an appropriate DiscoveryError or Exception) with the error
details instead of proceeding to build an empty spec; ensure the error includes
the JSON "errors" content and short context so callers can fail fast on
introspection errors.

In `@src/ducktap/generator/language_scaffolds.py`:
- Around line 54-66: The package.json "bin" entry in the TypeScript CLI scaffold
currently points to "./src/index.js" while the generator (_write call that
creates package.json inside TypeScriptCLIGenerator) only emits src/index.ts and
src/commands.ts; update the created JSON so "bin": {bin_name: "./src/index.ts"}
(or alternatively emit a matching src/index.js wrapper) so the installed CLI
points to the actual generated entry point (adjust the _write call that builds
package.json accordingly).

In `@src/ducktap/generator/templates/cli/commands.py.j2`:
- Around line 457-464: data_search currently allows limit <= 0 which can cause
unbounded/invalid queries; add a guard at the start of the data_search function
to reject values less than 1 (e.g. if limit < 1: raise click.BadParameter("limit
must be >= 1") or emit an error and ctx.exit) before calling _get_client or
client.mirror.search so that invalid limits are rejected early.

In `@src/ducktap/webui/app.py`:
- Around line 36-39: The current comprehension that builds _TARGETS using
next(...) will raise StopIteration at import time if DEFAULT_TARGETS contains a
key missing from _TARGET_DETAILS; replace it with a safe lookup: build a dict
_TARGET_LOOKUP from _TARGET_DETAILS and iterate DEFAULT_TARGETS, either
appending a sensible default entry or raising an explicit ValueError with a
clear message like "Missing _TARGET_DETAILS entry for target: {target}" when a
target is absent; update references to _TARGETS to use this validated list.

In `@tests/test_multilang_generators.py`:
- Around line 1-2: The test currently calls subprocess.run(["go", "test",
"./..."], cwd=go_root, ...) without guarding for a missing Go toolchain; modify
the test to first check for the Go binary (e.g., via shutil.which("go")) or wrap
the subprocess.run call in a try/except catching FileNotFoundError and then call
pytest.skip("go not found; skipping test") (or otherwise skip/return) so CI
won’t fail when Go is absent; update the code around the existing subprocess.run
invocation (the proc variable and go_root usage) to perform this guard and skip
behavior.

---

Outside diff comments:
In `@src/ducktap/core/pipeline.py`:
- Around line 48-49: The discovery priority tuple in the for-loop (for name in
("openapi", "har", "browser-sniff"): ...) omits the built-in "graphql" provider
so GraphQL never gets auto-discovered; update that tuple to include "graphql"
(e.g., ("openapi", "har", "graphql", "browser-sniff")) so the loop that does d =
discs.get(name) will check and select the GraphQL disco handler during automatic
discovery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 905a005b-4867-4962-9a9f-34fb12568e04

📥 Commits

Reviewing files that changed from the base of the PR and between 5c225b6 and 8e1459b.

⛔ Files ignored due to path filters (1)
  • docs/img/ducktap-ui.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • CHANGELOG.md
  • README.md
  • docs/COMPARISON.md
  • docs/ROADMAP.md
  • pyproject.toml
  • src/ducktap/__init__.py
  • src/ducktap/cli.py
  • src/ducktap/core/pipeline.py
  • src/ducktap/core/plugins.py
  • src/ducktap/discovery/graphql.py
  • src/ducktap/generator/language_scaffolds.py
  • src/ducktap/generator/templates/cli/README.md.j2
  • src/ducktap/generator/templates/cli/commands.py.j2
  • src/ducktap/generator/templates/cli/main.py.j2
  • src/ducktap/generator/templates/cli/mirror.py.j2
  • src/ducktap/verify/scorecard.py
  • src/ducktap/webui/app.py
  • tests/test_e2e.py
  • tests/test_generated_cli_runtime.py
  • tests/test_graphql_discovery.py
  • tests/test_multilang_generators.py
  • tests/test_plugins.py
  • tests/test_publish.py
  • tests/test_webui.py

Comment thread docs/COMPARISON.md
Comment on lines +55 to +56
Pick **Printing Press** if you want the most mature Claude Code focused
experience today, a single Go binary, and the larger built-in catalog.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use hyphenated compound modifier for readability.

Line 55 should use “Claude Code-focused experience” (or “Claude-focused Code experience”) to avoid the dangling modifier.

✏️ Suggested doc fix
-Pick **Printing Press** if you want the most mature Claude Code focused
+Pick **Printing Press** if you want the most mature Claude Code-focused
 experience today, a single Go binary, and the larger built-in catalog.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Pick **Printing Press** if you want the most mature Claude Code focused
experience today, a single Go binary, and the larger built-in catalog.
Pick **Printing Press** if you want the most mature Claude Code-focused
experience today, a single Go binary, and the larger built-in catalog.
🧰 Tools
🪛 LanguageTool

[grammar] ~55-~55: Use a hyphen to join words.
Context: ... if you want the most mature Claude Code focused experience today, a single Go bi...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/COMPARISON.md` around lines 55 - 56, Update the phrase in the sentence
mentioning "Printing Press" to use a hyphenated compound modifier for clarity;
change "Claude Code focused experience" to "Claude Code-focused experience" (or
"Claude-focused Code experience") so the sentence reads e.g. "Pick Printing
Press if you want the most mature Claude Code-focused experience today, a single
Go binary, and the larger built-in catalog." Ensure the hyphenated form replaces
the existing dangling modifier around the "Claude Code" phrase.

Comment thread docs/ROADMAP.md
Comment on lines +37 to +39
- [x] GraphQL promoted to a first-class discoverer through introspection
- [x] GraphQL operations normalized into the same `APISpec` model as OpenAPI/HAR
- [x] Plugin autoload includes GraphQL by default

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve GraphQL landing-version inconsistency.

This doc states GraphQL landed in v0.3.0 (Line 37) but also says it landed in v0.6.0 (Line 94). Please align both to one version to keep roadmap history trustworthy.

✏️ Suggested doc fix (if v0.3.0 is correct)
-| GraphQL discovery | landed v0.6.0 |
+| GraphQL discovery | landed v0.3.0 |

Also applies to: 94-94

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ROADMAP.md` around lines 37 - 39, Update the ROADMAP entry so the
GraphQL landing version is consistent: choose the correct release (either v0.3.0
or v0.6.0), then replace the mismatched occurrence of "landed in v0.6.0" or
"landed in v0.3.0" so both checklist items/readme lines that mention GraphQL
show the same version string ("v0.3.0" or "v0.6.0") and ensure the surrounding
checklist text (the three GraphQL bullets) remains semantically identical.

Comment on lines +62 to +63
schema = response.json().get("data", {}).get("__schema", {})
fields = ((schema.get("queryType") or {}).get("fields") or [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast on GraphQL introspection errors in 200 responses.

Line 62 currently ignores the GraphQL errors payload shape, so failed introspection can silently produce an empty spec. Treat errors as a discovery failure.

Proposed fix
-        schema = response.json().get("data", {}).get("__schema", {})
+        payload = response.json()
+        if payload.get("errors"):
+            msgs = ", ".join(
+                e.get("message", "unknown GraphQL error")
+                for e in payload.get("errors", [])
+                if isinstance(e, dict)
+            )
+            raise ValueError(f"GraphQL introspection failed: {msgs or 'unknown error'}")
+        schema = payload.get("data", {}).get("__schema", {})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
schema = response.json().get("data", {}).get("__schema", {})
fields = ((schema.get("queryType") or {}).get("fields") or [])
payload = response.json()
if payload.get("errors"):
msgs = ", ".join(
e.get("message", "unknown GraphQL error")
for e in payload.get("errors", [])
if isinstance(e, dict)
)
raise ValueError(f"GraphQL introspection failed: {msgs or 'unknown error'}")
schema = payload.get("data", {}).get("__schema", {})
fields = ((schema.get("queryType") or {}).get("fields") or [])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ducktap/discovery/graphql.py` around lines 62 - 63, The code currently
ignores GraphQL introspection errors in the JSON payload; update the logic
around response, schema and fields (the block using response.json().get("data",
{}).get("__schema", {}) and fields = ((schema.get("queryType") or
{}).get("fields") or [])) to first check response.json().get("errors") and, if
present and non-empty, raise a discovery failure (e.g., raise an appropriate
DiscoveryError or Exception) with the error details instead of proceeding to
build an empty spec; ensure the error includes the JSON "errors" content and
short context so callers can fail fast on introspection errors.

Comment on lines +54 to +66
_write(root / "package.json", json.dumps({
"name": bin_name,
"version": spec.version,
"type": "module",
"bin": {bin_name: "./src/index.js"},
"scripts": {"start": "tsx src/index.ts"},
"dependencies": {"commander": "^12.0.0"},
"devDependencies": {"tsx": "^4.0.0", "typescript": "^5.0.0"},
}, indent=2) + "\n", written)
_write(root / "src" / "commands.ts", "export const commands = "
+ json.dumps(ops, indent=2) + " as const;\n", written)
_write(root / "src" / "index.ts", f"""#!/usr/bin/env node
import {{ Command }} from "commander";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For npm packages, if package.jsonhas"bin": {"mycli": "./src/index.js"}but onlysrc/index.ts exists, will the installed binary work without a build step? What is the recommended approach for TypeScript CLIs using tsx?

💡 Result:

No, the installed binary will not work if the bin field points directly to a .ts file without a build step [1][2]. The npm install process links the file specified in package.json to the system or local node_modules/.bin directory [3]. When executed, this file is run by the Node.js runtime, which does not natively understand TypeScript syntax and will fail to execute a .ts file [2]. To use tsx (or similar tools like ts-node) for a TypeScript CLI, the recommended approach involves these steps: 1. Configure the Shebang: The top of your source file should include a shebang that invokes tsx to ensure the file is executed correctly when called as a command [2]. #!/usr/bin/env -S npx tsx Alternatively, you can create a lightweight JavaScript wrapper file (e.g., bin/cli.js) that includes the shebang and imports your main TypeScript file using tsx or a similar loader [1][2]. 2. Set File Permissions: For the binary to be executable on Unix-like systems, the file must have the execute bit set (chmod +x). Since npm may not preserve these permissions across all environments, many developers handle this by explicitly compiling to a JavaScript file in a dist/ directory, which ensures platform-independent execution [4][5]. 3. Recommended Production Approach: While tsx is excellent for development (zero-config, high speed, and watch mode) [6][7], the standard best practice for publishing npm packages is to compile your TypeScript code into JavaScript using tsc or a bundler (like tsup or esbuild) [5][7]. Your package.json should then point the bin field to the generated JavaScript file in your build output (e.g., ./dist/index.js) [8][5]. This approach ensures that your package is performant, does not require users to have tsx or typescript installed in their own environment, and avoids runtime transpilation overhead [9][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the referenced section
sed -n '1,120p' src/ducktap/generator/language_scaffolds.py | nl -ba | sed -n '45,90p'

# Search for any writes of src/index.js (or similar bin wrappers)
rg -n --fixed-strings 'src/index.js' src/ducktap/generator/language_scaffolds.py src/ducktap/generator -S || true

# Search for writes of index.* in the language scaffold generator(s)
rg -n --regex 'src\/index\.(js|cjs|mjs|ts)' src/ducktap/generator -S || true

# Also search for any "bin" generation beyond package.json snippet
rg -n --fixed-strings '"bin"' src/ducktap/generator -S || true

Repository: zanni098/DuckTap

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/ducktap/generator/language_scaffolds.py"

# Print lines 40-90 around the cited region (use sed line addressing)
sed -n '40,90p' "$FILE" | sed -n '1,120p'

# Search for any writes of src/index.js (or similar bin wrappers) within generator code
rg -n --fixed-strings 'src/index.js' src/ducktap/generator -S || true

# Search for writes of index.* variants
rg -n --regex 'src\/index\.(js|cjs|mjs|ts)' src/ducktap/generator -S || true

# Search for "bin" assignment/generation elsewhere in generator
rg -n --fixed-strings '"bin"' src/ducktap/generator -S || true

# Additionally search whole repo for "src/index.js" to rule out other generator/templates
rg -n --fixed-strings 'src/index.js' -S || true

Repository: zanni098/DuckTap

Length of output: 2611


Fix TypeScript CLI bin entry pointing to a non-existent file
TypeScriptCLIGenerator sets package.json "bin" to ./src/index.js (src/ducktap/generator/language_scaffolds.py line 58), but it only generates ./src/index.ts (and ./src/commands.ts). The installed CLI will therefore reference a file that isn’t created. Update "bin" to the generated entry (or generate a matching src/index.js wrapper / build output).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ducktap/generator/language_scaffolds.py` around lines 54 - 66, The
package.json "bin" entry in the TypeScript CLI scaffold currently points to
"./src/index.js" while the generator (_write call that creates package.json
inside TypeScriptCLIGenerator) only emits src/index.ts and src/commands.ts;
update the created JSON so "bin": {bin_name: "./src/index.ts"} (or alternatively
emit a matching src/index.js wrapper) so the installed CLI points to the actual
generated entry point (adjust the _write call that builds package.json
accordingly).

Comment on lines +457 to +464
@click.option("--limit", type=int, default=20, show_default=True)
@click.pass_context
def data_search(ctx: click.Context, text: str, limit: int) -> None:
client = _get_client(ctx)
if client.mirror is None:
click.echo(json.dumps({"error": "local data lake is disabled"}), err=True)
ctx.exit(10)
_emit(ctx, client.mirror.search(text, limit=limit), raw=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate --limit lower bound in data search.

limit currently accepts 0/negative values, which can lead to unexpected or effectively unbounded reads. Reject values < 1 before executing the query.

Suggested fix
 `@data_group.command`("search")
 `@click.argument`("text")
 `@click.option`("--limit", type=int, default=20, show_default=True)
 `@click.pass_context`
 def data_search(ctx: click.Context, text: str, limit: int) -> None:
+    if limit < 1:
+        click.echo(json.dumps({"error": "--limit must be >= 1"}), err=True)
+        ctx.exit(10)
     client = _get_client(ctx)
     if client.mirror is None:
         click.echo(json.dumps({"error": "local data lake is disabled"}), err=True)
         ctx.exit(10)
     _emit(ctx, client.mirror.search(text, limit=limit), raw=True)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@click.option("--limit", type=int, default=20, show_default=True)
@click.pass_context
def data_search(ctx: click.Context, text: str, limit: int) -> None:
client = _get_client(ctx)
if client.mirror is None:
click.echo(json.dumps({"error": "local data lake is disabled"}), err=True)
ctx.exit(10)
_emit(ctx, client.mirror.search(text, limit=limit), raw=True)
`@click.option`("--limit", type=int, default=20, show_default=True)
`@click.pass_context`
def data_search(ctx: click.Context, text: str, limit: int) -> None:
if limit < 1:
click.echo(json.dumps({"error": "--limit must be >= 1"}), err=True)
ctx.exit(10)
client = _get_client(ctx)
if client.mirror is None:
click.echo(json.dumps({"error": "local data lake is disabled"}), err=True)
ctx.exit(10)
_emit(ctx, client.mirror.search(text, limit=limit), raw=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ducktap/generator/templates/cli/commands.py.j2` around lines 457 - 464,
data_search currently allows limit <= 0 which can cause unbounded/invalid
queries; add a guard at the start of the data_search function to reject values
less than 1 (e.g. if limit < 1: raise click.BadParameter("limit must be >= 1")
or emit an error and ctx.exit) before calling _get_client or
client.mirror.search so that invalid limits are rejected early.

Comment thread src/ducktap/webui/app.py
Comment on lines +36 to +39
_TARGETS = [
next(details for details in _TARGET_DETAILS if details[0] == target)
for target in DEFAULT_TARGETS
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

StopIteration crash if DEFAULT_TARGETS and _TARGET_DETAILS drift.

If a new target is added to DEFAULT_TARGETS but not to _TARGET_DETAILS, next() raises StopIteration at import time, crashing the app with an unhelpful traceback. Provide a default or explicit error.

Proposed fix: add a meaningful fallback or error
 _TARGETS = [
-    next(details for details in _TARGET_DETAILS if details[0] == target)
+    next(
+        (details for details in _TARGET_DETAILS if details[0] == target),
+        (target, target, ""),  # fallback: use target ID as label if not found
+    )
     for target in DEFAULT_TARGETS
 ]

Or raise an explicit error during startup:

_TARGET_LOOKUP = {t[0]: t for t in _TARGET_DETAILS}
_TARGETS = []
for target in DEFAULT_TARGETS:
    if target not in _TARGET_LOOKUP:
        raise ValueError(f"Missing _TARGET_DETAILS entry for target: {target}")
    _TARGETS.append(_TARGET_LOOKUP[target])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ducktap/webui/app.py` around lines 36 - 39, The current comprehension
that builds _TARGETS using next(...) will raise StopIteration at import time if
DEFAULT_TARGETS contains a key missing from _TARGET_DETAILS; replace it with a
safe lookup: build a dict _TARGET_LOOKUP from _TARGET_DETAILS and iterate
DEFAULT_TARGETS, either appending a sensible default entry or raising an
explicit ValueError with a clear message like "Missing _TARGET_DETAILS entry for
target: {target}" when a target is absent; update references to _TARGETS to use
this validated list.

Comment on lines +1 to +2
import subprocess
from pathlib import Path

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Go toolchain availability:"
command -v go || echo "go missing"

echo
echo "Relevant test lines:"
rg -n -C2 'subprocess\.run\(\["go",\s*"test",\s*"\./\.\."\]|shutil\.which\("go"\)|pytest\.skip\(' --type=py

Repository: zanni098/DuckTap

Length of output: 130


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="tests/test_multilang_generators.py"
echo "=== File header / around line 1-80 ==="
nl -ba "$FILE" | sed -n '1,120p'

echo
echo "=== Search for go test invocation ==="
rg -n --hidden --no-ignore -S '(\bgo\b|subprocess\.run\(|go test|\["go",\s*"test")' "$FILE"

echo
echo "=== Search for any existing skip/guard logic ==="
rg -n --hidden --no-ignore -S '(pytest\.skip|shutil\.which\("go"\)|which\("go"\)|GO_TOOLCHAIN|toolchain)' "$FILE" || true

Repository: zanni098/DuckTap

Length of output: 160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="tests/test_multilang_generators.py"
echo "=== First 160 lines of $FILE (with line numbers) ==="
cat -n "$FILE" | sed -n '1,160p'

echo
echo "=== Search for go test invocation ==="
rg -n --hidden --no-ignore -S '(\bgo\b|subprocess\.run\(|go test|\["go",\s*"test")' "$FILE"

echo
echo "=== Search for any existing skip/guard logic ==="
rg -n --hidden --no-ignore -S '(pytest\.skip|shutil\.which\("go"\)|which\("go"\)|GO_TOOLCHAIN|toolchain)' "$FILE" || true

Repository: zanni098/DuckTap

Length of output: 2215


Guard Go toolchain invocation in tests/test_multilang_generators.py to prevent CI crashes when go isn’t installed.

proc = subprocess.run(["go", "test", "./..."], cwd=go_root, ...) at lines 31-32 is unguarded; if go is missing on PATH, subprocess.run raises FileNotFoundError and fails unrelated CI runs.

Proposed fix
 import subprocess
+import shutil
 from pathlib import Path
 
+import pytest
 from ducktap.core.pipeline import press
@@
-    proc = subprocess.run(["go", "test", "./..."], cwd=go_root, capture_output=True, text=True)
+    if shutil.which("go") is None:
+        pytest.skip("go toolchain not available on test runner")
+    proc = subprocess.run(
+        ["go", "test", "./..."], cwd=go_root, capture_output=True, text=True
+    )
     assert proc.returncode == 0, proc.stderr
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_multilang_generators.py` around lines 1 - 2, The test currently
calls subprocess.run(["go", "test", "./..."], cwd=go_root, ...) without guarding
for a missing Go toolchain; modify the test to first check for the Go binary
(e.g., via shutil.which("go")) or wrap the subprocess.run call in a try/except
catching FileNotFoundError and then call pytest.skip("go not found; skipping
test") (or otherwise skip/return) so CI won’t fail when Go is absent; update the
code around the existing subprocess.run invocation (the proc variable and
go_root usage) to perform this guard and skip behavior.

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.

1 participant