Skip to content

feat: add curated Drata compliance workflows#1

Merged
ethanolivertroy merged 14 commits intomainfrom
feat/curated-compliance-workflows
Apr 25, 2026
Merged

feat: add curated Drata compliance workflows#1
ethanolivertroy merged 14 commits intomainfrom
feat/curated-compliance-workflows

Conversation

@ethanolivertroy
Copy link
Copy Markdown
Owner

@ethanolivertroy ethanolivertroy commented Apr 24, 2026

Summary

Adds read-only curated compliance workflow commands on top of the generated Drata OpenAPI CLI.

These commands provide quick compliance triage without sacrificing the existing generic operation surface.

Added

  • drata summary --json --compact
  • drata controls failing --json --compact
  • drata monitors failing --json --compact
  • drata connections list --status DISCONNECTED --json --compact
  • drata personnel issues --json --compact
  • drata evidence expiring --days 60 --json --compact
  • drata auth check --json
  • --compact and workflow --limit
  • v1 page/limit pagination support for --all-pages
  • shell completion support for new workflow commands

Notes

Curated workflows are read-only and reject --dry-run to avoid misleading empty summaries.

Test plan

  • npm test
  • npm pack --dry-run

Open in Devin Review

Summary by CodeRabbit

  • New Features

    • Added curated compliance workflow commands: summary, controls failing, monitors failing, connections list, personnel issues, and evidence expiring for streamlined compliance triage.
    • Added auth check subcommand for API validation.
    • Added --compact and --limit flags to filter and format workflow output.
  • Documentation

    • Updated README with authentication flow example and comprehensive documentation for new compliance workflow commands.

Copilot AI review requested due to automatic review settings April 24, 2026 20:13
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@ethanolivertroy has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 29 minutes and 16 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 29 minutes and 16 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a3660959-af90-4e0a-8345-380643d9e6e0

📥 Commits

Reviewing files that changed from the base of the PR and between 291c6be and 0ec0148.

📒 Files selected for processing (6)
  • README.md
  • src/cli.mjs
  • src/lib/completion.mjs
  • src/lib/http.mjs
  • src/lib/workflows.mjs
  • test/cli-core.test.mjs
📝 Walkthrough

Walkthrough

This PR introduces curated compliance workflow commands (summary, controls failing, monitors failing, connections list, personnel issues, evidence expiring) with pagination support via --limit and --compact flags. It adds an auth check subcommand, implements page-based pagination for API requests, and updates CLI tooling (argument parsing, completions) to support the new functionality.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added "Curated compliance workflows" section documenting read-only compliance triage commands, pagination behavior, --limit, and --compact flags. Updated authentication example to include drata auth check --json.
CLI Command Handlers
src/cli.mjs
Added auth check subcommand that validates API credentials and outputs company/region info. Wired new top-level workflow commands (summary, controls failing, monitors failing, connections list, personnel issues, evidence expiring) with handlers that parse flags, invoke workflow operations, and print results. Expanded usage documentation.
Argument Parsing
src/lib/args.mjs
Added support for --compact boolean flag and --limit numeric flag with validation (non-negative integer). Both flags parsed and added to request object.
Command Completion
src/lib/completion.mjs
Added completion entries for new workflow commands and auth check subcommand. Extended flag completions with --compact and --limit. Added value completions for --limit, --status, and --days.
HTTP Request Handling
src/lib/http.mjs
Implemented page-based pagination for --all-pages when endpoints support page and limit query parameters. Added --max-pages guard and pagination continuation logic based on response data length and total count.
Workflows Module
src/lib/workflows.mjs
New module providing workflow execution and payload-building functions. Includes prepareWorkflowFlags (API key resolution, read-only enforcement), run* functions for each workflow command, payload builders that transform API responses into structured outputs with limit-aware counts, and printWorkflowPayload for formatting.
Tests
test/cli-core.test.mjs
Added test cases covering --limit flag parsing, page-based pagination with sequential API responses, auth check end-to-end validation, workflow command integration tests (summary --compact --json, connections list with status filtering), and dry-run rejection for workflows.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI
    participant ArgParser
    participant WorkflowLib
    participant HTTP
    participant API
    participant Output
    
    User->>CLI: summary --compact --json --limit 50
    CLI->>ArgParser: parseRequestFlags()
    ArgParser->>ArgParser: Parse --compact, --limit
    ArgParser-->>CLI: flags {compact, limit, ...}
    
    CLI->>WorkflowLib: prepareWorkflowFlags(flags)
    WorkflowLib->>WorkflowLib: Resolve API key, set readOnly
    WorkflowLib-->>CLI: prepared flags
    
    CLI->>WorkflowLib: runSummary(flags)
    WorkflowLib->>HTTP: invokeOperation('controls', {page: 1, limit: 100})
    HTTP->>API: GET /controls?page=1&limit=100
    API-->>HTTP: {data: [...], total: N}
    
    HTTP->>HTTP: Check if more pages needed
    HTTP->>API: GET /controls?page=2&limit=100
    API-->>HTTP: {data: [...], total: N}
    
    HTTP-->>WorkflowLib: Merged payload
    
    WorkflowLib->>WorkflowLib: buildSummaryPayload()
    WorkflowLib->>WorkflowLib: Compute totals, apply limit
    WorkflowLib-->>CLI: {kind: "summary", ...}
    
    CLI->>Output: printWorkflowPayload(payload, {compact, json})
    Output-->>User: JSON output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Workflows bloom like clover in the CLI,
