Skip to content

Todo - #10

Merged
giveen merged 25 commits into
mainfrom
todo
May 12, 2026
Merged

Todo#10
giveen merged 25 commits into
mainfrom
todo

Conversation

@giveen

@giveen giveen commented May 12, 2026

Copy link
Copy Markdown
Owner

Description of Changes

Contributor License Agreement (CLA)

To accept your code, we legally need you to agree to our CLA so we can maintain the project's Business Source License (BSL) and future open-source transitions.

  • By checking this box, I confirm that I have read and agree to the terms of the CLA.md in this repository. (To check the box, put an x between the brackets like this: [x])

giveen added 24 commits May 5, 2026 17:53
- cmd/late-sast: add 4 failure-injection tests covering prompt load
  failure, mkdirAll failure, retest readFile failure, and non-existent
  retest path — completes TODO item 1 (full-pipeline regression coverage)

- internal/rescan: new package scaffolding Phase 1 of the incremental
  rescan architecture (TODO item 2)
  - models.go: SourceItem, TransformRecord, RunSummary, DeltaScope
  - hash.go: HashFile, HashBytes, TransformKey (deterministic SHA-256 keys)
  - store.go: Store interface
  - file_store.go: JSON-on-disk Store with atomic writes and reopen safety
  - scope.go: ComputeDeltaScope (changed-source detection against prior state)
  - rescan_test.go: 16 tests covering all primitives, round-trips,
    persistence across reopen, and delta-scope edge cases
…g persistence

- internal/rescan/models.go: add FindingRecord, FindingStatus enum,
  ReconcileResult
- internal/rescan/hash.go: add FindingID(cwe, location, title) — stable
  SHA-256 key derived from normalized composite fields
- internal/rescan/store.go: extend Store interface with GetFinding,
  PutFinding, ListFindings
- internal/rescan/file_store.go: implement new Store methods; extend
  storeState JSON envelope to persist findings across reopen
- internal/rescan/reconcile.go: new Reconcile() — compares current
  FindingInput set against prior state, emits inserted/updated/
  resolved/unchanged; marks resolved findings with ResolvedRunID
- internal/rescan/rescan_test.go: 14 new tests covering FindingID
  determinism, case normalization, finding store round-trip and
  persistence across reopen, and all Reconcile() outcome paths

- internal/tool/subagent.go: rename unused 'out' param to _
- cmd/late-sast/main.go: rename unused 'notifyRootAgent' param to _
The cve.circl.lu API now returns CVE 5.x records where CVSS is buried
at containers.cna.metrics[].cvssV3_1.baseScore and package name is at
containers.cna.affected[0].packageName. Tools were returning raw JSON,
causing the LLM to produce 'unknown:unknown' packages and 0.0 CVSS.

Changes:
- internal/tool/cve_search.go: add CVE5 record types (cve5Record,
  cve5CNA, cve5Metric, cve5CVSSScore, etc.) and ParsedCVEFinding output
  type; add extractCVSS (V3.1 > V4.0 > V3.0 > V2.0 preference),
  extractPackage, extractDescription (single-line, 200-char truncation),
  extractAffectedVersions, parseCVE5RecordToFinding helpers; add
  parseCVE5SearchResponse / parseCVE5SingleResponse / parseCVE5LastResponse
  for the three API endpoint shapes
- VulVendorProductCVETool: add limit (default 50) and min_cvss params;
  return {vendor, product, total, returned, findings:[...]} instead of
  raw API JSON; fall back to raw body on unexpected API shape
- VulCVESearchTool: return parsed ParsedCVEFinding instead of raw record
- VulLastCVEsTool: return {count, findings:[...]} instead of raw list
- internal/tool/cve_search_test.go: add testCVE5SearchBody,
  testCVE5SingleBody, testCVE5LastBody fixtures in CVE5 format; add
  14 parser unit tests (extractCVSS, extractDescription, extractPackage,
  parseCVE5*); update Execute success tests to assert parsed fields
- internal/assets/prompts/instruction-sast-scanner.md: document new
  structured output schema; add min_cvss=7.0 to call examples; add
  explicit 'do not invent CVE IDs' guardrail
- internal/assets/prompts/instruction-sast-scanner-binary.md: same
Adds directed lineage graph between findings and a RetestScope function
that uses it to determine which findings need retesting.