With pages turning swift as bunnies hop,
Compliance checks now dance before the eye,
--compact flags and limits at the top!
From auth to evidence, the rabbit hops,
Each curated command, a structured grace—
Let workflows thrive, let no audit stop! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'feat: add curated Drata compliance workflows' accurately captures the main change: introducing new read-only workflow commands for compliance triage.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/curated-compliance-workflows

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add curated compliance workflows and auth check command

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add curated compliance workflow commands for common triage tasks
  - drata summary, drata controls failing, drata monitors failing
  - drata connections list, drata personnel issues, drata evidence expiring
• Implement drata auth check to validate API credentials against Drata API
• Add --compact flag for condensed workflow output and --limit for result capping
• Support v1 page/limit pagination alongside existing cursor-based pagination
• Extend shell completion for new workflow commands and their parameters
Diagram
flowchart LR
  CLI["CLI Entry Point"]
  AUTH["Auth Handler"]
  WORKFLOWS["Workflow Handlers"]
  WORKFLOWLIB["Workflows Library"]
  HTTP["HTTP/Pagination"]
  
  CLI -->|"auth check"| AUTH
  CLI -->|"summary/controls/monitors/etc"| WORKFLOWS
  WORKFLOWS -->|"prepareWorkflowFlags"| WORKFLOWLIB
  WORKFLOWLIB -->|"runWorkflowOperation"| HTTP
  HTTP -->|"page/limit pagination"| HTTP
  WORKFLOWLIB -->|"buildPayload"| WORKFLOWLIB
Loading

Grey Divider

File Changes

1. README.md 📝 Documentation +19/-1

Document curated workflows and auth check

• Add documentation for drata auth check --json command
• Document new curated compliance workflow commands with examples
• Add --compact and --limit flags to supported flags list
• Explain v1 list endpoints and pagination behavior for workflows

README.md


2. src/cli.mjs ✨ Enhancement +192/-2

Add workflow command routing and auth check

• Import workflow functions from new workflows.mjs module
• Add auth check subcommand to validate credentials via API call
• Implement handlers for six new workflow commands: summary, controls failing, `monitors
 failing, connections list, personnel issues, evidence expiring`
• Route workflow commands to appropriate handlers with flag parsing
• Add workflow command examples to usage documentation

src/cli.mjs


3. src/lib/args.mjs ✨ Enhancement +15/-0

Add compact and limit flag parsing

• Add compact boolean flag to parsed request flags
• Add limit numeric flag with validation for non-negative integers
• Parse --compact and --limit from command-line arguments

src/lib/args.mjs


View more (4)
4. src/lib/completion.mjs ✨ Enhancement +80/-2

Add shell completion for workflows

• Add new workflow commands to top-level command completions
• Add check subcommand to auth command completions
• Create WORKFLOW_FLAGS constant with common workflow flag options
• Add CONNECTION_STATUS_VALUES for status parameter completion
• Implement completion logic for workflow-specific flags and parameters
• Add value suggestions for --limit, --status, and --days flags

src/lib/completion.mjs


5. src/lib/http.mjs ✨ Enhancement +46/-1

Implement page/limit pagination support

• Add compact and limit fields to effective request flags resolution
• Implement page/limit pagination support alongside cursor pagination
• Detect operations supporting page/limit parameters and initialize pagination
• Track page number and increment for page-based pagination
• Merge paginated responses from page-based endpoints
• Continue pagination loop while cursor or page is active

src/lib/http.mjs


6. src/lib/workflows.mjs ✨ Enhancement +534/-0

Implement curated workflow operations

• Create new module with workflow command implementations
• Implement prepareWorkflowFlags to validate and prepare workflow execution
• Add runWorkflowOperation to execute v1 operations with workflow context
• Implement payload builders for each workflow: buildControlsFailingPayload,
 buildMonitorsFailingPayload, buildConnectionsListPayload, buildPersonnelIssuesPayload,
 buildEvidenceExpiringPayload, buildSummaryPayload
• Add compact output formatters that reduce payload to essential fields
• Implement runSummary, runControlsFailing, runMonitorsFailing, runConnectionsList,
 runPersonnelIssues, runEvidenceExpiring workflow runners
• Add text formatting for human-readable workflow output
• Support --limit to cap displayed items without affecting summary counts

src/lib/workflows.mjs


7. test/cli-core.test.mjs 🧪 Tests +233/-0

Add workflow and auth check tests

• Add test for --limit flag preservation as query parameter
• Add test for page/limit pagination with multiple pages
• Add test for auth check command validating credentials
• Add test for summary workflow with compact JSON output
• Add test for connections list filtering by status parameter
• Add test for workflow rejection of --dry-run flag

test/cli-core.test.mjs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 24, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Workflow limit sent to API🐞 Bug ≡ Correctness
Description
Curated workflows document --limit as a display-only cap, but the flag is also forwarded as an
operation query parameter, changing v1 list pagination behavior. This can dramatically increase
pages fetched (and hit --max-pages failures) when a user only intended to limit displayed items.
Code

src/lib/workflows.mjs[R30-36]

+function withListDefaults(flags) {
+  const next = cloneFlags(flags);
+  next.readOnly = true;
+  next.allPages = true;
+  setNamedDefault(next, "page", 1);
+  setNamedDefault(next, "limit", 100);
+  return next;
Evidence
README promises --limit N only caps displayed workflow items, but parseRequestFlags stores
--limit into flags.named (operation parameters) and withListDefaults only sets limit=100
when named.limit is absent—so a user-provided --limit becomes the API page size. With v1
page/limit auto-pagination, smaller page sizes increase request count and can trip the pagination
guard in invokeOperation.

README.md[216-230]
src/lib/args.mjs[32-63]
src/lib/args.mjs[204-213]
src/lib/workflows.mjs[30-37]
src/lib/http.mjs[836-863]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Workflow `--limit` is intended to cap displayed items only, but it is currently forwarded as an operation query parameter (`named.limit`), changing v1 page size and increasing the number of paginated requests.
### Issue Context
- `parseRequestFlags` stores `--limit` both as `flags.limit` (number) and as `named.limit` (operation parameter).
- Workflows use `withListDefaults()` which sets `limit=100` only if `named.limit` is not already set.
### Fix Focus Areas
- src/cli.mjs[506-508]
- src/lib/workflows.mjs[30-37]
- src/lib/args.mjs[204-213]
### Suggested fix
- In workflow flag preparation (e.g., inside `parseWorkflowRequestFlags` or `prepareWorkflowFlags`), delete `flags.named` entry for `limit` so it is not sent to the API.
- Keep using `flags.limit` for display limiting (`applyLimit`).
- Ensure `withListDefaults()` always applies the API paging limit (e.g., default `limit=100`) regardless of display limit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Limit zero overridden by input 🐞 Bug ≡ Correctness
Description
resolveEffectiveRequestFlags computes limit with a logical-OR, so an explicit --limit 0 is
treated as falsy and replaced by an --input-provided limit. This makes it impossible to disable an
input-configured limit via CLI when using --input mode.
Code

src/lib/http.mjs[R580-582]

+    compact: parsedFlags.compact || Boolean(input.compact),
+    limit: parsedFlags.limit || Number(input.limit ?? 0),
retry: parsedFlags.retryProvided ? parsedFlags.retry : resolveInputRetry(input.retry, parsedFlags.retry),
Evidence
parseRequestFlags explicitly allows non-negative integers (including 0) for --limit, but
resolveEffectiveRequestFlags uses parsedFlags.limit || Number(input.limit ?? 0) which ignores an
explicitly provided 0 and falls back to input.limit. Both raw operation execution and workflow
flag parsing call resolveEffectiveRequestFlags, so the behavior applies whenever --input is
used.

src/lib/args.mjs[204-209]
src/lib/http.mjs[553-586]
src/cli.mjs[395-403]
src/cli.mjs[506-508]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`resolveEffectiveRequestFlags` uses `parsedFlags.limit || Number(input.limit ?? 0)`, which causes an explicit `--limit 0` to be ignored when an `--input` payload provides a non-zero limit.
### Issue Context
- `parseRequestFlags` treats `--limit` as a valid non-negative integer.
- `0` is a meaningful value (commonly used to mean “no limit” / “disable limiting”).
### Fix Focus Areas
- src/lib/args.mjs[32-63]
- src/lib/args.mjs[204-213]
- src/lib/http.mjs[553-586]
### Suggested fix
- Track whether `--limit` was explicitly provided (e.g., `limitProvided`, similar to `retryProvided`/`timeoutMsProvided`).
- In `resolveEffectiveRequestFlags`, resolve with explicit-precedence semantics:
- `limit: parsedFlags.limitProvided ? parsedFlags.limit : Number(input.limit ?? 0)`
- Ensure existing behavior remains unchanged when no `--input` is used.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces curated compliance workflows to the CLI, including commands for summaries, failing controls, failing monitors, connection lists, personnel issues, and expiring evidence. It also adds an auth check command to validate credentials and implements page-based pagination support via new --limit and --compact flags. Review feedback identified an opportunity to optimize workspace retrieval by disabling unnecessary pagination and pointed out several bugs in the text formatters where property names were incorrectly mapped when the --compact flag is active.

Comment thread src/lib/workflows.mjs Outdated
}