internal/rescan/lineage.go (new):
- LineageEdge model: ParentID, ChildID, Kind (confirmed/escalated/chained),
  RunID
- RetestScope(findings, delta, edges) — marks a finding for retest when:
  1. its source file appears in DeltaScope.ChangedSources
  2. ExploitStatus is not 'confirmed'
  3. Status is FindingNew or FindingUpdated
  4. any ancestor in the lineage graph satisfies the above (BFS propagation)
- Internal helpers: putLineageEdge (idempotent), listEdgesFrom/To/All

internal/rescan/store.go:
- Extended Store interface with PutLineageEdge, ListEdgesFrom, ListEdgesTo,
  ListAllEdges

internal/rescan/file_store.go:
- Added lineageEdges map field and Edges field in storeState (JSON-persisted)
- Wired the four new Store methods through to the in-memory map + save()
- Edges survive fileStore reopen (backwards-compatible: omitempty)

internal/rescan/rescan_test.go:
- 16 new tests: LineageEdge round-trip, idempotency, persistence across
  reopen, ListEdgesFrom empty guard; RetestScope: changed source, unconfirmed
  exploit, confirmed+unchanged skip, new finding always retested,
  lineage propagation, no propagation when root is skipped
- Add debug.Logger.LogOperatorError: always writes [operator-error] <component>: <msg>: <err>
  to stderr; also writes OPERATOR_ERROR event to debug log file when enabled
- MCP load/connect errors in main.go use [operator-error] prefix for grep-friendliness
- internal/mcp/client.go Close() error uses [operator-error] prefix
- internal/gui/confirm.go: replace silent fyne.LogError allowlist-save failures with
  dialog.ShowError shown to operator in GUI; add win param to applyChoice helper
- internal/tool/cleanup_scan_environment.go: add operator_note field to partial cleanup
  JSON response listing failed step names and count
- internal/tool/write_sast_report.go: add OnError func(path, err) callback field
- cmd/late-sast/scan_build.go: wire WriteSASTReportTool.OnError to debugLog.LogOperatorError
- internal/debug/logger_test.go: 2 new tests for LogOperatorError (enabled + disabled paths)
bootstrap_scan_toolchain:
- Replace 12 serial commandAvailable() docker execs for final availability
  check with a single batched exec (batchAvailabilityCmd + parseBatchAvailability)
- Replace serial PM detection + node/go/cargo presence + java/node marker
  file-scans with a single batched exec (batchProbeCmd + parseBatchProbe)
- Net reduction: ~18-20 docker exec calls → ~9-11 per bootstrap invocation
- Remove now-unused javaMarkerCmd, nodeMarkerCmd, detectPackageManagerCmd
- Update test stubs to match new batched output format

cmd/late-sast/main.go:
- Replace sync.Once for architecture metadata fetch with sync.Mutex + bool
  so failed fetches (e.g. MCP not yet connected) are retried on subsequent
  subagent spawns instead of locking in the failure forever
- resolveBudget reads cachedMeta under the lock to avoid data races
Add isParallelSafe() classifier for deterministic read-only tools (read_file,
context_index, docs_*, cve_search, vul_*, assess_disclosure_context).

When all tool calls in a turn are parallel-safe and there are at least two,
executeParallelBatch runs them concurrently with a bounded semaphore
(maxParallelToolCalls=4). Results are buffered in a pre-allocated slice
(indexed by original position) and added to session sequentially, preserving
session history thread-safety.

Mixed batches (any non-parallel-safe tool) fall through to the existing
sequential loop unchanged.

Tests: TestExecuteParallelBatch, TestExecuteToolCallsWithStats_ParallelBatch
… batch, --tui)

1. Architecture metadata fetch timing (cmd/late-sast/main.go)
   - Add retry loop (3 attempts, 200ms apart) inside fetchMetaOnce to absorb
     brief MCP startup lag without waiting for the next subagent spawn.
   - Guard against all-zero ComplexityMeta (repo not yet indexed): treat empty
     data as a failed fetch so metaFetched stays false and we keep retrying.
     Previously a zero-metric success response would lock in a wrong budget.