async function getFirstWorkspaceId(flags) {
const { data } = await listV1("list-workspaces", flags);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This function fetches all workspaces just to retrieve the first one, which is inefficient if the user has many workspaces. Since only the first ID is needed, it's better to disable pagination and set a limit of 1 for this specific request.

Suggested change
const { data } = await listV1("list-workspaces", flags);
const { data } = await runWorkflowOperation("v1", "list-workspaces", { ...flags, allPages: false, limit: 1 });

Comment thread src/lib/workflows.mjs
case "monitors.failing":
return [`Failing Monitors: matching=${payload.matching} showing=${payload.showing}`, ...payload.monitors.map((m) => `${m.id} ${monitorStatus(m)} ${m.name}`)].join("\n");
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The text formatter for connections.list fails to correctly display status and alias when the --compact flag is used. Compact connection objects store the calculated status in a status property and rename clientAlias to alias. The connectionState helper only works on raw objects and will default to DISCONNECTED for compact objects because the raw properties it expects are missing.

Suggested change
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${c.status ?? connectionState(c)} ${c.alias || c.clientAlias || c.clientType || ""}`)].join("\n");

Comment thread src/lib/workflows.mjs
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
case "personnel.issues":
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.devicesFailingComplianceCount ?? 0}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The text formatter for personnel.issues does not account for property renaming in compact objects. When --compact is used, devicesFailingComplianceCount is renamed to failing_devices, and the email is flattened to an email property. This causes the text output to show failing_devices=0 for compact objects even when issues exist.

Suggested change
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.devicesFailingComplianceCount ?? 0}`)].join("\n");
case "personnel.issues":
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.email ?? p.user?.email ?? ""} failing_devices=${p.failing_devices ?? p.devicesFailingComplianceCount ?? 0}`)].join("\n");

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds curated, read-only “workflow” commands (summary/triage style) on top of the generated Drata OpenAPI CLI surface, including compact outputs and improved pagination behavior.

Changes:

  • Introduces src/lib/workflows.mjs implementing curated compliance workflows and compact output shaping.
  • Extends CLI routing + shell completion to support new top-level workflow commands and auth check.
  • Adds --compact, workflow --limit, and v1 page/limit pagination support for --all-pages, with accompanying tests and docs.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/cli-core.test.mjs Adds tests for --limit handling, page/limit pagination merging, auth check, curated workflow behavior.
src/lib/workflows.mjs New workflow runner + payload builders (summary, failing controls/monitors, connections list, personnel issues, evidence expiring).
src/lib/http.mjs Adds effective flag resolution for compact/limit and supports v1 page/limit pagination in invokeOperation.
src/lib/completion.mjs Extends completion suggestions for new workflow commands/flags and auth check.
src/lib/args.mjs Adds parsing/validation for --compact and --limit.
src/cli.mjs Wires new commands into CLI (summary, controls failing, etc.) and implements auth check.
README.md Documents curated workflows and new flags/commands.

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

Comment thread src/lib/workflows.mjs Outdated
Comment on lines +30 to +35
function withListDefaults(flags) {
const next = cloneFlags(flags);
next.readOnly = true;
next.allPages = true;
setNamedDefault(next, "page", 1);
setNamedDefault(next, "limit", 100);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

withListDefaults sets the default limit query parameter to 100 for v1 list workflows. In the embedded v1 OpenAPI spec, several list endpoints used by these workflows (e.g. /controls, /connections, /workspaces/{workspaceId}/evidence-library) declare limit with a maximum of 50, so sending 100 risks 400/validation failures in real API calls. Consider defaulting to 50 (or the spec default), and/or clamping any requested limit to the parameter schema maximum when building workflow list flags.

Suggested change
function withListDefaults(flags) {
const next = cloneFlags(flags);
next.readOnly = true;
next.allPages = true;
setNamedDefault(next, "page", 1);
setNamedDefault(next, "limit", 100);
function clampNamedLimit(flags, max) {
const current = flags.named.get("limit");
if (!current) {
return;
}
flags.named.set(
"limit",
current.map((value) => {
const numeric = Number(value);
if (!Number.isFinite(numeric)) {
return value;
}
return String(Math.min(numeric, max));
}),
);
}
function withListDefaults(flags) {
const next = cloneFlags(flags);
next.readOnly = true;
next.allPages = true;
setNamedDefault(next, "page", 1);
setNamedDefault(next, "limit", 50);
clampNamedLimit(next, 50);

Copilot uses AI. Check for mistakes.
Comment thread test/cli-core.test.mjs Outdated
const payload = responses[url.pathname];
assert.ok(payload, `unexpected path ${url.pathname}`);
assert.equal(url.searchParams.get("page"), "1");
assert.equal(url.searchParams.get("limit"), "100");
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

This test asserts that curated workflow list requests always use limit=100, but the v1 spec limits many list endpoints to limit <= 50. If the workflow defaults are adjusted to respect the OpenAPI maximum, this assertion should be updated accordingly (and ideally verify the default stays within the spec’s declared bounds).

Suggested change
assert.equal(url.searchParams.get("limit"), "100");
const limit = Number(url.searchParams.get("limit"));
assert.ok(Number.isInteger(limit), "expected limit query parameter to be an integer");
assert.ok(limit >= 1, "expected limit query parameter to be positive");
assert.ok(limit <= 50, "expected limit query parameter to stay within the v1 spec maximum");

Copilot uses AI. Check for mistakes.
Comment thread src/lib/http.mjs
Comment on lines 579 to 582
json: parsedFlags.json || Boolean(input.json),
compact: parsedFlags.compact || Boolean(input.compact),
limit: parsedFlags.limit || Number(input.limit ?? 0),
retry: parsedFlags.retryProvided ? parsedFlags.retry : resolveInputRetry(input.retry, parsedFlags.retry),
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

resolveEffectiveRequestFlags merges limit using parsedFlags.limit || Number(input.limit ?? 0). This makes it impossible to override an --input limit with an explicit --limit 0 (since 0 is falsy), and it also bypasses the non-negative-integer validation that parseRequestFlags applies to --limit (input could be negative/float/NaN). Track whether --limit was provided (similar to retryProvided / timeoutMsProvided) and validate/coerce input.limit the same way as the CLI flag.

Copilot uses AI. Check for mistakes.
Comment thread src/cli.mjs
Comment on lines +588 to +604
async function handleEvidenceWorkflow(args) {
const [subcommand, ...rest] = args;
if (!subcommand || subcommand === "--help" || subcommand === "help") {
printUsage();
return;
}

const flags = await parseWorkflowRequestFlags(rest);
const days = Number(takeWorkflowNamedFlag(flags, "days") ?? 30);
const workspaceId = takeWorkflowNamedFlag(flags, "workspace-id");
if (!Number.isInteger(days) || days < 0) {
fail("invalid_days", `--days must be a non-negative integer`, { days });
}

if (subcommand === "expiring") {
printWorkflowPayload(await runEvidenceExpiring(flags, { days, workspaceId }), flags);
return;
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The new evidence expiring workflow path (including --days validation and optional --workspace-id) isn’t covered by the added CLI integration tests, while other new workflows (auth check, summary, connections list, dry-run rejection) are. Adding at least one test that stubs the workspace lookup + evidence listing and asserts filtering/--days behavior would help prevent regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread src/lib/workflows.mjs
Comment on lines +429 to +439
return {
status: summary.status,
controls: {
total: summary.controls.total,
passing: summary.controls.passing,
needs_attention: summary.controls.needs_attention,
},
monitors: summary.monitors,
personnel: summary.personnel,
connections: summary.connections,
};
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot Apr 24, 2026

Choose a reason for hiding this comment

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

🔴 Compact summary payload missing kind field, breaking text formatter and losing recommendation

In buildSummaryPayload at lines 429-441, the compact branch constructs a new object without the kind or recommendation properties. Unlike other compact helpers (e.g. compactControlsPayload, compactConnectionsPayload) which use ...payload to preserve all existing keys, the compact summary manually picks fields and omits kind: "summary" and recommendation. This causes two issues: (1) formatWorkflowText (src/lib/workflows.mjs:502) won't match the "summary" case and falls through to the default branch (raw JSON dump), and (2) the actionable recommendation string is lost even in --json --compact output.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/lib/workflows.mjs
case "monitors.failing":
return [`Failing Monitors: matching=${payload.matching} showing=${payload.showing}`, ...payload.monitors.map((m) => `${m.id} ${monitorStatus(m)} ${m.name}`)].join("\n");
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 formatWorkflowText calls connectionState() on compact connection objects, always showing "NEVER_CONNECTED"

At src/lib/workflows.mjs:518, the text formatter calls connectionState(c) on each connection. connectionState reads raw API fields (connection.connected, connection.failedAt, connection.connectedAt) at src/lib/workflows.mjs:211-222. However, when --compact is used, connections have been transformed by compactConnection (src/lib/workflows.mjs:224-233) into { id, clientType, alias, status, providers } — none of the raw fields exist. Since c.connected is undefined (falsy), c.failedAt is undefined (falsy), and c.connectedAt is undefined (falsy → !c.connectedAt is true), connectionState always returns "NEVER_CONNECTED" regardless of actual status. Additionally, the formatter accesses c.clientAlias but compact connections use c.alias, so the alias is also never displayed.

Suggested change
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${c.status ?? connectionState(c)} ${c.alias || c.clientAlias || c.clientType || ""}`)].join("\n");
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/lib/workflows.mjs
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
case "personnel.issues":
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.devicesFailingComplianceCount ?? 0}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 formatWorkflowText reads devicesFailingComplianceCount on compact personnel objects where the field is named failing_devices

At src/lib/workflows.mjs:520, the text formatter reads p.devicesFailingComplianceCount ?? 0 for personnel items. When --compact is used, compactPersonnel (src/lib/workflows.mjs:242-248) renames this field to failing_devices. So for compact personnel objects, p.devicesFailingComplianceCount is undefined and the ?? 0 fallback always produces 0, making every person appear to have zero failing devices in the text output.

Suggested change
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.devicesFailingComplianceCount ?? 0}`)].join("\n");
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.failing_devices ?? p.devicesFailingComplianceCount ?? 0}`)].join("\n");
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

qodo-code-review[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 291c6be0b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/workflows.mjs
case "monitors.failing":
return [`Failing Monitors: matching=${payload.matching} showing=${payload.showing}`, ...payload.monitors.map((m) => `${m.id} ${monitorStatus(m)} ${m.name}`)].join("\n");
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Render compact connections with existing status field

When --compact is used without --json, connections.list entries have already been transformed to { status, alias, ... }, but this formatter recomputes state via connectionState(c) and reads clientAlias. Because compact items no longer carry connected/connectedAt/failedAt or clientAlias, disconnected/failed rows are printed as NEVER_CONNECTED (and aliases are dropped), which misreports integration health in human-readable output.

Useful? React with 👍 / 👎.

Comment thread src/lib/workflows.mjs
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
case "personnel.issues":
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.devicesFailingComplianceCount ?? 0}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve failing device counts in compact personnel text