2. Merge docker inspect round-trips in launch_docker.go
   - Add inspectNameAndPorts(): single 'docker inspect -f' call returning both
     .Name and .NetworkSettings.Ports via delimited template, replaces two
     serial calls in launchCompose.
   - Add inspectIDAndPorts(): same for launchDockerfile (Id + Ports).
   - Add parsePortsJSON() helper used by both.
   - Saves one docker exec per launch invocation.
   - Update launch_docker_test.go stubs to match the new combined template.

3. --tui flag clarification (cmd/late-sast/main.go)
   - Update flag description: it no longer switches to a TUI mode, it just
     keeps stdout/stderr on the terminal instead of redirecting to the log file.
   - Remove stale 'TUI mode removed' comment.
Move domain-grouped tools into dedicated sub-packages:
- internal/tool/docker: bootstrap_scan_toolchain, cleanup_scan_environment,
  launch_docker, resolve_install_strategy, setup_container, wait_for_target_ready
- internal/tool/sast: run_secrets_scanner, run_semgrep_scan, run_trivy_scan,
  write_sast_report
- internal/tool/exploit: assess_disclosure_context, run_exploit_replay
- internal/tool/knowledge: context_index, cve_search, docs_lookup

Update all import paths in: cmd/late-sast/{main,scan_build}.go,
cmd/run-tools/main.go, internal/agent/agent.go, internal/tool/implementations.go,
and all affected test files.
… orchestrator

executor:
- Fix ToolResultCache.Get TOCTOU race: re-read entry under write lock before
  deleting to avoid killing a concurrently-refreshed valid entry
- Thread onToolState into executeParallelBatch so GUI shows tool status for
  parallel read batches (was silently skipped)
- Add toolcache_test.go with TTL/InvalidateAll/concurrent-Set coverage

session:
- Replace static toolRequiresArgs list with JSON-schema introspection via
  t.Parameters() so it stays accurate as tools are added/removed
- Fix ListSessions sort order: descending (newest first) to match browser UX

orchestrator:
- Extract prepareContext() and doRunLoop() from Execute/run to eliminate
  ~80 lines of copy-pasted RunLoop setup

gui:
- Remove duplicate formatSubagentContextUsage; all sites use formatContextUsage
- Extract finalize closure in events.go; fix idle double-enable bug and
  closed path missing streaming flag reset

debug:
- Logger now lazy-opens the log file on first write and holds the fd open
  for the lifetime of the logger instead of reopening on every logEntry call
- Add Close() method for explicit cleanup
- Fix LogToolResultWithMeta redundant intermediate map

mcp:
- Connect() now takes a name parameter so ConnectFromConfig sessions are
  stored by server name; previously every server overwrote the 'default' key,
  leaking N-1 connections and dropping all but the last server's tools
internal/git/worktree.go:
- Hoist worktreePattern and new detachedPattern to package-level vars
  (previously recompiled on every call / every loop iteration)
- Extract parseWorktreeLines([]string) []WorktreeInfo so tests can
  exercise the real parsing logic
- Fix detached-HEAD detection: git worktree list emits
  'path hash (detached HEAD)' in parens, not brackets; the old regex
  only matched the bracket form so detached worktrees were silently
  dropped

internal/git/worktree_test.go:
- Replace parseMockWorktreeOutput/regexpWorktreeParser helpers (which
  used an invented format) with direct calls to parseWorktreeLines
- Update all mock fixtures to the real git-worktree-list format:
  'path  hash [branch]' and 'path  hash (detached HEAD)'
- Update TestWorktreeParsing_RegexEdgeCases to test the real package
  patterns instead of an inline dead regex
- Remove bufio/regexp imports no longer needed

internal/rescan/file_store.go:
- Add dirty bool field; Put* methods mark dirty instead of calling
  save() on every mutation (was O(n^2) bytes written for n findings)
- Close() now flushes pending writes (satisfying the Store.Close()
  contract: 'flushes any pending writes and releases resources')
…l on Submit

mutatesWorkspace() and cacheTTLFor() both referenced dead tool names
'compose_patch' and 'implementations'. The actual registered tool names
are 'patch_compose_network' and 'target_edit'. Because mutatesWorkspace()
never matched these tools, cache.InvalidateAll() was never called after a
target_edit or patch_compose_network call, causing subsequent read_file
results to be served from the pre-edit cache — the LLM would see stale
file content after every targeted edit or compose patch.

Also fix a spurious StopRequestedEvent in BaseOrchestrator.Submit(): if
Cancel() was called after the previous run had already completed normally,
stopCh held a residual signal. The next Submit()→run() would drain it via
IsStopRequested() at run end and emit a StopRequestedEvent for a run that
was never cancelled. Fixed by draining stopCh inside the Submit() lock
before starting the new goroutine.
…oss updates

internal/tool/utils.go — GetToolParam's streaming fallback called
regexp.MustCompile on every invocation when JSON unmarshal failed (i.e.
on every partial-JSON chunk during streaming). During a typical run with
many streaming chunks, each tool call's parameter ("path", "command",
etc.) triggered a fresh regex compile, adding measurable overhead. Fixed
by caching compiled *regexp.Regexp values in a package-level sync.Map
keyed by the parameter name; each pattern is compiled at most once.

internal/session/session.go — GenerateSessionMeta() always returned
CreatedAt: time.Now(). Since UpdateSessionMetadata() calls
GenerateSessionMeta() and saveAndNotify() calls UpdateSessionMetadata()
on every message, the stored CreatedAt was overwritten with the current
time on every message, making every session appear brand-new in the
session list. Fixed by loading the existing metadata record before
constructing the return value; if a stored CreatedAt exists and is
non-zero it is preserved, otherwise time.Now() is used (first save).
…mDir write

internal/mcp/config.go — ExpandEnvVars called regexp.MustCompile with
the static pattern $\{([^}]+)\} on every invocation. ExpandServerEnvVars
calls ExpandEnvVars for every string field of every MCPServer on each
config load. Hoisted the compiled regexp to a package-level var so it is
compiled once; simplified the match extraction to use the capture group
directly instead of TrimPrefix/TrimSuffix string manipulation. Removed
now-unused 'strings' import.

internal/config/config.go — SaveConfigFromDir used os.WriteFile (direct
truncate-then-write), which leaves a zero-length or partially-written
config.json if the process crashes mid-write. Every other persistence
function in the codebase (SaveHistory, SaveSessionMeta) uses an atomic
temp-file + rename pattern. Applied the same pattern here: write to a
temp file in the same directory, chmod it, then os.Rename into place so
the update is atomic from the filesystem's perspective.
…ndalone auditor

- Remove references to late IDE and mlhher/late agent engine base
- Clarify late-sast as autonomous security auditor
- Remove fallback config paths to late/ directory
- Simplify configuration section to reference only late-sast config
- Update session persistence and config file location documentation
- Add enabled_tools field showing default tools
- Use realistic model names (Qwen3.6-35B-A3B-Uncensored, VulnLLM-R-7B, etc.)
- Show auditor_model can be set without auditor_base_url (falls back to main)
- Include debug_logging as an optional field
- Clarify optional fields and fallback behavior
- Show llama-swap endpoint without /v1 suffix (correct format)
- Add config.json example for llama-swap setup
- Add same-turn deduplication for identical tool calls (prevents duplicate execution of repeated model emissions)
- Track tool calls with fromDedup flag to distinguish cache hits from duplicates
- Add maxToolPlanCycleLength and toolPlanHistoryWindow constants for plan cycle detection
- Improve duplicate tool turn detection with sliding window signature comparison
- Add tool call repair test suite for validating execution recovery
- Enhance session logging with detailed tool execution events
Copilot AI review requested due to automatic review settings May 12, 2026 15:26

Copilot AI 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.

Pull request overview

This PR is a broad refactor + hardening pass across the late-sast scanning pipeline: it reorganizes tool packages, adds incremental rescan state (store/delta/reconcile/lineage), improves tool-call robustness (repair + loop detection), introduces parallel execution for safe tool batches, and upgrades operator-visible error surfacing and documentation accordingly.