buildPersonnelIssuesPayload(..., { compact: true }) renames the count field to failing_devices, but the text formatter still reads devicesFailingComplianceCount. As a result, drata personnel issues --compact always prints failing_devices=0 even for users that were filtered in for nonzero failures, which understates the severity of compliance issues.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/lib/workflows.mjs (1)

57-78: Dead !flags.dryRun check.

flags.dryRun is guaranteed false by the time we reach line 63 because we already fail(...) at line 58–60 when flags.dryRun is truthy. The second guard can be simplified.

♻️ Proposed cleanup
   const { apiKey, source } = await resolveApiKey(flags);
-  if (!apiKey && !flags.dryRun) {
+  if (!apiKey) {
     fail(
       "missing_api_key",
       "Missing Drata API key. Use auth login, DRATA_API_KEY, DRATA_API_KEY_CMD, --api-key, --api-key-file, or --api-key-stdin.",
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/workflows.mjs` around lines 57 - 78, In prepareWorkflowFlags, remove
the redundant dead check for !flags.dryRun when validating apiKey: since
flags.dryRun is already handled (fail(...) earlier), simplify the guard to just
check apiKey (e.g., if (!apiKey) call fail("missing_api_key", ...)), keeping
resolveApiKey usage and returning the same augmented flags (apiKey,
apiKeySource: source, apiKeyFile: null, apiKeyStdin: false, readOnly: true);
update only the condition around the fail call and leave fail, resolveApiKey,
and the returned shape unchanged.
test/cli-core.test.mjs (1)

389-399: Redundant parsedFlags.named.set("limit", ["2"]).

parseRequestFlags(["--limit", "2", ...]) already pushes "2" into parsed.named["limit"] via pushValue(parsed.named, "limit", result.value) in src/lib/args.mjs, so this explicit set is a no-op and can be removed to avoid implying that the parser doesn't already handle this.

♻️ Proposed cleanup
     "--limit",
     "2",
     "--raw",
   ]);
-  parsedFlags.named.set("limit", ["2"]);
   const originalFetch = globalThis.fetch;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/cli-core.test.mjs` around lines 389 - 399, Remove the redundant manual
set of the "limit" flag in the test: parsedFlags.named.set("limit", ["2"]) is
unnecessary because parseRequestFlags already adds the value via
pushValue(parsed.named, "limit", result.value); update the test to rely on
parseRequestFlags(pars) producing the "limit" entry (i.e., delete the
parsedFlags.named.set(...) line) and run the test to confirm behavior remains
the same.
src/cli.mjs (1)

184-206: auth check --dry-run will be rejected as unsupported_workflow_dry_run.

Because this path funnels through prepareWorkflowFlags, which rejects --dry-run, users can't preview the auth check request the way they can with other read-only commands. If that's deliberate (since auth check is meant to hit the API), consider a slightly friendlier error for this entry point, or wire auth check directly through resolveEffectiveRequestFlags and handle --dry-run by short-circuiting to serializePreparedRequest. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.mjs` around lines 184 - 206, The auth check path currently calls
prepareWorkflowFlags which rejects --dry-run causing
unsupported_workflow_dry_run; fix by bypassing prepareWorkflowFlags for the
"check" subcommand: call resolveEffectiveRequestFlags(parseRequestFlags(rest))
directly, then if flags.dryRun is set short-circuit and output
serializePreparedRequest(flags) (or otherwise serializePreparedRequest of the
prepared request) instead of calling runWorkflowOperation; if dry-run is not
set, proceed to call runWorkflowOperation as before. Update references:
subcommand "check", prepareWorkflowFlags, resolveEffectiveRequestFlags,
parseRequestFlags, runWorkflowOperation, and serializePreparedRequest.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/workflows.mjs`:
- Around line 484-489: runEvidenceExpiring can end up with workspaceId in both
the path and flags.named because withListDefaults(flags) copies flags without
removing a user-supplied flags.named.workspaceId, then withPath(next, {
workspaceId }) appends it to next.path; fix by ensuring the user-provided
workspace ID is removed from flags.named (or prefer it and skip
getFirstWorkspaceId) before calling withListDefaults/withPath: check
flags.named.workspaceId and if present use that value for workspaceId and delete
flags.named.workspaceId (or bypass getFirstWorkspaceId entirely), then call
withListDefaults(flags) and withPath(next, { workspaceId }) so only the path
contains workspaceId (references: runEvidenceExpiring, withListDefaults,
withPath, getFirstWorkspaceId, flags.named).
- Around line 376-404: The summary's needs_attention currently includes
controlSummary.ready but buildControlsFailingPayload filters out "READY",
causing the mismatch; update buildSummaryPayload to remove controlSummary.ready
from needs_attention and instead add a new summary bucket (e.g., not_monitored
or not_active) set to controlSummary.ready, and update any consumers/UI labels
accordingly (referencing the needs_attention key in the summary object, the
controlSummary.ready value, and buildControlsFailingPayload which filters
["NOT_READY","NO_OWNER","NEEDS_EVIDENCE"]).

---

Nitpick comments:
In `@src/cli.mjs`:
- Around line 184-206: The auth check path currently calls prepareWorkflowFlags
which rejects --dry-run causing unsupported_workflow_dry_run; fix by bypassing
prepareWorkflowFlags for the "check" subcommand: call
resolveEffectiveRequestFlags(parseRequestFlags(rest)) directly, then if
flags.dryRun is set short-circuit and output serializePreparedRequest(flags) (or
otherwise serializePreparedRequest of the prepared request) instead of calling
runWorkflowOperation; if dry-run is not set, proceed to call
runWorkflowOperation as before. Update references: subcommand "check",
prepareWorkflowFlags, resolveEffectiveRequestFlags, parseRequestFlags,
runWorkflowOperation, and serializePreparedRequest.

In `@src/lib/workflows.mjs`:
- Around line 57-78: In prepareWorkflowFlags, remove the redundant dead check
for !flags.dryRun when validating apiKey: since flags.dryRun is already handled
(fail(...) earlier), simplify the guard to just check apiKey (e.g., if (!apiKey)
call fail("missing_api_key", ...)), keeping resolveApiKey usage and returning
the same augmented flags (apiKey, apiKeySource: source, apiKeyFile: null,
apiKeyStdin: false, readOnly: true); update only the condition around the fail
call and leave fail, resolveApiKey, and the returned shape unchanged.

In `@test/cli-core.test.mjs`:
- Around line 389-399: Remove the redundant manual set of the "limit" flag in
the test: parsedFlags.named.set("limit", ["2"]) is unnecessary because
parseRequestFlags already adds the value via pushValue(parsed.named, "limit",
result.value); update the test to rely on parseRequestFlags(pars) producing the
"limit" entry (i.e., delete the parsedFlags.named.set(...) line) and run the
test to confirm behavior remains the same.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8069118-bbe5-4edd-b486-5e062da2425a

📥 Commits

Reviewing files that changed from the base of the PR and between 89c90e7 and 291c6be.

📒 Files selected for processing (7)
  • README.md
  • src/cli.mjs
  • src/lib/args.mjs
  • src/lib/completion.mjs
  • src/lib/http.mjs
  • src/lib/workflows.mjs
  • test/cli-core.test.mjs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (1)
src/cli.mjs (1)

595-600: This issue is not present—normalizeFlagName standardizes to kebab-case, not camelCase.

The verification shows that normalizeFlagName("workspace-id") returns "workspace-id", not "workspaceId". Flags are stored in flags.named using the kebab-case key, so takeWorkflowNamedFlag(flags, "workspace-id") will correctly retrieve the value. The same normalization applies to "status" and "days", which is why all three lookups work as expected. No API call duplication or flag conflict will occur when a user provides --workspace-id.

			> Likely an incorrect or invalid review comment.

Comment thread src/lib/workflows.mjs
Comment thread src/lib/workflows.mjs
Comment on lines +484 to +489
export async function runEvidenceExpiring(flags, options = {}) {
const workspaceId = options.workspaceId || (await getFirstWorkspaceId(flags));
const listFlags = withPath(withListDefaults(flags), { workspaceId });
const { data } = await runWorkflowOperation("v1", "list-evidence", listFlags);
return buildEvidenceExpiringPayload(data, flags, options.days);
}
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

runEvidenceExpiring may double-inject workspaceId into the path.

withListDefaults(flags) clones flags but does not strip the user's --workspace-id value from flags.named. withPath(..., { workspaceId }) then appends ["workspaceId", ...] to next.path. When invokeOperation runs, resolveNamedParameters will merge both the path entry (auto-detected) and the named entry (user's explicit value) into values.path.workspaceId. Today this happens to work because only the last value is kept for non-array params, but it's coupled to that behavior and also causes an unnecessary list-workspaces API call (see the handleEvidenceWorkflow comment).

Once --workspace-id is correctly taken out of flags.named in the CLI handler, consider asserting that the user-supplied ID skips getFirstWorkspaceId entirely so the extra request is avoided.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/workflows.mjs` around lines 484 - 489, runEvidenceExpiring can end up
with workspaceId in both the path and flags.named because
withListDefaults(flags) copies flags without removing a user-supplied
flags.named.workspaceId, then withPath(next, { workspaceId }) appends it to
next.path; fix by ensuring the user-provided workspace ID is removed from
flags.named (or prefer it and skip getFirstWorkspaceId) before calling
withListDefaults/withPath: check flags.named.workspaceId and if present use that
value for workspaceId and delete flags.named.workspaceId (or bypass
getFirstWorkspaceId entirely), then call withListDefaults(flags) and
withPath(next, { workspaceId }) so only the path contains workspaceId
(references: runEvidenceExpiring, withListDefaults, withPath,
getFirstWorkspaceId, flags.named).

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f486d57b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/http.mjs
Comment on lines +844 to +845
(total === null ? true : mergedItems.length < total) &&
(!requestedLimit || items.length >= requestedLimit);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Continue page fetches when total says more items exist

In page-based --all-pages mode, the continuation check still requires items.length >= requestedLimit even when the response provides a total and mergedItems.length < total. If the API returns fewer items than requested on a page (for example due to server-side page-size caps), pagination stops early and silently drops remaining records, which makes workflow summaries and raw paginated outputs incomplete.

Useful? React with 👍 / 👎.

Comment thread src/lib/workflows.mjs Outdated
next.allPages = true;
next.named.delete("limit");
setNamedDefault(next, "page", 1);
pushNamed(next, "limit", 100);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a v1-valid default page size in workflow list calls

withListDefaults hardcodes limit=100 for all curated v1 workflow list requests, but the v1 list endpoints used here define a maximum limit of 50 in the local OpenAPI spec. This sends out-of-range query values on every workflow call and can cause request failures (or truncated collection when the server clamps the page size).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bd1346300

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli.mjs Outdated
return;
}

const flags = await parseWorkflowRequestFlags(rest);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate workflow subcommand before resolving flags

This handler resolves workflow flags (parseWorkflowRequestFlags) before verifying the subcommand, and parseWorkflowRequestFlags performs auth checks. As a result, unauthenticated users see missing_api_key for typos like drata controls nope (and for ... --help) instead of the intended unknown-command/help response, which blocks basic command discovery when not logged in.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread src/lib/workflows.mjs
case "monitors.failing":
return [`Failing Monitors: matching=${payload.matching} showing=${payload.showing}`, ...payload.monitors.map((m) => `${m.id} ${monitorStatus(m)} ${m.name}`)].join("\n");
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 formatWorkflowText calls connectionState() on compact connection objects, producing wrong status

When --compact is used without --json, formatWorkflowText at line 537 calls connectionState(c) on connection objects that have already been transformed by compactConnection (src/lib/workflows.mjs:227-235). Compact connections have a pre-computed status field but lack the raw API properties (connected, failedAt, connectedAt) that connectionState() inspects. Since all three are undefined on compact objects, connectionState always returns "NEVER_CONNECTED" (line 221: !undefined → true), regardless of the actual connection state. Additionally, c.clientAlias is accessed but the compact form uses c.alias instead.

Trace: compact connection through connectionState()

Compact shape: { id, clientType, alias, status, providers }

connectionState(c): c.connected → undefined → skip; c.failedAt → undefined → skip; !c.connectedAt!undefinedtrue → return "NEVER_CONNECTED".

Suggested change
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${c.status ?? connectionState(c)} ${c.alias ?? c.clientAlias || c.clientType || ""}`)].join("\n");
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/lib/workflows.mjs
case "connections.list":
return [`Connections: matching=${payload.matching} showing=${payload.showing}`, ...payload.connections.map((c) => `${c.id} ${connectionState(c)} ${c.clientAlias || c.clientType || ""}`)].join("\n");
case "personnel.issues":
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.devicesFailingComplianceCount ?? 0}`)].join("\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 formatWorkflowText reads devicesFailingComplianceCount on compact personnel objects that use failing_devices

When --compact is used without --json, formatWorkflowText at line 539 accesses p.devicesFailingComplianceCount on personnel objects. After compactPersonnel transformation (src/lib/workflows.mjs:245-251), this property is renamed to failing_devices. Since p.devicesFailingComplianceCount is undefined on compact objects, the ?? 0 fallback triggers and every person displays failing_devices=0 regardless of the actual count.

Suggested change
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.devicesFailingComplianceCount ?? 0}`)].join("\n");
return [`Personnel with device issues: matching=${payload.matching} showing=${payload.showing}`, ...payload.personnel.map((p) => `${p.id} ${p.user?.email ?? p.email ?? ""} failing_devices=${p.failing_devices ?? p.devicesFailingComplianceCount ?? 0}`)].join("\n");
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/lib/workflows.mjs
}

if (flags.compact) {
return {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Compact summary payload omits kind field, breaking text rendering

In buildSummaryPayload (src/lib/workflows.mjs:438-449), the compact return path omits the kind property that all other workflow payloads include. When --compact is used without --json, formatWorkflowText switches on payload.kind (line 520), which is undefined for the compact summary. This causes it to fall through to the default case (line 542) and dump raw JSON instead of the formatted summary text.

Suggested change
return {
kind: "summary",
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@ethanolivertroy ethanolivertroy merged commit 0c2ed28 into main Apr 25, 2026
3 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