Changes:

  • Split tools into subpackages (tool/docker, tool/sast, tool/exploit, tool/knowledge) and introduced shared runner/utilities (tool.CommandRunner, tool.GetToolParam, tool.Truncate, tool.ShQuote).
  • Added incremental rescan primitives (internal/rescan/*) including persistent file store, delta scope, reconciliation, and lineage/retest scope logic.
  • Improved runtime behavior: parallel-safe tool batching, stronger tool-call argument repair, orchestrator cancellation fixes, operator-error logging, and multiple test additions/adjustments.

Reviewed changes

Copilot reviewed 84 out of 86 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
TODO.md Updates TODO tracking to reflect completed work and adds backlog notes.
internal/tool/utils.go Exports GetToolParam/Truncate and adds regex caching for streaming fallback parsing.
internal/tool/utils_test.go Adds unit tests for GetToolParam including streaming/partial JSON cases.
internal/tool/targetEdit.go Switches to exported GetToolParam/Truncate.
internal/tool/subagent.go Minor cleanup + uses exported GetToolParam/Truncate.
internal/tool/sast/write_sast_report.go Moves SAST report tool to package sast and adds OnError callback.
internal/tool/sast/write_sast_report_test.go Updates tests for package sast.
internal/tool/sast/run_trivy_scan.go Moves to package sast, uses shared runner/util helpers.
internal/tool/sast/run_trivy_scan_test.go Updates tests for package sast.
internal/tool/sast/run_semgrep_scan.go Moves to package sast, uses shared runner/util helpers.
internal/tool/sast/run_semgrep_scan_test.go Updates tests for package sast.
internal/tool/sast/run_secrets_scanner.go Moves to package sast, uses shared runner/util helpers, uses shared ShQuote.
internal/tool/sast/run_secrets_scanner_test.go Updates tests and adds unit coverage for parsing helpers.
internal/tool/sast_tools_test.go Makes heartbeat test deterministic via channel synchronization.
internal/tool/runner.go Introduces CommandRunner, RunSetupCommand, and ShQuote for DI + shell safety.
internal/tool/run_exploit_replay_test.go Removes old exploit replay tests from the root tool package (relocated).
internal/tool/permissions_user_test.go Updates to use exported GetToolParam; minor whitespace fixes.
internal/tool/knowledge/docs_lookup.go Moves docs tools to package knowledge and uses shared util functions.
internal/tool/knowledge/docs_lookup_test.go Updates tests for package knowledge and improves struct formatting.
internal/tool/knowledge/cve_search_test.go Adds CVE5 fixtures + parser tests and updates tool tests to CVE5 schema.
internal/tool/knowledge/context_index.go Moves context index tool to package knowledge.
internal/tool/knowledge/context_index_test.go Updates tests for package knowledge.
internal/tool/implementations.go Updates call-string parsing to use exported utils; removes implementation plan tool.
internal/tool/exploit/run_exploit_replay.go Moves exploit replay tool to package exploit, uses shared runner/util, fixes body read error handling.
internal/tool/exploit/run_exploit_replay_test.go Adds/relocates exploit replay tests + coverage for endpoint building/verdict classification.
internal/tool/exploit/assess_disclosure_context.go Moves to package exploit and uses shared util functions.
internal/tool/exploit/assess_disclosure_context_test.go Updates tests for package exploit.
internal/tool/docker/wait_for_target_ready.go Moves to package docker, uses shared runner/util functions.
internal/tool/docker/wait_for_target_ready_test.go Updates tests for package docker.
internal/tool/docker/setup_container.go Moves to package docker, swaps local runner typedef for shared CommandRunner.
internal/tool/docker/setup_container_test.go Updates tests for package docker.
internal/tool/docker/resolve_install_strategy.go Moves to package docker, uses shared util functions.
internal/tool/docker/resolve_install_strategy_test.go Updates tests for package docker.
internal/tool/docker/launch_docker.go Moves to package docker, uses shared runner/util, reduces docker inspect round-trips.
internal/tool/docker/launch_docker_test.go Updates tests for package docker and new inspect formatting.
internal/tool/docker/cleanup_scan_environment.go Moves to package docker, adds operator note for partial cleanups, uses shared ShQuote.
internal/tool/docker/cleanup_scan_environment_test.go Updates tests for package docker.
internal/tool/docker/bootstrap_scan_toolchain.go Moves to package docker, batches docker exec probes for performance.
internal/tool/docker/bootstrap_scan_toolchain_test.go Updates tests for batching behavior.
internal/tool/compose_patch.go Renames pure function to exported PatchComposeNetwork.
internal/tool/compose_patch_test.go Updates to call PatchComposeNetwork.
internal/session/tool_call_repair_test.go Adds extensive tests for argument repair and tool-call dropping behavior.
internal/session/session.go Improves tool-call validation/repair, uses registry-based required-args detection, preserves session CreatedAt.
internal/session/models.go Fixes sorting comment/behavior to “most recent first”.
internal/rescan/store.go Adds rescan persistence interface.
internal/rescan/scope.go Adds delta scope computation.
internal/rescan/reconcile.go Adds finding reconciliation (insert/update/unchanged/resolve).
internal/rescan/models.go Adds rescan data models and enums.
internal/rescan/lineage.go Adds lineage edges + retest scope propagation logic.
internal/rescan/hash.go Adds deterministic hashing + stable FindingID + TransformKey.
internal/rescan/file_store.go Adds file-backed JSON store with atomic writes and lineage persistence.
internal/orchestrator/base.go Fixes cancellation/stop signaling, preserves root context, refactors shared run-loop logic.
internal/mcp/config.go Caches env var regex; avoids recompiling and removes strings dependency.
internal/mcp/client.go Allows multiple named MCP sessions; improves close error visibility.
internal/gui/sast_picker.go Removes sleep-based GUI sync; uses queued callbacks to signal readiness.
internal/gui/events.go Deduplicates “finalize” logic for idle/closed/error transitions.
internal/gui/confirm.go Surfaces allowlist-save failures via GUI dialogs (operator-visible).
internal/gui/app.go Removes subagent-only context usage formatter; uses unified formatter.
internal/git/worktree.go Threads contexts into git commands, improves error surfacing, normalizes symlink comparisons.
internal/executor/toolcache.go Fixes cache expiry deletion race and updates mutating tool set.
internal/executor/toolcache_test.go Adds full branch coverage for TTL/timeout switch tables.
internal/executor/executor.go Adds parallel-safe batch execution + dedup, improves loop detection, simplifies tool registration.
internal/executor/executor_test.go Updates tests for RegisterTools signature and adds tests for new batch/cycle detection helpers.
internal/debug/logger.go Adds operator-error logging, lazy file open, and logger Close method.
internal/debug/logger_test.go Adds tests for operator-error logging behavior.
internal/config/config.go Switches config writes to atomic temp+rename pattern.
internal/config/config_test.go Adds tests for secure permission enforcement.
internal/common/utils_test.go Adds tests for EstimateToolDefinitionTokens.
internal/assets/prompts/instruction-sast-scanner.md Updates CVE tool usage and documents structured CVE output.
internal/assets/prompts/instruction-sast-scanner-binary.md Updates binary scanner prompt for structured CVE output and min_cvss.
internal/assets/prompts/instruction-planning.md Removes planning-agent prompt (planning tool removed).
internal/agent/agent.go Updates tool inheritance rules and improves context-file read error surfacing.
internal/agent/agent_test.go Adds unit tests for replay candidate parsing and ad-hoc cleanup detection.
go.mod Removes unused TUI-related indirect dependencies.
docs/quickstart.md Updates configuration docs and environment variable examples.
docs/architecture.md Updates high-level description and config/session path references.
cmd/run-tools/main.go Updates direct tool runner to new tool subpackages.
cmd/mcp-run/main.go Updates MCP connect call to provide a session name.
cmd/late-sast/scan_build.go Updates scan tool registration to new subpackages; wires report write error callback.
cmd/late-sast/main.go Improves CLI flag docs, hardens CBM download (context/limits), improves dynamic budget retry logic, updates tool package usage.
cmd/late-sast/main_test.go Updates tests for new tool packages and adds additional failure-injection tests.
.gitignore Stops ignoring implementation_plan.md (planning tool removed).
Comments suppressed due to low confidence (1)

internal/tool/utils.go:51

  • Truncate will panic when maxLen < 3 because it slices with s[:maxLen-3]. Please guard small values (e.g., return ""/s[:maxLen] when maxLen <= 3) so callers can’t crash the process by passing a small limit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/session/session.go
Comment thread internal/mcp/client.go
- remove unused jsonExtractRe in session tool-call repair path
- close/reap replaced MCP session before overwriting by name
- remove stale tools bound to replaced MCP session
- harden Truncate() for maxLen <= 3 and non-positive values
@giveen
giveen merged commit 04b1ecb into main May 12, 2026
6 checks passed
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.

2 participants